@mrciphersmith/keryx 0.2.43 → 0.2.45

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 (2) hide show
  1. package/dist/cli.js +1191 -650
  2. package/package.json +1 -1
package/dist/cli.js CHANGED
@@ -12095,6 +12095,10 @@ function touchesAgentCredentials(command) {
12095
12095
  const text = command.toLowerCase();
12096
12096
  return CREDENTIAL_MARKERS.some((marker2) => text.includes(marker2));
12097
12097
  }
12098
+ function touchesSacConfirmReview(command) {
12099
+ const text = command.toLowerCase();
12100
+ return SAC_REVIEW_MARKERS.some((marker2) => text.includes(marker2));
12101
+ }
12098
12102
  var CATASTROPHIC_TARGETS, RECURSIVE_FLAG, PRIVILEGE, INTERPRETERS, DOWNLOADERS, CONTAINER_RUNTIMES, PROTECTED_BRANCHES, BLOCK_DEVICE, rulePrivilege = (v) => PRIVILEGE.has(v.cmd), ruleBlockDeviceRedirect = (v) => BLOCK_DEVICE.test(v.raw) && /(?:^|\s)(?:>|>>)\s*\/dev\//.test(v.raw), ruleRm = (v) => v.cmd === "rm" && v.positionals.some(isCatastrophicTarget), ruleDd = (v) => v.cmd === "dd" && v.args.some((x) => /^of=/i.test(x) && BLOCK_DEVICE.test(x)), rulePermissionSweep = (v) => (v.cmd === "chmod" || v.cmd === "chown" || v.cmd === "chgrp") && hasRecursive(v.words) && v.positionals.some(isCatastrophicTarget), ruleHostState = (v) => {
12099
12103
  if (v.cmd === "shutdown" || v.cmd === "reboot" || v.cmd === "halt" || v.cmd === "poweroff")
12100
12104
  return true;
@@ -12115,7 +12119,7 @@ var CATASTROPHIC_TARGETS, RECURSIVE_FLAG, PRIVILEGE, INTERPRETERS, DOWNLOADERS,
12115
12119
  return true;
12116
12120
  const named = after.map((x) => x.split("/").pop()?.toLowerCase() ?? "");
12117
12121
  return named.some((n) => PROTECTED_BRANCHES.includes(n));
