continuous-improvement 3.20.0 → 3.20.4

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.
Files changed (53) hide show
  1. package/.claude-plugin/marketplace.json +1 -1
  2. package/QUICKSTART.md +1 -1
  3. package/README.md +2 -2
  4. package/bin/check-landing-version.mjs +63 -0
  5. package/bin/check-scripts-citation-drift.mjs +61 -13
  6. package/bin/generate-plugin-manifests.mjs +2 -0
  7. package/bin/install.mjs +19 -11
  8. package/commands/verify-install.md +1 -1
  9. package/hooks/gateguard.mjs +22 -3
  10. package/hooks/query-cost-nudge.mjs +1 -0
  11. package/hooks/typecheck-stop.mjs +2 -1
  12. package/package.json +5 -3
  13. package/plugins/beginner.json +1 -1
  14. package/plugins/continuous-improvement/.claude-plugin/marketplace.json +1 -1
  15. package/plugins/continuous-improvement/.claude-plugin/plugin.json +1 -1
  16. package/plugins/continuous-improvement/README.md +1 -0
  17. package/plugins/continuous-improvement/commands/verify-install.md +1 -1
  18. package/plugins/continuous-improvement/hooks/gateguard.mjs +22 -3
  19. package/plugins/continuous-improvement/hooks/query-cost-nudge.mjs +1 -0
  20. package/plugins/continuous-improvement/hooks/typecheck-stop.mjs +2 -1
  21. package/plugins/continuous-improvement/scripts/README.md +33 -0
  22. package/plugins/continuous-improvement/scripts/detect-deploy-target.sh +66 -0
  23. package/plugins/continuous-improvement/scripts/get-deployed-sha.sh +113 -0
  24. package/plugins/continuous-improvement/scripts/git-state-snapshot.sh +48 -0
  25. package/plugins/continuous-improvement/scripts/resolve-verify-ladder.mjs +241 -0
  26. package/plugins/continuous-improvement/scripts/route-recommendation.mjs +178 -0
  27. package/plugins/continuous-improvement/scripts/route-recommendation.routes.json +213 -0
  28. package/plugins/continuous-improvement/scripts/run-synthetic.mjs +298 -0
  29. package/plugins/continuous-improvement/scripts/scan-past-mistakes.mjs +285 -0
  30. package/plugins/continuous-improvement/skills/deploy-receipt/SKILL.md +2 -2
  31. package/plugins/continuous-improvement/skills/gateguard/SKILL.md +2 -2
  32. package/plugins/continuous-improvement/skills/proceed-with-the-recommendation/SKILL.md +2 -2
  33. package/plugins/continuous-improvement/skills/reconcile/SKILL.md +1 -1
  34. package/plugins/continuous-improvement/skills/verification-loop/SKILL.md +5 -5
  35. package/plugins/continuous-improvement/skills/workspace-surface-audit/SKILL.md +1 -1
  36. package/plugins/continuous-improvement/skills/worktree-safety/SKILL.md +1 -1
  37. package/plugins/expert.json +1 -1
  38. package/scripts/README.md +33 -0
  39. package/scripts/detect-deploy-target.sh +66 -0
  40. package/scripts/get-deployed-sha.sh +113 -0
  41. package/scripts/git-state-snapshot.sh +48 -0
  42. package/scripts/resolve-verify-ladder.mjs +241 -0
  43. package/scripts/route-recommendation.mjs +178 -0
  44. package/scripts/route-recommendation.routes.json +213 -0
  45. package/scripts/run-synthetic.mjs +298 -0
  46. package/scripts/scan-past-mistakes.mjs +285 -0
  47. package/skills/deploy-receipt.md +2 -2
  48. package/skills/gateguard.md +2 -2
  49. package/skills/proceed-with-the-recommendation.md +2 -2
  50. package/skills/reconcile.md +1 -1
  51. package/skills/verification-loop.md +5 -5
  52. package/skills/workspace-surface-audit.md +1 -1
  53. package/skills/worktree-safety.md +1 -1
