@event4u/agent-config 8.8.0 → 8.9.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 (131) hide show
  1. package/.claude-plugin/marketplace.json +1 -1
  2. package/CHANGELOG.md +109 -274
  3. package/README.md +2 -2
  4. package/dist/agent-src/commands/agent-handoff.md +39 -2
  5. package/dist/agent-src/commands/research/deep.md +8 -0
  6. package/dist/agent-src/commands/research/report.md +7 -0
  7. package/dist/agent-src/commands/roadmap/create.md +12 -0
  8. package/dist/agent-src/commands/roadmap/process-full.md +31 -5
  9. package/dist/agent-src/contexts/communication/rules-auto/think-before-action-mechanics.md +30 -1
  10. package/dist/agent-src/contexts/execution/orchestration-telemetry.md +21 -5
  11. package/dist/agent-src/contexts/execution/roadmap-process-loop.md +37 -0
  12. package/dist/agent-src/contexts/execution/subagent-topologies.md +34 -0
  13. package/dist/agent-src/contexts/execution/verification-mechanics.md +9 -9
  14. package/dist/agent-src/rules/agent-authority.md +1 -1
  15. package/dist/agent-src/rules/communication-through-line.md +1 -1
  16. package/dist/agent-src/rules/delegation-policy.md +5 -0
  17. package/dist/agent-src/rules/direct-answers.md +1 -1
  18. package/dist/agent-src/rules/downstream-changes.md +1 -0
  19. package/dist/agent-src/rules/external-code-graph-interop.md +65 -0
  20. package/dist/agent-src/rules/git-history-discipline.md +18 -0
  21. package/dist/agent-src/rules/lethal-trifecta-guard.md +6 -1
  22. package/dist/agent-src/rules/minimal-safe-diff.md +58 -10
  23. package/dist/agent-src/rules/non-destructive-by-default.md +3 -0
  24. package/dist/agent-src/rules/question-not-instruction.md +76 -0
  25. package/dist/agent-src/rules/senior-engineering-discipline.md +1 -1
  26. package/dist/agent-src/rules/think-before-action.md +1 -0
  27. package/dist/agent-src/rules/tool-safety.md +8 -0
  28. package/dist/agent-src/rules/untrusted-input-defense.md +87 -22
  29. package/dist/agent-src/scripts/update_roadmap_progress.ts +2 -1
  30. package/dist/agent-src/skills/accessibility-auditor/SKILL.md +19 -0
  31. package/dist/agent-src/skills/ai-code-blindspots/SKILL.md +10 -3
  32. package/dist/agent-src/skills/code-review/SKILL.md +2 -0
  33. package/dist/agent-src/skills/command-writing/SKILL.md +53 -40
  34. package/dist/agent-src/skills/dcf-modeling/evals/domain-truth.json +29 -0
  35. package/dist/agent-src/skills/deep-reading-analyst/SKILL.md +1 -0
  36. package/dist/agent-src/skills/design-intelligence/SKILL.md +49 -12
  37. package/dist/agent-src/skills/design-review/SKILL.md +29 -15
  38. package/dist/agent-src/skills/design-system-capture/SKILL.md +63 -24
  39. package/dist/agent-src/skills/forecasting/evals/domain-truth.json +29 -0
  40. package/dist/agent-src/skills/frontend-render-security/SKILL.md +2 -2
  41. package/dist/agent-src/skills/markitdown/SKILL.md +1 -0
  42. package/dist/agent-src/skills/mcp/SKILL.md +24 -0
  43. package/dist/agent-src/skills/mcp-builder/SKILL.md +9 -1
  44. package/dist/agent-src/skills/memory-consolidation/SKILL.md +55 -4
  45. package/dist/agent-src/skills/roadmap-writing/SKILL.md +39 -5
  46. package/dist/agent-src/skills/runway-cognition/evals/domain-truth.json +29 -0
  47. package/dist/agent-src/skills/scenario-modeling/evals/domain-truth.json +29 -0
  48. package/dist/agent-src/skills/secrets-management/SKILL.md +7 -0
  49. package/dist/agent-src/skills/skill-writing/SKILL.md +12 -3
  50. package/dist/agent-src/skills/subagent-orchestration/SKILL.md +27 -33
  51. package/dist/agent-src/skills/supply-chain-intake/SKILL.md +12 -0
  52. package/dist/agent-src/skills/testing-anti-patterns/SKILL.md +29 -0
  53. package/dist/agent-src/skills/ui-component-architect/SKILL.md +9 -0
  54. package/dist/agent-src/skills/unit-economics-modeling/evals/domain-truth.json +40 -0
  55. package/dist/agent-src/templates/agents/agent-project-settings.example.yml +1 -1
  56. package/dist/agent-src/templates/roadmaps.md +36 -0
  57. package/dist/cli/registry.js +3 -0
  58. package/dist/cli/registry.js.map +1 -1
  59. package/dist/discovery/deprecation-report.md +1 -1
  60. package/dist/discovery/discovery-manifest.json +90 -48
  61. package/dist/discovery/discovery-manifest.json.sha256 +1 -1
  62. package/dist/discovery/discovery-manifest.summary.md +4 -4
  63. package/dist/discovery/orphan-report.md +1 -1
  64. package/dist/discovery/packs.json +11 -9
  65. package/dist/discovery/trust-report.md +2 -2
  66. package/dist/discovery/workspaces.json +9 -7
  67. package/dist/install/install.mjs +136 -1
  68. package/dist/mcp/registry-manifest.json +3 -3
  69. package/dist/router.json +1 -1
  70. package/dist/scripts/_lib/claude_builtin_names.js +145 -0
  71. package/dist/scripts/_lib/claude_builtin_names.js.map +1 -0
  72. package/dist/scripts/condense.js +14 -0
  73. package/dist/scripts/condense.js.map +1 -1
  74. package/docs/CLAIMS.md +34 -6
  75. package/docs/architecture.md +2 -2
  76. package/docs/archive/CHANGELOG-pre-8.9.0.md +306 -0
  77. package/docs/benchmark.md +113 -0
  78. package/docs/catalog.md +20 -7
  79. package/docs/contracts/command-clusters.md +18 -0
  80. package/docs/contracts/design-artifact-verification.md +124 -0
  81. package/docs/contracts/injected-block-authenticity-model.md +66 -0
  82. package/docs/contracts/mcp-tool-inventory.md +28 -28
  83. package/docs/decisions/ADR-061-corpus-grounding-layer.md +13 -0
  84. package/docs/decisions/ADR-116-memory-tripwire-activation-path.md +9 -0
  85. package/docs/getting-started-by-role.md +1 -1
  86. package/docs/getting-started.md +1 -1
  87. package/docs/governance-advantage.md +1 -1
  88. package/docs/guidelines/agent-infra/asking-and-brevity-examples.md +27 -0
  89. package/docs/guidelines/agent-infra/emphasis-budget.md +45 -0
  90. package/docs/guidelines/agent-infra/simplicity-and-goal-demos.md +151 -0
  91. package/docs/guidelines/agent-infra/tool-description-as-policy.md +35 -0
  92. package/docs/guidelines/design-canon.md +1 -0
  93. package/docs/proof.md +36 -21
  94. package/docs/second-brain-scope.md +69 -26
  95. package/package.json +1 -1
  96. package/src/agent-src/scripts/update_roadmap_progress.ts +2 -1
  97. package/src/scripts/_dispatch.bash +27 -0
  98. package/src/scripts/_lib/claude_builtin_names.ts +148 -0
  99. package/src/scripts/_lib/file_slicer.ts +117 -0
  100. package/src/scripts/_lib/lexical_index.ts +155 -0
  101. package/src/scripts/_lib/orchestration_record.ts +170 -0
  102. package/src/scripts/_lib/orchestration_savings.ts +62 -9
  103. package/src/scripts/_lib/retrieval_sanitize.ts +71 -0
  104. package/src/scripts/_lib/security_lint.ts +1 -0
  105. package/src/scripts/_lib/stat_index.ts +98 -0
  106. package/src/scripts/benchmark.ts +122 -0
  107. package/src/scripts/build_proof.ts +30 -17
  108. package/src/scripts/check_memory.ts +2 -0
  109. package/src/scripts/check_quality_regression.ts +51 -0
  110. package/src/scripts/check_roadmap_trackable.ts +3 -2
  111. package/src/scripts/condense.ts +14 -0
  112. package/src/scripts/discovery_graph.ts +0 -0
  113. package/src/scripts/external_sources_denylist.json +4 -1
  114. package/src/scripts/generate_index.ts +5 -2
  115. package/src/scripts/install-hooks.sh +25 -0
  116. package/src/scripts/install.ts +35 -2
  117. package/src/scripts/learning_sidecar.ts +0 -0
  118. package/src/scripts/lint_agent_skill_names.ts +29 -0
  119. package/src/scripts/lint_knowledge_scale.ts +4 -3
  120. package/src/scripts/lint_roadmap_complexity.ts +124 -0
  121. package/src/scripts/lint_versioned_cache.ts +195 -0
  122. package/src/scripts/mcp_server/consumer_tool_catalog.json +2 -1
  123. package/src/scripts/mcp_server/tools.ts +24 -1
  124. package/src/scripts/measure_lexical_ranking.ts +173 -0
  125. package/src/scripts/memory_lookup.ts +0 -0
  126. package/src/scripts/orchestration_record.ts +140 -0
  127. package/src/scripts/orchestration_savings_report.ts +29 -5
  128. package/src/scripts/schemas/skill.schema.json +4 -0
  129. package/src/scripts/score_domain_truth.ts +192 -0
  130. package/src/scripts/second_brain_retrieval.ts +278 -0
  131. package/src/scripts/second_brain_run.ts +341 -0
