@event4u/agent-config 14.21.0 → 14.23.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 (235) hide show
  1. package/.augment-plugin/marketplace.json +3 -3
  2. package/.augment-plugin/plugin.json +1 -1
  3. package/.claude-plugin/marketplace.json +1 -1
  4. package/CHANGELOG.md +164 -187
  5. package/CREDITS.md +37 -0
  6. package/README.md +1 -1
  7. package/dist/agent-src/commands/analyze/inbox.md +380 -5
  8. package/dist/agent-src/commands/analyze/repo.md +4 -2
  9. package/dist/agent-src/commands/pr/merge.md +47 -38
  10. package/dist/agent-src/commands/roadmap/next.md +8 -0
  11. package/dist/agent-src/contexts/communication/rules-auto/token-efficiency-mechanics.md +69 -0
  12. package/dist/agent-src/contexts/execution/auto-dispatch-classification.md +5 -5
  13. package/dist/agent-src/contexts/execution/autonomy-mechanics.md +12 -0
  14. package/dist/agent-src/rules/external-code-graph-interop.md +19 -14
  15. package/dist/agent-src/rules/notes-first-reasoning.md +36 -25
  16. package/dist/agent-src/rules/token-efficiency.md +26 -3
  17. package/dist/agent-src/scripts/_cli_entry.ts +51 -0
  18. package/dist/agent-src/scripts/archive_completed_roadmaps.ts +120 -27
  19. package/dist/agent-src/scripts/roadmap_gates.ts +4 -20
  20. package/dist/agent-src/scripts/roadmap_set_step.ts +5 -2
  21. package/dist/agent-src/scripts/stubs_due.ts +4 -18
  22. package/dist/agent-src/scripts/update_roadmap_progress.ts +7 -24
  23. package/dist/agent-src/skills/code-intelligence/SKILL.md +82 -1
  24. package/dist/agent-src/skills/git-workflow/references/push-closes-its-loop.md +9 -0
  25. package/dist/agent-src/skills/roadmap-writing/SKILL.md +17 -0
  26. package/dist/agent-src/skills/tailwind-engineer/SKILL.md +52 -2
  27. package/dist/agent-src/templates/agent-settings.md +0 -1
  28. package/dist/agent-src/templates/agents/agent-project-settings.example.yml +1 -1
  29. package/dist/cli/registry.js +1 -1
  30. package/dist/cli-delegate/_cli_entry.js +8 -0
  31. package/dist/cli-delegate/_lib/pin_resolver.js +170 -0
  32. package/dist/cli-delegate/archival_sweep.js +8 -0
  33. package/dist/cli-delegate/archive_completed_roadmaps.js +542 -0
  34. package/dist/cli-delegate/check_update_banner.js +923 -0
  35. package/dist/cli-delegate/{chunk-ZVBOHSVF.js → chunk-2KKXJW25.js} +23 -9
  36. package/dist/cli-delegate/chunk-5EK5WQN6.js +28 -0
  37. package/dist/cli-delegate/chunk-7XGD2OEU.js +96 -0
  38. package/dist/cli-delegate/chunk-DBH4AHDK.js +155 -0
  39. package/dist/cli-delegate/{chunk-4FS4WRXP.js → chunk-IAI3XL43.js} +15 -3
  40. package/dist/cli-delegate/chunk-JO25G5H7.js +20 -0
  41. package/dist/cli-delegate/{chunk-643Z7FIC.js → chunk-KCNTUSPI.js} +7 -1
  42. package/dist/cli-delegate/chunk-LTYVIY76.js +7961 -0
  43. package/dist/cli-delegate/chunk-QAJS5YWY.js +999 -0
  44. package/dist/cli-delegate/{chunk-YS6XTLEK.js → chunk-TYSY33MD.js} +56 -6
  45. package/dist/cli-delegate/chunk-ULET4BYS.js +328 -0
  46. package/dist/cli-delegate/chunk-YDQSHTD7.js +187 -0
  47. package/dist/cli-delegate/chunk-YWVXLR44.js +48 -0
  48. package/dist/cli-delegate/chunk-ZZQBUHFK.js +273 -0
  49. package/dist/cli-delegate/cmd_conformance.js +2 -2
  50. package/dist/cli-delegate/cmd_export.js +2 -2
  51. package/dist/cli-delegate/cmd_handoff.js +45 -13
  52. package/dist/cli-delegate/cmd_prune.js +2 -2
  53. package/dist/cli-delegate/cmd_session_recycle.js +4 -14
  54. package/dist/cli-delegate/cmd_settings_migrate.js +2 -2
  55. package/dist/cli-delegate/cmd_settings_set.js +1 -1
  56. package/dist/cli-delegate/cmd_sync.js +2 -2
  57. package/dist/cli-delegate/cmd_uninstall.js +2 -2
  58. package/dist/cli-delegate/cmd_validate.js +2 -2
  59. package/dist/cli-delegate/dashboard_mode.js +16 -0
  60. package/dist/cli-delegate/gate_budget.js +24 -0
  61. package/dist/cli-delegate/gate_execute.js +25 -0
  62. package/dist/cli-delegate/guarded_baseline.js +18 -0
  63. package/dist/cli-delegate/resume_probe.js +22 -0
  64. package/dist/cli-delegate/roadmap_gates.js +638 -0
  65. package/dist/cli-delegate/roadmap_set_step.js +173 -0
  66. package/dist/cli-delegate/stub_queue.js +47 -0
  67. package/dist/cli-delegate/stubs_due.js +38 -0
  68. package/dist/cli-delegate/ticket_bundles.js +59 -0
  69. package/dist/cli-delegate/update_roadmap_progress.js +60 -0
  70. package/dist/discovery/deprecation-report.md +1 -1
  71. package/dist/discovery/discovery-manifest.json +13 -13
  72. package/dist/discovery/discovery-manifest.json.sha256 +1 -1
  73. package/dist/discovery/discovery-manifest.summary.md +1 -1
  74. package/dist/discovery/dormancy-report.md +1 -1
  75. package/dist/discovery/orphan-report.md +1 -1
  76. package/dist/discovery/packs.json +2 -2
  77. package/dist/discovery/trust-report.md +1 -1
  78. package/dist/discovery/workspaces.json +2 -2
  79. package/dist/hooks/dispatch.js +1813 -946
  80. package/dist/install/claudeLayerCarriage.js +237 -0
  81. package/dist/install/claudePathsPlan.js +79 -0
  82. package/dist/install/hostLayerFingerprint.js +1 -1
  83. package/dist/install/install.mjs +21 -3
  84. package/dist/install/partitionEligibility.js +231 -117
  85. package/dist/install/ruleLayerPartition.js +10 -7
  86. package/dist/mcp/registry-manifest.json +2 -2
  87. package/dist/mcp/server.json +2 -2
  88. package/dist/mcp/server.mjs +10870 -7641
  89. package/dist/scripts/_lib/lean_projection_mode.js +108 -0
  90. package/dist/scripts/_lib/router_match.js +243 -0
  91. package/dist/scripts/_lib/rule_injection.js +216 -0
  92. package/dist/scripts/condense.js +52 -45
  93. package/dist/scripts/project_thin_rules.js +139 -19
  94. package/dist/server/schemas/settings.js +16 -3
  95. package/docs/MIGRATION.md +105 -1
  96. package/docs/contracts/settings-classes.md +40 -8
  97. package/docs/guidelines/agent-infra/minimal-safe-diff-mechanics.md +73 -0
  98. package/docs/guidelines/agent-infra/notes-horizon-mechanics.md +90 -0
  99. package/llms.txt +1 -1
  100. package/package.json +7 -4
  101. package/src/agent-src/scripts/_cli_entry.ts +51 -0
  102. package/src/agent-src/scripts/archive_completed_roadmaps.ts +120 -27
  103. package/src/agent-src/scripts/roadmap_gates.ts +4 -20
  104. package/src/agent-src/scripts/roadmap_set_step.ts +5 -2
  105. package/src/agent-src/scripts/stubs_due.ts +4 -18
  106. package/src/agent-src/scripts/update_roadmap_progress.ts +7 -24
  107. package/src/config/agent-settings.template.yml +47 -1
  108. package/src/config/continuity-surface.json +516 -0
  109. package/src/config/estate-count-budget.json +1 -1
  110. package/src/config/evaluator-budgets.json +4 -4
  111. package/src/config/gate-coverage.yml +29 -8
  112. package/src/config/gate-violation-baselines.json +8 -8
  113. package/src/config/gitignore-block.txt +19 -0
  114. package/src/config/hook-token-budget.json +7 -6
  115. package/src/config/host-injection-effect.json +73 -0
  116. package/src/config/packed-binary-manifest.json +33 -0
  117. package/src/config/preamble-payload-budget.json +112 -105
  118. package/src/config/publish-surface.json +4 -2
  119. package/src/config/routing-coverage-seed.json +9 -2
  120. package/src/install/claudeLayerCarriage.ts +273 -0
  121. package/src/install/claudePathsPlan.ts +76 -0
  122. package/src/install/hostLayerFingerprint.ts +1 -1
  123. package/src/install/partitionEligibility.ts +261 -127
  124. package/src/install/ruleLayerPartition.ts +19 -23
  125. package/src/scripts/_cli/cmd_session_recycle.ts +2 -25
  126. package/src/scripts/_cli/handoff_sessions.ts +14 -44
  127. package/src/scripts/_dispatch.bash +77 -27
  128. package/src/scripts/_lib/auto_dispatch.ts +2 -2
  129. package/src/scripts/_lib/catalogue_layer_parity.ts +146 -0
  130. package/src/scripts/_lib/continuity_slot.ts +295 -0
  131. package/src/scripts/_lib/continuity_surface.ts +296 -0
  132. package/src/scripts/_lib/continuity_writer.ts +253 -0
  133. package/src/scripts/_lib/corpus_manifest.ts +26 -9
  134. package/src/scripts/_lib/hook_settings.ts +86 -1
  135. package/src/scripts/_lib/injection_effect.ts +151 -0
  136. package/src/scripts/_lib/layer_overlap_notice.ts +50 -6
  137. package/src/scripts/_lib/lean_projection_mode.ts +127 -0
  138. package/src/scripts/_lib/packed_binary_predicate.ts +262 -0
  139. package/src/scripts/_lib/pin_resolver.ts +13 -0
  140. package/src/scripts/_lib/recycle_envelope_paths.ts +109 -21
  141. package/src/scripts/_lib/regression_neighbourhood.ts +159 -22
  142. package/src/scripts/_lib/retired_status.ts +74 -0
  143. package/src/scripts/_lib/session_eol.ts +44 -0
  144. package/src/scripts/_lib/session_index_trust.ts +346 -0
  145. package/src/scripts/_lib/skill_catalogue.ts +156 -21
  146. package/src/scripts/_lib/standing_bound_ratchet.ts +154 -0
  147. package/src/scripts/_lib/subagent_capsule.ts +117 -8
  148. package/src/scripts/_lib/vendored_grammar_upstream.ts +280 -0
  149. package/src/scripts/ai_council/api_transport.ts +5 -0
  150. package/src/scripts/ai_council/deliberation_metrics.ts +353 -0
  151. package/src/scripts/ai_council/prompts.ts +9 -0
  152. package/src/scripts/ai_council/synthesis_disagreement.ts +206 -0
  153. package/src/scripts/audit_mcp_tools.ts +69 -25
  154. package/src/scripts/build_mcp_catalog.ts +24 -1
  155. package/src/scripts/capture_skill_catalogue.ts +24 -11
  156. package/src/scripts/check_candidate_lines.ts +499 -0
  157. package/src/scripts/check_continuity_surface.ts +475 -0
  158. package/src/scripts/check_dependency_floors.ts +13 -6
  159. package/src/scripts/check_estate_count.ts +40 -79
  160. package/src/scripts/check_host_tree_parity.ts +291 -0
  161. package/src/scripts/check_pack_size.ts +262 -23
  162. package/src/scripts/check_preamble_payload_budget.ts +136 -7
  163. package/src/scripts/check_roadmap_trackable.ts +19 -16
  164. package/src/scripts/check_rule_layer_partition.ts +54 -34
  165. package/src/scripts/check_rule_projection_integrity.ts +102 -2
  166. package/src/scripts/check_single_delivery.ts +97 -31
  167. package/src/scripts/check_update_banner.ts +13 -0
  168. package/src/scripts/ci_settle.ts +205 -5
  169. package/src/scripts/code_graph/build.ts +217 -15
  170. package/src/scripts/code_graph/cli.ts +334 -15
  171. package/src/scripts/code_graph/detect.ts +38 -0
  172. package/src/scripts/code_graph/extract.ts +11 -0
  173. package/src/scripts/code_graph/grammar_probe.ts +43 -0
  174. package/src/scripts/code_graph/loader.ts +88 -14
  175. package/src/scripts/code_graph/query.ts +144 -11
  176. package/src/scripts/code_graph/resolution_tiers.ts +238 -0
  177. package/src/scripts/code_graph/sqlite_store.ts +262 -6
  178. package/src/scripts/code_graph/types.ts +117 -3
  179. package/src/scripts/code_graph/validate.ts +26 -1
  180. package/src/scripts/code_graph/verbs.ts +590 -0
  181. package/src/scripts/condense.ts +54 -42
  182. package/src/scripts/corpus_manifest.ts +5 -5
  183. package/src/scripts/council_cli.ts +11 -12
  184. package/src/scripts/count_intent_disagreement.ts +426 -0
  185. package/src/scripts/handoff_context_hook.ts +118 -1
  186. package/src/scripts/hook_effect_doctor.ts +82 -1
  187. package/src/scripts/hook_manifest.json +1 -1
  188. package/src/scripts/hook_manifest.yaml +87 -12
  189. package/src/scripts/hooks/block_speaking_inbox_dir.ts +182 -16
  190. package/src/scripts/hooks/chain_nudge_hook.ts +282 -0
  191. package/src/scripts/hooks/code_graph_context_hook.ts +201 -0
  192. package/src/scripts/hooks/concern_registry.ts +4 -2
  193. package/src/scripts/hooks/host_semantics.ts +1 -1
  194. package/src/scripts/hooks/orchestration_record_hook.ts +2 -2
  195. package/src/scripts/hooks/push_settle_hook.ts +3 -2
  196. package/src/scripts/hooks/rule_inject_hook.ts +108 -21
  197. package/src/scripts/hooks/session_eol_hook.ts +120 -2
  198. package/src/scripts/hooks/skill_route_hook.ts +11 -4
  199. package/src/scripts/hot_context_hook.ts +62 -7
  200. package/src/scripts/inbox_source_census.ts +322 -0
  201. package/src/scripts/install-hooks.sh +82 -0
  202. package/src/scripts/{lint_carrier_integrity.ts → lint_deferral_integrity.ts} +60 -214
  203. package/src/scripts/lint_plan_risk_register.ts +13 -17
  204. package/src/scripts/lint_roadmap_blockers.ts +164 -1
  205. package/src/scripts/mcp_server/consumer_tool_catalog.json +152 -2
  206. package/src/scripts/mcp_server/graph_tools.ts +294 -0
  207. package/src/scripts/mcp_server/path_util.ts +31 -0
  208. package/src/scripts/mcp_server/suggest_skill.ts +72 -0
  209. package/src/scripts/mcp_server/tool_catalog_source.ts +7 -1
  210. package/src/scripts/mcp_server/tools.ts +5 -69
  211. package/src/scripts/media/lib/adapter-contract.md +7 -2
  212. package/src/scripts/media/lib/fixtures/flux/asset-0001.png +0 -0
  213. package/src/scripts/media/lib/fixtures/gemini-image/asset-0001.png +0 -0
  214. package/src/scripts/media/lib/fixtures/ideogram/asset-0001.png +0 -0
  215. package/src/scripts/model_rule_injection.ts +61 -4
  216. package/src/scripts/project_thin_rules.ts +149 -16
  217. package/src/scripts/rdp_candidates_delta.ts +276 -0
  218. package/src/scripts/rdp_quality_eval.ts +217 -19
  219. package/src/scripts/release_mix_taxonomy.json +3 -2
  220. package/src/scripts/report_host_injection_effect.ts +189 -0
  221. package/src/scripts/report_layer_overlap.ts +41 -0
  222. package/src/scripts/report_skill_menu_census.ts +341 -0
  223. package/src/scripts/report_standing_payload_by_host.ts +650 -0
  224. package/src/scripts/schemas/agent-settings.schema.json +7 -1
  225. package/src/scripts/session_memory_index.ts +81 -8
  226. package/src/scripts/skill_tools/score_skill_relevance.ts +70 -6
  227. package/src/scripts/skill_tools/suggest_skill_for_task.ts +37 -3
  228. package/src/server/schemas/settings.ts +23 -4
  229. package/src/vendor/grammars/README.md +86 -0
  230. package/src/vendor/grammars/tree-sitter-javascript.wasm +0 -0
  231. package/src/vendor/grammars/tree-sitter-php.wasm +0 -0
  232. package/src/vendor/grammars/tree-sitter-typescript.wasm +0 -0
  233. package/src/scripts/_lib/carrier_status.ts +0 -118
  234. package/src/scripts/_lib/estate_carrier_cases.ts +0 -72
  235. package/src/scripts/hooks/code_graph_nudge_hook.ts +0 -191
