continuous-improvement 3.1.0 → 3.9.1

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 (136) hide show
  1. package/.claude-plugin/marketplace.json +66 -0
  2. package/CHANGELOG.md +216 -0
  3. package/QUICKSTART.md +79 -12
  4. package/README.md +229 -349
  5. package/SKILL.md +87 -9
  6. package/action.yml +1 -1
  7. package/bin/analyze.sh +9 -1
  8. package/bin/backfill.mjs +172 -0
  9. package/bin/check-doc-runtime-claims.mjs +120 -0
  10. package/bin/check-docs-substrings.mjs +333 -0
  11. package/bin/check-everything-mirror.mjs +145 -0
  12. package/bin/check-routing-targets.mjs +151 -0
  13. package/bin/check-skill-law-tag.mjs +128 -0
  14. package/bin/check-skill-mirror.mjs +119 -0
  15. package/bin/check-skill-tiers.mjs +116 -0
  16. package/bin/check-third-party-shape.mjs +202 -0
  17. package/bin/generate-plugin-manifests.mjs +171 -0
  18. package/bin/harvest-friction.mjs +279 -0
  19. package/bin/hook-stats.mjs +258 -0
  20. package/bin/install.mjs +407 -499
  21. package/bin/lint-transcript.mjs +182 -210
  22. package/bin/mcp-server.mjs +834 -608
  23. package/bin/observe.mjs +148 -0
  24. package/bin/pre-commit-block-strays.sh +49 -0
  25. package/bin/refresh-third-party.mjs +416 -0
  26. package/bin/unified-cli.mjs +533 -0
  27. package/commands/continuous-improvement.md +43 -2
  28. package/commands/discipline.md +14 -0
  29. package/commands/harvest.md +76 -0
  30. package/commands/learn-eval.md +117 -0
  31. package/commands/planning-with-files.md +66 -0
  32. package/commands/proceed-with-the-recommendation.md +62 -0
  33. package/commands/ralph.md +103 -0
  34. package/commands/release-train.md +81 -0
  35. package/commands/seven-laws.md +16 -0
  36. package/commands/superpowers.md +180 -0
  37. package/commands/swarm.md +101 -0
  38. package/commands/workspace-surface-audit.md +77 -0
  39. package/hooks/gateguard.mjs +172 -0
  40. package/hooks/observe.sh +42 -4
  41. package/hooks/session.sh +3 -3
  42. package/hooks/three-section-close.mjs +181 -0
  43. package/instinct-packs/meta.json +16 -0
  44. package/lib/cli-anything.mjs +401 -0
  45. package/lib/compound-engineering.mjs +831 -0
  46. package/lib/gateguard-state.mjs +85 -0
  47. package/lib/observe-event.mjs +128 -0
  48. package/lib/plugin-metadata.mjs +435 -0
  49. package/lib/pm-marketplace.mjs +61 -0
  50. package/lib/pm-skills.mjs +1274 -0
  51. package/lib/resolve-home-dir.mjs +43 -0
  52. package/lib/skill-tiers.mjs +137 -0
  53. package/lib/unified-plugin.mjs +924 -0
  54. package/llms.txt +32 -7
  55. package/package.json +29 -19
  56. package/plugins/beginner.json +17 -6
  57. package/plugins/continuous-improvement/.claude-plugin/marketplace.json +20 -0
  58. package/plugins/continuous-improvement/.claude-plugin/plugin.json +26 -0
  59. package/plugins/continuous-improvement/LICENSE +21 -0
  60. package/plugins/continuous-improvement/README.md +57 -0
  61. package/plugins/continuous-improvement/agents/README.md +120 -0
  62. package/plugins/continuous-improvement/agents/code-reviewer.md +97 -0
  63. package/plugins/continuous-improvement/agents/security-auditor.md +101 -0
  64. package/plugins/continuous-improvement/agents/test-engineer.md +95 -0
  65. package/plugins/continuous-improvement/bin/backfill.mjs +172 -0
  66. package/plugins/continuous-improvement/bin/mcp-server.mjs +889 -0
  67. package/plugins/continuous-improvement/bin/observe.mjs +148 -0
  68. package/plugins/continuous-improvement/commands/continuous-improvement.md +115 -0
  69. package/plugins/continuous-improvement/commands/dashboard.md +56 -0
  70. package/plugins/continuous-improvement/commands/discipline.md +51 -0
  71. package/plugins/continuous-improvement/commands/harvest.md +76 -0
  72. package/plugins/continuous-improvement/commands/learn-eval.md +117 -0
  73. package/plugins/continuous-improvement/commands/planning-with-files.md +66 -0
  74. package/plugins/continuous-improvement/commands/proceed-with-the-recommendation.md +62 -0
  75. package/plugins/continuous-improvement/commands/ralph.md +103 -0
  76. package/plugins/continuous-improvement/commands/release-train.md +81 -0
  77. package/plugins/continuous-improvement/commands/seven-laws.md +16 -0
  78. package/plugins/continuous-improvement/commands/superpowers.md +180 -0
  79. package/plugins/continuous-improvement/commands/swarm.md +101 -0
  80. package/plugins/continuous-improvement/commands/workspace-surface-audit.md +77 -0
  81. package/plugins/continuous-improvement/hooks/gateguard.mjs +172 -0
  82. package/plugins/continuous-improvement/hooks/hooks.json +65 -0
  83. package/plugins/continuous-improvement/hooks/observe.sh +172 -0
  84. package/plugins/continuous-improvement/hooks/session.sh +106 -0
  85. package/plugins/continuous-improvement/hooks/three-section-close.mjs +181 -0
  86. package/plugins/continuous-improvement/instinct-packs/go.json +58 -0
  87. package/plugins/continuous-improvement/instinct-packs/meta.json +16 -0
  88. package/plugins/continuous-improvement/instinct-packs/python.json +58 -0
  89. package/plugins/continuous-improvement/instinct-packs/react.json +58 -0
  90. package/plugins/continuous-improvement/lib/observe-event.mjs +128 -0
  91. package/plugins/continuous-improvement/lib/plugin-metadata.mjs +435 -0
  92. package/plugins/continuous-improvement/lib/resolve-home-dir.mjs +43 -0
  93. package/plugins/continuous-improvement/skills/README.md +37 -0
  94. package/plugins/continuous-improvement/skills/continuous-improvement/SKILL.md +249 -0
  95. package/plugins/continuous-improvement/skills/deploy-receipt/SKILL.md +178 -0
  96. package/plugins/continuous-improvement/skills/gateguard/SKILL.md +163 -0
  97. package/plugins/continuous-improvement/skills/para-memory-files/SKILL.md +108 -0
  98. package/plugins/continuous-improvement/skills/proceed-with-the-recommendation/SKILL.md +447 -0
  99. package/plugins/continuous-improvement/skills/ralph/SKILL.md +221 -0
  100. package/plugins/continuous-improvement/skills/recovery-classification/SKILL.md +73 -0
  101. package/plugins/continuous-improvement/skills/safety-guard/SKILL.md +76 -0
  102. package/plugins/continuous-improvement/skills/state-reconciliation/SKILL.md +63 -0
  103. package/plugins/continuous-improvement/skills/strategic-compact/SKILL.md +104 -0
  104. package/plugins/continuous-improvement/skills/superpowers/SKILL.md +219 -0
  105. package/plugins/continuous-improvement/skills/tdd-workflow/SKILL.md +411 -0
  106. package/plugins/continuous-improvement/skills/token-budget-advisor/SKILL.md +136 -0
  107. package/plugins/continuous-improvement/skills/verification-loop/SKILL.md +224 -0
  108. package/plugins/continuous-improvement/skills/wild-risa-balance/SKILL.md +191 -0
  109. package/plugins/continuous-improvement/skills/workspace-surface-audit/SKILL.md +147 -0
  110. package/plugins/continuous-improvement/skills/worktree-safety/SKILL.md +66 -0
  111. package/plugins/continuous-improvement/templates/planning-with-files/findings.md +8 -0
  112. package/plugins/continuous-improvement/templates/planning-with-files/progress.md +7 -0
  113. package/plugins/continuous-improvement/templates/planning-with-files/task_plan.md +23 -0
  114. package/plugins/expert.json +26 -5
  115. package/skills/README.md +79 -0
  116. package/skills/deploy-receipt.md +178 -0
  117. package/skills/gateguard.md +163 -0
  118. package/skills/para-memory-files.md +108 -0
  119. package/skills/proceed-with-the-recommendation.md +447 -0
  120. package/skills/ralph.md +221 -0
  121. package/skills/recovery-classification.md +73 -0
  122. package/skills/safety-guard.md +76 -0
  123. package/skills/state-reconciliation.md +63 -0
  124. package/skills/strategic-compact.md +104 -0
  125. package/skills/superpowers.md +219 -0
  126. package/skills/tdd-workflow.md +411 -0
  127. package/skills/token-budget-advisor.md +136 -0
  128. package/skills/verification-loop.md +224 -0
  129. package/skills/wild-risa-balance.md +191 -0
  130. package/skills/workspace-surface-audit.md +147 -0
  131. package/skills/worktree-safety.md +66 -0
  132. package/templates/insights-claude-md.md +91 -0
  133. package/templates/planning-with-files/findings.md +8 -0
  134. package/templates/planning-with-files/progress.md +7 -0
  135. package/templates/planning-with-files/task_plan.md +23 -0
  136. package/templates/verify-ladder.example.json +60 -0
