@event4u/agent-config 14.13.0 → 14.14.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.
- package/.augment-plugin/marketplace.json +3 -3
- package/.augment-plugin/plugin.json +1 -1
- package/.claude-plugin/marketplace.json +1 -1
- package/CHANGELOG.md +116 -387
- package/dist/agent-src/commands/analyze/inbox.md +36 -0
- package/dist/agent-src/rules/code-comment-discipline.md +6 -0
- package/dist/agent-src/scripts/archive_completed_roadmaps.ts +6 -6
- package/dist/agent-src/scripts/guarded_baseline.ts +22 -7
- package/dist/agent-src/scripts/stubs_due.ts +118 -2
- package/dist/agent-src/scripts/update_roadmap_progress.ts +8 -8
- package/dist/agent-src/templates/agents/agent-project-settings.example.yml +1 -1
- package/dist/cli-delegate/cmd_conformance.js +38 -1
- package/dist/discovery/deprecation-report.md +1 -1
- package/dist/discovery/discovery-manifest.json +4 -4
- package/dist/discovery/discovery-manifest.json.sha256 +1 -1
- package/dist/discovery/discovery-manifest.summary.md +1 -1
- package/dist/discovery/dormancy-report.md +1 -1
- package/dist/discovery/orphan-report.md +1 -1
- package/dist/discovery/packs.json +2 -2
- package/dist/discovery/trust-report.md +1 -1
- package/dist/discovery/workspaces.json +2 -2
- package/dist/hooks/dispatch.js +1438 -711
- package/dist/mcp/registry-manifest.json +2 -2
- package/dist/mcp/server.json +2 -2
- package/dist/mcp/server.mjs +112 -17
- package/docs/guidelines/agent-infra/guarded-baseline.md +1 -1
- package/docs/guidelines/code-clarity.md +18 -0
- package/package.json +1 -1
- package/src/agent-src/scripts/archive_completed_roadmaps.ts +6 -6
- package/src/agent-src/scripts/guarded_baseline.ts +22 -7
- package/src/agent-src/scripts/stubs_due.ts +118 -2
- package/src/agent-src/scripts/update_roadmap_progress.ts +8 -8
- package/src/config/estate-count-budget.json +20 -1
- package/src/config/gate-coverage.yml +150 -0
- package/src/config/gate-violation-baselines.json +8 -3
- package/src/config/preamble-payload-budget.json +19 -11
- package/src/config/rule-stub-ceilings.json +14 -7
- package/src/scripts/_dispatch.bash +0 -4
- package/src/scripts/_lib/activation_ladder.ts +47 -3
- package/src/scripts/_lib/activation_receipt_producer.ts +341 -0
- package/src/scripts/_lib/adr_frontmatter.ts +26 -0
- package/src/scripts/_lib/candidate_pair_delta.ts +349 -0
- package/src/scripts/_lib/carrier_status.ts +118 -0
- package/src/scripts/_lib/cascade_stage_enumeration.ts +92 -0
- package/src/scripts/_lib/corpus_manifest.ts +726 -0
- package/src/scripts/_lib/delivery_arm_experiment.ts +18 -2
- package/src/scripts/_lib/estate_carrier_cases.ts +72 -0
- package/src/scripts/_lib/evaluation_cascade.ts +452 -0
- package/src/scripts/_lib/evolution_roi.ts +562 -0
- package/src/scripts/_lib/harness_evolution_guards.ts +10 -0
- package/src/scripts/_lib/lexical_shortlist.ts +215 -0
- package/src/scripts/_lib/llm_candidate_proposer.ts +461 -0
- package/src/scripts/_lib/llm_proposer_transport.ts +153 -0
- package/src/scripts/_lib/pathology_archive.ts +393 -0
- package/src/scripts/_lib/promotion_capability.ts +315 -0
- package/src/scripts/_lib/promotion_evidence.ts +414 -0
- package/src/scripts/_lib/promotion_review.ts +294 -0
- package/src/scripts/_lib/prompt_shape.ts +92 -0
- package/src/scripts/_lib/release_highlights.ts +70 -1
- package/src/scripts/_lib/release_material.ts +125 -2
- package/src/scripts/_lib/routing_corpus.ts +307 -0
- package/src/scripts/_lib/routing_index_input.ts +104 -0
- package/src/scripts/_lib/rule_injection.ts +22 -1
- package/src/scripts/_lib/semantic_noop.ts +167 -0
- package/src/scripts/activation_receipt.ts +258 -0
- package/src/scripts/ai_council/cross_exam.ts +128 -0
- package/src/scripts/ai_council/explain_route.ts +157 -0
- package/src/scripts/ai_council/information_gain.ts +293 -0
- package/src/scripts/ai_council/leakage_corpus.ts +385 -0
- package/src/scripts/ai_council/leakage_patterns.ts +453 -0
- package/src/scripts/ai_council/minority_retention.ts +171 -0
- package/src/scripts/ai_council/recouncil_savings.ts +299 -0
- package/src/scripts/ai_council/replay_route.ts +223 -0
- package/src/scripts/ai_council/reviewer_assignment.ts +279 -0
- package/src/scripts/ai_council/routing_training_row.ts +197 -0
- package/src/scripts/ai_council/synthesis_strategy.ts +159 -0
- package/src/scripts/check_adr_frontmatter.ts +4 -9
- package/src/scripts/check_estate_count.ts +205 -24
- package/src/scripts/check_release_highlights.ts +96 -13
- package/src/scripts/check_roadmap_trackable.ts +17 -2
- package/src/scripts/corpus_manifest.ts +235 -0
- package/src/scripts/evolution_lab.ts +181 -8
- package/src/scripts/hook_manifest.json +1 -1
- package/src/scripts/hook_manifest.yaml +28 -6
- package/src/scripts/hooks/comment_discipline_hook.ts +146 -0
- package/src/scripts/hooks/concern_registry.ts +2 -0
- package/src/scripts/hooks/turn_end_gate_hook.ts +9 -1
- package/src/scripts/language_mirror_hook.ts +14 -2
- package/src/scripts/lint_carrier_integrity.ts +820 -0
- package/src/scripts/lint_code_comments.ts +643 -0
- package/src/scripts/lint_plan_risk_register.ts +39 -19
- package/src/scripts/lint_promotion_paths.ts +779 -0
- package/src/scripts/llm_propose.ts +256 -0
- package/src/scripts/measure_delivery_sets.ts +361 -0
- package/src/scripts/measure_routing_signal.ts +307 -0
- package/src/scripts/model_rule_injection.ts +19 -3
- package/src/scripts/probe_council_retention.ts +495 -0
- package/src/scripts/release.ts +21 -37
- package/src/scripts/release_drill.ts +143 -0
- package/src/scripts/release_env.ts +35 -0
- package/src/scripts/release_publication.ts +84 -0
|
@@ -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
|
-
|
|
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 {
|