@event4u/agent-config 8.7.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 (171) hide show
  1. package/.claude-plugin/marketplace.json +1 -1
  2. package/CHANGELOG.md +109 -222
  3. package/CONTRIBUTING.md +25 -0
  4. package/README.md +7 -4
  5. package/dist/agent-src/commands/agent-handoff.md +39 -2
  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/contexts/communication/rules-auto/think-before-action-mechanics.md +30 -1
  11. package/dist/agent-src/contexts/execution/auto-orchestration-activation.md +1 -1
  12. package/dist/agent-src/contexts/execution/orchestration-benchmark-gate.md +28 -16
  13. package/dist/agent-src/contexts/execution/orchestration-telemetry.md +36 -5
  14. package/dist/agent-src/contexts/execution/roadmap-process-loop.md +37 -0
  15. package/dist/agent-src/contexts/execution/subagent-topologies.md +34 -0
  16. package/dist/agent-src/contexts/execution/verification-mechanics.md +9 -9
  17. package/dist/agent-src/rules/active-remediation.md +92 -0
  18. package/dist/agent-src/rules/agent-authority.md +1 -1
  19. package/dist/agent-src/rules/broken-access-control.md +106 -0
  20. package/dist/agent-src/rules/communication-through-line.md +42 -0
  21. package/dist/agent-src/rules/decision-revisit-gate.md +8 -0
  22. package/dist/agent-src/rules/delegation-policy.md +5 -0
  23. package/dist/agent-src/rules/direct-answers.md +1 -1
  24. package/dist/agent-src/rules/downstream-changes.md +1 -0
  25. package/dist/agent-src/rules/external-code-graph-interop.md +65 -0
  26. package/dist/agent-src/rules/git-history-discipline.md +18 -0
  27. package/dist/agent-src/rules/lethal-trifecta-guard.md +6 -1
  28. package/dist/agent-src/rules/minimal-safe-diff.md +66 -1
  29. package/dist/agent-src/rules/non-destructive-by-default.md +3 -0
  30. package/dist/agent-src/rules/prefer-enums-over-literals.md +65 -0
  31. package/dist/agent-src/rules/question-not-instruction.md +76 -0
  32. package/dist/agent-src/rules/senior-engineering-discipline.md +82 -0
  33. package/dist/agent-src/rules/think-before-action.md +1 -0
  34. package/dist/agent-src/rules/tool-safety.md +8 -0
  35. package/dist/agent-src/rules/untrusted-input-defense.md +87 -22
  36. package/dist/agent-src/scripts/update_roadmap_progress.ts +2 -1
  37. package/dist/agent-src/skills/accessibility-auditor/SKILL.md +19 -0
  38. package/dist/agent-src/skills/ai-code-blindspots/SKILL.md +112 -0
  39. package/dist/agent-src/skills/ai-code-blindspots/evals/triggers.json +52 -0
  40. package/dist/agent-src/skills/authz-review/SKILL.md +21 -4
  41. package/dist/agent-src/skills/code-review/SKILL.md +2 -0
  42. package/dist/agent-src/skills/command-writing/SKILL.md +53 -40
  43. package/dist/agent-src/skills/dcf-modeling/evals/domain-truth.json +29 -0
  44. package/dist/agent-src/skills/deep-reading-analyst/SKILL.md +1 -0
  45. package/dist/agent-src/skills/dependency-upgrade/SKILL.md +20 -0
  46. package/dist/agent-src/skills/design-intelligence/SKILL.md +49 -12
  47. package/dist/agent-src/skills/design-review/SKILL.md +29 -15
  48. package/dist/agent-src/skills/design-system-capture/SKILL.md +63 -24
  49. package/dist/agent-src/skills/forecasting/evals/domain-truth.json +29 -0
  50. package/dist/agent-src/skills/frontend-render-security/SKILL.md +104 -0
  51. package/dist/agent-src/skills/frontend-render-security/evals/triggers.json +52 -0
  52. package/dist/agent-src/skills/markitdown/SKILL.md +1 -0
  53. package/dist/agent-src/skills/mcp/SKILL.md +24 -0
  54. package/dist/agent-src/skills/mcp-builder/SKILL.md +9 -1
  55. package/dist/agent-src/skills/memory-consolidation/SKILL.md +55 -4
  56. package/dist/agent-src/skills/roadmap-writing/SKILL.md +46 -5
  57. package/dist/agent-src/skills/runway-cognition/evals/domain-truth.json +29 -0
  58. package/dist/agent-src/skills/scenario-modeling/evals/domain-truth.json +29 -0
  59. package/dist/agent-src/skills/secrets-management/SKILL.md +7 -0
  60. package/dist/agent-src/skills/skill-writing/SKILL.md +12 -3
  61. package/dist/agent-src/skills/subagent-orchestration/SKILL.md +27 -33
  62. package/dist/agent-src/skills/supply-chain-intake/SKILL.md +112 -0
  63. package/dist/agent-src/skills/supply-chain-intake/evals/triggers.json +52 -0
  64. package/dist/agent-src/skills/testing-anti-patterns/SKILL.md +72 -2
  65. package/dist/agent-src/skills/ui-component-architect/SKILL.md +9 -0
  66. package/dist/agent-src/skills/unit-economics-modeling/evals/domain-truth.json +40 -0
  67. package/dist/agent-src/templates/agents/agent-project-settings.example.yml +1 -1
  68. package/dist/agent-src/templates/roadmaps.md +60 -0
  69. package/dist/cli/registry.js +3 -0
  70. package/dist/cli/registry.js.map +1 -1
  71. package/dist/discovery/deprecation-report.md +1 -1
  72. package/dist/discovery/discovery-manifest.json +268 -46
  73. package/dist/discovery/discovery-manifest.json.sha256 +1 -1
  74. package/dist/discovery/discovery-manifest.summary.md +4 -4
  75. package/dist/discovery/orphan-report.md +1 -1
  76. package/dist/discovery/packs.json +18 -8
  77. package/dist/discovery/trust-report.md +11 -11
  78. package/dist/discovery/workspaces.json +16 -6
  79. package/dist/install/install.mjs +141 -3
  80. package/dist/mcp/awesome-mcp-servers.row.md +1 -1
  81. package/dist/mcp/mcp-cloudflare-catalogue.json +1 -1
  82. package/dist/mcp/registry-manifest.json +4 -4
  83. package/dist/router.json +1 -1
  84. package/dist/scripts/_lib/claude_builtin_names.js +145 -0
  85. package/dist/scripts/_lib/claude_builtin_names.js.map +1 -0
  86. package/dist/scripts/condense.js +14 -0
  87. package/dist/scripts/condense.js.map +1 -1
  88. package/dist/server/schemas/settings.js +2 -1
  89. package/dist/server/schemas/settings.js.map +1 -1
  90. package/docs/CLAIMS.md +58 -2
  91. package/docs/architecture.md +3 -3
  92. package/docs/archive/CHANGELOG-pre-8.9.0.md +306 -0
  93. package/docs/benchmark.md +113 -0
  94. package/docs/catalog.md +32 -5
  95. package/docs/contracts/command-clusters.md +18 -0
  96. package/docs/contracts/design-artifact-verification.md +124 -0
  97. package/docs/contracts/injected-block-authenticity-model.md +66 -0
  98. package/docs/contracts/mcp-tool-inventory.md +31 -30
  99. package/docs/decisions/ADR-061-corpus-grounding-layer.md +13 -0
  100. package/docs/decisions/ADR-105-automatic-subagent-orchestration.md +6 -0
  101. package/docs/decisions/ADR-116-memory-tripwire-activation-path.md +11 -2
  102. package/docs/decisions/ADR-117-subagents-auto-default-on.md +88 -0
  103. package/docs/decisions/INDEX.md +1 -0
  104. package/docs/experiences/content_creator.md +2 -0
  105. package/docs/experiences/finance.md +2 -0
  106. package/docs/experiences/founder.md +2 -0
  107. package/docs/experiences/ops.md +2 -0
  108. package/docs/featured-skills.md +2 -2
  109. package/docs/getting-started-by-role.md +1 -1
  110. package/docs/getting-started.md +1 -1
  111. package/docs/governance-advantage.md +1 -1
  112. package/docs/guidelines/agent-infra/asking-and-brevity-examples.md +27 -0
  113. package/docs/guidelines/agent-infra/emphasis-budget.md +45 -0
  114. package/docs/guidelines/agent-infra/memory-access.md +12 -0
  115. package/docs/guidelines/agent-infra/simplicity-and-goal-demos.md +151 -0
  116. package/docs/guidelines/agent-infra/tool-description-as-policy.md +35 -0
  117. package/docs/guidelines/design-canon.md +1 -0
  118. package/docs/proof.md +60 -3
  119. package/docs/release-runbook.md +138 -0
  120. package/docs/second-brain-scope.md +116 -0
  121. package/docs/succession.md +80 -0
  122. package/package.json +2 -2
  123. package/src/agent-src/scripts/update_roadmap_progress.ts +2 -1
  124. package/src/config/agent-settings.template.yml +25 -6
  125. package/src/scripts/_dispatch.bash +27 -0
  126. package/src/scripts/_lib/claude_builtin_names.ts +148 -0
  127. package/src/scripts/_lib/file_slicer.ts +117 -0
  128. package/src/scripts/_lib/lexical_index.ts +155 -0
  129. package/src/scripts/_lib/orchestration_gate.ts +12 -8
  130. package/src/scripts/_lib/orchestration_record.ts +170 -0
  131. package/src/scripts/_lib/orchestration_savings.ts +180 -0
  132. package/src/scripts/_lib/retrieval_sanitize.ts +71 -0
  133. package/src/scripts/_lib/security_lint.ts +1 -0
  134. package/src/scripts/_lib/stat_index.ts +98 -0
  135. package/src/scripts/benchmark.ts +122 -0
  136. package/src/scripts/build_proof.ts +51 -0
  137. package/src/scripts/chat_history.ts +79 -7
  138. package/src/scripts/check_memory.ts +2 -0
  139. package/src/scripts/check_quality_regression.ts +51 -0
  140. package/src/scripts/check_roadmap_trackable.ts +3 -2
  141. package/src/scripts/condense.ts +14 -0
  142. package/src/scripts/discovery_graph.ts +0 -0
  143. package/src/scripts/domain_soundness_status.ts +236 -0
  144. package/src/scripts/external_sources_denylist.json +4 -1
  145. package/src/scripts/generate_index.ts +17 -0
  146. package/src/scripts/hot_context_hook.ts +47 -2
  147. package/src/scripts/install-hooks.sh +25 -0
  148. package/src/scripts/install.ts +35 -2
  149. package/src/scripts/learning_sidecar.ts +0 -0
  150. package/src/scripts/lint_agent_skill_names.ts +29 -0
  151. package/src/scripts/lint_behavioural_eval_freshness.ts +140 -0
  152. package/src/scripts/lint_knowledge_scale.ts +4 -3
  153. package/src/scripts/lint_roadmap_complexity.ts +124 -0
  154. package/src/scripts/lint_versioned_cache.ts +195 -0
  155. package/src/scripts/mcp_server/consumer_tool_catalog.json +23 -3
  156. package/src/scripts/mcp_server/tools.ts +182 -1
  157. package/src/scripts/measure_lexical_ranking.ts +173 -0
  158. package/src/scripts/memory_lookup.ts +0 -0
  159. package/src/scripts/memory_replay.ts +285 -0
  160. package/src/scripts/orchestration_record.ts +140 -0
  161. package/src/scripts/orchestration_savings_report.ts +124 -0
  162. package/src/scripts/schemas/domain-truth.schema.json +94 -0
  163. package/src/scripts/schemas/evals.schema.json +113 -0
  164. package/src/scripts/schemas/skill.schema.json +4 -0
  165. package/src/scripts/score_domain_truth.ts +192 -0
  166. package/src/scripts/second_brain_retrieval.ts +278 -0
  167. package/src/scripts/second_brain_run.ts +341 -0
  168. package/src/scripts/second_brain_score.ts +186 -0
  169. package/src/scripts/session_memory_index.ts +94 -0
  170. package/src/scripts/skill_eval_coverage.ts +318 -0
  171. package/src/server/schemas/settings.ts +5 -2
