@event4u/agent-config 12.1.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.
Files changed (105) hide show
  1. package/.claude-plugin/marketplace.json +1 -1
  2. package/CHANGELOG.md +151 -0
  3. package/README.md +1 -1
  4. package/dist/agent-src/commands/cost/report.md +23 -0
  5. package/dist/agent-src/contexts/execution/subagent-routing.md +148 -33
  6. package/dist/agent-src/contexts/subagent-configuration.md +69 -7
  7. package/dist/agent-src/rules/autonomous-execution.md +1 -1
  8. package/dist/agent-src/rules/design-fidelity.md +1 -1
  9. package/dist/agent-src/rules/design-review-after-ui-write.md +10 -5
  10. package/dist/agent-src/rules/evaluator-independence.md +27 -5
  11. package/dist/agent-src/rules/git-history-discipline.md +5 -1
  12. package/dist/agent-src/rules/missing-skill-recovery.md +89 -0
  13. package/dist/agent-src/rules/ui-audit-gate.md +9 -4
  14. package/dist/agent-src/scripts/archive_completed_roadmaps.ts +33 -0
  15. package/dist/agent-src/scripts/update_roadmap_progress.ts +17 -2
  16. package/dist/agent-src/skills/authz-review/SKILL.md +4 -0
  17. package/dist/agent-src/skills/design-system-capture/SKILL.md +4 -0
  18. package/dist/agent-src/skills/design-system-capture/references/snapshot-preference-order.md +77 -0
  19. package/dist/agent-src/skills/merge-conflicts/SKILL.md +4 -0
  20. package/dist/agent-src/skills/prompt-optimizer/SKILL.md +17 -1
  21. package/dist/agent-src/skills/roadmap-writing/SKILL.md +4 -4
  22. package/dist/agent-src/skills/systematic-debugging/SKILL.md +4 -0
  23. package/dist/agent-src/skills/threat-modeling/SKILL.md +3 -0
  24. package/dist/agent-src/templates/agents/agent-project-settings.example.yml +1 -1
  25. package/dist/cli-delegate/{chunk-O52B4F5S.js → chunk-3B72LSZL.js} +1 -1
  26. package/dist/cli-delegate/{chunk-55WZN5VF.js → chunk-EVTY7ISR.js} +2 -2
  27. package/dist/cli-delegate/{chunk-K34QTSD5.js → chunk-N7FEST6D.js} +17 -17
  28. package/dist/cli-delegate/{chunk-LKZ5DOYW.js → chunk-XURJ4AQC.js} +11 -3
  29. package/dist/cli-delegate/cmd_conformance.js +2 -2
  30. package/dist/cli-delegate/cmd_doctor.js +2 -2
  31. package/dist/cli-delegate/cmd_export.js +2 -2
  32. package/dist/cli-delegate/cmd_handoff.js +1 -1
  33. package/dist/cli-delegate/cmd_prune.js +2 -2
  34. package/dist/cli-delegate/cmd_settings_migrate.js +2 -2
  35. package/dist/cli-delegate/cmd_settings_set.js +1 -1
  36. package/dist/cli-delegate/cmd_sync.js +2 -2
  37. package/dist/cli-delegate/cmd_uninstall.js +2 -2
  38. package/dist/cli-delegate/cmd_validate.js +2 -2
  39. package/dist/discovery/deprecation-report.md +1 -1
  40. package/dist/discovery/discovery-manifest.json +43 -22
  41. package/dist/discovery/discovery-manifest.json.sha256 +1 -1
  42. package/dist/discovery/discovery-manifest.summary.md +3 -3
  43. package/dist/discovery/dormancy-report.md +1 -1
  44. package/dist/discovery/orphan-report.md +1 -1
  45. package/dist/discovery/packs.json +6 -5
  46. package/dist/discovery/trust-report.md +2 -2
  47. package/dist/discovery/workspaces.json +5 -4
  48. package/dist/hooks/dispatch.js +936 -346
  49. package/dist/install/install.mjs +12 -4
  50. package/dist/mcp/registry-manifest.json +3 -3
  51. package/dist/mcp/server.json +2 -2
  52. package/dist/mcp/server.mjs +1192 -160
  53. package/dist/router.json +1 -1
  54. package/dist/server/schemas/settings.js +2 -2
  55. package/dist/server/schemas/settings.js.map +1 -1
  56. package/docs/guidelines/design-fidelity-mechanics.md +21 -0
  57. package/docs/guidelines/design-handover-extraction.md +87 -0
  58. package/docs/guidelines/prompt-templates.md +24 -1
  59. package/package.json +1 -1
  60. package/src/agent-src/scripts/archive_completed_roadmaps.ts +33 -0
  61. package/src/agent-src/scripts/update_roadmap_progress.ts +17 -2
  62. package/src/config/evaluator-budgets.json +5 -4
  63. package/src/config/gate-coverage.yml +33 -11
  64. package/src/config/gate-violation-baselines.json +9 -3
  65. package/src/config/hook-token-budget.json +8 -1
  66. package/src/scripts/_lib/bench_ab_complexity.ts +439 -0
  67. package/src/scripts/_lib/bench_ab_safety_tier.ts +142 -0
  68. package/src/scripts/_lib/bench_ab_scoring_v2.ts +28 -3
  69. package/src/scripts/_lib/bench_ab_search_adherence.ts +423 -0
  70. package/src/scripts/_lib/bench_ab_size_claim.ts +206 -0
  71. package/src/scripts/_lib/bench_ab_workspace.ts +111 -0
  72. package/src/scripts/_lib/skill_catalogue.ts +258 -5
  73. package/src/scripts/_lib/tier_budget_routing.ts +31 -331
  74. package/src/scripts/ai_council/clients.ts +21 -21
  75. package/src/scripts/bench_ab_v2_complexity.ts +252 -0
  76. package/src/scripts/bench_ab_v2_run.ts +3 -42
  77. package/src/scripts/bench_ab_v2_safety.ts +296 -0
  78. package/src/scripts/bench_ab_v2_search.ts +343 -0
  79. package/src/scripts/bench_ab_v2_stats.ts +191 -0
  80. package/src/scripts/build_archive_index.ts +383 -0
  81. package/src/scripts/capture_skill_catalogue.ts +107 -2
  82. package/src/scripts/check_source_size_budget.ts +428 -0
  83. package/src/scripts/check_suppression_hygiene.ts +5 -1
  84. package/src/scripts/cost/backfill_rates.mjs +298 -0
  85. package/src/scripts/cost/budget.mjs +12 -32
  86. package/src/scripts/cost_summary.ts +25 -0
  87. package/src/scripts/external_sources_denylist.json +1 -0
  88. package/src/scripts/hook_manifest.yaml +28 -2
  89. package/src/scripts/hooks/concern_registry.ts +2 -0
  90. package/src/scripts/hooks/skill_route_hook.ts +226 -0
  91. package/src/scripts/hooks/ui_route_nudge_hook.ts +6 -3
  92. package/src/scripts/lint_design_antipattern_parity.ts +40 -6
  93. package/src/scripts/lint_harvest_provenance.ts +35 -1
  94. package/src/scripts/lint_rule_skill_pack_reach.ts +43 -0
  95. package/src/scripts/lint_skill_router_head.ts +21 -0
  96. package/src/scripts/mcp_server/consumer_tool_catalog.json +26 -3
  97. package/src/scripts/mcp_server/tool_catalog_source.ts +6 -24
  98. package/src/scripts/mcp_server/tools.ts +87 -0
  99. package/src/scripts/release.ts +5 -5
  100. package/src/scripts/routing_doctor.ts +50 -13
  101. package/src/scripts/schemas/command.schema.json +1 -1
  102. package/src/scripts/schemas/skill.schema.json +18 -1
  103. package/src/scripts/trigger_coverage.ts +224 -3
  104. package/src/server/schemas/settings.ts +2 -2
  105. package/src/config/budget-routing.json +0 -7