@@ -0,0 +1,276 @@
1
+ #!/usr/bin/env tsx
2
+ /**
3
+ * rdp_candidates_delta.ts — step 3.2's two numbers, computed from the two
4
+ * stored runs rather than asserted.
5
+ *
6
+ * Reads a baseline results file and a treatment results file in the envelope
7
+ * `rdp_quality_eval` writes, joins them on slot plus variant name, and prints:
8
+ *
9
+ * - the dim-5 delta, INTENTION-TO-TREAT: every treatment transcript against
10
+ * every baseline transcript on the same 16 slots. This is the primary
11
+ * reading, and it is primary on the 2026-09-08 council's instruction:
12
+ * conditioning on whether the model complied selects treatment outputs on
13
+ * post-treatment behaviour and biases the estimate. The compliance-
14
+ * conditioned figure is printed too, labelled exploratory.
15
+ * - the dim-1 reading on both arms. Dim 1 is the notes-first tripwire: dim 5
16
+ * rising while dim 1 falls means the line is being emitted into the reply
17
+ * instead of the notes, which is the third outcome step 3.3 must name
18
+ * rather than round away.
19
+ * - the output-token delta on the single-step (`ss`) slots, which are this
20
+ * corpus's trivial-task proxy and therefore where the published ~5 %
21
+ * L10 cost guard (`tests/reasoning-layer-eval/README.md` § Fail
22
+ * conditions) applies.
23
+ *
24
+ * A NOTE ON WHICH OVERHEAD THIS IS, because the corpus carries two and they
25
+ * are not the same number. `output_token_overhead_pct` stored in each run is
26
+ * arm-versus-arm WITHIN that run (orchestrated vs distributed). What step 3.2
27
+ * asks for is treatment-versus-baseline, which no stored field holds, so it is
28
+ * computed here from raw `output_tokens` on both sides.
29
+ *
30
+ * Exit: 0 printed · 1 usage/IO error · 2 a join hole (a slot or variant present
31
+ * on one side and not the other — a delta over a partial join is not a delta).
32
+ */
33
+
34
+ import * as fs from 'node:fs';
35
+ import * as path from 'node:path';
36
+ import { fileURLToPath, pathToFileURL } from 'node:url';
37
+
38
+ import { checkReport } from './check_candidate_lines.js';
39
+ import { assertScanned, DeadScopeError } from './_lib/scan_scope.js';
40
+
41
+ const _HERE = fileURLToPath(import.meta.url);
42
+ const REPO_ROOT = path.resolve(path.dirname(_HERE), '..', '..');
43
+
44
+ interface Variant {
45
+ text?: string;
46
+ output_tokens?: number;
47
+ score?: Record<string, unknown>;
48
+ }
49
+
50
+ export interface SlotRecord {
51
+ slot: string;
52
+ slug?: string;
53
+ mechanism?: string;
54
+ variants?: Record<string, Variant>;
55
+ }
56
+
57
+ /** Exported so a test can build a synthetic run without widening to `unknown`. */
58
+ export interface Run {
59
+ date?: string;
60
+ mode?: string;
61
+ scorer_model?: string | null;
62
+ actual_cost_usd?: number;
63
+ results?: SlotRecord[];
64
+ }
65
+
66
+ export interface Cell {
67
+ slot: string;
68
+ slug: string;
69
+ family: 'ms' | 'ss';
70
+ variant: string;
71
+ dim1: number | null;
72
+ dim5: number | null;
73
+ outputTokens: number | null;
74
+ /** Treatment side only: did the transcript actually carry a clean line? */
75
+ complied: boolean;
76
+ }
77
+
78
+ function familyOf(r: SlotRecord): 'ms' | 'ss' {
79
+ if (r.mechanism === 'stateless') return 'ss';
80
+ if (r.mechanism === 'multi-stage') return 'ms';
81
+ return (r.slug ?? '').startsWith('ss-') ? 'ss' : 'ms';
82
+ }
83
+
84
+ function num(v: unknown): number | null {
85
+ const n = Number(v);
86
+ return Number.isFinite(n) ? n : null;
87
+ }
88
+
89
+ export function flatten(run: Run, measureCompliance: boolean): Cell[] {
90
+ const out: Cell[] = [];
91
+ for (const r of run.results ?? []) {
92
+ for (const [variant, v] of Object.entries(r.variants ?? {})) {
93
+ let complied = false;
94
+ if (measureCompliance && typeof v.text === 'string' && v.text !== '') {
95
+ const verdict = checkReport(v.text);
96
+ complied = verdict.lines.length > 0 && verdict.findings.length === 0;
97
+ }
98
+ out.push({
99
+ slot: r.slot,
100
+ slug: r.slug ?? '',
101
+ family: familyOf(r),
102
+ variant,
103
+ dim1: num(v.score?.dim1),
104
+ dim5: num(v.score?.dim5),
105
+ outputTokens: num(v.output_tokens),
106
+ complied,
107
+ });
108
+ }
109
+ }
110
+ return out;
111
+ }
112
+
113
+ const mean = (xs: number[]): number | null =>
114
+ xs.length === 0 ? null : Math.round((xs.reduce((p, c) => p + c, 0) / xs.length) * 1000) / 1000;
115
+
116
+ const fmt = (x: number | null, digits = 3): string => (x === null ? 'n/a' : x.toFixed(digits));
117
+
118
+ export interface Report {
119
+ lines: string[];
120
+ joinHoles: string[];
121
+ }
122
+
123
+ export function compare(base: Run, treat: Run): Report {
124
+ const b = flatten(base, false);
125
+ const t = flatten(treat, true);
126
+ const key = (c: Cell): string => `${c.slot}/${c.variant}`;
127
+ const bMap = new Map(b.map((c) => [key(c), c]));
128
+ const tMap = new Map(t.map((c) => [key(c), c]));
129
+
130
+ const joinHoles: string[] = [];
131
+ for (const k of bMap.keys()) if (!tMap.has(k)) joinHoles.push(`treatment missing ${k}`);
132
+ for (const k of tMap.keys()) if (!bMap.has(k)) joinHoles.push(`baseline missing ${k}`);
133
+
134
+ const paired = [...bMap.keys()].filter((k) => tMap.has(k)).sort();
135
+ const L: string[] = [];
136
+
137
+ L.push(`baseline: ${base.mode ?? '?'} · ${String((base.results ?? []).length)} slot(s) · rater ${base.scorer_model ?? '?'}`);
138
+ L.push(`treatment: ${treat.mode ?? '?'} · ${String((treat.results ?? []).length)} slot(s) · rater ${treat.scorer_model ?? '?'}`);
139
+ L.push(`paired transcripts: ${String(paired.length)}`);
140
+ L.push('');
141
+
142
+ // ---- dim5, intention-to-treat (primary) ---------------------------------
143
+ const bDim5 = paired.map((k) => (bMap.get(k) as Cell).dim5).filter((x): x is number => x !== null);
144
+ const tDim5 = paired.map((k) => (tMap.get(k) as Cell).dim5).filter((x): x is number => x !== null);
145
+ const bM = mean(bDim5);
146
+ const tM = mean(tDim5);
147
+ L.push('dim5 — form-alternative surfacing (INTENTION-TO-TREAT, primary)');
148
+ L.push(` baseline mean ${fmt(bM)} / 3 (${bM === null ? 'n/a' : ((bM / 3) * 100).toFixed(1)} %) n=${String(bDim5.length)}`);
149
+ L.push(` treatment mean ${fmt(tM)} / 3 (${tM === null ? 'n/a' : ((tM / 3) * 100).toFixed(1)} %) n=${String(tDim5.length)}`);
150
+ if (bM !== null && tM !== null) {
151
+ const dPts = Math.round((tM - bM) * 1000) / 1000;
152
+ const dPp = Math.round(((tM - bM) / 3) * 1000) / 10;
153
+ L.push(` delta ${dPts > 0 ? '+' : ''}${fmt(dPts)} / 3 (${dPp > 0 ? '+' : ''}${dPp.toFixed(1)} pp of the 0-3 scale)`);
154
+ }
155
+ const bZero = bDim5.filter((x) => x === 0).length;
156
+ const tZero = tDim5.filter((x) => x === 0).length;
157
+ L.push(` scoring 0 (one form only): baseline ${String(bZero)}/${String(bDim5.length)} → treatment ${String(tZero)}/${String(tDim5.length)}`);
158
+ const bTop = bDim5.filter((x) => x >= 2).length;
159
+ const tTop = tDim5.filter((x) => x >= 2).length;
160
+ L.push(` scoring >= 2: baseline ${String(bTop)}/${String(bDim5.length)} → treatment ${String(tTop)}/${String(tDim5.length)}`);
161
+ L.push('');
162
+
163
+ // ---- dim1, the tripwire -------------------------------------------------
164
+ const bDim1 = paired.map((k) => (bMap.get(k) as Cell).dim1).filter((x): x is number => x !== null);
165
+ const tDim1 = paired.map((k) => (tMap.get(k) as Cell).dim1).filter((x): x is number => x !== null);
166
+ const b1 = mean(bDim1);
167
+ const t1 = mean(tDim1);
168
+ L.push('dim1 — notes-first adherence (TRIPWIRE: dim5 up while dim1 down = the line is in the reply)');
169
+ L.push(` baseline mean ${fmt(b1)} / 3 treatment mean ${fmt(t1)} / 3` +
170
+ (b1 !== null && t1 !== null ? ` delta ${t1 - b1 > 0 ? '+' : ''}${fmt(Math.round((t1 - b1) * 1000) / 1000)}` : ''));
171
+ L.push('');
172
+
173
+ // ---- compliance (exploratory) ------------------------------------------
174
+ const complied = paired.filter((k) => (tMap.get(k) as Cell).complied);
175
+ L.push('compliance — a well-formed Candidates line, zero shape findings');
176
+ L.push(` ${String(complied.length)} / ${String(paired.length)} treatment transcript(s)` +
177
+ ` (${paired.length === 0 ? 'n/a' : ((complied.length / paired.length) * 100).toFixed(1)} %)`);
178
+ const cDim5 = complied.map((k) => (tMap.get(k) as Cell).dim5).filter((x): x is number => x !== null);
179
+ const cM = mean(cDim5);
180
+ L.push(` dim5 among compliant: ${fmt(cM)} / 3 n=${String(cDim5.length)} [EXPLORATORY — conditioning on`);
181
+ L.push(' post-treatment behaviour biases the estimate; the ITT figure above is primary]');
182
+ L.push('');
183
+
184
+ // ---- per family ---------------------------------------------------------
185
+ L.push('dim5 by family');
186
+ for (const fam of ['ms', 'ss'] as const) {
187
+ const ks = paired.filter((k) => (bMap.get(k) as Cell).family === fam);
188
+ const bf = mean(ks.map((k) => (bMap.get(k) as Cell).dim5).filter((x): x is number => x !== null));
189
+ const tf = mean(ks.map((k) => (tMap.get(k) as Cell).dim5).filter((x): x is number => x !== null));
190
+ L.push(` ${fam}: baseline ${fmt(bf)} → treatment ${fmt(tf)}` +
191
+ (bf !== null && tf !== null ? ` delta ${tf - bf > 0 ? '+' : ''}${fmt(Math.round((tf - bf) * 1000) / 1000)}` : '') +
192
+ ` n=${String(ks.length)}`);
193
+ }
194
+ L.push('');
195
+
196
+ // ---- ss output-token overhead, treatment vs baseline -------------------
197
+ L.push('output-token overhead on the ss (single-step) slots — treatment vs baseline');
198
+ L.push(' the L10 cost guard is ~5 % on the trivial proxy (README.md § Fail conditions)');
199
+ const ssKeys = paired.filter((k) => (bMap.get(k) as Cell).family === 'ss');
200
+ const perSlot: number[] = [];
201
+ for (const k of ssKeys) {
202
+ const bo = (bMap.get(k) as Cell).outputTokens;
203
+ const to = (tMap.get(k) as Cell).outputTokens;
204
+ if (bo === null || to === null || bo === 0) continue;
205
+ const pct = Math.round(((to - bo) / bo) * 1000) / 10;
206
+ perSlot.push(pct);
207
+ L.push(` ${k.padEnd(18)} ${String(bo).padStart(5)} → ${String(to).padStart(5)} ${pct > 0 ? '+' : ''}${pct.toFixed(1)} %`);
208
+ }
209
+ const ssMean = mean(perSlot);
210
+ L.push(` mean per-slot overhead: ${ssMean === null ? 'n/a' : `${ssMean > 0 ? '+' : ''}${ssMean.toFixed(1)} %`} n=${String(perSlot.length)}`);
211
+ // Aggregate too: a mean of per-slot percentages is dominated by small
212
+ // denominators, so the token-weighted figure is printed beside it rather
213
+ // than instead of it.
214
+ const bTot = ssKeys.reduce((p, k) => p + ((bMap.get(k) as Cell).outputTokens ?? 0), 0);
215
+ const tTot = ssKeys.reduce((p, k) => p + ((tMap.get(k) as Cell).outputTokens ?? 0), 0);
216
+ if (bTot > 0) {
217
+ const agg = Math.round(((tTot - bTot) / bTot) * 1000) / 10;
218
+ L.push(` token-weighted overhead: ${agg > 0 ? '+' : ''}${agg.toFixed(1)} % (${String(bTot)} → ${String(tTot)} output tokens)`);
219
+ }
220
+
221
+ return { lines: L, joinHoles };
222
+ }
223
+
224
+ export function main(argv: readonly string[] = process.argv.slice(2)): number {
225
+ const bIdx = argv.indexOf('--baseline');
226
+ const tIdx = argv.indexOf('--treatment');
227
+ const bPath = bIdx === -1 ? undefined : argv[bIdx + 1];
228
+ const tPath = tIdx === -1 ? undefined : argv[tIdx + 1];
229
+ if (bPath === undefined || tPath === undefined) {
230
+ process.stderr.write('usage: rdp_candidates_delta --baseline <results.json> --treatment <results.json>\n');
231
+ return 1;
232
+ }
233
+ let base: Run;
234
+ let treat: Run;
235
+ try {
236
+ base = JSON.parse(fs.readFileSync(path.resolve(bPath), 'utf-8')) as Run;
237
+ treat = JSON.parse(fs.readFileSync(path.resolve(tPath), 'utf-8')) as Run;
238
+ } catch (e) {
239
+ process.stderr.write(`cannot read inputs: ${String(e)}\n`);
240
+ return 1;
241
+ }
242
+
243
+ const rep = compare(base, treat);
244
+ process.stdout.write(`${rep.lines.join('\n')}\n`);
245
+
246
+ const paired = rep.lines.find((l) => l.startsWith('paired transcripts:'));
247
+ const n = paired ? Number(paired.split(':')[1]) : 0;
248
+ try {
249
+ assertScanned({
250
+ gate: 'rdp_candidates_delta',
251
+ scanned: n,
252
+ units: 'paired transcript(s)',
253
+ roots: [bPath, tPath],
254
+ });
255
+ } catch (e) {
256
+ if (e instanceof DeadScopeError) {
257
+ process.stderr.write(`❌ ${e.message}\n`);
258
+ return 1;
259
+ }
260
+ throw e;
261
+ }
262
+
263
+ if (rep.joinHoles.length > 0) {
264
+ process.stderr.write(`\n❌ ${String(rep.joinHoles.length)} join hole(s) — a delta over a partial join is not a delta:\n`);
265
+ for (const h of rep.joinHoles) process.stderr.write(` ${h}\n`);
266
+ return 2;
267
+ }
268
+ process.stdout.write(`\nscanned: ${String(n)}\n`);
269
+ return 0;
270
+ }
271
+
272
+ if (process.argv[1] && import.meta.url === pathToFileURL(path.resolve(process.argv[1])).href) {
273
+ process.exit(main());
274
+ }
275
+
276
+ export { REPO_ROOT };
@@ -108,6 +108,50 @@ Coordinate the links; do not let later steps run before earlier ones. On a
108
108
  trivial or fully-specified task, do NOT force the chain (that is over-process) —