package/SKILL.md CHANGED
@@ -1,6 +1,7 @@
1
1
  ---
2
2
  name: continuous-improvement
3
- description: "Install structured self-improvement loops with instinct-based learning into Claude Code — research, plan, execute, verify, reflect, learn, iterate. Auto-levels from silent observation to active suggestions to auto-applied behaviors."
3
+ tier: core
4
+ description: "Install structured self-improvement loops with instinct-based learning into Claude Code — research, plan, execute, verify, reflect, learn, iterate. On-demand or weekly analysis to save tokens. Supports multi-agent parallel analysis."
4
5
  ---
5
6
 
6
7
  # continuous-improvement
@@ -28,9 +29,9 @@ Before executing, state:
28
29
  ## Law 3: One Thing at a Time
29
30
 
30
31
  - Complete and verify one task before starting the next
31
- - Never spawn parallel work for tasks you can do directly
32
32
  - Never report completion until you've checked actual output
33
33
  - If you want to "also quickly add" something — stop. Finish first.
34
+ - **Multi-agent OK:** Delegate independent, parallelizable work to sub-agents (e.g., security review + code review + tests in parallel). Each agent follows the 7 Laws independently. Only parallelize when tasks have no shared state.
34
35
 
35
36
  ## Law 4: Verify Before Reporting
