@event4u/agent-config 14.13.0 → 14.14.1

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 (138) 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 +138 -376
  5. package/README.md +1 -1
  6. package/dist/agent-src/commands/analyze/inbox.md +36 -0
  7. package/dist/agent-src/commands/pr/create.md +16 -1
  8. package/dist/agent-src/contexts/execution/terminal-states.md +25 -4
  9. package/dist/agent-src/rules/code-comment-discipline.md +6 -0
  10. package/dist/agent-src/rules/missing-skill-recovery.md +2 -2
  11. package/dist/agent-src/rules/token-budget-discipline.md +2 -2
  12. package/dist/agent-src/scripts/archive_completed_roadmaps.ts +6 -6
  13. package/dist/agent-src/scripts/guarded_baseline.ts +22 -7
  14. package/dist/agent-src/scripts/stubs_due.ts +118 -2
  15. package/dist/agent-src/scripts/update_roadmap_progress.ts +8 -8
  16. package/dist/agent-src/templates/agents/agent-project-settings.example.yml +1 -1
  17. package/dist/cli/registry.js +1 -0
  18. package/dist/cli-delegate/{chunk-ILMUPLGW.js → chunk-7RXIKF62.js} +159 -86
  19. package/dist/cli-delegate/cmd_conformance.js +39 -2
  20. package/dist/cli-delegate/cmd_doctor.js +1 -1
  21. package/dist/discovery/deprecation-report.md +1 -1
  22. package/dist/discovery/discovery-manifest.json +7 -7
  23. package/dist/discovery/discovery-manifest.json.sha256 +1 -1
  24. package/dist/discovery/discovery-manifest.summary.md +1 -1
  25. package/dist/discovery/dormancy-report.md +1 -1
  26. package/dist/discovery/orphan-report.md +1 -1
  27. package/dist/discovery/packs.json +2 -2
  28. package/dist/discovery/trust-report.md +1 -1
  29. package/dist/discovery/workspaces.json +2 -2
  30. package/dist/hooks/dispatch.js +1696 -812
  31. package/dist/mcp/registry-manifest.json +2 -2
  32. package/dist/mcp/server.json +2 -2
  33. package/dist/mcp/server.mjs +297 -115
  34. package/docs/guidelines/agent-infra/guarded-baseline.md +1 -1
  35. package/docs/guidelines/code-clarity.md +18 -0
  36. package/package.json +1 -1
  37. package/src/agent-src/scripts/archive_completed_roadmaps.ts +6 -6
  38. package/src/agent-src/scripts/guarded_baseline.ts +22 -7
  39. package/src/agent-src/scripts/stubs_due.ts +118 -2
  40. package/src/agent-src/scripts/update_roadmap_progress.ts +8 -8
  41. package/src/config/assurance-capability-registry.json +1 -1
  42. package/src/config/estate-count-budget.json +20 -1
  43. package/src/config/evaluator-budgets.json +4 -4
  44. package/src/config/gate-coverage.yml +211 -1
  45. package/src/config/gate-reachability-exemptions.json +1 -0
  46. package/src/config/gate-violation-baselines.json +9 -3
  47. package/src/config/preamble-payload-budget.json +19 -11
  48. package/src/config/rule-stub-ceilings.json +14 -7
  49. package/src/config/web-launch-readiness.json +4 -2
  50. package/src/scripts/_cli/_doctor_emit.ts +71 -0
  51. package/src/scripts/_cli/_doctor_strict.ts +163 -0
  52. package/src/scripts/_cli/cmd_doctor.ts +16 -37
  53. package/src/scripts/_dispatch.bash +11 -4
  54. package/src/scripts/_lib/activation_ladder.ts +47 -3
  55. package/src/scripts/_lib/activation_receipt_producer.ts +341 -0
  56. package/src/scripts/_lib/adr_frontmatter.ts +26 -0
  57. package/src/scripts/_lib/branch_convergence.ts +186 -0
  58. package/src/scripts/_lib/candidate_pair_delta.ts +349 -0
  59. package/src/scripts/_lib/carrier_status.ts +118 -0
  60. package/src/scripts/_lib/cascade_stage_enumeration.ts +92 -0
  61. package/src/scripts/_lib/context_observation.ts +132 -0
  62. package/src/scripts/_lib/continuation_ladder.ts +188 -0
  63. package/src/scripts/_lib/corpus_manifest.ts +726 -0
  64. package/src/scripts/_lib/delivery_arm_experiment.ts +18 -2
  65. package/src/scripts/_lib/estate_carrier_cases.ts +72 -0
  66. package/src/scripts/_lib/evaluation_cascade.ts +452 -0
  67. package/src/scripts/_lib/evolution_roi.ts +562 -0
  68. package/src/scripts/_lib/experiment_binding.ts +95 -0
  69. package/src/scripts/_lib/harness_evolution_guards.ts +10 -0
  70. package/src/scripts/_lib/ignored_blocker.ts +8 -1
  71. package/src/scripts/_lib/lexical_shortlist.ts +215 -0
  72. package/src/scripts/_lib/llm_candidate_proposer.ts +461 -0
  73. package/src/scripts/_lib/llm_proposer_transport.ts +153 -0
  74. package/src/scripts/_lib/outcome_envelope.ts +6 -1
  75. package/src/scripts/_lib/outcome_vocabularies.ts +135 -5
  76. package/src/scripts/_lib/pathology_archive.ts +393 -0
  77. package/src/scripts/_lib/promotion_capability.ts +315 -0
  78. package/src/scripts/_lib/promotion_evidence.ts +414 -0
  79. package/src/scripts/_lib/promotion_review.ts +294 -0
  80. package/src/scripts/_lib/prompt_shape.ts +92 -0
  81. package/src/scripts/_lib/release_highlights.ts +79 -1
  82. package/src/scripts/_lib/release_material.ts +125 -2
  83. package/src/scripts/_lib/repeated_failure.ts +19 -2
  84. package/src/scripts/_lib/routing_corpus.ts +307 -0
  85. package/src/scripts/_lib/routing_index_input.ts +104 -0
  86. package/src/scripts/_lib/rule_injection.ts +22 -1
  87. package/src/scripts/_lib/runtime_journal.ts +11 -4
  88. package/src/scripts/_lib/semantic_noop.ts +167 -0
  89. package/src/scripts/activation_receipt.ts +258 -0
  90. package/src/scripts/ai_council/cross_exam.ts +128 -0
  91. package/src/scripts/ai_council/explain_route.ts +157 -0
  92. package/src/scripts/ai_council/information_gain.ts +293 -0
  93. package/src/scripts/ai_council/leakage_corpus.ts +385 -0
  94. package/src/scripts/ai_council/leakage_patterns.ts +453 -0
  95. package/src/scripts/ai_council/minority_retention.ts +171 -0
  96. package/src/scripts/ai_council/recouncil_savings.ts +299 -0
  97. package/src/scripts/ai_council/replay_route.ts +223 -0
  98. package/src/scripts/ai_council/reviewer_assignment.ts +279 -0
  99. package/src/scripts/ai_council/routing_training_row.ts +197 -0
  100. package/src/scripts/ai_council/synthesis_strategy.ts +159 -0
  101. package/src/scripts/check_adr_frontmatter.ts +4 -9
  102. package/src/scripts/check_agent_artifact_location.ts +337 -0
  103. package/src/scripts/check_artefact_count_messaging.ts +74 -8
  104. package/src/scripts/check_enforcement_coverage.ts +40 -1
  105. package/src/scripts/check_estate_count.ts +205 -24
  106. package/src/scripts/check_release_highlights.ts +113 -14
  107. package/src/scripts/check_roadmap_trackable.ts +17 -2
  108. package/src/scripts/check_web_launch_readiness.ts +310 -20
  109. package/src/scripts/corpus_manifest.ts +235 -0
  110. package/src/scripts/evolution_lab.ts +234 -9
  111. package/src/scripts/generate_capability_matrix.ts +16 -9
  112. package/src/scripts/grade_target_readiness.ts +221 -2
  113. package/src/scripts/hook_effect_doctor.ts +47 -4
  114. package/src/scripts/hook_manifest.json +1 -1
  115. package/src/scripts/hook_manifest.yaml +28 -6
  116. package/src/scripts/hooks/comment_discipline_hook.ts +146 -0
  117. package/src/scripts/hooks/concern_registry.ts +2 -0
  118. package/src/scripts/hooks/run_continuation_hook.ts +78 -88
  119. package/src/scripts/hooks/session_eol_hook.ts +12 -1
  120. package/src/scripts/hooks/turn_end_gate_hook.ts +9 -1
  121. package/src/scripts/install-hooks.sh +18 -0
  122. package/src/scripts/language_mirror_hook.ts +14 -2
  123. package/src/scripts/lint_carrier_integrity.ts +820 -0
  124. package/src/scripts/lint_code_comments.ts +748 -0
  125. package/src/scripts/lint_plan_risk_register.ts +39 -19
  126. package/src/scripts/lint_promotion_paths.ts +779 -0
  127. package/src/scripts/llm_propose.ts +256 -0
  128. package/src/scripts/measure_delivery_sets.ts +361 -0
  129. package/src/scripts/measure_routing_signal.ts +307 -0
  130. package/src/scripts/model_rule_injection.ts +19 -3
  131. package/src/scripts/probe_council_retention.ts +495 -0
  132. package/src/scripts/release.ts +21 -37
  133. package/src/scripts/release_drill.ts +199 -0
  134. package/src/scripts/release_env.ts +35 -0
  135. package/src/scripts/release_publication.ts +142 -0
  136. package/src/scripts/roadmap_context.ts +15 -0
  137. package/src/scripts/skill_scout.ts +633 -0
  138. package/src/scripts/sync_pr_branch.ts +521 -42
