@event4u/agent-config 9.29.0 → 9.31.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/.claude-plugin/marketplace.json +1 -1
- package/CHANGELOG.md +32 -214
- package/README.md +1 -0
- package/dist/agent-src/commands/analyze/inbox.md +22 -1
- package/dist/agent-src/commands/explain-run.md +31 -1
- package/dist/agent-src/commands/team/delegate.md +1 -0
- package/dist/agent-src/contexts/execution/orchestration-telemetry.md +4 -1
- package/dist/agent-src/contexts/execution/roadmap-process-loop.md +17 -0
- package/dist/agent-src/contexts/execution/subagent-response-contract.md +11 -1
- package/dist/agent-src/rules/preservation-guard.md +9 -0
- package/dist/agent-src/scripts/roadmap_gates.ts +312 -0
- package/dist/agent-src/scripts/update_roadmap_progress.ts +72 -4
- package/dist/agent-src/skills/subagent-orchestration/SKILL.md +10 -0
- package/dist/agent-src/templates/agents/agent-project-settings.example.yml +1 -1
- package/dist/cli/registry.js +2 -0
- package/dist/cli/registry.js.map +1 -1
- package/dist/cli-delegate/{chunk-RCIA67XN.js → chunk-2WCLVDWC.js} +45 -2
- package/dist/cli-delegate/{chunk-66OVWGB5.js → chunk-AG47LAGQ.js} +1 -1
- package/dist/cli-delegate/{chunk-MP3JT3IU.js → chunk-DS6BLDMG.js} +1 -1
- package/dist/cli-delegate/chunk-EJITJBJX.js +172 -0
- package/dist/cli-delegate/{chunk-NZ5N2XQL.js → chunk-ITIQSGOQ.js} +3 -3
- package/dist/cli-delegate/{chunk-LCFJIQ4J.js → chunk-JEJABGKB.js} +110 -6
- package/dist/cli-delegate/chunk-MMJL4TIX.js +18 -0
- package/dist/cli-delegate/{chunk-REKCMYOU.js → chunk-PFBV6XRT.js} +36 -20
- package/dist/cli-delegate/{chunk-OJM3MBJM.js → chunk-QEEB4LHO.js} +22 -35
- package/dist/cli-delegate/chunk-RVGBH4XK.js +142 -0
- package/dist/cli-delegate/{chunk-MMXA5GY7.js → chunk-UVKNIJH2.js} +14 -0
- package/dist/cli-delegate/cmd_analyze_session.js +1 -1
- package/dist/cli-delegate/cmd_brand_status.js +1 -1
- package/dist/cli-delegate/cmd_conformance.js +2861 -96
- package/dist/cli-delegate/cmd_doctor.js +3 -2
- package/dist/cli-delegate/cmd_explain.js +2 -2
- package/dist/cli-delegate/cmd_export.js +4 -4
- package/dist/cli-delegate/cmd_handoff.js +87 -7
- package/dist/cli-delegate/cmd_mcp_available.js +1 -1
- package/dist/cli-delegate/cmd_migrate.js +2 -2
- package/dist/cli-delegate/cmd_packs_active.js +2 -2
- package/dist/cli-delegate/cmd_prune.js +4 -4
- package/dist/cli-delegate/cmd_route_audit.js +3 -2
- package/dist/cli-delegate/cmd_route_explain.js +1 -1
- package/dist/cli-delegate/cmd_session_recycle.js +177 -0
- package/dist/cli-delegate/cmd_settings_get.js +3 -56
- package/dist/cli-delegate/cmd_settings_migrate.js +4 -4
- package/dist/cli-delegate/cmd_settings_set.js +2 -2
- package/dist/cli-delegate/cmd_sync.js +4 -4
- package/dist/cli-delegate/cmd_uninstall.js +4 -4
- package/dist/cli-delegate/cmd_update.js +1 -1
- package/dist/cli-delegate/cmd_upgrade.js +1 -1
- package/dist/cli-delegate/cmd_validate.js +4 -4
- package/dist/cli-delegate/cmd_versions.js +1 -1
- package/dist/discovery/deprecation-report.md +1 -1
- package/dist/discovery/discovery-manifest.json +7 -7
- package/dist/discovery/discovery-manifest.json.sha256 +1 -1
- package/dist/discovery/discovery-manifest.summary.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 +2143 -641
- package/dist/install/install.mjs +133 -14
- package/dist/mcp/registry-manifest.json +2 -2
- package/dist/mcp/server.json +2 -2
- package/dist/mcp/server.mjs +3967 -761
- package/dist/scripts/_lib/agent_settings.js +111 -2
- package/dist/scripts/_lib/agent_settings.js.map +1 -1
- package/dist/server/schemas/settings.js +6 -0
- package/dist/server/schemas/settings.js.map +1 -1
- package/dist/ui/assets/index-B5qGYOJo.js +26 -0
- package/dist/ui/assets/index-B5qGYOJo.js.map +1 -0
- package/dist/ui/index.html +1 -1
- package/docs/contracts/settings-classes.md +21 -5
- package/package.json +1 -1
- package/src/agent-src/scripts/roadmap_gates.ts +312 -0
- package/src/agent-src/scripts/update_roadmap_progress.ts +72 -4
- package/src/config/agent-settings.template.yml +36 -0
- package/src/config/dispatch-economy-metrics.json +61 -0
- package/src/config/evaluator-budgets.json +4 -4
- package/src/config/hook-token-budget.json +91 -0
- package/src/config/pack-size-budget.json +3 -2
- package/src/config/recycle-threshold-budget.json +12 -0
- package/src/config/rule-enforcement-baseline.json +86 -0
- package/src/scripts/_cli/cmd_conformance.ts +17 -2
- package/src/scripts/_cli/cmd_session_recycle.ts +226 -0
- package/src/scripts/_cli/explain_last/inputs.ts +13 -0
- package/src/scripts/_dispatch.bash +23 -0
- package/src/scripts/_lib/agent_settings.ts +119 -2
- package/src/scripts/_lib/carrier_divergence.ts +47 -0
- package/src/scripts/_lib/judgment_ladder.ts +254 -2
- package/src/scripts/_lib/orchestration_record.ts +41 -10
- package/src/scripts/_lib/recycle_envelope_paths.ts +35 -0
- package/src/scripts/_lib/self_repair.ts +194 -0
- package/src/scripts/_lib/session_eol.ts +230 -0
- package/src/scripts/_lib/session_role.ts +42 -0
- package/src/scripts/_lib/subagent_capsule.ts +159 -0
- package/src/scripts/_lib/subagent_response.ts +75 -0
- package/src/scripts/ai_council/clients.ts +50 -1
- package/src/scripts/ask_transport.ts +221 -0
- package/src/scripts/bench_hook_injection.ts +234 -0
- package/src/scripts/check_adr_frontmatter.ts +84 -40
- package/src/scripts/check_branch_freshness.ts +1 -0
- package/src/scripts/check_corpus_staleness.ts +18 -4
- package/src/scripts/check_detector_corpus.ts +17 -0
- package/src/scripts/check_gitignore_freshness.ts +42 -8
- package/src/scripts/check_hook_bundle_freshness.ts +20 -0
- package/src/scripts/check_kernel_prefix_stability.ts +1 -0
- package/src/scripts/check_standing_rule_delivery.ts +73 -32
- package/src/scripts/check_static_layer_stability.ts +154 -0
- package/src/scripts/check_suppression_hygiene.ts +48 -0
- package/src/scripts/dispatch_economy_report.ts +448 -0
- package/src/scripts/dispatch_r2_reviewer.ts +15 -2
- package/src/scripts/explain_run.ts +206 -4
- package/src/scripts/generate_settings_reference.ts +1 -0
- package/src/scripts/handoff_context_hook.ts +112 -4
- package/src/scripts/hook_manifest.yaml +117 -10
- package/src/scripts/hooks/concern_registry.ts +8 -0
- package/src/scripts/hooks/delegation_nudge_hook.ts +40 -3
- package/src/scripts/hooks/dispatch_hook.ts +39 -2
- package/src/scripts/hooks/edit_shape_hook.ts +303 -0
- package/src/scripts/hooks/orchestration_record_hook.ts +19 -1
- package/src/scripts/hooks/reread_guard_hook.ts +314 -0
- package/src/scripts/hooks/rtk_wrap_hook.ts +133 -14
- package/src/scripts/hooks/session_eol_hook.ts +268 -0
- package/src/scripts/hooks/turn_end_gate_hook.ts +715 -0
- package/src/scripts/install-hooks.sh +1 -1
- package/src/scripts/language_mirror_hook.ts +143 -1
- package/src/scripts/lint_decision_impact.ts +14 -1
- package/src/scripts/lint_hook_manifest.ts +82 -0
- package/src/scripts/lint_readme_serial_comma.ts +1 -0
- package/src/scripts/lint_rule_enforcement_declaration.ts +347 -0
- package/src/scripts/measure_turn_end_gate.ts +272 -0
- package/src/scripts/orchestration_record.ts +4 -1
- package/src/scripts/rebuild_hook_bundle.ts +1 -0
- package/src/scripts/report_carrier_divergence.ts +72 -24
- package/src/scripts/report_conformance_funnel.ts +291 -0
- package/src/scripts/report_imperative_density.ts +1 -0
- package/src/scripts/routing_doctor.ts +97 -0
- package/src/scripts/self_repair_cli.ts +51 -3
- package/src/scripts/session_eol_report.ts +261 -0
- package/src/server/schemas/settings.ts +14 -0
- package/src/templates/compact-instructions.md +40 -0
- package/dist/ui/assets/index-JDAF00bC.js +0 -26
- package/dist/ui/assets/index-JDAF00bC.js.map +0 -1
|
@@ -0,0 +1,272 @@
|
|
|
1
|
+
#!/usr/bin/env -S npx tsx
|
|
2
|
+
/**
|
|
3
|
+
* `measure-turn-end-gate` — the false-positive pass roadmap
|
|
4
|
+
* `road-to-conformance-round5.md` § 3.4 asks for, run against a REAL
|
|
5
|
+
* transcript corpus rather than a hand-written one.
|
|
6
|
+
*
|
|
7
|
+
* Why this exists as a script and not as a number in a commit message: the
|
|
8
|
+
* roadmap asks to "record measured precision per detector", and a recorded
|
|
9
|
+
* figure nobody can re-derive is the same class of claim this whole round
|
|
10
|
+
* exists to remove. One command, same store, same numbers.
|
|
11
|
+
*
|
|
12
|
+
* What it measures, and what it deliberately does NOT:
|
|
13
|
+
*
|
|
14
|
+
* · FIRE RATE of each detector PER TURN, over the last assistant text of the
|
|
15
|
+
* turn — the population the shipped gate inspects. R2 finding 1: the first
|
|
16
|
+
* version scored every assistant ENTRY and published the result as a
|
|
17
|
+
* per-turn rate, which inflated the population ~8.6x and scored a
|
|
18
|
+
* closing-paragraph detector over mid-turn prose.
|
|
19
|
+
* · The independent scanner's own language total, printed for CONTEXT only.
|
|
20
|
+
* It is not an agreement figure and no overlap is claimed from it: the two
|
|
21
|
+
* instruments read different spans (its first prose line; this one strips
|
|
22
|
+
* code/quotes/paths from the reply and then scores lead-first via the same
|
|
23
|
+
* classifier), and this script does no per-turn matching between them.
|
|
24
|
+
* R2 finding 7 removed the per-session min/max arithmetic that used to
|
|
25
|
+
* report disjoint findings of equal count as full agreement.
|
|
26
|
+
* · It does NOT compute recall against the 20 promissory occurrences the
|
|
27
|
+
* round-5 audit reported. Those came from subagent transcript READING;
|
|
28
|
+
* `conformance_scan.ts` states in its own header that promissory
|
|
29
|
+
* closings are "deliberately NOT scanned", so there is no machine-
|
|
30
|
+
* readable ground truth to score against. Claiming a recall figure here
|
|
31
|
+
* would invent one.
|
|
32
|
+
* · KNOWN RESIDUAL, R2 finding 8, not fixed here: this script re-derives the
|
|
33
|
+
* language pin with `classify` per user entry, while the gate reads the pin
|
|
34
|
+
* `language_mirror_hook` persisted — which also applies a system-locale
|
|
35
|
+
* fallback and a keep-previous rule. Where those diverge, a turn can move
|
|
36
|
+
* between "pinned de", "pinned en" and "no obligation", so detector B's
|
|
37
|
+
* rate here is close to but not identical with the gate's. Reaching parity
|
|
38
|
+
* means reusing `nextState`, which is a larger change than a measurement
|
|
39
|
+
* correction and would itself need re-measuring.
|
|
40
|
+
*
|
|
41
|
+
* Usage:
|
|
42
|
+
* ./scripts-run src/scripts/measure_turn_end_gate --store <dir> [--limit N]
|
|
43
|
+
*/
|
|
44
|
+
import * as fs from 'node:fs';
|
|
45
|
+
import * as path from 'node:path';
|
|
46
|
+
import { fileURLToPath, pathToFileURL } from 'node:url';
|
|
47
|
+
|
|
48
|
+
import { classify, isSyntheticPrompt } from './language_mirror_hook.js';
|
|
49
|
+
import { assistantText, scanSession, userText } from './conformance_scan.js';
|
|
50
|
+
import { detectLanguage, detectPromissory } from './hooks/turn_end_gate_hook.js';
|
|
51
|
+
|
|
52
|
+
interface Counts {
|
|
53
|
+
/**
|
|
54
|
+
* TURNS, not entries. R2 finding 1: the first version incremented a counter
|
|
55
|
+
* named `assistant_turns` on every assistant entry and ran both detectors on
|
|
56
|
+
* each one, while the gate only ever evaluates the LAST assistant text of a
|
|
57
|
+
* turn. A turn with k prose entries contributed k samples to the instrument
|
|
58
|
+
* and 1 to the gate, so the published "18.0% of turns" was a per-ENTRY rate
|
|
59
|
+
* wearing a per-turn label — and detector A, a closing-paragraph detector by
|
|
60
|
+
* construction, was being scored over mid-turn prose.
|
|
61
|
+
*/
|
|
62
|
+
turns: number;
|
|
63
|
+
/** Assistant entries seen — kept only to show the ratio the old bug hid. */
|
|
64
|
+
assistant_entries: number;
|
|
65
|
+
promissory_fires: number;
|
|
66
|
+
language_fires: number;
|
|
67
|
+
/** The independent scanner's own per-session language total, for context. */
|
|
68
|
+
scanner_language: number;
|
|
69
|
+
sessions: number;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
function sessionFiles(store: string, limit: number): string[] {
|
|
73
|
+
let entries: string[];
|
|
74
|
+
try {
|
|
75
|
+
entries = fs.readdirSync(store).filter((f) => f.endsWith('.jsonl'));
|
|
76
|
+
} catch {
|
|
77
|
+
return [];
|
|
78
|
+
}
|
|
79
|
+
const withTime = entries.map((f) => {
|
|
80
|
+
const p = path.join(store, f);
|
|
81
|
+
let mtime = 0;
|
|
82
|
+
try {
|
|
83
|
+
mtime = fs.statSync(p).mtimeMs;
|
|
84
|
+
} catch {
|
|
85
|
+
/* unreadable — sorts last */
|
|
86
|
+
}
|
|
87
|
+
return { p, mtime };
|
|
88
|
+
});
|
|
89
|
+
withTime.sort((a, b) => b.mtime - a.mtime);
|
|
90
|
+
return withTime.slice(0, limit).map((e) => e.p);
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
export function measure(store: string, limit: number): Counts {
|
|
94
|
+
const c: Counts = {
|
|
95
|
+
turns: 0,
|
|
96
|
+
assistant_entries: 0,
|
|
97
|
+
promissory_fires: 0,
|
|
98
|
+
language_fires: 0,
|
|
99
|
+
scanner_language: 0,
|
|
100
|
+
sessions: 0,
|
|
101
|
+
};
|
|
102
|
+
|
|
103
|
+
for (const file of sessionFiles(store, limit)) {
|
|
104
|
+
let lines: string[];
|
|
105
|
+
try {
|
|
106
|
+
lines = fs.readFileSync(file, 'utf-8').split('\n').filter((l) => l.trim() !== '');
|
|
107
|
+
} catch {
|
|
108
|
+
continue;
|
|
109
|
+
}
|
|
110
|
+
c.sessions += 1;
|
|
111
|
+
const sessionId = path.basename(file).slice(0, 8);
|
|
112
|
+
|
|
113
|
+
// The independent scanner's own language total for this session,
|
|
114
|
+
// reported side by side and NOT as agreement — see the header for why
|
|
115
|
+
// no overlap is derivable from two totals (R2 finding 7).
|
|
116
|
+
//
|
|
117
|
+
// A throw is surfaced, not swallowed to zero: swallowing silently
|
|
118
|
+
// reclassified a whole session's fires as scanner-disagreements.
|
|
119
|
+
try {
|
|
120
|
+
c.scanner_language += scanSession(sessionId, lines).violations.filter(
|
|
121
|
+
(v) => v.check === 'language-pin',
|
|
122
|
+
).length;
|
|
123
|
+
} catch (err) {
|
|
124
|
+
process.stderr.write(
|
|
125
|
+
`measure-turn-end-gate: scanSession failed for ${sessionId} — excluded from the scanner total: ${String(err)}\n`,
|
|
126
|
+
);
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
let pinned: 'de' | 'en' | 'und' = 'und';
|
|
130
|
+
// The LAST assistant text since the last genuine user prompt. This is
|
|
131
|
+
// the population the gate inspects; anything else is a different
|
|
132
|
+
// measurement wearing the same label (R2 finding 1).
|
|
133
|
+
let pendingReply: string | null = null;
|
|
134
|
+
let pendingPin: 'de' | 'en' | 'und' = 'und';
|
|
135
|
+
|
|
136
|
+
const scoreTurn = (): void => {
|
|
137
|
+
if (pendingReply === null) return;
|
|
138
|
+
c.turns += 1;
|
|
139
|
+
if (detectPromissory(pendingReply) !== null) c.promissory_fires += 1;
|
|
140
|
+
if (detectLanguage(pendingReply, pendingPin) !== null) c.language_fires += 1;
|
|
141
|
+
pendingReply = null;
|
|
142
|
+
};
|
|
143
|
+
|
|
144
|
+
for (const raw of lines) {
|
|
145
|
+
let entry: Record<string, unknown>;
|
|
146
|
+
try {
|
|
147
|
+
entry = JSON.parse(raw) as Record<string, unknown>;
|
|
148
|
+
} catch {
|
|
149
|
+
continue;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
const u = userText(entry);
|
|
153
|
+
if (u !== null) {
|
|
154
|
+
// `isSyntheticPrompt`, the SAME filter the gate uses for the
|
|
155
|
+
// ordinal — not `isInjectedBody`. R2 round 2, finding 10: the
|
|
156
|
+
// two filters classify differently, and any entry they disagree
|
|
157
|
+
// about shifts this instrument's turn boundary away from the
|
|
158
|
+
// gate's own. Population parity is the entire point of this
|
|
159
|
+
// correction, so the filter has to be the same one.
|
|
160
|
+
if (!isSyntheticPrompt(u)) {
|
|
161
|
+
scoreTurn(); // the previous turn ends here
|
|
162
|
+
const verdict = classify(u);
|
|
163
|
+
if (verdict.language !== 'und') pinned = verdict.language;
|
|
164
|
+
}
|
|
165
|
+
continue;
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
const a = assistantText(entry);
|
|
169
|
+
if (a === null || a.trim() === '') continue;
|
|
170
|
+
c.assistant_entries += 1;
|
|
171
|
+
// Overwrite: only the last one in the turn is the gate's input.
|
|
172
|
+
pendingReply = a;
|
|
173
|
+
pendingPin = pinned;
|
|
174
|
+
}
|
|
175
|
+
scoreTurn(); // the transcript's final turn
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
return c;
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
export function render(c: Counts): string {
|
|
182
|
+
const pct = (n: number): string => (c.turns === 0 ? '0.0' : ((100 * n) / c.turns).toFixed(1));
|
|
183
|
+
return [
|
|
184
|
+
`measure-turn-end-gate · ${c.sessions} sessions · ${c.turns} turns · ${c.assistant_entries} assistant entries`,
|
|
185
|
+
'',
|
|
186
|
+
` detector A (promissory) fires on ${c.promissory_fires} turns (${pct(c.promissory_fires)}%)`,
|
|
187
|
+
` detector B (language) fires on ${c.language_fires} turns (${pct(c.language_fires)}%)`,
|
|
188
|
+
'',
|
|
189
|
+
' Rates are per TURN, over the last assistant text of each turn — the same',
|
|
190
|
+
' population the shipped gate inspects. The entry count is printed beside',
|
|
191
|
+
' the turn count because the first version of this script scored every',
|
|
192
|
+
' assistant ENTRY and labelled the result per-turn; the gap between the two',
|
|
193
|
+
' numbers is the size of that error.',
|
|
194
|
+
'',
|
|
195
|
+
` For context only, the independent scanner's own language total: ${c.scanner_language}`,
|
|
196
|
+
' NOT an agreement figure. The two instruments read different spans (its',
|
|
197
|
+
' first prose line vs. this stripped, lead-first-scored reply) and this',
|
|
198
|
+
' script does no per-turn matching between them, so no overlap can be claimed from these',
|
|
199
|
+
' two totals — the previous version claimed one from per-session min/max',
|
|
200
|
+
' arithmetic, which reports disjoint findings of equal count as full',
|
|
201
|
+
' agreement.',
|
|
202
|
+
'',
|
|
203
|
+
' No recall figure for detector A: promissory closings are not machine-',
|
|
204
|
+
' scanned anywhere in this tree, so there is no ground truth to score.',
|
|
205
|
+
].join('\n');
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
export function main(argv: string[] = process.argv.slice(2)): number {
|
|
209
|
+
let store = '';
|
|
210
|
+
let limit = 30;
|
|
211
|
+
for (let i = 0; i < argv.length; i += 1) {
|
|
212
|
+
if (argv[i] === '--store' && argv[i + 1] !== undefined) {
|
|
213
|
+
store = argv[i + 1]!;
|
|
214
|
+
i += 1;
|
|
215
|
+
} else if (argv[i] === '--limit' && argv[i + 1] !== undefined) {
|
|
216
|
+
// R2 finding 15: an unvalidated parseInt made `--limit abc` yield
|
|
217
|
+
// NaN, `slice(0, NaN)` return nothing, and the script exit 2 with
|
|
218
|
+
// "no .jsonl sessions under <store>" — a real input error reported
|
|
219
|
+
// as a missing corpus.
|
|
220
|
+
const parsed = Number.parseInt(argv[i + 1]!, 10);
|
|
221
|
+
if (!Number.isFinite(parsed) || parsed < 1) {
|
|
222
|
+
process.stderr.write(
|
|
223
|
+
`measure-turn-end-gate: --limit must be a positive integer, got ${JSON.stringify(argv[i + 1])}\n`,
|
|
224
|
+
);
|
|
225
|
+
return 2;
|
|
226
|
+
}
|
|
227
|
+
limit = parsed;
|
|
228
|
+
i += 1;
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
if (!store) {
|
|
232
|
+
process.stderr.write('measure-turn-end-gate: --store <transcript dir> is required\n');
|
|
233
|
+
return 2;
|
|
234
|
+
}
|
|
235
|
+
const counts = measure(store, limit);
|
|
236
|
+
if (counts.sessions === 0) {
|
|
237
|
+
// A measurement over zero sessions that exits 0 is the repo's own
|
|
238
|
+
// "gates that scan nothing" failure. Refuse instead.
|
|
239
|
+
process.stderr.write(`measure-turn-end-gate: no .jsonl sessions under ${store}\n`);
|
|
240
|
+
return 2;
|
|
241
|
+
}
|
|
242
|
+
process.stdout.write(`${render(counts)}\n`);
|
|
243
|
+
return 0;
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
// Bundle-safety, same guard as the sibling hook. R2 finding 15: a substring
|
|
247
|
+
// test on argv[1] auto-runs the module when esbuild inlines it into a bundle,
|
|
248
|
+
// where every module shares the bundle's `import.meta.url`.
|
|
249
|
+
declare const __AGENT_CONFIG_BUNDLE__: boolean | undefined;
|
|
250
|
+
function _isCliEntry(): boolean {
|
|
251
|
+
if (typeof __AGENT_CONFIG_BUNDLE__ !== 'undefined' && __AGENT_CONFIG_BUNDLE__) {
|
|
252
|
+
return false;
|
|
253
|
+
}
|
|
254
|
+
if (process.argv[1] === undefined) {
|
|
255
|
+
return false;
|
|
256
|
+
}
|
|
257
|
+
const argvUrl = pathToFileURL(path.resolve(process.argv[1])).href;
|
|
258
|
+
if (import.meta.url === argvUrl) {
|
|
259
|
+
return true;
|
|
260
|
+
}
|
|
261
|
+
try {
|
|
262
|
+
const here = fs.realpathSync(fileURLToPath(import.meta.url));
|
|
263
|
+
const argv = fs.realpathSync(path.resolve(process.argv[1]));
|
|
264
|
+
return here === argv;
|
|
265
|
+
} catch {
|
|
266
|
+
return false;
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
if (_isCliEntry()) {
|
|
271
|
+
process.exit(main());
|
|
272
|
+
}
|
|
@@ -13,9 +13,10 @@
|
|
|
13
13
|
* [--tiers sonnet,opus] [--agent-combo implementer,judge] [--wall-clock-ms 18500] [--dispatch-outcome DONE] \
|
|
14
14
|
* [--first-pass-success true|false] [--escalated true|false] \
|
|
15
15
|
* [--init-tokens 1200] [--payload-hash <hex8-64>] [--lookup-class definition|references|string-existence|report-run] \
|
|
16
|
-
* [--route-taken primitive|subagent] [--budget-hit true|false] [--correctness-match true|false] \
|
|
16
|
+
* [--route-taken primitive|subagent|ask] [--budget-hit true|false] [--correctness-match true|false] \
|
|
17
17
|
* [--cache-hit true|false] [--origin lean-init-2026] \
|
|
18
18
|
* [--rules-carried 32] [--rules-used 5] \
|
|
19
|
+
* [--work-tokens 41000] [--floor-provenance measured|estimated] \
|
|
19
20
|
* [--capsule-emitted true|false] [--capsule-entries 9] \
|
|
20
21
|
* [--watermark-step 8] [--saturation-step 4] [--trigger-arm-earlier watermark|saturation|tie] \
|
|
21
22
|
* [--dir <audit-dir>] [--dry-run]
|
|
@@ -144,6 +145,8 @@ export function main(argv: string[] = process.argv.slice(2)): number {
|
|
|
144
145
|
origin: str(flags, 'origin'),
|
|
145
146
|
rules_carried: int(flags, 'rules-carried'),
|
|
146
147
|
rules_used: int(flags, 'rules-used'),
|
|
148
|
+
work_tokens: int(flags, 'work-tokens'),
|
|
149
|
+
floor_provenance: str(flags, 'floor-provenance') as Provenance | undefined,
|
|
147
150
|
capsule_emitted: bool(flags, 'capsule-emitted'),
|
|
148
151
|
capsule_entries: int(flags, 'capsule-entries'),
|
|
149
152
|
watermark_step: int(flags, 'watermark-step'),
|
|
@@ -47,6 +47,7 @@ import { fileURLToPath } from "node:url";
|
|
|
47
47
|
|
|
48
48
|
import { check } from "./check_hook_bundle_freshness.js";
|
|
49
49
|
|
|
50
|
+
// ledger-exempt: self-heal builder, not a checker — one build → probe → atomic-rename transaction whose outcome is a single aggregate heal verdict; the per-source staleness accounting lives in check_hook_bundle_freshness, which carries the ledger.
|
|
50
51
|
const HERE = path.dirname(fileURLToPath(import.meta.url));
|
|
51
52
|
const REPO_ROOT = path.resolve(HERE, "..", "..");
|
|
52
53
|
const LIVE_REL = path.join("dist", "hooks", "dispatch.js");
|
|
@@ -35,16 +35,30 @@
|
|
|
35
35
|
* ignored, which is the failure `check-rule-invariants` already paid for once.
|
|
36
36
|
* The point is that the condition becomes VISIBLE, not that it becomes illegal.
|
|
37
37
|
*
|
|
38
|
-
* THE
|
|
39
|
-
*
|
|
40
|
-
* body-diff the two carriers deliver different
|
|
38
|
+
* THE FOUR CLASSES, AND WHICH ONE A READER MUST ACT ON
|
|
39
|
+
* ---------------------------------------------------
|
|
40
|
+
* body-diff the two carriers deliver different PROSE. This is the class
|
|
41
41
|
* round 5's contradiction lived in, and the only one that
|
|
42
|
-
* needs a decision.
|
|
43
|
-
*
|
|
44
|
-
*
|
|
45
|
-
*
|
|
46
|
-
*
|
|
47
|
-
*
|
|
42
|
+
* needs a decision. What the host does about it: NOTHING —
|
|
43
|
+
* both copies load at launch with the same priority and no
|
|
44
|
+
* precedence marker between them, so the divergence is
|
|
45
|
+
* binding-undefined rather than resolved in either
|
|
46
|
+
* direction. Cited, not inferred:
|
|
47
|
+
* `agents/evidence/analysis/claude-code-rules-dir-contract.md`
|
|
48
|
+
* (host 2.1.226, first-party observation plus the host's own
|
|
49
|
+
* documentation). The project copy is the NEWER text, which
|
|
50
|
+
* is a fact about recency and not a precedence rule — the
|
|
51
|
+
* earlier wording of this header called it a win, and a
|
|
52
|
+
* reader acting on that would have believed the host resolves
|
|
53
|
+
* something it does not.
|
|
54
|
+
* frontmatter-only the prose is byte-identical and only the metadata block
|
|
55
|
+
* differs. The host delivers the prose without that block, so
|
|
56
|
+
* nothing a reader must act on reaches the model differently.
|
|
57
|
+
* Split out because it was, measured, the ENTIRE content of
|
|
58
|
+
* the `body-diff` class: 109 of 109 on 2026-08-10. Left inside
|
|
59
|
+
* body-diff it manufactured the report's only actionable
|
|
60
|
+
* class out of a metadata difference — the same mistake the
|
|
61
|
+
* `unreadable` branch below already refuses to make.
|
|
48
62
|
* provenance-only identical after removing the two installer ownership keys.
|
|
49
63
|
* Structural, expected, and decided against closing —
|
|
50
64
|
* `agents/settings/contexts/dedup-reachability-refusal.md`.
|
|
@@ -69,7 +83,7 @@ import * as path from 'node:path';
|
|
|
69
83
|
import process from 'node:process';
|
|
70
84
|
import { fileURLToPath, pathToFileURL } from 'node:url';
|
|
71
85
|
|
|
72
|
-
import { comparePair, type PairVerdict } from './_lib/carrier_divergence.js';
|
|
86
|
+
import { comparePair, type PairVerdict, proseEqual } from './_lib/carrier_divergence.js';
|
|
73
87
|
|
|
74
88
|
const _HERE = fileURLToPath(import.meta.url);
|
|
75
89
|
const REPO_ROOT = path.resolve(path.dirname(_HERE), '..', '..');
|
|
@@ -103,6 +117,13 @@ export interface CarrierDivergence {
|
|
|
103
117
|
shared: number;
|
|
104
118
|
identical: string[];
|
|
105
119
|
provenanceOnly: string[];
|
|
120
|
+
/**
|
|
121
|
+
* Prose byte-identical, metadata block different. A real byte difference — so
|
|
122
|
+
* `comparePair` still calls it `body-diff` and the dedup predicate is
|
|
123
|
+
* unchanged — but not a difference in what the host delivers, so it is not
|
|
124
|
+
* something a reader has to act on.
|
|
125
|
+
*/
|
|
126
|
+
frontmatterOnly: string[];
|
|
106
127
|
bodyDiff: string[];
|
|
107
128
|
projectOnly: string[];
|
|
108
129
|
globalOnly: string[];
|
|
@@ -171,6 +192,7 @@ export function compareCarriers(
|
|
|
171
192
|
shared: 0,
|
|
172
193
|
identical: [],
|
|
173
194
|
provenanceOnly: [],
|
|
195
|
+
frontmatterOnly: [],
|
|
174
196
|
bodyDiff: [],
|
|
175
197
|
projectOnly: [],
|
|
176
198
|
globalOnly: [],
|
|
@@ -196,11 +218,12 @@ export function compareCarriers(
|
|
|
196
218
|
}
|
|
197
219
|
out.shared += 1;
|
|
198
220
|
let verdict: PairVerdict;
|
|
221
|
+
let proseSame: boolean;
|
|
199
222
|
try {
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
);
|
|
223
|
+
const projectCopy = fs.readFileSync(path.join(projectDir, rule));
|
|
224
|
+
const globalCopy = fs.readFileSync(path.join(globalDir, rule));
|
|
225
|
+
verdict = comparePair(projectCopy, globalCopy);
|
|
226
|
+
proseSame = proseEqual(projectCopy.toString('utf-8'), globalCopy.toString('utf-8'));
|
|
204
227
|
} catch {
|
|
205
228
|
// An unreadable copy is not a body difference and must not be
|
|
206
229
|
// reported as one — a permission error would otherwise manufacture
|
|
@@ -213,6 +236,7 @@ export function compareCarriers(
|
|
|
213
236
|
}
|
|
214
237
|
if (verdict === 'identical') out.identical.push(rule);
|
|
215
238
|
else if (verdict === 'provenance-only') out.provenanceOnly.push(rule);
|
|
239
|
+
else if (proseSame) out.frontmatterOnly.push(rule);
|
|
216
240
|
else out.bodyDiff.push(rule);
|
|
217
241
|
}
|
|
218
242
|
return out;
|
|
@@ -244,7 +268,8 @@ export function render(d: CarrierDivergence): string {
|
|
|
244
268
|
lines.push(` shared rule names ${d.shared}`);
|
|
245
269
|
lines.push(` byte-identical ${d.identical.length}`);
|
|
246
270
|
lines.push(` differ ONLY in the install stamp ${d.provenanceOnly.length}`);
|
|
247
|
-
lines.push(` differ in
|
|
271
|
+
lines.push(` differ ONLY in frontmatter ${d.frontmatterOnly.length}`);
|
|
272
|
+
lines.push(` differ in PROSE ${d.bodyDiff.length}`);
|
|
248
273
|
if (d.unreadable.length > 0) {
|
|
249
274
|
lines.push(` unreadable on one side ${d.unreadable.length} (${d.unreadable.join(', ')})`);
|
|
250
275
|
lines.push(' Not counted as shared and NOT a body difference — a dangling entry is');
|
|
@@ -255,16 +280,34 @@ export function render(d: CarrierDivergence): string {
|
|
|
255
280
|
lines.push('');
|
|
256
281
|
|
|
257
282
|
if (d.bodyDiff.length > 0) {
|
|
258
|
-
lines.push('
|
|
259
|
-
lines.push(' Both copies reach the model,
|
|
260
|
-
lines.push('
|
|
261
|
-
lines.push('
|
|
262
|
-
lines.push('
|
|
263
|
-
lines.push('
|
|
283
|
+
lines.push(' PROSE DIVERGENCE — the two carriers deliver different governed text for');
|
|
284
|
+
lines.push(' these rules. Both copies reach the model, and the host resolves NOTHING:');
|
|
285
|
+
lines.push(' rules without a `paths` key load at launch with the same priority as');
|
|
286
|
+
lines.push(' CLAUDE.md, with no precedence marker between the layers, so a claim one');
|
|
287
|
+
lines.push(' copy retracts can be re-asserted by the other and which text binds is');
|
|
288
|
+
lines.push(' UNDEFINED. Cited, not inferred — agents/evidence/analysis/');
|
|
289
|
+
lines.push(' claude-code-rules-dir-contract.md (host 2.1.226, the host\'s own docs plus');
|
|
290
|
+
lines.push(' a first-party observation). The project copy is the NEWER text, which is a');
|
|
291
|
+
lines.push(' fact about recency and NOT a host precedence rule: close the divergence by');
|
|
292
|
+
lines.push(' reinstalling the global copy, or decide which text binds — do not assume');
|
|
293
|
+
lines.push(' the newer one already won.');
|
|
264
294
|
for (const r of d.bodyDiff) lines.push(` - ${r}`);
|
|
265
295
|
lines.push('');
|
|
266
296
|
}
|
|
267
297
|
|
|
298
|
+
if (d.frontmatterOnly.length > 0) {
|
|
299
|
+
lines.push(` ${String(d.frontmatterOnly.length)} pair(s) differ ONLY in the frontmatter block — prose byte-identical.`);
|
|
300
|
+
lines.push(' Not a governed-text difference and nothing to act on: the host delivers the');
|
|
301
|
+
lines.push(' prose without that block, and none of the keys in it is one this host reads');
|
|
302
|
+
lines.push(' (claude-code-rules-dir-contract.md: it reads `paths`, and agent-config\'s own');
|
|
303
|
+
lines.push(' vocabulary is not read at all). Two writers, two frontmatter policies —');
|
|
304
|
+
lines.push(' `generate-tools` emits `paths` where a rule is path-scoped and nothing');
|
|
305
|
+
lines.push(' otherwise; `install.ts` writes the full vocabulary plus its ownership stamp.');
|
|
306
|
+
lines.push(' Reported as a count on purpose: naming every pair would bury the class');
|
|
307
|
+
lines.push(' above, which is the one a reader must act on.');
|
|
308
|
+
lines.push('');
|
|
309
|
+
}
|
|
310
|
+
|
|
268
311
|
if (d.provenanceOnly.length > 0) {
|
|
269
312
|
lines.push(` ${String(d.provenanceOnly.length)} pair(s) differ only by the installer's package:/source_path: stamp.`);
|
|
270
313
|
lines.push(' Not a content difference and NOT a defect: install.ts stamps every installed');
|
|
@@ -302,9 +345,14 @@ export function render(d: CarrierDivergence): string {
|
|
|
302
345
|
|
|
303
346
|
if (d.bodyDiff.length === 0) {
|
|
304
347
|
lines.push('');
|
|
305
|
-
lines.push(' No
|
|
306
|
-
lines.push('
|
|
307
|
-
lines.push('
|
|
348
|
+
lines.push(' No prose divergence — no rule\'s governed text differs between the carriers.');
|
|
349
|
+
lines.push(' The condition is transient — it returns whenever the checkout moves ahead of');
|
|
350
|
+
lines.push(' the installed release — so this is a reading of right now, not a property of');
|
|
351
|
+
lines.push(' the repo. Re-run it, do not cite it. That holds doubly here: the reading also');
|
|
352
|
+
lines.push(' depends on WHICH GENERATION of the emitter produced the project tree (a');
|
|
353
|
+
lines.push(' frontmatter-carrying symlink tree and a frontmatter-less real-file tree');
|
|
354
|
+
lines.push(' classify the same commit differently), so a number from another checkout is');
|
|
355
|
+
lines.push(' not comparable to this one.');
|
|
308
356
|
}
|
|
309
357
|
return lines.join('\n');
|
|
310
358
|
}
|