continuous-improvement 3.12.3 → 3.16.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.
Files changed (137) hide show
  1. package/.claude-plugin/marketplace.json +2 -2
  2. package/CHANGELOG.md +45 -1
  3. package/LICENSE +21 -21
  4. package/QUICKSTART.md +1 -1
  5. package/README.md +29 -8
  6. package/bin/analyze.sh +161 -161
  7. package/bin/backfill.mjs +19 -19
  8. package/bin/check-doc-runtime-claims.mjs +0 -0
  9. package/bin/check-docs-substrings.mjs +0 -0
  10. package/bin/check-everything-mirror.mjs +0 -0
  11. package/bin/check-routing-targets.mjs +0 -0
  12. package/bin/check-scripts-citation-drift.mjs +0 -0
  13. package/bin/check-skill-count-prose.mjs +168 -0
  14. package/bin/check-skill-count.mjs +32 -1
  15. package/bin/check-skill-law-tag.mjs +0 -0
  16. package/bin/check-skill-mirror.mjs +0 -0
  17. package/bin/check-skill-tiers.mjs +0 -0
  18. package/bin/check-test-imports-only.mjs +1 -1
  19. package/bin/check-third-party-shape.mjs +0 -0
  20. package/bin/check-tool-count.mjs +0 -0
  21. package/bin/companion-preference-status.mjs +0 -0
  22. package/bin/gateguard-clear.mjs +0 -0
  23. package/bin/generate-plugin-manifests.mjs +2 -1
  24. package/bin/harvest-friction.mjs +0 -0
  25. package/bin/hook-stats.mjs +21 -21
  26. package/bin/install.mjs +120 -2
  27. package/bin/lint-transcript.mjs +15 -3
  28. package/bin/mcp-server.mjs +66 -8
  29. package/bin/observe.mjs +0 -0
  30. package/bin/plan-pack.mjs +77 -0
  31. package/bin/pre-commit-block-strays.sh +0 -0
  32. package/bin/refresh-third-party.mjs +0 -0
  33. package/bin/unified-cli.mjs +55 -410
  34. package/commands/continuous-improvement.md +115 -115
  35. package/commands/dashboard.md +56 -56
  36. package/commands/model-forward.md +13 -0
  37. package/commands/production-readiness-review.md +53 -0
  38. package/commands/roast.md +34 -0
  39. package/commands/seven-laws.md +16 -16
  40. package/commands/ship.md +57 -0
  41. package/commands/workspace-surface-audit.md +77 -77
  42. package/hooks/companion-preference.mjs +31 -19
  43. package/hooks/gateguard.mjs +38 -16
  44. package/hooks/goal-drift-stop.mjs +0 -0
  45. package/hooks/hook-pack.mjs +110 -0
  46. package/hooks/observe.sh +0 -0
  47. package/hooks/recall-briefing.mjs +0 -0
  48. package/hooks/route-prompt.mjs +0 -0
  49. package/hooks/session.sh +106 -106
  50. package/hooks/three-section-close.mjs +0 -0
  51. package/hooks/workflow-distill.mjs +145 -0
  52. package/instinct-packs/go.json +58 -58
  53. package/instinct-packs/meta.json +16 -16
  54. package/instinct-packs/python.json +58 -58
  55. package/instinct-packs/react.json +58 -58
  56. package/lib/cli-anything.mjs +0 -0
  57. package/lib/gateguard-state.mjs +62 -13
  58. package/lib/goal-drift-gate.mjs +0 -0
  59. package/lib/goal-state.mjs +8 -4
  60. package/lib/hook-pack-gate.mjs +65 -0
  61. package/lib/install-targets.mjs +121 -0
  62. package/lib/observe-event.mjs +0 -0
  63. package/lib/plan-review-packet.mjs +96 -0
  64. package/lib/plugin-metadata.mjs +30 -4
  65. package/lib/pm-marketplace.mjs +0 -0
  66. package/lib/recall-briefing.mjs +0 -0
  67. package/lib/recall-index.mjs +0 -0
  68. package/lib/resolve-home-dir.mjs +0 -0
  69. package/lib/skill-distill.mjs +141 -0
  70. package/lib/skill-tiers.mjs +0 -0
  71. package/lib/version-check.mjs +115 -0
  72. package/llms.txt +2 -2
  73. package/package.json +6 -4
  74. package/plugins/beginner.json +2 -2
  75. package/plugins/continuous-improvement/.claude-plugin/marketplace.json +2 -2
  76. package/plugins/continuous-improvement/.claude-plugin/plugin.json +2 -2
  77. package/plugins/continuous-improvement/LICENSE +21 -21
  78. package/plugins/continuous-improvement/README.md +1 -1
  79. package/plugins/continuous-improvement/bin/backfill.mjs +19 -19
  80. package/plugins/continuous-improvement/bin/gateguard-clear.mjs +0 -0
  81. package/plugins/continuous-improvement/bin/mcp-server.mjs +66 -8
  82. package/plugins/continuous-improvement/bin/observe.mjs +0 -0
  83. package/plugins/continuous-improvement/commands/continuous-improvement.md +115 -115
  84. package/plugins/continuous-improvement/commands/dashboard.md +56 -56
  85. package/plugins/continuous-improvement/commands/model-forward.md +13 -0
  86. package/plugins/continuous-improvement/commands/production-readiness-review.md +53 -0
  87. package/plugins/continuous-improvement/commands/roast.md +34 -0
  88. package/plugins/continuous-improvement/commands/seven-laws.md +16 -16
  89. package/plugins/continuous-improvement/commands/ship.md +57 -0
  90. package/plugins/continuous-improvement/commands/workspace-surface-audit.md +77 -77
  91. package/plugins/continuous-improvement/hooks/companion-preference.mjs +31 -19
  92. package/plugins/continuous-improvement/hooks/gateguard.mjs +38 -16
  93. package/plugins/continuous-improvement/hooks/goal-drift-stop.mjs +0 -0
  94. package/plugins/continuous-improvement/hooks/hook-pack.mjs +110 -0
  95. package/plugins/continuous-improvement/hooks/hooks.json +16 -1
  96. package/plugins/continuous-improvement/hooks/observe.sh +0 -0
  97. package/plugins/continuous-improvement/hooks/recall-briefing.mjs +0 -0
  98. package/plugins/continuous-improvement/hooks/route-prompt.mjs +0 -0
  99. package/plugins/continuous-improvement/hooks/session.sh +106 -106
  100. package/plugins/continuous-improvement/hooks/three-section-close.mjs +0 -0
  101. package/plugins/continuous-improvement/hooks/workflow-distill.mjs +145 -0
  102. package/plugins/continuous-improvement/instinct-packs/go.json +58 -58
  103. package/plugins/continuous-improvement/instinct-packs/meta.json +16 -16
  104. package/plugins/continuous-improvement/instinct-packs/python.json +58 -58
  105. package/plugins/continuous-improvement/instinct-packs/react.json +58 -58
  106. package/plugins/continuous-improvement/lib/gateguard-state.mjs +62 -13
  107. package/plugins/continuous-improvement/lib/goal-drift-gate.mjs +0 -0
  108. package/plugins/continuous-improvement/lib/goal-state.mjs +8 -4
  109. package/plugins/continuous-improvement/lib/hook-pack-gate.mjs +65 -0
  110. package/plugins/continuous-improvement/lib/observe-event.mjs +0 -0
  111. package/plugins/continuous-improvement/lib/plugin-metadata.mjs +30 -4
  112. package/plugins/continuous-improvement/lib/recall-briefing.mjs +0 -0
  113. package/plugins/continuous-improvement/lib/recall-index.mjs +0 -0
  114. package/plugins/continuous-improvement/lib/resolve-home-dir.mjs +0 -0
  115. package/plugins/continuous-improvement/lib/skill-distill.mjs +141 -0
  116. package/plugins/continuous-improvement/skills/README.md +2 -1
  117. package/plugins/continuous-improvement/skills/gateguard/SKILL.md +4 -4
  118. package/plugins/continuous-improvement/skills/goal-monitor/SKILL.md +1 -1
  119. package/plugins/continuous-improvement/skills/handoff/SKILL.md +0 -1
  120. package/plugins/continuous-improvement/skills/model-forward/SKILL.md +44 -0
  121. package/plugins/continuous-improvement/skills/roast/SKILL.md +108 -0
  122. package/plugins/continuous-improvement/skills/strategic-compact/SKILL.md +12 -32
  123. package/plugins/continuous-improvement/skills/tdd-workflow/SKILL.md +411 -411
  124. package/plugins/expert.json +5 -1
  125. package/skills/README.md +5 -3
  126. package/skills/gateguard.md +4 -4
  127. package/skills/goal-monitor.md +1 -1
  128. package/skills/handoff.md +0 -1
  129. package/skills/model-forward.md +44 -0
  130. package/skills/roast.md +108 -0
  131. package/skills/strategic-compact.md +12 -32
  132. package/skills/tdd-workflow.md +411 -411
  133. package/lib/compound-engineering.mjs +0 -831
  134. package/lib/pm-skills.mjs +0 -1274
  135. package/lib/unified-plugin.mjs +0 -924
  136. package/plugins/continuous-improvement/skills/para-memory-files/SKILL.md +0 -108
  137. package/skills/para-memory-files.md +0 -108