@@ -1,351 +1,51 @@
1
1
  /**
2
- * Budget-aware tier routing (road-to-tested-routing Phase 7).
2
+ * Tier vocabulary + cool-down state.
3
3
  *
4
- * Deterministic decision layer for `docs/contracts/budget-routing.md`:
5
- * pick the cheapest CLASSIFIER-ADEQUATE tier that has available budget;
6
- * cheap exhausted but a stronger tier funded → route up, never block;
7
- * everything exhausted or unreadable → session model + notice (fail-open).
4
+ * ARCHIVED 2026-08-16 — the budget-routing DECISION LAYER and PERMIT LIFECYCLE
5
+ * that used to live here (`pickTier`, `acquireBudgetPermit`, `settlePermit`,
6
+ * `tripCooldown`, and the reserve/lock machinery behind them) were removed by a
7
+ * converged AI-council verdict (anthropic + openai, 2 of 2, 2026-08-16), which
8
+ * reversed the v1 contract locked on 2026-08-03 and retired its pre-registered
9
+ * AC1–AC5. `docs/contracts/budget-routing.md` is the migration record.
8
10
  *
9
- * Two hard mechanism requirements from the contract:
10
- * - `acquireBudgetPermit` is an ATOMIC reserve — O_EXCL lock file, then
11
- * sum + append-pending in one critical section. Check-then-spend races
12
- * two concurrent dispatches past the ceiling (council finding
13
- * 2026-08-03).
14
- * - `tripCooldown` / cool-down state — a 429/quota error pauses a tier
15
- * (default 60 min) instead of retry-looping; `pickTier` skips cooling
16
- * tiers via the same relation.
11
+ * The short reason: `pickTier` required a `routing_switch` input whose sole
12
+ * source — the `subagents.budget_routing` settings key — was DELETED on purpose
13
+ * by always-on orchestration. Wiring it therefore meant inventing a replacement
14
+ * for a category that had been removed deliberately, not finishing an
15
+ * integration. It had zero production callers, and with `session_tier` non-null
16
+ * in 0 of 327 orchestration records there was no basis on which its saving could
17
+ * ever have been measured.
17
18
  *
18
- * Pure decision logic is I/O-free; only the permit/cool-down helpers
19
- * touch the filesystem (ledger + state under agents/cost-tracking/).
19
+ * What stayed, and why: `TIER_ORDER` and `readCooldowns` have a live consumer in
20
+ * `routing_doctor.ts`, which reports cool-down state as a diagnostic. That is
21
+ * monitoring, not routing, and it never depended on the decision layer.
22
+ *
23
+ * Reversing this needs a real change of conditions, not a preference. Both
24
+ * council members attached one: an authoritative per-request tier-selection
25
+ * signal appearing with a named production dispatch point, plus telemetry
26
+ * carrying BOTH the chosen and the realized tier so a saving can be computed at
27
+ * all. Until then the git history holds the implementation.
20
28
  */
