@event4u/agent-config 9.29.0 → 9.30.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 +108 -7
- 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-OJM3MBJM.js → chunk-BTJNRUX3.js} +21 -34
- package/dist/cli-delegate/{chunk-MMXA5GY7.js → chunk-DDUNNFYX.js} +14 -0
- package/dist/cli-delegate/{chunk-NZ5N2XQL.js → chunk-EQY4FLJB.js} +1 -1
- package/dist/cli-delegate/{chunk-RCIA67XN.js → chunk-IYTGIG2Y.js} +44 -1
- package/dist/cli-delegate/chunk-MMJL4TIX.js +18 -0
- package/dist/cli-delegate/chunk-O3CO5BFB.js +161 -0
- package/dist/cli-delegate/{chunk-REKCMYOU.js → chunk-PFBV6XRT.js} +36 -20
- package/dist/cli-delegate/chunk-RVGBH4XK.js +142 -0
- package/dist/cli-delegate/cmd_conformance.js +2798 -95
- package/dist/cli-delegate/cmd_doctor.js +2 -1
- package/dist/cli-delegate/cmd_export.js +2 -2
- package/dist/cli-delegate/cmd_handoff.js +86 -6
- package/dist/cli-delegate/cmd_prune.js +2 -2
- package/dist/cli-delegate/cmd_route_audit.js +2 -1
- package/dist/cli-delegate/cmd_session_recycle.js +177 -0
- package/dist/cli-delegate/cmd_settings_get.js +1 -1
- package/dist/cli-delegate/cmd_settings_migrate.js +2 -2
- package/dist/cli-delegate/cmd_settings_set.js +2 -2
- package/dist/cli-delegate/cmd_sync.js +2 -2
- package/dist/cli-delegate/cmd_uninstall.js +2 -2
- package/dist/cli-delegate/cmd_validate.js +2 -2
- 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 +1904 -575
- package/dist/install/install.mjs +14 -0
- package/dist/mcp/registry-manifest.json +2 -2
- package/dist/mcp/server.json +2 -2
- package/dist/mcp/server.mjs +3760 -726
- 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/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/_dispatch.bash +23 -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 +507 -0
- package/src/scripts/install-hooks.sh +1 -1
- package/src/scripts/language_mirror_hook.ts +89 -0
- 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 +194 -0
- package/src/scripts/orchestration_record.ts +4 -1
- package/src/scripts/rebuild_hook_bundle.ts +1 -0
- package/src/scripts/report_conformance_funnel.ts +284 -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,448 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* Dispatch-economy report — computes the two registered metrics from
|
|
4
|
+
* `src/config/dispatch-economy-metrics.json` (road-to-token-economy-dispatch
|
|
5
|
+
* Phase 1):
|
|
6
|
+
*
|
|
7
|
+
* dispatch_floor median init_tokens per role + init/work ratio per leg
|
|
8
|
+
* rules_efficiency median rules_used / rules_carried per worker envelope
|
|
9
|
+
*
|
|
10
|
+
* Metric semantics (restated from the registration file, load-bearing):
|
|
11
|
+
*
|
|
12
|
+
* init_tokens = billable_input of the leg's FIRST assistant record
|
|
13
|
+
* work_tokens = leg total billable_input − init_tokens
|
|
14
|
+
* billable_input = input_tokens + cache_read_input_tokens
|
|
15
|
+
* + cache_creation_input_tokens
|
|
16
|
+
*
|
|
17
|
+
* Sources, in the registration file's priority order:
|
|
18
|
+
* 1. Transcript ledger via `_lib/cc_transcript.ts` (per-agentId legs,
|
|
19
|
+
* message.id+requestId dedupe) — provenance `measured`.
|
|
20
|
+
* 2. Audit-log orchestration lines carrying `init_tokens`/`work_tokens`
|
|
21
|
+
* with `floor_provenance: "measured"`, and the `rules_carried`/
|
|
22
|
+
* `rules_used` pairs for rules_efficiency.
|
|
23
|
+
*
|
|
24
|
+
* ROLE ATTRIBUTION — honest limitation until the Phase 2 role axis ships:
|
|
25
|
+
* transcripts carry no session role, so every transcript leg reports as
|
|
26
|
+
* role `worker`; reviewer legs are attributed ONLY from audit-line
|
|
27
|
+
* `agent_combo` entries matching /review|judge|validator/i. The output
|
|
28
|
+
* names the attribution source per number.
|
|
29
|
+
*
|
|
30
|
+
* Class A (ADR-124 no-runtime-boundary): in-process, per-invocation, no
|
|
31
|
+
* socket, no daemon, no network, no writes — stdout report only. This is a
|
|
32
|
+
* REPORT, not a gate: exit code is 0 on every computed path (2 only on
|
|
33
|
+
* unusable invocation, e.g. unreadable registration file).
|
|
34
|
+
*
|
|
35
|
+
* Usage:
|
|
36
|
+
* ./scripts-run src/scripts/dispatch_economy_report \
|
|
37
|
+
* [--format text|json] [--root <transcripts-root>] [--audit-dir <dir>]
|
|
38
|
+
* [--max-age-days 14]
|
|
39
|
+
*/
|
|
40
|
+
import * as fs from 'node:fs';
|
|
41
|
+
import * as path from 'node:path';
|
|
42
|
+
import { fileURLToPath, pathToFileURL } from 'node:url';
|
|
43
|
+
|
|
44
|
+
import {
|
|
45
|
+
billableInputTokens,
|
|
46
|
+
DEFAULT_PROJECTS_ROOT,
|
|
47
|
+
scanTranscripts,
|
|
48
|
+
weightedInputUnits,
|
|
49
|
+
type TranscriptRecord,
|
|
50
|
+
} from './_lib/cc_transcript.js';
|
|
51
|
+
|
|
52
|
+
const _HERE = fileURLToPath(import.meta.url);
|
|
53
|
+
// src/scripts/dispatch_economy_report.ts → parents[1] is the repo root (same
|
|
54
|
+
// 2-up resolution cache_realization_report.ts uses at the same depth).
|
|
55
|
+
const REPO_ROOT = path.resolve(path.dirname(_HERE), '..', '..');
|
|
56
|
+
const REGISTRATION_FILE = path.join(REPO_ROOT, 'src', 'config', 'dispatch-economy-metrics.json');
|
|
57
|
+
const DEFAULT_AUDIT_DIR = path.join('agents', 'runtime', 'state', 'audit');
|
|
58
|
+
|
|
59
|
+
/** Reviewer attribution off audit-line agent_combo — id-shaped types only. */
|
|
60
|
+
const REVIEWER_TYPE_RE = /review|judge|validator/i;
|
|
61
|
+
|
|
62
|
+
export interface LegStat {
|
|
63
|
+
agentId: string;
|
|
64
|
+
calls: number;
|
|
65
|
+
/** Raw billable_input of the first record — comparable to the cache-economy 235.5k baseline. */
|
|
66
|
+
init_tokens: number;
|
|
67
|
+
/** Raw billable_input of the rest of the leg. */
|
|
68
|
+
work_tokens: number;
|
|
69
|
+
/** COST-shaped ratio: weightedInputUnits(init) / weightedInputUnits(rest)
|
|
70
|
+
* (read 0.1× · 5m write 1.25× · 1h write 2×). Raw billable_input would
|
|
71
|
+
* count every turn's cache-READ re-transmission at full weight and make
|
|
72
|
+
* the "more spent loading context than working" threshold unmeetable by
|
|
73
|
+
* construction — measured live 2026-08-10: raw ratio 0.02 on legs whose
|
|
74
|
+
* weighted ratio is the number the threshold means. null when the leg
|
|
75
|
+
* has a single call (floor-only, no work to ratio against). */
|
|
76
|
+
ratio: number | null;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
export interface RoleFloor {
|
|
80
|
+
role: 'worker' | 'reviewer';
|
|
81
|
+
attribution: 'transcript-leg (role axis not shipped — all legs report as worker)' | 'audit-line agent_combo';
|
|
82
|
+
legs: number;
|
|
83
|
+
single_call_legs: number;
|
|
84
|
+
median_init_tokens: number | null;
|
|
85
|
+
median_ratio_finite: number | null;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
export interface RulesEfficiency {
|
|
89
|
+
envelopes_with_pair: number;
|
|
90
|
+
median_quota: number | null;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
export interface Report {
|
|
94
|
+
schema: 'dispatch-economy-report/v1';
|
|
95
|
+
registration: { registered_at: string; review_by: string };
|
|
96
|
+
window: { max_age_days: number; transcript_records: number; legs: number };
|
|
97
|
+
dispatch_floor: {
|
|
98
|
+
roles: RoleFloor[];
|
|
99
|
+
thresholds: { projection_mandatory_ratio: number; success_ratio_after_projection: number };
|
|
100
|
+
/** Single-run reading — the registered verdict is a sustained-window call at review date. */
|
|
101
|
+
projection_mandatory_signal: boolean | null;
|
|
102
|
+
};
|
|
103
|
+
rules_efficiency: RulesEfficiency & { threshold_low_quota: number; low_quota_signal: boolean | null };
|
|
104
|
+
ask_economy: {
|
|
105
|
+
ask_lines: number;
|
|
106
|
+
adopted: number;
|
|
107
|
+
escalated: number;
|
|
108
|
+
threshold_escalation_kill: number;
|
|
109
|
+
/** null until asks exist; the registered verdict is a review-window call. */
|
|
110
|
+
escalation_kill_signal: boolean | null;
|
|
111
|
+
};
|
|
112
|
+
return_channel: {
|
|
113
|
+
lines_with_field: number;
|
|
114
|
+
median_chars: number | null;
|
|
115
|
+
envelope_cap_chars: number;
|
|
116
|
+
/** median above the committed envelope cap = transcript-shaped returns flowing back. */
|
|
117
|
+
cap_breach_signal: boolean | null;
|
|
118
|
+
};
|
|
119
|
+
notes: string[];
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
export function median(values: readonly number[]): number | null {
|
|
123
|
+
if (values.length === 0) return null;
|
|
124
|
+
const s = [...values].sort((a, b) => a - b);
|
|
125
|
+
const mid = Math.floor(s.length / 2);
|
|
126
|
+
const lo = s[mid - 1];
|
|
127
|
+
const hi = s[mid];
|
|
128
|
+
if (s.length % 2 === 1) return hi ?? null;
|
|
129
|
+
return lo !== undefined && hi !== undefined ? (lo + hi) / 2 : null;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
/** Group subagent records into per-agentId legs and compute init/work per leg. */
|
|
133
|
+
export function computeLegs(records: readonly TranscriptRecord[]): LegStat[] {
|
|
134
|
+
const byAgent = new Map<string, TranscriptRecord[]>();
|
|
135
|
+
for (const r of records) {
|
|
136
|
+
if (r.bucket !== 'subagent' || r.agentId === null) continue;
|
|
137
|
+
const list = byAgent.get(r.agentId) ?? [];
|
|
138
|
+
list.push(r);
|
|
139
|
+
byAgent.set(r.agentId, list);
|
|
140
|
+
}
|
|
141
|
+
const legs: LegStat[] = [];
|
|
142
|
+
for (const [agentId, recs] of byAgent) {
|
|
143
|
+
// Timestamp sort; records without timestamps keep file order (stable sort).
|
|
144
|
+
const sorted = [...recs].sort((a, b) => {
|
|
145
|
+
if (a.timestamp === null || b.timestamp === null) return 0;
|
|
146
|
+
return a.timestamp < b.timestamp ? -1 : a.timestamp > b.timestamp ? 1 : 0;
|
|
147
|
+
});
|
|
148
|
+
const first = sorted[0];
|
|
149
|
+
if (first === undefined) continue;
|
|
150
|
+
const init = billableInputTokens(first.usage);
|
|
151
|
+
const total = sorted.reduce((acc, r) => acc + billableInputTokens(r.usage), 0);
|
|
152
|
+
const work = total - init;
|
|
153
|
+
const initUnits = weightedInputUnits(first.usage);
|
|
154
|
+
const workUnits = sorted.slice(1).reduce((acc, r) => acc + weightedInputUnits(r.usage), 0);
|
|
155
|
+
legs.push({
|
|
156
|
+
agentId,
|
|
157
|
+
calls: sorted.length,
|
|
158
|
+
init_tokens: init,
|
|
159
|
+
work_tokens: work,
|
|
160
|
+
ratio: workUnits > 0 ? initUnits / workUnits : null,
|
|
161
|
+
});
|
|
162
|
+
}
|
|
163
|
+
return legs.sort((a, b) => (a.agentId < b.agentId ? -1 : 1));
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
interface AuditOrchestration {
|
|
167
|
+
rules_carried?: unknown;
|
|
168
|
+
rules_used?: unknown;
|
|
169
|
+
init_tokens?: unknown;
|
|
170
|
+
work_tokens?: unknown;
|
|
171
|
+
floor_provenance?: unknown;
|
|
172
|
+
agent_combo?: unknown;
|
|
173
|
+
route_taken?: unknown;
|
|
174
|
+
outcome?: unknown;
|
|
175
|
+
escalated?: unknown;
|
|
176
|
+
return_channel_chars?: unknown;
|
|
177
|
+
origin?: unknown;
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
function isFiniteNonNegInt(v: unknown): v is number {
|
|
181
|
+
return typeof v === 'number' && Number.isInteger(v) && v >= 0;
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
/**
|
|
185
|
+
* Read every orchestration object out of the audit dir's monthly JSONL files,
|
|
186
|
+
* honouring the same age window the transcript scan uses. Files are monthly,
|
|
187
|
+
* so the mtime filter (mtime updates on append) is the available granularity —
|
|
188
|
+
* a file whose LAST append is inside the window is read whole; per-line `ts`
|
|
189
|
+
* filtering then trims to the window exactly.
|
|
190
|
+
*/
|
|
191
|
+
export function readAuditOrchestrations(
|
|
192
|
+
auditDir: string,
|
|
193
|
+
opts: { maxAgeDays?: number; now?: Date } = {},
|
|
194
|
+
): AuditOrchestration[] {
|
|
195
|
+
if (!fs.existsSync(auditDir)) return [];
|
|
196
|
+
const out: AuditOrchestration[] = [];
|
|
197
|
+
const cutoffMs =
|
|
198
|
+
opts.maxAgeDays !== undefined
|
|
199
|
+
? (opts.now ?? new Date()).getTime() - opts.maxAgeDays * 24 * 60 * 60 * 1000
|
|
200
|
+
: null;
|
|
201
|
+
let entries: string[];
|
|
202
|
+
try {
|
|
203
|
+
entries = fs.readdirSync(auditDir).filter((f) => f.endsWith('.jsonl')).sort();
|
|
204
|
+
} catch {
|
|
205
|
+
return [];
|
|
206
|
+
}
|
|
207
|
+
if (cutoffMs !== null) {
|
|
208
|
+
entries = entries.filter((f) => {
|
|
209
|
+
try {
|
|
210
|
+
return fs.statSync(path.join(auditDir, f)).mtimeMs >= cutoffMs;
|
|
211
|
+
} catch {
|
|
212
|
+
return false;
|
|
213
|
+
}
|
|
214
|
+
});
|
|
215
|
+
}
|
|
216
|
+
for (const f of entries) {
|
|
217
|
+
let text: string;
|
|
218
|
+
try {
|
|
219
|
+
text = fs.readFileSync(path.join(auditDir, f), 'utf8');
|
|
220
|
+
} catch {
|
|
221
|
+
continue;
|
|
222
|
+
}
|
|
223
|
+
for (const line of text.split('\n')) {
|
|
224
|
+
if (line.trim().length === 0) continue;
|
|
225
|
+
try {
|
|
226
|
+
const parsed = JSON.parse(line) as { orchestration?: AuditOrchestration; ts?: unknown };
|
|
227
|
+
if (!parsed.orchestration || typeof parsed.orchestration !== 'object') continue;
|
|
228
|
+
if (cutoffMs !== null && typeof parsed.ts === 'string') {
|
|
229
|
+
const t = Date.parse(parsed.ts);
|
|
230
|
+
if (Number.isFinite(t) && t < cutoffMs) continue; // outside the window
|
|
231
|
+
}
|
|
232
|
+
out.push(parsed.orchestration);
|
|
233
|
+
} catch {
|
|
234
|
+
continue;
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
return out;
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
export function buildReport(opts: {
|
|
242
|
+
root: string;
|
|
243
|
+
auditDir: string;
|
|
244
|
+
maxAgeDays: number;
|
|
245
|
+
now?: Date;
|
|
246
|
+
}): Report {
|
|
247
|
+
const registrationRaw = fs.readFileSync(REGISTRATION_FILE, 'utf8');
|
|
248
|
+
const registration = JSON.parse(registrationRaw) as {
|
|
249
|
+
registered_at: string;
|
|
250
|
+
review_by: string;
|
|
251
|
+
metrics: {
|
|
252
|
+
dispatch_floor: { thresholds: { projection_mandatory_ratio: number; success_ratio_after_projection: number } };
|
|
253
|
+
rules_efficiency: { thresholds: { low_quota_signal: number } };
|
|
254
|
+
ask_economy: { thresholds: { escalation_kill: number } };
|
|
255
|
+
return_channel: { thresholds: { envelope_cap_chars: number } };
|
|
256
|
+
};
|
|
257
|
+
};
|
|
258
|
+
|
|
259
|
+
const scanOpts: Parameters<typeof scanTranscripts>[0] = {
|
|
260
|
+
root: opts.root,
|
|
261
|
+
maxAgeDays: opts.maxAgeDays,
|
|
262
|
+
...(opts.now !== undefined ? { now: opts.now } : {}),
|
|
263
|
+
};
|
|
264
|
+
const scan = scanTranscripts(scanOpts);
|
|
265
|
+
const legs = computeLegs(scan.records);
|
|
266
|
+
const finiteRatios = legs.map((l) => l.ratio).filter((r): r is number => r !== null);
|
|
267
|
+
const singleCall = legs.filter((l) => l.ratio === null).length;
|
|
268
|
+
|
|
269
|
+
const orchestrations = readAuditOrchestrations(opts.auditDir, {
|
|
270
|
+
maxAgeDays: opts.maxAgeDays,
|
|
271
|
+
...(opts.now !== undefined ? { now: opts.now } : {}),
|
|
272
|
+
});
|
|
273
|
+
const reviewerMeasured: { init: number; work: number }[] = [];
|
|
274
|
+
const quotas: number[] = [];
|
|
275
|
+
let askLines = 0;
|
|
276
|
+
let askAdopted = 0;
|
|
277
|
+
let askEscalated = 0;
|
|
278
|
+
const returnChars: number[] = [];
|
|
279
|
+
for (const o of orchestrations) {
|
|
280
|
+
if (o.route_taken === 'ask') {
|
|
281
|
+
askLines += 1;
|
|
282
|
+
if (o.outcome === 'DONE') askAdopted += 1;
|
|
283
|
+
}
|
|
284
|
+
// Ask->spawn escalation is recorded on the CALLER's follow-up SPAWN
|
|
285
|
+
// line (route_taken=subagent, escalated=true, origin-tagged) — the
|
|
286
|
+
// ask line itself cannot know it was insufficient. Counting
|
|
287
|
+
// escalated on ask lines would make the registered kill criterion
|
|
288
|
+
// unmeasurable by construction (review finding, 2026-08-10).
|
|
289
|
+
if (o.route_taken === 'subagent' && o.escalated === true && o.origin === 'dispatch-economy-2026') {
|
|
290
|
+
askEscalated += 1;
|
|
291
|
+
}
|
|
292
|
+
if (isFiniteNonNegInt(o.return_channel_chars)) {
|
|
293
|
+
returnChars.push(o.return_channel_chars);
|
|
294
|
+
}
|
|
295
|
+
if (isFiniteNonNegInt(o.rules_carried) && isFiniteNonNegInt(o.rules_used) && o.rules_carried > 0) {
|
|
296
|
+
quotas.push(o.rules_used / o.rules_carried);
|
|
297
|
+
}
|
|
298
|
+
const isReviewer = Array.isArray(o.agent_combo) && o.agent_combo.some((t) => typeof t === 'string' && REVIEWER_TYPE_RE.test(t));
|
|
299
|
+
if (isReviewer && o.floor_provenance === 'measured' && isFiniteNonNegInt(o.init_tokens) && isFiniteNonNegInt(o.work_tokens)) {
|
|
300
|
+
reviewerMeasured.push({ init: o.init_tokens, work: o.work_tokens });
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
const workerFloor: RoleFloor = {
|
|
305
|
+
role: 'worker',
|
|
306
|
+
attribution: 'transcript-leg (role axis not shipped — all legs report as worker)',
|
|
307
|
+
legs: legs.length,
|
|
308
|
+
single_call_legs: singleCall,
|
|
309
|
+
median_init_tokens: median(legs.map((l) => l.init_tokens)),
|
|
310
|
+
median_ratio_finite: median(finiteRatios),
|
|
311
|
+
};
|
|
312
|
+
// Audit lines carry raw token counts only (no cache split), so the
|
|
313
|
+
// reviewer ratio is raw init/work — flagged via the attribution string.
|
|
314
|
+
const reviewerRatios = reviewerMeasured.filter((p) => p.work > 0).map((p) => p.init / p.work);
|
|
315
|
+
const reviewerFloor: RoleFloor = {
|
|
316
|
+
role: 'reviewer',
|
|
317
|
+
attribution: 'audit-line agent_combo',
|
|
318
|
+
legs: reviewerMeasured.length,
|
|
319
|
+
single_call_legs: reviewerMeasured.filter((p) => p.work === 0).length,
|
|
320
|
+
median_init_tokens: median(reviewerMeasured.map((p) => p.init)),
|
|
321
|
+
median_ratio_finite: median(reviewerRatios),
|
|
322
|
+
};
|
|
323
|
+
|
|
324
|
+
const t = registration.metrics.dispatch_floor.thresholds;
|
|
325
|
+
const lowQuota = registration.metrics.rules_efficiency.thresholds.low_quota_signal;
|
|
326
|
+
const medianQuota = median(quotas);
|
|
327
|
+
const medianWorkerRatio = workerFloor.median_ratio_finite;
|
|
328
|
+
|
|
329
|
+
const notes: string[] = [
|
|
330
|
+
`dedup: ${scan.totalSeen} raw assistant records → ${scan.dedupedCount} (replay inflation removed)`,
|
|
331
|
+
'single_call_legs are floor-only legs (all init, zero work) — they are excluded from the finite-ratio median and support, never dilute, the projection-mandatory reading',
|
|
332
|
+
'projection_mandatory_signal is a single-run reading; the registered verdict is a sustained-window call at review date (dispatch-economy-metrics.json)',
|
|
333
|
+
];
|
|
334
|
+
|
|
335
|
+
return {
|
|
336
|
+
schema: 'dispatch-economy-report/v1',
|
|
337
|
+
registration: { registered_at: registration.registered_at, review_by: registration.review_by },
|
|
338
|
+
window: { max_age_days: opts.maxAgeDays, transcript_records: scan.dedupedCount, legs: legs.length },
|
|
339
|
+
dispatch_floor: {
|
|
340
|
+
roles: [workerFloor, reviewerFloor],
|
|
341
|
+
thresholds: t,
|
|
342
|
+
projection_mandatory_signal: medianWorkerRatio === null ? null : medianWorkerRatio > t.projection_mandatory_ratio,
|
|
343
|
+
},
|
|
344
|
+
rules_efficiency: {
|
|
345
|
+
envelopes_with_pair: quotas.length,
|
|
346
|
+
median_quota: medianQuota,
|
|
347
|
+
threshold_low_quota: lowQuota,
|
|
348
|
+
low_quota_signal: medianQuota === null ? null : medianQuota < lowQuota,
|
|
349
|
+
},
|
|
350
|
+
ask_economy: {
|
|
351
|
+
ask_lines: askLines,
|
|
352
|
+
adopted: askAdopted,
|
|
353
|
+
escalated: askEscalated,
|
|
354
|
+
threshold_escalation_kill: registration.metrics.ask_economy.thresholds.escalation_kill,
|
|
355
|
+
escalation_kill_signal:
|
|
356
|
+
askLines === 0
|
|
357
|
+
? null
|
|
358
|
+
: askEscalated / askLines > registration.metrics.ask_economy.thresholds.escalation_kill,
|
|
359
|
+
},
|
|
360
|
+
return_channel: {
|
|
361
|
+
lines_with_field: returnChars.length,
|
|
362
|
+
median_chars: median(returnChars),
|
|
363
|
+
envelope_cap_chars: registration.metrics.return_channel.thresholds.envelope_cap_chars,
|
|
364
|
+
cap_breach_signal:
|
|
365
|
+
returnChars.length === 0
|
|
366
|
+
? null
|
|
367
|
+
: (median(returnChars) ?? 0) > registration.metrics.return_channel.thresholds.envelope_cap_chars,
|
|
368
|
+
},
|
|
369
|
+
notes,
|
|
370
|
+
};
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
function fmtTokens(n: number | null): string {
|
|
374
|
+
if (n === null) return '—';
|
|
375
|
+
return n >= 1000 ? `${(n / 1000).toFixed(1)}k` : String(Math.round(n));
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
function renderText(r: Report): string {
|
|
379
|
+
const lines: string[] = [];
|
|
380
|
+
lines.push('dispatch-economy report (registered: ' + r.registration.registered_at + ' · review by: ' + r.registration.review_by + ')');
|
|
381
|
+
lines.push(`window: ${r.window.max_age_days}d · ${r.window.transcript_records} deduped records · ${r.window.legs} legs`);
|
|
382
|
+
lines.push('');
|
|
383
|
+
lines.push('dispatch_floor:');
|
|
384
|
+
for (const role of r.dispatch_floor.roles) {
|
|
385
|
+
lines.push(
|
|
386
|
+
` ${role.role.padEnd(8)} legs=${role.legs} (single-call: ${role.single_call_legs}) · median init=${fmtTokens(role.median_init_tokens)} · median init/work=${role.median_ratio_finite === null ? '—' : role.median_ratio_finite.toFixed(2)}`,
|
|
387
|
+
);
|
|
388
|
+
lines.push(` attribution: ${role.attribution}`);
|
|
389
|
+
}
|
|
390
|
+
lines.push(
|
|
391
|
+
` projection-mandatory signal (ratio > ${r.dispatch_floor.thresholds.projection_mandatory_ratio}): ${r.dispatch_floor.projection_mandatory_signal === null ? 'no data' : String(r.dispatch_floor.projection_mandatory_signal)}`,
|
|
392
|
+
);
|
|
393
|
+
lines.push('');
|
|
394
|
+
lines.push('rules_efficiency:');
|
|
395
|
+
lines.push(
|
|
396
|
+
` envelopes with pair=${r.rules_efficiency.envelopes_with_pair} · median quota=${r.rules_efficiency.median_quota === null ? '—' : r.rules_efficiency.median_quota.toFixed(2)} · low-quota signal (< ${r.rules_efficiency.threshold_low_quota}): ${r.rules_efficiency.low_quota_signal === null ? 'no data' : String(r.rules_efficiency.low_quota_signal)}`,
|
|
397
|
+
);
|
|
398
|
+
lines.push('');
|
|
399
|
+
lines.push('return_channel:');
|
|
400
|
+
lines.push(
|
|
401
|
+
` lines=${r.return_channel.lines_with_field} · median chars=${r.return_channel.median_chars === null ? '—' : Math.round(r.return_channel.median_chars)} · cap-breach signal (> ${r.return_channel.envelope_cap_chars}): ${r.return_channel.cap_breach_signal === null ? 'no data' : String(r.return_channel.cap_breach_signal)}`,
|
|
402
|
+
);
|
|
403
|
+
lines.push('');
|
|
404
|
+
lines.push('ask_economy:');
|
|
405
|
+
lines.push(
|
|
406
|
+
` ask lines=${r.ask_economy.ask_lines} · adopted=${r.ask_economy.adopted} · escalated=${r.ask_economy.escalated} · escalation-kill signal (> ${r.ask_economy.threshold_escalation_kill}): ${r.ask_economy.escalation_kill_signal === null ? 'no data' : String(r.ask_economy.escalation_kill_signal)}`,
|
|
407
|
+
);
|
|
408
|
+
lines.push('');
|
|
409
|
+
for (const n of r.notes) lines.push(`note: ${n}`);
|
|
410
|
+
return lines.join('\n') + '\n';
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
export function main(argv: readonly string[]): number {
|
|
414
|
+
let format: 'text' | 'json' = 'text';
|
|
415
|
+
let root = DEFAULT_PROJECTS_ROOT;
|
|
416
|
+
let auditDir = path.join(process.cwd(), DEFAULT_AUDIT_DIR);
|
|
417
|
+
let maxAgeDays = 14;
|
|
418
|
+
for (let i = 0; i < argv.length; i++) {
|
|
419
|
+
const a = argv[i];
|
|
420
|
+
if (a === '--format') format = argv[++i] === 'json' ? 'json' : 'text';
|
|
421
|
+
else if (a === '--root') root = argv[++i] ?? root;
|
|
422
|
+
else if (a === '--audit-dir') auditDir = argv[++i] ?? auditDir;
|
|
423
|
+
else if (a === '--max-age-days') maxAgeDays = Number(argv[++i] ?? maxAgeDays) || maxAgeDays;
|
|
424
|
+
}
|
|
425
|
+
let report: Report;
|
|
426
|
+
try {
|
|
427
|
+
report = buildReport({ root, auditDir, maxAgeDays });
|
|
428
|
+
} catch (err) {
|
|
429
|
+
process.stderr.write(`dispatch_economy_report: ${err instanceof Error ? err.message : String(err)}\n`);
|
|
430
|
+
return 2;
|
|
431
|
+
}
|
|
432
|
+
process.stdout.write(format === 'json' ? JSON.stringify(report, null, 2) + '\n' : renderText(report));
|
|
433
|
+
return 0;
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
declare const __AGENT_CONFIG_BUNDLE__: boolean | undefined;
|
|
437
|
+
function _isCliEntry(): boolean {
|
|
438
|
+
if (typeof __AGENT_CONFIG_BUNDLE__ !== 'undefined' && __AGENT_CONFIG_BUNDLE__) return false;
|
|
439
|
+
if (process.argv[1] === undefined) return false;
|
|
440
|
+
const argvUrl = pathToFileURL(path.resolve(process.argv[1])).href;
|
|
441
|
+
if (import.meta.url === argvUrl) return true;
|
|
442
|
+
try {
|
|
443
|
+
return fs.realpathSync(fileURLToPath(import.meta.url)) === fs.realpathSync(path.resolve(process.argv[1]));
|
|
444
|
+
} catch {
|
|
445
|
+
return false;
|
|
446
|
+
}
|
|
447
|
+
}
|
|
448
|
+
if (_isCliEntry()) process.exit(main(process.argv.slice(2)));
|
|
@@ -350,7 +350,10 @@ export function extractAcceptanceCriteria(roadmapText: string): string {
|
|
|
350
350
|
const lines = roadmapText.split('\n');
|
|
351
351
|
let start = -1;
|
|
352
352
|
for (let i = 0; i < lines.length; i++) {
|
|
353
|
-
|
|
353
|
+
// Case-insensitive: the tree carries both `## Acceptance Criteria` and
|
|
354
|
+
// `## Acceptance criteria`; a case-sensitive match silently extracted
|
|
355
|
+
// nothing for the latter (found by the zcs-close R2 review, 2026-08-09).
|
|
356
|
+
if (/^## acceptance criteria\s*$/i.test(lines[i] as string)) {
|
|
354
357
|
start = i;
|
|
355
358
|
break;
|
|
356
359
|
}
|
|
@@ -924,7 +927,17 @@ function runDispatch(args: Args): number {
|
|
|
924
927
|
fs.mkdirSync(inputDirAbs, { recursive: true });
|
|
925
928
|
fs.writeFileSync(path.join(inputDirAbs, 'diff.patch'), scopeDiffText, 'utf-8');
|
|
926
929
|
if (roadmapText !== null) {
|
|
927
|
-
|
|
930
|
+
// The snapshot lands under agents/evidence/, which check_references
|
|
931
|
+
// walks, while the live roadmap layer is deliberately excluded from
|
|
932
|
+
// that gate — so a roadmap that legitimately quotes a nonexistent
|
|
933
|
+
// path (e.g. documenting a hallucinated council citation) would red
|
|
934
|
+
// CI through its own review snapshot. The header exemption keeps the
|
|
935
|
+
// snapshot verbatim below the marker; roadmap_hash binds the LIVE
|
|
936
|
+
// file and is unaffected. Found by the zcs-close CI run, 2026-08-09.
|
|
937
|
+
const snapshotHeader =
|
|
938
|
+
'<!-- check-refs: skip -->\n' +
|
|
939
|
+
'<!-- verbatim roadmap snapshot for the R2 reviewer; the live roadmap layer is excluded from check_references, and a snapshot must not fail a gate its source is exempt from -->\n';
|
|
940
|
+
fs.writeFileSync(path.join(inputDirAbs, 'roadmap.md'), snapshotHeader + roadmapText, 'utf-8');
|
|
928
941
|
}
|
|
929
942
|
fs.writeFileSync(path.join(inputDirAbs, 'acceptance-criteria.md'), acText ?? '', 'utf-8');
|
|
930
943
|
fs.writeFileSync(path.join(inputDirAbs, 'prompt.md'), promptText, 'utf-8');
|