@mrciphersmith/keryx 0.3.16 → 0.3.17

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.17",
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: {
@@ -34739,6 +34743,23 @@ function tokenize(text) {
34739
34743
  function tokenSet(text) {
34740
34744
  return new Set(tokenize(text));
34741
34745
  }
34746
+ function stem(token) {
34747
+ let base = token;
34748
+ if (base.length >= 6 && base.endsWith("ies")) {
34749
+ base = `${base.slice(0, -3)}y`;
34750
+ } else {
34751
+ for (const suffix of ["ing", "edly", "ed", "es", "s"]) {
34752
+ if (base.length - suffix.length >= 3 && base.endsWith(suffix)) {
34753
+ base = base.slice(0, base.length - suffix.length);
34754
+ break;
34755
+ }
34756
+ }
34757
+ }
34758
+ if (base.length >= 4 && base.endsWith("e")) {
34759
+ base = base.slice(0, -1);
34760
+ }
34761
+ return base;
34762
+ }
34742
34763
  function jaccard(a, b) {
34743
34764
  if (a.size === 0 || b.size === 0) {
34744
34765
  return 0;
@@ -35296,7 +35317,7 @@ var init_ingest = __esm(() => {
35296
35317
  function searchEntries(entries, query, filters, config, now) {
35297
35318
  validateQuery(query);
35298
35319
  validateSearchFilters(filters, now);
35299
- const queryTokens = [...new Set(tokenize(query))];
35320
+ const queryTokens = [...new Set(tokenize(query).map(stem))];
35300
35321
  const today = now.toISOString().slice(0, 10);
35301
35322
  const filtered = entries.filter((entry) => matchesFilters(entry, filters) && classMatch(entry, filters.class) && temporalMatch(entry, filters.asOf ?? null, config, today, Boolean(filters.status)));
35302
35323
  const scored = filtered.map((entry) => scoreEntry(entry, queryTokens, filters, config, now));
@@ -35305,7 +35326,7 @@ function searchEntries(entries, query, filters, config, now) {
35305
35326
  function candidatePool(entries, query, filters, config, now, k) {
35306
35327
  validateQuery(query);
35307
35328
  validateSearchFilters(filters, now);
35308
- const queryTokens = [...new Set(tokenize(query))];
35329
+ const queryTokens = [...new Set(tokenize(query).map(stem))];
35309
35330
  const today = now.toISOString().slice(0, 10);
35310
35331
  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
35332
  }
@@ -35356,8 +35377,8 @@ function temporalMatch(entry, asOf, config, today, hasStatusFilter) {
35356
35377
  }, new Date(`${today}T00:00:00.000Z`)).current;
35357
35378
  }
35358
35379
  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);
35380
+ const bodyTokens = new Set([...tokenSet(`${entry.title} ${entry.summary} ${entry.details} ${entry.tags.join(" ")}`)].map(stem));
35381
+ const titleTokens = new Set([...tokenSet(entry.title)].map(stem));
35361
35382
  let hits = 0;
35362
35383
  let titleHits = 0;
35363
35384
  for (const token of queryTokens) {
@@ -36214,6 +36235,7 @@ var init_service5 = __esm(() => {
36214
36235
  init_harness_identity();
36215
36236
  init_embedding();
36216
36237
  init_types3();
36238
+ init_embedding();
36217
36239
  });
36218
36240
 
36219
36241
  // src/wiki/ask.ts
@@ -39595,6 +39617,7 @@ var init_service7 = __esm(() => {
39595
39617
  init_query();
39596
39618
  init_repomap();
39597
39619
  init_affected_report();
39620
+ init_staleness();
39598
39621
  UnknownGraphTargetError = class UnknownGraphTargetError extends Error {
39599
39622
  target;
39600
39623
  constructor(target) {
@@ -60502,7 +60525,8 @@ function computeGate2(input) {
60502
60525
  }
60503
60526
  const missingOptional = sources.filter((s) => !s.required && s.status === "missing");
60504
60527
  for (const source of missingOptional) {
60505
- reasons.push(`OPTIONAL: ${source.source} source missing`);
60528
+ const detail = source.error ? `: ${source.error}` : "";
60529
+ reasons.push(`OPTIONAL: ${source.source} source missing${detail}`);
60506
60530
  }
60507
60531
  const brokenOptional = sources.filter((s) => !s.required && s.status === "configured-but-failed");
60508
60532
  if (brokenOptional.length > 0) {
@@ -62307,6 +62331,12 @@ function errorCodeSuffix(error) {
62307
62331
  const code = safeErrorCode(error);
62308
62332
  return code ? ` (${code})` : "";
62309
62333
  }
62334
+ function missingSourceReason(sourceId) {
62335
+ if (sourceId === "tests") {
62336
+ return "no `bun` binary found (checked node_modules/.bin and PATH)";
62337
+ }
62338
+ return;
62339
+ }
62310
62340
  function safeValidationError(error) {
62311
62341
  return error !== undefined && KNOWN_VALIDATION_ERRORS.has(error) ? error : "source output format was not recognized";
62312
62342
  }
@@ -62341,7 +62371,8 @@ async function runAdapter(adapter, ctx, cfg, stamp) {
62341
62371
  };
62342
62372
  }
62343
62373
  if (status === "skipped" || status === "missing") {
62344
- return { info: { ...base, status }, findings: [] };
62374
+ const reason = missingSourceReason(adapter.id);
62375
+ return { info: { ...base, status, ...reason !== undefined ? { error: reason } : {} }, findings: [] };
62345
62376
  }
62346
62377
  let raw;
62347
62378
  try {
@@ -62355,7 +62386,7 @@ async function runAdapter(adapter, ctx, cfg, stamp) {
62355
62386
  } catch (error) {
62356
62387
  if (error instanceof NoImportError) {
62357
62388
  if (adapter.id === "tests") {
62358
- return { info: { ...base, status: "missing" }, findings: [] };
62389
+ return { info: { ...base, status }, findings: [] };
62359
62390
  }
62360
62391
  raw = await adapter.run(ctx);
62361
62392
  } else {
@@ -62720,6 +62751,7 @@ var init_service10 = __esm(() => {
62720
62751
  init_sources();
62721
62752
  init_util();
62722
62753
  init_lifecycle2();
62754
+ init_wiki_freshness();
62723
62755
  });
62724
62756
 
62725
62757
  // src/flow/machine.ts
@@ -70731,6 +70763,7 @@ async function runSearch(args) {
70731
70763
  }
70732
70764
  const report = args.includes("--save-report") ? await getService().writeReport({ cwd: process.cwd(), search: result, filters }) : undefined;
70733
70765
  const removalTrail = result.results.length === 0 ? searchRemovals(await loadDeletionTrail(process.cwd()), query) : null;
70766
+ 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
70767
  const asOfMode = Boolean(filters.asOf);
70735
70768
  const now = new Date;
70736
70769
  const historicalByPath = new Map;
@@ -70759,11 +70792,16 @@ async function runSearch(args) {
70759
70792
  };
70760
70793
  }),
70761
70794
  ...removalTrail ? { removalTrail } : {},
70795
+ ...semanticHint ? { semanticHint } : {},
70762
70796
  ...report ? { report } : {}
70763
70797
  }, null, 2));
70764
70798
  return;
70765
70799
  }
70766
70800
  console.log(renderSearchMarkdown(query, result.results).trimEnd());
70801
+ if (semanticHint) {
70802
+ console.log("");
70803
+ console.log(semanticHint);
70804
+ }
70767
70805
  if (removalTrail) {
70768
70806
  console.log("");
70769
70807
  console.log("## Removal trail");
@@ -71060,6 +71098,7 @@ var init_memory = __esm(() => {
71060
71098
  init_config4();
71061
71099
  init_reflect();
71062
71100
  init_search();
71101
+ init_service5();
71063
71102
  init_report2();
71064
71103
  init_lifecycle();
71065
71104
  init_args();
@@ -71636,7 +71675,7 @@ var init_debug_log = __esm(() => {
71636
71675
  // src/tui/debug-watcher.ts
71637
71676
  import { spawn as spawn8, spawnSync as spawnSync8 } from "child_process";
71638
71677
  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";
71678
+ import path317 from "path";
71640
71679
  function parseEpollFdinfo(text) {
71641
71680
  const out = [];
71642
71681
  for (const line of text.split(`
@@ -71825,8 +71864,8 @@ async function runDebugWatcher(argv) {
71825
71864
  process.exitCode = 2;
71826
71865
  return;
71827
71866
  }
71828
- const logFile = path316.join(dirArg, "watcher.ndjson");
71829
- const shellLog = path316.join(dirArg, "shell.ndjson");
71867
+ const logFile = path317.join(dirArg, "watcher.ndjson");
71868
+ const shellLog = path317.join(dirArg, "shell.ndjson");
71830
71869
  const log = (kind, data = {}) => {
71831
71870
  try {
71832
71871
  appendOwnerOnlyLine(logFile, JSON.stringify({ ts: new Date().toISOString(), watcher: process.pid, target: pid, kind, ...data }));
@@ -107084,6 +107123,18 @@ var HELP_GROUPS = [
107084
107123
  summary: "Grouped command help by task \u2014 keryx help [group|command]."
107085
107124
  },
107086
107125
  { kind: "slash", name: "/help", group: "Start here", summary: "Show available commands, grouped by task." },
107126
+ {
107127
+ kind: "cli",
107128
+ name: "doctor",
107129
+ group: "Start here",
107130
+ summary: "One-page health check: version, Bun floor, ripgrep, sandbox, providers, MCP, integrations, standard, worktrees, graph/wiki freshness."
107131
+ },
107132
+ {
107133
+ kind: "slash",
107134
+ name: "/doctor",
107135
+ group: "Start here",
107136
+ summary: "Same one-page health check, inside the session."
107137
+ },
107087
107138
  {
107088
107139
  kind: "cli",
107089
107140
  name: "auth",
@@ -109216,6 +109267,19 @@ var COMMAND_DESCRIPTORS = [
109216
109267
  json: false,
109217
109268
  read: true
109218
109269
  },
109270
+ {
109271
+ module: "core",
109272
+ command: "doctor",
109273
+ summary: "One-page health check: version, Bun floor, ripgrep, sandbox, providers, MCP, integrations, standard, worktrees, graph/wiki freshness.",
109274
+ 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"],
109275
+ args: [{ name: "json", type: "bool", required: false, desc: "structured {checks:[{id,status,detail,fix?}]}" }],
109276
+ json: true,
109277
+ read: false,
109278
+ sideEffects: [
109279
+ "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",
109280
+ "writes <per-user keryx config dir>/version-check.json (outside this project) as part of that same check"
109281
+ ]
109282
+ },
109219
109283
  {
109220
109284
  module: "core",
109221
109285
  command: "status",
@@ -111307,6 +111371,49 @@ async function sandboxCommand(args = [], deps = {}) {
111307
111371
  init_args();
111308
111372
  init_ui();
111309
111373
 
111374
+ // src/lib/suggest.ts
111375
+ var MAX_SUGGESTIONS = 3;
111376
+ var MAX_EDIT_DISTANCE = 2;
111377
+ function levenshtein2(a, b) {
111378
+ const rows = a.length + 1;
111379
+ const cols = b.length + 1;
111380
+ const dp = Array.from({ length: rows }, () => new Array(cols).fill(0));
111381
+ for (let i = 0;i < rows; i += 1) {
111382
+ const row = dp[i];
111383
+ if (row !== undefined)
111384
+ row[0] = i;
111385
+ }
111386
+ const first = dp[0];
111387
+ if (first !== undefined) {
111388
+ for (let j = 0;j < cols; j += 1)
111389
+ first[j] = j;
111390
+ }
111391
+ for (let i = 1;i < rows; i += 1) {
111392
+ for (let j = 1;j < cols; j += 1) {
111393
+ const same = a[i - 1] === b[j - 1];
111394
+ const up = dp[i - 1]?.[j] ?? 0;
111395
+ const left = dp[i]?.[j - 1] ?? 0;
111396
+ const diag = dp[i - 1]?.[j - 1] ?? 0;
111397
+ const row = dp[i];
111398
+ if (row !== undefined)
111399
+ row[j] = same ? diag : 1 + Math.min(up, left, diag);
111400
+ }
111401
+ }
111402
+ return dp[rows - 1]?.[cols - 1] ?? Math.max(a.length, b.length);
111403
+ }
111404
+ function suggestClosest(input, known) {
111405
+ const needle = input.trim().toLowerCase();
111406
+ if (needle.length === 0) {
111407
+ return [];
111408
+ }
111409
+ 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);
111410
+ }
111411
+ function formatUnknownCommandMessage(input, known, helpCommand = "keryx --help") {
111412
+ const suggestions = suggestClosest(input, known);
111413
+ const didYouMean = suggestions.length > 0 ? ` Did you mean: ${suggestions.join(", ")}?` : "";
111414
+ return `Unknown command: ${input}.${didYouMean} Run \`${helpCommand}\` for the list.`;
111415
+ }
111416
+
111310
111417
  // src/commands/integrate.ts
111311
111418
  init_ui();
111312
111419
  import path257 from "path";
@@ -111853,12 +111960,12 @@ function loadMcpServers(options) {
111853
111960
  const home = options.home ?? (options.configDir === undefined ? undefined : NO_HOME);
111854
111961
  for (const entry of compatFiles(options.cwd, home)) {
111855
111962
  const read = readCompatFile(entry, options.cwd);
111856
- problems.push(...read.problems);
111963
+ problems.push(...read.problems.map((p) => ({ ...p, foreign: true })));
111857
111964
  for (const [name, value] of Object.entries(read.servers)) {
111858
111965
  const found = entryProblems(name, value);
111859
111966
  if (found.length > 0) {
111860
111967
  for (const message of found)
111861
- problems.push({ file: entry.file, message });
111968
+ problems.push({ file: entry.file, message, foreign: true });
111862
111969
  continue;
111863
111970
  }
111864
111971
  winner.set(name, {
@@ -114032,19 +114139,31 @@ function publicView(server, env) {
114032
114139
  function listCommand(args, deps) {
114033
114140
  const config = load(deps);
114034
114141
  const wantJson = splitAtSeparator(args).own.includes("--json");
114142
+ const ownProblems = config.problems.filter((p) => !p.foreign);
114143
+ const foreignProblems = config.problems.filter((p) => p.foreign);
114035
114144
  if (wantJson) {
114036
114145
  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;
114146
+ deps.log(JSON.stringify({
114147
+ servers: config.servers.map((s) => publicView(s, env)),
114148
+ problems: ownProblems,
114149
+ warnings: foreignProblems
114150
+ }, null, 2));
114151
+ return ownProblems.length > 0 ? 1 : 0;
114039
114152
  }
114040
- for (const problem of config.problems) {
114153
+ for (const problem of ownProblems) {
114041
114154
  deps.err(`config problem \u2014 ${problem.file}: ${problem.message}`);
114042
114155
  }
114156
+ if (foreignProblems.length > 0) {
114157
+ deps.err("warnings:");
114158
+ for (const problem of foreignProblems) {
114159
+ deps.err(` ${problem.file}: ${problem.message}`);
114160
+ }
114161
+ }
114043
114162
  if (config.servers.length === 0) {
114044
114163
  deps.log("No MCP servers configured.");
114045
114164
  deps.log(` user: ${userConfigFile(deps.configDir)}`);
114046
114165
  deps.log(` project: ${projectConfigFile(projectRootOf2(deps))}`);
114047
- return config.problems.length > 0 ? 1 : 0;
114166
+ return ownProblems.length > 0 ? 1 : 0;
114048
114167
  }
114049
114168
  const approvals = loadTrustStore(deps.configDir);
114050
114169
  let held = 0;
@@ -114063,7 +114182,7 @@ function listCommand(args, deps) {
114063
114182
  deps.log(`${held} server(s) from committed config are not started until approved \u2014 a committed config is code someone else wrote.`);
114064
114183
  deps.log("Read what it launches above, then: keryx mcp trust <name>");
114065
114184
  }
114066
- return config.problems.length > 0 ? 1 : 0;
114185
+ return ownProblems.length > 0 ? 1 : 0;
114067
114186
  }
114068
114187
  function describeTarget(server) {
114069
114188
  const raw = server.raw;
@@ -116960,8 +117079,12 @@ async function mcpCommand(args = [], cwd = process.cwd()) {
116960
117079
  await serveMcpCommand(args.slice(1), cwd);
116961
117080
  return;
116962
117081
  }
116963
- console.error(`Unknown mcp command: ${subcommand}`);
116964
- printMcpHelp();
117082
+ console.error(formatUnknownCommandMessage(subcommand ?? "", [
117083
+ ...MCP_CONSUMER_SUBCOMMANDS,
117084
+ "install",
117085
+ "uninstall",
117086
+ "serve"
117087
+ ], "keryx mcp --help"));
116965
117088
  process.exitCode = 1;
116966
117089
  }
116967
117090
  function announceRename(retired, replacement) {
@@ -135479,6 +135602,569 @@ class NextStepSuggestionGate {
135479
135602
  }
135480
135603
  }
135481
135604
 
135605
+ // src/commands/doctor.ts
135606
+ init_package();
135607
+ import { readdir as readdir53 } from "fs/promises";
135608
+ import { existsSync as existsSync48 } from "fs";
135609
+ import path302 from "path";
135610
+
135611
+ // src/lib/version-check.ts
135612
+ init_config_dir();
135613
+ init_fs();
135614
+ import path301 from "path";
135615
+ var REGISTRY_URL = "https://registry.npmjs.org/@mrciphersmith%2Fkeryx/latest";
135616
+ var FIXED_INSTALL_COMMAND = "npm install -g @mrciphersmith/keryx@latest";
135617
+ var RESPONSE_BODY_LIMIT_BYTES = 64 * 1024;
135618
+ var VERSION_STRING_LIMIT_CHARS = 64;
135619
+ var REQUEST_TIMEOUT_MS = 2000;
135620
+ var SUCCESS_CACHE_TTL_MS = 24 * 60 * 60 * 1000;
135621
+ var FAILURE_BACKOFF_MS = 15 * 60 * 1000;
135622
+ var CACHE_LOCK_TIMEOUT_MS2 = 250;
135623
+ var CACHE_LOCK_RETRY_MS2 = 10;
135624
+ var CACHE_LOCK_STALE_MS2 = 5000;
135625
+ var SYSTEM_TIMER = {
135626
+ schedule: (callback, delayMs) => setTimeout(callback, delayMs),
135627
+ cancel: (handle) => clearTimeout(handle)
135628
+ };
135629
+ function formatVersionUpdateAdvisory(result) {
135630
+ if (result.status !== "update-available")
135631
+ return;
135632
+ return `Keryx update ${result.currentVersion} \u2192 ${result.latestVersion}
135633
+ ${result.installCommand}`;
135634
+ }
135635
+ 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-]+)*))?$/;
135636
+ function parseSemVer(value) {
135637
+ if (value.length > VERSION_STRING_LIMIT_CHARS)
135638
+ return;
135639
+ const match = STRICT_SEMVER.exec(value);
135640
+ if (match === null)
135641
+ return;
135642
+ const prereleaseText = match[4];
135643
+ const prerelease = [];
135644
+ if (prereleaseText !== undefined) {
135645
+ for (const identifier of prereleaseText.split(".")) {
135646
+ if (/^\d+$/.test(identifier)) {
135647
+ if (identifier.length > 1 && identifier.startsWith("0"))
135648
+ return;
135649
+ prerelease.push(BigInt(identifier));
135650
+ } else {
135651
+ prerelease.push(identifier);
135652
+ }
135653
+ }
135654
+ }
135655
+ return {
135656
+ major: BigInt(match[1]),
135657
+ minor: BigInt(match[2]),
135658
+ patch: BigInt(match[3]),
135659
+ prerelease
135660
+ };
135661
+ }
135662
+ function compareSemVer(left, right) {
135663
+ for (const key of ["major", "minor", "patch"]) {
135664
+ if (left[key] < right[key])
135665
+ return -1;
135666
+ if (left[key] > right[key])
135667
+ return 1;
135668
+ }
135669
+ if (left.prerelease.length === 0 || right.prerelease.length === 0) {
135670
+ if (left.prerelease.length === right.prerelease.length)
135671
+ return 0;
135672
+ return left.prerelease.length === 0 ? 1 : -1;
135673
+ }
135674
+ const length = Math.max(left.prerelease.length, right.prerelease.length);
135675
+ for (let index = 0;index < length; index += 1) {
135676
+ const a = left.prerelease[index];
135677
+ const b = right.prerelease[index];
135678
+ if (a === undefined || b === undefined) {
135679
+ if (a === b)
135680
+ return 0;
135681
+ return a === undefined ? -1 : 1;
135682
+ }
135683
+ if (typeof a === "bigint" && typeof b === "bigint") {
135684
+ if (a < b)
135685
+ return -1;
135686
+ if (a > b)
135687
+ return 1;
135688
+ } else if (typeof a === "bigint") {
135689
+ return -1;
135690
+ } else if (typeof b === "bigint") {
135691
+ return 1;
135692
+ } else if (a !== b) {
135693
+ return a < b ? -1 : 1;
135694
+ }
135695
+ }
135696
+ return 0;
135697
+ }
135698
+ function parseCache(file) {
135699
+ const read = readConfigFile(file);
135700
+ if (!read.ok)
135701
+ return;
135702
+ try {
135703
+ const value = JSON.parse(read.text);
135704
+ if (value === null || typeof value !== "object" || Array.isArray(value))
135705
+ return;
135706
+ const record = value;
135707
+ const cache = {};
135708
+ if (typeof record.latestVersion === "string" && parseSemVer(record.latestVersion) !== undefined) {
135709
+ cache.latestVersion = record.latestVersion;
135710
+ }
135711
+ if (typeof record.successAt === "number" && Number.isFinite(record.successAt))
135712
+ cache.successAt = record.successAt;
135713
+ if (typeof record.failureAt === "number" && Number.isFinite(record.failureAt))
135714
+ cache.failureAt = record.failureAt;
135715
+ return cache;
135716
+ } catch {
135717
+ return;
135718
+ }
135719
+ }
135720
+ function resultFor(currentVersion, current, latestVersion, source) {
135721
+ const latest = parseSemVer(latestVersion);
135722
+ return compareSemVer(current, latest) < 0 ? { status: "update-available", currentVersion, latestVersion, installCommand: FIXED_INSTALL_COMMAND, source } : { status: "up-to-date", currentVersion, latestVersion, source };
135723
+ }
135724
+ function cancelWithoutWaiting(cancel) {
135725
+ try {
135726
+ cancel().catch(() => {});
135727
+ } catch {}
135728
+ }
135729
+ async function boundedResponseText(response) {
135730
+ const declared = response.headers.get("content-length");
135731
+ if (declared !== null && /^\d+$/.test(declared) && BigInt(declared) > BigInt(RESPONSE_BODY_LIMIT_BYTES)) {
135732
+ if (response.body !== null) {
135733
+ cancelWithoutWaiting(() => response.body.cancel());
135734
+ }
135735
+ return;
135736
+ }
135737
+ if (response.body === null)
135738
+ return "";
135739
+ const reader = response.body.getReader();
135740
+ const decoder = new TextDecoder;
135741
+ let size = 0;
135742
+ let text = "";
135743
+ try {
135744
+ for (;; ) {
135745
+ const chunk = await reader.read();
135746
+ if (chunk.done)
135747
+ break;
135748
+ size += chunk.value.byteLength;
135749
+ if (size > RESPONSE_BODY_LIMIT_BYTES) {
135750
+ cancelWithoutWaiting(() => reader.cancel());
135751
+ return;
135752
+ }
135753
+ text += decoder.decode(chunk.value, { stream: true });
135754
+ }
135755
+ return text + decoder.decode();
135756
+ } finally {
135757
+ reader.releaseLock();
135758
+ }
135759
+ }
135760
+ function unavailable(currentVersion, reason, cache) {
135761
+ return {
135762
+ status: "unavailable",
135763
+ currentVersion,
135764
+ reason,
135765
+ ...cache?.latestVersion !== undefined ? { cachedLatestVersion: cache.latestVersion } : {}
135766
+ };
135767
+ }
135768
+ function saveCache2(file, cache) {
135769
+ try {
135770
+ writeOwnerOnlyFileAtomic(file, `${JSON.stringify(cache, null, 2)}
135771
+ `);
135772
+ return true;
135773
+ } catch {
135774
+ return false;
135775
+ }
135776
+ }
135777
+ function mergeSuccessfulCache(committed, latestVersion, successAt) {
135778
+ if (committed.latestVersion === undefined || committed.successAt === undefined) {
135779
+ return { latestVersion, successAt };
135780
+ }
135781
+ if (committed.successAt > successAt)
135782
+ return committed;
135783
+ if (committed.successAt < successAt)
135784
+ return { latestVersion, successAt };
135785
+ const committedVersion = parseSemVer(committed.latestVersion);
135786
+ const candidateVersion = parseSemVer(latestVersion);
135787
+ if (committedVersion !== undefined && candidateVersion !== undefined) {
135788
+ const precedence = compareSemVer(committedVersion, candidateVersion);
135789
+ if (precedence > 0)
135790
+ return committed;
135791
+ if (precedence < 0)
135792
+ return { latestVersion, successAt };
135793
+ if (committed.latestVersion >= latestVersion)
135794
+ return committed;
135795
+ }
135796
+ return { latestVersion, successAt };
135797
+ }
135798
+ async function updateCache(file, update) {
135799
+ try {
135800
+ return await withFileLock(`${file}.lock`, async () => saveCache2(file, update(parseCache(file) ?? {})), {
135801
+ timeoutMs: CACHE_LOCK_TIMEOUT_MS2,
135802
+ retryMs: CACHE_LOCK_RETRY_MS2,
135803
+ staleMs: CACHE_LOCK_STALE_MS2
135804
+ });
135805
+ } catch {
135806
+ return false;
135807
+ }
135808
+ }
135809
+ async function checkVersion(options) {
135810
+ const current = parseSemVer(options.currentVersion);
135811
+ if (current === undefined)
135812
+ return unavailable(options.currentVersion, "invalid-current-version");
135813
+ const now = options.now ?? Date.now;
135814
+ const timestamp = now();
135815
+ const configDir = ensureKeryxConfigDir(options.cacheDir);
135816
+ const cacheFile = path301.join(configDir, "version-check.json");
135817
+ const cache = parseCache(cacheFile);
135818
+ if (cache?.latestVersion !== undefined && cache.successAt !== undefined && timestamp - cache.successAt >= 0 && timestamp - cache.successAt < SUCCESS_CACHE_TTL_MS) {
135819
+ return resultFor(options.currentVersion, current, cache.latestVersion, "cache");
135820
+ }
135821
+ if (cache?.failureAt !== undefined && timestamp - cache.failureAt >= 0 && timestamp - cache.failureAt < FAILURE_BACKOFF_MS) {
135822
+ return unavailable(options.currentVersion, "suppressed", cache);
135823
+ }
135824
+ const fetchImpl = options.fetch ?? globalThis.fetch;
135825
+ const controller = new AbortController;
135826
+ const timer = options.timer ?? SYSTEM_TIMER;
135827
+ let timeout;
135828
+ let timeoutScheduled = false;
135829
+ let failure;
135830
+ let latestVersion;
135831
+ try {
135832
+ timeout = timer.schedule(() => controller.abort(), REQUEST_TIMEOUT_MS);
135833
+ timeoutScheduled = true;
135834
+ const response = await fetchImpl(REGISTRY_URL, {
135835
+ method: "GET",
135836
+ headers: { accept: "application/json" },
135837
+ signal: controller.signal,
135838
+ credentials: "omit"
135839
+ });
135840
+ if (!response.ok) {
135841
+ failure = "http";
135842
+ } else {
135843
+ const body = await boundedResponseText(response);
135844
+ if (body === undefined) {
135845
+ failure = "response-too-large";
135846
+ } else {
135847
+ let parsed;
135848
+ try {
135849
+ parsed = JSON.parse(body);
135850
+ } catch {
135851
+ parsed = undefined;
135852
+ }
135853
+ if (parsed === null || typeof parsed !== "object" || Array.isArray(parsed)) {
135854
+ failure = "malformed-response";
135855
+ } else {
135856
+ const record = parsed;
135857
+ if (record.name !== "@mrciphersmith/keryx" || typeof record.version !== "string") {
135858
+ failure = "malformed-response";
135859
+ } else if (parseSemVer(record.version) === undefined) {
135860
+ failure = "invalid-latest-version";
135861
+ } else {
135862
+ latestVersion = record.version;
135863
+ }
135864
+ }
135865
+ }
135866
+ }
135867
+ } catch {
135868
+ failure = controller.signal.aborted ? "timeout" : "network";
135869
+ } finally {
135870
+ if (timeoutScheduled) {
135871
+ try {
135872
+ timer.cancel(timeout);
135873
+ } catch {}
135874
+ }
135875
+ }
135876
+ if (latestVersion === undefined) {
135877
+ await updateCache(cacheFile, (committed) => ({ ...committed, failureAt: timestamp }));
135878
+ return unavailable(options.currentVersion, failure ?? "network", cache);
135879
+ }
135880
+ await updateCache(cacheFile, (committed) => mergeSuccessfulCache(committed, latestVersion, timestamp));
135881
+ return resultFor(options.currentVersion, current, latestVersion, "registry");
135882
+ }
135883
+
135884
+ // src/commands/doctor.ts
135885
+ init_contained_path();
135886
+ init_providers();
135887
+ init_service();
135888
+ init_service7();
135889
+ init_service10();
135890
+ async function registeredWorktrees(cwd) {
135891
+ if (!Bun.which("git"))
135892
+ return new Set;
135893
+ try {
135894
+ const proc = Bun.spawn(["git", "worktree", "list", "--porcelain"], { cwd, stdout: "pipe", stderr: "ignore" });
135895
+ const [stdout, code] = await Promise.all([new Response(proc.stdout).text(), proc.exited]);
135896
+ if (code !== 0)
135897
+ return new Set;
135898
+ const paths = new Set;
135899
+ for (const line of stdout.split(`
135900
+ `)) {
135901
+ if (line.startsWith("worktree ")) {
135902
+ paths.add(path302.resolve(line.slice("worktree ".length).trim()));
135903
+ }
135904
+ }
135905
+ return paths;
135906
+ } catch {
135907
+ return new Set;
135908
+ }
135909
+ }
135910
+ async function checkWorktrees(cwd) {
135911
+ const worktreesDir = path302.join(cwd, ".claude", "worktrees");
135912
+ let entries;
135913
+ try {
135914
+ entries = (await readdir53(worktreesDir, { withFileTypes: true })).filter((entry) => entry.isDirectory()).map((entry) => entry.name);
135915
+ } catch {
135916
+ return { id: "worktrees", status: "ok", detail: "no .claude/worktrees directory" };
135917
+ }
135918
+ if (entries.length === 0) {
135919
+ return { id: "worktrees", status: "ok", detail: ".claude/worktrees is empty" };
135920
+ }
135921
+ const registered = await registeredWorktrees(cwd);
135922
+ const stale = entries.filter((name) => {
135923
+ const full = path302.resolve(worktreesDir, name);
135924
+ return !existsSync48(path302.join(full, ".git")) || !registered.has(full);
135925
+ });
135926
+ if (stale.length === 0) {
135927
+ return {
135928
+ id: "worktrees",
135929
+ status: "ok",
135930
+ detail: `${entries.length} worktree(s) under .claude/worktrees, all registered`
135931
+ };
135932
+ }
135933
+ return {
135934
+ id: "worktrees",
135935
+ status: "warn",
135936
+ detail: `${stale.length} stale entr${stale.length === 1 ? "y" : "ies"} under .claude/worktrees: ${stale.join(", ")}`,
135937
+ fix: "git worktree prune, then remove the listed directories under .claude/worktrees by hand"
135938
+ };
135939
+ }
135940
+ function meetsBunFloor(current, floor) {
135941
+ const floorMatch = /^>=\s*(\d+)\.(\d+)\.(\d+)/.exec(floor);
135942
+ const currentMatch = /^(\d+)\.(\d+)\.(\d+)/.exec(current);
135943
+ if (!floorMatch || !currentMatch)
135944
+ return true;
135945
+ for (let i = 1;i <= 3; i += 1) {
135946
+ const f = Number(floorMatch[i]);
135947
+ const c = Number(currentMatch[i]);
135948
+ if (c !== f)
135949
+ return c > f;
135950
+ }
135951
+ return true;
135952
+ }
135953
+ function checkBun(bunFloorOverride) {
135954
+ const floor = bunFloorOverride ?? package_default.engines?.bun;
135955
+ const current = Bun.version;
135956
+ if (floor === undefined || meetsBunFloor(current, floor)) {
135957
+ return { id: "bun", status: "ok", detail: `Bun ${current}${floor ? ` (floor ${floor})` : ""}` };
135958
+ }
135959
+ return {
135960
+ id: "bun",
135961
+ status: "fail",
135962
+ detail: `Bun ${current} is below the documented floor ${floor}`,
135963
+ fix: "upgrade Bun: https://bun.sh/docs/installation"
135964
+ };
135965
+ }
135966
+ function checkRipgrep() {
135967
+ const rg = Bun.which("rg");
135968
+ return rg ? { id: "ripgrep", status: "ok", detail: `rg at ${rg}` } : {
135969
+ id: "ripgrep",
135970
+ status: "warn",
135971
+ detail: "ripgrep (rg) not found on PATH \u2014 keryx ctx rg and search_code are unavailable",
135972
+ fix: "brew install ripgrep (macOS) or apt install ripgrep (Debian/Ubuntu)"
135973
+ };
135974
+ }
135975
+ async function checkVersionAvailability() {
135976
+ const result = await checkVersion({ currentVersion: package_default.version });
135977
+ if (result.status === "update-available") {
135978
+ return {
135979
+ id: "version",
135980
+ status: "warn",
135981
+ detail: `keryx ${result.currentVersion} \u2192 ${result.latestVersion} is available`,
135982
+ fix: result.installCommand
135983
+ };
135984
+ }
135985
+ if (result.status === "up-to-date") {
135986
+ return { id: "version", status: "ok", detail: `keryx ${result.currentVersion} is up to date` };
135987
+ }
135988
+ return { id: "version", status: "ok", detail: `keryx ${result.currentVersion}; update check unavailable (${result.reason})` };
135989
+ }
135990
+ function checkSandbox() {
135991
+ const report = buildSandboxReport();
135992
+ if (report.launcher.available) {
135993
+ return { id: "sandbox", status: "ok", detail: `${report.launcherName ?? "sandbox launcher"} available` };
135994
+ }
135995
+ const known = report.launcherName !== undefined;
135996
+ return {
135997
+ id: "sandbox",
135998
+ status: "warn",
135999
+ detail: known ? `${report.launcherName} not installed on ${report.platform} \u2014 OS-sandboxed shell exec is unavailable` : `no OS sandbox launcher support on ${report.platform}`,
136000
+ fix: known ? "keryx sandbox status for what installing it would unlock" : "keryx sandbox status for details"
136001
+ };
136002
+ }
136003
+ function checkProviders(env) {
136004
+ const providers = configuredProviders(env);
136005
+ if (providers.length === 0) {
136006
+ return {
136007
+ id: "providers",
136008
+ status: "warn",
136009
+ detail: "no provider credentials configured",
136010
+ fix: "keryx auth login <provider>, or set a provider API key"
136011
+ };
136012
+ }
136013
+ return {
136014
+ id: "providers",
136015
+ status: "ok",
136016
+ detail: `${providers.length} provider(s) configured: ${providers.map((p) => p.name).join(", ")}`
136017
+ };
136018
+ }
136019
+ function checkMcp(cwd) {
136020
+ const projectRoot = resolveProjectRoot(cwd);
136021
+ const config = loadMcpServers({ cwd, gitRoot: projectRoot });
136022
+ const ownProblems = config.problems.filter((p) => !p.foreign);
136023
+ if (ownProblems.length > 0) {
136024
+ return {
136025
+ id: "mcp",
136026
+ status: "fail",
136027
+ detail: `${ownProblems.length} config problem(s) in keryx's own MCP config: ${ownProblems.map((p) => p.message).join("; ")}`,
136028
+ fix: "keryx mcp list for details"
136029
+ };
136030
+ }
136031
+ const approvals = loadTrustStore();
136032
+ const held = config.servers.filter((server) => server.enabled && requiresApproval(server, approvals));
136033
+ if (held.length > 0) {
136034
+ return {
136035
+ id: "mcp",
136036
+ status: "warn",
136037
+ detail: `${held.length} server(s) from committed config awaiting approval: ${held.map((s) => s.name).join(", ")}`,
136038
+ fix: "keryx mcp trust <name>, after reading what it launches"
136039
+ };
136040
+ }
136041
+ return {
136042
+ id: "mcp",
136043
+ status: "ok",
136044
+ detail: `${config.servers.length} server(s) configured; run \`keryx mcp doctor\` to also check connectivity`
136045
+ };
136046
+ }
136047
+ async function checkIntegrations(cwd) {
136048
+ const results = await Promise.all(harnessAdapterIds().map(async (id) => {
136049
+ try {
136050
+ return await doctorIntegration(cwd, id);
136051
+ } catch {
136052
+ return { runtimeId: id, surfaces: [], problems: [], ok: true };
136053
+ }
136054
+ }));
136055
+ const drifted = results.filter((r) => !r.ok);
136056
+ if (drifted.length === 0) {
136057
+ return { id: "integrations", status: "ok", detail: "no drift in any installed integration" };
136058
+ }
136059
+ return {
136060
+ id: "integrations",
136061
+ status: "warn",
136062
+ detail: `drift in: ${drifted.map((r) => r.runtimeId).join(", ")}`,
136063
+ fix: "keryx integrations doctor --runtime all"
136064
+ };
136065
+ }
136066
+ async function checkStandard(cwd) {
136067
+ const result = await runDoctor(cwd);
136068
+ if (result.errors.length > 0) {
136069
+ return {
136070
+ id: "standard",
136071
+ status: "fail",
136072
+ detail: `${result.errors.length} error(s): ${result.errors.map((e) => e.message).join("; ")}`,
136073
+ fix: "keryx standard doctor"
136074
+ };
136075
+ }
136076
+ if (result.warnings.length > 0) {
136077
+ return {
136078
+ id: "standard",
136079
+ status: "warn",
136080
+ detail: `${result.warnings.length} warning(s): ${result.warnings.map((w) => w.message).join("; ")}`,
136081
+ fix: "keryx standard doctor"
136082
+ };
136083
+ }
136084
+ return { id: "standard", status: "ok", detail: "workspace is Metaproject Standard compliant" };
136085
+ }
136086
+ async function checkGraphFreshness(cwd) {
136087
+ const result = await checkGraphStaleness(cwd);
136088
+ if (result.status === "fresh") {
136089
+ return { id: "graph-freshness", status: "ok", detail: "code graph is fresh" };
136090
+ }
136091
+ return {
136092
+ id: "graph-freshness",
136093
+ status: "warn",
136094
+ detail: result.reasons.join("; ") || `code graph is ${result.status}`,
136095
+ fix: "keryx gdgraph build"
136096
+ };
136097
+ }
136098
+ async function checkWikiFreshness(cwd) {
136099
+ const metric = await readWikiFreshnessMetric(cwd);
136100
+ if (metric.status === "measured") {
136101
+ const pct = metric.ratio !== undefined ? `${Math.round(metric.ratio * 100)}%` : "an unmeasured share";
136102
+ return {
136103
+ id: "wiki-freshness",
136104
+ status: "ok",
136105
+ detail: `${pct} of ${metric.pagesTotal ?? "?"} wiki page(s) fresh`
136106
+ };
136107
+ }
136108
+ return {
136109
+ id: "wiki-freshness",
136110
+ status: "warn",
136111
+ detail: metric.reason ?? `wiki freshness: ${metric.status}`,
136112
+ fix: "keryx wiki freshness"
136113
+ };
136114
+ }
136115
+ async function buildDoctorReport(cwd, env = process.env, overrides = {}) {
136116
+ const checks = await Promise.all([
136117
+ checkVersionAvailability(),
136118
+ Promise.resolve(checkBun(overrides.bunFloor)),
136119
+ Promise.resolve(checkRipgrep()),
136120
+ Promise.resolve(checkSandbox()),
136121
+ Promise.resolve(checkProviders(env)),
136122
+ Promise.resolve(checkMcp(cwd)),
136123
+ checkIntegrations(cwd),
136124
+ checkStandard(cwd),
136125
+ checkWorktrees(cwd),
136126
+ checkGraphFreshness(cwd),
136127
+ checkWikiFreshness(cwd)
136128
+ ]);
136129
+ return { checks };
136130
+ }
136131
+ function doctorFailed(report) {
136132
+ return report.checks.some((check) => check.status === "fail");
136133
+ }
136134
+ function formatDoctorReport2(report) {
136135
+ const symbolFor = { ok: "\u2713", warn: "!", fail: "\u2717" };
136136
+ const lines = ["keryx doctor", ""];
136137
+ for (const check of report.checks) {
136138
+ lines.push(` ${symbolFor[check.status]} ${check.id}: ${check.detail}`);
136139
+ if (check.fix) {
136140
+ lines.push(` fix: ${check.fix}`);
136141
+ }
136142
+ }
136143
+ return lines.join(`
136144
+ `);
136145
+ }
136146
+ async function doctorCommand2(args = [], cwd = process.cwd(), overrides = {}) {
136147
+ if (args.includes("--help") || args.includes("-h")) {
136148
+ console.log(`keryx doctor [--json]
136149
+
136150
+ One page: keryx's own version and update availability, the Bun floor,
136151
+ ripgrep on PATH, the OS sandbox launcher, providers with a credential
136152
+ present (names only), MCP servers and their trust state, integrations
136153
+ drift, Metaproject Standard warnings, stale .claude/worktrees entries, and
136154
+ graph/wiki freshness. Exits 0 unless something is a "fail".`);
136155
+ return;
136156
+ }
136157
+ const report = await buildDoctorReport(cwd, process.env, overrides);
136158
+ if (args.includes("--json")) {
136159
+ console.log(JSON.stringify(report, null, 2));
136160
+ } else {
136161
+ console.log(formatDoctorReport2(report));
136162
+ }
136163
+ if (doctorFailed(report)) {
136164
+ process.exitCode = 1;
136165
+ }
136166
+ }
136167
+
135482
136168
  // src/tui/tui-shell.ts
135483
136169
  init_slate_lifecycle();
135484
136170
  init_slate();
@@ -135491,7 +136177,7 @@ init_workspace_service();
135491
136177
  init_service11();
135492
136178
  init_store3();
135493
136179
  init_fs();
135494
- import path301 from "path";
136180
+ import path303 from "path";
135495
136181
  import { readFile as readFile157 } from "fs/promises";
135496
136182
  var POSITIVE_INTEGER = /^[1-9][0-9]*$/;
135497
136183
  var DEFAULT_AUTO_GOAL_ROUNDS = 8;
@@ -135567,7 +136253,7 @@ function getFlowService() {
135567
136253
  async function autoProvisionFlow(cwd, goalText) {
135568
136254
  const service = getFlowService();
135569
136255
  const result = await service.init({ cwd, title: goalText });
135570
- const acFile = path301.join(cwd, result.dir, "acceptance-criteria.md");
136256
+ const acFile = path303.join(cwd, result.dir, "acceptance-criteria.md");
135571
136257
  await writeFileAtomic(acFile, [
135572
136258
  "# Acceptance Criteria",
135573
136259
  "",
@@ -137255,8 +137941,8 @@ init_store4();
137255
137941
  init_proposal_lifecycle();
137256
137942
  init_workspace_service();
137257
137943
  import { randomUUID as randomUUID34 } from "crypto";
137258
- import { readdir as readdir53, stat as stat30 } from "fs/promises";
137259
- import path302 from "path";
137944
+ import { readdir as readdir54, stat as stat30 } from "fs/promises";
137945
+ import path304 from "path";
137260
137946
 
137261
137947
  // src/sac/lifecycle-flag.ts
137262
137948
  init_store();
@@ -137395,7 +138081,7 @@ async function readExternalUnboundCandidates(cwd) {
137395
138081
  const extDir = externalSlatesDir(cwd);
137396
138082
  let externalIds;
137397
138083
  try {
137398
- const entries = await readdir53(extDir);
138084
+ const entries = await readdir54(extDir);
137399
138085
  externalIds = entries.filter((name) => name.endsWith(".json")).map((name) => name.slice(0, -".json".length));
137400
138086
  } catch {
137401
138087
  return [];
@@ -137426,7 +138112,7 @@ async function readExternalUnboundCandidates(cwd) {
137426
138112
  }
137427
138113
  return groups;
137428
138114
  }, []));
137429
- const metaPath = path302.join(extDir, `${id}.json`);
138115
+ const metaPath = path304.join(extDir, `${id}.json`);
137430
138116
  if (await pathExists(unboundDismissedReceiptPath(metaPath)))
137431
138117
  continue;
137432
138118
  candidates.push({
@@ -137440,16 +138126,16 @@ async function readExternalUnboundCandidates(cwd) {
137440
138126
  return candidates;
137441
138127
  }
137442
138128
  async function readNewestUnboundCandidateForExternal(cwd, externalSessionId) {
137443
- const evidenceDir = path302.join(externalSlatesDir(cwd), externalSessionId);
138129
+ const evidenceDir = path304.join(externalSlatesDir(cwd), externalSessionId);
137444
138130
  let entries;
137445
138131
  try {
137446
- entries = (await readdir53(evidenceDir)).filter((name) => name.endsWith("-unbound-candidate.json"));
138132
+ entries = (await readdir54(evidenceDir)).filter((name) => name.endsWith("-unbound-candidate.json"));
137447
138133
  } catch {
137448
138134
  return;
137449
138135
  }
137450
138136
  entries.sort();
137451
138137
  for (let i = entries.length - 1;i >= 0; i--) {
137452
- const evidencePath = path302.join(evidenceDir, entries[i]);
138138
+ const evidencePath = path304.join(evidenceDir, entries[i]);
137453
138139
  if (await pathExists(unboundDismissedReceiptPath(evidencePath)))
137454
138140
  continue;
137455
138141
  const result = readConfigFile(evidencePath);
@@ -137492,23 +138178,23 @@ var SESSION_ATTRIBUTION_SLACK_MS = 5 * 60000;
137492
138178
  var MTIME_CLOCK_SKEW_TOLERANCE_MS = 5000;
137493
138179
  async function collectReceiptTargets(cwd, owner) {
137494
138180
  const targets = new Set;
137495
- const workspacesDir = path302.join(cwd, ".metaproject", "workspaces");
138181
+ const workspacesDir = path304.join(cwd, ".metaproject", "workspaces");
137496
138182
  let workspaceIds;
137497
138183
  try {
137498
- workspaceIds = (await readdir53(workspacesDir, { withFileTypes: true })).filter((entry) => entry.isDirectory()).map((entry) => entry.name);
138184
+ workspaceIds = (await readdir54(workspacesDir, { withFileTypes: true })).filter((entry) => entry.isDirectory()).map((entry) => entry.name);
137499
138185
  } catch {
137500
138186
  return targets;
137501
138187
  }
137502
138188
  for (const workspaceId of workspaceIds) {
137503
- const receiptsDir = path302.join(workspacesDir, workspaceId, `${owner}-write-receipts`);
138189
+ const receiptsDir = path304.join(workspacesDir, workspaceId, `${owner}-write-receipts`);
137504
138190
  let files;
137505
138191
  try {
137506
- files = (await readdir53(receiptsDir)).filter((name) => name.endsWith(".json"));
138192
+ files = (await readdir54(receiptsDir)).filter((name) => name.endsWith(".json"));
137507
138193
  } catch {
137508
138194
  continue;
137509
138195
  }
137510
138196
  for (const file of files) {
137511
- const result = readConfigFile(path302.join(receiptsDir, file));
138197
+ const result = readConfigFile(path304.join(receiptsDir, file));
137512
138198
  if (!result.ok)
137513
138199
  continue;
137514
138200
  try {
@@ -137545,13 +138231,13 @@ async function attributeToSession(absolutePath, sessionsNewestFirst) {
137545
138231
  async function findSkillFiles(dir) {
137546
138232
  let entries;
137547
138233
  try {
137548
- entries = await readdir53(dir, { withFileTypes: true });
138234
+ entries = await readdir54(dir, { withFileTypes: true });
137549
138235
  } catch {
137550
138236
  return [];
137551
138237
  }
137552
138238
  const found = [];
137553
138239
  for (const entry of entries) {
137554
- const full = path302.join(dir, entry.name);
138240
+ const full = path304.join(dir, entry.name);
137555
138241
  if (entry.isDirectory()) {
137556
138242
  found.push(...await findSkillFiles(full));
137557
138243
  } else if (entry.isFile() && entry.name === "SKILL.md") {
@@ -137610,10 +138296,10 @@ async function detectUnreviewedSacPathChanges(cwd, sessions) {
137610
138296
  changedAt: attribution.changedAt
137611
138297
  });
137612
138298
  }
137613
- const sacSkillsDir = path302.join(cwd, ".metaproject", "project-skills", "sac");
138299
+ const sacSkillsDir = path304.join(cwd, ".metaproject", "project-skills", "sac");
137614
138300
  const skillFiles = await findSkillFiles(sacSkillsDir);
137615
138301
  for (const absolutePath of skillFiles) {
137616
- const targetRef = path302.relative(path302.join(cwd, ".metaproject"), absolutePath).split(path302.sep).join("/");
138302
+ const targetRef = path304.relative(path304.join(cwd, ".metaproject"), absolutePath).split(path304.sep).join("/");
137617
138303
  if (skillReceipts.has(targetRef))
137618
138304
  continue;
137619
138305
  const attribution = await attributeToSession(absolutePath, scoped);
@@ -137631,12 +138317,12 @@ async function detectUnreviewedSacPathChanges(cwd, sessions) {
137631
138317
  return items;
137632
138318
  }
137633
138319
  async function isSlateEngaged(dir) {
137634
- if (await pathExists(path302.join(dir, "slate.json")))
138320
+ if (await pathExists(path304.join(dir, "slate.json")))
137635
138321
  return true;
137636
- if (await pathExists(path302.join(dir, "terminal-state.json")))
138322
+ if (await pathExists(path304.join(dir, "terminal-state.json")))
137637
138323
  return true;
137638
138324
  try {
137639
- const entries = await readdir53(path302.join(dir, "slate-archive"));
138325
+ const entries = await readdir54(path304.join(dir, "slate-archive"));
137640
138326
  return entries.length > 0;
137641
138327
  } catch {
137642
138328
  return false;
@@ -137661,7 +138347,7 @@ async function safeReadSlate(dir) {
137661
138347
  }
137662
138348
  }
137663
138349
  async function readTerminalState(dir) {
137664
- const result = readConfigFile(path302.join(dir, "terminal-state.json"));
138350
+ const result = readConfigFile(path304.join(dir, "terminal-state.json"));
137665
138351
  if (!result.ok) {
137666
138352
  return;
137667
138353
  }
@@ -137685,17 +138371,17 @@ async function resolveUnboundCandidateTarget(cwd, target) {
137685
138371
  if (session === undefined) {
137686
138372
  throw new Error(`No session or evidence path matches "${target}"`);
137687
138373
  }
137688
- const archiveDir = path302.join(sessionDir(session.projectPath, session.id), "slate-archive");
138374
+ const archiveDir = path304.join(sessionDir(session.projectPath, session.id), "slate-archive");
137689
138375
  let entries;
137690
138376
  try {
137691
- entries = (await readdir53(archiveDir)).filter((name) => name.endsWith("-unbound-candidate.json"));
138377
+ entries = (await readdir54(archiveDir)).filter((name) => name.endsWith("-unbound-candidate.json"));
137692
138378
  } catch {
137693
138379
  throw new Error(`No unbound-candidate artifacts for session ${session.id}`);
137694
138380
  }
137695
138381
  if (entries.length === 0)
137696
138382
  throw new Error(`No unbound-candidate artifacts for session ${session.id}`);
137697
138383
  entries.sort();
137698
- return path302.join(archiveDir, entries[entries.length - 1]);
138384
+ return path304.join(archiveDir, entries[entries.length - 1]);
137699
138385
  }
137700
138386
  async function dismissUnboundByTarget(cwd, target, reason) {
137701
138387
  const evidencePath = await resolveUnboundCandidateTarget(cwd, target);
@@ -137721,16 +138407,16 @@ async function dismissUnboundCandidate(candidatePath, reason) {
137721
138407
  return { removed: candidatePath, receipt: receiptPath };
137722
138408
  }
137723
138409
  async function readNewestUnboundCandidate(dir) {
137724
- const archiveDir = path302.join(dir, "slate-archive");
138410
+ const archiveDir = path304.join(dir, "slate-archive");
137725
138411
  let entries;
137726
138412
  try {
137727
- entries = (await readdir53(archiveDir)).filter((name) => name.endsWith("-unbound-candidate.json"));
138413
+ entries = (await readdir54(archiveDir)).filter((name) => name.endsWith("-unbound-candidate.json"));
137728
138414
  } catch {
137729
138415
  return;
137730
138416
  }
137731
138417
  entries.sort();
137732
138418
  for (let i = entries.length - 1;i >= 0; i--) {
137733
- const evidencePath = path302.join(archiveDir, entries[i]);
138419
+ const evidencePath = path304.join(archiveDir, entries[i]);
137734
138420
  if (await pathExists(unboundDismissedReceiptPath(evidencePath)))
137735
138421
  continue;
137736
138422
  const result = readConfigFile(evidencePath);
@@ -137760,16 +138446,16 @@ function isFailureOutcome2(g) {
137760
138446
  return g.outcome === "error" || g.outcome === "no_credential" || g.outcome === "conflict";
137761
138447
  }
137762
138448
  async function readNewestWrapUpOutcome(dir) {
137763
- const archiveDir = path302.join(dir, "slate-archive");
138449
+ const archiveDir = path304.join(dir, "slate-archive");
137764
138450
  let entries;
137765
138451
  try {
137766
- entries = (await readdir53(archiveDir)).filter((name) => name.endsWith("-wrap-up-outcome.json"));
138452
+ entries = (await readdir54(archiveDir)).filter((name) => name.endsWith("-wrap-up-outcome.json"));
137767
138453
  } catch {
137768
138454
  return;
137769
138455
  }
137770
138456
  entries.sort();
137771
138457
  for (let i = entries.length - 1;i >= 0; i--) {
137772
- const evidencePath = path302.join(archiveDir, entries[i]);
138458
+ const evidencePath = path304.join(archiveDir, entries[i]);
137773
138459
  const result = readConfigFile(evidencePath);
137774
138460
  if (!result.ok) {
137775
138461
  continue;
@@ -138376,6 +139062,11 @@ var AGENT_SLASH_COMMANDS = [
138376
139062
  modes: BOTH
138377
139063
  },
138378
139064
  { name: "/flows", description: "Browse project flows and inspect one", modes: BOTH },
139065
+ {
139066
+ name: "/doctor",
139067
+ description: "One-page health check: version, sandbox, providers, MCP, integrations, freshness",
139068
+ modes: BOTH
139069
+ },
138379
139070
  {
138380
139071
  name: "/ac",
138381
139072
  description: "Check the active flow's acceptance criteria (advisory, cached) \u2014 press `c` in /flows to re-check",
@@ -138718,7 +139409,7 @@ function classifyBusyDispatch(params) {
138718
139409
  init_fs();
138719
139410
  init_json();
138720
139411
  import { mkdir as mkdir67 } from "fs/promises";
138721
- import path304 from "path";
139412
+ import path306 from "path";
138722
139413
 
138723
139414
  // src/governance/aggregate.ts
138724
139415
  init_store3();
@@ -138726,17 +139417,17 @@ init_store3();
138726
139417
  // src/governance/spend.ts
138727
139418
  init_fs();
138728
139419
  init_store3();
138729
- import { readdir as readdir54, readFile as readFile158 } from "fs/promises";
138730
- import path303 from "path";
139420
+ import { readdir as readdir55, readFile as readFile158 } from "fs/promises";
139421
+ import path305 from "path";
138731
139422
  async function readFlowReviewManifests(cwd, flowDir) {
138732
- const reviewsDir = path303.join(flowsRoot(cwd), flowDir, "reviews");
139423
+ const reviewsDir = path305.join(flowsRoot(cwd), flowDir, "reviews");
138733
139424
  if (!await pathExists(reviewsDir)) {
138734
139425
  return [];
138735
139426
  }
138736
- const entries = (await readdir54(reviewsDir, { withFileTypes: true })).filter((entry) => entry.isDirectory());
139427
+ const entries = (await readdir55(reviewsDir, { withFileTypes: true })).filter((entry) => entry.isDirectory());
138737
139428
  const manifests = [];
138738
139429
  for (const entry of entries) {
138739
- const manifestPath = path303.join(reviewsDir, entry.name, "manifest.json");
139430
+ const manifestPath = path305.join(reviewsDir, entry.name, "manifest.json");
138740
139431
  if (!await pathExists(manifestPath)) {
138741
139432
  continue;
138742
139433
  }
@@ -138983,11 +139674,11 @@ async function collectProjectGovernance(root, filters, displayName) {
138983
139674
  // src/governance/report.ts
138984
139675
  var GOVERNANCE_SCHEMA_VERSION = 1;
138985
139676
  function governanceDataRoot(cwd) {
138986
- return path304.join(cwd, ".metaproject", "data", "governance");
139677
+ return path306.join(cwd, ".metaproject", "data", "governance");
138987
139678
  }
138988
139679
  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") };
139680
+ const artifacts = path306.join(governanceDataRoot(cwd), "artifacts");
139681
+ return { markdown: path306.join(artifacts, "latest.md"), json: path306.join(artifacts, "latest.json") };
138991
139682
  }
138992
139683
  async function buildGovernanceReport(options) {
138993
139684
  const { cwd, filters, allProjects, now } = options;
@@ -138995,9 +139686,9 @@ async function buildGovernanceReport(options) {
138995
139686
  if (allProjects) {
138996
139687
  const warnings = [];
138997
139688
  const registered = listProjects(options.registryDir, (message) => warnings.push(message));
138998
- const seen = new Set([path304.resolve(cwd)]);
139689
+ const seen = new Set([path306.resolve(cwd)]);
138999
139690
  for (const entry of registered) {
139000
- const resolved = path304.resolve(entry.path);
139691
+ const resolved = path306.resolve(entry.path);
139001
139692
  if (seen.has(resolved)) {
139002
139693
  continue;
139003
139694
  }
@@ -139150,7 +139841,7 @@ function renderTriggerSpendLine(spend) {
139150
139841
  }
139151
139842
  async function writeGovernanceArtifacts(cwd, report) {
139152
139843
  const { markdown, json } = artifactPaths(cwd);
139153
- await mkdir67(path304.dirname(markdown), { recursive: true });
139844
+ await mkdir67(path306.dirname(markdown), { recursive: true });
139154
139845
  await writeFileAtomic(markdown, renderGovernanceMarkdown(report));
139155
139846
  await writeFileAtomic(json, `${JSON.stringify(report, null, 2)}
139156
139847
  `);
@@ -139355,7 +140046,7 @@ function mountGovernancePanel(otui, renderer, parent, options) {
139355
140046
 
139356
140047
  // src/tui/governance-inspector.ts
139357
140048
  import { readFile as readFile159 } from "fs/promises";
139358
- import path305 from "path";
140049
+ import path307 from "path";
139359
140050
  var GOVERNANCE_COMMAND = "/governance";
139360
140051
  var GOVERNANCE_FOOTER = [
139361
140052
  { key: "\u2191/\u2193 j/k", label: "scroll" },
@@ -139366,7 +140057,7 @@ var GOVERNANCE_FOOTER = [
139366
140057
  var HEADER_TEXT_ROWS = 2;
139367
140058
  var GOVERNANCE_HEADER_ROWS = HEADER_TEXT_ROWS + 1;
139368
140059
  function governanceMarkdownPath(cwd) {
139369
- return path305.join(governanceDataRoot(cwd), "artifacts", "latest.md");
140060
+ return path307.join(governanceDataRoot(cwd), "artifacts", "latest.md");
139370
140061
  }
139371
140062
  async function defaultReadMarkdown(cwd) {
139372
140063
  try {
@@ -139511,7 +140202,7 @@ function openGovernanceReport(otui, chrome, options) {
139511
140202
 
139512
140203
  // src/tui/trigger-ledger.ts
139513
140204
  import { stat as fsStat2 } from "fs/promises";
139514
- import path306 from "path";
140205
+ import path308 from "path";
139515
140206
  var DEFAULT_RECORDS_PER_ENTRY = 20;
139516
140207
  function isScheduledEntry(entry) {
139517
140208
  return entry.fire.kind === "schedule";
@@ -139570,7 +140261,7 @@ function triggerLedgerPaths(root) {
139570
140261
  return {
139571
140262
  runs: triggerRunsPath(root),
139572
140263
  triggers: triggersConfigPath(root),
139573
- governance: path306.join(governanceDataRoot(root), "artifacts", "latest.json"),
140264
+ governance: path308.join(governanceDataRoot(root), "artifacts", "latest.json"),
139574
140265
  schedules: scheduleStorePath(root)
139575
140266
  };
139576
140267
  }
@@ -140143,7 +140834,7 @@ function openTriggers(otui, chrome, options) {
140143
140834
  // src/tui/trigger-run-now.ts
140144
140835
  import { spawn as nodeSpawn2 } from "child_process";
140145
140836
  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";
140837
+ import path309 from "path";
140147
140838
  var OUTPUT_TAIL_CHARS = 4000;
140148
140839
  var defaultSpawn = (command, args, options) => nodeSpawn2(command, [...args], { cwd: options.cwd, env: options.env, detached: true, stdio: [...options.stdio] });
140149
140840
  function triggerRunArgv(name, invocation = resolveKeryxInvocation(), options = {}) {
@@ -140158,7 +140849,7 @@ function runNowChildEnv(env) {
140158
140849
  return out;
140159
140850
  }
140160
140851
  function runNowLogDir(root) {
140161
- return path307.join(triggerDataDir(root), "run-now");
140852
+ return path309.join(triggerDataDir(root), "run-now");
140162
140853
  }
140163
140854
  var RUN_NOW_LOGS_KEPT = 5;
140164
140855
  var OUTPUT_TAIL_BYTES = 16 * 1024;
@@ -140166,13 +140857,13 @@ function stampOf(iso) {
140166
140857
  return iso.replace(/[:.]/g, "-");
140167
140858
  }
140168
140859
  function runNowLogPath(root, name, startedAt) {
140169
- return path307.join(runNowLogDir(root), `${name}-${stampOf(startedAt)}.log`);
140860
+ return path309.join(runNowLogDir(root), `${name}-${stampOf(startedAt)}.log`);
140170
140861
  }
140171
140862
  function isCode(error, code) {
140172
140863
  return typeof error === "object" && error !== null && "code" in error && error.code === code;
140173
140864
  }
140174
140865
  function prepareRunNowLogDir(root) {
140175
- mkdirSync7(path307.join(root, ".metaproject", "data"), { recursive: true });
140866
+ mkdirSync7(path309.join(root, ".metaproject", "data"), { recursive: true });
140176
140867
  for (const segment of [triggerDataDir(root), runNowLogDir(root)]) {
140177
140868
  let info;
140178
140869
  try {
@@ -140191,7 +140882,7 @@ function prepareRunNowLogDir(root) {
140191
140882
  }
140192
140883
  const dir = runNowLogDir(root);
140193
140884
  try {
140194
- const fd = openSync5(path307.join(dir, ".gitignore"), constants3.O_WRONLY | constants3.O_CREAT | constants3.O_EXCL | (constants3.O_NOFOLLOW ?? 0), 420);
140885
+ const fd = openSync5(path309.join(dir, ".gitignore"), constants3.O_WRONLY | constants3.O_CREAT | constants3.O_EXCL | (constants3.O_NOFOLLOW ?? 0), 420);
140195
140886
  try {
140196
140887
  writeSync(fd, `# keryx run-now logs \u2014 never commit
140197
140888
  *
@@ -140232,7 +140923,7 @@ function pruneRunLogs(dir, name, keep) {
140232
140923
  }
140233
140924
  for (const old of names.slice(0, Math.max(0, names.length - keep))) {
140234
140925
  try {
140235
- unlinkSync4(path307.join(dir, old));
140926
+ unlinkSync4(path309.join(dir, old));
140236
140927
  } catch {}
140237
140928
  }
140238
140929
  }
@@ -141250,279 +141941,6 @@ function mountSchedulesSidebar(options) {
141250
141941
  // src/tui/tui-shell.ts
141251
141942
  init_debug_log();
141252
141943
 
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
141944
  // src/tui/shell-chrome.ts
141527
141945
  init_debug_log();
141528
141946
 
@@ -143826,7 +144244,7 @@ init_model_tier();
143826
144244
  init_config_dir();
143827
144245
  init_fs();
143828
144246
  init_table();
143829
- import path309 from "path";
144247
+ import path310 from "path";
143830
144248
  var UNCATEGORIZED = "uncategorized";
143831
144249
  var EMPTY_TASK_COST_STORE = {};
143832
144250
  var MIN_MEASURED_TASKS = 20;
@@ -143892,7 +144310,7 @@ var TASK_COST_LOCK_TIMEOUT_MS = 3000;
143892
144310
  var TASK_COST_LOCK_RETRY_MS = 15;
143893
144311
  var TASK_COST_LOCK_STALE_MS = 1e4;
143894
144312
  function taskCostFilePath(dir) {
143895
- return path309.join(ensureKeryxSubdir(["routing"], dir), TASK_COST_FILE);
144313
+ return path310.join(ensureKeryxSubdir(["routing"], dir), TASK_COST_FILE);
143896
144314
  }
143897
144315
  function isTaskCostRecord(value) {
143898
144316
  if (typeof value !== "object" || value === null)
@@ -144956,11 +145374,11 @@ function openConform(otui, chrome, options) {
144956
145374
  }
144957
145375
 
144958
145376
  // src/tui/conform-source.ts
144959
- import { readFile as readFile160, readdir as readdir55, stat as stat31 } from "fs/promises";
144960
- import path310 from "path";
145377
+ import { readFile as readFile160, readdir as readdir56, stat as stat31 } from "fs/promises";
145378
+ import path311 from "path";
144961
145379
  async function readRecentConformDocs2(cwd) {
144962
145380
  try {
144963
- const raw = JSON.parse(await readFile160(path310.join(cwd, CONFORM_RECENTS_PATH), "utf8"));
145381
+ const raw = JSON.parse(await readFile160(path311.join(cwd, CONFORM_RECENTS_PATH), "utf8"));
144964
145382
  return Array.isArray(raw) ? raw : [];
144965
145383
  } catch {
144966
145384
  return [];
@@ -144981,25 +145399,25 @@ async function currentBranchPrTarget(cwd) {
144981
145399
  }
144982
145400
  }
144983
145401
  async function mostRecentReviewPackage(cwd) {
144984
- const flowsDir = path310.join(cwd, ".metaproject", "flows");
145402
+ const flowsDir = path311.join(cwd, ".metaproject", "flows");
144985
145403
  let best;
144986
145404
  try {
144987
- for (const flowEntry of await readdir55(flowsDir, { withFileTypes: true })) {
145405
+ for (const flowEntry of await readdir56(flowsDir, { withFileTypes: true })) {
144988
145406
  if (!flowEntry.isDirectory())
144989
145407
  continue;
144990
- const reviewsDir = path310.join(flowsDir, flowEntry.name, "reviews");
145408
+ const reviewsDir = path311.join(flowsDir, flowEntry.name, "reviews");
144991
145409
  let reviewEntries;
144992
145410
  try {
144993
- reviewEntries = await readdir55(reviewsDir, { withFileTypes: true });
145411
+ reviewEntries = await readdir56(reviewsDir, { withFileTypes: true });
144994
145412
  } catch {
144995
145413
  continue;
144996
145414
  }
144997
145415
  for (const reviewEntry of reviewEntries) {
144998
145416
  if (!reviewEntry.isDirectory())
144999
145417
  continue;
145000
- const dir = path310.join(reviewsDir, reviewEntry.name);
145418
+ const dir = path311.join(reviewsDir, reviewEntry.name);
145001
145419
  try {
145002
- const info = await stat31(path310.join(dir, "report.md"));
145420
+ const info = await stat31(path311.join(dir, "report.md"));
145003
145421
  if (best === undefined || info.mtimeMs > best.mtimeMs)
145004
145422
  best = { dir, mtimeMs: info.mtimeMs };
145005
145423
  } catch {}
@@ -145025,14 +145443,14 @@ async function loadConformSetup(cwd) {
145025
145443
  targets.push(pr);
145026
145444
  const reviewDir = await mostRecentReviewPackage(cwd);
145027
145445
  if (reviewDir !== undefined) {
145028
- targets.push({ id: `report:${reviewDir}`, kind: "report", label: `most recent review package \u2014 ${path310.relative(cwd, reviewDir)}` });
145446
+ targets.push({ id: `report:${reviewDir}`, kind: "report", label: `most recent review package \u2014 ${path311.relative(cwd, reviewDir)}` });
145029
145447
  }
145030
145448
  targets.push({ id: "diff:working", kind: "diff", label: "the working diff" });
145031
145449
  return { recents, targets };
145032
145450
  }
145033
145451
  async function readReportFindings2(reportDir) {
145034
145452
  try {
145035
- const raw = JSON.parse(await readFile160(path310.join(reportDir, "findings.json"), "utf8"));
145453
+ const raw = JSON.parse(await readFile160(path311.join(reportDir, "findings.json"), "utf8"));
145036
145454
  return Array.isArray(raw) ? raw : [];
145037
145455
  } catch {
145038
145456
  return [];
@@ -145148,7 +145566,7 @@ async function runConformForTarget(cwd, refPath, target, signal) {
145148
145566
  await scoreHunkRegions(hunkClauses, hunkRegionsFromDiff(info.diff));
145149
145567
  } else if (target.kind === "report") {
145150
145568
  const reportDir = target.id.slice("report:".length);
145151
- const reportMarkdown = await readFile160(path310.join(reportDir, "report.md"), "utf8").catch(() => "");
145569
+ const reportMarkdown = await readFile160(path311.join(reportDir, "report.md"), "utf8").catch(() => "");
145152
145570
  const findings = await readReportFindings2(reportDir);
145153
145571
  const facts = computeReportConformFacts(reportMarkdown, findings);
145154
145572
  const redacted = reportRedactedStateText(reportMarkdown);
@@ -145174,7 +145592,7 @@ async function runConformForTarget(cwd, refPath, target, signal) {
145174
145592
  async function writeRecentConformDoc(cwd, refPath) {
145175
145593
  await Promise.resolve().then(() => init_fs());
145176
145594
  const current = await readRecentConformDocs2(cwd);
145177
- await writeFileAtomic(path310.join(cwd, CONFORM_RECENTS_PATH), `${JSON.stringify(withRecentDoc(current, refPath), null, 2)}
145595
+ await writeFileAtomic(path311.join(cwd, CONFORM_RECENTS_PATH), `${JSON.stringify(withRecentDoc(current, refPath), null, 2)}
145178
145596
  `, {
145179
145597
  mode: 384
145180
145598
  }).catch(() => {});
@@ -145310,8 +145728,8 @@ async function runJevContractForShell(cwd, gitDiff = defaultGitDiff3) {
145310
145728
  }
145311
145729
 
145312
145730
  // 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";
145731
+ import { readdir as readdir57, readFile as readFile161, stat as stat32 } from "fs/promises";
145732
+ import path312 from "path";
145315
145733
  var JEV_TRIAGE_COMMAND = "/triage";
145316
145734
  function isJevTriageCommand(name) {
145317
145735
  return name === JEV_TRIAGE_COMMAND;
@@ -145320,25 +145738,25 @@ function renderJevTriageForShell(result) {
145320
145738
  return renderTriageMarkdown(result).trimEnd();
145321
145739
  }
145322
145740
  async function resolveLatestReviewPackage(cwd) {
145323
- const flowsDir = path311.join(cwd, ".metaproject", "flows");
145741
+ const flowsDir = path312.join(cwd, ".metaproject", "flows");
145324
145742
  let flowEntries;
145325
145743
  try {
145326
- flowEntries = (await readdir56(flowsDir, { withFileTypes: true })).filter((e) => e.isDirectory()).map((e) => e.name);
145744
+ flowEntries = (await readdir57(flowsDir, { withFileTypes: true })).filter((e) => e.isDirectory()).map((e) => e.name);
145327
145745
  } catch {
145328
145746
  return;
145329
145747
  }
145330
145748
  const candidates = [];
145331
145749
  for (const flowName of flowEntries) {
145332
- const reviewsDir = path311.join(flowsDir, flowName, "reviews");
145750
+ const reviewsDir = path312.join(flowsDir, flowName, "reviews");
145333
145751
  let reviewEntries;
145334
145752
  try {
145335
- reviewEntries = (await readdir56(reviewsDir, { withFileTypes: true })).filter((e) => e.isDirectory()).map((e) => e.name);
145753
+ reviewEntries = (await readdir57(reviewsDir, { withFileTypes: true })).filter((e) => e.isDirectory()).map((e) => e.name);
145336
145754
  } catch {
145337
145755
  continue;
145338
145756
  }
145339
145757
  for (const reviewName of reviewEntries) {
145340
- const packageDir = path311.join(reviewsDir, reviewName);
145341
- const findingsPath = path311.join(packageDir, "findings.json");
145758
+ const packageDir = path312.join(reviewsDir, reviewName);
145759
+ const findingsPath = path312.join(packageDir, "findings.json");
145342
145760
  try {
145343
145761
  const stats = await stat32(findingsPath);
145344
145762
  candidates.push({ dir: packageDir, mtimeMs: stats.mtimeMs });
@@ -145361,9 +145779,9 @@ async function runJevTriageForShell(cwd) {
145361
145779
  if (packageDir === undefined) {
145362
145780
  return "review-jev-triage: no review package found under .metaproject/flows/*/reviews/ \u2014 run a review round first.";
145363
145781
  }
145364
- const findingsRaw = JSON.parse(await readFile161(path311.join(packageDir, "findings.json"), "utf8"));
145782
+ const findingsRaw = JSON.parse(await readFile161(path312.join(packageDir, "findings.json"), "utf8"));
145365
145783
  const result = await computeJevTriageResult({ findingsRaw });
145366
- return `review-jev-triage: ${path311.relative(cwd, packageDir)}
145784
+ return `review-jev-triage: ${path312.relative(cwd, packageDir)}
145367
145785
  ${renderJevTriageForShell(result)}`;
145368
145786
  }
145369
145787
 
@@ -148277,10 +148695,10 @@ function openHelpModal(otui, chrome, options) {
148277
148695
 
148278
148696
  // src/tui/help-first-run.ts
148279
148697
  init_config_dir();
148280
- import path312 from "path";
148698
+ import path313 from "path";
148281
148699
  var FIRST_RUN_HELP_FILE = "help-first-run.json";
148282
148700
  function firstRunHelpPath(dir) {
148283
- return path312.join(keryxConfigDir(dir), FIRST_RUN_HELP_FILE);
148701
+ return path313.join(keryxConfigDir(dir), FIRST_RUN_HELP_FILE);
148284
148702
  }
148285
148703
  function helpFirstRunShown(dir) {
148286
148704
  const result = readConfigFile(firstRunHelpPath(dir));
@@ -149364,10 +149782,10 @@ function describeElicitationPrompt(tool, inputJson) {
149364
149782
  // src/lib/permission-mode-config.ts
149365
149783
  init_permission_mode();
149366
149784
  init_config_dir();
149367
- import path313 from "path";
149785
+ import path314 from "path";
149368
149786
  var EMPTY3 = { schemaVersion: 1, projects: {} };
149369
149787
  function permissionModeConfigPath(dir) {
149370
- return path313.join(keryxConfigDir(dir), "permission-mode.json");
149788
+ return path314.join(keryxConfigDir(dir), "permission-mode.json");
149371
149789
  }
149372
149790
  function withRegistryLock2(dir, fn) {
149373
149791
  return withFileLock2(`${permissionModeConfigPath(dir)}.lock`, fn, {
@@ -152316,7 +152734,7 @@ function mountExecutionPlanPanel(otui, renderer, parent, options) {
152316
152734
  // src/commands/schedule.ts
152317
152735
  import { createInterface } from "readline/promises";
152318
152736
  import { readFile as readFile162 } from "fs/promises";
152319
- import path315 from "path";
152737
+ import path316 from "path";
152320
152738
 
152321
152739
  // src/commands/trigger.ts
152322
152740
  init_gdgraph();
@@ -152327,9 +152745,9 @@ init_agent();
152327
152745
  init_agent_hooks();
152328
152746
  import { execFile as execFile11 } from "child_process";
152329
152747
  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";
152748
+ import { mkdtemp as mkdtemp5, mkdir as mkdir68, readdir as readdir58, rm as rm19, writeFile as writeFile47 } from "fs/promises";
152331
152749
  import { homedir as homedir13, tmpdir as tmpdir11 } from "os";
152332
- import path314 from "path";
152750
+ import path315 from "path";
152333
152751
  init_interactive_tools();
152334
152752
 
152335
152753
  // src/harness/process/sandbox/proxy.ts
@@ -152797,9 +153215,9 @@ function refusal3(runId, code, detail, action) {
152797
153215
  function agentTaskScratchParent(env = process.env) {
152798
153216
  const uid = typeof process.getuid === "function" ? process.getuid() : 0;
152799
153217
  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}`);
153218
+ if (runtime !== undefined && runtime.length > 0 && path315.isAbsolute(runtime))
153219
+ return path315.join(runtime, "keryx-agent-tasks");
153220
+ return path315.join(tmpdir11(), `keryx-agent-tasks-${uid}`);
152803
153221
  }
152804
153222
  function lastAssistantText(history) {
152805
153223
  for (let i = history.length - 1;i >= 0; i -= 1) {
@@ -152810,9 +153228,9 @@ function lastAssistantText(history) {
152810
153228
  return "";
152811
153229
  }
152812
153230
  async function pruneReports(dir, keep) {
152813
- const names = (await readdir57(dir).catch(() => [])).filter((n) => n.endsWith(".md")).sort();
153231
+ const names = (await readdir58(dir).catch(() => [])).filter((n) => n.endsWith(".md")).sort();
152814
153232
  for (const old of names.slice(0, Math.max(0, names.length - keep))) {
152815
- await rm19(path314.join(dir, old), { force: true }).catch(() => {});
153233
+ await rm19(path315.join(dir, old), { force: true }).catch(() => {});
152816
153234
  }
152817
153235
  }
152818
153236
  async function runAgentTaskDispatch(projectRoot, entry, action, deps = {}) {
@@ -152849,10 +153267,10 @@ async function runLocked(projectRoot, entry, action, deps, runId, now) {
152849
153267
  };
152850
153268
  }
152851
153269
  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");
153270
+ const scratch = await mkdtemp5(path315.join(scratchParent, `${entry.name.replace(/[^A-Za-z0-9._-]/g, "-")}-`));
153271
+ const workdir = path315.join(scratch, "work");
153272
+ const scratchHome = path315.join(scratch, "home");
153273
+ const grantedCwd = path315.join(scratch, "granted-cwd");
152856
153274
  await mkdir68(workdir, { recursive: true });
152857
153275
  await mkdir68(scratchHome, { recursive: true });
152858
153276
  await mkdir68(grantedCwd, { recursive: true, mode: 448 });
@@ -152865,7 +153283,7 @@ async function runLocked(projectRoot, entry, action, deps, runId, now) {
152865
153283
  const invocation = keryxInvocation();
152866
153284
  let networkInput = action.grants.network === "full";
152867
153285
  if (action.grants.network === "allowlist") {
152868
- const proxySocketPath = path314.join(scratch, "net-proxy.sock");
153286
+ const proxySocketPath = path315.join(scratch, "net-proxy.sock");
152869
153287
  try {
152870
153288
  const proxy = await createAllowlistProxy({
152871
153289
  allowedDomains: [...action.grants.domains],
@@ -153014,12 +153432,12 @@ async function runLocked(projectRoot, entry, action, deps, runId, now) {
153014
153432
  let reportNote = "";
153015
153433
  try {
153016
153434
  await ensureTriggerDataIgnored(projectRoot);
153017
- const dir = path314.join(triggerReportsDir(projectRoot), entry.name);
153435
+ const dir = path315.join(triggerReportsDir(projectRoot), entry.name);
153018
153436
  await mkdir68(dir, { recursive: true });
153019
- const file = path314.join(dir, `${runId}.md`);
153437
+ const file = path315.join(dir, `${runId}.md`);
153020
153438
  await writeFile47(file, renderReport2({ entry, action, runId, at: now().toISOString(), outcome, why, cost, grantedCalls, denials, sandboxNote, summary, networkDecisions }), "utf8");
153021
153439
  await pruneReports(dir, action.report.keep);
153022
- reportPath = path314.relative(projectRoot, file);
153440
+ reportPath = path315.relative(projectRoot, file);
153023
153441
  } catch (error) {
153024
153442
  reportNote = ` (report NOT written: ${error instanceof Error ? error.message : String(error)})`;
153025
153443
  }
@@ -153927,7 +154345,7 @@ async function showSubcommand(cwd, name, host, deps) {
153927
154345
  if (row.reportPath !== undefined) {
153928
154346
  console.log(`
153929
154347
  --- ${row.reportPath} ---`);
153930
- console.log(await readFile162(path315.join(cwd, row.reportPath), "utf8").catch(() => "(the report could not be read)"));
154348
+ console.log(await readFile162(path316.join(cwd, row.reportPath), "utf8").catch(() => "(the report could not be read)"));
153931
154349
  }
153932
154350
  }
153933
154351
  async function simpleAction(cwd, name, verb, action) {
@@ -159560,6 +159978,14 @@ ${formatThemeList(getThemeId())}`);
159560
159978
  });
159561
159979
  return;
159562
159980
  }
159981
+ if (command.name === "/doctor") {
159982
+ (async () => {
159983
+ const report = await buildDoctorReport(sessionCwd);
159984
+ io.onSystem?.(`${formatDoctorReport2(report)}
159985
+ `);
159986
+ })();
159987
+ return;
159988
+ }
159563
159989
  if (command.name === "/game") {
159564
159990
  showGame(line);
159565
159991
  return;
@@ -160515,6 +160941,7 @@ var READLINE_AGENT_COMMANDS = [
160515
160941
  "/compact",
160516
160942
  "/status",
160517
160943
  "/flows",
160944
+ "/doctor",
160518
160945
  "/theme",
160519
160946
  "/mode",
160520
160947
  "/reasoning",
@@ -161025,6 +161452,12 @@ Kept the current session.
161025
161452
  system(HELP_TEXT);
161026
161453
  continue;
161027
161454
  }
161455
+ if (command === "/doctor") {
161456
+ const report = await buildDoctorReport(deps.session?.cwd ?? process.cwd());
161457
+ system(`${formatDoctorReport2(report)}
161458
+ `);
161459
+ continue;
161460
+ }
161028
161461
  if (isSessionInfoCommand(command)) {
161029
161462
  const cwd = deps.session?.cwd;
161030
161463
  const [workspaces, flows] = cwd === undefined ? [[], []] : await Promise.all([loadInspectorWorkspaces(cwd), loadInspectorFlows(cwd)]);
@@ -162194,6 +162627,10 @@ ${GUTTER}${turnSeparator()}
162194
162627
  await runBusSlashCommand(bus, rest, (text) => agentIo.onSystem?.(text));
162195
162628
  } else if (command === "/help") {
162196
162629
  agentIo.onSystem?.(readlineAgentHelpText());
162630
+ } else if (command === "/doctor") {
162631
+ const report = await buildDoctorReport(sessionCwd);
162632
+ agentIo.onSystem?.(`${formatDoctorReport2(report)}
162633
+ `);
162197
162634
  } else if (isSessionInfoCommand(command)) {
162198
162635
  const cwd = sessionCwd;
162199
162636
  const [workspaces, flows] = await Promise.all([
@@ -165738,7 +166175,7 @@ init_fs();
165738
166175
  init_prompt();
165739
166176
  import { readFile as readFile163 } from "fs/promises";
165740
166177
  import { stdin } from "process";
165741
- import path317 from "path";
166178
+ import path318 from "path";
165742
166179
  init_ui();
165743
166180
  var MODULES = [
165744
166181
  { name: "gdgraph", flag: "--no-gdgraph", desc: "code graph, symbols, affected context", defaultEnabled: true },
@@ -165785,8 +166222,8 @@ async function modulesCommand(args = []) {
165785
166222
  return;
165786
166223
  }
165787
166224
  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");
166225
+ const metaprojectRoot = path318.join(process.cwd(), ".metaproject");
166226
+ const manifestPath = path318.join(metaprojectRoot, "metaproject.json");
165790
166227
  if (!await pathExists(manifestPath)) {
165791
166228
  if (wantsJson) {
165792
166229
  console.log(JSON.stringify({ schemaVersion: 1, error: "not-initialized", modules: [] }, null, 2));
@@ -165909,7 +166346,7 @@ import { createHash as createHash68, randomBytes as randomBytes11, randomUUID as
165909
166346
  import {
165910
166347
  chmodSync as chmodSync5,
165911
166348
  closeSync as closeSync7,
165912
- existsSync as existsSync48,
166349
+ existsSync as existsSync49,
165913
166350
  fsyncSync as fsyncSync2,
165914
166351
  openSync as openSync8,
165915
166352
  renameSync as renameSync5,
@@ -165917,9 +166354,9 @@ import {
165917
166354
  unlinkSync as unlinkSync6,
165918
166355
  writeFileSync as writeFileSync10
165919
166356
  } from "fs";
165920
- import path318 from "path";
166357
+ import path319 from "path";
165921
166358
  function serveCredentialPath(dir) {
165922
- return path318.join(keryxConfigDir(dir), "serve-credentials.json");
166359
+ return path319.join(keryxConfigDir(dir), "serve-credentials.json");
165923
166360
  }
165924
166361
  function constantTimeEqual(a, b) {
165925
166362
  const width = Math.max(a.length, b.length);
@@ -165953,7 +166390,7 @@ function isGroupOrOtherAccessible(file) {
165953
166390
  }
165954
166391
  function readServeCredential(dir) {
165955
166392
  const file = serveCredentialPath(dir);
165956
- if (!existsSync48(file)) {
166393
+ if (!existsSync49(file)) {
165957
166394
  return { status: "absent" };
165958
166395
  }
165959
166396
  if (isGroupOrOtherAccessible(file)) {
@@ -166152,23 +166589,23 @@ class AuthFailureThrottle {
166152
166589
  // src/lib/serve-turn-store.ts
166153
166590
  init_config_dir();
166154
166591
  import { createHash as createHash69 } from "crypto";
166155
- import { existsSync as existsSync49, readdirSync as readdirSync12, rmSync as rmSync4 } from "fs";
166156
- import path319 from "path";
166592
+ import { existsSync as existsSync50, readdirSync as readdirSync12, rmSync as rmSync4 } from "fs";
166593
+ import path320 from "path";
166157
166594
  var MAX_TURN_EVENTS = 1e4;
166158
166595
  function turnsRoot(dir) {
166159
- return path319.join(keryxConfigDir(dir), "turns");
166596
+ return path320.join(keryxConfigDir(dir), "turns");
166160
166597
  }
166161
166598
  function turnDir(turnId, dir) {
166162
- return path319.join(turnsRoot(dir), turnId);
166599
+ return path320.join(turnsRoot(dir), turnId);
166163
166600
  }
166164
166601
  function keyPath(project, idempotencyKey, dir) {
166165
166602
  const projectBytes = Buffer.byteLength(project, "utf8");
166166
166603
  const digest = createHash69("sha256").update(`${projectBytes}:${project}\x00${idempotencyKey}`, "utf8").digest("hex");
166167
- return path319.join(turnsRoot(dir), "keys", `${digest}.json`);
166604
+ return path320.join(turnsRoot(dir), "keys", `${digest}.json`);
166168
166605
  }
166169
166606
  function legacyKeyPath(idempotencyKey, dir) {
166170
166607
  const digest = createHash69("sha256").update(idempotencyKey, "utf8").digest("hex");
166171
- return path319.join(turnsRoot(dir), "keys", `${digest}.json`);
166608
+ return path320.join(turnsRoot(dir), "keys", `${digest}.json`);
166172
166609
  }
166173
166610
  function adoptLegacyClaim(project, idempotencyKey, dir) {
166174
166611
  const legacy = legacyKeyPath(idempotencyKey, dir);
@@ -166232,7 +166669,7 @@ function ensureTurnDir(turnId, dir) {
166232
166669
  }
166233
166670
  function createTurnRecord(record, dir) {
166234
166671
  ensureTurnDir(record.turnId, dir);
166235
- writeOwnerOnlyFile(path319.join(turnDir(record.turnId, dir), "turn.json"), `${JSON.stringify(record, null, 2)}
166672
+ writeOwnerOnlyFile(path320.join(turnDir(record.turnId, dir), "turn.json"), `${JSON.stringify(record, null, 2)}
166236
166673
  `);
166237
166674
  }
166238
166675
  function appendTurnEvent(event, dir, opts) {
@@ -166241,12 +166678,12 @@ function appendTurnEvent(event, dir, opts) {
166241
166678
  }
166242
166679
  const line = JSON.stringify(event);
166243
166680
  try {
166244
- appendOwnerOnlyLine(path319.join(turnDir(event.turnId, dir), "events.jsonl"), line);
166681
+ appendOwnerOnlyLine(path320.join(turnDir(event.turnId, dir), "events.jsonl"), line);
166245
166682
  } catch (error) {
166246
166683
  if (error?.code !== "ENOENT") {
166247
166684
  throw error;
166248
166685
  }
166249
- appendOwnerOnlyLine(path319.join(ensureTurnDir(event.turnId, dir), "events.jsonl"), line);
166686
+ appendOwnerOnlyLine(path320.join(ensureTurnDir(event.turnId, dir), "events.jsonl"), line);
166250
166687
  }
166251
166688
  return true;
166252
166689
  }
@@ -166254,7 +166691,7 @@ function readTurnEvents(turnId, after = -1, dir) {
166254
166691
  if (!isTurnId(turnId)) {
166255
166692
  return { ok: false, reason: "not-a-turn-id" };
166256
166693
  }
166257
- const read = readTurnFile(path319.join(turnDir(turnId, dir), "events.jsonl"));
166694
+ const read = readTurnFile(path320.join(turnDir(turnId, dir), "events.jsonl"));
166258
166695
  if (!read.ok) {
166259
166696
  if (isDefiniteAbsence2(read.reason)) {
166260
166697
  return { ok: true, value: [] };
@@ -166282,7 +166719,7 @@ function readTurnRecord(turnId, dir) {
166282
166719
  if (!isTurnId(turnId)) {
166283
166720
  return { ok: false, reason: "not-a-turn-id" };
166284
166721
  }
166285
- const read = readTurnFile(path319.join(turnDir(turnId, dir), "turn.json"));
166722
+ const read = readTurnFile(path320.join(turnDir(turnId, dir), "turn.json"));
166286
166723
  if (!read.ok) {
166287
166724
  return { ok: false, reason: read.reason };
166288
166725
  }
@@ -166301,7 +166738,7 @@ function finishTurn(turnId, result, dir) {
166301
166738
  if (!record.ok) {
166302
166739
  return false;
166303
166740
  }
166304
- writeOwnerOnlyFile(path319.join(turnDir(turnId, dir), "turn.json"), `${JSON.stringify({ ...record.value, result }, null, 2)}
166741
+ writeOwnerOnlyFile(path320.join(turnDir(turnId, dir), "turn.json"), `${JSON.stringify({ ...record.value, result }, null, 2)}
166305
166742
  `);
166306
166743
  return true;
166307
166744
  }
@@ -166348,7 +166785,7 @@ function releaseIdempotencyKey(project, idempotencyKey, turnId, dir) {
166348
166785
  // src/lib/serve-turn.ts
166349
166786
  import { randomUUID as randomUUID44 } from "crypto";
166350
166787
  init_hooks();
166351
- import path320 from "path";
166788
+ import path321 from "path";
166352
166789
  init_service4();
166353
166790
  init_service9();
166354
166791
  init_impact_evidence_hook_adapter();
@@ -166419,9 +166856,9 @@ function isUuid(value) {
166419
166856
  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
166857
  }
166421
166858
  function resolveProject(declared, dir) {
166422
- const wanted = path320.resolve(declared);
166859
+ const wanted = path321.resolve(declared);
166423
166860
  for (const entry of listProjects(dir, () => {})) {
166424
- if (path320.resolve(entry.path) === wanted) {
166861
+ if (path321.resolve(entry.path) === wanted) {
166425
166862
  return { ok: true, project: entry.path };
166426
166863
  }
166427
166864
  }
@@ -166494,7 +166931,7 @@ function buildRemoteHookRuntime(opts) {
166494
166931
  });
166495
166932
  emitRemoteHookNotices(opts.projectRoot, formatHookLoadNotices(loaded, {
166496
166933
  projectRoot: opts.projectRoot,
166497
- userFile: path320.join(homeDir, ".keryx", "hooks.json"),
166934
+ userFile: path321.join(homeDir, ".keryx", "hooks.json"),
166498
166935
  surface: "headless"
166499
166936
  }));
166500
166937
  if (!loaded.ok) {
@@ -167543,9 +167980,9 @@ function printHelp23() {
167543
167980
  // src/commands/update.ts
167544
167981
  init_contained_write();
167545
167982
  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";
167983
+ import { readFile as readFile164, readdir as readdir59 } from "fs/promises";
167984
+ import { access as access4, constants as constants5, existsSync as existsSync51 } from "fs";
167985
+ import path322 from "path";
167549
167986
  import { fileURLToPath as fileURLToPath10 } from "url";
167550
167987
  init_catalog2();
167551
167988
  init_model_choice();
@@ -167560,17 +167997,17 @@ init_ui();
167560
167997
  init_install_plan();
167561
167998
  init_templates3();
167562
167999
  function containFromMetaprojectPath(filePath) {
167563
- const marker = `${path321.sep}.metaproject${path321.sep}`;
168000
+ const marker = `${path322.sep}.metaproject${path322.sep}`;
167564
168001
  const idx = filePath.lastIndexOf(marker);
167565
168002
  if (idx < 0) {
167566
- if (filePath.endsWith(`${path321.sep}.metaproject`)) {
167567
- const root = filePath.slice(0, filePath.length - ".metaproject".length - path321.sep.length);
168003
+ if (filePath.endsWith(`${path322.sep}.metaproject`)) {
168004
+ const root = filePath.slice(0, filePath.length - ".metaproject".length - path322.sep.length);
167568
168005
  return { root, rel: ".metaproject" };
167569
168006
  }
167570
168007
  throw new Error(`${filePath}: expected a path under a .metaproject directory`);
167571
168008
  }
167572
168009
  const root = filePath.slice(0, idx);
167573
- const rel = filePath.slice(idx + path321.sep.length).split(path321.sep).join("/");
168010
+ const rel = filePath.slice(idx + path322.sep.length).split(path322.sep).join("/");
167574
168011
  return { root, rel };
167575
168012
  }
167576
168013
  async function updateCommand(args = []) {
@@ -167580,8 +168017,8 @@ async function updateCommand(args = []) {
167580
168017
  return;
167581
168018
  }
167582
168019
  const projectRoot = process.cwd();
167583
- const metaprojectRoot = path321.join(projectRoot, ".metaproject");
167584
- banner("keryx update", `Refreshing the .metaproject workspace in ${path321.basename(projectRoot)}/`);
168020
+ const metaprojectRoot = path322.join(projectRoot, ".metaproject");
168021
+ banner("keryx update", `Refreshing the .metaproject workspace in ${path322.basename(projectRoot)}/`);
167585
168022
  if (!await pathExists(metaprojectRoot)) {
167586
168023
  console.log(` ${style.red(symbols.cross)} Metaproject is not initialized.`);
167587
168024
  console.log(` ${style.cyan(symbols.arrow)} Run ${style.cyan("keryx init")} first.`);
@@ -167657,7 +168094,7 @@ async function updateCommand(args = []) {
167657
168094
  nextSteps(steps);
167658
168095
  }
167659
168096
  async function previewServiceFiles(projectRoot, options) {
167660
- const metaprojectRoot = path321.join(projectRoot, ".metaproject");
168097
+ const metaprojectRoot = path322.join(projectRoot, ".metaproject");
167661
168098
  const manifestState = await readManifest5(metaprojectRoot);
167662
168099
  const manifest = manifestState.manifest;
167663
168100
  const enableTasks = moduleEnabled2(manifest, "tasks") || !options.noTasks;
@@ -167686,13 +168123,13 @@ async function previewServiceFiles(projectRoot, options) {
167686
168123
  });
167687
168124
  }
167688
168125
  async function refreshServiceFiles(projectRoot, options) {
167689
- const metaprojectRoot = path321.join(projectRoot, ".metaproject");
168126
+ const metaprojectRoot = path322.join(projectRoot, ".metaproject");
167690
168127
  const manifestState = await readManifest5(metaprojectRoot);
167691
168128
  const manifest = manifestState.manifest;
167692
168129
  const recoveredManifest = !manifestState.exists || !manifestState.valid;
167693
168130
  if (manifestState.migrated) {
167694
168131
  {
167695
- const { root, rel } = containFromMetaprojectPath(path321.join(metaprojectRoot, "metaproject.json"));
168132
+ const { root, rel } = containFromMetaprojectPath(path322.join(metaprojectRoot, "metaproject.json"));
167696
168133
  await writeContained(root, rel, `${JSON.stringify(manifest, null, 2)}
167697
168134
  `);
167698
168135
  }
@@ -167732,9 +168169,9 @@ async function refreshServiceFiles(projectRoot, options) {
167732
168169
  enableSecurity,
167733
168170
  enableSac
167734
168171
  });
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 }));
168172
+ await writeTextIfChanged4(path322.join(metaprojectRoot, "core", "README.md"), renderMetaprojectCoreReadme());
168173
+ await writeTextIfChanged4(path322.join(metaprojectRoot, "hooks", "README.md"), renderHooksReadme());
168174
+ await writeTextIfChanged4(path322.join(metaprojectRoot, "skills", "project-rules", "README.md"), renderProjectRulesSkillReadme({ sources: ruleSources }));
167738
168175
  await writeRoutingEntrypointPair(metaprojectRoot, {
167739
168176
  enableGdgraph,
167740
168177
  enableGdctx,
@@ -167758,7 +168195,7 @@ async function refreshServiceFiles(projectRoot, options) {
167758
168195
  }
167759
168196
  });
167760
168197
  console.log(` ${await describeModelChoiceStatus(projectRoot)}`);
167761
- await writeTextIfChanged4(path321.join(metaprojectRoot, "keryx-dashboard.html"), renderMetaprojectDashboardHtml({
168198
+ await writeTextIfChanged4(path322.join(metaprojectRoot, "keryx-dashboard.html"), renderMetaprojectDashboardHtml({
167762
168199
  enableGdgraph,
167763
168200
  enableGdctx,
167764
168201
  enableGdwiki,
@@ -167770,7 +168207,7 @@ async function refreshServiceFiles(projectRoot, options) {
167770
168207
  enableSecurity,
167771
168208
  data: dashboardData
167772
168209
  }));
167773
- await writeTextIfMissing4(path321.join(metaprojectRoot, "README.md"), renderMetaprojectReadme({
168210
+ await writeTextIfMissing4(path322.join(metaprojectRoot, "README.md"), renderMetaprojectReadme({
167774
168211
  enableGdgraph,
167775
168212
  enableGdctx,
167776
168213
  enableGdwiki,
@@ -167783,9 +168220,9 @@ async function refreshServiceFiles(projectRoot, options) {
167783
168220
  }));
167784
168221
  if (enableGdgraph) {
167785
168222
  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());
168223
+ await writeTextIfChanged4(path322.join(metaprojectRoot, "modules", "gdgraph.md"), renderGdgraphManifest());
168224
+ await writeTextIfChanged4(path322.join(metaprojectRoot, "core", "gdgraph", "README.md"), renderGdgraphCoreReadme());
168225
+ await writeTextIfChanged4(path322.join(metaprojectRoot, "skills", "gdgraph", "SKILL.md"), renderGdgraphSkillReadme());
167789
168226
  await seedAssetsLock(metaprojectRoot);
167790
168227
  if (manifest.modules?.gdgraph?.hooks?.gitPostCommit) {
167791
168228
  const warning = await installManagedHookOrWarn(projectRoot, "post-commit", "gdgraph-post-commit", renderGdgraphPostCommitHook());
@@ -167794,15 +168231,15 @@ async function refreshServiceFiles(projectRoot, options) {
167794
168231
  }
167795
168232
  }
167796
168233
  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());
168234
+ await writeTextIfMissing4(path322.join(metaprojectRoot, "gdctx.config.json"), renderGdctxConfig());
168235
+ await writeTextIfChanged4(path322.join(metaprojectRoot, "modules", "gdctx.md"), renderGdctxManifest());
168236
+ await writeTextIfChanged4(path322.join(metaprojectRoot, "core", "gdctx", "README.md"), renderGdctxCoreReadme());
168237
+ await writeTextIfChanged4(path322.join(metaprojectRoot, "skills", "gdctx", "SKILL.md"), renderGdctxSkillReadme());
167801
168238
  }
167802
168239
  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());
168240
+ await writeTextIfMissing4(path322.join(metaprojectRoot, "wiki", "templates", "page.md"), renderWikiPageTemplate());
168241
+ await writeTextIfChanged4(path322.join(metaprojectRoot, "modules", "gdwiki.md"), renderGdwikiManifest());
168242
+ await writeTextIfChanged4(path322.join(metaprojectRoot, "skills", "gdwiki", "SKILL.md"), renderGdwikiSkillReadme());
167806
168243
  if (manifest.modules?.gdgraph?.hooks?.gitPostCommit) {
167807
168244
  const warning = await installManagedHookOrWarn(projectRoot, "post-commit", "gdwiki-post-commit", renderGdwikiPostCommitHook());
167808
168245
  if (warning)
@@ -167810,8 +168247,8 @@ async function refreshServiceFiles(projectRoot, options) {
167810
168247
  }
167811
168248
  }
167812
168249
  if (enableSac) {
167813
- await writeTextIfMissing4(path321.join(metaprojectRoot, "modules", "sac.md"), renderSacManifest());
167814
- await writeTextIfChanged4(path321.join(metaprojectRoot, "skills", "sac", "SKILL.md"), renderSacSkillReadme());
168250
+ await writeTextIfMissing4(path322.join(metaprojectRoot, "modules", "sac.md"), renderSacManifest());
168251
+ await writeTextIfChanged4(path322.join(metaprojectRoot, "skills", "sac", "SKILL.md"), renderSacSkillReadme());
167815
168252
  }
167816
168253
  let gdskillsWarnings = [];
167817
168254
  let gdskillsNotices = [];
@@ -167826,10 +168263,10 @@ async function refreshServiceFiles(projectRoot, options) {
167826
168263
  }
167827
168264
  }
167828
168265
  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());
168266
+ await writeTextIfMissing4(path322.join(metaprojectRoot, "health.config.json"), renderHealthConfig());
168267
+ await writeTextIfChanged4(path322.join(metaprojectRoot, "modules", "health.md"), renderHealthManifest());
168268
+ await writeTextIfChanged4(path322.join(metaprojectRoot, "core", "health", "README.md"), renderHealthCoreReadme());
168269
+ await writeTextIfChanged4(path322.join(metaprojectRoot, "skills", "health", "SKILL.md"), renderHealthSkillReadme());
167833
168270
  if (manifest.modules?.health?.hooks?.gitPostCommit) {
167834
168271
  const warning = await installManagedHookOrWarn(projectRoot, "post-commit", "health-post-commit", renderHealthPostCommitHook());
167835
168272
  if (warning)
@@ -167837,16 +168274,16 @@ async function refreshServiceFiles(projectRoot, options) {
167837
168274
  }
167838
168275
  }
167839
168276
  if (enableTesting) {
167840
- await writeTextIfMissing4(path321.join(metaprojectRoot, "testing.config.json"), renderTestingConfig({
168277
+ await writeTextIfMissing4(path322.join(metaprojectRoot, "testing.config.json"), renderTestingConfig({
167841
168278
  postCommitRefresh: Boolean(manifest.modules?.testing?.hooks?.gitPostCommit),
167842
168279
  prePushGate: Boolean(manifest.modules?.testing?.hooks?.prePush)
167843
168280
  }));
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());
168281
+ await writeTextIfChanged4(path322.join(metaprojectRoot, "modules", "testing.md"), renderTestingManifest());
168282
+ await writeTextIfChanged4(path322.join(metaprojectRoot, "core", "testing", "README.md"), renderTestingCoreReadme());
168283
+ await writeTextIfChanged4(path322.join(metaprojectRoot, "skills", "testing", "SKILL.md"), renderTestingSkillReadme());
167847
168284
  if (enableGdwiki) {
167848
- await writeTextIfMissing4(path321.join(metaprojectRoot, "wiki", "testing", "README.md"), renderTestingWikiReadme());
167849
- await writeTextIfMissing4(path321.join(metaprojectRoot, "wiki", "testing", "conventions.md"), renderTestingWikiConventions());
168285
+ await writeTextIfMissing4(path322.join(metaprojectRoot, "wiki", "testing", "README.md"), renderTestingWikiReadme());
168286
+ await writeTextIfMissing4(path322.join(metaprojectRoot, "wiki", "testing", "conventions.md"), renderTestingWikiConventions());
167850
168287
  }
167851
168288
  if (manifest.modules?.testing?.hooks?.gitPostCommit) {
167852
168289
  const warning = await installManagedHookOrWarn(projectRoot, "post-commit", "testing-post-commit", renderTestingPostCommitHook());
@@ -167865,24 +168302,24 @@ async function refreshServiceFiles(projectRoot, options) {
167865
168302
  hookWarnings.push(warning);
167866
168303
  }
167867
168304
  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());
168305
+ await writeTextIfMissing4(path322.join(metaprojectRoot, "memory.config.json"), renderMemoryConfig());
168306
+ await writeTextIfMissing4(path322.join(metaprojectRoot, "memory", "templates", "entry.md"), renderMemoryEntryTemplate());
168307
+ await writeTextIfChanged4(path322.join(metaprojectRoot, "modules", "memory.md"), renderMemoryManifest());
168308
+ await writeTextIfChanged4(path322.join(metaprojectRoot, "core", "memory", "README.md"), renderMemoryCoreReadme());
168309
+ await writeTextIfChanged4(path322.join(metaprojectRoot, "skills", "memory", "SKILL.md"), renderMemorySkillReadme());
167873
168310
  }
167874
168311
  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());
168312
+ await writeTextIfChanged4(path322.join(metaprojectRoot, "flows", "README.md"), renderFlowsReadme());
168313
+ await writeTextIfChanged4(path322.join(metaprojectRoot, "modules", "tasks.md"), renderTasksManifest());
168314
+ await writeTextIfChanged4(path322.join(metaprojectRoot, "skills", "flow", "SKILL.md"), renderFlowSkillRouter());
168315
+ await writeTextIfChanged4(path322.join(metaprojectRoot, "skills", "flow", "init.md"), renderFlowInitSkill());
168316
+ await writeTextIfChanged4(path322.join(metaprojectRoot, "skills", "flow", "manage.md"), renderFlowManageSkill());
168317
+ await writeTextIfChanged4(path322.join(metaprojectRoot, "skills", "flow", "complete.md"), renderFlowCompleteSkill());
167881
168318
  }
167882
168319
  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());
168320
+ await writeTextIfMissing4(path322.join(metaprojectRoot, "security.config.json"), renderSecurityConfig());
168321
+ await writeTextIfChanged4(path322.join(metaprojectRoot, "modules", "security.md"), renderSecurityManifest());
168322
+ await writeTextIfChanged4(path322.join(metaprojectRoot, "core", "security", "README.md"), renderSecurityCoreReadme());
167886
168323
  if (manifest.modules?.security?.hooks?.prePush) {
167887
168324
  const warning = await installManagedHookOrWarn(projectRoot, "pre-push", "security-pre-push", renderSecurityPrePushHook());
167888
168325
  if (warning)
@@ -167940,13 +168377,13 @@ async function refreshServiceFiles(projectRoot, options) {
167940
168377
  };
167941
168378
  }
167942
168379
  async function buildDashboard(projectRoot = process.cwd()) {
167943
- const metaprojectRoot = path321.join(projectRoot, ".metaproject");
168380
+ const metaprojectRoot = path322.join(projectRoot, ".metaproject");
167944
168381
  if (!await pathExists(metaprojectRoot)) {
167945
168382
  throw new Error("Metaproject is not initialized. Run: keryx init");
167946
168383
  }
167947
168384
  const manifest = (await readManifest5(metaprojectRoot)).manifest;
167948
168385
  const data = await collectDashboardData(metaprojectRoot);
167949
- const dashboardPath = path321.join(metaprojectRoot, "keryx-dashboard.html");
168386
+ const dashboardPath = path322.join(metaprojectRoot, "keryx-dashboard.html");
167950
168387
  await writeTextIfChanged4(dashboardPath, renderMetaprojectDashboardHtml({
167951
168388
  enableGdgraph: moduleEnabled2(manifest, "gdgraph"),
167952
168389
  enableGdctx: moduleEnabled2(manifest, "gdctx"),
@@ -167966,7 +168403,7 @@ async function shouldInstallDashboardPostCommitHook(projectRoot, manifest) {
167966
168403
  if (Object.values(modules).some((module) => Boolean(module.hooks?.gitPostCommit))) {
167967
168404
  return true;
167968
168405
  }
167969
- const hookPath = path321.join(projectRoot, ".git", "hooks", "post-commit");
168406
+ const hookPath = path322.join(projectRoot, ".git", "hooks", "post-commit");
167970
168407
  if (!await pathExists(hookPath)) {
167971
168408
  return false;
167972
168409
  }
@@ -167986,11 +168423,11 @@ async function collectDashboardData(metaprojectRoot) {
167986
168423
  if (testing) {
167987
168424
  data.testing = testing;
167988
168425
  }
167989
- const wiki = await collectMarkdownPages(path321.join(metaprojectRoot, "wiki"), "wiki");
168426
+ const wiki = await collectMarkdownPages(path322.join(metaprojectRoot, "wiki"), "wiki");
167990
168427
  if (wiki.length > 0) {
167991
168428
  data.wiki = { pages: wiki };
167992
168429
  }
167993
- const memory = await collectMarkdownPages(path321.join(metaprojectRoot, "memory"), "memory");
168430
+ const memory = await collectMarkdownPages(path322.join(metaprojectRoot, "memory"), "memory");
167994
168431
  if (memory.length > 0) {
167995
168432
  data.memory = { entries: memory };
167996
168433
  }
@@ -168005,19 +168442,19 @@ async function collectDashboardData(metaprojectRoot) {
168005
168442
  return data;
168006
168443
  }
168007
168444
  async function collectTasksDashboardData(metaprojectRoot) {
168008
- const flowsRoot = path321.join(metaprojectRoot, "flows");
168445
+ const flowsRoot = path322.join(metaprojectRoot, "flows");
168009
168446
  if (!await pathExists(flowsRoot)) {
168010
168447
  return null;
168011
168448
  }
168012
168449
  let dirEntries;
168013
168450
  try {
168014
- dirEntries = (await readdir58(flowsRoot, { withFileTypes: true })).filter((entry) => entry.isDirectory() && /^\d{3}-/.test(entry.name)).map((entry) => entry.name).sort();
168451
+ dirEntries = (await readdir59(flowsRoot, { withFileTypes: true })).filter((entry) => entry.isDirectory() && /^\d{3}-/.test(entry.name)).map((entry) => entry.name).sort();
168015
168452
  } catch {
168016
168453
  return null;
168017
168454
  }
168018
168455
  const flows = [];
168019
168456
  for (const dir of dirEntries) {
168020
- const flowPath = path321.join(flowsRoot, dir, "flow.json");
168457
+ const flowPath = path322.join(flowsRoot, dir, "flow.json");
168021
168458
  if (!await pathExists(flowPath)) {
168022
168459
  continue;
168023
168460
  }
@@ -168025,7 +168462,7 @@ async function collectTasksDashboardData(metaprojectRoot) {
168025
168462
  const flow = JSON.parse(await readFile164(flowPath, "utf8"));
168026
168463
  const tasks = Array.isArray(flow.tasks) ? flow.tasks : [];
168027
168464
  let acTotal = 0;
168028
- const acPath = path321.join(flowsRoot, dir, "acceptance-criteria.md");
168465
+ const acPath = path322.join(flowsRoot, dir, "acceptance-criteria.md");
168029
168466
  if (await pathExists(acPath)) {
168030
168467
  const acContent = await readFile164(acPath, "utf8");
168031
168468
  acTotal = (acContent.match(/^- AC\d+:/gm) ?? []).length;
@@ -168079,7 +168516,7 @@ async function collectDashboardDocs(metaprojectRoot, wiki, memory) {
168079
168516
  "data/testing/context.md"
168080
168517
  ];
168081
168518
  for (const href of staticHrefs) {
168082
- const filePath = path321.join(metaprojectRoot, ...href.split("/"));
168519
+ const filePath = path322.join(metaprojectRoot, ...href.split("/"));
168083
168520
  if (!await pathExists(filePath)) {
168084
168521
  continue;
168085
168522
  }
@@ -168096,7 +168533,7 @@ async function collectDashboardDocs(metaprojectRoot, wiki, memory) {
168096
168533
  return docs;
168097
168534
  }
168098
168535
  async function collectHealthDashboardData(metaprojectRoot) {
168099
- const reportPath = path321.join(metaprojectRoot, "data", "health", "artifacts", "latest.json");
168536
+ const reportPath = path322.join(metaprojectRoot, "data", "health", "artifacts", "latest.json");
168100
168537
  if (!await pathExists(reportPath)) {
168101
168538
  return;
168102
168539
  }
@@ -168205,8 +168642,8 @@ function metricToScope(metric) {
168205
168642
  };
168206
168643
  }
168207
168644
  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");
168645
+ const nodesPath = path322.join(metaprojectRoot, "data", "gdgraph", "storage", "nodes.jsonl");
168646
+ const edgesPath = path322.join(metaprojectRoot, "data", "gdgraph", "storage", "edges.jsonl");
168210
168647
  if (!await pathExists(nodesPath) || !await pathExists(edgesPath)) {
168211
168648
  return;
168212
168649
  }
@@ -168257,8 +168694,8 @@ async function collectGraphDashboardData(metaprojectRoot) {
168257
168694
  };
168258
168695
  }
168259
168696
  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");
168697
+ const reportPath = path322.join(metaprojectRoot, "data", "testing", "artifacts", "latest.json");
168698
+ const contextPath = path322.join(metaprojectRoot, "data", "testing", "context.md");
168262
168699
  if (await pathExists(reportPath)) {
168263
168700
  const report = JSON.parse(await readFile164(reportPath, "utf8"));
168264
168701
  const totalTests = numberOrUndefined(report.total);
@@ -168287,7 +168724,7 @@ async function collectMarkdownPages(root, hrefPrefix) {
168287
168724
  const files = await listMarkdownFiles(root);
168288
168725
  const pages = [];
168289
168726
  for (const filePath of files.slice(0, 40)) {
168290
- const relativePath = path321.relative(root, filePath).split(path321.sep).join("/");
168727
+ const relativePath = path322.relative(root, filePath).split(path322.sep).join("/");
168291
168728
  if (relativePath === "index.md" || relativePath.startsWith("templates/")) {
168292
168729
  continue;
168293
168730
  }
@@ -168305,10 +168742,10 @@ async function collectMarkdownPages(root, hrefPrefix) {
168305
168742
  return pages;
168306
168743
  }
168307
168744
  async function listMarkdownFiles(root) {
168308
- const entries = await readdir58(root, { withFileTypes: true });
168745
+ const entries = await readdir59(root, { withFileTypes: true });
168309
168746
  const files = [];
168310
168747
  for (const entry of entries) {
168311
- const fullPath = path321.join(root, entry.name);
168748
+ const fullPath = path322.join(root, entry.name);
168312
168749
  if (entry.isDirectory()) {
168313
168750
  files.push(...await listMarkdownFiles(fullPath));
168314
168751
  } else if (entry.isFile() && entry.name.endsWith(".md")) {
@@ -168356,7 +168793,7 @@ async function writeRecoveredManifest(metaprojectRoot, modules) {
168356
168793
  const manifest = {
168357
168794
  schemaVersion: 1,
168358
168795
  standardVersion: STANDARD_VERSION,
168359
- name: `${path321.basename(path321.dirname(metaprojectRoot))}-metaproject`,
168796
+ name: `${path322.basename(path322.dirname(metaprojectRoot))}-metaproject`,
168360
168797
  createdBy: "keryx",
168361
168798
  profiles: computeProfiles(enabledModuleKeys),
168362
168799
  paths: {
@@ -168447,13 +168884,13 @@ async function writeRecoveredManifest(metaprojectRoot, modules) {
168447
168884
  }
168448
168885
  };
168449
168886
  {
168450
- const { root, rel } = containFromMetaprojectPath(path321.join(metaprojectRoot, "metaproject.json"));
168887
+ const { root, rel } = containFromMetaprojectPath(path322.join(metaprojectRoot, "metaproject.json"));
168451
168888
  await writeContained(root, rel, `${JSON.stringify(manifest, null, 2)}
168452
168889
  `);
168453
168890
  }
168454
168891
  }
168455
168892
  async function enableTasksInManifest(metaprojectRoot) {
168456
- const manifestPath = path321.join(metaprojectRoot, "metaproject.json");
168893
+ const manifestPath = path322.join(metaprojectRoot, "metaproject.json");
168457
168894
  if (!await pathExists(manifestPath)) {
168458
168895
  return;
168459
168896
  }
@@ -168479,7 +168916,7 @@ async function enableTasksInManifest(metaprojectRoot) {
168479
168916
  }
168480
168917
  }
168481
168918
  async function updateManifestAgentEntrypoints(metaprojectRoot, ruleSources) {
168482
- const manifestPath = path321.join(metaprojectRoot, "metaproject.json");
168919
+ const manifestPath = path322.join(metaprojectRoot, "metaproject.json");
168483
168920
  if (!await pathExists(manifestPath)) {
168484
168921
  return;
168485
168922
  }
@@ -168547,61 +168984,61 @@ async function updateRuntime(projectRoot) {
168547
168984
  }
168548
168985
  }
168549
168986
  async function findRuntimeRoot(projectRoot) {
168550
- const projectRuntime = path321.join(projectRoot, ".metaproject", "runtime", "keryx");
168551
- if (await pathExists(path321.join(projectRuntime, ".git"))) {
168987
+ const projectRuntime = path322.join(projectRoot, ".metaproject", "runtime", "keryx");
168988
+ if (await pathExists(path322.join(projectRuntime, ".git"))) {
168552
168989
  return projectRuntime;
168553
168990
  }
168554
168991
  const home = process.env.HOME;
168555
168992
  if (!home) {
168556
168993
  return null;
168557
168994
  }
168558
- const globalRuntime = path321.join(home, ".keryx", "keryx");
168559
- if (await pathExists(path321.join(globalRuntime, ".git"))) {
168995
+ const globalRuntime = path322.join(home, ".keryx", "keryx");
168996
+ if (await pathExists(path322.join(globalRuntime, ".git"))) {
168560
168997
  return globalRuntime;
168561
168998
  }
168562
168999
  return null;
168563
169000
  }
168564
169001
  async function createServiceDirs(metaprojectRoot, modules) {
168565
169002
  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"),
169003
+ path322.join(metaprojectRoot, "core"),
169004
+ path322.join(metaprojectRoot, "hooks", "post-update.d"),
169005
+ path322.join(metaprojectRoot, "modules"),
169006
+ path322.join(metaprojectRoot, "rules"),
169007
+ path322.join(metaprojectRoot, "skills", "project-rules"),
168571
169008
  ...modules.enableGdgraph ? [
168572
- path321.join(metaprojectRoot, "core", "gdgraph"),
168573
- path321.join(metaprojectRoot, "skills", "gdgraph")
169009
+ path322.join(metaprojectRoot, "core", "gdgraph"),
169010
+ path322.join(metaprojectRoot, "skills", "gdgraph")
168574
169011
  ] : [],
168575
169012
  ...modules.enableGdctx ? [
168576
- path321.join(metaprojectRoot, "core", "gdctx"),
168577
- path321.join(metaprojectRoot, "skills", "gdctx")
169013
+ path322.join(metaprojectRoot, "core", "gdctx"),
169014
+ path322.join(metaprojectRoot, "skills", "gdctx")
168578
169015
  ] : [],
168579
169016
  ...modules.enableGdwiki ? [
168580
- path321.join(metaprojectRoot, "skills", "gdwiki"),
168581
- path321.join(metaprojectRoot, "wiki", "templates")
169017
+ path322.join(metaprojectRoot, "skills", "gdwiki"),
169018
+ path322.join(metaprojectRoot, "wiki", "templates")
168582
169019
  ] : [],
168583
169020
  ...modules.enableHealth ? [
168584
- path321.join(metaprojectRoot, "core", "health"),
168585
- path321.join(metaprojectRoot, "skills", "health")
169021
+ path322.join(metaprojectRoot, "core", "health"),
169022
+ path322.join(metaprojectRoot, "skills", "health")
168586
169023
  ] : [],
168587
169024
  ...modules.enableTesting ? [
168588
- path321.join(metaprojectRoot, "core", "testing"),
168589
- path321.join(metaprojectRoot, "skills", "testing")
169025
+ path322.join(metaprojectRoot, "core", "testing"),
169026
+ path322.join(metaprojectRoot, "skills", "testing")
168590
169027
  ] : [],
168591
169028
  ...modules.enableMemory ? [
168592
- path321.join(metaprojectRoot, "core", "memory"),
168593
- path321.join(metaprojectRoot, "skills", "memory"),
168594
- path321.join(metaprojectRoot, "memory", "templates")
169029
+ path322.join(metaprojectRoot, "core", "memory"),
169030
+ path322.join(metaprojectRoot, "skills", "memory"),
169031
+ path322.join(metaprojectRoot, "memory", "templates")
168595
169032
  ] : [],
168596
169033
  ...modules.enableTasks ? [
168597
- path321.join(metaprojectRoot, "flows"),
168598
- path321.join(metaprojectRoot, "skills", "flow")
169034
+ path322.join(metaprojectRoot, "flows"),
169035
+ path322.join(metaprojectRoot, "skills", "flow")
168599
169036
  ] : [],
168600
169037
  ...modules.enableSecurity ? [
168601
- path321.join(metaprojectRoot, "core", "security")
169038
+ path322.join(metaprojectRoot, "core", "security")
168602
169039
  ] : [],
168603
169040
  ...modules.enableSac ? [
168604
- path321.join(metaprojectRoot, "skills", "sac")
169041
+ path322.join(metaprojectRoot, "skills", "sac")
168605
169042
  ] : []
168606
169043
  ];
168607
169044
  await Promise.all(dirs.map((dir) => {
@@ -168610,22 +169047,22 @@ async function createServiceDirs(metaprojectRoot, modules) {
168610
169047
  }));
168611
169048
  }
168612
169049
  async function installGdgraphCoreScripts2(metaprojectRoot) {
168613
- const gdgraphCoreRoot = path321.join(metaprojectRoot, "core", "gdgraph");
169050
+ const gdgraphCoreRoot = path322.join(metaprojectRoot, "core", "gdgraph");
168614
169051
  {
168615
169052
  const { root, rel } = containFromMetaprojectPath(gdgraphCoreRoot);
168616
169053
  await mkdirContained(root, rel);
168617
169054
  }
168618
169055
  for (const file of GDGRAPH_CORE_SOURCES) {
168619
- await copyFileIfChanged2(runtimeSourcePath2(`../gdgraph/${file}`), path321.join(gdgraphCoreRoot, file));
169056
+ await copyFileIfChanged2(runtimeSourcePath2(`../gdgraph/${file}`), path322.join(gdgraphCoreRoot, file));
168620
169057
  }
168621
- await writeTextIfChanged4(path321.join(gdgraphCoreRoot, "cli.ts"), renderGdgraphCoreCli());
169058
+ await writeTextIfChanged4(path322.join(gdgraphCoreRoot, "cli.ts"), renderGdgraphCoreCli());
168622
169059
  }
168623
169060
  async function prePushHasSecurityBlock2(projectRoot) {
168624
169061
  const hooksRoot = await resolveGitHooksRoot(projectRoot);
168625
169062
  if (!hooksRoot) {
168626
169063
  return false;
168627
169064
  }
168628
- const hookPath = path321.join(hooksRoot, "pre-push");
169065
+ const hookPath = path322.join(hooksRoot, "pre-push");
168629
169066
  if (!await pathExists(hookPath)) {
168630
169067
  return false;
168631
169068
  }
@@ -168640,7 +169077,7 @@ async function agentSettingsHasSecuritySentinel2(projectRoot) {
168640
169077
  return (await readFile164(file, "utf8")).includes(AGENT_HOOKS_SENTINEL);
168641
169078
  }
168642
169079
  async function readManifest5(metaprojectRoot) {
168643
- const manifestPath = path321.join(metaprojectRoot, "metaproject.json");
169080
+ const manifestPath = path322.join(metaprojectRoot, "metaproject.json");
168644
169081
  if (!await pathExists(manifestPath)) {
168645
169082
  return {
168646
169083
  exists: false,
@@ -168709,7 +169146,7 @@ async function inferManifestFromExistingMetaproject(metaprojectRoot) {
168709
169146
  }
168710
169147
  async function anyPathExists(root, candidates) {
168711
169148
  for (const candidate of candidates) {
168712
- if (await pathExists(path321.join(root, candidate))) {
169149
+ if (await pathExists(path322.join(root, candidate))) {
168713
169150
  return true;
168714
169151
  }
168715
169152
  }
@@ -168732,13 +169169,13 @@ function parseUpdateArgs(args) {
168732
169169
  };
168733
169170
  }
168734
169171
  async function runPostUpdateHooks(projectRoot) {
168735
- const hooksDir = path321.join(projectRoot, ".metaproject", "hooks", "post-update.d");
169172
+ const hooksDir = path322.join(projectRoot, ".metaproject", "hooks", "post-update.d");
168736
169173
  if (!await pathExists(hooksDir)) {
168737
169174
  return;
168738
169175
  }
168739
- const entries = (await readdir58(hooksDir)).sort();
169176
+ const entries = (await readdir59(hooksDir)).sort();
168740
169177
  for (const entry of entries) {
168741
- const hookPath = path321.join(hooksDir, entry);
169178
+ const hookPath = path322.join(hooksDir, entry);
168742
169179
  try {
168743
169180
  await accessExecutable(hookPath);
168744
169181
  } catch {
@@ -168799,12 +169236,12 @@ async function copyFileIfChanged2(from, to) {
168799
169236
  }
168800
169237
  function runtimeSourcePath2(relativePath) {
168801
169238
  const directPath = fileURLToPath10(new URL(relativePath, import.meta.url));
168802
- if (existsSync50(directPath)) {
169239
+ if (existsSync51(directPath)) {
168803
169240
  return directPath;
168804
169241
  }
168805
169242
  if (relativePath.startsWith("../")) {
168806
- const packagedSourcePath = path321.join(path321.dirname(fileURLToPath10(import.meta.url)), "..", "src", relativePath.slice(3));
168807
- if (existsSync50(packagedSourcePath)) {
169243
+ const packagedSourcePath = path322.join(path322.dirname(fileURLToPath10(import.meta.url)), "..", "src", relativePath.slice(3));
169244
+ if (existsSync51(packagedSourcePath)) {
168808
169245
  return packagedSourcePath;
168809
169246
  }
168810
169247
  }
@@ -168835,7 +169272,7 @@ function printHelp24() {
168835
169272
 
168836
169273
  // src/commands/dashboard.ts
168837
169274
  import { spawn as spawn10 } from "child_process";
168838
- import path322 from "path";
169275
+ import path323 from "path";
168839
169276
  init_args();
168840
169277
  init_ui();
168841
169278
  async function dashboardCommand(args = []) {
@@ -168847,7 +169284,7 @@ async function dashboardCommand(args = []) {
168847
169284
  }
168848
169285
  if (subcommand === "build") {
168849
169286
  const result = await buildDashboard();
168850
- const rel = path322.relative(process.cwd(), result.path);
169287
+ const rel = path323.relative(process.cwd(), result.path);
168851
169288
  console.log(` ${style.green(symbols.ok)} Dashboard built ${style.cyan(symbols.arrow)} ${style.cyan(rel)}`);
168852
169289
  note(`Open it: keryx dashboard open`);
168853
169290
  return;
@@ -168855,7 +169292,7 @@ async function dashboardCommand(args = []) {
168855
169292
  if (subcommand === "open") {
168856
169293
  const result = await buildDashboard();
168857
169294
  await openFile(result.path);
168858
- const rel = path322.relative(process.cwd(), result.path);
169295
+ const rel = path323.relative(process.cwd(), result.path);
168859
169296
  console.log(` ${style.green(symbols.ok)} Opened ${style.cyan(rel)}`);
168860
169297
  return;
168861
169298
  }
@@ -168906,7 +169343,7 @@ import { readFileSync as readFileSync27 } from "fs";
168906
169343
  import { randomUUID as randomUUID46 } from "crypto";
168907
169344
  import { mkdir as mkdir69 } from "fs/promises";
168908
169345
  import { tmpdir as tmpdir12 } from "os";
168909
- import path323 from "path";
169346
+ import path324 from "path";
168910
169347
  import { createInterface as createInterface3 } from "readline";
168911
169348
 
168912
169349
  // src/harness/external-agent-probe.ts
@@ -169168,7 +169605,7 @@ async function runCommand4(args, deps, log) {
169168
169605
  const requestApproval = unattended ? undefined : seams.requestApproval ?? terminalApprover();
169169
169606
  const timeoutRaw = optionValue(args, "--timeout");
169170
169607
  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");
169608
+ const worktreesDir = path324.join(tmpdir12(), "keryx-external-worktrees");
169172
169609
  const worktree = seams.worktree ?? createGitWorktreePort({ repoRoot: cwd, worktreesDir });
169173
169610
  if (seams.worktree === undefined)
169174
169611
  await mkdir69(worktreesDir, { recursive: true });
@@ -169256,8 +169693,8 @@ init_service8();
169256
169693
  init_bundled_eval();
169257
169694
  init_args();
169258
169695
  init_ui();
169259
- import { existsSync as existsSync51, lstatSync as lstatSync7, readFileSync as readFileSync26, writeFileSync as writeFileSync11 } from "fs";
169260
- import path324 from "path";
169696
+ import { existsSync as existsSync52, lstatSync as lstatSync7, readFileSync as readFileSync26, writeFileSync as writeFileSync11 } from "fs";
169697
+ import path325 from "path";
169261
169698
  function resolveDeps(deps) {
169262
169699
  return {
169263
169700
  cwd: deps.cwd ?? process.cwd(),
@@ -169590,11 +170027,11 @@ function generateCommand(args, depsIn) {
169590
170027
  process.exitCode = 1;
169591
170028
  return;
169592
170029
  }
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)) {
170030
+ const bundledAgentsRoot = path325.join(depsIn.bundledRoot ?? defaultBundledRoot(), "agents");
170031
+ const stacksRoot = path325.join(bundledAgentsRoot, "..", "stacks");
170032
+ const packDir = path325.join(stacksRoot, stackId);
170033
+ const packJsonPath = path325.join(packDir, "pack.json");
170034
+ if (!existsSync52(packJsonPath)) {
169598
170035
  error(`No stack pack "${stackId}" found at ${packJsonPath}.`);
169599
170036
  process.exitCode = 1;
169600
170037
  return;
@@ -169640,9 +170077,9 @@ function generateCommand(args, depsIn) {
169640
170077
  }
169641
170078
  const prepared = [];
169642
170079
  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)) {
170080
+ const filePath = path325.join(bundledAgentsRoot, file.fileName);
170081
+ const relative = path325.relative(bundledAgentsRoot, filePath);
170082
+ if (relative.startsWith("..") || path325.isAbsolute(relative)) {
169646
170083
  error(`Refusing to write "${file.fileName}": it resolves outside the bundled agents directory.`);
169647
170084
  process.exitCode = 1;
169648
170085
  return;
@@ -169963,7 +170400,7 @@ function printBootstrapHelp() {
169963
170400
  // src/commands/metrics.ts
169964
170401
  init_args();
169965
170402
  import { readFile as readFile165 } from "fs/promises";
169966
- import path326 from "path";
170403
+ import path327 from "path";
169967
170404
 
169968
170405
  // src/metrics/benchmark.ts
169969
170406
  var RELIABILITIES2 = new Set(["exact", "estimated", "unknown"]);
@@ -170780,7 +171217,7 @@ function buildContainmentManifest(inputs, options = {}) {
170780
171217
 
170781
171218
  // src/metrics/oracle-runner.ts
170782
171219
  import { mkdir as mkdir70, writeFile as writeFile48 } from "fs/promises";
170783
- import path325 from "path";
171220
+ import path326 from "path";
170784
171221
 
170785
171222
  // src/metrics/ir.ts
170786
171223
  function toIdSet(ids) {
@@ -171226,9 +171663,9 @@ function buildEvidenceBundle(input, options = {}) {
171226
171663
  async function persistEvidenceBundle(outDir, bundle, ladder = "metastore") {
171227
171664
  const safeTarget = bundle.target.replace(/[^A-Za-z0-9._/-]/g, "_");
171228
171665
  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));
171666
+ const dir = path326.join(outDir, "bench", ladder, safeTarget, safeCase, bundle.variant, String(bundle.seed));
171230
171667
  await mkdir70(dir, { recursive: true });
171231
- const write = (name, value) => writeFile48(path325.join(dir, name), `${JSON.stringify(value, null, 2)}
171668
+ const write = (name, value) => writeFile48(path326.join(dir, name), `${JSON.stringify(value, null, 2)}
171232
171669
  `, "utf8");
171233
171670
  await Promise.all([
171234
171671
  write("inputs.json", bundle.inputs),
@@ -171268,7 +171705,7 @@ async function metricsCommand(args = [], projectRoot = process.cwd()) {
171268
171705
  console.log("# metrics status");
171269
171706
  console.log("");
171270
171707
  console.log(`root: ${root}`);
171271
- console.log(`enabled: ${await Bun.file(path326.join(projectRoot, ".metaproject", "metaproject.json")).exists() ? "yes" : "no"}`);
171708
+ console.log(`enabled: ${await Bun.file(path327.join(projectRoot, ".metaproject", "metaproject.json")).exists() ? "yes" : "no"}`);
171272
171709
  const latest = await readLatestPointer(root);
171273
171710
  console.log(`latest: ${latest.status}`);
171274
171711
  return;
@@ -171280,7 +171717,7 @@ async function metricsCommand(args = [], projectRoot = process.cwd()) {
171280
171717
  process.exitCode = 1;
171281
171718
  return;
171282
171719
  }
171283
- const record = JSON.parse(await readFile165(path326.resolve(projectRoot, file), "utf8"));
171720
+ const record = JSON.parse(await readFile165(path327.resolve(projectRoot, file), "utf8"));
171284
171721
  const result = validateRunRecord(record);
171285
171722
  console.log(result.valid ? "valid: yes" : "valid: no");
171286
171723
  for (const error of result.errors)
@@ -171305,7 +171742,7 @@ async function metricsCommand(args = [], projectRoot = process.cwd()) {
171305
171742
  process.exitCode = 1;
171306
171743
  return;
171307
171744
  }
171308
- const file = path326.join(metricsRoot(projectRoot), "runs", `${runId}.json`);
171745
+ const file = path327.join(metricsRoot(projectRoot), "runs", `${runId}.json`);
171309
171746
  if (!await Bun.file(file).exists()) {
171310
171747
  console.error(`Run not found: ${runId}`);
171311
171748
  process.exitCode = 1;
@@ -171322,8 +171759,8 @@ async function metricsCommand(args = [], projectRoot = process.cwd()) {
171322
171759
  process.exitCode = 1;
171323
171760
  return;
171324
171761
  }
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"));
171762
+ const a = JSON.parse(await readFile165(path327.join(metricsRoot(projectRoot), "runs", `${runA}.json`), "utf8"));
171763
+ const b = JSON.parse(await readFile165(path327.join(metricsRoot(projectRoot), "runs", `${runB}.json`), "utf8"));
171327
171764
  const comparison = compareExecutionRuns(a, b);
171328
171765
  console.log(stableJson(comparison));
171329
171766
  return;
@@ -171357,8 +171794,8 @@ async function metricsCommand(args = [], projectRoot = process.cwd()) {
171357
171794
  return;
171358
171795
  }
171359
171796
  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))}`);
171797
+ await Bun.write(path327.resolve(projectRoot, out), stableJson(template));
171798
+ console.log(`manifest: ${path327.relative(projectRoot, path327.resolve(projectRoot, out))}`);
171362
171799
  return;
171363
171800
  }
171364
171801
  if (subcommand === "benchmark" && args[1] === "run") {
@@ -171372,7 +171809,7 @@ async function metricsCommand(args = [], projectRoot = process.cwd()) {
171372
171809
  process.exitCode = 1;
171373
171810
  return;
171374
171811
  }
171375
- const raw = JSON.parse(await readFile165(path326.resolve(projectRoot, file), "utf8"));
171812
+ const raw = JSON.parse(await readFile165(path327.resolve(projectRoot, file), "utf8"));
171376
171813
  const input = Array.isArray(raw) ? raw : raw.runs ?? [];
171377
171814
  const result = validatePairedBenchmark(input);
171378
171815
  console.log(stableJson(result));
@@ -171384,7 +171821,7 @@ async function metricsCommand(args = [], projectRoot = process.cwd()) {
171384
171821
  process.exitCode = 1;
171385
171822
  }
171386
171823
  async function loadAffectedSets(projectRoot, file) {
171387
- const raw = JSON.parse(await readFile165(path326.resolve(projectRoot, file), "utf8"));
171824
+ const raw = JSON.parse(await readFile165(path327.resolve(projectRoot, file), "utf8"));
171388
171825
  const map = new Map;
171389
171826
  for (const entry of raw.targets ?? []) {
171390
171827
  if (typeof entry.target === "string")
@@ -171455,7 +171892,7 @@ async function runHarnessLayer(projectRoot, args, ladder) {
171455
171892
  let tasks;
171456
171893
  let model;
171457
171894
  try {
171458
- const raw = JSON.parse(await readFile165(path326.resolve(projectRoot, resultsPath), "utf8"));
171895
+ const raw = JSON.parse(await readFile165(path327.resolve(projectRoot, resultsPath), "utf8"));
171459
171896
  tasks = raw.tasks ?? [];
171460
171897
  model = raw.model;
171461
171898
  } catch (error) {
@@ -171486,7 +171923,7 @@ async function runSafetyCompletionHonestyLayer(projectRoot, args, ladder) {
171486
171923
  let cases;
171487
171924
  let model;
171488
171925
  try {
171489
- const raw = JSON.parse(await readFile165(path326.resolve(projectRoot, resultsPath), "utf8"));
171926
+ const raw = JSON.parse(await readFile165(path327.resolve(projectRoot, resultsPath), "utf8"));
171490
171927
  cases = raw.cases ?? [];
171491
171928
  model = raw.model;
171492
171929
  } catch (error) {
@@ -171511,7 +171948,7 @@ async function runSafetyFalsePremiseLayer(projectRoot, args, ladder) {
171511
171948
  let cases;
171512
171949
  let model;
171513
171950
  try {
171514
- const raw = JSON.parse(await readFile165(path326.resolve(projectRoot, resultsPath), "utf8"));
171951
+ const raw = JSON.parse(await readFile165(path327.resolve(projectRoot, resultsPath), "utf8"));
171515
171952
  cases = raw.cases ?? [];
171516
171953
  model = raw.model;
171517
171954
  } catch (error) {
@@ -171536,7 +171973,7 @@ async function runSafetyContainmentLayer(projectRoot, args, ladder, caseClass) {
171536
171973
  let cases;
171537
171974
  let model;
171538
171975
  try {
171539
- const raw = JSON.parse(await readFile165(path326.resolve(projectRoot, resultsPath), "utf8"));
171976
+ const raw = JSON.parse(await readFile165(path327.resolve(projectRoot, resultsPath), "utf8"));
171540
171977
  cases = raw.cases ?? [];
171541
171978
  model = raw.model;
171542
171979
  } catch (error) {
@@ -171598,12 +172035,12 @@ async function runGdgraphLayer(projectRoot, args, ladder) {
171598
172035
  }
171599
172036
  const manifests = buildOracleManifestsByGold(inputs, { ladder });
171600
172037
  if (outDir) {
171601
- const resolvedOut = path326.resolve(projectRoot, outDir);
172038
+ const resolvedOut = path327.resolve(projectRoot, outDir);
171602
172039
  for (const input of inputs) {
171603
172040
  for (const named of input.golds) {
171604
172041
  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)}`);
172042
+ const dir = await persistEvidenceBundle(path327.join(resolvedOut, named.kind), bundle, ladder);
172043
+ console.error(`bundle[${named.kind}]: ${path327.relative(projectRoot, dir)}`);
171607
172044
  }
171608
172045
  }
171609
172046
  }
@@ -171627,7 +172064,7 @@ async function runGdgraphLayer(projectRoot, args, ladder) {
171627
172064
  return allValid;
171628
172065
  }
171629
172066
  async function loadCoverageMap2(projectRoot, file) {
171630
- const raw = JSON.parse(await readFile165(path326.resolve(projectRoot, file), "utf8"));
172067
+ const raw = JSON.parse(await readFile165(path327.resolve(projectRoot, file), "utf8"));
171631
172068
  return raw.coverageMap ?? {};
171632
172069
  }
171633
172070
  async function runTestingLayer(projectRoot, args, ladder) {
@@ -171664,7 +172101,7 @@ async function runTestingLayer(projectRoot, args, ladder) {
171664
172101
  return result.valid;
171665
172102
  }
171666
172103
  async function loadMemoryGoldK(projectRoot, file) {
171667
- const raw = JSON.parse(await readFile165(path326.resolve(projectRoot, file), "utf8"));
172104
+ const raw = JSON.parse(await readFile165(path327.resolve(projectRoot, file), "utf8"));
171668
172105
  return typeof raw.k === "number" && raw.k > 0 ? raw.k : 3;
171669
172106
  }
171670
172107
  async function runMemoryLayer(projectRoot, args, ladder) {
@@ -171701,11 +172138,11 @@ async function runMemoryLayer(projectRoot, args, ladder) {
171701
172138
  return result.valid;
171702
172139
  }
171703
172140
  async function loadWikiGoldK(projectRoot, file) {
171704
- const raw = JSON.parse(await readFile165(path326.resolve(projectRoot, file), "utf8"));
172141
+ const raw = JSON.parse(await readFile165(path327.resolve(projectRoot, file), "utf8"));
171705
172142
  return typeof raw.k === "number" && raw.k > 0 ? raw.k : 5;
171706
172143
  }
171707
172144
  async function loadWikiGroundedness(projectRoot, file) {
171708
- const raw = JSON.parse(await readFile165(path326.resolve(projectRoot, file), "utf8"));
172145
+ const raw = JSON.parse(await readFile165(path327.resolve(projectRoot, file), "utf8"));
171709
172146
  const map = new Map;
171710
172147
  for (const entry of raw.targets ?? []) {
171711
172148
  if (typeof entry.target !== "string" || !Array.isArray(entry.scores) || entry.scores.length !== 3)
@@ -171761,7 +172198,7 @@ async function runWikiLayer(projectRoot, args, ladder) {
171761
172198
  return result.valid;
171762
172199
  }
171763
172200
  async function loadGdctxFacts(projectRoot, file) {
171764
- const raw = JSON.parse(await readFile165(path326.resolve(projectRoot, file), "utf8"));
172201
+ const raw = JSON.parse(await readFile165(path327.resolve(projectRoot, file), "utf8"));
171765
172202
  const inputs = [];
171766
172203
  for (const entry of raw.inputs ?? []) {
171767
172204
  if (typeof entry.input === "string") {
@@ -171799,7 +172236,7 @@ async function collect(projectRoot, args) {
171799
172236
  process.exitCode = 1;
171800
172237
  return;
171801
172238
  }
171802
- const raw = JSON.parse(await readFile165(path326.resolve(projectRoot, eventFile), "utf8"));
172239
+ const raw = JSON.parse(await readFile165(path327.resolve(projectRoot, eventFile), "utf8"));
171803
172240
  const events = Array.isArray(raw) ? raw : raw.events;
171804
172241
  const startedAt = optionValue(args, "--started-at") ?? events[0]?.timestamp_utc ?? new Date().toISOString();
171805
172242
  const finishedAt = optionValue(args, "--finished-at") ?? events.at(-1)?.timestamp_utc ?? startedAt;
@@ -171815,11 +172252,11 @@ async function collect(projectRoot, args) {
171815
172252
  parentRunId: optionValue(args, "--parent-run-id") ?? null
171816
172253
  });
171817
172254
  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)}`);
172255
+ console.log(`json: ${path327.relative(projectRoot, result.jsonPath)}`);
172256
+ console.log(`markdown: ${path327.relative(projectRoot, result.markdownPath)}`);
171820
172257
  }
171821
172258
  function metricsRoot(projectRoot) {
171822
- return path326.join(projectRoot, ".metaproject", "data", "metrics");
172259
+ return path327.join(projectRoot, ".metaproject", "data", "metrics");
171823
172260
  }
171824
172261
  function printMetricsHelp() {
171825
172262
  console.log(`keryx metrics
@@ -173236,8 +173673,8 @@ init_contained_write();
173236
173673
  init_prompt();
173237
173674
  init_terminal_safe();
173238
173675
  init_agent_hooks();
173239
- import { existsSync as existsSync52, readFileSync as readFileSync28 } from "fs";
173240
- import path327 from "path";
173676
+ import { existsSync as existsSync53, readFileSync as readFileSync28 } from "fs";
173677
+ import path328 from "path";
173241
173678
  init_hooks();
173242
173679
  init_package();
173243
173680
  var CLI_VERSION = package_default.version;
@@ -173253,10 +173690,10 @@ function optConfigDir(deps) {
173253
173690
  return deps.configDir !== undefined ? { configDir: deps.configDir } : {};
173254
173691
  }
173255
173692
  function userHooksPath(homeDir) {
173256
- return path327.join(homeDir, ".keryx", "hooks.json");
173693
+ return path328.join(homeDir, ".keryx", "hooks.json");
173257
173694
  }
173258
173695
  function projectHooksPath(projectRoot) {
173259
- return path327.join(projectRoot, ".metaproject", "hooks.json");
173696
+ return path328.join(projectRoot, ".metaproject", "hooks.json");
173260
173697
  }
173261
173698
  function fail3(message) {
173262
173699
  console.error(message);
@@ -173377,10 +173814,10 @@ async function runList6(args, deps) {
173377
173814
  function argv0Resolves(argv0) {
173378
173815
  if (argv0 === "keryx")
173379
173816
  return true;
173380
- if (path327.isAbsolute(argv0))
173381
- return existsSync52(argv0);
173817
+ if (path328.isAbsolute(argv0))
173818
+ return existsSync53(argv0);
173382
173819
  const pathEnv = process.env.PATH ?? "";
173383
- return pathEnv.split(path327.delimiter).filter((dir) => dir.length > 0).some((dir) => existsSync52(path327.join(dir, argv0)));
173820
+ return pathEnv.split(path328.delimiter).filter((dir) => dir.length > 0).some((dir) => existsSync53(path328.join(dir, argv0)));
173384
173821
  }
173385
173822
  function checkArgvResolutions(registrations) {
173386
173823
  const seen = new Set;
@@ -173835,8 +174272,8 @@ async function writeDocAtomic(filePath, doc, scope, cwd, homeDir) {
173835
174272
  if (scope === "project") {
173836
174273
  await writeContained(cwd, PROJECT_HOOKS_REL, body);
173837
174274
  } else {
173838
- const userConfigDir = path327.join(homeDir, ".keryx");
173839
- if (!existsSync52(userConfigDir)) {
174275
+ const userConfigDir = path328.join(homeDir, ".keryx");
174276
+ if (!existsSync53(userConfigDir)) {
173840
174277
  await mkdirContained(homeDir, ".keryx");
173841
174278
  }
173842
174279
  await writeContained(userConfigDir, "hooks.json", body);
@@ -174093,7 +174530,7 @@ init_ui();
174093
174530
  init_args();
174094
174531
  init_contained_path();
174095
174532
  init_package();
174096
- import path336 from "path";
174533
+ import path337 from "path";
174097
174534
 
174098
174535
  // src/bundle/types.ts
174099
174536
  var BUNDLE_FORMAT_VERSION = "1.0.0";
@@ -174147,13 +174584,13 @@ init_contained_path();
174147
174584
  init_keryx_home();
174148
174585
  init_types3();
174149
174586
  import { lstat as lstat18, readFile as readFile166 } from "fs/promises";
174150
- import path328 from "path";
174587
+ import path329 from "path";
174151
174588
  var MEMORY_FOLDERS = new Set(MEMORY_TYPES.map((entry) => entry.folder));
174152
174589
  function scopeRoot(scope, ctx) {
174153
174590
  if (scope === "user") {
174154
174591
  return userStoreRoot(ctx.env ?? process.env, ctx.homeDir);
174155
174592
  }
174156
- return path328.join(ctx.projectRoot, ".metaproject");
174593
+ return path329.join(ctx.projectRoot, ".metaproject");
174157
174594
  }
174158
174595
  var CONTROL_CHAR_RE = /[\x00-\x1f\x7f\u2028\u2029]/;
174159
174596
  var MARKER_FORGING_RE = /[<>`]|<!--|-->/;
@@ -174342,10 +174779,10 @@ async function targetFor(entry, targetScope, ctx) {
174342
174779
  return { ok: true, absolutePath: containment.absolutePath };
174343
174780
  }
174344
174781
  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)) {
174782
+ const absolute = path329.resolve(root, relPath);
174783
+ const rootResolved = path329.resolve(root);
174784
+ const relative = path329.relative(rootResolved, absolute);
174785
+ if (relative === "" || relative === ".." || relative.startsWith(`..${path329.sep}`) || path329.isAbsolute(relative)) {
174349
174786
  return {
174350
174787
  ok: false,
174351
174788
  refusal: { reason: BUNDLE_REFUSAL.pathEscape, path: relPath, message: `${relPath} resolves outside the scope root` }
@@ -174354,7 +174791,7 @@ async function resolveContainedPathForWrite(root, relPath) {
174354
174791
  return { ok: true, absolutePath: absolute };
174355
174792
  }
174356
174793
  async function refuseSymlinkChain(root, relPath) {
174357
- const rootResolved = path328.resolve(root);
174794
+ const rootResolved = path329.resolve(root);
174358
174795
  try {
174359
174796
  const rootStat = await lstat18(rootResolved);
174360
174797
  if (rootStat.isSymbolicLink()) {
@@ -174367,7 +174804,7 @@ async function refuseSymlinkChain(root, relPath) {
174367
174804
  const segments = relPath.split("/");
174368
174805
  let current = rootResolved;
174369
174806
  for (const segment of segments) {
174370
- current = path328.join(current, segment);
174807
+ current = path329.join(current, segment);
174371
174808
  try {
174372
174809
  const stat = await lstat18(current);
174373
174810
  if (stat.isSymbolicLink()) {
@@ -174395,8 +174832,8 @@ async function readTargetFile(absolutePath) {
174395
174832
  }
174396
174833
  }
174397
174834
  // src/bundle/archive.ts
174398
- import { readdir as readdir59, readFile as readFile167, lstat as lstat19 } from "fs/promises";
174399
- import path329 from "path";
174835
+ import { readdir as readdir60, readFile as readFile167, lstat as lstat19 } from "fs/promises";
174836
+ import path330 from "path";
174400
174837
  import { gzipSync, gunzipSync } from "zlib";
174401
174838
  var BLOCK = 512;
174402
174839
  var BUNDLE_MANIFEST_NAME = "bundle.json";
@@ -174487,11 +174924,11 @@ function buildBundleArchive(files, manifestBytes) {
174487
174924
  const dirs = new Set;
174488
174925
  const sortedPaths = [...files.keys()].sort();
174489
174926
  for (const p of sortedPaths) {
174490
- let dir = path329.dirname(p);
174927
+ let dir = path330.dirname(p);
174491
174928
  const segs = [];
174492
174929
  while (dir !== "." && dir !== "/" && dir !== "") {
174493
174930
  segs.unshift(dir);
174494
- dir = path329.dirname(dir);
174931
+ dir = path330.dirname(dir);
174495
174932
  }
174496
174933
  for (const d of segs)
174497
174934
  dirs.add(d);
@@ -174559,7 +174996,7 @@ function parseUstar(tar, limits) {
174559
174996
  const name = prefix.length > 0 ? `${prefix}/${nameRaw}` : nameRaw;
174560
174997
  const typeflag = String.fromCharCode(header[156] ?? 0);
174561
174998
  const size = readOctal(header, 124, 12);
174562
- if (path329.isAbsolute(name) || name.split("/").includes("..") || name.length === 0) {
174999
+ if (path330.isAbsolute(name) || name.split("/").includes("..") || name.length === 0) {
174563
175000
  return { ok: false, refusal: { reason: BUNDLE_REFUSAL.archiveInvalid, path: name, message: `unsafe entry name: ${name}` } };
174564
175001
  }
174565
175002
  const isDir = typeflag === "5";
@@ -174621,9 +175058,9 @@ async function openBundle(bundlePath, limits) {
174621
175058
  let entryCount = 0;
174622
175059
  let totalBytes = 0;
174623
175060
  async function walk(dir, relPrefix) {
174624
- const entries = await readdir59(dir, { withFileTypes: true });
175061
+ const entries = await readdir60(dir, { withFileTypes: true });
174625
175062
  for (const entry of entries.sort((a, b) => a.name.localeCompare(b.name))) {
174626
- const abs = path329.join(dir, entry.name);
175063
+ const abs = path330.join(dir, entry.name);
174627
175064
  const rel = relPrefix.length > 0 ? `${relPrefix}/${entry.name}` : entry.name;
174628
175065
  const entryStat = await lstat19(abs);
174629
175066
  if (entryStat.isSymbolicLink()) {
@@ -175010,9 +175447,9 @@ init_validator();
175010
175447
  init_frontmatter();
175011
175448
  init_types3();
175012
175449
  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";
175450
+ import { mkdir as mkdir71, readdir as readdir61, readFile as readFile168, writeFile as writeFile50, lstat as lstat20 } from "fs/promises";
175451
+ import { existsSync as existsSync54 } from "fs";
175452
+ import path331 from "path";
175016
175453
  // docs/requirements/keryx-agent-platform-expansion/schemas/hook-config.schema.json
175017
175454
  var hook_config_schema_default2 = {
175018
175455
  $schema: "https://json-schema.org/draft/2020-12/schema",
@@ -175556,12 +175993,12 @@ function contentDigestBundleId(scope, entries) {
175556
175993
  async function walkFiles2(dir, relPrefix, out, skipped) {
175557
175994
  let entries;
175558
175995
  try {
175559
- entries = await readdir60(dir, { withFileTypes: true });
175996
+ entries = await readdir61(dir, { withFileTypes: true });
175560
175997
  } catch {
175561
175998
  return;
175562
175999
  }
175563
176000
  for (const entry of entries.sort((a, b) => a.name.localeCompare(b.name))) {
175564
- const abs = path330.join(dir, entry.name);
176001
+ const abs = path331.join(dir, entry.name);
175565
176002
  const rel = relPrefix.length > 0 ? `${relPrefix}/${entry.name}` : entry.name;
175566
176003
  const st = await lstat20(abs);
175567
176004
  if (st.isSymbolicLink()) {
@@ -175578,8 +176015,8 @@ async function walkFiles2(dir, relPrefix, out, skipped) {
175578
176015
  async function collectSkills(root, scope, skipped) {
175579
176016
  const candidates = [];
175580
176017
  for (const baseDirName of scope === "project" ? ["skills", "project-skills"] : ["skills"]) {
175581
- const base = path330.join(root, baseDirName);
175582
- if (!existsSync53(base))
176018
+ const base = path331.join(root, baseDirName);
176019
+ if (!existsSync54(base))
175583
176020
  continue;
175584
176021
  const files = [];
175585
176022
  await walkFiles2(base, baseDirName, files, skipped);
@@ -175591,26 +176028,26 @@ async function collectSkills(root, scope, skipped) {
175591
176028
  async function collectRules(root, scope, skipped) {
175592
176029
  if (scope === "user")
175593
176030
  return [];
175594
- const base = path330.join(root, "rules");
175595
- if (!existsSync53(base))
176031
+ const base = path331.join(root, "rules");
176032
+ if (!existsSync54(base))
175596
176033
  return [];
175597
176034
  const files = [];
175598
176035
  await walkFiles2(base, "rules", files, skipped);
175599
176036
  return files.filter((f) => /\.(md|mdc)$/.test(f.path)).map((f) => ({ bundlePath: f.path, kind: "rule", absolutePath: f.abs }));
175600
176037
  }
175601
176038
  async function collectAgents(root, skipped) {
175602
- const base = path330.join(root, "agents");
175603
- if (!existsSync53(base))
176039
+ const base = path331.join(root, "agents");
176040
+ if (!existsSync54(base))
175604
176041
  return [];
175605
176042
  let entries;
175606
176043
  try {
175607
- entries = await readdir60(base, { withFileTypes: true });
176044
+ entries = await readdir61(base, { withFileTypes: true });
175608
176045
  } catch {
175609
176046
  return [];
175610
176047
  }
175611
176048
  const candidates = [];
175612
176049
  for (const entry of entries.sort((a, b) => a.name.localeCompare(b.name))) {
175613
- const abs = path330.join(base, entry.name);
176050
+ const abs = path331.join(base, entry.name);
175614
176051
  const st = await lstat20(abs);
175615
176052
  if (st.isSymbolicLink()) {
175616
176053
  skipped.push({ path: `agents/${entry.name}`, reason: "symlink" });
@@ -175625,17 +176062,17 @@ async function collectAgents(root, skipped) {
175625
176062
  async function collectMemory(root, skipped) {
175626
176063
  const candidates = [];
175627
176064
  for (const { folder } of MEMORY_TYPES) {
175628
- const base = path330.join(root, "memory", folder);
175629
- if (!existsSync53(base))
176065
+ const base = path331.join(root, "memory", folder);
176066
+ if (!existsSync54(base))
175630
176067
  continue;
175631
176068
  let entries;
175632
176069
  try {
175633
- entries = await readdir60(base, { withFileTypes: true });
176070
+ entries = await readdir61(base, { withFileTypes: true });
175634
176071
  } catch {
175635
176072
  continue;
175636
176073
  }
175637
176074
  for (const entry of entries.sort((a, b) => a.name.localeCompare(b.name))) {
175638
- const abs = path330.join(base, entry.name);
176075
+ const abs = path331.join(base, entry.name);
175639
176076
  const st = await lstat20(abs);
175640
176077
  if (st.isSymbolicLink()) {
175641
176078
  skipped.push({ path: `memory/${folder}/${entry.name}`, reason: "symlink" });
@@ -175649,8 +176086,8 @@ async function collectMemory(root, skipped) {
175649
176086
  return candidates;
175650
176087
  }
175651
176088
  async function collectHookConfig(root) {
175652
- const abs = path330.join(root, "hooks.json");
175653
- if (!existsSync53(abs))
176089
+ const abs = path331.join(root, "hooks.json");
176090
+ if (!existsSync54(abs))
175654
176091
  return [];
175655
176092
  const st = await lstat20(abs);
175656
176093
  if (st.isSymbolicLink() || !st.isFile())
@@ -175659,19 +176096,19 @@ async function collectHookConfig(root) {
175659
176096
  }
175660
176097
  async function collectLearnedPatterns(root, scope) {
175661
176098
  const relDir = scope === "user" ? "learning/patterns" : "data/learning/candidates";
175662
- const base = path330.join(root, ...relDir.split("/"));
175663
- if (!existsSync53(base))
176099
+ const base = path331.join(root, ...relDir.split("/"));
176100
+ if (!existsSync54(base))
175664
176101
  return [];
175665
176102
  let entries;
175666
176103
  try {
175667
- entries = await readdir60(base, { withFileTypes: true });
176104
+ entries = await readdir61(base, { withFileTypes: true });
175668
176105
  } catch {
175669
176106
  return [];
175670
176107
  }
175671
176108
  const candidates = [];
175672
176109
  for (const entry of entries.sort((a, b) => a.name.localeCompare(b.name))) {
175673
176110
  if (entry.isFile() && entry.name.endsWith(".json")) {
175674
- candidates.push({ bundlePath: `${relDir}/${entry.name}`, kind: "learned-pattern", absolutePath: path330.join(base, entry.name) });
176111
+ candidates.push({ bundlePath: `${relDir}/${entry.name}`, kind: "learned-pattern", absolutePath: path331.join(base, entry.name) });
175675
176112
  }
175676
176113
  }
175677
176114
  return candidates;
@@ -175704,7 +176141,7 @@ function rewriteAgentOrigin(content, sourceRef) {
175704
176141
  }
175705
176142
  async function outDirIsEmpty(out) {
175706
176143
  try {
175707
- const entries = await readdir60(out);
176144
+ const entries = await readdir61(out);
175708
176145
  return entries.length === 0;
175709
176146
  } catch {
175710
176147
  return true;
@@ -175712,10 +176149,10 @@ async function outDirIsEmpty(out) {
175712
176149
  }
175713
176150
  async function writeDirectoryBundle(out, manifestBytes, files) {
175714
176151
  await mkdir71(out, { recursive: true });
175715
- await writeFile50(path330.join(out, "bundle.json"), manifestBytes);
176152
+ await writeFile50(path331.join(out, "bundle.json"), manifestBytes);
175716
176153
  for (const [rel, bytes] of files) {
175717
- const abs = path330.join(out, ...rel.split("/"));
175718
- await mkdir71(path330.dirname(abs), { recursive: true });
176154
+ const abs = path331.join(out, ...rel.split("/"));
176155
+ await mkdir71(path331.dirname(abs), { recursive: true });
175719
176156
  await writeFile50(abs, bytes);
175720
176157
  }
175721
176158
  }
@@ -175843,16 +176280,16 @@ async function exportBundle(opts) {
175843
176280
  const manifestBytes = Buffer.from(serializeManifest(manifest), "utf8");
175844
176281
  const isArchive = opts.out.endsWith(".tar.gz") || opts.out.endsWith(".tgz");
175845
176282
  if (isArchive) {
175846
- if (existsSync53(opts.out)) {
176283
+ if (existsSync54(opts.out)) {
175847
176284
  return { ok: false, refusals: [{ reason: BUNDLE_REFUSAL.notABundle, message: `${opts.out} already exists` }] };
175848
176285
  }
175849
176286
  const archive = buildBundleArchive(files, manifestBytes);
175850
176287
  if (!archive.ok)
175851
176288
  return { ok: false, refusals: [archive.refusal] };
175852
- await mkdir71(path330.dirname(opts.out), { recursive: true });
176289
+ await mkdir71(path331.dirname(opts.out), { recursive: true });
175853
176290
  await writeFile50(opts.out, archive.value);
175854
176291
  } else {
175855
- if (existsSync53(opts.out) && !await outDirIsEmpty(opts.out)) {
176292
+ if (existsSync54(opts.out) && !await outDirIsEmpty(opts.out)) {
175856
176293
  return { ok: false, refusals: [{ reason: BUNDLE_REFUSAL.notABundle, message: `${opts.out} already exists and is not empty` }] };
175857
176294
  }
175858
176295
  await writeDirectoryBundle(opts.out, manifestBytes, files);
@@ -175899,16 +176336,16 @@ async function verifyBundlePath(bundlePath) {
175899
176336
  // src/bundle/applied-state.ts
175900
176337
  init_contained_write();
175901
176338
  import { readFile as readFile169 } from "fs/promises";
175902
- import path331 from "path";
176339
+ import path332 from "path";
175903
176340
  var APPLIED_STATE_SCHEMA_VERSION = 2;
175904
176341
  function emptyState() {
175905
176342
  return { schemaVersion: APPLIED_STATE_SCHEMA_VERSION, entries: {} };
175906
176343
  }
175907
176344
  function appliedStatePath(scope, ctx) {
175908
176345
  if (scope === "user") {
175909
- return path331.join(scopeRoot("user", ctx), "bundles", "applied-state.json");
176346
+ return path332.join(scopeRoot("user", ctx), "bundles", "applied-state.json");
175910
176347
  }
175911
- return path331.join(scopeRoot(scope, ctx), "data", "bundles", "applied-state.json");
176348
+ return path332.join(scopeRoot(scope, ctx), "data", "bundles", "applied-state.json");
175912
176349
  }
175913
176350
  function isPlainEntryShape(value) {
175914
176351
  if (typeof value !== "object" || value === null)
@@ -175990,14 +176427,14 @@ async function readAppliedState(ledgerPath) {
175990
176427
  return { ok: false, reason: "corrupt-ledger", message: `applied-state ledger at ${ledgerPath} has an unrecognized shape` };
175991
176428
  }
175992
176429
  function containedRootAndRel(ledgerPath) {
175993
- const marker = `${path331.sep}.metaproject${path331.sep}`;
176430
+ const marker = `${path332.sep}.metaproject${path332.sep}`;
175994
176431
  const idx = ledgerPath.indexOf(marker);
175995
176432
  if (idx >= 0) {
175996
- return { root: ledgerPath.slice(0, idx), rel: ledgerPath.slice(idx + path331.sep.length).split(path331.sep).join("/") };
176433
+ return { root: ledgerPath.slice(0, idx), rel: ledgerPath.slice(idx + path332.sep.length).split(path332.sep).join("/") };
175997
176434
  }
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("/");
176435
+ const bundlesDir = path332.dirname(ledgerPath);
176436
+ const root = path332.dirname(bundlesDir);
176437
+ const rel = path332.join(path332.basename(bundlesDir), path332.basename(ledgerPath)).split(path332.sep).join("/");
176001
176438
  return { root, rel };
176002
176439
  }
176003
176440
  async function writeAppliedState(ledgerPath, state) {
@@ -176018,17 +176455,17 @@ init_service8();
176018
176455
  init_fs();
176019
176456
  init_symlink_safety();
176020
176457
  import { lstat as lstat21, mkdir as mkdir72, open as open8, readFile as readFile170 } from "fs/promises";
176021
- import path332 from "path";
176458
+ import path333 from "path";
176022
176459
  var PRIVATE_DIR_GITIGNORE = `# keryx:private-dir \u2014 managed by keryx; this directory stays out of version control
176023
176460
  *
176024
176461
  !.gitignore
176025
176462
  `;
176026
176463
  async function checkPrivateDirGitignore(dir, root) {
176027
176464
  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("/");
176465
+ const rootResolved = path333.resolve(root);
176466
+ const dirResolved = path333.resolve(dir);
176467
+ const relative = path333.relative(rootResolved, dirResolved);
176468
+ const relativePosix = relative.split(path333.sep).join("/");
176032
176469
  const refusal = await refuseEscapingSymlink(rootResolved, relativePosix);
176033
176470
  if (refusal !== undefined) {
176034
176471
  return {
@@ -176058,7 +176495,7 @@ async function checkPrivateDirGitignore(dir, root) {
176058
176495
  message: `${dir} is a symlink; keryx will not manage a private directory reached through a symlink.`
176059
176496
  };
176060
176497
  }
176061
- const gitignorePath = path332.join(dir, ".gitignore");
176498
+ const gitignorePath = path333.join(dir, ".gitignore");
176062
176499
  let stats;
176063
176500
  try {
176064
176501
  stats = await lstat21(gitignorePath);
@@ -176110,7 +176547,7 @@ async function ensurePrivateDirGitignore(dir, root) {
176110
176547
  if (!check.ok || check.action === "present") {
176111
176548
  return check;
176112
176549
  }
176113
- const gitignorePath = path332.join(dir, ".gitignore");
176550
+ const gitignorePath = path333.join(dir, ".gitignore");
176114
176551
  await mkdir72(dir, { recursive: true });
176115
176552
  try {
176116
176553
  const handle = await open8(gitignorePath, "wx");
@@ -176507,7 +176944,7 @@ async function planBundleImport(opts) {
176507
176944
  init_service4();
176508
176945
  import { mkdtemp as mkdtemp6, mkdir as mkdir73, rm as rm20, writeFile as writeFile51 } from "fs/promises";
176509
176946
  import { tmpdir as tmpdir13 } from "os";
176510
- import path333 from "path";
176947
+ import path334 from "path";
176511
176948
  function entriesToStage(plan) {
176512
176949
  return plan.entries.filter((e) => e.bucket === "new" || e.bucket === "update" || e.bucket === "conflict" && e.forced);
176513
176950
  }
@@ -176517,12 +176954,12 @@ async function auditBundlePlan(plan, opts = {}) {
176517
176954
  return { ok: true, refusals: [], report: null };
176518
176955
  }
176519
176956
  const runAudit = opts.runAudit ?? runHarnessAudit;
176520
- const stagingDir = await mkdtemp6(path333.join(tmpdir13(), "keryx-bundle-audit-"));
176957
+ const stagingDir = await mkdtemp6(path334.join(tmpdir13(), "keryx-bundle-audit-"));
176521
176958
  try {
176522
176959
  try {
176523
176960
  for (const entry of toStage) {
176524
- const abs = path333.join(stagingDir, ...entry.path.split("/"));
176525
- await mkdir73(path333.dirname(abs), { recursive: true });
176961
+ const abs = path334.join(stagingDir, ...entry.path.split("/"));
176962
+ await mkdir73(path334.dirname(abs), { recursive: true });
176526
176963
  await writeFile51(abs, entry.bytes);
176527
176964
  }
176528
176965
  } catch (err) {
@@ -176553,7 +176990,7 @@ async function auditBundlePlan(plan, opts = {}) {
176553
176990
  // src/bundle/apply.ts
176554
176991
  init_contained_write();
176555
176992
  import { mkdir as mkdir74, rmdir as rmdir2, stat as stat33 } from "fs/promises";
176556
- import path334 from "path";
176993
+ import path335 from "path";
176557
176994
  init_keryx_home();
176558
176995
  init_checksum();
176559
176996
  class ApplyAbort extends Error {
@@ -176633,7 +177070,7 @@ async function applyBundlePlan(plan, auditResult, opts = {}) {
176633
177070
  throw new ApplyAbort({ ...previousFile.refusal, path: entry.path });
176634
177071
  }
176635
177072
  const previousBytes = previousFile.bytes ?? null;
176636
- const createdDirs = await mkdirRecordingCreated(path334.dirname(entry.targetPath));
177073
+ const createdDirs = await mkdirRecordingCreated(path335.dirname(entry.targetPath));
176637
177074
  for (const dir of [...createdDirs].reverse())
176638
177075
  rollback.push({ kind: "dir", absolutePath: dir });
176639
177076
  rollback.push({ kind: "file", root, rel: entry.targetRelative, previousBytes });
@@ -176716,14 +177153,14 @@ async function applyBundlePlan(plan, auditResult, opts = {}) {
176716
177153
  }
176717
177154
  async function mkdirRecordingCreated(dir) {
176718
177155
  const missing = [];
176719
- let current = path334.resolve(dir);
177156
+ let current = path335.resolve(dir);
176720
177157
  for (;; ) {
176721
177158
  try {
176722
177159
  await stat33(current);
176723
177160
  break;
176724
177161
  } catch {
176725
177162
  missing.push(current);
176726
- const parent = path334.dirname(current);
177163
+ const parent = path335.dirname(current);
176727
177164
  if (parent === current)
176728
177165
  break;
176729
177166
  current = parent;
@@ -176738,9 +177175,9 @@ function resolveLedgerPathForEntry(entry, opts) {
176738
177175
  if (entry.targetScope === "user") {
176739
177176
  return userStorePaths(opts.env ?? process.env, opts.homeDir).appliedState;
176740
177177
  }
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");
177178
+ const metaprojectIdx = entry.targetPath.indexOf(`${path335.sep}.metaproject${path335.sep}`);
177179
+ const metaprojectRoot = metaprojectIdx === -1 ? path335.join(path335.dirname(entry.targetPath), ".metaproject") : entry.targetPath.slice(0, metaprojectIdx + `${path335.sep}.metaproject`.length);
177180
+ return path335.join(metaprojectRoot, "data", "bundles", "applied-state.json");
176744
177181
  }
176745
177182
  // src/bundle/inspect.ts
176746
177183
  init_matrix();
@@ -176792,28 +177229,28 @@ function defaultCapabilityLookup() {
176792
177229
  }
176793
177230
  // src/bundle/uninstall.ts
176794
177231
  init_contained_write();
176795
- import path335 from "path";
177232
+ import path336 from "path";
176796
177233
  init_checksum();
176797
177234
  var PROTECTED_KIND_ROOTS = new Set(["skills", "project-skills", "rules", "agents", "memory", "learning", "data/learning"]);
176798
177235
  function isContained(root, candidate) {
176799
- const rel = path335.relative(root, candidate);
176800
- return rel === "" || !rel.startsWith(`..${path335.sep}`) && rel !== ".." && !path335.isAbsolute(rel);
177236
+ const rel = path336.relative(root, candidate);
177237
+ return rel === "" || !rel.startsWith(`..${path336.sep}`) && rel !== ".." && !path336.isAbsolute(rel);
176801
177238
  }
176802
177239
  async function removeNowEmptyParents(root, relFilePath) {
176803
- let relDir = path335.posix.dirname(relFilePath);
177240
+ let relDir = path336.posix.dirname(relFilePath);
176804
177241
  while (relDir !== "." && relDir !== "/" && relDir !== "") {
176805
177242
  if (PROTECTED_KIND_ROOTS.has(relDir))
176806
177243
  break;
176807
177244
  const removed = await rmdirIfEmptyContained(root, relDir).catch(() => false);
176808
177245
  if (!removed)
176809
177246
  break;
176810
- relDir = path335.posix.dirname(relDir);
177247
+ relDir = path336.posix.dirname(relDir);
176811
177248
  }
176812
177249
  }
176813
177250
  async function uninstallBundle(opts) {
176814
177251
  const ctx = { projectRoot: opts.projectRoot, env: opts.env, homeDir: opts.homeDir };
176815
177252
  const root = scopeRoot(opts.targetScope, ctx);
176816
- const rootResolved = path335.resolve(root);
177253
+ const rootResolved = path336.resolve(root);
176817
177254
  const ledgerPath = appliedStatePath(opts.targetScope, ctx);
176818
177255
  const ledgerRead = await readAppliedState(ledgerPath);
176819
177256
  if (!ledgerRead.ok) {
@@ -176853,7 +177290,7 @@ async function uninstallBundle(opts) {
176853
177290
  if (!kindCheck.ok) {
176854
177291
  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
177292
  }
176856
- const absolute = path335.resolve(rootResolved, normalized.path);
177293
+ const absolute = path336.resolve(rootResolved, normalized.path);
176857
177294
  if (!isContained(rootResolved, absolute)) {
176858
177295
  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
177296
  }
@@ -177045,7 +177482,7 @@ async function handleExport(args, cwd) {
177045
177482
  const outcome = await exportBundle({
177046
177483
  projectRoot,
177047
177484
  scope: scopeArg,
177048
- out: path336.resolve(cwd, out),
177485
+ out: path337.resolve(cwd, out),
177049
177486
  include: include.length > 0 ? include : undefined,
177050
177487
  kinds: kindsRaw.length > 0 ? kindsRaw : undefined,
177051
177488
  bundleId,
@@ -177092,7 +177529,7 @@ async function handleImport(args, cwd) {
177092
177529
  }
177093
177530
  const projectRoot = resolveBundleProjectRoot(cwd);
177094
177531
  if (external) {
177095
- await handleImportExternal(path336.resolve(cwd, target), projectRoot, { dryRun, json });
177532
+ await handleImportExternal(path337.resolve(cwd, target), projectRoot, { dryRun, json });
177096
177533
  return;
177097
177534
  }
177098
177535
  const targetScopeArg = optionValue([...args], "--target-scope");
@@ -177119,7 +177556,7 @@ async function handleImport(args, cwd) {
177119
177556
  return;
177120
177557
  }
177121
177558
  }
177122
- const opened = await openBundle(path336.resolve(cwd, target));
177559
+ const opened = await openBundle(path337.resolve(cwd, target));
177123
177560
  if (!opened.ok) {
177124
177561
  printRefusals([opened.refusal], json);
177125
177562
  process.exitCode = 1;
@@ -177297,7 +177734,7 @@ async function handleInspect(args, cwd) {
177297
177734
  return;
177298
177735
  }
177299
177736
  const projectRoot = resolveBundleProjectRoot(cwd);
177300
- const result = await inspectBundle(path336.resolve(cwd, bundlePath), {
177737
+ const result = await inspectBundle(path337.resolve(cwd, bundlePath), {
177301
177738
  projectRoot,
177302
177739
  targetScope: targetScopeArg
177303
177740
  });
@@ -177341,7 +177778,7 @@ async function handleVerify(args, cwd) {
177341
177778
  failUsage("keryx bundle verify requires a <bundle> argument (or --external-imports)");
177342
177779
  return;
177343
177780
  }
177344
- const outcome = await verifyBundlePath(path336.resolve(cwd, bundlePath));
177781
+ const outcome = await verifyBundlePath(path337.resolve(cwd, bundlePath));
177345
177782
  if (!outcome.ok) {
177346
177783
  printRefusals(outcome.refusals, json);
177347
177784
  process.exitCode = 1;
@@ -177469,10 +177906,10 @@ function printUninstallHelp2() {
177469
177906
  }
177470
177907
 
177471
177908
  // src/commands/learn.ts
177472
- import { existsSync as existsSync54 } from "fs";
177909
+ import { existsSync as existsSync55 } from "fs";
177473
177910
  import { readFile as readFile171 } from "fs/promises";
177474
177911
  import { createInterface as createInterface4 } from "readline/promises";
177475
- import path337 from "path";
177912
+ import path338 from "path";
177476
177913
  init_contained_path();
177477
177914
  init_service9();
177478
177915
  var STDIN_DEADLINE_MS2 = 2000;
@@ -177495,16 +177932,16 @@ function resolveRoot2(deps) {
177495
177932
  return resolveProjectRoot(deps.cwd ?? process.cwd());
177496
177933
  }
177497
177934
  function resolveLearnRoot(startDir, env) {
177498
- const hasMetaproject = (dir) => existsSync54(path337.join(dir, ".metaproject"));
177935
+ const hasMetaproject = (dir) => existsSync55(path338.join(dir, ".metaproject"));
177499
177936
  const fromEnv = env.CLAUDE_PROJECT_DIR;
177500
177937
  if (typeof fromEnv === "string" && fromEnv.length > 0 && hasMetaproject(fromEnv)) {
177501
177938
  return fromEnv;
177502
177939
  }
177503
- let current = path337.resolve(startDir);
177940
+ let current = path338.resolve(startDir);
177504
177941
  for (;; ) {
177505
177942
  if (hasMetaproject(current))
177506
177943
  return current;
177507
- const parent = path337.dirname(current);
177944
+ const parent = path338.dirname(current);
177508
177945
  if (parent === current)
177509
177946
  return;
177510
177947
  current = parent;
@@ -178169,6 +178606,190 @@ async function helpCommand(rest) {
178169
178606
  process.exitCode = 1;
178170
178607
  }
178171
178608
 
178609
+ // src/lib/group-subcommands.ts
178610
+ var GROUP_SUBCOMMANDS = new Map([
178611
+ ["modules", ["status", "list", "enable", "on", "disable", "off", "interactive", "help"]],
178612
+ ["projects", ["list", "register", "forget", "help"]],
178613
+ ["providers", ["list", "status", "cross-family", "test", "remove"]],
178614
+ ["routing", ["list", "set", "unset", "trust", "profile", "stats"]],
178615
+ ["external", ["on", "off", "status", "list"]],
178616
+ ["auth", ["list", "login", "logout", "status"]],
178617
+ ["version", ["check"]],
178618
+ ["gdgraph", ["build", "query", "find", "symbol", "symbols", "path", "affected", "repomap", "context", "assets"]],
178619
+ ["ctx", ["status", "diff", "rg", "read", "run", "show", "hook", "install-hook", "uninstall-hook"]],
178620
+ [
178621
+ "wiki",
178622
+ [
178623
+ "status",
178624
+ "new",
178625
+ "index",
178626
+ "collect",
178627
+ "check-links",
178628
+ "validate",
178629
+ "freshness",
178630
+ "refresh",
178631
+ "verify",
178632
+ "migrate-markers",
178633
+ "ask",
178634
+ "sections",
178635
+ "enrich",
178636
+ "context",
178637
+ "backlinks"
178638
+ ]
178639
+ ],
178640
+ ["stack", ["detect"]],
178641
+ ["health", ["run", "status", "gate", "sources", "explain", "baseline", "trend"]],
178642
+ ["metrics", ["status", "validate", "collect", "latest", "show", "compare", "rebuild", "plan", "benchmark"]],
178643
+ ["test", ["init", "analyze", "run", "status", "context", "report", "related", "explain", "coverage-map", "suggest"]],
178644
+ ["memory", ["new", "index", "search", "supersede", "transition", "assets", "ingest", "check", "reflect", "handoff"]],
178645
+ [
178646
+ "flow",
178647
+ [
178648
+ "init",
178649
+ "list",
178650
+ "status",
178651
+ "freeze",
178652
+ "start",
178653
+ "next",
178654
+ "task",
178655
+ "ac",
178656
+ "check-ac",
178657
+ "owner",
178658
+ "implemented",
178659
+ "complete",
178660
+ "confirm",
178661
+ "recover",
178662
+ "block",
178663
+ "unblock",
178664
+ "check",
178665
+ "renumber",
178666
+ "repair-reviews",
178667
+ "plan",
178668
+ "schema"
178669
+ ]
178670
+ ],
178671
+ ["job", ["init", "status", "step", "document", "complete", "list"]],
178672
+ [
178673
+ "review",
178674
+ [
178675
+ "attach",
178676
+ "start",
178677
+ "ingest",
178678
+ "scope",
178679
+ "floor",
178680
+ "blast-radius",
178681
+ "budget",
178682
+ "tier",
178683
+ "comments",
178684
+ "ci-triage",
178685
+ "conform",
178686
+ "jev-rules",
178687
+ "jev-edit-guard",
178688
+ "jev-risk",
178689
+ "jev-scenarios",
178690
+ "jev-docs",
178691
+ "jev-comments",
178692
+ "jev-contract",
178693
+ "jev-triage",
178694
+ "jev-select",
178695
+ "jev-profile",
178696
+ "learn",
178697
+ "loop",
178698
+ "stack",
178699
+ "reviewers",
178700
+ "import",
178701
+ "status",
178702
+ "complete",
178703
+ "lightweight"
178704
+ ]
178705
+ ],
178706
+ ["rules", ["distill", "sync"]],
178707
+ ["standard", ["validate", "doctor", "capabilities", "baseline", "emit"]],
178708
+ [
178709
+ "security",
178710
+ [
178711
+ "status",
178712
+ "scan",
178713
+ "scan-mcp",
178714
+ "audit-harness",
178715
+ "impact-evidence",
178716
+ "check-input",
178717
+ "check-output",
178718
+ "redact",
178719
+ "report",
178720
+ "policy",
178721
+ "incidents",
178722
+ "hooks",
178723
+ "eval"
178724
+ ]
178725
+ ],
178726
+ ["sandbox", ["status"]],
178727
+ ["integrations", ["install", "uninstall", "doctor", "matrix"]],
178728
+ [
178729
+ "workspace",
178730
+ [
178731
+ "create",
178732
+ "list",
178733
+ "show",
178734
+ "add-resource",
178735
+ "archive",
178736
+ "remove-resource",
178737
+ "rename",
178738
+ "overview",
178739
+ "read",
178740
+ "propose",
178741
+ "confirm-review",
178742
+ "review",
178743
+ "dismiss-candidate",
178744
+ "handoff",
178745
+ "collaboration",
178746
+ "policy-readiness",
178747
+ "catch-up",
178748
+ "list-proposals",
178749
+ "help"
178750
+ ]
178751
+ ],
178752
+ ["retention", ["status", "sweep"]],
178753
+ ["forgetting", ["trail", "lookup"]],
178754
+ ["trigger", ["run", "install", "uninstall", "list", "status", "schedule", "resolve"]],
178755
+ ["schedule", ["add", "list", "show", "pause", "resume", "run", "remove"]],
178756
+ ["governance", ["report", "show"]],
178757
+ ["hooks", ["list", "validate", "test", "enable", "disable", "trust", "untrust"]],
178758
+ ["bundle", ["export", "import", "inspect", "verify", "uninstall"]],
178759
+ ["learn", ["observe", "extract", "list", "review", "accept", "reject", "apply", "promote", "graduate", "prune"]],
178760
+ ["sessions", ["list", "fork", "export", "path", "help"]],
178761
+ ["session", ["list", "fork", "export", "path", "help"]],
178762
+ ["serve", ["status", "token", "config"]],
178763
+ ["bus", ["list", "log", "send", "pause", "resume", "prune", "help"]],
178764
+ ["dashboard", ["build", "open"]],
178765
+ [
178766
+ "skills",
178767
+ [
178768
+ "catalog",
178769
+ "install",
178770
+ "doctor",
178771
+ "uninstall",
178772
+ "status",
178773
+ "list",
178774
+ "inspect",
178775
+ "route",
178776
+ "create",
178777
+ "generate",
178778
+ "import",
178779
+ "update",
178780
+ "verify",
178781
+ "learn",
178782
+ "export",
178783
+ "sync",
178784
+ "contracts",
178785
+ "scout",
178786
+ "eval",
178787
+ "judge-check",
178788
+ "stocktake"
178789
+ ]
178790
+ ]
178791
+ ]);
178792
+
178172
178793
  // src/cli.ts
178173
178794
  init_package();
178174
178795
  if (import.meta.main) {
@@ -178178,6 +178799,7 @@ var VERSION2 = package_default.version;
178178
178799
  var CLI_ROUTES = {
178179
178800
  init: initCommand,
178180
178801
  help: helpCommand,
178802
+ doctor: doctorCommand2,
178181
178803
  status: statusCommand,
178182
178804
  modules: modulesCommand,
178183
178805
  projects: projectsCommand,
@@ -178232,6 +178854,15 @@ var CLI_ROUTES = {
178232
178854
  learn: learnCommand,
178233
178855
  "__sandbox-net-forward": sandboxNetForwardCommand
178234
178856
  };
178857
+ var MCP_KNOWN_SUBCOMMANDS = [...MCP_CONSUMER_SUBCOMMANDS, "install", "uninstall", "serve"];
178858
+ function knownSubcommandsFor(command) {
178859
+ if (command === "mcp")
178860
+ return MCP_KNOWN_SUBCOMMANDS;
178861
+ return GROUP_SUBCOMMANDS.get(command);
178862
+ }
178863
+ function groupsWithKnownSubcommands() {
178864
+ return ["mcp", ...GROUP_SUBCOMMANDS.keys()];
178865
+ }
178235
178866
  async function main() {
178236
178867
  setModelTurnPort(async (request) => runModelTurn(request));
178237
178868
  const args = process.argv.slice(2);
@@ -178250,16 +178881,23 @@ async function main() {
178250
178881
  await printCommandHelp(command);
178251
178882
  return;
178252
178883
  }
178884
+ const known = knownSubcommandsFor(command);
178885
+ const sub = args[1];
178886
+ if (known !== undefined && sub !== undefined && !sub.startsWith("-") && !known.includes(sub)) {
178887
+ console.error(formatUnknownCommandMessage(sub, known, `keryx ${command} --help`));
178888
+ process.exitCode = 1;
178889
+ return;
178890
+ }
178253
178891
  await route(args.slice(1));
178254
178892
  return;
178255
178893
  }
178256
- console.error(`Unknown command: ${command}`);
178257
- printHelp30();
178894
+ console.error(formatUnknownCommandMessage(command, Object.keys(CLI_ROUTES).filter((name) => name !== "__sandbox-net-forward")));
178258
178895
  process.exitCode = 1;
178259
178896
  }
178260
178897
  var USAGE_BODY = `Usage:
178261
178898
  keryx Show CLI usage
178262
178899
  keryx help [group|command] Grouped command help by task (--help/-h keep this flat usage)
178900
+ keryx doctor [--json] One page: version, Bun floor, ripgrep, sandbox, providers, MCP, integrations, standard, worktrees, graph/wiki freshness
178263
178901
  keryx shell [-c|--continue] [-r|--resume [id]] [--provider <p>] [--model <m>] [--base-url <url>] [--agent|--chat] [--tui|--no-tui]
178264
178902
  Start TUI agent shell (sessions are per-project)
178265
178903
  keryx sessions list|fork <id>|export <id>|path
@@ -178439,6 +179077,7 @@ var USAGE_BODY = `Usage:
178439
179077
 
178440
179078
  Commands:
178441
179079
  help Grouped command help by task: every verb, in nine onboarding-ordered groups
179080
+ doctor One-page health check with a fix hint per line; --json for {checks:[...]}
178442
179081
  shell Start the interactive TUI agent harness. Use --no-tui or --chat to opt out.
178443
179082
  Sessions: -c continue last in this project, -r [id] resume (per-project).
178444
179083
  sessions List or export per-project shell sessions
@@ -178587,7 +179226,9 @@ export {
178587
179226
  USAGE_BODY,
178588
179227
  exitCodeForError,
178589
179228
  groupUsage,
179229
+ groupsWithKnownSubcommands,
178590
179230
  helpRequestedFor,
179231
+ knownSubcommandsFor,
178591
179232
  main,
178592
179233
  printCommandHelp,
178593
179234
  shouldInterceptHelp