@event4u/agent-config 8.8.0 → 8.10.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 (215) hide show
  1. package/.claude-plugin/marketplace.json +1 -1
  2. package/CHANGELOG.md +139 -263
  3. package/README.md +5 -5
  4. package/dist/agent-src/commands/agent-handoff.md +39 -2
  5. package/dist/agent-src/commands/cost/report.md +13 -3
  6. package/dist/agent-src/commands/research/deep.md +8 -0
  7. package/dist/agent-src/commands/research/report.md +7 -0
  8. package/dist/agent-src/commands/roadmap/create.md +12 -0
  9. package/dist/agent-src/commands/roadmap/process-full.md +31 -5
  10. package/dist/agent-src/commands/tests/create.md +28 -6
  11. package/dist/agent-src/commands/work.md +33 -0
  12. package/dist/agent-src/commands/worktree/cleanup.md +78 -0
  13. package/dist/agent-src/commands/worktree/create.md +62 -0
  14. package/dist/agent-src/commands/worktree/status.md +63 -0
  15. package/dist/agent-src/commands/worktree/verify.md +70 -0
  16. package/dist/agent-src/commands/worktree.md +85 -0
  17. package/dist/agent-src/contexts/communication/rules-auto/think-before-action-mechanics.md +30 -1
  18. package/dist/agent-src/contexts/execution/orchestration-telemetry.md +60 -31
  19. package/dist/agent-src/contexts/execution/roadmap-process-loop.md +37 -0
  20. package/dist/agent-src/contexts/execution/subagent-topologies.md +34 -0
  21. package/dist/agent-src/contexts/execution/verification-mechanics.md +9 -9
  22. package/dist/agent-src/rules/agent-authority.md +1 -1
  23. package/dist/agent-src/rules/communication-through-line.md +1 -1
  24. package/dist/agent-src/rules/delegation-policy.md +5 -0
  25. package/dist/agent-src/rules/design-fidelity.md +40 -0
  26. package/dist/agent-src/rules/direct-answers.md +1 -1
  27. package/dist/agent-src/rules/downstream-changes.md +1 -0
  28. package/dist/agent-src/rules/external-code-graph-interop.md +65 -0
  29. package/dist/agent-src/rules/git-history-discipline.md +18 -0
  30. package/dist/agent-src/rules/lethal-trifecta-guard.md +6 -1
  31. package/dist/agent-src/rules/minimal-safe-diff.md +58 -10
  32. package/dist/agent-src/rules/non-destructive-by-default.md +3 -0
  33. package/dist/agent-src/rules/question-not-instruction.md +76 -0
  34. package/dist/agent-src/rules/senior-engineering-discipline.md +1 -1
  35. package/dist/agent-src/rules/spreadsheet-source-quality.md +61 -0
  36. package/dist/agent-src/rules/think-before-action.md +1 -0
  37. package/dist/agent-src/rules/tool-safety.md +8 -0
  38. package/dist/agent-src/rules/untrusted-input-defense.md +87 -22
  39. package/dist/agent-src/scripts/update_roadmap_progress.ts +2 -1
  40. package/dist/agent-src/skills/accessibility-auditor/SKILL.md +19 -0
  41. package/dist/agent-src/skills/accessibility-auditor/evals/evals.json +15 -0
  42. package/dist/agent-src/skills/ai-code-blindspots/SKILL.md +10 -3
  43. package/dist/agent-src/skills/analysis-skill-router/evals/evals.json +13 -0
  44. package/dist/agent-src/skills/api-testing/SKILL.md +15 -1
  45. package/dist/agent-src/skills/character-consistency/evals/evals.json +16 -0
  46. package/dist/agent-src/skills/code-review/SKILL.md +2 -0
  47. package/dist/agent-src/skills/command-routing/evals/evals.json +14 -0
  48. package/dist/agent-src/skills/command-writing/SKILL.md +53 -40
  49. package/dist/agent-src/skills/dashboard-design/evals/evals.json +16 -0
  50. package/dist/agent-src/skills/dcf-modeling/evals/domain-truth.json +29 -0
  51. package/dist/agent-src/skills/dcf-modeling/evals/evals.json +20 -0
  52. package/dist/agent-src/skills/decision-record/evals/evals.json +20 -0
  53. package/dist/agent-src/skills/deep-reading-analyst/SKILL.md +1 -0
  54. package/dist/agent-src/skills/design-intelligence/SKILL.md +49 -12
  55. package/dist/agent-src/skills/design-intelligence/evals/evals.json +14 -0
  56. package/dist/agent-src/skills/design-review/SKILL.md +35 -15
  57. package/dist/agent-src/skills/design-system-capture/SKILL.md +84 -24
  58. package/dist/agent-src/skills/design-system-capture/evals/evals.json +15 -0
  59. package/dist/agent-src/skills/developer-like-execution/evals/evals.json +16 -0
  60. package/dist/agent-src/skills/doc-coauthoring/SKILL.md +27 -0
  61. package/dist/agent-src/skills/doc-coauthoring/evals/evals.json +16 -0
  62. package/dist/agent-src/skills/editorial-calendar/evals/domain-truth.json +16 -0
  63. package/dist/agent-src/skills/editorial-calendar/evals/evals.json +16 -0
  64. package/dist/agent-src/skills/estimate-ticket/evals/evals.json +16 -0
  65. package/dist/agent-src/skills/existing-ui-audit/SKILL.md +30 -0
  66. package/dist/agent-src/skills/existing-ui-audit/evals/triggers.json +47 -0
  67. package/dist/agent-src/skills/fe-design/SKILL.md +36 -5
  68. package/dist/agent-src/skills/fe-design/evals/triggers.json +47 -0
  69. package/dist/agent-src/skills/forecasting/evals/domain-truth.json +29 -0
  70. package/dist/agent-src/skills/forecasting/evals/evals.json +20 -0
  71. package/dist/agent-src/skills/frontend-render-security/SKILL.md +2 -2
  72. package/dist/agent-src/skills/fundraising-narrative/evals/domain-truth.json +16 -0
  73. package/dist/agent-src/skills/fundraising-narrative/evals/evals.json +16 -0
  74. package/dist/agent-src/skills/html-deck/SKILL.md +27 -0
  75. package/dist/agent-src/skills/iconography/SKILL.md +24 -0
  76. package/dist/agent-src/skills/incident-commander/evals/domain-truth.json +16 -0
  77. package/dist/agent-src/skills/incident-commander/evals/evals.json +20 -0
  78. package/dist/agent-src/skills/launch-readiness/evals/evals.json +21 -0
  79. package/dist/agent-src/skills/logging-monitoring/evals/evals.json +16 -0
  80. package/dist/agent-src/skills/markitdown/SKILL.md +1 -0
  81. package/dist/agent-src/skills/mcp/SKILL.md +24 -0
  82. package/dist/agent-src/skills/mcp-builder/SKILL.md +9 -1
  83. package/dist/agent-src/skills/memory-consolidation/SKILL.md +55 -4
  84. package/dist/agent-src/skills/messaging-architecture/evals/domain-truth.json +16 -0
  85. package/dist/agent-src/skills/messaging-architecture/evals/evals.json +16 -0
  86. package/dist/agent-src/skills/perf-feedback-craft/evals/evals.json +16 -0
  87. package/dist/agent-src/skills/pest-testing/SKILL.md +1 -1
  88. package/dist/agent-src/skills/playwright-testing/SKILL.md +16 -0
  89. package/dist/agent-src/skills/prompt-engineering-patterns/evals/evals.json +16 -0
  90. package/dist/agent-src/skills/refine-prompt/evals/evals.json +16 -0
  91. package/dist/agent-src/skills/refine-ticket/evals/evals.json +16 -0
  92. package/dist/agent-src/skills/release-comms/evals/evals.json +16 -0
  93. package/dist/agent-src/skills/rice-prioritization/evals/evals.json +20 -0
  94. package/dist/agent-src/skills/roadmap-writing/SKILL.md +39 -5
  95. package/dist/agent-src/skills/runway-cognition/evals/domain-truth.json +29 -0
  96. package/dist/agent-src/skills/runway-cognition/evals/evals.json +20 -0
  97. package/dist/agent-src/skills/scenario-modeling/evals/domain-truth.json +29 -0
  98. package/dist/agent-src/skills/scenario-modeling/evals/evals.json +20 -0
  99. package/dist/agent-src/skills/secrets-management/SKILL.md +7 -0
  100. package/dist/agent-src/skills/skill-writing/SKILL.md +12 -3
  101. package/dist/agent-src/skills/spreadsheet-authoring/SKILL.md +98 -0
  102. package/dist/agent-src/skills/spreadsheet-authoring/evals/triggers.json +47 -0
  103. package/dist/agent-src/skills/subagent-orchestration/SKILL.md +27 -33
  104. package/dist/agent-src/skills/supply-chain-intake/SKILL.md +12 -0
  105. package/dist/agent-src/skills/systematic-debugging/evals/evals.json +20 -0
  106. package/dist/agent-src/skills/test-case-discovery/SKILL.md +118 -0
  107. package/dist/agent-src/skills/test-case-discovery/evals/triggers.json +52 -0
  108. package/dist/agent-src/skills/test-driven-development/SKILL.md +32 -6
  109. package/dist/agent-src/skills/test-driven-development/evals/evals.json +16 -0
  110. package/dist/agent-src/skills/testing-anti-patterns/SKILL.md +34 -4
  111. package/dist/agent-src/skills/threat-modeling/evals/evals.json +21 -0
  112. package/dist/agent-src/skills/typography-system/evals/evals.json +14 -0
  113. package/dist/agent-src/skills/ui-component-architect/SKILL.md +9 -0
  114. package/dist/agent-src/skills/unit-economics-modeling/evals/domain-truth.json +40 -0
  115. package/dist/agent-src/skills/unit-economics-modeling/evals/evals.json +24 -0
  116. package/dist/agent-src/skills/verify-completion-evidence/evals/evals.json +16 -0
  117. package/dist/agent-src/skills/vision-articulation/evals/evals.json +16 -0
  118. package/dist/agent-src/skills/voice-and-tone-design/evals/evals.json +16 -0
  119. package/dist/agent-src/skills/worktree-lifecycle/SKILL.md +180 -0
  120. package/dist/agent-src/skills/worktree-lifecycle/evals/triggers.json +52 -0
  121. package/dist/agent-src/templates/agents/agent-project-settings.example.yml +1 -1
  122. package/dist/agent-src/templates/roadmaps.md +36 -0
  123. package/dist/cli/registry.js +3 -0
  124. package/dist/cli/registry.js.map +1 -1
  125. package/dist/discovery/deprecation-report.md +1 -1
  126. package/dist/discovery/discovery-manifest.json +326 -67
  127. package/dist/discovery/discovery-manifest.json.sha256 +1 -1
  128. package/dist/discovery/discovery-manifest.summary.md +6 -6
  129. package/dist/discovery/orphan-report.md +1 -1
  130. package/dist/discovery/packs.json +25 -14
  131. package/dist/discovery/trust-report.md +4 -4
  132. package/dist/discovery/workspaces.json +25 -12
  133. package/dist/install/install.mjs +136 -1
  134. package/dist/mcp/registry-manifest.json +3 -3
  135. package/dist/router.json +1 -1
  136. package/dist/scripts/_lib/claude_builtin_names.js +145 -0
  137. package/dist/scripts/_lib/claude_builtin_names.js.map +1 -0
  138. package/dist/scripts/condense.js +14 -0
  139. package/dist/scripts/condense.js.map +1 -1
  140. package/docs/CLAIMS.md +36 -8
  141. package/docs/architecture.md +4 -4
  142. package/docs/archive/CHANGELOG-pre-8.9.0.md +306 -0
  143. package/docs/benchmark.md +113 -0
  144. package/docs/catalog.md +32 -8
  145. package/docs/command-flows.md +6 -1
  146. package/docs/contracts/CHANGELOG-conventions.md +6 -0
  147. package/docs/contracts/command-clusters.md +18 -0
  148. package/docs/contracts/design-artifact-lifecycle.md +93 -0
  149. package/docs/contracts/design-artifact-verification.md +153 -0
  150. package/docs/contracts/injected-block-authenticity-model.md +66 -0
  151. package/docs/contracts/mcp-tool-inventory.md +28 -28
  152. package/docs/contracts/release-pr-gating.md +26 -0
  153. package/docs/contracts/release-sizing.md +85 -0
  154. package/docs/contracts/surface-agent-contracts.md +153 -0
  155. package/docs/decisions/ADR-061-corpus-grounding-layer.md +13 -0
  156. package/docs/decisions/ADR-115-command-cluster-consolidation-phase-4.md +1 -1
  157. package/docs/decisions/ADR-116-memory-tripwire-activation-path.md +9 -0
  158. package/docs/distribution/consumer-matrix.md +96 -0
  159. package/docs/featured-skills.md +2 -2
  160. package/docs/getting-started-by-role.md +1 -1
  161. package/docs/getting-started.md +2 -2
  162. package/docs/governance-advantage.md +1 -1
  163. package/docs/guidelines/agent-infra/asking-and-brevity-examples.md +27 -0
  164. package/docs/guidelines/agent-infra/emphasis-budget.md +45 -0
  165. package/docs/guidelines/agent-infra/simplicity-and-goal-demos.md +151 -0
  166. package/docs/guidelines/agent-infra/tool-description-as-policy.md +35 -0
  167. package/docs/guidelines/design-canon.md +1 -0
  168. package/docs/proof.md +38 -23
  169. package/docs/release-runbook.md +3 -0
  170. package/docs/second-brain-scope.md +69 -26
  171. package/docs/self-review-gate.md +51 -0
  172. package/package.json +1 -1
  173. package/src/agent-src/scripts/update_roadmap_progress.ts +2 -1
  174. package/src/scripts/_dispatch.bash +27 -0
  175. package/src/scripts/_lib/claude_builtin_names.ts +148 -0
  176. package/src/scripts/_lib/file_slicer.ts +117 -0
  177. package/src/scripts/_lib/lexical_index.ts +155 -0
  178. package/src/scripts/_lib/orchestration_record.ts +183 -0
  179. package/src/scripts/_lib/orchestration_savings.ts +114 -9
  180. package/src/scripts/_lib/retrieval_sanitize.ts +71 -0
  181. package/src/scripts/_lib/security_lint.ts +1 -0
  182. package/src/scripts/_lib/stat_index.ts +98 -0
  183. package/src/scripts/benchmark.ts +122 -0
  184. package/src/scripts/build_proof.ts +30 -17
  185. package/src/scripts/check_memory.ts +2 -0
  186. package/src/scripts/check_quality_regression.ts +51 -0
  187. package/src/scripts/check_release_adjacent_health.ts +121 -0
  188. package/src/scripts/check_roadmap_trackable.ts +3 -2
  189. package/src/scripts/check_rule_invariants.ts +275 -0
  190. package/src/scripts/condense.ts +14 -0
  191. package/src/scripts/consumer_matrix.ts +353 -0
  192. package/src/scripts/discovery_graph.ts +0 -0
  193. package/src/scripts/external_sources_denylist.json +4 -1
  194. package/src/scripts/generate_index.ts +6 -2
  195. package/src/scripts/install-hooks.sh +25 -0
  196. package/src/scripts/install.ts +35 -2
  197. package/src/scripts/learning_sidecar.ts +0 -0
  198. package/src/scripts/lint_agent_skill_names.ts +29 -0
  199. package/src/scripts/lint_changelog_rollback.ts +293 -0
  200. package/src/scripts/lint_knowledge_scale.ts +4 -3
  201. package/src/scripts/lint_roadmap_complexity.ts +124 -0
  202. package/src/scripts/lint_versioned_cache.ts +195 -0
  203. package/src/scripts/mcp_server/consumer_tool_catalog.json +2 -1
  204. package/src/scripts/mcp_server/tools.ts +24 -1
  205. package/src/scripts/measure_lexical_ranking.ts +173 -0
  206. package/src/scripts/memory_lookup.ts +0 -0
  207. package/src/scripts/orchestration_record.ts +156 -0
  208. package/src/scripts/orchestration_savings_report.ts +65 -8
  209. package/src/scripts/schemas/skill.schema.json +4 -0
  210. package/src/scripts/score_domain_truth.ts +192 -0
  211. package/src/scripts/second_brain_retrieval.ts +320 -0
  212. package/src/scripts/second_brain_run.ts +341 -0
  213. package/src/scripts/self_review_gate.test.ts +65 -0
  214. package/src/scripts/self_review_gate.ts +272 -0
  215. package/src/scripts/trigger_eval_grandfather.json +223 -224