@@ -0,0 +1,122 @@
1
+ #!/usr/bin/env tsx
2
+ /**
3
+ * `agent-config benchmark` — self-measuring token-reduction report
4
+ * (road-to-retrieval-substrate-hardening B7a).
5
+ *
6
+ * Reports the context-token reduction of the retrieval economy against the
7
+ * REAL alternative: the FULL always-loaded projection (council Q4) — the
8
+ * honest "what the user pays if everything loads eagerly + full descriptions".
9
+ * NOT a synthetic full-corpus strawman (explicitly rejected).
10
+ *
11
+ * Baseline components (from the pinned internal/bench/reports/token-baseline.json):
12
+ * eager_rule_load + skill_descriptions + command_descriptions + mcp_schemas
13
+ * Reduced: eager→thin rule projection, descriptions/schemas unchanged.
14
+ *
15
+ * Deterministic (reads the pinned baseline). Every emitted number binds to
16
+ * docs/CLAIMS.md with a method line (council Q4 hard condition).
17
+ *
18
+ * Usage: benchmark.ts [--baseline PATH] [--format text|json]
19
+ * Exit codes: 0 ok, 1 baseline missing/malformed, 2 usage error.
20
+ */
21
+ import * as fs from 'node:fs';
22
+ import * as path from 'node:path';
23
+ import process from 'node:process';
24
+ import { fileURLToPath } from 'node:url';
25
+
26
+ const PROG = 'benchmark.ts';
27
+ const _HERE = fileURLToPath(import.meta.url);
28
+ export const REPO_ROOT = path.resolve(path.dirname(_HERE), '..', '..');
29
+ export const BASELINE = path.join(REPO_ROOT, 'internal', 'bench', 'reports', 'token-baseline.json');
30
+
31
+ interface Metrics {
32
+ eager_rule_load: number;
33
+ thin_rule_load: number;
34
+ skill_descriptions: number;
35
+ command_descriptions: number;
36
+ mcp_schemas: number;
37
+ }
38
+ export interface BenchmarkReport {
39
+ baseline_full_projection: number;
40
+ reduced_projection: number;
41
+ saved: number;
42
+ reduction_ratio: number; // 0..1
43
+ method: string;
44
+ components: Metrics;
45
+ }
46
+
47
+ function _num(v: unknown): number {
48
+ return typeof v === 'number' && Number.isFinite(v) ? v : 0;
49
+ }
50
+
51
+ /** Compute the reduction report from a token-baseline metrics object. */
52
+ export function computeReport(metrics: Metrics): BenchmarkReport {
53
+ const shared = metrics.skill_descriptions + metrics.command_descriptions + metrics.mcp_schemas;
54
+ const full = metrics.eager_rule_load + shared;
55
+ const reduced = metrics.thin_rule_load + shared;
56
+ const saved = full - reduced;
57
+ const ratio = full > 0 ? Math.round((saved / full) * 10000) / 10000 : 0;
58
+ return {
59
+ baseline_full_projection: full,
60
+ reduced_projection: reduced,
61
+ saved,
62
+ reduction_ratio: ratio,
63
+ method:
64
+ 'Baseline = FULL always-loaded projection (eager rule load + skill/command ' +
65
+ 'descriptions + MCP schemas) from token-baseline.json; reduced = thin rule ' +
66
+ 'projection, shared surfaces unchanged. Not a synthetic strawman (council Q4).',
67
+ components: metrics,
68
+ };
69
+ }
70
+
71
+ export function loadMetrics(baselinePath: string): Metrics {
72
+ const parsed = JSON.parse(fs.readFileSync(baselinePath, 'utf-8')) as { metrics?: Record<string, unknown> };
73
+ const m = parsed.metrics ?? {};
74
+ return {
75
+ eager_rule_load: _num(m['eager_rule_load']),
76
+ thin_rule_load: _num(m['thin_rule_load']),
77
+ skill_descriptions: _num(m['skill_descriptions']),
78
+ command_descriptions: _num(m['command_descriptions']),
79
+ mcp_schemas: _num(m['mcp_schemas']),
80
+ };
81
+ }
82
+
83
+ export function main(argv: string[]): number {
84
+ let baselinePath = BASELINE;
85
+ let format: 'text' | 'json' = 'text';
86
+ for (let i = 0; i < argv.length; i++) {
87
+ const a = argv[i] as string;
88
+ if (a === '--baseline') baselinePath = argv[++i] ?? baselinePath;
89
+ else if (a === '--format') format = (argv[++i] as 'text' | 'json') ?? 'text';
90
+ else if (a === '-h' || a === '--help') {
91
+ process.stdout.write(`usage: ${PROG} [--baseline PATH] [--format text|json]\n`);
92
+ return 0;
93
+ } else {
94
+ process.stderr.write(`${PROG}: error: unknown argument ${a}\n`);
95
+ return 2;
96
+ }
97
+ }
98
+ let report: BenchmarkReport;
99
+ try {
100
+ report = computeReport(loadMetrics(baselinePath));
101
+ } catch (exc) {
102
+ process.stderr.write(`${PROG}: cannot read baseline ${baselinePath}: ${String(exc)}\n`);
103
+ return 1;
104
+ }
105
+ if (format === 'json') {
106
+ process.stdout.write(JSON.stringify(report, null, 2) + '\n');
107
+ return 0;
108
+ }
109
+ const pct = (report.reduction_ratio * 100).toFixed(1);
110
+ process.stdout.write(
111
+ `context token reduction (vs full always-loaded projection):\n` +
112
+ ` full projection ${report.baseline_full_projection} tokens\n` +
113
+ ` reduced (thin) ${report.reduced_projection} tokens\n` +
114
+ ` saved ${report.saved} tokens (${pct}%)\n` +
115
+ ` method ${report.method}\n`,
116
+ );
117
+ return 0;
118
+ }
119
+
120
+ if (fileURLToPath(import.meta.url) === path.resolve(process.argv[1] ?? '')) {
121
+ process.exit(main(process.argv.slice(2)));
122
+ }
@@ -26,6 +26,7 @@ import { fileURLToPath, pathToFileURL } from 'node:url';
26
26
  import { REPO, LEDGER_REL, load_ledger, pointer_unresolved } from './check_claims.js';