21
29
  import * as fs from 'node:fs';
22
30
  import * as path from 'node:path';
23
- import { fileURLToPath } from 'node:url';
24
31
 
25
32
  export type BudgetTier = 'cheap' | 'medium' | 'strong';
26
33
 
27
- /** Ordered cheapest-first — the relation walks this order upward. */
34
+ /** Ordered cheapest-first. */
28
35
  export const TIER_ORDER: readonly BudgetTier[] = ['cheap', 'medium', 'strong'];
29
36
 
30
- export interface TierBudgetState {
31
- /** USD ceiling from cost.budgets.per_tier.<tier>; null = no tier cap. */
32
- ceiling_usd: number | null;
33
- /** Ledger-summed spend for this tier in the ceiling's window (USD). */
34
- spent_usd: number;
35
- /** Epoch ms until which this tier is cooling after a quota error; 0 = live. */
36
- cooldown_until_ms: number;
37
- }
38
-
39
- export type BudgetRoutingSwitch = 'ask' | 'auto' | 'off';
40
-
41
- export interface PickTierInputs {
42
- /** Cheapest tier the classifier marked adequate (quality floor). */
43
- adequate_tier: BudgetTier;
44
- /** Per-tier budget state, complete over TIER_ORDER. */
45
- budgets: Record<BudgetTier, TierBudgetState>;
46
- /**
47
- * Caller-supplied switch. Formerly sourced from the `subagents.budget_routing`
48
- * settings key, deleted by always-on orchestration (Phase 1) — this
49
- * function has no production caller today, so the field stays as the
50
- * DESIGNED input shape; a future wiring supplies its own source.
51
- */
52
- routing_switch: BudgetRoutingSwitch;
53
- /** Estimated cost of the dispatch (USD) for the availability check. */
54
- estimated_cost_usd: number;
55
- /** Clock injection — never Date.now() in the decision path. */
56
- now_ms: number;
57
- }
58
-
59
- export interface PickTierDecision {
60
- /** 'tier' → dispatch on `tier`; 'session' → run on the session model. */
61
- route: 'tier' | 'session';
62
- tier: BudgetTier | null;
63
- /** 'proceed' | 'ask' (switch=ask and a budget-motivated downshift) */
64
- action: 'proceed' | 'ask';
65
- /** Non-empty when the relation degraded (exhausted / cooling / off). */
66
- notice: string;
67
- reason: string;
68
- }
69
-
70
- function tierAvailable(
71
- s: TierBudgetState,
72
- est: number,
73
- now: number,
74
- ): { ok: boolean; why: string } {
75
- if (s.cooldown_until_ms > now) {
76
- return { ok: false, why: 'cooling after quota error' };
77
- }
78
- if (s.ceiling_usd === null) {
79
- return { ok: true, why: 'no tier cap' };
80
- }
81
- if (s.spent_usd + est > s.ceiling_usd) {
82
- return { ok: false, why: `ceiling ${s.ceiling_usd} reached (spent ${s.spent_usd})` };
83
- }
84
- return { ok: true, why: 'within ceiling' };
85
- }
86
-
87
- /** The binding relation from docs/contracts/budget-routing.md. */
88
- export function pickTier(inp: PickTierInputs): PickTierDecision {
89
- if (inp.routing_switch === 'off') {
90
- return {
91
- route: 'session',
92
- tier: null,
93
- action: 'proceed',
94
- notice: '',
95
- reason: 'budget_routing off — session model, today\'s behavior',
96
- };
97
- }
98
- const startIdx = TIER_ORDER.indexOf(inp.adequate_tier);
99
- const skipped: string[] = [];
100
- for (let i = startIdx; i < TIER_ORDER.length; i += 1) {
101
- const tier = TIER_ORDER[i] as BudgetTier;
102
- const avail = tierAvailable(inp.budgets[tier], inp.estimated_cost_usd, inp.now_ms);
103
- if (avail.ok) {
104
- const degraded = i > startIdx;
105
- return {
106
- route: 'tier',
107
- tier,
108
- action: inp.routing_switch === 'ask' ? 'ask' : 'proceed',
109
- notice: degraded
110
- ? `cheapest adequate tier unavailable (${skipped.join('; ')}) — routed up to ${tier}`
111
- : '',
112
- reason: `${tier}: ${avail.why}${degraded ? '' : ' — cheapest adequate tier'}`,
113
- };
114
- }
115
- skipped.push(`${tier}: ${avail.why}`);
116
- }
117
- return {
118
- route: 'session',
119
- tier: null,
120
- action: 'proceed',
121
- notice: `all tier budgets unavailable (${skipped.join('; ')}) — session model, work not blocked`,
122
- reason: 'fail-open: budget machinery degrades savings, never work',
123
- };
124
- }
125
-
126
- // ── Atomic permit + cool-down (filesystem side) ────────────────────────────
127
-
128
- export const RESERVE_FILE = 'tier-reserves.jsonl';
129
37
  export const COOLDOWN_FILE = 'tier-cooldowns.json';