@@ -0,0 +1,307 @@
1
+ #!/usr/bin/env tsx
2
+ /**
3
+ * measure_routing_signal — does the skill BODY carry routing signal the
4
+ * description does not? (`road-to-governed-harness-evolution` step 5.1.)
5
+ *
6
+ * THE BAR WAS FIXED BEFORE THIS FILE EXISTED. Every threshold, the k, the
7
+ * corpus partition, the verdict function and a committed prediction live in
8
+ * `agents/evidence/analysis/routing-signal-preregistration-2026-08-31.md`,
9
+ * which is committed in an earlier commit that adds no measurement module. The
10
+ * constants below are transcribed from it; they are not chosen here.
11
+ *
12
+ * TWO GAPS, AND ONLY ONE OF THEM IS MEASURABLE HERE.
13
+ *
14
+ * Gap A — proxy-to-real-session fidelity. Stated by the description-surface
15
+ * route checker's own header: asking a model which units it would
16
+ * load is not the host's selection procedure. Measuring it needs real
17
+ * sessions, and step 5.2 parks the live harness for this whole
18
+ * roadmap. So this run emits `null` with `status:
19
+ * unmeasured-by-construction` and the reason. That field is not a
20
+ * caveat — it bounds the external validity of the Gap B verdict, and
21
+ * a consumer reading the verdict file gets both or neither.
22
+ *
23
+ * Gap B — the description-vs-body question this run answers.
24
+ *
25
+ * WHY McNEMAR AND NOT A RERUN BAND. The estimator is deterministic: same tree,
26
+ * same ranking, every time. A rerun band would be identically zero and would
27
+ * measure nothing. The variance that exists is sampling variance over cases, so
28
+ * the paired discordant-pair test is the form that addresses it.
29
+ *
30
+ * ZERO MODEL CALLS AND ZERO SPEND, and that follows from the imports rather
31
+ * than from good behaviour: this module imports a file reader and a pure
32
+ * scorer, and nothing that can open a socket.
33
+ *
34
+ * Usage:
35
+ * ./scripts-run src/scripts/measure_routing_signal human table
36
+ * ./scripts-run src/scripts/measure_routing_signal --json machine record
37
+ * ./scripts-run src/scripts/measure_routing_signal --write persist verdict
38
+ *
39
+ * Exit 0 = the measurement ran · 2 = it could not run.
40
+ */
41
+ import * as fs from 'node:fs';
42
+ import * as path from 'node:path';
43
+ import { fileURLToPath, pathToFileURL } from 'node:url';
44
+
45
+ import {
46
+ ARMS,
47
+ type Arm,
48
+ type CatalogueEntry,
49
+ type CorpusCase,
50
+ legacyShaped,
51
+ loadCatalogue,
52
+ loadTrainCases,
53
+ termIndex,
54
+ topK,
55
+ } from './_lib/routing_corpus.js';
56
+
57
+ export const REPO = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..', '..');
58
+
59
+ /** Where the verdict lands. Step 6.5 derives its input set from this file. */
60
+ export const VERDICT_REL = 'agents/evidence/analysis/routing-body-signal-verdict.json';
61
+ export const PREREG_REL = 'agents/evidence/analysis/routing-signal-preregistration-2026-08-31.md';
62
+
63
+ // --- the pre-registered constants, transcribed ------------------------------
64
+
65
+ export const K = 5;
66
+ export const RECALL_GAIN_BAR_PP = 5.0;
67
+ export const FALSE_ACTIVATION_GUARD_PP = 2.0;
68
+ export const POWER_FLOOR_DISCORDANT = 10;
69
+ export const ALPHA = 0.05;
70
+
71
+ export type Verdict = 'signal' | 'null' | 'harmful' | 'underpowered';
72
+
73
+ export interface ArmCounts {
74
+ readonly positives: number;
75
+ readonly positiveHits: number;
76
+ readonly negatives: number;
77
+ readonly negativeHits: number;
78
+ }
79
+
80
+ export interface Discordance {
81
+ /** control miss → body hit. */
82
+ readonly gained: number;
83
+ /** control hit → body miss. */
84
+ readonly lost: number;
85
+ }
86
+
87
+ export interface Measurement {
88
+ readonly k: number;
89
+ readonly catalogueSize: number;
90
+ readonly trainCorpora: number;
91
+ readonly legacyShaped: readonly string[];
92
+ readonly cases: number;
93
+ readonly arms: Record<Arm, ArmCounts>;
94
+ readonly recallPp: Record<Arm, number>;
95
+ readonly falseActivationPp: Record<Arm, number>;
96
+ readonly deltaRecallPp: number;
97
+ readonly deltaFalseActivationPp: number;
98
+ readonly positiveDiscordance: Discordance;
99
+ readonly pValue: number;
100
+ readonly verdict: Verdict;
101
+ readonly verdictReason: string;
102
+ }
103
+
104
+ /** Exact two-sided McNemar: a binomial sign test over the discordant pairs. */
105
+ export function mcnemarExactP(gained: number, lost: number): number {
106
+ const n = gained + lost;
107
+ if (n === 0) return 1;
108
+ const logFact: number[] = [0];
109
+ for (let i = 1; i <= n; i++) logFact.push((logFact[i - 1] as number) + Math.log(i));
110
+ const logChoose = (a: number, b: number): number =>
111
+ (logFact[a] as number) - (logFact[b] as number) - (logFact[a - b] as number);
112
+ const extreme = Math.min(gained, lost);
113
+ let tail = 0;
114
+ for (let i = 0; i <= extreme; i++) tail += Math.exp(logChoose(n, i) - n * Math.LN2);
115
+ return Math.min(1, 2 * tail);
116
+ }
117
+
118
+ const pp = (hits: number, total: number): number => (total === 0 ? 0 : (hits / total) * 100);
119
+
120
+ export function measure(repo = REPO, k = K): Measurement {
121
+ const catalogue: CatalogueEntry[] = loadCatalogue(repo);
122
+ const cases: CorpusCase[] = loadTrainCases(repo);
123
+ if (catalogue.length === 0 || cases.length === 0) {
124
+ throw new Error('measure_routing_signal: empty catalogue or corpus — a run over nothing');
125
+ }
126
+ const indexes = new Map<Arm, ReturnType<typeof termIndex>>();
127
+ for (const arm of ARMS) indexes.set(arm, termIndex(catalogue, arm));
128
+
129
+ const counts: Record<string, { p: number; ph: number; n: number; nh: number }> = {};
130
+ for (const arm of ARMS) counts[arm] = { p: 0, ph: 0, n: 0, nh: 0 };
131
+ let gained = 0;
132
+ let lost = 0;
133
+
134
+ // One ranking pass per (case, arm). The prompt is re-tokenized per arm by
135
+ // `topK`; that is the price of keeping the control arm byte-identical to
136
+ // the shipped call rather than hand-rolling a shared fast path.
137
+ for (const c of cases) {
138
+ const hit: Record<string, boolean> = {};
139
+ for (const arm of ARMS) {
140
+ const ranked = topK(c.prompt, catalogue, indexes.get(arm) as Map<string, Set<string>>, k);
141
+ hit[arm] = ranked.some((r) => r.name === c.skill);
142
+ const bucket = counts[arm] as { p: number; ph: number; n: number; nh: number };
143
+ if (c.expect) {
144
+ bucket.p += 1;
145
+ if (hit[arm]) bucket.ph += 1;
146
+ } else {
147
+ bucket.n += 1;
148
+ if (hit[arm]) bucket.nh += 1;
149
+ }
150
+ }
151
+ if (c.expect) {
152
+ const control = hit['description'] === true;
153
+ const body = hit['description+body'] === true;
154
+ if (!control && body) gained += 1;
155
+ if (control && !body) lost += 1;
156
+ }
157
+ }
158
+
159
+ const arms = {} as Record<Arm, ArmCounts>;
160
+ const recallPp = {} as Record<Arm, number>;
161
+ const falsePp = {} as Record<Arm, number>;
162
+ for (const arm of ARMS) {
163
+ const b = counts[arm] as { p: number; ph: number; n: number; nh: number };
164
+ arms[arm] = { positives: b.p, positiveHits: b.ph, negatives: b.n, negativeHits: b.nh };
165
+ recallPp[arm] = pp(b.ph, b.p);
166
+ falsePp[arm] = pp(b.nh, b.n);
167
+ }
168
+ const deltaRecall = (recallPp['description+body'] as number) - (recallPp['description'] as number);
169
+ const deltaFalse =
170
+ (falsePp['description+body'] as number) - (falsePp['description'] as number);
171
+ const p = mcnemarExactP(gained, lost);
172
+
173
+ // The pre-registered verdict function, in its pre-registered order.
174
+ let verdict: Verdict;
175
+ let reason: string;
176
+ if (gained + lost < POWER_FLOOR_DISCORDANT) {
177
+ verdict = 'underpowered';
178
+ reason = `${gained + lost} discordant positive pairs < power floor ${POWER_FLOOR_DISCORDANT}`;
179
+ } else if (deltaFalse > FALSE_ACTIVATION_GUARD_PP) {
180
+ verdict = 'harmful';
181
+ reason = `false activation +${deltaFalse.toFixed(2)} pp breaches the +${FALSE_ACTIVATION_GUARD_PP.toFixed(1)} pp guard`;
182
+ } else if (deltaRecall >= RECALL_GAIN_BAR_PP && p < ALPHA) {
183
+ verdict = 'signal';
184
+ reason = `recall +${deltaRecall.toFixed(2)} pp at p=${p.toExponential(2)}`;
185
+ } else {
186
+ verdict = 'null';
187
+ reason = `recall +${deltaRecall.toFixed(2)} pp at p=${p.toExponential(2)} does not clear +${RECALL_GAIN_BAR_PP.toFixed(1)} pp and p<${ALPHA}`;
188
+ }
189
+
190
+ return {
191
+ k,
192
+ catalogueSize: catalogue.length,
193
+ trainCorpora: new Set(cases.map((c) => c.skill)).size,
194
+ legacyShaped: legacyShaped(repo),
195
+ cases: cases.length,
196
+ arms,
197
+ recallPp,
198
+ falseActivationPp: falsePp,
199
+ deltaRecallPp: deltaRecall,
200
+ deltaFalseActivationPp: deltaFalse,
201
+ positiveDiscordance: { gained, lost },
202
+ pValue: p,
203
+ verdict,
204
+ verdictReason: reason,
205
+ };
206
+ }
207
+
208
+ /**
209
+ * The verdict record step 6.5 reads.
210
+ *
211
+ * `proxy_to_real_fidelity` is a REQUIRED field carrying `null`. A consumer that
212
+ * reads `body_signal` without reading it has taken the conclusion without its
213
+ * bound, so the two ship in one object.
214
+ */
215
+ export function verdictRecord(m: Measurement, measuredAt: string): Record<string, unknown> {
216
+ return {
217
+ schema: 1,
218
+ step: '5.1',
219
+ roadmap: 'road-to-governed-harness-evolution',
220
+ preregistration: PREREG_REL,
221
+ measured_at: measuredAt,
222
+ proxy_to_real_fidelity: {
223
+ value: null,
224
+ status: 'unmeasured-by-construction',
225
+ reason:
226
+ 'Step 5.2 parks the live routing harness for this roadmap; a fidelity ' +
227
+ 'figure requires real sessions. Every routing conclusion in this file is ' +
228
+ 'bounded by this unmeasured quantity.',
229
+ },
230
+ body_signal: {
231
+ verdict: m.verdict,
232
+ reason: m.verdictReason,
233
+ k: m.k,
234
+ delta_recall_pp: Number(m.deltaRecallPp.toFixed(3)),
235
+ delta_false_activation_pp: Number(m.deltaFalseActivationPp.toFixed(3)),
236
+ p_value: Number(m.pValue.toPrecision(4)),
237
+ discordant_positive_pairs: m.positiveDiscordance,
238
+ bars: {
239
+ recall_gain_pp: RECALL_GAIN_BAR_PP,
240
+ false_activation_guard_pp: FALSE_ACTIVATION_GUARD_PP,
241
+ power_floor_discordant: POWER_FLOOR_DISCORDANT,
242
+ alpha: ALPHA,
243
+ },
244
+ arms: m.arms,
245
+ recall_pp: {
246
+ description: Number((m.recallPp['description'] as number).toFixed(3)),
247
+ 'description+body': Number((m.recallPp['description+body'] as number).toFixed(3)),
248
+ },
249
+ false_activation_pp: {
250
+ description: Number((m.falseActivationPp['description'] as number).toFixed(3)),
251
+ 'description+body': Number(
252
+ (m.falseActivationPp['description+body'] as number).toFixed(3),
253
+ ),
254
+ },
255
+ },
256
+ corpus: {
257
+ partition: 'train',
258
+ holdout_sealed: true,
259
+ train_corpora: m.trainCorpora,
260
+ legacy_shaped_corpora: m.legacyShaped,
261
+ cases: m.cases,
262
+ catalogue_size: m.catalogueSize,
263
+ },
264
+ };
265
+ }
266
+
267
+ export function render(m: Measurement): string {
268
+ const rows = ARMS.map(
269
+ (arm) =>
270
+ ` ${arm.padEnd(18)} recall@${m.k} ${(m.recallPp[arm] as number).toFixed(2).padStart(6)} %` +
271
+ ` false-activation@${m.k} ${(m.falseActivationPp[arm] as number).toFixed(2).padStart(6)} %`,
272
+ );
273
+ return [
274
+ `measure_routing_signal — step 5.1, k=${m.k}`,
275
+ ` catalogue ${m.catalogueSize} skills · train corpora ${m.trainCorpora}` +
276
+ ` (${m.legacyShaped.length} legacy-shaped: ${m.legacyShaped.join(', ') || 'none'})` +
277
+ ` · cases ${m.cases}`,
278
+ ...rows,
279
+ ` delta recall ${m.deltaRecallPp >= 0 ? '+' : ''}${m.deltaRecallPp.toFixed(2)} pp` +
280
+ ` (bar +${RECALL_GAIN_BAR_PP.toFixed(1)} pp)`,
281
+ ` delta false activation ${m.deltaFalseActivationPp >= 0 ? '+' : ''}${m.deltaFalseActivationPp.toFixed(2)} pp` +
282
+ ` (guard +${FALSE_ACTIVATION_GUARD_PP.toFixed(1)} pp)`,
283
+ ` discordant positives gained ${m.positiveDiscordance.gained} lost ${m.positiveDiscordance.lost}` +
284
+ ` · McNemar exact p=${m.pValue.toExponential(3)}`,
285
+ ` proxy-to-real fidelity: null (unmeasured-by-construction — 5.2 park)`,
286
+ ` VERDICT: ${m.verdict} — ${m.verdictReason}`,
287
+ ].join('\n');
288
+ }
289
+
290
+ export function main(argv: readonly string[]): number {
291
+ const m = measure();
292
+ const record = verdictRecord(m, new Date().toISOString().slice(0, 10));
293
+ if (argv.includes('--write')) {
294
+ fs.writeFileSync(path.join(REPO, VERDICT_REL), `${JSON.stringify(record, null, 2)}\n`);
295
+ }
296
+ process.stdout.write(argv.includes('--json') ? `${JSON.stringify(record, null, 2)}\n` : `${render(m)}\n`);
297
+ return 0;
298
+ }
299
+
300
+ if (process.argv[1] !== undefined && import.meta.url === pathToFileURL(process.argv[1]).href) {
301
+ try {
302
+ process.exit(main(process.argv.slice(2)));
303
+ } catch (err) {
304
+ process.stderr.write(`measure_routing_signal: ${(err as Error).message}\n`);
305
+ process.exit(2);
306
+ }
307
+ }
@@ -52,6 +52,7 @@ import { parse as parseYaml } from 'yaml';
52
52
 