27
27
  import { collectSkillGaps } from './check_skill_gaps.js';
28
28
  import { loadRows as loadComparisonRows } from './check_comparison.js';
29
+ import { computeStatus as domainSoundnessStatus } from './domain_soundness_status.js';
29
30
 
30
31
  const _FILE = fileURLToPath(import.meta.url);
31
32
  const OUT_REL = 'docs/proof.md';
@@ -118,30 +119,42 @@ function render(): string {
118
119
  L.push('checks the wrong property is worse than none), so the number grows');
119
120
  L.push('deliberately, not overnight.');
120
121
  L.push('');
122
+ const ds = domainSoundnessStatus();
121
123
  L.push('**Non-coding domain soundness — scoped, not proven.** The `finance` /');
122
124
  L.push('`founder` / `ops` / `content` profiles sell concrete domain value (DCF,');
123
125
  L.push('runway, RICE, incident command, messaging), but the skills are forged on');
124
126
  L.push('TS/PHP — "promising, not proven" off those stacks. A disclaimer floor');
125
127
  L.push('bounds *liability*, not *correctness*: a skill can be format-correct,');
126
- L.push('disclaimered, and still embed a wrong domain assumption. Today **0 of 20**');
128
+ L.push(`disclaimered, and still embed a wrong domain assumption. Today **${ds.validated} of ${ds.total}**`);
127
129
  L.push('default-surface domain skills carry a sourced `domain-truth` fixture');
128
- L.push('(`./scripts-run src/scripts/domain_soundness_status`); all are labeled');
129
- L.push('`unvalidated` and the validated count is CI-ratcheted. Authoring the');
130
- L.push('answer keys needs domain competence (a cited method, not the skill\'s own');
131
- L.push('output), so validation lands deliberately — the gap is published, never');
132
- L.push('implied away.');
130
+ L.push('(`./scripts-run src/scripts/domain_soundness_status`); the rest are labeled');
131
+ L.push('`unvalidated` and the validated count is CI-ratcheted. The fixtures landed');
132
+ L.push('so far are the **deterministic** targets (runway, unit-economics, DCF,');
133
+ L.push('forecasting, scenario band/sensitivity), whose answer keys are computed');
134
+ L.push('from cited standard formulas — never the skill\'s own output; the');
135
+ L.push('**rubric** targets (incident command, messaging, fundraising, editorial)');
136
+ L.push('need domain-competent grounding and remain unvalidated, so validation');
137
+ L.push('lands deliberately — the gap is published, never implied away.');
133
138
  L.push('');
134
- L.push('**Second-brain substrate — continuity convenience, task-lift unmeasured.**');
135
- L.push('The memory substrate is built (working-memory continuity across');
136
- L.push('compaction, promotable knowledge cards, contradiction surfacing), but');
137
- L.push('whether it beats a no-memory baseline on a reproducible task is **not yet');
138
- L.push('measured**. The measurement rig exists — a deterministic multi-session');
139
- L.push('recall corpus + scorer (`./scripts-run src/scripts/second_brain_score');
140
- L.push('--dry-run`, no model-in-the-loop grading) — but the paired');
141
- L.push('`memory-on`/`off`/`placebo` run is spend-bearing and pending. So no');
142
- L.push('"second brain" capability claim is made anywhere; the substrate ships as');
143
- L.push('continuity convenience until a paired run backs a lift. Boundary vs a');
144
- L.push('human PKM is stated in [`docs/second-brain-scope.md`](second-brain-scope.md).');
139
+ L.push('**Second-brain substrate — measured recall lift, honestly bounded.** On a');
140
+ L.push('deterministic multi-session recall corpus, the memory substrate beats a');
141
+ L.push('no-memory baseline AND an equal-byte placebo: memory-on **27/27** vs');
142
+ L.push('no-memory **10/27** vs placebo **9/27** (claude-haiku-4-5, 9 tasks × 3');
143
+ L.push('seeds, sign test **p = 0.031** for both pairings) — a real,');
144
+ L.push('placebo-controlled lift (`internal/bench/reports/second-brain-delta.json`).');
145
+ L.push('Scoped, not oversold: this is the context-value upper bound (perfect');
146
+ L.push('retrieval on a one-fact-per-task corpus), NOT retrieval precision under a');
147
+ L.push('large store; the lift concentrates exactly where memory is the only source');
148
+ L.push('and ties where the prompt self-contains the fact. Boundary vs a human PKM,');
149
+ L.push('and why the Obsidian export stays declined, are in');
150
+ L.push('[`docs/second-brain-scope.md`](second-brain-scope.md).');
151
+ L.push('');
152
+ L.push('A follow-up removed the perfect-retrieval assumption: against a store of');
153
+ L.push('keyword-overlapping confusers the REAL retrieval recalls the needed');
154
+ L.push('decision into the top-5 (9/9) and the model disambiguates it — retrieval-on');
155
+ L.push('**27/27** vs no-memory **5/27** and vs placebo **5/27** (p=0.008 both). The');
156
+ L.push('honest limit: the keyword scorer recalls but does not rank (mean tie-set');
157
+ L.push('3.3), which is the SQLite-FTS5 activation signal (ADR-116) at scale.');
145
158
  L.push('');
146
159
  L.push('## 3. Known limits (published, witness-tested)');
147
160
  L.push('');
@@ -81,7 +81,9 @@ const CRITICAL_WARN_THRESHOLD = 10;
81
81
  const CRITICAL_STALE_DAYS = 90;
82
82
  const KNOWN_TYPES: ReadonlySet<string> = new Set([
83
83
  'domain-invariants',
84
+ 'historical-patterns',
84
85
  'incident-learnings',
86
+ 'ownership',
85
87
  'product-rules',
86
88
  ]);
87
89
 
@@ -108,6 +108,57 @@ export function evaluatePair(
108
108
  return { id: task.id, winner, length_delta, winner_is_longer };
109
109
  }
110
110
 
111
+ /**
112
+ * Cohen's Kappa — chance-corrected agreement between two raters over the same
113
+ * categorical labels (road-to-retrieval-substrate-hardening B7b). 1 = perfect,
114
+ * 0 = chance-level, <0 = worse than chance. `NaN` when there is no data.
115
+ */
116
+ export function cohensKappa(labelsA: readonly string[], labelsB: readonly string[]): number {
117
+ const n = Math.min(labelsA.length, labelsB.length);
118
+ if (n === 0) return Number.NaN;
119
+ const cats = new Set<string>();
120
+ for (let i = 0; i < n; i++) {
121
+ cats.add(labelsA[i] as string);
122
+ cats.add(labelsB[i] as string);
123
+ }
124
+ let observedAgree = 0;
125
+ const countA = new Map<string, number>();
126
+ const countB = new Map<string, number>();
127
+ for (let i = 0; i < n; i++) {
128
+ const a = labelsA[i] as string;
129
+ const b = labelsB[i] as string;
130
+ if (a === b) observedAgree += 1;
131
+ countA.set(a, (countA.get(a) ?? 0) + 1);
132
+ countB.set(b, (countB.get(b) ?? 0) + 1);
133
+ }
134
+ const po = observedAgree / n;
135
+ let pe = 0;
136
+ for (const c of cats) pe += ((countA.get(c) ?? 0) / n) * ((countB.get(c) ?? 0) / n);
137
+ if (pe === 1) return 1; // both raters unanimous on one label → perfect by convention
138
+ return Math.round(((po - pe) / (1 - pe)) * 10000) / 10000;
139
+ }
140
+
141
+ /**
142
+ * Second-independent-judge validation: Cohen's Kappa over the two judges'
143
+ * per-pair winner labels (aligned by task id). A LOW kappa means the win-rate
144
+ * verdict rests on an unreliable grader — the trustworthiness gap the
145
+ * token-saving live run surfaced (33% judge-inconsistency), distinct from the
146
+ * effect-significance the Wilcoxon/McNemar gates already test.
147
+ */
148
+ export function judgeKappa(resultsA: readonly PairResult[], resultsB: readonly PairResult[]): number {
149
+ const byIdB = new Map(resultsB.map((r) => [r.id, r.winner]));
150
+ const a: string[] = [];
151
+ const b: string[] = [];
152
+ for (const r of resultsA) {
153
+ const bw = byIdB.get(r.id);
154
+ if (bw !== undefined) {
155
+ a.push(r.winner);
156
+ b.push(bw);
157
+ }
158
+ }
159
+ return cohensKappa(a, b);
160
+ }
161
+
111
162
  export interface QualityAggregate {
112
163
  total: number;
113
164
  thin_wins: number;
@@ -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']);
@@ -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
  }
Binary file
@@ -54,7 +54,10 @@
54
54
  "gztchan",
55
55
  "amaancoderx",
56
56
  "npxskillui",
57
- "\\bskillui\\b"
57
+ "\\bskillui\\b",
58
+ "\\bgraphify\\b",
59
+ "graphify-labs",
60
+ "penpax"
58
61
  ],
