@mrciphersmith/keryx 0.3.16 → 0.3.18

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/cli.js CHANGED
@@ -5275,10 +5275,14 @@ function configuredProviders(env = process.env, dir) {
5275
5275
  }
5276
5276
  async function providersCommand(args, deps = {}) {
5277
5277
  const command = args[0];
5278
- if (!command || command === "--help" || command === "-h") {
5278
+ if (command === "--help" || command === "-h") {
5279
5279
  printProvidersHelp();
5280
5280
  return;
5281
5281
  }
5282
+ if (!command) {
5283
+ await runProvidersStatus([], deps);
5284
+ return;
5285
+ }
5282
5286
  if (command === "list") {
5283
5287
  runProvidersList(args.slice(1));
5284
5288
  return;
@@ -17637,7 +17641,7 @@ var package_default;
17637
17641
  var init_package = __esm(() => {
17638
17642
  package_default = {
17639
17643
  name: "@mrciphersmith/keryx",
17640
- version: "0.3.16",
17644
+ version: "0.3.18",
17641
17645
  description: "Version-controlled project context for AI coding agents: code graph, architecture wiki, project memory, relevant tests, quality signals, and task flows.",
17642
17646
  private: false,
17643
17647
  publishConfig: {
@@ -28951,7 +28955,7 @@ function retryableFor2(kind, fallback) {
28951
28955
  const concrete = defaultRetryable(kind);
28952
28956
  return concrete === undefined ? fallback : concrete;
28953
28957
  }
28954
- function mergeUsage2(promptTokens, completionTokens, totalTokens) {
28958
+ function mergeUsage2(promptTokens, completionTokens, totalTokens, cacheReadTokens) {
28955
28959
  const usage = { exact: true };
28956
28960
  if (promptTokens !== undefined) {
28957
28961
  usage.inputTokens = promptTokens;
@@ -28964,6 +28968,9 @@ function mergeUsage2(promptTokens, completionTokens, totalTokens) {
28964
28968
  } else if (promptTokens !== undefined || completionTokens !== undefined) {
28965
28969
  usage.totalTokens = (promptTokens ?? 0) + (completionTokens ?? 0);
28966
28970
  }
28971
+ if (cacheReadTokens !== undefined) {
28972
+ usage.cacheReadTokens = cacheReadTokens;
28973
+ }
28967
28974
  return usage;
28968
28975
  }
28969
28976
  function classifyHttpError2(status, headers, code) {
@@ -29390,6 +29397,14 @@ ${message.content}` });
29390
29397
  }
29391
29398
  return "idx:0";
29392
29399
  };
29400
+ const pendingToolCallStillAccumulating = () => {
29401
+ for (const acc of pendingTools.values()) {
29402
+ if (!acc.ended) {
29403
+ return acc.id;
29404
+ }
29405
+ }
29406
+ return;
29407
+ };
29393
29408
  const flushPendingToolEnds = () => {
29394
29409
  for (const acc of pendingTools.values()) {
29395
29410
  if (acc.ended) {
@@ -29483,7 +29498,7 @@ ${message.content}` });
29483
29498
  const usage = asRecord4(data.usage);
29484
29499
  bodies.push({
29485
29500
  kind: "usage_update",
29486
- usage: mergeUsage2(asNumber3(usage.prompt_tokens), asNumber3(usage.completion_tokens), asNumber3(usage.total_tokens))
29501
+ usage: mergeUsage2(asNumber3(usage.prompt_tokens), asNumber3(usage.completion_tokens), asNumber3(usage.total_tokens), asNumber3(asRecord4(usage.prompt_tokens_details).cached_tokens))
29487
29502
  });
29488
29503
  }
29489
29504
  const choice0 = asRecord4(asArray(data.choices)[0]);
@@ -29596,6 +29611,7 @@ ${message.content}` });
29596
29611
  parser.push(trailing);
29597
29612
  }
29598
29613
  const torn = parser.flush();
29614
+ const pendingToolCallId = pendingToolCallStillAccumulating();
29599
29615
  if (torn.length > 0) {
29600
29616
  malformed = {
29601
29617
  kind: "malformed",
@@ -29608,8 +29624,14 @@ ${message.content}` });
29608
29624
  retryable: retryableFor2("malformed", false),
29609
29625
  message: "empty response body"
29610
29626
  };
29627
+ } else if (pendingToolCallId !== undefined) {
29628
+ malformed = {
29629
+ kind: "malformed",
29630
+ retryable: retryableFor2("malformed", false),
29631
+ message: `${this.label} SSE stream ended while a tool call was still accumulating`,
29632
+ detail: { pendingToolCallId }
29633
+ };
29611
29634
  } else {
29612
- flushPendingToolEnds();
29613
29635
  if (sawStart && sawFinish) {
29614
29636
  flushThinkTagParser();
29615
29637
  flushFieldTagStripper();
@@ -30268,6 +30290,16 @@ class GeminiProvider {
30268
30290
  break;
30269
30291
  }
30270
30292
  const chunk = asRecord5(parsed);
30293
+ const errorEnvelope = asRecord5(chunk.error);
30294
+ if (Object.keys(errorEnvelope).length > 0) {
30295
+ const classified = classifyGeminiError(asNumber4(errorEnvelope.code) ?? 0, asString4(errorEnvelope.status));
30296
+ const envelopeMessage = asString4(errorEnvelope.message);
30297
+ malformed = {
30298
+ ...classified,
30299
+ message: redact(envelopeMessage !== undefined && envelopeMessage.length > 0 ? envelopeMessage : "Gemini stream carried an in-band error envelope")
30300
+ };
30301
+ break;
30302
+ }
30271
30303
  if (!sawFirstChunk) {
30272
30304
  sawFirstChunk = true;
30273
30305
  bodies.push({ kind: "model_start" });
@@ -30291,7 +30323,7 @@ class GeminiProvider {
30291
30323
  bodies.push({ kind: "text_delta", text });
30292
30324
  }
30293
30325
  } else if (callName !== undefined) {
30294
- const callId = asString4(functionCall.id) ?? callName;
30326
+ const callId = asString4(functionCall.id) ?? `${callName}#${functionCallIndexInRound}`;
30295
30327
  const argsInput = JSON.stringify(asRecord5(functionCall.args));
30296
30328
  bodies.push({ kind: "tool_call_start", toolCallId: callId, toolName: callName });
30297
30329
  bodies.push({ kind: "tool_call_end", toolCallId: callId, input: argsInput });
@@ -30508,7 +30540,7 @@ function retryableFor4(kind, fallback) {
30508
30540
  const concrete = defaultRetryable(kind);
30509
30541
  return concrete === undefined ? fallback : concrete;
30510
30542
  }
30511
- function mergeUsage4(inputTokens, outputTokens, totalTokens) {
30543
+ function mergeUsage4(inputTokens, outputTokens, totalTokens, cacheReadTokens) {
30512
30544
  const usage = { exact: true };
30513
30545
  if (inputTokens !== undefined) {
30514
30546
  usage.inputTokens = inputTokens;
@@ -30521,6 +30553,9 @@ function mergeUsage4(inputTokens, outputTokens, totalTokens) {
30521
30553
  } else if (inputTokens !== undefined || outputTokens !== undefined) {
30522
30554
  usage.totalTokens = (inputTokens ?? 0) + (outputTokens ?? 0);
30523
30555
  }
30556
+ if (cacheReadTokens !== undefined) {
30557
+ usage.cacheReadTokens = cacheReadTokens;
30558
+ }
30524
30559
  return usage;
30525
30560
  }
30526
30561
  function extractErrorFields(data) {
@@ -30938,9 +30973,10 @@ class OpenAiProvider {
30938
30973
  const outputTokens = asNumber5(usage.output_tokens);
30939
30974
  const totalTokens = asNumber5(usage.total_tokens);
30940
30975
  const reasoningTokens = asNumber5(asRecord6(usage.output_tokens_details).reasoning_tokens);
30976
+ const cacheReadTokens = asNumber5(asRecord6(usage.input_tokens_details).cached_tokens);
30941
30977
  const usageBody = {
30942
30978
  kind: "usage_update",
30943
- usage: mergeUsage4(inputTokens, outputTokens, totalTokens)
30979
+ usage: mergeUsage4(inputTokens, outputTokens, totalTokens, cacheReadTokens)
30944
30980
  };
30945
30981
  if (reasoningTokens !== undefined) {
30946
30982
  usageBody.unknownExtensions = { "openai.reasoning_tokens": reasoningTokens };
@@ -34739,6 +34775,23 @@ function tokenize(text) {
34739
34775
  function tokenSet(text) {
34740
34776
  return new Set(tokenize(text));
34741
34777
  }
34778
+ function stem(token) {
34779
+ let base = token;
34780
+ if (base.length >= 6 && base.endsWith("ies")) {
34781
+ base = `${base.slice(0, -3)}y`;
34782
+ } else {
34783
+ for (const suffix of ["ing", "edly", "ed", "es", "s"]) {
34784
+ if (base.length - suffix.length >= 3 && base.endsWith(suffix)) {
34785
+ base = base.slice(0, base.length - suffix.length);
34786
+ break;
34787
+ }
34788
+ }
34789
+ }
34790
+ if (base.length >= 4 && base.endsWith("e")) {
34791
+ base = base.slice(0, -1);
34792
+ }
34793
+ return base;
34794
+ }
34742
34795
  function jaccard(a, b) {
34743
34796
  if (a.size === 0 || b.size === 0) {
34744
34797
  return 0;
@@ -35296,7 +35349,7 @@ var init_ingest = __esm(() => {
35296
35349
  function searchEntries(entries, query, filters, config, now) {
35297
35350
  validateQuery(query);
35298
35351
  validateSearchFilters(filters, now);
35299
- const queryTokens = [...new Set(tokenize(query))];
35352
+ const queryTokens = [...new Set(tokenize(query).map(stem))];
35300
35353
  const today = now.toISOString().slice(0, 10);
35301
35354
  const filtered = entries.filter((entry) => matchesFilters(entry, filters) && classMatch(entry, filters.class) && temporalMatch(entry, filters.asOf ?? null, config, today, Boolean(filters.status)));
35302
35355
  const scored = filtered.map((entry) => scoreEntry(entry, queryTokens, filters, config, now));
@@ -35305,7 +35358,7 @@ function searchEntries(entries, query, filters, config, now) {
35305
35358
  function candidatePool(entries, query, filters, config, now, k) {
35306
35359
  validateQuery(query);
35307
35360
  validateSearchFilters(filters, now);
35308
- const queryTokens = [...new Set(tokenize(query))];
35361
+ const queryTokens = [...new Set(tokenize(query).map(stem))];
35309
35362
  const today = now.toISOString().slice(0, 10);
35310
35363
  return entries.filter((entry) => matchesFilters(entry, filters) && classMatch(entry, filters.class) && temporalMatch(entry, filters.asOf ?? null, config, today, Boolean(filters.status))).map((entry) => scoreEntry(entry, queryTokens, filters, config, now)).sort((a, b) => b.score - a.score).slice(0, Math.min(MAX_GENERAL_RESULTS, Math.max(0, k)));
35311
35364
  }
@@ -35356,8 +35409,8 @@ function temporalMatch(entry, asOf, config, today, hasStatusFilter) {
35356
35409
  }, new Date(`${today}T00:00:00.000Z`)).current;
35357
35410
  }
35358
35411
  function scoreEntry(entry, queryTokens, filters, config, now) {
35359
- const bodyTokens = tokenSet(`${entry.title} ${entry.summary} ${entry.details} ${entry.tags.join(" ")}`);
35360
- const titleTokens = tokenSet(entry.title);
35412
+ const bodyTokens = new Set([...tokenSet(`${entry.title} ${entry.summary} ${entry.details} ${entry.tags.join(" ")}`)].map(stem));
35413
+ const titleTokens = new Set([...tokenSet(entry.title)].map(stem));
35361
35414
  let hits = 0;
35362
35415
  let titleHits = 0;
35363
35416
  for (const token of queryTokens) {
@@ -36214,6 +36267,7 @@ var init_service5 = __esm(() => {
36214
36267
  init_harness_identity();
36215
36268
  init_embedding();
36216
36269
  init_types3();
36270
+ init_embedding();
36217
36271
  });
36218
36272
 
36219
36273
  // src/wiki/ask.ts
@@ -39595,6 +39649,7 @@ var init_service7 = __esm(() => {
39595
39649
  init_query();
39596
39650
  init_repomap();
39597
39651
  init_affected_report();
39652
+ init_staleness();
39598
39653
  UnknownGraphTargetError = class UnknownGraphTargetError extends Error {
39599
39654
  target;
39600
39655
  constructor(target) {
@@ -58125,8 +58180,10 @@ async function runAgentTurn(io, deps, history, userLine, options = {}) {
58125
58180
 
58126
58181
  ${buildControlMarkerInstruction(controlNonce)}`
58127
58182
  };
58183
+ const caughtToolErrors = [];
58128
58184
  try {
58129
- const result = await runAgentTurnCore(io, turnDeps, history, userLine, options);
58185
+ const coreResult = await runAgentTurnCore(io, turnDeps, history, userLine, options, caughtToolErrors);
58186
+ const result = caughtToolErrors.length > 0 ? { ...coreResult, caughtToolErrors } : coreResult;
58130
58187
  await fireStopHookBestEffort(io, deps, result);
58131
58188
  return result;
58132
58189
  } finally {
@@ -58208,7 +58265,7 @@ function computeReasoningDurationMs(startedAt, endedAt) {
58208
58265
  const endMs = Date.parse(endedAt);
58209
58266
  return !Number.isNaN(startMs) && !Number.isNaN(endMs) && endMs >= startMs ? endMs - startMs : undefined;
58210
58267
  }
58211
- async function runAgentTurnCore(io, deps, history, userLine, options = {}) {
58268
+ async function runAgentTurnCore(io, deps, history, userLine, options = {}, caughtToolErrors = []) {
58212
58269
  const now = deps.now ?? (() => new Date().toISOString());
58213
58270
  const controlNonce = deps.controlNonce ?? generateControlNonce();
58214
58271
  const scrub = (text) => scrubControlNonce(text, controlNonce);
@@ -58798,7 +58855,7 @@ ${shown.join(`
58798
58855
  }
58799
58856
  const spawnConcurrencyCandidates = calls.filter((call) => call.name === "spawn_subagent" && reservationByCallId.get(call.id)?.ok === true);
58800
58857
  const untrustedGateBlocksSpawns = untrustedContentSeen || batchContainsUntrustedWeb;
58801
- const concurrentSpawnResults = spawnConcurrencyCandidates.length >= 2 && !untrustedGateBlocksSpawns && maxToolCalls === undefined ? await runConcurrentSpawnBatch(spawnConcurrencyCandidates, toolByName, io, deps, hasInvocationCapacity, reserveInvocation, signal) : undefined;
58858
+ const concurrentSpawnResults = spawnConcurrencyCandidates.length >= 2 && !untrustedGateBlocksSpawns && maxToolCalls === undefined ? await runConcurrentSpawnBatch(spawnConcurrencyCandidates, toolByName, io, deps, hasInvocationCapacity, reserveInvocation, signal, caughtToolErrors) : undefined;
58802
58859
  const answeredToolIds = new Set;
58803
58860
  let anchorsToAnnounce;
58804
58861
  let repeatedFailureHint;
@@ -58898,7 +58955,19 @@ ${shown.join(`
58898
58955
  if (toolByName.has(call.name)) {
58899
58956
  turnExecutedToolCall = true;
58900
58957
  }
58901
- const result = concurrentSpawnResults?.get(call.id) ?? await executeCall(call, toolByName, io.requestApproval, io.permissionMode, io.readOnly, io.onAutoApproved, hasInvocationCapacity, reserveInvocation, invocationBudget.maxCalls, signal, deps.busLeases, deps.hardDeny === undefined ? undefined : { check: deps.hardDeny, onDenied: io.onUnattendedDenial }, deps.hooks);
58958
+ const precomputedResult = concurrentSpawnResults?.get(call.id);
58959
+ let result;
58960
+ if (precomputedResult !== undefined) {
58961
+ result = precomputedResult;
58962
+ } else {
58963
+ try {
58964
+ result = await executeCall(call, toolByName, io.requestApproval, io.permissionMode, io.readOnly, io.onAutoApproved, hasInvocationCapacity, reserveInvocation, invocationBudget.maxCalls, signal, deps.busLeases, deps.hardDeny === undefined ? undefined : { check: deps.hardDeny, onDenied: io.onUnattendedDenial }, deps.hooks);
58965
+ } catch (err) {
58966
+ const message = err instanceof Error ? err.message : String(err);
58967
+ result = { output: `${call.name} failed: ${message}`, isError: true };
58968
+ caughtToolErrors.push({ toolName: call.name, message });
58969
+ }
58970
+ }
58902
58971
  io.onToolResult?.(call.name, result);
58903
58972
  const modelOutput = redactSensitiveText(result.output);
58904
58973
  const untrusted = result.untrusted === true;
@@ -59348,7 +59417,7 @@ function isApprovalFor(response, fingerprint) {
59348
59417
  }
59349
59418
  return response.fingerprint === undefined || response.fingerprint === fingerprint;
59350
59419
  }
59351
- async function runConcurrentSpawnBatch(spawnCalls, toolByName, io, deps, hasInvocationCapacity, reserveInvocation, signal) {
59420
+ async function runConcurrentSpawnBatch(spawnCalls, toolByName, io, deps, hasInvocationCapacity, reserveInvocation, signal, caughtToolErrors) {
59352
59421
  const maxConcurrency = deps.maxSubagentConcurrency ?? DEFAULT_MAX_SUBAGENT_CONCURRENCY;
59353
59422
  const perTaskRuntimeMs = NOMINAL_CONCURRENT_SPAWN_RUNTIME_MS;
59354
59423
  const tasks = spawnCalls.map((call) => ({
@@ -59375,6 +59444,7 @@ async function runConcurrentSpawnBatch(spawnCalls, toolByName, io, deps, hasInvo
59375
59444
  output: `subagent call ${call.id} failed: sequential fallback error: ${message}`,
59376
59445
  isError: true
59377
59446
  });
59447
+ caughtToolErrors.push({ toolName: call.name, message });
59378
59448
  }
59379
59449
  }
59380
59450
  return results;
@@ -59402,6 +59472,9 @@ async function runConcurrentSpawnBatch(spawnCalls, toolByName, io, deps, hasInvo
59402
59472
  const results = new Map;
59403
59473
  for (const call of spawnCalls) {
59404
59474
  const settled = partialResults?.get(call.id);
59475
+ if (settled === undefined) {
59476
+ caughtToolErrors.push({ toolName: call.name, message });
59477
+ }
59405
59478
  results.set(call.id, settled ?? {
59406
59479
  output: `subagent call ${call.id} failed: concurrent wave error: ${message}`,
59407
59480
  isError: true
@@ -60502,7 +60575,8 @@ function computeGate2(input) {
60502
60575
  }
60503
60576
  const missingOptional = sources.filter((s) => !s.required && s.status === "missing");
60504
60577
  for (const source of missingOptional) {
60505
- reasons.push(`OPTIONAL: ${source.source} source missing`);
60578
+ const detail = source.error ? `: ${source.error}` : "";
60579
+ reasons.push(`OPTIONAL: ${source.source} source missing${detail}`);
60506
60580
  }
60507
60581
  const brokenOptional = sources.filter((s) => !s.required && s.status === "configured-but-failed");
60508
60582
  if (brokenOptional.length > 0) {
@@ -62307,6 +62381,12 @@ function errorCodeSuffix(error) {
62307
62381
  const code = safeErrorCode(error);
62308
62382
  return code ? ` (${code})` : "";
62309
62383
  }
62384
+ function missingSourceReason(sourceId) {
62385
+ if (sourceId === "tests") {
62386
+ return "no `bun` binary found (checked node_modules/.bin and PATH)";
62387
+ }
62388
+ return;
62389
+ }
62310
62390
  function safeValidationError(error) {
62311
62391
  return error !== undefined && KNOWN_VALIDATION_ERRORS.has(error) ? error : "source output format was not recognized";
62312
62392
  }
@@ -62341,7 +62421,8 @@ async function runAdapter(adapter, ctx, cfg, stamp) {
62341
62421
  };
62342
62422
  }
62343
62423
  if (status === "skipped" || status === "missing") {
62344
- return { info: { ...base, status }, findings: [] };
62424
+ const reason = missingSourceReason(adapter.id);
62425
+ return { info: { ...base, status, ...reason !== undefined ? { error: reason } : {} }, findings: [] };
62345
62426
  }
62346
62427
  let raw;
62347
62428
  try {
@@ -62355,7 +62436,7 @@ async function runAdapter(adapter, ctx, cfg, stamp) {
62355
62436
  } catch (error) {
62356
62437
  if (error instanceof NoImportError) {
62357
62438
  if (adapter.id === "tests") {
62358
- return { info: { ...base, status: "missing" }, findings: [] };
62439
+ return { info: { ...base, status }, findings: [] };
62359
62440
  }
62360
62441
  raw = await adapter.run(ctx);
62361
62442
  } else {
@@ -62720,6 +62801,7 @@ var init_service10 = __esm(() => {
62720
62801
  init_sources();
62721
62802
  init_util();
62722
62803
  init_lifecycle2();
62804
+ init_wiki_freshness();
62723
62805
  });
62724
62806
 
62725
62807
  // src/flow/machine.ts
@@ -70731,6 +70813,7 @@ async function runSearch(args) {
70731
70813
  }
70732
70814
  const report = args.includes("--save-report") ? await getService().writeReport({ cwd: process.cwd(), search: result, filters }) : undefined;
70733
70815
  const removalTrail = result.results.length === 0 ? searchRemovals(await loadDeletionTrail(process.cwd()), query) : null;
70816
+ const semanticHint = result.results.length === 0 && !filters.semantic ? await loadEmbeddingIndex(process.cwd()) !== null ? "Zero hits in lexical mode. An embeddings index exists for this project \u2014 try `--semantic`." : "Zero hits in lexical mode. No embeddings index exists yet \u2014 build one with `keryx memory index --embeddings`, then retry with `--semantic`." : undefined;
70734
70817
  const asOfMode = Boolean(filters.asOf);
70735
70818
  const now = new Date;
70736
70819
  const historicalByPath = new Map;
@@ -70759,11 +70842,16 @@ async function runSearch(args) {
70759
70842
  };
70760
70843
  }),
70761
70844
  ...removalTrail ? { removalTrail } : {},
70845
+ ...semanticHint ? { semanticHint } : {},
70762
70846
  ...report ? { report } : {}
70763
70847
  }, null, 2));
70764
70848
  return;
70765
70849
  }
70766
70850
  console.log(renderSearchMarkdown(query, result.results).trimEnd());
70851
+ if (semanticHint) {
70852
+ console.log("");
70853
+ console.log(semanticHint);
70854
+ }
70767
70855
  if (removalTrail) {
70768
70856
  console.log("");
70769
70857
  console.log("## Removal trail");
@@ -71060,6 +71148,7 @@ var init_memory = __esm(() => {
71060
71148
  init_config4();
71061
71149
  init_reflect();
71062
71150
  init_search();
71151
+ init_service5();
71063
71152
  init_report2();
71064
71153
  init_lifecycle();
71065
71154
  init_args();
@@ -71636,7 +71725,7 @@ var init_debug_log = __esm(() => {
71636
71725
  // src/tui/debug-watcher.ts
71637
71726
  import { spawn as spawn8, spawnSync as spawnSync8 } from "child_process";
71638
71727
  import { closeSync as closeSync6, constants as constants4, openSync as openSync7, readdirSync as readdirSync11, readFileSync as readFileSync24, readlinkSync as readlinkSync3, statSync as statSync16 } from "fs";
71639
- import path316 from "path";
71728
+ import path317 from "path";
71640
71729
  function parseEpollFdinfo(text) {
71641
71730
  const out = [];
71642
71731
  for (const line of text.split(`
@@ -71825,8 +71914,8 @@ async function runDebugWatcher(argv) {
71825
71914
  process.exitCode = 2;
71826
71915
  return;
71827
71916
  }
71828
- const logFile = path316.join(dirArg, "watcher.ndjson");
71829
- const shellLog = path316.join(dirArg, "shell.ndjson");
71917
+ const logFile = path317.join(dirArg, "watcher.ndjson");
71918
+ const shellLog = path317.join(dirArg, "shell.ndjson");
71830
71919
  const log = (kind, data = {}) => {
71831
71920
  try {
71832
71921
  appendOwnerOnlyLine(logFile, JSON.stringify({ ts: new Date().toISOString(), watcher: process.pid, target: pid, kind, ...data }));
@@ -107084,6 +107173,18 @@ var HELP_GROUPS = [
107084
107173
  summary: "Grouped command help by task \u2014 keryx help [group|command]."
107085
107174
  },
107086
107175
  { kind: "slash", name: "/help", group: "Start here", summary: "Show available commands, grouped by task." },
107176
+ {
107177
+ kind: "cli",
107178
+ name: "doctor",
107179
+ group: "Start here",
107180
+ summary: "One-page health check: version, Bun floor, ripgrep, sandbox, providers, MCP, integrations, standard, worktrees, graph/wiki freshness."
107181
+ },
107182
+ {
107183
+ kind: "slash",
107184
+ name: "/doctor",
107185
+ group: "Start here",
107186
+ summary: "Same one-page health check, inside the session."
107187
+ },
107087
107188
  {
107088
107189
  kind: "cli",
107089
107190
  name: "auth",
@@ -109216,6 +109317,19 @@ var COMMAND_DESCRIPTORS = [
109216
109317
  json: false,
109217
109318
  read: true
109218
109319
  },
109320
+ {
109321
+ module: "core",
109322
+ command: "doctor",
109323
+ summary: "One-page health check: version, Bun floor, ripgrep, sandbox, providers, MCP, integrations, standard, worktrees, graph/wiki freshness.",
109324
+ intent: ["keryx doctor", "health check", "\u043F\u0440\u043E\u0432\u0435\u0440\u044C \u043E\u043A\u0440\u0443\u0436\u0435\u043D\u0438\u0435", "is keryx set up correctly", "\u0434\u0438\u0430\u0433\u043D\u043E\u0441\u0442\u0438\u043A\u0430"],
109325
+ args: [{ name: "json", type: "bool", required: false, desc: "structured {checks:[{id,status,detail,fix?}]}" }],
109326
+ json: true,
109327
+ read: false,
109328
+ sideEffects: [
109329
+ "makes an outbound HTTPS request to the npm registry as part of its version check, unless served from the local cache or failure-backoff window",
109330
+ "writes <per-user keryx config dir>/version-check.json (outside this project) as part of that same check"
109331
+ ]
109332
+ },
109219
109333
  {
109220
109334
  module: "core",
109221
109335
  command: "status",
@@ -111307,6 +111421,49 @@ async function sandboxCommand(args = [], deps = {}) {
111307
111421
  init_args();
111308
111422
  init_ui();
111309
111423
 
111424
+ // src/lib/suggest.ts
111425
+ var MAX_SUGGESTIONS = 3;
111426
+ var MAX_EDIT_DISTANCE = 2;
111427
+ function levenshtein2(a, b) {
111428
+ const rows = a.length + 1;
111429
+ const cols = b.length + 1;
111430
+ const dp = Array.from({ length: rows }, () => new Array(cols).fill(0));
111431
+ for (let i = 0;i < rows; i += 1) {
111432
+ const row = dp[i];
111433
+ if (row !== undefined)
111434
+ row[0] = i;
111435
+ }
111436
+ const first = dp[0];
111437
+ if (first !== undefined) {
111438
+ for (let j = 0;j < cols; j += 1)
111439
+ first[j] = j;
111440
+ }
111441
+ for (let i = 1;i < rows; i += 1) {
111442
+ for (let j = 1;j < cols; j += 1) {
111443
+ const same = a[i - 1] === b[j - 1];
111444
+ const up = dp[i - 1]?.[j] ?? 0;
111445
+ const left = dp[i]?.[j - 1] ?? 0;
111446
+ const diag = dp[i - 1]?.[j - 1] ?? 0;
111447
+ const row = dp[i];
111448
+ if (row !== undefined)
111449
+ row[j] = same ? diag : 1 + Math.min(up, left, diag);
111450
+ }
111451
+ }
111452
+ return dp[rows - 1]?.[cols - 1] ?? Math.max(a.length, b.length);
111453
+ }
111454
+ function suggestClosest(input, known) {
111455
+ const needle = input.trim().toLowerCase();
111456
+ if (needle.length === 0) {
111457
+ return [];
111458
+ }
111459
+ return [...new Set(known)].map((candidate) => ({ candidate, distance: levenshtein2(needle, candidate.toLowerCase()) })).filter((entry) => entry.distance <= MAX_EDIT_DISTANCE).sort((a, b) => a.distance - b.distance || a.candidate.localeCompare(b.candidate)).slice(0, MAX_SUGGESTIONS).map((entry) => entry.candidate);
111460
+ }
111461
+ function formatUnknownCommandMessage(input, known, helpCommand = "keryx --help") {
111462
+ const suggestions = suggestClosest(input, known);
111463
+ const didYouMean = suggestions.length > 0 ? ` Did you mean: ${suggestions.join(", ")}?` : "";
111464
+ return `Unknown command: ${input}.${didYouMean} Run \`${helpCommand}\` for the list.`;
111465
+ }
111466
+
111310
111467
  // src/commands/integrate.ts
111311
111468
  init_ui();
111312
111469
  import path257 from "path";
@@ -111853,12 +112010,12 @@ function loadMcpServers(options) {
111853
112010
  const home = options.home ?? (options.configDir === undefined ? undefined : NO_HOME);
111854
112011
  for (const entry of compatFiles(options.cwd, home)) {
111855
112012
  const read = readCompatFile(entry, options.cwd);
111856
- problems.push(...read.problems);
112013
+ problems.push(...read.problems.map((p) => ({ ...p, foreign: true })));
111857
112014
  for (const [name, value] of Object.entries(read.servers)) {
111858
112015
  const found = entryProblems(name, value);
111859
112016
  if (found.length > 0) {
111860
112017
  for (const message of found)
111861
- problems.push({ file: entry.file, message });
112018
+ problems.push({ file: entry.file, message, foreign: true });
111862
112019
  continue;
111863
112020
  }
111864
112021
  winner.set(name, {
@@ -114032,19 +114189,31 @@ function publicView(server, env) {
114032
114189
  function listCommand(args, deps) {
114033
114190
  const config = load(deps);
114034
114191
  const wantJson = splitAtSeparator(args).own.includes("--json");
114192
+ const ownProblems = config.problems.filter((p) => !p.foreign);
114193
+ const foreignProblems = config.problems.filter((p) => p.foreign);
114035
114194
  if (wantJson) {
114036
114195
  const env = deps.env ?? process.env;
114037
- deps.log(JSON.stringify({ servers: config.servers.map((s) => publicView(s, env)), problems: config.problems }, null, 2));
114038
- return config.problems.length > 0 ? 1 : 0;
114196
+ deps.log(JSON.stringify({
114197
+ servers: config.servers.map((s) => publicView(s, env)),
114198
+ problems: ownProblems,
114199
+ warnings: foreignProblems
114200
+ }, null, 2));
114201
+ return ownProblems.length > 0 ? 1 : 0;
114039
114202
  }
114040
- for (const problem of config.problems) {
114203
+ for (const problem of ownProblems) {
114041
114204
  deps.err(`config problem \u2014 ${problem.file}: ${problem.message}`);
114042
114205
  }
114206
+ if (foreignProblems.length > 0) {
114207
+ deps.err("warnings:");
114208
+ for (const problem of foreignProblems) {
114209
+ deps.err(` ${problem.file}: ${problem.message}`);
114210
+ }
114211
+ }
114043
114212
  if (config.servers.length === 0) {
114044
114213
  deps.log("No MCP servers configured.");
114045
114214
  deps.log(` user: ${userConfigFile(deps.configDir)}`);
114046
114215
  deps.log(` project: ${projectConfigFile(projectRootOf2(deps))}`);
114047
- return config.problems.length > 0 ? 1 : 0;
114216
+ return ownProblems.length > 0 ? 1 : 0;
114048
114217
  }
114049
114218
  const approvals = loadTrustStore(deps.configDir);
114050
114219
  let held = 0;
@@ -114063,7 +114232,7 @@ function listCommand(args, deps) {
114063
114232
  deps.log(`${held} server(s) from committed config are not started until approved \u2014 a committed config is code someone else wrote.`);
114064
114233
  deps.log("Read what it launches above, then: keryx mcp trust <name>");
114065
114234
  }
114066
- return config.problems.length > 0 ? 1 : 0;
114235
+ return ownProblems.length > 0 ? 1 : 0;
114067
114236
  }
114068
114237
  function describeTarget(server) {
114069
114238
  const raw = server.raw;
@@ -116960,8 +117129,12 @@ async function mcpCommand(args = [], cwd = process.cwd()) {
116960
117129
  await serveMcpCommand(args.slice(1), cwd);
116961
117130
  return;
116962
117131
  }
116963
- console.error(`Unknown mcp command: ${subcommand}`);
116964
- printMcpHelp();
117132
+ console.error(formatUnknownCommandMessage(subcommand ?? "", [
117133
+ ...MCP_CONSUMER_SUBCOMMANDS,
117134
+ "install",
117135
+ "uninstall",
117136
+ "serve"
117137
+ ], "keryx mcp --help"));
116965
117138
  process.exitCode = 1;
116966
117139
  }
116967
117140
  function announceRename(retired, replacement) {
@@ -127578,7 +127751,7 @@ async function dispatchLocked(projectRoot, entry, flow, dispatch, deps, runId) {
127578
127751
  });
127579
127752
  const provider = guardUsage(built, () => meter.markUsageMissing());
127580
127753
  try {
127581
- const tools = buildUnattendedRoster(worktree, unattendedRunner(worktree, sandbox));
127754
+ const tools = [...buildUnattendedRoster(worktree, unattendedRunner(worktree, sandbox)), ...deps.extraTools ?? []];
127582
127755
  const toolNames = tools.map((t) => t.definition.name);
127583
127756
  const shellHooks = buildShellHookRuntime({
127584
127757
  projectRoot: worktree,
@@ -127633,6 +127806,9 @@ async function dispatchLocked(projectRoot, entry, flow, dispatch, deps, runId) {
127633
127806
  signal: controller.signal
127634
127807
  });
127635
127808
  finishReason = result.finishReason;
127809
+ if (result.caughtToolErrors !== undefined && result.caughtToolErrors.length > 0) {
127810
+ crashed = result.caughtToolErrors.map((e) => `${e.toolName}: ${e.message}`).join("; ");
127811
+ }
127636
127812
  } catch (error) {
127637
127813
  crashed = error instanceof Error ? error.message : String(error);
127638
127814
  } finally {
@@ -135479,6 +135655,569 @@ class NextStepSuggestionGate {
135479
135655
  }
135480
135656
  }
135481
135657
 
135658
+ // src/commands/doctor.ts
135659
+ init_package();
135660
+ import { readdir as readdir53 } from "fs/promises";
135661
+ import { existsSync as existsSync48 } from "fs";
135662
+ import path302 from "path";
135663
+
135664
+ // src/lib/version-check.ts
135665
+ init_config_dir();
135666
+ init_fs();
135667
+ import path301 from "path";
135668
+ var REGISTRY_URL = "https://registry.npmjs.org/@mrciphersmith%2Fkeryx/latest";
135669
+ var FIXED_INSTALL_COMMAND = "npm install -g @mrciphersmith/keryx@latest";
135670
+ var RESPONSE_BODY_LIMIT_BYTES = 64 * 1024;
135671
+ var VERSION_STRING_LIMIT_CHARS = 64;
135672
+ var REQUEST_TIMEOUT_MS = 2000;
135673
+ var SUCCESS_CACHE_TTL_MS = 24 * 60 * 60 * 1000;
135674
+ var FAILURE_BACKOFF_MS = 15 * 60 * 1000;
135675
+ var CACHE_LOCK_TIMEOUT_MS2 = 250;
135676
+ var CACHE_LOCK_RETRY_MS2 = 10;
135677
+ var CACHE_LOCK_STALE_MS2 = 5000;
135678
+ var SYSTEM_TIMER = {
135679
+ schedule: (callback, delayMs) => setTimeout(callback, delayMs),
135680
+ cancel: (handle) => clearTimeout(handle)
135681
+ };
135682
+ function formatVersionUpdateAdvisory(result) {
135683
+ if (result.status !== "update-available")
135684
+ return;
135685
+ return `Keryx update ${result.currentVersion} \u2192 ${result.latestVersion}
135686
+ ${result.installCommand}`;
135687
+ }
135688
+ var STRICT_SEMVER = /^(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)(?:-([0-9A-Za-z-]+(?:\.[0-9A-Za-z-]+)*))?(?:\+([0-9A-Za-z-]+(?:\.[0-9A-Za-z-]+)*))?$/;
135689
+ function parseSemVer(value) {
135690
+ if (value.length > VERSION_STRING_LIMIT_CHARS)
135691
+ return;
135692
+ const match = STRICT_SEMVER.exec(value);
135693
+ if (match === null)
135694
+ return;
135695
+ const prereleaseText = match[4];
135696
+ const prerelease = [];
135697
+ if (prereleaseText !== undefined) {
135698
+ for (const identifier of prereleaseText.split(".")) {
135699
+ if (/^\d+$/.test(identifier)) {
135700
+ if (identifier.length > 1 && identifier.startsWith("0"))
135701
+ return;
135702
+ prerelease.push(BigInt(identifier));
135703
+ } else {
135704
+ prerelease.push(identifier);
135705
+ }
135706
+ }
135707
+ }
135708
+ return {
135709
+ major: BigInt(match[1]),
135710
+ minor: BigInt(match[2]),
135711
+ patch: BigInt(match[3]),
135712
+ prerelease
135713
+ };
135714
+ }
135715
+ function compareSemVer(left, right) {
135716
+ for (const key of ["major", "minor", "patch"]) {
135717
+ if (left[key] < right[key])
135718
+ return -1;
135719
+ if (left[key] > right[key])
135720
+ return 1;
135721
+ }
135722
+ if (left.prerelease.length === 0 || right.prerelease.length === 0) {
135723
+ if (left.prerelease.length === right.prerelease.length)
135724
+ return 0;
135725
+ return left.prerelease.length === 0 ? 1 : -1;
135726
+ }
135727
+ const length = Math.max(left.prerelease.length, right.prerelease.length);
135728
+ for (let index = 0;index < length; index += 1) {
135729
+ const a = left.prerelease[index];
135730
+ const b = right.prerelease[index];
135731
+ if (a === undefined || b === undefined) {
135732
+ if (a === b)
135733
+ return 0;
135734
+ return a === undefined ? -1 : 1;
135735
+ }
135736
+ if (typeof a === "bigint" && typeof b === "bigint") {
135737
+ if (a < b)
135738
+ return -1;
135739
+ if (a > b)
135740
+ return 1;
135741
+ } else if (typeof a === "bigint") {
135742
+ return -1;
135743
+ } else if (typeof b === "bigint") {
135744
+ return 1;
135745
+ } else if (a !== b) {
135746
+ return a < b ? -1 : 1;
135747
+ }
135748
+ }
135749
+ return 0;
135750
+ }
135751
+ function parseCache(file) {
135752
+ const read = readConfigFile(file);
135753
+ if (!read.ok)
135754
+ return;
135755
+ try {
135756
+ const value = JSON.parse(read.text);
135757
+ if (value === null || typeof value !== "object" || Array.isArray(value))
135758
+ return;
135759
+ const record = value;
135760
+ const cache = {};
135761
+ if (typeof record.latestVersion === "string" && parseSemVer(record.latestVersion) !== undefined) {
135762
+ cache.latestVersion = record.latestVersion;
135763
+ }
135764
+ if (typeof record.successAt === "number" && Number.isFinite(record.successAt))
135765
+ cache.successAt = record.successAt;
135766
+ if (typeof record.failureAt === "number" && Number.isFinite(record.failureAt))
135767
+ cache.failureAt = record.failureAt;
135768
+ return cache;
135769
+ } catch {
135770
+ return;
135771
+ }
135772
+ }
135773
+ function resultFor(currentVersion, current, latestVersion, source) {
135774
+ const latest = parseSemVer(latestVersion);
135775
+ return compareSemVer(current, latest) < 0 ? { status: "update-available", currentVersion, latestVersion, installCommand: FIXED_INSTALL_COMMAND, source } : { status: "up-to-date", currentVersion, latestVersion, source };
135776
+ }
135777
+ function cancelWithoutWaiting(cancel) {
135778
+ try {
135779
+ cancel().catch(() => {});
135780
+ } catch {}
135781
+ }
135782
+ async function boundedResponseText(response) {
135783
+ const declared = response.headers.get("content-length");
135784
+ if (declared !== null && /^\d+$/.test(declared) && BigInt(declared) > BigInt(RESPONSE_BODY_LIMIT_BYTES)) {
135785
+ if (response.body !== null) {
135786
+ cancelWithoutWaiting(() => response.body.cancel());
135787
+ }
135788
+ return;
135789
+ }
135790
+ if (response.body === null)
135791
+ return "";
135792
+ const reader = response.body.getReader();
135793
+ const decoder = new TextDecoder;
135794
+ let size = 0;
135795
+ let text = "";
135796
+ try {
135797
+ for (;; ) {
135798
+ const chunk = await reader.read();
135799
+ if (chunk.done)
135800
+ break;
135801
+ size += chunk.value.byteLength;
135802
+ if (size > RESPONSE_BODY_LIMIT_BYTES) {
135803
+ cancelWithoutWaiting(() => reader.cancel());
135804
+ return;
135805
+ }
135806
+ text += decoder.decode(chunk.value, { stream: true });
135807
+ }
135808
+ return text + decoder.decode();
135809
+ } finally {
135810
+ reader.releaseLock();
135811
+ }
135812
+ }
135813
+ function unavailable(currentVersion, reason, cache) {
135814
+ return {
135815
+ status: "unavailable",
135816
+ currentVersion,
135817
+ reason,
135818
+ ...cache?.latestVersion !== undefined ? { cachedLatestVersion: cache.latestVersion } : {}
135819
+ };
135820
+ }
135821
+ function saveCache2(file, cache) {
135822
+ try {
135823
+ writeOwnerOnlyFileAtomic(file, `${JSON.stringify(cache, null, 2)}
135824
+ `);
135825
+ return true;
135826
+ } catch {
135827
+ return false;
135828
+ }
135829
+ }
135830
+ function mergeSuccessfulCache(committed, latestVersion, successAt) {
135831
+ if (committed.latestVersion === undefined || committed.successAt === undefined) {
135832
+ return { latestVersion, successAt };
135833
+ }
135834
+ if (committed.successAt > successAt)
135835
+ return committed;
135836
+ if (committed.successAt < successAt)
135837
+ return { latestVersion, successAt };
135838
+ const committedVersion = parseSemVer(committed.latestVersion);
135839
+ const candidateVersion = parseSemVer(latestVersion);
135840
+ if (committedVersion !== undefined && candidateVersion !== undefined) {
135841
+ const precedence = compareSemVer(committedVersion, candidateVersion);
135842
+ if (precedence > 0)
135843
+ return committed;
135844
+ if (precedence < 0)
135845
+ return { latestVersion, successAt };
135846
+ if (committed.latestVersion >= latestVersion)
135847
+ return committed;
135848
+ }
135849
+ return { latestVersion, successAt };
135850
+ }
135851
+ async function updateCache(file, update) {
135852
+ try {
135853
+ return await withFileLock(`${file}.lock`, async () => saveCache2(file, update(parseCache(file) ?? {})), {
135854
+ timeoutMs: CACHE_LOCK_TIMEOUT_MS2,
135855
+ retryMs: CACHE_LOCK_RETRY_MS2,
135856
+ staleMs: CACHE_LOCK_STALE_MS2
135857
+ });
135858
+ } catch {
135859
+ return false;
135860
+ }
135861
+ }
135862
+ async function checkVersion(options) {
135863
+ const current = parseSemVer(options.currentVersion);
135864
+ if (current === undefined)
135865
+ return unavailable(options.currentVersion, "invalid-current-version");
135866
+ const now = options.now ?? Date.now;
135867
+ const timestamp = now();
135868
+ const configDir = ensureKeryxConfigDir(options.cacheDir);
135869
+ const cacheFile = path301.join(configDir, "version-check.json");
135870
+ const cache = parseCache(cacheFile);
135871
+ if (cache?.latestVersion !== undefined && cache.successAt !== undefined && timestamp - cache.successAt >= 0 && timestamp - cache.successAt < SUCCESS_CACHE_TTL_MS) {
135872
+ return resultFor(options.currentVersion, current, cache.latestVersion, "cache");
135873
+ }
135874
+ if (cache?.failureAt !== undefined && timestamp - cache.failureAt >= 0 && timestamp - cache.failureAt < FAILURE_BACKOFF_MS) {
135875
+ return unavailable(options.currentVersion, "suppressed", cache);
135876
+ }
135877
+ const fetchImpl = options.fetch ?? globalThis.fetch;
135878
+ const controller = new AbortController;
135879
+ const timer = options.timer ?? SYSTEM_TIMER;
135880
+ let timeout;
135881
+ let timeoutScheduled = false;
135882
+ let failure;
135883
+ let latestVersion;
135884
+ try {
135885
+ timeout = timer.schedule(() => controller.abort(), REQUEST_TIMEOUT_MS);
135886
+ timeoutScheduled = true;
135887
+ const response = await fetchImpl(REGISTRY_URL, {
135888
+ method: "GET",
135889
+ headers: { accept: "application/json" },
135890
+ signal: controller.signal,
135891
+ credentials: "omit"
135892
+ });
135893
+ if (!response.ok) {
135894
+ failure = "http";
135895
+ } else {
135896
+ const body = await boundedResponseText(response);
135897
+ if (body === undefined) {
135898
+ failure = "response-too-large";
135899
+ } else {
135900
+ let parsed;
135901
+ try {
135902
+ parsed = JSON.parse(body);
135903
+ } catch {
135904
+ parsed = undefined;
135905
+ }
135906
+ if (parsed === null || typeof parsed !== "object" || Array.isArray(parsed)) {
135907
+ failure = "malformed-response";
135908
+ } else {
135909
+ const record = parsed;
135910
+ if (record.name !== "@mrciphersmith/keryx" || typeof record.version !== "string") {
135911
+ failure = "malformed-response";
135912
+ } else if (parseSemVer(record.version) === undefined) {
135913
+ failure = "invalid-latest-version";
135914
+ } else {
135915
+ latestVersion = record.version;
135916
+ }
135917
+ }
135918
+ }
135919
+ }
135920
+ } catch {
135921
+ failure = controller.signal.aborted ? "timeout" : "network";
135922
+ } finally {
135923
+ if (timeoutScheduled) {
135924
+ try {
135925
+ timer.cancel(timeout);
135926
+ } catch {}
135927
+ }
135928
+ }
135929
+ if (latestVersion === undefined) {
135930
+ await updateCache(cacheFile, (committed) => ({ ...committed, failureAt: timestamp }));
135931
+ return unavailable(options.currentVersion, failure ?? "network", cache);
135932
+ }
135933
+ await updateCache(cacheFile, (committed) => mergeSuccessfulCache(committed, latestVersion, timestamp));
135934
+ return resultFor(options.currentVersion, current, latestVersion, "registry");
135935
+ }
135936
+
135937
+ // src/commands/doctor.ts
135938
+ init_contained_path();
135939
+ init_providers();
135940
+ init_service();
135941
+ init_service7();
135942
+ init_service10();
135943
+ async function registeredWorktrees(cwd) {
135944
+ if (!Bun.which("git"))
135945
+ return new Set;
135946
+ try {
135947
+ const proc = Bun.spawn(["git", "worktree", "list", "--porcelain"], { cwd, stdout: "pipe", stderr: "ignore" });
135948
+ const [stdout, code] = await Promise.all([new Response(proc.stdout).text(), proc.exited]);
135949
+ if (code !== 0)
135950
+ return new Set;
135951
+ const paths = new Set;
135952
+ for (const line of stdout.split(`
135953
+ `)) {
135954
+ if (line.startsWith("worktree ")) {
135955
+ paths.add(path302.resolve(line.slice("worktree ".length).trim()));
135956
+ }
135957
+ }
135958
+ return paths;
135959
+ } catch {
135960
+ return new Set;
135961
+ }
135962
+ }
135963
+ async function checkWorktrees(cwd) {
135964
+ const worktreesDir = path302.join(cwd, ".claude", "worktrees");
135965
+ let entries;
135966
+ try {
135967
+ entries = (await readdir53(worktreesDir, { withFileTypes: true })).filter((entry) => entry.isDirectory()).map((entry) => entry.name);
135968
+ } catch {
135969
+ return { id: "worktrees", status: "ok", detail: "no .claude/worktrees directory" };
135970
+ }
135971
+ if (entries.length === 0) {
135972
+ return { id: "worktrees", status: "ok", detail: ".claude/worktrees is empty" };
135973
+ }
135974
+ const registered = await registeredWorktrees(cwd);
135975
+ const stale = entries.filter((name) => {
135976
+ const full = path302.resolve(worktreesDir, name);
135977
+ return !existsSync48(path302.join(full, ".git")) || !registered.has(full);
135978
+ });
135979
+ if (stale.length === 0) {
135980
+ return {
135981
+ id: "worktrees",
135982
+ status: "ok",
135983
+ detail: `${entries.length} worktree(s) under .claude/worktrees, all registered`
135984
+ };
135985
+ }
135986
+ return {
135987
+ id: "worktrees",
135988
+ status: "warn",
135989
+ detail: `${stale.length} stale entr${stale.length === 1 ? "y" : "ies"} under .claude/worktrees: ${stale.join(", ")}`,
135990
+ fix: "git worktree prune, then remove the listed directories under .claude/worktrees by hand"
135991
+ };
135992
+ }
135993
+ function meetsBunFloor(current, floor) {
135994
+ const floorMatch = /^>=\s*(\d+)\.(\d+)\.(\d+)/.exec(floor);
135995
+ const currentMatch = /^(\d+)\.(\d+)\.(\d+)/.exec(current);
135996
+ if (!floorMatch || !currentMatch)
135997
+ return true;
135998
+ for (let i = 1;i <= 3; i += 1) {
135999
+ const f = Number(floorMatch[i]);
136000
+ const c = Number(currentMatch[i]);
136001
+ if (c !== f)
136002
+ return c > f;
136003
+ }
136004
+ return true;
136005
+ }
136006
+ function checkBun(bunFloorOverride) {
136007
+ const floor = bunFloorOverride ?? package_default.engines?.bun;
136008
+ const current = Bun.version;
136009
+ if (floor === undefined || meetsBunFloor(current, floor)) {
136010
+ return { id: "bun", status: "ok", detail: `Bun ${current}${floor ? ` (floor ${floor})` : ""}` };
136011
+ }
136012
+ return {
136013
+ id: "bun",
136014
+ status: "fail",
136015
+ detail: `Bun ${current} is below the documented floor ${floor}`,
136016
+ fix: "upgrade Bun: https://bun.sh/docs/installation"
136017
+ };
136018
+ }
136019
+ function checkRipgrep() {
136020
+ const rg = Bun.which("rg");
136021
+ return rg ? { id: "ripgrep", status: "ok", detail: `rg at ${rg}` } : {
136022
+ id: "ripgrep",
136023
+ status: "warn",
136024
+ detail: "ripgrep (rg) not found on PATH \u2014 keryx ctx rg and search_code are unavailable",
136025
+ fix: "brew install ripgrep (macOS) or apt install ripgrep (Debian/Ubuntu)"
136026
+ };
136027
+ }
136028
+ async function checkVersionAvailability() {
136029
+ const result = await checkVersion({ currentVersion: package_default.version });
136030
+ if (result.status === "update-available") {
136031
+ return {
136032
+ id: "version",
136033
+ status: "warn",
136034
+ detail: `keryx ${result.currentVersion} \u2192 ${result.latestVersion} is available`,
136035
+ fix: result.installCommand
136036
+ };
136037
+ }
136038
+ if (result.status === "up-to-date") {
136039
+ return { id: "version", status: "ok", detail: `keryx ${result.currentVersion} is up to date` };
136040
+ }
136041
+ return { id: "version", status: "ok", detail: `keryx ${result.currentVersion}; update check unavailable (${result.reason})` };
136042
+ }
136043
+ function checkSandbox() {
136044
+ const report = buildSandboxReport();
136045
+ if (report.launcher.available) {
136046
+ return { id: "sandbox", status: "ok", detail: `${report.launcherName ?? "sandbox launcher"} available` };
136047
+ }
136048
+ const known = report.launcherName !== undefined;
136049
+ return {
136050
+ id: "sandbox",
136051
+ status: "warn",
136052
+ detail: known ? `${report.launcherName} not installed on ${report.platform} \u2014 OS-sandboxed shell exec is unavailable` : `no OS sandbox launcher support on ${report.platform}`,
136053
+ fix: known ? "keryx sandbox status for what installing it would unlock" : "keryx sandbox status for details"
136054
+ };
136055
+ }
136056
+ function checkProviders(env) {
136057
+ const providers = configuredProviders(env);
136058
+ if (providers.length === 0) {
136059
+ return {
136060
+ id: "providers",
136061
+ status: "warn",
136062
+ detail: "no provider credentials configured",
136063
+ fix: "keryx auth login <provider>, or set a provider API key"
136064
+ };
136065
+ }
136066
+ return {
136067
+ id: "providers",
136068
+ status: "ok",
136069
+ detail: `${providers.length} provider(s) configured: ${providers.map((p) => p.name).join(", ")}`
136070
+ };
136071
+ }
136072
+ function checkMcp(cwd) {
136073
+ const projectRoot = resolveProjectRoot(cwd);
136074
+ const config = loadMcpServers({ cwd, gitRoot: projectRoot });
136075
+ const ownProblems = config.problems.filter((p) => !p.foreign);
136076
+ if (ownProblems.length > 0) {
136077
+ return {
136078
+ id: "mcp",
136079
+ status: "fail",
136080
+ detail: `${ownProblems.length} config problem(s) in keryx's own MCP config: ${ownProblems.map((p) => p.message).join("; ")}`,
136081
+ fix: "keryx mcp list for details"
136082
+ };
136083
+ }
136084
+ const approvals = loadTrustStore();
136085
+ const held = config.servers.filter((server) => server.enabled && requiresApproval(server, approvals));
136086
+ if (held.length > 0) {
136087
+ return {
136088
+ id: "mcp",
136089
+ status: "warn",
136090
+ detail: `${held.length} server(s) from committed config awaiting approval: ${held.map((s) => s.name).join(", ")}`,
136091
+ fix: "keryx mcp trust <name>, after reading what it launches"
136092
+ };
136093
+ }
136094
+ return {
136095
+ id: "mcp",
136096
+ status: "ok",
136097
+ detail: `${config.servers.length} server(s) configured; run \`keryx mcp doctor\` to also check connectivity`
136098
+ };
136099
+ }
136100
+ async function checkIntegrations(cwd) {
136101
+ const results = await Promise.all(harnessAdapterIds().map(async (id) => {
136102
+ try {
136103
+ return await doctorIntegration(cwd, id);
136104
+ } catch {
136105
+ return { runtimeId: id, surfaces: [], problems: [], ok: true };
136106
+ }
136107
+ }));
136108
+ const drifted = results.filter((r) => !r.ok);
136109
+ if (drifted.length === 0) {
136110
+ return { id: "integrations", status: "ok", detail: "no drift in any installed integration" };
136111
+ }
136112
+ return {
136113
+ id: "integrations",
136114
+ status: "warn",
136115
+ detail: `drift in: ${drifted.map((r) => r.runtimeId).join(", ")}`,
136116
+ fix: "keryx integrations doctor --runtime all"
136117
+ };
136118
+ }
136119
+ async function checkStandard(cwd) {
136120
+ const result = await runDoctor(cwd);
136121
+ if (result.errors.length > 0) {
136122
+ return {
136123
+ id: "standard",
136124
+ status: "fail",
136125
+ detail: `${result.errors.length} error(s): ${result.errors.map((e) => e.message).join("; ")}`,
136126
+ fix: "keryx standard doctor"
136127
+ };
136128
+ }
136129
+ if (result.warnings.length > 0) {
136130
+ return {
136131
+ id: "standard",
136132
+ status: "warn",
136133
+ detail: `${result.warnings.length} warning(s): ${result.warnings.map((w) => w.message).join("; ")}`,
136134
+ fix: "keryx standard doctor"
136135
+ };
136136
+ }
136137
+ return { id: "standard", status: "ok", detail: "workspace is Metaproject Standard compliant" };
136138
+ }
136139
+ async function checkGraphFreshness(cwd) {
136140
+ const result = await checkGraphStaleness(cwd);
136141
+ if (result.status === "fresh") {
136142
+ return { id: "graph-freshness", status: "ok", detail: "code graph is fresh" };
136143
+ }
136144
+ return {
136145
+ id: "graph-freshness",
136146
+ status: "warn",
136147
+ detail: result.reasons.join("; ") || `code graph is ${result.status}`,
136148
+ fix: "keryx gdgraph build"
136149
+ };
136150
+ }
136151
+ async function checkWikiFreshness(cwd) {
136152
+ const metric = await readWikiFreshnessMetric(cwd);
136153
+ if (metric.status === "measured") {
136154
+ const pct = metric.ratio !== undefined ? `${Math.round(metric.ratio * 100)}%` : "an unmeasured share";
136155
+ return {
136156
+ id: "wiki-freshness",
136157
+ status: "ok",
136158
+ detail: `${pct} of ${metric.pagesTotal ?? "?"} wiki page(s) fresh`
136159
+ };
136160
+ }
136161
+ return {
136162
+ id: "wiki-freshness",
136163
+ status: "warn",
136164
+ detail: metric.reason ?? `wiki freshness: ${metric.status}`,
136165
+ fix: "keryx wiki freshness"
136166
+ };
136167
+ }
136168
+ async function buildDoctorReport(cwd, env = process.env, overrides = {}) {
136169
+ const checks = await Promise.all([
136170
+ checkVersionAvailability(),
136171
+ Promise.resolve(checkBun(overrides.bunFloor)),
136172
+ Promise.resolve(checkRipgrep()),
136173
+ Promise.resolve(checkSandbox()),
136174
+ Promise.resolve(checkProviders(env)),
136175
+ Promise.resolve(checkMcp(cwd)),
136176
+ checkIntegrations(cwd),
136177
+ checkStandard(cwd),
136178
+ checkWorktrees(cwd),
136179
+ checkGraphFreshness(cwd),
136180
+ checkWikiFreshness(cwd)
136181
+ ]);
136182
+ return { checks };
136183
+ }
136184
+ function doctorFailed(report) {
136185
+ return report.checks.some((check) => check.status === "fail");
136186
+ }
136187
+ function formatDoctorReport2(report) {
136188
+ const symbolFor = { ok: "\u2713", warn: "!", fail: "\u2717" };
136189
+ const lines = ["keryx doctor", ""];
136190
+ for (const check of report.checks) {
136191
+ lines.push(` ${symbolFor[check.status]} ${check.id}: ${check.detail}`);
136192
+ if (check.fix) {
136193
+ lines.push(` fix: ${check.fix}`);
136194
+ }
136195
+ }
136196
+ return lines.join(`
136197
+ `);
136198
+ }
136199
+ async function doctorCommand2(args = [], cwd = process.cwd(), overrides = {}) {
136200
+ if (args.includes("--help") || args.includes("-h")) {
136201
+ console.log(`keryx doctor [--json]
136202
+
136203
+ One page: keryx's own version and update availability, the Bun floor,
136204
+ ripgrep on PATH, the OS sandbox launcher, providers with a credential
136205
+ present (names only), MCP servers and their trust state, integrations
136206
+ drift, Metaproject Standard warnings, stale .claude/worktrees entries, and
136207
+ graph/wiki freshness. Exits 0 unless something is a "fail".`);
136208
+ return;
136209
+ }
136210
+ const report = await buildDoctorReport(cwd, process.env, overrides);
136211
+ if (args.includes("--json")) {
136212
+ console.log(JSON.stringify(report, null, 2));
136213
+ } else {
136214
+ console.log(formatDoctorReport2(report));
136215
+ }
136216
+ if (doctorFailed(report)) {
136217
+ process.exitCode = 1;
136218
+ }
136219
+ }
136220
+
135482
136221
  // src/tui/tui-shell.ts
135483
136222
  init_slate_lifecycle();
135484
136223
  init_slate();
@@ -135491,7 +136230,7 @@ init_workspace_service();
135491
136230
  init_service11();
135492
136231
  init_store3();
135493
136232
  init_fs();
135494
- import path301 from "path";
136233
+ import path303 from "path";
135495
136234
  import { readFile as readFile157 } from "fs/promises";
135496
136235
  var POSITIVE_INTEGER = /^[1-9][0-9]*$/;
135497
136236
  var DEFAULT_AUTO_GOAL_ROUNDS = 8;
@@ -135567,7 +136306,7 @@ function getFlowService() {
135567
136306
  async function autoProvisionFlow(cwd, goalText) {
135568
136307
  const service = getFlowService();
135569
136308
  const result = await service.init({ cwd, title: goalText });
135570
- const acFile = path301.join(cwd, result.dir, "acceptance-criteria.md");
136309
+ const acFile = path303.join(cwd, result.dir, "acceptance-criteria.md");
135571
136310
  await writeFileAtomic(acFile, [
135572
136311
  "# Acceptance Criteria",
135573
136312
  "",
@@ -137255,8 +137994,8 @@ init_store4();
137255
137994
  init_proposal_lifecycle();
137256
137995
  init_workspace_service();
137257
137996
  import { randomUUID as randomUUID34 } from "crypto";
137258
- import { readdir as readdir53, stat as stat30 } from "fs/promises";
137259
- import path302 from "path";
137997
+ import { readdir as readdir54, stat as stat30 } from "fs/promises";
137998
+ import path304 from "path";
137260
137999
 
137261
138000
  // src/sac/lifecycle-flag.ts
137262
138001
  init_store();
@@ -137395,7 +138134,7 @@ async function readExternalUnboundCandidates(cwd) {
137395
138134
  const extDir = externalSlatesDir(cwd);
137396
138135
  let externalIds;
137397
138136
  try {
137398
- const entries = await readdir53(extDir);
138137
+ const entries = await readdir54(extDir);
137399
138138
  externalIds = entries.filter((name) => name.endsWith(".json")).map((name) => name.slice(0, -".json".length));
137400
138139
  } catch {
137401
138140
  return [];
@@ -137426,7 +138165,7 @@ async function readExternalUnboundCandidates(cwd) {
137426
138165
  }
137427
138166
  return groups;
137428
138167
  }, []));
137429
- const metaPath = path302.join(extDir, `${id}.json`);
138168
+ const metaPath = path304.join(extDir, `${id}.json`);
137430
138169
  if (await pathExists(unboundDismissedReceiptPath(metaPath)))
137431
138170
  continue;
137432
138171
  candidates.push({
@@ -137440,16 +138179,16 @@ async function readExternalUnboundCandidates(cwd) {
137440
138179
  return candidates;
137441
138180
  }
137442
138181
  async function readNewestUnboundCandidateForExternal(cwd, externalSessionId) {
137443
- const evidenceDir = path302.join(externalSlatesDir(cwd), externalSessionId);
138182
+ const evidenceDir = path304.join(externalSlatesDir(cwd), externalSessionId);
137444
138183
  let entries;
137445
138184
  try {
137446
- entries = (await readdir53(evidenceDir)).filter((name) => name.endsWith("-unbound-candidate.json"));
138185
+ entries = (await readdir54(evidenceDir)).filter((name) => name.endsWith("-unbound-candidate.json"));
137447
138186
  } catch {
137448
138187
  return;
137449
138188
  }
137450
138189
  entries.sort();
137451
138190
  for (let i = entries.length - 1;i >= 0; i--) {
137452
- const evidencePath = path302.join(evidenceDir, entries[i]);
138191
+ const evidencePath = path304.join(evidenceDir, entries[i]);
137453
138192
  if (await pathExists(unboundDismissedReceiptPath(evidencePath)))
137454
138193
  continue;
137455
138194
  const result = readConfigFile(evidencePath);
@@ -137492,23 +138231,23 @@ var SESSION_ATTRIBUTION_SLACK_MS = 5 * 60000;
137492
138231
  var MTIME_CLOCK_SKEW_TOLERANCE_MS = 5000;
137493
138232
  async function collectReceiptTargets(cwd, owner) {
137494
138233
  const targets = new Set;
137495
- const workspacesDir = path302.join(cwd, ".metaproject", "workspaces");
138234
+ const workspacesDir = path304.join(cwd, ".metaproject", "workspaces");
137496
138235
  let workspaceIds;
137497
138236
  try {
137498
- workspaceIds = (await readdir53(workspacesDir, { withFileTypes: true })).filter((entry) => entry.isDirectory()).map((entry) => entry.name);
138237
+ workspaceIds = (await readdir54(workspacesDir, { withFileTypes: true })).filter((entry) => entry.isDirectory()).map((entry) => entry.name);
137499
138238
  } catch {
137500
138239
  return targets;
137501
138240
  }
137502
138241
  for (const workspaceId of workspaceIds) {
137503
- const receiptsDir = path302.join(workspacesDir, workspaceId, `${owner}-write-receipts`);
138242
+ const receiptsDir = path304.join(workspacesDir, workspaceId, `${owner}-write-receipts`);
137504
138243
  let files;
137505
138244
  try {
137506
- files = (await readdir53(receiptsDir)).filter((name) => name.endsWith(".json"));
138245
+ files = (await readdir54(receiptsDir)).filter((name) => name.endsWith(".json"));
137507
138246
  } catch {
137508
138247
  continue;
137509
138248
  }
137510
138249
  for (const file of files) {
137511
- const result = readConfigFile(path302.join(receiptsDir, file));
138250
+ const result = readConfigFile(path304.join(receiptsDir, file));
137512
138251
  if (!result.ok)
137513
138252
  continue;
137514
138253
  try {
@@ -137545,13 +138284,13 @@ async function attributeToSession(absolutePath, sessionsNewestFirst) {
137545
138284
  async function findSkillFiles(dir) {
137546
138285
  let entries;
137547
138286
  try {
137548
- entries = await readdir53(dir, { withFileTypes: true });
138287
+ entries = await readdir54(dir, { withFileTypes: true });
137549
138288
  } catch {
137550
138289
  return [];
137551
138290
  }
137552
138291
  const found = [];
137553
138292
  for (const entry of entries) {
137554
- const full = path302.join(dir, entry.name);
138293
+ const full = path304.join(dir, entry.name);
137555
138294
  if (entry.isDirectory()) {
137556
138295
  found.push(...await findSkillFiles(full));
137557
138296
  } else if (entry.isFile() && entry.name === "SKILL.md") {
@@ -137610,10 +138349,10 @@ async function detectUnreviewedSacPathChanges(cwd, sessions) {
137610
138349
  changedAt: attribution.changedAt
137611
138350
  });
137612
138351
  }
137613
- const sacSkillsDir = path302.join(cwd, ".metaproject", "project-skills", "sac");
138352
+ const sacSkillsDir = path304.join(cwd, ".metaproject", "project-skills", "sac");
137614
138353
  const skillFiles = await findSkillFiles(sacSkillsDir);
137615
138354
  for (const absolutePath of skillFiles) {
137616
- const targetRef = path302.relative(path302.join(cwd, ".metaproject"), absolutePath).split(path302.sep).join("/");
138355
+ const targetRef = path304.relative(path304.join(cwd, ".metaproject"), absolutePath).split(path304.sep).join("/");
137617
138356
  if (skillReceipts.has(targetRef))
137618
138357
  continue;
137619
138358
  const attribution = await attributeToSession(absolutePath, scoped);
@@ -137631,12 +138370,12 @@ async function detectUnreviewedSacPathChanges(cwd, sessions) {
137631
138370
  return items;
137632
138371
  }
137633
138372
  async function isSlateEngaged(dir) {
137634
- if (await pathExists(path302.join(dir, "slate.json")))
138373
+ if (await pathExists(path304.join(dir, "slate.json")))
137635
138374
  return true;
137636
- if (await pathExists(path302.join(dir, "terminal-state.json")))
138375
+ if (await pathExists(path304.join(dir, "terminal-state.json")))
137637
138376
  return true;
137638
138377
  try {
137639
- const entries = await readdir53(path302.join(dir, "slate-archive"));
138378
+ const entries = await readdir54(path304.join(dir, "slate-archive"));
137640
138379
  return entries.length > 0;
137641
138380
  } catch {
137642
138381
  return false;
@@ -137661,7 +138400,7 @@ async function safeReadSlate(dir) {
137661
138400
  }
137662
138401
  }
137663
138402
  async function readTerminalState(dir) {
137664
- const result = readConfigFile(path302.join(dir, "terminal-state.json"));
138403
+ const result = readConfigFile(path304.join(dir, "terminal-state.json"));
137665
138404
  if (!result.ok) {
137666
138405
  return;
137667
138406
  }
@@ -137685,17 +138424,17 @@ async function resolveUnboundCandidateTarget(cwd, target) {
137685
138424
  if (session === undefined) {
137686
138425
  throw new Error(`No session or evidence path matches "${target}"`);
137687
138426
  }
137688
- const archiveDir = path302.join(sessionDir(session.projectPath, session.id), "slate-archive");
138427
+ const archiveDir = path304.join(sessionDir(session.projectPath, session.id), "slate-archive");
137689
138428
  let entries;
137690
138429
  try {
137691
- entries = (await readdir53(archiveDir)).filter((name) => name.endsWith("-unbound-candidate.json"));
138430
+ entries = (await readdir54(archiveDir)).filter((name) => name.endsWith("-unbound-candidate.json"));
137692
138431
  } catch {
137693
138432
  throw new Error(`No unbound-candidate artifacts for session ${session.id}`);
137694
138433
  }
137695
138434
  if (entries.length === 0)
137696
138435
  throw new Error(`No unbound-candidate artifacts for session ${session.id}`);
137697
138436
  entries.sort();
137698
- return path302.join(archiveDir, entries[entries.length - 1]);
138437
+ return path304.join(archiveDir, entries[entries.length - 1]);
137699
138438
  }
137700
138439
  async function dismissUnboundByTarget(cwd, target, reason) {
137701
138440
  const evidencePath = await resolveUnboundCandidateTarget(cwd, target);
@@ -137721,16 +138460,16 @@ async function dismissUnboundCandidate(candidatePath, reason) {
137721
138460
  return { removed: candidatePath, receipt: receiptPath };
137722
138461
  }
137723
138462
  async function readNewestUnboundCandidate(dir) {
137724
- const archiveDir = path302.join(dir, "slate-archive");
138463
+ const archiveDir = path304.join(dir, "slate-archive");
137725
138464
  let entries;
137726
138465
  try {
137727
- entries = (await readdir53(archiveDir)).filter((name) => name.endsWith("-unbound-candidate.json"));
138466
+ entries = (await readdir54(archiveDir)).filter((name) => name.endsWith("-unbound-candidate.json"));
137728
138467
  } catch {
137729
138468
  return;
137730
138469
  }
137731
138470
  entries.sort();
137732
138471
  for (let i = entries.length - 1;i >= 0; i--) {
137733
- const evidencePath = path302.join(archiveDir, entries[i]);
138472
+ const evidencePath = path304.join(archiveDir, entries[i]);
137734
138473
  if (await pathExists(unboundDismissedReceiptPath(evidencePath)))
137735
138474
  continue;
137736
138475
  const result = readConfigFile(evidencePath);
@@ -137760,16 +138499,16 @@ function isFailureOutcome2(g) {
137760
138499
  return g.outcome === "error" || g.outcome === "no_credential" || g.outcome === "conflict";
137761
138500
  }
137762
138501
  async function readNewestWrapUpOutcome(dir) {
137763
- const archiveDir = path302.join(dir, "slate-archive");
138502
+ const archiveDir = path304.join(dir, "slate-archive");
137764
138503
  let entries;
137765
138504
  try {
137766
- entries = (await readdir53(archiveDir)).filter((name) => name.endsWith("-wrap-up-outcome.json"));
138505
+ entries = (await readdir54(archiveDir)).filter((name) => name.endsWith("-wrap-up-outcome.json"));
137767
138506
  } catch {
137768
138507
  return;
137769
138508
  }
137770
138509
  entries.sort();
137771
138510
  for (let i = entries.length - 1;i >= 0; i--) {
137772
- const evidencePath = path302.join(archiveDir, entries[i]);
138511
+ const evidencePath = path304.join(archiveDir, entries[i]);
137773
138512
  const result = readConfigFile(evidencePath);
137774
138513
  if (!result.ok) {
137775
138514
  continue;
@@ -138376,6 +139115,11 @@ var AGENT_SLASH_COMMANDS = [
138376
139115
  modes: BOTH
138377
139116
  },
138378
139117
  { name: "/flows", description: "Browse project flows and inspect one", modes: BOTH },
139118
+ {
139119
+ name: "/doctor",
139120
+ description: "One-page health check: version, sandbox, providers, MCP, integrations, freshness",
139121
+ modes: BOTH
139122
+ },
138379
139123
  {
138380
139124
  name: "/ac",
138381
139125
  description: "Check the active flow's acceptance criteria (advisory, cached) \u2014 press `c` in /flows to re-check",
@@ -138718,7 +139462,7 @@ function classifyBusyDispatch(params) {
138718
139462
  init_fs();
138719
139463
  init_json();
138720
139464
  import { mkdir as mkdir67 } from "fs/promises";
138721
- import path304 from "path";
139465
+ import path306 from "path";
138722
139466
 
138723
139467
  // src/governance/aggregate.ts
138724
139468
  init_store3();
@@ -138726,17 +139470,17 @@ init_store3();
138726
139470
  // src/governance/spend.ts
138727
139471
  init_fs();
138728
139472
  init_store3();
138729
- import { readdir as readdir54, readFile as readFile158 } from "fs/promises";
138730
- import path303 from "path";
139473
+ import { readdir as readdir55, readFile as readFile158 } from "fs/promises";
139474
+ import path305 from "path";
138731
139475
  async function readFlowReviewManifests(cwd, flowDir) {
138732
- const reviewsDir = path303.join(flowsRoot(cwd), flowDir, "reviews");
139476
+ const reviewsDir = path305.join(flowsRoot(cwd), flowDir, "reviews");
138733
139477
  if (!await pathExists(reviewsDir)) {
138734
139478
  return [];
138735
139479
  }
138736
- const entries = (await readdir54(reviewsDir, { withFileTypes: true })).filter((entry) => entry.isDirectory());
139480
+ const entries = (await readdir55(reviewsDir, { withFileTypes: true })).filter((entry) => entry.isDirectory());
138737
139481
  const manifests = [];
138738
139482
  for (const entry of entries) {
138739
- const manifestPath = path303.join(reviewsDir, entry.name, "manifest.json");
139483
+ const manifestPath = path305.join(reviewsDir, entry.name, "manifest.json");
138740
139484
  if (!await pathExists(manifestPath)) {
138741
139485
  continue;
138742
139486
  }
@@ -138983,11 +139727,11 @@ async function collectProjectGovernance(root, filters, displayName) {
138983
139727
  // src/governance/report.ts
138984
139728
  var GOVERNANCE_SCHEMA_VERSION = 1;
138985
139729
  function governanceDataRoot(cwd) {
138986
- return path304.join(cwd, ".metaproject", "data", "governance");
139730
+ return path306.join(cwd, ".metaproject", "data", "governance");
138987
139731
  }
138988
139732
  function artifactPaths(cwd) {
138989
- const artifacts = path304.join(governanceDataRoot(cwd), "artifacts");
138990
- return { markdown: path304.join(artifacts, "latest.md"), json: path304.join(artifacts, "latest.json") };
139733
+ const artifacts = path306.join(governanceDataRoot(cwd), "artifacts");
139734
+ return { markdown: path306.join(artifacts, "latest.md"), json: path306.join(artifacts, "latest.json") };
138991
139735
  }
138992
139736
  async function buildGovernanceReport(options) {
138993
139737
  const { cwd, filters, allProjects, now } = options;
@@ -138995,9 +139739,9 @@ async function buildGovernanceReport(options) {
138995
139739
  if (allProjects) {
138996
139740
  const warnings = [];
138997
139741
  const registered = listProjects(options.registryDir, (message) => warnings.push(message));
138998
- const seen = new Set([path304.resolve(cwd)]);
139742
+ const seen = new Set([path306.resolve(cwd)]);
138999
139743
  for (const entry of registered) {
139000
- const resolved = path304.resolve(entry.path);
139744
+ const resolved = path306.resolve(entry.path);
139001
139745
  if (seen.has(resolved)) {
139002
139746
  continue;
139003
139747
  }
@@ -139150,7 +139894,7 @@ function renderTriggerSpendLine(spend) {
139150
139894
  }
139151
139895
  async function writeGovernanceArtifacts(cwd, report) {
139152
139896
  const { markdown, json } = artifactPaths(cwd);
139153
- await mkdir67(path304.dirname(markdown), { recursive: true });
139897
+ await mkdir67(path306.dirname(markdown), { recursive: true });
139154
139898
  await writeFileAtomic(markdown, renderGovernanceMarkdown(report));
139155
139899
  await writeFileAtomic(json, `${JSON.stringify(report, null, 2)}
139156
139900
  `);
@@ -139355,7 +140099,7 @@ function mountGovernancePanel(otui, renderer, parent, options) {
139355
140099
 
139356
140100
  // src/tui/governance-inspector.ts
139357
140101
  import { readFile as readFile159 } from "fs/promises";
139358
- import path305 from "path";
140102
+ import path307 from "path";
139359
140103
  var GOVERNANCE_COMMAND = "/governance";
139360
140104
  var GOVERNANCE_FOOTER = [
139361
140105
  { key: "\u2191/\u2193 j/k", label: "scroll" },
@@ -139366,7 +140110,7 @@ var GOVERNANCE_FOOTER = [
139366
140110
  var HEADER_TEXT_ROWS = 2;
139367
140111
  var GOVERNANCE_HEADER_ROWS = HEADER_TEXT_ROWS + 1;
139368
140112
  function governanceMarkdownPath(cwd) {
139369
- return path305.join(governanceDataRoot(cwd), "artifacts", "latest.md");
140113
+ return path307.join(governanceDataRoot(cwd), "artifacts", "latest.md");
139370
140114
  }
139371
140115
  async function defaultReadMarkdown(cwd) {
139372
140116
  try {
@@ -139511,7 +140255,7 @@ function openGovernanceReport(otui, chrome, options) {
139511
140255
 
139512
140256
  // src/tui/trigger-ledger.ts
139513
140257
  import { stat as fsStat2 } from "fs/promises";
139514
- import path306 from "path";
140258
+ import path308 from "path";
139515
140259
  var DEFAULT_RECORDS_PER_ENTRY = 20;
139516
140260
  function isScheduledEntry(entry) {
139517
140261
  return entry.fire.kind === "schedule";
@@ -139570,7 +140314,7 @@ function triggerLedgerPaths(root) {
139570
140314
  return {
139571
140315
  runs: triggerRunsPath(root),
139572
140316
  triggers: triggersConfigPath(root),
139573
- governance: path306.join(governanceDataRoot(root), "artifacts", "latest.json"),
140317
+ governance: path308.join(governanceDataRoot(root), "artifacts", "latest.json"),
139574
140318
  schedules: scheduleStorePath(root)
139575
140319
  };
139576
140320
  }
@@ -140143,7 +140887,7 @@ function openTriggers(otui, chrome, options) {
140143
140887
  // src/tui/trigger-run-now.ts
140144
140888
  import { spawn as nodeSpawn2 } from "child_process";
140145
140889
  import { closeSync as closeSync5, constants as constants3, fstatSync as fstatSync3, lstatSync as lstatSync6, mkdirSync as mkdirSync7, openSync as openSync5, readdirSync as readdirSync10, readSync as readSync3, unlinkSync as unlinkSync4, writeSync } from "fs";
140146
- import path307 from "path";
140890
+ import path309 from "path";
140147
140891
  var OUTPUT_TAIL_CHARS = 4000;
140148
140892
  var defaultSpawn = (command, args, options) => nodeSpawn2(command, [...args], { cwd: options.cwd, env: options.env, detached: true, stdio: [...options.stdio] });
140149
140893
  function triggerRunArgv(name, invocation = resolveKeryxInvocation(), options = {}) {
@@ -140158,7 +140902,7 @@ function runNowChildEnv(env) {
140158
140902
  return out;
140159
140903
  }
140160
140904
  function runNowLogDir(root) {
140161
- return path307.join(triggerDataDir(root), "run-now");
140905
+ return path309.join(triggerDataDir(root), "run-now");
140162
140906
  }
140163
140907
  var RUN_NOW_LOGS_KEPT = 5;
140164
140908
  var OUTPUT_TAIL_BYTES = 16 * 1024;
@@ -140166,13 +140910,13 @@ function stampOf(iso) {
140166
140910
  return iso.replace(/[:.]/g, "-");
140167
140911
  }
140168
140912
  function runNowLogPath(root, name, startedAt) {
140169
- return path307.join(runNowLogDir(root), `${name}-${stampOf(startedAt)}.log`);
140913
+ return path309.join(runNowLogDir(root), `${name}-${stampOf(startedAt)}.log`);
140170
140914
  }
140171
140915
  function isCode(error, code) {
140172
140916
  return typeof error === "object" && error !== null && "code" in error && error.code === code;
140173
140917
  }
140174
140918
  function prepareRunNowLogDir(root) {
140175
- mkdirSync7(path307.join(root, ".metaproject", "data"), { recursive: true });
140919
+ mkdirSync7(path309.join(root, ".metaproject", "data"), { recursive: true });
140176
140920
  for (const segment of [triggerDataDir(root), runNowLogDir(root)]) {
140177
140921
  let info;
140178
140922
  try {
@@ -140191,7 +140935,7 @@ function prepareRunNowLogDir(root) {
140191
140935
  }
140192
140936
  const dir = runNowLogDir(root);
140193
140937
  try {
140194
- const fd = openSync5(path307.join(dir, ".gitignore"), constants3.O_WRONLY | constants3.O_CREAT | constants3.O_EXCL | (constants3.O_NOFOLLOW ?? 0), 420);
140938
+ const fd = openSync5(path309.join(dir, ".gitignore"), constants3.O_WRONLY | constants3.O_CREAT | constants3.O_EXCL | (constants3.O_NOFOLLOW ?? 0), 420);
140195
140939
  try {
140196
140940
  writeSync(fd, `# keryx run-now logs \u2014 never commit
140197
140941
  *
@@ -140232,7 +140976,7 @@ function pruneRunLogs(dir, name, keep) {
140232
140976
  }
140233
140977
  for (const old of names.slice(0, Math.max(0, names.length - keep))) {
140234
140978
  try {
140235
- unlinkSync4(path307.join(dir, old));
140979
+ unlinkSync4(path309.join(dir, old));
140236
140980
  } catch {}
140237
140981
  }
140238
140982
  }
@@ -141250,279 +141994,6 @@ function mountSchedulesSidebar(options) {
141250
141994
  // src/tui/tui-shell.ts
141251
141995
  init_debug_log();
141252
141996
 
141253
- // src/lib/version-check.ts
141254
- init_config_dir();
141255
- init_fs();
141256
- import path308 from "path";
141257
- var REGISTRY_URL = "https://registry.npmjs.org/@mrciphersmith%2Fkeryx/latest";
141258
- var FIXED_INSTALL_COMMAND = "npm install -g @mrciphersmith/keryx@latest";
141259
- var RESPONSE_BODY_LIMIT_BYTES = 64 * 1024;
141260
- var VERSION_STRING_LIMIT_CHARS = 64;
141261
- var REQUEST_TIMEOUT_MS = 2000;
141262
- var SUCCESS_CACHE_TTL_MS = 24 * 60 * 60 * 1000;
141263
- var FAILURE_BACKOFF_MS = 15 * 60 * 1000;
141264
- var CACHE_LOCK_TIMEOUT_MS2 = 250;
141265
- var CACHE_LOCK_RETRY_MS2 = 10;
141266
- var CACHE_LOCK_STALE_MS2 = 5000;
141267
- var SYSTEM_TIMER = {
141268
- schedule: (callback, delayMs) => setTimeout(callback, delayMs),
141269
- cancel: (handle) => clearTimeout(handle)
141270
- };
141271
- function formatVersionUpdateAdvisory(result) {
141272
- if (result.status !== "update-available")
141273
- return;
141274
- return `Keryx update ${result.currentVersion} \u2192 ${result.latestVersion}
141275
- ${result.installCommand}`;
141276
- }
141277
- var STRICT_SEMVER = /^(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)(?:-([0-9A-Za-z-]+(?:\.[0-9A-Za-z-]+)*))?(?:\+([0-9A-Za-z-]+(?:\.[0-9A-Za-z-]+)*))?$/;
141278
- function parseSemVer(value) {
141279
- if (value.length > VERSION_STRING_LIMIT_CHARS)
141280
- return;
141281
- const match = STRICT_SEMVER.exec(value);
141282
- if (match === null)
141283
- return;
141284
- const prereleaseText = match[4];
141285
- const prerelease = [];
141286
- if (prereleaseText !== undefined) {
141287
- for (const identifier of prereleaseText.split(".")) {
141288
- if (/^\d+$/.test(identifier)) {
141289
- if (identifier.length > 1 && identifier.startsWith("0"))
141290
- return;
141291
- prerelease.push(BigInt(identifier));
141292
- } else {
141293
- prerelease.push(identifier);
141294
- }
141295
- }
141296
- }
141297
- return {
141298
- major: BigInt(match[1]),
141299
- minor: BigInt(match[2]),
141300
- patch: BigInt(match[3]),
141301
- prerelease
141302
- };
141303
- }
141304
- function compareSemVer(left, right) {
141305
- for (const key of ["major", "minor", "patch"]) {
141306
- if (left[key] < right[key])
141307
- return -1;
141308
- if (left[key] > right[key])
141309
- return 1;
141310
- }
141311
- if (left.prerelease.length === 0 || right.prerelease.length === 0) {
141312
- if (left.prerelease.length === right.prerelease.length)
141313
- return 0;
141314
- return left.prerelease.length === 0 ? 1 : -1;
141315
- }
141316
- const length = Math.max(left.prerelease.length, right.prerelease.length);
141317
- for (let index = 0;index < length; index += 1) {
141318
- const a = left.prerelease[index];
141319
- const b = right.prerelease[index];
141320
- if (a === undefined || b === undefined) {
141321
- if (a === b)
141322
- return 0;
141323
- return a === undefined ? -1 : 1;
141324
- }
141325
- if (typeof a === "bigint" && typeof b === "bigint") {
141326
- if (a < b)
141327
- return -1;
141328
- if (a > b)
141329
- return 1;
141330
- } else if (typeof a === "bigint") {
141331
- return -1;
141332
- } else if (typeof b === "bigint") {
141333
- return 1;
141334
- } else if (a !== b) {
141335
- return a < b ? -1 : 1;
141336
- }
141337
- }
141338
- return 0;
141339
- }
141340
- function parseCache(file) {
141341
- const read = readConfigFile(file);
141342
- if (!read.ok)
141343
- return;
141344
- try {
141345
- const value = JSON.parse(read.text);
141346
- if (value === null || typeof value !== "object" || Array.isArray(value))
141347
- return;
141348
- const record = value;
141349
- const cache = {};
141350
- if (typeof record.latestVersion === "string" && parseSemVer(record.latestVersion) !== undefined) {
141351
- cache.latestVersion = record.latestVersion;
141352
- }
141353
- if (typeof record.successAt === "number" && Number.isFinite(record.successAt))
141354
- cache.successAt = record.successAt;
141355
- if (typeof record.failureAt === "number" && Number.isFinite(record.failureAt))
141356
- cache.failureAt = record.failureAt;
141357
- return cache;
141358
- } catch {
141359
- return;
141360
- }
141361
- }
141362
- function resultFor(currentVersion, current, latestVersion, source) {
141363
- const latest = parseSemVer(latestVersion);
141364
- return compareSemVer(current, latest) < 0 ? { status: "update-available", currentVersion, latestVersion, installCommand: FIXED_INSTALL_COMMAND, source } : { status: "up-to-date", currentVersion, latestVersion, source };
141365
- }
141366
- function cancelWithoutWaiting(cancel) {
141367
- try {
141368
- cancel().catch(() => {});
141369
- } catch {}
141370
- }
141371
- async function boundedResponseText(response) {
141372
- const declared = response.headers.get("content-length");
141373
- if (declared !== null && /^\d+$/.test(declared) && BigInt(declared) > BigInt(RESPONSE_BODY_LIMIT_BYTES)) {
141374
- if (response.body !== null) {
141375
- cancelWithoutWaiting(() => response.body.cancel());
141376
- }
141377
- return;
141378
- }
141379
- if (response.body === null)
141380
- return "";
141381
- const reader = response.body.getReader();
141382
- const decoder = new TextDecoder;
141383
- let size = 0;
141384
- let text = "";
141385
- try {
141386
- for (;; ) {
141387
- const chunk = await reader.read();
141388
- if (chunk.done)
141389
- break;
141390
- size += chunk.value.byteLength;
141391
- if (size > RESPONSE_BODY_LIMIT_BYTES) {
141392
- cancelWithoutWaiting(() => reader.cancel());
141393
- return;
141394
- }
141395
- text += decoder.decode(chunk.value, { stream: true });
141396
- }
141397
- return text + decoder.decode();
141398
- } finally {
141399
- reader.releaseLock();
141400
- }
141401
- }
141402
- function unavailable(currentVersion, reason, cache) {
141403
- return {
141404
- status: "unavailable",
141405
- currentVersion,
141406
- reason,
141407
- ...cache?.latestVersion !== undefined ? { cachedLatestVersion: cache.latestVersion } : {}
141408
- };
141409
- }
141410
- function saveCache2(file, cache) {
141411
- try {
141412
- writeOwnerOnlyFileAtomic(file, `${JSON.stringify(cache, null, 2)}
141413
- `);
141414
- return true;
141415
- } catch {
141416
- return false;
141417
- }
141418
- }
141419
- function mergeSuccessfulCache(committed, latestVersion, successAt) {
141420
- if (committed.latestVersion === undefined || committed.successAt === undefined) {
141421
- return { latestVersion, successAt };
141422
- }
141423
- if (committed.successAt > successAt)
141424
- return committed;
141425
- if (committed.successAt < successAt)
141426
- return { latestVersion, successAt };
141427
- const committedVersion = parseSemVer(committed.latestVersion);
141428
- const candidateVersion = parseSemVer(latestVersion);
141429
- if (committedVersion !== undefined && candidateVersion !== undefined) {
141430
- const precedence = compareSemVer(committedVersion, candidateVersion);
141431
- if (precedence > 0)
141432
- return committed;
141433
- if (precedence < 0)
141434
- return { latestVersion, successAt };
141435
- if (committed.latestVersion >= latestVersion)
141436
- return committed;
141437
- }
141438
- return { latestVersion, successAt };
141439
- }
141440
- async function updateCache(file, update) {
141441
- try {
141442
- return await withFileLock(`${file}.lock`, async () => saveCache2(file, update(parseCache(file) ?? {})), {
141443
- timeoutMs: CACHE_LOCK_TIMEOUT_MS2,
141444
- retryMs: CACHE_LOCK_RETRY_MS2,
141445
- staleMs: CACHE_LOCK_STALE_MS2
141446
- });
141447
- } catch {
141448
- return false;
141449
- }
141450
- }
141451
- async function checkVersion(options) {
141452
- const current = parseSemVer(options.currentVersion);
141453
- if (current === undefined)
141454
- return unavailable(options.currentVersion, "invalid-current-version");
141455
- const now = options.now ?? Date.now;
141456
- const timestamp = now();
141457
- const configDir = ensureKeryxConfigDir(options.cacheDir);
141458
- const cacheFile = path308.join(configDir, "version-check.json");
141459
- const cache = parseCache(cacheFile);
141460
- if (cache?.latestVersion !== undefined && cache.successAt !== undefined && timestamp - cache.successAt >= 0 && timestamp - cache.successAt < SUCCESS_CACHE_TTL_MS) {
141461
- return resultFor(options.currentVersion, current, cache.latestVersion, "cache");
141462
- }
141463
- if (cache?.failureAt !== undefined && timestamp - cache.failureAt >= 0 && timestamp - cache.failureAt < FAILURE_BACKOFF_MS) {
141464
- return unavailable(options.currentVersion, "suppressed", cache);
141465
- }
141466
- const fetchImpl = options.fetch ?? globalThis.fetch;
141467
- const controller = new AbortController;
141468
- const timer = options.timer ?? SYSTEM_TIMER;
141469
- let timeout;
141470
- let timeoutScheduled = false;
141471
- let failure;
141472
- let latestVersion;
141473
- try {
141474
- timeout = timer.schedule(() => controller.abort(), REQUEST_TIMEOUT_MS);
141475
- timeoutScheduled = true;
141476
- const response = await fetchImpl(REGISTRY_URL, {
141477
- method: "GET",
141478
- headers: { accept: "application/json" },
141479
- signal: controller.signal,
141480
- credentials: "omit"
141481
- });
141482
- if (!response.ok) {
141483
- failure = "http";
141484
- } else {
141485
- const body = await boundedResponseText(response);
141486
- if (body === undefined) {
141487
- failure = "response-too-large";
141488
- } else {
141489
- let parsed;
141490
- try {
141491
- parsed = JSON.parse(body);
141492
- } catch {
141493
- parsed = undefined;
141494
- }
141495
- if (parsed === null || typeof parsed !== "object" || Array.isArray(parsed)) {
141496
- failure = "malformed-response";
141497
- } else {
141498
- const record = parsed;
141499
- if (record.name !== "@mrciphersmith/keryx" || typeof record.version !== "string") {
141500
- failure = "malformed-response";
141501
- } else if (parseSemVer(record.version) === undefined) {
141502
- failure = "invalid-latest-version";
141503
- } else {
141504
- latestVersion = record.version;
141505
- }
141506
- }
141507
- }
141508
- }
141509
- } catch {
141510
- failure = controller.signal.aborted ? "timeout" : "network";
141511
- } finally {
141512
- if (timeoutScheduled) {
141513
- try {
141514
- timer.cancel(timeout);
141515
- } catch {}
141516
- }
141517
- }
141518
- if (latestVersion === undefined) {
141519
- await updateCache(cacheFile, (committed) => ({ ...committed, failureAt: timestamp }));
141520
- return unavailable(options.currentVersion, failure ?? "network", cache);
141521
- }
141522
- await updateCache(cacheFile, (committed) => mergeSuccessfulCache(committed, latestVersion, timestamp));
141523
- return resultFor(options.currentVersion, current, latestVersion, "registry");
141524
- }
141525
-
141526
141997
  // src/tui/shell-chrome.ts
141527
141998
  init_debug_log();
141528
141999
 
@@ -143826,7 +144297,7 @@ init_model_tier();
143826
144297
  init_config_dir();
143827
144298
  init_fs();
143828
144299
  init_table();
143829
- import path309 from "path";
144300
+ import path310 from "path";
143830
144301
  var UNCATEGORIZED = "uncategorized";
143831
144302
  var EMPTY_TASK_COST_STORE = {};
143832
144303
  var MIN_MEASURED_TASKS = 20;
@@ -143892,7 +144363,7 @@ var TASK_COST_LOCK_TIMEOUT_MS = 3000;
143892
144363
  var TASK_COST_LOCK_RETRY_MS = 15;
143893
144364
  var TASK_COST_LOCK_STALE_MS = 1e4;
143894
144365
  function taskCostFilePath(dir) {
143895
- return path309.join(ensureKeryxSubdir(["routing"], dir), TASK_COST_FILE);
144366
+ return path310.join(ensureKeryxSubdir(["routing"], dir), TASK_COST_FILE);
143896
144367
  }
143897
144368
  function isTaskCostRecord(value) {
143898
144369
  if (typeof value !== "object" || value === null)
@@ -144956,11 +145427,11 @@ function openConform(otui, chrome, options) {
144956
145427
  }
144957
145428
 
144958
145429
  // src/tui/conform-source.ts
144959
- import { readFile as readFile160, readdir as readdir55, stat as stat31 } from "fs/promises";
144960
- import path310 from "path";
145430
+ import { readFile as readFile160, readdir as readdir56, stat as stat31 } from "fs/promises";
145431
+ import path311 from "path";
144961
145432
  async function readRecentConformDocs2(cwd) {
144962
145433
  try {
144963
- const raw = JSON.parse(await readFile160(path310.join(cwd, CONFORM_RECENTS_PATH), "utf8"));
145434
+ const raw = JSON.parse(await readFile160(path311.join(cwd, CONFORM_RECENTS_PATH), "utf8"));
144964
145435
  return Array.isArray(raw) ? raw : [];
144965
145436
  } catch {
144966
145437
  return [];
@@ -144981,25 +145452,25 @@ async function currentBranchPrTarget(cwd) {
144981
145452
  }
144982
145453
  }
144983
145454
  async function mostRecentReviewPackage(cwd) {
144984
- const flowsDir = path310.join(cwd, ".metaproject", "flows");
145455
+ const flowsDir = path311.join(cwd, ".metaproject", "flows");
144985
145456
  let best;
144986
145457
  try {
144987
- for (const flowEntry of await readdir55(flowsDir, { withFileTypes: true })) {
145458
+ for (const flowEntry of await readdir56(flowsDir, { withFileTypes: true })) {
144988
145459
  if (!flowEntry.isDirectory())
144989
145460
  continue;
144990
- const reviewsDir = path310.join(flowsDir, flowEntry.name, "reviews");
145461
+ const reviewsDir = path311.join(flowsDir, flowEntry.name, "reviews");
144991
145462
  let reviewEntries;
144992
145463
  try {
144993
- reviewEntries = await readdir55(reviewsDir, { withFileTypes: true });
145464
+ reviewEntries = await readdir56(reviewsDir, { withFileTypes: true });
144994
145465
  } catch {
144995
145466
  continue;
144996
145467
  }
144997
145468
  for (const reviewEntry of reviewEntries) {
144998
145469
  if (!reviewEntry.isDirectory())
144999
145470
  continue;
145000
- const dir = path310.join(reviewsDir, reviewEntry.name);
145471
+ const dir = path311.join(reviewsDir, reviewEntry.name);
145001
145472
  try {
145002
- const info = await stat31(path310.join(dir, "report.md"));
145473
+ const info = await stat31(path311.join(dir, "report.md"));
145003
145474
  if (best === undefined || info.mtimeMs > best.mtimeMs)
145004
145475
  best = { dir, mtimeMs: info.mtimeMs };
145005
145476
  } catch {}
@@ -145025,14 +145496,14 @@ async function loadConformSetup(cwd) {
145025
145496
  targets.push(pr);
145026
145497
  const reviewDir = await mostRecentReviewPackage(cwd);
145027
145498
  if (reviewDir !== undefined) {
145028
- targets.push({ id: `report:${reviewDir}`, kind: "report", label: `most recent review package \u2014 ${path310.relative(cwd, reviewDir)}` });
145499
+ targets.push({ id: `report:${reviewDir}`, kind: "report", label: `most recent review package \u2014 ${path311.relative(cwd, reviewDir)}` });
145029
145500
  }
145030
145501
  targets.push({ id: "diff:working", kind: "diff", label: "the working diff" });
145031
145502
  return { recents, targets };
145032
145503
  }
145033
145504
  async function readReportFindings2(reportDir) {
145034
145505
  try {
145035
- const raw = JSON.parse(await readFile160(path310.join(reportDir, "findings.json"), "utf8"));
145506
+ const raw = JSON.parse(await readFile160(path311.join(reportDir, "findings.json"), "utf8"));
145036
145507
  return Array.isArray(raw) ? raw : [];
145037
145508
  } catch {
145038
145509
  return [];
@@ -145148,7 +145619,7 @@ async function runConformForTarget(cwd, refPath, target, signal) {
145148
145619
  await scoreHunkRegions(hunkClauses, hunkRegionsFromDiff(info.diff));
145149
145620
  } else if (target.kind === "report") {
145150
145621
  const reportDir = target.id.slice("report:".length);
145151
- const reportMarkdown = await readFile160(path310.join(reportDir, "report.md"), "utf8").catch(() => "");
145622
+ const reportMarkdown = await readFile160(path311.join(reportDir, "report.md"), "utf8").catch(() => "");
145152
145623
  const findings = await readReportFindings2(reportDir);
145153
145624
  const facts = computeReportConformFacts(reportMarkdown, findings);
145154
145625
  const redacted = reportRedactedStateText(reportMarkdown);
@@ -145174,7 +145645,7 @@ async function runConformForTarget(cwd, refPath, target, signal) {
145174
145645
  async function writeRecentConformDoc(cwd, refPath) {
145175
145646
  await Promise.resolve().then(() => init_fs());
145176
145647
  const current = await readRecentConformDocs2(cwd);
145177
- await writeFileAtomic(path310.join(cwd, CONFORM_RECENTS_PATH), `${JSON.stringify(withRecentDoc(current, refPath), null, 2)}
145648
+ await writeFileAtomic(path311.join(cwd, CONFORM_RECENTS_PATH), `${JSON.stringify(withRecentDoc(current, refPath), null, 2)}
145178
145649
  `, {
145179
145650
  mode: 384
145180
145651
  }).catch(() => {});
@@ -145310,8 +145781,8 @@ async function runJevContractForShell(cwd, gitDiff = defaultGitDiff3) {
145310
145781
  }
145311
145782
 
145312
145783
  // src/tui/jev-triage-command.ts
145313
- import { readdir as readdir56, readFile as readFile161, stat as stat32 } from "fs/promises";
145314
- import path311 from "path";
145784
+ import { readdir as readdir57, readFile as readFile161, stat as stat32 } from "fs/promises";
145785
+ import path312 from "path";
145315
145786
  var JEV_TRIAGE_COMMAND = "/triage";
145316
145787
  function isJevTriageCommand(name) {
145317
145788
  return name === JEV_TRIAGE_COMMAND;
@@ -145320,25 +145791,25 @@ function renderJevTriageForShell(result) {
145320
145791
  return renderTriageMarkdown(result).trimEnd();
145321
145792
  }
145322
145793
  async function resolveLatestReviewPackage(cwd) {
145323
- const flowsDir = path311.join(cwd, ".metaproject", "flows");
145794
+ const flowsDir = path312.join(cwd, ".metaproject", "flows");
145324
145795
  let flowEntries;
145325
145796
  try {
145326
- flowEntries = (await readdir56(flowsDir, { withFileTypes: true })).filter((e) => e.isDirectory()).map((e) => e.name);
145797
+ flowEntries = (await readdir57(flowsDir, { withFileTypes: true })).filter((e) => e.isDirectory()).map((e) => e.name);
145327
145798
  } catch {
145328
145799
  return;
145329
145800
  }
145330
145801
  const candidates = [];
145331
145802
  for (const flowName of flowEntries) {
145332
- const reviewsDir = path311.join(flowsDir, flowName, "reviews");
145803
+ const reviewsDir = path312.join(flowsDir, flowName, "reviews");
145333
145804
  let reviewEntries;
145334
145805
  try {
145335
- reviewEntries = (await readdir56(reviewsDir, { withFileTypes: true })).filter((e) => e.isDirectory()).map((e) => e.name);
145806
+ reviewEntries = (await readdir57(reviewsDir, { withFileTypes: true })).filter((e) => e.isDirectory()).map((e) => e.name);
145336
145807
  } catch {
145337
145808
  continue;
145338
145809
  }
145339
145810
  for (const reviewName of reviewEntries) {
145340
- const packageDir = path311.join(reviewsDir, reviewName);
145341
- const findingsPath = path311.join(packageDir, "findings.json");
145811
+ const packageDir = path312.join(reviewsDir, reviewName);
145812
+ const findingsPath = path312.join(packageDir, "findings.json");
145342
145813
  try {
145343
145814
  const stats = await stat32(findingsPath);
145344
145815
  candidates.push({ dir: packageDir, mtimeMs: stats.mtimeMs });
@@ -145361,9 +145832,9 @@ async function runJevTriageForShell(cwd) {
145361
145832
  if (packageDir === undefined) {
145362
145833
  return "review-jev-triage: no review package found under .metaproject/flows/*/reviews/ \u2014 run a review round first.";
145363
145834
  }
145364
- const findingsRaw = JSON.parse(await readFile161(path311.join(packageDir, "findings.json"), "utf8"));
145835
+ const findingsRaw = JSON.parse(await readFile161(path312.join(packageDir, "findings.json"), "utf8"));
145365
145836
  const result = await computeJevTriageResult({ findingsRaw });
145366
- return `review-jev-triage: ${path311.relative(cwd, packageDir)}
145837
+ return `review-jev-triage: ${path312.relative(cwd, packageDir)}
145367
145838
  ${renderJevTriageForShell(result)}`;
145368
145839
  }
145369
145840
 
@@ -148277,10 +148748,10 @@ function openHelpModal(otui, chrome, options) {
148277
148748
 
148278
148749
  // src/tui/help-first-run.ts
148279
148750
  init_config_dir();
148280
- import path312 from "path";
148751
+ import path313 from "path";
148281
148752
  var FIRST_RUN_HELP_FILE = "help-first-run.json";
148282
148753
  function firstRunHelpPath(dir) {
148283
- return path312.join(keryxConfigDir(dir), FIRST_RUN_HELP_FILE);
148754
+ return path313.join(keryxConfigDir(dir), FIRST_RUN_HELP_FILE);
148284
148755
  }
148285
148756
  function helpFirstRunShown(dir) {
148286
148757
  const result = readConfigFile(firstRunHelpPath(dir));
@@ -149364,10 +149835,10 @@ function describeElicitationPrompt(tool, inputJson) {
149364
149835
  // src/lib/permission-mode-config.ts
149365
149836
  init_permission_mode();
149366
149837
  init_config_dir();
149367
- import path313 from "path";
149838
+ import path314 from "path";
149368
149839
  var EMPTY3 = { schemaVersion: 1, projects: {} };
149369
149840
  function permissionModeConfigPath(dir) {
149370
- return path313.join(keryxConfigDir(dir), "permission-mode.json");
149841
+ return path314.join(keryxConfigDir(dir), "permission-mode.json");
149371
149842
  }
149372
149843
  function withRegistryLock2(dir, fn) {
149373
149844
  return withFileLock2(`${permissionModeConfigPath(dir)}.lock`, fn, {
@@ -152316,7 +152787,7 @@ function mountExecutionPlanPanel(otui, renderer, parent, options) {
152316
152787
  // src/commands/schedule.ts
152317
152788
  import { createInterface } from "readline/promises";
152318
152789
  import { readFile as readFile162 } from "fs/promises";
152319
- import path315 from "path";
152790
+ import path316 from "path";
152320
152791
 
152321
152792
  // src/commands/trigger.ts
152322
152793
  init_gdgraph();
@@ -152327,9 +152798,9 @@ init_agent();
152327
152798
  init_agent_hooks();
152328
152799
  import { execFile as execFile11 } from "child_process";
152329
152800
  import { randomUUID as randomUUID36 } from "crypto";
152330
- import { mkdtemp as mkdtemp5, mkdir as mkdir68, readdir as readdir57, rm as rm19, writeFile as writeFile47 } from "fs/promises";
152801
+ import { mkdtemp as mkdtemp5, mkdir as mkdir68, readdir as readdir58, rm as rm19, writeFile as writeFile47 } from "fs/promises";
152331
152802
  import { homedir as homedir13, tmpdir as tmpdir11 } from "os";
152332
- import path314 from "path";
152803
+ import path315 from "path";
152333
152804
  init_interactive_tools();
152334
152805
 
152335
152806
  // src/harness/process/sandbox/proxy.ts
@@ -152797,9 +153268,9 @@ function refusal3(runId, code, detail, action) {
152797
153268
  function agentTaskScratchParent(env = process.env) {
152798
153269
  const uid = typeof process.getuid === "function" ? process.getuid() : 0;
152799
153270
  const runtime = env["XDG_RUNTIME_DIR"];
152800
- if (runtime !== undefined && runtime.length > 0 && path314.isAbsolute(runtime))
152801
- return path314.join(runtime, "keryx-agent-tasks");
152802
- return path314.join(tmpdir11(), `keryx-agent-tasks-${uid}`);
153271
+ if (runtime !== undefined && runtime.length > 0 && path315.isAbsolute(runtime))
153272
+ return path315.join(runtime, "keryx-agent-tasks");
153273
+ return path315.join(tmpdir11(), `keryx-agent-tasks-${uid}`);
152803
153274
  }
152804
153275
  function lastAssistantText(history) {
152805
153276
  for (let i = history.length - 1;i >= 0; i -= 1) {
@@ -152810,9 +153281,9 @@ function lastAssistantText(history) {
152810
153281
  return "";
152811
153282
  }
152812
153283
  async function pruneReports(dir, keep) {
152813
- const names = (await readdir57(dir).catch(() => [])).filter((n) => n.endsWith(".md")).sort();
153284
+ const names = (await readdir58(dir).catch(() => [])).filter((n) => n.endsWith(".md")).sort();
152814
153285
  for (const old of names.slice(0, Math.max(0, names.length - keep))) {
152815
- await rm19(path314.join(dir, old), { force: true }).catch(() => {});
153286
+ await rm19(path315.join(dir, old), { force: true }).catch(() => {});
152816
153287
  }
152817
153288
  }
152818
153289
  async function runAgentTaskDispatch(projectRoot, entry, action, deps = {}) {
@@ -152849,10 +153320,10 @@ async function runLocked(projectRoot, entry, action, deps, runId, now) {
152849
153320
  };
152850
153321
  }
152851
153322
  const scratchParent = parent.dir;
152852
- const scratch = await mkdtemp5(path314.join(scratchParent, `${entry.name.replace(/[^A-Za-z0-9._-]/g, "-")}-`));
152853
- const workdir = path314.join(scratch, "work");
152854
- const scratchHome = path314.join(scratch, "home");
152855
- const grantedCwd = path314.join(scratch, "granted-cwd");
153323
+ const scratch = await mkdtemp5(path315.join(scratchParent, `${entry.name.replace(/[^A-Za-z0-9._-]/g, "-")}-`));
153324
+ const workdir = path315.join(scratch, "work");
153325
+ const scratchHome = path315.join(scratch, "home");
153326
+ const grantedCwd = path315.join(scratch, "granted-cwd");
152856
153327
  await mkdir68(workdir, { recursive: true });
152857
153328
  await mkdir68(scratchHome, { recursive: true });
152858
153329
  await mkdir68(grantedCwd, { recursive: true, mode: 448 });
@@ -152865,7 +153336,7 @@ async function runLocked(projectRoot, entry, action, deps, runId, now) {
152865
153336
  const invocation = keryxInvocation();
152866
153337
  let networkInput = action.grants.network === "full";
152867
153338
  if (action.grants.network === "allowlist") {
152868
- const proxySocketPath = path314.join(scratch, "net-proxy.sock");
153339
+ const proxySocketPath = path315.join(scratch, "net-proxy.sock");
152869
153340
  try {
152870
153341
  const proxy = await createAllowlistProxy({
152871
153342
  allowedDomains: [...action.grants.domains],
@@ -152950,7 +153421,12 @@ async function runLocked(projectRoot, entry, action, deps, runId, now) {
152950
153421
  const env = deps.grantedEnv ?? process.env;
152951
153422
  const extraSecrets = await ghAuthToken(binaries.realpaths["gh"], grantedCwd, env);
152952
153423
  const granted = action.grants.tools.map((id) => grantedToolSpec(id)).filter((spec) => spec !== undefined).map((spec) => grantedTool(spec, action, grantedCwd, env, grantedCalls, binaries.realpaths, extraSecrets, binaries.stats));
152953
- const tools = [...builtinReadOnlyTools(projectRoot), shellExecTool(workdir, sandboxedRunner(workdir, sandbox)), ...granted];
153424
+ const tools = [
153425
+ ...builtinReadOnlyTools(projectRoot),
153426
+ shellExecTool(workdir, sandboxedRunner(workdir, sandbox)),
153427
+ ...granted,
153428
+ ...deps.extraTools ?? []
153429
+ ];
152954
153430
  const offending = tools.filter((t) => UNATTENDED_EXCLUDED_TOOLS.includes(t.definition.name));
152955
153431
  if (offending.length > 0)
152956
153432
  throw new Error(`agent-task roster must not offer: ${offending.map((t) => t.definition.name).join(", ")}`);
@@ -153000,6 +153476,9 @@ async function runLocked(projectRoot, entry, action, deps, runId, now) {
153000
153476
  };
153001
153477
  const result = await runAgentTurn(io, agentDeps, history, buildAgentTaskPrompt(entry, action), { signal: controller.signal });
153002
153478
  finishReason = result.finishReason;
153479
+ if (result.caughtToolErrors !== undefined && result.caughtToolErrors.length > 0) {
153480
+ crashed = result.caughtToolErrors.map((e) => `${e.toolName}: ${e.message}`).join("; ");
153481
+ }
153003
153482
  } catch (error) {
153004
153483
  crashed = error instanceof Error ? error.message : String(error);
153005
153484
  } finally {
@@ -153014,12 +153493,12 @@ async function runLocked(projectRoot, entry, action, deps, runId, now) {
153014
153493
  let reportNote = "";
153015
153494
  try {
153016
153495
  await ensureTriggerDataIgnored(projectRoot);
153017
- const dir = path314.join(triggerReportsDir(projectRoot), entry.name);
153496
+ const dir = path315.join(triggerReportsDir(projectRoot), entry.name);
153018
153497
  await mkdir68(dir, { recursive: true });
153019
- const file = path314.join(dir, `${runId}.md`);
153498
+ const file = path315.join(dir, `${runId}.md`);
153020
153499
  await writeFile47(file, renderReport2({ entry, action, runId, at: now().toISOString(), outcome, why, cost, grantedCalls, denials, sandboxNote, summary, networkDecisions }), "utf8");
153021
153500
  await pruneReports(dir, action.report.keep);
153022
- reportPath = path314.relative(projectRoot, file);
153501
+ reportPath = path315.relative(projectRoot, file);
153023
153502
  } catch (error) {
153024
153503
  reportNote = ` (report NOT written: ${error instanceof Error ? error.message : String(error)})`;
153025
153504
  }
@@ -153927,7 +154406,7 @@ async function showSubcommand(cwd, name, host, deps) {
153927
154406
  if (row.reportPath !== undefined) {
153928
154407
  console.log(`
153929
154408
  --- ${row.reportPath} ---`);
153930
- console.log(await readFile162(path315.join(cwd, row.reportPath), "utf8").catch(() => "(the report could not be read)"));
154409
+ console.log(await readFile162(path316.join(cwd, row.reportPath), "utf8").catch(() => "(the report could not be read)"));
153931
154410
  }
153932
154411
  }
153933
154412
  async function simpleAction(cwd, name, verb, action) {
@@ -155089,14 +155568,19 @@ function attachUsageIo(io, chrome) {
155089
155568
  }
155090
155569
  });
155091
155570
  }
155092
- function estimateTaskCostUsd(profile, inputTokens, outputTokens) {
155571
+ var OPENAI_CACHED_INPUT_DISCOUNT = 0.5;
155572
+ function estimateTaskCostUsd(profile, providerId, inputTokens, outputTokens, cacheReadTokens) {
155093
155573
  if (profile === undefined)
155094
155574
  return;
155095
155575
  const priceIn = profile.priceInputPerMillion.value;
155096
155576
  const priceOut = profile.priceOutputPerMillion.value;
155097
155577
  if (priceIn === "unknown" || priceOut === "unknown")
155098
155578
  return;
155099
- return inputTokens / 1e6 * priceIn + outputTokens / 1e6 * priceOut;
155579
+ const cachedInputDiscount = providerId === "openai" || providerId === "openai-codex" ? OPENAI_CACHED_INPUT_DISCOUNT : 1;
155580
+ const cacheRead = cacheReadTokens !== undefined && cacheReadTokens > 0 ? Math.min(cacheReadTokens, inputTokens) : 0;
155581
+ const fullPriceInputTokens = inputTokens - cacheRead;
155582
+ const inputCost = fullPriceInputTokens / 1e6 * priceIn + cacheRead / 1e6 * priceIn * cachedInputDiscount;
155583
+ return inputCost + outputTokens / 1e6 * priceOut;
155100
155584
  }
155101
155585
  async function recordTurnTaskCostBestEffort(input) {
155102
155586
  if (input.inputTokens === 0 && input.outputTokens === 0)
@@ -155104,7 +155588,7 @@ async function recordTurnTaskCostBestEffort(input) {
155104
155588
  try {
155105
155589
  const profiles = loadModelProfiles(input.userConfigDir);
155106
155590
  const profile = profiles[profileKey(input.providerId, input.modelId)];
155107
- const costUsd = estimateTaskCostUsd(profile, input.inputTokens, input.outputTokens);
155591
+ const costUsd = estimateTaskCostUsd(profile, input.providerId, input.inputTokens, input.outputTokens, input.cacheReadTokens);
155108
155592
  await appendTaskCostRecord({
155109
155593
  providerId: input.providerId,
155110
155594
  modelId: input.modelId,
@@ -159560,6 +160044,14 @@ ${formatThemeList(getThemeId())}`);
159560
160044
  });
159561
160045
  return;
159562
160046
  }
160047
+ if (command.name === "/doctor") {
160048
+ (async () => {
160049
+ const report = await buildDoctorReport(sessionCwd);
160050
+ io.onSystem?.(`${formatDoctorReport2(report)}
160051
+ `);
160052
+ })();
160053
+ return;
160054
+ }
159563
160055
  if (command.name === "/game") {
159564
160056
  showGame(line);
159565
160057
  return;
@@ -159870,10 +160362,12 @@ Usage: /reasoning <${REASONING_EFFORT_LEVELS.join("|")}>
159870
160362
  };
159871
160363
  let turnInputTokens = 0;
159872
160364
  let turnOutputTokens = 0;
160365
+ let turnCacheReadTokens = 0;
159873
160366
  const prevOnUsageForCost = io.onUsage;
159874
160367
  io.onUsage = (usage) => {
159875
160368
  turnInputTokens += usage.inputTokens ?? 0;
159876
160369
  turnOutputTokens += usage.outputTokens ?? 0;
160370
+ turnCacheReadTokens += usage.cacheReadTokens ?? 0;
159877
160371
  prevOnUsageForCost?.(usage);
159878
160372
  };
159879
160373
  const suggestNextStep = async () => {
@@ -159973,6 +160467,7 @@ Usage: /reasoning <${REASONING_EFFORT_LEVELS.join("|")}>
159973
160467
  modelId: deps.modelId,
159974
160468
  inputTokens: turnInputTokens,
159975
160469
  outputTokens: turnOutputTokens,
160470
+ ...turnCacheReadTokens > 0 ? { cacheReadTokens: turnCacheReadTokens } : {},
159976
160471
  success: !turnFailed,
159977
160472
  ...turnCategory !== undefined ? { category: turnCategory } : {}
159978
160473
  });
@@ -160515,6 +161010,7 @@ var READLINE_AGENT_COMMANDS = [
160515
161010
  "/compact",
160516
161011
  "/status",
160517
161012
  "/flows",
161013
+ "/doctor",
160518
161014
  "/theme",
160519
161015
  "/mode",
160520
161016
  "/reasoning",
@@ -161025,6 +161521,12 @@ Kept the current session.
161025
161521
  system(HELP_TEXT);
161026
161522
  continue;
161027
161523
  }
161524
+ if (command === "/doctor") {
161525
+ const report = await buildDoctorReport(deps.session?.cwd ?? process.cwd());
161526
+ system(`${formatDoctorReport2(report)}
161527
+ `);
161528
+ continue;
161529
+ }
161028
161530
  if (isSessionInfoCommand(command)) {
161029
161531
  const cwd = deps.session?.cwd;
161030
161532
  const [workspaces, flows] = cwd === undefined ? [[], []] : await Promise.all([loadInspectorWorkspaces(cwd), loadInspectorFlows(cwd)]);
@@ -161579,7 +162081,9 @@ async function runAgentRepl(lines, rich, deps, metaprojectPort, sessionOpts, ini
161579
162081
  return settled;
161580
162082
  }
161581
162083
  const completion = new Promise((resolve) => {
162084
+ completionWaiters = [];
161582
162085
  completionWaiters.push(() => resolve({ kind: "completion" }));
162086
+ rich.onCompletionWaitersSize?.(completionWaiters.length);
161583
162087
  });
161584
162088
  const winner = await Promise.race([pendingLine, completion]);
161585
162089
  if (winner.kind !== "completion") {
@@ -162194,6 +162698,10 @@ ${GUTTER}${turnSeparator()}
162194
162698
  await runBusSlashCommand(bus, rest, (text) => agentIo.onSystem?.(text));
162195
162699
  } else if (command === "/help") {
162196
162700
  agentIo.onSystem?.(readlineAgentHelpText());
162701
+ } else if (command === "/doctor") {
162702
+ const report = await buildDoctorReport(sessionCwd);
162703
+ agentIo.onSystem?.(`${formatDoctorReport2(report)}
162704
+ `);
162197
162705
  } else if (isSessionInfoCommand(command)) {
162198
162706
  const cwd = sessionCwd;
162199
162707
  const [workspaces, flows] = await Promise.all([
@@ -162240,6 +162748,8 @@ ${GUTTER}${turnSeparator()}
162240
162748
  continue;
162241
162749
  }
162242
162750
  await closeSlateSession(slateSession, mintTimestampAttemptId);
162751
+ lastToolOutput = undefined;
162752
+ lastToolName = undefined;
162243
162753
  if (nextLive !== undefined) {
162244
162754
  live = nextLive;
162245
162755
  history = [];
@@ -165738,7 +166248,7 @@ init_fs();
165738
166248
  init_prompt();
165739
166249
  import { readFile as readFile163 } from "fs/promises";
165740
166250
  import { stdin } from "process";
165741
- import path317 from "path";
166251
+ import path318 from "path";
165742
166252
  init_ui();
165743
166253
  var MODULES = [
165744
166254
  { name: "gdgraph", flag: "--no-gdgraph", desc: "code graph, symbols, affected context", defaultEnabled: true },
@@ -165785,8 +166295,8 @@ async function modulesCommand(args = []) {
165785
166295
  return;
165786
166296
  }
165787
166297
  const wantsJson = args.includes("--json") && (sub === undefined || sub === "status" || sub === "list" || sub === "--json");
165788
- const metaprojectRoot = path317.join(process.cwd(), ".metaproject");
165789
- const manifestPath = path317.join(metaprojectRoot, "metaproject.json");
166298
+ const metaprojectRoot = path318.join(process.cwd(), ".metaproject");
166299
+ const manifestPath = path318.join(metaprojectRoot, "metaproject.json");
165790
166300
  if (!await pathExists(manifestPath)) {
165791
166301
  if (wantsJson) {
165792
166302
  console.log(JSON.stringify({ schemaVersion: 1, error: "not-initialized", modules: [] }, null, 2));
@@ -165909,7 +166419,7 @@ import { createHash as createHash68, randomBytes as randomBytes11, randomUUID as
165909
166419
  import {
165910
166420
  chmodSync as chmodSync5,
165911
166421
  closeSync as closeSync7,
165912
- existsSync as existsSync48,
166422
+ existsSync as existsSync49,
165913
166423
  fsyncSync as fsyncSync2,
165914
166424
  openSync as openSync8,
165915
166425
  renameSync as renameSync5,
@@ -165917,9 +166427,9 @@ import {
165917
166427
  unlinkSync as unlinkSync6,
165918
166428
  writeFileSync as writeFileSync10
165919
166429
  } from "fs";
165920
- import path318 from "path";
166430
+ import path319 from "path";
165921
166431
  function serveCredentialPath(dir) {
165922
- return path318.join(keryxConfigDir(dir), "serve-credentials.json");
166432
+ return path319.join(keryxConfigDir(dir), "serve-credentials.json");
165923
166433
  }
165924
166434
  function constantTimeEqual(a, b) {
165925
166435
  const width = Math.max(a.length, b.length);
@@ -165953,7 +166463,7 @@ function isGroupOrOtherAccessible(file) {
165953
166463
  }
165954
166464
  function readServeCredential(dir) {
165955
166465
  const file = serveCredentialPath(dir);
165956
- if (!existsSync48(file)) {
166466
+ if (!existsSync49(file)) {
165957
166467
  return { status: "absent" };
165958
166468
  }
165959
166469
  if (isGroupOrOtherAccessible(file)) {
@@ -166152,23 +166662,23 @@ class AuthFailureThrottle {
166152
166662
  // src/lib/serve-turn-store.ts
166153
166663
  init_config_dir();
166154
166664
  import { createHash as createHash69 } from "crypto";
166155
- import { existsSync as existsSync49, readdirSync as readdirSync12, rmSync as rmSync4 } from "fs";
166156
- import path319 from "path";
166665
+ import { existsSync as existsSync50, readdirSync as readdirSync12, rmSync as rmSync4 } from "fs";
166666
+ import path320 from "path";
166157
166667
  var MAX_TURN_EVENTS = 1e4;
166158
166668
  function turnsRoot(dir) {
166159
- return path319.join(keryxConfigDir(dir), "turns");
166669
+ return path320.join(keryxConfigDir(dir), "turns");
166160
166670
  }
166161
166671
  function turnDir(turnId, dir) {
166162
- return path319.join(turnsRoot(dir), turnId);
166672
+ return path320.join(turnsRoot(dir), turnId);
166163
166673
  }
166164
166674
  function keyPath(project, idempotencyKey, dir) {
166165
166675
  const projectBytes = Buffer.byteLength(project, "utf8");
166166
166676
  const digest = createHash69("sha256").update(`${projectBytes}:${project}\x00${idempotencyKey}`, "utf8").digest("hex");
166167
- return path319.join(turnsRoot(dir), "keys", `${digest}.json`);
166677
+ return path320.join(turnsRoot(dir), "keys", `${digest}.json`);
166168
166678
  }
166169
166679
  function legacyKeyPath(idempotencyKey, dir) {
166170
166680
  const digest = createHash69("sha256").update(idempotencyKey, "utf8").digest("hex");
166171
- return path319.join(turnsRoot(dir), "keys", `${digest}.json`);
166681
+ return path320.join(turnsRoot(dir), "keys", `${digest}.json`);
166172
166682
  }
166173
166683
  function adoptLegacyClaim(project, idempotencyKey, dir) {
166174
166684
  const legacy = legacyKeyPath(idempotencyKey, dir);
@@ -166232,7 +166742,7 @@ function ensureTurnDir(turnId, dir) {
166232
166742
  }
166233
166743
  function createTurnRecord(record, dir) {
166234
166744
  ensureTurnDir(record.turnId, dir);
166235
- writeOwnerOnlyFile(path319.join(turnDir(record.turnId, dir), "turn.json"), `${JSON.stringify(record, null, 2)}
166745
+ writeOwnerOnlyFile(path320.join(turnDir(record.turnId, dir), "turn.json"), `${JSON.stringify(record, null, 2)}
166236
166746
  `);
166237
166747
  }
166238
166748
  function appendTurnEvent(event, dir, opts) {
@@ -166241,12 +166751,12 @@ function appendTurnEvent(event, dir, opts) {
166241
166751
  }
166242
166752
  const line = JSON.stringify(event);
166243
166753
  try {
166244
- appendOwnerOnlyLine(path319.join(turnDir(event.turnId, dir), "events.jsonl"), line);
166754
+ appendOwnerOnlyLine(path320.join(turnDir(event.turnId, dir), "events.jsonl"), line);
166245
166755
  } catch (error) {
166246
166756
  if (error?.code !== "ENOENT") {
166247
166757
  throw error;
166248
166758
  }
166249
- appendOwnerOnlyLine(path319.join(ensureTurnDir(event.turnId, dir), "events.jsonl"), line);
166759
+ appendOwnerOnlyLine(path320.join(ensureTurnDir(event.turnId, dir), "events.jsonl"), line);
166250
166760
  }
166251
166761
  return true;
166252
166762
  }
@@ -166254,7 +166764,7 @@ function readTurnEvents(turnId, after = -1, dir) {
166254
166764
  if (!isTurnId(turnId)) {
166255
166765
  return { ok: false, reason: "not-a-turn-id" };
166256
166766
  }
166257
- const read = readTurnFile(path319.join(turnDir(turnId, dir), "events.jsonl"));
166767
+ const read = readTurnFile(path320.join(turnDir(turnId, dir), "events.jsonl"));
166258
166768
  if (!read.ok) {
166259
166769
  if (isDefiniteAbsence2(read.reason)) {
166260
166770
  return { ok: true, value: [] };
@@ -166282,7 +166792,7 @@ function readTurnRecord(turnId, dir) {
166282
166792
  if (!isTurnId(turnId)) {
166283
166793
  return { ok: false, reason: "not-a-turn-id" };
166284
166794
  }
166285
- const read = readTurnFile(path319.join(turnDir(turnId, dir), "turn.json"));
166795
+ const read = readTurnFile(path320.join(turnDir(turnId, dir), "turn.json"));
166286
166796
  if (!read.ok) {
166287
166797
  return { ok: false, reason: read.reason };
166288
166798
  }
@@ -166301,7 +166811,7 @@ function finishTurn(turnId, result, dir) {
166301
166811
  if (!record.ok) {
166302
166812
  return false;
166303
166813
  }
166304
- writeOwnerOnlyFile(path319.join(turnDir(turnId, dir), "turn.json"), `${JSON.stringify({ ...record.value, result }, null, 2)}
166814
+ writeOwnerOnlyFile(path320.join(turnDir(turnId, dir), "turn.json"), `${JSON.stringify({ ...record.value, result }, null, 2)}
166305
166815
  `);
166306
166816
  return true;
166307
166817
  }
@@ -166348,7 +166858,7 @@ function releaseIdempotencyKey(project, idempotencyKey, turnId, dir) {
166348
166858
  // src/lib/serve-turn.ts
166349
166859
  import { randomUUID as randomUUID44 } from "crypto";
166350
166860
  init_hooks();
166351
- import path320 from "path";
166861
+ import path321 from "path";
166352
166862
  init_service4();
166353
166863
  init_service9();
166354
166864
  init_impact_evidence_hook_adapter();
@@ -166419,9 +166929,9 @@ function isUuid(value) {
166419
166929
  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);
166420
166930
  }
166421
166931
  function resolveProject(declared, dir) {
166422
- const wanted = path320.resolve(declared);
166932
+ const wanted = path321.resolve(declared);
166423
166933
  for (const entry of listProjects(dir, () => {})) {
166424
- if (path320.resolve(entry.path) === wanted) {
166934
+ if (path321.resolve(entry.path) === wanted) {
166425
166935
  return { ok: true, project: entry.path };
166426
166936
  }
166427
166937
  }
@@ -166494,7 +167004,7 @@ function buildRemoteHookRuntime(opts) {
166494
167004
  });
166495
167005
  emitRemoteHookNotices(opts.projectRoot, formatHookLoadNotices(loaded, {
166496
167006
  projectRoot: opts.projectRoot,
166497
- userFile: path320.join(homeDir, ".keryx", "hooks.json"),
167007
+ userFile: path321.join(homeDir, ".keryx", "hooks.json"),
166498
167008
  surface: "headless"
166499
167009
  }));
166500
167010
  if (!loaded.ok) {
@@ -167543,9 +168053,9 @@ function printHelp23() {
167543
168053
  // src/commands/update.ts
167544
168054
  init_contained_write();
167545
168055
  import { spawn as spawn9 } from "child_process";
167546
- import { readFile as readFile164, readdir as readdir58 } from "fs/promises";
167547
- import { access as access4, constants as constants5, existsSync as existsSync50 } from "fs";
167548
- import path321 from "path";
168056
+ import { readFile as readFile164, readdir as readdir59 } from "fs/promises";
168057
+ import { access as access4, constants as constants5, existsSync as existsSync51 } from "fs";
168058
+ import path322 from "path";
167549
168059
  import { fileURLToPath as fileURLToPath10 } from "url";
167550
168060
  init_catalog2();
167551
168061
  init_model_choice();
@@ -167560,17 +168070,17 @@ init_ui();
167560
168070
  init_install_plan();
167561
168071
  init_templates3();
167562
168072
  function containFromMetaprojectPath(filePath) {
167563
- const marker = `${path321.sep}.metaproject${path321.sep}`;
168073
+ const marker = `${path322.sep}.metaproject${path322.sep}`;
167564
168074
  const idx = filePath.lastIndexOf(marker);
167565
168075
  if (idx < 0) {
167566
- if (filePath.endsWith(`${path321.sep}.metaproject`)) {
167567
- const root = filePath.slice(0, filePath.length - ".metaproject".length - path321.sep.length);
168076
+ if (filePath.endsWith(`${path322.sep}.metaproject`)) {
168077
+ const root = filePath.slice(0, filePath.length - ".metaproject".length - path322.sep.length);
167568
168078
  return { root, rel: ".metaproject" };
167569
168079
  }
167570
168080
  throw new Error(`${filePath}: expected a path under a .metaproject directory`);
167571
168081
  }
167572
168082
  const root = filePath.slice(0, idx);
167573
- const rel = filePath.slice(idx + path321.sep.length).split(path321.sep).join("/");
168083
+ const rel = filePath.slice(idx + path322.sep.length).split(path322.sep).join("/");
167574
168084
  return { root, rel };
167575
168085
  }
167576
168086
  async function updateCommand(args = []) {
@@ -167580,8 +168090,8 @@ async function updateCommand(args = []) {
167580
168090
  return;
167581
168091
  }
167582
168092
  const projectRoot = process.cwd();
167583
- const metaprojectRoot = path321.join(projectRoot, ".metaproject");
167584
- banner("keryx update", `Refreshing the .metaproject workspace in ${path321.basename(projectRoot)}/`);
168093
+ const metaprojectRoot = path322.join(projectRoot, ".metaproject");
168094
+ banner("keryx update", `Refreshing the .metaproject workspace in ${path322.basename(projectRoot)}/`);
167585
168095
  if (!await pathExists(metaprojectRoot)) {
167586
168096
  console.log(` ${style.red(symbols.cross)} Metaproject is not initialized.`);
167587
168097
  console.log(` ${style.cyan(symbols.arrow)} Run ${style.cyan("keryx init")} first.`);
@@ -167657,7 +168167,7 @@ async function updateCommand(args = []) {
167657
168167
  nextSteps(steps);
167658
168168
  }
167659
168169
  async function previewServiceFiles(projectRoot, options) {
167660
- const metaprojectRoot = path321.join(projectRoot, ".metaproject");
168170
+ const metaprojectRoot = path322.join(projectRoot, ".metaproject");
167661
168171
  const manifestState = await readManifest5(metaprojectRoot);
167662
168172
  const manifest = manifestState.manifest;
167663
168173
  const enableTasks = moduleEnabled2(manifest, "tasks") || !options.noTasks;
@@ -167686,13 +168196,13 @@ async function previewServiceFiles(projectRoot, options) {
167686
168196
  });
167687
168197
  }
167688
168198
  async function refreshServiceFiles(projectRoot, options) {
167689
- const metaprojectRoot = path321.join(projectRoot, ".metaproject");
168199
+ const metaprojectRoot = path322.join(projectRoot, ".metaproject");
167690
168200
  const manifestState = await readManifest5(metaprojectRoot);
167691
168201
  const manifest = manifestState.manifest;
167692
168202
  const recoveredManifest = !manifestState.exists || !manifestState.valid;
167693
168203
  if (manifestState.migrated) {
167694
168204
  {
167695
- const { root, rel } = containFromMetaprojectPath(path321.join(metaprojectRoot, "metaproject.json"));
168205
+ const { root, rel } = containFromMetaprojectPath(path322.join(metaprojectRoot, "metaproject.json"));
167696
168206
  await writeContained(root, rel, `${JSON.stringify(manifest, null, 2)}
167697
168207
  `);
167698
168208
  }
@@ -167732,9 +168242,9 @@ async function refreshServiceFiles(projectRoot, options) {
167732
168242
  enableSecurity,
167733
168243
  enableSac
167734
168244
  });
167735
- await writeTextIfChanged4(path321.join(metaprojectRoot, "core", "README.md"), renderMetaprojectCoreReadme());
167736
- await writeTextIfChanged4(path321.join(metaprojectRoot, "hooks", "README.md"), renderHooksReadme());
167737
- await writeTextIfChanged4(path321.join(metaprojectRoot, "skills", "project-rules", "README.md"), renderProjectRulesSkillReadme({ sources: ruleSources }));
168245
+ await writeTextIfChanged4(path322.join(metaprojectRoot, "core", "README.md"), renderMetaprojectCoreReadme());
168246
+ await writeTextIfChanged4(path322.join(metaprojectRoot, "hooks", "README.md"), renderHooksReadme());
168247
+ await writeTextIfChanged4(path322.join(metaprojectRoot, "skills", "project-rules", "README.md"), renderProjectRulesSkillReadme({ sources: ruleSources }));
167738
168248
  await writeRoutingEntrypointPair(metaprojectRoot, {
167739
168249
  enableGdgraph,
167740
168250
  enableGdctx,
@@ -167758,7 +168268,7 @@ async function refreshServiceFiles(projectRoot, options) {
167758
168268
  }
167759
168269
  });
167760
168270
  console.log(` ${await describeModelChoiceStatus(projectRoot)}`);
167761
- await writeTextIfChanged4(path321.join(metaprojectRoot, "keryx-dashboard.html"), renderMetaprojectDashboardHtml({
168271
+ await writeTextIfChanged4(path322.join(metaprojectRoot, "keryx-dashboard.html"), renderMetaprojectDashboardHtml({
167762
168272
  enableGdgraph,
167763
168273
  enableGdctx,
167764
168274
  enableGdwiki,
@@ -167770,7 +168280,7 @@ async function refreshServiceFiles(projectRoot, options) {
167770
168280
  enableSecurity,
167771
168281
  data: dashboardData
167772
168282
  }));
167773
- await writeTextIfMissing4(path321.join(metaprojectRoot, "README.md"), renderMetaprojectReadme({
168283
+ await writeTextIfMissing4(path322.join(metaprojectRoot, "README.md"), renderMetaprojectReadme({
167774
168284
  enableGdgraph,
167775
168285
  enableGdctx,
167776
168286
  enableGdwiki,
@@ -167783,9 +168293,9 @@ async function refreshServiceFiles(projectRoot, options) {
167783
168293
  }));
167784
168294
  if (enableGdgraph) {
167785
168295
  await installGdgraphCoreScripts2(metaprojectRoot);
167786
- await writeTextIfChanged4(path321.join(metaprojectRoot, "modules", "gdgraph.md"), renderGdgraphManifest());
167787
- await writeTextIfChanged4(path321.join(metaprojectRoot, "core", "gdgraph", "README.md"), renderGdgraphCoreReadme());
167788
- await writeTextIfChanged4(path321.join(metaprojectRoot, "skills", "gdgraph", "SKILL.md"), renderGdgraphSkillReadme());
168296
+ await writeTextIfChanged4(path322.join(metaprojectRoot, "modules", "gdgraph.md"), renderGdgraphManifest());
168297
+ await writeTextIfChanged4(path322.join(metaprojectRoot, "core", "gdgraph", "README.md"), renderGdgraphCoreReadme());
168298
+ await writeTextIfChanged4(path322.join(metaprojectRoot, "skills", "gdgraph", "SKILL.md"), renderGdgraphSkillReadme());
167789
168299
  await seedAssetsLock(metaprojectRoot);
167790
168300
  if (manifest.modules?.gdgraph?.hooks?.gitPostCommit) {
167791
168301
  const warning = await installManagedHookOrWarn(projectRoot, "post-commit", "gdgraph-post-commit", renderGdgraphPostCommitHook());
@@ -167794,15 +168304,15 @@ async function refreshServiceFiles(projectRoot, options) {
167794
168304
  }
167795
168305
  }
167796
168306
  if (enableGdctx) {
167797
- await writeTextIfMissing4(path321.join(metaprojectRoot, "gdctx.config.json"), renderGdctxConfig());
167798
- await writeTextIfChanged4(path321.join(metaprojectRoot, "modules", "gdctx.md"), renderGdctxManifest());
167799
- await writeTextIfChanged4(path321.join(metaprojectRoot, "core", "gdctx", "README.md"), renderGdctxCoreReadme());
167800
- await writeTextIfChanged4(path321.join(metaprojectRoot, "skills", "gdctx", "SKILL.md"), renderGdctxSkillReadme());
168307
+ await writeTextIfMissing4(path322.join(metaprojectRoot, "gdctx.config.json"), renderGdctxConfig());
168308
+ await writeTextIfChanged4(path322.join(metaprojectRoot, "modules", "gdctx.md"), renderGdctxManifest());
168309
+ await writeTextIfChanged4(path322.join(metaprojectRoot, "core", "gdctx", "README.md"), renderGdctxCoreReadme());
168310
+ await writeTextIfChanged4(path322.join(metaprojectRoot, "skills", "gdctx", "SKILL.md"), renderGdctxSkillReadme());
167801
168311
  }
167802
168312
  if (enableGdwiki) {
167803
- await writeTextIfMissing4(path321.join(metaprojectRoot, "wiki", "templates", "page.md"), renderWikiPageTemplate());
167804
- await writeTextIfChanged4(path321.join(metaprojectRoot, "modules", "gdwiki.md"), renderGdwikiManifest());
167805
- await writeTextIfChanged4(path321.join(metaprojectRoot, "skills", "gdwiki", "SKILL.md"), renderGdwikiSkillReadme());
168313
+ await writeTextIfMissing4(path322.join(metaprojectRoot, "wiki", "templates", "page.md"), renderWikiPageTemplate());
168314
+ await writeTextIfChanged4(path322.join(metaprojectRoot, "modules", "gdwiki.md"), renderGdwikiManifest());
168315
+ await writeTextIfChanged4(path322.join(metaprojectRoot, "skills", "gdwiki", "SKILL.md"), renderGdwikiSkillReadme());
167806
168316
  if (manifest.modules?.gdgraph?.hooks?.gitPostCommit) {
167807
168317
  const warning = await installManagedHookOrWarn(projectRoot, "post-commit", "gdwiki-post-commit", renderGdwikiPostCommitHook());
167808
168318
  if (warning)
@@ -167810,8 +168320,8 @@ async function refreshServiceFiles(projectRoot, options) {
167810
168320
  }
167811
168321
  }
167812
168322
  if (enableSac) {
167813
- await writeTextIfMissing4(path321.join(metaprojectRoot, "modules", "sac.md"), renderSacManifest());
167814
- await writeTextIfChanged4(path321.join(metaprojectRoot, "skills", "sac", "SKILL.md"), renderSacSkillReadme());
168323
+ await writeTextIfMissing4(path322.join(metaprojectRoot, "modules", "sac.md"), renderSacManifest());
168324
+ await writeTextIfChanged4(path322.join(metaprojectRoot, "skills", "sac", "SKILL.md"), renderSacSkillReadme());
167815
168325
  }
167816
168326
  let gdskillsWarnings = [];
167817
168327
  let gdskillsNotices = [];
@@ -167826,10 +168336,10 @@ async function refreshServiceFiles(projectRoot, options) {
167826
168336
  }
167827
168337
  }
167828
168338
  if (enableHealth) {
167829
- await writeTextIfMissing4(path321.join(metaprojectRoot, "health.config.json"), renderHealthConfig());
167830
- await writeTextIfChanged4(path321.join(metaprojectRoot, "modules", "health.md"), renderHealthManifest());
167831
- await writeTextIfChanged4(path321.join(metaprojectRoot, "core", "health", "README.md"), renderHealthCoreReadme());
167832
- await writeTextIfChanged4(path321.join(metaprojectRoot, "skills", "health", "SKILL.md"), renderHealthSkillReadme());
168339
+ await writeTextIfMissing4(path322.join(metaprojectRoot, "health.config.json"), renderHealthConfig());
168340
+ await writeTextIfChanged4(path322.join(metaprojectRoot, "modules", "health.md"), renderHealthManifest());
168341
+ await writeTextIfChanged4(path322.join(metaprojectRoot, "core", "health", "README.md"), renderHealthCoreReadme());
168342
+ await writeTextIfChanged4(path322.join(metaprojectRoot, "skills", "health", "SKILL.md"), renderHealthSkillReadme());
167833
168343
  if (manifest.modules?.health?.hooks?.gitPostCommit) {
167834
168344
  const warning = await installManagedHookOrWarn(projectRoot, "post-commit", "health-post-commit", renderHealthPostCommitHook());
167835
168345
  if (warning)
@@ -167837,16 +168347,16 @@ async function refreshServiceFiles(projectRoot, options) {
167837
168347
  }
167838
168348
  }
167839
168349
  if (enableTesting) {
167840
- await writeTextIfMissing4(path321.join(metaprojectRoot, "testing.config.json"), renderTestingConfig({
168350
+ await writeTextIfMissing4(path322.join(metaprojectRoot, "testing.config.json"), renderTestingConfig({
167841
168351
  postCommitRefresh: Boolean(manifest.modules?.testing?.hooks?.gitPostCommit),
167842
168352
  prePushGate: Boolean(manifest.modules?.testing?.hooks?.prePush)
167843
168353
  }));
167844
- await writeTextIfChanged4(path321.join(metaprojectRoot, "modules", "testing.md"), renderTestingManifest());
167845
- await writeTextIfChanged4(path321.join(metaprojectRoot, "core", "testing", "README.md"), renderTestingCoreReadme());
167846
- await writeTextIfChanged4(path321.join(metaprojectRoot, "skills", "testing", "SKILL.md"), renderTestingSkillReadme());
168354
+ await writeTextIfChanged4(path322.join(metaprojectRoot, "modules", "testing.md"), renderTestingManifest());
168355
+ await writeTextIfChanged4(path322.join(metaprojectRoot, "core", "testing", "README.md"), renderTestingCoreReadme());
168356
+ await writeTextIfChanged4(path322.join(metaprojectRoot, "skills", "testing", "SKILL.md"), renderTestingSkillReadme());
167847
168357
  if (enableGdwiki) {
167848
- await writeTextIfMissing4(path321.join(metaprojectRoot, "wiki", "testing", "README.md"), renderTestingWikiReadme());
167849
- await writeTextIfMissing4(path321.join(metaprojectRoot, "wiki", "testing", "conventions.md"), renderTestingWikiConventions());
168358
+ await writeTextIfMissing4(path322.join(metaprojectRoot, "wiki", "testing", "README.md"), renderTestingWikiReadme());
168359
+ await writeTextIfMissing4(path322.join(metaprojectRoot, "wiki", "testing", "conventions.md"), renderTestingWikiConventions());
167850
168360
  }
167851
168361
  if (manifest.modules?.testing?.hooks?.gitPostCommit) {
167852
168362
  const warning = await installManagedHookOrWarn(projectRoot, "post-commit", "testing-post-commit", renderTestingPostCommitHook());
@@ -167865,24 +168375,24 @@ async function refreshServiceFiles(projectRoot, options) {
167865
168375
  hookWarnings.push(warning);
167866
168376
  }
167867
168377
  if (enableMemory) {
167868
- await writeTextIfMissing4(path321.join(metaprojectRoot, "memory.config.json"), renderMemoryConfig());
167869
- await writeTextIfMissing4(path321.join(metaprojectRoot, "memory", "templates", "entry.md"), renderMemoryEntryTemplate());
167870
- await writeTextIfChanged4(path321.join(metaprojectRoot, "modules", "memory.md"), renderMemoryManifest());
167871
- await writeTextIfChanged4(path321.join(metaprojectRoot, "core", "memory", "README.md"), renderMemoryCoreReadme());
167872
- await writeTextIfChanged4(path321.join(metaprojectRoot, "skills", "memory", "SKILL.md"), renderMemorySkillReadme());
168378
+ await writeTextIfMissing4(path322.join(metaprojectRoot, "memory.config.json"), renderMemoryConfig());
168379
+ await writeTextIfMissing4(path322.join(metaprojectRoot, "memory", "templates", "entry.md"), renderMemoryEntryTemplate());
168380
+ await writeTextIfChanged4(path322.join(metaprojectRoot, "modules", "memory.md"), renderMemoryManifest());
168381
+ await writeTextIfChanged4(path322.join(metaprojectRoot, "core", "memory", "README.md"), renderMemoryCoreReadme());
168382
+ await writeTextIfChanged4(path322.join(metaprojectRoot, "skills", "memory", "SKILL.md"), renderMemorySkillReadme());
167873
168383
  }
167874
168384
  if (enableTasks) {
167875
- await writeTextIfChanged4(path321.join(metaprojectRoot, "flows", "README.md"), renderFlowsReadme());
167876
- await writeTextIfChanged4(path321.join(metaprojectRoot, "modules", "tasks.md"), renderTasksManifest());
167877
- await writeTextIfChanged4(path321.join(metaprojectRoot, "skills", "flow", "SKILL.md"), renderFlowSkillRouter());
167878
- await writeTextIfChanged4(path321.join(metaprojectRoot, "skills", "flow", "init.md"), renderFlowInitSkill());
167879
- await writeTextIfChanged4(path321.join(metaprojectRoot, "skills", "flow", "manage.md"), renderFlowManageSkill());
167880
- await writeTextIfChanged4(path321.join(metaprojectRoot, "skills", "flow", "complete.md"), renderFlowCompleteSkill());
168385
+ await writeTextIfChanged4(path322.join(metaprojectRoot, "flows", "README.md"), renderFlowsReadme());
168386
+ await writeTextIfChanged4(path322.join(metaprojectRoot, "modules", "tasks.md"), renderTasksManifest());
168387
+ await writeTextIfChanged4(path322.join(metaprojectRoot, "skills", "flow", "SKILL.md"), renderFlowSkillRouter());
168388
+ await writeTextIfChanged4(path322.join(metaprojectRoot, "skills", "flow", "init.md"), renderFlowInitSkill());
168389
+ await writeTextIfChanged4(path322.join(metaprojectRoot, "skills", "flow", "manage.md"), renderFlowManageSkill());
168390
+ await writeTextIfChanged4(path322.join(metaprojectRoot, "skills", "flow", "complete.md"), renderFlowCompleteSkill());
167881
168391
  }
167882
168392
  if (enableSecurity) {
167883
- await writeTextIfMissing4(path321.join(metaprojectRoot, "security.config.json"), renderSecurityConfig());
167884
- await writeTextIfChanged4(path321.join(metaprojectRoot, "modules", "security.md"), renderSecurityManifest());
167885
- await writeTextIfChanged4(path321.join(metaprojectRoot, "core", "security", "README.md"), renderSecurityCoreReadme());
168393
+ await writeTextIfMissing4(path322.join(metaprojectRoot, "security.config.json"), renderSecurityConfig());
168394
+ await writeTextIfChanged4(path322.join(metaprojectRoot, "modules", "security.md"), renderSecurityManifest());
168395
+ await writeTextIfChanged4(path322.join(metaprojectRoot, "core", "security", "README.md"), renderSecurityCoreReadme());
167886
168396
  if (manifest.modules?.security?.hooks?.prePush) {
167887
168397
  const warning = await installManagedHookOrWarn(projectRoot, "pre-push", "security-pre-push", renderSecurityPrePushHook());
167888
168398
  if (warning)
@@ -167940,13 +168450,13 @@ async function refreshServiceFiles(projectRoot, options) {
167940
168450
  };
167941
168451
  }
167942
168452
  async function buildDashboard(projectRoot = process.cwd()) {
167943
- const metaprojectRoot = path321.join(projectRoot, ".metaproject");
168453
+ const metaprojectRoot = path322.join(projectRoot, ".metaproject");
167944
168454
  if (!await pathExists(metaprojectRoot)) {
167945
168455
  throw new Error("Metaproject is not initialized. Run: keryx init");
167946
168456
  }
167947
168457
  const manifest = (await readManifest5(metaprojectRoot)).manifest;
167948
168458
  const data = await collectDashboardData(metaprojectRoot);
167949
- const dashboardPath = path321.join(metaprojectRoot, "keryx-dashboard.html");
168459
+ const dashboardPath = path322.join(metaprojectRoot, "keryx-dashboard.html");
167950
168460
  await writeTextIfChanged4(dashboardPath, renderMetaprojectDashboardHtml({
167951
168461
  enableGdgraph: moduleEnabled2(manifest, "gdgraph"),
167952
168462
  enableGdctx: moduleEnabled2(manifest, "gdctx"),
@@ -167966,7 +168476,7 @@ async function shouldInstallDashboardPostCommitHook(projectRoot, manifest) {
167966
168476
  if (Object.values(modules).some((module) => Boolean(module.hooks?.gitPostCommit))) {
167967
168477
  return true;
167968
168478
  }
167969
- const hookPath = path321.join(projectRoot, ".git", "hooks", "post-commit");
168479
+ const hookPath = path322.join(projectRoot, ".git", "hooks", "post-commit");
167970
168480
  if (!await pathExists(hookPath)) {
167971
168481
  return false;
167972
168482
  }
@@ -167986,11 +168496,11 @@ async function collectDashboardData(metaprojectRoot) {
167986
168496
  if (testing) {
167987
168497
  data.testing = testing;
167988
168498
  }
167989
- const wiki = await collectMarkdownPages(path321.join(metaprojectRoot, "wiki"), "wiki");
168499
+ const wiki = await collectMarkdownPages(path322.join(metaprojectRoot, "wiki"), "wiki");
167990
168500
  if (wiki.length > 0) {
167991
168501
  data.wiki = { pages: wiki };
167992
168502
  }
167993
- const memory = await collectMarkdownPages(path321.join(metaprojectRoot, "memory"), "memory");
168503
+ const memory = await collectMarkdownPages(path322.join(metaprojectRoot, "memory"), "memory");
167994
168504
  if (memory.length > 0) {
167995
168505
  data.memory = { entries: memory };
167996
168506
  }
@@ -168005,19 +168515,19 @@ async function collectDashboardData(metaprojectRoot) {
168005
168515
  return data;
168006
168516
  }
168007
168517
  async function collectTasksDashboardData(metaprojectRoot) {
168008
- const flowsRoot = path321.join(metaprojectRoot, "flows");
168518
+ const flowsRoot = path322.join(metaprojectRoot, "flows");
168009
168519
  if (!await pathExists(flowsRoot)) {
168010
168520
  return null;
168011
168521
  }
168012
168522
  let dirEntries;
168013
168523
  try {
168014
- dirEntries = (await readdir58(flowsRoot, { withFileTypes: true })).filter((entry) => entry.isDirectory() && /^\d{3}-/.test(entry.name)).map((entry) => entry.name).sort();
168524
+ dirEntries = (await readdir59(flowsRoot, { withFileTypes: true })).filter((entry) => entry.isDirectory() && /^\d{3}-/.test(entry.name)).map((entry) => entry.name).sort();
168015
168525
  } catch {
168016
168526
  return null;
168017
168527
  }
168018
168528
  const flows = [];
168019
168529
  for (const dir of dirEntries) {
168020
- const flowPath = path321.join(flowsRoot, dir, "flow.json");
168530
+ const flowPath = path322.join(flowsRoot, dir, "flow.json");
168021
168531
  if (!await pathExists(flowPath)) {
168022
168532
  continue;
168023
168533
  }
@@ -168025,7 +168535,7 @@ async function collectTasksDashboardData(metaprojectRoot) {
168025
168535
  const flow = JSON.parse(await readFile164(flowPath, "utf8"));
168026
168536
  const tasks = Array.isArray(flow.tasks) ? flow.tasks : [];
168027
168537
  let acTotal = 0;
168028
- const acPath = path321.join(flowsRoot, dir, "acceptance-criteria.md");
168538
+ const acPath = path322.join(flowsRoot, dir, "acceptance-criteria.md");
168029
168539
  if (await pathExists(acPath)) {
168030
168540
  const acContent = await readFile164(acPath, "utf8");
168031
168541
  acTotal = (acContent.match(/^- AC\d+:/gm) ?? []).length;
@@ -168079,7 +168589,7 @@ async function collectDashboardDocs(metaprojectRoot, wiki, memory) {
168079
168589
  "data/testing/context.md"
168080
168590
  ];
168081
168591
  for (const href of staticHrefs) {
168082
- const filePath = path321.join(metaprojectRoot, ...href.split("/"));
168592
+ const filePath = path322.join(metaprojectRoot, ...href.split("/"));
168083
168593
  if (!await pathExists(filePath)) {
168084
168594
  continue;
168085
168595
  }
@@ -168096,7 +168606,7 @@ async function collectDashboardDocs(metaprojectRoot, wiki, memory) {
168096
168606
  return docs;
168097
168607
  }
168098
168608
  async function collectHealthDashboardData(metaprojectRoot) {
168099
- const reportPath = path321.join(metaprojectRoot, "data", "health", "artifacts", "latest.json");
168609
+ const reportPath = path322.join(metaprojectRoot, "data", "health", "artifacts", "latest.json");
168100
168610
  if (!await pathExists(reportPath)) {
168101
168611
  return;
168102
168612
  }
@@ -168205,8 +168715,8 @@ function metricToScope(metric) {
168205
168715
  };
168206
168716
  }
168207
168717
  async function collectGraphDashboardData(metaprojectRoot) {
168208
- const nodesPath = path321.join(metaprojectRoot, "data", "gdgraph", "storage", "nodes.jsonl");
168209
- const edgesPath = path321.join(metaprojectRoot, "data", "gdgraph", "storage", "edges.jsonl");
168718
+ const nodesPath = path322.join(metaprojectRoot, "data", "gdgraph", "storage", "nodes.jsonl");
168719
+ const edgesPath = path322.join(metaprojectRoot, "data", "gdgraph", "storage", "edges.jsonl");
168210
168720
  if (!await pathExists(nodesPath) || !await pathExists(edgesPath)) {
168211
168721
  return;
168212
168722
  }
@@ -168257,8 +168767,8 @@ async function collectGraphDashboardData(metaprojectRoot) {
168257
168767
  };
168258
168768
  }
168259
168769
  async function collectTestingDashboardData(metaprojectRoot) {
168260
- const reportPath = path321.join(metaprojectRoot, "data", "testing", "artifacts", "latest.json");
168261
- const contextPath = path321.join(metaprojectRoot, "data", "testing", "context.md");
168770
+ const reportPath = path322.join(metaprojectRoot, "data", "testing", "artifacts", "latest.json");
168771
+ const contextPath = path322.join(metaprojectRoot, "data", "testing", "context.md");
168262
168772
  if (await pathExists(reportPath)) {
168263
168773
  const report = JSON.parse(await readFile164(reportPath, "utf8"));
168264
168774
  const totalTests = numberOrUndefined(report.total);
@@ -168287,7 +168797,7 @@ async function collectMarkdownPages(root, hrefPrefix) {
168287
168797
  const files = await listMarkdownFiles(root);
168288
168798
  const pages = [];
168289
168799
  for (const filePath of files.slice(0, 40)) {
168290
- const relativePath = path321.relative(root, filePath).split(path321.sep).join("/");
168800
+ const relativePath = path322.relative(root, filePath).split(path322.sep).join("/");
168291
168801
  if (relativePath === "index.md" || relativePath.startsWith("templates/")) {
168292
168802
  continue;
168293
168803
  }
@@ -168305,10 +168815,10 @@ async function collectMarkdownPages(root, hrefPrefix) {
168305
168815
  return pages;
168306
168816
  }
168307
168817
  async function listMarkdownFiles(root) {
168308
- const entries = await readdir58(root, { withFileTypes: true });
168818
+ const entries = await readdir59(root, { withFileTypes: true });
168309
168819
  const files = [];
168310
168820
  for (const entry of entries) {
168311
- const fullPath = path321.join(root, entry.name);
168821
+ const fullPath = path322.join(root, entry.name);
168312
168822
  if (entry.isDirectory()) {
168313
168823
  files.push(...await listMarkdownFiles(fullPath));
168314
168824
  } else if (entry.isFile() && entry.name.endsWith(".md")) {
@@ -168356,7 +168866,7 @@ async function writeRecoveredManifest(metaprojectRoot, modules) {
168356
168866
  const manifest = {
168357
168867
  schemaVersion: 1,
168358
168868
  standardVersion: STANDARD_VERSION,
168359
- name: `${path321.basename(path321.dirname(metaprojectRoot))}-metaproject`,
168869
+ name: `${path322.basename(path322.dirname(metaprojectRoot))}-metaproject`,
168360
168870
  createdBy: "keryx",
168361
168871
  profiles: computeProfiles(enabledModuleKeys),
168362
168872
  paths: {
@@ -168447,13 +168957,13 @@ async function writeRecoveredManifest(metaprojectRoot, modules) {
168447
168957
  }
168448
168958
  };
168449
168959
  {
168450
- const { root, rel } = containFromMetaprojectPath(path321.join(metaprojectRoot, "metaproject.json"));
168960
+ const { root, rel } = containFromMetaprojectPath(path322.join(metaprojectRoot, "metaproject.json"));
168451
168961
  await writeContained(root, rel, `${JSON.stringify(manifest, null, 2)}
168452
168962
  `);
168453
168963
  }
168454
168964
  }
168455
168965
  async function enableTasksInManifest(metaprojectRoot) {
168456
- const manifestPath = path321.join(metaprojectRoot, "metaproject.json");
168966
+ const manifestPath = path322.join(metaprojectRoot, "metaproject.json");
168457
168967
  if (!await pathExists(manifestPath)) {
168458
168968
  return;
168459
168969
  }
@@ -168479,7 +168989,7 @@ async function enableTasksInManifest(metaprojectRoot) {
168479
168989
  }
168480
168990
  }
168481
168991
  async function updateManifestAgentEntrypoints(metaprojectRoot, ruleSources) {
168482
- const manifestPath = path321.join(metaprojectRoot, "metaproject.json");
168992
+ const manifestPath = path322.join(metaprojectRoot, "metaproject.json");
168483
168993
  if (!await pathExists(manifestPath)) {
168484
168994
  return;
168485
168995
  }
@@ -168547,61 +169057,61 @@ async function updateRuntime(projectRoot) {
168547
169057
  }
168548
169058
  }
168549
169059
  async function findRuntimeRoot(projectRoot) {
168550
- const projectRuntime = path321.join(projectRoot, ".metaproject", "runtime", "keryx");
168551
- if (await pathExists(path321.join(projectRuntime, ".git"))) {
169060
+ const projectRuntime = path322.join(projectRoot, ".metaproject", "runtime", "keryx");
169061
+ if (await pathExists(path322.join(projectRuntime, ".git"))) {
168552
169062
  return projectRuntime;
168553
169063
  }
168554
169064
  const home = process.env.HOME;
168555
169065
  if (!home) {
168556
169066
  return null;
168557
169067
  }
168558
- const globalRuntime = path321.join(home, ".keryx", "keryx");
168559
- if (await pathExists(path321.join(globalRuntime, ".git"))) {
169068
+ const globalRuntime = path322.join(home, ".keryx", "keryx");
169069
+ if (await pathExists(path322.join(globalRuntime, ".git"))) {
168560
169070
  return globalRuntime;
168561
169071
  }
168562
169072
  return null;
168563
169073
  }
168564
169074
  async function createServiceDirs(metaprojectRoot, modules) {
168565
169075
  const dirs = [
168566
- path321.join(metaprojectRoot, "core"),
168567
- path321.join(metaprojectRoot, "hooks", "post-update.d"),
168568
- path321.join(metaprojectRoot, "modules"),
168569
- path321.join(metaprojectRoot, "rules"),
168570
- path321.join(metaprojectRoot, "skills", "project-rules"),
169076
+ path322.join(metaprojectRoot, "core"),
169077
+ path322.join(metaprojectRoot, "hooks", "post-update.d"),
169078
+ path322.join(metaprojectRoot, "modules"),
169079
+ path322.join(metaprojectRoot, "rules"),
169080
+ path322.join(metaprojectRoot, "skills", "project-rules"),
168571
169081
  ...modules.enableGdgraph ? [
168572
- path321.join(metaprojectRoot, "core", "gdgraph"),
168573
- path321.join(metaprojectRoot, "skills", "gdgraph")
169082
+ path322.join(metaprojectRoot, "core", "gdgraph"),
169083
+ path322.join(metaprojectRoot, "skills", "gdgraph")
168574
169084
  ] : [],
168575
169085
  ...modules.enableGdctx ? [
168576
- path321.join(metaprojectRoot, "core", "gdctx"),
168577
- path321.join(metaprojectRoot, "skills", "gdctx")
169086
+ path322.join(metaprojectRoot, "core", "gdctx"),
169087
+ path322.join(metaprojectRoot, "skills", "gdctx")
168578
169088
  ] : [],
168579
169089
  ...modules.enableGdwiki ? [
168580
- path321.join(metaprojectRoot, "skills", "gdwiki"),
168581
- path321.join(metaprojectRoot, "wiki", "templates")
169090
+ path322.join(metaprojectRoot, "skills", "gdwiki"),
169091
+ path322.join(metaprojectRoot, "wiki", "templates")
168582
169092
  ] : [],
168583
169093
  ...modules.enableHealth ? [
168584
- path321.join(metaprojectRoot, "core", "health"),
168585
- path321.join(metaprojectRoot, "skills", "health")
169094
+ path322.join(metaprojectRoot, "core", "health"),
169095
+ path322.join(metaprojectRoot, "skills", "health")
168586
169096
  ] : [],
168587
169097
  ...modules.enableTesting ? [
168588
- path321.join(metaprojectRoot, "core", "testing"),
168589
- path321.join(metaprojectRoot, "skills", "testing")
169098
+ path322.join(metaprojectRoot, "core", "testing"),
169099
+ path322.join(metaprojectRoot, "skills", "testing")
168590
169100
  ] : [],
168591
169101
  ...modules.enableMemory ? [
168592
- path321.join(metaprojectRoot, "core", "memory"),
168593
- path321.join(metaprojectRoot, "skills", "memory"),
168594
- path321.join(metaprojectRoot, "memory", "templates")
169102
+ path322.join(metaprojectRoot, "core", "memory"),
169103
+ path322.join(metaprojectRoot, "skills", "memory"),
169104
+ path322.join(metaprojectRoot, "memory", "templates")
168595
169105
  ] : [],
168596
169106
  ...modules.enableTasks ? [
168597
- path321.join(metaprojectRoot, "flows"),
168598
- path321.join(metaprojectRoot, "skills", "flow")
169107
+ path322.join(metaprojectRoot, "flows"),
169108
+ path322.join(metaprojectRoot, "skills", "flow")
168599
169109
  ] : [],
168600
169110
  ...modules.enableSecurity ? [
168601
- path321.join(metaprojectRoot, "core", "security")
169111
+ path322.join(metaprojectRoot, "core", "security")
168602
169112
  ] : [],
168603
169113
  ...modules.enableSac ? [
168604
- path321.join(metaprojectRoot, "skills", "sac")
169114
+ path322.join(metaprojectRoot, "skills", "sac")
168605
169115
  ] : []
168606
169116
  ];
168607
169117
  await Promise.all(dirs.map((dir) => {
@@ -168610,22 +169120,22 @@ async function createServiceDirs(metaprojectRoot, modules) {
168610
169120
  }));
168611
169121
  }
168612
169122
  async function installGdgraphCoreScripts2(metaprojectRoot) {
168613
- const gdgraphCoreRoot = path321.join(metaprojectRoot, "core", "gdgraph");
169123
+ const gdgraphCoreRoot = path322.join(metaprojectRoot, "core", "gdgraph");
168614
169124
  {
168615
169125
  const { root, rel } = containFromMetaprojectPath(gdgraphCoreRoot);
168616
169126
  await mkdirContained(root, rel);
168617
169127
  }
168618
169128
  for (const file of GDGRAPH_CORE_SOURCES) {
168619
- await copyFileIfChanged2(runtimeSourcePath2(`../gdgraph/${file}`), path321.join(gdgraphCoreRoot, file));
169129
+ await copyFileIfChanged2(runtimeSourcePath2(`../gdgraph/${file}`), path322.join(gdgraphCoreRoot, file));
168620
169130
  }
168621
- await writeTextIfChanged4(path321.join(gdgraphCoreRoot, "cli.ts"), renderGdgraphCoreCli());
169131
+ await writeTextIfChanged4(path322.join(gdgraphCoreRoot, "cli.ts"), renderGdgraphCoreCli());
168622
169132
  }
168623
169133
  async function prePushHasSecurityBlock2(projectRoot) {
168624
169134
  const hooksRoot = await resolveGitHooksRoot(projectRoot);
168625
169135
  if (!hooksRoot) {
168626
169136
  return false;
168627
169137
  }
168628
- const hookPath = path321.join(hooksRoot, "pre-push");
169138
+ const hookPath = path322.join(hooksRoot, "pre-push");
168629
169139
  if (!await pathExists(hookPath)) {
168630
169140
  return false;
168631
169141
  }
@@ -168640,7 +169150,7 @@ async function agentSettingsHasSecuritySentinel2(projectRoot) {
168640
169150
  return (await readFile164(file, "utf8")).includes(AGENT_HOOKS_SENTINEL);
168641
169151
  }
168642
169152
  async function readManifest5(metaprojectRoot) {
168643
- const manifestPath = path321.join(metaprojectRoot, "metaproject.json");
169153
+ const manifestPath = path322.join(metaprojectRoot, "metaproject.json");
168644
169154
  if (!await pathExists(manifestPath)) {
168645
169155
  return {
168646
169156
  exists: false,
@@ -168709,7 +169219,7 @@ async function inferManifestFromExistingMetaproject(metaprojectRoot) {
168709
169219
  }
168710
169220
  async function anyPathExists(root, candidates) {
168711
169221
  for (const candidate of candidates) {
168712
- if (await pathExists(path321.join(root, candidate))) {
169222
+ if (await pathExists(path322.join(root, candidate))) {
168713
169223
  return true;
168714
169224
  }
168715
169225
  }
@@ -168732,13 +169242,13 @@ function parseUpdateArgs(args) {
168732
169242
  };
168733
169243
  }
168734
169244
  async function runPostUpdateHooks(projectRoot) {
168735
- const hooksDir = path321.join(projectRoot, ".metaproject", "hooks", "post-update.d");
169245
+ const hooksDir = path322.join(projectRoot, ".metaproject", "hooks", "post-update.d");
168736
169246
  if (!await pathExists(hooksDir)) {
168737
169247
  return;
168738
169248
  }
168739
- const entries = (await readdir58(hooksDir)).sort();
169249
+ const entries = (await readdir59(hooksDir)).sort();
168740
169250
  for (const entry of entries) {
168741
- const hookPath = path321.join(hooksDir, entry);
169251
+ const hookPath = path322.join(hooksDir, entry);
168742
169252
  try {
168743
169253
  await accessExecutable(hookPath);
168744
169254
  } catch {
@@ -168799,12 +169309,12 @@ async function copyFileIfChanged2(from, to) {
168799
169309
  }
168800
169310
  function runtimeSourcePath2(relativePath) {
168801
169311
  const directPath = fileURLToPath10(new URL(relativePath, import.meta.url));
168802
- if (existsSync50(directPath)) {
169312
+ if (existsSync51(directPath)) {
168803
169313
  return directPath;
168804
169314
  }
168805
169315
  if (relativePath.startsWith("../")) {
168806
- const packagedSourcePath = path321.join(path321.dirname(fileURLToPath10(import.meta.url)), "..", "src", relativePath.slice(3));
168807
- if (existsSync50(packagedSourcePath)) {
169316
+ const packagedSourcePath = path322.join(path322.dirname(fileURLToPath10(import.meta.url)), "..", "src", relativePath.slice(3));
169317
+ if (existsSync51(packagedSourcePath)) {
168808
169318
  return packagedSourcePath;
168809
169319
  }
168810
169320
  }
@@ -168835,7 +169345,7 @@ function printHelp24() {
168835
169345
 
168836
169346
  // src/commands/dashboard.ts
168837
169347
  import { spawn as spawn10 } from "child_process";
168838
- import path322 from "path";
169348
+ import path323 from "path";
168839
169349
  init_args();
168840
169350
  init_ui();
168841
169351
  async function dashboardCommand(args = []) {
@@ -168847,7 +169357,7 @@ async function dashboardCommand(args = []) {
168847
169357
  }
168848
169358
  if (subcommand === "build") {
168849
169359
  const result = await buildDashboard();
168850
- const rel = path322.relative(process.cwd(), result.path);
169360
+ const rel = path323.relative(process.cwd(), result.path);
168851
169361
  console.log(` ${style.green(symbols.ok)} Dashboard built ${style.cyan(symbols.arrow)} ${style.cyan(rel)}`);
168852
169362
  note(`Open it: keryx dashboard open`);
168853
169363
  return;
@@ -168855,7 +169365,7 @@ async function dashboardCommand(args = []) {
168855
169365
  if (subcommand === "open") {
168856
169366
  const result = await buildDashboard();
168857
169367
  await openFile(result.path);
168858
- const rel = path322.relative(process.cwd(), result.path);
169368
+ const rel = path323.relative(process.cwd(), result.path);
168859
169369
  console.log(` ${style.green(symbols.ok)} Opened ${style.cyan(rel)}`);
168860
169370
  return;
168861
169371
  }
@@ -168906,7 +169416,7 @@ import { readFileSync as readFileSync27 } from "fs";
168906
169416
  import { randomUUID as randomUUID46 } from "crypto";
168907
169417
  import { mkdir as mkdir69 } from "fs/promises";
168908
169418
  import { tmpdir as tmpdir12 } from "os";
168909
- import path323 from "path";
169419
+ import path324 from "path";
168910
169420
  import { createInterface as createInterface3 } from "readline";
168911
169421
 
168912
169422
  // src/harness/external-agent-probe.ts
@@ -169168,7 +169678,7 @@ async function runCommand4(args, deps, log) {
169168
169678
  const requestApproval = unattended ? undefined : seams.requestApproval ?? terminalApprover();
169169
169679
  const timeoutRaw = optionValue(args, "--timeout");
169170
169680
  const timeoutMs = timeoutRaw !== undefined && Number.isInteger(Number(timeoutRaw)) && Number(timeoutRaw) > 0 ? Number(timeoutRaw) : gate.config.defaultTimeoutMs;
169171
- const worktreesDir = path323.join(tmpdir12(), "keryx-external-worktrees");
169681
+ const worktreesDir = path324.join(tmpdir12(), "keryx-external-worktrees");
169172
169682
  const worktree = seams.worktree ?? createGitWorktreePort({ repoRoot: cwd, worktreesDir });
169173
169683
  if (seams.worktree === undefined)
169174
169684
  await mkdir69(worktreesDir, { recursive: true });
@@ -169256,8 +169766,8 @@ init_service8();
169256
169766
  init_bundled_eval();
169257
169767
  init_args();
169258
169768
  init_ui();
169259
- import { existsSync as existsSync51, lstatSync as lstatSync7, readFileSync as readFileSync26, writeFileSync as writeFileSync11 } from "fs";
169260
- import path324 from "path";
169769
+ import { existsSync as existsSync52, lstatSync as lstatSync7, readFileSync as readFileSync26, writeFileSync as writeFileSync11 } from "fs";
169770
+ import path325 from "path";
169261
169771
  function resolveDeps(deps) {
169262
169772
  return {
169263
169773
  cwd: deps.cwd ?? process.cwd(),
@@ -169590,11 +170100,11 @@ function generateCommand(args, depsIn) {
169590
170100
  process.exitCode = 1;
169591
170101
  return;
169592
170102
  }
169593
- const bundledAgentsRoot = path324.join(depsIn.bundledRoot ?? defaultBundledRoot(), "agents");
169594
- const stacksRoot = path324.join(bundledAgentsRoot, "..", "stacks");
169595
- const packDir = path324.join(stacksRoot, stackId);
169596
- const packJsonPath = path324.join(packDir, "pack.json");
169597
- if (!existsSync51(packJsonPath)) {
170103
+ const bundledAgentsRoot = path325.join(depsIn.bundledRoot ?? defaultBundledRoot(), "agents");
170104
+ const stacksRoot = path325.join(bundledAgentsRoot, "..", "stacks");
170105
+ const packDir = path325.join(stacksRoot, stackId);
170106
+ const packJsonPath = path325.join(packDir, "pack.json");
170107
+ if (!existsSync52(packJsonPath)) {
169598
170108
  error(`No stack pack "${stackId}" found at ${packJsonPath}.`);
169599
170109
  process.exitCode = 1;
169600
170110
  return;
@@ -169640,9 +170150,9 @@ function generateCommand(args, depsIn) {
169640
170150
  }
169641
170151
  const prepared = [];
169642
170152
  for (const file of generatedFiles) {
169643
- const filePath = path324.join(bundledAgentsRoot, file.fileName);
169644
- const relative = path324.relative(bundledAgentsRoot, filePath);
169645
- if (relative.startsWith("..") || path324.isAbsolute(relative)) {
170153
+ const filePath = path325.join(bundledAgentsRoot, file.fileName);
170154
+ const relative = path325.relative(bundledAgentsRoot, filePath);
170155
+ if (relative.startsWith("..") || path325.isAbsolute(relative)) {
169646
170156
  error(`Refusing to write "${file.fileName}": it resolves outside the bundled agents directory.`);
169647
170157
  process.exitCode = 1;
169648
170158
  return;
@@ -169963,7 +170473,7 @@ function printBootstrapHelp() {
169963
170473
  // src/commands/metrics.ts
169964
170474
  init_args();
169965
170475
  import { readFile as readFile165 } from "fs/promises";
169966
- import path326 from "path";
170476
+ import path327 from "path";
169967
170477
 
169968
170478
  // src/metrics/benchmark.ts
169969
170479
  var RELIABILITIES2 = new Set(["exact", "estimated", "unknown"]);
@@ -170780,7 +171290,7 @@ function buildContainmentManifest(inputs, options = {}) {
170780
171290
 
170781
171291
  // src/metrics/oracle-runner.ts
170782
171292
  import { mkdir as mkdir70, writeFile as writeFile48 } from "fs/promises";
170783
- import path325 from "path";
171293
+ import path326 from "path";
170784
171294
 
170785
171295
  // src/metrics/ir.ts
170786
171296
  function toIdSet(ids) {
@@ -171226,9 +171736,9 @@ function buildEvidenceBundle(input, options = {}) {
171226
171736
  async function persistEvidenceBundle(outDir, bundle, ladder = "metastore") {
171227
171737
  const safeTarget = bundle.target.replace(/[^A-Za-z0-9._/-]/g, "_");
171228
171738
  const safeCase = bundle.caseId.replace(/[^A-Za-z0-9._-]/g, "_");
171229
- const dir = path325.join(outDir, "bench", ladder, safeTarget, safeCase, bundle.variant, String(bundle.seed));
171739
+ const dir = path326.join(outDir, "bench", ladder, safeTarget, safeCase, bundle.variant, String(bundle.seed));
171230
171740
  await mkdir70(dir, { recursive: true });
171231
- const write = (name, value) => writeFile48(path325.join(dir, name), `${JSON.stringify(value, null, 2)}
171741
+ const write = (name, value) => writeFile48(path326.join(dir, name), `${JSON.stringify(value, null, 2)}
171232
171742
  `, "utf8");
171233
171743
  await Promise.all([
171234
171744
  write("inputs.json", bundle.inputs),
@@ -171268,7 +171778,7 @@ async function metricsCommand(args = [], projectRoot = process.cwd()) {
171268
171778
  console.log("# metrics status");
171269
171779
  console.log("");
171270
171780
  console.log(`root: ${root}`);
171271
- console.log(`enabled: ${await Bun.file(path326.join(projectRoot, ".metaproject", "metaproject.json")).exists() ? "yes" : "no"}`);
171781
+ console.log(`enabled: ${await Bun.file(path327.join(projectRoot, ".metaproject", "metaproject.json")).exists() ? "yes" : "no"}`);
171272
171782
  const latest = await readLatestPointer(root);
171273
171783
  console.log(`latest: ${latest.status}`);
171274
171784
  return;
@@ -171280,7 +171790,7 @@ async function metricsCommand(args = [], projectRoot = process.cwd()) {
171280
171790
  process.exitCode = 1;
171281
171791
  return;
171282
171792
  }
171283
- const record = JSON.parse(await readFile165(path326.resolve(projectRoot, file), "utf8"));
171793
+ const record = JSON.parse(await readFile165(path327.resolve(projectRoot, file), "utf8"));
171284
171794
  const result = validateRunRecord(record);
171285
171795
  console.log(result.valid ? "valid: yes" : "valid: no");
171286
171796
  for (const error of result.errors)
@@ -171305,7 +171815,7 @@ async function metricsCommand(args = [], projectRoot = process.cwd()) {
171305
171815
  process.exitCode = 1;
171306
171816
  return;
171307
171817
  }
171308
- const file = path326.join(metricsRoot(projectRoot), "runs", `${runId}.json`);
171818
+ const file = path327.join(metricsRoot(projectRoot), "runs", `${runId}.json`);
171309
171819
  if (!await Bun.file(file).exists()) {
171310
171820
  console.error(`Run not found: ${runId}`);
171311
171821
  process.exitCode = 1;
@@ -171322,8 +171832,8 @@ async function metricsCommand(args = [], projectRoot = process.cwd()) {
171322
171832
  process.exitCode = 1;
171323
171833
  return;
171324
171834
  }
171325
- const a = JSON.parse(await readFile165(path326.join(metricsRoot(projectRoot), "runs", `${runA}.json`), "utf8"));
171326
- const b = JSON.parse(await readFile165(path326.join(metricsRoot(projectRoot), "runs", `${runB}.json`), "utf8"));
171835
+ const a = JSON.parse(await readFile165(path327.join(metricsRoot(projectRoot), "runs", `${runA}.json`), "utf8"));
171836
+ const b = JSON.parse(await readFile165(path327.join(metricsRoot(projectRoot), "runs", `${runB}.json`), "utf8"));
171327
171837
  const comparison = compareExecutionRuns(a, b);
171328
171838
  console.log(stableJson(comparison));
171329
171839
  return;
@@ -171357,8 +171867,8 @@ async function metricsCommand(args = [], projectRoot = process.cwd()) {
171357
171867
  return;
171358
171868
  }
171359
171869
  const template = createPairedBenchmarkTemplate(taskIds);
171360
- await Bun.write(path326.resolve(projectRoot, out), stableJson(template));
171361
- console.log(`manifest: ${path326.relative(projectRoot, path326.resolve(projectRoot, out))}`);
171870
+ await Bun.write(path327.resolve(projectRoot, out), stableJson(template));
171871
+ console.log(`manifest: ${path327.relative(projectRoot, path327.resolve(projectRoot, out))}`);
171362
171872
  return;
171363
171873
  }
171364
171874
  if (subcommand === "benchmark" && args[1] === "run") {
@@ -171372,7 +171882,7 @@ async function metricsCommand(args = [], projectRoot = process.cwd()) {
171372
171882
  process.exitCode = 1;
171373
171883
  return;
171374
171884
  }
171375
- const raw = JSON.parse(await readFile165(path326.resolve(projectRoot, file), "utf8"));
171885
+ const raw = JSON.parse(await readFile165(path327.resolve(projectRoot, file), "utf8"));
171376
171886
  const input = Array.isArray(raw) ? raw : raw.runs ?? [];
171377
171887
  const result = validatePairedBenchmark(input);
171378
171888
  console.log(stableJson(result));
@@ -171384,7 +171894,7 @@ async function metricsCommand(args = [], projectRoot = process.cwd()) {
171384
171894
  process.exitCode = 1;
171385
171895
  }
171386
171896
  async function loadAffectedSets(projectRoot, file) {
171387
- const raw = JSON.parse(await readFile165(path326.resolve(projectRoot, file), "utf8"));
171897
+ const raw = JSON.parse(await readFile165(path327.resolve(projectRoot, file), "utf8"));
171388
171898
  const map = new Map;
171389
171899
  for (const entry of raw.targets ?? []) {
171390
171900
  if (typeof entry.target === "string")
@@ -171455,7 +171965,7 @@ async function runHarnessLayer(projectRoot, args, ladder) {
171455
171965
  let tasks;
171456
171966
  let model;
171457
171967
  try {
171458
- const raw = JSON.parse(await readFile165(path326.resolve(projectRoot, resultsPath), "utf8"));
171968
+ const raw = JSON.parse(await readFile165(path327.resolve(projectRoot, resultsPath), "utf8"));
171459
171969
  tasks = raw.tasks ?? [];
171460
171970
  model = raw.model;
171461
171971
  } catch (error) {
@@ -171486,7 +171996,7 @@ async function runSafetyCompletionHonestyLayer(projectRoot, args, ladder) {
171486
171996
  let cases;
171487
171997
  let model;
171488
171998
  try {
171489
- const raw = JSON.parse(await readFile165(path326.resolve(projectRoot, resultsPath), "utf8"));
171999
+ const raw = JSON.parse(await readFile165(path327.resolve(projectRoot, resultsPath), "utf8"));
171490
172000
  cases = raw.cases ?? [];
171491
172001
  model = raw.model;
171492
172002
  } catch (error) {
@@ -171511,7 +172021,7 @@ async function runSafetyFalsePremiseLayer(projectRoot, args, ladder) {
171511
172021
  let cases;
171512
172022
  let model;
171513
172023
  try {
171514
- const raw = JSON.parse(await readFile165(path326.resolve(projectRoot, resultsPath), "utf8"));
172024
+ const raw = JSON.parse(await readFile165(path327.resolve(projectRoot, resultsPath), "utf8"));
171515
172025
  cases = raw.cases ?? [];
171516
172026
  model = raw.model;
171517
172027
  } catch (error) {
@@ -171536,7 +172046,7 @@ async function runSafetyContainmentLayer(projectRoot, args, ladder, caseClass) {
171536
172046
  let cases;
171537
172047
  let model;
171538
172048
  try {
171539
- const raw = JSON.parse(await readFile165(path326.resolve(projectRoot, resultsPath), "utf8"));
172049
+ const raw = JSON.parse(await readFile165(path327.resolve(projectRoot, resultsPath), "utf8"));
171540
172050
  cases = raw.cases ?? [];
171541
172051
  model = raw.model;
171542
172052
  } catch (error) {
@@ -171598,12 +172108,12 @@ async function runGdgraphLayer(projectRoot, args, ladder) {
171598
172108
  }
171599
172109
  const manifests = buildOracleManifestsByGold(inputs, { ladder });
171600
172110
  if (outDir) {
171601
- const resolvedOut = path326.resolve(projectRoot, outDir);
172111
+ const resolvedOut = path327.resolve(projectRoot, outDir);
171602
172112
  for (const input of inputs) {
171603
172113
  for (const named of input.golds) {
171604
172114
  const bundle = buildEvidenceBundle({ target: input.target, system: input.system, gold: named.gold }, { ladder, goldReference: goldPathFor(named.kind), timestamp: new Date().toISOString() });
171605
- const dir = await persistEvidenceBundle(path326.join(resolvedOut, named.kind), bundle, ladder);
171606
- console.error(`bundle[${named.kind}]: ${path326.relative(projectRoot, dir)}`);
172115
+ const dir = await persistEvidenceBundle(path327.join(resolvedOut, named.kind), bundle, ladder);
172116
+ console.error(`bundle[${named.kind}]: ${path327.relative(projectRoot, dir)}`);
171607
172117
  }
171608
172118
  }
171609
172119
  }
@@ -171627,7 +172137,7 @@ async function runGdgraphLayer(projectRoot, args, ladder) {
171627
172137
  return allValid;
171628
172138
  }
171629
172139
  async function loadCoverageMap2(projectRoot, file) {
171630
- const raw = JSON.parse(await readFile165(path326.resolve(projectRoot, file), "utf8"));
172140
+ const raw = JSON.parse(await readFile165(path327.resolve(projectRoot, file), "utf8"));
171631
172141
  return raw.coverageMap ?? {};
171632
172142
  }
171633
172143
  async function runTestingLayer(projectRoot, args, ladder) {
@@ -171664,7 +172174,7 @@ async function runTestingLayer(projectRoot, args, ladder) {
171664
172174
  return result.valid;
171665
172175
  }
171666
172176
  async function loadMemoryGoldK(projectRoot, file) {
171667
- const raw = JSON.parse(await readFile165(path326.resolve(projectRoot, file), "utf8"));
172177
+ const raw = JSON.parse(await readFile165(path327.resolve(projectRoot, file), "utf8"));
171668
172178
  return typeof raw.k === "number" && raw.k > 0 ? raw.k : 3;
171669
172179
  }
171670
172180
  async function runMemoryLayer(projectRoot, args, ladder) {
@@ -171701,11 +172211,11 @@ async function runMemoryLayer(projectRoot, args, ladder) {
171701
172211
  return result.valid;
171702
172212
  }
171703
172213
  async function loadWikiGoldK(projectRoot, file) {
171704
- const raw = JSON.parse(await readFile165(path326.resolve(projectRoot, file), "utf8"));
172214
+ const raw = JSON.parse(await readFile165(path327.resolve(projectRoot, file), "utf8"));
171705
172215
  return typeof raw.k === "number" && raw.k > 0 ? raw.k : 5;
171706
172216
  }
171707
172217
  async function loadWikiGroundedness(projectRoot, file) {
171708
- const raw = JSON.parse(await readFile165(path326.resolve(projectRoot, file), "utf8"));
172218
+ const raw = JSON.parse(await readFile165(path327.resolve(projectRoot, file), "utf8"));
171709
172219
  const map = new Map;
171710
172220
  for (const entry of raw.targets ?? []) {
171711
172221
  if (typeof entry.target !== "string" || !Array.isArray(entry.scores) || entry.scores.length !== 3)
@@ -171761,7 +172271,7 @@ async function runWikiLayer(projectRoot, args, ladder) {
171761
172271
  return result.valid;
171762
172272
  }
171763
172273
  async function loadGdctxFacts(projectRoot, file) {
171764
- const raw = JSON.parse(await readFile165(path326.resolve(projectRoot, file), "utf8"));
172274
+ const raw = JSON.parse(await readFile165(path327.resolve(projectRoot, file), "utf8"));
171765
172275
  const inputs = [];
171766
172276
  for (const entry of raw.inputs ?? []) {
171767
172277
  if (typeof entry.input === "string") {
@@ -171799,7 +172309,7 @@ async function collect(projectRoot, args) {
171799
172309
  process.exitCode = 1;
171800
172310
  return;
171801
172311
  }
171802
- const raw = JSON.parse(await readFile165(path326.resolve(projectRoot, eventFile), "utf8"));
172312
+ const raw = JSON.parse(await readFile165(path327.resolve(projectRoot, eventFile), "utf8"));
171803
172313
  const events = Array.isArray(raw) ? raw : raw.events;
171804
172314
  const startedAt = optionValue(args, "--started-at") ?? events[0]?.timestamp_utc ?? new Date().toISOString();
171805
172315
  const finishedAt = optionValue(args, "--finished-at") ?? events.at(-1)?.timestamp_utc ?? startedAt;
@@ -171815,11 +172325,11 @@ async function collect(projectRoot, args) {
171815
172325
  parentRunId: optionValue(args, "--parent-run-id") ?? null
171816
172326
  });
171817
172327
  const result = await writeRunArtifacts(metricsRoot(projectRoot), record, { cwd: projectRoot });
171818
- console.log(`json: ${path326.relative(projectRoot, result.jsonPath)}`);
171819
- console.log(`markdown: ${path326.relative(projectRoot, result.markdownPath)}`);
172328
+ console.log(`json: ${path327.relative(projectRoot, result.jsonPath)}`);
172329
+ console.log(`markdown: ${path327.relative(projectRoot, result.markdownPath)}`);
171820
172330
  }
171821
172331
  function metricsRoot(projectRoot) {
171822
- return path326.join(projectRoot, ".metaproject", "data", "metrics");
172332
+ return path327.join(projectRoot, ".metaproject", "data", "metrics");
171823
172333
  }
171824
172334
  function printMetricsHelp() {
171825
172335
  console.log(`keryx metrics
@@ -173236,8 +173746,8 @@ init_contained_write();
173236
173746
  init_prompt();
173237
173747
  init_terminal_safe();
173238
173748
  init_agent_hooks();
173239
- import { existsSync as existsSync52, readFileSync as readFileSync28 } from "fs";
173240
- import path327 from "path";
173749
+ import { existsSync as existsSync53, readFileSync as readFileSync28 } from "fs";
173750
+ import path328 from "path";
173241
173751
  init_hooks();
173242
173752
  init_package();
173243
173753
  var CLI_VERSION = package_default.version;
@@ -173253,10 +173763,10 @@ function optConfigDir(deps) {
173253
173763
  return deps.configDir !== undefined ? { configDir: deps.configDir } : {};
173254
173764
  }
173255
173765
  function userHooksPath(homeDir) {
173256
- return path327.join(homeDir, ".keryx", "hooks.json");
173766
+ return path328.join(homeDir, ".keryx", "hooks.json");
173257
173767
  }
173258
173768
  function projectHooksPath(projectRoot) {
173259
- return path327.join(projectRoot, ".metaproject", "hooks.json");
173769
+ return path328.join(projectRoot, ".metaproject", "hooks.json");
173260
173770
  }
173261
173771
  function fail3(message) {
173262
173772
  console.error(message);
@@ -173377,10 +173887,10 @@ async function runList6(args, deps) {
173377
173887
  function argv0Resolves(argv0) {
173378
173888
  if (argv0 === "keryx")
173379
173889
  return true;
173380
- if (path327.isAbsolute(argv0))
173381
- return existsSync52(argv0);
173890
+ if (path328.isAbsolute(argv0))
173891
+ return existsSync53(argv0);
173382
173892
  const pathEnv = process.env.PATH ?? "";
173383
- return pathEnv.split(path327.delimiter).filter((dir) => dir.length > 0).some((dir) => existsSync52(path327.join(dir, argv0)));
173893
+ return pathEnv.split(path328.delimiter).filter((dir) => dir.length > 0).some((dir) => existsSync53(path328.join(dir, argv0)));
173384
173894
  }
173385
173895
  function checkArgvResolutions(registrations) {
173386
173896
  const seen = new Set;
@@ -173835,8 +174345,8 @@ async function writeDocAtomic(filePath, doc, scope, cwd, homeDir) {
173835
174345
  if (scope === "project") {
173836
174346
  await writeContained(cwd, PROJECT_HOOKS_REL, body);
173837
174347
  } else {
173838
- const userConfigDir = path327.join(homeDir, ".keryx");
173839
- if (!existsSync52(userConfigDir)) {
174348
+ const userConfigDir = path328.join(homeDir, ".keryx");
174349
+ if (!existsSync53(userConfigDir)) {
173840
174350
  await mkdirContained(homeDir, ".keryx");
173841
174351
  }
173842
174352
  await writeContained(userConfigDir, "hooks.json", body);
@@ -174093,7 +174603,7 @@ init_ui();
174093
174603
  init_args();
174094
174604
  init_contained_path();
174095
174605
  init_package();
174096
- import path336 from "path";
174606
+ import path337 from "path";
174097
174607
 
174098
174608
  // src/bundle/types.ts
174099
174609
  var BUNDLE_FORMAT_VERSION = "1.0.0";
@@ -174147,13 +174657,13 @@ init_contained_path();
174147
174657
  init_keryx_home();
174148
174658
  init_types3();
174149
174659
  import { lstat as lstat18, readFile as readFile166 } from "fs/promises";
174150
- import path328 from "path";
174660
+ import path329 from "path";
174151
174661
  var MEMORY_FOLDERS = new Set(MEMORY_TYPES.map((entry) => entry.folder));
174152
174662
  function scopeRoot(scope, ctx) {
174153
174663
  if (scope === "user") {
174154
174664
  return userStoreRoot(ctx.env ?? process.env, ctx.homeDir);
174155
174665
  }
174156
- return path328.join(ctx.projectRoot, ".metaproject");
174666
+ return path329.join(ctx.projectRoot, ".metaproject");
174157
174667
  }
174158
174668
  var CONTROL_CHAR_RE = /[\x00-\x1f\x7f\u2028\u2029]/;
174159
174669
  var MARKER_FORGING_RE = /[<>`]|<!--|-->/;
@@ -174342,10 +174852,10 @@ async function targetFor(entry, targetScope, ctx) {
174342
174852
  return { ok: true, absolutePath: containment.absolutePath };
174343
174853
  }
174344
174854
  async function resolveContainedPathForWrite(root, relPath) {
174345
- const absolute = path328.resolve(root, relPath);
174346
- const rootResolved = path328.resolve(root);
174347
- const relative = path328.relative(rootResolved, absolute);
174348
- if (relative === "" || relative === ".." || relative.startsWith(`..${path328.sep}`) || path328.isAbsolute(relative)) {
174855
+ const absolute = path329.resolve(root, relPath);
174856
+ const rootResolved = path329.resolve(root);
174857
+ const relative = path329.relative(rootResolved, absolute);
174858
+ if (relative === "" || relative === ".." || relative.startsWith(`..${path329.sep}`) || path329.isAbsolute(relative)) {
174349
174859
  return {
174350
174860
  ok: false,
174351
174861
  refusal: { reason: BUNDLE_REFUSAL.pathEscape, path: relPath, message: `${relPath} resolves outside the scope root` }
@@ -174354,7 +174864,7 @@ async function resolveContainedPathForWrite(root, relPath) {
174354
174864
  return { ok: true, absolutePath: absolute };
174355
174865
  }
174356
174866
  async function refuseSymlinkChain(root, relPath) {
174357
- const rootResolved = path328.resolve(root);
174867
+ const rootResolved = path329.resolve(root);
174358
174868
  try {
174359
174869
  const rootStat = await lstat18(rootResolved);
174360
174870
  if (rootStat.isSymbolicLink()) {
@@ -174367,7 +174877,7 @@ async function refuseSymlinkChain(root, relPath) {
174367
174877
  const segments = relPath.split("/");
174368
174878
  let current = rootResolved;
174369
174879
  for (const segment of segments) {
174370
- current = path328.join(current, segment);
174880
+ current = path329.join(current, segment);
174371
174881
  try {
174372
174882
  const stat = await lstat18(current);
174373
174883
  if (stat.isSymbolicLink()) {
@@ -174395,8 +174905,8 @@ async function readTargetFile(absolutePath) {
174395
174905
  }
174396
174906
  }
174397
174907
  // src/bundle/archive.ts
174398
- import { readdir as readdir59, readFile as readFile167, lstat as lstat19 } from "fs/promises";
174399
- import path329 from "path";
174908
+ import { readdir as readdir60, readFile as readFile167, lstat as lstat19 } from "fs/promises";
174909
+ import path330 from "path";
174400
174910
  import { gzipSync, gunzipSync } from "zlib";
174401
174911
  var BLOCK = 512;
174402
174912
  var BUNDLE_MANIFEST_NAME = "bundle.json";
@@ -174487,11 +174997,11 @@ function buildBundleArchive(files, manifestBytes) {
174487
174997
  const dirs = new Set;
174488
174998
  const sortedPaths = [...files.keys()].sort();
174489
174999
  for (const p of sortedPaths) {
174490
- let dir = path329.dirname(p);
175000
+ let dir = path330.dirname(p);
174491
175001
  const segs = [];
174492
175002
  while (dir !== "." && dir !== "/" && dir !== "") {
174493
175003
  segs.unshift(dir);
174494
- dir = path329.dirname(dir);
175004
+ dir = path330.dirname(dir);
174495
175005
  }
174496
175006
  for (const d of segs)
174497
175007
  dirs.add(d);
@@ -174559,7 +175069,7 @@ function parseUstar(tar, limits) {
174559
175069
  const name = prefix.length > 0 ? `${prefix}/${nameRaw}` : nameRaw;
174560
175070
  const typeflag = String.fromCharCode(header[156] ?? 0);
174561
175071
  const size = readOctal(header, 124, 12);
174562
- if (path329.isAbsolute(name) || name.split("/").includes("..") || name.length === 0) {
175072
+ if (path330.isAbsolute(name) || name.split("/").includes("..") || name.length === 0) {
174563
175073
  return { ok: false, refusal: { reason: BUNDLE_REFUSAL.archiveInvalid, path: name, message: `unsafe entry name: ${name}` } };
174564
175074
  }
174565
175075
  const isDir = typeflag === "5";
@@ -174621,9 +175131,9 @@ async function openBundle(bundlePath, limits) {
174621
175131
  let entryCount = 0;
174622
175132
  let totalBytes = 0;
174623
175133
  async function walk(dir, relPrefix) {
174624
- const entries = await readdir59(dir, { withFileTypes: true });
175134
+ const entries = await readdir60(dir, { withFileTypes: true });
174625
175135
  for (const entry of entries.sort((a, b) => a.name.localeCompare(b.name))) {
174626
- const abs = path329.join(dir, entry.name);
175136
+ const abs = path330.join(dir, entry.name);
174627
175137
  const rel = relPrefix.length > 0 ? `${relPrefix}/${entry.name}` : entry.name;
174628
175138
  const entryStat = await lstat19(abs);
174629
175139
  if (entryStat.isSymbolicLink()) {
@@ -175010,9 +175520,9 @@ init_validator();
175010
175520
  init_frontmatter();
175011
175521
  init_types3();
175012
175522
  import { execFileSync as execFileSync3 } from "child_process";
175013
- import { mkdir as mkdir71, readdir as readdir60, readFile as readFile168, writeFile as writeFile50, lstat as lstat20 } from "fs/promises";
175014
- import { existsSync as existsSync53 } from "fs";
175015
- import path330 from "path";
175523
+ import { mkdir as mkdir71, readdir as readdir61, readFile as readFile168, writeFile as writeFile50, lstat as lstat20 } from "fs/promises";
175524
+ import { existsSync as existsSync54 } from "fs";
175525
+ import path331 from "path";
175016
175526
  // docs/requirements/keryx-agent-platform-expansion/schemas/hook-config.schema.json
175017
175527
  var hook_config_schema_default2 = {
175018
175528
  $schema: "https://json-schema.org/draft/2020-12/schema",
@@ -175556,12 +176066,12 @@ function contentDigestBundleId(scope, entries) {
175556
176066
  async function walkFiles2(dir, relPrefix, out, skipped) {
175557
176067
  let entries;
175558
176068
  try {
175559
- entries = await readdir60(dir, { withFileTypes: true });
176069
+ entries = await readdir61(dir, { withFileTypes: true });
175560
176070
  } catch {
175561
176071
  return;
175562
176072
  }
175563
176073
  for (const entry of entries.sort((a, b) => a.name.localeCompare(b.name))) {
175564
- const abs = path330.join(dir, entry.name);
176074
+ const abs = path331.join(dir, entry.name);
175565
176075
  const rel = relPrefix.length > 0 ? `${relPrefix}/${entry.name}` : entry.name;
175566
176076
  const st = await lstat20(abs);
175567
176077
  if (st.isSymbolicLink()) {
@@ -175578,8 +176088,8 @@ async function walkFiles2(dir, relPrefix, out, skipped) {
175578
176088
  async function collectSkills(root, scope, skipped) {
175579
176089
  const candidates = [];
175580
176090
  for (const baseDirName of scope === "project" ? ["skills", "project-skills"] : ["skills"]) {
175581
- const base = path330.join(root, baseDirName);
175582
- if (!existsSync53(base))
176091
+ const base = path331.join(root, baseDirName);
176092
+ if (!existsSync54(base))
175583
176093
  continue;
175584
176094
  const files = [];
175585
176095
  await walkFiles2(base, baseDirName, files, skipped);
@@ -175591,26 +176101,26 @@ async function collectSkills(root, scope, skipped) {
175591
176101
  async function collectRules(root, scope, skipped) {
175592
176102
  if (scope === "user")
175593
176103
  return [];
175594
- const base = path330.join(root, "rules");
175595
- if (!existsSync53(base))
176104
+ const base = path331.join(root, "rules");
176105
+ if (!existsSync54(base))
175596
176106
  return [];
175597
176107
  const files = [];
175598
176108
  await walkFiles2(base, "rules", files, skipped);
175599
176109
  return files.filter((f) => /\.(md|mdc)$/.test(f.path)).map((f) => ({ bundlePath: f.path, kind: "rule", absolutePath: f.abs }));
175600
176110
  }
175601
176111
  async function collectAgents(root, skipped) {
175602
- const base = path330.join(root, "agents");
175603
- if (!existsSync53(base))
176112
+ const base = path331.join(root, "agents");
176113
+ if (!existsSync54(base))
175604
176114
  return [];
175605
176115
  let entries;
175606
176116
  try {
175607
- entries = await readdir60(base, { withFileTypes: true });
176117
+ entries = await readdir61(base, { withFileTypes: true });
175608
176118
  } catch {
175609
176119
  return [];
175610
176120
  }
175611
176121
  const candidates = [];
175612
176122
  for (const entry of entries.sort((a, b) => a.name.localeCompare(b.name))) {
175613
- const abs = path330.join(base, entry.name);
176123
+ const abs = path331.join(base, entry.name);
175614
176124
  const st = await lstat20(abs);
175615
176125
  if (st.isSymbolicLink()) {
175616
176126
  skipped.push({ path: `agents/${entry.name}`, reason: "symlink" });
@@ -175625,17 +176135,17 @@ async function collectAgents(root, skipped) {
175625
176135
  async function collectMemory(root, skipped) {
175626
176136
  const candidates = [];
175627
176137
  for (const { folder } of MEMORY_TYPES) {
175628
- const base = path330.join(root, "memory", folder);
175629
- if (!existsSync53(base))
176138
+ const base = path331.join(root, "memory", folder);
176139
+ if (!existsSync54(base))
175630
176140
  continue;
175631
176141
  let entries;
175632
176142
  try {
175633
- entries = await readdir60(base, { withFileTypes: true });
176143
+ entries = await readdir61(base, { withFileTypes: true });
175634
176144
  } catch {
175635
176145
  continue;
175636
176146
  }
175637
176147
  for (const entry of entries.sort((a, b) => a.name.localeCompare(b.name))) {
175638
- const abs = path330.join(base, entry.name);
176148
+ const abs = path331.join(base, entry.name);
175639
176149
  const st = await lstat20(abs);
175640
176150
  if (st.isSymbolicLink()) {
175641
176151
  skipped.push({ path: `memory/${folder}/${entry.name}`, reason: "symlink" });
@@ -175649,8 +176159,8 @@ async function collectMemory(root, skipped) {
175649
176159
  return candidates;
175650
176160
  }
175651
176161
  async function collectHookConfig(root) {
175652
- const abs = path330.join(root, "hooks.json");
175653
- if (!existsSync53(abs))
176162
+ const abs = path331.join(root, "hooks.json");
176163
+ if (!existsSync54(abs))
175654
176164
  return [];
175655
176165
  const st = await lstat20(abs);
175656
176166
  if (st.isSymbolicLink() || !st.isFile())
@@ -175659,19 +176169,19 @@ async function collectHookConfig(root) {
175659
176169
  }
175660
176170
  async function collectLearnedPatterns(root, scope) {
175661
176171
  const relDir = scope === "user" ? "learning/patterns" : "data/learning/candidates";
175662
- const base = path330.join(root, ...relDir.split("/"));
175663
- if (!existsSync53(base))
176172
+ const base = path331.join(root, ...relDir.split("/"));
176173
+ if (!existsSync54(base))
175664
176174
  return [];
175665
176175
  let entries;
175666
176176
  try {
175667
- entries = await readdir60(base, { withFileTypes: true });
176177
+ entries = await readdir61(base, { withFileTypes: true });
175668
176178
  } catch {
175669
176179
  return [];
175670
176180
  }
175671
176181
  const candidates = [];
175672
176182
  for (const entry of entries.sort((a, b) => a.name.localeCompare(b.name))) {
175673
176183
  if (entry.isFile() && entry.name.endsWith(".json")) {
175674
- candidates.push({ bundlePath: `${relDir}/${entry.name}`, kind: "learned-pattern", absolutePath: path330.join(base, entry.name) });
176184
+ candidates.push({ bundlePath: `${relDir}/${entry.name}`, kind: "learned-pattern", absolutePath: path331.join(base, entry.name) });
175675
176185
  }
175676
176186
  }
175677
176187
  return candidates;
@@ -175704,7 +176214,7 @@ function rewriteAgentOrigin(content, sourceRef) {
175704
176214
  }
175705
176215
  async function outDirIsEmpty(out) {
175706
176216
  try {
175707
- const entries = await readdir60(out);
176217
+ const entries = await readdir61(out);
175708
176218
  return entries.length === 0;
175709
176219
  } catch {
175710
176220
  return true;
@@ -175712,10 +176222,10 @@ async function outDirIsEmpty(out) {
175712
176222
  }
175713
176223
  async function writeDirectoryBundle(out, manifestBytes, files) {
175714
176224
  await mkdir71(out, { recursive: true });
175715
- await writeFile50(path330.join(out, "bundle.json"), manifestBytes);
176225
+ await writeFile50(path331.join(out, "bundle.json"), manifestBytes);
175716
176226
  for (const [rel, bytes] of files) {
175717
- const abs = path330.join(out, ...rel.split("/"));
175718
- await mkdir71(path330.dirname(abs), { recursive: true });
176227
+ const abs = path331.join(out, ...rel.split("/"));
176228
+ await mkdir71(path331.dirname(abs), { recursive: true });
175719
176229
  await writeFile50(abs, bytes);
175720
176230
  }
175721
176231
  }
@@ -175843,16 +176353,16 @@ async function exportBundle(opts) {
175843
176353
  const manifestBytes = Buffer.from(serializeManifest(manifest), "utf8");
175844
176354
  const isArchive = opts.out.endsWith(".tar.gz") || opts.out.endsWith(".tgz");
175845
176355
  if (isArchive) {
175846
- if (existsSync53(opts.out)) {
176356
+ if (existsSync54(opts.out)) {
175847
176357
  return { ok: false, refusals: [{ reason: BUNDLE_REFUSAL.notABundle, message: `${opts.out} already exists` }] };
175848
176358
  }
175849
176359
  const archive = buildBundleArchive(files, manifestBytes);
175850
176360
  if (!archive.ok)
175851
176361
  return { ok: false, refusals: [archive.refusal] };
175852
- await mkdir71(path330.dirname(opts.out), { recursive: true });
176362
+ await mkdir71(path331.dirname(opts.out), { recursive: true });
175853
176363
  await writeFile50(opts.out, archive.value);
175854
176364
  } else {
175855
- if (existsSync53(opts.out) && !await outDirIsEmpty(opts.out)) {
176365
+ if (existsSync54(opts.out) && !await outDirIsEmpty(opts.out)) {
175856
176366
  return { ok: false, refusals: [{ reason: BUNDLE_REFUSAL.notABundle, message: `${opts.out} already exists and is not empty` }] };
175857
176367
  }
175858
176368
  await writeDirectoryBundle(opts.out, manifestBytes, files);
@@ -175899,16 +176409,16 @@ async function verifyBundlePath(bundlePath) {
175899
176409
  // src/bundle/applied-state.ts
175900
176410
  init_contained_write();
175901
176411
  import { readFile as readFile169 } from "fs/promises";
175902
- import path331 from "path";
176412
+ import path332 from "path";
175903
176413
  var APPLIED_STATE_SCHEMA_VERSION = 2;
175904
176414
  function emptyState() {
175905
176415
  return { schemaVersion: APPLIED_STATE_SCHEMA_VERSION, entries: {} };
175906
176416
  }
175907
176417
  function appliedStatePath(scope, ctx) {
175908
176418
  if (scope === "user") {
175909
- return path331.join(scopeRoot("user", ctx), "bundles", "applied-state.json");
176419
+ return path332.join(scopeRoot("user", ctx), "bundles", "applied-state.json");
175910
176420
  }
175911
- return path331.join(scopeRoot(scope, ctx), "data", "bundles", "applied-state.json");
176421
+ return path332.join(scopeRoot(scope, ctx), "data", "bundles", "applied-state.json");
175912
176422
  }
175913
176423
  function isPlainEntryShape(value) {
175914
176424
  if (typeof value !== "object" || value === null)
@@ -175990,14 +176500,14 @@ async function readAppliedState(ledgerPath) {
175990
176500
  return { ok: false, reason: "corrupt-ledger", message: `applied-state ledger at ${ledgerPath} has an unrecognized shape` };
175991
176501
  }
175992
176502
  function containedRootAndRel(ledgerPath) {
175993
- const marker = `${path331.sep}.metaproject${path331.sep}`;
176503
+ const marker = `${path332.sep}.metaproject${path332.sep}`;
175994
176504
  const idx = ledgerPath.indexOf(marker);
175995
176505
  if (idx >= 0) {
175996
- return { root: ledgerPath.slice(0, idx), rel: ledgerPath.slice(idx + path331.sep.length).split(path331.sep).join("/") };
176506
+ return { root: ledgerPath.slice(0, idx), rel: ledgerPath.slice(idx + path332.sep.length).split(path332.sep).join("/") };
175997
176507
  }
175998
- const bundlesDir = path331.dirname(ledgerPath);
175999
- const root = path331.dirname(bundlesDir);
176000
- const rel = path331.join(path331.basename(bundlesDir), path331.basename(ledgerPath)).split(path331.sep).join("/");
176508
+ const bundlesDir = path332.dirname(ledgerPath);
176509
+ const root = path332.dirname(bundlesDir);
176510
+ const rel = path332.join(path332.basename(bundlesDir), path332.basename(ledgerPath)).split(path332.sep).join("/");
176001
176511
  return { root, rel };
176002
176512
  }
176003
176513
  async function writeAppliedState(ledgerPath, state) {
@@ -176018,17 +176528,17 @@ init_service8();
176018
176528
  init_fs();
176019
176529
  init_symlink_safety();
176020
176530
  import { lstat as lstat21, mkdir as mkdir72, open as open8, readFile as readFile170 } from "fs/promises";
176021
- import path332 from "path";
176531
+ import path333 from "path";
176022
176532
  var PRIVATE_DIR_GITIGNORE = `# keryx:private-dir \u2014 managed by keryx; this directory stays out of version control
176023
176533
  *
176024
176534
  !.gitignore
176025
176535
  `;
176026
176536
  async function checkPrivateDirGitignore(dir, root) {
176027
176537
  if (root !== undefined) {
176028
- const rootResolved = path332.resolve(root);
176029
- const dirResolved = path332.resolve(dir);
176030
- const relative = path332.relative(rootResolved, dirResolved);
176031
- const relativePosix = relative.split(path332.sep).join("/");
176538
+ const rootResolved = path333.resolve(root);
176539
+ const dirResolved = path333.resolve(dir);
176540
+ const relative = path333.relative(rootResolved, dirResolved);
176541
+ const relativePosix = relative.split(path333.sep).join("/");
176032
176542
  const refusal = await refuseEscapingSymlink(rootResolved, relativePosix);
176033
176543
  if (refusal !== undefined) {
176034
176544
  return {
@@ -176058,7 +176568,7 @@ async function checkPrivateDirGitignore(dir, root) {
176058
176568
  message: `${dir} is a symlink; keryx will not manage a private directory reached through a symlink.`
176059
176569
  };
176060
176570
  }
176061
- const gitignorePath = path332.join(dir, ".gitignore");
176571
+ const gitignorePath = path333.join(dir, ".gitignore");
176062
176572
  let stats;
176063
176573
  try {
176064
176574
  stats = await lstat21(gitignorePath);
@@ -176110,7 +176620,7 @@ async function ensurePrivateDirGitignore(dir, root) {
176110
176620
  if (!check.ok || check.action === "present") {
176111
176621
  return check;
176112
176622
  }
176113
- const gitignorePath = path332.join(dir, ".gitignore");
176623
+ const gitignorePath = path333.join(dir, ".gitignore");
176114
176624
  await mkdir72(dir, { recursive: true });
176115
176625
  try {
176116
176626
  const handle = await open8(gitignorePath, "wx");
@@ -176507,7 +177017,7 @@ async function planBundleImport(opts) {
176507
177017
  init_service4();
176508
177018
  import { mkdtemp as mkdtemp6, mkdir as mkdir73, rm as rm20, writeFile as writeFile51 } from "fs/promises";
176509
177019
  import { tmpdir as tmpdir13 } from "os";
176510
- import path333 from "path";
177020
+ import path334 from "path";
176511
177021
  function entriesToStage(plan) {
176512
177022
  return plan.entries.filter((e) => e.bucket === "new" || e.bucket === "update" || e.bucket === "conflict" && e.forced);
176513
177023
  }
@@ -176517,12 +177027,12 @@ async function auditBundlePlan(plan, opts = {}) {
176517
177027
  return { ok: true, refusals: [], report: null };
176518
177028
  }
176519
177029
  const runAudit = opts.runAudit ?? runHarnessAudit;
176520
- const stagingDir = await mkdtemp6(path333.join(tmpdir13(), "keryx-bundle-audit-"));
177030
+ const stagingDir = await mkdtemp6(path334.join(tmpdir13(), "keryx-bundle-audit-"));
176521
177031
  try {
176522
177032
  try {
176523
177033
  for (const entry of toStage) {
176524
- const abs = path333.join(stagingDir, ...entry.path.split("/"));
176525
- await mkdir73(path333.dirname(abs), { recursive: true });
177034
+ const abs = path334.join(stagingDir, ...entry.path.split("/"));
177035
+ await mkdir73(path334.dirname(abs), { recursive: true });
176526
177036
  await writeFile51(abs, entry.bytes);
176527
177037
  }
176528
177038
  } catch (err) {
@@ -176553,7 +177063,7 @@ async function auditBundlePlan(plan, opts = {}) {
176553
177063
  // src/bundle/apply.ts
176554
177064
  init_contained_write();
176555
177065
  import { mkdir as mkdir74, rmdir as rmdir2, stat as stat33 } from "fs/promises";
176556
- import path334 from "path";
177066
+ import path335 from "path";
176557
177067
  init_keryx_home();
176558
177068
  init_checksum();
176559
177069
  class ApplyAbort extends Error {
@@ -176633,7 +177143,7 @@ async function applyBundlePlan(plan, auditResult, opts = {}) {
176633
177143
  throw new ApplyAbort({ ...previousFile.refusal, path: entry.path });
176634
177144
  }
176635
177145
  const previousBytes = previousFile.bytes ?? null;
176636
- const createdDirs = await mkdirRecordingCreated(path334.dirname(entry.targetPath));
177146
+ const createdDirs = await mkdirRecordingCreated(path335.dirname(entry.targetPath));
176637
177147
  for (const dir of [...createdDirs].reverse())
176638
177148
  rollback.push({ kind: "dir", absolutePath: dir });
176639
177149
  rollback.push({ kind: "file", root, rel: entry.targetRelative, previousBytes });
@@ -176716,14 +177226,14 @@ async function applyBundlePlan(plan, auditResult, opts = {}) {
176716
177226
  }
176717
177227
  async function mkdirRecordingCreated(dir) {
176718
177228
  const missing = [];
176719
- let current = path334.resolve(dir);
177229
+ let current = path335.resolve(dir);
176720
177230
  for (;; ) {
176721
177231
  try {
176722
177232
  await stat33(current);
176723
177233
  break;
176724
177234
  } catch {
176725
177235
  missing.push(current);
176726
- const parent = path334.dirname(current);
177236
+ const parent = path335.dirname(current);
176727
177237
  if (parent === current)
176728
177238
  break;
176729
177239
  current = parent;
@@ -176738,9 +177248,9 @@ function resolveLedgerPathForEntry(entry, opts) {
176738
177248
  if (entry.targetScope === "user") {
176739
177249
  return userStorePaths(opts.env ?? process.env, opts.homeDir).appliedState;
176740
177250
  }
176741
- const metaprojectIdx = entry.targetPath.indexOf(`${path334.sep}.metaproject${path334.sep}`);
176742
- const metaprojectRoot = metaprojectIdx === -1 ? path334.join(path334.dirname(entry.targetPath), ".metaproject") : entry.targetPath.slice(0, metaprojectIdx + `${path334.sep}.metaproject`.length);
176743
- return path334.join(metaprojectRoot, "data", "bundles", "applied-state.json");
177251
+ const metaprojectIdx = entry.targetPath.indexOf(`${path335.sep}.metaproject${path335.sep}`);
177252
+ const metaprojectRoot = metaprojectIdx === -1 ? path335.join(path335.dirname(entry.targetPath), ".metaproject") : entry.targetPath.slice(0, metaprojectIdx + `${path335.sep}.metaproject`.length);
177253
+ return path335.join(metaprojectRoot, "data", "bundles", "applied-state.json");
176744
177254
  }
176745
177255
  // src/bundle/inspect.ts
176746
177256
  init_matrix();
@@ -176792,28 +177302,28 @@ function defaultCapabilityLookup() {
176792
177302
  }
176793
177303
  // src/bundle/uninstall.ts
176794
177304
  init_contained_write();
176795
- import path335 from "path";
177305
+ import path336 from "path";
176796
177306
  init_checksum();
176797
177307
  var PROTECTED_KIND_ROOTS = new Set(["skills", "project-skills", "rules", "agents", "memory", "learning", "data/learning"]);
176798
177308
  function isContained(root, candidate) {
176799
- const rel = path335.relative(root, candidate);
176800
- return rel === "" || !rel.startsWith(`..${path335.sep}`) && rel !== ".." && !path335.isAbsolute(rel);
177309
+ const rel = path336.relative(root, candidate);
177310
+ return rel === "" || !rel.startsWith(`..${path336.sep}`) && rel !== ".." && !path336.isAbsolute(rel);
176801
177311
  }
176802
177312
  async function removeNowEmptyParents(root, relFilePath) {
176803
- let relDir = path335.posix.dirname(relFilePath);
177313
+ let relDir = path336.posix.dirname(relFilePath);
176804
177314
  while (relDir !== "." && relDir !== "/" && relDir !== "") {
176805
177315
  if (PROTECTED_KIND_ROOTS.has(relDir))
176806
177316
  break;
176807
177317
  const removed = await rmdirIfEmptyContained(root, relDir).catch(() => false);
176808
177318
  if (!removed)
176809
177319
  break;
176810
- relDir = path335.posix.dirname(relDir);
177320
+ relDir = path336.posix.dirname(relDir);
176811
177321
  }
176812
177322
  }
176813
177323
  async function uninstallBundle(opts) {
176814
177324
  const ctx = { projectRoot: opts.projectRoot, env: opts.env, homeDir: opts.homeDir };
176815
177325
  const root = scopeRoot(opts.targetScope, ctx);
176816
- const rootResolved = path335.resolve(root);
177326
+ const rootResolved = path336.resolve(root);
176817
177327
  const ledgerPath = appliedStatePath(opts.targetScope, ctx);
176818
177328
  const ledgerRead = await readAppliedState(ledgerPath);
176819
177329
  if (!ledgerRead.ok) {
@@ -176853,7 +177363,7 @@ async function uninstallBundle(opts) {
176853
177363
  if (!kindCheck.ok) {
176854
177364
  return { ok: false, refusals: [{ reason: BUNDLE_REFUSAL.corruptLedger, path: relPath, message: `applied-state ledger record "${relPath}" does not match its recorded kind "${record.kind}": ${kindCheck.refusal.message}` }], removed: [], kept: [], missing: [] };
176855
177365
  }
176856
- const absolute = path335.resolve(rootResolved, normalized.path);
177366
+ const absolute = path336.resolve(rootResolved, normalized.path);
176857
177367
  if (!isContained(rootResolved, absolute)) {
176858
177368
  return { ok: false, refusals: [{ reason: BUNDLE_REFUSAL.corruptLedger, path: relPath, message: `applied-state ledger record "${relPath}" resolves outside the scope root` }], removed: [], kept: [], missing: [] };
176859
177369
  }
@@ -177045,7 +177555,7 @@ async function handleExport(args, cwd) {
177045
177555
  const outcome = await exportBundle({
177046
177556
  projectRoot,
177047
177557
  scope: scopeArg,
177048
- out: path336.resolve(cwd, out),
177558
+ out: path337.resolve(cwd, out),
177049
177559
  include: include.length > 0 ? include : undefined,
177050
177560
  kinds: kindsRaw.length > 0 ? kindsRaw : undefined,
177051
177561
  bundleId,
@@ -177092,7 +177602,7 @@ async function handleImport(args, cwd) {
177092
177602
  }
177093
177603
  const projectRoot = resolveBundleProjectRoot(cwd);
177094
177604
  if (external) {
177095
- await handleImportExternal(path336.resolve(cwd, target), projectRoot, { dryRun, json });
177605
+ await handleImportExternal(path337.resolve(cwd, target), projectRoot, { dryRun, json });
177096
177606
  return;
177097
177607
  }
177098
177608
  const targetScopeArg = optionValue([...args], "--target-scope");
@@ -177119,7 +177629,7 @@ async function handleImport(args, cwd) {
177119
177629
  return;
177120
177630
  }
177121
177631
  }
177122
- const opened = await openBundle(path336.resolve(cwd, target));
177632
+ const opened = await openBundle(path337.resolve(cwd, target));
177123
177633
  if (!opened.ok) {
177124
177634
  printRefusals([opened.refusal], json);
177125
177635
  process.exitCode = 1;
@@ -177297,7 +177807,7 @@ async function handleInspect(args, cwd) {
177297
177807
  return;
177298
177808
  }
177299
177809
  const projectRoot = resolveBundleProjectRoot(cwd);
177300
- const result = await inspectBundle(path336.resolve(cwd, bundlePath), {
177810
+ const result = await inspectBundle(path337.resolve(cwd, bundlePath), {
177301
177811
  projectRoot,
177302
177812
  targetScope: targetScopeArg
177303
177813
  });
@@ -177341,7 +177851,7 @@ async function handleVerify(args, cwd) {
177341
177851
  failUsage("keryx bundle verify requires a <bundle> argument (or --external-imports)");
177342
177852
  return;
177343
177853
  }
177344
- const outcome = await verifyBundlePath(path336.resolve(cwd, bundlePath));
177854
+ const outcome = await verifyBundlePath(path337.resolve(cwd, bundlePath));
177345
177855
  if (!outcome.ok) {
177346
177856
  printRefusals(outcome.refusals, json);
177347
177857
  process.exitCode = 1;
@@ -177469,10 +177979,10 @@ function printUninstallHelp2() {
177469
177979
  }
177470
177980
 
177471
177981
  // src/commands/learn.ts
177472
- import { existsSync as existsSync54 } from "fs";
177982
+ import { existsSync as existsSync55 } from "fs";
177473
177983
  import { readFile as readFile171 } from "fs/promises";
177474
177984
  import { createInterface as createInterface4 } from "readline/promises";
177475
- import path337 from "path";
177985
+ import path338 from "path";
177476
177986
  init_contained_path();
177477
177987
  init_service9();
177478
177988
  var STDIN_DEADLINE_MS2 = 2000;
@@ -177495,16 +178005,16 @@ function resolveRoot2(deps) {
177495
178005
  return resolveProjectRoot(deps.cwd ?? process.cwd());
177496
178006
  }
177497
178007
  function resolveLearnRoot(startDir, env) {
177498
- const hasMetaproject = (dir) => existsSync54(path337.join(dir, ".metaproject"));
178008
+ const hasMetaproject = (dir) => existsSync55(path338.join(dir, ".metaproject"));
177499
178009
  const fromEnv = env.CLAUDE_PROJECT_DIR;
177500
178010
  if (typeof fromEnv === "string" && fromEnv.length > 0 && hasMetaproject(fromEnv)) {
177501
178011
  return fromEnv;
177502
178012
  }
177503
- let current = path337.resolve(startDir);
178013
+ let current = path338.resolve(startDir);
177504
178014
  for (;; ) {
177505
178015
  if (hasMetaproject(current))
177506
178016
  return current;
177507
- const parent = path337.dirname(current);
178017
+ const parent = path338.dirname(current);
177508
178018
  if (parent === current)
177509
178019
  return;
177510
178020
  current = parent;
@@ -178169,6 +178679,190 @@ async function helpCommand(rest) {
178169
178679
  process.exitCode = 1;
178170
178680
  }
178171
178681
 
178682
+ // src/lib/group-subcommands.ts
178683
+ var GROUP_SUBCOMMANDS = new Map([
178684
+ ["modules", ["status", "list", "enable", "on", "disable", "off", "interactive", "help"]],
178685
+ ["projects", ["list", "register", "forget", "help"]],
178686
+ ["providers", ["list", "status", "cross-family", "test", "remove"]],
178687
+ ["routing", ["list", "set", "unset", "trust", "profile", "stats"]],
178688
+ ["external", ["on", "off", "status", "list"]],
178689
+ ["auth", ["list", "login", "logout", "status"]],
178690
+ ["version", ["check"]],
178691
+ ["gdgraph", ["build", "query", "find", "symbol", "symbols", "path", "affected", "repomap", "context", "assets"]],
178692
+ ["ctx", ["status", "diff", "rg", "read", "run", "show", "hook", "install-hook", "uninstall-hook"]],
178693
+ [
178694
+ "wiki",
178695
+ [
178696
+ "status",
178697
+ "new",
178698
+ "index",
178699
+ "collect",
178700
+ "check-links",
178701
+ "validate",
178702
+ "freshness",
178703
+ "refresh",
178704
+ "verify",
178705
+ "migrate-markers",
178706
+ "ask",
178707
+ "sections",
178708
+ "enrich",
178709
+ "context",
178710
+ "backlinks"
178711
+ ]
178712
+ ],
178713
+ ["stack", ["detect"]],
178714
+ ["health", ["run", "status", "gate", "sources", "explain", "baseline", "trend"]],
178715
+ ["metrics", ["status", "validate", "collect", "latest", "show", "compare", "rebuild", "plan", "benchmark"]],
178716
+ ["test", ["init", "analyze", "run", "status", "context", "report", "related", "explain", "coverage-map", "suggest"]],
178717
+ ["memory", ["new", "index", "search", "supersede", "transition", "assets", "ingest", "check", "reflect", "handoff"]],
178718
+ [
178719
+ "flow",
178720
+ [
178721
+ "init",
178722
+ "list",
178723
+ "status",
178724
+ "freeze",
178725
+ "start",
178726
+ "next",
178727
+ "task",
178728
+ "ac",
178729
+ "check-ac",
178730
+ "owner",
178731
+ "implemented",
178732
+ "complete",
178733
+ "confirm",
178734
+ "recover",
178735
+ "block",
178736
+ "unblock",
178737
+ "check",
178738
+ "renumber",
178739
+ "repair-reviews",
178740
+ "plan",
178741
+ "schema"
178742
+ ]
178743
+ ],
178744
+ ["job", ["init", "status", "step", "document", "complete", "list"]],
178745
+ [
178746
+ "review",
178747
+ [
178748
+ "attach",
178749
+ "start",
178750
+ "ingest",
178751
+ "scope",
178752
+ "floor",
178753
+ "blast-radius",
178754
+ "budget",
178755
+ "tier",
178756
+ "comments",
178757
+ "ci-triage",
178758
+ "conform",
178759
+ "jev-rules",
178760
+ "jev-edit-guard",
178761
+ "jev-risk",
178762
+ "jev-scenarios",
178763
+ "jev-docs",
178764
+ "jev-comments",
178765
+ "jev-contract",
178766
+ "jev-triage",
178767
+ "jev-select",
178768
+ "jev-profile",
178769
+ "learn",
178770
+ "loop",
178771
+ "stack",
178772
+ "reviewers",
178773
+ "import",
178774
+ "status",
178775
+ "complete",
178776
+ "lightweight"
178777
+ ]
178778
+ ],
178779
+ ["rules", ["distill", "sync"]],
178780
+ ["standard", ["validate", "doctor", "capabilities", "baseline", "emit"]],
178781
+ [
178782
+ "security",
178783
+ [
178784
+ "status",
178785
+ "scan",
178786
+ "scan-mcp",
178787
+ "audit-harness",
178788
+ "impact-evidence",
178789
+ "check-input",
178790
+ "check-output",
178791
+ "redact",
178792
+ "report",
178793
+ "policy",
178794
+ "incidents",
178795
+ "hooks",
178796
+ "eval"
178797
+ ]
178798
+ ],
178799
+ ["sandbox", ["status"]],
178800
+ ["integrations", ["install", "uninstall", "doctor", "matrix"]],
178801
+ [
178802
+ "workspace",
178803
+ [
178804
+ "create",
178805
+ "list",
178806
+ "show",
178807
+ "add-resource",
178808
+ "archive",
178809
+ "remove-resource",
178810
+ "rename",
178811
+ "overview",
178812
+ "read",
178813
+ "propose",
178814
+ "confirm-review",
178815
+ "review",
178816
+ "dismiss-candidate",
178817
+ "handoff",
178818
+ "collaboration",
178819
+ "policy-readiness",
178820
+ "catch-up",
178821
+ "list-proposals",
178822
+ "help"
178823
+ ]
178824
+ ],
178825
+ ["retention", ["status", "sweep"]],
178826
+ ["forgetting", ["trail", "lookup"]],
178827
+ ["trigger", ["run", "install", "uninstall", "list", "status", "schedule", "resolve"]],
178828
+ ["schedule", ["add", "list", "show", "pause", "resume", "run", "remove"]],
178829
+ ["governance", ["report", "show"]],
178830
+ ["hooks", ["list", "validate", "test", "enable", "disable", "trust", "untrust"]],
178831
+ ["bundle", ["export", "import", "inspect", "verify", "uninstall"]],
178832
+ ["learn", ["observe", "extract", "list", "review", "accept", "reject", "apply", "promote", "graduate", "prune"]],
178833
+ ["sessions", ["list", "fork", "export", "path", "help"]],
178834
+ ["session", ["list", "fork", "export", "path", "help"]],
178835
+ ["serve", ["status", "token", "config"]],
178836
+ ["bus", ["list", "log", "send", "pause", "resume", "prune", "help"]],
178837
+ ["dashboard", ["build", "open"]],
178838
+ [
178839
+ "skills",
178840
+ [
178841
+ "catalog",
178842
+ "install",
178843
+ "doctor",
178844
+ "uninstall",
178845
+ "status",
178846
+ "list",
178847
+ "inspect",
178848
+ "route",
178849
+ "create",
178850
+ "generate",
178851
+ "import",
178852
+ "update",
178853
+ "verify",
178854
+ "learn",
178855
+ "export",
178856
+ "sync",
178857
+ "contracts",
178858
+ "scout",
178859
+ "eval",
178860
+ "judge-check",
178861
+ "stocktake"
178862
+ ]
178863
+ ]
178864
+ ]);
178865
+
178172
178866
  // src/cli.ts
178173
178867
  init_package();
178174
178868
  if (import.meta.main) {
@@ -178178,6 +178872,7 @@ var VERSION2 = package_default.version;
178178
178872
  var CLI_ROUTES = {
178179
178873
  init: initCommand,
178180
178874
  help: helpCommand,
178875
+ doctor: doctorCommand2,
178181
178876
  status: statusCommand,
178182
178877
  modules: modulesCommand,
178183
178878
  projects: projectsCommand,
@@ -178232,6 +178927,15 @@ var CLI_ROUTES = {
178232
178927
  learn: learnCommand,
178233
178928
  "__sandbox-net-forward": sandboxNetForwardCommand
178234
178929
  };
178930
+ var MCP_KNOWN_SUBCOMMANDS = [...MCP_CONSUMER_SUBCOMMANDS, "install", "uninstall", "serve"];
178931
+ function knownSubcommandsFor(command) {
178932
+ if (command === "mcp")
178933
+ return MCP_KNOWN_SUBCOMMANDS;
178934
+ return GROUP_SUBCOMMANDS.get(command);
178935
+ }
178936
+ function groupsWithKnownSubcommands() {
178937
+ return ["mcp", ...GROUP_SUBCOMMANDS.keys()];
178938
+ }
178235
178939
  async function main() {
178236
178940
  setModelTurnPort(async (request) => runModelTurn(request));
178237
178941
  const args = process.argv.slice(2);
@@ -178250,16 +178954,23 @@ async function main() {
178250
178954
  await printCommandHelp(command);
178251
178955
  return;
178252
178956
  }
178957
+ const known = knownSubcommandsFor(command);
178958
+ const sub = args[1];
178959
+ if (known !== undefined && sub !== undefined && !sub.startsWith("-") && !known.includes(sub)) {
178960
+ console.error(formatUnknownCommandMessage(sub, known, `keryx ${command} --help`));
178961
+ process.exitCode = 1;
178962
+ return;
178963
+ }
178253
178964
  await route(args.slice(1));
178254
178965
  return;
178255
178966
  }
178256
- console.error(`Unknown command: ${command}`);
178257
- printHelp30();
178967
+ console.error(formatUnknownCommandMessage(command, Object.keys(CLI_ROUTES).filter((name) => name !== "__sandbox-net-forward")));
178258
178968
  process.exitCode = 1;
178259
178969
  }
178260
178970
  var USAGE_BODY = `Usage:
178261
178971
  keryx Show CLI usage
178262
178972
  keryx help [group|command] Grouped command help by task (--help/-h keep this flat usage)
178973
+ keryx doctor [--json] One page: version, Bun floor, ripgrep, sandbox, providers, MCP, integrations, standard, worktrees, graph/wiki freshness
178263
178974
  keryx shell [-c|--continue] [-r|--resume [id]] [--provider <p>] [--model <m>] [--base-url <url>] [--agent|--chat] [--tui|--no-tui]
178264
178975
  Start TUI agent shell (sessions are per-project)
178265
178976
  keryx sessions list|fork <id>|export <id>|path
@@ -178439,6 +179150,7 @@ var USAGE_BODY = `Usage:
178439
179150
 
178440
179151
  Commands:
178441
179152
  help Grouped command help by task: every verb, in nine onboarding-ordered groups
179153
+ doctor One-page health check with a fix hint per line; --json for {checks:[...]}
178442
179154
  shell Start the interactive TUI agent harness. Use --no-tui or --chat to opt out.
178443
179155
  Sessions: -c continue last in this project, -r [id] resume (per-project).
178444
179156
  sessions List or export per-project shell sessions
@@ -178587,7 +179299,9 @@ export {
178587
179299
  USAGE_BODY,
178588
179300
  exitCodeForError,
178589
179301
  groupUsage,
179302
+ groupsWithKnownSubcommands,
178590
179303
  helpRequestedFor,
179304
+ knownSubcommandsFor,
178591
179305
  main,
178592
179306
  printCommandHelp,
178593
179307
  shouldInterceptHelp