36
37
 
@@ -50,10 +51,35 @@ After non-trivial tasks:
50
51
  - What failed:
51
52
  - What I'd do differently:
52
53
  - Rule to add:
54
+ - Iteration — Next best recommendations (ranked, top 3):
55
+ 1. <primary — strongest next move>
56
+ 2. <alternative — different angle, if user wants to pivot>
57
+ 3. <alternative — smaller/larger scope, if user wants to adjust>
53
58
  ```
54
59
 
55
60
  The "Rule to add" field feeds Law 7 — it becomes an instinct with 0.6 starting confidence.
56
61
 
62
+ The "Iteration — Next best recommendations" field feeds Law 6. List the **top 3 ranked** core-development moves based on the current code state — what to build, fix, refactor, or investigate next so the feature/system advances. Item #1 is the strongest recommendation; #2 and #3 are alternative directions the user can pick from. NOT git plumbing (commit, push, PR), NOT pure CI ceremony (run tests, type-check), NOT deploy steps. Those belong in the end-of-run summary, not here.
63
+
64
+ Format per item: `<verb> <object at path:line> (<why, one clause grounded in current context>)`.
65
+
66
+ Good examples (development progression):
67
+ - `Implement settleWeekAndPostPrizes writer for quiz source in src/scheduled.ts (real_contest writer exists; quiz path is recognized but inert)`
68
+ - `Refactor contestModeGuard at src/routes/trading-contest.ts to share the 4-mode switch with /admin/mode (logic duplicated, drift risk)`
69
+ - `Investigate why Saturday cron occasionally skips Week activation in src/scheduled.ts:625 (one missed run on 2026-04-19; root cause unknown)`
70
+ - `Add server-side enforcement for the $100 new-deposit rule in real-contest entry handler (currently advisory; admin reviews post-contest)`
71
+
72
+ Anti-examples (rejected — these are workflow, not development):
73
+ - `Commit changes` / `Open PR` / `Push to origin` → belongs in summary, not here
74
+ - `Run vitest` / `Run tsc` → that is verification (Law 4), already done before reporting
75
+ - `Deploy to prod` → operational, needs-approval, never an autonomous next step
76
+
77
+ Rules:
78
+ - Always exactly 3 items, ranked. Not 2, not 5.
79
+ - All 3 must be distinct directions — do not pad with rephrases of #1.
80
+ - If fewer than 3 real moves exist, fill remaining slots with `None — goal met from this angle.` rather than inventing busywork.
81
+ - If the goal is fully met across all angles, write `1. None — goal met, stop.` and omit #2 and #3.
82
+
57
83
  ## Law 6: Iterate Means One Thing
58
84
 
59
85
  One change → verify → next change.
@@ -74,6 +100,19 @@ If the user corrects you, the instinct weakens. If they don't, it strengthens.
74
100
 
75
101
  Nothing learned is permanent. Everything decays without reinforcement.
76
102
 
103
+ ### Friction Harvest Pipeline (`/harvest`)
104
+
105
+ Beyond the reflection-driven path above, the **friction harvest classifier** turns observation logs (`~/.claude/instincts/<project-hash>/observations.jsonl`) into typed instincts automatically. Run via `/harvest` or `node bin/harvest-friction.mjs`. Four typed friction patterns with confidence scoring:
106
+
107
+ - **`env_issue`** — jq missing, command not found, not recognized as cmdlet
108
+ - **`permission_block`** — sandbox / harness blocked, Permission denied
109
+ - **`wrong_approach`** — file changed since last read (parallel-actor stale)
110
+ - **`buggy_code`** — file not read first, old_string ambiguous, file too large
111
+
112
+ Idempotent: each instinct's `dedup_key = sha1(type + tool + summary[:120])`; re-running on the same observations does not duplicate previously-written instincts. Confidence weights frequency × recency-decay so old failures fade and recurring ones strengthen, in line with the Law 7 contract above.
113
+
114
+ The harvest is opt-in: it runs only when explicitly invoked. Cron / hook triggers are deliberately not wired so the operator stays in control of when the classifier reads observation history.
115
+
77
116
  ## The Loop
78
117
 
79
118
  ```
