Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,15 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## [12.3.1] - 2026-02-15

### Added
- Installer trigger-check regression tests for adopted private debug skills (`systematic-debugging`, `parallel-debugging`, `gh-fix-ci`).
- Installer trigger-check regression tests for legacy private skills (`audit-pr-skills`, `rebuild-skill-index`) after schema modernization.

### Changed
- Aligned `src/VERSION` with root `VERSION` for release consistency.

## [12.3.0] - 2026-02-15

### Added
Expand Down
2 changes: 1 addition & 1 deletion VERSION
Original file line number Diff line number Diff line change
@@ -1 +1 @@
12.3.0
12.3.1
2 changes: 1 addition & 1 deletion src/VERSION
Original file line number Diff line number Diff line change
@@ -1 +1 @@
12.2.0
12.3.1
83 changes: 83 additions & 0 deletions tests/installer/adopted-debug-skills.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
import test from "node:test";
import assert from "node:assert/strict";
import fs from "node:fs";
import path from "node:path";
import { execFileSync } from "node:child_process";

const repoRoot = process.cwd();
const checkerScript = path.join(repoRoot, "scripts", "skill-trigger-check.mjs");
const privateSkillsRoot = path.join(repoRoot, "private-skills", "skills");
const hasPrivateSkills = fs.existsSync(privateSkillsRoot);

function runChecker(skillPath: string): { ok: true; stdout: string } | { ok: false; stdout: string; stderr: string } {
try {
const stdout = execFileSync(process.execPath, [checkerScript, "--skill", skillPath], {
cwd: repoRoot,
encoding: "utf8",
stdio: ["ignore", "pipe", "pipe"],
});
return { ok: true, stdout };
} catch (error) {
const err = error as { stdout?: string | Buffer; stderr?: string | Buffer };
return {
ok: false,
stdout: typeof err.stdout === "string" ? err.stdout : (err.stdout?.toString("utf8") ?? ""),
stderr: typeof err.stderr === "string" ? err.stderr : (err.stderr?.toString("utf8") ?? ""),
};
}
}

const adoptedSkills = [
{
name: "systematic-debugging",
path: path.join(repoRoot, "private-skills", "skills", "systematic-debugging", "SKILL.md"),
expectedPhrases: [
"NO FIXES WITHOUT ROOT CAUSE INVESTIGATION FIRST",
"## Acceptance Tests",
"Positive trigger",
"Negative trigger",
"Behavior",
],
},
{
name: "parallel-debugging",
path: path.join(repoRoot, "private-skills", "skills", "parallel-debugging", "SKILL.md"),
expectedPhrases: [
"Analysis of Competing Hypotheses",
"## Acceptance Tests",
"Positive trigger",
"Negative trigger",
"Behavior",
],
},
{
name: "gh-fix-ci",
path: path.join(repoRoot, "private-skills", "skills", "gh-fix-ci", "SKILL.md"),
expectedPhrases: [
"GitHub Actions",
"gh pr checks",
"## Acceptance Tests",
"Positive trigger",
"Negative trigger",
"Behavior",
],
},
];

for (const skill of adoptedSkills) {
test(`adopted skill exists: ${skill.name}`, { skip: !hasPrivateSkills }, () => {
assert.equal(fs.existsSync(skill.path), true, `${skill.name} should exist at ${skill.path}`);
});

test(`adopted skill passes trigger checks: ${skill.name}`, { skip: !hasPrivateSkills }, () => {
const result = runChecker(skill.path);
assert.equal(result.ok, true, `${skill.name} should pass skill-trigger-check`);
});

test(`adopted skill includes TDD acceptance coverage: ${skill.name}`, { skip: !hasPrivateSkills }, () => {
const text = fs.readFileSync(skill.path, "utf8");
for (const phrase of skill.expectedPhrases) {
assert.match(text, new RegExp(phrase.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"), "i"));
}
});
}
41 changes: 41 additions & 0 deletions tests/installer/private-skills-legacy-trigger.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
import test from "node:test";
import assert from "node:assert/strict";
import fs from "node:fs";
import path from "node:path";
import { execFileSync } from "node:child_process";

const repoRoot = process.cwd();
const checkerScript = path.join(repoRoot, "scripts", "skill-trigger-check.mjs");
const privateSkillsRoot = path.join(repoRoot, "private-skills", "skills");
const hasPrivateSkills = process.env.ICA_REQUIRE_PRIVATE_SKILLS === "1" || fs.existsSync(privateSkillsRoot);

function runChecker(skillPath: string): { ok: true; stdout: string } | { ok: false; stdout: string; stderr: string } {
try {
const stdout = execFileSync(process.execPath, [checkerScript, "--skill", skillPath], {
cwd: repoRoot,
encoding: "utf8",
stdio: ["ignore", "pipe", "pipe"],
});
return { ok: true, stdout };
} catch (error) {
const err = error as { stdout?: string | Buffer; stderr?: string | Buffer };
return {
ok: false,
stdout: typeof err.stdout === "string" ? err.stdout : (err.stdout?.toString("utf8") ?? ""),
stderr: typeof err.stderr === "string" ? err.stderr : (err.stderr?.toString("utf8") ?? ""),
};
}
}

const legacySkills = [
path.join(repoRoot, "private-skills", "skills", "audit-pr-skills", "SKILL.md"),
path.join(repoRoot, "private-skills", "skills", "rebuild-skill-index", "SKILL.md"),
];

for (const skillPath of legacySkills) {
const skillName = path.basename(path.dirname(skillPath));
test(`legacy private skill passes trigger check: ${skillName}`, { skip: !hasPrivateSkills }, () => {
const result = runChecker(skillPath);
assert.equal(result.ok, true, `${skillName} should pass skill-trigger-check`);
});
}