@event4u/agent-config 14.21.0 → 14.22.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 (181) 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 +198 -0
  5. package/CREDITS.md +37 -0
  6. package/dist/agent-src/commands/analyze/inbox.md +380 -5
  7. package/dist/agent-src/commands/analyze/repo.md +4 -2
  8. package/dist/agent-src/contexts/communication/rules-auto/token-efficiency-mechanics.md +69 -0
  9. package/dist/agent-src/contexts/execution/auto-dispatch-classification.md +5 -5
  10. package/dist/agent-src/rules/external-code-graph-interop.md +19 -14
  11. package/dist/agent-src/rules/token-efficiency.md +26 -3
  12. package/dist/agent-src/scripts/_cli_entry.ts +51 -0
  13. package/dist/agent-src/scripts/archive_completed_roadmaps.ts +14 -23
  14. package/dist/agent-src/scripts/roadmap_gates.ts +4 -20
  15. package/dist/agent-src/scripts/roadmap_set_step.ts +5 -2
  16. package/dist/agent-src/scripts/stubs_due.ts +4 -18
  17. package/dist/agent-src/scripts/update_roadmap_progress.ts +7 -24
  18. package/dist/agent-src/skills/code-intelligence/SKILL.md +25 -0
  19. package/dist/agent-src/skills/roadmap-writing/SKILL.md +17 -0
  20. package/dist/agent-src/templates/agent-settings.md +0 -1
  21. package/dist/agent-src/templates/agents/agent-project-settings.example.yml +1 -1
  22. package/dist/cli-delegate/_cli_entry.js +8 -0
  23. package/dist/cli-delegate/_lib/pin_resolver.js +170 -0
  24. package/dist/cli-delegate/archival_sweep.js +8 -0
  25. package/dist/cli-delegate/archive_completed_roadmaps.js +494 -0
  26. package/dist/cli-delegate/check_update_banner.js +923 -0
  27. package/dist/cli-delegate/chunk-5EK5WQN6.js +28 -0
  28. package/dist/cli-delegate/chunk-7XGD2OEU.js +96 -0
  29. package/dist/cli-delegate/{chunk-643Z7FIC.js → chunk-BIXEDQPZ.js} +6 -0
  30. package/dist/cli-delegate/chunk-DBH4AHDK.js +155 -0
  31. package/dist/cli-delegate/chunk-JO25G5H7.js +20 -0
  32. package/dist/cli-delegate/chunk-LTYVIY76.js +7961 -0
  33. package/dist/cli-delegate/chunk-QAJS5YWY.js +999 -0
  34. package/dist/cli-delegate/{chunk-ZVBOHSVF.js → chunk-TGIPZBCW.js} +1 -1
  35. package/dist/cli-delegate/{chunk-YS6XTLEK.js → chunk-TYSY33MD.js} +56 -6
  36. package/dist/cli-delegate/chunk-ULET4BYS.js +328 -0
  37. package/dist/cli-delegate/chunk-YDQSHTD7.js +187 -0
  38. package/dist/cli-delegate/chunk-YWVXLR44.js +48 -0
  39. package/dist/cli-delegate/chunk-ZZQBUHFK.js +273 -0
  40. package/dist/cli-delegate/cmd_conformance.js +2 -2
  41. package/dist/cli-delegate/cmd_export.js +1 -1
  42. package/dist/cli-delegate/cmd_handoff.js +26 -2
  43. package/dist/cli-delegate/cmd_prune.js +1 -1
  44. package/dist/cli-delegate/cmd_session_recycle.js +2 -2
  45. package/dist/cli-delegate/cmd_settings_migrate.js +1 -1
  46. package/dist/cli-delegate/cmd_sync.js +1 -1
  47. package/dist/cli-delegate/cmd_uninstall.js +1 -1
  48. package/dist/cli-delegate/cmd_validate.js +1 -1
  49. package/dist/cli-delegate/dashboard_mode.js +16 -0
  50. package/dist/cli-delegate/gate_budget.js +24 -0
  51. package/dist/cli-delegate/gate_execute.js +25 -0
  52. package/dist/cli-delegate/guarded_baseline.js +18 -0
  53. package/dist/cli-delegate/resume_probe.js +22 -0
  54. package/dist/cli-delegate/roadmap_gates.js +638 -0
  55. package/dist/cli-delegate/roadmap_set_step.js +173 -0
  56. package/dist/cli-delegate/stub_queue.js +47 -0
  57. package/dist/cli-delegate/stubs_due.js +38 -0
  58. package/dist/cli-delegate/ticket_bundles.js +59 -0
  59. package/dist/cli-delegate/update_roadmap_progress.js +60 -0
  60. package/dist/discovery/deprecation-report.md +1 -1
  61. package/dist/discovery/discovery-manifest.json +8 -8
  62. package/dist/discovery/discovery-manifest.json.sha256 +1 -1
  63. package/dist/discovery/discovery-manifest.summary.md +1 -1
  64. package/dist/discovery/dormancy-report.md +1 -1
  65. package/dist/discovery/orphan-report.md +1 -1
  66. package/dist/discovery/packs.json +2 -2
  67. package/dist/discovery/trust-report.md +1 -1
  68. package/dist/discovery/workspaces.json +2 -2
  69. package/dist/hooks/dispatch.js +1193 -917
  70. package/dist/install/claudeLayerCarriage.js +237 -0
  71. package/dist/install/claudePathsPlan.js +79 -0
  72. package/dist/install/hostLayerFingerprint.js +1 -1
  73. package/dist/install/install.mjs +6 -0
  74. package/dist/install/partitionEligibility.js +231 -117
  75. package/dist/install/ruleLayerPartition.js +10 -7
  76. package/dist/mcp/registry-manifest.json +2 -2
  77. package/dist/mcp/server.json +2 -2
  78. package/dist/mcp/server.mjs +39219 -38995
  79. package/dist/scripts/condense.js +32 -25
  80. package/docs/MIGRATION.md +49 -1
  81. package/docs/contracts/settings-classes.md +1 -1
  82. package/docs/guidelines/agent-infra/minimal-safe-diff-mechanics.md +73 -0
  83. package/package.json +7 -4
  84. package/src/agent-src/scripts/_cli_entry.ts +51 -0
  85. package/src/agent-src/scripts/archive_completed_roadmaps.ts +14 -23
  86. package/src/agent-src/scripts/roadmap_gates.ts +4 -20
  87. package/src/agent-src/scripts/roadmap_set_step.ts +5 -2
  88. package/src/agent-src/scripts/stubs_due.ts +4 -18
  89. package/src/agent-src/scripts/update_roadmap_progress.ts +7 -24
  90. package/src/config/continuity-surface.json +509 -0
  91. package/src/config/estate-count-budget.json +1 -1
  92. package/src/config/gate-coverage.yml +29 -8
  93. package/src/config/gate-violation-baselines.json +8 -8
  94. package/src/config/gitignore-block.txt +19 -0
  95. package/src/config/host-injection-effect.json +73 -0
  96. package/src/config/packed-binary-manifest.json +33 -0
  97. package/src/config/preamble-payload-budget.json +112 -105
  98. package/src/config/publish-surface.json +4 -2
  99. package/src/install/claudeLayerCarriage.ts +273 -0
  100. package/src/install/claudePathsPlan.ts +76 -0
  101. package/src/install/hostLayerFingerprint.ts +1 -1
  102. package/src/install/partitionEligibility.ts +261 -127
  103. package/src/install/ruleLayerPartition.ts +19 -23
  104. package/src/scripts/_dispatch.bash +77 -27
  105. package/src/scripts/_lib/auto_dispatch.ts +2 -2
  106. package/src/scripts/_lib/catalogue_layer_parity.ts +146 -0
  107. package/src/scripts/_lib/continuity_surface.ts +296 -0
  108. package/src/scripts/_lib/corpus_manifest.ts +26 -9
  109. package/src/scripts/_lib/hook_settings.ts +1 -1
  110. package/src/scripts/_lib/injection_effect.ts +151 -0
  111. package/src/scripts/_lib/layer_overlap_notice.ts +50 -6
  112. package/src/scripts/_lib/packed_binary_predicate.ts +225 -0
  113. package/src/scripts/_lib/pin_resolver.ts +13 -0
  114. package/src/scripts/_lib/regression_neighbourhood.ts +1 -1
  115. package/src/scripts/_lib/retired_status.ts +74 -0
  116. package/src/scripts/_lib/skill_catalogue.ts +156 -21
  117. package/src/scripts/_lib/subagent_capsule.ts +117 -8
  118. package/src/scripts/ai_council/api_transport.ts +5 -0
  119. package/src/scripts/ai_council/deliberation_metrics.ts +353 -0
  120. package/src/scripts/ai_council/prompts.ts +9 -0
  121. package/src/scripts/ai_council/synthesis_disagreement.ts +206 -0
  122. package/src/scripts/capture_skill_catalogue.ts +24 -11
  123. package/src/scripts/check_candidate_lines.ts +499 -0
  124. package/src/scripts/check_continuity_surface.ts +475 -0
  125. package/src/scripts/check_dependency_floors.ts +13 -6
  126. package/src/scripts/check_estate_count.ts +40 -79
  127. package/src/scripts/check_pack_size.ts +262 -23
  128. package/src/scripts/check_roadmap_trackable.ts +19 -16
  129. package/src/scripts/check_rule_layer_partition.ts +54 -34
  130. package/src/scripts/check_single_delivery.ts +97 -31
  131. package/src/scripts/check_update_banner.ts +13 -0
  132. package/src/scripts/code_graph/build.ts +138 -15
  133. package/src/scripts/code_graph/cli.ts +76 -8
  134. package/src/scripts/code_graph/extract.ts +11 -0
  135. package/src/scripts/code_graph/grammar_probe.ts +43 -0
  136. package/src/scripts/code_graph/loader.ts +88 -14
  137. package/src/scripts/code_graph/query.ts +121 -11
  138. package/src/scripts/code_graph/resolution_tiers.ts +238 -0
  139. package/src/scripts/code_graph/sqlite_store.ts +247 -6
  140. package/src/scripts/code_graph/types.ts +66 -1
  141. package/src/scripts/code_graph/validate.ts +23 -0
  142. package/src/scripts/condense.ts +34 -22
  143. package/src/scripts/corpus_manifest.ts +5 -5
  144. package/src/scripts/council_cli.ts +11 -12
  145. package/src/scripts/count_intent_disagreement.ts +426 -0
  146. package/src/scripts/handoff_context_hook.ts +79 -0
  147. package/src/scripts/hook_effect_doctor.ts +82 -1
  148. package/src/scripts/hook_manifest.json +1 -1
  149. package/src/scripts/hook_manifest.yaml +64 -12
  150. package/src/scripts/hooks/chain_nudge_hook.ts +282 -0
  151. package/src/scripts/hooks/code_graph_context_hook.ts +212 -0
  152. package/src/scripts/hooks/concern_registry.ts +4 -2
  153. package/src/scripts/hooks/host_semantics.ts +1 -1
  154. package/src/scripts/hooks/orchestration_record_hook.ts +2 -2
  155. package/src/scripts/hooks/rule_inject_hook.ts +1 -1
  156. package/src/scripts/hooks/skill_route_hook.ts +11 -4
  157. package/src/scripts/inbox_source_census.ts +322 -0
  158. package/src/scripts/install-hooks.sh +82 -0
  159. package/src/scripts/{lint_carrier_integrity.ts → lint_deferral_integrity.ts} +60 -214
  160. package/src/scripts/lint_plan_risk_register.ts +13 -17
  161. package/src/scripts/mcp_server/suggest_skill.ts +72 -0
  162. package/src/scripts/mcp_server/tools.ts +2 -56
  163. package/src/scripts/media/lib/adapter-contract.md +7 -2
  164. package/src/scripts/media/lib/fixtures/flux/asset-0001.png +0 -0
  165. package/src/scripts/media/lib/fixtures/gemini-image/asset-0001.png +0 -0
  166. package/src/scripts/media/lib/fixtures/ideogram/asset-0001.png +0 -0
  167. package/src/scripts/rdp_candidates_delta.ts +276 -0
  168. package/src/scripts/rdp_quality_eval.ts +217 -19
  169. package/src/scripts/release_mix_taxonomy.json +3 -2
  170. package/src/scripts/report_host_injection_effect.ts +189 -0
  171. package/src/scripts/report_layer_overlap.ts +41 -0
  172. package/src/scripts/report_skill_menu_census.ts +341 -0
  173. package/src/scripts/skill_tools/score_skill_relevance.ts +70 -6
  174. package/src/scripts/skill_tools/suggest_skill_for_task.ts +37 -3
  175. package/src/vendor/grammars/README.md +61 -0
  176. package/src/vendor/grammars/tree-sitter-javascript.wasm +0 -0
  177. package/src/vendor/grammars/tree-sitter-php.wasm +0 -0
  178. package/src/vendor/grammars/tree-sitter-typescript.wasm +0 -0
  179. package/src/scripts/_lib/carrier_status.ts +0 -118
  180. package/src/scripts/_lib/estate_carrier_cases.ts +0 -72
  181. package/src/scripts/hooks/code_graph_nudge_hook.ts +0 -191