@@ -86,11 +125,23 @@ If you're skipping a step, that's the step you need most.
86
125
 
87
126
  ## Instinct System (Mulahazah)
88
127
 
89
- At the start of every session, check `~/.claude/instincts/` for this project's instincts.
128
+ ### Execution Mode: On-Demand (Default)
129
+
130
+ The instinct system does **NOT** run automatically at session start. This saves tokens.
131
+
132
+ | Mode | When it runs | Token cost |
133
+ |------|-------------|------------|
134
+ | **On-demand** (default) | Only when user runs `/continuous-improvement` or `/dashboard` | Zero overhead per session |
135
+ | **Weekly** | User schedules via `/loop 7d /continuous-improvement analyze` or cron | One analysis per week |
136
+ | **Always-on** (opt-in) | Set `always_on: true` in project instinct config | Runs at every session start |
137
+
138
+ **To enable always-on:** Create `~/.claude/instincts/<hash>/config.yaml` with `always_on: true`. Otherwise, instincts are only loaded when explicitly requested.
139
+
140
+ **Hooks still capture silently** — observations accumulate in `observations.jsonl` with near-zero cost. The expensive part (reading, analyzing, creating instincts) only happens when you ask for it.
90
141
 
91
142
  ### Auto-Level Detection
92
143
 
93
- Determine current level automatically:
144
+ When analysis is triggered (on-demand, weekly, or always-on), determine level:
94
145
 
95
146
  1. **Find project hash:** Run `git rev-parse --show-toplevel 2>/dev/null`, then SHA-256 first 12 chars of the path
96
147
  2. **Check observations:** Count lines in `~/.claude/instincts/<hash>/observations.jsonl`
@@ -105,9 +156,9 @@ Determine current level automatically:
105
156
 
106
157
  Multiple levels can be active simultaneously — you might auto-apply some instincts while suggesting others.
107
158
 
108
- ### Inline Analysis
159
+ ### Analysis (On-Demand)
109
160
 
110
- When 20+ unprocessed observations exist, analyze them as part of session startup:
161
+ When triggered by `/continuous-improvement analyze`, weekly schedule, or always-on mode:
111
162
 
112
163
  1. Read `observations.jsonl` (last 500 lines)
113
164
  2. Read existing instincts (project + global `*.yaml` files)
@@ -119,6 +170,16 @@ When 20+ unprocessed observations exist, analyze them as part of session startup
119
170
  4. Create/update instinct YAML files in the project directory
120
171
  5. Be conservative: only create instincts for 3+ observations of the same pattern
121
172
 
