@event4u/agent-config 12.0.0 → 12.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 (89) hide show
  1. package/.claude-plugin/marketplace.json +1 -1
  2. package/CHANGELOG.md +122 -0
  3. package/agents/templates/.ai-council.yml.example +46 -3
  4. package/dist/agent-src/contexts/model-recommendations.md +6 -4
  5. package/dist/agent-src/rules/low-impact-corpus-privacy-floor.md +1 -0
  6. package/dist/agent-src/scripts/roadmap_gates.ts +30 -2
  7. package/dist/agent-src/scripts/update_roadmap_progress.ts +32 -1
  8. package/dist/agent-src/skills/roadmap-writing/SKILL.md +7 -7
  9. package/dist/agent-src/templates/agents/agent-project-settings.example.yml +1 -1
  10. package/dist/agent-src/templates/roadmaps.md +41 -3
  11. package/dist/cli/registry.js +1 -0
  12. package/dist/cli/registry.js.map +1 -1
  13. package/dist/cli-delegate/chunk-K34QTSD5.js +3895 -0
  14. package/dist/cli-delegate/{chunk-L565YFXH.js → chunk-LKZ5DOYW.js} +557 -297
  15. package/dist/cli-delegate/{chunk-NN4KW4HZ.js → chunk-O52B4F5S.js} +123 -2338
  16. package/dist/cli-delegate/cmd_conformance.js +4 -4
  17. package/dist/cli-delegate/cmd_doctor.js +2 -2
  18. package/dist/cli-delegate/cmd_export.js +1 -1
  19. package/dist/cli-delegate/cmd_handoff.js +1 -1
  20. package/dist/cli-delegate/cmd_prune.js +1 -1
  21. package/dist/cli-delegate/cmd_settings_migrate.js +1 -1
  22. package/dist/cli-delegate/cmd_sync.js +1 -1
  23. package/dist/cli-delegate/cmd_uninstall.js +1 -1
  24. package/dist/cli-delegate/cmd_validate.js +1 -1
  25. package/dist/discovery/deprecation-report.md +1 -1
  26. package/dist/discovery/discovery-manifest.json +4 -4
  27. package/dist/discovery/discovery-manifest.json.sha256 +1 -1
  28. package/dist/discovery/discovery-manifest.summary.md +1 -1
  29. package/dist/discovery/dormancy-report.md +1 -1
  30. package/dist/discovery/orphan-report.md +1 -1
  31. package/dist/discovery/packs.json +2 -2
  32. package/dist/discovery/trust-report.md +1 -1
  33. package/dist/discovery/workspaces.json +2 -2
  34. package/dist/hooks/dispatch.js +2574 -1080
  35. package/dist/install/install.mjs +744 -482
  36. package/dist/mcp/registry-manifest.json +2 -2
  37. package/dist/mcp/server.json +2 -2
  38. package/dist/mcp/server.mjs +113 -10
  39. package/dist/router.json +1 -1
  40. package/dist/scripts/_lib/model_tier.js +9 -0
  41. package/dist/scripts/_lib/model_tier.js.map +1 -1
  42. package/dist/scripts/condense.js +50 -0
  43. package/dist/scripts/condense.js.map +1 -1
  44. package/docs/MIGRATION.md +20 -1
  45. package/package.json +1 -1
  46. package/src/agent-src/scripts/roadmap_gates.ts +30 -2
  47. package/src/agent-src/scripts/update_roadmap_progress.ts +32 -1
  48. package/src/config/evaluator-budgets.json +4 -4
  49. package/src/config/gate-coverage.yml +39 -9
  50. package/src/config/gate-violation-baselines.json +6 -0
  51. package/src/config/hook-token-budget.json +7 -1
  52. package/src/config/preamble-payload-budget.json +28 -0
  53. package/src/scripts/_dispatch.bash +10 -0
  54. package/src/scripts/_lib/git_common_dir.ts +173 -0
  55. package/src/scripts/_lib/model_tier.ts +9 -0
  56. package/src/scripts/_lib/release_highlights.ts +108 -7
  57. package/src/scripts/_lib/skill_catalogue.ts +862 -0
  58. package/src/scripts/ai_council/clients.ts +182 -3
  59. package/src/scripts/ai_council/config.ts +16 -1
  60. package/src/scripts/ai_council/low_impact.ts +14 -1
  61. package/src/scripts/ai_council/orchestrator.ts +14 -1
  62. package/src/scripts/ai_council/transport_resolver.ts +32 -2
  63. package/src/scripts/capture_skill_catalogue.ts +185 -328
  64. package/src/scripts/check_branch_freshness.ts +7 -1
  65. package/src/scripts/check_release_published.ts +6 -2
  66. package/src/scripts/check_release_trunk_sync.ts +18 -2
  67. package/src/scripts/condense.ts +50 -1
  68. package/src/scripts/dispatch_r2_reviewer.ts +15 -5
  69. package/src/scripts/evidence_report.ts +15 -4
  70. package/src/scripts/hook_manifest.yaml +47 -13
  71. package/src/scripts/hooks/concern_registry.ts +2 -0
  72. package/src/scripts/hooks/orchestration_record_hook.ts +21 -0
  73. package/src/scripts/hooks/tool_result_bytes_hook.ts +219 -0
  74. package/src/scripts/hot_context_hook.ts +13 -1
  75. package/src/scripts/install.ts +129 -0
  76. package/src/scripts/janitor.ts +77 -2
  77. package/src/scripts/lint_plan_risk_register.ts +22 -9
  78. package/src/scripts/lint_roadmap_blockers.ts +109 -6
  79. package/src/scripts/lint_scheduled_deprecations.ts +738 -0
  80. package/src/scripts/migration_status.ts +17 -1
  81. package/src/scripts/probe_review_binding_drift.ts +731 -0
  82. package/src/scripts/release.ts +95 -1
  83. package/src/scripts/schemas/command.schema.json +2 -2
  84. package/src/scripts/schemas/skill.schema.json +2 -1
  85. package/src/scripts/schemas/subagent.schema.json +2 -2
  86. package/src/scripts/session_eol_report.ts +81 -0
  87. package/src/scripts/workspace_doctor.ts +464 -0
  88. package/src/scripts/worktree_cleanup_check.ts +2 -1
  89. package/dist/cli-delegate/chunk-AYKQGIBG.js +0 -1594