@@ -47,6 +47,7 @@ import {
47
47
  slice_around as _chSliceAround,
48
48
  read_header as _chReadHeaderImpl,
49
49
  } from '../chat_history.js';
50
+ import { suggestSkillForTask } from './suggest_skill.js';
50
51
  import {
51
52
  type CatalogEntry,
52
53
  install_hint as _catalog_install_hint,
@@ -1210,61 +1211,6 @@ async function _readResourceBodyHandler(
1210
1211
  // unlisted tool. Boot-time stderr log enumerates the registered set.
1211
1212
  // ---------------------------------------------------------------------
1212
1213
 
1213
- /**
1214
- * `suggest_skill_for_task` — the recovery path for a truncated catalogue.
1215
- *
1216
- * Promoted from a discovery stub because the defect it answers is measured, not
1217
- * hypothetical: on the one host that publishes its own truncation, a default
1218
- * install had 402 entries dropped from the model-visible skills list — the
1219
- * host's own count against its own denominator, not a subtraction over ours
1220
- * (`agents/evidence/analysis/scoped-projection-host-delivery.md`). An agent
1221
- * that cannot see a skill also cannot ask for it by name, so the only reachable
1222
- * question is "what fits this task" — which is what the deterministic ranker
1223
- * answers, over the TREE rather than over the host's catalogue.
1224
- *
1225
- * Read-only and shell-free by construction: it reads SKILL.md frontmatter and
1226
- * returns names, scores and declared personas. No skill BODY is returned — the
1227
- * caller opens what it picked, so a wrong rank costs one read rather than a
1228
- * context payload.
1229
- */
1230
- async function _suggestSkillForTaskHandler(
1231
- args: Record<string, unknown>,
1232
- consumerRoot: string,
1233
- ): Promise<Record<string, unknown>> {
1234
- const task = typeof args['task'] === 'string' ? args['task'] : '';
1235
- if (task.trim() === '') {
1236
- return { status: 'error', error: 'task must be a non-empty string', suggestions: [] };
1237
- }
1238
- // `limit`, not `top` — the stub published `limit` and a consumer may have
1239
- // read that contract already. Renaming it on promotion would break a
1240
- // caller for cosmetic reasons.
1241
- const limitRaw = args['limit'];
1242
- const limit =
1243
- typeof limitRaw === 'number' && Number.isFinite(limitRaw) ? Math.floor(limitRaw) : 5;
1244
-
1245
- const catalogue = await import('../_lib/skill_catalogue.js');
1246
- const skillsDir = catalogue.resolveSkillsRoot(consumerRoot);
1247
- if (skillsDir === null) {
1248
- // A missing catalogue is UNKNOWN, never "no skill fits this task". The
1249
- // second reads as a ranked answer and would be a confident empty list —
1250
- // the same zero-from-silence failure `capture_skill_catalogue` refuses.
1251
- return {
1252
- status: 'no_catalogue',
1253
- searched: [...catalogue.DEFAULT_CATALOGUE_ROOTS],
1254
- consumer_root: consumerRoot,
1255
- suggestions: [],
1256
- };
1257
- }
1258
-
1259
- const { rank } = await import('../skill_tools/score_skill_relevance.js');
1260
- const rows = rank(task, skillsDir).slice(0, Math.max(1, limit));
1261
- return {
1262
- status: 'ok',
1263
- skills_root: skillsDir,
1264
- suggestions: rows.map(([name, score, personas]) => ({ skill: name, score, personas })),
1265
- };
1266
- }
1267
-
1268
1214
  export const ALLOWLIST: Record<string, BuiltinTool> = {
1269
1215
  suggest_skill_for_task: {
1270
1216
  name: 'suggest_skill_for_task',
@@ -1296,7 +1242,7 @@ export const ALLOWLIST: Record<string, BuiltinTool> = {
1296
1242
  },
1297
1243
  additionalProperties: false,
1298
1244
  },
1299
- handler: _suggestSkillForTaskHandler,
1245
+ handler: suggestSkillForTask,
1300
1246
  },
1301
1247
  lint_skills: {
1302
1248
  name: 'lint_skills',
@@ -355,8 +355,13 @@ the explicit `dry-run` subcommand:
355
355
  `scripts/media/lib/fixtures/<adapter-id>/`.
356
356
  - Exit code 0.
357
357
 
358
- Fixtures are committed and cover one happy path per adapter.
359
- Phase 6 golden runs assert byte-identical stdout under
358
+ Fixtures are committed and cover one happy path per adapter;
359
+ committed media assets are **non-decodable ASCII placeholders**
360
+ whose documented paths and presence, rather than encoded-media
361
+ validity, form the fixture contract. The placeholder literal is
362
+ `FIXTURE-<adapter-id>-<ext>`. A test that needs decodable media
363
+ generates it at runtime — it must not read a fixture expecting
364
+ one. Phase 6 golden runs assert byte-identical stdout under
360
365
  `AIV_DRYRUN=true`.
361
366
 
362
367
  ## Logging & redaction
@@ -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 };