@@ -0,0 +1,121 @@
1
+ #!/usr/bin/env tsx
2
+ /**
3
+ * Release-adjacent workflow health tripwire — road-to-proof-under-real-conditions
4
+ * Phase 1. The "red for five releases without anyone noticing" detector:
5
+ * the MCP worker deploy failed on every release from one minor line to the
6
+ * next and nothing surfaced it, because post-tag workflows are nobody's PR.
7
+ *
8
+ * For each watched workflow, look at its most recent COMPLETED run on the
9
+ * default branch (any trigger). If that run's conclusion is not `success`
10
+ * and it finished more than --max-red-hours ago (default 48) with no newer
11
+ * successful run, the workflow counts as durably red → exit 1 and print a
12
+ * GitHub error annotation per offender. The calling workflow turns that
13
+ * into a visible failure on main.
14
+ *
15
+ * Auth: GITHUB_TOKEN (or GH_TOKEN) env. Repo: GITHUB_REPOSITORY env or
16
+ * --repo owner/name.
17
+ *
18
+ * Exit codes: 0 all green/absent · 1 durably-red workflow(s) · 2 usage/env.
19
+ */
20
+
21
+ import { argv, env, exit } from 'node:process';
22
+
23
+ const WATCHED = [
24
+ 'publish-npm.yml',
25
+ 'deploy-mcp-worker.yml',
26
+ 'deploy-site.yml',
27
+ 'site.yml',
28
+ 'consumer-matrix.yml',
29
+ 'release.yml',
30
+ ] as const;
31
+
32
+ interface RunInfo {
33
+ conclusion: string | null;
34
+ updated_at: string;
35
+ html_url: string;
36
+ }
37
+
38
+ async function latestCompletedRun(
39
+ repo: string,
40
+ token: string,
41
+ workflow: string,
42
+ branch: string,
43
+ ): Promise<RunInfo | null> {
44
+ const url =
45
+ `https://api.github.com/repos/${repo}/actions/workflows/${workflow}/runs` +
46
+ `?status=completed&per_page=1${branch ? `&branch=${branch}` : ''}`;
47
+ const res = await fetch(url, {
48
+ headers: {
49
+ Authorization: `Bearer ${token}`,
50
+ Accept: 'application/vnd.github+json',
51
+ 'X-GitHub-Api-Version': '2022-11-28',
52
+ },
53
+ });
54
+ if (res.status === 404) return null; // workflow not present (yet)
55
+ if (!res.ok) throw new Error(`GitHub API ${res.status} for ${workflow}: ${await res.text()}`);
56
+ const body = (await res.json()) as { workflow_runs?: RunInfo[] };
57
+ return body.workflow_runs?.[0] ?? null;
58
+ }
59
+
60
+ async function main(): Promise<number> {
61
+ const args = argv.slice(2);
62
+ const getOpt = (flag: string): string | undefined => {
63
+ const i = args.indexOf(flag);
64
+ return i >= 0 ? args[i + 1] : undefined;
65
+ };
66
+ const token = env.GITHUB_TOKEN ?? env.GH_TOKEN ?? '';
67
+ const repo = getOpt('--repo') ?? env.GITHUB_REPOSITORY ?? '';
68
+ const maxRedHours = Number(getOpt('--max-red-hours') ?? '48');
69
+ if (!token || !repo) {
70
+ process.stderr.write('❌ release-adjacent-health: GITHUB_TOKEN and GITHUB_REPOSITORY (or --repo) required.\n');
71
+ return 2;
72
+ }
73
+
74
+ // Tag/release-triggered workflows have no branch association — query
75
+ // without the branch filter for those; branch-scoped for PR/push ones.
76
+ const branchless = new Set(['publish-npm.yml', 'deploy-mcp-worker.yml', 'release.yml']);
77
+ let red = 0;
78
+ for (const wf of WATCHED) {
79
+ let run: RunInfo | null;
80
+ try {
81
+ run = await latestCompletedRun(repo, token, wf, branchless.has(wf) ? '' : 'main');
82
+ } catch (err) {
83
+ process.stderr.write(`⚠️ ${wf}: ${(err as Error).message}\n`);
84
+ continue; // transient API failure must not fake a red
85
+ }
86
+ if (!run) {
87
+ process.stderr.write(`· ${wf}: no completed runs (or workflow absent) — skipping\n`);
88
+ continue;
89
+ }
90
+ if (run.conclusion === 'success') {
91
+ process.stderr.write(`✅ ${wf}: last completed run green\n`);
92
+ continue;
93
+ }
94
+ const ageHours = (Date.now() - Date.parse(run.updated_at)) / 3_600_000;
95
+ if (ageHours >= maxRedHours) {
96
+ process.stdout.write(
97
+ `::error::release-adjacent workflow durably red: ${wf} — last completed run ` +
98
+ `'${run.conclusion}' ${ageHours.toFixed(0)}h ago with no newer success. ${run.html_url}\n`,
99
+ );
100
+ red += 1;
101
+ } else {
102
+ process.stderr.write(
103
+ `⚠️ ${wf}: last run '${run.conclusion}' ${ageHours.toFixed(0)}h ago (< ${maxRedHours}h window)\n`,
104
+ );
105
+ }
106
+ }
107
+ if (red > 0) {
108
+ process.stderr.write(`❌ ${red} release-adjacent workflow(s) durably red.\n`);
109
+ return 1;
110
+ }
111
+ process.stderr.write('✅ release-adjacent workflows healthy.\n');
112
+ return 0;
113
+ }
114
+
115
+ main().then(
116
+ (code) => exit(code),
117
+ (err) => {
118
+ process.stderr.write(`❌ release-adjacent-health crashed: ${(err as Error).stack ?? err}\n`);
119
+ exit(2);
120
+ },
121
+ );
@@ -37,10 +37,11 @@ const ROADMAP_ROOT = 'agents/roadmaps';
37
37
  // CHECKBOX_RE = re.compile(r"^\s*[-*]\s+\[([ xX~\-])\]\s", re.MULTILINE)
