@event4u/agent-config 13.0.0 → 14.1.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 (123) hide show
  1. package/.claude-plugin/marketplace.json +1 -1
  2. package/CHANGELOG.md +195 -315
  3. package/README.md +1 -1
  4. package/dist/agent-src/commands/evals/roadmap.json +8 -0
  5. package/dist/agent-src/commands/fix/ci.md +39 -1
  6. package/dist/agent-src/commands/pr/create.md +65 -4
  7. package/dist/agent-src/commands/roadmap/next.md +28 -5
  8. package/dist/agent-src/commands/work.md +16 -0
  9. package/dist/agent-src/contexts/contracts/command-suggestion-flow.md +47 -0
  10. package/dist/agent-src/contexts/execution/contract-decision-sheet.md +94 -0
  11. package/dist/agent-src/contexts/execution/plan-confidence-gate.md +30 -0
  12. package/dist/agent-src/contexts/execution/roadmap-execution-contract.md +21 -8
  13. package/dist/agent-src/contexts/execution/roadmap-process-loop.md +55 -8
  14. package/dist/agent-src/contexts/execution/subagent-response-contract.md +42 -0
  15. package/dist/agent-src/contexts/execution/subagent-spawn-contract.md +29 -0
  16. package/dist/agent-src/rules/command-suggestion-policy.md +33 -0
  17. package/dist/agent-src/rules/design-review-after-ui-write.md +25 -8
  18. package/dist/agent-src/rules/ui-audit-gate.md +25 -8
  19. package/dist/agent-src/scripts/gate_execute.ts +395 -0
  20. package/dist/agent-src/scripts/resume_probe.ts +422 -0
  21. package/dist/agent-src/scripts/roadmap_gates.ts +138 -4
  22. package/dist/agent-src/scripts/update_roadmap_progress.ts +53 -2
  23. package/dist/agent-src/skills/ai-council/SKILL.md +61 -0
  24. package/dist/agent-src/skills/experiment-loop/SKILL.md +116 -0
  25. package/dist/agent-src/skills/experiment-loop/evals/output-schema.yml +27 -0
  26. package/dist/agent-src/skills/experiment-loop/evals/triggers.json +17 -0
  27. package/dist/agent-src/skills/experiment-loop/references/pivot-ladder.md +54 -0
  28. package/dist/agent-src/skills/experiment-loop/references/protocol.md +61 -0
  29. package/dist/agent-src/skills/experiment-loop/references/register-format.md +66 -0
  30. package/dist/agent-src/skills/roadmap-management/SKILL.md +2 -2
  31. package/dist/agent-src/skills/roadmap-writing/SKILL.md +1 -1
  32. package/dist/agent-src/skills/subagent-orchestration/schemas/team-review-status.json +8 -3
  33. package/dist/agent-src/templates/agents/agent-project-settings.example.yml +1 -1
  34. package/dist/agent-src/templates/roadmaps.md +43 -3
  35. package/dist/cli/registry.js +1 -0
  36. package/dist/cli/registry.js.map +1 -1
  37. package/dist/cli-delegate/{chunk-N7FEST6D.js → chunk-7PZ4FSZB.js} +351 -174
  38. package/dist/cli-delegate/{chunk-3B72LSZL.js → chunk-F3YUF7SA.js} +1 -1
  39. package/dist/cli-delegate/cmd_conformance.js +336 -112
  40. package/dist/cli-delegate/cmd_doctor.js +2 -2
  41. package/dist/cli-delegate/cmd_handoff.js +1 -1
  42. package/dist/discovery/deprecation-report.md +1 -1
  43. package/dist/discovery/discovery-manifest.json +40 -18
  44. package/dist/discovery/discovery-manifest.json.sha256 +1 -1
  45. package/dist/discovery/discovery-manifest.summary.md +3 -3
  46. package/dist/discovery/dormancy-report.md +1 -1
  47. package/dist/discovery/orphan-report.md +1 -1
  48. package/dist/discovery/packs.json +6 -5
  49. package/dist/discovery/trust-report.md +2 -2
  50. package/dist/discovery/workspaces.json +5 -4
  51. package/dist/hooks/dispatch.js +2128 -1326
  52. package/dist/mcp/registry-manifest.json +3 -3
  53. package/dist/mcp/server.json +2 -2
  54. package/dist/mcp/server.mjs +1764 -899
  55. package/dist/router.json +1 -1
  56. package/dist/scripts/condense.js +1 -1
  57. package/dist/scripts/condense.js.map +1 -1
  58. package/dist/ui/assets/{index-B0aMJnuz.js → index-C1pBRm1f.js} +2 -2
  59. package/dist/ui/assets/index-C1pBRm1f.js.map +1 -0
  60. package/dist/ui/index.html +1 -1
  61. package/package.json +1 -1
  62. package/src/agent-src/scripts/gate_execute.ts +395 -0
  63. package/src/agent-src/scripts/resume_probe.ts +422 -0
  64. package/src/agent-src/scripts/roadmap_gates.ts +138 -4
  65. package/src/agent-src/scripts/update_roadmap_progress.ts +53 -2
  66. package/src/config/ci-local-parity.yml +13 -0
  67. package/src/config/evaluator-budgets.json +4 -4
  68. package/src/config/gate-coverage.yml +111 -5
  69. package/src/config/gate-violation-baselines.json +5 -5
  70. package/src/config/rule-activation-census.json +41 -0
  71. package/src/config/rule-stub-ceilings.json +52 -0
  72. package/src/scripts/_dispatch.bash +4 -0
  73. package/src/scripts/_lib/evaluator_contract.ts +159 -0
  74. package/src/scripts/_lib/gate_ledger.ts +5 -1
  75. package/src/scripts/_lib/gh_transient.ts +80 -0
  76. package/src/scripts/_lib/release_material.ts +16 -5
  77. package/src/scripts/_lib/review_independence.ts +111 -0
  78. package/src/scripts/_lib/session_register.ts +14 -0
  79. package/src/scripts/_lib/skill_catalogue.ts +20 -0
  80. package/src/scripts/_lib/skill_catalogue_series.ts +411 -0
  81. package/src/scripts/_lib/turn_end_refusals.ts +482 -0
  82. package/src/scripts/ai_council/_py_json.ts +96 -0
  83. package/src/scripts/ai_council/cli_call_budget.ts +357 -0
  84. package/src/scripts/ai_council/clients.ts +80 -143
  85. package/src/scripts/ai_council/config.ts +25 -11
  86. package/src/scripts/ai_council/probe_store.ts +196 -0
  87. package/src/scripts/ai_council/qualification.ts +425 -0
  88. package/src/scripts/ai_council/qualification_wiring.ts +243 -0
  89. package/src/scripts/ai_council/quorum_wiring.ts +211 -0
  90. package/src/scripts/ai_team/team_dispatch.ts +40 -6
  91. package/src/scripts/build_archive_index.ts +62 -3
  92. package/src/scripts/capture_skill_catalogue.ts +264 -4
  93. package/src/scripts/check_evaluator_schema.ts +249 -0
  94. package/src/scripts/check_gate_coverage.ts +93 -0
  95. package/src/scripts/check_pr_ci_current.ts +492 -0
  96. package/src/scripts/check_review_schema.ts +195 -0
  97. package/src/scripts/check_rule_activation_census.ts +451 -0
  98. package/src/scripts/check_rule_stub_ceiling.ts +1128 -0
  99. package/src/scripts/condense.ts +1 -1
  100. package/src/scripts/council_cli.ts +141 -191
  101. package/src/scripts/dispatch_r2_reviewer.ts +8 -1
  102. package/src/scripts/git_authorization_hook.ts +186 -1
  103. package/src/scripts/hook_manifest.yaml +72 -5
  104. package/src/scripts/hooks/block_unauthorized_git.ts +62 -6
  105. package/src/scripts/hooks/concern_registry.ts +4 -0
  106. package/src/scripts/hooks/dispatch_hook.ts +43 -1
  107. package/src/scripts/hooks/interruption_ledger_hook.ts +256 -0
  108. package/src/scripts/hooks/ship_diff_volume_hook.ts +180 -0
  109. package/src/scripts/hooks/turn_end_gate_hook.ts +119 -26
  110. package/src/scripts/hooks_doctor.ts +112 -0
  111. package/src/scripts/interruption_report.ts +443 -0
  112. package/src/scripts/lint_evidence_artifacts.ts +359 -0
  113. package/src/scripts/lint_roadmap_blockers.ts +65 -0
  114. package/src/scripts/lint_roadmap_complexity.ts +7 -1
  115. package/src/scripts/release.ts +38 -53
  116. package/src/scripts/rule_activation_census.ts +292 -0
  117. package/src/scripts/schemas/evaluator-output.schema.json +60 -0
  118. package/src/scripts/schemas/review-findings.schema.json +31 -0
  119. package/src/scripts/self_review_gate.ts +6 -0
  120. package/src/scripts/session_register_hook.ts +30 -1
  121. package/src/scripts/sync_pr_branch.ts +271 -0
  122. package/src/scripts/test_council_qualification.ts +213 -0
  123. package/dist/ui/assets/index-B0aMJnuz.js.map +0 -1