@@ -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';
@@ -107,6 +108,54 @@ function render(): string {
107
108
  L.push('Benchmark results are published under `docs/` including measured nulls.');
108
109
  }
109
110
  L.push('');
111
+ L.push('**Behavioural-eval coverage — the honest baseline.** Skill *quality* is only');
112
+ L.push('as good as its measurement. Today **2 of 264** skills carry a behavioural');
113
+ L.push('`evals.json`, and the highest-traffic / highest-cost tiers (default-surface +');
114
+ L.push('`rich` + routers, **0 of 35**) are not yet covered. We publish that gap rather');
115
+ L.push('than imply "264 evaluated skills": coverage is measured per tier');
116
+ L.push('(`./scripts-run src/scripts/skill_eval_coverage`) and **CI-ratcheted so it can');
117
+ L.push('only rise** — a merged change can never lower it. Authoring the priority-tier');
118
+ L.push('evals is gated on per-case human ratification (a generated assertion that');
119
+ L.push('checks the wrong property is worse than none), so the number grows');
120
+ L.push('deliberately, not overnight.');
121
+ L.push('');
122
+ const ds = domainSoundnessStatus();
123
+ L.push('**Non-coding domain soundness — scoped, not proven.** The `finance` /');
124
+ L.push('`founder` / `ops` / `content` profiles sell concrete domain value (DCF,');
125
+ L.push('runway, RICE, incident command, messaging), but the skills are forged on');
126
+ L.push('TS/PHP — "promising, not proven" off those stacks. A disclaimer floor');
127
+ L.push('bounds *liability*, not *correctness*: a skill can be format-correct,');
128
+ L.push(`disclaimered, and still embed a wrong domain assumption. Today **${ds.validated} of ${ds.total}**`);
129
+ L.push('default-surface domain skills carry a sourced `domain-truth` fixture');
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.');
138
+ L.push('');
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.');
158
+ L.push('');
110
159
  L.push('## 3. Known limits (published, witness-tested)');