@@ -0,0 +1,285 @@
1
+ #!/usr/bin/env node
2
+ // scripts/scan-past-mistakes.mjs
3
+ //
4
+ // Scan the three past-mistake surfaces named in Phase 0 P-MAG of
5
+ // `proceed-with-the-recommendation` and surface their content as a
6
+ // machine-readable list, so the skill's most-skipped phase becomes
7
+ // mechanically detectable: if the scan never runs, no output is emitted.
8
+ //
9
+ // Surfaces (all three are scanned every invocation):
10
+ // 1. observations.jsonl — JSONL lines whose type (or legacy event field)
11
+ // is "failure" or "correction". Last N (default 10) returned.
12
+ // 2. memory feedback — every feedback_*.md file in the project's
13
+ // auto-memory directory whose frontmatter declares `type: feedback`.
14
+ // 3. CLAUDE.md — rows extracted from a "## Past Mistakes" table
15
+ // in the project's CLAUDE.md (markdown table, first column = date).
16
+ //
17
+ // Defaults derive each path from the project root (positional arg; default
18
+ // cwd). All three can be overridden explicitly via flags for testing or
19
+ // non-default layouts.
20
+ //
21
+ // Usage:
22
+ // node scripts/scan-past-mistakes.mjs # cwd default
23
+ // node scripts/scan-past-mistakes.mjs <repo-root> # explicit root
24
+ // node scripts/scan-past-mistakes.mjs --json # JSON output
25
+ // node scripts/scan-past-mistakes.mjs \
26
+ // --observations <path> --memory-dir <dir> --claude-md <path>
27
+ // node scripts/scan-past-mistakes.mjs --max-observations 25
28
+ //
29
+ // Active-in-scope assessment is the LLM's job, not the script's. The script
30
+ // surfaces raw entries with citations; the skill body annotates each line
31
+ // with "Active in current scope: yes|no" based on the current task.
32
+ //
33
+ // Cited by:
34
+ // - skills/proceed-with-the-recommendation.md Phase 0 Rule 1
35
+
36
+ import { existsSync, readFileSync, readdirSync } from "node:fs";
37
+ import { homedir } from "node:os";
38
+ import { join } from "node:path";
39
+ import { argv, cwd, exit, stdout } from "node:process";
40
+
41
+ function parseArgs() {
42
+ const args = argv.slice(2);
43
+ const out = {
44
+ json: false,
45
+ root: cwd(),
46
+ observations: null,
47
+ memoryDir: null,
48
+ claudeMd: null,
49
+ maxObservations: 10,
50
+ };
51
+ for (let i = 0; i < args.length; i++) {
52
+ const a = args[i];
53
+ if (a === "--json") {
54
+ out.json = true;
55
+ } else if (a === "--observations") {
56
+ out.observations = args[++i];
57
+ } else if (a === "--memory-dir") {
58
+ out.memoryDir = args[++i];
59
+ } else if (a === "--claude-md") {
60
+ out.claudeMd = args[++i];
61
+ } else if (a === "--max-observations") {
62
+ out.maxObservations = parseInt(args[++i], 10) || 10;
63
+ } else if (a === "-h" || a === "--help") {
64
+ stdout.write(
65
+ "usage: scan-past-mistakes.mjs [--json] [<repo-root>]\n" +
66
+ " [--observations <path>] [--memory-dir <dir>] [--claude-md <path>]\n" +
67
+ " [--max-observations <n>]\n",
68
+ );
69
+ exit(0);
70
+ } else {
71
+ out.root = a;
72
+ }
73
+ }
74
+ return out;
75
+ }
76
+
77
+ // Map a project root to the auto-memory subdirectory name. Lowercases the
78
+ // Windows drive letter and replaces path separators with dashes, matching
79
+ // the convention used by `~/.claude/projects/<hash>/memory/` and
80
+ // `~/.claude/instincts/<hash>/`. On POSIX paths the leading slash becomes
81
+ // a leading dash, which is fine — the host has the same path on both
82
+ // surfaces and the resolved file existence is what's checked, not the
83
+ // hash format itself.
84
+ function projectHash(root) {
85
+ let p = root;
86
+ p = p.replace(/^([A-Za-z]):/, (_m, d) => d.toLowerCase() + ":");
87
+ p = p.replace(/:[\\/]/, "--");
88
+ p = p.replace(/[\\/]/g, "-");
89
+ return p;
90
+ }
91
+
92
+ function defaultObservationsPath(root) {
93
+ return join(homedir(), ".claude", "instincts", projectHash(root), "observations.jsonl");
94
+ }
95
+
96
+ function defaultMemoryDir(root) {
97
+ return join(homedir(), ".claude", "projects", projectHash(root), "memory");
98
+ }
99
+
100
+ function defaultClaudeMdPath(root) {
101
+ return join(root, "CLAUDE.md");
102
+ }
103
+
104
+ function scanObservations(path, maxN) {
105
+ if (!path || !existsSync(path)) return [];
106
+ const raw = readFileSync(path, "utf8");
107
+ const lines = raw.split(/\r?\n/);
108
+ const matches = [];
109
+ for (let i = 0; i < lines.length; i++) {
110
+ const line = lines[i].trim();
111
+ if (!line) continue;
112
+ let obj;
113
+ try {
114
+ obj = JSON.parse(line);
115
+ } catch {
116
+ continue;
117
+ }
118
+ // Support both the current `type` field and the legacy `event` field
119
+ // (per feedback_observer_field_name_bug — pre-2026-05-06T00:38Z rows).
120
+ const t = obj.type ?? obj.event;
121
+ if (t !== "failure" && t !== "correction") continue;
122
+ const summary =
123
+ obj.summary ??
124
+ obj.output_summary ??
125
+ (typeof obj.tool_response === "string"
126
+ ? obj.tool_response
127
+ : JSON.stringify(obj).slice(0, 240));
128
+ matches.push({
129
+ line: i + 1,
130
+ ts: obj.ts ?? obj.timestamp ?? null,
131
+ type: t,
132
+ summary,
133
+ });
134
+ }
135
+ // Last N (chronologically — JSONL is append-only).
136
+ return matches.slice(-maxN);
137
+ }
138
+
139
+ function parseFrontmatter(content) {
140
+ const m = /^---\r?\n([\s\S]*?)\r?\n---/.exec(content);
141
+ if (!m) return null;
142
+ const fm = {};
143
+ for (const line of m[1].split(/\r?\n/)) {
144
+ const kv = /^([A-Za-z][A-Za-z0-9_-]*):\s*(.*)$/.exec(line);
145
+ if (!kv) continue;
146
+ let value = kv[2].trim();
147
+ if (
148
+ (value.startsWith('"') && value.endsWith('"')) ||
149
+ (value.startsWith("'") && value.endsWith("'"))
150
+ ) {
151
+ value = value.slice(1, -1);
152
+ }
153
+ fm[kv[1]] = value;
154
+ }
155
+ return fm;
156
+ }
157
+
158
+ function scanFeedbackMemories(dir) {
159
+ if (!dir || !existsSync(dir)) return [];
160
+ let entries;
161
+ try {
162
+ entries = readdirSync(dir);
163
+ } catch {
164
+ return [];
165
+ }
166
+ const out = [];
167
+ for (const file of entries) {
168
+ if (!file.startsWith("feedback_") || !file.endsWith(".md")) continue;
169
+ let content;
170
+ try {
171
+ content = readFileSync(join(dir, file), "utf8");
172
+ } catch {
173
+ continue;
174
+ }
175
+ const fm = parseFrontmatter(content);
176
+ if (!fm) continue;
177
+ // Only surface entries explicitly typed as feedback.
178
+ if (fm.type && fm.type !== "feedback") continue;
179
+ out.push({
180
+ file,
181
+ name: fm.name ?? null,
182
+ description: fm.description ?? null,
183
+ });
184
+ }
185
+ out.sort((a, b) => a.file.localeCompare(b.file));
186
+ return out;
187
+ }
188
+
189
+ function scanClaudeMdPastMistakes(path) {
190
+ if (!path || !existsSync(path)) return [];
191
+ const content = readFileSync(path, "utf8");
192
+ const headingIdx = content.search(/^##\s+Past Mistakes\s*$/m);
193
+ if (headingIdx === -1) return [];
194
+
195
+ // Slice from the heading to the start of the next ## heading (or EOF).
196
+ const fromHeading = content.slice(headingIdx);
197
+ const nextSectionRel = fromHeading.slice(2).search(/^##\s/m);
198
+ const section = nextSectionRel === -1 ? fromHeading : fromHeading.slice(0, nextSectionRel + 2);
199
+
200
+ const out = [];
201
+ for (const rawLine of section.split(/\r?\n/)) {
202
+ const line = rawLine.trim();
203
+ // Skip blank, heading, and the markdown table divider.
204
+ if (!line || line.startsWith("#")) continue;
205
+ if (/^\|[\s|:-]+\|$/.test(line)) continue;
206
+ if (!line.startsWith("|") || !line.endsWith("|")) continue;
207
+
208
+ // Parse pipe-delimited cells; strip leading/trailing pipe.
209
+ const inner = line.slice(1, -1);
210
+ const cells = inner.split("|").map((c) => c.trim());
211
+ if (cells.length < 2) continue;
212
+
213
+ // Header detection: literal "Date" first cell.
214
+ if (cells[0].toLowerCase() === "date") continue;
215
+ if (/^[-: ]+$/.test(cells[0])) continue;
216
+
217
+ out.push({
218
+ date: cells[0],
219
+ mistake: cells[1] ?? "",
220
+ lesson: cells[2] ?? null,
221
+ });
222
+ }
223
+ return out;
224
+ }
225
+
226
+ function pretty(scan) {
227
+ const total = scan.observations.length + scan.feedback.length + scan.claude_md.length;
228
+ if (total === 0) return "No prior mistakes recorded — proceed.\n";
229
+
230
+ const out = [`Past mistakes scanned: ${total} found across 3 surfaces.`, ""];
231
+
232
+ if (scan.observations.length > 0) {
233
+ out.push(`== observations.jsonl (${scan.observations.length}) ==`);
234
+ for (const o of scan.observations) {
235
+ const ts = o.ts ? `${o.ts} ` : "";
236
+ out.push(` [line ${o.line}] ${ts}${o.type}: ${o.summary}`);
237
+ }
238
+ out.push("");
239
+ }
240
+
241
+ if (scan.feedback.length > 0) {
242
+ out.push(`== feedback memories (${scan.feedback.length}) ==`);
243
+ for (const f of scan.feedback) {
244
+ out.push(` [${f.file}] ${f.name ?? ""}`);
245
+ if (f.description) out.push(` — ${f.description}`);
246
+ }
247
+ out.push("");
248
+ }
249
+
250
+ if (scan.claude_md.length > 0) {
251
+ out.push(`== CLAUDE.md Past Mistakes (${scan.claude_md.length}) ==`);
252
+ for (const e of scan.claude_md) {
253
+ out.push(` [${e.date}] ${e.mistake}`);
254
+ if (e.lesson) out.push(` Lesson: ${e.lesson}`);
255
+ }
256
+ }
257
+
258
+ return out.join("\n") + "\n";
259
+ }
260
+
261
+ function main() {
262
+ const args = parseArgs();
263
+ const obsPath = args.observations ?? defaultObservationsPath(args.root);
264
+ const memDir = args.memoryDir ?? defaultMemoryDir(args.root);
265
+ const claudeMd = args.claudeMd ?? defaultClaudeMdPath(args.root);
266
+
267
+ const scan = {
268
+ observations: scanObservations(obsPath, args.maxObservations),
269
+ feedback: scanFeedbackMemories(memDir),
270
+ claude_md: scanClaudeMdPastMistakes(claudeMd),
271
+ sources: {
272
+ observations: obsPath,
273
+ memory_dir: memDir,
274
+ claude_md: claudeMd,
275
+ },
276
+ };
277
+
278
+ if (args.json) {
279
+ stdout.write(JSON.stringify(scan, null, 2) + "\n");
280
+ } else {
281
+ stdout.write(pretty(scan));
282
+ }
283
+ }
284
+
285
+ main();
@@ -21,7 +21,7 @@ This skill defines the receipt that closes that gap, without modifying the vendo
21
21
  Activate when ALL of the following are true:
22
22
 
23
23
  1. A merge into the deploy branch (typically `main` or `master`) has just landed
24
- 2. [`scripts/detect-deploy-target.sh`](../scripts/detect-deploy-target.sh) returns a value other than `none` at the repo root. The script encodes the full file-marker table — `railway.toml` / `railway.json` → `railway`, `wrangler.toml` / `wrangler.jsonc` → `cloudflare`, `vercel.json` / `.vercel/` → `vercel`, `netlify.toml` → `netlify`, `fly.toml` → `fly`, `app.yaml` → `appengine`, `apprunner.yaml` → `apprunner`, `.github/workflows/*.yml` with a `deploy:` job → `gha-deploy`. First match wins, in that order. The script is the source of truth; the file list above is documentation
24
+ 2. `bash "${CLAUDE_PLUGIN_ROOT}/scripts/detect-deploy-target.sh"` (source: `scripts/detect-deploy-target.sh`) returns a value other than `none` at the repo root. The script encodes the full file-marker table — `railway.toml` / `railway.json` → `railway`, `wrangler.toml` / `wrangler.jsonc` → `cloudflare`, `vercel.json` / `.vercel/` → `vercel`, `netlify.toml` → `netlify`, `fly.toml` → `fly`, `app.yaml` → `appengine`, `apprunner.yaml` → `apprunner`, `.github/workflows/*.yml` with a `deploy:` job → `gha-deploy`. First match wins, in that order. The script is the source of truth; the file list above is documentation
25
25
  3. `finishing-a-development-branch` has reported "merged" — not "PR opened", not "review pending"
26
26
 
27
27
  Do NOT activate when:
@@ -45,7 +45,7 @@ The skill is provider-aware but never hardcodes a specific API key or token shap
45
45
 
46
46
  ### Route A — Provider CLI (preferred when authenticated)
47
47
 
48
- The CLI is the highest-fidelity source. Run [`scripts/get-deployed-sha.sh <provider>`](../scripts/get-deployed-sha.sh) — the script owns the per-provider pipeline (CLI + jq filter) and prints just the SHA on stdout. Inspect the pipeline shape without executing via `bash scripts/get-deployed-sha.sh --show-command <provider>`.
48
+ The CLI is the highest-fidelity source. Run `bash "${CLAUDE_PLUGIN_ROOT}/scripts/get-deployed-sha.sh" <provider>` (source: `scripts/get-deployed-sha.sh`) — the script owns the per-provider pipeline (CLI + jq filter) and prints just the SHA on stdout. Inspect the pipeline shape without executing via `bash "${CLAUDE_PLUGIN_ROOT}/scripts/get-deployed-sha.sh" --show-command <provider>`.
49
49
 
50
50
  Provider-to-pipeline map (cited from the script, not redefined here):
51
51
 
@@ -97,7 +97,7 @@ A second Claude/Codex/Maulana session can be running on the same host and the sa
97
97
 
98
98
  **On the first Edit / Write / mutating Bash of a session:**
99
99
 
100
- Run [`scripts/git-state-snapshot.sh`](../scripts/git-state-snapshot.sh) and quote its JSON envelope verbatim. Example output:
100
+ Run `bash "${CLAUDE_PLUGIN_ROOT}/scripts/git-state-snapshot.sh"` (source: `scripts/git-state-snapshot.sh`) and quote its JSON envelope verbatim. Example output:
101
101
 
102
102
  ```
103
103
  {"head":"966ce51","upstream":"966ce51","dirty":0,"root":"/path/to/repo","branch":"main"}
@@ -115,7 +115,7 @@ If `upstream` is `"none"` or `branch` is `"detached"`, say so explicitly. Do not
115
115
 
116
116
  **On every subsequent Edit / Write / mutating Bash, before allowing the action:**
117
117
 
118
- Re-run [`scripts/git-state-snapshot.sh`](../scripts/git-state-snapshot.sh) and diff against the baseline:
118
+ Re-run `bash "${CLAUDE_PLUGIN_ROOT}/scripts/git-state-snapshot.sh"` (source: `scripts/git-state-snapshot.sh`) and diff against the baseline:
119
119
 
120
120
  1. `head` — has it advanced past your baseline without your commits?
121
121
  2. `upstream` — did upstream move while you worked?
@@ -135,7 +135,7 @@ Before research begins, the skill must read its own track record. The instinct s
135
135
 
136
136
  ### Rule 1 — Acknowledge before context (right context from the beginning)
137
137
 
138
- Run [`scripts/scan-past-mistakes.mjs`](../scripts/scan-past-mistakes.mjs) at the project root. Scan three surfaces in one pass and surface every entry with a citation:
138
+ Run `node "${CLAUDE_PLUGIN_ROOT}/scripts/scan-past-mistakes.mjs"` (source: `scripts/scan-past-mistakes.mjs`) at the project root. Scan three surfaces in one pass and surface every entry with a citation:
139
139
 
140
140
  - `~/.claude/instincts/<project-hash>/observations.jsonl` — last N (default 10) entries with `type: failure` or `correction` (legacy `event` field also matched for pre-2026-05-06 rows)
141
141
  - `~/.claude/projects/<project-hash>/memory/feedback_*.md` — every file whose frontmatter declares `type: feedback`; the canonical home of the operator's named corrections (e.g. `feedback_past_mistake_gate.md`, `feedback_no_git_add_all_on_windows.md`)
@@ -202,7 +202,7 @@ For each item in the ORIGINAL order:
202
202
 
203
203
  ### Routing Table (with Inline Fallbacks)
204
204
 
205
- Run [`scripts/route-recommendation.mjs "<item>"`](../scripts/route-recommendation.mjs) to match a single recommendation item to its preferred chain + inline fallback. Default mode prints the matched row; `--json` for programmatic consumption; `--list` enumerates every row. The programmatic source of truth is [`scripts/route-recommendation.routes.json`](../scripts/route-recommendation.routes.json) — the table below is the human-readable documentation that mirrors it. If they drift, the routes.json file wins.
205
+ Run `node "${CLAUDE_PLUGIN_ROOT}/scripts/route-recommendation.mjs" "<item>"` (source: `scripts/route-recommendation.mjs`) to match a single recommendation item to its preferred chain + inline fallback. Default mode prints the matched row; `--json` for programmatic consumption; `--list` enumerates every row. The programmatic source of truth is `scripts/route-recommendation.routes.json` — the table below is the human-readable documentation that mirrors it. If they drift, the routes.json file wins.
206
206
 
207
207
  Rows whose **Preferred skill** is not bundled with the `continuous-improvement` plugin carry a `(Reference behavior — does not require <skill>.)` marker on the fallback cell. The marker makes the soft-dependency contract visible at point of use: the inline fallback is fully self-contained and runs without that skill installed. Rows whose preferred skill ships with the plugin (`ralph`, `tdd-workflow`, `continuous-improvement`) carry no marker — the dedicated skill is always available.
208
208
 
@@ -40,7 +40,7 @@ When another session/loop may be active, do not assume the tree is yours:
40
40
  - An in-progress `MERGE_HEAD` / `rebase-merge` you did not start means another actor is mid-operation. Do not "help" by editing conflicted files — wait, or hand off.
41
41
  - Re-read the current branch immediately before any mutation; if it shifted since your snapshot, re-survey from the top.
42
42
  - If `.git/index` keeps changing while you are idle, a writer is active. Pause and surface it rather than racing.
43
- - If `gateguard` is installed, its Parallel-Actor Gate already captured this baseline on the session's first mutation (via `scripts/git-state-snapshot.sh`) and divergence-checks every later mutation — `reconcile` complements that gate, it does not replace it. Without gateguard, run the snapshot above yourself.
43
+ - If `gateguard` is installed, its Parallel-Actor Gate already captured this baseline on the session's first mutation by running `bash "${CLAUDE_PLUGIN_ROOT}/scripts/git-state-snapshot.sh"` (source: `scripts/git-state-snapshot.sh`) and divergence-checks every later mutation — `reconcile` complements that gate, it does not replace it. Without gateguard, run the snapshot above yourself.
44
44
 
45
45
  ## Classify, Then Act
46
46
 
@@ -23,10 +23,10 @@ Invoke this skill:
23
23
 
24
24
  Every project has its own actual invocation for build / typecheck / lint / test / security / deploy-receipt. Hardcoding `npm run build` and `npm run test` works when the project happens to use those exact scripts; for everything else (pnpm, yarn, cargo, go, mise, just, custom scripts, monorepos with workspace-scoped commands) it returns "deps not installed" or "config not found" misreads from the wrong invocation. Phase 0 runs first so Phases 1–6 never have to guess.
25
25
 
26
- **Run [`scripts/resolve-verify-ladder.mjs`](../scripts/resolve-verify-ladder.mjs)** at the repo root. It encodes the full four-step resolution priority and emits the fenced block below. Use `--json` for machine consumption.
26
+ **Run `node "${CLAUDE_PLUGIN_ROOT}/scripts/resolve-verify-ladder.mjs"`** (source: `scripts/resolve-verify-ladder.mjs`) at the repo root. It encodes the full four-step resolution priority and emits the fenced block below. Use `--json` for machine consumption.
27
27
 
28
28
  ```
29
- $ node scripts/resolve-verify-ladder.mjs
29
+ $ node "${CLAUDE_PLUGIN_ROOT}/scripts/resolve-verify-ladder.mjs"
30
30
  verify-ladder (resolved):
31
31
  build: npm run build (sniff:package.json:scripts.build)
32
32
  typecheck: npm run typecheck (sniff:package.json:scripts.typecheck)
@@ -147,9 +147,9 @@ If either is `No`, the verification report goes back to the operator with the ex
147
147
 
148
148
  For repos whose `verify-ladder.json` declares a `deploy_receipt` field — or whose sniff path detects an auto-deploy target — the verify is not complete until the deployed SHA matches the merge SHA and a healthcheck returns 200. Hand off to the `deploy-receipt` skill (Law 4 deploy-seam companion landed in PR #83) and treat its `Receipt status: COMPLETE` as the gate.
149
149
 
150
- **Detection.** Run [`scripts/detect-deploy-target.sh`](../scripts/detect-deploy-target.sh) at the repo root. Output is one of `railway` / `cloudflare` / `vercel` / `netlify` / `fly` / `appengine` / `apprunner` / `gha-deploy` / `none`. Anything except `none` triggers handoff to `deploy-receipt`; `none` means Phase 8 is skipped (no deploy seam exists).
150
+ **Detection.** Run `bash "${CLAUDE_PLUGIN_ROOT}/scripts/detect-deploy-target.sh"` (source: `scripts/detect-deploy-target.sh`) at the repo root. Output is one of `railway` / `cloudflare` / `vercel` / `netlify` / `fly` / `appengine` / `apprunner` / `gha-deploy` / `none`. Anything except `none` triggers handoff to `deploy-receipt`; `none` means Phase 8 is skipped (no deploy seam exists).
151
151
 
152
- **SHA extraction.** For the detected provider, [`scripts/get-deployed-sha.sh <provider>`](../scripts/get-deployed-sha.sh) returns the currently-deployed SHA via the provider CLI; `--show-command <provider>` prints the pipeline shape without executing (useful for dry-runs and citation). `deploy-receipt` owns the receipt's other components (health endpoint, build artifact, on-incomplete modes) and Route B/C fallbacks.
152
+ **SHA extraction.** For the detected provider, `bash "${CLAUDE_PLUGIN_ROOT}/scripts/get-deployed-sha.sh" <provider>` (source: `scripts/get-deployed-sha.sh`) returns the currently-deployed SHA via the provider CLI; `bash "${CLAUDE_PLUGIN_ROOT}/scripts/get-deployed-sha.sh" --show-command <provider>` prints the pipeline shape without executing (useful for dry-runs and citation). `deploy-receipt` owns the receipt's other components (health endpoint, build artifact, on-incomplete modes) and Route B/C fallbacks.
153
153
 
154
154
  INCOMPLETE receipts move to "Immediate operator action" in the close, never to "ready". Library-only / package-published repos skip this phase entirely (no deploy seam exists).
155
155
 
@@ -164,7 +164,7 @@ Phase 8 confirms the deploy seam. Phase 9 confirms the deployed surface matches
164
164
 
165
165
  **What the runner does:**
166
166
 
167
- Implementation: `scripts/run-synthetic.mjs` encodes the lexical walk, interpreter map, env injection, and exit-code aggregation below. The prose is documentation, not the contract — when the two disagree, the script wins.
167
+ Run `node "${CLAUDE_PLUGIN_ROOT}/scripts/run-synthetic.mjs"` (source: `scripts/run-synthetic.mjs`); it encodes the lexical walk, interpreter map, env injection, and exit-code aggregation below. The prose is documentation, not the contract — when the two disagree, the script wins.
168
168
 
169
169
  1. List every `*.synthetic.{sh,mjs,ts,py}` file in the resolved directory in lexical order.
170
170
  2. For each file, set the input env vars: `BASE_URL` (production base from project config), `BASELINE_URL` (staging baseline from project config), `EXPECTED_SHA` (the merge SHA Phase 8 reported COMPLETE), `DEPLOY_BRANCH` (the deploy branch name), `RECEIPT_TIMESTAMP` (ISO-8601 of the receipt).
@@ -74,7 +74,7 @@ Probe and record (no destructive commands; quote results inline):
74
74
  - **jq availability.** `command -v jq` (or `Get-Command jq`). When jq is missing, observation-pipeline hooks fall back to a thin schema and curl/JSON one-liners need a node/python rewrite.
75
75
  - **Case-sensitive filesystem.** Test by creating two paths differing only in case in a tempdir. NTFS (Windows) and APFS (macOS default) are case-insensitive; Linux ext4 and case-sensitive APFS are case-sensitive. Affects `CLAUDE.md` vs `claude.md` resolution and import paths.
76
76
  - **CWD baseline.** `pwd` (or `Get-Location`) recorded at session start. `tsc`, build scripts, and some test runners change CWD as a side effect; subsequent commands run from the wrong directory return "deps not installed" or "config not found" misreads.
77
- - **Parallel-actor expectation.** Document whether a second Claude / Codex / Maulana session may operate on the same working tree. If yes, the `gateguard` Parallel-Actor Gate uses [`scripts/git-state-snapshot.sh`](../scripts/git-state-snapshot.sh) to produce a single JSON envelope (`{head, upstream, dirty, root, branch}`) for the baseline and the divergence check. This skill records whether parallel-actor is expected; gateguard owns the runtime mechanics, so the audit doesn't restate the git-command triple.
77
+ - **Parallel-actor expectation.** Document whether a second Claude / Codex / Maulana session may operate on the same working tree. If yes, the `gateguard` Parallel-Actor Gate runs `bash "${CLAUDE_PLUGIN_ROOT}/scripts/git-state-snapshot.sh"` (source: `scripts/git-state-snapshot.sh`) to produce a single JSON envelope (`{head, upstream, dirty, root, branch}`) for the baseline and the divergence check. This skill records whether parallel-actor is expected; gateguard owns the runtime mechanics, so the audit doesn't restate the git-command triple.
78
78
 
79
79
  Output the recorded grain as a single fenced block so it survives context compaction and any later phase can reference it without re-probing:
80
80
 
@@ -32,7 +32,7 @@ The continuous-improvement repo runs on Windows + Git Bash with `autocrlf=true`
32
32
 
33
33
  Before any source-writing call, verify all five. Fail closed on any miss.
34
34
 
35
- 1. **Root validity** — `git rev-parse --show-toplevel` resolves; the resolved path matches CWD after symlink-safe canonicalization. The `root` field of [`scripts/git-state-snapshot.sh`](../scripts/git-state-snapshot.sh) carries the canonicalized root, and its non-zero exit on `{"error":"not-a-git-repo"}` is itself the fail-closed signal — no second probe needed.
35
+ 1. **Root validity** — `git rev-parse --show-toplevel` resolves; the resolved path matches CWD after symlink-safe canonicalization. Run `bash "${CLAUDE_PLUGIN_ROOT}/scripts/git-state-snapshot.sh"` (source: `scripts/git-state-snapshot.sh`); its `root` field carries the canonicalized root, and its non-zero exit on `{"error":"not-a-git-repo"}` is itself the fail-closed signal — no second probe needed.
36
36
  2. **`.git` presence** — `.git` exists (file pointer for worktrees, directory for primary checkout). A missing or unreadable `.git` is an immediate stop.
37
37
  3. **Worktree registration** — `git worktree list` includes the resolved root with no `prunable` flag. Prunable worktrees can be deleted by another process at any moment.
38
38
  4. **Branch alignment** — current branch matches the lease ledger; `HEAD` is not detached unless the unit explicitly asked for detached state. The snapshot script's `branch` field carries either the branch name or the literal `"detached"`, so detached-state is observable without a second probe.
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "continuous-improvement",
3
- "version": "3.20.0",
3
+ "version": "3.20.4",
4
4
  "mode": "expert",
5
5
  "description": "Expert mode: tune confidence, manage instincts, and persist plans on disk. Adds safety, token-budget, and strategic-compact skills plus the /learn-eval command so long sessions stay sharp and learnings survive context resets.",
6
6
  "tools": [
@@ -0,0 +1,33 @@
1
+ # `scripts/` — deterministic primitives skills cite
2
+
3
+ This directory holds small, hand-authored scripts that skills (and hooks) cite instead of restating fixed operations inline. The motivation is the skills audit's "deterministic vs non-deterministic" axis: when a step inside a skill is the same operation every time (a fixed git command, a regex parse, a path lookup), it does not belong in the LLM-driven part of the skill — it belongs here, where the script runs the same way every invocation, costs no tokens, and stays in one place so multiple skills can share it.
4
+
5
+ ## Conventions
6
+
7
+ - **Hand-authored, no `.mts` source.** Files in `scripts/` are not part of the `tsc` build pipeline (see [`CLAUDE.md`](../CLAUDE.md) → "Build pipeline"). The pipeline owns `bin/`, `lib/`, `test/`. `scripts/` is the bare-metal home for shell scripts and one-off Node utilities skills cite directly.
8
+ - **One concern per script.** Each script does one thing and prints either machine-readable output (JSON envelope, single value) or a stable fenced block. Skills consume the output; they do not re-derive it.
9
+ - **Cross-platform shape.** Bash scripts run via Git Bash on Windows, native bash on macOS/Linux. Tests for bash scripts skip when bash is not on PATH (see `src/test/hook.test.mts` for the established skip pattern).
10
+ - **Cite from skills.** When a skill needs a primitive, the skill body cites the script path (`scripts/<name>.<ext>`) and quotes the expected output shape once. The skill does not restate the script's inner mechanics.
11
+
12
+ ## Inventory
13
+
14
+ | Script | Purpose | Cited by |
15
+ |---|---|---|
16
+ | `git-state-snapshot.sh` | JSON envelope `{head, upstream, dirty, root, branch}` for the current git working tree | `skills/gateguard.md` (Parallel-Actor Gate), `skills/worktree-safety.md` (Root + branch), `skills/workspace-surface-audit.md` (Environment Grain — parallel-actor row), `skills/reconcile.md` (Detect a Concurrent Writer) |
17
+ | `detect-deploy-target.sh` | Detect the auto-deploy provider for the repo at the current working directory (or first arg). Prints one of `railway`/`cloudflare`/`vercel`/`netlify`/`fly`/`appengine`/`apprunner`/`gha-deploy`/`none`. Always exits 0. | `skills/verification-loop.md` (Phase 8 deploy-receipt handoff), `skills/deploy-receipt.md` (When to Activate gate) |
18
+ | `get-deployed-sha.sh` | Per-provider deployed-SHA extraction. Default mode runs the CLI; `--show-command <provider>` prints the pipeline shape without executing (useful for citation, dry-run, tests). | `skills/verification-loop.md` (Phase 8), `skills/deploy-receipt.md` (Route A — provider CLI extraction) |
19
+ | `resolve-verify-ladder.mjs` | Resolve the per-project verification ladder for Phase 0 of `verification-loop`. Encodes the four-step priority — manifest > package.json sniff > per-language toolchain > ask-operator. Default mode prints the fenced block the skill displays; `--json` mode emits a JSON object for machine consumption. | `skills/verification-loop.md` (Phase 0) |
20
+ | `scan-past-mistakes.mjs` | Scan the three Past-Mistake Acknowledgment Gate surfaces — `~/.claude/instincts/<hash>/observations.jsonl` (last N failure/correction rows), `~/.claude/projects/<hash>/memory/feedback_*.md`, and `<root>/CLAUDE.md` "## Past Mistakes" table. Active-in-scope judgment is the LLM's job; the script provides quotes + citations only. | `skills/proceed-with-the-recommendation.md` (Phase 0 Rule 1) |
21
+ | `route-recommendation.mjs` + `route-recommendation.routes.json` | Match a recommendation item to its preferred-skill chain + inline fallback from the Phase 3 routing table (29 rows). Data file is the programmatic source of truth; the skill's table is documentation that mirrors it. Default mode prints a match block; `--json` for machine consumption; `--list` enumerates all rows. | `skills/proceed-with-the-recommendation.md` (Phase 3 routing table) |
22
+ | `run-synthetic.mjs` | Phase 9 runner: invoke every `*.synthetic.{sh,mjs,ts,py}` in `synthetic-checks/`, inject `BASE_URL`/`BASELINE_URL`/`EXPECTED_SHA`/`DEPLOY_BRANCH`/`RECEIPT_TIMESTAMP` (unset vars pass through as `""`), capture stdout/stderr/exit per check, aggregate. Default mode prints the report block; `--json` for machine consumption; `--fail-fast` halts on first drift or timeout; `--timeout <sec>` per-check wall-clock cap; `--show-command` dry-run. Exit 0 all-pass, 1 drift, 2 config error, 3 usage. | `skills/verification-loop.md` (Phase 9 — production-vs-baseline diff) |
23
+
24
+ When a new script lands here, add a row to this table in the same PR and cite the script from at least one skill — otherwise the script is dead code on arrival.
25
+
26
+ ## Relationship to other locations
27
+
28
+ - `bin/` — generated CLI entrypoints (`.mjs` from `src/bin/*.mts`). Do not hand-edit; see [`CLAUDE.md`](../CLAUDE.md) → "Build pipeline".
29
+ - `lib/` — generated library code (`.mjs` from `src/lib/*.mts`). Same rule.
30
+ - `hooks/` — generated PreToolUse / PostToolUse / Stop hooks (`.mjs` from `src/hooks/*.mts`). Wired in `plugins/continuous-improvement/hooks/hooks.json`.
31
+ - `scripts/` — this directory. Hand-authored primitives skills cite.
32
+
33
+ When deciding where a new piece of code belongs: if it implements a runtime hook the harness will call, it goes in `src/hooks/`. If it's a verification or CLI entrypoint the plugin or `npm run` invokes, it goes in `src/bin/`. If it's reusable logic shared between those, it goes in `src/lib/`. If it's a small primitive a skill body cites by path (and the operator or harness runs ad hoc), it goes here.
@@ -0,0 +1,66 @@
1
+ #!/usr/bin/env bash
2
+ # scripts/detect-deploy-target.sh
3
+ #
4
+ # Detect the auto-deploy provider for the repo rooted at the current working
5
+ # directory (or the first argument, if supplied). Composability primitive used
6
+ # by skills and hooks that need to know "does this repo auto-deploy, and if so
7
+ # from where?" without each restating the file-marker table.
8
+ #
9
+ # Output: one of
10
+ # railway | cloudflare | vercel | netlify | fly | appengine | apprunner |
11
+ # gha-deploy | none
12
+ #
13
+ # Resolution priority (first match wins):
14
+ # 1. railway.toml | railway.json → railway
15
+ # 2. wrangler.toml | wrangler.jsonc → cloudflare
16
+ # 3. vercel.json | .vercel/ → vercel
17
+ # 4. netlify.toml → netlify
18
+ # 5. fly.toml → fly
19
+ # 6. app.yaml → appengine
20
+ # 7. apprunner.yaml → apprunner
21
+ # 8. .github/workflows/*.{yml,yaml} containing "deploy:" job → gha-deploy
22
+ # 9. nothing matched → none
23
+ #
24
+ # Always exits 0. `none` is a valid result, not an error condition.
25
+ #
26
+ # Cited by:
27
+ # - skills/verification-loop.md Phase 8 (deploy-receipt handoff trigger)
28
+ # - skills/deploy-receipt.md "When to Activate" gate
29
+
30
+ set -u
31
+
32
+ ROOT="${1:-$PWD}"
33
+
34
+ emit() {
35
+ printf '%s\n' "$1"
36
+ exit 0
37
+ }
38
+
39
+ # Order is the contract — earlier rows shadow later ones when multiple
40
+ # markers exist in the same repo.
41
+ [ -f "$ROOT/railway.toml" ] && emit railway
42
+ [ -f "$ROOT/railway.json" ] && emit railway
43
+ [ -f "$ROOT/wrangler.toml" ] && emit cloudflare
44
+ [ -f "$ROOT/wrangler.jsonc" ] && emit cloudflare
45
+ [ -f "$ROOT/vercel.json" ] && emit vercel
46
+ [ -d "$ROOT/.vercel" ] && emit vercel
47
+ [ -f "$ROOT/netlify.toml" ] && emit netlify
48
+ [ -f "$ROOT/fly.toml" ] && emit fly
49
+ [ -f "$ROOT/app.yaml" ] && emit appengine
50
+ [ -f "$ROOT/apprunner.yaml" ] && emit apprunner
51
+
52
+ # GitHub Actions deploy workflow: scan .github/workflows/*.{yml,yaml} for a
53
+ # job whose key is literally `deploy:`. Cheap and conservative — false
54
+ # positives (a non-deploy job named "deploy") are unlikely; false negatives
55
+ # (a deploy job named something else) require the repo to declare the
56
+ # provider via one of the file markers above instead.
57
+ if [ -d "$ROOT/.github/workflows" ]; then
58
+ if grep -lE '^[[:space:]]*deploy:[[:space:]]*$' \
59
+ "$ROOT/.github/workflows/"*.yml \
60
+ "$ROOT/.github/workflows/"*.yaml 2>/dev/null \
61
+ | head -n 1 | grep -q .; then
62
+ emit gha-deploy
63
+ fi
64
+ fi
65
+
66
+ emit none
@@ -0,0 +1,113 @@
1
+ #!/usr/bin/env bash
2
+ # scripts/get-deployed-sha.sh
3
+ #
4
+ # Print the currently-deployed commit SHA for an auto-deploy provider, or
5
+ # print the CLI command shape (with `--show-command`) without executing it.
6
+ # Composability primitive that owns the per-provider CLI knowledge so skills
7
+ # can cite one path instead of restating the 5-provider extraction table.
8
+ #
9
+ # Usage:
10
+ # bash scripts/get-deployed-sha.sh <provider>
11
+ # bash scripts/get-deployed-sha.sh --show-command <provider>
12
+ #
13
+ # Providers: railway | cloudflare | vercel | netlify | fly
14
+ #
15
+ # Default mode: runs the provider CLI, pipes through jq, prints the SHA on
16
+ # stdout. Requires the CLI to be installed and authenticated; exits 3 with a
17
+ # clear error if the CLI is missing.
18
+ #
19
+ # --show-command mode: prints the command pipeline that would run, without
20
+ # executing it. Useful for skill citations, dry-runs, and tests that should
21
+ # not require live CLI auth.
22
+ #
23
+ # Exit codes:
24
+ # 0 — SHA printed (default mode) or command printed (--show-command mode)
25
+ # 2 — missing or unknown provider (usage error)
26
+ # 3 — required CLI not installed (default mode only)
27
+ # non-zero — CLI failure (passed through)
28
+ #
29
+ # Cited by:
30
+ # - skills/verification-loop.md Phase 8 (deploy-receipt handoff trigger)
31
+ # - skills/deploy-receipt.md Route A (provider CLI extraction)
32
+
33
+ set -u
34
+
35
+ SHOW_COMMAND=false
36
+ PROVIDER=""
37
+
38
+ while [ $# -gt 0 ]; do
39
+ case "$1" in
40
+ --show-command)
41
+ SHOW_COMMAND=true
42
+ shift
43
+ ;;
44
+ -h|--help)
45
+ sed -n '2,/^$/p' "$0" >&2
46
+ exit 0
47
+ ;;
48
+ *)
49
+ if [ -z "$PROVIDER" ]; then
50
+ PROVIDER="$1"
51
+ else
52
+ printf 'usage: get-deployed-sha.sh [--show-command] <provider>\n' >&2
53
+ exit 2
54
+ fi
55
+ shift
56
+ ;;
57
+ esac
58
+ done
59
+
60
+ if [ -z "$PROVIDER" ]; then
61
+ printf 'usage: get-deployed-sha.sh [--show-command] <provider>\n' >&2
62
+ exit 2
63
+ fi
64
+
65
+ # Single source of truth for the per-provider command pipeline. Each value is
66
+ # the literal pipeline that would run; the jq filter extracts the SHA.
67
+ case "$PROVIDER" in
68
+ railway)
69
+ CMD='railway status --json | jq -r .deployments[0].meta.commitHash'
70
+ CLI=railway
71
+ ;;
72
+ cloudflare)
73
+ CMD='wrangler deployments list --json | jq -r .[0].metadata.deployment_trigger.metadata.commit_hash'
74
+ CLI=wrangler
75
+ ;;
76
+ vercel)
77
+ CMD='vercel inspect "$(vercel ls --json | jq -r .[0].url)" --json | jq -r .gitSource.sha'
78
+ CLI=vercel
79
+ ;;
80
+ netlify)
81
+ CMD='netlify api listSiteDeploys --data="{\"site_id\":\"$NETLIFY_SITE_ID\"}" | jq -r .[0].commit_ref'
82
+ CLI=netlify
83
+ ;;
84
+ fly)
85
+ CMD='fly releases --json | jq -r .[0].commit_sha'
86
+ CLI=fly
87
+ ;;
88
+ *)
89
+ printf 'unknown or unsupported provider: %s\n' "$PROVIDER" >&2
90
+ printf 'supported: railway | cloudflare | vercel | netlify | fly\n' >&2
91
+ exit 2
92
+ ;;
93
+ esac
94
+
95
+ if [ "$SHOW_COMMAND" = "true" ]; then
96
+ printf '%s\n' "$CMD"
97
+ exit 0
98
+ fi
99
+
100
+ if ! command -v "$CLI" >/dev/null 2>&1; then
101
+ printf 'required CLI "%s" not installed for provider "%s"\n' "$CLI" "$PROVIDER" >&2
102
+ exit 3
103
+ fi
104
+
105
+ if ! command -v jq >/dev/null 2>&1; then
106
+ printf 'required CLI "jq" not installed\n' >&2
107
+ exit 3
108
+ fi
109
+
110
+ # Execute the pipeline. eval is intentional — the per-provider CMD contains
111
+ # pipes and command substitution that need shell interpretation. CMDs are
112
+ # sourced from the literal table above, not user input.
113
+ eval "$CMD"