130
- export const DEFAULT_COOLDOWN_MS = 60 * 60 * 1000;
131
-
132
- // Reserve lifecycle constants — single source of truth shared with
133
- // budget.mjs (the second reserve reader) via src/config/budget-routing.json.
134
- // Duplicating the literal here was the exact two-truths defect the external
135
- // review flagged; both readers now load the same file.
136
- const _LIFECYCLE_CONFIG = path.join(
137
- path.dirname(fileURLToPath(import.meta.url)),
138
- '..',
139
- '..',
140
- 'config',
141
- 'budget-routing.json',
142
- );
143
-
144
- function _lifecycle(): { reserve_ttl_ms: number; lock_break_ms: number } {
145
- try {
146
- const raw = JSON.parse(fs.readFileSync(_LIFECYCLE_CONFIG, 'utf-8')) as Record<string, unknown>;
147
- const ttl = typeof raw['reserve_ttl_ms'] === 'number' ? raw['reserve_ttl_ms'] : 600_000;
148
- const brk = typeof raw['lock_break_ms'] === 'number' ? raw['lock_break_ms'] : 30_000;
149
- return { reserve_ttl_ms: ttl, lock_break_ms: brk };
150
- } catch {
151
- return { reserve_ttl_ms: 600_000, lock_break_ms: 30_000 };
152
- }
153
- }
154
-
155
- export function reserveTtlMs(): number {
156
- return _lifecycle().reserve_ttl_ms;
157
- }
158
-
159
- interface ReserveEntry {
160
- ts_ms: number;
161
- tier: BudgetTier;
162
- est_usd: number;
163
- status: 'pending' | 'settled';
164
- }
165
-
166
- function _readReserves(reservePath: string): ReserveEntry[] {
167
- if (!fs.existsSync(reservePath)) {
168
- return [];
169
- }
170
- const out: ReserveEntry[] = [];
171
- for (const line of fs.readFileSync(reservePath, 'utf-8').split('\n')) {
172
- if (!line.trim()) continue;
173
- try {
174
- out.push(JSON.parse(line) as ReserveEntry);
175
- } catch {
176
- // tolerate a torn line — reserve file is advisory spend state
177
- }
178
- }
179
- return out;
180
- }
181
-
182
- /** Take the reserve lock; break a stale one (crash leftover) once. */
183
- function _takeLock(lock: string, now_ms: number, lock_break_ms: number): number | null {
184
- for (let attempt = 0; attempt < 2; attempt += 1) {
185
- try {
186
- return fs.openSync(lock, 'wx');
187
- } catch {
188
- try {
189
- const age = now_ms - fs.statSync(lock).mtimeMs;
190
- if (age > lock_break_ms) {
191
- fs.rmSync(lock, { force: true });
192
- continue; // one retry after breaking the stale lock
193
- }
194
- } catch {
195
- continue; // lock vanished between open and stat — retry once
196
- }
197
- return null;
198
- }
199
- }
200
- return null;
201
- }
202
-
203
- /**
204
- * Atomic budget reserve — full lifecycle (external review 2026-08-03):
205
- *
206
- * acquire → expire (TTL) / settle → compact-on-write
207
- *
208
- * Under the lock: live reserves = pending entries younger than the shared
209
- * TTL (reserves are RACE PROTECTION, not spend accounting — real spend is
210
- * the ledger's job); the file is COMPACTED to exactly those live entries
211
- * on every acquire, so it stays bounded; a lock file older than
212
- * `lock_break_ms` is a crash leftover and is broken with one retry.
213
- * Returns granted=false (never throws) when the ceiling would be crossed
214
- * or a fresh lock is genuinely held — the caller falls back per the
215
- * relation (fail-open at the DECISION layer, conservative at the SPEND
216
- * layer).
217
- */
218
- export function acquireBudgetPermit(options: {
219
- tracking_dir: string;
220
- tier: BudgetTier;
221
- estimated_cost_usd: number;
222
- ceiling_usd: number | null;
223
- spent_usd: number;
224
- now_ms: number;
225
- }): { granted: boolean; reason: string } {
226
- const dir = options.tracking_dir;
227
- fs.mkdirSync(dir, { recursive: true });
228
- const lock = path.join(dir, `${RESERVE_FILE}.lock`);
229
- const { reserve_ttl_ms, lock_break_ms } = _lifecycle();
230
- const fd = _takeLock(lock, options.now_ms, lock_break_ms);
231
- if (fd === null) {
232
- return { granted: false, reason: 'reserve lock busy — treat tier as unavailable this instant' };
233
- }
234
- try {
235
- const reservePath = path.join(dir, RESERVE_FILE);
236
- const live = _readReserves(reservePath).filter(
237
- (e) => e.status === 'pending' && options.now_ms - e.ts_ms < reserve_ttl_ms,
238
- );
239
- const reserved = live
240
- .filter((e) => e.tier === options.tier)
241
- .reduce((acc, e) => acc + e.est_usd, 0);
242
- if (
243
- options.ceiling_usd !== null &&
244
- options.spent_usd + reserved + options.estimated_cost_usd > options.ceiling_usd
245
- ) {
246
- // Compact even on deny — expiry must not depend on a grant.
247
- fs.writeFileSync(
248
- reservePath,
249
- live.map((e) => JSON.stringify(e)).join('\n') + (live.length ? '\n' : ''),
250
- 'utf-8',
251
- );
252
- return {
253
- granted: false,
254
- reason: `ceiling ${options.ceiling_usd} would be crossed (spent ${options.spent_usd} + reserved ${reserved})`,
255
- };
256
- }
257
- live.push({
258
- ts_ms: options.now_ms,
259
- tier: options.tier,
260
- est_usd: options.estimated_cost_usd,
261
- status: 'pending',
262
- });
263
- fs.writeFileSync(
264
- reservePath,
265
- live.map((e) => JSON.stringify(e)).join('\n') + '\n',
266
- 'utf-8',
267
- );
268
- return { granted: true, reason: 'reserved' };
269
- } finally {
270
- fs.closeSync(fd);
271
- fs.rmSync(lock, { force: true });
272
- }
273
- }
274
-
275
- /**
276
- * Settle the oldest live pending reserve of a tier (call when the real
277
- * cost lands in the ledger, so the estimate stops counting immediately
278
- * instead of waiting out the TTL). Settled entries are dropped by the
279
- * next compaction. Best-effort: lock busy → no-op (the TTL is the
280
- * backstop that makes settling optional, never load-bearing).
281
- */
282
- export function settlePermit(options: {
283
- tracking_dir: string;
284
- tier: BudgetTier;
285
- now_ms: number;
286
- }): boolean {
287
- const lock = path.join(options.tracking_dir, `${RESERVE_FILE}.lock`);
288
- const { reserve_ttl_ms, lock_break_ms } = _lifecycle();
289
- const fd = _takeLock(lock, options.now_ms, lock_break_ms);
290
- if (fd === null) {
291
- return false;
292
- }
293
- try {
294
- const reservePath = path.join(options.tracking_dir, RESERVE_FILE);
295
- const live = _readReserves(reservePath).filter(
296
- (e) => e.status === 'pending' && options.now_ms - e.ts_ms < reserve_ttl_ms,
297
- );
298
- const idx = live.findIndex((e) => e.tier === options.tier);
299
- if (idx === -1) {
300
- return false;
301
- }
302
- live.splice(idx, 1);
303
- fs.writeFileSync(
304
- reservePath,
305
- live.map((e) => JSON.stringify(e)).join('\n') + (live.length ? '\n' : ''),
306
- 'utf-8',
307
- );
308
- return true;
309
- } finally {
310
- fs.closeSync(fd);
311
- fs.rmSync(lock, { force: true });
312
- }
313
- }
314
38
 