173
+ ### Multi-Agent Analysis
174
+
175
+ For large observation backlogs (500+ lines), parallelize analysis across agents:
176
+
177
+ - **Agent 1:** Analyze user corrections and error→fix sequences
178
+ - **Agent 2:** Analyze repeated workflows and tool preferences
179
+ - **Agent 3:** Cross-reference with existing instincts for updates/promotions
180
+
181
+ Each agent writes to separate temp files; the orchestrator merges results and deduplicates.
182
+
122
183
  ### Instinct Format
123
184
 
124
185
  Each instinct is a YAML file in `~/.claude/instincts/<hash>/` or `~/.claude/instincts/global/`:
@@ -160,12 +221,29 @@ Cap: 0.9 max. Scope: default to project; promote to global when seen in 2+ proje
160
221
 
161
222
  ## /continuous-improvement Command
162
223
 
163
- Run `/continuous-improvement` after significant work:
224
+ Run `/continuous-improvement` when you want to reflect and learn — not every session.
164
225
 
165
226
  1. **Reflect** — Generate Law 5 reflection
166
227
  2. **Analyze** — Process pending observations into instincts
167
228
  3. **Status** — Show all instincts with confidence and current level
168
229
 
169
230
  Subcommands:
170
- - `/continuous-improvement status` — Instinct overview only
171
- - `/continuous-improvement analyze` — Force analysis of pending observations
231
+ - `/continuous-improvement status` — Instinct overview only (lightweight, reads YAML only)
232
+ - `/continuous-improvement analyze` — Process pending observations into instincts
233
+ - `/continuous-improvement weekly` — Set up weekly analysis schedule
234
+ - `/continuous-improvement always-on` — Enable/disable always-on mode for this project
235
+
236
+ ## Planning-With-Files (Opt-In)
237
+
238
+ Use this workflow only when the user explicitly asks for persistent, file-based planning or asks to use Planning-With-Files.
239
+
240
+ - Detect the project root with `git rev-parse --show-toplevel`; if that fails, use the current working directory.
241
+ - Create and maintain three project-root files:
242
+ - `task_plan.md` — phases, status, questions, decisions, errors
243
+ - `findings.md` — research notes, sources, synthesized discoveries
244
+ - `progress.md` — session log, verification notes, checkpoints
245
+ - Default phases in `task_plan.md`: `Research`, `Plan`, `Execute`, `Verify`, `Reflect`
246
+ - Never create these files automatically for normal work. This workflow is opt-in.
247
+ - Never overwrite existing planning files unless the user explicitly asks to reset or replace them.
248
+
249
+ When resuming work, read the three files before making major decisions so context survives long tasks and new sessions.
package/action.yml CHANGED
@@ -1,5 +1,5 @@
1
1
  name: 'AI Agent Discipline Linter'
2
- description: 'Lint AI agent transcripts for compliance with the 7 Laws of AI Agent Discipline'
2
+ description: 'Catch agents that skipped research, planning, or verification — by linting their transcript in CI against the 7 Laws of AI Agent Discipline.'
3
3
  author: 'naimkatiman'
4
4
 
5
5
  branding:
package/bin/analyze.sh CHANGED
@@ -20,7 +20,15 @@ if [[ -z "$PROJECT_ROOT" ]]; then
20
20
  PROJECT_ROOT="global"
21
21
  fi
22
22
 
23
- PROJECT_HASH="$(printf '%s' "$PROJECT_ROOT" | sha256sum | cut -c1-12)"
23
+ # Use shasum on macOS, sha256sum on Linux
24
+ if command -v sha256sum &>/dev/null; then
25
+ PROJECT_HASH="$(printf '%s' "$PROJECT_ROOT" | sha256sum | cut -c1-12)"
26
+ elif command -v shasum &>/dev/null; then
27
+ PROJECT_HASH="$(printf '%s' "$PROJECT_ROOT" | shasum -a 256 | cut -c1-12)"
28
+ else
29
+ # Fallback: use project root basename if no hashing available
30
+ PROJECT_HASH="$(basename "$PROJECT_ROOT" | tr -cd 'a-zA-Z0-9' | cut -c1-12)"
31
+ fi
24
32
  PROJECT_NAME="$(basename "${PROJECT_ROOT%.git}")"
25
33
  PROJECT_DIR="${INSTINCTS_DIR}/${PROJECT_HASH}"
26
34
  OBS_FILE="${PROJECT_DIR}/observations.jsonl"