@@ -1,1594 +0,0 @@
1
- import { createRequire as __acCreateRequire } from 'node:module'; const require = globalThis.require ?? __acCreateRequire(import.meta.url);
2
- import {
3
- resolve_with_fallback,
4
- write_target
5
- } from "./chunk-NDJLNC6Q.js";
6
- import {
7
- require_dist
8
- } from "./chunk-OY53J6II.js";
9
- import {
10
- __export,
11
- __toESM
12
- } from "./chunk-J55WI3RR.js";
13
-
14
- // src/scripts/ai_council/config.ts
15
- var config_exports = {};
16
- __export(config_exports, {
17
- COUNCIL_CONFIG_ENV: () => COUNCIL_CONFIG_ENV,
18
- COUNCIL_CONFIG_RELNAME: () => COUNCIL_CONFIG_RELNAME,
19
- COUNCIL_CONFIG_USER_GLOBAL_REL: () => COUNCIL_CONFIG_USER_GLOBAL_REL,
20
- CouncilConfigError: () => CouncilConfigError,
21
- _RAW_KEY_PREFIXES: () => _RAW_KEY_PREFIXES,
22
- _build_config: () => _build_config,
23
- load_council_config: () => load_council_config,
24
- resolve_api_key: () => resolve_api_key,
25
- resolve_config_path: () => resolve_config_path
26
- });
27
- var import_yaml = __toESM(require_dist(), 1);
28
- import * as fs from "node:fs";
29
- import * as os from "node:os";
30
- import * as path from "node:path";
31
-
32
- // src/scripts/ai_council/quorum.ts
33
- var SOLO_FLOOR_MIN_PRESENT = 2;
34
-
35
- // src/scripts/ai_council/config.ts
36
- var _VALID_PROVIDERS = /* @__PURE__ */ new Set([
37
- "anthropic",
38
- "openai",
39
- "gemini",
40
- "xai",
41
- "perplexity"
42
- ]);
43
- var _RAW_KEY_PREFIXES = [
44
- "sk-",
45
- "sk-ant-",
46
- "ya29.",
47
- "AIza",
48
- "xai-",
49
- "pplx-",
50
- "gsk_"
51
- ];
52
- var CouncilConfigError = class extends Error {
53
- constructor(message) {
54
- super(message);
55
- this.name = "CouncilConfigError";
56
- }
57
- };
58
- function _pyReprStr(s) {
59
- const hasSingle = s.includes("'");
60
- const hasDouble = s.includes('"');
61
- const quote = hasSingle && !hasDouble ? '"' : "'";
62
- let body = s.replace(/\\/g, "\\\\").replace(/\n/g, "\\n").replace(/\r/g, "\\r").replace(/\t/g, "\\t");
63
- if (quote === "'") {
64
- body = body.replace(/'/g, "\\'");
65
- } else {
66
- body = body.replace(/"/g, '\\"');
67
- }
68
- return `${quote}${body}${quote}`;
69
- }
70
- function _pyReprFloat(value) {
71
- if (Number.isInteger(value) && Number.isFinite(value)) {
72
- return `${value}.0`;
73
- }
74
- if (value === Infinity) {
75
- return "inf";
76
- }
77
- if (value === -Infinity) {
78
- return "-inf";
79
- }
80
- if (Number.isNaN(value)) {
81
- return "nan";
82
- }
83
- return String(value);
84
- }
85
- function _pyRepr(value) {
86
- if (value instanceof _Float) {
87
- return _pyReprFloat(value.value);
88
- }
89
- if (value === null || value === void 0) {
90
- return "None";
91
- }
92
- if (typeof value === "boolean") {
93
- return value ? "True" : "False";
94
- }
95
- if (typeof value === "string") {
96
- return _pyReprStr(value);
97
- }
98
- if (typeof value === "number") {
99
- return String(value);
100
- }
101
- if (Array.isArray(value)) {
102
- return `[${value.map((v) => _pyRepr(v)).join(", ")}]`;
103
- }
104
- if (typeof value === "object") {
105
- const parts = [];
106
- for (const [k, v] of Object.entries(value)) {
107
- parts.push(`${_pyReprStr(k)}: ${_pyRepr(v)}`);
108
- }
109
- return `{${parts.join(", ")}}`;
110
- }
111
- return String(value);
112
- }
113
- var _Float = class {
114
- constructor(value) {
115
- this.value = value;
116
- }
117
- value;
118
- };
119
- function _f(n) {
120
- return new _Float(n);
121
- }
122
- function _pyTypeName(value) {
123
- if (value === null || value === void 0) {
124
- return "NoneType";
125
- }
126
- if (typeof value === "boolean") {
127
- return "bool";
128
- }
129
- if (typeof value === "number") {
130
- return Number.isInteger(value) ? "int" : "float";
131
- }
132
- if (typeof value === "string") {
133
- return "str";
134
- }
135
- if (Array.isArray(value)) {
136
- return "list";
137
- }
138
- if (typeof value === "object") {
139
- return "dict";
140
- }
141
- return typeof value;
142
- }
143
- function _sortedListRepr(items) {
144
- const sorted = [...items].sort();
145
- return `[${sorted.map((s) => _pyReprStr(s)).join(", ")}]`;
146
- }
147
- function _pyOct(mode) {
148
- return `0o${mode.toString(8)}`;
149
- }
150
- function _pyInt(value) {
151
- if (typeof value === "boolean") {
152
- return value ? 1 : 0;
153
- }
154
- if (typeof value === "number") {
155
- return Math.trunc(value);
156
- }
157
- if (typeof value === "string") {
158
- const trimmed = value.trim();
159
- if (/^[+-]?\d+$/.test(trimmed)) {
160
- return parseInt(trimmed, 10);
161
- }
162
- throw new CouncilConfigError(
163
- `invalid literal for int() with base 10: ${_pyReprStr(value)}`
164
- );
165
- }
166
- throw new CouncilConfigError(
167
- `int() argument must be a string or a number, not '${_pyTypeName(value)}'`
168
- );
169
- }
170
- function _pyFloat(value) {
171
- if (typeof value === "boolean") {
172
- return value ? 1 : 0;
173
- }
174
- if (typeof value === "number") {
175
- return value;
176
- }
177
- if (typeof value === "string") {
178
- const trimmed = value.trim();
179
- const parsed = Number(trimmed);
180
- if (trimmed !== "" && Number.isFinite(parsed)) {
181
- return parsed;
182
- }
183
- if (/^[+-]?inf(inity)?$/i.test(trimmed)) {
184
- return trimmed.startsWith("-") ? -Infinity : Infinity;
185
- }
186
- if (/^[+-]?nan$/i.test(trimmed)) {
187
- return NaN;
188
- }
189
- throw new CouncilConfigError(
190
- `could not convert string to float: ${_pyReprStr(value)}`
191
- );
192
- }
193
- throw new CouncilConfigError(
194
- `float() argument must be a string or a number, not '${_pyTypeName(value)}'`
195
- );
196
- }
197
- function _isBool(value) {
198
- return typeof value === "boolean";
199
- }
200
- function _isInt(value) {
201
- return typeof value === "number" && !Number.isNaN(value) && Number.isInteger(value);
202
- }
203
- function _isNumber(value) {
204
- return typeof value === "number";
205
- }
206
- function _isDict(value) {
207
- return typeof value === "object" && value !== null && !Array.isArray(value);
208
- }
209
- function _isStr(value) {
210
- return typeof value === "string";
211
- }
212
- function _isList(value) {
213
- return Array.isArray(value);
214
- }
215
- function _getOr(d, key, fallback) {
216
- const v = d[key];
217
- if (v === void 0) {
218
- return fallback;
219
- }
220
- if (v === null || v === false || v === 0 || v === "" || Array.isArray(v) && v.length === 0 || _isDict(v) && Object.keys(v).length === 0) {
221
- return fallback;
222
- }
223
- return v;
224
- }
225
- function _get(d, key, fallback) {
226
- const v = d[key];
227
- return v === void 0 ? fallback : v;
228
- }
229
- var _VALID_NECESSITY_MODES = /* @__PURE__ */ new Set([
230
- "off",
231
- "educate",
232
- "block",
233
- "warn-only"
234
- ]);
235
- var _VALID_DISCLOSURE_MODES = /* @__PURE__ */ new Set([
236
- "always",
237
- "above_threshold",
238
- "off"
239
- ]);
240
- var _VALID_CHAIRMAN_MODES = /* @__PURE__ */ new Set(["host", "member", "auto"]);
241
- var COUNCIL_CONFIG_RELNAME = ".ai-council.yml";
242
- var COUNCIL_CONFIG_USER_GLOBAL_REL = "settings/.ai-council.yml";
243
- var COUNCIL_CONFIG_ENV = "AI_COUNCIL_CONFIG";
244
- function _expanduser(p) {
245
- if (p === "~") {
246
- return os.homedir();
247
- }
248
- if (p.startsWith("~/") || process.platform === "win32" && p.startsWith("~\\")) {
249
- return path.join(os.homedir(), p.slice(2));
250
- }
251
- return p;
252
- }
253
- function _isAbsoluteLikePython(p) {
254
- if (process.platform === "win32") {
255
- return /^[a-zA-Z]:[\\/]/.test(p) || /^([\\/]{2})[^\\/]+[\\/][^\\/]+/.test(p);
256
- }
257
- return p.startsWith("/");
258
- }
259
- function resolve_config_path(project_root, options = {}) {
260
- void project_root;
261
- const env_map = options.env != null ? options.env : process.env;
262
- const override = env_map[COUNCIL_CONFIG_ENV];
263
- if (override) {
264
- return _expanduser(override);
265
- }
266
- const found = resolve_with_fallback(
267
- COUNCIL_CONFIG_USER_GLOBAL_REL,
268
- { env: options.env ?? null }
269
- );
270
- if (found !== null) {
271
- return found;
272
- }
273
- return write_target(COUNCIL_CONFIG_USER_GLOBAL_REL, {
274
- env: options.env ?? null
275
- });
276
- }
277
- function load_council_config(p) {
278
- if (!fs.existsSync(p)) {
279
- throw new CouncilConfigError(
280
- `Council config not found at ${p}. Create it per docs/contracts/ai-council-config.md.`
281
- );
282
- }
283
- let raw;
284
- try {
285
- const text = fs.readFileSync(p, "utf-8");
286
- const parsed = (0, import_yaml.parse)(text, { version: "1.1" });
287
- raw = parsed === null || parsed === void 0 ? {} : parsed;
288
- } catch (exc) {
289
- if (exc instanceof CouncilConfigError) {
290
- throw exc;
291
- }
292
- const msg = exc instanceof Error ? exc.message : String(exc);
293
- throw new CouncilConfigError(`${p}: invalid YAML \u2014 ${msg}`);
294
- }
295
- if (!_isDict(raw)) {
296
- throw new CouncilConfigError(`${p}: top-level must be a mapping.`);
297
- }
298
- return _build_config(raw, p);
299
- }
300
- function _build_config(raw, source_path) {
301
- const enabled = _get(raw, "enabled", false);
302
- if (!_isBool(enabled)) {
303
- throw new CouncilConfigError("`enabled` must be a bool.");
304
- }
305
- const ignored_transport_keys = [];
306
- const defaults = _build_defaults(
307
- _asDict(_getOr(raw, "defaults", {})),
308
- ignored_transport_keys
309
- );
310
- const cost_budget = _build_cost_budget(
311
- _asDict(_getOr(raw, "cost_budget", {}))
312
- );
313
- const members_raw = _getOr(raw, "members", {});
314
- if (!_isDict(members_raw)) {
315
- throw new CouncilConfigError("`members` must be a mapping.");
316
- }
317
- const members = /* @__PURE__ */ new Map();
318
- for (const [name, cfg] of Object.entries(members_raw)) {
319
- members.set(
320
- name,
321
- _build_member(
322
- name,
323
- _asDict(_orEmpty(cfg)),
324
- defaults.mode,
325
- ignored_transport_keys
326
- )
327
- );
328
- }
329
- const advisors_raw = _getOr(raw, "advisors", {});
330
- if (!_isDict(advisors_raw)) {
331
- throw new CouncilConfigError("`advisors` must be a mapping.");
332
- }
333
- const advisors = /* @__PURE__ */ new Map();
334
- for (const [adv_name, adv_cfg] of Object.entries(advisors_raw)) {
335
- advisors.set(adv_name, _build_advisor(adv_name, _asDict(_orEmpty(adv_cfg))));
336
- }
337
- for (const adv of advisors.values()) {
338
- if (!adv.enabled) {
339
- continue;
340
- }
341
- const bound = members.get(adv.member);
342
- if (bound === void 0) {
343
- throw new CouncilConfigError(
344
- `advisors.${adv.name}.member=${_pyReprStr(adv.member)}: no such member in the \`members\` block.`
345
- );
346
- }
347
- if (!bound.enabled) {
348
- throw new CouncilConfigError(
349
- `advisors.${adv.name}.member=${_pyReprStr(adv.member)}: member exists but is disabled. Enable the member or disable the advisor.`
350
- );
351
- }
352
- }
353
- const consensus = _build_consensus_scoring(
354
- _asDict(_getOr(raw, "consensus_scoring", {}))
355
- );
356
- const cli_call_budget = _build_cli_call_budget(
357
- _asDict(_getOr(raw, "cli_call_budget", {}))
358
- );
359
- const quorum = _build_quorum(_get(raw, "quorum", "majority"));
360
- const quorum_min_present = _build_quorum_min_present(
361
- _get(raw, "quorum_min_present", SOLO_FLOOR_MIN_PRESENT)
362
- );
363
- const necessity_classifier = _build_necessity_classifier(
364
- _asDict(_getOr(raw, "necessity_classifier", {}))
365
- );
366
- const model_downgrade = _build_model_downgrade(
367
- _asDict(_getOr(raw, "model_downgrade", {}))
368
- );
369
- const debate = _build_debate(_asDict(_getOr(raw, "debate", {})));
370
- const decision_replay = _build_decision_replay(
371
- _asDict(_getOr(raw, "decision_replay", {})),
372
- "decision_replay"
373
- );
374
- const stance_tally = _build_stance_tally(
375
- _asDict(_getOr(raw, "stance_tally", {})),
376
- "stance_tally"
377
- );
378
- const chairman = _build_chairman(
379
- _asDict(_getOr(raw, "chairman", {})),
380
- "chairman",
381
- members
382
- );
383
- const debate_gates = _build_debate_gates(
384
- _asDict(_getOr(raw, "debate_gates", {})),
385
- "debate_gates"
386
- );
387
- const restate = _build_restate(_asDict(_getOr(raw, "restate", {})), "restate");
388
- const critic_protocol = _build_critic_protocol(_getOr(raw, "critic_protocol", "legacy"));
389
- const decision_resolution = _build_decision_resolution(
390
- _asDict(_getOr(raw, "decision_resolution", {}))
391
- );
392
- const routing = _build_routing(_asDict(_getOr(raw, "routing", {})), members);
393
- const low_impact = _build_low_impact(
394
- _asDict(_getOr(raw, "low_impact", {})),
395
- members,
396
- routing
397
- );
398
- _reject_top_level_locked_dispatch(raw);
399
- const lens_overrides = _build_lens_overrides(
400
- _asDict(_getOr(raw, "lenses", {}))
401
- );
402
- return {
403
- enabled,
404
- defaults,
405
- cost_budget,
406
- members,
407
- advisors,
408
- consensus_scoring: consensus,
409
- cli_call_budget,
410
- quorum,
411
- quorum_min_present,
412
- necessity_classifier,
413
- model_downgrade,
414
- debate,
415
- decision_replay,
416
- stance_tally,
417
- chairman,
418
- debate_gates,
419
- restate,
420
- critic_protocol,
421
- decision_resolution,
422
- routing,
423
- low_impact,
424
- lens_overrides,
425
- source_path,
426
- ignored_transport_keys
427
- };
428
- }
429
- function _orEmpty(value) {
430
- if (value === null || value === void 0 || value === false || value === 0 || value === "" || Array.isArray(value) && value.length === 0 || _isDict(value) && Object.keys(value).length === 0) {
431
- return {};
432
- }
433
- return value;
434
- }
435
- function _asDict(value) {
436
- return value;
437
- }
438
- function _build_necessity_classifier(d) {
439
- if (!_isDict(d)) {
440
- throw new CouncilConfigError("`necessity_classifier` must be a mapping.");
441
- }
442
- const enabled = _get(d, "enabled", true);
443
- if (!_isBool(enabled)) {
444
- throw new CouncilConfigError(
445
- "`necessity_classifier.enabled` must be a bool."
446
- );
447
- }
448
- const mode = _get(d, "mode", "educate");
449
- if (!(_isStr(mode) && _VALID_NECESSITY_MODES.has(mode))) {
450
- throw new CouncilConfigError(
451
- `necessity_classifier.mode=${_pyRepr(mode)} not in ${_sortedListRepr(_VALID_NECESSITY_MODES)}.`
452
- );
453
- }
454
- const user_explicit_mode = _get(d, "user_explicit_mode", "warn-only");
455
- if (!(_isStr(user_explicit_mode) && _VALID_NECESSITY_MODES.has(user_explicit_mode))) {
456
- throw new CouncilConfigError(
457
- `necessity_classifier.user_explicit_mode=${_pyRepr(user_explicit_mode)} not in ${_sortedListRepr(_VALID_NECESSITY_MODES)}.`
458
- );
459
- }
460
- return {
461
- enabled: Boolean(enabled),
462
- mode,
463
- user_explicit_mode
464
- };
465
- }
466
- function _build_model_downgrade(d) {
467
- if (!_isDict(d)) {
468
- throw new CouncilConfigError("`model_downgrade` must be a mapping.");
469
- }
470
- const enabled = _get(d, "enabled", true);
471
- if (!_isBool(enabled)) {
472
- throw new CouncilConfigError("`model_downgrade.enabled` must be a bool.");
473
- }
474
- const auto_apply = _get(d, "auto_apply", false);
475
- if (!_isBool(auto_apply)) {
476
- throw new CouncilConfigError("`model_downgrade.auto_apply` must be a bool.");
477
- }
478
- const override_raw = _get(d, "model_tier_override", {});
479
- if (!_isDict(override_raw)) {
480
- throw new CouncilConfigError("`model_downgrade.model_tier_override` must be a mapping.");
481
- }
482
- const model_tier_override = {};
483
- for (const [k, v] of Object.entries(override_raw)) {
484
- if (!_isStr(v) || v.length === 0) {
485
- throw new CouncilConfigError(
486
- `\`model_downgrade.model_tier_override.${k}\` must be a non-empty string model id.`
487
- );
488
- }
489
- model_tier_override[k] = v;
490
- }
491
- return { enabled: Boolean(enabled), auto_apply: Boolean(auto_apply), model_tier_override };
492
- }
493
- function _build_cost_disclosure(d, scope) {
494
- if (!_isDict(d)) {
495
- throw new CouncilConfigError(`\`${scope}\` must be a mapping.`);
496
- }
497
- const mode = _get(d, "mode", "always");
498
- if (!(_isStr(mode) && _VALID_DISCLOSURE_MODES.has(mode))) {
499
- throw new CouncilConfigError(
500
- `${scope}.mode=${_pyRepr(mode)} not in ${_sortedListRepr(_VALID_DISCLOSURE_MODES)}.`
501
- );
502
- }
503
- const threshold = _pyFloat(_get(d, "threshold_usd", 1));
504
- if (threshold < 0) {
505
- throw new CouncilConfigError(
506
- `${scope}.threshold_usd must be >= 0 (got ${_pyRepr(_f(threshold))}).`
507
- );
508
- }
509
- const show_per_member = _get(d, "show_per_member", true);
510
- if (!_isBool(show_per_member)) {
511
- throw new CouncilConfigError(`\`${scope}.show_per_member\` must be a bool.`);
512
- }
513
- return {
514
- mode,
515
- threshold_usd: threshold,
516
- show_per_member: Boolean(show_per_member)
517
- };
518
- }
519
- function _build_debate(d) {
520
- if (!_isDict(d)) {
521
- throw new CouncilConfigError("`debate` must be a mapping.");
522
- }
523
- const cap = _pyFloat(_get(d, "max_cost_usd", 5));
524
- if (cap < 0) {
525
- throw new CouncilConfigError(
526
- `debate.max_cost_usd must be >= 0 (got ${_pyRepr(_f(cap))}; use 0 to disable the cap).`
527
- );
528
- }
529
- const disclosure_raw = _getOr(d, "cost_disclosure", {});
530
- const disclosure = _build_cost_disclosure(
531
- _asDict(disclosure_raw),
532
- "debate.cost_disclosure"
533
- );
534
- return { max_cost_usd: cap, cost_disclosure: disclosure };
535
- }
536
- function _build_decision_replay(d, scope) {
537
- if (!_isDict(d)) {
538
- throw new CouncilConfigError(`\`${scope}\` must be a mapping.`);
539
- }
540
- const enabled = _get(d, "enabled", true);
541
- if (!_isBool(enabled)) {
542
- throw new CouncilConfigError(`\`${scope}.enabled\` must be a bool.`);
543
- }
544
- const include_args = _get(d, "include_member_arguments", true);
545
- if (!_isBool(include_args)) {
546
- throw new CouncilConfigError(
547
- `\`${scope}.include_member_arguments\` must be a bool.`
548
- );
549
- }
550
- return {
551
- enabled: Boolean(enabled),
552
- include_member_arguments: Boolean(include_args)
553
- };
554
- }
555
- function _build_stance_tally(d, scope) {
556
- if (!_isDict(d)) {
557
- throw new CouncilConfigError(`\`${scope}\` must be a mapping.`);
558
- }
559
- const enabled = _get(d, "enabled", false);
560
- if (!_isBool(enabled)) {
561
- throw new CouncilConfigError(`\`${scope}.enabled\` must be a bool.`);
562
- }
563
- return { enabled: Boolean(enabled) };
564
- }
565
- function _build_chairman(d, scope, members) {
566
- if (!_isDict(d)) {
567
- throw new CouncilConfigError(`\`${scope}\` must be a mapping.`);
568
- }
569
- const mode = _get(d, "mode", "host");
570
- if (!(_isStr(mode) && _VALID_CHAIRMAN_MODES.has(mode))) {
571
- throw new CouncilConfigError(
572
- `\`${scope}.mode\`=${_pyRepr(mode)} not in ${_sortedListRepr(_VALID_CHAIRMAN_MODES)}.`
573
- );
574
- }
575
- let member = null;
576
- const rawMember = _get(d, "member", null);
577
- if (rawMember !== null && rawMember !== void 0) {
578
- if (!_isStr(rawMember)) {
579
- throw new CouncilConfigError(`\`${scope}.member\` must be a string.`);
580
- }
581
- member = rawMember;
582
- }
583
- if (mode === "member") {
584
- if (member === null) {
585
- throw new CouncilConfigError(
586
- `\`${scope}.mode\` is 'member' but \`${scope}.member\` is unset.`
587
- );
588
- }
589
- const bound = members.get(member);
590
- if (bound === void 0) {
591
- throw new CouncilConfigError(
592
- `\`${scope}.member\`=${_pyReprStr(member)}: no such member in the \`members\` block.`
593
- );
594
- }
595
- if (!bound.enabled) {
596
- throw new CouncilConfigError(
597
- `\`${scope}.member\`=${_pyReprStr(member)}: member exists but is disabled.`
598
- );
599
- }
600
- }
601
- return { mode, member };
602
- }
603
- function _build_debate_gates(d, scope) {
604
- if (!_isDict(d)) {
605
- throw new CouncilConfigError(`\`${scope}\` must be a mapping.`);
606
- }
607
- const enabled = _get(d, "enabled", false);
608
- if (!_isBool(enabled)) {
609
- throw new CouncilConfigError(`\`${scope}.enabled\` must be a bool.`);
610
- }
611
- return { enabled: Boolean(enabled) };
612
- }
613
- function _build_restate(d, scope) {
614
- if (!_isDict(d)) {
615
- throw new CouncilConfigError(`\`${scope}\` must be a mapping.`);
616
- }
617
- const enabled = _get(d, "enabled", false);
618
- if (!_isBool(enabled)) {
619
- throw new CouncilConfigError(`\`${scope}.enabled\` must be a bool.`);
620
- }
621
- return { enabled: Boolean(enabled) };
622
- }
623
- var _VALID_CRITIC_PROTOCOLS = /* @__PURE__ */ new Set(["legacy", "load_bearing"]);
624
- function _build_critic_protocol(v) {
625
- if (v === void 0 || v === null) return "legacy";
626
- if (!(_isStr(v) && _VALID_CRITIC_PROTOCOLS.has(v))) {
627
- throw new CouncilConfigError(
628
- `\`critic_protocol\`=${_pyRepr(v)} not in ${_sortedListRepr(_VALID_CRITIC_PROTOCOLS)}.`
629
- );
630
- }
631
- return v;
632
- }
633
- var _VALID_RESOLUTION_MODES = /* @__PURE__ */ new Set([
634
- "agent",
635
- "council",
636
- "user"
637
- ]);
638
- var _IMPACT_CLASSES = [
639
- "trivial",
640
- "low_impact",
641
- "medium_impact",
642
- "high_impact",
643
- "user_required"
644
- ];
645
- var _LOCKED_IMPACT_CLASSES = /* @__PURE__ */ new Set([
646
- "high_impact",
647
- "user_required"
648
- ]);
649
- var _DEFAULT_RESOLUTION_MODES = {
650
- trivial: "agent",
651
- low_impact: "agent",
652
- medium_impact: "council",
653
- high_impact: "user",
654
- user_required: "user"
655
- };
656
- function _build_decision_resolution(d) {
657
- if (!_isDict(d)) {
658
- throw new CouncilConfigError("`decision_resolution` must be a mapping.");
659
- }
660
- const enabled = _get(d, "enabled", true);
661
- if (!_isBool(enabled)) {
662
- throw new CouncilConfigError(
663
- "`decision_resolution.enabled` must be a bool."
664
- );
665
- }
666
- const classes_raw = _getOr(d, "classes", {});
667
- if (!_isDict(classes_raw)) {
668
- throw new CouncilConfigError(
669
- "`decision_resolution.classes` must be a mapping."
670
- );
671
- }
672
- const classes = /* @__PURE__ */ new Map();
673
- for (const cls of _IMPACT_CLASSES) {
674
- const entry_raw = _orEmpty(classes_raw[cls] ?? null);
675
- if (!_isDict(entry_raw)) {
676
- throw new CouncilConfigError(
677
- `\`decision_resolution.classes.${cls}\` must be a mapping.`
678
- );
679
- }
680
- const mode = _get(entry_raw, "mode", _DEFAULT_RESOLUTION_MODES[cls]);
681
- if (!(_isStr(mode) && _VALID_RESOLUTION_MODES.has(mode))) {
682
- throw new CouncilConfigError(
683
- `decision_resolution.classes.${cls}.mode=${_pyRepr(mode)} not in ${_sortedListRepr(_VALID_RESOLUTION_MODES)}.`
684
- );
685
- }
686
- if (_LOCKED_IMPACT_CLASSES.has(cls) && mode !== "user") {
687
- throw new CouncilConfigError(
688
- `decision_resolution.classes.${cls}.mode=${_pyRepr(mode)}: class \`${cls}\` is LOCKED to \`user\` (Iron Law) \u2014 high-impact and user-required decisions never bypass the user.`
689
- );
690
- }
691
- if (_LOCKED_IMPACT_CLASSES.has(cls) && "dispatch" in entry_raw) {
692
- throw new CouncilConfigError(
693
- `decision_resolution.classes.${cls}.dispatch=${_pyRepr(entry_raw.dispatch)}: dispatch is not configurable for high-impact / user-required decisions \u2014 always full council.`
694
- );
695
- }
696
- const threshold = _pyFloat(_get(entry_raw, "confidence_threshold", 0.6));
697
- if (!(0 <= threshold && threshold <= 1)) {
698
- throw new CouncilConfigError(
699
- `decision_resolution.classes.${cls}.confidence_threshold must be in [0.0, 1.0] (got ${_pyRepr(_f(threshold))}).`
700
- );
701
- }
702
- classes.set(cls, { mode, confidence_threshold: threshold });
703
- }
704
- const fast_path_raw = _getOr(d, "fast_path", {});
705
- if (!_isDict(fast_path_raw)) {
706
- throw new CouncilConfigError(
707
- "`decision_resolution.fast_path` must be a mapping."
708
- );
709
- }
710
- const fast_path = _build_fast_path(_asDict(fast_path_raw));
711
- return { enabled: Boolean(enabled), classes, fast_path };
712
- }
713
- function _build_fast_path(d) {
714
- const max_members = _get(d, "max_members", 2);
715
- if (!_isInt(max_members) || _isBool(max_members)) {
716
- throw new CouncilConfigError(
717
- `decision_resolution.fast_path.max_members must be an int (got ${_pyTypeName(max_members)}).`
718
- );
719
- }
720
- if (max_members < 1 || max_members > 2) {
721
- throw new CouncilConfigError(
722
- `decision_resolution.fast_path.max_members must be 1 or 2 (got ${max_members}). Fast-path is by design a 1-2 member lookup \u2014 wider fan-out belongs in the standard council path.`
723
- );
724
- }
725
- const max_rounds = _get(d, "max_rounds", 1);
726
- if (max_rounds !== 1) {
727
- throw new CouncilConfigError(
728
- `decision_resolution.fast_path.max_rounds is LOCKED to 1 (got ${_pyRepr(max_rounds)}). Multi-round fast-paths defeat the purpose \u2014 escalate to standard council instead.`
729
- );
730
- }
731
- const max_tokens = _get(d, "max_tokens", 2500);
732
- if (!_isInt(max_tokens) || _isBool(max_tokens) || max_tokens <= 0) {
733
- throw new CouncilConfigError(
734
- `decision_resolution.fast_path.max_tokens must be a positive int (got ${_pyRepr(max_tokens)}).`
735
- );
736
- }
737
- const max_cost_raw = _get(d, "max_cost_usd", 0.05);
738
- if (_isBool(max_cost_raw) || !_isNumber(max_cost_raw)) {
739
- throw new CouncilConfigError(
740
- `decision_resolution.fast_path.max_cost_usd must be a number (got ${_pyTypeName(max_cost_raw)}).`
741
- );
742
- }
743
- const max_cost = _pyFloat(max_cost_raw);
744
- if (max_cost <= 0) {
745
- throw new CouncilConfigError(
746
- `decision_resolution.fast_path.max_cost_usd must be > 0 (got ${_pyRepr(_f(max_cost))}).`
747
- );
748
- }
749
- const fuzzy_match = _build_fuzzy_match(_asDict(_getOr(d, "fuzzy_match", {})));
750
- return {
751
- max_members,
752
- max_rounds: 1,
753
- max_tokens,
754
- max_cost_usd: max_cost,
755
- fuzzy_match
756
- };
757
- }
758
- function _build_fuzzy_match(d) {
759
- if (!_isDict(d)) {
760
- throw new CouncilConfigError(
761
- "decision_resolution.fast_path.fuzzy_match must be a mapping."
762
- );
763
- }
764
- const enabled = _get(d, "enabled", false);
765
- if (!_isBool(enabled)) {
766
- throw new CouncilConfigError(
767
- `decision_resolution.fast_path.fuzzy_match.enabled must be a bool (got ${_pyTypeName(enabled)}).`
768
- );
769
- }
770
- const threshold_raw = _get(d, "threshold", 0.92);
771
- if (_isBool(threshold_raw) || !_isNumber(threshold_raw)) {
772
- throw new CouncilConfigError(
773
- `decision_resolution.fast_path.fuzzy_match.threshold must be a number (got ${_pyTypeName(threshold_raw)}).`
774
- );
775
- }
776
- const threshold = _pyFloat(threshold_raw);
777
- if (!(0 < threshold && threshold <= 1)) {
778
- throw new CouncilConfigError(
779
- `decision_resolution.fast_path.fuzzy_match.threshold must be in (0.0, 1.0] (got ${_pyRepr(_f(threshold))}).`
780
- );
781
- }
782
- return { enabled, threshold };
783
- }
784
- function _build_lens_overrides(d) {
785
- if (!_isDict(d)) {
786
- throw new CouncilConfigError("`lenses` must be a mapping.");
787
- }
788
- const nc_overrides = /* @__PURE__ */ new Map();
789
- const nc_user_overrides = /* @__PURE__ */ new Map();
790
- const md_overrides = /* @__PURE__ */ new Map();
791
- const cd_overrides = /* @__PURE__ */ new Map();
792
- const dr_overrides = /* @__PURE__ */ new Map();
793
- for (const [lens_name, lens_cfg] of Object.entries(d)) {
794
- if (!_isDict(lens_cfg)) {
795
- throw new CouncilConfigError(
796
- `\`lenses.${lens_name}\` must be a mapping.`
797
- );
798
- }
799
- const nc_block = lens_cfg.necessity_classifier;
800
- if (nc_block !== void 0 && nc_block !== null) {
801
- if (!_isDict(nc_block)) {
802
- throw new CouncilConfigError(
803
- `\`lenses.${lens_name}.necessity_classifier\` must be a mapping.`
804
- );
805
- }
806
- const mode = nc_block.mode;
807
- if (mode !== void 0 && mode !== null) {
808
- if (!(_isStr(mode) && _VALID_NECESSITY_MODES.has(mode))) {
809
- throw new CouncilConfigError(
810
- `lenses.${lens_name}.necessity_classifier.mode=${_pyRepr(mode)} not in ${_sortedListRepr(_VALID_NECESSITY_MODES)}.`
811
- );
812
- }
813
- nc_overrides.set(lens_name, mode);
814
- }
815
- const user_mode = nc_block.user_explicit_mode;
816
- if (user_mode !== void 0 && user_mode !== null) {
817
- if (!(_isStr(user_mode) && _VALID_NECESSITY_MODES.has(user_mode))) {
818
- throw new CouncilConfigError(
819
- `lenses.${lens_name}.necessity_classifier.user_explicit_mode=${_pyRepr(user_mode)} not in ${_sortedListRepr(_VALID_NECESSITY_MODES)}.`
820
- );
821
- }
822
- nc_user_overrides.set(lens_name, user_mode);
823
- }
824
- }
825
- const md_block = lens_cfg.model_downgrade;
826
- if (md_block !== void 0 && md_block !== null) {
827
- if (!_isDict(md_block)) {
828
- throw new CouncilConfigError(
829
- `\`lenses.${lens_name}.model_downgrade\` must be a mapping.`
830
- );
831
- }
832
- const md_enabled = _get(md_block, "enabled", true);
833
- if (!_isBool(md_enabled)) {
834
- throw new CouncilConfigError(
835
- `\`lenses.${lens_name}.model_downgrade.enabled\` must be a bool.`
836
- );
837
- }
838
- const md_auto = _get(md_block, "auto_apply", false);
839
- if (!_isBool(md_auto)) {
840
- throw new CouncilConfigError(
841
- `\`lenses.${lens_name}.model_downgrade.auto_apply\` must be a bool.`
842
- );
843
- }
844
- md_overrides.set(lens_name, {
845
- enabled: Boolean(md_enabled),
846
- auto_apply: Boolean(md_auto),
847
- model_tier_override: {}
848
- });
849
- }
850
- const cd_block = lens_cfg.cost_disclosure;
851
- if (cd_block !== void 0 && cd_block !== null) {
852
- cd_overrides.set(
853
- lens_name,
854
- _build_cost_disclosure(
855
- _asDict(cd_block),
856
- `lenses.${lens_name}.cost_disclosure`
857
- )
858
- );
859
- }
860
- const dr_block = lens_cfg.decision_replay;
861
- if (dr_block !== void 0 && dr_block !== null) {
862
- dr_overrides.set(
863
- lens_name,
864
- _build_decision_replay(
865
- _asDict(dr_block),
866
- `lenses.${lens_name}.decision_replay`
867
- )
868
- );
869
- }
870
- }
871
- return {
872
- necessity_classifier_mode: nc_overrides,
873
- necessity_classifier_user_explicit_mode: nc_user_overrides,
874
- model_downgrade: md_overrides,
875
- cost_disclosure: cd_overrides,
876
- decision_replay: dr_overrides
877
- };
878
- }
879
- function _build_consensus_scoring(d) {
880
- if (!_isDict(d)) {
881
- throw new CouncilConfigError("`consensus_scoring` must be a mapping.");
882
- }
883
- const strong = _pyFloat(_get(d, "strong_threshold", 0.7));
884
- const minority = _pyFloat(_get(d, "minority_threshold", 0.4));
885
- if (!(0 <= minority && minority <= strong && strong <= 1)) {
886
- throw new CouncilConfigError(
887
- `consensus_scoring thresholds broken: require 0 <= minority (${_pyNum(minority)}) <= strong (${_pyNum(strong)}) <= 1.`
888
- );
889
- }
890
- const lenses_raw = _get(d, "lenses", ["analysis"]);
891
- if (!_isList(lenses_raw) || !lenses_raw.every((x) => _isStr(x))) {
892
- throw new CouncilConfigError(
893
- "`consensus_scoring.lenses` must be a list of strings."
894
- );
895
- }
896
- return {
897
- enabled: Boolean(_pyTruthy(_get(d, "enabled", false))),
898
- strong_threshold: strong,
899
- minority_threshold: minority,
900
- lenses: lenses_raw
901
- };
902
- }
903
- var _VALID_MEMBER_MODES = /* @__PURE__ */ new Set(["cli", "api"]);
904
- function _build_defaults(d, ignored = []) {
905
- if (!_isDict(d)) {
906
- throw new CouncilConfigError("`defaults` must be a mapping.");
907
- }
908
- if (_get(d, "mode", null) !== null) {
909
- ignored.push("defaults.mode");
910
- }
911
- const mode = "auto";
912
- const member_mode = _get(d, "member_mode", "cli");
913
- if (!(_isStr(member_mode) && _VALID_MEMBER_MODES.has(member_mode))) {
914
- throw new CouncilConfigError(
915
- `defaults.member_mode=${_pyRepr(member_mode)} not in ${_sortedListRepr(_VALID_MEMBER_MODES)}.`
916
- );
917
- }
918
- return {
919
- mode,
920
- member_mode,
921
- min_rounds: _pyInt(_get(d, "min_rounds", 2)),
922
- deep_min_rounds: _pyInt(_get(d, "deep_min_rounds", 3)),
923
- max_output_tokens: _pyInt(_get(d, "max_output_tokens", 0)),
924
- session_retention_days: _pyInt(_get(d, "session_retention_days", 7)),
925
- debate_max_rounds: _pyInt(_get(d, "debate_max_rounds", 4))
926
- };
927
- }
928
- var _VALID_DISPATCH_MODES = /* @__PURE__ */ new Set(["full", "single"]);
929
- function _build_routing(d, members) {
930
- if (!_isDict(d)) {
931
- throw new CouncilConfigError("`routing` must be a mapping.");
932
- }
933
- const chain_raw = _get(d, "solo_member_fallback_chain", []);
934
- if (!_isList(chain_raw)) {
935
- throw new CouncilConfigError(
936
- `\`routing.solo_member_fallback_chain\` must be a list (got ${_pyTypeName(chain_raw)}).`
937
- );
938
- }
939
- const chain = [];
940
- const seen = /* @__PURE__ */ new Set();
941
- for (let idx = 0; idx < chain_raw.length; idx++) {
942
- const entry = chain_raw[idx];
943
- if (!_isStr(entry) || entry.trim() === "") {
944
- throw new CouncilConfigError(
945
- `routing.solo_member_fallback_chain[${idx}]: each entry must be a non-empty string (got ${_pyRepr(entry)}).`
946
- );
947
- }
948
- if (seen.has(entry)) {
949
- throw new CouncilConfigError(
950
- `routing.solo_member_fallback_chain[${idx}]: duplicate entry ${_pyReprStr(entry)} \u2014 chain order must be unique.`
951
- );
952
- }
953
- if (!members.has(entry)) {
954
- throw new CouncilConfigError(
955
- `routing.solo_member_fallback_chain[${idx}]=${_pyReprStr(entry)}: no such member in the \`members\` block.`
956
- );
957
- }
958
- seen.add(entry);
959
- chain.push(entry);
960
- }
961
- const timeout_raw = _get(d, "auth_check_timeout_seconds", 3);
962
- if (!_isInt(timeout_raw) || _isBool(timeout_raw) || !(1 <= timeout_raw && timeout_raw <= 30)) {
963
- throw new CouncilConfigError(
964
- `routing.auth_check_timeout_seconds must be an int in [1, 30] (got ${_pyRepr(timeout_raw)}).`
965
- );
966
- }
967
- return {
968
- solo_member_fallback_chain: chain,
969
- auth_check_timeout_seconds: timeout_raw
970
- };
971
- }
972
- function _build_low_impact(d, members, routing) {
973
- if (!_isDict(d)) {
974
- throw new CouncilConfigError("`low_impact` must be a mapping.");
975
- }
976
- const dispatch = _get(d, "dispatch", "full");
977
- if (!(_isStr(dispatch) && _VALID_DISPATCH_MODES.has(dispatch))) {
978
- throw new CouncilConfigError(
979
- `low_impact.dispatch=${_pyRepr(dispatch)} not in ${_sortedListRepr(_VALID_DISPATCH_MODES)}.`
980
- );
981
- }
982
- if (dispatch === "single") {
983
- const enabled_in_chain = routing.solo_member_fallback_chain.filter(
984
- (name) => members.get(name) !== void 0 && members.get(name).enabled
985
- );
986
- if (enabled_in_chain.length === 0) {
987
- throw new CouncilConfigError(
988
- `low_impact.dispatch='single' requires at least one enabled member in routing.solo_member_fallback_chain (chain=${_pyRepr([...routing.solo_member_fallback_chain])}). Enable a chain member or set dispatch back to 'full'.`
989
- );
990
- }
991
- }
992
- const shadow_raw = _get(d, "shadow_sample_rate", 0.1);
993
- if (_isBool(shadow_raw) || !_isNumber(shadow_raw)) {
994
- throw new CouncilConfigError(
995
- `low_impact.shadow_sample_rate must be a number (got ${_pyTypeName(shadow_raw)}).`
996
- );
997
- }
998
- const shadow = _pyFloat(shadow_raw);
999
- if (!(0 <= shadow && shadow <= 1)) {
1000
- throw new CouncilConfigError(
1001
- `low_impact.shadow_sample_rate must be in [0.0, 1.0] (got ${_pyRepr(_f(shadow))}).`
1002
- );
1003
- }
1004
- const floor_raw = _get(d, "solo_confidence_floor", 0.7);
1005
- if (_isBool(floor_raw) || !_isNumber(floor_raw)) {
1006
- throw new CouncilConfigError(
1007
- `low_impact.solo_confidence_floor must be a number (got ${_pyTypeName(floor_raw)}).`
1008
- );
1009
- }
1010
- const floor = _pyFloat(floor_raw);
1011
- if (!(0 <= floor && floor <= 1)) {
1012
- throw new CouncilConfigError(
1013
- `low_impact.solo_confidence_floor must be in [0.0, 1.0] (got ${_pyRepr(_f(floor))}).`
1014
- );
1015
- }
1016
- return {
1017
- dispatch,
1018
- shadow_sample_rate: shadow,
1019
- solo_confidence_floor: floor
1020
- };
1021
- }
1022
- function _reject_top_level_locked_dispatch(raw) {
1023
- for (const cls of _LOCKED_IMPACT_CLASSES) {
1024
- const block = raw[cls];
1025
- if (!_isDict(block)) {
1026
- continue;
1027
- }
1028
- if ("dispatch" in block) {
1029
- throw new CouncilConfigError(
1030
- `${cls}.dispatch=${_pyRepr(block.dispatch)}: dispatch is not configurable for high-impact / user-required decisions \u2014 always full council.`
1031
- );
1032
- }
1033
- if ("solo_confidence_floor" in block) {
1034
- throw new CouncilConfigError(
1035
- `${cls}.solo_confidence_floor=${_pyRepr(block.solo_confidence_floor)}: irrelevant on high-impact / user-required classes \u2014 they never dispatch solo. Set on \`low_impact\` instead.`
1036
- );
1037
- }
1038
- }
1039
- }
1040
- function _build_cost_budget(d) {
1041
- if (!_isDict(d)) {
1042
- throw new CouncilConfigError("`cost_budget` must be a mapping.");
1043
- }
1044
- const cb = {
1045
- max_input_tokens: _pyInt(_get(d, "max_input_tokens", 5e5)),
1046
- max_output_tokens: _pyInt(_get(d, "max_output_tokens", 2e5)),
1047
- max_calls: _pyInt(_get(d, "max_calls", 50)),
1048
- max_total_usd: _pyFloat(_get(d, "max_total_usd", 20)),
1049
- daily_limit_usd: _pyFloat(_get(d, "daily_limit_usd", 0))
1050
- };
1051
- const fields = [
1052
- ["max_input_tokens", false],
1053
- ["max_output_tokens", false],
1054
- ["max_calls", false],
1055
- ["max_total_usd", true],
1056
- ["daily_limit_usd", true]
1057
- ];
1058
- for (const [fname, isFloat] of fields) {
1059
- const val = cb[fname];
1060
- if (val < 0) {
1061
- throw new CouncilConfigError(
1062
- `cost_budget.${fname} must be >= 0 (got ${isFloat ? _pyRepr(_f(val)) : _pyRepr(val)}).`
1063
- );
1064
- }
1065
- }
1066
- return cb;
1067
- }
1068
- function _build_member(name, cfg, default_mode = "auto", ignored = []) {
1069
- if (!_VALID_PROVIDERS.has(name)) {
1070
- throw new CouncilConfigError(
1071
- `members.${name}: unknown provider; valid: ${_sortedListRepr(_VALID_PROVIDERS)}.`
1072
- );
1073
- }
1074
- const member_enabled = Boolean(_pyTruthy(_get(cfg, "enabled", false)));
1075
- const modelVal = _get(cfg, "model", null);
1076
- const model = _pyTruthy(modelVal) ? modelVal : "";
1077
- const api_key_ref_raw = _get(cfg, "api_key_ref", null);
1078
- const api_key_ref = api_key_ref_raw === void 0 ? null : api_key_ref_raw;
1079
- if (_get(cfg, "mode", null) !== null) {
1080
- ignored.push(`members.${name}.mode`);
1081
- }
1082
- const member_mode = null;
1083
- const effective_mode = default_mode;
1084
- const binary_raw = _get(cfg, "binary", null);
1085
- const binary = binary_raw === void 0 ? null : binary_raw;
1086
- if (binary !== null) {
1087
- if (!_isStr(binary) || binary.trim() === "") {
1088
- throw new CouncilConfigError(
1089
- `members.${name}.binary must be a non-empty string when set.`
1090
- );
1091
- }
1092
- if (effective_mode !== "cli" && effective_mode !== "auto") {
1093
- throw new CouncilConfigError(
1094
- `members.${name}.binary is only valid when the member's effective mode is 'cli' or 'auto' (got ${_pyRepr(effective_mode)}). Set \`mode: cli\` on the member or \`defaults.mode: cli\` to use this field.`
1095
- );
1096
- }
1097
- }
1098
- if (member_enabled) {
1099
- if (!model) {
1100
- throw new CouncilConfigError(
1101
- `members.${name}: enabled members require a non-empty \`model\`.`
1102
- );
1103
- }
1104
- if (effective_mode === "api" && !_pyTruthy(api_key_ref)) {
1105
- throw new CouncilConfigError(
1106
- `members.${name}: enabled api-mode members require an \`api_key_ref\`.`
1107
- );
1108
- }
1109
- }
1110
- if (api_key_ref !== null) {
1111
- _validate_api_key_ref(`members.${name}`, api_key_ref);
1112
- }
1113
- const ladder_raw = _getOr(cfg, "model_ladder", []);
1114
- if (!_isList(ladder_raw)) {
1115
- throw new CouncilConfigError(
1116
- `members.${name}.model_ladder must be a list (got ${_pyTypeName(ladder_raw)}).`
1117
- );
1118
- }
1119
- let ladder = [];
1120
- if (ladder_raw.length > 0) {
1121
- const entries = [];
1122
- for (const entry of ladder_raw) {
1123
- if (!_isStr(entry) || entry.trim() === "") {
1124
- throw new CouncilConfigError(
1125
- `members.${name}.model_ladder entries must be non-empty strings (got ${_pyRepr(entry)}).`
1126
- );
1127
- }
1128
- entries.push(entry);
1129
- }
1130
- if (member_enabled && model && !entries.includes(model)) {
1131
- throw new CouncilConfigError(
1132
- `members.${name}.model_ladder must include the active \`model\` (${_pyReprStr(model)}); got ${_pyRepr(entries)}.`
1133
- );
1134
- }
1135
- ladder = entries;
1136
- }
1137
- const participate_raw = _get(cfg, "participate_low_impact", false);
1138
- if (!_isBool(participate_raw)) {
1139
- throw new CouncilConfigError(
1140
- `members.${name}.participate_low_impact must be a bool (got ${_pyTypeName(participate_raw)}).`
1141
- );
1142
- }
1143
- const tier_raw = _get(cfg, "tier", null);
1144
- let tier = null;
1145
- if (tier_raw !== null && tier_raw !== void 0) {
1146
- if (typeof tier_raw !== "number" || !Number.isInteger(tier_raw) || tier_raw < 1) {
1147
- throw new CouncilConfigError(
1148
- `members.${name}.tier must be an integer >= 1 when set (got ${_pyRepr(tier_raw)}).`
1149
- );
1150
- }
1151
- tier = tier_raw;
1152
- }
1153
- const prompt_cache_raw = _get(cfg, "prompt_cache", null);
1154
- let prompt_cache_ttl = "5m";
1155
- if (prompt_cache_raw !== null && prompt_cache_raw !== void 0 && !_isBool(prompt_cache_raw)) {
1156
- if (!_isDict(prompt_cache_raw)) {
1157
- throw new CouncilConfigError(
1158
- `members.${name}.prompt_cache must be a bool or a mapping (got ${_pyTypeName(prompt_cache_raw)}).`
1159
- );
1160
- }
1161
- const ttl_raw = _get(prompt_cache_raw, "ttl", "5m");
1162
- if (ttl_raw !== "5m" && ttl_raw !== "1h") {
1163
- throw new CouncilConfigError(
1164
- `members.${name}.prompt_cache.ttl must be '5m' or '1h' (got ${_pyRepr(ttl_raw)}). '5m' remains the default until a pre-registered 30-debate gap sample clears 40% at >=5-minute inter-round gaps.`
1165
- );
1166
- }
1167
- prompt_cache_ttl = ttl_raw;
1168
- }
1169
- return {
1170
- name,
1171
- enabled: member_enabled,
1172
- model,
1173
- api_key_ref: api_key_ref ?? null,
1174
- mode: member_mode ?? null,
1175
- binary: binary ?? null,
1176
- model_ladder: ladder,
1177
- participate_low_impact: participate_raw,
1178
- tier,
1179
- prompt_cache_ttl
1180
- };
1181
- }
1182
- var _DEFAULT_CLI_CALLS_PER_DAY = 50;
1183
- function _build_cli_call_budget(d) {
1184
- if (!_isDict(d)) {
1185
- throw new CouncilConfigError("`cli_call_budget` must be a mapping.");
1186
- }
1187
- const raw_caps = _getOr(d, "max_calls_per_day", {});
1188
- if (!_isDict(raw_caps)) {
1189
- throw new CouncilConfigError(
1190
- "`cli_call_budget.max_calls_per_day` must be a mapping."
1191
- );
1192
- }
1193
- const caps = /* @__PURE__ */ new Map();
1194
- for (const provider of _VALID_PROVIDERS) {
1195
- caps.set(provider, _DEFAULT_CLI_CALLS_PER_DAY);
1196
- }
1197
- for (const [provider, value] of Object.entries(raw_caps)) {
1198
- if (!_VALID_PROVIDERS.has(provider)) {
1199
- throw new CouncilConfigError(
1200
- `cli_call_budget.max_calls_per_day.${provider}: unknown provider; valid: ${_sortedListRepr(_VALID_PROVIDERS)}.`
1201
- );
1202
- }
1203
- if (!_isInt(value) || _isBool(value) || value < 0) {
1204
- throw new CouncilConfigError(
1205
- `cli_call_budget.max_calls_per_day.${provider} must be a non-negative integer (got ${_pyRepr(value)}).`
1206
- );
1207
- }
1208
- caps.set(provider, value);
1209
- }
1210
- const warn_at_raw = _get(d, "warn_at", 0.8);
1211
- if (_isBool(warn_at_raw) || !_isNumber(warn_at_raw)) {
1212
- throw new CouncilConfigError(
1213
- `cli_call_budget.warn_at must be a number in [0.0, 1.0] (got ${_pyRepr(warn_at_raw)}).`
1214
- );
1215
- }
1216
- const warn_at = _pyFloat(warn_at_raw);
1217
- if (!(0 <= warn_at && warn_at <= 1)) {
1218
- throw new CouncilConfigError(
1219
- `cli_call_budget.warn_at must be in [0.0, 1.0] (got ${_pyNum(warn_at)}).`
1220
- );
1221
- }
1222
- return { max_calls_per_day: caps, warn_at };
1223
- }
1224
- function _build_quorum(raw) {
1225
- if (raw === "majority") {
1226
- return "majority";
1227
- }
1228
- if (_isInt(raw) && !_isBool(raw) && raw >= 1) {
1229
- return raw;
1230
- }
1231
- throw new CouncilConfigError(
1232
- `\`quorum\`=${_pyRepr(raw)} must be 'majority' or an integer >= 1 (got ${_pyTypeName(raw)}).`
1233
- );
1234
- }
1235
- function _build_quorum_min_present(raw) {
1236
- if (_isInt(raw) && raw >= 1) {
1237
- return raw;
1238
- }
1239
- throw new CouncilConfigError(
1240
- `\`quorum_min_present\`=${_pyRepr(raw)} must be an integer >= 1 (got ${_pyTypeName(raw)}).`
1241
- );
1242
- }
1243
- function _build_advisor(name, cfg) {
1244
- if (!_isDict(cfg)) {
1245
- throw new CouncilConfigError(`advisors.${name}: must be a mapping.`);
1246
- }
1247
- const member = _get(cfg, "member", null);
1248
- if (!(_isStr(member) && _VALID_PROVIDERS.has(member))) {
1249
- throw new CouncilConfigError(
1250
- `advisors.${name}.member=${_pyRepr(member)} not a valid provider; valid: ${_sortedListRepr(_VALID_PROVIDERS)}.`
1251
- );
1252
- }
1253
- const personaVal = _get(cfg, "persona", null);
1254
- const persona = _pyTruthy(personaVal) ? personaVal : `personas/advisors/${name}.md`;
1255
- const modelVal = _get(cfg, "model", null);
1256
- const model = modelVal === void 0 ? null : modelVal;
1257
- if (model !== null && !_isStr(model)) {
1258
- throw new CouncilConfigError(
1259
- `advisors.${name}.model must be a string when set.`
1260
- );
1261
- }
1262
- return {
1263
- name,
1264
- enabled: Boolean(_pyTruthy(_get(cfg, "enabled", false))),
1265
- member,
1266
- persona,
1267
- model: model ?? null
1268
- };
1269
- }
1270
- function _validate_api_key_ref(scope, ref) {
1271
- if (!_isStr(ref) || ref === "") {
1272
- throw new CouncilConfigError(`${scope}.api_key_ref must be a non-empty string.`);
1273
- }
1274
- if (_RAW_KEY_PREFIXES.some((prefix) => ref.startsWith(prefix))) {
1275
- throw new CouncilConfigError(
1276
- `${scope}.api_key_ref looks like a raw API key. Use \`file:<path>\` (0600) or \`env:<VAR>\` \u2014 never inline secrets.`
1277
- );
1278
- }
1279
- if (ref.startsWith("file:")) {
1280
- if (ref.slice("file:".length).trim() === "") {
1281
- throw new CouncilConfigError(`${scope}.api_key_ref \`file:\` ref missing path.`);
1282
- }
1283
- return;
1284
- }
1285
- if (ref.startsWith("env:")) {
1286
- if (ref.slice("env:".length).trim() === "") {
1287
- throw new CouncilConfigError(
1288
- `${scope}.api_key_ref \`env:\` ref missing variable name.`
1289
- );
1290
- }
1291
- return;
1292
- }
1293
- throw new CouncilConfigError(
1294
- `${scope}.api_key_ref must start with \`file:\` or \`env:\` (got ${_pyRepr(ref)}).`
1295
- );
1296
- }
1297
- function resolve_api_key(ref, scope = "api_key_ref") {
1298
- _validate_api_key_ref(scope, ref);
1299
- if (ref.startsWith("env:")) {
1300
- const variable = ref.slice("env:".length).trim();
1301
- if (variable === "") {
1302
- throw new CouncilConfigError(`${scope}: \`env:\` ref missing variable name.`);
1303
- }
1304
- const value2 = (process.env[variable] ?? "").trim();
1305
- if (value2 === "") {
1306
- throw new CouncilConfigError(
1307
- `${scope}: env var ${_pyReprStr(variable)} is unset or empty.`
1308
- );
1309
- }
1310
- return value2;
1311
- }
1312
- const spec = ref.slice("file:".length).trim();
1313
- if (spec === "") {
1314
- throw new CouncilConfigError(`${scope}: \`file:\` ref missing path.`);
1315
- }
1316
- let resolved = _expanduser(spec);
1317
- if (!_isAbsoluteLikePython(resolved)) {
1318
- const found = resolve_with_fallback(spec);
1319
- if (found === null) {
1320
- const target = write_target(spec);
1321
- throw new CouncilConfigError(
1322
- `${scope}: key file not found at ${target} (or legacy fallback).`
1323
- );
1324
- }
1325
- resolved = found;
1326
- }
1327
- if (!fs.existsSync(resolved)) {
1328
- throw new CouncilConfigError(`${scope}: key file does not exist at ${resolved}.`);
1329
- }
1330
- const mode = fs.statSync(resolved).mode & 4095;
1331
- if (mode !== 384) {
1332
- throw new CouncilConfigError(
1333
- `${scope}: unsafe permissions on ${resolved}: got ${_pyOct(mode)}, expected 0o600. Fix: chmod 600 ${resolved}`
1334
- );
1335
- }
1336
- const value = fs.readFileSync(resolved, "utf-8").trim();
1337
- if (value === "") {
1338
- throw new CouncilConfigError(`${scope}: key file at ${resolved} is empty.`);
1339
- }
1340
- return value;
1341
- }
1342
- function _pyNum(value) {
1343
- return _pyReprFloat(value);
1344
- }
1345
- function _pyTruthy(value) {
1346
- if (value === null || value === void 0) {
1347
- return false;
1348
- }
1349
- if (typeof value === "boolean") {
1350
- return value;
1351
- }
1352
- if (typeof value === "number") {
1353
- return value !== 0;
1354
- }
1355
- if (typeof value === "string") {
1356
- return value !== "";
1357
- }
1358
- if (Array.isArray(value)) {
1359
- return value.length > 0;
1360
- }
1361
- return Object.keys(value).length > 0;
1362
- }
1363
-
1364
- // src/scripts/_lib/cc_transcript.ts
1365
- import * as fs2 from "node:fs";
1366
- import * as path2 from "node:path";
1367
- import { homedir as homedir2 } from "node:os";
1368
- var DEFAULT_PROJECTS_ROOT = path2.join(homedir2(), ".claude", "projects");
1369
- function projectStoreSlug(cwd) {
1370
- return cwd.replace(/[^A-Za-z0-9-]/g, "-");
1371
- }
1372
- function billableInputTokens(u) {
1373
- return u.input_tokens + u.cache_read_input_tokens + u.cache_creation_input_tokens;
1374
- }
1375
- function weightedInputUnits(u) {
1376
- const knownSplit = u.ephemeral_5m_input_tokens + u.ephemeral_1h_input_tokens;
1377
- const unaccounted = Math.max(0, u.cache_creation_input_tokens - knownSplit);
1378
- return u.input_tokens * 1 + u.cache_read_input_tokens * 0.1 + (u.ephemeral_5m_input_tokens + unaccounted) * 1.25 + u.ephemeral_1h_input_tokens * 2;
1379
- }
1380
- function numOr0(value) {
1381
- return typeof value === "number" && Number.isFinite(value) ? value : 0;
1382
- }
1383
- function toTokenCounts(raw) {
1384
- const cc = raw?.cache_creation ?? {};
1385
- return {
1386
- input_tokens: numOr0(raw?.input_tokens),
1387
- cache_read_input_tokens: numOr0(raw?.cache_read_input_tokens),
1388
- cache_creation_input_tokens: numOr0(raw?.cache_creation_input_tokens),
1389
- output_tokens: numOr0(raw?.output_tokens),
1390
- ephemeral_5m_input_tokens: numOr0(cc.ephemeral_5m_input_tokens),
1391
- ephemeral_1h_input_tokens: numOr0(cc.ephemeral_1h_input_tokens)
1392
- };
1393
- }
1394
- function classifyBucket(raw) {
1395
- const hasAgentId = typeof raw.agentId === "string" && raw.agentId.length > 0;
1396
- return hasAgentId || raw.isSidechain === true ? "subagent" : "main";
1397
- }
1398
- function dedupKey(raw) {
1399
- const id = typeof raw.message?.id === "string" ? raw.message.id : "";
1400
- const reqId = typeof raw.requestId === "string" ? raw.requestId : "";
1401
- return `${id}\0${reqId}`;
1402
- }
1403
- function parseLine(line) {
1404
- if (line.trim().length === 0) return null;
1405
- try {
1406
- return JSON.parse(line);
1407
- } catch {
1408
- return null;
1409
- }
1410
- }
1411
- function listTranscriptFiles(opts = {}) {
1412
- const root = opts.root ?? DEFAULT_PROJECTS_ROOT;
1413
- const base = opts.projectDir ? path2.isAbsolute(opts.projectDir) ? opts.projectDir : path2.join(root, opts.projectDir) : root;
1414
- if (!fs2.existsSync(base)) return [];
1415
- const cutoffMs = opts.maxAgeDays !== void 0 ? (opts.now ?? /* @__PURE__ */ new Date()).getTime() - opts.maxAgeDays * 24 * 60 * 60 * 1e3 : null;
1416
- const out = [];
1417
- const stack = [base];
1418
- while (stack.length > 0) {
1419
- const dir = stack.pop();
1420
- let entries;
1421
- try {
1422
- entries = fs2.readdirSync(dir, { withFileTypes: true });
1423
- } catch {
1424
- continue;
1425
- }
1426
- for (const entry of entries) {
1427
- const full = path2.join(dir, entry.name);
1428
- if (entry.isDirectory()) {
1429
- stack.push(full);
1430
- continue;
1431
- }
1432
- if (!entry.isFile() || !entry.name.endsWith(".jsonl")) continue;
1433
- if (cutoffMs !== null) {
1434
- let mtimeMs;
1435
- try {
1436
- mtimeMs = fs2.statSync(full).mtimeMs;
1437
- } catch {
1438
- continue;
1439
- }
1440
- if (mtimeMs < cutoffMs) continue;
1441
- }
1442
- out.push(full);
1443
- }
1444
- }
1445
- return out.sort();
1446
- }
1447
- function scanTranscripts(opts = {}) {
1448
- const files = listTranscriptFiles(opts);
1449
- const seen = /* @__PURE__ */ new Set();
1450
- const records = [];
1451
- let totalSeen = 0;
1452
- for (const file of files) {
1453
- let text;
1454
- try {
1455
- text = fs2.readFileSync(file, "utf-8");
1456
- } catch {
1457
- continue;
1458
- }
1459
- for (const line of text.split("\n")) {
1460
- const raw = parseLine(line);
1461
- if (!raw || raw.type !== "assistant" || !raw.message?.usage) continue;
1462
- totalSeen += 1;
1463
- const key = dedupKey(raw);
1464
- if (seen.has(key)) continue;
1465
- seen.add(key);
1466
- records.push({
1467
- bucket: classifyBucket(raw),
1468
- agentId: typeof raw.agentId === "string" ? raw.agentId : null,
1469
- model: typeof raw.message.model === "string" ? raw.message.model : "unknown",
1470
- timestamp: typeof raw.timestamp === "string" ? raw.timestamp : null,
1471
- usage: toTokenCounts(raw.message.usage)
1472
- });
1473
- }
1474
- }
1475
- const dedupedCount = records.length;
1476
- return {
1477
- records,
1478
- totalSeen,
1479
- dedupedCount,
1480
- dedup_ratio: totalSeen > 0 ? (totalSeen - dedupedCount) / totalSeen : 0
1481
- };
1482
- }
1483
- function emptyAggregate(bucket) {
1484
- return {
1485
- bucket,
1486
- calls: 0,
1487
- input_tokens: 0,
1488
- cache_read_input_tokens: 0,
1489
- cache_creation_input_tokens: 0,
1490
- output_tokens: 0,
1491
- ephemeral_5m_input_tokens: 0,
1492
- ephemeral_1h_input_tokens: 0,
1493
- read_share: 0,
1494
- weighted_input_units: 0
1495
- };
1496
- }
1497
- function aggregateByBucket(records) {
1498
- const buckets = {
1499
- main: emptyAggregate("main"),
1500
- subagent: emptyAggregate("subagent")
1501
- };
1502
- for (const rec of records) {
1503
- const agg = buckets[rec.bucket];
1504
- agg.calls += 1;
1505
- agg.input_tokens += rec.usage.input_tokens;
1506
- agg.cache_read_input_tokens += rec.usage.cache_read_input_tokens;
1507
- agg.cache_creation_input_tokens += rec.usage.cache_creation_input_tokens;
1508
- agg.output_tokens += rec.usage.output_tokens;
1509
- agg.ephemeral_5m_input_tokens += rec.usage.ephemeral_5m_input_tokens;
1510
- agg.ephemeral_1h_input_tokens += rec.usage.ephemeral_1h_input_tokens;
1511
- }
1512
- for (const bucket of Object.values(buckets)) {
1513
- const billable = billableInputTokens(bucket);
1514
- bucket.read_share = billable > 0 ? bucket.cache_read_input_tokens / billable : 0;
1515
- bucket.weighted_input_units = weightedInputUnits(bucket);
1516
- }
1517
- return buckets;
1518
- }
1519
-
1520
- // src/scripts/_lib/spawn_env.ts
1521
- var DENY_EXACT = /* @__PURE__ */ new Set([
1522
- // dynamic loader
1523
- "LD_PRELOAD",
1524
- "LD_LIBRARY_PATH",
1525
- "LD_AUDIT",
1526
- "DYLD_INSERT_LIBRARIES",
1527
- "DYLD_LIBRARY_PATH",
1528
- "DYLD_FRAMEWORK_PATH",
1529
- "GCONV_PATH",
1530
- // git command-injection
1531
- "GIT_EXTERNAL_DIFF",
1532
- "GIT_SSH",
1533
- "GIT_SSH_COMMAND",
1534
- "GIT_PROXY_COMMAND",
1535
- "GIT_PAGER",
1536
- "PAGER",
1537
- // git config-injection: GIT_ALTERNATE_OBJECT_DIRECTORIES can point git at
1538
- // attacker-controlled object stores; the GIT_CONFIG* family is handled by
1539
- // prefix below (it is a general arbitrary-config-injection primitive).
1540
- "GIT_ALTERNATE_OBJECT_DIRECTORIES",
1541
- // git path-redirection: GIT_DIR / GIT_INDEX_FILE / GIT_NAMESPACE point a
1542
- // child's git at an attacker-controlled repo, index file, or ref namespace
1543
- // — ordinary git ops then read and write attacker state (low severity,
1544
- // same family as GIT_ALTERNATE_OBJECT_DIRECTORIES above).
1545
- "GIT_DIR",
1546
- "GIT_INDEX_FILE",
1547
- "GIT_NAMESPACE",
1548
- // resolver hijack: HOSTALIASES remaps hostnames via an attacker file,
1549
- // redirecting any network fetch the child performs (CWE-427-adjacent).
1550
- "HOSTALIASES",
1551
- // runtime auto-exec hooks
1552
- "NODE_OPTIONS",
1553
- "BASH_ENV",
1554
- "ENV",
1555
- "PYTHONPATH",
1556
- "PYTHONSTARTUP",
1557
- "PYTHONINSPECT",
1558
- "PERL5OPT",
1559
- "PERL5LIB",
1560
- "RUBYOPT",
1561
- // shell field-splitting
1562
- "IFS"
1563
- ]);
1564
- function isDeniedByFamily(name) {
1565
- if (name.startsWith("LD_") || name.startsWith("DYLD_")) return true;
1566
- if (name.startsWith("GIT_") && name.endsWith("_COMMAND")) return true;
1567
- if (name === "GIT_CONFIG" || name.startsWith("GIT_CONFIG_")) return true;
1568
- return false;
1569
- }
1570
- function hardenedSpawnEnv(overrides = {}) {
1571
- const out = {};
1572
- for (const [key, value] of Object.entries(process.env)) {
1573
- if (value === void 0) continue;
1574
- if (DENY_EXACT.has(key)) continue;
1575
- if (isDeniedByFamily(key)) continue;
1576
- out[key] = value;
1577
- }
1578
- for (const [key, value] of Object.entries(overrides)) {
1579
- out[key] = value;
1580
- }
1581
- return out;
1582
- }
1583
-
1584
- export {
1585
- DEFAULT_PROJECTS_ROOT,
1586
- projectStoreSlug,
1587
- billableInputTokens,
1588
- weightedInputUnits,
1589
- scanTranscripts,
1590
- aggregateByBucket,
1591
- hardenedSpawnEnv,
1592
- _RAW_KEY_PREFIXES,
1593
- config_exports
1594
- };