@@ -0,0 +1,168 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * Skill Count Prose Check
4
+ *
5
+ * Sibling to `verify:skill-count`. That lint gates the literal phrase
6
+ * "N bundled skills" in the 5 generator-owned manifest surfaces. This lint
7
+ * gates the *human-facing* prose — the phrasings the marketing/docs copy
8
+ * actually uses, which no other invariant checks and which therefore rot
9
+ * silently every time a skill is added without a full prose cascade.
10
+ *
11
+ * Origin: PR #255 (`/roast`, the 26th skill) bumped the gated "N bundled
12
+ * skills" manifests but left README.md / docs/skills.md / docs/skill-use-cases.md
13
+ * / CONTRIBUTING.md / docs/using-this-plugin.md at "25 skills / 14 tier-2".
14
+ * verify:all stayed green because nothing checked those surfaces.
15
+ * `reports/daily-improvement.md` logs three prior rounds of the same drift.
16
+ *
17
+ * Single source of truth: the skill set on disk. The total and the
18
+ * core/featured/tier-1/tier-2/companion breakdown are *derived* from
19
+ * `skills/*.md` + the root `SKILL.md` frontmatter via the same primitives the
20
+ * tier lint uses (`discoverSkillSources`, `parseSkillFrontmatter`,
21
+ * `normalizeTier`). Nothing is hardcoded — add or retire a skill and this lint
22
+ * recomputes the expected numbers, then names every doc that disagrees.
23
+ *
24
+ * Checked surfaces (file → which derived claims it must contain):
25
+ * - README.md breakdown, total-skills, next-ordinal
26
+ * - docs/skills.md breakdown, total-skills, total-catalog, next-ordinal
27
+ * - docs/skill-use-cases.md breakdown, total-skills
28
+ * - CONTRIBUTING.md total-skills
29
+ * - docs/using-this-plugin.md total-catalog
30
+ *
31
+ * Usage:
32
+ * node bin/check-skill-count-prose.mjs # Check the current repo
33
+ * node bin/check-skill-count-prose.mjs <repo-root> # Check a specific repo root
34
+ *
35
+ * Exit codes:
36
+ * 0 — every checked surface states the bundle-derived count
37
+ * 1 — at least one surface states a stale count, or a surface file is missing
38
+ */
39
+ import { readFileSync } from "node:fs";
40
+ import { join } from "node:path";
41
+ import { argv, cwd, exit } from "node:process";
42
+ import { normalizeTier, parseSkillFrontmatter } from "../lib/skill-tiers.mjs";
43
+ import { discoverSkillSources } from "./check-skill-tiers.mjs";
44
+ /** Derive the total + per-tier breakdown from `skills/` + root SKILL.md frontmatter. */
45
+ export function deriveCounts(repoRoot) {
46
+ const sources = discoverSkillSources(repoRoot);
47
+ const tally = { core: 0, featured: 0, tier1: 0, tier2: 0, companion: 0, unknown: 0 };
48
+ for (const src of sources) {
49
+ const front = parseSkillFrontmatter(readFileSync(src.path, "utf8"));
50
+ switch (normalizeTier(front.tier)) {
51
+ case "core":
52
+ tally.core++;
53
+ break;
54
+ case "featured":
55
+ tally.featured++;
56
+ break;
57
+ case "1":
58
+ tally.tier1++;
59
+ break;
60
+ case "2":
61
+ tally.tier2++;
62
+ break;
63
+ case "companion":
64
+ tally.companion++;
65
+ break;
66
+ default:
67
+ tally.unknown++;
68
+ break;
69
+ }
70
+ }
71
+ const total = sources.length;
72
+ return { ...tally, total, next: total + 1 };
73
+ }
74
+ /** English ordinal: 1→1st, 2→2nd, 3→3rd, 11→11th, 21→21st, 27→27th. */
75
+ export function ordinal(n) {
76
+ const mod100 = n % 100;
77
+ if (mod100 >= 11 && mod100 <= 13)
78
+ return `${n}th`;
79
+ switch (n % 10) {
80
+ case 1:
81
+ return `${n}st`;
82
+ case 2:
83
+ return `${n}nd`;
84
+ case 3:
85
+ return `${n}rd`;
86
+ default:
87
+ return `${n}th`;
88
+ }
89
+ }
90
+ /** The exact substring a surface must contain for the given check kind. */
91
+ export function expectedFor(check, c) {
92
+ switch (check) {
93
+ case "breakdown":
94
+ return `${c.core} core + ${c.featured} featured + ${c.tier1} tier-1 + ${c.tier2} tier-2 + ${c.companion} always-bundled`;
95
+ case "total-skills":
96
+ return `${c.total} skills`;
97
+ case "total-catalog":
98
+ return `${c.total}-skill catalog`;
99
+ case "next-ordinal":
100
+ return `${ordinal(c.next)} skill`;
101
+ }
102
+ }
103
+ export const PROSE_SURFACES = [
104
+ { file: "README.md", checks: ["breakdown", "total-skills", "next-ordinal"] },
105
+ {
106
+ file: "docs/skills.md",
107
+ checks: ["breakdown", "total-skills", "total-catalog", "next-ordinal"],
108
+ },
109
+ { file: "docs/skill-use-cases.md", checks: ["breakdown", "total-skills"] },
110
+ { file: "CONTRIBUTING.md", checks: ["total-skills"] },
111
+ { file: "docs/using-this-plugin.md", checks: ["total-catalog"] },
112
+ ];
113
+ /**
114
+ * Assert every surface contains every derived claim it owns. `counts` defaults
115
+ * to the value derived from `repoRoot`; pass it explicitly to test the
116
+ * surface-matching logic in isolation.
117
+ */
118
+ export function checkProseSurfaces(repoRoot, counts, surfaces = PROSE_SURFACES) {
119
+ const c = counts ?? deriveCounts(repoRoot);
120
+ const failures = [];
121
+ for (const surface of surfaces) {
122
+ let content;
123
+ try {
124
+ content = readFileSync(join(repoRoot, surface.file), "utf8");
125
+ }
126
+ catch {
127
+ content = null;
128
+ }
129
+ for (const check of surface.checks) {
130
+ const expected = expectedFor(check, c);
131
+ if (content === null) {
132
+ failures.push({ file: surface.file, check, expected, reason: "target-missing" });
133
+ continue;
134
+ }
135
+ if (!content.includes(expected)) {
136
+ failures.push({ file: surface.file, check, expected, reason: "stale" });
137
+ }
138
+ }
139
+ }
140
+ return failures;
141
+ }
142
+ function main() {
143
+ const repoRoot = argv[2] ?? cwd();
144
+ const counts = deriveCounts(repoRoot);
145
+ const failures = checkProseSurfaces(repoRoot, counts);
146
+ if (failures.length === 0) {
147
+ console.log(`OK skill-count-prose: all ${PROSE_SURFACES.length} doc surface(s) state the derived count (total=${counts.total}: ${expectedFor("breakdown", counts)}).`);
148
+ exit(0);
149
+ }
150
+ console.error(`FAIL skill-count-prose: ${failures.length} doc prose claim(s) do not match the bundle-derived count of ${counts.total}.`);
151
+ console.error(`Source of truth: skills/ frontmatter → ${expectedFor("breakdown", counts)} = ${counts.total} skills; next skill is the ${ordinal(counts.next)}.`);
152
+ console.error("");
153
+ for (const f of failures) {
154
+ if (f.reason === "target-missing") {
155
+ console.error(` ${f.file} — file not found (expected to contain "${f.expected}")`);
156
+ }
157
+ else {
158
+ console.error(` ${f.file} — missing "${f.expected}" [${f.check}]`);
159
+ }
160
+ }
161
+ console.error("");
162
+ console.error("Fix: bump the prose in the named file(s) to the derived count. When a skill is genuinely added or retired, this lint enumerates every human-facing surface that needs the same change.");
163
+ exit(1);
164
+ }
165
+ const invokedDirectly = argv[1] !== undefined && import.meta.url.endsWith(argv[1].replace(/\\/g, "/"));
166
+ if (invokedDirectly || argv[1]?.endsWith("check-skill-count-prose.mjs")) {
167
+ main();
168
+ }
@@ -24,6 +24,11 @@
24
24
  * - plugins/continuous-improvement/.claude-plugin/plugin.json
