@event4u/agent-config 12.0.0 → 13.0.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 +273 -0
- package/README.md +1 -1
- package/agents/templates/.ai-council.yml.example +46 -3
- package/dist/agent-src/commands/cost/report.md +23 -0
- package/dist/agent-src/contexts/execution/subagent-routing.md +148 -33
- package/dist/agent-src/contexts/model-recommendations.md +6 -4
- package/dist/agent-src/contexts/subagent-configuration.md +69 -7
- package/dist/agent-src/rules/autonomous-execution.md +1 -1
- package/dist/agent-src/rules/design-fidelity.md +1 -1
- package/dist/agent-src/rules/design-review-after-ui-write.md +10 -5
- package/dist/agent-src/rules/evaluator-independence.md +27 -5
- package/dist/agent-src/rules/git-history-discipline.md +5 -1
- package/dist/agent-src/rules/low-impact-corpus-privacy-floor.md +1 -0
- package/dist/agent-src/rules/missing-skill-recovery.md +89 -0
- package/dist/agent-src/rules/ui-audit-gate.md +9 -4
- package/dist/agent-src/scripts/archive_completed_roadmaps.ts +33 -0
- package/dist/agent-src/scripts/roadmap_gates.ts +30 -2
- package/dist/agent-src/scripts/update_roadmap_progress.ts +47 -1
- package/dist/agent-src/skills/authz-review/SKILL.md +4 -0
- package/dist/agent-src/skills/design-system-capture/SKILL.md +4 -0
- package/dist/agent-src/skills/design-system-capture/references/snapshot-preference-order.md +77 -0
- package/dist/agent-src/skills/merge-conflicts/SKILL.md +4 -0
- package/dist/agent-src/skills/prompt-optimizer/SKILL.md +17 -1
- package/dist/agent-src/skills/roadmap-writing/SKILL.md +11 -11
- package/dist/agent-src/skills/systematic-debugging/SKILL.md +4 -0
- package/dist/agent-src/skills/threat-modeling/SKILL.md +3 -0
- package/dist/agent-src/templates/agents/agent-project-settings.example.yml +1 -1
- package/dist/agent-src/templates/roadmaps.md +41 -3
- package/dist/cli/registry.js +1 -0
- package/dist/cli/registry.js.map +1 -1
- package/dist/cli-delegate/{chunk-NN4KW4HZ.js → chunk-3B72LSZL.js} +123 -2338
- package/dist/cli-delegate/{chunk-55WZN5VF.js → chunk-EVTY7ISR.js} +2 -2
- package/dist/cli-delegate/chunk-N7FEST6D.js +3895 -0
- package/dist/cli-delegate/{chunk-L565YFXH.js → chunk-XURJ4AQC.js} +566 -298
- package/dist/cli-delegate/cmd_conformance.js +4 -4
- package/dist/cli-delegate/cmd_doctor.js +2 -2
- package/dist/cli-delegate/cmd_export.js +2 -2
- package/dist/cli-delegate/cmd_handoff.js +1 -1
- package/dist/cli-delegate/cmd_prune.js +2 -2
- package/dist/cli-delegate/cmd_settings_migrate.js +2 -2
- package/dist/cli-delegate/cmd_settings_set.js +1 -1
- 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 +44 -23
- package/dist/discovery/discovery-manifest.json.sha256 +1 -1
- package/dist/discovery/discovery-manifest.summary.md +3 -3
- package/dist/discovery/dormancy-report.md +1 -1
- package/dist/discovery/orphan-report.md +1 -1
- package/dist/discovery/packs.json +6 -5
- package/dist/discovery/trust-report.md +2 -2
- package/dist/discovery/workspaces.json +5 -4
- package/dist/hooks/dispatch.js +10625 -8541
- package/dist/install/install.mjs +754 -484
- package/dist/mcp/registry-manifest.json +3 -3
- package/dist/mcp/server.json +2 -2
- package/dist/mcp/server.mjs +1302 -167
- package/dist/router.json +1 -1
- package/dist/scripts/_lib/model_tier.js +9 -0
- package/dist/scripts/_lib/model_tier.js.map +1 -1
- package/dist/scripts/condense.js +50 -0
- package/dist/scripts/condense.js.map +1 -1
- package/dist/server/schemas/settings.js +2 -2
- package/dist/server/schemas/settings.js.map +1 -1
- package/docs/MIGRATION.md +20 -1
- package/docs/guidelines/design-fidelity-mechanics.md +21 -0
- package/docs/guidelines/design-handover-extraction.md +87 -0
- package/docs/guidelines/prompt-templates.md +24 -1
- package/package.json +1 -1
- package/src/agent-src/scripts/archive_completed_roadmaps.ts +33 -0
- package/src/agent-src/scripts/roadmap_gates.ts +30 -2
- package/src/agent-src/scripts/update_roadmap_progress.ts +47 -1
- package/src/config/evaluator-budgets.json +9 -8
- package/src/config/gate-coverage.yml +63 -11
- package/src/config/gate-violation-baselines.json +12 -0
- package/src/config/hook-token-budget.json +15 -2
- package/src/config/preamble-payload-budget.json +28 -0
- package/src/scripts/_dispatch.bash +10 -0
- package/src/scripts/_lib/bench_ab_complexity.ts +439 -0
- package/src/scripts/_lib/bench_ab_safety_tier.ts +142 -0
- package/src/scripts/_lib/bench_ab_scoring_v2.ts +28 -3
- package/src/scripts/_lib/bench_ab_search_adherence.ts +423 -0
- package/src/scripts/_lib/bench_ab_size_claim.ts +206 -0
- package/src/scripts/_lib/bench_ab_workspace.ts +111 -0
- package/src/scripts/_lib/git_common_dir.ts +173 -0
- package/src/scripts/_lib/model_tier.ts +9 -0
- package/src/scripts/_lib/release_highlights.ts +108 -7
- package/src/scripts/_lib/skill_catalogue.ts +1115 -0
- package/src/scripts/_lib/tier_budget_routing.ts +31 -331
- package/src/scripts/ai_council/clients.ts +197 -18
- package/src/scripts/ai_council/config.ts +16 -1
- package/src/scripts/ai_council/low_impact.ts +14 -1
- package/src/scripts/ai_council/orchestrator.ts +14 -1
- package/src/scripts/ai_council/transport_resolver.ts +32 -2
- package/src/scripts/bench_ab_v2_complexity.ts +252 -0
- package/src/scripts/bench_ab_v2_run.ts +3 -42
- package/src/scripts/bench_ab_v2_safety.ts +296 -0
- package/src/scripts/bench_ab_v2_search.ts +343 -0
- package/src/scripts/bench_ab_v2_stats.ts +191 -0
- package/src/scripts/build_archive_index.ts +383 -0
- package/src/scripts/capture_skill_catalogue.ts +279 -317
- package/src/scripts/check_branch_freshness.ts +7 -1
- package/src/scripts/check_release_published.ts +6 -2
- package/src/scripts/check_release_trunk_sync.ts +18 -2
- package/src/scripts/check_source_size_budget.ts +428 -0
- package/src/scripts/check_suppression_hygiene.ts +5 -1
- package/src/scripts/condense.ts +50 -1
- package/src/scripts/cost/backfill_rates.mjs +298 -0
- package/src/scripts/cost/budget.mjs +12 -32
- package/src/scripts/cost_summary.ts +25 -0
- package/src/scripts/dispatch_r2_reviewer.ts +15 -5
- package/src/scripts/evidence_report.ts +15 -4
- package/src/scripts/external_sources_denylist.json +1 -0
- package/src/scripts/hook_manifest.yaml +75 -15
- package/src/scripts/hooks/concern_registry.ts +4 -0
- package/src/scripts/hooks/orchestration_record_hook.ts +21 -0
- package/src/scripts/hooks/skill_route_hook.ts +226 -0
- package/src/scripts/hooks/tool_result_bytes_hook.ts +219 -0
- package/src/scripts/hooks/ui_route_nudge_hook.ts +6 -3
- package/src/scripts/hot_context_hook.ts +13 -1
- package/src/scripts/install.ts +129 -0
- package/src/scripts/janitor.ts +77 -2
- package/src/scripts/lint_design_antipattern_parity.ts +40 -6
- package/src/scripts/lint_harvest_provenance.ts +35 -1
- package/src/scripts/lint_plan_risk_register.ts +22 -9
- package/src/scripts/lint_roadmap_blockers.ts +109 -6
- package/src/scripts/lint_rule_skill_pack_reach.ts +43 -0
- package/src/scripts/lint_scheduled_deprecations.ts +738 -0
- package/src/scripts/lint_skill_router_head.ts +21 -0
- package/src/scripts/mcp_server/consumer_tool_catalog.json +26 -3
- package/src/scripts/mcp_server/tool_catalog_source.ts +6 -24
- package/src/scripts/mcp_server/tools.ts +87 -0
- package/src/scripts/migration_status.ts +17 -1
- package/src/scripts/probe_review_binding_drift.ts +731 -0
- package/src/scripts/release.ts +100 -6
- package/src/scripts/routing_doctor.ts +50 -13
- package/src/scripts/schemas/command.schema.json +3 -3
- package/src/scripts/schemas/skill.schema.json +20 -2
- package/src/scripts/schemas/subagent.schema.json +2 -2
- package/src/scripts/session_eol_report.ts +81 -0
- package/src/scripts/trigger_coverage.ts +224 -3
- package/src/scripts/workspace_doctor.ts +464 -0
- package/src/scripts/worktree_cleanup_check.ts +2 -1
- package/src/server/schemas/settings.ts +2 -2
- package/dist/cli-delegate/chunk-AYKQGIBG.js +0 -1594
- package/src/config/budget-routing.json +0 -7
|
@@ -0,0 +1,298 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// cost-backfill — re-price ledger rows that `track.mjs` flagged `rate_missing`,
|
|
3
|
+
// using a rate table supplied for the model ids that had none at capture time.
|
|
4
|
+
//
|
|
5
|
+
// Why this exists: `track.mjs` prices an unrecognised model at $0 rather than
|
|
6
|
+
// throwing, because losing a whole session of real token counts over one
|
|
7
|
+
// unknown id is worse than an understated total. It keeps the token counts and
|
|
8
|
+
// sets `rate_missing` precisely so the cost can be repaired later. This is that
|
|
9
|
+
// repair pass.
|
|
10
|
+
//
|
|
11
|
+
// BUILT AGAINST AN OBSERVED ROW, NOT A GUESSED ONE. The shape below was read
|
|
12
|
+
// off a real flagged row produced from this repo's own transcripts on
|
|
13
|
+
// 2026-08-16 (`claude-fable-5`, 468 messages, 236,695,963 cache-read tokens,
|
|
14
|
+
// cost 0). Two limits are properties of that observed shape, and both are
|
|
15
|
+
// stated rather than papered over — a re-priced row that looks exact while
|
|
16
|
+
// resting on an invented split would be worse than the zero it replaces:
|
|
17
|
+
//
|
|
18
|
+
// 1. THE CACHE-WRITE TTL SPLIT IS NOT RETAINED ON THE ROW. `track.mjs`
|
|
19
|
+
// prices `ephemeral_5m` and `ephemeral_1h` cache writes at different
|
|
20
|
+
// rates (1.25x / 2x of input), but `byModel[m]` aggregates both into one
|
|
21
|
+
// `cache_creation_input_tokens` tally. A backfill therefore prices every
|
|
22
|
+
// cache write at the 5m rate — Anthropic's default TTL, and the same
|
|
23
|
+
// fallback `costForUsage` already applies to any unaccounted remainder.
|
|
24
|
+
// A session that really used 1h writes is under-priced by the difference.
|
|
25
|
+
// Recorded per row as `cache_ttl_assumed: "5m"`.
|
|
26
|
+
//
|
|
27
|
+
// 2. `byBucket[*].cost_usd` IS NOT REPAIRED. The row records per-bucket
|
|
28
|
+
// totals and per-model totals, but not per-bucket-per-model tokens, so
|
|
29
|
+
// the recovered cost cannot be attributed across main vs subagent. The
|
|
30
|
+
// alternative is not a better number but an invented one — the same
|
|
31
|
+
// honest-limit stance `cost-summary-schema` takes for `by_date`. The
|
|
32
|
+
// bucket figures stay as captured and the report says so.
|
|
33
|
+
//
|
|
34
|
+
// Rates file (JSON), either form per model id:
|
|
35
|
+
// { "claude-fable-5": "opus" } // reuse a known tier's rates
|
|
36
|
+
// { "claude-fable-5": { "input": 5, "output": 25, // explicit USD per 1M
|
|
37
|
+
// "cache_write_5m": 6.25, "cache_read": 0.5 } }
|
|
38
|
+
// Every rate must be a finite number >= 0, and an all-zero table is refused.
|
|
39
|
+
// `cache_write_1h` is accepted but unused — see RATE_KEYS.
|
|
40
|
+
//
|
|
41
|
+
// Usage: node src/scripts/cost/backfill_rates.mjs --rates <file.json>
|
|
42
|
+
// [--store <ledger.jsonl>] [--apply]
|
|
43
|
+
// Env:
|
|
44
|
+
// BACKFILL_STORE=<path> ledger to re-price (default: agents/cost-tracking/sessions.jsonl)
|
|
45
|
+
// BACKFILL_RATES=<path> rates file (same as --rates)
|
|
46
|
+
// BACKFILL_APPLY=1 write the result (same as --apply); default is a dry run
|
|
47
|
+
// BACKFILL_QUIET=1 suppress the report; exit code and writes are unchanged
|
|
48
|
+
|
|
49
|
+
import { readFileSync, writeFileSync, renameSync, existsSync } from 'node:fs';
|
|
50
|
+
|
|
51
|
+
const DEFAULT_STORE = 'agents/cost-tracking/sessions.jsonl';
|
|
52
|
+
|
|
53
|
+
// Mirrored from track.mjs PRICING. It is a copy rather than an import for the
|
|
54
|
+
// reason that file's own header gives: track.mjs exports nothing and calls
|
|
55
|
+
// main() at module scope, so importing it would scan the developer's real
|
|
56
|
+
// ~/.claude as a side effect. Keep the two in step.
|
|
57
|
+
const PRICING = {
|
|
58
|
+
haiku: { input: 0.25, output: 1.25, cache_write_5m: 0.30, cache_write_1h: 0.50, cache_read: 0.03 },
|
|
59
|
+
sonnet: { input: 3.00, output: 15.00, cache_write_5m: 3.75, cache_write_1h: 6.00, cache_read: 0.30 },
|
|
60
|
+
opus: { input: 15.00, output: 75.00, cache_write_5m: 18.75, cache_write_1h: 30.00, cache_read: 1.50 },
|
|
61
|
+
};
|
|
62
|
+
|
|
63
|
+
// The keys `costForModelEntry` actually reads. `cache_write_1h` is deliberately
|
|
64
|
+
// NOT here: the row does not retain the TTL split (honest limit 1), so a 1h
|
|
65
|
+
// rate cannot affect any output, and requiring a number that provably changes
|
|
66
|
+
// nothing is a usability wart rather than a safeguard. It is accepted when
|
|
67
|
+
// present — a tier alias carries one — and ignored. A future 1h-aware pass
|
|
68
|
+
// would need the producer to keep the split first, at which point this list is
|
|
69
|
+
// where the key goes back.
|
|
70
|
+
const RATE_KEYS = ['input', 'output', 'cache_write_5m', 'cache_read'];
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* A usable rate is finite and non-negative.
|
|
74
|
+
* `typeof x === 'number'` is NOT enough, and the gap is not theoretical: JSON
|
|
75
|
+
* parses `1e999` to `Infinity`, which prices to `null` in the written row;
|
|
76
|
+
* a negative rate writes a negative total. Both then clear `rate_missing`,
|
|
77
|
+
* which is worse than the silent zero 2.4 removed — the row now carries a
|
|
78
|
+
* confident wrong figure with its own warning switched off, and the token
|
|
79
|
+
* counts that made a re-price possible have already been overwritten.
|
|
80
|
+
*/
|
|
81
|
+
function isUsableRate(v) {
|
|
82
|
+
return typeof v === 'number' && Number.isFinite(v) && v >= 0;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* Resolve one rates-file entry to `{ tier, rates }`.
|
|
87
|
+
* A string is a tier alias; an object is an explicit table. Anything else, a
|
|
88
|
+
* table missing a key, an unusable value, or an all-zero table is rejected by
|
|
89
|
+
* name — a silently-defaulted rate would reintroduce the understatement this
|
|
90
|
+
* pass exists to remove.
|
|
91
|
+
*/
|
|
92
|
+
function resolveRate(model, spec) {
|
|
93
|
+
if (typeof spec === 'string') {
|
|
94
|
+
const rates = PRICING[spec];
|
|
95
|
+
if (!rates) throw new Error(`rates["${model}"]: unknown tier alias "${spec}" (known: ${Object.keys(PRICING).join(', ')})`);
|
|
96
|
+
return { tier: spec, rates };
|
|
97
|
+
}
|
|
98
|
+
if (spec && typeof spec === 'object') {
|
|
99
|
+
const missing = RATE_KEYS.filter((k) => spec[k] === undefined);
|
|
100
|
+
if (missing.length) throw new Error(`rates["${model}"]: missing ${missing.join(', ')}`);
|
|
101
|
+
const unusable = RATE_KEYS.filter((k) => !isUsableRate(spec[k]));
|
|
102
|
+
if (unusable.length) throw new Error(`rates["${model}"]: ${unusable.map((k) => `${k}=${JSON.stringify(spec[k])}`).join(', ')} — a rate must be a finite number >= 0`);
|
|
103
|
+
// An all-zero table is not a price, it is the absence of one, and no tier
|
|
104
|
+
// in PRICING is free. Accepting it would re-create the exact case the flag
|
|
105
|
+
// exists to distinguish: a $0 row that reads as cheap work.
|
|
106
|
+
if (RATE_KEYS.every((k) => spec[k] === 0)) throw new Error(`rates["${model}"]: an all-zero table is not a price — it re-creates the $0 row rate_missing exists to flag`);
|
|
107
|
+
return { tier: 'backfilled', rates: spec };
|
|
108
|
+
}
|
|
109
|
+
throw new Error(`rates["${model}"]: expected a tier alias or a rate table, got ${typeof spec}`);
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
/**
|
|
113
|
+
* Cost for one `byModel` entry at the supplied rates.
|
|
114
|
+
* Cache writes price at the 5m rate — see honest limit 1 in the header.
|
|
115
|
+
*/
|
|
116
|
+
function costForModelEntry(entry, rates) {
|
|
117
|
+
return (entry.input_tokens || 0) / 1e6 * rates.input
|
|
118
|
+
+ (entry.output_tokens || 0) / 1e6 * rates.output
|
|
119
|
+
+ (entry.cache_creation_input_tokens || 0) / 1e6 * rates.cache_write_5m
|
|
120
|
+
+ (entry.cache_read_input_tokens || 0) / 1e6 * rates.cache_read;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
/** Mirrors track.mjs's flag condition: a row with no billable tokens costs 0 at any rate. */
|
|
124
|
+
function billableTokens(entry) {
|
|
125
|
+
if (!entry) return 0;
|
|
126
|
+
return (entry.input_tokens || 0) + (entry.output_tokens || 0)
|
|
127
|
+
+ (entry.cache_creation_input_tokens || 0) + (entry.cache_read_input_tokens || 0);
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
/**
|
|
131
|
+
* Re-price one row in place. Returns the per-model repairs made, or [] when
|
|
132
|
+
* the row is not flagged, carries no supplied rate, or was already backfilled
|
|
133
|
+
* for that model — which is what makes a second run a no-op rather than a
|
|
134
|
+
* double count.
|
|
135
|
+
*/
|
|
136
|
+
export function backfillRow(row, rateTable) {
|
|
137
|
+
if (!row || row.rate_missing !== true) return [];
|
|
138
|
+
const already = new Set((row.rate_backfill || []).map((b) => b.model));
|
|
139
|
+
const repairs = [];
|
|
140
|
+
|
|
141
|
+
for (const model of row.rate_missing_models || []) {
|
|
142
|
+
if (already.has(model)) continue;
|
|
143
|
+
const spec = rateTable[model];
|
|
144
|
+
if (!spec) continue;
|
|
145
|
+
const entry = (row.byModel || {})[model];
|
|
146
|
+
if (!entry || billableTokens(entry) <= 0) continue;
|
|
147
|
+
|
|
148
|
+
const { tier, rates } = spec;
|
|
149
|
+
const cost = costForModelEntry(entry, rates);
|
|
150
|
+
const previous = entry.cost_usd || 0;
|
|
151
|
+
const delta = cost - previous;
|
|
152
|
+
|
|
153
|
+
entry.cost_usd = cost;
|
|
154
|
+
entry.tier = tier;
|
|
155
|
+
row.byTier = row.byTier || {};
|
|
156
|
+
row.byTier[tier] = (row.byTier[tier] || 0) + delta;
|
|
157
|
+
row.total_cost_usd = (row.total_cost_usd || 0) + delta;
|
|
158
|
+
|
|
159
|
+
repairs.push({ model, tier, cost_usd: cost, delta_usd: delta });
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
if (!repairs.length) return [];
|
|
163
|
+
|
|
164
|
+
row.rate_backfill = [
|
|
165
|
+
...(row.rate_backfill || []),
|
|
166
|
+
...repairs.map((r) => ({
|
|
167
|
+
model: r.model,
|
|
168
|
+
tier: r.tier,
|
|
169
|
+
cost_usd: r.cost_usd,
|
|
170
|
+
cache_ttl_assumed: '5m',
|
|
171
|
+
bucket_split_repaired: false,
|
|
172
|
+
at: new Date().toISOString(),
|
|
173
|
+
})),
|
|
174
|
+
];
|
|
175
|
+
|
|
176
|
+
// Whatever is still unpriced keeps the flag. A row re-priced for one model
|
|
177
|
+
// and not another is still understated, and saying otherwise is the exact
|
|
178
|
+
// silent zero 2.4 removed.
|
|
179
|
+
const remaining = (row.rate_missing_models || []).filter(
|
|
180
|
+
(m) => !row.rate_backfill.some((b) => b.model === m),
|
|
181
|
+
);
|
|
182
|
+
row.rate_missing_models = remaining;
|
|
183
|
+
row.rate_missing = remaining.length > 0;
|
|
184
|
+
|
|
185
|
+
return repairs;
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
function parseArgs(argv) {
|
|
189
|
+
const out = { rates: process.env.BACKFILL_RATES || null, store: process.env.BACKFILL_STORE || DEFAULT_STORE, apply: process.env.BACKFILL_APPLY === '1' };
|
|
190
|
+
for (let i = 0; i < argv.length; i++) {
|
|
191
|
+
const a = argv[i];
|
|
192
|
+
if (a === '--apply') out.apply = true;
|
|
193
|
+
else if (a === '--rates') out.rates = argv[++i];
|
|
194
|
+
else if (a === '--store') out.store = argv[++i];
|
|
195
|
+
else if (a.startsWith('--rates=')) out.rates = a.slice('--rates='.length);
|
|
196
|
+
else if (a.startsWith('--store=')) out.store = a.slice('--store='.length);
|
|
197
|
+
else throw new Error(`unknown argument: ${a}`);
|
|
198
|
+
}
|
|
199
|
+
return out;
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
function main() {
|
|
203
|
+
let args;
|
|
204
|
+
try {
|
|
205
|
+
args = parseArgs(process.argv.slice(2));
|
|
206
|
+
} catch (e) {
|
|
207
|
+
console.error(`cost-backfill: ${e.message}`);
|
|
208
|
+
process.exit(2);
|
|
209
|
+
}
|
|
210
|
+
if (!args.rates) {
|
|
211
|
+
console.error('cost-backfill: --rates <file.json> is required (or BACKFILL_RATES)');
|
|
212
|
+
process.exit(2);
|
|
213
|
+
}
|
|
214
|
+
if (!existsSync(args.rates)) {
|
|
215
|
+
console.error(`cost-backfill: rates file not found: ${args.rates}`);
|
|
216
|
+
process.exit(2);
|
|
217
|
+
}
|
|
218
|
+
if (!existsSync(args.store)) {
|
|
219
|
+
console.error(`cost-backfill: ledger not found: ${args.store}`);
|
|
220
|
+
process.exit(2);
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
let rateTable;
|
|
224
|
+
try {
|
|
225
|
+
const raw = JSON.parse(readFileSync(args.rates, 'utf-8'));
|
|
226
|
+
rateTable = Object.fromEntries(Object.entries(raw).map(([m, spec]) => [m, resolveRate(m, spec)]));
|
|
227
|
+
} catch (e) {
|
|
228
|
+
console.error(`cost-backfill: ${e.message}`);
|
|
229
|
+
process.exit(2);
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
const before = readFileSync(args.store, 'utf-8');
|
|
233
|
+
const lines = before.split('\n').filter((l) => l.trim().length > 0);
|
|
234
|
+
const rows = [];
|
|
235
|
+
const repaired = [];
|
|
236
|
+
let flaggedRows = 0;
|
|
237
|
+
|
|
238
|
+
for (const line of lines) {
|
|
239
|
+
let row;
|
|
240
|
+
try {
|
|
241
|
+
row = JSON.parse(line);
|
|
242
|
+
} catch {
|
|
243
|
+
// A malformed line is preserved verbatim rather than dropped: this pass
|
|
244
|
+
// repairs prices, it does not curate the ledger.
|
|
245
|
+
rows.push(line);
|
|
246
|
+
continue;
|
|
247
|
+
}
|
|
248
|
+
if (row.rate_missing === true) flaggedRows++;
|
|
249
|
+
const repairs = backfillRow(row, rateTable);
|
|
250
|
+
if (repairs.length) repaired.push({ sessionId: row.sessionId, repairs, stillMissing: row.rate_missing_models });
|
|
251
|
+
rows.push(JSON.stringify(row));
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
if (args.apply && repaired.length) {
|
|
255
|
+
// This is a read-modify-REWRITE against a store whose producer APPENDS
|
|
256
|
+
// (`track.mjs` uses appendFileSync). temp-then-rename makes the write
|
|
257
|
+
// atomic; it does not make the read-write pair safe — a `track.mjs` run
|
|
258
|
+
// landing in between would be silently discarded by the rename. There is
|
|
259
|
+
// no lock, so the honest move is to detect the collision and refuse rather
|
|
260
|
+
// than to overwrite: on an append-only cost ledger a lost row is silent
|
|
261
|
+
// data loss, and re-running the pass afterwards costs nothing.
|
|
262
|
+
if (readFileSync(args.store, 'utf-8') !== before) {
|
|
263
|
+
console.error('cost-backfill: the ledger changed while this pass was running — refusing to overwrite. Re-run it.');
|
|
264
|
+
process.exit(2);
|
|
265
|
+
}
|
|
266
|
+
const tmp = `${args.store}.backfill.tmp`;
|
|
267
|
+
writeFileSync(tmp, rows.join('\n') + '\n');
|
|
268
|
+
renameSync(tmp, args.store);
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
if (process.env.BACKFILL_QUIET === '1') return;
|
|
272
|
+
|
|
273
|
+
const total = repaired.reduce((s, r) => s + r.repairs.reduce((t, x) => t + x.delta_usd, 0), 0);
|
|
274
|
+
console.log(`# cost-backfill — ${args.apply ? 'APPLIED' : 'DRY RUN (pass --apply to write)'}`);
|
|
275
|
+
console.log('');
|
|
276
|
+
console.log(`Ledger: \`${args.store}\` · rows ${lines.length} · flagged ${flaggedRows} · re-priced ${repaired.length}`);
|
|
277
|
+
console.log('');
|
|
278
|
+
if (!repaired.length) {
|
|
279
|
+
console.log('No row was re-priced — either nothing is flagged, or no rate was supplied for the flagged ids.');
|
|
280
|
+
} else {
|
|
281
|
+
console.log('| Session | Model | Tier | Recovered | Still missing |');
|
|
282
|
+
console.log('|---|---|---|---:|---|');
|
|
283
|
+
for (const r of repaired) {
|
|
284
|
+
for (const x of r.repairs) {
|
|
285
|
+
console.log(`| \`${(r.sessionId || '').slice(0, 8)}\` | \`${x.model}\` | ${x.tier} | $${x.delta_usd.toFixed(6)} | ${r.stillMissing.length ? r.stillMissing.join(', ') : '—'} |`);
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
console.log('');
|
|
289
|
+
console.log(`**Recovered total: $${total.toFixed(6)}** — previously reported as $0.`);
|
|
290
|
+
}
|
|
291
|
+
console.log('');
|
|
292
|
+
console.log('Two limits of the recovered figure, both properties of the stored row:');
|
|
293
|
+
console.log('- Cache writes price at the **5m** rate; the row does not retain the 5m/1h TTL split, so a session that used 1h writes stays under-priced by the difference.');
|
|
294
|
+
console.log('- `byBucket[*].cost_usd` is **not** repaired — the row carries no per-bucket-per-model tokens, so the recovered cost cannot be attributed to main vs subagent.');
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
// Importable for tests; runs only when invoked directly, unlike track.mjs.
|
|
298
|
+
if (process.argv[1] && process.argv[1].endsWith('backfill_rates.mjs')) main();
|
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
// Env: BUDGET_STORE, BUDGET_CONFIG, BUDGET_PERIOD={today|week|month|all}, BUDGET_QUIET=1
|
|
11
11
|
|
|
12
12
|
import { readFileSync, writeFileSync, existsSync, mkdirSync } from 'node:fs';
|
|
13
|
-
import { dirname
|
|
13
|
+
import { dirname } from 'node:path';
|
|
14
14
|
|
|
15
15
|
const STORE = process.env.BUDGET_STORE || 'agents/cost-tracking/sessions.jsonl';
|
|
16
16
|
const CONFIG = process.env.BUDGET_CONFIG || 'agents/cost-tracking/budget.json';
|
|
@@ -200,20 +200,16 @@ function cmdCheck() {
|
|
|
200
200
|
|
|
201
201
|
// --- Per-tier budget state (docs/contracts/budget-routing.md) -------------
|
|
202
202
|
// v1 window for per-tier ceilings: rolling 24h for SPEND (ledger entries
|
|
203
|
-
// tagged `tier:`).
|
|
204
|
-
//
|
|
205
|
-
//
|
|
206
|
-
//
|
|
207
|
-
//
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
if (Number.isFinite(raw.reserve_ttl_ms)) return raw.reserve_ttl_ms;
|
|
214
|
-
} catch { /* fall through to the safe default below */ }
|
|
215
|
-
return 600000;
|
|
216
|
-
}
|
|
203
|
+
// tagged `tier:`).
|
|
204
|
+
//
|
|
205
|
+
// The pending-RESERVE half was removed 2026-08-16 with the permit lifecycle it
|
|
206
|
+
// belonged to (converged council verdict, anthropic + openai 2 of 2; migration
|
|
207
|
+
// record in the contract above). This reader summed `tier-reserves.jsonl`, whose
|
|
208
|
+
// only writer was `acquireBudgetPermit` — a function with no production caller —
|
|
209
|
+
// so `reserved_usd` was provably always 0 here, and a reader could not tell that
|
|
210
|
+
// zero from "nothing is currently reserved". The shared-TTL config it loaded
|
|
211
|
+
// (src/config/budget-routing.json) existed solely to keep the two reserve
|
|
212
|
+
// readers in step and went with them.
|
|
217
213
|
|
|
218
214
|
function loadPerTierCeilings() {
|
|
219
215
|
const out = { cheap: null, medium: null, strong: null };
|
|
@@ -245,29 +241,13 @@ function cmdTier(rest) {
|
|
|
245
241
|
const spent = loadSessions()
|
|
246
242
|
.filter((s) => s.tier === tier && inWindow(s.capturedAt || s.endedAt || s.ts))
|
|
247
243
|
.reduce((acc, s) => acc + (Number(s.total_cost_usd ?? s.cost_usd ?? 0) || 0), 0);
|
|
248
|
-
let reserved = 0;
|
|
249
|
-
const reserveTtl = loadReserveTtlMs();
|
|
250
|
-
const reservePath = join(dirname(STORE), 'tier-reserves.jsonl');
|
|
251
|
-
if (existsSync(reservePath)) {
|
|
252
|
-
for (const line of readFileSync(reservePath, 'utf-8').split('\n')) {
|
|
253
|
-
if (!line.trim()) continue;
|
|
254
|
-
try {
|
|
255
|
-
const e = JSON.parse(line);
|
|
256
|
-
if (e.tier === tier && e.status === 'pending' && (now - e.ts_ms) < reserveTtl) {
|
|
257
|
-
reserved += Number(e.est_usd || 0) || 0;
|
|
258
|
-
}
|
|
259
|
-
} catch { /* skip malformed line */ }
|
|
260
|
-
}
|
|
261
|
-
}
|
|
262
244
|
const ceiling = loadPerTierCeilings()[tier];
|
|
263
|
-
const available = ceiling === null ? true : spent
|
|
245
|
+
const available = ceiling === null ? true : spent < ceiling;
|
|
264
246
|
console.log(JSON.stringify({
|
|
265
247
|
tier,
|
|
266
248
|
window: 'rolling-24h',
|
|
267
|
-
reserve_ttl_ms: reserveTtl,
|
|
268
249
|
ceiling_usd: ceiling,
|
|
269
250
|
spent_usd: spent,
|
|
270
|
-
reserved_usd: reserved,
|
|
271
251
|
available,
|
|
272
252
|
}));
|
|
273
253
|
}
|
|
@@ -167,6 +167,15 @@ export function aggregate(rows: Row[]): Json {
|
|
|
167
167
|
const by_model = new Map<string, ModelBucket>();
|
|
168
168
|
const by_date = new Map<string, KvBucket>();
|
|
169
169
|
const rate_missing_models = new Set<string>();
|
|
170
|
+
// Re-priced rows, counted at the totals layer only — deliberately NOT
|
|
171
|
+
// per grouping. `rate_missing_sessions` propagates into every grouping
|
|
172
|
+
// because a flagged row makes THAT grouping's total a floor. A backfilled
|
|
173
|
+
// row is the opposite: it no longer understates anything, so nothing about
|
|
174
|
+
// a slice changes. What changes is the provenance of the summary as a
|
|
175
|
+
// whole — part of it is priced at operator-supplied rates rather than
|
|
176
|
+
// measured — and that is a totals-level fact.
|
|
177
|
+
const rate_backfilled_models = new Set<string>();
|
|
178
|
+
let rate_backfilled_sessions = 0;
|
|
170
179
|
const totals = _zero_kv();
|
|
171
180
|
|
|
172
181
|
for (const row of rows) {
|
|
@@ -185,6 +194,16 @@ export function aggregate(rows: Row[]): Json {
|
|
|
185
194
|
for (const id of _strList(row['rate_missing_models'])) {
|
|
186
195
|
rate_missing_models.add(id);
|
|
187
196
|
}
|
|
197
|
+
const backfill = row['rate_backfill'];
|
|
198
|
+
if (Array.isArray(backfill) && backfill.length > 0) {
|
|
199
|
+
rate_backfilled_sessions += 1;
|
|
200
|
+
for (const entry of backfill) {
|
|
201
|
+
if (entry && typeof entry === 'object') {
|
|
202
|
+
const id = _str((entry as Record<string, unknown>)['model'] ?? '');
|
|
203
|
+
if (id) rate_backfilled_models.add(id);
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
}
|
|
188
207
|
|
|
189
208
|
const sessBucket = _getOr(by_sess, sid, _zero_kv);
|
|
190
209
|
const convBucket = _getOr(by_conv, cid, _zero_kv);
|
|
@@ -231,6 +250,12 @@ export function aggregate(rows: Row[]): Json {
|
|
|
231
250
|
// aggregated past: a non-zero count means total_cost_usd is a floor.
|
|
232
251
|
rate_missing_sessions: totals.rate_missing_sessions,
|
|
233
252
|
rate_missing_models: [...rate_missing_models].sort(),
|
|
253
|
+
// The re-priced qualification. Without it, `rate_missing: false`
|
|
254
|
+
// on a repaired row and on a row that was never flagged aggregate
|
|
255
|
+
// identically, so a total carrying operator-supplied estimates
|
|
256
|
+
// reads as fully measured.
|
|
257
|
+
rate_backfilled_sessions,
|
|
258
|
+
rate_backfilled_models: [...rate_backfilled_models].sort(),
|
|
234
259
|
},
|
|
235
260
|
by_session: _sortedEntries(by_sess).map(([k, v]) => ({
|
|
236
261
|
key: k,
|
|
@@ -222,13 +222,23 @@ export const REVIEW_SCOPE_NAME_ONLY_FLAGS: readonly string[] = [
|
|
|
222
222
|
'-O/dev/null',
|
|
223
223
|
];
|
|
224
224
|
|
|
225
|
-
/**
|
|
226
|
-
|
|
225
|
+
/**
|
|
226
|
+
* `git diff` argv for the review scope (patch body), config-pinned.
|
|
227
|
+
*
|
|
228
|
+
* `head` defaults to `HEAD` — the only value the dispatcher and the validator
|
|
229
|
+
* ever pass, since both bind the scope being dispatched *now*. It is a
|
|
230
|
+
* parameter so that an out-of-band measurement can re-derive the scope at a
|
|
231
|
+
* HISTORICAL revision through this same definition instead of assembling a
|
|
232
|
+
* second `git diff` argv beside it: a private copy would drift from the
|
|
233
|
+
* normative flag list above the first time a flag changes, and then report a
|
|
234
|
+
* hash no gate would ever produce.
|
|
235
|
+
*/
|
|
236
|
+
export function reviewScopeDiffArgs(base: string, head = 'HEAD'): string[] {
|
|
227
237
|
return [
|
|
228
238
|
...REVIEW_SCOPE_GIT_CONFIG,
|
|
229
239
|
'diff',
|
|
230
240
|
...REVIEW_SCOPE_DIFF_FLAGS,
|
|
231
|
-
`${base}
|
|
241
|
+
`${base}...${head}`,
|
|
232
242
|
'--',
|
|
233
243
|
':/',
|
|
234
244
|
...REVIEW_SCOPE_EXCLUDES,
|
|
@@ -236,12 +246,12 @@ export function reviewScopeDiffArgs(base: string): string[] {
|
|
|
236
246
|
}
|
|
237
247
|
|
|
238
248
|
/** `git diff --name-only` argv for the review scope (changed-file list), config-pinned. */
|
|
239
|
-
export function reviewScopeNameOnlyArgs(base: string): string[] {
|
|
249
|
+
export function reviewScopeNameOnlyArgs(base: string, head = 'HEAD'): string[] {
|
|
240
250
|
return [
|
|
241
251
|
...REVIEW_SCOPE_GIT_CONFIG,
|
|
242
252
|
'diff',
|
|
243
253
|
...REVIEW_SCOPE_NAME_ONLY_FLAGS,
|
|
244
|
-
`${base}
|
|
254
|
+
`${base}...${head}`,
|
|
245
255
|
'--',
|
|
246
256
|
':/',
|
|
247
257
|
...REVIEW_SCOPE_EXCLUDES,
|
|
@@ -29,6 +29,8 @@ import * as path from 'node:path';
|
|
|
29
29
|
import process from 'node:process';
|
|
30
30
|
import { fileURLToPath, pathToFileURL } from 'node:url';
|
|
31
31
|
|
|
32
|
+
import { workspaceIdentity } from './_lib/git_common_dir.js';
|
|
33
|
+
|
|
32
34
|
// argparse `prog` is hardcoded to "evidence_report.py" in the retired Python implementation,
|
|
33
35
|
// so usage strings stay byte-identical regardless of the `.py`/`.ts` filename.
|
|
34
36
|
const PROG = 'evidence_report.py';
|
|
@@ -39,15 +41,24 @@ const DESCRIPTION = 'Evidence Report template automation.';
|
|
|
39
41
|
// Repo-root resolution
|
|
40
42
|
// ---------------------------------------------------------------------------
|
|
41
43
|
|
|
42
|
-
/**
|
|
44
|
+
/**
|
|
45
|
+
* Return repo root, falling back to the relative-path heuristic.
|
|
46
|
+
*
|
|
47
|
+
* Census row R4. The previous implementation spawned
|
|
48
|
+
* `git rev-parse --show-toplevel` with **no `cwd` and no env scrub**, so an
|
|
49
|
+
* inherited `GIT_DIR` (every git hook exports one) silently answered about the
|
|
50
|
+
* hook's repository instead of this one. `workspaceIdentity()` reads files, so
|
|
51
|
+
* there is no child process to redirect. The fallback is unchanged, and in a
|
|
52
|
+
* plain invocation both forms return the same path.
|
|
53
|
+
*/
|
|
43
54
|
function _repo_root(): string {
|
|
44
|
-
const
|
|
45
|
-
if (
|
|
55
|
+
const root = workspaceIdentity().repoRoot;
|
|
56
|
+
if (!root.resolved) {
|
|
46
57
|
// Script lives at src/scripts/; repo root is two levels up.
|
|
47
58
|
const here = fileURLToPath(import.meta.url);
|
|
48
59
|
return path.resolve(path.dirname(here), '..', '..');
|
|
49
60
|
}
|
|
50
|
-
return
|
|
61
|
+
return root.value;
|
|
51
62
|
}
|
|
52
63
|
|
|
53
64
|
function _git_head_short(): string {
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
"_README": "Denylist for check_no_external_sources.py — blocks readable inspiration/harvest/comparison source names from re-entering the tracked tree. See rule source-confidentiality + the 2026-06-13 source-confidentiality sweep. Recommending an integrated tool is allowed; recording that we copied/derived/were-inspired-by an external source is not. To intentionally retain a source link, encrypt it via src/scripts/_lib/link_crypto.py.",
|
|
3
3
|
"deny": [
|
|
4
4
|
"kdcllc",
|
|
5
|
+
"nidhinjs",
|
|
5
6
|
"\\bmicrock\\b",
|
|
6
7
|
"obra/superpowers",
|
|
7
8
|
"composiohq",
|
|
@@ -434,6 +434,28 @@ concerns:
|
|
|
434
434
|
args: []
|
|
435
435
|
fail_closed: false
|
|
436
436
|
severity: advisory
|
|
437
|
+
# road-to-inbox-harvest-2026-08-d-runtime-skill-routing Phase 2 — binds the
|
|
438
|
+
# deterministic task→skill ranker, which had shipped with ZERO production
|
|
439
|
+
# callers: `score_skill_relevance.rank` and its `suggest_skill_for_task`
|
|
440
|
+
# wrapper were reachable only from their CLIs and from `routing:doctor`.
|
|
441
|
+
# Ranks the SUBMITTED prompt over the installed skill catalogue and injects
|
|
442
|
+
# at most one line naming the top-3 skill ids and their scores — POINTERS,
|
|
443
|
+
# never bodies, never a load instruction. Same delivery as `delegation-nudge`
|
|
444
|
+
# above: `{decision:"warn", reason, additional_context}` at exit 2, reduced
|
|
445
|
+
# to a real exit of 0 by `host_semantics.emitFor`'s warn branch, so it never
|
|
446
|
+
# blocks a turn.
|
|
447
|
+
#
|
|
448
|
+
# Silence is the default and its floor is MEASURED, not chosen: `rank`
|
|
449
|
+
# returns something on nearly every real prompt, so the trigger is a top-1
|
|
450
|
+
# score at or above 30/100 — the p90 of the routing-matrix corpus (496 prompt
|
|
451
|
+
# lines; median 18, p75 28, max 70), firing on 13.9% of them. A median floor
|
|
452
|
+
# would speak on half of all turns, which is the per-turn-reminder shape this
|
|
453
|
+
# estate has already measured failing. fail_closed: false.
|
|
454
|
+
skill-route:
|
|
455
|
+
script: src/scripts/hooks/skill_route_hook.ts
|
|
456
|
+
args: []
|
|
457
|
+
fail_closed: false
|
|
458
|
+
severity: advisory
|
|
437
459
|
# road-to-orchestrator-discipline-carriers Phase 5 (F4-lite) — end-review
|
|
438
460
|
# nudge. Fires ONLY when the session mutated tracked+untracked, non-doc
|
|
439
461
|
# files beyond MUTATION_LINE_THRESHOLD (50 lines; untracked files added
|
|
@@ -556,6 +578,30 @@ concerns:
|
|
|
556
578
|
args: []
|
|
557
579
|
fail_closed: false
|
|
558
580
|
severity: advisory
|
|
581
|
+
# road-to-inbox-harvest-2026-08-d-context-ledger Phase 1 Step 1.1 — the
|
|
582
|
+
# tool-result byte counter. `hook-token-budget.json` registers the gap it
|
|
583
|
+
# closes in its own words: bytes-into-context from tool RESULTS are
|
|
584
|
+
# instrumented nowhere, so every context-cost claim in this tree is made
|
|
585
|
+
# against a denominator missing its dominant term.
|
|
586
|
+
#
|
|
587
|
+
# CAPTURE ONLY. It appends `{ts, tool, bytes, measurable}` to a gitignored
|
|
588
|
+
# census file and emits NOTHING to the model — zero injection bytes, which
|
|
589
|
+
# is why it carries a metric registration rather than a per-concern byte
|
|
590
|
+
# cap. The threshold question (when is a result too large) is deliberately
|
|
591
|
+
# unanswered: the distribution has never been observed, and the house rule
|
|
592
|
+
# is baseline before mechanism.
|
|
593
|
+
#
|
|
594
|
+
# The record type has four fields and none can hold free text — a tool
|
|
595
|
+
# NAME, a byte COUNT, a timestamp and a measurable flag. Tool results
|
|
596
|
+
# routinely carry file contents and command output; none of it can reach
|
|
597
|
+
# this output because the output has no field able to carry it.
|
|
598
|
+
#
|
|
599
|
+
# Never blocks, never warns: exit 0 on every path. fail_closed: false.
|
|
600
|
+
tool-result-bytes:
|
|
601
|
+
script: src/scripts/hooks/tool_result_bytes_hook.ts
|
|
602
|
+
args: []
|
|
603
|
+
fail_closed: false
|
|
604
|
+
severity: advisory
|
|
559
605
|
# road-to-subagent-lifecycle-integrity Phase 3 Step 1 — spawn guard, SHADOW
|
|
560
606
|
# ONLY, per docs/contracts/concern-activation-policy.md. On every Agent/Task
|
|
561
607
|
# call it appends one `spawn_guard_shadow` line recording what a guard WOULD
|
|
@@ -615,7 +661,11 @@ roles:
|
|
|
615
661
|
# Orchestrator-only concerns (Phase 2.3): a worker never talks to the
|
|
616
662
|
# user, never dispatches sub-workers, never runs the council, and its
|
|
617
663
|
# complaints route via the orchestrator.
|
|
618
|
-
|
|
664
|
+
# `skill-route` joins the drop list for the same reason `delegation-nudge`
|
|
665
|
+
# is on it: a worker was handed a bounded slice by an orchestrator that
|
|
666
|
+
# already chose the approach, so a routing pointer there advises nobody who
|
|
667
|
+
# can act on it and costs the dispatch anyway.
|
|
668
|
+
drop: [delegation-nudge, skill-route, end-review-nudge, council-availability, team-review-gate, self-repair, session-eol]
|
|
619
669
|
|
|
620
670
|
platforms:
|
|
621
671
|
augment:
|
|
@@ -623,22 +673,32 @@ platforms:
|
|
|
623
673
|
session_end: [chat-history, memory-learn, session-register]
|
|
624
674
|
stop: [chat-history, hot-context, verify-before-complete, self-repair, session-register]
|
|
625
675
|
pre_tool_use: [block-no-verify, block-unauthorized-git, evidence-independence, block-kernel-rule-writes, block-config-weakening, rtk-wrap, design-slop, ui-route-nudge, code-graph-nudge, reread-guard]
|
|
626
|
-
post_tool_use: [chat-history, roadmap-progress, context-hygiene, verify-before-complete, minimal-safe-diff, injection-scan, pr-url-reminder, orchestration-record, edit-shape]
|
|
676
|
+
post_tool_use: [chat-history, roadmap-progress, context-hygiene, verify-before-complete, minimal-safe-diff, injection-scan, pr-url-reminder, orchestration-record, tool-result-bytes, edit-shape]
|
|
627
677
|
|
|
628
678
|
claude:
|
|
629
679
|
session_start: [chat-history, hot-context, handoff-context, session-canary, council-availability, first-run-gate, onboarding-gate, verify-before-complete, minimal-safe-diff, profile-staleness, wrapper-freshness, surface-probe, session-register]
|
|
630
680
|
session_end: [chat-history, memory-learn, session-register]
|
|
631
681
|
stop: [chat-history, hot-context, verify-before-complete, team-review-gate, end-review-nudge, turn-end-gate, self-repair, session-register, session-eol]
|
|
632
|
-
user_prompt_submit: [chat-history, verify-before-complete, minimal-safe-diff, language-mirror, delegation-nudge, git-authorization, session-canary, self-repair, session-register]
|
|
682
|
+
user_prompt_submit: [chat-history, verify-before-complete, minimal-safe-diff, language-mirror, delegation-nudge, skill-route, git-authorization, session-canary, self-repair, session-register]
|
|
633
683
|
pre_tool_use: [block-no-verify, block-unauthorized-git, evidence-independence, block-kernel-rule-writes, block-config-weakening, rtk-wrap, design-slop, ui-route-nudge, code-graph-nudge, reread-guard, spawn-guard-shadow]
|
|
634
|
-
post_tool_use: [chat-history, roadmap-progress, context-hygiene, verify-before-complete, minimal-safe-diff, injection-scan, pr-url-reminder, orchestration-record, edit-shape, language-mirror]
|
|
635
|
-
# round-5 § 6.1
|
|
636
|
-
#
|
|
637
|
-
#
|
|
638
|
-
#
|
|
639
|
-
#
|
|
640
|
-
#
|
|
641
|
-
|
|
684
|
+
post_tool_use: [chat-history, roadmap-progress, context-hygiene, verify-before-complete, minimal-safe-diff, injection-scan, pr-url-reminder, orchestration-record, tool-result-bytes, edit-shape, language-mirror]
|
|
685
|
+
# `language-mirror` — round-5 § 6.1. `pre_compact` sets a pin-lost marker;
|
|
686
|
+
# the `post_tool_use` entry above re-emits the pin exactly once while that
|
|
687
|
+
# marker is set, then clears it. One extra injection per compaction EVENT —
|
|
688
|
+
# never one per tool call, which is the shape § 6.2 refuses. claude only: it
|
|
689
|
+
# is the sole verified platform, and the measured compaction defect is a
|
|
690
|
+
# Claude Code transcript fact. (It was the ONLY binding on this slot until
|
|
691
|
+
# the entry below; that sentence is retired rather than left to rot.)
|
|
692
|
+
#
|
|
693
|
+
# `hot-context` — road-to-inbox-harvest-2026-08-d-context-ledger Step 2.1.
|
|
694
|
+
# This is the one slot that fires WHILE state is being destroyed, and it
|
|
695
|
+
# carried no WRITER at all: the cache a post-compaction `session_start`
|
|
696
|
+
# restores was written at the last `stop`, so everything the session did
|
|
697
|
+
# between that stop and the compaction was lost with it. The concern is
|
|
698
|
+
# already bound on `stop` and `session_start`; this adds the write at the
|
|
699
|
+
# moment the loss actually happens. Deterministic rebuild, emits nothing
|
|
700
|
+
# here — the 4,096-byte row it carries is for its session_start restore.
|
|
701
|
+
pre_compact: [language-mirror, hot-context]
|
|
642
702
|
# road-to-subagent-lifecycle-integrity Phase 1 Step 1. `claude` is the only
|
|
643
703
|
# platform where both native events were measured present on the installed
|
|
644
704
|
# binary (2.1.229 — see the evidence page cited on the alias rows below).
|
|
@@ -675,7 +735,7 @@ platforms:
|
|
|
675
735
|
stop: [chat-history, hot-context, verify-before-complete, self-repair, session-register]
|
|
676
736
|
user_prompt_submit: [chat-history, verify-before-complete, minimal-safe-diff, language-mirror, git-authorization, session-canary, self-repair, session-register]
|
|
677
737
|
pre_tool_use: [block-no-verify, block-unauthorized-git, evidence-independence, block-kernel-rule-writes, block-config-weakening, rtk-wrap, design-slop, ui-route-nudge, code-graph-nudge, reread-guard, spawn-guard-shadow]
|
|
678
|
-
post_tool_use: [chat-history, roadmap-progress, context-hygiene, verify-before-complete, minimal-safe-diff, injection-scan, pr-url-reminder, orchestration-record, edit-shape]
|
|
738
|
+
post_tool_use: [chat-history, roadmap-progress, context-hygiene, verify-before-complete, minimal-safe-diff, injection-scan, pr-url-reminder, orchestration-record, tool-result-bytes, edit-shape]
|
|
679
739
|
# subagent-ledger IS bound here, unlike the two nudges above, and the
|
|
680
740
|
# difference is the delivery channel rather than the platform: the nudges
|
|
681
741
|
# depend on stdout reaching the model, which this trampoline discards by
|
|
@@ -698,7 +758,7 @@ platforms:
|
|
|
698
758
|
session_end: [chat-history, memory-learn, session-register]
|
|
699
759
|
stop: [chat-history, hot-context, verify-before-complete, self-repair, session-register]
|
|
700
760
|
user_prompt_submit: [chat-history, verify-before-complete, minimal-safe-diff, language-mirror, git-authorization, session-canary, self-repair, session-register]
|
|
701
|
-
post_tool_use: [chat-history, roadmap-progress, context-hygiene, verify-before-complete, minimal-safe-diff, injection-scan, pr-url-reminder, orchestration-record, edit-shape]
|
|
761
|
+
post_tool_use: [chat-history, roadmap-progress, context-hygiene, verify-before-complete, minimal-safe-diff, injection-scan, pr-url-reminder, orchestration-record, tool-result-bytes, edit-shape]
|
|
702
762
|
|
|
703
763
|
# Phase 7.6 — Cline. Hooks live under `.clinerules/hooks/<HookName>`
|
|
704
764
|
# (project, no file extension, must be executable per Cline docs) or
|
|
@@ -713,7 +773,7 @@ platforms:
|
|
|
713
773
|
session_end: [chat-history, memory-learn, session-register]
|
|
714
774
|
stop: [chat-history, hot-context, verify-before-complete, self-repair, session-register]
|
|
715
775
|
user_prompt_submit: [chat-history, verify-before-complete, minimal-safe-diff, language-mirror, git-authorization, session-canary, self-repair, session-register]
|
|
716
|
-
post_tool_use: [chat-history, roadmap-progress, context-hygiene, verify-before-complete, minimal-safe-diff, injection-scan, pr-url-reminder, orchestration-record, edit-shape]
|
|
776
|
+
post_tool_use: [chat-history, roadmap-progress, context-hygiene, verify-before-complete, minimal-safe-diff, injection-scan, pr-url-reminder, orchestration-record, tool-result-bytes, edit-shape]
|
|
717
777
|
|
|
718
778
|
# Phase 7.7 — Windsurf (Cascade). Hooks live at `.windsurf/hooks.json`
|
|
719
779
|
# (project) or `~/.codeium/windsurf/hooks.json` (user). Cascade has
|
|
@@ -749,7 +809,7 @@ platforms:
|
|
|
749
809
|
session_end: [chat-history, memory-learn, session-register]
|
|
750
810
|
stop: [chat-history, hot-context, verify-before-complete, self-repair, session-register]
|
|
751
811
|
user_prompt_submit: [chat-history, verify-before-complete, minimal-safe-diff, language-mirror, git-authorization, session-canary, self-repair, session-register]
|
|
752
|
-
post_tool_use: [chat-history, roadmap-progress, context-hygiene, verify-before-complete, minimal-safe-diff, injection-scan, pr-url-reminder, orchestration-record, edit-shape]
|
|
812
|
+
post_tool_use: [chat-history, roadmap-progress, context-hygiene, verify-before-complete, minimal-safe-diff, injection-scan, pr-url-reminder, orchestration-record, tool-result-bytes, edit-shape]
|
|
753
813
|
|
|
754
814
|
# Phase 7.9 — Copilot has no hook surface. Concerns route through
|
|
755
815
|
# rule-only fallback; the dispatcher silently no-ops on --platform copilot.
|