59
62
  "skip_paths": [
60
63
  "src/scripts/check_no_external_sources.py",
@@ -24,6 +24,7 @@ import {
24
24
  iter_commands,
25
25
  strip_source_prefix,
26
26
  } from './_lib/agent_src.js';
27
+ import { computeStatus as domainSoundnessStatus } from './domain_soundness_status.js';
27
28
 
28
29
  const _HERE = fileURLToPath(import.meta.url);
29
30
 
@@ -398,6 +399,7 @@ export function _render_catalog(
398
399
  const publicRules = rules.filter((r) => !INTERNAL_RULES.has(r.name));
399
400
  const publicCommands = commands.filter((c) => c.kind === 'command');
400
401
  const total = skills.length + publicRules.length + publicCommands.length + guidelines.length;
402
+ const ds = domainSoundnessStatus();
401
403
  const parts = [
402
404
  '# agent-config — Public Catalog',
403
405
  '',
@@ -419,8 +421,9 @@ export function _render_catalog(
419
421
  '',
420
422
  '> **Non-coding domain correctness is scoped, not proven.** The',
421
423
  '> `finance` / `founder` / `ops` / `content` skills are forged on TS/PHP',
422
- '> codebases; their embedded domain heuristics are labeled `unvalidated`',
423
- '> until they pass a sourced `evals/domain-truth.json` fixture',
424
+ `> codebases; ${ds.validated} of ${ds.total} default-surface domain skills carry a sourced`,
425
+ '> `evals/domain-truth.json` fixture (deterministic targets, keys from',
426
+ '> cited formulas); the rest are labeled `unvalidated` until they pass one',
424
427
  '> (`./scripts-run src/scripts/domain_soundness_status`). A disclaimer',
425
428
  '> floor bounds liability, not correctness — see [the proof page](proof.md).',
426
429
  '',
@@ -42,6 +42,31 @@ if ! ./scripts-run src/scripts/check_no_conflict_markers --quiet; then
42
42
  fail=1
43
43
  fi
44
44
 
45
+ # Changed-files static pass (typecheck + lint) — the deterministic backstop to
46
+ # the behavioural verify-before-complete rule (#818). Catches the compile/lint
47
+ # errors that reach remote CI "Static Checks" (e.g. TS18048). Runs only when
48
+ # the push touches .ts, so docs-only pushes stay fast. Skip a genuine WIP push
49
+ # with AGENT_CONFIG_SKIP_PREPUSH_STATIC=1 (the agent cannot use --no-verify).
50
+ echo "🔍 Static check on changed TypeScript (typecheck + lint)..."
51
+ if [ "${AGENT_CONFIG_SKIP_PREPUSH_STATIC:-}" = "1" ]; then
52
+ echo "⏭️ skipped via AGENT_CONFIG_SKIP_PREPUSH_STATIC=1"
53
+ else
54
+ base="$(git merge-base HEAD origin/main 2>/dev/null || echo HEAD~1)"
55
+ changed_ts="$(git diff --name-only --diff-filter=d "$base"...HEAD -- '*.ts' 2>/dev/null || true)"
56
+ if [ -n "$changed_ts" ]; then
57
+ if ! npx eslint $changed_ts; then
58
+ echo "❌ ESLint failed on changed TypeScript. Fix before pushing (or AGENT_CONFIG_SKIP_PREPUSH_STATIC=1 for a WIP push)."
59
+ fail=1
60
+ fi
61
+ if ! npm run --silent typecheck; then
62
+ echo "❌ Typecheck (tsc) failed — this is the class of error remote CI 'Static Checks' catches (#818). Fix before pushing."
63
+ fail=1
64
+ fi
65
+ else
66
+ echo "⏭️ no changed .ts vs origin/main — skipping."
67
+ fi
68
+ fi
69
+
45
70
  if [ $fail -ne 0 ]; then
46
71
  echo ""
47
72
  echo " Push blocked — fix the failures above and re-push."
@@ -72,6 +72,7 @@ import { fileURLToPath, pathToFileURL } from 'node:url';
72
72
  import type * as YamlModule from 'yaml';
73
73
 
74
74
  import { build_merge_entries } from './_lib/json_pointers.js';
75
+ import { is_claude_builtin_name } from './_lib/claude_builtin_names.js';
75
76
  import * as installed_lock from './_lib/installed_lock.js';
76
77
  import * as surface_tiers from './_lib/surface_tiers.js';
77
78
  import * as global_deploy_inventory from './_lib/global_deploy_inventory.js';
@@ -2847,15 +2848,40 @@ function _deploy_claude_desktop(
2847
2848
  * discovery (revert = one package release, no per-machine gating).
2848
2849
  * Tier-2/internal flat commands stay command files. A command whose name
2849
2850
  * collides with a real skill is skipped (the skill already owns `/name`).
2851
+ *
2852
+ * Reserved-name floor: a flat `/name` that equals a Claude Code BUILT-IN
2853
+ * command or bundled skill (see `_lib/claude_builtin_names.ts`) would shadow
2854
+ * the built-in — e.g. a `review` wrapper hides Claude Code's own `/review`.
2855
+ * Such slugs get NEITHER a skill wrapper NOR a flat command file on this
2856
+ * anchor (their nested `/cluster:sub` commands are untouched); the flat file
2857
+ * is removed so a future upstream flat-discovery fix cannot resurrect the
2858
+ * shadowing. The suite complements the host — it never overlays built-ins.
2850
2859
  */
2851
2860
  const _CLAUDE_FLAT_WRAPPER_EXTRA = new Set(['commit']); // essential; absent from the manifest
2852
2861
  export function _apply_claude_flat_command_wrappers(
2853
2862
  anchor: string,
2854
2863
  package_root: string,
2855
2864
  current_files: Set<string>,
2856
- ): { wrapped: string[]; collisions: string[] } {
2865
+ ): { wrapped: string[]; collisions: string[]; reserved: string[] } {
2857
2866
  const wrapped: string[] = [];
2858
2867
  const collisions: string[] = [];
2868
+ const reserved: string[] = [];
2869
+ // Reserved-name sweep over ALL flat command files (any tier): a name
2870
+ // shadowing a Claude Code built-in never ships on the claude-code anchor.
2871
+ const commands_dir = path.join(anchor, 'commands');
2872
+ let flat_entries: string[] = [];
2873
+ try {
2874
+ flat_entries = fs.readdirSync(commands_dir).filter((f) => f.endsWith('.md'));
2875
+ } catch {
2876
+ // No commands dir → nothing to sweep.
2877
+ }
2878
+ for (const fname of flat_entries.sort()) {
2879
+ const slug = fname.slice(0, -'.md'.length);
2880
+ if (!is_claude_builtin_name(slug)) continue;
2881
+ fs.rmSync(path.join(commands_dir, fname), { force: true });
2882
+ current_files.delete(`commands/${fname}`);
2883
+ reserved.push(slug);
2884
+ }
2859
2885
  // Tier-0/1 visible command slugs from the locked discovery manifest.
2860
2886
  const eligible = new Set<string>(_CLAUDE_FLAT_WRAPPER_EXTRA);
2861
2887
  try {
@@ -2892,7 +2918,7 @@ export function _apply_claude_flat_command_wrappers(
2892
2918
  current_files.add(`skills/${slug}/SKILL.md`);
2893
2919
  wrapped.push(slug);
2894
2920
  }
2895
- return { wrapped, collisions };
2921
+ return { wrapped, collisions, reserved };
2896
2922
  }
2897
2923
 
2898
2924
  function _deploy_global_content(
@@ -2949,6 +2975,13 @@ function _deploy_global_content(
2949
2975
  'skill wrappers (Claude Code flat-command discovery workaround)',
2950
2976
  );
2951
2977
  }
2978
+ if (res.reserved.length > 0 && !state.QUIET) {
2979
+ info(
2980
+ ` claude-code: ${res.reserved.length} flat command(s) withheld — name is a ` +
2981
+ `Claude Code built-in (${res.reserved.join(', ')}); nested /cluster:sub ` +
2982
+ 'commands remain available',
2983
+ );
2984
+ }
2952
2985
  }
2953
2986
 
2954
2987
  const missing_targets = _verify_deploy_targets(anchor, plan);
Binary file
@@ -14,6 +14,7 @@ import * as path from 'node:path';
14
14
  import { fileURLToPath, pathToFileURL } from 'node:url';
15
15
 
16
16
  import { command_slug } from './_lib/agent_src.js';
17
+ import { is_claude_builtin_name } from './_lib/claude_builtin_names.js';
17
18
 
18
19
  const SCRIPTS_DIR = path.dirname(fileURLToPath(import.meta.url));
19
20
  const ROOT = path.dirname(path.dirname(SCRIPTS_DIR));
@@ -97,6 +98,18 @@ function frontmatterNameFromFile(p: string): string | null {
97
98
  return _frontmatter_name(fs.readFileSync(p, 'utf-8'));
98
99
  }
99
100
 
101
+ const USER_INVOCABLE_FALSE_RE = /^user-invocable:\s*false\s*$/m;
102
+
103
+ /** True when the SKILL.md opts out of slash registration (`user-invocable: false`). */
104
+ export function _opts_out_of_slash(text: string): boolean {
105
+ if (!text.startsWith('---')) {
106
+ return false;
107
+ }
108
+ const end = text.indexOf('\n---', 3);
109
+ const block = end !== -1 ? text.slice(3, end) : text;
110
+ return USER_INVOCABLE_FALSE_RE.test(block);
111
+ }
112
+
100
113
  export function _spec_violation(name: string): string | null {
101
114
  if (name.length > MAX_NAME_LEN) {
102
115
  return `longer than ${MAX_NAME_LEN} chars`;
@@ -178,6 +191,22 @@ export function check_skills(): string[] {
178
191
  'requires the folder name to match the `name:` field',
179
192
  );
180
193
  }
194
+ // Reserved-name floor: a skill named after a Claude Code built-in
195
+ // command / bundled skill shadows the built-in when projected as
196
+ // `/name` (e.g. a `mcp` skill hides Claude Code's own /mcp dialog).
197
+ // Opting out of slash registration (`user-invocable: false`) keeps
198
+ // the skill model-loadable while the host retains the /name.
199
+ // Reserved set: src/scripts/_lib/claude_builtin_names.ts.
200
+ if (
201
+ is_claude_builtin_name(dirname) &&
202
+ !_opts_out_of_slash(fs.readFileSync(md, 'utf-8'))
203
+ ) {
204
+ violations.push(
205
+ `${rel}: \`${dirname}\` is a Claude Code built-in command name — ` +
206
+ 'add `user-invocable: false` (model-only invocation) or rename ' +
207
+ 'the skill; the suite must never shadow a host built-in',
208
+ );
209
+ }
181
210
  }
182
211
  return violations;
183
212
  }
@@ -14,9 +14,10 @@
14
14
  * - sessions scale — `agents/knowledge/sessions/` > 50 pages
15
15
  * → design fold-through-consolidate-gate for tracked pages
16
16
  * - type scale — any single memory/knowledge type > 200 files
17
- * → build the pre-decided file-first in-memory BM25 CLI
17
+ * → rank via `_lib/lexical_index.ts` (hand-rolled BM25 +
18
+ * trigram, no engine fork / no FTS5; ADR-061 honoured)
18
19
  * - corpus scale — > 500 files across all types
19
- * → same BM25 activation path
20
+ * → same lexical-index activation path
20
21
  * - hot-context size — `agents/runtime/state/hot-context.md` > 600 tokens
21
22
  * (estimated at 4 chars/token) → trim schema / fix the
22
23
  * deterministic writer
@@ -151,7 +152,7 @@ export function runChecks(root: string): Warning[] {
151
152
  metric: `${n}/${TYPE_FILES_MAX}`,
152
153
  message:
153
154
  `type '${type}' holds ${n} files (> ${TYPE_FILES_MAX}). ` +
154
- 'Activation path (pre-decided): build the file-first in-memory BM25 CLI (re-index at session start, no vectors, no service).',
155
+ 'Activation path (resolved, road-to-retrieval-substrate-hardening B2): rank via the hand-rolled BM25 + trigram index in `_lib/lexical_index.ts` (pure stdlib, NO engine fork / NO SQLite-FTS5, ADR-061 honoured). Measured lift: mean tie-set 3.333 → 1.0 (internal/bench/reports/lexical-ranking.json). Wire it lazily at first lookup + a stat-index, no vectors, no service.',
155
156
  });
156
157
  }
157
158
  }