25
25
  * - package.json
26
26
  * - llms.txt
27
+ * - .cloudplugin/marketplace.json (hand-maintained, not generator-owned)
28
+ *
29
+ * Stat-block files (count lives in a split HTML stat, not the phrase):
30
+ * - docs/landing/index.html — `<span class="v">N</span><span class="k">Bundled skills</span>`
31
+ * (PR #229 found this surface stale at 25 while every phrase file said 26)
27
32
  *
28
33
  * Usage:
29
34
  * node bin/check-skill-count.mjs # Check the current repo
@@ -42,7 +47,9 @@ const CHECKED_FILES = [
42
47
  "plugins/continuous-improvement/.claude-plugin/plugin.json",
43
48
  "package.json",
44
49
  "llms.txt",
50
+ ".cloudplugin/marketplace.json",
45
51
  ];
52
+ const STAT_CHECKED_FILES = ["docs/landing/index.html"];
46
53
  export function countSkills(repoRoot) {
47
54
  const skillsDir = join(repoRoot, BUNDLE_SKILLS_DIR);
48
55
  const entries = readdirSync(skillsDir);
@@ -75,6 +82,24 @@ function checkFile(repoRoot, relPath, expected) {
75
82
  found: staleMatch ? staleMatch[0] : undefined,
76
83
  };
77
84
  }
85
+ function checkStatFile(repoRoot, relPath, expected) {
86
+ let content;
87
+ try {
88
+ content = readFileSync(join(repoRoot, relPath), "utf8");
89
+ }
90
+ catch {
91
+ return { file: relPath, reason: "missing" };
92
+ }
93
+ const expectedPattern = new RegExp(`class="v">${expected}</span><span class="k">Bundled skills`);
94
+ if (expectedPattern.test(content))
95
+ return null;
96
+ const staleMatch = content.match(/class="v">(\d+)<\/span><span class="k">Bundled skills/);
97
+ return {
98
+ file: relPath,
99
+ reason: staleMatch ? "stale" : "missing",
100
+ found: staleMatch ? `${staleMatch[1]} Bundled skills (stat block)` : undefined,
101
+ };
102
+ }
78
103
  function main() {
79
104
  const repoRoot = argv[2] ?? cwd();
80
105
  const expected = countSkills(repoRoot);
@@ -84,8 +109,14 @@ function main() {
84
109
  if (v)
85
110
  violations.push(v);
86
111
  }
112
+ for (const file of STAT_CHECKED_FILES) {
113
+ const v = checkStatFile(repoRoot, file, expected);
114
+ if (v)
115
+ violations.push(v);
116
+ }
117
+ const totalChecked = CHECKED_FILES.length + STAT_CHECKED_FILES.length;
87
118
  if (violations.length === 0) {
88
- console.log(`OK skill-count: all ${CHECKED_FILES.length} description string(s) state "${expected} bundled skills" (matches skills/ source-of-truth).`);
119
+ console.log(`OK skill-count: all ${totalChecked} description string(s) state "${expected} bundled skills" (matches skills/ source-of-truth).`);
89
120
  exit(0);
90
121
  }
91
122
  console.error(`FAIL skill-count: ${violations.length} description string(s) do not state the actual bundled-skill count of ${expected}.`);
File without changes
File without changes
File without changes
@@ -12,7 +12,7 @@
12
12
  * - reach-through into `hooks/`, `scripts/`, `src/`, or deeper paths under
13
13
  * `bin/` or `lib/`
14
14
  *
15
- * Baseline: all 50 `src/test/*.test.mts` files pass today.
15
+ * Baseline: all 56 `src/test/*.test.mts` files pass today.
16
16
  *
17
17
  * Usage:
18
18
  * node bin/check-test-imports-only.mjs # Check the current repo
File without changes
File without changes
File without changes
File without changes
@@ -107,7 +107,7 @@ async function writePluginBundleReadme() {
107
107
  "- `tdd-workflow` — RED/GREEN/REFACTOR + 80% coverage gate",
108
108
  "- `workspace-surface-audit` — environment + capability audit",
109
109
  "- Tier-1/Tier-2 enforcement skills (`gateguard`, `verification-loop`,",
110
- " `para-memory-files`, `safety-guard`, `token-budget-advisor`,",
110
+ " `safety-guard`, `token-budget-advisor`,",
111
111
  " `strategic-compact`, `wild-risa-balance`)",
112
112
  "",
113
113
  "**Optional companions the orchestrator routes to (install separately if",
@@ -152,6 +152,7 @@ async function writePluginBundle() {
152
152
  copyFileTo(join(REPO_ROOT, "lib", "observe-event.mjs"), join(PLUGIN_BUNDLE_DIR, "lib", "observe-event.mjs")),
153
153
  copyFileTo(join(REPO_ROOT, "lib", "goal-state.mjs"), join(PLUGIN_BUNDLE_DIR, "lib", "goal-state.mjs")),
154
154
  copyFileTo(join(REPO_ROOT, "lib", "goal-drift-gate.mjs"), join(PLUGIN_BUNDLE_DIR, "lib", "goal-drift-gate.mjs")),
155
+ copyFileTo(join(REPO_ROOT, "lib", "hook-pack-gate.mjs"), join(PLUGIN_BUNDLE_DIR, "lib", "hook-pack-gate.mjs")),
155
156
  copyFileTo(join(REPO_ROOT, "lib", "recall-index.mjs"), join(PLUGIN_BUNDLE_DIR, "lib", "recall-index.mjs")),
156
157
  copyFileTo(join(REPO_ROOT, "lib", "recall-briefing.mjs"), join(PLUGIN_BUNDLE_DIR, "lib", "recall-briefing.mjs")),
157
158
  copyFileTo(join(REPO_ROOT, "lib", "skill-distill.mjs"), join(PLUGIN_BUNDLE_DIR, "lib", "skill-distill.mjs")),
File without changes
@@ -46,27 +46,27 @@ import { platform } from "node:os";
46
46
  import { join } from "node:path";
47
47
  import { argv, cwd, exit, stderr, stdout } from "node:process";
48
48
  import { resolveHomeDir } from "../lib/resolve-home-dir.mjs";
49
- const USAGE = `Usage: node bin/hook-stats.mjs [options]
50
-
51
- Reads JSONL telemetry from <HOME>/.claude/hook-telemetry/*.jsonl and
52
- prints per-hook aggregate counts over a recent time window.
53
-
54
- Options:
55
- --hours=<n> Time window in hours (default: 24)
56
- --hash=<hash> Only read this single <hash>.jsonl file
57
- (default: sha256 of the current working directory,
58
- normalized to forward slashes and lowercased on
59
- Windows, then first 12 hex chars)
60
- --telemetry-dir=<path> Override telemetry dir
61
- (default: <HOME>/.claude/hook-telemetry)
62
- --help Show this help and exit 0
63
-
64
- Output:
65
- One line per hook: "<hook> last <hours>h: pass=<n> block=<n>
66
- skip-short=<n> total=<n> avg-duration=<X>ms".
67
- If there are no records in the window: "no records in last <hours>h".
68
-
69
- The reader is parse-tolerant: malformed JSONL lines are skipped silently.
49
+ const USAGE = `Usage: node bin/hook-stats.mjs [options]
50
+
51
+ Reads JSONL telemetry from <HOME>/.claude/hook-telemetry/*.jsonl and
52
+ prints per-hook aggregate counts over a recent time window.
53
+
54
+ Options:
55
+ --hours=<n> Time window in hours (default: 24)
56
+ --hash=<hash> Only read this single <hash>.jsonl file
57
+ (default: sha256 of the current working directory,
58
+ normalized to forward slashes and lowercased on
59
+ Windows, then first 12 hex chars)
60
+ --telemetry-dir=<path> Override telemetry dir
61
+ (default: <HOME>/.claude/hook-telemetry)
62
+ --help Show this help and exit 0
63
+
64
+ Output:
65
+ One line per hook: "<hook> last <hours>h: pass=<n> block=<n>
66
+ skip-short=<n> total=<n> avg-duration=<X>ms".
67
+ If there are no records in the window: "no records in last <hours>h".
68
+
69
+ The reader is parse-tolerant: malformed JSONL lines are skipped silently.
70
70
  `;
71
71
  function parseArgs(rawArgs) {
72
72
  const opts = {
package/bin/install.mjs CHANGED
@@ -6,6 +6,7 @@
6
6
  * npx continuous-improvement install # beginner mode (default)
7
7
  * npx continuous-improvement install --mode expert # + MCP server + session hooks
8
8
  * npx continuous-improvement install --pack react # load starter instinct pack
9
+ * npx continuous-improvement install --target gemini,codex # skill into other agents' rules files
9
10
  * npx continuous-improvement install --uninstall # remove everything
10
11
  */
11
12
  import { chmodSync, copyFileSync, existsSync, mkdirSync, readFileSync, readdirSync, rmSync, statSync, writeFileSync, } from "node:fs";
@@ -14,7 +15,9 @@ import { homedir } from "node:os";
14
15
  import { dirname, join } from "node:path";
15
16
  import { fileURLToPath } from "node:url";
16
17
  import { createHash } from "node:crypto";
17
- import { getToolNames } from "../lib/plugin-metadata.mjs";
18
+ import { PACKAGE_NAME, VERSION, getToolNames } from "../lib/plugin-metadata.mjs";
19
+ import { TARGET_IDS, planTargetWrites, resolveTargets } from "../lib/install-targets.mjs";
20
+ import { evaluateUpdateCheck, fetchLatestNpmVersion, isThrottled, pendingNotice, } from "../lib/version-check.mjs";
18
21
  const __filename = fileURLToPath(import.meta.url);
19
22
  const __dirname = dirname(__filename);
20
23
  const SKILL_SOURCE = join(__dirname, "..", "SKILL.md");
@@ -493,6 +496,17 @@ Options for 'install':
493
496
  beginner — hooks + skill + commands (default)
494
497
  expert — beginner + MCP server + session hooks
495
498
  --pack <name> Load a starter instinct pack (react, python, go, meta)
499
+ --target <names> Comma-separated platform list (default: claude):
500
+ claude — full install: hooks + skill + commands
501
+ gemini — GEMINI.md (Gemini CLI)
502
+ codex — AGENTS.md (Codex CLI / agents.md standard)
503
+ cursor — .cursor/rules/continuous-improvement.mdc
504
+ windsurf — .windsurf/rules/continuous-improvement.md
505
+ zed — .rules
506
+ aider — CONVENTIONS.md + .aider.conf.yml
507
+ copilot — .github/copilot-instructions.md
508
+ Non-claude targets write the 7-Laws skill text into the
509
+ current project; hooks/MCP/instincts stay Claude Code-only.
496
510
  --help Show this help
497
511
 
498
512
  Options for 'backfill':
@@ -503,6 +517,7 @@ Examples:
503
517
  npx continuous-improvement install # beginner (default)
504
518
  npx continuous-improvement install --mode expert # full power
505
519
  npx continuous-improvement install --pack react # load React instincts
520
+ npx continuous-improvement install --target gemini,codex # other agents' rules files
506
521
  npx continuous-improvement backfill --dry-run # report thin/rich row counts
507
522
  npx continuous-improvement backfill # tag rows in place
508
523
  npx continuous-improvement install --uninstall # remove everything
@@ -510,7 +525,12 @@ Examples:
510
525
  }
511
526
  function getProjectHashSync() {
512
527
  try {
513
- const root = execSync("git rev-parse --show-toplevel 2>/dev/null", { encoding: "utf8" }).trim();
528
+ // No shell redirect `2>/dev/null` breaks under cmd.exe (tries to open a
529
+ // file literally named /dev/null); ignore stderr via stdio instead.
530
+ const root = execSync("git rev-parse --show-toplevel", {
531
+ encoding: "utf8",
532
+ stdio: ["ignore", "pipe", "ignore"],
533
+ }).trim();
514
534
  const hash = createHash("sha256").update(root).digest("hex").slice(0, 12);
515
535
  return { root, hash };
516
536
  }
@@ -547,6 +567,60 @@ if (args.includes("--uninstall")) {
547
567
  uninstallAll();
548
568
  process.exit(0);
549
569
  }
570
+ // --target <names>: comma-separated platform list, default claude-only.
571
+ // Non-claude targets receive the skill text in their platform's rules file
572
+ // (project-level); the full hook/MCP/instinct install stays Claude Code-only.
573
+ const targetFlagIndex = rawArgs.indexOf("--target");
574
+ let requestedTargets = ["claude"];
575
+ if (targetFlagIndex !== -1) {
576
+ const targetCsv = rawArgs[targetFlagIndex + 1];
577
+ if (!targetCsv || targetCsv.startsWith("--")) {
578
+ console.error(`--target requires a value. Valid targets: ${TARGET_IDS.join(", ")}`);
579
+ process.exit(1);
580
+ }
581
+ const resolved = resolveTargets(targetCsv);
582
+ if (resolved.unknown.length > 0) {
583
+ console.error(`Unknown --target name(s): ${resolved.unknown.join(", ")}. Valid targets: ${TARGET_IDS.join(", ")}`);
584
+ process.exit(1);
585
+ }
586
+ if (resolved.targets.length === 0) {
587
+ console.error(`--target requires at least one name. Valid targets: ${TARGET_IDS.join(", ")}`);
588
+ process.exit(1);
589
+ }
590
+ requestedTargets = resolved.targets;
591
+ }
592
+ function installNonClaudeTargets(targetIds) {
593
+ const skillMd = readFileSync(SKILL_SOURCE, "utf8");
594
+ const project = getProjectHashSync();
595
+ const projectRoot = project.root === "global" ? process.cwd() : project.root;
596
+ const readExisting = (relPath) => {
597
+ const absPath = join(projectRoot, relPath);
598
+ return existsSync(absPath) ? readFileSync(absPath, "utf8") : null;
599
+ };
600
+ const notes = new Set();
601
+ for (const targetId of targetIds) {
602
+ const plan = planTargetWrites(targetId, skillMd, readExisting);
603
+ for (const write of plan.writes) {
604
+ const absPath = join(projectRoot, write.relPath);
605
+ mkdirSync(dirname(absPath), { recursive: true });
606
+ writeFileSync(absPath, write.content);
607
+ console.log(` ✓ ${targetId} → ${write.relPath}`);
608
+ }
609
+ for (const note of plan.notes)
610
+ notes.add(note);
611
+ }
612
+ for (const note of notes)
613
+ console.log(` ℹ ${note}`);
614
+ }
615
+ const nonClaudeTargets = requestedTargets.filter((targetId) => targetId !== "claude");
616
+ if (nonClaudeTargets.length > 0) {
617
+ console.log("\ncontinuous-improvement multi-platform install\n");
618
+ installNonClaudeTargets(nonClaudeTargets);
619
+ if (!requestedTargets.includes("claude")) {
620
+ console.log("\nDone. Claude Code install skipped (not in --target list).");
621
+ process.exit(0);
622
+ }
623
+ }
550
624
  console.log(`
551
625
  continuous-improvement (mode: ${INSTALL_MODE})
552
626
  Research → Plan → Execute → Verify → Reflect → Learn → Iterate
@@ -621,3 +695,47 @@ Next steps:
621
695
  ${INSTALL_MODE === "expert" ? `\nMCP tools available (${getToolNames("expert").length}): ${getToolNames("expert").join(", ")}` : ""}
622
696
  Available instinct packs: npx continuous-improvement install --pack react|python|go|meta
623
697
  `);
698
+ // Update-available nudge for the npm/CLI install path (the marketplace path is
699
+ // covered by Claude Code's native plugin auto-update). Reads the public npm
700
+ // registry only — no telemetry. Throttled, default-on, off via
701
+ // CLAUDE_CI_UPDATE_CHECK=off. Fully fail-open: it can never change the
702
+ // installer's outcome. See docs/plans/2026-06-25-version-check-nudge.md.
703
+ async function maybeNotifyUpdate() {
704
+ if ((process.env.CLAUDE_CI_UPDATE_CHECK ?? "on").trim().toLowerCase() === "off")
705
+ return;
706
+ const cacheFile = join(getHomeDir(), ".claude", "instincts", "global", "update-check.json");
707
+ const now = Date.now();
708
+ let cache = null;
709
+ try {
710
+ if (existsSync(cacheFile))
711
+ cache = JSON.parse(readFileSync(cacheFile, "utf8"));
712
+ }
713
+ catch {
714
+ cache = null;
715
+ }
716
+ if (isThrottled(cache, now)) {
717
+ const notice = pendingNotice(cache, VERSION);
718
+ if (notice)
719
+ console.log(`\n${notice}`);
720
+ return;
721
+ }
722
+ const remote = await fetchLatestNpmVersion(PACKAGE_NAME);
723
+ const decision = evaluateUpdateCheck({ local: VERSION, remote, now });
724
+ if (decision.notice)
725
+ console.log(`\n${decision.notice}`);
726
+ if (decision.nextCache) {
727
+ try {
728
+ mkdirSync(dirname(cacheFile), { recursive: true });
729
+ writeFileSync(cacheFile, JSON.stringify(decision.nextCache) + "\n", "utf8");
730
+ }
731
+ catch {
732
+ // cache persistence is best-effort; never throw
733
+ }
734
+ }
735
+ }
736
+ try {
737
+ await maybeNotifyUpdate();
738
+ }
739
+ catch {
740
+ // a version check must never change the installer's outcome
741
+ }
@@ -160,6 +160,12 @@ function formatReport(result) {
160
160
  return lines.join("\n");
161
161
  }
162
162
  const args = process.argv.slice(2);
163
+ function actionInput(name) {
164
+ return (process.env[`INPUT_${name.toUpperCase()}`] ?? "").trim();
165
+ }
166
+ function actionInputBoolean(name) {
167
+ return /^(1|true|yes)$/i.test(actionInput(name));
168
+ }
163
169
  if (args.includes("--help") || args.includes("-h")) {
164
170
  console.log(`
165
171
  Agent Transcript Linter — The 7 Laws of AI Agent Discipline
@@ -176,7 +182,7 @@ Options:
176
182
  `);
177
183
  process.exit(0);
178
184
  }
179
- const isStrict = args.includes("--strict");
185
+ const isStrict = args.includes("--strict") || actionInputBoolean("strict");
180
186
  const isJson = args.includes("--json");
181
187
  const isStdin = args.includes("--stdin");
182
188
  async function main() {
@@ -190,7 +196,9 @@ async function main() {
190
196
  }
191
197
  }
192
198
  else {
193
- const filePath = args.find((arg) => !arg.startsWith("--"));
199
+ const filePath = args.find((arg) => !arg.startsWith("--"))
200
+ || actionInput("transcript-path")
201
+ || actionInput("observations-path");
194
202
  if (!filePath) {
195
203
  console.error("Error: provide a file path or use --stdin");
196
204
  process.exit(1);
@@ -213,16 +221,20 @@ async function main() {
213
221
  process.exit(0);
214
222
  }
215
223
  const result = analyzeTranscript(events, parseFailures);
224
+ const report = formatReport(result);
216
225
  if (isJson) {
217
226
  console.log(JSON.stringify(result, null, 2));
218
227
  }
219
228
  else {
220
- console.log(formatReport(result));
229
+ console.log(report);
221
230
  }
222
231
  if (process.env.GITHUB_OUTPUT) {
223
232
  const outputLines = [
224
233
  `violations=${result.violations.length}`,
225
234
  `score=${result.score}`,
235
+ `report<<CI_REPORT_EOF`,
236
+ report,
237
+ "CI_REPORT_EOF",
226
238
  ];
227
239
  const { appendFileSync } = await import("node:fs");
228
240
  for (const line of outputLines) {
@@ -13,15 +13,15 @@
13
13
  import { execSync } from "node:child_process";
14
14
  import { existsSync, mkdirSync, readFileSync, readdirSync, rmSync, writeFileSync } from "node:fs";
15
15
  import { homedir } from "node:os";
16
- import { basename, dirname, join } from "node:path";
16
+ import { basename, dirname, join, resolve } from "node:path";
17
17
  import { createInterface } from "node:readline";
18
18
  import { fileURLToPath } from "node:url";
19
19
  import { createHash } from "node:crypto";
20
20
  import { PACKAGE_NAME, VERSION, getToolDefinitions, isPluginMode, } from "../lib/plugin-metadata.mjs";
21
21
  import { formatDriftReport, parseGoalFromPlan, scoreObservations, } from "../lib/goal-state.mjs";
22
22
  import { buildIndex, formatRecallHits, parseSince, query as queryRecall, } from "../lib/recall-index.mjs";
23
- import { draftFromCandidate, extractTrajectories, findCandidates, formatCandidates, serializeDraft, } from "../lib/skill-distill.mjs";
24
- import { MAX_CLEARED_FILES, clearFiles, resolveSessionDir, } from "../lib/gateguard-state.mjs";
23
+ import { draftFromCandidate, draftFromWorkflowRun, extractTrajectories, findCandidates, formatCandidates, serializeDraft, workflowRunFromObservations, } from "../lib/skill-distill.mjs";
24
+ import { MAX_CLEARED_FILES, canonicalizeFileKey, clearFiles, resolveInstinctsRoot, resolveSessionDir, } from "../lib/gateguard-state.mjs";
25
25
  function getHomeDir() {
26
26
  return process.env.HOME || process.env.USERPROFILE || homedir();
27
27
  }
@@ -219,6 +219,7 @@ function readDistillObservations(projectHash) {
219
219
  tool: getString(observation.tool),
220
220
  input_summary: getString(observation.input_summary),
221
221
  output_summary: getString(observation.output_summary),
222
+ event: getString(observation.event),
222
223
  }));
223
224
  }
224
225
  function detectLevel(projectHash) {
@@ -508,10 +509,11 @@ function handleTool(name, params) {
508
509
  }
509
510
  case "ci_gateguard_clear": {
510
511
  // Beginner-available on purpose: the GateGuard hook fires for every
511
- // install, so the clearance action must too. Resolves the session dir via
512
- // gateguard-state (canonical), the same way the hook does, so the marker
513
- // lands where the hook looks regardless of how each process spelled the
514
- // project root.
512
+ // install, so the clearance action must too. The hook's block reason now
513
+ // prints state_path (the session-scoped state file); honoring it writes
514
+ // the marker exactly where the hook looks. Without it the MCP server can't
515
+ // know the caller's session, so it falls back to the canonical session dir
516
+ // — correct only on the legacy unscoped path.
515
517
  const rawList = Array.isArray(params.file_paths) ? params.file_paths : [];
516
518
  const listPaths = rawList.filter((value) => typeof value === "string" && value.length > 0);
517
519
  const single = getString(params.file_path).trim();
@@ -519,7 +521,24 @@ function handleTool(name, params) {
519
521
  if (paths.length === 0) {
520
522
  return error("file_paths is required — pass the file path(s) named in the GateGuard block reason, e.g. { file_paths: [\"src/x.ts\"] }.");
521
523
  }
522
- const sessionDir = resolveSessionDir();
524
+ // state_path is a model-controlled argument, so it could be a traversal
525
+ // string (`../../etc/...`). dirname leaves `..` segments for the OS to
526
+ // resolve at write time, so without a bound this is an arbitrary-write
527
+ // primitive. Resolve + canonicalize, then require containment within the
528
+ // instincts root (the only tree the hook ever prints a state_path inside).
529
+ const rawStatePath = getString(params.state_path).trim();
530
+ let sessionDir;
531
+ if (rawStatePath) {
532
+ const resolvedKey = canonicalizeFileKey(resolve(rawStatePath));
533
+ const rootKey = canonicalizeFileKey(resolveInstinctsRoot());
534
+ if (resolvedKey !== rootKey && !resolvedKey.startsWith(`${rootKey}/`)) {
535
+ return error("state_path must resolve inside ~/.claude/instincts/. Pass it verbatim from the GateGuard block reason.");
536
+ }
537
+ sessionDir = dirname(resolve(rawStatePath));
538
+ }
539
+ else {
540
+ sessionDir = resolveSessionDir();
541
+ }
523
542
  const { cleared, skippedForCap } = clearFiles(sessionDir, paths);
524
543
  const lines = [
525
544
  "## GateGuard clearance",
@@ -837,6 +856,45 @@ function handleTool(name, params) {
837
856
  "```",
838
857
  ].join("\n"));
839
858
  }
859
+ case "ci_distill_from_workflow": {
860
+ if (MODE !== "expert") {
861
+ return error("ci_distill_from_workflow requires expert mode");
862
+ }
863
+ const run = workflowRunFromObservations(readDistillObservations(project.hash));
864
+ if (!run) {
865
+ return text("No completed-and-verified Workflow run found in this project's observations. " +
866
+ "A run qualifies when a `Workflow` tool call is followed by a passing verify/test/build in the same feed. " +
867
+ "Run a workflow, verify its output, then try `ci_distill_from_workflow` again.");
868
+ }
869
+ const draftInstinct = draftFromWorkflowRun(run);
870
+ if (!isSafeDraftId(draftInstinct.id)) {
871
+ return error(`Internal error: generated draft id "${draftInstinct.id}" failed the safety check.`);
872
+ }
873
+ const draft = serializeDraft(draftInstinct);
874
+ const draftsDir = join(INSTINCTS_DIR, project.hash, "drafts");
875
+ const draftPath = join(draftsDir, `${draftInstinct.id}.yaml`);
876
+ try {
877
+ mkdirSync(draftsDir, { recursive: true });
878
+ writeFileSync(draftPath, draft);
879
+ }
880
+ catch (err) {
881
+ return error(`Failed to write draft to ${draftPath}: ${err instanceof Error ? err.message : String(err)}`);
882
+ }
883
+ return text([
884
+ "## Draft written from a verified workflow run",
885
+ "",
886
+ `**Workflow:** ${run.name}`,
887
+ `**Path:** ${draftPath}`,
888
+ "",
889
+ "Edit the body to capture the real recipe (preconditions, concrete steps, gotchas), then promote with:",
890
+ "",
891
+ ` ci_distill_promote id=${draftInstinct.id}`,
892
+ "",
893
+ "```yaml",
894
+ draft.trimEnd(),
895
+ "```",
896
+ ].join("\n"));
897
+ }
840
898
  case "ci_distill_promote": {
841
899
  if (MODE !== "expert") {
842
900
  return error("ci_distill_promote requires expert mode");