315
39
  /**
316
- * Record a quota/429 error: the tier cools down; pickTier skips it.
40
+ * Read the cool-down map (missing/torn file → all tiers live).
317
41
  *
318
- * Deliberately lock-free: last-writer-wins on a 3-key timestamp map is
319
- * benign (two concurrent trips write near-identical "until" values), so
320
- * the reserve lock is not reused here (accepted in the 2026-08-03 review).
321
- * Expired entries are purged on every write so the map never grows stale.
42
+ * NOTE the asymmetry this now has, because it is load-bearing for the caller:
43
+ * nothing in the tree WRITES this file any more — `tripCooldown` was the only
44
+ * writer and it went with the permit lifecycle. `routing_doctor` already
45
+ * accounts for that and reports cool-down state as *unavailable, no producer*
46
+ * rather than reading an all-zero map as a measured "not cooling". Keep that
47
+ * distinction if you ever add a writer back.
322
48
  */
323
- export function tripCooldown(options: {
324
- tracking_dir: string;
325
- tier: BudgetTier;
326
- now_ms: number;
327
- cooldown_ms?: number;
328
- }): number {
329
- const until = options.now_ms + (options.cooldown_ms ?? DEFAULT_COOLDOWN_MS);
330
- const p = path.join(options.tracking_dir, COOLDOWN_FILE);
331
- let state: Record<string, number> = {};
332
- try {
333
- state = JSON.parse(fs.readFileSync(p, 'utf-8')) as Record<string, number>;
334
- } catch {
335
- state = {};
336
- }
337
- for (const [k, v] of Object.entries(state)) {
338
- if (typeof v !== 'number' || v <= options.now_ms) {
339
- delete state[k];
340
- }
341
- }
342
- state[options.tier] = until;
343
- fs.mkdirSync(options.tracking_dir, { recursive: true });
344
- fs.writeFileSync(p, `${JSON.stringify(state, null, 2)}\n`, 'utf-8');
345
- return until;
346
- }
347
-
348
- /** Read the cool-down map (missing/torn file → all tiers live). */
349
49
  export function readCooldowns(tracking_dir: string): Record<BudgetTier, number> {
350
50
  const empty: Record<BudgetTier, number> = { cheap: 0, medium: 0, strong: 0 };
351
51
  try {
@@ -1679,23 +1679,23 @@ export class AnthropicCliClient extends CliClient {
1679
1679
  // documented "disable all tools" value.
1680
1680
  //
1681
1681
  // It also repairs a live failure, which is how the grant was found
1682
- // rather than reasoned about. The spawn inherits the caller's cwd
1683
- // (`_runSubprocess` sets none, deliberately — the worker-role marker
1684
- // below depends on the vendor CLI loading this repo's hook chain),
1685
- // so invoking the council from inside a repository made the child
1686
- // load that project's own instructions AND every tool definition.
1687
- // In this package that overflowed the model's context window
1688
- // outright: `is_error: true`, `"the request is ~214331 tokens
1682
+ // rather than reasoned about. AT THE TIME (2026-08-12) the spawn
1683
+ // inherited the caller's cwd, so invoking the council from inside a
1684
+ // repository made the child load that project's own instructions AND
1685
+ // every tool definition. In this package that overflowed the context
1686
+ // window outright: `is_error: true`, `"the request is ~214331 tokens
1689
1687
  // (limit 200000) … the rest is system prompt, tool definitions"`,
1690
- // exit 1, empty stderr — so the member reported as unavailable with
1691
- // no diagnosable cause. Measured 2026-08-12: identical call from
1692
- // `/tmp` exit 0, from the worktree exit 1, and adding this one flag
1693
- // returns the worktree call to exit 0.
1688
+ // exit 1, empty stderr — the member reported unavailable with no
1689
+ // diagnosable cause. Identical call from `/tmp` exit 0, from the
1690
+ // worktree exit 1, and this one flag returns the worktree to exit 0.
1694
1691
  //
1695
- // Dropping the tools rather than neutralising the cwd is the
1696
- // narrower fix of the two: it leaves the worker-role hook chain
1697
- // intact and removes only the capability the role never had a use
1698
- // for. The context saving is a consequence, not the justification.
1692
+ // TENSE CORRECTED 2026-08-16. This block read "`_runSubprocess` sets
1693
+ // none, deliberately" and "dropping the tools RATHER THAN
1694
+ // neutralising the cwd" — both false since `_runSubprocess` set
1695
+ // `cwd: os.tmpdir()` for a DIFFERENT contamination (a member
1696
+ // answering this repo's `end-review-nudge`). Two repairs, one
1697
+ // cause, neither aware of the other, so a reader here concluded
1698
+ // the cwd is inherited. Both are live; this flag bounds what a member may DO.
1699
1699
  '--tools',
1700
1700
  '',
1701
1701
  '--append-system-prompt',
@@ -1836,10 +1836,10 @@ export class OpenAICliClient extends CliClient {
1836
1836
  `"The '${this.model}' model is not supported when using Codex with a ` +
1837
1837
  'ChatGPT account." The CLI publishes no list of models it DOES accept, ' +
1838
1838
  'so there is no supported set to offer here — the one configuration ' +
1839
- 'measured to work is no pin at all. Fix: remove the `model:` line from ' +
1840
- 'the openai member (or set it to `codex-default`) to use the CLI\'s own ' +
1841
- 'default. A pinned model is only reachable on the API transport ' +
1842
- '(`api_key_ref:` without `mode: cli`).',
1839
+ 'measured to work is no pin at all. Fix: set `model: codex-default` on ' +
1840
+ 'the openai member. Do NOT delete the `model:` line — an enabled member ' +
1841
+ 'with no model fails config load, so that takes EVERY provider to NOT ' +
1842
+ 'CONFIGURED. A pin is reachable only on the API transport.',
1843
1843
  };
1844
1844
  }
1845
1845
 
@@ -1985,8 +1985,8 @@ export class OpenAICliClient extends CliClient {
1985
1985
  ? {
1986
1986
  hint:
1987
1987
  'the codex subscription transport serves no explicitly pinned ' +
1988
- 'model measured so far — remove the `model:` line from the ' +
1989
- 'openai member to use the CLI default',
1988
+ 'model measured so far — set `model: codex-default` on the ' +
1989
+ 'openai member; deleting the line fails config load',
1990
1990
  }
1991
1991
  : {}),
1992
1992
  },