continuous-improvement 3.20.0 → 3.21.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude-plugin/marketplace.json +2 -2
- package/CHANGELOG.md +10 -0
- package/QUICKSTART.md +1 -1
- package/README.md +7 -6
- package/bin/check-landing-version.mjs +63 -0
- package/bin/check-scripts-citation-drift.mjs +61 -13
- package/bin/generate-plugin-manifests.mjs +2 -0
- package/bin/install.mjs +29 -60
- package/commands/production-readiness-review.md +5 -4
- package/commands/simplicity-review.md +35 -0
- package/commands/verify-install.md +2 -2
- package/hooks/gateguard.mjs +22 -3
- package/hooks/query-cost-nudge.mjs +1 -0
- package/hooks/session.mjs +85 -0
- package/hooks/typecheck-stop.mjs +2 -1
- package/lib/plugin-metadata.mjs +18 -20
- package/llms.txt +1 -1
- package/package.json +6 -4
- package/plugins/beginner.json +1 -1
- package/plugins/continuous-improvement/.claude-plugin/marketplace.json +2 -2
- package/plugins/continuous-improvement/.claude-plugin/plugin.json +2 -2
- package/plugins/continuous-improvement/README.md +1 -0
- package/plugins/continuous-improvement/commands/production-readiness-review.md +5 -4
- package/plugins/continuous-improvement/commands/simplicity-review.md +35 -0
- package/plugins/continuous-improvement/commands/verify-install.md +2 -2
- package/plugins/continuous-improvement/hooks/gateguard.mjs +22 -3
- package/plugins/continuous-improvement/hooks/hooks.json +15 -16
- package/plugins/continuous-improvement/hooks/query-cost-nudge.mjs +1 -0
- package/plugins/continuous-improvement/hooks/session.mjs +85 -0
- package/plugins/continuous-improvement/hooks/typecheck-stop.mjs +2 -1
- package/plugins/continuous-improvement/lib/plugin-metadata.mjs +18 -20
- package/plugins/continuous-improvement/scripts/README.md +33 -0
- package/plugins/continuous-improvement/scripts/detect-deploy-target.sh +66 -0
- package/plugins/continuous-improvement/scripts/get-deployed-sha.sh +113 -0
- package/plugins/continuous-improvement/scripts/git-state-snapshot.sh +48 -0
- package/plugins/continuous-improvement/scripts/resolve-verify-ladder.mjs +241 -0
- package/plugins/continuous-improvement/scripts/route-recommendation.mjs +178 -0
- package/plugins/continuous-improvement/scripts/route-recommendation.routes.json +213 -0
- package/plugins/continuous-improvement/scripts/run-synthetic.mjs +298 -0
- package/plugins/continuous-improvement/scripts/scan-past-mistakes.mjs +285 -0
- package/plugins/continuous-improvement/skills/README.md +1 -0
- package/plugins/continuous-improvement/skills/deploy-receipt/SKILL.md +2 -2
- package/plugins/continuous-improvement/skills/gateguard/SKILL.md +2 -2
- package/plugins/continuous-improvement/skills/proceed-with-the-recommendation/SKILL.md +3 -2
- package/plugins/continuous-improvement/skills/reconcile/SKILL.md +1 -1
- package/plugins/continuous-improvement/skills/simplicity-review/SKILL.md +80 -0
- package/plugins/continuous-improvement/skills/verification-loop/SKILL.md +5 -5
- package/plugins/continuous-improvement/skills/workspace-surface-audit/SKILL.md +1 -1
- package/plugins/continuous-improvement/skills/worktree-safety/SKILL.md +1 -1
- package/plugins/expert.json +1 -1
- package/scripts/README.md +33 -0
- package/scripts/detect-deploy-target.sh +66 -0
- package/scripts/get-deployed-sha.sh +113 -0
- package/scripts/git-state-snapshot.sh +48 -0
- package/scripts/resolve-verify-ladder.mjs +241 -0
- package/scripts/route-recommendation.mjs +178 -0
- package/scripts/route-recommendation.routes.json +213 -0
- package/scripts/run-synthetic.mjs +298 -0
- package/scripts/scan-past-mistakes.mjs +285 -0
- package/skills/deploy-receipt.md +2 -2
- package/skills/gateguard.md +2 -2
- package/skills/proceed-with-the-recommendation.md +3 -2
- package/skills/reconcile.md +1 -1
- package/skills/simplicity-review.md +80 -0
- package/skills/verification-loop.md +5 -5
- package/skills/workspace-surface-audit.md +1 -1
- package/skills/worktree-safety.md +1 -1
|
@@ -7,8 +7,8 @@
|
|
|
7
7
|
"plugins": [
|
|
8
8
|
{
|
|
9
9
|
"name": "continuous-improvement",
|
|
10
|
-
"description": "The persistent-memory and runtime-discipline layer for Claude Code. It remembers the corrections you already gave, grounds every edit in real facts before it lands, and — through the Mulahazah engine — turns each fix into a reusable instinct, so a lesson learned once is applied automatically next time with no re-teaching. Built on the 7 Laws of AI Agent Discipline (research, plan, verify, reflect, learn) and shipped as
|
|
11
|
-
"version": "3.
|
|
10
|
+
"description": "The persistent-memory and runtime-discipline layer for Claude Code. It remembers the corrections you already gave, grounds every edit in real facts before it lands, and — through the Mulahazah engine — turns each fix into a reusable instinct, so a lesson learned once is applied automatically next time with no re-teaching. Built on the 7 Laws of AI Agent Discipline (research, plan, verify, reflect, learn) and shipped as 28 bundled skills, instinct-aware hooks, an MCP toolset for recall and reflection, and a GitHub Action transcript linter that feeds real work history back into sharper instincts.",
|
|
11
|
+
"version": "3.21.0",
|
|
12
12
|
"source": "./plugins/continuous-improvement",
|
|
13
13
|
"author": {
|
|
14
14
|
"name": "naimkatiman"
|
package/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,16 @@ All notable changes to this skill are documented here.
|
|
|
4
4
|
|
|
5
5
|
---
|
|
6
6
|
|
|
7
|
+
## [3.21.0] — 2026-07-11
|
|
8
|
+
|
|
9
|
+
### Added
|
|
10
|
+
|
|
11
|
+
- **`/simplicity-review`**: a diff-scoped over-engineering reviewer. It reads the current diff and walks a reuse ladder (does it need to exist? already in the codebase? stdlib? native feature? one line?), reporting `GO` or `TRIM` trim findings without editing, with a safety carve-out that never flags input validation, data-loss handling, security, or accessibility. Review-only (Law 4), tier 2, routed into `proceed-with-the-recommendation`. Brings the bundle to 28 skills. (#285)
|
|
12
|
+
|
|
13
|
+
### Changed
|
|
14
|
+
|
|
15
|
+
- **`/production-readiness-review` gains a fifth blind reviewer**: a simplicity and over-engineering dimension that delegates to the `simplicity-review` skill, so the readiness gate now spans performance, security, UI/UX, test coverage, and simplicity. (#286)
|
|
16
|
+
|
|
7
17
|
## [3.17.0] — 2026-06-28
|
|
8
18
|
|
|
9
19
|
### Added
|
package/QUICKSTART.md
CHANGED
|
@@ -139,7 +139,7 @@ npx continuous-improvement install --mode expert
|
|
|
139
139
|
npx continuous-improvement install --pack react # optional: react | python | go | meta
|
|
140
140
|
```
|
|
141
141
|
|
|
142
|
-
|
|
142
|
+
Precondition: Node 18 / 20 / 22. Runtime hooks execute Node directly, so Git Bash and `jq` are not required. Re-run the installer once after upgrading to migrate legacy Bash hook rows. See [README.md § Expert](README.md#expert--adds-mcp-server-observation-hooks-and-instinct-packs) for the troubleshooting matrix.
|
|
143
143
|
|
|
144
144
|
Verify with `/dashboard` — you should see instinct health and observation count.
|
|
145
145
|
|
package/README.md
CHANGED
|
@@ -137,7 +137,7 @@ V1 honest limitations: the runtime gate is honor-system once the agent flips `_g
|
|
|
137
137
|
|
|
138
138
|
Pick this if you want the MCP tools (19 of them, including `ci_plan_init` / `ci_plan_status` for `task_plan.md`-style planning), the session hooks that feed Mulahazah, and starter packs.
|
|
139
139
|
|
|
140
|
-
|
|
140
|
+
Precondition: Node 18 / 20 / 22. Observation and session hooks execute Node directly, so Git Bash and `jq` are not required. Re-run the installer once after upgrading to migrate installer-owned `observe.sh` and `session.sh` rows to the Node entrypoints.
|
|
141
141
|
|
|
142
142
|
```bash
|
|
143
143
|
npx continuous-improvement install --mode expert
|
|
@@ -158,7 +158,7 @@ Three failures account for nearly every install support thread. Try them in orde
|
|
|
158
158
|
| Symptom | Real cause | Fix |
|
|
159
159
|
|---|---|---|
|
|
160
160
|
| `/discipline` says "command not recognized" right after `/plugin install` | Slash commands load on session start; the marketplace did pick the plugin up | Quit and reopen Claude Code, then run `/discipline` again |
|
|
161
|
-
|
|
|
161
|
+
| Hooks report Bash path errors after upgrading | Stale installer-owned `observe.sh` or `session.sh` rows remain in `settings.json` | Re-run `npx continuous-improvement install --mode expert`; the installer migrates those rows to Node and preserves foreign hooks |
|
|
162
162
|
| `/plugin marketplace add ...` returned nothing visible | Marketplace add was silent; the plugin is not yet selected | Run `/plugin install continuous-improvement@continuous-improvement` to select and activate it |
|
|
163
163
|
|
|
164
164
|
If none of those apply, paste the output of `npx continuous-improvement install` into a GitHub issue — that surface logs every step.
|
|
@@ -270,7 +270,7 @@ Hooks capture every tool call. After ~20 observations Claude analyzes patterns a
|
|
|
270
270
|
## Slash commands
|
|
271
271
|
|
|
272
272
|
<details>
|
|
273
|
-
<summary><b>All
|
|
273
|
+
<summary><b>All 29 commands (Beginner gets every one)</b></summary>
|
|
274
274
|
|
|
275
275
|
`/seven-laws` is the canonical reflect-and-learn command. `/continuous-improvement` is kept as an alias for backward compatibility — both run the same workflow.
|
|
276
276
|
|
|
@@ -290,6 +290,7 @@ Hooks capture every tool call. After ~20 observations Claude analyzes patterns a
|
|
|
290
290
|
/audit Audit recent commits for real defects, confirm each before fixing
|
|
291
291
|
/ship Single-defect fast path — reconcile, TDD fix, verify, one PR
|
|
292
292
|
/production-readiness-review Parallel readiness gate — severity-ranked punch-list (reports only)
|
|
293
|
+
/simplicity-review Judge the current diff for over-engineering (GO / TRIM, reports only)
|
|
293
294
|
/handoff End-of-session compaction into mktemp brief for the next agent
|
|
294
295
|
/recall BM25 search over past observations — "have I hit this before?"
|
|
295
296
|
/discipline Quick reference card of the 7 Laws
|
|
@@ -305,7 +306,7 @@ Hooks capture every tool call. After ~20 observations Claude analyzes patterns a
|
|
|
305
306
|
/swarm Fan-out coordination across parallel sub-agents
|
|
306
307
|
```
|
|
307
308
|
|
|
308
|
-
All
|
|
309
|
+
All 29 ship in the marketplace bundle. The Beginner install gets all of them — with one caveat: `/learn-eval`, `/harvest`, and `/distill` only produce useful output once Mulahazah has accumulated observation history (~20 observations), so running them on day 1 returns an empty result, not a broken command. `/swarm` and `/release-train` are orchestration commands aimed at larger multi-agent or multi-PR work. In Expert (`npx`) mode, the installer mirrors the full set into `~/.claude/commands/` and additionally exposes the planning workflow through the MCP tools `ci_plan_init` (initialize `task_plan.md`, `findings.md`, `progress.md` in the project root) and `ci_plan_status` (summarize their current contents).
|
|
309
310
|
|
|
310
311
|
</details>
|
|
311
312
|
|
|
@@ -313,7 +314,7 @@ All 28 ship in the marketplace bundle. The Beginner install gets all of them —
|
|
|
313
314
|
|
|
314
315
|
## Skills
|
|
315
316
|
|
|
316
|
-
The plugin ships **
|
|
317
|
+
The plugin ships **28 skills** — 1 core + 1 featured + 6 tier-1 + 17 tier-2 + 3 always-bundled. Beginner install gets tier-1, featured, and the always-bundled companion; Expert adds tier-2, the MCP server, and observation hooks. Full catalog with per-skill descriptions, Law tagging, and drop-in single-file install: [docs/skills.md](docs/skills.md). Adding a 29th skill: [CONTRIBUTING.md § Evolution — adding a new skill](CONTRIBUTING.md#evolution--adding-a-new-skill).
|
|
317
318
|
|
|
318
319
|
---
|
|
319
320
|
|
|
@@ -356,7 +357,7 @@ Proof-format templates ship in [templates/](templates/): `release_receipt_templa
|
|
|
356
357
|
|
|
357
358
|
- [QUICKSTART.md](QUICKSTART.md) — 2-minute setup
|
|
358
359
|
- [SKILL.md](SKILL.md) — full 7 Laws spec
|
|
359
|
-
- [docs/skills.md](docs/skills.md) — full
|
|
360
|
+
- [docs/skills.md](docs/skills.md) — full 28-skill catalog
|
|
360
361
|
- [examples/](examples/) — bug fix, feature build, refactor walkthroughs
|
|
361
362
|
- [templates/insights-claude-md.md](templates/insights-claude-md.md) — paste-in CLAUDE.md blocks for verification discipline, environment notes, think-before-acting, and git/deploy workflow (sourced from the 28-day usage report)
|
|
362
363
|
- [CONTRIBUTING.md](CONTRIBUTING.md) — architecture, repo internals, adding a new skill
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { readFileSync } from "node:fs";
|
|
3
|
+
import { resolve } from "node:path";
|
|
4
|
+
import { argv, cwd } from "node:process";
|
|
5
|
+
import { fileURLToPath } from "node:url";
|
|
6
|
+
const VERSION_RE = "([0-9]+\\.[0-9]+\\.[0-9]+)";
|
|
7
|
+
function firstMatch(content, pattern) {
|
|
8
|
+
return pattern.exec(content)?.[1] ?? null;
|
|
9
|
+
}
|
|
10
|
+
export function checkLandingVersion(repoRoot) {
|
|
11
|
+
const packagePath = resolve(repoRoot, "package.json");
|
|
12
|
+
const landingPath = resolve(repoRoot, "docs", "landing", "index.html");
|
|
13
|
+
const packageJson = JSON.parse(readFileSync(packagePath, "utf8"));
|
|
14
|
+
const packageVersion = typeof packageJson.version === "string" ? packageJson.version : "";
|
|
15
|
+
const landing = readFileSync(landingPath, "utf8");
|
|
16
|
+
const markers = {
|
|
17
|
+
badge: firstMatch(landing, new RegExp(`<span\\b[^>]*class=["'][^"']*\\bver\\b[^"']*["'][^>]*>\\s*v${VERSION_RE}\\s*</span>`, "i")),
|
|
18
|
+
hero: firstMatch(landing, new RegExp(`<span\\b[^>]*class=["'][^"']*\\bkicker\\b[^"']*["'][^>]*>[\\s\\S]*?\\bREV\\s+${VERSION_RE}[\\s\\S]*?</span>`, "i")),
|
|
19
|
+
current: firstMatch(landing, new RegExp(`<span\\b[^>]*class=["'][^"']*\\bv\\b[^"']*["'][^>]*>\\s*v${VERSION_RE}\\s*</span>\\s*<span\\b[^>]*class=["'][^"']*\\bk\\b[^"']*["'][^>]*>\\s*Current rev\\s*</span>`, "i")),
|
|
20
|
+
footer: firstMatch(landing, new RegExp(`<span\\b[^>]*class=["'][^"']*\\bfoot-doc\\b[^"']*["'][^>]*>[\\s\\S]*?\\bREV\\s+${VERSION_RE}[\\s\\S]*?</span>`, "i")),
|
|
21
|
+
};
|
|
22
|
+
const errors = [];
|
|
23
|
+
if (!/^[0-9]+\.[0-9]+\.[0-9]+$/.test(packageVersion)) {
|
|
24
|
+
errors.push(`package.json has an invalid version: ${packageVersion || "<missing>"}`);
|
|
25
|
+
}
|
|
26
|
+
const missing = Object.entries(markers)
|
|
27
|
+
.filter(([, version]) => version === null)
|
|
28
|
+
.map(([name]) => name);
|
|
29
|
+
if (missing.length > 0) {
|
|
30
|
+
errors.push(`landing release marker(s) missing: ${missing.join(", ")}`);
|
|
31
|
+
}
|
|
32
|
+
const landingVersions = [...new Set(Object.values(markers).filter((value) => value !== null))];
|
|
33
|
+
if (landingVersions.length > 1) {
|
|
34
|
+
errors.push(`landing release markers are mixed: ${landingVersions.join(", ")}`);
|
|
35
|
+
}
|
|
36
|
+
for (const [name, version] of Object.entries(markers)) {
|
|
37
|
+
if (version !== null && version !== packageVersion) {
|
|
38
|
+
errors.push(`${name} marker is ${version}; package.json is ${packageVersion}`);
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
return { ok: errors.length === 0, packageVersion, markers, errors };
|
|
42
|
+
}
|
|
43
|
+
function main() {
|
|
44
|
+
const repoRoot = argv[2] ?? cwd();
|
|
45
|
+
try {
|
|
46
|
+
const result = checkLandingVersion(repoRoot);
|
|
47
|
+
if (result.ok) {
|
|
48
|
+
console.log(`OK landing-version: all 4 markers match package.json ${result.packageVersion}.`);
|
|
49
|
+
return;
|
|
50
|
+
}
|
|
51
|
+
console.error(`FAIL landing-version: ${result.errors.join("; ")}`);
|
|
52
|
+
process.exitCode = 1;
|
|
53
|
+
}
|
|
54
|
+
catch (error) {
|
|
55
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
56
|
+
console.error(`FAIL landing-version: ${message}`);
|
|
57
|
+
process.exitCode = 1;
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
const entryPath = argv[1] ? resolve(argv[1]) : "";
|
|
61
|
+
if (entryPath === fileURLToPath(import.meta.url)) {
|
|
62
|
+
main();
|
|
63
|
+
}
|
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
* Three sides of the contract are enforced as one invariant:
|
|
12
12
|
*
|
|
13
13
|
* Side A — every file in scripts/ (excluding README.md) must appear in the
|
|
14
|
-
* Inventory table
|
|
14
|
+
* Inventory table, and every inventoried helper must exist on disk.
|
|
15
15
|
* Side B — for every Inventory row, every `skills/<name>.md` token in the
|
|
16
16
|
* Cited by cell must reference a file whose body contains the
|
|
17
17
|
* literal substring `scripts/<script-filename>` for at least one
|
|
@@ -85,30 +85,67 @@ function listSkillFiles(repoRoot) {
|
|
|
85
85
|
const dir = join(repoRoot, SKILLS_DIR);
|
|
86
86
|
if (!existsSync(dir) || !statSync(dir).isDirectory())
|
|
87
87
|
return [];
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
.
|
|
91
|
-
|
|
88
|
+
const skills = [];
|
|
89
|
+
for (const entry of readdirSync(dir, { withFileTypes: true })) {
|
|
90
|
+
if (entry.isFile() && entry.name.endsWith(".md")) {
|
|
91
|
+
skills.push(`${SKILLS_DIR}/${entry.name}`);
|
|
92
|
+
continue;
|
|
93
|
+
}
|
|
94
|
+
if (entry.isDirectory() &&
|
|
95
|
+
existsSync(join(dir, entry.name, "SKILL.md"))) {
|
|
96
|
+
skills.push(`${SKILLS_DIR}/${entry.name}.md`);
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
return skills.sort();
|
|
100
|
+
}
|
|
101
|
+
function resolveSkillFile(repoRoot, skillPath) {
|
|
102
|
+
const flatPath = join(repoRoot, skillPath);
|
|
103
|
+
if (existsSync(flatPath))
|
|
104
|
+
return flatPath;
|
|
105
|
+
const bundledSkill = /^skills\/([^/]+)\.md$/.exec(skillPath);
|
|
106
|
+
if (bundledSkill?.[1]) {
|
|
107
|
+
return join(repoRoot, SKILLS_DIR, bundledSkill[1], "SKILL.md");
|
|
108
|
+
}
|
|
109
|
+
return flatPath;
|
|
92
110
|
}
|
|
93
111
|
function toPosix(relPath) {
|
|
94
112
|
return relPath.split(sep).join(posix.sep);
|
|
95
113
|
}
|
|
96
114
|
export function checkRepo(repoRoot) {
|
|
115
|
+
const structure = [];
|
|
97
116
|
const sideA = [];
|
|
98
117
|
const sideB = [];
|
|
99
118
|
const sideC = [];
|
|
100
|
-
const
|
|
119
|
+
const scriptsPath = join(repoRoot, SCRIPTS_DIR);
|
|
101
120
|
const readmePath = join(repoRoot, SCRIPTS_README);
|
|
102
|
-
const
|
|
121
|
+
const scriptsDirectoryExists = existsSync(scriptsPath) && statSync(scriptsPath).isDirectory();
|
|
122
|
+
const readmeExists = scriptsDirectoryExists && existsSync(readmePath);
|
|
123
|
+
const readmeContent = readmeExists ? readFileSync(readmePath, "utf8") : "";
|
|
103
124
|
const rows = parseInventoryTable(readmeContent);
|
|
125
|
+
if (!scriptsDirectoryExists) {
|
|
126
|
+
structure.push("scripts/ directory is missing");
|
|
127
|
+
}
|
|
128
|
+
else if (!readmeExists) {
|
|
129
|
+
structure.push("scripts/README.md inventory is missing");
|
|
130
|
+
}
|
|
131
|
+
else if (rows.length === 0) {
|
|
132
|
+
structure.push("scripts/README.md inventory has no rows");
|
|
133
|
+
}
|
|
134
|
+
const scriptFiles = listScriptFiles(repoRoot);
|
|
104
135
|
const inventoryScripts = new Set();
|
|
105
136
|
for (const row of rows) {
|
|
106
137
|
for (const s of row.scripts)
|
|
107
138
|
inventoryScripts.add(s);
|
|
108
139
|
}
|
|
140
|
+
const scriptFileSet = new Set(scriptFiles);
|
|
109
141
|
for (const file of scriptFiles) {
|
|
110
142
|
if (!inventoryScripts.has(file)) {
|
|
111
|
-
sideA.push(file);
|
|
143
|
+
sideA.push(`${file} — file is missing from the inventory`);
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
for (const inventoryScript of inventoryScripts) {
|
|
147
|
+
if (!scriptFileSet.has(inventoryScript)) {
|
|
148
|
+
sideA.push(`${inventoryScript} — inventoried helper file is missing`);
|
|
112
149
|
}
|
|
113
150
|
}
|
|
114
151
|
let citationCount = 0;
|
|
@@ -122,7 +159,7 @@ export function checkRepo(repoRoot) {
|
|
|
122
159
|
for (const row of rows) {
|
|
123
160
|
for (const skillPath of row.skills) {
|
|
124
161
|
citationCount += 1;
|
|
125
|
-
const fullSkill =
|
|
162
|
+
const fullSkill = resolveSkillFile(repoRoot, skillPath);
|
|
126
163
|
let skillBody = "";
|
|
127
164
|
try {
|
|
128
165
|
skillBody = readFileSync(fullSkill, "utf8");
|
|
@@ -142,7 +179,7 @@ export function checkRepo(repoRoot) {
|
|
|
142
179
|
for (const skillPath of skillFiles) {
|
|
143
180
|
let body = "";
|
|
144
181
|
try {
|
|
145
|
-
body = readFileSync(
|
|
182
|
+
body = readFileSync(resolveSkillFile(repoRoot, skillPath), "utf8");
|
|
146
183
|
}
|
|
147
184
|
catch {
|
|
148
185
|
continue;
|
|
@@ -160,6 +197,7 @@ export function checkRepo(repoRoot) {
|
|
|
160
197
|
}
|
|
161
198
|
}
|
|
162
199
|
return {
|
|
200
|
+
structure,
|
|
163
201
|
sideA,
|
|
164
202
|
sideB,
|
|
165
203
|
sideC,
|
|
@@ -170,15 +208,25 @@ export function checkRepo(repoRoot) {
|
|
|
170
208
|
function main() {
|
|
171
209
|
const repoRoot = argv[2] ?? cwd();
|
|
172
210
|
const result = checkRepo(repoRoot);
|
|
173
|
-
const totalViolations = result.
|
|
211
|
+
const totalViolations = result.structure.length +
|
|
212
|
+
result.sideA.length +
|
|
213
|
+
result.sideB.length +
|
|
214
|
+
result.sideC.length;
|
|
174
215
|
if (totalViolations === 0) {
|
|
175
216
|
console.log(`OK scripts-citation-drift: ${result.scriptCount} script(s), ${result.citationCount} skill citation(s), all three sides reconciled.`);
|
|
176
217
|
exit(0);
|
|
177
218
|
}
|
|
178
219
|
console.error(`FAIL scripts-citation-drift: ${totalViolations} drift(s) across the inventory-citation contract.`);
|
|
220
|
+
if (result.structure.length > 0) {
|
|
221
|
+
console.error("");
|
|
222
|
+
console.error("Contract structure:");
|
|
223
|
+
for (const v of result.structure) {
|
|
224
|
+
console.error(` ${toPosix(v)}`);
|
|
225
|
+
}
|
|
226
|
+
}
|
|
179
227
|
if (result.sideA.length > 0) {
|
|
180
228
|
console.error("");
|
|
181
|
-
console.error("Side A —
|
|
229
|
+
console.error("Side A — scripts/ files and inventory rows do not match:");
|
|
182
230
|
for (const v of result.sideA) {
|
|
183
231
|
console.error(` ${toPosix(v)}`);
|
|
184
232
|
}
|
|
@@ -198,7 +246,7 @@ function main() {
|
|
|
198
246
|
}
|
|
199
247
|
}
|
|
200
248
|
console.error("");
|
|
201
|
-
console.error("Fix:
|
|
249
|
+
console.error("Fix: restore scripts/ and its inventory, update Side A/C, or correct the Side B skill citation.");
|
|
202
250
|
exit(1);
|
|
203
251
|
}
|
|
204
252
|
const scriptPathFromArgv = argv[1];
|
|
@@ -82,6 +82,7 @@ async function writePluginBundleReadme() {
|
|
|
82
82
|
"- `commands/`",
|
|
83
83
|
"- `agents/` — `code-reviewer`, `security-auditor`, `test-engineer` personas (auto-discovered Claude Code subagents; pattern from addy/agent-skills)",
|
|
84
84
|
"- `hooks/`",
|
|
85
|
+
"- `scripts/`",
|
|
85
86
|
"- `bin/mcp-server.mjs`",
|
|
86
87
|
"- `bin/observe.mjs`",
|
|
87
88
|
"- `bin/backfill.mjs`",
|
|
@@ -140,6 +141,7 @@ async function writePluginBundle() {
|
|
|
140
141
|
copyDirectory(join(REPO_ROOT, "commands"), join(PLUGIN_BUNDLE_DIR, "commands")),
|
|
141
142
|
copyDirectory(join(REPO_ROOT, "agents"), join(PLUGIN_BUNDLE_DIR, "agents")),
|
|
142
143
|
copyDirectory(join(REPO_ROOT, "hooks"), join(PLUGIN_BUNDLE_DIR, "hooks")),
|
|
144
|
+
copyDirectory(join(REPO_ROOT, "scripts"), join(PLUGIN_BUNDLE_DIR, "scripts")),
|
|
143
145
|
copyDirectory(join(REPO_ROOT, "instinct-packs"), join(PLUGIN_BUNDLE_DIR, "instinct-packs")),
|
|
144
146
|
copyDirectory(join(REPO_ROOT, "templates", "planning-with-files"), join(PLUGIN_BUNDLE_DIR, "templates", "planning-with-files")),
|
|
145
147
|
copyFileTo(join(REPO_ROOT, "bin", "mcp-server.mjs"), join(PLUGIN_BUNDLE_DIR, "bin", "mcp-server.mjs")),
|
package/bin/install.mjs
CHANGED
|
@@ -45,29 +45,25 @@ const SESSION_HOOK_TYPES = ["SessionStart", "SessionEnd"];
|
|
|
45
45
|
// accidental overlap between HOOK_TYPES and SESSION_HOOK_TYPES never causes
|
|
46
46
|
// a hook bucket to be processed twice.
|
|
47
47
|
const ALL_HOOK_TYPES = Array.from(new Set([...HOOK_TYPES, ...SESSION_HOOK_TYPES]));
|
|
48
|
-
//
|
|
49
|
-
//
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
const INSTALLER_OBSERVE_SESSION_COMMAND_RE = /^bash ".*[\\/]\.claude[\\/]instincts[\\/](?:observe|session)\.sh"$/;
|
|
54
|
-
function isBrokenObserveOrSessionCommand(command) {
|
|
48
|
+
// Match only installer-owned lifecycle commands. Bash rows are legacy and get
|
|
49
|
+
// migrated on install; both legacy and current Node rows are removed on uninstall.
|
|
50
|
+
const LEGACY_OBSERVE_SESSION_COMMAND_RE = /^bash ".*[\\/]\.claude[\\/]instincts[\\/](?:observe|session)\.sh"$/;
|
|
51
|
+
const NODE_OBSERVE_SESSION_COMMAND_RE = /^node ".*[\\/]\.claude[\\/]instincts[\\/](?:bin[\\/]observe|session)\.mjs"$/;
|
|
52
|
+
function isLegacyObserveOrSessionCommand(command) {
|
|
55
53
|
if (typeof command !== "string")
|
|
56
54
|
return false;
|
|
57
|
-
return
|
|
55
|
+
return LEGACY_OBSERVE_SESSION_COMMAND_RE.test(command);
|
|
58
56
|
}
|
|
59
|
-
// Any installer-owned observe.sh / session.sh hook command, broken or clean.
|
|
60
|
-
// Used by uninstall to drop both freshly-installed forward-slash hooks and any
|
|
61
|
-
// stale legacy entries. Pairs with isBrokenObserveOrSessionCommand above.
|
|
62
57
|
function isOurObserveOrSessionCommand(command) {
|
|
63
58
|
if (typeof command !== "string")
|
|
64
59
|
return false;
|
|
65
|
-
return
|
|
60
|
+
return LEGACY_OBSERVE_SESSION_COMMAND_RE.test(command) ||
|
|
61
|
+
NODE_OBSERVE_SESSION_COMMAND_RE.test(command);
|
|
66
62
|
}
|
|
67
63
|
function getHomeDir() {
|
|
68
64
|
return process.env.HOME || process.env.USERPROFILE || homedir();
|
|
69
65
|
}
|
|
70
|
-
function
|
|
66
|
+
function toCommandPath(filePath) {
|
|
71
67
|
return filePath.replace(/\\/g, "/");
|
|
72
68
|
}
|
|
73
69
|
function isInstallMode(value) {
|
|
@@ -84,23 +80,6 @@ function readJsonFile(filePath) {
|
|
|
84
80
|
return null;
|
|
85
81
|
}
|
|
86
82
|
}
|
|
87
|
-
// The observation hooks (hooks/observe.sh, hooks/session.sh) are bash scripts.
|
|
88
|
-
// On Windows without Git Bash / WSL the hook commands written into settings.json
|
|
89
|
-
// silently no-op, so the user finishes install thinking observation capture is
|
|
90
|
-
// live when it never fires. Refuse the install with one actionable line instead.
|
|
91
|
-
function assertBashAvailableOnWindows() {
|
|
92
|
-
if (process.platform !== "win32")
|
|
93
|
-
return;
|
|
94
|
-
try {
|
|
95
|
-
execSync("bash --version", { stdio: "ignore" });
|
|
96
|
-
}
|
|
97
|
-
catch {
|
|
98
|
-
console.error(" ✗ Install refused: the observation hooks (hooks/observe.sh, hooks/session.sh) " +
|
|
99
|
-
"are bash scripts, but `bash --version` is not on PATH. Install Git Bash or WSL, " +
|
|
100
|
-
"reopen your shell, and re-run — see README → Troubleshooting install.");
|
|
101
|
-
process.exit(1);
|
|
102
|
-
}
|
|
103
|
-
}
|
|
104
83
|
// The marketplace `/plugin install` path and this npx installer both write into
|
|
105
84
|
// ~/.claude/. Running both duplicates hooks, commands, and skills. We cannot
|
|
106
85
|
// fully resolve Claude Code's marketplace layout from here, so this is a loud
|
|
@@ -164,39 +143,26 @@ function setupMulahazah() {
|
|
|
164
143
|
const globalDir = join(instinctsDir, "global");
|
|
165
144
|
mkdirSync(globalDir, { recursive: true });
|
|
166
145
|
console.log(` ✓ Instincts dir → ${instinctsDir}/`);
|
|
167
|
-
|
|
168
|
-
const observeDest = join(instinctsDir, "observe.sh");
|
|
169
|
-
if (existsSync(observeSrc)) {
|
|
170
|
-
copyFileSync(observeSrc, observeDest);
|
|
171
|
-
chmodSync(observeDest, 0o755);
|
|
172
|
-
console.log(` ✓ observe.sh → ${observeDest}`);
|
|
173
|
-
}
|
|
174
|
-
// Node observer (Phase 1 of the two-phase hook). The bash shim above
|
|
175
|
-
// exec's this when `node` and the file are both present; otherwise it
|
|
176
|
-
// falls back to the in-bash thin-schema path. Layout under instinctsDir
|
|
177
|
-
// mirrors the repo's bin/ + lib/ structure so the relative import in
|
|
178
|
-
// observe.mjs (`../lib/observe-event.mjs`) resolves correctly.
|
|
146
|
+
// Layout mirrors bin/ + lib/ so observe.mjs's relative import resolves.
|
|
179
147
|
const observerJsSrc = join(REPO_ROOT, "bin", "observe.mjs");
|
|
180
148
|
const observeEventSrc = join(REPO_ROOT, "lib", "observe-event.mjs");
|
|
149
|
+
const observerJsDest = join(instinctsDir, "bin", "observe.mjs");
|
|
181
150
|
if (existsSync(observerJsSrc) && existsSync(observeEventSrc)) {
|
|
182
151
|
const binDir = join(instinctsDir, "bin");
|
|
183
152
|
const libDir = join(instinctsDir, "lib");
|
|
184
153
|
mkdirSync(binDir, { recursive: true });
|
|
185
154
|
mkdirSync(libDir, { recursive: true });
|
|
186
|
-
const observerJsDest = join(binDir, "observe.mjs");
|
|
187
155
|
const observeEventDest = join(libDir, "observe-event.mjs");
|
|
188
156
|
copyFileSync(observerJsSrc, observerJsDest);
|
|
189
157
|
copyFileSync(observeEventSrc, observeEventDest);
|
|
190
158
|
console.log(` ✓ Node observer → ${observerJsDest}`);
|
|
191
159
|
}
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
console.log(` ✓ session.sh → ${sessionDest}`);
|
|
199
|
-
}
|
|
160
|
+
const sessionSrc = join(REPO_ROOT, "hooks", "session.mjs");
|
|
161
|
+
const sessionDest = join(instinctsDir, "session.mjs");
|
|
162
|
+
if (existsSync(sessionSrc)) {
|
|
163
|
+
copyFileSync(sessionSrc, sessionDest);
|
|
164
|
+
chmodSync(sessionDest, 0o755);
|
|
165
|
+
console.log(` ✓ Node session hook → ${sessionDest}`);
|
|
200
166
|
}
|
|
201
167
|
const commandsDir = join(home, ".claude", "commands");
|
|
202
168
|
mkdirSync(commandsDir, { recursive: true });
|
|
@@ -208,7 +174,7 @@ function setupMulahazah() {
|
|
|
208
174
|
console.log(` ✓ /${commandFile.replace(".md", "")} command → ${commandDest}`);
|
|
209
175
|
}
|
|
210
176
|
}
|
|
211
|
-
patchClaudeSettings(
|
|
177
|
+
patchClaudeSettings(observerJsDest);
|
|
212
178
|
if (INSTALL_MODE === "expert") {
|
|
213
179
|
setupMcpServer();
|
|
214
180
|
}
|
|
@@ -271,6 +237,10 @@ function patchClaudeSettings(observePath) {
|
|
|
271
237
|
if (!settings.hooks) {
|
|
272
238
|
settings.hooks = {};
|
|
273
239
|
}
|
|
240
|
+
const preserveSessionHooks = INSTALL_MODE === "expert" || SESSION_HOOK_TYPES.some((hookType) => {
|
|
241
|
+
const entries = settings.hooks?.[hookType];
|
|
242
|
+
return Array.isArray(entries) && entries.some((entry) => Array.isArray(entry?.hooks) && entry.hooks.some((hook) => isOurObserveOrSessionCommand(hook?.command)));
|
|
243
|
+
});
|
|
274
244
|
// Strip broken legacy observe/session hooks at the hook level, not the entry
|
|
275
245
|
// level. A single entry may carry a foreign command alongside a broken hook;
|
|
276
246
|
// dropping the whole entry to remove the broken hook would also wipe the
|
|
@@ -289,7 +259,7 @@ function patchClaudeSettings(observePath) {
|
|
|
289
259
|
cleanedEntries.push(entry);
|
|
290
260
|
continue;
|
|
291
261
|
}
|
|
292
|
-
const filteredHooks = entryHooks.filter((hook) => !
|
|
262
|
+
const filteredHooks = entryHooks.filter((hook) => !isLegacyObserveOrSessionCommand(hook?.command));
|
|
293
263
|
if (filteredHooks.length === entryHooks.length) {
|
|
294
264
|
cleanedEntries.push(entry);
|
|
295
265
|
continue;
|
|
@@ -308,7 +278,7 @@ function patchClaudeSettings(observePath) {
|
|
|
308
278
|
changed = true;
|
|
309
279
|
}
|
|
310
280
|
}
|
|
311
|
-
const observeCommand = `
|
|
281
|
+
const observeCommand = `node "${toCommandPath(observePath)}"`;
|
|
312
282
|
for (const hookType of HOOK_TYPES) {
|
|
313
283
|
if (!Array.isArray(settings.hooks[hookType])) {
|
|
314
284
|
settings.hooks[hookType] = [];
|
|
@@ -327,9 +297,9 @@ function patchClaudeSettings(observePath) {
|
|
|
327
297
|
changed = true;
|
|
328
298
|
}
|
|
329
299
|
}
|
|
330
|
-
if (
|
|
331
|
-
const sessionPath = join(getHomeDir(), ".claude", "instincts", "session.
|
|
332
|
-
const sessionCommand = `
|
|
300
|
+
if (preserveSessionHooks) {
|
|
301
|
+
const sessionPath = join(getHomeDir(), ".claude", "instincts", "session.mjs");
|
|
302
|
+
const sessionCommand = `node "${toCommandPath(sessionPath)}"`;
|
|
333
303
|
for (const hookType of SESSION_HOOK_TYPES) {
|
|
334
304
|
if (!Array.isArray(settings.hooks[hookType])) {
|
|
335
305
|
settings.hooks[hookType] = [];
|
|
@@ -346,7 +316,7 @@ function patchClaudeSettings(observePath) {
|
|
|
346
316
|
}
|
|
347
317
|
if (changed) {
|
|
348
318
|
writeFileSync(settingsPath, JSON.stringify(settings, null, 2) + "\n");
|
|
349
|
-
const hookTypes =
|
|
319
|
+
const hookTypes = preserveSessionHooks
|
|
350
320
|
? "PreToolUse/PostToolUse/SessionStart/SessionEnd"
|
|
351
321
|
: "PreToolUse/PostToolUse";
|
|
352
322
|
console.log(` ✓ Patched ~/.claude/settings.json with ${hookTypes} hooks`);
|
|
@@ -382,7 +352,7 @@ function uninstallAll() {
|
|
|
382
352
|
console.error(` ✗ ${commandName}: ${getErrorMessage(error)}`);
|
|
383
353
|
}
|
|
384
354
|
}
|
|
385
|
-
for (const hookFile of ["observe.sh", "session.sh"]) {
|
|
355
|
+
for (const hookFile of ["observe.sh", "session.sh", "session.mjs"]) {
|
|
386
356
|
const filePath = join(home, ".claude", "instincts", hookFile);
|
|
387
357
|
if (!existsSync(filePath)) {
|
|
388
358
|
continue;
|
|
@@ -625,7 +595,6 @@ console.log(`
|
|
|
625
595
|
continuous-improvement (mode: ${INSTALL_MODE})
|
|
626
596
|
Research → Plan → Execute → Verify → Reflect → Learn → Iterate
|
|
627
597
|
`);
|
|
628
|
-
assertBashAvailableOnWindows();
|
|
629
598
|
warnOnMarketplaceCollision();
|
|
630
599
|
console.log("Installing to Claude Code...\n");
|
|
631
600
|
const installed = installSkill() ? 1 : 0;
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: production-readiness-review
|
|
3
|
-
description: "Parallel multi-agent readiness gate — fan blind reviewers across performance, security, UI/UX,
|
|
3
|
+
description: "Parallel multi-agent readiness gate — fan blind reviewers across performance, security, UI/UX, test coverage, and simplicity, each grounding findings in real code/logs/live data, then reconcile into one deduplicated, severity-ranked punch-list. Reports only; never fixes, merges, or deploys."
|
|
4
4
|
---
|
|
5
5
|
|
|
6
6
|
# /production-readiness-review
|
|
@@ -20,11 +20,12 @@ Pure routing over existing skills and agents. Adds no new code.
|
|
|
20
20
|
## Behavior
|
|
21
21
|
|
|
22
22
|
1. **Scope** — establish ground truth: the diff under review and which changes are recent (`git diff`; `reconcile` fallback for branch/base state). Recent changes get extra scrutiny because they are the likeliest source of self-inflicted defects.
|
|
23
|
-
2. **Fan out** — `superpowers:dispatching-parallel-agents` launches
|
|
23
|
+
2. **Fan out** — `superpowers:dispatching-parallel-agents` launches five reviewers, each blind to the others. Every reviewer is instructed to ground each finding in real code, logs, or live queries, and never to assume or fabricate state:
|
|
24
24
|
- **Performance & bundle-size** — hot paths, N+1 queries, unbounded work, regressions.
|
|
25
25
|
- **Security & data-access** (`security-auditor`) — authn/authz, input handling, injection, secret exposure, unsafe data access.
|
|
26
26
|
- **UI/UX correctness** — verified live with Playwright when the MCP is available, else static review of the changed surface.
|
|
27
27
|
- **Test coverage & flaky/stale mocks** (`test-engineer`) — uncovered branches, stale mocks, timing-flaky tests.
|
|
28
|
+
- **Simplicity & over-engineering** (`simplicity-review`) — code that could reuse an existing file, a stdlib or native feature, or fewer lines; reports trim opportunities via the reuse ladder and never flags input validation, data-loss handling, security, or accessibility.
|
|
28
29
|
3. **Reconcile** — a final pass dedupes findings across reviewers, ranks each CRITICAL / HIGH / MEDIUM / LOW by severity and confidence, and explicitly flags any defect introduced by the changes under review.
|
|
29
30
|
4. **Present** — emit the consolidated punch-list, severity-ranked, with file references. **Stop.**
|
|
30
31
|
|
|
@@ -42,7 +43,7 @@ Pure routing over existing skills and agents. Adds no new code.
|
|
|
42
43
|
|
|
43
44
|
## Composition
|
|
44
45
|
|
|
45
|
-
Routes through: `reconcile` (scope/ground truth) → `superpowers:dispatching-parallel-agents` (fan-out) → the `security-auditor` and `test-engineer` agents (
|
|
46
|
+
Routes through: `reconcile` (scope/ground truth) → `superpowers:dispatching-parallel-agents` (fan-out) → the `security-auditor` and `test-engineer` agents and the `simplicity-review` skill (three of the five dimensions) → a reconciliation pass that ranks and dedupes. Each step falls back to its inline behavior when the preferred skill or agent is not installed.
|
|
46
47
|
|
|
47
48
|
## Example
|
|
48
49
|
|
|
@@ -50,4 +51,4 @@ Routes through: `reconcile` (scope/ground truth) → `superpowers:dispatching-pa
|
|
|
50
51
|
/production-readiness-review #246
|
|
51
52
|
```
|
|
52
53
|
|
|
53
|
-
Scopes PR #246's diff, fans
|
|
54
|
+
Scopes PR #246's diff, fans five blind reviewers across performance, security, UI/UX, test coverage, and simplicity, then returns one deduplicated severity-ranked punch-list — flagging anything the PR's own changes introduced — and stops for you to prioritize.
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: simplicity-review
|
|
3
|
+
description: Review the current diff for over-engineering — flag code that could reuse an existing file, a stdlib or native feature, or fewer lines — and report GO/TRIM findings without touching code. Enforces Law 4 (Verify Before Reporting).
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# /simplicity-review — Judge the Diff Before You Ship It
|
|
7
|
+
|
|
8
|
+
Read the current change like the laziest senior dev in the room: could this have been smaller? Passing tests prove correctness, not minimality. Backed by the `simplicity-review` skill.
|
|
9
|
+
|
|
10
|
+
## What it does
|
|
11
|
+
|
|
12
|
+
Takes the working-tree diff (vs HEAD by default; accepts an optional commit range or file list), reads each changed block, and walks a fixed reuse ladder:
|
|
13
|
+
|
|
14
|
+
```
|
|
15
|
+
1. Does this need to exist? -> skip it (YAGNI)
|
|
16
|
+
2. Already in this codebase? -> reuse it
|
|
17
|
+
3. Stdlib does it? -> use it
|
|
18
|
+
4. Native platform feature? -> use it
|
|
19
|
+
5. Installed dependency? -> use it
|
|
20
|
+
6. One line? -> one line
|
|
21
|
+
7. Only then: the minimum that works
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
It reports `file:line`, what is over-built, the specific simpler path, and closes with `GO` (already minimal) or `TRIM` (findings to apply). It does not edit code.
|
|
25
|
+
|
|
26
|
+
## Default skeptical
|
|
27
|
+
|
|
28
|
+
A finding is a hypothesis. Read the surrounding code and prove the simpler path exists and preserves behavior before asserting it; a wrong trim is worse than the over-build. Never flag input validation, data-loss-preventing error handling, security, or accessibility — lazy, not negligent.
|
|
29
|
+
|
|
30
|
+
## Pairs with
|
|
31
|
+
|
|
32
|
+
- **`simplicity-review`** skill — the discipline this command runs.
|
|
33
|
+
- **`proceed-with-the-recommendation`** — apply the trims under the 7 Laws.
|
|
34
|
+
- **`verification-loop`** — the ladder to re-run on whatever you trim.
|
|
35
|
+
- **`production-readiness-review`** — the sibling diff review for performance, security, UI, and test coverage.
|
|
@@ -40,8 +40,8 @@ The observation hook appends one row per tool call to
|
|
|
40
40
|
`<project-hash>` from the current repo, or check `~/.claude/instincts/global/`).
|
|
41
41
|
|
|
42
42
|
- If it exists and has at least one row — capture is recording. Record `observe: ✓`.
|
|
43
|
-
- If it is missing or empty
|
|
44
|
-
|
|
43
|
+
- If it is missing or empty, record `observe: ✗ (observation hook not recording; re-run
|
|
44
|
+
the installer to migrate legacy Bash hook rows to the Node observer)`.
|
|
45
45
|
|
|
46
46
|
## Report
|
|
47
47
|
|