53
53
  import { LexicalIndex } from './_lib/lexical_index.js';
54
54
  import { gpt_tokens } from './_lib/token_count.js';
55
+ import { makePromptShortlist } from './_lib/lexical_shortlist.js';
55
56
  import {
56
57
  allTierRules,
57
58
  kernelIds,
@@ -820,6 +821,7 @@ export function runSelftest(corpusDir: string): SelftestCase[] {
820
821
  export function runThreeArm(
821
822
  corpusDir: string,
822
823
  capBytes: number = CAP_BYTES,
824
+ useShortlist = false,
823
825
  ): { rows: ArmRow[]; lines: string[] } {
824
826
  const router = loadRouter(REPO_ROOT);
825
827
  const cases = loadCorpus(corpusDir);
@@ -829,6 +831,10 @@ export function runThreeArm(
829
831
  thin: sc.thinTokens,
830
832
  delivery: sc.thinTokens,
831
833
  };
834
+ // STEP 6.3 — the shortlist is OPT-IN and OFF by default, so the figures 6.1
835
+ // recorded reproduce byte-for-byte without it. It reaches the delivery arm
836
+ // as a tie-break under the matcher's score and can move nothing but which
837
+ // matched bodies survive a binding cap.
832
838
  const rows = runArmExperiment({
833
839
  repoRoot: REPO_ROOT,
834
840
  router,
@@ -836,8 +842,15 @@ export function runThreeArm(
836
842
  capBytes,
837
843
  standing,
838
844
  tokensOf,
845
+ shortlist: useShortlist ? makePromptShortlist(REPO_ROOT, router) : null,
839
846
  });
840
- return { rows, lines: renderArmExperiment(rows, capBytes) };
847
+ const lines = renderArmExperiment(rows, capBytes);
848
+ lines.push(
849
+ useShortlist
850
+ ? 'shortlist: ON — lexical BM25 tie-break under the matcher score (step 6.3)'
851
+ : 'shortlist: OFF — cap order is score then router order (step 6.1 baseline)',
852
+ );
853
+ return { rows, lines };
841
854
  }
842
855
 
843
856
  // ── CLI ──────────────────────────────────────────────────────────────────
@@ -845,7 +858,7 @@ export function runThreeArm(
845
858
  const USAGE = `usage: model_rule_injection [--corpus DIR] [--baseline-comparison]
846
859
  [--honour-open-files]
847
860
  [--selftest] [--endpoints] [--json]
848
- [--three-arm] [--cap-bytes N]
861
+ [--three-arm] [--cap-bytes N] [--shortlist]
849
862
 
850
863
  Offline recall + cost model for trigger-delivered rule bodies. No metered call
851
864
  on any path.
@@ -860,6 +873,7 @@ export function main(argv: string[] | null = null): number {
860
873
  let endpoints = false;
861
874
  let json = false;
862
875
  let threeArm = false;
876
+ let shortlist = false;
863
877
  let capBytes = CAP_BYTES;
864
878
  for (let i = 0; i < args.length; i += 1) {
865
879
  const a = args[i] as string;
@@ -878,6 +892,8 @@ export function main(argv: string[] | null = null): number {
878
892
  json = true;
879
893
  } else if (a === '--three-arm') {
880
894
  threeArm = true;
895
+ } else if (a === '--shortlist') {
896
+ shortlist = true;
881
897
  } else if (a === '--cap-bytes') {
882
898
  i += 1;
883
899
  capBytes = Number.parseInt(args[i] as string, 10);
@@ -895,7 +911,7 @@ export function main(argv: string[] | null = null): number {
895
911
  }
896
912
 
897
913
  if (threeArm) {
898
- const res = runThreeArm(corpus, capBytes);
914
+ const res = runThreeArm(corpus, capBytes, shortlist);
899
915
  if (json) {
900
916
  process.stdout.write(`${JSON.stringify(res.rows, null, 2)}\n`);
901
917
  } else {