@@ -0,0 +1,172 @@
1
+ #!/usr/bin/env node
2
+ // backfill.mts — Tag legacy thin-schema observation rows for skip-clean
3
+ // consumption by the analysis pass.
4
+ //
5
+ // Walks ~/.claude/instincts/<hash>/observations.jsonl files and writes a
6
+ // `schema: "thin" | "rich"` field on every row that doesn't already have one.
7
+ // Per the plan doc: this is a flag-pass, not a content-recovery — the original
8
+ // thin rows lack the input we'd need to fabricate richer fields. Tagging lets
9
+ // the analysis pass cleanly skip thin rows and surface a "X% of observations
10
+ // were thin" stat to operators so they understand why their auto-instinct
11
+ // pipeline historically produced nothing.
12
+ //
13
+ // Output:
14
+ // - <home>/.claude/instincts/<hash>/observations.jsonl (rewritten in place
15
+ // with schema field appended; original preserved as .bak)
16
+ // - <home>/.claude/instincts/backfill-summary.json (per-project digest)
17
+ //
18
+ // Flags:
19
+ // --dry-run Read everything, write nothing; print counts to stdout.
20
+ //
21
+ // Idempotent: re-running on already-tagged rows is a no-op.
22
+ // Exit code: always 0. Failures land on stderr only.
23
+ import { existsSync, mkdirSync, readFileSync, readdirSync, renameSync, statSync, writeFileSync } from "node:fs";
24
+ import { homedir } from "node:os";
25
+ import { basename, join } from "node:path";
26
+ const args = process.argv.slice(2);
27
+ const DRY_RUN = args.includes("--dry-run");
28
+ const HELP = args.includes("--help") || args.includes("-h");
29
+ if (HELP) {
30
+ console.log(`
31
+ backfill — Tag legacy thin-schema observation rows.
32
+
33
+ Usage:
34
+ npx continuous-improvement backfill Tag rows in place
35
+ npx continuous-improvement backfill --dry-run Report counts, write nothing
36
+
37
+ What it does:
38
+ Walks ~/.claude/instincts/<hash>/observations.jsonl files and adds a
39
+ schema field ("thin" or "rich") to every row that lacks one. Thin rows
40
+ came from the legacy bash fallback (jq missing) and have no input_summary;
41
+ rich rows came from the Node observer and have full tool_input.command /
42
+ Edit.file_path / etc.
43
+
44
+ Output:
45
+ - observations.jsonl rewritten in place (original kept as .bak)
46
+ - backfill-summary.json with per-project counts
47
+
48
+ Idempotent. Always exits 0 — failures go to stderr only.
49
+ `);
50
+ process.exit(0);
51
+ }
52
+ main();
53
+ function main() {
54
+ try {
55
+ runBackfill();
56
+ }
57
+ catch (error) {
58
+ process.stderr.write(`[backfill] ${error instanceof Error ? error.message : String(error)}\n`);
59
+ }
60
+ process.exit(0);
61
+ }
62
+ function runBackfill() {
63
+ const home = process.env.HOME || process.env.USERPROFILE || homedir();
64
+ const instinctsDir = join(home, ".claude", "instincts");
65
+ if (!existsSync(instinctsDir)) {
66
+ if (!DRY_RUN)
67
+ mkdirSync(instinctsDir, { recursive: true });
68
+ return;
69
+ }
70
+ const projectDirs = listProjectDirs(instinctsDir);
71
+ const summary = {
72
+ total: 0,
73
+ thin: 0,
74
+ rich: 0,
75
+ projects: {},
76
+ generated_at: new Date().toISOString(),
77
+ };
78
+ for (const dir of projectDirs) {
79
+ const obsFile = join(dir, "observations.jsonl");
80
+ if (!existsSync(obsFile))
81
+ continue;
82
+ const projectId = basename(dir);
83
+ const stat = { total: 0, thin: 0, rich: 0 };
84
+ const newLines = [];
85
+ const corruptLines = [];
86
+ let mutated = false;
87
+ const raw = readFileSync(obsFile, "utf8");
88
+ for (const line of raw.split("\n")) {
89
+ const trimmed = line.trim();
90
+ if (trimmed.length === 0)
91
+ continue;
92
+ let row;
93
+ try {
94
+ row = JSON.parse(trimmed);
95
+ }
96
+ catch {
97
+ process.stderr.write(`[backfill] skipped corrupt row in ${projectId}: ${trimmed.slice(0, 80)}\n`);
98
+ // Drop from main file but preserve in observations.corrupt.jsonl
99
+ // so the operator never silently loses data.
100
+ corruptLines.push(trimmed);
101
+ mutated = true;
102
+ continue;
103
+ }
104
+ stat.total++;
105
+ summary.total++;
106
+ const previousSchema = row.schema;
107
+ const isRich = typeof row.input_summary === "string" && row.input_summary.length > 0;
108
+ const newSchema = isRich ? "rich" : "thin";
109
+ if (previousSchema !== newSchema) {
110
+ row.schema = newSchema;
111
+ mutated = true;
112
+ }
113
+ if (newSchema === "thin") {
114
+ stat.thin++;
115
+ summary.thin++;
116
+ }
117
+ else {
118
+ stat.rich++;
119
+ summary.rich++;
120
+ }
121
+ newLines.push(JSON.stringify(row));
122
+ }
123
+ summary.projects[projectId] = stat;
124
+ if (mutated && !DRY_RUN) {
125
+ // Atomic-ish write: backup → write → keep .bak for one cycle.
126
+ const backup = obsFile + ".bak";
127
+ renameSync(obsFile, backup);
128
+ writeFileSync(obsFile, newLines.join("\n") + "\n", "utf8");
129
+ if (corruptLines.length > 0) {
130
+ // Quarantine corrupt rows so operator data is never silently lost.
131
+ const corruptFile = join(dir, "observations.corrupt.jsonl");
132
+ const existing = existsSync(corruptFile) ? readFileSync(corruptFile, "utf8") : "";
133
+ writeFileSync(corruptFile, existing + corruptLines.join("\n") + "\n", "utf8");
134
+ }
135
+ }
136
+ }
137
+ if (DRY_RUN) {
138
+ process.stdout.write(formatReport(summary, true));
139
+ return;
140
+ }
141
+ const summaryPath = join(instinctsDir, "backfill-summary.json");
142
+ writeFileSync(summaryPath, JSON.stringify(summary, null, 2) + "\n", "utf8");
143
+ process.stdout.write(formatReport(summary, false));
144
+ }
145
+ function listProjectDirs(instinctsDir) {
146
+ // Real project-hash dirs are sha256[:12] (hex). Test fixtures use 12-char
147
+ // alpha labels (g/h/i are not hex but make tests readable). Both are
148
+ // matched here; the broader pattern is harmless because real instincts/
149
+ // sub-dirs only ever contain hex hashes plus 'global' / loose dotfiles.
150
+ return readdirSync(instinctsDir)
151
+ .filter((name) => /^[0-9a-z]{12}$/.test(name))
152
+ .map((name) => join(instinctsDir, name))
153
+ .filter((dir) => {
154
+ try {
155
+ return statSync(dir).isDirectory();
156
+ }
157
+ catch {
158
+ return false;
159
+ }
160
+ });
161
+ }
162
+ function formatReport(summary, isDryRun) {
163
+ const projects = Object.keys(summary.projects);
164
+ const lines = [
165
+ isDryRun ? "[backfill --dry-run] would tag the following:" : "[backfill] tagged:",
166
+ ` total rows: ${summary.total}`,
167
+ ` thin rows: ${summary.thin}`,
168
+ ` rich rows: ${summary.rich}`,
169
+ ` projects: ${projects.length}`,
170
+ ];
171
+ return lines.join("\n") + "\n";
172
+ }
@@ -0,0 +1,120 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * Doc Runtime-Claims Check
4
+ *
5
+ * Codifies the audit-twice rule (memory: feedback_grep_hook_before_claim).
6
+ *
7
+ * Any user-facing line that claims a runtime hook / PreToolUse gate /
8
+ * physical block must be accompanied by a `hooks/<file>.mjs` reference
9
+ * inside a ±5-line window. Without that anchor, the doc is making a
10
+ * claim a future reader cannot verify against the actual filesystem.
11
+ *
12
+ * Origin: PR #105 shipped a smoke test that promised gateguard would
13
+ * physically block, when the hook did not yet exist. PR #108 built the
14
+ * hook. PR #115 reverted the inverse drift after the hook shipped but
15
+ * docs still said "roadmap." This lint locks the audit-twice rule into
16
+ * automation so the next runtime claim cannot land without its anchor.
17
+ *
18
+ * Scope: QUICKSTART.md, README.md, skills/*.md.
19
+ * Out of scope: bundle mirrors under plugins/, docs/, third-party/.
20
+ *
21
+ * Trigger phrases (case-insensitive substring match on the line):
22
+ * - "PreToolUse hook"
23
+ * - "physically block" (catches "physically blocks", "physical block")
24
+ * - "runtime gate"
25
+ *
26
+ * Deliberately narrow per the spec that authorized this lint. "runtime
27
+ * hook" was considered and dropped: too prone to false positives on
28
+ * disclaimers ("not a runtime hook", "no runtime hook is bundled").
29
+ * Future trigger additions need an explicit owner decision; do not bolt
30
+ * on phrases just because they sound related.
31
+ *
32
+ * Anchor: any reference matching `hooks/<name>.mjs` (case-insensitive)
33
+ * inside the ±5-line window (the trigger line itself plus 5 lines above
34
+ * and 5 lines below).
35
+ *
36
+ * Usage:
37
+ * node bin/check-doc-runtime-claims.mjs # Check the current repo
38
+ * node bin/check-doc-runtime-claims.mjs <repo-root> # Check a specific repo root
39
+ *
40
+ * Exit codes:
41
+ * 0 — every runtime-claim line has an in-window hooks/ anchor
42
+ * 1 — at least one claim line is unanchored
43
+ */
44
+ import { readFileSync, readdirSync } from "node:fs";
45
+ import { join } from "node:path";
46
+ import { argv, cwd, exit } from "node:process";
47
+ const SCAN_FILES = ["QUICKSTART.md", "README.md"];
48
+ const SCAN_SKILL_DIR = "skills";
49
+ const ANCHOR_WINDOW = 5;
50
+ const TRIGGER_PHRASES = [
51
+ "pretooluse hook",
52
+ "physically block",
53
+ "runtime gate",
54
+ ];
55
+ const ANCHOR_PATTERN = /hooks\/[a-z0-9-]+\.mjs/i;
56
+ function listSkillFiles(repoRoot) {
57
+ const skillsDir = join(repoRoot, SCAN_SKILL_DIR);
58
+ let entries;
59
+ try {
60
+ entries = readdirSync(skillsDir);
61
+ }
62
+ catch {
63
+ return [];
64
+ }
65
+ return entries
66
+ .filter((f) => /^[a-z][a-z0-9-]*\.md$/.test(f))
67
+ .map((f) => join(SCAN_SKILL_DIR, f))
68
+ .sort();
69
+ }
70
+ function scanFile(repoRoot, relPath) {
71
+ const fullPath = join(repoRoot, relPath);
72
+ let content;
73
+ try {
74
+ content = readFileSync(fullPath, "utf8");
75
+ }
76
+ catch {
77
+ return [];
78
+ }
79
+ const lines = content.split(/\r?\n/);
80
+ const violations = [];
81
+ for (let i = 0; i < lines.length; i += 1) {
82
+ const lower = lines[i].toLowerCase();
83
+ const matchedPhrase = TRIGGER_PHRASES.find((p) => lower.includes(p));
84
+ if (!matchedPhrase)
85
+ continue;
86
+ const start = Math.max(0, i - ANCHOR_WINDOW);
87
+ const end = Math.min(lines.length - 1, i + ANCHOR_WINDOW);
88
+ const window = lines.slice(start, end + 1).join("\n");
89
+ if (ANCHOR_PATTERN.test(window))
90
+ continue;
91
+ violations.push({
92
+ file: relPath,
93
+ line: i + 1,
94
+ text: lines[i].trim(),
95
+ });
96
+ }
97
+ return violations;
98
+ }
99
+ function main() {
100
+ const repoRoot = argv[2] ?? cwd();
101
+ const files = [...SCAN_FILES, ...listSkillFiles(repoRoot)];
102
+ const allViolations = [];
103
+ for (const file of files) {
104
+ allViolations.push(...scanFile(repoRoot, file));
105
+ }
106
+ if (allViolations.length === 0) {
107
+ console.log(`OK doc-runtime-claims: every runtime-claim line in ${files.length} scanned file(s) has a hooks/<name>.mjs anchor within ±${ANCHOR_WINDOW} lines.`);
108
+ exit(0);
109
+ }
110
+ console.error(`FAIL doc-runtime-claims: ${allViolations.length} unanchored runtime-claim line(s) found.`);
111
+ console.error(`Each line below contains a runtime-claim phrase but has no hooks/<name>.mjs reference within ±${ANCHOR_WINDOW} lines.`);
112
+ console.error("Add the hook file path inline so a reader can verify the claim against the filesystem.");
113
+ console.error("");
114
+ for (const v of allViolations) {
115
+ console.error(` ${v.file}:${v.line}`);
116
+ console.error(` ${v.text}`);
117
+ }
118
+ exit(1);
119
+ }
120
+ main();