38
38
  const CHECKBOX_RE = /^[ \t]*[-*][ \t]+\[([ xX~\-])\][ \t\f\v]/m;
39
39
  // PHASE_RE = re.compile(
40
- // r"^(#{2,3})\s+Phase\s+(\d+[a-z]?|[IVX]+|[A-Z](?:\d+)?)"
40
+ // r"^(#{2,3})\s+Phase\s+(\d+(?:\.\d+)*[a-z]?|[IVX]+|[A-Z](?:\d+)?)"
41
41
  // r"(?:[\s:—\-]+(.*?))?\s*$", re.MULTILINE)
42
+ // Numeric ids accept dotted sub-phases ("1.0", "4.1") — mirrors the dashboard's PHASE_RE.
42
43
  const PHASE_RE =
43
- /^(#{2,3})[ \t\f\v]+Phase[ \t\f\v]+(\d+[a-z]?|[IVX]+|[A-Z](?:\d+)?)(?:[\s:—\-]+(.*?))?[ \t\f\v]*$/gm;
44
+ /^(#{2,3})[ \t\f\v]+Phase[ \t\f\v]+(\d+(?:\.\d+)*[a-z]?|[IVX]+|[A-Z](?:\d+)?)(?:[\s:—\-]+(.*?))?[ \t\f\v]*$/gm;
44
45
  // FRONTMATTER_RE = re.compile(r"\A---\n(.*?)\n---\s*\n", re.DOTALL)
45
46
  const FRONTMATTER_RE = /^---\n([\s\S]*?)\n---\s*\n/;
46
47
  const DRAFT_VALUES: ReadonlySet<string> = new Set(['draft']);
@@ -0,0 +1,275 @@
1
+ #!/usr/bin/env tsx
2
+ /**
3
+ * Kernel semantic-invariants checker.
4
+ *
5
+ * Counterfactual (2026-07-10): PRs #840/#844 showed MERGED on GitHub but their
6
+ * content vanished from main (merge commits unreachable after a history repair);
7
+ * re-landed as #847 (13f244c9b — non-destructive "Irreversible external action"
8
+ * row + "Never act while asking" clause) and #849 (2c46e7f3b — direct-answers
9
+ * "no duration estimates" + "never cite the rule"). This checker's invariant set
10
+ * covers those exact strings — it would have flagged the loss at CI time.
11
+ *
12
+ * Loads `tests/golden/invariants.json` and verifies that every invariant
13
+ * string (whitespace-normalised: runs of whitespace collapse to a single
14
+ * space) is present in BOTH the kernel rule's `src/rules/` source and its
15
+ * `dist/agent-src/rules/` projection. A missing string means a rule's
16
+ * behaviour guarantee was silently lost in a merge / condensation pass.
17
+ *
18
+ * Kernel set: docs/contracts/kernel-membership.md § 4 (9 locked rules).
19
+ *
20
+ * Usage:
21
+ * npx tsx src/scripts/check_rule_invariants.ts [--root ROOT]
22
+ * npx tsx src/scripts/check_rule_invariants.ts --mutation-selftest
23
+ *
24
+ * `--mutation-selftest` picks the first invariant and verifies the check
25
+ * WOULD fail if that string were absent (in-memory mutation) — guards the
26
+ * checker itself against a silently-green normalisation bug.
27
+ *
28
+ * Exit codes: 0 = clean / selftest passed, 1 = missing invariant(s) or
29
+ * selftest failure, 2 = usage error, 3 = internal error.
30
+ */
31
+
32
+ import * as fs from 'node:fs';
33
+ import * as path from 'node:path';
34
+ import { fileURLToPath, pathToFileURL } from 'node:url';
35
+
36
+ // src/scripts/check_rule_invariants.ts → two dirs up is the repo root.
37
+ const _HERE = fileURLToPath(import.meta.url);
38
+ const ROOT = path.resolve(path.dirname(_HERE), '..', '..');
39
+
40
+ const INVARIANTS_JSON = 'tests/golden/invariants.json';
41
+
42
+ interface InvariantEntry {
43
+ rule: string;
44
+ file: string;
45
+ dist: string;
46
+ strings: string[];
47
+ }
48
+
49
+ interface Miss {
50
+ rule: string;
51
+ file: string;
52
+ invariant: string;
53
+ }
54
+
55
+ /** Collapse every run of whitespace (incl. newlines) to a single space. */
56
+ function normalize(text: string): string {
57
+ return text.replace(/\s+/g, ' ').trim();
58
+ }
59
+
60
+ function loadEntries(root: string): InvariantEntry[] {
61
+ const p = path.join(root, INVARIANTS_JSON);
62
+ const raw = fs.readFileSync(p, 'utf-8');
63
+ const parsed = JSON.parse(raw) as unknown;
64
+ if (!Array.isArray(parsed)) {
65
+ throw new Error(`${INVARIANTS_JSON}: expected a top-level array`);
66
+ }
67
+ for (const entry of parsed as InvariantEntry[]) {
68
+ if (
69
+ typeof entry.rule !== 'string' ||
70
+ typeof entry.file !== 'string' ||
71
+ typeof entry.dist !== 'string' ||
72
+ !Array.isArray(entry.strings) ||
73
+ entry.strings.some((s) => typeof s !== 'string' || s.length === 0)
74
+ ) {
75
+ throw new Error(
76
+ `${INVARIANTS_JSON}: malformed entry ${JSON.stringify(entry.rule ?? entry)}`,
77
+ );
78
+ }
79
+ }
80
+ return parsed as InvariantEntry[];
81
+ }
82
+
83
+ /** Check one entry against pre-normalised file contents. */
84
+ function checkEntry(
85
+ entry: InvariantEntry,
86
+ normalizedByFile: ReadonlyMap<string, string>,
87
+ ): Miss[] {
88
+ const misses: Miss[] = [];
89
+ for (const target of [entry.file, entry.dist]) {
90
+ const haystack = normalizedByFile.get(target);
91
+ if (haystack === undefined) {
92
+ for (const s of entry.strings) {
93
+ misses.push({ rule: entry.rule, file: target, invariant: s });
94
+ }
95
+ continue;
96
+ }
97
+ for (const s of entry.strings) {
98
+ if (!haystack.includes(normalize(s))) {
99
+ misses.push({ rule: entry.rule, file: target, invariant: s });
100
+ }
101
+ }
102
+ }
103
+ return misses;
104
+ }
105
+
106
+ function readNormalized(root: string, entries: readonly InvariantEntry[]): Map<string, string> {
107
+ const out = new Map<string, string>();
108
+ for (const entry of entries) {
109
+ for (const target of [entry.file, entry.dist]) {
110
+ if (out.has(target)) {
111
+ continue;
112
+ }
113
+ const p = path.join(root, target);
114
+ try {
115
+ out.set(target, normalize(fs.readFileSync(p, 'utf-8')));
116
+ } catch {
117
+ // Leave absent — checkEntry reports every string as missing.
118
+ }
119
+ }
120
+ }
121
+ return out;
122
+ }
123
+
124
+ function scan(root: string): Miss[] {
125
+ const entries = loadEntries(root);
126
+ const normalizedByFile = readNormalized(root, entries);
127
+ const misses: Miss[] = [];
128
+ for (const entry of entries) {
129
+ misses.push(...checkEntry(entry, normalizedByFile));
130
+ }
131
+ return misses;
132
+ }
133
+
134
+ function formatText(misses: Miss[], invariantCount: number): string {
135
+ if (misses.length === 0) {
136
+ return `✅ All ${invariantCount} kernel rule invariants present in src + dist.`;
137
+ }
138
+ const lines: string[] = [
139
+ `❌ ${misses.length} missing kernel rule invariant(s):\n`,
140
+ ];
141
+ for (const m of misses) {
142
+ lines.push(` 🔴 ${m.rule} — ${m.file}\n missing: "${m.invariant}"`);
143
+ }
144
+ lines.push(
145
+ '\nA kernel rule lost a load-bearing clause — likely a merge or condensation ' +
146
+ 'dropped it silently (see the #840/#844 incident in this file header). ' +
147
+ 'Restore the clause or deliberately update tests/golden/invariants.json.',
148
+ );
149
+ return lines.join('\n');
150
+ }
151
+
152
+ /**
153
+ * Mutation self-test: remove the first invariant string from its src file's
154
+ * content in-memory and verify the check catches the absence.
155
+ */
156
+ function mutationSelftest(root: string): number {
157
+ const entries = loadEntries(root);
158
+ const first = entries[0];
159
+ if (first === undefined || first.strings[0] === undefined) {
160
+ process.stderr.write('mutation-selftest: no invariants defined\n');
161
+ return 1;
162
+ }
163
+ const invariant = first.strings[0];
164
+ const p = path.join(root, first.file);
165
+ const original = normalize(fs.readFileSync(p, 'utf-8'));
166
+ const needle = normalize(invariant);
167
+ if (!original.includes(needle)) {
168
+ process.stderr.write(
169
+ `mutation-selftest: baseline broken — "${invariant}" not present in ${first.file}\n`,
170
+ );
171
+ return 1;
172
+ }
173
+ const mutated = original.split(needle).join('');
174
+ if (mutated.includes(needle)) {
175
+ process.stderr.write('mutation-selftest: mutation did not remove the string\n');
176
+ return 1;
177
+ }
178
+ // Run the REAL check path against the mutated content: swap the src
179
+ // file's normalised content in-memory and assert checkEntry reports
180
+ // the removed invariant as missing.
181
+ const normalizedByFile = readNormalized(root, [first]);
182
+ normalizedByFile.set(first.file, mutated);
183
+ const misses = checkEntry(first, normalizedByFile);
184
+ const detected = misses.some(
185
+ (m) => m.file === first.file && m.invariant === invariant,
186
+ );
187
+ if (!detected) {
188
+ process.stderr.write(
189
+ `mutation-selftest: FAILED — removing "${invariant}" from ${first.file} was NOT detected\n`,
190
+ );
191
+ return 1;
192
+ }
193
+ process.stdout.write(
194
+ `✅ mutation-selftest passed: removing "${invariant}" from ${first.file} is detected.\n`,
195
+ );
196
+ return 0;
197
+ }
198
+
199
+ interface ParsedArgs {
200
+ root: string;
201
+ mutationSelftest: boolean;
202
+ }
203
+
204
+ function parse_args(argv: readonly string[]): ParsedArgs {
205
+ let root = ROOT;
206
+ let selftest = false;
207
+ for (let i = 0; i < argv.length; i++) {
208
+ const arg = argv[i]!;
209
+ if (arg === '--root') {
210
+ const v = argv[++i];
211
+ if (v === undefined) {
212
+ _argparse_error('argument --root: expected one argument');
213
+ }
214
+ root = v;
215
+ } else if (arg.startsWith('--root=')) {
216
+ root = arg.slice('--root='.length);
217
+ } else if (arg === '--mutation-selftest') {
218
+ selftest = true;
219
+ } else if (arg === '-h' || arg === '--help') {
220
+ process.stdout.write(
221
+ 'usage: check_rule_invariants [-h] [--root ROOT] [--mutation-selftest]\n',
222
+ );
223
+ process.exit(0);
224
+ } else {
225
+ _argparse_error(`unrecognized arguments: ${arg}`);
226
+ }
227
+ }
228
+ return { root, mutationSelftest: selftest };
229
+ }
230
+
231
+ function _argparse_error(message: string): never {
232
+ process.stderr.write(`check_rule_invariants: error: ${message}\n`);
233
+ process.exit(2);
234
+ }
235
+
236
+ function main(): number {
237
+ const args = parse_args(process.argv.slice(2));
238
+ try {
239
+ if (args.mutationSelftest) {
240
+ return mutationSelftest(args.root);
241
+ }
242
+ const entries = loadEntries(args.root);
243
+ const invariantCount = entries.reduce((n, e) => n + e.strings.length, 0);
244
+ const misses = scan(args.root);
245
+ process.stdout.write(formatText(misses, invariantCount) + '\n');
246
+ return misses.length ? 1 : 0;
247
+ } catch (e) {
248
+ const msg = e instanceof Error ? e.message : String(e);
249
+ process.stderr.write(`Internal error: ${msg}\n`);
250
+ return 3;
251
+ }
252
+ }
253
+
254
+ function _isCliEntry(): boolean {
255
+ if (process.argv[1] === undefined) {
256
+ return false;
257
+ }
258
+ const argvUrl = pathToFileURL(path.resolve(process.argv[1])).href;
259
+ if (import.meta.url === argvUrl) {
260
+ return true;
261
+ }
262
+ try {
263
+ const here = fs.realpathSync(fileURLToPath(import.meta.url));
264
+ const argv = fs.realpathSync(path.resolve(process.argv[1]));
265
+ return here === argv;
266
+ } catch {
267
+ return false;
268
+ }
269
+ }
270
+
271
+ if (_isCliEntry() || process.argv[1] === _HERE) {
272
+ process.exit(main());
273
+ }
274
+
275
+ export { type InvariantEntry, type Miss, ROOT, INVARIANTS_JSON, normalize, scan, main };
@@ -40,6 +40,7 @@ import {
40
40
  strip_source_prefix,
41
41
  } from './_lib/agent_src.js';
42
42
  import { build_claude_hook_matrix } from './_lib/claude_settings_hooks.js';
43
+ import { is_claude_builtin_name } from './_lib/claude_builtin_names.js';
43
44
  import { project_settings_path, load_agent_settings } from './_lib/agent_settings.js';
44
45
  import { info, success, flush_summary, resolve_level } from './_lib/script_output.js';
45
46
  import {
@@ -1529,6 +1530,7 @@ export function generate_claude_commands(active_command_slugs: ReadonlySet<strin
1529
1530
  const current_slugs = new Set<string>();
1530
1531
  let count = 0;
1531
1532
  let skipped = 0;
1533
+ let reserved = 0;
1532
1534
  let rendered = 0;
1533
1535
  const auto = _read_model_auto_switch() === 'auto';
1534
1536
  for (const [source_file, slug] of iterCommands()) {
@@ -1539,6 +1541,15 @@ export function generate_claude_commands(active_command_slugs: ReadonlySet<strin
1539
1541
  skipped += 1;
1540
1542
  continue;
1541
1543
  }
1544
+ // Never claim a `/name` that is a Claude Code built-in command or
1545
+ // bundled skill — a same-name entry shadows the built-in (e.g. a
1546
+ // `review` entry hides Claude Code's own /review). Nested
1547
+ // `/cluster:sub` slugs are hyphen-joined and cannot collide.
1548
+ // See src/scripts/_lib/claude_builtin_names.ts.
1549
+ if (is_claude_builtin_name(slug)) {
1550
+ reserved += 1;
1551
+ continue;
1552
+ }
1542
1553
  current_slugs.add(slug);
1543
1554
 
1544
1555
  const skill_dir = path.join(MODULE_STATE.CLAUDE_SKILLS_DIR, slug);
@@ -1588,6 +1599,9 @@ export function generate_claude_commands(active_command_slugs: ReadonlySet<strin
1588
1599
  if (skipped) {
1589
1600
  msg += ` (${skipped} skipped — same-name skill exists)`;
1590
1601
  }
1602
+ if (reserved) {
1603
+ msg += ` (${reserved} withheld — Claude Code built-in name)`;
1604
+ }
1591
1605
  if (removed_dirs) {
1592
1606
  msg += ` (${removed_dirs} stale dirs removed)`;
1593
1607
  }