12118
- }, RULES3, CREDENTIAL_MARKERS;
12122
+ }, RULES3, CREDENTIAL_MARKERS, SAC_REVIEW_MARKERS;
12119
12123
  var init_command_risk = __esm(() => {
12120
12124
  init_shell_syntax();
12121
12125
  CATASTROPHIC_TARGETS = new Set([
@@ -12180,6 +12184,7 @@ var init_command_risk = __esm(() => {
12180
12184
  ".local/share/keryx",
12181
12185
  ".config/keryx"
12182
12186
  ];
12187
+ SAC_REVIEW_MARKERS = ["confirm-review", "workspace review"];
12183
12188
  });
12184
12189
 
12185
12190
  // src/commands/permission-mode.ts
@@ -12187,11 +12192,11 @@ function isPermissionMode(value) {
12187
12192
  return PERMISSION_MODES.includes(value);
12188
12193
  }
12189
12194
  function resolveApprovalDecision(input2) {
12190
- const { mode, risk, destructive, credentials } = input2;
12195
+ const { mode, risk, destructive, credentials, sacReviewConfirmation } = input2;
12191
12196
  if (risk === "read") {
12192
12197
  return "auto";
12193
12198
  }
12194
- if (credentials) {
12199
+ if (credentials || sacReviewConfirmation) {
12195
12200
  return "ask";
12196
12201
  }
12197
12202
  if (mode === "auto") {
@@ -12208,6 +12213,304 @@ var init_permission_mode = __esm(() => {
12208
12213
  PERMISSION_MODES = ["ask", "trust", "auto"];
12209
12214
  });
12210
12215
 
12216
+ // src/harness/policy/ranks.ts
12217
+ function axesOf(trustMode) {
12218
+ switch (trustMode) {
12219
+ case "read-only":
12220
+ return { authority: "read-only", inputTrust: "vetted" };
12221
+ case "trusted-local":
12222
+ return { authority: "acting", inputTrust: "vetted" };
12223
+ case "untrusted":
12224
+ return { authority: "acting", inputTrust: "unvetted" };
12225
+ default:
12226
+ return;
12227
+ }
12228
+ }
12229
+ function rankOf(map, value) {
12230
+ return Object.prototype.hasOwnProperty.call(map, value) ? map[value] : undefined;
12231
+ }
12232
+ function exceedingAxes(ceiling, candidate) {
12233
+ const low = axesOf(ceiling);
12234
+ const high = axesOf(candidate);
12235
+ if (low === undefined || high === undefined) {
12236
+ return [AUTHORITY_AXIS, INPUT_TRUST_AXIS];
12237
+ }
12238
+ const exceeded = [];
12239
+ if (AUTHORITY_RANK[high.authority] > AUTHORITY_RANK[low.authority]) {
12240
+ exceeded.push(AUTHORITY_AXIS);
12241
+ }
12242
+ if (INPUT_TRUST_RANK[high.inputTrust] > INPUT_TRUST_RANK[low.inputTrust]) {
12243
+ exceeded.push(INPUT_TRUST_AXIS);
12244
+ }
12245
+ return exceeded;
12246
+ }
12247
+ function broadeningAxes(parent, child) {
12248
+ const above = axesOf(parent);
12249
+ const below = axesOf(child);
12250
+ if (above === undefined || below === undefined) {
12251
+ return [AUTHORITY_AXIS, INPUT_TRUST_AXIS];
12252
+ }
12253
+ const broadened = [];
12254
+ if (AUTHORITY_RANK[below.authority] > AUTHORITY_RANK[above.authority]) {
12255
+ broadened.push(AUTHORITY_AXIS);
12256
+ }
12257
+ if (INPUT_TRUST_RANK[below.inputTrust] < INPUT_TRUST_RANK[above.inputTrust]) {
12258
+ broadened.push(INPUT_TRUST_AXIS);
12259
+ }
12260
+ return broadened;
12261
+ }
12262
+ var AUTHORITY_RANK, INPUT_TRUST_RANK, OUTCOME_RANK, ISOLATION_RANK, AUTHORITY_AXIS = "trustMode.authority", INPUT_TRUST_AXIS = "trustMode.inputTrust";
12263
+ var init_ranks = __esm(() => {
12264
+ AUTHORITY_RANK = {
12265
+ "read-only": 0,
12266
+ acting: 1
12267
+ };
12268
+ INPUT_TRUST_RANK = {
12269
+ unvetted: 0,
12270
+ vetted: 1
12271
+ };
12272
+ OUTCOME_RANK = {
12273
+ deny: 0,
12274
+ ask: 1,
12275
+ allow: 2
12276
+ };
12277
+ ISOLATION_RANK = {
12278
+ "not-required": 0,
12279
+ "required-fail-closed": 1
12280
+ };
12281
+ });
12282
+
12283
+ // src/harness/child/isolation.ts
12284
+ function inheritBudget(parentRemaining, childRequest) {
12285
+ if (childRequest.maxRuntimeMs > parentRemaining.maxRuntimeMs) {
12286
+ return {
12287
+ ok: false,
12288
+ reason: `child maxRuntimeMs ${childRequest.maxRuntimeMs} exceeds parent remaining ${parentRemaining.maxRuntimeMs}`
12289
+ };
12290
+ }
12291
+ if (childRequest.maxToolCalls !== undefined) {
12292
+ if (parentRemaining.maxToolCalls === undefined) {
12293
+ return {
12294
+ ok: false,
12295
+ reason: `child requests ${childRequest.maxToolCalls} tool calls but the parent exposes no tool-call budget to inherit`
12296
+ };
12297
+ }
12298
+ if (childRequest.maxToolCalls > parentRemaining.maxToolCalls) {
12299
+ return {
12300
+ ok: false,
12301
+ reason: `child maxToolCalls ${childRequest.maxToolCalls} exceeds parent remaining ${parentRemaining.maxToolCalls}`
12302
+ };
12303
+ }
12304
+ }
12305
+ const reservation = {
12306
+ reservationId: childRequest.reservationId,
12307
+ maxRuntimeMs: childRequest.maxRuntimeMs,
12308
+ ...childRequest.maxToolCalls !== undefined ? { maxToolCalls: childRequest.maxToolCalls } : {}
12309
+ };
12310
+ return { ok: true, reservation };
12311
+ }
12312
+ function isKnownCapability(value) {
12313
+ return CAPABILITY_KEYS.includes(value);
12314
+ }
12315
+ function inheritPolicy(parent, childRequest) {
12316
+ if (axesOf(childRequest.trustMode) === undefined || axesOf(parent.trustMode) === undefined) {
12317
+ return {
12318
+ ok: false,
12319
+ reason: `unrecognized trustMode (child "${childRequest.trustMode}", parent "${parent.trustMode}")`
12320
+ };
12321
+ }
12322
+ const broadened = broadeningAxes(parent.trustMode, childRequest.trustMode);
12323
+ if (broadened.length > 0) {
12324
+ return {
12325
+ ok: false,
12326
+ reason: `child trustMode "${childRequest.trustMode}" is broader than parent "${parent.trustMode}" on ${broadened.join(", ")}`
12327
+ };
12328
+ }
12329
+ for (const capability of CAPABILITY_KEYS) {
12330
+ const childOutcome = childRequest.defaults[capability];
12331
+ const parentOutcome = parent.defaults[capability];
12332
+ const childRank = rankOf(OUTCOME_RANK, childOutcome);
12333
+ const parentRank = rankOf(OUTCOME_RANK, parentOutcome);
12334
+ if (childRank === undefined || parentRank === undefined) {
12335
+ return {
12336
+ ok: false,
12337
+ reason: `unrecognized capability outcome for "${capability}" (child "${childOutcome}", parent "${parentOutcome}")`
12338
+ };
12339
+ }
12340
+ if (childRank > parentRank) {
12341
+ return {
12342
+ ok: false,
12343
+ reason: `child capability "${capability}" default "${childOutcome}" is more permissive than parent "${parentOutcome}"`
12344
+ };
12345
+ }
12346
+ }
12347
+ const childIsolation = rankOf(ISOLATION_RANK, childRequest.requiredControls.isolation);
12348
+ const parentIsolation = rankOf(ISOLATION_RANK, parent.requiredControls.isolation);
12349
+ if (childIsolation === undefined || parentIsolation === undefined) {
12350
+ return {
12351
+ ok: false,
12352
+ reason: `unrecognized isolation control (child "${childRequest.requiredControls.isolation}", parent "${parent.requiredControls.isolation}")`
12353
+ };
12354
+ }
12355
+ if (childIsolation < parentIsolation) {
12356
+ return {
12357
+ ok: false,
12358
+ reason: `child isolation "${childRequest.requiredControls.isolation}" is weaker than parent "${parent.requiredControls.isolation}"`
12359
+ };
12360
+ }
12361
+ return { ok: true, policy: childRequest };
12362
+ }
12363
+ function childProvenance(parent, deps) {
12364
+ const taintIds = [...parent.taintIds ?? [], parent.provenanceId];
12365
+ const provenance = {
12366
+ provenanceId: deps.idSeq(),
12367
+ trustLevel: "derived",
12368
+ sourceKind: parent.sourceKind,
12369
+ taintIds
12370
+ };
12371
+ if (parent.sourceHash !== undefined) {
12372
+ provenance.sourceHash = parent.sourceHash;
12373
+ }
12374
+ return provenance;
12375
+ }
12376
+ var CAPABILITY_KEYS;
12377
+ var init_isolation = __esm(() => {
12378
+ init_ranks();
12379
+ CAPABILITY_KEYS = [
12380
+ "read",
12381
+ "write",
12382
+ "shell",
12383
+ "network",
12384
+ "delegate"
12385
+ ];
12386
+ });
12387
+
12388
+ // src/harness/parallel/scheduler.ts
12389
+ function byTaskId(a, b) {
12390
+ return a.taskId < b.taskId ? -1 : a.taskId > b.taskId ? 1 : 0;
12391
+ }
12392
+ function computeExcluded(tasks) {
12393
+ const excluded = new Set;
12394
+ for (const t of tasks) {
12395
+ if (t.cancelled === true)
12396
+ excluded.add(t.taskId);
12397
+ }
12398
+ let changed = true;
12399
+ while (changed) {
12400
+ changed = false;
12401
+ for (const t of tasks) {
12402
+ if (excluded.has(t.taskId))
12403
+ continue;
12404
+ if (t.dependsOn.some((dep) => excluded.has(dep))) {
12405
+ excluded.add(t.taskId);
12406
+ changed = true;
12407
+ }
12408
+ }
12409
+ }
12410
+ return excluded;
12411
+ }
12412
+ function decrementRemaining(remaining, reservation) {
12413
+ const maxRuntimeMs = remaining.maxRuntimeMs - reservation.maxRuntimeMs;
12414
+ if (remaining.maxToolCalls !== undefined && reservation.maxToolCalls !== undefined) {
12415
+ return { maxRuntimeMs, maxToolCalls: remaining.maxToolCalls - reservation.maxToolCalls };
12416
+ }
12417
+ return remaining.maxToolCalls !== undefined ? { maxRuntimeMs, maxToolCalls: remaining.maxToolCalls } : { maxRuntimeMs };
12418
+ }
12419
+ function planWaves(tasks, config, _deps) {
12420
+ if (!Number.isInteger(config.maxConcurrency) || config.maxConcurrency < 1) {
12421
+ return { ok: false, reason: `maxConcurrency must be a positive integer, got ${config.maxConcurrency}` };
12422
+ }
12423
+ const excluded = computeExcluded(tasks);
12424
+ const universe = tasks.filter((t) => !excluded.has(t.taskId));
12425
+ const scheduled = new Set;
12426
+ const waveTaskLists = [];
12427
+ while (scheduled.size < universe.length) {
12428
+ const ready = universe.filter((t) => !scheduled.has(t.taskId) && t.dependsOn.every((dep) => scheduled.has(dep))).sort(byTaskId);
12429
+ if (ready.length === 0) {
12430
+ return { ok: false, reason: "dependency cycle detected: no ready task set could be formed" };
12431
+ }
12432
+ const waveTasks = ready.slice(0, config.maxConcurrency);
12433
+ for (const t of waveTasks)
12434
+ scheduled.add(t.taskId);
12435
+ waveTaskLists.push(waveTasks);
12436
+ }
12437
+ let remaining = config.parentRemaining;
12438
+ const waves = [];
12439
+ for (const waveTasks of waveTaskLists) {
12440
+ const taskIds = [];
12441
+ const reservations = [];
12442
+ for (const t of waveTasks) {
12443
+ const granted = inheritBudget(remaining, t.budgetRequest);
12444
+ if (!granted.ok) {
12445
+ return { ok: false, reason: granted.reason };
12446
+ }
12447
+ taskIds.push(t.taskId);
12448
+ reservations.push(granted.reservation);
12449
+ remaining = decrementRemaining(remaining, granted.reservation);
12450
+ }
12451
+ waves.push({ taskIds, reservations });
12452
+ }
12453
+ return { ok: true, waves };
12454
+ }
12455
+ async function executeWaves(tasks, waves, deps) {
12456
+ const byTaskId2 = new Map;
12457
+ for (const t of tasks)
12458
+ byTaskId2.set(t.taskId, t);
12459
+ const results = new Map;
12460
+ for (let waveIndex = 0;waveIndex < waves.length; waveIndex++) {
12461
+ const wave = waves[waveIndex];
12462
+ if (wave === undefined)
12463
+ continue;
12464
+ const settled = await Promise.allSettled(wave.taskIds.map((taskId, i) => {
12465
+ const task = byTaskId2.get(taskId);
12466
+ if (task === undefined) {
12467
+ return Promise.reject(new Error(`executeWaves: wave ${waveIndex} references unknown taskId "${taskId}" (not in \`tasks\`)`));
12468
+ }
12469
+ const reservation = wave.reservations[i];
12470
+ if (reservation === undefined) {
12471
+ return Promise.reject(new Error(`executeWaves: wave ${waveIndex} taskIds/reservations length mismatch at index ${i} (malformed Wave)`));
12472
+ }
12473
+ return deps.run(task, reservation);
12474
+ }));
12475
+ const failedTaskIds = [];
12476
+ const causes = [];
12477
+ for (let i = 0;i < settled.length; i++) {
12478
+ const outcome = settled[i];
12479
+ const taskId = wave.taskIds[i];
12480
+ if (outcome === undefined || taskId === undefined)
12481
+ continue;
12482
+ if (outcome.status === "fulfilled") {
12483
+ results.set(taskId, outcome.value);
12484
+ } else {
12485
+ failedTaskIds.push(taskId);
12486
+ causes.push(outcome.reason);
12487
+ }
12488
+ }
12489
+ if (failedTaskIds.length > 0) {
12490
+ throw new WaveExecutionError(waveIndex, failedTaskIds, causes, new Map(results));
12491
+ }
12492
+ }
12493
+ return results;
12494
+ }
12495
+ var WaveExecutionError;
12496
+ var init_scheduler = __esm(() => {
12497
+ init_isolation();
12498
+ WaveExecutionError = class WaveExecutionError extends Error {
12499
+ waveIndex;
12500
+ failedTaskIds;
12501
+ causes;
12502
+ partialResults;
12503
+ constructor(waveIndex, failedTaskIds, causes, partialResults) {
12504
+ super(`executeWaves: wave ${waveIndex} had ${failedTaskIds.length} rejected task(s): ${failedTaskIds.join(", ")}`);
12505
+ this.name = "WaveExecutionError";
12506
+ this.waveIndex = waveIndex;
12507
+ this.failedTaskIds = failedTaskIds;
12508
+ this.causes = causes;
12509
+ this.partialResults = partialResults;
12510
+ }
12511
+ };
12512
+ });
12513
+
12211
12514
  // src/ctx/assembly.ts
12212
12515
  import { createHash as createHash6 } from "crypto";
12213
12516
  import { mkdir as mkdir24, rename as rename2, writeFile as writeFile26 } from "fs/promises";
@@ -17198,7 +17501,7 @@ ${block}
17198
17501
  }
17199
17502
  async function runAgentTurn(io, deps, history, userLine, options = {}) {
17200
17503
  try {
17201
- await runAgentTurnCore(io, deps, history, userLine, options);
17504
+ return await runAgentTurnCore(io, deps, history, userLine, options);
17202
17505
  } finally {
17203
17506
  await closeSlateOnFlowDone(io, deps, options);
17204
17507
  }
@@ -17240,7 +17543,7 @@ async function runAgentTurnCore(io, deps, history, userLine, options = {}) {
17240
17543
  io.onSystem?.(`
17241
17544
  [stopped] Model turn interrupted by user.
17242
17545
  `);
17243
- return;
17546
+ return {};
17244
17547
  }
17245
17548
  const toolByName = new Map(deps.tools.map((t) => [t.definition.name, t]));
17246
17549
  const toolDefs = deps.tools.map((t) => t.definition);
@@ -17349,7 +17652,7 @@ async function runAgentTurnCore(io, deps, history, userLine, options = {}) {
17349
17652
  system(`
17350
17653
  [stopped] Model turn interrupted by user.
17351
17654
  `);
17352
- return;
17655
+ return {};
17353
17656
  }
17354
17657
  if (event.kind === "reasoning_delta") {
17355
17658
  reasoningText += event.text ?? "";
@@ -17396,7 +17699,7 @@ async function runAgentTurnCore(io, deps, history, userLine, options = {}) {
17396
17699
  system(`
17397
17700
  [stopped] Model turn interrupted by user.
17398
17701
  `);
17399
- return;
17702
+ return {};
17400
17703
  }
17401
17704
  system(`
17402
17705
  [error] ${cause instanceof Error ? cause.message : String(cause)}
@@ -17412,10 +17715,10 @@ async function runAgentTurnCore(io, deps, history, userLine, options = {}) {
17412
17715
  system(`
17413
17716
  [stopped] Model turn interrupted by user.
17414
17717
  `);
17415
- return;
17718
+ return {};
17416
17719
  }
17417
17720
  if (errored) {
17418
- return;
17721
+ return {};
17419
17722
  }
17420
17723
  if (calls.length === 0) {
17421
17724
  const shouldReprompt = actionRequest && (assistantText.length === 0 || modelClaimedAction(assistantText));
@@ -17436,27 +17739,35 @@ async function runAgentTurnCore(io, deps, history, userLine, options = {}) {
17436
17739
  system(`
17437
17740
  [warning] The provider/model did not emit a tool call for an explicit action request. ` + "Use a chat-safe fallback (`keryx shell --chat`) or switch to a tool-capable model.\n");
17438
17741
  }
17439
- return;
17742
+ return {};
17440
17743
  }
17441
17744
  if (isAborted()) {
17442
17745
  system(`
17443
17746
  [stopped] Model turn interrupted by user.
17444
17747
  `);
17445
- return;
17748
+ return {};
17446
17749
  }
17447
17750
  let exhaustedBudget;
17448
17751
  let executedAny = false;
17449
17752
  const batchContainsUntrustedWeb = calls.some((call) => call.name === "web_fetch" || call.name === "web_search");
17753
+ const reservationByCallId = new Map;
17754
+ for (const call of calls) {
17755
+ const callRisk = toolByName.get(call.name)?.definition.risk;
17756
+ reservationByCallId.set(call.id, reserveToolAttempt(budget, call.name, call.input, callRisk));
17757
+ }
17758
+ const spawnConcurrencyCandidates = calls.filter((call) => call.name === "spawn_subagent" && reservationByCallId.get(call.id)?.ok === true);
17759
+ const untrustedGateBlocksSpawns = untrustedContentSeen || batchContainsUntrustedWeb;
17760
+ const concurrentSpawnResults = spawnConcurrencyCandidates.length >= 2 && !untrustedGateBlocksSpawns ? await runConcurrentSpawnBatch(spawnConcurrencyCandidates, toolByName, io, deps) : undefined;
17450
17761
  for (const call of calls) {
17451
17762
  if (isAborted()) {
17452
17763
  system(`
17453
17764
  [stopped] Model turn interrupted by user.
17454
17765
  `);
17455
- return;
17766
+ return {};
17456
17767
  }
17457
17768
  if (deps.unattended === true && call.name === "ask_user") {
17458
17769
  await emitTerminalState(io, deps, options, "ask_user_unanswerable");
17459
- return;
17770
+ return {};
17460
17771
  }
17461
17772
  if (untrustedContentSeen || batchContainsUntrustedWeb && call.name !== "web_fetch" && call.name !== "web_search") {
17462
17773
  const result2 = {
@@ -17470,7 +17781,7 @@ async function runAgentTurnCore(io, deps, history, userLine, options = {}) {
17470
17781
  }
17471
17782
  io.onToolCall?.(call.name, call.input);
17472
17783
  const risk = toolByName.get(call.name)?.definition.risk;
17473
- const reservation = reserveToolAttempt(budget, call.name, call.input, risk);
17784
+ const reservation = reservationByCallId.get(call.id) ?? reserveToolAttempt(budget, call.name, call.input, risk);
17474
17785
  if (!reservation.ok) {
17475
17786
  const result2 = { output: reservation.reason, isError: true };
17476
17787
  io.onToolResult?.(call.name, result2);
@@ -17487,7 +17798,7 @@ async function runAgentTurnCore(io, deps, history, userLine, options = {}) {
17487
17798
  continue;
17488
17799
  }
17489
17800
  executedAny = true;
17490
- const result = await executeCall(call, toolByName, io.requestApproval, io.permissionMode, io.onAutoApproved);
17801
+ const result = concurrentSpawnResults?.get(call.id) ?? await executeCall(call, toolByName, io.requestApproval, io.permissionMode, io.onAutoApproved);
17491
17802
  io.onToolResult?.(call.name, result);
17492
17803
  const modelOutput = redactSensitiveText(result.output);
17493
17804
  history.push({
@@ -17539,9 +17850,10 @@ ${hint}
17539
17850
  }
17540
17851
  const noProgress = !executedAny && calls.length > 0;
17541
17852
  if (exhaustedBudget !== undefined || noProgress) {
17853
+ const finishReason = exhaustedBudget !== undefined ? "budget" : "no-progress";
17542
17854
  if (deps.unattended === true) {
17543
17855
  await emitTerminalState(io, deps, options, "budget_exhausted");
17544
- return;
17856
+ return { finishReason };
17545
17857
  }
17546
17858
  await finishWithBudgetSummary(io, deps, history, parentRunId, {
17547
17859
  maxUnique: maxToolCalls,
@@ -17555,7 +17867,7 @@ ${hint}
17555
17867
  ...exhaustedBudget !== undefined ? { exhaustedBudget } : {},
17556
17868
  noProgress
17557
17869
  });
17558
- return;
17870
+ return { finishReason };
17559
17871
  }
17560
17872
  }
17561
17873
  }
@@ -17647,6 +17959,68 @@ function isApprovalFor(response, fingerprint) {
17647
17959
  }
17648
17960
  return response.fingerprint === undefined || response.fingerprint === fingerprint;
17649
17961
  }
17962
+ async function runConcurrentSpawnBatch(spawnCalls, toolByName, io, deps) {
17963
+ const maxConcurrency = deps.maxSubagentConcurrency ?? DEFAULT_MAX_SUBAGENT_CONCURRENCY;
17964
+ const perTaskRuntimeMs = NOMINAL_CONCURRENT_SPAWN_RUNTIME_MS;
17965
+ const tasks = spawnCalls.map((call) => ({
17966
+ taskId: call.id,
17967
+ dependsOn: [],
17968
+ budgetRequest: { reservationId: call.id, maxRuntimeMs: perTaskRuntimeMs }
17969
+ }));
17970
+ const runOne = (call) => executeCall(call, toolByName, io.requestApproval, io.permissionMode, io.onAutoApproved);
17971
+ const plan = planWaves(tasks, {
17972
+ maxConcurrency,
17973
+ parentRemaining: { maxRuntimeMs: perTaskRuntimeMs * tasks.length }
17974
+ });
17975
+ if (!plan.ok) {
17976
+ io.onSystem?.(`
17977
+ [warning] concurrent subagent wave planning denied (${plan.reason}); running sequentially.
17978
+ `);
17979
+ const results = new Map;
17980
+ for (const call of spawnCalls) {
17981
+ try {
17982
+ results.set(call.id, await runOne(call));
17983
+ } catch (cause) {
17984
+ const message2 = cause instanceof Error ? cause.message : String(cause);
17985
+ results.set(call.id, {
17986
+ output: `subagent call ${call.id} failed: sequential fallback error: ${message2}`,
17987
+ isError: true
17988
+ });
17989
+ }
17990
+ }
17991
+ return results;
17992
+ }
17993
+ try {
17994
+ return await executeWaves(tasks, plan.waves, {
17995
+ run: (task) => {
17996
+ const call = spawnCalls.find((c) => c.id === task.taskId);
17997
+ if (call === undefined) {
17998
+ return Promise.resolve({
17999
+ output: `internal error: unknown concurrent spawn taskId ${task.taskId}`,
18000
+ isError: true
18001
+ });
18002
+ }
18003
+ return runOne(call);
18004
+ }
18005
+ });
18006
+ } catch (cause) {
18007
+ const message2 = cause instanceof Error ? cause.message : String(cause);
18008
+ const isWaveError = cause instanceof WaveExecutionError;
18009
+ io.onSystem?.(`
18010
+ [warning] concurrent subagent wave failed${isWaveError ? "" : " (unexpected)"} (degraded): ${message2}
18011
+ `);
18012
+ const partialResults = cause instanceof WaveExecutionError ? cause.partialResults : undefined;
18013
+ const results = new Map;
18014
+ for (const call of spawnCalls) {
18015
+ const settled = partialResults?.get(call.id);
18016
+ results.set(call.id, settled ?? {
18017
+ output: `subagent call ${call.id} failed: concurrent wave error: ${message2}`,
18018
+ isError: true
18019
+ });
18020
+ }
18021
+ return results;
18022
+ }
18023
+ }
17650
18024
  async function executeCall(call, toolByName, requestApproval, permissionMode, onAutoApproved) {
17651
18025
  const tool = toolByName.get(call.name);
17652
18026
  if (tool === undefined) {
@@ -17667,7 +18041,8 @@ async function executeCall(call, toolByName, requestApproval, permissionMode, on
17667
18041
  const command = typeof input2.command === "string" ? input2.command : "";
17668
18042
  const destructive = risk === "destructive" || isDestructiveCommand(command);
17669
18043
  const credentials = touchesAgentCredentials(command);
17670
- const decision = resolveApprovalDecision({ mode, risk, destructive, credentials });
18044
+ const sacReviewConfirmation = touchesSacConfirmReview(command);
18045
+ const decision = resolveApprovalDecision({ mode, risk, destructive, credentials, sacReviewConfirmation });
17671
18046
  if (decision === "auto") {
17672
18047
  onAutoApproved?.(call.name, call.input, { destructive, credentials });
17673
18048
  } else {
@@ -17682,7 +18057,7 @@ async function executeCall(call, toolByName, requestApproval, permissionMode, on
17682
18057
  }
17683
18058
  }
17684
18059
  } else if (risk === "delegate") {
17685
- const decision = resolveApprovalDecision({ mode, risk, destructive: false, credentials: false });
18060
+ const decision = resolveApprovalDecision({ mode, risk, destructive: false, credentials: false, sacReviewConfirmation: false });
17686
18061
  if (decision === "auto") {
17687
18062
  onAutoApproved?.(call.name, call.input, { destructive: false, credentials: false });
17688
18063
  } else {
@@ -17697,190 +18072,20 @@ async function executeCall(call, toolByName, requestApproval, permissionMode, on
17697
18072
  }
17698
18073
  return tool.invoke(input2);
17699
18074
  }
17700
- var DEFAULT_MAX_TOOL_CALLS = 48, ENV_AGENT_MAX_TOOL_CALLS = "KERYX_AGENT_MAX_TOOL_CALLS", MAX_AGENT_MAX_TOOL_CALLS = 256, DEFAULT_MAX_READ_TOOL_CALLS = 40, DEFAULT_MAX_NON_READ_TOOL_CALLS = 8, MAX_ATTEMPTS_PER_HASH = 3, ENV_AGENT_MAX_ATTEMPTS_PER_HASH = "KERYX_AGENT_MAX_ATTEMPTS_PER_HASH", MAX_AGENT_MAX_ATTEMPTS_PER_HASH = 10, REPEAT_FAILURE_HINT_THRESHOLD = 2, MAX_TOOLLESS_REPROMPTS = 1;
18075
+ var DEFAULT_MAX_TOOL_CALLS = 48, ENV_AGENT_MAX_TOOL_CALLS = "KERYX_AGENT_MAX_TOOL_CALLS", MAX_AGENT_MAX_TOOL_CALLS = 256, DEFAULT_MAX_READ_TOOL_CALLS = 40, DEFAULT_MAX_NON_READ_TOOL_CALLS = 8, DEFAULT_MAX_SUBAGENT_CONCURRENCY = 3, MAX_ATTEMPTS_PER_HASH = 3, ENV_AGENT_MAX_ATTEMPTS_PER_HASH = "KERYX_AGENT_MAX_ATTEMPTS_PER_HASH", MAX_AGENT_MAX_ATTEMPTS_PER_HASH = 10, REPEAT_FAILURE_HINT_THRESHOLD = 2, MAX_TOOLLESS_REPROMPTS = 1, NOMINAL_CONCURRENT_SPAWN_RUNTIME_MS;
17701
18076
  var init_agent = __esm(() => {
17702
18077
  init_validator();
17703
18078
  init_command_risk();
17704
18079
  init_permission_mode();
17705
18080
  init_redact();
18081
+ init_scheduler();
17706
18082
  init_slate();
17707
18083
  init_slate_course();
17708
18084
  init_workspace_resolve();
17709
18085
  init_machine_wrap_up();
17710
18086
  init_slate_lifecycle();
17711
18087
  init_slate_terminal_state();
17712
- });
17713
-
17714
- // src/harness/policy/ranks.ts
17715
- function axesOf(trustMode) {
17716
- switch (trustMode) {
17717
- case "read-only":
17718
- return { authority: "read-only", inputTrust: "vetted" };
17719
- case "trusted-local":
17720
- return { authority: "acting", inputTrust: "vetted" };
17721
- case "untrusted":
17722
- return { authority: "acting", inputTrust: "unvetted" };
17723
- default:
17724
- return;
17725
- }
17726
- }
17727
- function rankOf(map, value) {
17728
- return Object.prototype.hasOwnProperty.call(map, value) ? map[value] : undefined;
17729
- }
17730
- function exceedingAxes(ceiling, candidate) {
17731
- const low = axesOf(ceiling);
17732
- const high = axesOf(candidate);
17733
- if (low === undefined || high === undefined) {
17734
- return [AUTHORITY_AXIS, INPUT_TRUST_AXIS];
17735
- }
17736
- const exceeded = [];
17737
- if (AUTHORITY_RANK[high.authority] > AUTHORITY_RANK[low.authority]) {
17738
- exceeded.push(AUTHORITY_AXIS);
17739
- }
17740
- if (INPUT_TRUST_RANK[high.inputTrust] > INPUT_TRUST_RANK[low.inputTrust]) {
17741
- exceeded.push(INPUT_TRUST_AXIS);
17742
- }
17743
- return exceeded;
17744
- }
17745
- function broadeningAxes(parent, child) {
17746
- const above = axesOf(parent);
17747
- const below = axesOf(child);
17748
- if (above === undefined || below === undefined) {
17749
- return [AUTHORITY_AXIS, INPUT_TRUST_AXIS];
17750
- }
17751
- const broadened = [];
17752
- if (AUTHORITY_RANK[below.authority] > AUTHORITY_RANK[above.authority]) {
17753
- broadened.push(AUTHORITY_AXIS);
17754
- }
17755
- if (INPUT_TRUST_RANK[below.inputTrust] < INPUT_TRUST_RANK[above.inputTrust]) {
17756
- broadened.push(INPUT_TRUST_AXIS);
17757
- }
17758
- return broadened;
17759
- }
17760
- var AUTHORITY_RANK, INPUT_TRUST_RANK, OUTCOME_RANK, ISOLATION_RANK, AUTHORITY_AXIS = "trustMode.authority", INPUT_TRUST_AXIS = "trustMode.inputTrust";
17761
- var init_ranks = __esm(() => {
17762
- AUTHORITY_RANK = {
17763
- "read-only": 0,
17764
- acting: 1
17765
- };
17766
- INPUT_TRUST_RANK = {
17767
- unvetted: 0,
17768
- vetted: 1
17769
- };
17770
- OUTCOME_RANK = {
17771
- deny: 0,
17772
- ask: 1,
17773
- allow: 2
17774
- };
17775
- ISOLATION_RANK = {
17776
- "not-required": 0,
17777
- "required-fail-closed": 1
17778
- };
17779
- });
17780
-
17781
- // src/harness/child/isolation.ts
17782
- function inheritBudget(parentRemaining, childRequest) {
17783
- if (childRequest.maxRuntimeMs > parentRemaining.maxRuntimeMs) {
17784
- return {
17785
- ok: false,
17786
- reason: `child maxRuntimeMs ${childRequest.maxRuntimeMs} exceeds parent remaining ${parentRemaining.maxRuntimeMs}`
17787
- };
17788
- }
17789
- if (childRequest.maxToolCalls !== undefined) {
17790
- if (parentRemaining.maxToolCalls === undefined) {
17791
- return {
17792
- ok: false,
17793
- reason: `child requests ${childRequest.maxToolCalls} tool calls but the parent exposes no tool-call budget to inherit`
17794
- };
17795
- }
17796
- if (childRequest.maxToolCalls > parentRemaining.maxToolCalls) {
17797
- return {
17798
- ok: false,
17799
- reason: `child maxToolCalls ${childRequest.maxToolCalls} exceeds parent remaining ${parentRemaining.maxToolCalls}`
17800
- };
17801
- }
17802
- }
17803
- const reservation = {
17804
- reservationId: childRequest.reservationId,
17805
- maxRuntimeMs: childRequest.maxRuntimeMs,
17806
- ...childRequest.maxToolCalls !== undefined ? { maxToolCalls: childRequest.maxToolCalls } : {}
17807
- };
17808
- return { ok: true, reservation };
17809
- }
17810
- function isKnownCapability(value) {
17811
- return CAPABILITY_KEYS.includes(value);
17812
- }
17813
- function inheritPolicy(parent, childRequest) {
17814
- if (axesOf(childRequest.trustMode) === undefined || axesOf(parent.trustMode) === undefined) {
17815
- return {
17816
- ok: false,
17817
- reason: `unrecognized trustMode (child "${childRequest.trustMode}", parent "${parent.trustMode}")`
17818
- };
17819
- }
17820
- const broadened = broadeningAxes(parent.trustMode, childRequest.trustMode);
17821
- if (broadened.length > 0) {
17822
- return {
17823
- ok: false,
17824
- reason: `child trustMode "${childRequest.trustMode}" is broader than parent "${parent.trustMode}" on ${broadened.join(", ")}`
17825
- };
17826
- }
17827
- for (const capability of CAPABILITY_KEYS) {
17828
- const childOutcome = childRequest.defaults[capability];
17829
- const parentOutcome = parent.defaults[capability];
17830
- const childRank = rankOf(OUTCOME_RANK, childOutcome);
17831
- const parentRank = rankOf(OUTCOME_RANK, parentOutcome);
17832
- if (childRank === undefined || parentRank === undefined) {
17833
- return {
17834
- ok: false,
17835
- reason: `unrecognized capability outcome for "${capability}" (child "${childOutcome}", parent "${parentOutcome}")`
17836
- };
17837
- }
17838
- if (childRank > parentRank) {
17839
- return {
17840
- ok: false,
17841
- reason: `child capability "${capability}" default "${childOutcome}" is more permissive than parent "${parentOutcome}"`
17842
- };
17843
- }
17844
- }
17845
- const childIsolation = rankOf(ISOLATION_RANK, childRequest.requiredControls.isolation);
17846
- const parentIsolation = rankOf(ISOLATION_RANK, parent.requiredControls.isolation);
17847
- if (childIsolation === undefined || parentIsolation === undefined) {
17848
- return {
17849
- ok: false,
17850
- reason: `unrecognized isolation control (child "${childRequest.requiredControls.isolation}", parent "${parent.requiredControls.isolation}")`
17851
- };
17852
- }
17853
- if (childIsolation < parentIsolation) {
17854
- return {
17855
- ok: false,
17856
- reason: `child isolation "${childRequest.requiredControls.isolation}" is weaker than parent "${parent.requiredControls.isolation}"`
17857
- };
17858
- }
17859
- return { ok: true, policy: childRequest };
17860
- }
17861
- function childProvenance(parent, deps) {
17862
- const taintIds = [...parent.taintIds ?? [], parent.provenanceId];
17863
- const provenance = {
17864
- provenanceId: deps.idSeq(),
17865
- trustLevel: "derived",
17866
- sourceKind: parent.sourceKind,
17867
- taintIds
17868
- };
17869
- if (parent.sourceHash !== undefined) {
17870
- provenance.sourceHash = parent.sourceHash;
17871
- }
17872
- return provenance;
17873
- }
17874
- var CAPABILITY_KEYS;
17875
- var init_isolation = __esm(() => {
17876
- init_ranks();
17877
- CAPABILITY_KEYS = [
17878
- "read",
17879
- "write",
17880
- "shell",
17881
- "network",
17882
- "delegate"
17883
- ];
18088
+ NOMINAL_CONCURRENT_SPAWN_RUNTIME_MS = 5 * 60000;
17884
18089
  });
17885
18090
 
17886
18091
  // src/harness/child/ledger.ts
@@ -44890,76 +45095,8 @@ function evaluateExtensionGrant(input2, deps) {
44890
45095
  return { ok: true };
44891
45096
  }
44892
45097
 
44893
- // src/harness/parallel/scheduler.ts
44894
- init_isolation();
44895
- function byTaskId(a, b) {
44896
- return a.taskId < b.taskId ? -1 : a.taskId > b.taskId ? 1 : 0;
44897
- }
44898
- function computeExcluded(tasks) {
44899
- const excluded = new Set;
44900
- for (const t of tasks) {
44901
- if (t.cancelled === true)
44902
- excluded.add(t.taskId);
44903
- }
44904
- let changed = true;
44905
- while (changed) {
44906
- changed = false;
44907
- for (const t of tasks) {
44908
- if (excluded.has(t.taskId))
44909
- continue;
44910
- if (t.dependsOn.some((dep) => excluded.has(dep))) {
44911
- excluded.add(t.taskId);
44912
- changed = true;
44913
- }
44914
- }
44915
- }
44916
- return excluded;
44917
- }
44918
- function decrementRemaining(remaining, reservation) {
44919
- const maxRuntimeMs = remaining.maxRuntimeMs - reservation.maxRuntimeMs;
44920
- if (remaining.maxToolCalls !== undefined && reservation.maxToolCalls !== undefined) {
44921
- return { maxRuntimeMs, maxToolCalls: remaining.maxToolCalls - reservation.maxToolCalls };
44922
- }
44923
- return remaining.maxToolCalls !== undefined ? { maxRuntimeMs, maxToolCalls: remaining.maxToolCalls } : { maxRuntimeMs };
44924
- }
44925
- function planWaves(tasks, config, _deps) {
44926
- if (!Number.isInteger(config.maxConcurrency) || config.maxConcurrency < 1) {
44927
- return { ok: false, reason: `maxConcurrency must be a positive integer, got ${config.maxConcurrency}` };
44928
- }
44929
- const excluded = computeExcluded(tasks);
44930
- const universe = tasks.filter((t) => !excluded.has(t.taskId));
44931
- const scheduled = new Set;
44932
- const waveTaskLists = [];
44933
- while (scheduled.size < universe.length) {
44934
- const ready = universe.filter((t) => !scheduled.has(t.taskId) && t.dependsOn.every((dep) => scheduled.has(dep))).sort(byTaskId);
44935
- if (ready.length === 0) {
44936
- return { ok: false, reason: "dependency cycle detected: no ready task set could be formed" };
44937
- }
44938
- const waveTasks = ready.slice(0, config.maxConcurrency);
44939
- for (const t of waveTasks)
44940
- scheduled.add(t.taskId);
44941
- waveTaskLists.push(waveTasks);
44942
- }
44943
- let remaining = config.parentRemaining;
44944
- const waves = [];
44945
- for (const waveTasks of waveTaskLists) {
44946
- const taskIds = [];
44947
- const reservations = [];
44948
- for (const t of waveTasks) {
44949
- const granted = inheritBudget(remaining, t.budgetRequest);
44950
- if (!granted.ok) {
44951
- return { ok: false, reason: granted.reason };
44952
- }
44953
- taskIds.push(t.taskId);
44954
- reservations.push(granted.reservation);
44955
- remaining = decrementRemaining(remaining, granted.reservation);
44956
- }
44957
- waves.push({ taskIds, reservations });
44958
- }
44959
- return { ok: true, waves };
44960
- }
44961
-
44962
45098
  // src/harness/extension/bound-wave.ts
45099
+ init_scheduler();
44963
45100
  function buildPlannedAttemptEvidence(extension, deps) {
44964
45101
  const causal = {
44965
45102
  runId: extension.parentRunId,
@@ -45817,7 +45954,7 @@ function harnessWave(args2, deps) {
45817
45954
  init_make_provider();
45818
45955
  init_orient();
45819
45956
  init_metaproject_adapter();
45820
- import { randomUUID as randomUUID23 } from "crypto";
45957
+ import { randomUUID as randomUUID25 } from "crypto";
45821
45958
  import * as readline2 from "readline";
45822
45959
 
45823
45960
  // src/commands/agent-approval-context.ts
@@ -47104,6 +47241,9 @@ function buildInteractiveAgentTools(input2) {
47104
47241
  ];
47105
47242
  }
47106
47243
 
47244
+ // src/commands/shell-approval.ts
47245
+ init_command_risk();
47246
+
47107
47247
  // src/lib/shell-permissions.ts
47108
47248
  init_config_dir();
47109
47249
  init_shell_config();
@@ -47254,6 +47394,12 @@ function validateShellPattern(pattern) {
47254
47394
  reason: "touches the agent's own permission/credential files; remembering it would let one approved command disable the approval gate for every future session"
47255
47395
  };
47256
47396
  }
47397
+ if (touchesSacConfirmReview(trimmed)) {
47398
+ return {
47399
+ ok: false,
47400
+ reason: "touches SAC's proposal-review/confirm-token family; that guarantee depends on a human answering a real approval prompt, so it is never remembered"
47401
+ };
47402
+ }
47257
47403
  const banned = bannedPrefixGrant(trimmed, firstToken);
47258
47404
  if (banned !== undefined) {
47259
47405
  return { ok: false, reason: banned.reason };
@@ -47391,6 +47537,9 @@ function isShellCommandAllowed(command, allow) {
47391
47537
  if (touchesAgentCredentials(cmd)) {
47392
47538
  return false;
47393
47539
  }
47540
+ if (touchesSacConfirmReview(cmd)) {
47541
+ return false;
47542
+ }
47394
47543
  return allow.some((pat) => matchShellPattern(pat, cmd));
47395
47544
  }
47396
47545
  function shellPermissionsFingerprint(dir) {
@@ -47416,12 +47565,12 @@ function suggestShellPatterns(command) {
47416
47565
  const collapsed = firstLine2.replace(/\s+/g, " ").trim();
47417
47566
  const first = collapsed.split(" ")[0] ?? collapsed;
47418
47567
  const prefix = first.length > 0 ? `${first} *` : exact;
47419
- const destructive = trimmed.length > 0 && isDestructiveCommand(trimmed);
47568
+ const neverRemember = trimmed.length > 0 && (isDestructiveCommand(trimmed) || touchesSacConfirmReview(trimmed));
47420
47569
  return {
47421
47570
  exact,
47422
47571
  prefix,
47423
- offerExact: !destructive && validateShellPattern(exact).ok,
47424
- offerPrefix: !destructive && validateShellPattern(prefix).ok
47572
+ offerExact: !neverRemember && validateShellPattern(exact).ok,
47573
+ offerPrefix: !neverRemember && validateShellPattern(prefix).ok
47425
47574
  };
47426
47575
  }
47427
47576
  function parseShellExecCommand(inputJson) {
@@ -47444,16 +47593,18 @@ function evaluateShellApproval(input2) {
47444
47593
  const command = parseShellExecCommand(input2.inputJson);
47445
47594
  const destructive = input2.meta?.destructive === true;
47446
47595
  const credentials = input2.meta?.credentials === true;
47596
+ const sacReviewConfirmation = touchesSacConfirmReview(command);
47447
47597
  const audit = io.loadAudit();
47448
47598
  for (const pattern of audit.permissions.allow) {
47449
47599
  input2.sessionAllow.add(pattern);
47450
47600
  }
47451
47601
  const tampered = io.fingerprint() !== input2.fingerprintAtStart;
47452
- const autoApprove = !destructive && !credentials && isShellCommandAllowed(command, [...input2.sessionAllow]);
47602
+ const autoApprove = !destructive && !credentials && !sacReviewConfirmation && isShellCommandAllowed(command, [...input2.sessionAllow]);
47453
47603
  return {
47454
47604
  command,
47455
47605
  destructive,
47456
47606
  credentials,
47607
+ sacReviewConfirmation,
47457
47608
  autoApprove,
47458
47609
  rejected: audit.rejected,
47459
47610
  tampered
@@ -47478,6 +47629,9 @@ function formatShellApprovalHints(evaled) {
47478
47629
  if (evaled.credentials) {
47479
47630
  lines.push("touches agent credentials \u2014 will not be remembered");
47480
47631
  }
47632
+ if (evaled.sacReviewConfirmation) {
47633
+ lines.push("SAC proposal review/confirm-token \u2014 will not be remembered");
47634
+ }
47481
47635
  return lines;
47482
47636
  }
47483
47637
 
@@ -47880,7 +48034,7 @@ function createSpawnSubagentTool(deps) {
47880
48034
  invoke: async (input2) => {
47881
48035
  const task = typeof input2.task === "string" ? input2.task.trim() : "";
47882
48036
  if (task.length === 0) {
47883
- return { output: "spawn_subagent requires a non-empty 'task'", isError: true };
48037
+ return { status: "Error", output: "spawn_subagent requires a non-empty 'task'", isError: true };
47884
48038
  }
47885
48039
  const mode = input2.mode === "general" ? "general" : "read_only";
47886
48040
  const maxToolCalls = typeof input2.max_tool_calls === "number" && input2.max_tool_calls > 0 ? Math.min(MAX_SUBAGENT_MAX_TOOL_CALLS, Math.floor(input2.max_tool_calls)) : DEFAULT_SUBAGENT_MAX_TOOL_CALLS;
@@ -47931,6 +48085,7 @@ function createSpawnSubagentTool(deps) {
47931
48085
  task
47932
48086
  });
47933
48087
  return {
48088
+ status: "Denied",
47934
48089
  output: `spawn_subagent denied by MAE: ${spawned.reason}`,
47935
48090
  isError: true
47936
48091
  };
@@ -48120,6 +48275,7 @@ function createSpawnSubagentTool(deps) {
48120
48275
 
48121
48276
  ` + "Return a concise summary of findings and any recommended next steps for the parent agent.";
48122
48277
  const turn = runAgentTurn(io, childDeps, history, userLine, { signal: childAbort.signal });
48278
+ let turnResult;
48123
48279
  if (deadlineMs > 0) {
48124
48280
  let timer;
48125
48281
  const expired = new Promise((resolve3) => {
@@ -48141,20 +48297,26 @@ function createSpawnSubagentTool(deps) {
48141
48297
  await foldChildSlateAndCleanup("incomplete");
48142
48298
  const partial = assistant.trim();
48143
48299
  return {
48300
+ status: "Timeout",
48144
48301
  output: `subagent ${label} (${workerId}) timed out after ${deadlineMs}ms and was abandoned ` + `(tighten or disable with ${ENV_SUBAGENT_TIMEOUT_MS})` + (partial.length > 0 ? `
48145
48302
  --- partial output ---
48146
48303
  ${boundSummary(partial)}` : ""),
48147
- isError: true
48304
+ isError: true,
48305
+ ...partial.length > 0 ? { partial: boundSummary(partial) } : {}
48148
48306
  };
48149
48307
  }
48308
+ turnResult = await turn;
48150
48309
  } else {
48151
- await turn;
48310
+ turnResult = await turn;
48152
48311
  }
48153
48312
  closed = true;
48154
48313
  releaseBudget();
48155
48314
  const raw = assistant.trim().length > 0 ? assistant.trim() : history.filter((m) => m.role === "assistant").map((m) => m.content).join(`
48156
48315
  `).trim() || "(subagent produced no text)";
48157
48316
  const folded = foldChildSummary(raw);
48317
+ const finishReason = turnResult?.finishReason;
48318
+ const status = finishReason === "budget" ? "BudgetExhausted" : finishReason === "no-progress" ? "NoProgress" : "Completed";
48319
+ const isError = status !== "Completed";
48158
48320
  emitSubagentFleet({
48159
48321
  kind: "upsert",
48160
48322
  id: workerId,
@@ -48166,11 +48328,13 @@ ${boundSummary(partial)}` : ""),
48166
48328
  });
48167
48329
  await foldChildSlateAndCleanup("completed");
48168
48330
  return {
48331
+ status,
48332
+ isError,
48169
48333
  output: `subagent ${label} (${workerId}) ${mode} via ${runModel.provider}/${runModel.model}
48170
48334
  ` + `MAE reservation: tools\u2264${spawned.reservation.maxToolCalls ?? maxToolCalls} ` + `runtime\u2264${spawned.reservation.maxRuntimeMs}ms children=${ledger.childCount}
48171
48335
  ` + `--- summary ---
48172
48336
  ${boundSummary(folded.text)}`,
48173
- isError: false
48337
+ ...status !== "Completed" ? { partial: boundSummary(folded.text) } : {}
48174
48338
  };
48175
48339
  } catch (cause) {
48176
48340
  closed = true;
@@ -48186,7 +48350,7 @@ ${boundSummary(folded.text)}`,
48186
48350
  task
48187
48351
  });
48188
48352
  await foldChildSlateAndCleanup("incomplete");
48189
- return { output: `subagent ${label} failed: ${msg}`, isError: true };
48353
+ return { status: "Error", output: `subagent ${label} failed: ${msg}`, isError: true };
48190
48354
  }
48191
48355
  }
48192
48356
  };
@@ -48582,7 +48746,7 @@ import { spawnSync as spawnSync2 } from "child_process";
48582
48746
  // package.json
48583
48747
  var package_default = {
48584
48748
  name: "@mrciphersmith/keryx",
48585
- version: "0.2.43",
48749
+ version: "0.2.45",
48586
48750
  description: "Version-controlled project context for AI coding agents: code graph, architecture wiki, project memory, relevant tests, quality signals, and task flows.",
48587
48751
  private: false,
48588
48752
  publishConfig: {
@@ -49832,8 +49996,195 @@ function openModal(otui, chrome, input2) {
49832
49996
  // src/tui/inspector-sources.ts
49833
49997
  init_store2();
49834
49998
  init_workspace_service();
49999
+ import { randomUUID as randomUUID24 } from "crypto";
50000
+
50001
+ // src/sac/catch-up.ts
50002
+ init_fs();
50003
+ init_config_dir();
50004
+ init_paths();
49835
50005
  init_slate();
50006
+ init_store3();
50007
+ init_proposal_lifecycle();
50008
+ init_workspace_service();
50009
+ import { randomUUID as randomUUID23 } from "crypto";
50010
+ import { readdir as readdir24 } from "fs/promises";
50011
+ import path145 from "path";
50012
+
50013
+ // src/sac/lifecycle-flag.ts
50014
+ init_store();
50015
+ init_service3();
50016
+ init_decision_dedup();
50017
+ init_workspace_service();
49836
50018
  import { randomUUID as randomUUID22 } from "crypto";
50019
+ function normalize3(raw) {
50020
+ return raw.replace(/^\.\//, "");
50021
+ }
50022
+ function isStillPresent(recorded, valid) {
50023
+ const normalized = normalize3(recorded);
50024
+ return valid.has(normalized) || valid.has(moduleNameFromProjectPath(normalized));
50025
+ }
50026
+ async function computeLifecycleFlags(cwd, now = () => new Date) {
50027
+ const valid = await validModuleNames(cwd);
50028
+ if (valid === undefined)
50029
+ return [];
50030
+ const flaggedAt = now().toISOString();
50031
+ const flags = [];
50032
+ const workspaceService = new WorkspaceService({
50033
+ workspaceRoot: cwd,
50034
+ authorizationServer: localWorkspaceAuthorizationServer(),
50035
+ strictGuard: { mode: "strict", availability: "available", decision: "pass", policyRevision: "local-offline-v1" }
50036
+ });
50037
+ const workspaces = await workspaceService.list({ request: undefined, requestCorrelationId: randomUUID22(), includeArchived: true });
50038
+ for (const workspace of workspaces) {
50039
+ const component = workspace.resources.find((r) => r.kind === "component")?.uri;
50040
+ if (component !== undefined && !isStillPresent(component, valid)) {
50041
+ flags.push({ kind: "workspace", ref: workspace.id, missingComponent: normalize3(component), flaggedAt });
50042
+ }
50043
+ }
50044
+ for (const entry of await collectEntries(cwd)) {
50045
+ const module = entry.scopes.module;
50046
+ if (module !== null && module.length > 0 && !isStillPresent(module, valid)) {
50047
+ flags.push({ kind: "memory-entry", ref: entry.relativePath, missingComponent: normalize3(module), flaggedAt });
50048
+ }
50049
+ }
50050
+ for (const decision of await collectWikiDecisionEntries(cwd)) {
50051
+ const module = decision.scopes.module;
50052
+ if (module !== null && module.length > 0 && !isStillPresent(module, valid)) {
50053
+ flags.push({ kind: "wiki-decision", ref: decision.relativePath, missingComponent: normalize3(module), flaggedAt });
50054
+ }
50055
+ }
50056
+ return flags;
50057
+ }
50058
+
50059
+ // src/sac/catch-up.ts
50060
+ async function buildCatchUp(input2) {
50061
+ const [proposals, sessionCategories, lifecycleFlagsAll] = await Promise.all([
50062
+ collectProposals(input2.cwd, input2.workspaceId),
50063
+ collectSessionCategories(input2.cwd),
50064
+ computeLifecycleFlags(input2.cwd)
50065
+ ]);
50066
+ const lifecycleFlags = input2.workspaceId === undefined ? lifecycleFlagsAll : lifecycleFlagsAll.filter((flag) => flag.kind !== "workspace" || flag.ref === input2.workspaceId);
50067
+ return { proposals, ...sessionCategories, lifecycleFlags };
50068
+ }
50069
+ async function collectProposals(cwd, workspaceId) {
50070
+ const authorizationServer = localWorkspaceAuthorizationServer();
50071
+ const actor = await authorizationServer.actorContextFor(undefined, randomUUID23());
50072
+ if (!actor)
50073
+ throw new Error("trusted ActorContext is required for catch-up");
50074
+ const proposalService = createLocalProposalLifecycleService(cwd);
50075
+ const groups = await proposalService.listVisibleProposedProposals(actor);
50076
+ const scoped = workspaceId === undefined ? groups : groups.filter((group) => group.workspace.id === workspaceId);
50077
+ const flattened = scoped.flatMap((group) => group.proposals.map((proposal) => ({ group, proposal })));
50078
+ return Promise.all(flattened.map(async ({ group, proposal }) => {
50079
+ const fresh = await proposalService.isEvidenceFresh(proposal, actor);
50080
+ return { type: "proposal", workspaceId: group.workspace.id, proposalId: proposal.id, fresh };
50081
+ }));
50082
+ }
50083
+ async function classifySession(session) {
50084
+ const dir = sessionDir(session.projectPath, session.id);
50085
+ if (await isLockHeld(slateLockPath(dir)))
50086
+ return;
50087
+ const terminalState = await readTerminalState(dir);
50088
+ if (terminalState !== undefined) {
50089
+ const workspaceId2 = (await safeReadSlate(dir))?.workspaceId;
50090
+ return { kind: "blocked", item: { type: "blocked", sessionId: session.id, ...workspaceId2 !== undefined ? { workspaceId: workspaceId2 } : {}, terminalState } };
50091
+ }
50092
+ const unboundCandidate = await readNewestUnboundCandidate(dir);
50093
+ if (unboundCandidate !== undefined) {
50094
+ return {
50095
+ kind: "unbound-candidate",
50096
+ item: { type: "unbound-candidate", sessionId: session.id, evidencePath: unboundCandidate.evidencePath, summary: unboundCandidate.summary }
50097
+ };
50098
+ }
50099
+ if (!await isSlateEngaged(dir))
50100
+ return;
50101
+ const workspaceId = (await safeReadSlate(dir))?.workspaceId;
50102
+ return { kind: "unknown", item: { type: "unknown", sessionId: session.id, ...workspaceId !== undefined ? { workspaceId } : {}, lastSeenAt: session.updatedAt } };
50103
+ }
50104
+ async function collectSessionCategories(cwd) {
50105
+ const classified = await Promise.all(listSessions(cwd).map((session) => classifySession(session)));
50106
+ const blocked2 = [];
50107
+ const unboundCandidates = [];
50108
+ const unknown = [];
50109
+ for (const category of classified) {
50110
+ if (category === undefined)
50111
+ continue;
50112
+ if (category.kind === "blocked")
50113
+ blocked2.push(category.item);
50114
+ else if (category.kind === "unbound-candidate")
50115
+ unboundCandidates.push(category.item);
50116
+ else
50117
+ unknown.push(category.item);
50118
+ }
50119
+ return { blocked: blocked2, unboundCandidates, unknown };
50120
+ }
50121
+ async function isSlateEngaged(dir) {
50122
+ if (await pathExists(path145.join(dir, "slate.json")))
50123
+ return true;
50124
+ if (await pathExists(path145.join(dir, "terminal-state.json")))
50125
+ return true;
50126
+ try {
50127
+ const entries = await readdir24(path145.join(dir, "slate-archive"));
50128
+ return entries.length > 0;
50129
+ } catch {
50130
+ return false;
50131
+ }
50132
+ }
50133
+ async function safeReadSlate(dir) {
50134
+ try {
50135
+ return await readSlate(dir);
50136
+ } catch {
50137
+ return;
50138
+ }
50139
+ }
50140
+ async function readTerminalState(dir) {
50141
+ const result = readConfigFile(path145.join(dir, "terminal-state.json"));
50142
+ if (!result.ok) {
50143
+ return;
50144
+ }
50145
+ try {
50146
+ return JSON.parse(result.text);
50147
+ } catch {
50148
+ return;
50149
+ }
50150
+ }
50151
+ async function readNewestUnboundCandidate(dir) {
50152
+ const archiveDir = path145.join(dir, "slate-archive");
50153
+ let entries;
50154
+ try {
50155
+ entries = (await readdir24(archiveDir)).filter((name) => name.endsWith("-unbound-candidate.json"));
50156
+ } catch {
50157
+ return;
50158
+ }
50159
+ entries.sort();
50160
+ for (let i = entries.length - 1;i >= 0; i--) {
50161
+ const evidencePath = path145.join(archiveDir, entries[i]);
50162
+ const result = readConfigFile(evidencePath);
50163
+ if (!result.ok) {
50164
+ continue;
50165
+ }
50166
+ try {
50167
+ const parsed = JSON.parse(result.text);
50168
+ if (parsed.recordType !== "unbound-candidate")
50169
+ continue;
50170
+ return { evidencePath, summary: summarizeUnboundCandidate(parsed.groups) };
50171
+ } catch {
50172
+ continue;
50173
+ }
50174
+ }
50175
+ return;
50176
+ }
50177
+ function summarizeUnboundCandidate(groups) {
50178
+ const safeGroups = groups ?? [];
50179
+ if (safeGroups.length === 0)
50180
+ return "no seeds captured";
50181
+ const seedCount = safeGroups.reduce((sum, group) => sum + (group.seeds?.length ?? 0), 0);
50182
+ const kinds = safeGroups.map((group) => typeof group.kind === "string" ? group.kind : "unknown").join(", ");
50183
+ return `${seedCount} untriaged seed(s) across ${safeGroups.length} kind(s) (${kinds})`;
50184
+ }
50185
+
50186
+ // src/tui/inspector-sources.ts
50187
+ init_slate();
49837
50188
 
49838
50189
  // src/session/index.ts
49839
50190
  init_paths();
@@ -49912,7 +50263,7 @@ async function loadInspectorWorkspaces(cwd) {
49912
50263
  authorizationServer: localWorkspaceAuthorizationServer(),
49913
50264
  strictGuard: localSacGuard()
49914
50265
  });
49915
- const listed = await service5.list({ request: undefined, requestCorrelationId: randomUUID22() });
50266
+ const listed = await service5.list({ request: undefined, requestCorrelationId: randomUUID24() });
49916
50267
  return listed.map(workspaceFromManifest);
49917
50268
  } catch {
49918
50269
  return [];
@@ -49925,7 +50276,7 @@ async function loadInspectorWorkspace(cwd, workspaceId) {
49925
50276
  authorizationServer: localWorkspaceAuthorizationServer(),
49926
50277
  strictGuard: localSacGuard()
49927
50278
  });
49928
- const manifest = await service5.show({ request: undefined, requestCorrelationId: randomUUID22(), workspaceId });
50279
+ const manifest = await service5.show({ request: undefined, requestCorrelationId: randomUUID24(), workspaceId });
49929
50280
  return workspaceFromManifest(manifest);
49930
50281
  } catch {
49931
50282
  return;
@@ -49998,6 +50349,16 @@ function formatWorkspaceLines(workspaces) {
49998
50349
  const width = workspaces.reduce((max, workspace) => Math.max(max, workspace.id.length), 0);
49999
50350
  return workspaces.map((workspace) => `${workspace.id.padEnd(width)} ${workspace.status} ${workspace.title}`);
50000
50351
  }
50352
+ async function loadInspectorCatchUp(cwd) {
50353
+ try {
50354
+ return await buildCatchUp({ cwd });
50355
+ } catch {
50356
+ return { proposals: [], blocked: [], unboundCandidates: [], unknown: [], lifecycleFlags: [] };
50357
+ }
50358
+ }
50359
+ function catchUpItems(report) {
50360
+ return [...report.proposals, ...report.blocked, ...report.unboundCandidates, ...report.unknown];
50361
+ }
50001
50362
  function formatSessionFlowLines(flows) {
50002
50363
  if (flows.length === 0) {
50003
50364
  return ["No flows recorded in this session."];
@@ -50490,6 +50851,332 @@ function openWorkspace(otui, chrome, options) {
50490
50851
  return presentWorkspace((hostOtui, hostChrome, input2) => openModal(hostOtui, hostChrome, input2), otui, chrome, options);
50491
50852
  }
50492
50853
 
50854
+ // src/tui/review-inspector.ts
50855
+ var REVIEW_COMMAND = "/review";
50856
+ var REVIEW_FOOTER = [
50857
+ { key: "[/]", label: "item" },
50858
+ { key: "a y", label: "accept" },
50859
+ { key: "\u2191/\u2193", label: "scroll" },
50860
+ { key: "\u2190/\u2192", label: "tabs" },
50861
+ { key: "esc", label: "close" }
50862
+ ];
50863
+ function isReviewCommand(line) {
50864
+ const token = line.trim().split(/\s+/)[0] ?? "";
50865
+ return token === REVIEW_COMMAND;
50866
+ }
50867
+ var TYPE_LABEL = {
50868
+ proposal: "PROPOSAL",
50869
+ blocked: "BLOCKED",
50870
+ "unbound-candidate": "UNBOUND",
50871
+ unknown: "UNKNOWN"
50872
+ };
50873
+ function summarizeReviewItem(item) {
50874
+ switch (item.type) {
50875
+ case "proposal":
50876
+ return `${item.proposalId} in ${item.workspaceId}${item.fresh ? "" : " (stale)"}`;
50877
+ case "blocked":
50878
+ return `${item.sessionId} \u2014 ${item.terminalState.reason}`;
50879
+ case "unbound-candidate":
50880
+ return `${item.sessionId} \u2014 ${item.summary}`;
50881
+ case "unknown":
50882
+ return `${item.sessionId} \u2014 last seen ${item.lastSeenAt}`;
50883
+ }
50884
+ }
50885
+ function formatReviewListLines(items, selected) {
50886
+ if (items.length === 0) {
50887
+ return ["Nothing needs review right now."];
50888
+ }
50889
+ return items.map((item, index) => {
50890
+ const mark = index === selected ? ">" : " ";
50891
+ return `${mark} ${TYPE_LABEL[item.type].padEnd(8)} ${summarizeReviewItem(item)}`;
50892
+ });
50893
+ }
50894
+ function describeReviewItem(item) {
50895
+ switch (item.type) {
50896
+ case "proposal":
50897
+ return [
50898
+ `Proposal ${item.proposalId}`,
50899
+ `Workspace ${item.workspaceId}`,
50900
+ `Evidence ${item.fresh ? "fresh" : "stale \u2014 evidence has drifted since this proposal was created; re-run wrap-up before deciding"}`,
50901
+ "",
50902
+ `Reject/dismiss from a terminal: keryx workspace review ${item.workspaceId} ${item.proposalId} --decision <rejected|dismissed>`
50903
+ ];
50904
+ case "blocked":
50905
+ return [
50906
+ `Session ${item.sessionId}${item.workspaceId !== undefined ? ` (workspace ${item.workspaceId})` : ""}`,
50907
+ `Stopped unattended: ${item.terminalState.reason}`,
50908
+ `Occurred ${item.terminalState.occurredAt}`,
50909
+ "",
50910
+ `Resume: keryx shell -r ${item.sessionId}`
50911
+ ];
50912
+ case "unbound-candidate":
50913
+ return [
50914
+ `Session ${item.sessionId}`,
50915
+ `Untriaged seeds: ${item.summary}`,
50916
+ `Evidence ${item.evidencePath}`,
50917
+ "",
50918
+ `Bind: keryx workspace propose <workspace-id> --kind <kind> --session ${item.sessionId}`
50919
+ ];
50920
+ case "unknown":
50921
+ return [
50922
+ `Session ${item.sessionId}${item.workspaceId !== undefined ? ` (workspace ${item.workspaceId})` : ""}`,
50923
+ `Last seen ${item.lastSeenAt}`,
50924
+ "No proposal, terminal state, or unbound-candidate artifact recorded.",
50925
+ "",
50926
+ `Investigate: keryx sessions list / keryx shell -r ${item.sessionId}`
50927
+ ];
50928
+ }
50929
+ }
50930
+ function formatReviewDetailLines(item, status) {
50931
+ if (item === undefined) {
50932
+ return ["No item selected.", "", "Press Enter (or click a row) on the Review tab to view one."];
50933
+ }
50934
+ const lines = describeReviewItem(item);
50935
+ if (item.type !== "proposal") {
50936
+ return lines;
50937
+ }
50938
+ const withAction = [...lines, ""];
50939
+ if (status.kind === "armed") {
50940
+ withAction.push("Press [y] to CONFIRM accept, any other key cancels.");
50941
+ } else if (status.kind === "running") {
50942
+ withAction.push("Accepting\u2026 running `keryx workspace confirm-review` then `keryx workspace review`.");
50943
+ } else if (status.kind === "done" && status.outcome.ok) {
50944
+ withAction.push("\u2713 Accepted.");
50945
+ } else if (status.kind === "done" && !status.outcome.ok) {
50946
+ withAction.push(`\u2717 Accept failed: ${status.outcome.message}`);
50947
+ } else {
50948
+ withAction.push("[a] Accept this proposal");
50949
+ }
50950
+ return withAction;
50951
+ }
50952
+ function clampScroll3(offset, lineCount, height) {
50953
+ const max = Math.max(0, lineCount - height);
50954
+ return Math.min(max, Math.max(0, offset));
50955
+ }
50956
+ function windowLines3(lines, offset, height) {
50957
+ if (height < 1) {
50958
+ return [];
50959
+ }
50960
+ const start = clampScroll3(offset, lines.length, height);
50961
+ return lines.slice(start, start + height);
50962
+ }
50963
+ function scrollToReveal3(index, offset, height) {
50964
+ if (index < offset) {
50965
+ return index;
50966
+ }
50967
+ if (index >= offset + height) {
50968
+ return index - height + 1;
50969
+ }
50970
+ return offset;
50971
+ }
50972
+ function wrapLines3(text, width) {
50973
+ if (width === undefined || width < 8) {
50974
+ return text;
50975
+ }
50976
+ return text.split(`
50977
+ `).flatMap((line) => {
50978
+ if (line.length <= width) {
50979
+ return [line];
50980
+ }
50981
+ const chunks = [];
50982
+ for (let i = 0;i < line.length; i += width) {
50983
+ chunks.push(line.slice(i, i + width));
50984
+ }
50985
+ return chunks;
50986
+ }).join(`
50987
+ `);
50988
+ }
50989
+ function paintLines3(otui, renderer, body, lines, width) {
50990
+ if (otui === undefined || otui === null || body === undefined || body === null) {
50991
+ return;
50992
+ }
50993
+ const parent = body;
50994
+ const ctor = otui.TextRenderable;
50995
+ if (parent.add === undefined || ctor === undefined) {
50996
+ return;
50997
+ }
50998
+ const node = new ctor(renderer, { id: "review-body", content: wrapLines3(lines.join(`
50999
+ `), width) });
51000
+ parent.add(node);
51001
+ return node;
51002
+ }
51003
+ function presentReview(openModal2, otui, chrome, options) {
51004
+ const items = [...options.items];
51005
+ let selected = 0;
51006
+ let listScroll = 0;
51007
+ let detailScroll = 0;
51008
+ let status = { kind: "idle" };
51009
+ let listNode;
51010
+ let detailNode;
51011
+ let unsubscribeKey;
51012
+ const rendererHint = options.renderer ?? chrome?.renderer;
51013
+ const bodyRows = options.visibleRows ?? (typeof rendererHint?.width === "number" && typeof rendererHint.height === "number" ? modalBodyRows(resolveModalPanelSize(rendererHint.width, rendererHint.height).height) : 13);
51014
+ let tabWidth;
51015
+ const listLines = () => formatReviewListLines(items, selected);
51016
+ const detailLines = () => wrapLines3(formatReviewDetailLines(items[selected], status).join(`
51017
+ `), tabWidth).split(`
51018
+ `);
51019
+ const paintSelection = () => {
51020
+ listScroll = scrollToReveal3(selected, listScroll, bodyRows);
51021
+ listScroll = clampScroll3(listScroll, items.length, bodyRows);
51022
+ detailScroll = clampScroll3(detailScroll, detailLines().length, bodyRows);
51023
+ if (listNode !== undefined) {
51024
+ listNode.content = windowLines3(listLines(), listScroll, bodyRows).join(`
51025
+ `);
51026
+ }
51027
+ if (detailNode !== undefined) {
51028
+ detailNode.content = windowLines3(detailLines(), detailScroll, bodyRows).join(`
51029
+ `);
51030
+ }
51031
+ };
51032
+ const moveSelection = (next) => {
51033
+ if (items.length === 0) {
51034
+ return;
51035
+ }
51036
+ const clamped = Math.min(items.length - 1, Math.max(0, next));
51037
+ if (clamped === selected) {
51038
+ return;
51039
+ }
51040
+ selected = clamped;
51041
+ detailScroll = 0;
51042
+ status = { kind: "idle" };
51043
+ paintSelection();
51044
+ };
51045
+ const runAccept = () => {
51046
+ const item = items[selected];
51047
+ if (item === undefined || item.type !== "proposal" || options.acceptProposal === undefined) {
51048
+ return;
51049
+ }
51050
+ status = { kind: "running" };
51051
+ paintSelection();
51052
+ options.acceptProposal(item).then((outcome) => {
51053
+ status = { kind: "done", outcome };
51054
+ if (outcome.ok) {
51055
+ options.onAccepted?.(item);
51056
+ }
51057
+ paintSelection();
51058
+ });
51059
+ };
51060
+ const handle = openModal2(otui, chrome, {
51061
+ title: REVIEW_COMMAND,
51062
+ tabs: [
51063
+ { id: "list", label: "Review" },
51064
+ { id: "detail", label: "Detail" }
51065
+ ],
51066
+ initialTab: "list",
51067
+ footer: REVIEW_FOOTER,
51068
+ renderTab: (tabId, body, ctx) => {
51069
+ const renderer = options.renderer ?? chrome?.renderer;
51070
+ tabWidth = ctx?.width;
51071
+ if (tabId === "list") {
51072
+ listScroll = scrollToReveal3(selected, listScroll, bodyRows);
51073
+ listNode = paintLines3(otui, renderer, body, windowLines3(listLines(), listScroll, bodyRows));
51074
+ return;
51075
+ }
51076
+ detailScroll = clampScroll3(detailScroll, detailLines().length, bodyRows);
51077
+ detailNode = paintLines3(otui, renderer, body, windowLines3(detailLines(), detailScroll, bodyRows), tabWidth);
51078
+ },
51079
+ onClose: () => {
51080
+ unsubscribeKey?.();
51081
+ }
51082
+ });
51083
+ if (handle === undefined) {
51084
+ return;
51085
+ }
51086
+ if (options.onKeypress !== undefined) {
51087
+ unsubscribeKey = options.onKeypress((key) => {
51088
+ const token = key.name || key.sequence;
51089
+ if (items.length === 0) {
51090
+ return;
51091
+ }
51092
+ const onDetail = handle.activeTab() === "detail";
51093
+ if (onDetail && status.kind === "armed") {
51094
+ if (token === "y") {
51095
+ runAccept();
51096
+ } else {
51097
+ status = { kind: "idle" };
51098
+ paintSelection();
51099
+ }
51100
+ return;
51101
+ }
51102
+ if (token === "[" || token === "p") {
51103
+ moveSelection(selected - 1);
51104
+ return;
51105
+ }
51106
+ if (token === "]" || token === "n") {
51107
+ moveSelection(selected + 1);
51108
+ return;
51109
+ }
51110
+ if (token === "return" || token === "enter") {
51111
+ handle.setTab("detail");
51112
+ return;
51113
+ }
51114
+ if (onDetail && token === "a" && items[selected]?.type === "proposal" && options.acceptProposal !== undefined && status.kind !== "running") {
51115
+ status = { kind: "armed" };
51116
+ paintSelection();
51117
+ return;
51118
+ }
51119
+ if (token === "up" || token === "k") {
51120
+ if (onDetail) {
51121
+ detailScroll = clampScroll3(detailScroll - 1, detailLines().length, bodyRows);
51122
+ paintSelection();
51123
+ } else {
51124
+ moveSelection(selected - 1);
51125
+ }
51126
+ return;
51127
+ }
51128
+ if (token === "down" || token === "j") {
51129
+ if (onDetail) {
51130
+ detailScroll = clampScroll3(detailScroll + 1, detailLines().length, bodyRows);
51131
+ paintSelection();
51132
+ } else {
51133
+ moveSelection(selected + 1);
51134
+ }
51135
+ return;
51136
+ }
51137
+ if (onDetail && (token === "pageup" || token === "pagedown")) {
51138
+ const step = token === "pageup" ? -bodyRows : bodyRows;
51139
+ detailScroll = clampScroll3(detailScroll + step, detailLines().length, bodyRows);
51140
+ paintSelection();
51141
+ }
51142
+ });
51143
+ }
51144
+ return handle;
51145
+ }
51146
+ function openReview(otui, chrome, options) {
51147
+ return presentReview((hostOtui, hostChrome, input2) => openModal(hostOtui, hostChrome, input2), otui, chrome, options);
51148
+ }
51149
+
51150
+ // src/tui/review-accept.ts
51151
+ function shQuote(value) {
51152
+ return `'${value.replace(/'/g, `'\\''`)}'`;
51153
+ }
51154
+ async function acceptProposalViaShell(run, workspaceId, proposalId) {
51155
+ const mint = await run(`keryx workspace confirm-review ${shQuote(workspaceId)} ${shQuote(proposalId)}`);
51156
+ if (mint.isError) {
51157
+ return { ok: false, message: mint.output };
51158
+ }
51159
+ let token;
51160
+ try {
51161
+ const parsed = JSON.parse(mint.output);
51162
+ const candidate = parsed?.token;
51163
+ if (typeof candidate !== "string" || candidate.length === 0) {
51164
+ throw new Error("no token in confirm-review output");
51165
+ }
51166
+ token = candidate;
51167
+ } catch (cause) {
51168
+ return {
51169
+ ok: false,
51170
+ message: `could not parse \`keryx workspace confirm-review\` output: ${cause instanceof Error ? cause.message : String(cause)}`
51171
+ };
51172
+ }
51173
+ const accept = await run(`keryx workspace review ${shQuote(workspaceId)} ${shQuote(proposalId)} --decision accepted --confirm-token ${shQuote(token)}`);
51174
+ if (accept.isError) {
51175
+ return { ok: false, message: accept.output };
51176
+ }
51177
+ return { ok: true };
51178
+ }
51179
+
50493
51180
  // src/tui/tui-shell.ts
50494
51181
  init_slate();
50495
51182
 
@@ -50854,6 +51541,11 @@ var AGENT_SLASH_COMMANDS = [
50854
51541
  description: "Show this session's SAC workspace and its slates",
50855
51542
  modes: AGENT_ONLY
50856
51543
  },
51544
+ {
51545
+ name: "/review",
51546
+ description: "Show project-wide items needing review (proposals, blocked sessions)",
51547
+ modes: AGENT_ONLY
51548
+ },
50857
51549
  {
50858
51550
  name: "/compact",
50859
51551
  description: "Compact model context \u2014 /compact [focus] (archive kept)",
@@ -51217,10 +51909,10 @@ init_shell_config();
51217
51909
  // src/lib/permission-mode-config.ts
51218
51910
  init_permission_mode();
51219
51911
  init_config_dir();
51220
- import path145 from "path";
51912
+ import path146 from "path";
51221
51913
  var EMPTY3 = { schemaVersion: 1, projects: {} };
51222
51914
  function permissionModeConfigPath(dir) {
51223
- return path145.join(keryxConfigDir(dir), "permission-mode.json");
51915
+ return path146.join(keryxConfigDir(dir), "permission-mode.json");
51224
51916
  }
51225
51917
  function withRegistryLock2(dir, fn) {
51226
51918
  return withFileLock(`${permissionModeConfigPath(dir)}.lock`, fn, {
@@ -51565,7 +52257,7 @@ function showComposerChoice(otui, r, dock, request) {
51565
52257
  // src/lib/version-check.ts
51566
52258
  init_config_dir();
51567
52259
  init_fs();
51568
- import path146 from "path";
52260
+ import path147 from "path";
51569
52261
  var REGISTRY_URL = "https://registry.npmjs.org/@mrciphersmith%2Fkeryx/latest";
51570
52262
  var FIXED_INSTALL_COMMAND = "npm install -g @mrciphersmith/keryx@latest";
51571
52263
  var RESPONSE_BODY_LIMIT_BYTES = 64 * 1024;
@@ -51767,7 +52459,7 @@ async function checkVersion(options) {
51767
52459
  const now = options.now ?? Date.now;
51768
52460
  const timestamp = now();
51769
52461
  const configDir = ensureKeryxConfigDir(options.cacheDir);
51770
- const cacheFile = path146.join(configDir, "version-check.json");
52462
+ const cacheFile = path147.join(configDir, "version-check.json");
51771
52463
  const cache = parseCache(cacheFile);
51772
52464
  if (cache?.latestVersion !== undefined && cache.successAt !== undefined && timestamp - cache.successAt >= 0 && timestamp - cache.successAt < SUCCESS_CACHE_TTL_MS) {
51773
52465
  return resultFor(options.currentVersion, current, cache.latestVersion, "cache");
@@ -53950,6 +54642,20 @@ async function launchTuiAgentShell(opts) {
53950
54642
  currentSlates = slates;
53951
54643
  sbWorkspaceV.content = workspace === undefined ? otui.t`${otui.dim("\u2014")}` : otui.t`${otui.dim(`${shortenCwd(workspace.title, SIDEBAR_TEXT_WIDTH)} \xB7 ${workspace.status} \xB7 ${slates.length} slate${slates.length === 1 ? "" : "s"}`)}`;
53952
54644
  };
54645
+ sidebar.add(new otui.TextRenderable(r, { id: "sb-review-k", content: otui.t`${otui.dim("Review")}`, marginTop: 1 }));
54646
+ const sbReviewV = new otui.TextRenderable(r, {
54647
+ id: "sb-review-v",
54648
+ content: otui.t`${otui.dim("\u2014")}`,
54649
+ onMouseDown: () => {
54650
+ showReview();
54651
+ }
54652
+ });
54653
+ sidebar.add(sbReviewV);
54654
+ const refreshReviewSidebar = async () => {
54655
+ const cwd = opts.session?.cwd ?? process.cwd();
54656
+ const count = catchUpItems(await loadInspectorCatchUp(cwd)).length;
54657
+ sbReviewV.content = count === 0 ? otui.t`${otui.dim("\u2014 nothing to review")}` : otui.t`${otui.yellow(`${count} item${count === 1 ? "" : "s"} need review`)}`;
54658
+ };
53953
54659
  sidebar.add(new otui.TextRenderable(r, { id: "sb-ctx-k", content: otui.t`${otui.dim("Context")}`, marginTop: 1 }));
53954
54660
  const sbContext = new otui.TextRenderable(r, { id: "sb-ctx-v", content: otui.t`${otui.dim("0 tokens")}` });
53955
54661
  sidebar.add(sbContext);
@@ -54390,6 +55096,7 @@ async function launchTuiAgentShell(opts) {
54390
55096
  }
54391
55097
  slateSession = { dir: liveSession.dir, cwd: sessionCwd, opened: false };
54392
55098
  refreshWorkspaceSidebar();
55099
+ refreshReviewSidebar();
54393
55100
  const paintSessionHeader = () => {
54394
55101
  const label = `${currentSel.provider}/${currentSel.model}`;
54395
55102
  const sid = shortSessionId(liveSession.summary.id);
@@ -54559,6 +55266,21 @@ Staying in the current session.
54559
55266
  });
54560
55267
  })();
54561
55268
  };
55269
+ const showReview = () => {
55270
+ (async () => {
55271
+ const cwd = inspectorCwd();
55272
+ const items = catchUpItems(await loadInspectorCatchUp(cwd));
55273
+ openReview(otui, chrome, {
55274
+ items,
55275
+ acceptProposal: (item) => acceptProposalViaShell(makeCommandRunner(cwd), item.workspaceId, item.proposalId),
55276
+ onAccepted: () => {
55277
+ refreshReviewSidebar();
55278
+ },
55279
+ renderer: r,
55280
+ ...inspectorKeys
55281
+ });
55282
+ })();
55283
+ };
54562
55284
  const updateModelLabels = () => {
54563
55285
  paintSessionHeader();
54564
55286
  const label = `${currentSel.provider}/${currentSel.model}`;
@@ -55078,6 +55800,7 @@ Staying in the current session.
55078
55800
  startNewSession();
55079
55801
  slateSession = { dir: liveSession.dir, cwd: sessionCwd, opened: false };
55080
55802
  refreshWorkspaceSidebar();
55803
+ refreshReviewSidebar();
55081
55804
  sessions.clear();
55082
55805
  deps.resetSubagentBudget?.();
55083
55806
  io.onSystem?.(`New session ${shortSessionId(liveSession.summary.id)} (previous kept on disk \xB7 /resume)
@@ -55216,6 +55939,10 @@ Staying in the current session.
55216
55939
  showWorkspace();
55217
55940
  return;
55218
55941
  }
55942
+ if (isReviewCommand(command.name)) {
55943
+ showReview();
55944
+ return;
55945
+ }
55219
55946
  if (command.name === "/copy") {
55220
55947
  const target = newestBlock();
55221
55948
  if (target === undefined || !copyBlock(target.id)) {
@@ -55535,6 +56262,7 @@ ${formatThemeList(getThemeId())}`);
55535
56262
  const secs = ((Date.now() - startedAt) / 1000).toFixed(1);
55536
56263
  stopBusy();
55537
56264
  refreshWorkspaceSidebar();
56265
+ refreshReviewSidebar();
55538
56266
  setMainAgent(turnFailed ? "failed" : "done", turnFailed ? "error" : "idle");
55539
56267
  try {
55540
56268
  flushSessionCheckpoint();
@@ -56903,7 +57631,7 @@ ${indentBlock(style.dim(context), GUTTER)}`);
56903
57631
  out(`${GUTTER}${style.yellow(hint)}
56904
57632
  `);
56905
57633
  }
56906
- const rememberable = !evaled.destructive && !evaled.credentials;
57634
+ const rememberable = !evaled.destructive && !evaled.credentials && !evaled.sacReviewConfirmation;
56907
57635
  const prompt = rememberable ? "[y/N/A=always] " : "[y/N] ";
56908
57636
  out(`
56909
57637
  ${GUTTER}${style.yellow(`Run: ${evaled.command}`)} ${style.dim(prompt)}`);
@@ -57445,7 +58173,7 @@ async function shellCommand(args2, runtime = {}) {
57445
58173
  modelId: sel.model
57446
58174
  }),
57447
58175
  maxToolCalls: resolveAgentMaxToolCalls(),
57448
- idSeq: () => randomUUID23(),
58176
+ idSeq: () => randomUUID25(),
57449
58177
  ...resetSubagentBudget !== undefined ? { resetSubagentBudget } : {}
57450
58178
  };
57451
58179
  };
@@ -57477,7 +58205,7 @@ async function shellCommand(args2, runtime = {}) {
57477
58205
  makeShellDeps: (sel) => ({
57478
58206
  makeProvider: chatFactory,
57479
58207
  clock: () => new Date().toISOString(),
57480
- idSeq: () => randomUUID23(),
58208
+ idSeq: () => randomUUID25(),
57481
58209
  initial: sel,
57482
58210
  session: {
57483
58211
  cwd,
@@ -57548,7 +58276,7 @@ async function shellCommand(args2, runtime = {}) {
57548
58276
  const deps = {
57549
58277
  makeProvider: baseFactory,
57550
58278
  clock: () => new Date().toISOString(),
57551
- idSeq: () => randomUUID23(),
58279
+ idSeq: () => randomUUID25(),
57552
58280
  initial: baseUrl2 === undefined ? { provider, model } : { provider, model, baseUrl: baseUrl2 },
57553
58281
  selectProviderModel: realSelectProviderModel(baseUrl2)
57554
58282
  };
@@ -57599,7 +58327,7 @@ async function shellCommand(args2, runtime = {}) {
57599
58327
  modelId: model
57600
58328
  }),
57601
58329
  maxToolCalls: resolveAgentMaxToolCalls(),
57602
- idSeq: () => randomUUID23(),
58330
+ idSeq: () => randomUUID25(),
57603
58331
  ...resetSubagentBudget !== undefined ? { resetSubagentBudget } : {}
57604
58332
  };
57605
58333
  let resumeId = flags.resumeId;
@@ -57771,7 +58499,7 @@ Shell:
57771
58499
  init_fs();
57772
58500
  import { readFile as readFile78 } from "fs/promises";
57773
58501
  import { stdin } from "process";
57774
- import path147 from "path";
58502
+ import path148 from "path";
57775
58503
  var MODULES = [
57776
58504
  { name: "gdgraph", flag: "--no-gdgraph", desc: "code graph, symbols, affected context", defaultEnabled: true },
57777
58505
  { name: "gdctx", flag: "--no-gdctx", desc: "token-aware command/read output", defaultEnabled: true },
@@ -57817,8 +58545,8 @@ async function modulesCommand(args2 = []) {
57817
58545
  return;
57818
58546
  }
57819
58547
  const wantsJson = args2.includes("--json") && (sub === undefined || sub === "status" || sub === "list" || sub === "--json");
57820
- const metaprojectRoot = path147.join(process.cwd(), ".metaproject");
57821
- const manifestPath = path147.join(metaprojectRoot, "metaproject.json");
58548
+ const metaprojectRoot = path148.join(process.cwd(), ".metaproject");
58549
+ const manifestPath = path148.join(metaprojectRoot, "metaproject.json");
57822
58550
  if (!await pathExists(manifestPath)) {
57823
58551
  if (wantsJson) {
57824
58552
  console.log(JSON.stringify({ schemaVersion: 1, error: "not-initialized", modules: [] }, null, 2));
@@ -57932,18 +58660,18 @@ function printHelp16() {
57932
58660
  }
57933
58661
 
57934
58662
  // src/commands/serve.ts
57935
- import { randomUUID as randomUUID26 } from "crypto";
58663
+ import { randomUUID as randomUUID28 } from "crypto";
57936
58664
 
57937
58665
  // src/lib/serve-config.ts
57938
58666
  init_config_dir();
57939
58667
  import { existsSync as existsSync27 } from "fs";
57940
- import path148 from "path";
58668
+ import path149 from "path";
57941
58669
  var SERVE_CONFIG_SCHEMA_VERSION = "1.0.0";
57942
58670
  var DEFAULT_SERVE_BIND_ADDRESS = "127.0.0.1";
57943
58671
  var DEFAULT_SERVE_PORT = 7377;
57944
58672
  var DEFAULT_SERVE_PROFILE = "remote-restricted";
57945
58673
  function serveConfigPath(dir) {
57946
- return path148.join(keryxConfigDir(dir), "serve.json");
58674
+ return path149.join(keryxConfigDir(dir), "serve.json");
57947
58675
  }
57948
58676
  function parseIpv4(value) {
57949
58677
  const parts = value.split(".");
@@ -58259,7 +58987,7 @@ function saveServeConfig(config, dir, onWarn) {
58259
58987
 
58260
58988
  // src/lib/serve-credential.ts
58261
58989
  init_config_dir();
58262
- import { createHash as createHash33, randomBytes as randomBytes3, randomUUID as randomUUID24 } from "crypto";
58990
+ import { createHash as createHash33, randomBytes as randomBytes3, randomUUID as randomUUID26 } from "crypto";
58263
58991
  import {
58264
58992
  chmodSync as chmodSync4,
58265
58993
  closeSync as closeSync3,
@@ -58271,9 +58999,9 @@ import {
58271
58999
  unlinkSync as unlinkSync3,
58272
59000
  writeFileSync as writeFileSync8
58273
59001
  } from "fs";
58274
- import path149 from "path";
59002
+ import path150 from "path";
58275
59003
  function serveCredentialPath(dir) {
58276
- return path149.join(keryxConfigDir(dir), "serve-credentials.json");
59004
+ return path150.join(keryxConfigDir(dir), "serve-credentials.json");
58277
59005
  }
58278
59006
  function constantTimeEqual(a, b) {
58279
59007
  const width = Math.max(a.length, b.length);
@@ -58343,7 +59071,7 @@ function readServeCredential(dir) {
58343
59071
  }
58344
59072
  function writeStore(store, dir) {
58345
59073
  const file = serveCredentialPath(dir);
58346
- const temp = `${file}.${randomUUID24()}.tmp`;
59074
+ const temp = `${file}.${randomUUID26()}.tmp`;
58347
59075
  try {
58348
59076
  ensureKeryxConfigDir(dir);
58349
59077
  const handle = openSync3(temp, "wx", 384);
@@ -58383,7 +59111,7 @@ function mintRecord(now) {
58383
59111
  const salt = randomBytes3(32).toString("hex");
58384
59112
  return {
58385
59113
  token,
58386
- record: { id: randomUUID24(), algorithm: "sha256", salt, hash: hashToken(salt, token), createdAt: now }
59114
+ record: { id: randomUUID26(), algorithm: "sha256", salt, hash: hashToken(salt, token), createdAt: now }
58387
59115
  };
58388
59116
  }
58389
59117
  function issueServeToken(dir, now = () => new Date().toISOString(), onWaiting) {
@@ -58507,22 +59235,22 @@ class AuthFailureThrottle {
58507
59235
  init_config_dir();
58508
59236
  import { createHash as createHash34 } from "crypto";
58509
59237
  import { existsSync as existsSync29, readdirSync as readdirSync2, rmSync as rmSync2 } from "fs";
58510
- import path150 from "path";
59238
+ import path151 from "path";
58511
59239
  var MAX_TURN_EVENTS = 1e4;
58512
59240
  function turnsRoot(dir) {
58513
- return path150.join(keryxConfigDir(dir), "turns");
59241
+ return path151.join(keryxConfigDir(dir), "turns");
58514
59242
  }
58515
59243
  function turnDir(turnId, dir) {
58516
- return path150.join(turnsRoot(dir), turnId);
59244
+ return path151.join(turnsRoot(dir), turnId);
58517
59245
  }
58518
59246
  function keyPath(project, idempotencyKey, dir) {
58519
59247
  const projectBytes = Buffer.byteLength(project, "utf8");
58520
59248
  const digest2 = createHash34("sha256").update(`${projectBytes}:${project}\x00${idempotencyKey}`, "utf8").digest("hex");
58521
- return path150.join(turnsRoot(dir), "keys", `${digest2}.json`);
59249
+ return path151.join(turnsRoot(dir), "keys", `${digest2}.json`);
58522
59250
  }
58523
59251
  function legacyKeyPath(idempotencyKey, dir) {
58524
59252
  const digest2 = createHash34("sha256").update(idempotencyKey, "utf8").digest("hex");
58525
- return path150.join(turnsRoot(dir), "keys", `${digest2}.json`);
59253
+ return path151.join(turnsRoot(dir), "keys", `${digest2}.json`);
58526
59254
  }
58527
59255
  function adoptLegacyClaim(project, idempotencyKey, dir) {
58528
59256
  const legacy = legacyKeyPath(idempotencyKey, dir);
@@ -58586,7 +59314,7 @@ function ensureTurnDir(turnId, dir) {
58586
59314
  }
58587
59315
  function createTurnRecord(record, dir) {
58588
59316
  ensureTurnDir(record.turnId, dir);
58589
- writeOwnerOnlyFile(path150.join(turnDir(record.turnId, dir), "turn.json"), `${JSON.stringify(record, null, 2)}
59317
+ writeOwnerOnlyFile(path151.join(turnDir(record.turnId, dir), "turn.json"), `${JSON.stringify(record, null, 2)}
58590
59318
  `);
58591
59319
  }
58592
59320
  function appendTurnEvent(event, dir, opts) {
@@ -58595,12 +59323,12 @@ function appendTurnEvent(event, dir, opts) {
58595
59323
  }
58596
59324
  const line = JSON.stringify(event);
58597
59325
  try {
58598
- appendOwnerOnlyLine(path150.join(turnDir(event.turnId, dir), "events.jsonl"), line);
59326
+ appendOwnerOnlyLine(path151.join(turnDir(event.turnId, dir), "events.jsonl"), line);
58599
59327
  } catch (error2) {
58600
59328
  if (error2?.code !== "ENOENT") {
58601
59329
  throw error2;
58602
59330
  }
58603
- appendOwnerOnlyLine(path150.join(ensureTurnDir(event.turnId, dir), "events.jsonl"), line);
59331
+ appendOwnerOnlyLine(path151.join(ensureTurnDir(event.turnId, dir), "events.jsonl"), line);
58604
59332
  }
58605
59333
  return true;
58606
59334
  }
@@ -58608,7 +59336,7 @@ function readTurnEvents(turnId, after = -1, dir) {
58608
59336
  if (!isTurnId(turnId)) {
58609
59337
  return { ok: false, reason: "not-a-turn-id" };
58610
59338
  }
58611
- const read = readTurnFile(path150.join(turnDir(turnId, dir), "events.jsonl"));
59339
+ const read = readTurnFile(path151.join(turnDir(turnId, dir), "events.jsonl"));
58612
59340
  if (!read.ok) {
58613
59341
  if (isDefiniteAbsence2(read.reason)) {
58614
59342
  return { ok: true, value: [] };
@@ -58636,7 +59364,7 @@ function readTurnRecord(turnId, dir) {
58636
59364
  if (!isTurnId(turnId)) {
58637
59365
  return { ok: false, reason: "not-a-turn-id" };
58638
59366
  }
58639
- const read = readTurnFile(path150.join(turnDir(turnId, dir), "turn.json"));
59367
+ const read = readTurnFile(path151.join(turnDir(turnId, dir), "turn.json"));
58640
59368
  if (!read.ok) {
58641
59369
  return { ok: false, reason: read.reason };
58642
59370
  }
@@ -58655,7 +59383,7 @@ function finishTurn(turnId, result, dir) {
58655
59383
  if (!record.ok) {
58656
59384
  return false;
58657
59385
  }
58658
- writeOwnerOnlyFile(path150.join(turnDir(turnId, dir), "turn.json"), `${JSON.stringify({ ...record.value, result }, null, 2)}
59386
+ writeOwnerOnlyFile(path151.join(turnDir(turnId, dir), "turn.json"), `${JSON.stringify({ ...record.value, result }, null, 2)}
58659
59387
  `);
58660
59388
  return true;
58661
59389
  }
@@ -58700,8 +59428,8 @@ function releaseIdempotencyKey(project, idempotencyKey, turnId, dir) {
58700
59428
  }
58701
59429
 
58702
59430
  // src/lib/serve-turn.ts
58703
- import { randomUUID as randomUUID25 } from "crypto";
58704
- import path151 from "path";
59431
+ import { randomUUID as randomUUID27 } from "crypto";
59432
+ import path152 from "path";
58705
59433
  init_service();
58706
59434
  var REMOTE_ORIGIN = "remote:http";
58707
59435
  var MAX_PROMPT_CHARS = 32000;
@@ -58770,9 +59498,9 @@ function isUuid(value) {
58770
59498
  return typeof value === "string" && /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/.test(value);
58771
59499
  }
58772
59500
  function resolveProject(declared, dir) {
58773
- const wanted = path151.resolve(declared);
59501
+ const wanted = path152.resolve(declared);
58774
59502
  for (const entry of listProjects(dir, () => {})) {
58775
- if (path151.resolve(entry.path) === wanted) {
59503
+ if (path152.resolve(entry.path) === wanted) {
58776
59504
  return { ok: true, project: entry.path };
58777
59505
  }
58778
59506
  }
@@ -58819,7 +59547,7 @@ async function redactOut(security, text) {
58819
59547
  async function runRemoteTurn(input2) {
58820
59548
  const scanRoot = input2.scanRoot;
58821
59549
  const security = createSecurityService(scanRoot);
58822
- const newId = input2.newId ?? (() => randomUUID25());
59550
+ const newId = input2.newId ?? (() => randomUUID27());
58823
59551
  const clock = input2.clock ?? (() => new Date().toISOString());
58824
59552
  const turnId = input2.turnId ?? newId();
58825
59553
  const sessionId = input2.request.sessionId ?? newId();
@@ -58962,7 +59690,7 @@ function outcomeOf(status, gate, unresolvedBlockerIds) {
58962
59690
  }
58963
59691
  function createSubmitTurn(deps) {
58964
59692
  return async (request, project) => {
58965
- const turnId = (deps.newId ?? (() => randomUUID25()))();
59693
+ const turnId = (deps.newId ?? (() => randomUUID27()))();
58966
59694
  const scanned = await scanPrompt(deps.dir, request.prompt);
58967
59695
  if (scanned.rejected) {
58968
59696
  return { kind: "rejected" };
@@ -59649,7 +60377,7 @@ function runConfig(args2) {
59649
60377
  return;
59650
60378
  }
59651
60379
  const credential2 = readServeCredential();
59652
- const credentialId = credential2.status === "ok" ? credential2.record.id : randomUUID26();
60380
+ const credentialId = credential2.status === "ok" ? credential2.record.id : randomUUID28();
59653
60381
  const config = defaultServeConfig(credentialId, {
59654
60382
  address: parsed.parsed.values.get("--bind") ?? DEFAULT_SERVE_BIND_ADDRESS,
59655
60383
  port: port ?? DEFAULT_SERVE_PORT,
@@ -59805,9 +60533,9 @@ function printHelp17() {
59805
60533
 
59806
60534
  // src/commands/update.ts
59807
60535
  import { spawn as spawn5 } from "child_process";
59808
- import { chmod as chmod4, mkdir as mkdir54, readFile as readFile79, readdir as readdir24, writeFile as writeFile48 } from "fs/promises";
60536
+ import { chmod as chmod4, mkdir as mkdir54, readFile as readFile79, readdir as readdir25, writeFile as writeFile48 } from "fs/promises";
59809
60537
  import { access as access4, constants as constants2, existsSync as existsSync30 } from "fs";
59810
- import path152 from "path";
60538
+ import path153 from "path";
59811
60539
  import { fileURLToPath as fileURLToPath7 } from "url";
59812
60540
  init_config();
59813
60541
  init_config2();
@@ -59823,8 +60551,8 @@ async function updateCommand(args2 = []) {
59823
60551
  return;
59824
60552
  }
59825
60553
  const projectRoot = process.cwd();
59826
- const metaprojectRoot = path152.join(projectRoot, ".metaproject");
59827
- banner("keryx update", `Refreshing the .metaproject workspace in ${path152.basename(projectRoot)}/`);
60554
+ const metaprojectRoot = path153.join(projectRoot, ".metaproject");
60555
+ banner("keryx update", `Refreshing the .metaproject workspace in ${path153.basename(projectRoot)}/`);
59828
60556
  if (!await pathExists(metaprojectRoot)) {
59829
60557
  console.log(` ${style.red(symbols.cross)} Metaproject is not initialized.`);
59830
60558
  console.log(` ${style.cyan(symbols.arrow)} Run ${style.cyan("keryx init")} first.`);
@@ -59870,12 +60598,12 @@ async function updateCommand(args2 = []) {
59870
60598
  nextSteps(steps);
59871
60599
  }
59872
60600
  async function refreshServiceFiles(projectRoot, options) {
59873
- const metaprojectRoot = path152.join(projectRoot, ".metaproject");
60601
+ const metaprojectRoot = path153.join(projectRoot, ".metaproject");
59874
60602
  const manifestState = await readManifest5(metaprojectRoot);
59875
60603
  const manifest = manifestState.manifest;
59876
60604
  const recoveredManifest = !manifestState.exists || !manifestState.valid;
59877
60605
  if (manifestState.migrated) {
59878
- await writeFile48(path152.join(metaprojectRoot, "metaproject.json"), `${JSON.stringify(manifest, null, 2)}
60606
+ await writeFile48(path153.join(metaprojectRoot, "metaproject.json"), `${JSON.stringify(manifest, null, 2)}
59879
60607
  `, "utf8");
59880
60608
  }
59881
60609
  const enableGdgraph = moduleEnabled2(manifest, "gdgraph");
@@ -59912,11 +60640,11 @@ async function refreshServiceFiles(projectRoot, options) {
59912
60640
  enableSecurity,
59913
60641
  enableSac
59914
60642
  });
59915
- await writeTextIfChanged4(path152.join(metaprojectRoot, "core", "README.md"), renderMetaprojectCoreReadme());
59916
- await writeTextIfChanged4(path152.join(metaprojectRoot, "hooks", "README.md"), renderHooksReadme());
59917
- await writeTextIfChanged4(path152.join(metaprojectRoot, "rules", "README.md"), renderProjectRulesReadme());
59918
- await writeTextIfChanged4(path152.join(metaprojectRoot, "skills", "project-rules", "README.md"), renderProjectRulesSkillReadme({ sources: ruleSources }));
59919
- await writeTextIfChanged4(path152.join(metaprojectRoot, "index.md"), renderIndexMarkdown({
60643
+ await writeTextIfChanged4(path153.join(metaprojectRoot, "core", "README.md"), renderMetaprojectCoreReadme());
60644
+ await writeTextIfChanged4(path153.join(metaprojectRoot, "hooks", "README.md"), renderHooksReadme());
60645
+ await writeTextIfChanged4(path153.join(metaprojectRoot, "rules", "README.md"), renderProjectRulesReadme());
60646
+ await writeTextIfChanged4(path153.join(metaprojectRoot, "skills", "project-rules", "README.md"), renderProjectRulesSkillReadme({ sources: ruleSources }));
60647
+ await writeTextIfChanged4(path153.join(metaprojectRoot, "index.md"), renderIndexMarkdown({
59920
60648
  enableGdgraph,
59921
60649
  enableGdctx,
59922
60650
  enableGdwiki,
@@ -59929,7 +60657,7 @@ async function refreshServiceFiles(projectRoot, options) {
59929
60657
  ruleSources,
59930
60658
  hasDistilledEntrypoints: await hasDistilledEntrypoints(metaprojectRoot)
59931
60659
  }));
59932
- await writeTextIfChanged4(path152.join(metaprojectRoot, "keryx-dashboard.html"), renderMetaprojectDashboardHtml({
60660
+ await writeTextIfChanged4(path153.join(metaprojectRoot, "keryx-dashboard.html"), renderMetaprojectDashboardHtml({
59933
60661
  enableGdgraph,
59934
60662
  enableGdctx,
59935
60663
  enableGdwiki,
@@ -59941,7 +60669,7 @@ async function refreshServiceFiles(projectRoot, options) {
59941
60669
  enableSecurity,
59942
60670
  data: dashboardData
59943
60671
  }));
59944
- await writeTextIfMissing4(path152.join(metaprojectRoot, "README.md"), renderMetaprojectReadme({
60672
+ await writeTextIfMissing4(path153.join(metaprojectRoot, "README.md"), renderMetaprojectReadme({
59945
60673
  enableGdgraph,
59946
60674
  enableGdctx,
59947
60675
  enableGdwiki,
@@ -59954,31 +60682,31 @@ async function refreshServiceFiles(projectRoot, options) {
59954
60682
  }));
59955
60683
  if (enableGdgraph) {
59956
60684
  await installGdgraphCoreScripts2(metaprojectRoot);
59957
- await writeTextIfChanged4(path152.join(metaprojectRoot, "modules", "gdgraph.md"), renderGdgraphManifest());
59958
- await writeTextIfChanged4(path152.join(metaprojectRoot, "core", "gdgraph", "README.md"), renderGdgraphCoreReadme());
59959
- await writeTextIfChanged4(path152.join(metaprojectRoot, "skills", "gdgraph", "SKILL.md"), renderGdgraphSkillReadme());
60685
+ await writeTextIfChanged4(path153.join(metaprojectRoot, "modules", "gdgraph.md"), renderGdgraphManifest());
60686
+ await writeTextIfChanged4(path153.join(metaprojectRoot, "core", "gdgraph", "README.md"), renderGdgraphCoreReadme());
60687
+ await writeTextIfChanged4(path153.join(metaprojectRoot, "skills", "gdgraph", "SKILL.md"), renderGdgraphSkillReadme());
59960
60688
  await seedAssetsLock(metaprojectRoot);
59961
60689
  if (manifest.modules?.gdgraph?.hooks?.gitPostCommit) {
59962
60690
  await installManagedHook2(projectRoot, "post-commit", "gdgraph-post-commit", renderGdgraphPostCommitHook());
59963
60691
  }
59964
60692
  }
59965
60693
  if (enableGdctx) {
59966
- await writeTextIfMissing4(path152.join(metaprojectRoot, "gdctx.config.json"), renderGdctxConfig());
59967
- await writeTextIfChanged4(path152.join(metaprojectRoot, "modules", "gdctx.md"), renderGdctxManifest());
59968
- await writeTextIfChanged4(path152.join(metaprojectRoot, "core", "gdctx", "README.md"), renderGdctxCoreReadme());
59969
- await writeTextIfChanged4(path152.join(metaprojectRoot, "skills", "gdctx", "SKILL.md"), renderGdctxSkillReadme());
60694
+ await writeTextIfMissing4(path153.join(metaprojectRoot, "gdctx.config.json"), renderGdctxConfig());
60695
+ await writeTextIfChanged4(path153.join(metaprojectRoot, "modules", "gdctx.md"), renderGdctxManifest());
60696
+ await writeTextIfChanged4(path153.join(metaprojectRoot, "core", "gdctx", "README.md"), renderGdctxCoreReadme());
60697
+ await writeTextIfChanged4(path153.join(metaprojectRoot, "skills", "gdctx", "SKILL.md"), renderGdctxSkillReadme());
59970
60698
  }
59971
60699
  if (enableGdwiki) {
59972
- await writeTextIfMissing4(path152.join(metaprojectRoot, "wiki", "templates", "page.md"), renderWikiPageTemplate());
59973
- await writeTextIfChanged4(path152.join(metaprojectRoot, "modules", "gdwiki.md"), renderGdwikiManifest());
59974
- await writeTextIfChanged4(path152.join(metaprojectRoot, "skills", "gdwiki", "SKILL.md"), renderGdwikiSkillReadme());
60700
+ await writeTextIfMissing4(path153.join(metaprojectRoot, "wiki", "templates", "page.md"), renderWikiPageTemplate());
60701
+ await writeTextIfChanged4(path153.join(metaprojectRoot, "modules", "gdwiki.md"), renderGdwikiManifest());
60702
+ await writeTextIfChanged4(path153.join(metaprojectRoot, "skills", "gdwiki", "SKILL.md"), renderGdwikiSkillReadme());
59975
60703
  if (manifest.modules?.gdgraph?.hooks?.gitPostCommit) {
59976
60704
  await installManagedHook2(projectRoot, "post-commit", "gdwiki-post-commit", renderGdwikiPostCommitHook());
59977
60705
  }
59978
60706
  }
59979
60707
  if (enableSac) {
59980
- await writeTextIfMissing4(path152.join(metaprojectRoot, "modules", "sac.md"), renderSacManifest());
59981
- await writeTextIfChanged4(path152.join(metaprojectRoot, "skills", "sac", "SKILL.md"), renderSacSkillReadme());
60708
+ await writeTextIfMissing4(path153.join(metaprojectRoot, "modules", "sac.md"), renderSacManifest());
60709
+ await writeTextIfChanged4(path153.join(metaprojectRoot, "skills", "sac", "SKILL.md"), renderSacSkillReadme());
59982
60710
  }
59983
60711
  if (enableGdskills) {
59984
60712
  await installGdskills(metaprojectRoot, gdskillsProfile, { createDataDirs: false });
@@ -59987,25 +60715,25 @@ async function refreshServiceFiles(projectRoot, options) {
59987
60715
  }
59988
60716
  }
59989
60717
  if (enableHealth) {
59990
- await writeTextIfMissing4(path152.join(metaprojectRoot, "health.config.json"), renderHealthConfig());
59991
- await writeTextIfChanged4(path152.join(metaprojectRoot, "modules", "health.md"), renderHealthManifest());
59992
- await writeTextIfChanged4(path152.join(metaprojectRoot, "core", "health", "README.md"), renderHealthCoreReadme());
59993
- await writeTextIfChanged4(path152.join(metaprojectRoot, "skills", "health", "SKILL.md"), renderHealthSkillReadme());
60718
+ await writeTextIfMissing4(path153.join(metaprojectRoot, "health.config.json"), renderHealthConfig());
60719
+ await writeTextIfChanged4(path153.join(metaprojectRoot, "modules", "health.md"), renderHealthManifest());
60720
+ await writeTextIfChanged4(path153.join(metaprojectRoot, "core", "health", "README.md"), renderHealthCoreReadme());
60721
+ await writeTextIfChanged4(path153.join(metaprojectRoot, "skills", "health", "SKILL.md"), renderHealthSkillReadme());
59994
60722
  if (manifest.modules?.health?.hooks?.gitPostCommit) {
59995
60723
  await installManagedHook2(projectRoot, "post-commit", "health-post-commit", renderHealthPostCommitHook());
59996
60724
  }
59997
60725
  }
59998
60726
  if (enableTesting) {
59999
- await writeTextIfMissing4(path152.join(metaprojectRoot, "testing.config.json"), renderTestingConfig({
60727
+ await writeTextIfMissing4(path153.join(metaprojectRoot, "testing.config.json"), renderTestingConfig({
60000
60728
  postCommitRefresh: Boolean(manifest.modules?.testing?.hooks?.gitPostCommit),
60001
60729
  prePushGate: Boolean(manifest.modules?.testing?.hooks?.prePush)
60002
60730
  }));
60003
- await writeTextIfChanged4(path152.join(metaprojectRoot, "modules", "testing.md"), renderTestingManifest());
60004
- await writeTextIfChanged4(path152.join(metaprojectRoot, "core", "testing", "README.md"), renderTestingCoreReadme());
60005
- await writeTextIfChanged4(path152.join(metaprojectRoot, "skills", "testing", "SKILL.md"), renderTestingSkillReadme());
60731
+ await writeTextIfChanged4(path153.join(metaprojectRoot, "modules", "testing.md"), renderTestingManifest());
60732
+ await writeTextIfChanged4(path153.join(metaprojectRoot, "core", "testing", "README.md"), renderTestingCoreReadme());
60733
+ await writeTextIfChanged4(path153.join(metaprojectRoot, "skills", "testing", "SKILL.md"), renderTestingSkillReadme());
60006
60734
  if (enableGdwiki) {
60007
- await writeTextIfMissing4(path152.join(metaprojectRoot, "wiki", "testing", "README.md"), renderTestingWikiReadme());
60008
- await writeTextIfMissing4(path152.join(metaprojectRoot, "wiki", "testing", "conventions.md"), renderTestingWikiConventions());
60735
+ await writeTextIfMissing4(path153.join(metaprojectRoot, "wiki", "testing", "README.md"), renderTestingWikiReadme());
60736
+ await writeTextIfMissing4(path153.join(metaprojectRoot, "wiki", "testing", "conventions.md"), renderTestingWikiConventions());
60009
60737
  }
60010
60738
  if (manifest.modules?.testing?.hooks?.gitPostCommit) {
60011
60739
  await installManagedHook2(projectRoot, "post-commit", "testing-post-commit", renderTestingPostCommitHook());
@@ -60018,24 +60746,24 @@ async function refreshServiceFiles(projectRoot, options) {
60018
60746
  await installManagedHook2(projectRoot, "post-commit", "metaproject-dashboard-post-commit", renderMetaprojectDashboardPostCommitHook());
60019
60747
  }
60020
60748
  if (enableMemory) {
60021
- await writeTextIfMissing4(path152.join(metaprojectRoot, "memory.config.json"), renderMemoryConfig());
60022
- await writeTextIfMissing4(path152.join(metaprojectRoot, "memory", "templates", "entry.md"), renderMemoryEntryTemplate());
60023
- await writeTextIfChanged4(path152.join(metaprojectRoot, "modules", "memory.md"), renderMemoryManifest());
60024
- await writeTextIfChanged4(path152.join(metaprojectRoot, "core", "memory", "README.md"), renderMemoryCoreReadme());
60025
- await writeTextIfChanged4(path152.join(metaprojectRoot, "skills", "memory", "SKILL.md"), renderMemorySkillReadme());
60749
+ await writeTextIfMissing4(path153.join(metaprojectRoot, "memory.config.json"), renderMemoryConfig());
60750
+ await writeTextIfMissing4(path153.join(metaprojectRoot, "memory", "templates", "entry.md"), renderMemoryEntryTemplate());
60751
+ await writeTextIfChanged4(path153.join(metaprojectRoot, "modules", "memory.md"), renderMemoryManifest());
60752
+ await writeTextIfChanged4(path153.join(metaprojectRoot, "core", "memory", "README.md"), renderMemoryCoreReadme());
60753
+ await writeTextIfChanged4(path153.join(metaprojectRoot, "skills", "memory", "SKILL.md"), renderMemorySkillReadme());
60026
60754
  }
60027
60755
  if (enableTasks) {
60028
- await writeTextIfChanged4(path152.join(metaprojectRoot, "flows", "README.md"), renderFlowsReadme());
60029
- await writeTextIfChanged4(path152.join(metaprojectRoot, "modules", "tasks.md"), renderTasksManifest());
60030
- await writeTextIfChanged4(path152.join(metaprojectRoot, "skills", "flow", "SKILL.md"), renderFlowSkillRouter());
60031
- await writeTextIfChanged4(path152.join(metaprojectRoot, "skills", "flow", "init.md"), renderFlowInitSkill());
60032
- await writeTextIfChanged4(path152.join(metaprojectRoot, "skills", "flow", "manage.md"), renderFlowManageSkill());
60033
- await writeTextIfChanged4(path152.join(metaprojectRoot, "skills", "flow", "complete.md"), renderFlowCompleteSkill());
60756
+ await writeTextIfChanged4(path153.join(metaprojectRoot, "flows", "README.md"), renderFlowsReadme());
60757
+ await writeTextIfChanged4(path153.join(metaprojectRoot, "modules", "tasks.md"), renderTasksManifest());
60758
+ await writeTextIfChanged4(path153.join(metaprojectRoot, "skills", "flow", "SKILL.md"), renderFlowSkillRouter());
60759
+ await writeTextIfChanged4(path153.join(metaprojectRoot, "skills", "flow", "init.md"), renderFlowInitSkill());
60760
+ await writeTextIfChanged4(path153.join(metaprojectRoot, "skills", "flow", "manage.md"), renderFlowManageSkill());
60761
+ await writeTextIfChanged4(path153.join(metaprojectRoot, "skills", "flow", "complete.md"), renderFlowCompleteSkill());
60034
60762
  }
60035
60763
  if (enableSecurity) {
60036
- await writeTextIfMissing4(path152.join(metaprojectRoot, "security.config.json"), renderSecurityConfig());
60037
- await writeTextIfChanged4(path152.join(metaprojectRoot, "modules", "security.md"), renderSecurityManifest());
60038
- await writeTextIfChanged4(path152.join(metaprojectRoot, "core", "security", "README.md"), renderSecurityCoreReadme());
60764
+ await writeTextIfMissing4(path153.join(metaprojectRoot, "security.config.json"), renderSecurityConfig());
60765
+ await writeTextIfChanged4(path153.join(metaprojectRoot, "modules", "security.md"), renderSecurityManifest());
60766
+ await writeTextIfChanged4(path153.join(metaprojectRoot, "core", "security", "README.md"), renderSecurityCoreReadme());
60039
60767
  if (manifest.modules?.security?.hooks?.prePush) {
60040
60768
  await installManagedHook2(projectRoot, "pre-push", "security-pre-push", renderSecurityPrePushHook());
60041
60769
  }
@@ -60086,13 +60814,13 @@ async function refreshServiceFiles(projectRoot, options) {
60086
60814
  };
60087
60815
  }
60088
60816
  async function buildDashboard(projectRoot = process.cwd()) {
60089
- const metaprojectRoot = path152.join(projectRoot, ".metaproject");
60817
+ const metaprojectRoot = path153.join(projectRoot, ".metaproject");
60090
60818
  if (!await pathExists(metaprojectRoot)) {
60091
60819
  throw new Error("Metaproject is not initialized. Run: keryx init");
60092
60820
  }
60093
60821
  const manifest = (await readManifest5(metaprojectRoot)).manifest;
60094
60822
  const data = await collectDashboardData(metaprojectRoot);
60095
- const dashboardPath = path152.join(metaprojectRoot, "keryx-dashboard.html");
60823
+ const dashboardPath = path153.join(metaprojectRoot, "keryx-dashboard.html");
60096
60824
  await writeTextIfChanged4(dashboardPath, renderMetaprojectDashboardHtml({
60097
60825
  enableGdgraph: moduleEnabled2(manifest, "gdgraph"),
60098
60826
  enableGdctx: moduleEnabled2(manifest, "gdctx"),
@@ -60112,7 +60840,7 @@ async function shouldInstallDashboardPostCommitHook(projectRoot, manifest) {
60112
60840
  if (Object.values(modules).some((module) => Boolean(module.hooks?.gitPostCommit))) {
60113
60841
  return true;
60114
60842
  }
60115
- const hookPath = path152.join(projectRoot, ".git", "hooks", "post-commit");
60843
+ const hookPath = path153.join(projectRoot, ".git", "hooks", "post-commit");
60116
60844
  if (!await pathExists(hookPath)) {
60117
60845
  return false;
60118
60846
  }
@@ -60132,11 +60860,11 @@ async function collectDashboardData(metaprojectRoot) {
60132
60860
  if (testing) {
60133
60861
  data.testing = testing;
60134
60862
  }
60135
- const wiki = await collectMarkdownPages(path152.join(metaprojectRoot, "wiki"), "wiki");
60863
+ const wiki = await collectMarkdownPages(path153.join(metaprojectRoot, "wiki"), "wiki");
60136
60864
  if (wiki.length > 0) {
60137
60865
  data.wiki = { pages: wiki };
60138
60866
  }
60139
- const memory = await collectMarkdownPages(path152.join(metaprojectRoot, "memory"), "memory");
60867
+ const memory = await collectMarkdownPages(path153.join(metaprojectRoot, "memory"), "memory");
60140
60868
  if (memory.length > 0) {
60141
60869
  data.memory = { entries: memory };
60142
60870
  }
@@ -60151,19 +60879,19 @@ async function collectDashboardData(metaprojectRoot) {
60151
60879
  return data;
60152
60880
  }
60153
60881
  async function collectTasksDashboardData(metaprojectRoot) {
60154
- const flowsRoot2 = path152.join(metaprojectRoot, "flows");
60882
+ const flowsRoot2 = path153.join(metaprojectRoot, "flows");
60155
60883
  if (!await pathExists(flowsRoot2)) {
60156
60884
  return null;
60157
60885
  }
60158
60886
  let dirEntries;
60159
60887
  try {
60160
- dirEntries = (await readdir24(flowsRoot2, { withFileTypes: true })).filter((entry) => entry.isDirectory() && /^\d{3}-/.test(entry.name)).map((entry) => entry.name).sort();
60888
+ dirEntries = (await readdir25(flowsRoot2, { withFileTypes: true })).filter((entry) => entry.isDirectory() && /^\d{3}-/.test(entry.name)).map((entry) => entry.name).sort();
60161
60889
  } catch {
60162
60890
  return null;
60163
60891
  }
60164
60892
  const flows = [];
60165
60893
  for (const dir of dirEntries) {
60166
- const flowPath = path152.join(flowsRoot2, dir, "flow.json");
60894
+ const flowPath = path153.join(flowsRoot2, dir, "flow.json");
60167
60895
  if (!await pathExists(flowPath)) {
60168
60896
  continue;
60169
60897
  }
@@ -60171,7 +60899,7 @@ async function collectTasksDashboardData(metaprojectRoot) {
60171
60899
  const flow = JSON.parse(await readFile79(flowPath, "utf8"));
60172
60900
  const tasks = Array.isArray(flow.tasks) ? flow.tasks : [];
60173
60901
  let acTotal = 0;
60174
- const acPath2 = path152.join(flowsRoot2, dir, "acceptance-criteria.md");
60902
+ const acPath2 = path153.join(flowsRoot2, dir, "acceptance-criteria.md");
60175
60903
  if (await pathExists(acPath2)) {
60176
60904
  const acContent = await readFile79(acPath2, "utf8");
60177
60905
  acTotal = (acContent.match(/^- AC\d+:/gm) ?? []).length;
@@ -60225,7 +60953,7 @@ async function collectDashboardDocs(metaprojectRoot, wiki, memory) {
60225
60953
  "data/testing/context.md"
60226
60954
  ];
60227
60955
  for (const href of staticHrefs) {
60228
- const filePath = path152.join(metaprojectRoot, ...href.split("/"));
60956
+ const filePath = path153.join(metaprojectRoot, ...href.split("/"));
60229
60957
  if (!await pathExists(filePath)) {
60230
60958
  continue;
60231
60959
  }
@@ -60242,7 +60970,7 @@ async function collectDashboardDocs(metaprojectRoot, wiki, memory) {
60242
60970
  return docs;
60243
60971
  }
60244
60972
  async function collectHealthDashboardData(metaprojectRoot) {
60245
- const reportPath2 = path152.join(metaprojectRoot, "data", "health", "artifacts", "latest.json");
60973
+ const reportPath2 = path153.join(metaprojectRoot, "data", "health", "artifacts", "latest.json");
60246
60974
  if (!await pathExists(reportPath2)) {
60247
60975
  return;
60248
60976
  }
@@ -60351,8 +61079,8 @@ function metricToScope(metric) {
60351
61079
  };
60352
61080
  }
60353
61081
  async function collectGraphDashboardData(metaprojectRoot) {
60354
- const nodesPath = path152.join(metaprojectRoot, "data", "gdgraph", "storage", "nodes.jsonl");
60355
- const edgesPath = path152.join(metaprojectRoot, "data", "gdgraph", "storage", "edges.jsonl");
61082
+ const nodesPath = path153.join(metaprojectRoot, "data", "gdgraph", "storage", "nodes.jsonl");
61083
+ const edgesPath = path153.join(metaprojectRoot, "data", "gdgraph", "storage", "edges.jsonl");
60356
61084
  if (!await pathExists(nodesPath) || !await pathExists(edgesPath)) {
60357
61085
  return;
60358
61086
  }
@@ -60403,8 +61131,8 @@ async function collectGraphDashboardData(metaprojectRoot) {
60403
61131
  };
60404
61132
  }
60405
61133
  async function collectTestingDashboardData(metaprojectRoot) {
60406
- const reportPath2 = path152.join(metaprojectRoot, "data", "testing", "artifacts", "latest.json");
60407
- const contextPath = path152.join(metaprojectRoot, "data", "testing", "context.md");
61134
+ const reportPath2 = path153.join(metaprojectRoot, "data", "testing", "artifacts", "latest.json");
61135
+ const contextPath = path153.join(metaprojectRoot, "data", "testing", "context.md");
60408
61136
  if (await pathExists(reportPath2)) {
60409
61137
  const report = JSON.parse(await readFile79(reportPath2, "utf8"));
60410
61138
  const totalTests = numberOrUndefined(report.total);
@@ -60433,7 +61161,7 @@ async function collectMarkdownPages(root, hrefPrefix) {
60433
61161
  const files = await listMarkdownFiles(root);
60434
61162
  const pages = [];
60435
61163
  for (const filePath of files.slice(0, 40)) {
60436
- const relativePath = path152.relative(root, filePath).split(path152.sep).join("/");
61164
+ const relativePath = path153.relative(root, filePath).split(path153.sep).join("/");
60437
61165
  if (relativePath === "index.md" || relativePath.startsWith("templates/")) {
60438
61166
  continue;
60439
61167
  }
@@ -60451,10 +61179,10 @@ async function collectMarkdownPages(root, hrefPrefix) {
60451
61179
  return pages;
60452
61180
  }
60453
61181
  async function listMarkdownFiles(root) {
60454
- const entries = await readdir24(root, { withFileTypes: true });
61182
+ const entries = await readdir25(root, { withFileTypes: true });
60455
61183
  const files = [];
60456
61184
  for (const entry of entries) {
60457
- const fullPath = path152.join(root, entry.name);
61185
+ const fullPath = path153.join(root, entry.name);
60458
61186
  if (entry.isDirectory()) {
60459
61187
  files.push(...await listMarkdownFiles(fullPath));
60460
61188
  } else if (entry.isFile() && entry.name.endsWith(".md")) {
@@ -60502,7 +61230,7 @@ async function writeRecoveredManifest(metaprojectRoot, modules) {
60502
61230
  const manifest = {
60503
61231
  schemaVersion: 1,
60504
61232
  standardVersion: STANDARD_VERSION,
60505
- name: `${path152.basename(path152.dirname(metaprojectRoot))}-metaproject`,
61233
+ name: `${path153.basename(path153.dirname(metaprojectRoot))}-metaproject`,
60506
61234
  createdBy: "keryx",
60507
61235
  profiles: computeProfiles(enabledModuleKeys2),
60508
61236
  paths: {
@@ -60603,11 +61331,11 @@ async function writeRecoveredManifest(metaprojectRoot, modules) {
60603
61331
  metaproject: ".metaproject/index.md"
60604
61332
  }
60605
61333
  };
60606
- await writeFile48(path152.join(metaprojectRoot, "metaproject.json"), `${JSON.stringify(manifest, null, 2)}
61334
+ await writeFile48(path153.join(metaprojectRoot, "metaproject.json"), `${JSON.stringify(manifest, null, 2)}
60607
61335
  `, "utf8");
60608
61336
  }
60609
61337
  async function enableTasksInManifest(metaprojectRoot) {
60610
- const manifestPath = path152.join(metaprojectRoot, "metaproject.json");
61338
+ const manifestPath = path153.join(metaprojectRoot, "metaproject.json");
60611
61339
  if (!await pathExists(manifestPath)) {
60612
61340
  return;
60613
61341
  }
@@ -60630,7 +61358,7 @@ async function enableTasksInManifest(metaprojectRoot) {
60630
61358
  `, "utf8");
60631
61359
  }
60632
61360
  async function updateManifestAgentEntrypoints(metaprojectRoot, ruleSources) {
60633
- const manifestPath = path152.join(metaprojectRoot, "metaproject.json");
61361
+ const manifestPath = path153.join(metaprojectRoot, "metaproject.json");
60634
61362
  if (!await pathExists(manifestPath)) {
60635
61363
  return;
60636
61364
  }
@@ -60666,72 +61394,72 @@ async function updateRuntime(projectRoot) {
60666
61394
  }
60667
61395
  }
60668
61396
  async function findRuntimeRoot(projectRoot) {
60669
- const projectRuntime = path152.join(projectRoot, ".metaproject", "runtime", "keryx");
60670
- if (await pathExists(path152.join(projectRuntime, ".git"))) {
61397
+ const projectRuntime = path153.join(projectRoot, ".metaproject", "runtime", "keryx");
61398
+ if (await pathExists(path153.join(projectRuntime, ".git"))) {
60671
61399
  return projectRuntime;
60672
61400
  }
60673
61401
  const home = process.env.HOME;
60674
61402
  if (!home) {
60675
61403
  return null;
60676
61404
  }
60677
- const globalRuntime = path152.join(home, ".keryx", "keryx");
60678
- if (await pathExists(path152.join(globalRuntime, ".git"))) {
61405
+ const globalRuntime = path153.join(home, ".keryx", "keryx");
61406
+ if (await pathExists(path153.join(globalRuntime, ".git"))) {
60679
61407
  return globalRuntime;
60680
61408
  }
60681
61409
  return null;
60682
61410
  }
60683
61411
  async function createServiceDirs(metaprojectRoot, modules) {
60684
61412
  const dirs = [
60685
- path152.join(metaprojectRoot, "core"),
60686
- path152.join(metaprojectRoot, "hooks", "post-update.d"),
60687
- path152.join(metaprojectRoot, "modules"),
60688
- path152.join(metaprojectRoot, "rules"),
60689
- path152.join(metaprojectRoot, "skills", "project-rules"),
61413
+ path153.join(metaprojectRoot, "core"),
61414
+ path153.join(metaprojectRoot, "hooks", "post-update.d"),
61415
+ path153.join(metaprojectRoot, "modules"),
61416
+ path153.join(metaprojectRoot, "rules"),
61417
+ path153.join(metaprojectRoot, "skills", "project-rules"),
60690
61418
  ...modules.enableGdgraph ? [
60691
- path152.join(metaprojectRoot, "core", "gdgraph"),
60692
- path152.join(metaprojectRoot, "skills", "gdgraph")
61419
+ path153.join(metaprojectRoot, "core", "gdgraph"),
61420
+ path153.join(metaprojectRoot, "skills", "gdgraph")
60693
61421
  ] : [],
60694
61422
  ...modules.enableGdctx ? [
60695
- path152.join(metaprojectRoot, "core", "gdctx"),
60696
- path152.join(metaprojectRoot, "skills", "gdctx")
61423
+ path153.join(metaprojectRoot, "core", "gdctx"),
61424
+ path153.join(metaprojectRoot, "skills", "gdctx")
60697
61425
  ] : [],
60698
61426
  ...modules.enableGdwiki ? [
60699
- path152.join(metaprojectRoot, "skills", "gdwiki"),
60700
- path152.join(metaprojectRoot, "wiki", "templates")
61427
+ path153.join(metaprojectRoot, "skills", "gdwiki"),
61428
+ path153.join(metaprojectRoot, "wiki", "templates")
60701
61429
  ] : [],
60702
61430
  ...modules.enableHealth ? [
60703
- path152.join(metaprojectRoot, "core", "health"),
60704
- path152.join(metaprojectRoot, "skills", "health")
61431
+ path153.join(metaprojectRoot, "core", "health"),
61432
+ path153.join(metaprojectRoot, "skills", "health")
60705
61433
  ] : [],
60706
61434
  ...modules.enableTesting ? [
60707
- path152.join(metaprojectRoot, "core", "testing"),
60708
- path152.join(metaprojectRoot, "skills", "testing")
61435
+ path153.join(metaprojectRoot, "core", "testing"),
61436
+ path153.join(metaprojectRoot, "skills", "testing")
60709
61437
  ] : [],
60710
61438
  ...modules.enableMemory ? [
60711
- path152.join(metaprojectRoot, "core", "memory"),
60712
- path152.join(metaprojectRoot, "skills", "memory"),
60713
- path152.join(metaprojectRoot, "memory", "templates")
61439
+ path153.join(metaprojectRoot, "core", "memory"),
61440
+ path153.join(metaprojectRoot, "skills", "memory"),
61441
+ path153.join(metaprojectRoot, "memory", "templates")
60714
61442
  ] : [],
60715
61443
  ...modules.enableTasks ? [
60716
- path152.join(metaprojectRoot, "flows"),
60717
- path152.join(metaprojectRoot, "skills", "flow")
61444
+ path153.join(metaprojectRoot, "flows"),
61445
+ path153.join(metaprojectRoot, "skills", "flow")
60718
61446
  ] : [],
60719
61447
  ...modules.enableSecurity ? [
60720
- path152.join(metaprojectRoot, "core", "security")
61448
+ path153.join(metaprojectRoot, "core", "security")
60721
61449
  ] : [],
60722
61450
  ...modules.enableSac ? [
60723
- path152.join(metaprojectRoot, "skills", "sac")
61451
+ path153.join(metaprojectRoot, "skills", "sac")
60724
61452
  ] : []
60725
61453
  ];
60726
61454
  await Promise.all(dirs.map((dir) => mkdir54(dir, { recursive: true })));
60727
61455
  }
60728
61456
  async function installGdgraphCoreScripts2(metaprojectRoot) {
60729
- const gdgraphCoreRoot = path152.join(metaprojectRoot, "core", "gdgraph");
61457
+ const gdgraphCoreRoot = path153.join(metaprojectRoot, "core", "gdgraph");
60730
61458
  await mkdir54(gdgraphCoreRoot, { recursive: true });
60731
61459
  for (const file of GDGRAPH_CORE_SOURCES) {
60732
- await copyFileIfChanged2(runtimeSourcePath2(`../gdgraph/${file}`), path152.join(gdgraphCoreRoot, file));
61460
+ await copyFileIfChanged2(runtimeSourcePath2(`../gdgraph/${file}`), path153.join(gdgraphCoreRoot, file));
60733
61461
  }
60734
- await writeTextIfChanged4(path152.join(gdgraphCoreRoot, "cli.ts"), renderGdgraphCoreCli());
61462
+ await writeTextIfChanged4(path153.join(gdgraphCoreRoot, "cli.ts"), renderGdgraphCoreCli());
60735
61463
  }
60736
61464
  async function installManagedHook2(projectRoot, hookName, blockId, content) {
60737
61465
  const hooksRoot = await resolveGitHooksRoot(projectRoot);
@@ -60739,7 +61467,7 @@ async function installManagedHook2(projectRoot, hookName, blockId, content) {
60739
61467
  return;
60740
61468
  }
60741
61469
  await mkdir54(hooksRoot, { recursive: true });
60742
- const hookPath = path152.join(hooksRoot, hookName);
61470
+ const hookPath = path153.join(hooksRoot, hookName);
60743
61471
  const blockStart = `# keryx:${blockId}:begin`;
60744
61472
  const blockEnd = `# keryx:${blockId}:end`;
60745
61473
  const managedBlock = `${blockStart}
@@ -60760,7 +61488,7 @@ async function removeManagedHook2(projectRoot, hookName, blockId) {
60760
61488
  if (!hooksRoot) {
60761
61489
  return;
60762
61490
  }
60763
- const hookPath = path152.join(hooksRoot, hookName);
61491
+ const hookPath = path153.join(hooksRoot, hookName);
60764
61492
  if (!await pathExists(hookPath)) {
60765
61493
  return;
60766
61494
  }
@@ -60782,7 +61510,7 @@ async function prePushHasSecurityBlock2(projectRoot) {
60782
61510
  if (!hooksRoot) {
60783
61511
  return false;
60784
61512
  }
60785
- const hookPath = path152.join(hooksRoot, "pre-push");
61513
+ const hookPath = path153.join(hooksRoot, "pre-push");
60786
61514
  if (!await pathExists(hookPath)) {
60787
61515
  return false;
60788
61516
  }
@@ -60797,7 +61525,7 @@ async function agentSettingsHasSecuritySentinel2(projectRoot) {
60797
61525
  return (await readFile79(file, "utf8")).includes(AGENT_HOOKS_SENTINEL);
60798
61526
  }
60799
61527
  async function readManifest5(metaprojectRoot) {
60800
- const manifestPath = path152.join(metaprojectRoot, "metaproject.json");
61528
+ const manifestPath = path153.join(metaprojectRoot, "metaproject.json");
60801
61529
  if (!await pathExists(manifestPath)) {
60802
61530
  return {
60803
61531
  exists: false,
@@ -60866,7 +61594,7 @@ async function inferManifestFromExistingMetaproject(metaprojectRoot) {
60866
61594
  }
60867
61595
  async function anyPathExists(root, candidates) {
60868
61596
  for (const candidate of candidates) {
60869
- if (await pathExists(path152.join(root, candidate))) {
61597
+ if (await pathExists(path153.join(root, candidate))) {
60870
61598
  return true;
60871
61599
  }
60872
61600
  }
@@ -60887,13 +61615,13 @@ function parseUpdateArgs(args2) {
60887
61615
  };
60888
61616
  }
60889
61617
  async function runPostUpdateHooks(projectRoot) {
60890
- const hooksDir = path152.join(projectRoot, ".metaproject", "hooks", "post-update.d");
61618
+ const hooksDir = path153.join(projectRoot, ".metaproject", "hooks", "post-update.d");
60891
61619
  if (!await pathExists(hooksDir)) {
60892
61620
  return;
60893
61621
  }
60894
- const entries = (await readdir24(hooksDir)).sort();
61622
+ const entries = (await readdir25(hooksDir)).sort();
60895
61623
  for (const entry of entries) {
60896
- const hookPath = path152.join(hooksDir, entry);
61624
+ const hookPath = path153.join(hooksDir, entry);
60897
61625
  try {
60898
61626
  await accessExecutable(hookPath);
60899
61627
  } catch {
@@ -60934,14 +61662,14 @@ async function writeTextIfChanged4(filePath, content) {
60934
61662
  if (await pathExists(filePath) && await readFile79(filePath, "utf8") === content) {
60935
61663
  return;
60936
61664
  }
60937
- await mkdir54(path152.dirname(filePath), { recursive: true });
61665
+ await mkdir54(path153.dirname(filePath), { recursive: true });
60938
61666
  await writeFile48(filePath, content, "utf8");
60939
61667
  }
60940
61668
  async function writeTextIfMissing4(filePath, content) {
60941
61669
  if (await pathExists(filePath)) {
60942
61670
  return;
60943
61671
  }
60944
- await mkdir54(path152.dirname(filePath), { recursive: true });
61672
+ await mkdir54(path153.dirname(filePath), { recursive: true });
60945
61673
  await writeFile48(filePath, content, "utf8");
60946
61674
  }
60947
61675
  async function copyFileIfChanged2(from, to) {
@@ -60949,7 +61677,7 @@ async function copyFileIfChanged2(from, to) {
60949
61677
  if (await pathExists(to) && await readFile79(to, "utf8") === next) {
60950
61678
  return;
60951
61679
  }
60952
- await mkdir54(path152.dirname(to), { recursive: true });
61680
+ await mkdir54(path153.dirname(to), { recursive: true });
60953
61681
  await writeFile48(to, next, "utf8");
60954
61682
  }
60955
61683
  function runtimeSourcePath2(relativePath) {
@@ -60958,7 +61686,7 @@ function runtimeSourcePath2(relativePath) {
60958
61686
  return directPath;
60959
61687
  }
60960
61688
  if (relativePath.startsWith("../")) {
60961
- const packagedSourcePath = path152.join(path152.dirname(fileURLToPath7(import.meta.url)), "..", "src", relativePath.slice(3));
61689
+ const packagedSourcePath = path153.join(path153.dirname(fileURLToPath7(import.meta.url)), "..", "src", relativePath.slice(3));
60962
61690
  if (existsSync30(packagedSourcePath)) {
60963
61691
  return packagedSourcePath;
60964
61692
  }
@@ -60990,7 +61718,7 @@ function printHelp18() {
60990
61718
 
60991
61719
  // src/commands/dashboard.ts
60992
61720
  import { spawn as spawn6 } from "child_process";
60993
- import path153 from "path";
61721
+ import path154 from "path";
60994
61722
  init_args();
60995
61723
  async function dashboardCommand(args2 = []) {
60996
61724
  const options = parseOptions(args2);
@@ -61001,7 +61729,7 @@ async function dashboardCommand(args2 = []) {
61001
61729
  }
61002
61730
  if (subcommand === "build") {
61003
61731
  const result = await buildDashboard();
61004
- const rel = path153.relative(process.cwd(), result.path);
61732
+ const rel = path154.relative(process.cwd(), result.path);
61005
61733
  console.log(` ${style.green(symbols.ok)} Dashboard built ${style.cyan(symbols.arrow)} ${style.cyan(rel)}`);
61006
61734
  note(`Open it: keryx dashboard open`);
61007
61735
  return;
@@ -61009,7 +61737,7 @@ async function dashboardCommand(args2 = []) {
61009
61737
  if (subcommand === "open") {
61010
61738
  const result = await buildDashboard();
61011
61739
  await openFile(result.path);
61012
- const rel = path153.relative(process.cwd(), result.path);
61740
+ const rel = path154.relative(process.cwd(), result.path);
61013
61741
  console.log(` ${style.green(symbols.ok)} Opened ${style.cyan(rel)}`);
61014
61742
  return;
61015
61743
  }
@@ -61058,7 +61786,7 @@ import { readFileSync as readFileSync10 } from "fs";
61058
61786
  // src/agents/bootstrap.ts
61059
61787
  import { mkdir as mkdir55, readFile as readFile80, writeFile as writeFile49 } from "fs/promises";
61060
61788
  import { homedir as homedir7 } from "os";
61061
- import path154 from "path";
61789
+ import path155 from "path";
61062
61790
  init_fs();
61063
61791
  var AGENT_BOOTSTRAP_START = "<!-- keryx:global-bootstrap -->";
61064
61792
  var AGENT_BOOTSTRAP_END = "<!-- /keryx:global-bootstrap -->";
@@ -61068,35 +61796,35 @@ var AGENT_BOOTSTRAP_RUNTIMES = [
61068
61796
  aliases: ["claude-code"],
61069
61797
  label: "Claude Code",
61070
61798
  fileName: "CLAUDE.md",
61071
- filePath: (homeRoot) => path154.join(homeRoot, ".claude", "CLAUDE.md")
61799
+ filePath: (homeRoot) => path155.join(homeRoot, ".claude", "CLAUDE.md")
61072
61800
  },
61073
61801
  {
61074
61802
  id: "opencode",
61075
61803
  aliases: ["open-code"],
61076
61804
  label: "OpenCode",
61077
61805
  fileName: "AGENTS.md",
61078
- filePath: (homeRoot) => path154.join(homeRoot, ".config", "opencode", "AGENTS.md")
61806
+ filePath: (homeRoot) => path155.join(homeRoot, ".config", "opencode", "AGENTS.md")
61079
61807
  },
61080
61808
  {
61081
61809
  id: "zcode",
61082
61810
  aliases: ["zed", "zed-code"],
61083
61811
  label: "ZCode",
61084
61812
  fileName: "AGENTS.md",
61085
- filePath: (homeRoot) => path154.join(homeRoot, ".zcode", "AGENTS.md")
61813
+ filePath: (homeRoot) => path155.join(homeRoot, ".zcode", "AGENTS.md")
61086
61814
  },
61087
61815
  {
61088
61816
  id: "codex",
61089
61817
  aliases: [],
61090
61818
  label: "Codex",
61091
61819
  fileName: "AGENTS.md",
61092
- filePath: (homeRoot) => path154.join(homeRoot, ".codex", "AGENTS.md")
61820
+ filePath: (homeRoot) => path155.join(homeRoot, ".codex", "AGENTS.md")
61093
61821
  },
61094
61822
  {
61095
61823
  id: "antigravity",
61096
61824
  aliases: ["antigravuty", "antigravity-code"],
61097
61825
  label: "Antigravity",
61098
61826
  fileName: "AGENTS.md",
61099
- filePath: (homeRoot) => path154.join(homeRoot, ".config", "antigravity", "AGENTS.md")
61827
+ filePath: (homeRoot) => path155.join(homeRoot, ".config", "antigravity", "AGENTS.md")
61100
61828
  }
61101
61829
  ];
61102
61830
  function agentBootstrapRuntimeIds() {
@@ -61144,7 +61872,7 @@ async function installAgentBootstrap(runtime, options = {}) {
61144
61872
  const dryRun = options.dryRun === true;
61145
61873
  const wrote = next !== current;
61146
61874
  if (wrote && !dryRun) {
61147
- await mkdir55(path154.dirname(filePath), { recursive: true });
61875
+ await mkdir55(path155.dirname(filePath), { recursive: true });
61148
61876
  await writeFile49(filePath, next, "utf8");
61149
61877
  }
61150
61878
  const status = dryRun ? statusFromContent(runtime, filePath, exists2, next) : await agentBootstrapStatus(runtime, homeRoot);
@@ -61492,7 +62220,7 @@ function printBootstrapHelp() {
61492
62220
  // src/commands/metrics.ts
61493
62221
  init_args();
61494
62222
  import { readFile as readFile81 } from "fs/promises";
61495
- import path156 from "path";
62223
+ import path157 from "path";
61496
62224
 
61497
62225
  // src/metrics/benchmark.ts
61498
62226
  var RELIABILITIES2 = new Set(["exact", "estimated", "unknown"]);
@@ -62271,7 +62999,7 @@ function buildContainmentManifest(inputs, options = {}) {
62271
62999
 
62272
63000
  // src/metrics/oracle-runner.ts
62273
63001
  import { mkdir as mkdir56, writeFile as writeFile50 } from "fs/promises";
62274
- import path155 from "path";
63002
+ import path156 from "path";
62275
63003
 
62276
63004
  // src/metrics/ir.ts
62277
63005
  function toIdSet(ids) {
@@ -62713,9 +63441,9 @@ function buildEvidenceBundle(input2, options = {}) {
62713
63441
  async function persistEvidenceBundle(outDir, bundle, ladder = "metastore") {
62714
63442
  const safeTarget = bundle.target.replace(/[^A-Za-z0-9._/-]/g, "_");
62715
63443
  const safeCase = bundle.caseId.replace(/[^A-Za-z0-9._-]/g, "_");
62716
- const dir = path155.join(outDir, "bench", ladder, safeTarget, safeCase, bundle.variant, String(bundle.seed));
63444
+ const dir = path156.join(outDir, "bench", ladder, safeTarget, safeCase, bundle.variant, String(bundle.seed));
62717
63445
  await mkdir56(dir, { recursive: true });
62718
- const write = (name, value) => writeFile50(path155.join(dir, name), `${JSON.stringify(value, null, 2)}
63446
+ const write = (name, value) => writeFile50(path156.join(dir, name), `${JSON.stringify(value, null, 2)}
62719
63447
  `, "utf8");
62720
63448
  await Promise.all([
62721
63449
  write("inputs.json", bundle.inputs),
@@ -62755,7 +63483,7 @@ async function metricsCommand(args2 = [], projectRoot = process.cwd()) {
62755
63483
  console.log("# metrics status");
62756
63484
  console.log("");
62757
63485
  console.log(`root: ${root}`);
62758
- console.log(`enabled: ${await Bun.file(path156.join(projectRoot, ".metaproject", "metaproject.json")).exists() ? "yes" : "no"}`);
63486
+ console.log(`enabled: ${await Bun.file(path157.join(projectRoot, ".metaproject", "metaproject.json")).exists() ? "yes" : "no"}`);
62759
63487
  const latest2 = await readLatestPointer(root);
62760
63488
  console.log(`latest: ${latest2.status}`);
62761
63489
  return;
@@ -62767,7 +63495,7 @@ async function metricsCommand(args2 = [], projectRoot = process.cwd()) {
62767
63495
  process.exitCode = 1;
62768
63496
  return;
62769
63497
  }
62770
- const record2 = JSON.parse(await readFile81(path156.resolve(projectRoot, file), "utf8"));
63498
+ const record2 = JSON.parse(await readFile81(path157.resolve(projectRoot, file), "utf8"));
62771
63499
  const result = validateRunRecord(record2);
62772
63500
  console.log(result.valid ? "valid: yes" : "valid: no");
62773
63501
  for (const error2 of result.errors)
@@ -62792,7 +63520,7 @@ async function metricsCommand(args2 = [], projectRoot = process.cwd()) {
62792
63520
  process.exitCode = 1;
62793
63521
  return;
62794
63522
  }
62795
- const file = path156.join(metricsRoot(projectRoot), "runs", `${runId}.json`);
63523
+ const file = path157.join(metricsRoot(projectRoot), "runs", `${runId}.json`);
62796
63524
  if (!await Bun.file(file).exists()) {
62797
63525
  console.error(`Run not found: ${runId}`);
62798
63526
  process.exitCode = 1;
@@ -62809,8 +63537,8 @@ async function metricsCommand(args2 = [], projectRoot = process.cwd()) {
62809
63537
  process.exitCode = 1;
62810
63538
  return;
62811
63539
  }
62812
- const a = JSON.parse(await readFile81(path156.join(metricsRoot(projectRoot), "runs", `${runA}.json`), "utf8"));
62813
- const b = JSON.parse(await readFile81(path156.join(metricsRoot(projectRoot), "runs", `${runB}.json`), "utf8"));
63540
+ const a = JSON.parse(await readFile81(path157.join(metricsRoot(projectRoot), "runs", `${runA}.json`), "utf8"));
63541
+ const b = JSON.parse(await readFile81(path157.join(metricsRoot(projectRoot), "runs", `${runB}.json`), "utf8"));
62814
63542
  const comparison = compareExecutionRuns(a, b);
62815
63543
  console.log(stableJson(comparison));
62816
63544
  return;
@@ -62844,8 +63572,8 @@ async function metricsCommand(args2 = [], projectRoot = process.cwd()) {
62844
63572
  return;
62845
63573
  }
62846
63574
  const template = createPairedBenchmarkTemplate(taskIds);
62847
- await Bun.write(path156.resolve(projectRoot, out), stableJson(template));
62848
- console.log(`manifest: ${path156.relative(projectRoot, path156.resolve(projectRoot, out))}`);
63575
+ await Bun.write(path157.resolve(projectRoot, out), stableJson(template));
63576
+ console.log(`manifest: ${path157.relative(projectRoot, path157.resolve(projectRoot, out))}`);
62849
63577
  return;
62850
63578
  }
62851
63579
  if (subcommand === "benchmark" && args2[1] === "run") {
@@ -62859,7 +63587,7 @@ async function metricsCommand(args2 = [], projectRoot = process.cwd()) {
62859
63587
  process.exitCode = 1;
62860
63588
  return;
62861
63589
  }
62862
- const raw = JSON.parse(await readFile81(path156.resolve(projectRoot, file), "utf8"));
63590
+ const raw = JSON.parse(await readFile81(path157.resolve(projectRoot, file), "utf8"));
62863
63591
  const input2 = Array.isArray(raw) ? raw : raw.runs ?? [];
62864
63592
  const result = validatePairedBenchmark(input2);
62865
63593
  console.log(stableJson(result));
@@ -62871,7 +63599,7 @@ async function metricsCommand(args2 = [], projectRoot = process.cwd()) {
62871
63599
  process.exitCode = 1;
62872
63600
  }
62873
63601
  async function loadAffectedSets(projectRoot, file) {
62874
- const raw = JSON.parse(await readFile81(path156.resolve(projectRoot, file), "utf8"));
63602
+ const raw = JSON.parse(await readFile81(path157.resolve(projectRoot, file), "utf8"));
62875
63603
  const map = new Map;
62876
63604
  for (const entry of raw.targets ?? []) {
62877
63605
  if (typeof entry.target === "string")
@@ -62942,7 +63670,7 @@ async function runHarnessLayer(projectRoot, args2, ladder) {
62942
63670
  let tasks;
62943
63671
  let model;
62944
63672
  try {
62945
- const raw = JSON.parse(await readFile81(path156.resolve(projectRoot, resultsPath), "utf8"));
63673
+ const raw = JSON.parse(await readFile81(path157.resolve(projectRoot, resultsPath), "utf8"));
62946
63674
  tasks = raw.tasks ?? [];
62947
63675
  model = raw.model;
62948
63676
  } catch (error2) {
@@ -62973,7 +63701,7 @@ async function runSafetyCompletionHonestyLayer(projectRoot, args2, ladder) {
62973
63701
  let cases;
62974
63702
  let model;
62975
63703
  try {
62976
- const raw = JSON.parse(await readFile81(path156.resolve(projectRoot, resultsPath), "utf8"));
63704
+ const raw = JSON.parse(await readFile81(path157.resolve(projectRoot, resultsPath), "utf8"));
62977
63705
  cases = raw.cases ?? [];
62978
63706
  model = raw.model;
62979
63707
  } catch (error2) {
@@ -62998,7 +63726,7 @@ async function runSafetyFalsePremiseLayer(projectRoot, args2, ladder) {
62998
63726
  let cases;
62999
63727
  let model;
63000
63728
  try {
63001
- const raw = JSON.parse(await readFile81(path156.resolve(projectRoot, resultsPath), "utf8"));
63729
+ const raw = JSON.parse(await readFile81(path157.resolve(projectRoot, resultsPath), "utf8"));
63002
63730
  cases = raw.cases ?? [];
63003
63731
  model = raw.model;
63004
63732
  } catch (error2) {
@@ -63023,7 +63751,7 @@ async function runSafetyContainmentLayer(projectRoot, args2, ladder, caseClass)
63023
63751
  let cases;
63024
63752
  let model;
63025
63753
  try {
63026
- const raw = JSON.parse(await readFile81(path156.resolve(projectRoot, resultsPath), "utf8"));
63754
+ const raw = JSON.parse(await readFile81(path157.resolve(projectRoot, resultsPath), "utf8"));
63027
63755
  cases = raw.cases ?? [];
63028
63756
  model = raw.model;
63029
63757
  } catch (error2) {
@@ -63085,12 +63813,12 @@ async function runGdgraphLayer(projectRoot, args2, ladder) {
63085
63813
  }
63086
63814
  const manifests = buildOracleManifestsByGold(inputs, { ladder });
63087
63815
  if (outDir) {
63088
- const resolvedOut = path156.resolve(projectRoot, outDir);
63816
+ const resolvedOut = path157.resolve(projectRoot, outDir);
63089
63817
  for (const input2 of inputs) {
63090
63818
  for (const named of input2.golds) {
63091
63819
  const bundle = buildEvidenceBundle({ target: input2.target, system: input2.system, gold: named.gold }, { ladder, goldReference: goldPathFor(named.kind), timestamp: new Date().toISOString() });
63092
- const dir = await persistEvidenceBundle(path156.join(resolvedOut, named.kind), bundle, ladder);
63093
- console.error(`bundle[${named.kind}]: ${path156.relative(projectRoot, dir)}`);
63820
+ const dir = await persistEvidenceBundle(path157.join(resolvedOut, named.kind), bundle, ladder);
63821
+ console.error(`bundle[${named.kind}]: ${path157.relative(projectRoot, dir)}`);
63094
63822
  }
63095
63823
  }
63096
63824
  }
@@ -63114,7 +63842,7 @@ async function runGdgraphLayer(projectRoot, args2, ladder) {
63114
63842
  return allValid;
63115
63843
  }
63116
63844
  async function loadCoverageMap2(projectRoot, file) {
63117
- const raw = JSON.parse(await readFile81(path156.resolve(projectRoot, file), "utf8"));
63845
+ const raw = JSON.parse(await readFile81(path157.resolve(projectRoot, file), "utf8"));
63118
63846
  return raw.coverageMap ?? {};
63119
63847
  }
63120
63848
  async function runTestingLayer(projectRoot, args2, ladder) {
@@ -63151,7 +63879,7 @@ async function runTestingLayer(projectRoot, args2, ladder) {
63151
63879
  return result.valid;
63152
63880
  }
63153
63881
  async function loadMemoryGoldK(projectRoot, file) {
63154
- const raw = JSON.parse(await readFile81(path156.resolve(projectRoot, file), "utf8"));
63882
+ const raw = JSON.parse(await readFile81(path157.resolve(projectRoot, file), "utf8"));
63155
63883
  return typeof raw.k === "number" && raw.k > 0 ? raw.k : 3;
63156
63884
  }
63157
63885
  async function runMemoryLayer(projectRoot, args2, ladder) {
@@ -63188,11 +63916,11 @@ async function runMemoryLayer(projectRoot, args2, ladder) {
63188
63916
  return result.valid;
63189
63917
  }
63190
63918
  async function loadWikiGoldK(projectRoot, file) {
63191
- const raw = JSON.parse(await readFile81(path156.resolve(projectRoot, file), "utf8"));
63919
+ const raw = JSON.parse(await readFile81(path157.resolve(projectRoot, file), "utf8"));
63192
63920
  return typeof raw.k === "number" && raw.k > 0 ? raw.k : 5;
63193
63921
  }
63194
63922
  async function loadWikiGroundedness(projectRoot, file) {
63195
- const raw = JSON.parse(await readFile81(path156.resolve(projectRoot, file), "utf8"));
63923
+ const raw = JSON.parse(await readFile81(path157.resolve(projectRoot, file), "utf8"));
63196
63924
  const map = new Map;
63197
63925
  for (const entry of raw.targets ?? []) {
63198
63926
  if (typeof entry.target !== "string" || !Array.isArray(entry.scores) || entry.scores.length !== 3)
@@ -63248,7 +63976,7 @@ async function runWikiLayer(projectRoot, args2, ladder) {
63248
63976
  return result.valid;
63249
63977
  }
63250
63978
  async function loadGdctxFacts(projectRoot, file) {
63251
- const raw = JSON.parse(await readFile81(path156.resolve(projectRoot, file), "utf8"));
63979
+ const raw = JSON.parse(await readFile81(path157.resolve(projectRoot, file), "utf8"));
63252
63980
  const inputs = [];
63253
63981
  for (const entry of raw.inputs ?? []) {
63254
63982
  if (typeof entry.input === "string") {
@@ -63286,7 +64014,7 @@ async function collect(projectRoot, args2) {
63286
64014
  process.exitCode = 1;
63287
64015
  return;
63288
64016
  }
63289
- const raw = JSON.parse(await readFile81(path156.resolve(projectRoot, eventFile), "utf8"));
64017
+ const raw = JSON.parse(await readFile81(path157.resolve(projectRoot, eventFile), "utf8"));
63290
64018
  const events2 = Array.isArray(raw) ? raw : raw.events;
63291
64019
  const startedAt = optionValue(args2, "--started-at") ?? events2[0]?.timestamp_utc ?? new Date().toISOString();
63292
64020
  const finishedAt = optionValue(args2, "--finished-at") ?? events2.at(-1)?.timestamp_utc ?? startedAt;
@@ -63302,11 +64030,11 @@ async function collect(projectRoot, args2) {
63302
64030
  parentRunId: optionValue(args2, "--parent-run-id") ?? null
63303
64031
  });
63304
64032
  const result = await writeRunArtifacts(metricsRoot(projectRoot), record2, { cwd: projectRoot });
63305
- console.log(`json: ${path156.relative(projectRoot, result.jsonPath)}`);
63306
- console.log(`markdown: ${path156.relative(projectRoot, result.markdownPath)}`);
64033
+ console.log(`json: ${path157.relative(projectRoot, result.jsonPath)}`);
64034
+ console.log(`markdown: ${path157.relative(projectRoot, result.markdownPath)}`);
63307
64035
  }
63308
64036
  function metricsRoot(projectRoot) {
63309
- return path156.join(projectRoot, ".metaproject", "data", "metrics");
64037
+ return path157.join(projectRoot, ".metaproject", "data", "metrics");
63310
64038
  }
63311
64039
  function printMetricsHelp() {
63312
64040
  console.log(`keryx metrics
@@ -63412,193 +64140,6 @@ init_session_wrap_up();
63412
64140
  init_proposal_evidence();
63413
64141
  init_review_confirm_token();
63414
64142
  init_store3();
63415
-
63416
- // src/sac/catch-up.ts
63417
- init_fs();
63418
- init_config_dir();
63419
- init_paths();
63420
- init_slate();
63421
- init_store3();
63422
- init_proposal_lifecycle();
63423
- init_workspace_service();
63424
- import { randomUUID as randomUUID28 } from "crypto";
63425
- import { readdir as readdir25 } from "fs/promises";
63426
- import path157 from "path";
63427
-
63428
- // src/sac/lifecycle-flag.ts
63429
- init_store();
63430
- init_service3();
63431
- init_decision_dedup();
63432
- init_workspace_service();
63433
- import { randomUUID as randomUUID27 } from "crypto";
63434
- function normalize3(raw) {
63435
- return raw.replace(/^\.\//, "");
63436
- }
63437
- function isStillPresent(recorded, valid) {
63438
- const normalized = normalize3(recorded);
63439
- return valid.has(normalized) || valid.has(moduleNameFromProjectPath(normalized));
63440
- }
63441
- async function computeLifecycleFlags(cwd, now = () => new Date) {
63442
- const valid = await validModuleNames(cwd);
63443
- if (valid === undefined)
63444
- return [];
63445
- const flaggedAt = now().toISOString();
63446
- const flags = [];
63447
- const workspaceService = new WorkspaceService({
63448
- workspaceRoot: cwd,
63449
- authorizationServer: localWorkspaceAuthorizationServer(),
63450
- strictGuard: { mode: "strict", availability: "available", decision: "pass", policyRevision: "local-offline-v1" }
63451
- });
63452
- const workspaces = await workspaceService.list({ request: undefined, requestCorrelationId: randomUUID27(), includeArchived: true });
63453
- for (const workspace of workspaces) {
63454
- const component = workspace.resources.find((r) => r.kind === "component")?.uri;
63455
- if (component !== undefined && !isStillPresent(component, valid)) {
63456
- flags.push({ kind: "workspace", ref: workspace.id, missingComponent: normalize3(component), flaggedAt });
63457
- }
63458
- }
63459
- for (const entry of await collectEntries(cwd)) {
63460
- const module = entry.scopes.module;
63461
- if (module !== null && module.length > 0 && !isStillPresent(module, valid)) {
63462
- flags.push({ kind: "memory-entry", ref: entry.relativePath, missingComponent: normalize3(module), flaggedAt });
63463
- }
63464
- }
63465
- for (const decision of await collectWikiDecisionEntries(cwd)) {
63466
- const module = decision.scopes.module;
63467
- if (module !== null && module.length > 0 && !isStillPresent(module, valid)) {
63468
- flags.push({ kind: "wiki-decision", ref: decision.relativePath, missingComponent: normalize3(module), flaggedAt });
63469
- }
63470
- }
63471
- return flags;
63472
- }
63473
-
63474
- // src/sac/catch-up.ts
63475
- async function buildCatchUp(input2) {
63476
- const [proposals, sessionCategories, lifecycleFlagsAll] = await Promise.all([
63477
- collectProposals(input2.cwd, input2.workspaceId),
63478
- collectSessionCategories(input2.cwd),
63479
- computeLifecycleFlags(input2.cwd)
63480
- ]);
63481
- const lifecycleFlags = input2.workspaceId === undefined ? lifecycleFlagsAll : lifecycleFlagsAll.filter((flag) => flag.kind !== "workspace" || flag.ref === input2.workspaceId);
63482
- return { proposals, ...sessionCategories, lifecycleFlags };
63483
- }
63484
- async function collectProposals(cwd, workspaceId) {
63485
- const authorizationServer = localWorkspaceAuthorizationServer();
63486
- const actor = await authorizationServer.actorContextFor(undefined, randomUUID28());
63487
- if (!actor)
63488
- throw new Error("trusted ActorContext is required for catch-up");
63489
- const proposalService = createLocalProposalLifecycleService(cwd);
63490
- const groups = await proposalService.listVisibleProposedProposals(actor);
63491
- const scoped = workspaceId === undefined ? groups : groups.filter((group) => group.workspace.id === workspaceId);
63492
- const flattened = scoped.flatMap((group) => group.proposals.map((proposal) => ({ group, proposal })));
63493
- return Promise.all(flattened.map(async ({ group, proposal }) => {
63494
- const fresh = await proposalService.isEvidenceFresh(proposal, actor);
63495
- return { type: "proposal", workspaceId: group.workspace.id, proposalId: proposal.id, fresh };
63496
- }));
63497
- }
63498
- async function classifySession(session) {
63499
- const dir = sessionDir(session.projectPath, session.id);
63500
- if (await isLockHeld(slateLockPath(dir)))
63501
- return;
63502
- const terminalState = await readTerminalState(dir);
63503
- if (terminalState !== undefined) {
63504
- const workspaceId2 = (await safeReadSlate(dir))?.workspaceId;
63505
- return { kind: "blocked", item: { type: "blocked", sessionId: session.id, ...workspaceId2 !== undefined ? { workspaceId: workspaceId2 } : {}, terminalState } };
63506
- }
63507
- const unboundCandidate = await readNewestUnboundCandidate(dir);
63508
- if (unboundCandidate !== undefined) {
63509
- return {
63510
- kind: "unbound-candidate",
63511
- item: { type: "unbound-candidate", sessionId: session.id, evidencePath: unboundCandidate.evidencePath, summary: unboundCandidate.summary }
63512
- };
63513
- }
63514
- if (!await isSlateEngaged(dir))
63515
- return;
63516
- const workspaceId = (await safeReadSlate(dir))?.workspaceId;
63517
- return { kind: "unknown", item: { type: "unknown", sessionId: session.id, ...workspaceId !== undefined ? { workspaceId } : {}, lastSeenAt: session.updatedAt } };
63518
- }
63519
- async function collectSessionCategories(cwd) {
63520
- const classified = await Promise.all(listSessions(cwd).map((session) => classifySession(session)));
63521
- const blocked2 = [];
63522
- const unboundCandidates = [];
63523
- const unknown = [];
63524
- for (const category of classified) {
63525
- if (category === undefined)
63526
- continue;
63527
- if (category.kind === "blocked")
63528
- blocked2.push(category.item);
63529
- else if (category.kind === "unbound-candidate")
63530
- unboundCandidates.push(category.item);
63531
- else
63532
- unknown.push(category.item);
63533
- }
63534
- return { blocked: blocked2, unboundCandidates, unknown };
63535
- }
63536
- async function isSlateEngaged(dir) {
63537
- if (await pathExists(path157.join(dir, "slate.json")))
63538
- return true;
63539
- if (await pathExists(path157.join(dir, "terminal-state.json")))
63540
- return true;
63541
- try {
63542
- const entries = await readdir25(path157.join(dir, "slate-archive"));
63543
- return entries.length > 0;
63544
- } catch {
63545
- return false;
63546
- }
63547
- }
63548
- async function safeReadSlate(dir) {
63549
- try {
63550
- return await readSlate(dir);
63551
- } catch {
63552
- return;
63553
- }
63554
- }
63555
- async function readTerminalState(dir) {
63556
- const result = readConfigFile(path157.join(dir, "terminal-state.json"));
63557
- if (!result.ok) {
63558
- return;
63559
- }
63560
- try {
63561
- return JSON.parse(result.text);
63562
- } catch {
63563
- return;
63564
- }
63565
- }
63566
- async function readNewestUnboundCandidate(dir) {
63567
- const archiveDir = path157.join(dir, "slate-archive");
63568
- let entries;
63569
- try {
63570
- entries = (await readdir25(archiveDir)).filter((name) => name.endsWith("-unbound-candidate.json"));
63571
- } catch {
63572
- return;
63573
- }
63574
- entries.sort();
63575
- for (let i = entries.length - 1;i >= 0; i--) {
63576
- const evidencePath = path157.join(archiveDir, entries[i]);
63577
- const result = readConfigFile(evidencePath);
63578
- if (!result.ok) {
63579
- continue;
63580
- }
63581
- try {
63582
- const parsed = JSON.parse(result.text);
63583
- if (parsed.recordType !== "unbound-candidate")
63584
- continue;
63585
- return { evidencePath, summary: summarizeUnboundCandidate(parsed.groups) };
63586
- } catch {
63587
- continue;
63588
- }
63589
- }
63590
- return;
63591
- }
63592
- function summarizeUnboundCandidate(groups) {
63593
- const safeGroups = groups ?? [];
63594
- if (safeGroups.length === 0)
63595
- return "no seeds captured";
63596
- const seedCount = safeGroups.reduce((sum, group) => sum + (group.seeds?.length ?? 0), 0);
63597
- const kinds = safeGroups.map((group) => typeof group.kind === "string" ? group.kind : "unknown").join(", ");
63598
- return `${seedCount} untriaged seed(s) across ${safeGroups.length} kind(s) (${kinds})`;
63599
- }
63600
-
63601
- // src/commands/workspace.ts
63602
64143
  var PROPOSAL_KINDS2 = ["decision", "wiki-update", "memory-entry", "follow-up", "contract-change", "risk"];
63603
64144
  function service5() {
63604
64145
  return new WorkspaceService({