@@ -21,7 +21,11 @@ __export(clients_exports, {
21
21
  ANTHROPIC_KEY_PATH: () => ANTHROPIC_KEY_PATH,
22
22
  AnthropicCliClient: () => AnthropicCliClient,
23
23
  AnthropicClient: () => AnthropicClient,
24
+ CLI_CALLS_ATTRIBUTION_SUFFIX: () => CLI_CALLS_ATTRIBUTION_SUFFIX,
24
25
  CLI_CALLS_FILENAME: () => CLI_CALLS_FILENAME,
26
+ CLI_CONSUMER_COUNCIL: () => CLI_CONSUMER_COUNCIL,
27
+ CLI_CONSUMER_TEAM: () => CLI_CONSUMER_TEAM,
28
+ CLI_CONSUMER_UNKNOWN: () => CLI_CONSUMER_UNKNOWN,
25
29
  CODEX_MEASURED_UNSERVABLE: () => CODEX_MEASURED_UNSERVABLE,
26
30
  CliClient: () => CliClient,
27
31
  CliClientError: () => CliClientError,
@@ -52,6 +56,8 @@ __export(clients_exports, {
52
56
  PERPLEXITY_BASE_URL: () => PERPLEXITY_BASE_URL,
53
57
  PerplexityCliClient: () => PerplexityCliClient,
54
58
  PerplexityClient: () => PerplexityClient,
59
+ QUOTA_SOURCE_LOCAL_BUDGET: () => QUOTA_SOURCE_LOCAL_BUDGET,
60
+ QUOTA_SOURCE_PROVIDER: () => QUOTA_SOURCE_PROVIDER,
55
61
  SubprocessError: () => SubprocessError,
56
62
  UNLIMITED_TOKENS_FALLBACK: () => UNLIMITED_TOKENS_FALLBACK,
57
63
  ValueError: () => ValueError,
@@ -68,6 +74,7 @@ __export(clients_exports, {
68
74
  _today_utc_iso: () => _today_utc_iso,
69
75
  assertCacheBreakpointOrder: () => assertCacheBreakpointOrder,
70
76
  load_anthropic_key: () => load_anthropic_key,
77
+ load_cli_call_attribution: () => load_cli_call_attribution,
71
78
  load_cli_call_counts: () => load_cli_call_counts,
72
79
  load_openai_key: () => load_openai_key,
73
80
  quota_summary_line: () => quota_summary_line,
@@ -75,9 +82,9 @@ __export(clients_exports, {
75
82
  reset_cli_call_counts: () => reset_cli_call_counts
76
83
  });
77
84
  import { spawnSync } from "node:child_process";
78
- import * as fs2 from "node:fs";
85
+ import * as fs3 from "node:fs";
79
86
  import * as os from "node:os";
80
- import * as path2 from "node:path";
87
+ import * as path3 from "node:path";
81
88
 
82
89
  // src/scripts/_lib/spawn_env.ts
83
90
  var DENY_EXACT = /* @__PURE__ */ new Set([
@@ -151,10 +158,268 @@ function resolveSessionRole(env = process.env) {
151
158
  return KNOWN_ROLES.has(raw) ? raw : "orchestrator";
152
159
  }
153
160
 
154
- // src/scripts/ai_council/events_log.ts
155
- import * as crypto from "node:crypto";
161
+ // src/scripts/ai_council/_py_json.ts
162
+ function jsonDumpsIndent2(value) {
163
+ return _jsonDumpsIndented(value, 2, 0);
164
+ }
165
+ function _jsonDumpsIndented(value, indent, level) {
166
+ if (value === null || value === void 0) {
167
+ return "null";
168
+ }
169
+ switch (typeof value) {
170
+ case "boolean":
171
+ return value ? "true" : "false";
172
+ case "number":
173
+ return _pyJsonNumber(value);
174
+ case "string":
175
+ return _pyJsonStringAscii(value);
176
+ case "object":
177
+ break;
178
+ default:
179
+ throw new TypeError(`Object of type ${typeof value} is not JSON serializable`);
180
+ }
181
+ const pad = " ".repeat(indent * (level + 1));
182
+ const closePad = " ".repeat(indent * level);
183
+ if (Array.isArray(value)) {
184
+ if (value.length === 0) {
185
+ return "[]";
186
+ }
187
+ const items2 = value.map((v) => pad + _jsonDumpsIndented(v, indent, level + 1));
188
+ return `[
189
+ ${items2.join(",\n")}
190
+ ${closePad}]`;
191
+ }
192
+ const obj = value;
193
+ const keys = Object.keys(obj);
194
+ if (keys.length === 0) {
195
+ return "{}";
196
+ }
197
+ const items = keys.map(
198
+ (k) => `${pad}${_pyJsonStringAscii(k)}: ${_jsonDumpsIndented(obj[k], indent, level + 1)}`
199
+ );
200
+ return `{
201
+ ${items.join(",\n")}
202
+ ${closePad}}`;
203
+ }
204
+ function _pyJsonNumber(n) {
205
+ if (!Number.isFinite(n)) {
206
+ if (Number.isNaN(n)) {
207
+ return "NaN";
208
+ }
209
+ return n > 0 ? "Infinity" : "-Infinity";
210
+ }
211
+ return String(n);
212
+ }
213
+ function _pyJsonStringAscii(s) {
214
+ let out = '"';
215
+ for (const ch of s) {
216
+ const code = ch.codePointAt(0) ?? 0;
217
+ if (ch === '"') {
218
+ out += '\\"';
219
+ } else if (ch === "\\") {
220
+ out += "\\\\";
221
+ } else if (ch === "\b") {
222
+ out += "\\b";
223
+ } else if (ch === "\f") {
224
+ out += "\\f";
225
+ } else if (ch === "\n") {
226
+ out += "\\n";
227
+ } else if (ch === "\r") {
228
+ out += "\\r";
229
+ } else if (ch === " ") {
230
+ out += "\\t";
231
+ } else if (code < 32) {
232
+ out += `\\u${code.toString(16).padStart(4, "0")}`;
233
+ } else if (code < 127) {
234
+ out += ch;
235
+ } else if (code <= 65535) {
236
+ out += `\\u${code.toString(16).padStart(4, "0")}`;
237
+ } else {
238
+ const c = code - 65536;
239
+ const hi = 55296 + (c >> 10);
240
+ const lo = 56320 + (c & 1023);
241
+ out += `\\u${hi.toString(16).padStart(4, "0")}\\u${lo.toString(16).padStart(4, "0")}`;
242
+ }
243
+ }
244
+ return out + '"';
245
+ }
246
+
247
+ // src/scripts/ai_council/cli_call_budget.ts
156
248
  import * as fs from "node:fs";
157
249
  import * as path from "node:path";
250
+ var CLI_CALLS_ATTRIBUTION_SUFFIX = ".attribution.json";
251
+ var CLI_CONSUMER_COUNCIL = "council";
252
+ var CLI_CONSUMER_TEAM = "team";
253
+ var CLI_CONSUMER_UNKNOWN = "unknown";
254
+ var QUOTA_SOURCE_LOCAL_BUDGET = "local_budget";
255
+ var QUOTA_SOURCE_PROVIDER = "provider";
256
+ var DEFAULT_CLI_CALLS_PER_DAY = 50;
257
+ function attributionPath(target) {
258
+ return `${target}${CLI_CALLS_ATTRIBUTION_SUFFIX}`;
259
+ }
260
+ function resolveCliCallCaps(raw, validProviders) {
261
+ const out = {};
262
+ for (const provider of validProviders) {
263
+ out[provider] = DEFAULT_CLI_CALLS_PER_DAY;
264
+ }
265
+ if (!_isObject(raw)) {
266
+ return out;
267
+ }
268
+ const known = new Set(Object.keys(out));
269
+ for (const [provider, value] of Object.entries(raw)) {
270
+ if (!known.has(provider)) {
271
+ continue;
272
+ }
273
+ if (typeof value !== "number" || !Number.isInteger(value) || value < 0) {
274
+ continue;
275
+ }
276
+ out[provider] = value;
277
+ }
278
+ return out;
279
+ }
280
+ function writeStateAtomically(target, payload) {
281
+ fs.mkdirSync(path.dirname(target), { recursive: true });
282
+ const tmp = `${target}.${process.pid}.${_monotonicish()}.tmp`;
283
+ try {
284
+ fs.writeFileSync(tmp, payload, { encoding: "utf-8" });
285
+ fs.renameSync(tmp, target);
286
+ } catch (exc) {
287
+ try {
288
+ fs.unlinkSync(tmp);
289
+ } catch {
290
+ }
291
+ throw exc;
292
+ }
293
+ }
294
+ function withStateLock(target, fn) {
295
+ const lockPath = `${target}.lock`;
296
+ const waitMs = 1e3;
297
+ let held = false;
298
+ const started = _monotonicish();
299
+ while (_monotonicish() - started < waitMs) {
300
+ try {
301
+ fs.mkdirSync(path.dirname(lockPath), { recursive: true });
302
+ fs.closeSync(fs.openSync(lockPath, "wx"));
303
+ held = true;
304
+ break;
305
+ } catch {
306
+ try {
307
+ if (_monotonicish() - fs.statSync(lockPath).mtimeMs > waitMs) {
308
+ fs.unlinkSync(lockPath);
309
+ continue;
310
+ }
311
+ } catch {
312
+ }
313
+ _spin(20);
314
+ }
315
+ }
316
+ try {
317
+ return fn();
318
+ } finally {
319
+ if (held) {
320
+ try {
321
+ fs.unlinkSync(lockPath);
322
+ } catch {
323
+ }
324
+ }
325
+ }
326
+ }
327
+ function readAttribution(target, todayIso) {
328
+ const p = attributionPath(target);
329
+ if (!fs.existsSync(p)) {
330
+ return {};
331
+ }
332
+ let data;
333
+ try {
334
+ data = JSON.parse(fs.readFileSync(p, { encoding: "utf-8" }));
335
+ } catch {
336
+ return {};
337
+ }
338
+ if (!_isObject(data) || data["date"] !== todayIso) {
339
+ return {};
340
+ }
341
+ const raw = data["by_consumer"];
342
+ if (!_isObject(raw)) {
343
+ return {};
344
+ }
345
+ const out = {};
346
+ for (const [provider, perConsumer] of Object.entries(raw)) {
347
+ if (!_isObject(perConsumer)) {
348
+ continue;
349
+ }
350
+ const bucket = {};
351
+ for (const [consumer, v] of Object.entries(perConsumer)) {
352
+ if (typeof v === "number" && Number.isInteger(v)) {
353
+ bucket[consumer] = v;
354
+ }
355
+ }
356
+ out[provider] = bucket;
357
+ }
358
+ return out;
359
+ }
360
+ function recordAttribution(target, provider, consumer, todayIso, serialize) {
361
+ try {
362
+ const by_consumer = readAttribution(target, todayIso);
363
+ const bucket = by_consumer[provider] ?? {};
364
+ bucket[consumer] = (bucket[consumer] ?? 0) + 1;
365
+ by_consumer[provider] = bucket;
366
+ writeStateAtomically(attributionPath(target), serialize({ date: todayIso, by_consumer }));
367
+ } catch {
368
+ }
369
+ }
370
+ function resetAttribution(target, provider, todayIso, serialize) {
371
+ try {
372
+ let by_consumer = readAttribution(target, todayIso);
373
+ if (provider === null) {
374
+ by_consumer = {};
375
+ } else {
376
+ delete by_consumer[provider];
377
+ }
378
+ writeStateAtomically(attributionPath(target), serialize({ date: todayIso, by_consumer }));
379
+ } catch {
380
+ }
381
+ }
382
+ function quotaSummaryLine(clients, counts) {
383
+ const capOf = (c) => typeof c.max_calls_per_day === "number" && Number.isFinite(c.max_calls_per_day) ? c.max_calls_per_day : null;
384
+ const nameOf = (c) => typeof c.name === "string" && c.name !== "" ? c.name : "?";
385
+ const usedOf = (c) => Math.trunc(counts[nameOf(c)] ?? 0);
386
+ if (clients.length === 0) {
387
+ return ["", []];
388
+ }
389
+ const parts = [];
390
+ const warn = [];
391
+ for (const c of clients) {
392
+ const limit = capOf(c);
393
+ const used = usedOf(c);
394
+ if (limit === null) {
395
+ parts.push(`${nameOf(c)} ${used}/uncapped`);
396
+ continue;
397
+ }
398
+ parts.push(`${nameOf(c)} ${used}/${Math.trunc(limit)}`);
399
+ const warnAt = typeof c.warn_at === "number" ? c.warn_at : 0.8;
400
+ if (limit === 0 ? used > 0 : used / limit >= warnAt) {
401
+ warn.push(nameOf(c));
402
+ }
403
+ }
404
+ const prefix = warn.length > 0 ? "\u26A0\uFE0F " : "";
405
+ return [`${prefix}council:quota \xB7 ${parts.join(" \xB7 ")}`, warn];
406
+ }
407
+ function _isObject(x) {
408
+ return typeof x === "object" && x !== null && !Array.isArray(x);
409
+ }
410
+ function _monotonicish() {
411
+ return performance.now();
412
+ }
413
+ function _spin(ms) {
414
+ const until = _monotonicish() + ms;
415
+ while (_monotonicish() < until) {
416
+ }
417
+ }
418
+
419
+ // src/scripts/ai_council/events_log.ts
420
+ import * as crypto from "node:crypto";
421
+ import * as fs2 from "node:fs";
422
+ import * as path2 from "node:path";
158
423
  import { fileURLToPath } from "node:url";
159
424
 
160
425
  // src/scripts/_lib/env_kill_switch.ts
@@ -176,8 +441,8 @@ var _VALID_ACTIONS = /* @__PURE__ */ new Set([
176
441
  ]);
177
442
  var _KILL_SWITCH_ENV = "AGENT_CONFIG_NO_EVENTS_LOG";
178
443
  var _HERE = fileURLToPath(import.meta.url);
179
- var _REPO_ROOT = path.resolve(path.dirname(_HERE), "..", "..", "..");
180
- var _DEFAULT_LOG_PATH = path.join(
444
+ var _REPO_ROOT = path2.resolve(path2.dirname(_HERE), "..", "..", "..");
445
+ var _DEFAULT_LOG_PATH = path2.join(
181
446
  _REPO_ROOT,
182
447
  "agents",
183
448
  "runtime",
@@ -277,9 +542,9 @@ function appendEvent(event, opts = {}) {
277
542
  }
278
543
  }
279
544
  const target = opts.logPath != null ? opts.logPath : _DEFAULT_LOG_PATH;
280
- fs.mkdirSync(path.dirname(target), { recursive: true });
545
+ fs2.mkdirSync(path2.dirname(target), { recursive: true });
281
546
  const line = _pyJsonDumpsCompact(record);
282
- fs.appendFileSync(target, line + "\n", { encoding: "utf-8" });
547
+ fs2.appendFileSync(target, line + "\n", { encoding: "utf-8" });
283
548
  return true;
284
549
  }
285
550
  function _isoSecondsZ(d) {
@@ -312,7 +577,7 @@ function _pyJsonDumpsCompact(value) {
312
577
  case "boolean":
313
578
  return value ? "true" : "false";
314
579
  case "number":
315
- return _pyJsonNumber(value);
580
+ return _pyJsonNumber2(value);
316
581
  case "string":
317
582
  return _pyJsonStringRaw(value);
318
583
  case "object":
@@ -330,7 +595,7 @@ function _pyJsonDumpsCompact(value) {
330
595
  }
331
596
  return `{${parts.join(",")}}`;
332
597
  }
333
- function _pyJsonNumber(n) {
598
+ function _pyJsonNumber2(n) {
334
599
  if (!Number.isFinite(n)) {
335
600
  if (Number.isNaN(n)) {
336
601
  return "NaN";
@@ -501,13 +766,13 @@ function _setMonotonicSource(fn) {
501
766
  return prev;
502
767
  }
503
768
  function _loadKey(p, prefix, installScript) {
504
- if (!fs2.existsSync(p)) {
769
+ if (!fs3.existsSync(p)) {
505
770
  throw new KeyGateError(
506
771
  `Key not found at ${p}.
507
772
  Install it with: bash ${installScript}`
508
773
  );
509
774
  }
510
- const st = fs2.statSync(p);
775
+ const st = fs3.statSync(p);
511
776
  const mode = st.mode & 511;
512
777
  if (mode !== 384) {
513
778
  throw new KeyGateError(
@@ -515,7 +780,7 @@ function _loadKey(p, prefix, installScript) {
515
780
  Fix: chmod 600 ${p}`
516
781
  );
517
782
  }
518
- const key = fs2.readFileSync(p, { encoding: "utf-8" }).trim();
783
+ const key = fs3.readFileSync(p, { encoding: "utf-8" }).trim();
519
784
  if (!key) {
520
785
  throw new KeyGateError(`${p} is empty.`);
521
786
  }
@@ -978,12 +1243,12 @@ function _today_utc_iso() {
978
1243
  }
979
1244
  function load_cli_call_counts(p = null) {
980
1245
  const target = p !== null ? p : _cliCallsStatePath();
981
- if (!fs2.existsSync(target)) {
1246
+ if (!fs3.existsSync(target)) {
982
1247
  return {};
983
1248
  }
984
1249
  let data;
985
1250
  try {
986
- data = JSON.parse(fs2.readFileSync(target, { encoding: "utf-8" }));
1251
+ data = JSON.parse(fs3.readFileSync(target, { encoding: "utf-8" }));
987
1252
  } catch {
988
1253
  return {};
989
1254
  }
@@ -1006,56 +1271,38 @@ function load_cli_call_counts(p = null) {
1006
1271
  }
1007
1272
  return out;
1008
1273
  }
1009
- function record_cli_call(provider, p = null) {
1274
+ function load_cli_call_attribution(p = null) {
1010
1275
  const target = p !== null ? p : _cliCallsStatePath();
1011
- const counts = load_cli_call_counts(target);
1012
- counts[provider] = (counts[provider] ?? 0) + 1;
1013
- fs2.mkdirSync(path2.dirname(target), { recursive: true });
1014
- fs2.writeFileSync(
1015
- target,
1016
- _jsonDumpsIndent2({ date: _today_utc_iso(), counts }),
1017
- { encoding: "utf-8" }
1018
- );
1019
- return counts[provider];
1276
+ return readAttribution(target, _today_utc_iso());
1277
+ }
1278
+ function record_cli_call(provider, p = null, consumer = CLI_CONSUMER_UNKNOWN) {
1279
+ const target = p !== null ? p : _cliCallsStatePath();
1280
+ const today = _today_utc_iso();
1281
+ return withStateLock(target, () => {
1282
+ const counts = load_cli_call_counts(target);
1283
+ counts[provider] = (counts[provider] ?? 0) + 1;
1284
+ writeStateAtomically(target, jsonDumpsIndent2({ date: today, counts }));
1285
+ recordAttribution(target, provider, consumer, today, jsonDumpsIndent2);
1286
+ return counts[provider];
1287
+ });
1020
1288
  }
1021
1289
  function reset_cli_call_counts(provider = null, p = null) {
1022
1290
  const target = p !== null ? p : _cliCallsStatePath();
1023
- let counts = load_cli_call_counts(target);
1024
- if (provider === null) {
1025
- counts = {};
1026
- } else {
1027
- delete counts[provider];
1028
- }
1029
- fs2.mkdirSync(path2.dirname(target), { recursive: true });
1030
- fs2.writeFileSync(
1031
- target,
1032
- _jsonDumpsIndent2({ date: _today_utc_iso(), counts }),
1033
- { encoding: "utf-8" }
1034
- );
1035
- return counts;
1291
+ const today = _today_utc_iso();
1292
+ return withStateLock(target, () => {
1293
+ let counts = load_cli_call_counts(target);
1294
+ if (provider === null) {
1295
+ counts = {};
1296
+ } else {
1297
+ delete counts[provider];
1298
+ }
1299
+ writeStateAtomically(target, jsonDumpsIndent2({ date: today, counts }));
1300
+ resetAttribution(target, provider, today, jsonDumpsIndent2);
1301
+ return counts;
1302
+ });
1036
1303
  }
1037
1304
  function quota_summary_line(clients, opts = {}) {
1038
- const cliCallsPath = opts.cli_calls_path ?? null;
1039
- const capped = clients.filter((c) => _pyTruthy(_getattr(c, "max_calls_per_day", null)));
1040
- if (capped.length === 0) {
1041
- return ["", []];
1042
- }
1043
- const counts = load_cli_call_counts(cliCallsPath);
1044
- const parts = [];
1045
- const warn = [];
1046
- for (const c of capped) {
1047
- const name = _getattr(c, "name", "?");
1048
- const used = _pyInt(counts[name] ?? 0);
1049
- const limit = _pyInt(c.max_calls_per_day);
1050
- parts.push(`${name} ${used}/${limit}`);
1051
- const ratio = limit > 0 ? used / limit : 0;
1052
- const warnAt = Number(_getattr(c, "warn_at", 0.8));
1053
- if (ratio >= warnAt) {
1054
- warn.push(name);
1055
- }
1056
- }
1057
- const prefix = warn.length > 0 ? "\u26A0\uFE0F " : "";
1058
- return [`${prefix}council:quota \xB7 ${parts.join(" \xB7 ")}`, warn];
1305
+ return quotaSummaryLine(clients, load_cli_call_counts(opts.cli_calls_path ?? null));
1059
1306
  }
1060
1307
  var SubprocessError = class extends Error {
1061
1308
  kind;
@@ -1076,6 +1323,8 @@ var CliClient = class extends ExternalAIClient {
1076
1323
  warn_at;
1077
1324
  binary;
1078
1325
  _cli_calls_path;
1326
+ /** Attribution label for every booking this client makes. */
1327
+ _consumer;
1079
1328
  static _AUTH_FAILURE_PATTERNS = [
1080
1329
  "authentication",
1081
1330
  "unauthorized",
@@ -1114,6 +1363,7 @@ var CliClient = class extends ExternalAIClient {
1114
1363
  this.max_calls_per_day = opts.max_calls_per_day ?? null;
1115
1364
  this.warn_at = opts.warn_at ?? 0.8;
1116
1365
  this._cli_calls_path = opts.cli_calls_path ?? null;
1366
+ this._consumer = opts.consumer ?? CLI_CONSUMER_UNKNOWN;
1117
1367
  const binary = opts.binary ?? null;
1118
1368
  if (binary !== null) {
1119
1369
  this.binary = binary;
@@ -1145,7 +1395,7 @@ var CliClient = class extends ExternalAIClient {
1145
1395
  */
1146
1396
  _recordCallQuietly() {
1147
1397
  try {
1148
- record_cli_call(this.name, this._cli_calls_path);
1398
+ record_cli_call(this.name, this._cli_calls_path, this._consumer);
1149
1399
  } catch {
1150
1400
  }
1151
1401
  }
@@ -1283,7 +1533,10 @@ var CliClient = class extends ExternalAIClient {
1283
1533
  metadata: {
1284
1534
  cli: true,
1285
1535
  cli_calls_used: used,
1286
- cli_calls_max: this.max_calls_per_day
1536
+ cli_calls_max: this.max_calls_per_day,
1537
+ // Our counter refused before any spawn — nothing sent,
1538
+ // nothing booked for this call.
1539
+ quota_source: QUOTA_SOURCE_LOCAL_BUDGET
1287
1540
  }
1288
1541
  });
1289
1542
  }
@@ -1360,7 +1613,12 @@ var CliClient = class extends ExternalAIClient {
1360
1613
  metadata: {
1361
1614
  cli: true,
1362
1615
  returncode: proc.returncode,
1363
- stderr_tail: (proc.stderr || "").slice(-500)
1616
+ stderr_tail: (proc.stderr || "").slice(-500),
1617
+ // The vendor refused us; the local-budget refusal above
1618
+ // returns before spawning. Opposite remedies, one error
1619
+ // string — so discriminate explicitly rather than leaving it
1620
+ // inferable from which metadata keys happen to be absent.
1621
+ ...code === "cli_quota_exhausted" ? { quota_source: QUOTA_SOURCE_PROVIDER } : {}
1364
1622
  }
1365
1623
  });
1366
1624
  }
@@ -2203,92 +2461,8 @@ function _excMessage(exc) {
2203
2461
  }
2204
2462
  return String(exc);
2205
2463
  }
2206
- function _jsonDumpsIndent2(value) {
2207
- return _jsonDumpsIndented(value, 2, 0);
2208
- }
2209
- function _jsonDumpsIndented(value, indent, level) {
2210
- if (value === null || value === void 0) {
2211
- return "null";
2212
- }
2213
- switch (typeof value) {
2214
- case "boolean":
2215
- return value ? "true" : "false";
2216
- case "number":
2217
- return _pyJsonNumber2(value);
2218
- case "string":
2219
- return _pyJsonStringAscii(value);
2220
- case "object":
2221
- break;
2222
- default:
2223
- throw new TypeError(`Object of type ${typeof value} is not JSON serializable`);
2224
- }
2225
- const pad = " ".repeat(indent * (level + 1));
2226
- const closePad = " ".repeat(indent * level);
2227
- if (Array.isArray(value)) {
2228
- if (value.length === 0) {
2229
- return "[]";
2230
- }
2231
- const items2 = value.map((v) => pad + _jsonDumpsIndented(v, indent, level + 1));
2232
- return `[
2233
- ${items2.join(",\n")}
2234
- ${closePad}]`;
2235
- }
2236
- const obj = value;
2237
- const keys = Object.keys(obj);
2238
- if (keys.length === 0) {
2239
- return "{}";
2240
- }
2241
- const items = keys.map(
2242
- (k) => `${pad}${_pyJsonStringAscii(k)}: ${_jsonDumpsIndented(obj[k], indent, level + 1)}`
2243
- );
2244
- return `{
2245
- ${items.join(",\n")}
2246
- ${closePad}}`;
2247
- }
2248
- function _pyJsonNumber2(n) {
2249
- if (!Number.isFinite(n)) {
2250
- if (Number.isNaN(n)) {
2251
- return "NaN";
2252
- }
2253
- return n > 0 ? "Infinity" : "-Infinity";
2254
- }
2255
- return String(n);
2256
- }
2257
- function _pyJsonStringAscii(s) {
2258
- let out = '"';
2259
- for (const ch of s) {
2260
- const code = ch.codePointAt(0) ?? 0;
2261
- if (ch === '"') {
2262
- out += '\\"';
2263
- } else if (ch === "\\") {
2264
- out += "\\\\";
2265
- } else if (ch === "\b") {
2266
- out += "\\b";
2267
- } else if (ch === "\f") {
2268
- out += "\\f";
2269
- } else if (ch === "\n") {
2270
- out += "\\n";
2271
- } else if (ch === "\r") {
2272
- out += "\\r";
2273
- } else if (ch === " ") {
2274
- out += "\\t";
2275
- } else if (code < 32) {
2276
- out += `\\u${code.toString(16).padStart(4, "0")}`;
2277
- } else if (code < 127) {
2278
- out += ch;
2279
- } else if (code <= 65535) {
2280
- out += `\\u${code.toString(16).padStart(4, "0")}`;
2281
- } else {
2282
- const c = code - 65536;
2283
- const hi = 55296 + (c >> 10);
2284
- const lo = 56320 + (c & 1023);
2285
- out += `\\u${hi.toString(16).padStart(4, "0")}\\u${lo.toString(16).padStart(4, "0")}`;
2286
- }
2287
- }
2288
- return out + '"';
2289
- }
2290
2464
  function _which(name) {
2291
- if (name.includes(path2.sep) || path2.sep !== "/" && name.includes("/")) {
2465
+ if (name.includes(path3.sep) || path3.sep !== "/" && name.includes("/")) {
2292
2466
  return _isExecutable(name) ? name : null;
2293
2467
  }
2294
2468
  const pathEnv = process.env.PATH ?? "";
@@ -2296,12 +2470,12 @@ function _which(name) {
2296
2470
  return null;
2297
2471
  }
2298
2472
  const exts = process.platform === "win32" ? (process.env.PATHEXT ?? ".COM;.EXE;.BAT;.CMD").split(";").filter(Boolean) : [""];
2299
- for (const dir of pathEnv.split(path2.delimiter)) {
2473
+ for (const dir of pathEnv.split(path3.delimiter)) {
2300
2474
  if (!dir) {
2301
2475
  continue;
2302
2476
  }
2303
2477
  for (const ext of exts) {
2304
- const candidate = path2.join(dir, name + ext);
2478
+ const candidate = path3.join(dir, name + ext);
2305
2479
  if (_isExecutable(candidate)) {
2306
2480
  return candidate;
2307
2481
  }
@@ -2311,7 +2485,7 @@ function _which(name) {
2311
2485
  }
2312
2486
  function _isExecutable(p) {
2313
2487
  try {
2314
- const st = fs2.statSync(p);
2488
+ const st = fs3.statSync(p);
2315
2489
  if (!st.isFile()) {
2316
2490
  return false;
2317
2491
  }
@@ -2338,7 +2512,7 @@ function _defaultStdin() {
2338
2512
  function ensure() {
2339
2513
  if (buffer === null) {
2340
2514
  try {
2341
- buffer = fs2.readFileSync(0, { encoding: "utf-8" });
2515
+ buffer = fs3.readFileSync(0, { encoding: "utf-8" });
2342
2516
  } catch {
2343
2517
  buffer = "";
2344
2518
  }
@@ -2385,16 +2559,18 @@ __export(config_exports, {
2385
2559
  COUNCIL_CONFIG_RELNAME: () => COUNCIL_CONFIG_RELNAME,
2386
2560
  COUNCIL_CONFIG_USER_GLOBAL_REL: () => COUNCIL_CONFIG_USER_GLOBAL_REL,
2387
2561
  CouncilConfigError: () => CouncilConfigError,
2562
+ DEFAULT_CLI_CALLS_PER_DAY: () => DEFAULT_CLI_CALLS_PER_DAY2,
2388
2563
  _RAW_KEY_PREFIXES: () => _RAW_KEY_PREFIXES,
2389
2564
  _build_config: () => _build_config,
2390
2565
  load_council_config: () => load_council_config,
2391
2566
  resolve_api_key: () => resolve_api_key,
2567
+ resolve_cli_call_caps: () => resolve_cli_call_caps,
2392
2568
  resolve_config_path: () => resolve_config_path
2393
2569
  });
2394
2570
  var import_yaml = __toESM(require_dist(), 1);
2395
- import * as fs3 from "node:fs";
2571
+ import * as fs4 from "node:fs";
2396
2572
  import * as os2 from "node:os";
2397
- import * as path3 from "node:path";
2573
+ import * as path4 from "node:path";
2398
2574
  var _VALID_PROVIDERS = /* @__PURE__ */ new Set([
2399
2575
  "anthropic",
2400
2576
  "openai",
@@ -2608,7 +2784,7 @@ function _expanduser(p) {
2608
2784
  return os2.homedir();
2609
2785
  }
2610
2786
  if (p.startsWith("~/") || process.platform === "win32" && p.startsWith("~\\")) {
2611
- return path3.join(os2.homedir(), p.slice(2));
2787
+ return path4.join(os2.homedir(), p.slice(2));
2612
2788
  }
2613
2789
  return p;
2614
2790
  }
@@ -2637,14 +2813,14 @@ function resolve_config_path(project_root, options = {}) {
2637
2813
  });
2638
2814
  }
2639
2815
  function load_council_config(p) {
2640
- if (!fs3.existsSync(p)) {
2816
+ if (!fs4.existsSync(p)) {
2641
2817
  throw new CouncilConfigError(
2642
2818
  `Council config not found at ${p}. Create it per docs/contracts/ai-council-config.md.`
2643
2819
  );
2644
2820
  }
2645
2821
  let raw;
2646
2822
  try {
2647
- const text = fs3.readFileSync(p, "utf-8");
2823
+ const text = fs4.readFileSync(p, "utf-8");
2648
2824
  const parsed = (0, import_yaml.parse)(text, { version: "1.1" });
2649
2825
  raw = parsed === null || parsed === void 0 ? {} : parsed;
2650
2826
  } catch (exc) {
@@ -3541,7 +3717,10 @@ function _build_member(name, cfg, default_mode = "auto", ignored = []) {
3541
3717
  prompt_cache_ttl
3542
3718
  };
3543
3719
  }
3544
- var _DEFAULT_CLI_CALLS_PER_DAY = 50;
3720
+ var DEFAULT_CLI_CALLS_PER_DAY2 = DEFAULT_CLI_CALLS_PER_DAY;
3721
+ function resolve_cli_call_caps(raw) {
3722
+ return resolveCliCallCaps(raw, _VALID_PROVIDERS);
3723
+ }
3545
3724
  function _build_cli_call_budget(d) {
3546
3725
  if (!_isDict(d)) {
3547
3726
  throw new CouncilConfigError("`cli_call_budget` must be a mapping.");
@@ -3552,10 +3731,6 @@ function _build_cli_call_budget(d) {
3552
3731
  "`cli_call_budget.max_calls_per_day` must be a mapping."
3553
3732
  );
3554
3733
  }
3555
- const caps = /* @__PURE__ */ new Map();
3556
- for (const provider of _VALID_PROVIDERS) {
3557
- caps.set(provider, _DEFAULT_CLI_CALLS_PER_DAY);
3558
- }
3559
3734
  for (const [provider, value] of Object.entries(raw_caps)) {
3560
3735
  if (!_VALID_PROVIDERS.has(provider)) {
3561
3736
  throw new CouncilConfigError(
@@ -3567,8 +3742,10 @@ function _build_cli_call_budget(d) {
3567
3742
  `cli_call_budget.max_calls_per_day.${provider} must be a non-negative integer (got ${_pyRepr3(value)}).`
3568
3743
  );
3569
3744
  }
3570
- caps.set(provider, value);
3571
3745
  }
3746
+ const caps = new Map(
3747
+ Object.entries(resolve_cli_call_caps(raw_caps))
3748
+ );
3572
3749
  const warn_at_raw = _get(d, "warn_at", 0.8);
3573
3750
  if (_isBool(warn_at_raw) || !_isNumber(warn_at_raw)) {
3574
3751
  throw new CouncilConfigError(
@@ -3686,16 +3863,16 @@ function resolve_api_key(ref, scope = "api_key_ref") {
3686
3863
  }
3687
3864
  resolved = found;
3688
3865
  }
3689
- if (!fs3.existsSync(resolved)) {
3866
+ if (!fs4.existsSync(resolved)) {
3690
3867
  throw new CouncilConfigError(`${scope}: key file does not exist at ${resolved}.`);
3691
3868
  }
3692
- const mode = fs3.statSync(resolved).mode & 4095;
3869
+ const mode = fs4.statSync(resolved).mode & 4095;
3693
3870
  if (mode !== 384) {
3694
3871
  throw new CouncilConfigError(
3695
3872
  `${scope}: unsafe permissions on ${resolved}: got ${_pyOct(mode)}, expected 0o600. Fix: chmod 600 ${resolved}`
3696
3873
  );
3697
3874
  }
3698
- const value = fs3.readFileSync(resolved, "utf-8").trim();
3875
+ const value = fs4.readFileSync(resolved, "utf-8").trim();
3699
3876
  if (value === "") {
3700
3877
  throw new CouncilConfigError(`${scope}: key file at ${resolved} is empty.`);
3701
3878
  }
@@ -3724,10 +3901,10 @@ function _pyTruthy2(value) {
3724
3901
  }
3725
3902
 
3726
3903
  // src/scripts/_lib/cc_transcript.ts
3727
- import * as fs4 from "node:fs";
3728
- import * as path4 from "node:path";
3904
+ import * as fs5 from "node:fs";
3905
+ import * as path5 from "node:path";
3729
3906
  import { homedir as homedir2 } from "node:os";
3730
- var DEFAULT_PROJECTS_ROOT = path4.join(homedir2(), ".claude", "projects");
3907
+ var DEFAULT_PROJECTS_ROOT = path5.join(homedir2(), ".claude", "projects");
3731
3908
  function projectStoreSlug(cwd) {
3732
3909
  return cwd.replace(/[^A-Za-z0-9-]/g, "-");
3733
3910
  }
@@ -3772,8 +3949,8 @@ function parseLine(line) {
3772
3949
  }
3773
3950
  function listTranscriptFiles(opts = {}) {
3774
3951
  const root = opts.root ?? DEFAULT_PROJECTS_ROOT;
3775
- const base = opts.projectDir ? path4.isAbsolute(opts.projectDir) ? opts.projectDir : path4.join(root, opts.projectDir) : root;
3776
- if (!fs4.existsSync(base)) return [];
3952
+ const base = opts.projectDir ? path5.isAbsolute(opts.projectDir) ? opts.projectDir : path5.join(root, opts.projectDir) : root;
3953
+ if (!fs5.existsSync(base)) return [];
3777
3954
  const cutoffMs = opts.maxAgeDays !== void 0 ? (opts.now ?? /* @__PURE__ */ new Date()).getTime() - opts.maxAgeDays * 24 * 60 * 60 * 1e3 : null;
3778
3955
  const out = [];
3779
3956
  const stack = [base];
@@ -3781,12 +3958,12 @@ function listTranscriptFiles(opts = {}) {
3781
3958
  const dir = stack.pop();
3782
3959
  let entries;
3783
3960
  try {
3784
- entries = fs4.readdirSync(dir, { withFileTypes: true });
3961
+ entries = fs5.readdirSync(dir, { withFileTypes: true });
3785
3962
  } catch {
3786
3963
  continue;
3787
3964
  }
3788
3965
  for (const entry of entries) {
3789
- const full = path4.join(dir, entry.name);
3966
+ const full = path5.join(dir, entry.name);
3790
3967
  if (entry.isDirectory()) {
3791
3968
  stack.push(full);
3792
3969
  continue;
@@ -3795,7 +3972,7 @@ function listTranscriptFiles(opts = {}) {
3795
3972
  if (cutoffMs !== null) {
3796
3973
  let mtimeMs;
3797
3974
  try {
3798
- mtimeMs = fs4.statSync(full).mtimeMs;
3975
+ mtimeMs = fs5.statSync(full).mtimeMs;
3799
3976
  } catch {
3800
3977
  continue;
3801
3978
  }
@@ -3814,7 +3991,7 @@ function scanTranscripts(opts = {}) {
3814
3991
  for (const file of files) {
3815
3992
  let text;
3816
3993
  try {
3817
- text = fs4.readFileSync(file, "utf-8");
3994
+ text = fs5.readFileSync(file, "utf-8");
3818
3995
  } catch {
3819
3996
  continue;
3820
3997
  }