111
160
  L.push('');
112
161
  const skillGaps = collectSkillGaps();
@@ -164,6 +213,8 @@ function render(): string {
164
213
  L.push('task check-refs # no broken internal references');
165
214
  L.push('task check-skill-gaps # every logged known-limit cites a real witness test');
166
215
  L.push('task check-comparison # every comparison-table "our evidence" pointer resolves');
216
+ L.push('./scripts-run src/scripts/skill_eval_coverage # behavioural-eval coverage, per tier');
217
+ L.push('./scripts-run src/scripts/skill_eval_coverage --check # the ratchet: coverage may not drop');
167
218
  L.push('task build-proof-check # this page is in sync with its sources');
168
219
  L.push('```');
169
220
  L.push('');
@@ -43,6 +43,8 @@ import * as fs from 'node:fs';
43
43
  import * as path from 'node:path';
44
44
  import { fileURLToPath, pathToFileURL } from 'node:url';
45
45
 
46
+ import { _entry_tokens_estimate } from './memory_lookup.js';
47
+
46
48
  import { load_agent_settings } from './_lib/agent_settings.js';
47
49
 
48
50
  export const DEFAULT_FILE = 'agents/runtime/.agent-chat-history';
@@ -708,6 +710,26 @@ export function read_entries(
708
710
  agent?: string | null | undefined;
709
711
  } = {},
710
712
  ): Entry[] {
713
+ return read_entries_with_refs(options).map((p) => p.entry);
714
+ }
715
+
716
+ /**
717
+ * Like {@link read_entries} but each entry carries its `ref` — the 0-based
718
+ * ordinal in the file's FULL chronological entry list (header excluded,
719
+ * counted BEFORE any session/agent/last filter), so a ref from an index row
720
+ * stays valid regardless of the filters a later call applies. Refs are
721
+ * stable for the append-only log; file rotation invalidates them (documented
722
+ * — a ref is a within-file locator, not a durable id).
723
+ * (road-to-memory-retrieval-economy Phase 3.)
724
+ */
725
+ export function read_entries_with_refs(
726
+ options: {
727
+ last?: number | null | undefined;
728
+ path?: string | null | undefined;
729
+ session?: string | null | undefined;
730
+ agent?: string | null | undefined;
731
+ } = {},
732
+ ): Array<{ entry: Entry; ref: number }> {
711
733
  const target = options.path ?? file_path();
712
734
  const last = options.last ?? null;
713
735
  const session = options.session ?? null;
@@ -715,8 +737,9 @@ export function read_entries(
715
737
  if (!_isFile(target)) {
716
738
  return [];
717
739
  }
718
- let entries: Entry[] = [];
740
+ let pairs: Array<{ entry: Entry; ref: number }> = [];
719
741
  const lines = _readLines(target);
742
+ let ref = 0;
720
743
  for (let i = 0; i < lines.length; i++) {
721
744
  const line = _strip(lines[i] ?? '');
722
745
  if (!line) {
@@ -732,23 +755,72 @@ export function read_entries(
732
755
  continue;
733
756
  }
734
757
  if (_isPlainObject(obj)) {
735
- entries.push(obj);
758
+ pairs.push({ entry: obj, ref });
759
+ ref += 1;
736
760
  }
737
761
  }
738
762
  if (session !== null) {
739
- entries = entries.filter((e) => e.s === session);
763
+ pairs = pairs.filter((p) => p.entry.s === session);
740
764
  }
741
765
  if (agent !== null) {
742
766
  if (agent === AGENT_UNKNOWN) {
743
- entries = entries.filter((e) => !e.agent);
767
+ pairs = pairs.filter((p) => !p.entry.agent);
744
768
  } else {
745
- entries = entries.filter((e) => e.agent === agent);
769
+ pairs = pairs.filter((p) => p.entry.agent === agent);
746
770
  }
747
771
  }
748
772
  if (last !== null && last >= 0) {
749
- entries = entries.slice(entries.length - last);
773
+ pairs = pairs.slice(pairs.length - last);
774
+ }
775
+ return pairs;
776
+ }
777
+
778
+ /**
779
+ * Timeline anchor: the entries around `ref` in the FULL chronological list —
780
+ * `before`/`after` neighbours plus the anchor itself. JSONL is chronological,
781
+ * so this is slicing, not indexing (road-to-memory-retrieval-economy P3).
782
+ * An out-of-range ref returns [].
783
+ */
784
+ export function slice_around(
785
+ path_: string | null,
786
+ ref: number,
787
+ before = 3,
788
+ after = 3,
789
+ ): Array<{ entry: Entry; ref: number }> {
790
+ const all = read_entries_with_refs({ path: path_ });
791
+ if (ref < 0 || ref >= all.length) {
792
+ return [];
793
+ }
794
+ const start = Math.max(0, ref - Math.max(0, before));
795
+ const end = Math.min(all.length, ref + Math.max(0, after) + 1);
796
+ return all.slice(start, end);
797
+ }
798
+
799
+ /**
800
+ * Compact index row for a history entry: ref + type tag + timestamp-ish
801
+ * fields when present + a ~100-char preview + the real-tokenizer cost of
802
+ * fetching the full entry (same lazy-tokenizer contract as memory_lookup's
803
+ * index rows).
804
+ */
805
+ export function history_index_row(pair: { entry: Entry; ref: number }): Record<string, unknown> {
806
+ const e = pair.entry;
807
+ const text = typeof e.text === 'string' ? e.text : '';
808
+ const row: Record<string, unknown> = {
809
+ ref: pair.ref,
810
+ t: e.t ?? '',
811
+ preview: text.slice(0, 100),
812
+ tokens_estimate: _entry_tokens_estimate(e as Record<string, unknown>),
813
+ };
814
+ if (typeof e.s === 'string') {
815
+ row.s = e.s;
816
+ }
817
+ for (const tsField of ['ts', 'time', 'started']) {
818
+ if (typeof (e as Record<string, unknown>)[tsField] === 'string') {
819
+ row[tsField] = (e as Record<string, unknown>)[tsField];
820
+ break;
821
+ }
750
822
  }
751
- return entries;
823
+ return row;
752
824
  }
753
825
 
754
826
  export function read_entries_for_current(
@@ -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
@@ -0,0 +1,236 @@
1
+ #!/usr/bin/env tsx
2
+ /**
3
+ * Domain-soundness validation status + ratchet
4
+ * (road-to-domain-soundness Phase 1 + Phase 3).
5
+ *
6
+ * The four non-coding profiles (finance, founder, ops, content_creator) SELL
7
+ * concrete domain value (DCF, runway, RICE, incident command, messaging), but
8
+ * the skills are forged on TS/PHP — "promising, not proven" off those stacks.
9
+ * A disclaimer floor bounds liability, NOT correctness. This tool makes the
10
+ * provenance honest and machine-checkable: it enumerates the default-surface
11
+ * domain skills those profiles ship (from source — each profile's
12
+ * `skills_hint`, never hardcoded) and reports each as:
13
+ *
14
+ * - `validated` — ships `evals/domain-truth.json` (a sourced answer key,
15
+ * authored by domain competence, distinct from behavioural
16
+ * evals.json). Presence is the machine signal; a passing
17
+ * run against it is the maintainer's Phase-3 gate.
18
+ * - `unvalidated` — no domain-truth fixture: general-purpose scaffold, domain
19
+ * correctness not independently validated.
20
+ *
21
+ * A domain skill can pass a format/behavioural eval AND carry a disclaimer AND
22
+ * still embed a wrong domain assumption — this axis is the one that catches it.
23
+ *
24
+ * Modes:
25
+ * (default) human report.
26
+ * --json machine-readable.
27
+ * --check ratchet gate (CI): the count of `validated` skills may not
28
+ * drop below the pinned floor (internal/evals/domain-soundness-floor.json);
29
+ * a missing floor is an all-zero floor (bootstrap-inert). The
30
+ * floor only rises via --write-floor after a fixture lands and
31
+ * passes. This is the enforced-honesty gate: it makes a claimed
32
+ * validation impossible to assert without the fixture on disk.
33
+ * --write-floor pin the current validated count as the floor (maintainer
34
+ * action after authoring + running a domain-truth fixture).
35
+ *
36
+ * Exit codes: 0 ok / 1 ratchet regression / 2 usage error.
37
+ */
38
+ import * as fs from 'node:fs';
39
+ import * as path from 'node:path';
40
+ import { fileURLToPath, pathToFileURL } from 'node:url';
41
+
42
+ import { parse as parseYaml } from 'yaml';
43
+
44
+ const _HERE = fileURLToPath(import.meta.url);
45
+ export const REPO_ROOT = path.resolve(path.dirname(_HERE), '..', '..');
46
+
47
+ /** The non-coding profiles whose default surface this tool scopes. */
48
+ export const NON_CODING_PROFILES: readonly string[] = [
49
+ 'finance',
50
+ 'founder',
51
+ 'ops',
52
+ 'content_creator',
53
+ ];
54
+
55
+ export interface StatusOptions {
56
+ skillsDir?: string;
57
+ profilesDir?: string;
58
+ floorPath?: string;
59
+ }
60
+
61
+ export interface SkillStatus {
62
+ skill: string;
63
+ status: 'validated' | 'unvalidated';
64
+ }
65
+
66
+ export interface SoundnessReport {
67
+ validationSet: SkillStatus[];
68
+ validated: number;
69
+ unvalidated: number;
70
+ total: number;
71
+ }
72
+
73
+ function _skillsDir(o: StatusOptions): string {
74
+ return o.skillsDir ?? path.join(REPO_ROOT, 'src', 'skills');
75
+ }
76
+ function _profilesDir(o: StatusOptions): string {
77
+ return o.profilesDir ?? path.join(REPO_ROOT, 'src', 'agent-src', 'profiles');
78
+ }
79
+ export function _floorPath(o: StatusOptions = {}): string {
80
+ return (
81
+ o.floorPath ??
82
+ path.join(REPO_ROOT, 'internal', 'evals', 'domain-soundness-floor.json')
83
+ );
84
+ }
85
+
86
+ /** Union of the non-coding profiles' `skills_hint`, restricted to real skills. */
87
+ export function validationSet(o: StatusOptions = {}): string[] {
88
+ const profilesDir = _profilesDir(o);
89
+ const skillsDir = _skillsDir(o);
90
+ const out = new Set<string>();
91
+ for (const p of NON_CODING_PROFILES) {
92
+ const f = path.join(profilesDir, `${p}.yml`);
93
+ let doc: unknown;
94
+ try {
95
+ doc = parseYaml(fs.readFileSync(f, 'utf-8'));
96
+ } catch {
97
+ continue;
98
+ }
99
+ const profile = (doc as { profile?: unknown } | null)?.profile;
100
+ const defaults = (profile as { defaults?: unknown } | null)?.defaults;
101
+ const hint = (defaults as { skills_hint?: unknown } | null)?.skills_hint;
102
+ if (Array.isArray(hint)) {
103
+ for (const s of hint) {
104
+ if (typeof s === 'string' && fs.existsSync(path.join(skillsDir, s, 'SKILL.md'))) {
105
+ out.add(s);
106
+ }
107
+ }
108
+ }
109
+ }
110
+ return [...out].sort();
111
+ }
112
+
113
+ function _isValidated(skillsDir: string, skill: string): boolean {
114
+ return fs.existsSync(path.join(skillsDir, skill, 'evals', 'domain-truth.json'));
115
+ }
116
+
117
+ export function computeStatus(o: StatusOptions = {}): SoundnessReport {
118
+ const skillsDir = _skillsDir(o);
119
+ const set = validationSet(o);
120
+ const validationSetStatus: SkillStatus[] = set.map((skill) => ({
121
+ skill,
122
+ status: _isValidated(skillsDir, skill) ? 'validated' : 'unvalidated',
123
+ }));
124
+ const validated = validationSetStatus.filter((s) => s.status === 'validated').length;
125
+ return {
126
+ validationSet: validationSetStatus,
127
+ validated,
128
+ unvalidated: validationSetStatus.length - validated,
129
+ total: validationSetStatus.length,
130
+ };
131
+ }
132
+
133
+ function _readFloor(floorPath: string): number {
134
+ try {
135
+ const raw = JSON.parse(fs.readFileSync(floorPath, 'utf-8')) as { validated?: number };
136
+ return raw.validated ?? 0;
137
+ } catch {
138
+ return 0;
139
+ }
140
+ }
141
+
142
+ export function writeFloor(o: StatusOptions = {}): SoundnessReport {
143
+ const report = computeStatus(o);
144
+ const p = _floorPath(o);
145
+ fs.mkdirSync(path.dirname(p), { recursive: true });
146
+ fs.writeFileSync(
147
+ p,
148
+ JSON.stringify(
149
+ {
150
+ _comment:
151
+ 'Domain-soundness ratchet floor (road-to-domain-soundness Phase 3). ' +
152
+ 'domain_soundness_status --check fails if the validated count drops below this. ' +
153
+ 'Regenerate with --write-floor ONLY after a domain-truth fixture lands and passes.',
154
+ validated: report.validated,
155
+ },
156
+ null,
157
+ 2,
158
+ ) + '\n',
159
+ );
160
+ return report;
161
+ }
162
+
163
+ export function checkRatchet(o: StatusOptions = {}): { ok: boolean; message: string } {
164
+ const report = computeStatus(o);
165
+ const floor = _readFloor(_floorPath(o));
166
+ if (report.validated < floor) {
167
+ return {
168
+ ok: false,
169
+ message: `validated domain skills ${report.validated} < floor ${floor}`,
170
+ };
171
+ }
172
+ return { ok: true, message: `validated ${report.validated}/${report.total} (floor ${floor})` };
173
+ }
174
+
175
+ function _renderHuman(r: SoundnessReport): string {
176
+ const lines = [
177
+ 'Domain-soundness status — non-coding default-surface skills',
178
+ ` validated: ${r.validated}/${r.total}`,
179
+ ` unvalidated: ${r.unvalidated}/${r.total}`,
180
+ '',
181
+ ];
182
+ for (const s of r.validationSet) {
183
+ const mark = s.status === 'validated' ? '✅' : '·';
184
+ lines.push(` ${mark} ${s.skill.padEnd(28)} ${s.status}`);
185
+ }
186
+ return lines.join('\n');
187
+ }
188
+
189
+ export function main(argv: readonly string[] = process.argv.slice(2)): number {
190
+ const known = new Set(['--json', '--check', '--write-floor']);
191
+ for (const a of argv) {
192
+ if (!known.has(a)) {
193
+ process.stderr.write('usage: domain_soundness_status [--json | --check | --write-floor]\n');
194
+ return 2;
195
+ }
196
+ }
197
+ if (argv.includes('--write-floor')) {
198
+ const r = writeFloor();
199
+ process.stdout.write(`✅ domain-soundness floor pinned: validated ${r.validated}/${r.total}\n`);
200
+ return 0;
201
+ }
202
+ if (argv.includes('--check')) {
203
+ const { ok, message } = checkRatchet();
204
+ if (!ok) {
205
+ process.stdout.write(`❌ domain-soundness ratchet regressed: ${message}\n`);
206
+ return 1;
207
+ }
208
+ process.stdout.write(`✅ domain-soundness ratchet: ${message}.\n`);
209
+ return 0;
210
+ }
211
+ const report = computeStatus();
212
+ if (argv.includes('--json')) {
213
+ process.stdout.write(JSON.stringify(report, null, 2) + '\n');
214
+ return 0;
215
+ }
216
+ process.stdout.write(_renderHuman(report) + '\n');
217
+ return 0;
218
+ }
219
+
220
+ function _isCliEntry(): boolean {
221
+ if (process.argv[1] === undefined) return false;
222
+ const argvUrl = pathToFileURL(path.resolve(process.argv[1])).href;
223
+ if (import.meta.url === argvUrl) return true;
224
+ try {
225
+ return (
226
+ fs.realpathSync(fileURLToPath(import.meta.url)) ===
227
+ fs.realpathSync(path.resolve(process.argv[1]))
228
+ );
229
+ } catch {
230
+ return false;
231
+ }
232
+ }
233
+
234
+ if (_isCliEntry()) {
235
+ process.exit(main());
236
+ }
@@ -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
  '',
@@ -410,6 +412,21 @@ export function _render_catalog(
410
412
  '',
411
413
  `## Skills (${skills.length})`,
412
414
  '',
415
+ '> **Behavioural-eval coverage is measured, not implied.** A skill is',
416
+ '> behaviourally evaluated only if it ships `evals/evals.json`; most do',
417
+ '> not yet. Coverage is reported per tier (`./scripts-run',
418
+ '> src/scripts/skill_eval_coverage`) and CI-ratcheted so it can only rise.',
419
+ '> Listing here never implies a skill is behaviourally tested — the',
420
+ '> current coverage and its gap are published on [the proof page](proof.md).',
421
+ '',
422
+ '> **Non-coding domain correctness is scoped, not proven.** The',
423
+ '> `finance` / `founder` / `ops` / `content` skills are forged on TS/PHP',
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',
427
+ '> (`./scripts-run src/scripts/domain_soundness_status`). A disclaimer',
428
+ '> floor bounds liability, not correctness — see [the proof page](proof.md).',
429
+ '',
413
430
  _render_table(skills, ['kind', 'name', 'extra', 'description'], '../', _to_shipped_path),
414
431
  '',
415
432
  `## Rules (${publicRules.length})`,