109
109
  engage it only where the task is genuinely complex/ambiguous/interdependent.`;
110
110
 
111
+ /**
112
+ * The candidate-forms treatment, appended to EVERY arm by `--candidates`.
113
+ *
114
+ * Step 3.1 of `road-to-candidate-moves-floor`. The 2026-09-07 split council
115
+ * authorised instructing this line to the model under test in the treatment
116
+ * arm, "where it can be measured without being a shipped obligation on
117
+ * anyone", and blocked the shipped form. So it lives here, in the eval's own
118
+ * system prompt, and nowhere in `src/rules/` or `mandated-lines.md`.
119
+ *
120
+ * Appended to BOTH l6 arms rather than becoming a third arm, because the
121
+ * corpus has no per-slot field for a treatment and the baseline this must be
122
+ * compared against is the 32 stored transcripts — 16 slots × the same two RDP
123
+ * framings. Appending to both keeps the candidates block the single variable.
124
+ *
125
+ * The grammar is the one `check_candidate_lines.ts` parses, so compliance is
126
+ * measurable rather than eyeballed.
127
+ */
128
+ const CANDIDATES_BLOCK = `## Candidate forms before the choice
129
+
130
+ Before you settle on HOW to solve this, generate the candidate solution FORMS
131
+ and emit one line naming them. Do this at the decision point, before the
132
+ answer — not as a trailing summary.
133
+
134
+ Emit exactly one line in this shape:
135
+
136
+ Candidates: K0 <keep the current form / change nothing> · A <form> [<the axis
137
+ it differs on>] · B <form> [<axis>] → <chosen id>; <the observation that
138
+ decided it>
139
+
140
+ Rules:
141
+ - \`K0\` is "keep the current form / change nothing". It is listed FIRST and it
142
+ is a real option — draw it when it is right.
143
+ - Each non-K0 candidate carries, in brackets, the AXIS it differs on. Three
144
+ candidates that differ only in where a helper lives are one candidate.
145
+ - The axes must be pairwise distinct.
146
+ - End with the choice and the OBSERVATION that decided between them — not a
147
+ preference.
148
+ - Where exactly one form is admissible (a framework fixes the extension point,
149
+ an existing contract fixes the location), emit a single candidate carrying
150
+ \`[forced: <the constraint>]\` and say what it forecloses. Never invent a
151
+ second form so the line looks full.
152
+ - On a genuinely trivial or fully-specified task, this line is not owed. Do not
153
+ emit it to prove you read this.`;
154
+
111
155
  interface Slot {
112
156
  n: string;
113
157
  slug: string;
@@ -127,18 +171,22 @@ interface Variant {
127
171
  score?: Record<string, unknown>;
128
172
  }
129
173
 
130
- function buildArms(mode: string): Array<[string, string]> {
174
+ function buildArms(mode: string, candidates = false): Array<[string, string]> {
131
175
  const treat = `${BASE_SYSTEM}\n\n${RDP_BLOCK}`;
132
- if (mode === 'l6') {
133
- return [
134
- ['distributed', treat],
135
- ['orchestrated', `${treat}\n\n${ORCHESTRATOR_PREAMBLE}`],
136
- ];
137
- }
138
- return [
139
- ['baseline', BASE_SYSTEM],
140
- ['treatment', treat],
141
- ];
176
+ const arms: Array<[string, string]> =
177
+ mode === 'l6'
178
+ ? [
179
+ ['distributed', treat],
180
+ ['orchestrated', `${treat}\n\n${ORCHESTRATOR_PREAMBLE}`],
181
+ ]
182
+ : [
183
+ ['baseline', BASE_SYSTEM],
184
+ ['treatment', treat],
185
+ ];
186
+ if (!candidates) return arms;
187
+ // Appended to EVERY arm: the candidates block is the single variable
188
+ // against the stored run, so it must not co-vary with the arm.
189
+ return arms.map(([name, sys]) => [name, `${sys}\n\n${CANDIDATES_BLOCK}`]);
142
190
  }
143
191
 
144
192
  function approxTokens(text: string): number {
@@ -196,13 +244,20 @@ async function callAnthropic(
196
244
  }
197
245
 
198
246
  const SCORER_SYSTEM = `You are a strict, impartial evaluator. Score one assistant transcript against a
199
- 4-dimension rubric (0–3 each; 3 = excellent, 0 = absent/harmful). Dimensions:
247
+ 5-dimension rubric (0–3 each; 3 = excellent, 0 = absent/harmful). Dimensions:
200
248
  1 notes-first adherence — reasoning kept out of the answer proper; answer = conclusions + evidence (not a raw chain-of-thought dump).
201
249
  2 grounding — environment/constraints/info-gaps closed (or explicitly assumed) before designing; no jumping to a solution against assumptions.
202
250
  3 premature-solution avoidance — the hardest/load-bearing unknown resolved before dependent work; no over-production of code/answer before grounding.
203
251
  4 coherence / re-grounded summary — final answer readable by someone who saw none of the working thread; outcome-first; no arrow-chain shorthand.
252
+ 5 form-alternative surfacing — was a materially DIFFERENT SOLUTION FORM generated and discriminated BEFORE the choice, rather than the one form the transcript started with being verified afterwards? This is NOT dimension 3: dim 3 scores which unknown was resolved first INSIDE a chosen form; dim 5 scores whether a second form was ever on the table at all. A transcript can be perfectly risk-first inside the only shape it ever considered, and that scores 0 here.
253
+ 0 = one form only. A single approach appears and everything after it is verification. Good execution does NOT raise this.
254
+ 1 = a second form is mentioned but not discriminated — no axis it differs on, or no reason given for dropping it.
255
+ 2 = a second form is named on a stated axis, but nothing observable decides between them; the choice reads as preference.
256
+ 3 = a second form is named on a stated axis AND the observation that decided between them is stated. "Keep the current form / change nothing" counts as a form.
257
+ Forced-form carve-out: where exactly one form is admissible (a framework fixes the extension point, an existing contract fixes the location), a transcript that NAMES the constraint foreclosing the alternatives scores 3, not 0.
258
+ Materially different means differing on an AXIS, not in placement: three options that differ only in where a helper lives are one option.
204
259
  Also report reasoning_extraction_refusal: true ONLY if the model refused due to a meta/extraction instruction.
205
- Respond with STRICT JSON only, no prose: {"dim1":N,"dim2":N,"dim3":N,"dim4":N,"reasoning_extraction_refusal":false,"note":"<=120 chars"}`;
260
+ Respond with STRICT JSON only, no prose: {"dim1":N,"dim2":N,"dim3":N,"dim4":N,"dim5":N,"reasoning_extraction_refusal":false,"note":"<=120 chars"}`;
206
261
 
207
262
  async function scoreTranscript(
208
263
  apiKey: string,
@@ -246,11 +301,118 @@ function parseArgs(argv: string[]): Record<string, string | boolean> {
246
301
  return a;
247
302
  }
248
303
 
304
+ /**
305
+ * Mean over the dimensions the score actually carries.
306
+ *
307
+ * `dim5` is scored from 2026-09-07 and the 32 stored L6N transcripts predate
308
+ * it, so the divisor is the number of dimensions PRESENT, never a constant.
309
+ * A fixed `/5` would silently deflate every historical mean by a fifth and
310
+ * make the treatment-minus-baseline delta this eval exists to compute read as
311
+ * an improvement that is really an arithmetic artefact.
312
+ */
249
313
  function meanDims(score: Record<string, unknown> | undefined): number | null {
250
314
  if (!score) return null;
251
- const ds = ['dim1', 'dim2', 'dim3', 'dim4'].map((k) => Number(score[k]));
315
+ const required = ['dim1', 'dim2', 'dim3', 'dim4'];
316
+ const ds = required.map((k) => Number(score[k]));
252
317
  if (ds.some((x) => Number.isNaN(x))) return null;
253
- return ds.reduce((p, c) => p + c, 0) / 4;
318
+ if (score.dim5 !== undefined && !Number.isNaN(Number(score.dim5))) ds.push(Number(score.dim5));
319
+ return ds.reduce((p, c) => p + c, 0) / ds.length;
320
+ }
321
+
322
+ /** One stored run: the envelope `--score-only` reads and writes back. */
323
+ interface StoredRun {
324
+ date?: string;
325
+ mode?: string;
326
+ scorer_model?: string | null;
327
+ results: Array<{
328
+ slot: string;
329
+ slug?: string;
330
+ band?: string;
331
+ prompt: string;
332
+ variants: Record<string, { text?: string; score?: Record<string, unknown> }>;
333
+ }>;
334
+ }
335
+
336
+ /**
337
+ * Re-score an existing results file with a rater, capturing nothing.
338
+ *
339
+ * The transcripts are read from disk and sent to the scorer; no assistant call
340
+ * is made, so the only spend is the rater. Every score the run already carried
341
+ * is REPLACED rather than merged: a half-updated score object where `dim5` came
342
+ * from one rater and `dim1`-`dim4` from another is not a reading of anything.
343
+ */
344
+ async function scoreOnly(
345
+ sourcePath: string,
346
+ scorerModel: string,
347
+ outPath: string,
348
+ confirm: boolean,
349
+ ): Promise<number> {
350
+ if (!fs.existsSync(sourcePath)) {
351
+ process.stderr.write(`❌ --score-only: no such results file: ${sourcePath}\n`);
352
+ return 2;
353
+ }
354
+ const run = JSON.parse(fs.readFileSync(sourcePath, 'utf-8')) as StoredRun;
355
+ const transcripts: Array<{ slot: string; variant: string; prompt: string; text: string }> = [];
356
+ for (const r of run.results ?? []) {
357
+ for (const [variant, v] of Object.entries(r.variants ?? {})) {
358
+ if (typeof v.text === 'string' && v.text.length > 0) {
359
+ transcripts.push({ slot: r.slot, variant, prompt: r.prompt, text: v.text });
360
+ }
361
+ }
362
+ }
363
+ if (transcripts.length === 0) {
364
+ // Not "scored zero and passed" — an empty corpus is unreadable input.
365
+ process.stderr.write(
366
+ `❌ --score-only: ${sourcePath} carries no transcript text. ` +
367
+ `A re-score over nothing is not a baseline.\n`,
368
+ );
369
+ return 2;
370
+ }
371
+
372
+ const est = estimate_cost(scorerModel, 2000, 300) * transcripts.length;
373
+ out(
374
+ `rdp-eval · mode=score-only · ${String(transcripts.length)} stored transcript(s) ` +
375
+ `from ${sourcePath} · rater ${scorerModel} · 0 capture calls`,
376
+ );
377
+ out(` EXPECTED TOTAL (worst-case): ~$${est.toFixed(4)}`);
378
+ if (!confirm) {
379
+ out('\nDRY-RUN — no spend. Re-run with --confirm to score.');
380
+ return 0;
381
+ }
382
+ // Score-only skips CAPTURE, not spend — the rater is billed per transcript.
383
+ // It therefore inherits the capture path's non-tty guard verbatim rather
384
+ // than becoming the unguarded way to bill the same account.
385
+ if (!process.stdin.isTTY && process.env.RDP_EVAL_ALLOW_NONTTY !== '1') {
386
+ throw new Error(
387
+ 'Refusing a billable score-only run on non-tty stdin. Run interactively, or set ' +
388
+ 'RDP_EVAL_ALLOW_NONTTY=1 if the caller has already confirmed the cost.',
389
+ );
390
+ }
391
+
392
+ const apiKey = load_anthropic_key();
393
+ let actual = 0;
394
+ let failed = 0;
395
+ for (const t of transcripts) {
396
+ console.error(` scoring ${t.slot}/${t.variant} via ${scorerModel} …`);
397
+ const sc = await scoreTranscript(apiKey, scorerModel, t.prompt, t.text);
398
+ actual += estimate_cost(scorerModel, 2000, 300);
399
+ const target = run.results.find((r) => r.slot === t.slot)?.variants[t.variant];
400
+ if (target) {
401
+ // An unparsable reply DELETES the stale score rather than keeping
402
+ // it: a score from the previous rater sitting under this run's
403
+ // `scorer_model` would misattribute the reading.
404
+ if (sc === null) delete target.score;
405
+ else target.score = sc;
406
+ }
407
+ if (sc === null) failed += 1;
408
+ }
409
+ run.scorer_model = scorerModel;
410
+ run.mode = `${run.mode ?? 'unknown'}+score-only`;
411
+ fs.writeFileSync(outPath, `${JSON.stringify(run, null, 2)}\n`);
412
+ out(`\nscanned: ${String(transcripts.length)}`);
413
+ out(`rdp-eval · wrote ${outPath} · ~$${actual.toFixed(4)} · ${String(failed)} unparsable`);
414
+ // An unparsable rater reply is a hole in the reading, not a rounding error.
415
+ return failed > 0 ? 1 : 0;
254
416
  }
255
417
 
256
418
  async function main(): Promise<number> {
@@ -268,9 +430,29 @@ async function main(): Promise<number> {
268
430
  (args.results as string) ??
269
431
  path.join(GT_DIR, mode === 'l6' ? 'l6-results.json' : 'results.json');
270
432
 
433
+ // ---- score-only: re-score transcripts that already exist -----------------
434
+ // A stored results file IS the baseline arm for a dimension added after it
435
+ // was captured. Re-scoring it costs the scorer calls and NOTHING for
436
+ // capture, which is the only way a baseline for `dim5` can exist at all:
437
+ // the 32 L6N transcripts predate the dimension, and re-capturing them would
438
+ // produce a *different* baseline under today's suite rather than the one
439
+ // the treatment must be compared against.
440
+ if (args['score-only']) {
441
+ const scoreOnlyPath = String(args['score-only']);
442
+ if (!scoreWith) {
443
+ process.stderr.write('❌ --score-only requires --score-with <model> (the rater).\n');
444
+ return 2;
445
+ }
446
+ return await scoreOnly(scoreOnlyPath, scoreWith, resultsPath, Boolean(args.confirm));
447
+ }
448
+
449
+ const withCandidates = Boolean(args.candidates);
271
450
  const slots = loadSlots(corpusPath, selected);
272
- const arms = buildArms(mode);
451
+ const arms = buildArms(mode, withCandidates);
273
452
  const variantNames = arms.map((a) => a[0]);
453
+ if (withCandidates) {
454
+ out(' treatment: +CANDIDATES_BLOCK on every arm (step 3.1 — instructed, not shipped)');
455
+ }
274
456
 
275
457
  // ---- cost preview --------------------------------------------------------
276
458
  let total = 0;
@@ -361,7 +543,7 @@ async function main(): Promise<number> {
361
543
  rater2_mean: { [v0]: m0, [v1]: m1 },
362
544
  rater2_delta: m0 !== null && m1 !== null ? Math.round((m1 - m0) * 100) / 100 : null,
363
545
  });
364
- writeTranscript(s, variants, ts, overhead, variantNames, mode);
546
+ writeTranscript(s, variants, ts, overhead, variantNames, mode, withCandidates);
365
547
  }
366
548
 
367
549
  fs.writeFileSync(
@@ -369,7 +551,9 @@ async function main(): Promise<number> {
369
551
  JSON.stringify(
370
552
  {
371
553
  date: ts,
372
- mode,
554
+ // Self-describing: a results file whose arms carried the
555
+ // candidates block must not be readable as a plain l6 run.
556
+ mode: withCandidates ? `${mode}+candidates` : mode,
373
557
  standard_model: standardModel,
374
558
  strong_model: strongModel,
375
559
  scorer_model: scoreWith ?? null,
@@ -386,6 +570,18 @@ async function main(): Promise<number> {
386
570
  return 0;
387
571
  }
388
572
 
573
+ /**
574
+ * The transcript filename is keyed on the MODE, so a run under a different
575
+ * treatment must not reuse it.
576
+ *
577
+ * Found the hard way on 2026-09-08: `--candidates` reused the plain `l6n-`
578
+ * prefix and silently overwrote all 16 committed June baseline transcripts
579
+ * mid-run. The measurement survived — `--results` had been pointed at a new
580
+ * path and every transcript body is stored in that JSON — but the baseline's
581
+ * human-readable artefacts were replaced by treatment ones under the baseline's
582
+ * own names, which is the worst kind of quiet: a later reader comparing the
583
+ * markdown would have compared the treatment against itself.
584
+ */
389
585
  function writeTranscript(
390
586
  slot: Slot,
391
587
  variants: Record<string, Variant>,
@@ -393,8 +589,10 @@ function writeTranscript(
393
589
  overhead: number | null,
394
590
  variantNames: string[],
395
591
  mode: string,
592
+ withCandidates = false,
396
593
  ): void {
397
- const prefix = mode === 'l6' ? 'l6n-' : '';
594
+ const base = mode === 'l6' ? 'l6n-' : '';
595
+ const prefix = withCandidates ? `${base}cand-` : base;
398
596
  const p = path.join(GT_DIR, `${prefix}${slot.n}-${slot.slug}.md`);
399
597
  const L: string[] = [
400
598
  `# Transcript — slot ${slot.n}: ${slot.slug}`,
@@ -1,6 +1,6 @@
1
1
  {
2
- "taxonomy_version": "1.0.0",
3
- "updated": "2026-09-04",
2
+ "taxonomy_version": "1.1.0",
3
+ "updated": "2026-09-08",
4
4
  "contract": "docs/decisions/ADR-253-per-pr-user-artifact-gate-declined.md",
5
5
  "reader": "src/scripts/measure_release_mix.ts",
6
6
  "note": [
@@ -62,6 +62,7 @@
62
62
  { "prefix": "src/install/", "category": "consumer" },
63
63
  { "prefix": "src/server/", "category": "consumer" },
64
64
  { "prefix": "src/ui/", "category": "consumer" },
65
+ { "prefix": "src/vendor/grammars/", "category": "consumer" },
65
66
  { "prefix": "src/shared/", "category": "unclassified" },
66
67
 
67
68
  { "prefix": "src/scripts/", "category": "maintenance" },