@mrciphersmith/keryx 0.2.17 → 0.2.18

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (3) hide show
  1. package/README.md +26 -0
  2. package/dist/cli.js +686 -279
  3. package/package.json +1 -1
package/dist/cli.js CHANGED
@@ -55,9 +55,12 @@ import {
55
55
  chmodSync,
56
56
  mkdirSync,
57
57
  readFileSync,
58
+ renameSync,
58
59
  statSync,
60
+ unlinkSync,
59
61
  writeFileSync
60
62
  } from "fs";
63
+ import { randomUUID } from "crypto";
61
64
  import { homedir } from "os";
62
65
  import path from "path";
63
66
  function keryxConfigDir(dir) {
@@ -146,6 +149,21 @@ function writeOwnerOnlyFile(file, body) {
146
149
  chmodSync(file, 384);
147
150
  } catch {}
148
151
  }
152
+ function writeOwnerOnlyFileAtomic(file, body) {
153
+ const temporary = `${file}.${process.pid}.${randomUUID()}.tmp`;
154
+ try {
155
+ writeFileSync(temporary, body, { encoding: "utf8", mode: 384 });
156
+ if (process.platform !== "win32") {
157
+ chmodSync(temporary, 384);
158
+ }
159
+ renameSync(temporary, file);
160
+ } catch (cause) {
161
+ try {
162
+ unlinkSync(temporary);
163
+ } catch {}
164
+ throw cause;
165
+ }
166
+ }
149
167
  function ensureKeryxConfigDir(dir) {
150
168
  const base = keryxConfigDir(dir);
151
169
  try {
@@ -174,7 +192,7 @@ __export(exports_fs, {
174
192
  pathExists: () => pathExists,
175
193
  isPathInside: () => isPathInside
176
194
  });
177
- import { randomUUID as randomUUID3 } from "crypto";
195
+ import { randomUUID as randomUUID4 } from "crypto";
178
196
  import { access, mkdir, rename, rm, stat, writeFile } from "fs/promises";
179
197
  import path5 from "path";
180
198
  async function pathExists(filePath) {
@@ -195,7 +213,7 @@ function isPathInside(root, candidate) {
195
213
  async function writeFileAtomic(filePath, content) {
196
214
  const dir = path5.dirname(filePath);
197
215
  await mkdir(dir, { recursive: true });
198
- const tmp = path5.join(dir, `.${path5.basename(filePath)}.${process.pid}.${Date.now()}.${randomUUID3()}.tmp`);
216
+ const tmp = path5.join(dir, `.${path5.basename(filePath)}.${process.pid}.${Date.now()}.${randomUUID4()}.tmp`);
199
217
  try {
200
218
  await writeFile(tmp, content, "utf8");
201
219
  await rename(tmp, filePath);
@@ -11146,7 +11164,7 @@ var init_search = __esm(() => {
11146
11164
  });
11147
11165
 
11148
11166
  // src/memory/report.ts
11149
- import { randomUUID as randomUUID5 } from "crypto";
11167
+ import { randomUUID as randomUUID6 } from "crypto";
11150
11168
  import { access as access2, mkdir as mkdir27, readdir as readdir7, rename as rename3, rm as rm5, stat as stat4, writeFile as writeFile30 } from "fs/promises";
11151
11169
  import path71 from "path";
11152
11170
  function runtimeRoot(cwd) {
@@ -11306,7 +11324,7 @@ class MemoryReportStore {
11306
11324
  nextRunId;
11307
11325
  constructor(dependencies = {}) {
11308
11326
  this.clock = dependencies.clock ?? (() => new Date);
11309
- this.nextRunId = dependencies.runId ?? (() => randomUUID5());
11327
+ this.nextRunId = dependencies.runId ?? (() => randomUUID6());
11310
11328
  }
11311
11329
  async writeReport(input2) {
11312
11330
  const runId = input2.runId ?? this.nextRunId();
@@ -11327,7 +11345,7 @@ class MemoryReportStore {
11327
11345
  if ((await stat4(candidate)).mtimeMs <= staleBefore)
11328
11346
  await rm5(candidate, { recursive: true, force: true });
11329
11347
  }
11330
- const staging = path71.join(tempRoot, `${runId}.${randomUUID5()}`);
11348
+ const staging = path71.join(tempRoot, `${runId}.${randomUUID6()}`);
11331
11349
  try {
11332
11350
  await mkdir27(staging);
11333
11351
  await Promise.all([
@@ -12714,17 +12732,17 @@ import {
12714
12732
  fsyncSync,
12715
12733
  openSync as openSync2,
12716
12734
  realpathSync,
12717
- renameSync,
12735
+ renameSync as renameSync2,
12718
12736
  statSync as statSync3,
12719
- unlinkSync,
12737
+ unlinkSync as unlinkSync2,
12720
12738
  writeFileSync as writeFileSync3
12721
12739
  } from "fs";
12722
12740
  import path3 from "path";
12723
- import { randomUUID as randomUUID2 } from "crypto";
12741
+ import { randomUUID as randomUUID3 } from "crypto";
12724
12742
 
12725
12743
  // src/lib/file-lock.ts
12726
12744
  import { closeSync, mkdirSync as mkdirSync2, openSync, readFileSync as readFileSync2, rmSync, statSync as statSync2, writeFileSync as writeFileSync2 } from "fs";
12727
- import { randomUUID } from "crypto";
12745
+ import { randomUUID as randomUUID2 } from "crypto";
12728
12746
  import path2 from "path";
12729
12747
  var LOCK_STALE_MS = 1e4;
12730
12748
  var LOCK_TIMEOUT_MS = 15000;
@@ -12743,7 +12761,7 @@ function withFileLock(lockPath, fn, options = {}) {
12743
12761
  } catch {
12744
12762
  return null;
12745
12763
  }
12746
- const nonce = randomUUID();
12764
+ const nonce = randomUUID2();
12747
12765
  const started = Date.now();
12748
12766
  const deadline = started + LOCK_TIMEOUT_MS;
12749
12767
  let handle = null;
@@ -12925,7 +12943,7 @@ function quarantineDamagedRegistry(dir, onWarn) {
12925
12943
  }
12926
12944
  function saveProjectRegistry(registry, dir, onWarn) {
12927
12945
  const file = projectRegistryPath(dir);
12928
- const temp = `${file}.${randomUUID2()}.tmp`;
12946
+ const temp = `${file}.${randomUUID3()}.tmp`;
12929
12947
  try {
12930
12948
  ensureKeryxConfigDir(dir);
12931
12949
  const sorted = {
@@ -12940,11 +12958,11 @@ function saveProjectRegistry(registry, dir, onWarn) {
12940
12958
  } finally {
12941
12959
  closeSync2(handle);
12942
12960
  }
12943
- renameSync(temp, file);
12961
+ renameSync2(temp, file);
12944
12962
  return true;
12945
12963
  } catch {
12946
12964
  try {
12947
- unlinkSync(temp);
12965
+ unlinkSync2(temp);
12948
12966
  } catch {}
12949
12967
  return false;
12950
12968
  }
@@ -12981,7 +12999,7 @@ function registerProject(projectPath, options = {}) {
12981
12999
  return { ok: true, entry: existing, created: false };
12982
13000
  }
12983
13001
  const entry = {
12984
- projectId: randomUUID2(),
13002
+ projectId: randomUUID3(),
12985
13003
  path: absolute,
12986
13004
  displayName: options.displayName ?? path3.basename(absolute),
12987
13005
  state: "active",
@@ -13694,7 +13712,7 @@ function sessionDir(projectPath, sessionId, dataDir) {
13694
13712
 
13695
13713
  // src/harness/process/sandbox/network-run.ts
13696
13714
  import { Worker } from "worker_threads";
13697
- import { randomUUID as randomUUID4 } from "crypto";
13715
+ import { randomUUID as randomUUID5 } from "crypto";
13698
13716
  import { existsSync as existsSync3 } from "fs";
13699
13717
  import { rm as rm2, writeFile as writeFile3 } from "fs/promises";
13700
13718
  import { fileURLToPath } from "url";
@@ -13789,7 +13807,7 @@ async function setupNetworkRun(profile, options = {}) {
13789
13807
  for (const cred of options.masks ?? []) {
13790
13808
  if (cred.realValue.length === 0)
13791
13809
  continue;
13792
- const sentinel = `keryx-sentinel-${randomUUID4()}`;
13810
+ const sentinel = `keryx-sentinel-${randomUUID5()}`;
13793
13811
  maskedEnv[cred.name] = sentinel;
13794
13812
  proxyMasks.push({ sentinel, realValue: cred.realValue, injectHosts: cred.injectHosts });
13795
13813
  }
@@ -13799,7 +13817,7 @@ async function setupNetworkRun(profile, options = {}) {
13799
13817
  const trustEnv = {};
13800
13818
  let caPemPath;
13801
13819
  if (tlsTerminate && caCertPem) {
13802
- caPemPath = path9.join(tmpdir(), `keryx-run-ca-${randomUUID4()}.pem`);
13820
+ caPemPath = path9.join(tmpdir(), `keryx-run-ca-${randomUUID5()}.pem`);
13803
13821
  await writeFile3(caPemPath, caCertPem, { mode: 420 });
13804
13822
  trustEnv.SSL_CERT_FILE = caPemPath;
13805
13823
  trustEnv.CURL_CA_BUNDLE = caPemPath;
@@ -17192,7 +17210,8 @@ function renderIndexMarkdown({
17192
17210
  "First choose the capability: graph/navigation, compact context, wiki/domain knowledge, memory, testing, health, security, skills/orchestration, or flow lifecycle.",
17193
17211
  "If the same capability is available through MCP tools/resources, prefer MCP because it preserves structured inputs and outputs. If MCP is unavailable, use the module skill and `keryx` CLI fallback.",
17194
17212
  "Load the narrowest relevant skill/rule before reading broad source files. Do not ask the user which internal command to run unless multiple user-level outcomes are genuinely possible.",
17195
- "When reporting results, name the Metaproject sources used at a high level (for example: graph, wiki, memory, health), not every internal command."
17213
+ "When reporting results, name the Metaproject sources used at a high level (for example: graph, wiki, memory, health), not every internal command.",
17214
+ "Once per session, run `keryx version check --json`. Notify the user only when the result is `update-available`; this is an advisory and never installs anything. A timeout, offline registry, `unavailable`, or `unknown-command` result is non-blocking: never block project work on the check."
17196
17215
  ].map((item) => `- ${item}`).join(`
17197
17216
  `);
17198
17217
  const intentRows = [
@@ -32023,6 +32042,15 @@ var COMMAND_DESCRIPTORS = [
32023
32042
  args: [{ name: "json", type: "bool", required: false, desc: "structured module state" }],
32024
32043
  json: true,
32025
32044
  read: true
32045
+ },
32046
+ {
32047
+ module: "core",
32048
+ command: "version check",
32049
+ summary: "Check the fixed npm latest endpoint for a newer Keryx release.",
32050
+ intent: ["check keryx version", "check for update", "\u043F\u0440\u043E\u0432\u0435\u0440\u044C \u0432\u0435\u0440\u0441\u0438\u044E keryx"],
32051
+ args: [{ name: "json", type: "bool", required: false, desc: "typed structured result" }],
32052
+ json: true,
32053
+ read: true
32026
32054
  }
32027
32055
  ];
32028
32056
  function sortKey(descriptor) {
@@ -34743,7 +34771,7 @@ Reports the workspace root and one enabled/disabled line per module. Use
34743
34771
  // src/commands/harness.ts
34744
34772
  import { readFileSync as readFileSync7, writeFileSync as writeFileSync6 } from "fs";
34745
34773
  import path118 from "path";
34746
- import { randomUUID as randomUUID6 } from "crypto";
34774
+ import { randomUUID as randomUUID7 } from "crypto";
34747
34775
 
34748
34776
  // src/security/harness-scan.ts
34749
34777
  init_guard();
@@ -37093,7 +37121,7 @@ function resolveRuntime(deps) {
37093
37121
  const env = deps?.env ?? process.env;
37094
37122
  const clock = deps?.clock ?? (() => new Date().toISOString());
37095
37123
  let idCounter = 0;
37096
- const idSeq = deps?.idSeq ?? (() => `${randomUUID6()}-${idCounter++}`);
37124
+ const idSeq = deps?.idSeq ?? (() => `${randomUUID7()}-${idCounter++}`);
37097
37125
  return { env, clock, idSeq };
37098
37126
  }
37099
37127
  function shellAllowProfile() {
@@ -37184,7 +37212,7 @@ async function harnessCommand(args, deps) {
37184
37212
  const env = deps?.env ?? process.env;
37185
37213
  const clock = deps?.clock ?? (() => new Date().toISOString());
37186
37214
  let idCounter = 0;
37187
- const idSeq = deps?.idSeq ?? (() => `${randomUUID6()}-${idCounter++}`);
37215
+ const idSeq = deps?.idSeq ?? (() => `${randomUUID7()}-${idCounter++}`);
37188
37216
  const fetchImpl = deps?.fetch ?? globalThis.fetch;
37189
37217
  if (provider === "anthropic") {
37190
37218
  const apiKey = env.ANTHROPIC_API_KEY;
@@ -37301,7 +37329,7 @@ function harnessReplay(args, deps) {
37301
37329
  }
37302
37330
  const clock = deps?.clock ?? (() => new Date().toISOString());
37303
37331
  let idCounter = 0;
37304
- const idSeq = deps?.idSeq ?? (() => `${randomUUID6()}-${idCounter++}`);
37332
+ const idSeq = deps?.idSeq ?? (() => `${randomUUID7()}-${idCounter++}`);
37305
37333
  const fixture = (() => {
37306
37334
  if (parsed.fixture === undefined || parsed.fixture.length === 0) {
37307
37335
  return { ok: true, value: buildReplayFixture(run, { idSeq }), built: true };
@@ -37741,7 +37769,7 @@ function harnessWave(args, deps) {
37741
37769
  // src/commands/shell.ts
37742
37770
  init_make_provider();
37743
37771
  init_orient();
37744
- import { randomUUID as randomUUID9 } from "crypto";
37772
+ import { randomUUID as randomUUID10 } from "crypto";
37745
37773
  import * as readline2 from "readline";
37746
37774
 
37747
37775
  // src/harness/tool/builtin/ask-user-tool.ts
@@ -38385,7 +38413,7 @@ function shellExecTool(root, run = makeCommandRunner(root)) {
38385
38413
  }
38386
38414
 
38387
38415
  // src/harness/tool/builtin/spawn-subagent-tool.ts
38388
- import { createHash as createHash17, randomUUID as randomUUID7 } from "crypto";
38416
+ import { createHash as createHash17, randomUUID as randomUUID8 } from "crypto";
38389
38417
 
38390
38418
  // src/harness/child/ledger.ts
38391
38419
  function decrement(remaining, reservation) {
@@ -39501,7 +39529,7 @@ function sha2565(text) {
39501
39529
  var parentShellPolicy = shellParentProfile;
39502
39530
  var childReadOnlyPolicy = shellChildReadOnlyProfile;
39503
39531
  function createSpawnSubagentTool(deps) {
39504
- const idSeq = deps.idSeq ?? (() => randomUUID7());
39532
+ const idSeq = deps.idSeq ?? (() => randomUUID8());
39505
39533
  const clock = deps.clock ?? (() => new Date().toISOString());
39506
39534
  const parentRunId = deps.parentRunId ?? idSeq();
39507
39535
  const parentSessionId = deps.parentSessionId ?? idSeq();
@@ -40350,11 +40378,11 @@ import {
40350
40378
  existsSync as existsSync23,
40351
40379
  mkdirSync as mkdirSync6,
40352
40380
  readdirSync,
40353
- renameSync as renameSync2,
40381
+ renameSync as renameSync3,
40354
40382
  writeFileSync as writeFileSync7
40355
40383
  } from "fs";
40356
40384
  import path120 from "path";
40357
- import { randomUUID as randomUUID8 } from "crypto";
40385
+ import { randomUUID as randomUUID9 } from "crypto";
40358
40386
  var SESSION_SCHEMA_VERSION = 1;
40359
40387
  function nowIso() {
40360
40388
  return new Date().toISOString();
@@ -40393,7 +40421,7 @@ function tighten(target) {
40393
40421
  function atomicWriteText(file, body) {
40394
40422
  const tmp = `${file}.${process.pid}.${Date.now()}.tmp`;
40395
40423
  writeFileSync7(tmp, body, { encoding: "utf8", mode: 384 });
40396
- renameSync2(tmp, file);
40424
+ renameSync3(tmp, file);
40397
40425
  }
40398
40426
  function atomicWriteJson(file, value) {
40399
40427
  atomicWriteText(file, `${JSON.stringify(value, null, 2)}
@@ -40501,7 +40529,7 @@ function readJsonl2(file) {
40501
40529
  function createSession(opts) {
40502
40530
  const projectPath = resolveProjectRoot(opts.cwd);
40503
40531
  const projectKey2 = projectKeyFromPath(projectPath);
40504
- const id = opts.id ?? randomUUID8();
40532
+ const id = opts.id ?? randomUUID9();
40505
40533
  const dir = sessionDir(projectPath, id, opts.dataDir);
40506
40534
  ensureDir(dir, opts.dataDir);
40507
40535
  const ts = nowIso();
@@ -40944,6 +40972,279 @@ function showComposerChoice(otui, r, dock, request) {
40944
40972
  });
40945
40973
  }
40946
40974
 
40975
+ // src/lib/version-check.ts
40976
+ init_config_dir();
40977
+ init_fs();
40978
+ import path121 from "path";
40979
+ var REGISTRY_URL = "https://registry.npmjs.org/@mrciphersmith%2Fkeryx/latest";
40980
+ var FIXED_INSTALL_COMMAND = "npm install -g @mrciphersmith/keryx@latest";
40981
+ var RESPONSE_BODY_LIMIT_BYTES = 64 * 1024;
40982
+ var VERSION_STRING_LIMIT_CHARS = 64;
40983
+ var REQUEST_TIMEOUT_MS = 2000;
40984
+ var SUCCESS_CACHE_TTL_MS = 24 * 60 * 60 * 1000;
40985
+ var FAILURE_BACKOFF_MS = 15 * 60 * 1000;
40986
+ var CACHE_LOCK_TIMEOUT_MS = 250;
40987
+ var CACHE_LOCK_RETRY_MS = 10;
40988
+ var CACHE_LOCK_STALE_MS = 5000;
40989
+ var SYSTEM_TIMER = {
40990
+ schedule: (callback, delayMs) => setTimeout(callback, delayMs),
40991
+ cancel: (handle) => clearTimeout(handle)
40992
+ };
40993
+ function formatVersionUpdateAdvisory(result) {
40994
+ if (result.status !== "update-available")
40995
+ return;
40996
+ return `Keryx update ${result.currentVersion} \u2192 ${result.latestVersion}
40997
+ ${result.installCommand}`;
40998
+ }
40999
+ 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-]+)*))?$/;
41000
+ function parseSemVer(value) {
41001
+ if (value.length > VERSION_STRING_LIMIT_CHARS)
41002
+ return;
41003
+ const match = STRICT_SEMVER.exec(value);
41004
+ if (match === null)
41005
+ return;
41006
+ const prereleaseText = match[4];
41007
+ const prerelease = [];
41008
+ if (prereleaseText !== undefined) {
41009
+ for (const identifier of prereleaseText.split(".")) {
41010
+ if (/^\d+$/.test(identifier)) {
41011
+ if (identifier.length > 1 && identifier.startsWith("0"))
41012
+ return;
41013
+ prerelease.push(BigInt(identifier));
41014
+ } else {
41015
+ prerelease.push(identifier);
41016
+ }
41017
+ }
41018
+ }
41019
+ return {
41020
+ major: BigInt(match[1]),
41021
+ minor: BigInt(match[2]),
41022
+ patch: BigInt(match[3]),
41023
+ prerelease
41024
+ };
41025
+ }
41026
+ function compareSemVer(left, right) {
41027
+ for (const key of ["major", "minor", "patch"]) {
41028
+ if (left[key] < right[key])
41029
+ return -1;
41030
+ if (left[key] > right[key])
41031
+ return 1;
41032
+ }
41033
+ if (left.prerelease.length === 0 || right.prerelease.length === 0) {
41034
+ if (left.prerelease.length === right.prerelease.length)
41035
+ return 0;
41036
+ return left.prerelease.length === 0 ? 1 : -1;
41037
+ }
41038
+ const length = Math.max(left.prerelease.length, right.prerelease.length);
41039
+ for (let index = 0;index < length; index += 1) {
41040
+ const a = left.prerelease[index];
41041
+ const b = right.prerelease[index];
41042
+ if (a === undefined || b === undefined) {
41043
+ if (a === b)
41044
+ return 0;
41045
+ return a === undefined ? -1 : 1;
41046
+ }
41047
+ if (typeof a === "bigint" && typeof b === "bigint") {
41048
+ if (a < b)
41049
+ return -1;
41050
+ if (a > b)
41051
+ return 1;
41052
+ } else if (typeof a === "bigint") {
41053
+ return -1;
41054
+ } else if (typeof b === "bigint") {
41055
+ return 1;
41056
+ } else if (a !== b) {
41057
+ return a < b ? -1 : 1;
41058
+ }
41059
+ }
41060
+ return 0;
41061
+ }
41062
+ function parseCache(file) {
41063
+ const read = readConfigFile(file);
41064
+ if (!read.ok)
41065
+ return;
41066
+ try {
41067
+ const value = JSON.parse(read.text);
41068
+ if (value === null || typeof value !== "object" || Array.isArray(value))
41069
+ return;
41070
+ const record = value;
41071
+ const cache = {};
41072
+ if (typeof record.latestVersion === "string" && parseSemVer(record.latestVersion) !== undefined) {
41073
+ cache.latestVersion = record.latestVersion;
41074
+ }
41075
+ if (typeof record.successAt === "number" && Number.isFinite(record.successAt))
41076
+ cache.successAt = record.successAt;
41077
+ if (typeof record.failureAt === "number" && Number.isFinite(record.failureAt))
41078
+ cache.failureAt = record.failureAt;
41079
+ return cache;
41080
+ } catch {
41081
+ return;
41082
+ }
41083
+ }
41084
+ function resultFor(currentVersion, current, latestVersion, source) {
41085
+ const latest2 = parseSemVer(latestVersion);
41086
+ return compareSemVer(current, latest2) < 0 ? { status: "update-available", currentVersion, latestVersion, installCommand: FIXED_INSTALL_COMMAND, source } : { status: "up-to-date", currentVersion, latestVersion, source };
41087
+ }
41088
+ function cancelWithoutWaiting(cancel) {
41089
+ try {
41090
+ cancel().catch(() => {});
41091
+ } catch {}
41092
+ }
41093
+ async function boundedResponseText(response) {
41094
+ const declared = response.headers.get("content-length");
41095
+ if (declared !== null && /^\d+$/.test(declared) && BigInt(declared) > BigInt(RESPONSE_BODY_LIMIT_BYTES)) {
41096
+ if (response.body !== null) {
41097
+ cancelWithoutWaiting(() => response.body.cancel());
41098
+ }
41099
+ return;
41100
+ }
41101
+ if (response.body === null)
41102
+ return "";
41103
+ const reader = response.body.getReader();
41104
+ const decoder = new TextDecoder;
41105
+ let size = 0;
41106
+ let text = "";
41107
+ try {
41108
+ for (;; ) {
41109
+ const chunk = await reader.read();
41110
+ if (chunk.done)
41111
+ break;
41112
+ size += chunk.value.byteLength;
41113
+ if (size > RESPONSE_BODY_LIMIT_BYTES) {
41114
+ cancelWithoutWaiting(() => reader.cancel());
41115
+ return;
41116
+ }
41117
+ text += decoder.decode(chunk.value, { stream: true });
41118
+ }
41119
+ return text + decoder.decode();
41120
+ } finally {
41121
+ reader.releaseLock();
41122
+ }
41123
+ }
41124
+ function unavailable(currentVersion, reason, cache) {
41125
+ return {
41126
+ status: "unavailable",
41127
+ currentVersion,
41128
+ reason,
41129
+ ...cache?.latestVersion !== undefined ? { cachedLatestVersion: cache.latestVersion } : {}
41130
+ };
41131
+ }
41132
+ function saveCache(file, cache) {
41133
+ try {
41134
+ writeOwnerOnlyFileAtomic(file, `${JSON.stringify(cache, null, 2)}
41135
+ `);
41136
+ return true;
41137
+ } catch {
41138
+ return false;
41139
+ }
41140
+ }
41141
+ function mergeSuccessfulCache(committed, latestVersion, successAt) {
41142
+ if (committed.latestVersion === undefined || committed.successAt === undefined) {
41143
+ return { latestVersion, successAt };
41144
+ }
41145
+ if (committed.successAt > successAt)
41146
+ return committed;
41147
+ if (committed.successAt < successAt)
41148
+ return { latestVersion, successAt };
41149
+ const committedVersion = parseSemVer(committed.latestVersion);
41150
+ const candidateVersion = parseSemVer(latestVersion);
41151
+ if (committedVersion !== undefined && candidateVersion !== undefined) {
41152
+ const precedence = compareSemVer(committedVersion, candidateVersion);
41153
+ if (precedence > 0)
41154
+ return committed;
41155
+ if (precedence < 0)
41156
+ return { latestVersion, successAt };
41157
+ if (committed.latestVersion >= latestVersion)
41158
+ return committed;
41159
+ }
41160
+ return { latestVersion, successAt };
41161
+ }
41162
+ async function updateCache(file, update) {
41163
+ try {
41164
+ return await withFileLock2(`${file}.lock`, async () => saveCache(file, update(parseCache(file) ?? {})), {
41165
+ timeoutMs: CACHE_LOCK_TIMEOUT_MS,
41166
+ retryMs: CACHE_LOCK_RETRY_MS,
41167
+ staleMs: CACHE_LOCK_STALE_MS
41168
+ });
41169
+ } catch {
41170
+ return false;
41171
+ }
41172
+ }
41173
+ async function checkVersion(options) {
41174
+ const current = parseSemVer(options.currentVersion);
41175
+ if (current === undefined)
41176
+ return unavailable(options.currentVersion, "invalid-current-version");
41177
+ const now = options.now ?? Date.now;
41178
+ const timestamp = now();
41179
+ const configDir = ensureKeryxConfigDir(options.cacheDir);
41180
+ const cacheFile = path121.join(configDir, "version-check.json");
41181
+ const cache = parseCache(cacheFile);
41182
+ if (cache?.latestVersion !== undefined && cache.successAt !== undefined && timestamp - cache.successAt >= 0 && timestamp - cache.successAt < SUCCESS_CACHE_TTL_MS) {
41183
+ return resultFor(options.currentVersion, current, cache.latestVersion, "cache");
41184
+ }
41185
+ if (cache?.failureAt !== undefined && timestamp - cache.failureAt >= 0 && timestamp - cache.failureAt < FAILURE_BACKOFF_MS) {
41186
+ return unavailable(options.currentVersion, "suppressed", cache);
41187
+ }
41188
+ const fetchImpl = options.fetch ?? globalThis.fetch;
41189
+ const controller = new AbortController;
41190
+ const timer = options.timer ?? SYSTEM_TIMER;
41191
+ let timeout;
41192
+ let timeoutScheduled = false;
41193
+ let failure;
41194
+ let latestVersion;
41195
+ try {
41196
+ timeout = timer.schedule(() => controller.abort(), REQUEST_TIMEOUT_MS);
41197
+ timeoutScheduled = true;
41198
+ const response = await fetchImpl(REGISTRY_URL, {
41199
+ method: "GET",
41200
+ headers: { accept: "application/json" },
41201
+ signal: controller.signal,
41202
+ credentials: "omit"
41203
+ });
41204
+ if (!response.ok) {
41205
+ failure = "http";
41206
+ } else {
41207
+ const body = await boundedResponseText(response);
41208
+ if (body === undefined) {
41209
+ failure = "response-too-large";
41210
+ } else {
41211
+ let parsed;
41212
+ try {
41213
+ parsed = JSON.parse(body);
41214
+ } catch {
41215
+ parsed = undefined;
41216
+ }
41217
+ if (parsed === null || typeof parsed !== "object" || Array.isArray(parsed)) {
41218
+ failure = "malformed-response";
41219
+ } else {
41220
+ const record = parsed;
41221
+ if (record.name !== "@mrciphersmith/keryx" || typeof record.version !== "string") {
41222
+ failure = "malformed-response";
41223
+ } else if (parseSemVer(record.version) === undefined) {
41224
+ failure = "invalid-latest-version";
41225
+ } else {
41226
+ latestVersion = record.version;
41227
+ }
41228
+ }
41229
+ }
41230
+ }
41231
+ } catch {
41232
+ failure = controller.signal.aborted ? "timeout" : "network";
41233
+ } finally {
41234
+ if (timeoutScheduled) {
41235
+ try {
41236
+ timer.cancel(timeout);
41237
+ } catch {}
41238
+ }
41239
+ }
41240
+ if (latestVersion === undefined) {
41241
+ await updateCache(cacheFile, (committed) => ({ ...committed, failureAt: timestamp }));
41242
+ return unavailable(options.currentVersion, failure ?? "network", cache);
41243
+ }
41244
+ await updateCache(cacheFile, (committed) => mergeSuccessfulCache(committed, latestVersion, timestamp));
41245
+ return resultFor(options.currentVersion, current, latestVersion, "registry");
41246
+ }
41247
+
40947
41248
  // src/tui/shell-chrome.ts
40948
41249
  function onKeypress2(r, handler) {
40949
41250
  r._internalKeyInput.onInternal("keypress", handler);
@@ -40957,6 +41258,14 @@ var SIDEBAR_BORDER_LEFT = 1;
40957
41258
  var SIDEBAR_PADDING_LEFT = 2;
40958
41259
  var SIDEBAR_PADDING_RIGHT = 1;
40959
41260
  var SIDEBAR_TEXT_WIDTH = SIDEBAR_WIDTH - SIDEBAR_BORDER_LEFT - SIDEBAR_PADDING_LEFT - SIDEBAR_PADDING_RIGHT;
41261
+ function formatSidebarVersionUpdateAdvisory(result) {
41262
+ const advisory = formatVersionUpdateAdvisory(result);
41263
+ if (advisory === undefined || result.status !== "update-available")
41264
+ return;
41265
+ const split = Math.ceil(result.installCommand.length / 2);
41266
+ return advisory.replace(result.installCommand, `${result.installCommand.slice(0, split)}
41267
+ ${result.installCommand.slice(split)}`);
41268
+ }
40960
41269
  var SPINNER = ["\u280B", "\u2819", "\u2839", "\u2838", "\u283C", "\u2834", "\u2826", "\u2827", "\u2807", "\u280F"];
40961
41270
  var SPINNER_MS = 120;
40962
41271
  var TOAST_MS = 5000;
@@ -40986,6 +41295,7 @@ async function createShellChrome(otui, renderer, opts) {
40986
41295
  const toastMs = opts.toastMs ?? TOAST_MS;
40987
41296
  const filter = opts.filterCommands ?? ((query) => prefixFilter(opts.commands, query));
40988
41297
  let uid = 0;
41298
+ let alive = true;
40989
41299
  const rootRow = new otui.BoxRenderable(r, { id: "root-row", flexGrow: 1, flexDirection: "row" });
40990
41300
  r.root.add(rootRow);
40991
41301
  const main = new otui.BoxRenderable(r, { id: "main", flexGrow: 1, minWidth: 0, flexDirection: "column" });
@@ -41004,6 +41314,21 @@ async function createShellChrome(otui, renderer, opts) {
41004
41314
  rootRow.add(sidebar);
41005
41315
  const sidebarTop = new otui.BoxRenderable(r, { id: "sb-top", flexShrink: 0, flexDirection: "column" });
41006
41316
  sidebar.add(sidebarTop);
41317
+ if (opts.versionCheck !== undefined) {
41318
+ const versionNotice = new otui.TextRenderable(r, {
41319
+ id: `sb-version-${uid++}`,
41320
+ content: ""
41321
+ });
41322
+ sidebarTop.add(versionNotice);
41323
+ opts.versionCheck.then((result) => {
41324
+ const advisory = formatSidebarVersionUpdateAdvisory(result);
41325
+ if (!alive || advisory === undefined)
41326
+ return;
41327
+ try {
41328
+ versionNotice.content = otui.t`${otui.yellow(advisory)}`;
41329
+ } catch {}
41330
+ }, () => {});
41331
+ }
41007
41332
  const sidebarSpacer = new otui.BoxRenderable(r, { id: "sb-spacer", flexGrow: 1 });
41008
41333
  sidebar.add(sidebarSpacer);
41009
41334
  const toastText = new otui.TextRenderable(r, { id: "sb-toast", content: "" });
@@ -41384,6 +41709,7 @@ async function createShellChrome(otui, renderer, opts) {
41384
41709
  };
41385
41710
  },
41386
41711
  destroy: () => {
41712
+ alive = false;
41387
41713
  clearBusyTimer();
41388
41714
  clearToastTimer();
41389
41715
  unsubscribeMenuKeys();
@@ -42847,7 +43173,8 @@ async function launchTuiAgentShell(opts) {
42847
43173
  placeholder: "type a task or / for commands \xB7 Enter send \xB7 Shift+Enter newline",
42848
43174
  commands: commandsForMode("agent"),
42849
43175
  headerMeta: "\u21910 \u21930",
42850
- filterCommands: (query) => filterCommands(query, "agent")
43176
+ filterCommands: (query) => filterCommands(query, "agent"),
43177
+ ...opts.versionCheck !== undefined ? { versionCheck: opts.versionCheck } : {}
42851
43178
  });
42852
43179
  mountedChrome = chrome;
42853
43180
  const transcript = chrome.transcript;
@@ -43905,7 +44232,8 @@ async function mountChatShell(otui, renderer, opts) {
43905
44232
  placeholder: "type a message or / for commands \xB7 Enter send \xB7 Shift+Enter newline",
43906
44233
  commands: commandsForMode("chat"),
43907
44234
  headerMeta: "~0",
43908
- filterCommands: (query) => filterCommands(query, "chat")
44235
+ filterCommands: (query) => filterCommands(query, "chat"),
44236
+ ...opts.versionCheck !== undefined ? { versionCheck: opts.versionCheck } : {}
43909
44237
  });
43910
44238
  let uid = 0;
43911
44239
  const transcript = chrome.transcript;
@@ -44057,7 +44385,8 @@ async function launchTuiChatShell(opts) {
44057
44385
  },
44058
44386
  onExit: () => {
44059
44387
  r.destroy();
44060
- }
44388
+ },
44389
+ ...opts.versionCheck !== undefined ? { versionCheck: opts.versionCheck } : {}
44061
44390
  });
44062
44391
  await Promise.race([exited, handle.done]);
44063
44392
  return true;
@@ -44072,6 +44401,76 @@ async function launchTuiChatShell(opts) {
44072
44401
 
44073
44402
  // src/commands/shell.ts
44074
44403
  init_shell_config();
44404
+ // package.json
44405
+ var package_default = {
44406
+ name: "@mrciphersmith/keryx",
44407
+ version: "0.2.18",
44408
+ description: "Version-controlled project context for AI coding agents: code graph, architecture wiki, project memory, relevant tests, quality signals, and task flows.",
44409
+ private: false,
44410
+ publishConfig: {
44411
+ access: "public"
44412
+ },
44413
+ license: "MIT",
44414
+ type: "module",
44415
+ repository: {
44416
+ type: "git",
44417
+ url: "git+ssh://git@github.com/MrCipherSmith/keryx.git"
44418
+ },
44419
+ keywords: [
44420
+ "ai-agents",
44421
+ "coding-agents",
44422
+ "agent-harness",
44423
+ "agent-context",
44424
+ "repository-context",
44425
+ "code-graph",
44426
+ "project-memory",
44427
+ "test-impact-analysis",
44428
+ "developer-tools",
44429
+ "model-context-protocol",
44430
+ "mcp",
44431
+ "claude-code",
44432
+ "cursor",
44433
+ "codex",
44434
+ "cli",
44435
+ "bun"
44436
+ ],
44437
+ bin: {
44438
+ keryx: "./dist/cli.js"
44439
+ },
44440
+ scripts: {
44441
+ keryx: "bun ./src/cli.ts",
44442
+ build: "bun build ./src/cli.ts --outdir ./dist --target bun --external @modelcontextprotocol/sdk --external web-tree-sitter --external @opentui/core && bun build ./src/harness/process/sandbox/proxy-worker.ts --outdir ./dist --target bun --external @modelcontextprotocol/sdk --external web-tree-sitter --external @opentui/core",
44443
+ prepare: "bun run build",
44444
+ typecheck: "tsc --noEmit",
44445
+ test: "bun test",
44446
+ check: "tsc --noEmit && bun test",
44447
+ "check:doc-links": "bun scripts/check-doc-links.ts",
44448
+ "test:guards": "bun test src/lib/config-dir.ast.test.ts src/lib/config-dir.readers.test.ts src/lib/production-graph.test.ts src/harness/policy/profiles.test.ts src/lib/serve-server.test.ts"
44449
+ },
44450
+ files: [
44451
+ "dist",
44452
+ "src/gdgraph",
44453
+ "src/gdskills/bundled",
44454
+ "src/gdskills/contracts",
44455
+ "LICENSE",
44456
+ "README.md",
44457
+ "package.json"
44458
+ ],
44459
+ dependencies: {},
44460
+ optionalDependencies: {
44461
+ "@modelcontextprotocol/sdk": "^1.0.0",
44462
+ "@opentui/core": "^0.4.5",
44463
+ "web-tree-sitter": "^0.22.0"
44464
+ },
44465
+ devDependencies: {
44466
+ "@types/bun": "latest",
44467
+ "bun-types": "latest",
44468
+ typescript: "^5"
44469
+ },
44470
+ engines: {
44471
+ bun: ">=1.1.0"
44472
+ }
44473
+ };
44075
44474
 
44076
44475
  // src/commands/select.ts
44077
44476
  init_guard2();
@@ -44380,6 +44779,7 @@ Starting a new session.
44380
44779
  provider = makeActive();
44381
44780
  };
44382
44781
  for await (const line of io.lines) {
44782
+ io.onSafeBoundary?.();
44383
44783
  if (line.startsWith("/")) {
44384
44784
  const parts = line.trim().split(/\s+/);
44385
44785
  const command = parts[0] ?? "";
@@ -44528,6 +44928,7 @@ Starting a new session.
44528
44928
  } else {
44529
44929
  history.pop();
44530
44930
  }
44931
+ io.onSafeBoundary?.();
44531
44932
  io.write(`
44532
44933
 
44533
44934
  `);
@@ -44609,7 +45010,32 @@ function countRows(text, columns) {
44609
45010
  }
44610
45011
  return rows;
44611
45012
  }
44612
- function createRichIo(lines) {
45013
+ function createVersionAdvisoryBoundary(versionCheck) {
45014
+ let active = true;
45015
+ let pending;
45016
+ let shown = false;
45017
+ versionCheck.then((result) => {
45018
+ if (active)
45019
+ pending = formatVersionUpdateAdvisory(result);
45020
+ }, () => {});
45021
+ return {
45022
+ flush: (write) => {
45023
+ if (!active || shown || pending === undefined)
45024
+ return;
45025
+ shown = true;
45026
+ const advisory = pending;
45027
+ pending = undefined;
45028
+ write(`
45029
+ ${advisory}
45030
+ `);
45031
+ },
45032
+ destroy: () => {
45033
+ active = false;
45034
+ pending = undefined;
45035
+ }
45036
+ };
45037
+ }
45038
+ function createRichIo(lines, versionCheck) {
44613
45039
  const stdout2 = process.stdout;
44614
45040
  const rich = colorEnabled() && Boolean(stdout2.isTTY);
44615
45041
  const out = (s) => {
@@ -44619,6 +45045,7 @@ function createRichIo(lines) {
44619
45045
  let frame = 0;
44620
45046
  let awaitingFirstToken = false;
44621
45047
  let raw = "";
45048
+ const advisory = createVersionAdvisoryBoundary(versionCheck);
44622
45049
  const stopSpinner = () => {
44623
45050
  if (spinner !== undefined) {
44624
45051
  clearInterval(spinner);
@@ -44702,10 +45129,18 @@ ${GUTTER}${style.cyan("\u25C6")} ${style.bold(title)} ${style.dim(subtitle)}
44702
45129
 
44703
45130
  `);
44704
45131
  }
45132
+ advisory.flush(emitSystem);
44705
45133
  printPrompt();
44706
45134
  };
44707
- const io = { lines, write, onTurnStart, onTurnEnd, onSystem: emitSystem };
44708
- return { io, emitSystem, printHeader, printPrompt };
45135
+ const io = {
45136
+ lines,
45137
+ write,
45138
+ onTurnStart,
45139
+ onTurnEnd,
45140
+ onSystem: emitSystem,
45141
+ onSafeBoundary: () => advisory.flush(emitSystem)
45142
+ };
45143
+ return { io, emitSystem, printHeader, printPrompt, destroy: advisory.destroy };
44709
45144
  }
44710
45145
  function turnSeparator() {
44711
45146
  return style.dim("\u2500".repeat(24));
@@ -44926,6 +45361,7 @@ New session ${shortSessionId(live.summary.id)}.
44926
45361
  if (line === undefined) {
44927
45362
  return;
44928
45363
  }
45364
+ rich.safeBoundary?.();
44929
45365
  if (line.startsWith("/")) {
44930
45366
  const parts = line.trim().split(/\s+/);
44931
45367
  const command = parts[0] ?? "";
@@ -45022,6 +45458,7 @@ ${GUTTER}${usageLine}
45022
45458
  ${GUTTER}${turnSeparator()}
45023
45459
 
45024
45460
  `);
45461
+ rich.safeBoundary?.();
45025
45462
  rich.printPrompt();
45026
45463
  }
45027
45464
  }
@@ -45100,13 +45537,17 @@ function chooseShellSurface(flags, isTty) {
45100
45537
  }
45101
45538
  return flags.modeFlag === false ? "tui-chat" : "tui-agent";
45102
45539
  }
45103
- async function shellCommand(args2) {
45540
+ async function shellCommand(args2, runtime = {}) {
45541
+ const versionCheck = (runtime.checkVersion ?? (() => checkVersion({
45542
+ currentVersion: package_default.version,
45543
+ ...runtime.cacheDir !== undefined ? { cacheDir: runtime.cacheDir } : {}
45544
+ })))();
45104
45545
  const flags = parseShellCliFlags(args2);
45105
45546
  let providerArg = flags.providerArg;
45106
45547
  let modelArg = flags.modelArg;
45107
45548
  let baseUrl = flags.baseUrl;
45108
45549
  let modeFlag = flags.modeFlag;
45109
- const surface = chooseShellSurface(flags, process.stdout.isTTY === true);
45550
+ const surface = chooseShellSurface(flags, runtime.isTty ?? process.stdout.isTTY === true);
45110
45551
  if (surface !== "readline") {
45111
45552
  const cwd = process.cwd();
45112
45553
  const tuiProviderFactory = realMakeProvider(() => {});
@@ -45151,7 +45592,7 @@ async function shellCommand(args2) {
45151
45592
  modelId: sel.model
45152
45593
  }),
45153
45594
  maxToolCalls: resolveAgentMaxToolCalls(),
45154
- idSeq: () => randomUUID9()
45595
+ idSeq: () => randomUUID10()
45155
45596
  };
45156
45597
  };
45157
45598
  const redetect = () => detectProviders({
@@ -45163,7 +45604,8 @@ async function shellCommand(args2) {
45163
45604
  providerArg,
45164
45605
  modelArg,
45165
45606
  baseUrl,
45166
- detect: redetect
45607
+ detect: redetect,
45608
+ ...runtime.cacheDir !== undefined ? { configDir: runtime.cacheDir } : {}
45167
45609
  });
45168
45610
  const tuiInitial = startup.initial;
45169
45611
  const tuiDetected = startup.detected;
@@ -45173,7 +45615,7 @@ async function shellCommand(args2) {
45173
45615
  if (flags.resumePick === true && chatResumeId === undefined) {
45174
45616
  chatResumeId = latestSession(cwd)?.id;
45175
45617
  }
45176
- if (await launchTuiChatShell({
45618
+ if (await (runtime.launchChat ?? launchTuiChatShell)({
45177
45619
  detected: tuiDetected,
45178
45620
  redetect,
45179
45621
  ...tuiInitial !== undefined ? { initial: tuiInitial } : {},
@@ -45181,18 +45623,19 @@ async function shellCommand(args2) {
45181
45623
  makeShellDeps: (sel) => ({
45182
45624
  makeProvider: chatFactory,
45183
45625
  clock: () => new Date().toISOString(),
45184
- idSeq: () => randomUUID9(),
45626
+ idSeq: () => randomUUID10(),
45185
45627
  initial: sel,
45186
45628
  session: {
45187
45629
  cwd,
45188
45630
  ...flags.continueLast === true ? { continueLast: true } : {},
45189
45631
  ...chatResumeId !== undefined ? { resumeId: chatResumeId } : {}
45190
45632
  }
45191
- })
45633
+ }),
45634
+ versionCheck
45192
45635
  })) {
45193
45636
  return;
45194
45637
  }
45195
- } else if (await launchTuiAgentShell({
45638
+ } else if (await (runtime.launchAgent ?? launchTuiAgentShell)({
45196
45639
  detected: tuiDetected,
45197
45640
  makeAgentDeps,
45198
45641
  redetect,
@@ -45202,7 +45645,8 @@ async function shellCommand(args2) {
45202
45645
  ...flags.continueLast === true ? { continueLast: true } : {},
45203
45646
  ...flags.resumeId !== undefined ? { resumeId: flags.resumeId } : {},
45204
45647
  ...flags.resumePick === true ? { pickOnStart: true } : {}
45205
- }
45648
+ },
45649
+ versionCheck
45206
45650
  })) {
45207
45651
  return;
45208
45652
  }
@@ -45210,7 +45654,7 @@ async function shellCommand(args2) {
45210
45654
  const rl = readline2.createInterface({ input: process.stdin });
45211
45655
  const lineIterator = rl[Symbol.asyncIterator]();
45212
45656
  const sharedLines = { [Symbol.asyncIterator]: () => lineIterator };
45213
- const { io, emitSystem, printHeader, printPrompt } = createRichIo(sharedLines);
45657
+ const { io, emitSystem, printHeader, printPrompt, destroy } = createRichIo(sharedLines, versionCheck);
45214
45658
  let provider;
45215
45659
  let model;
45216
45660
  try {
@@ -45249,7 +45693,7 @@ async function shellCommand(args2) {
45249
45693
  const deps = {
45250
45694
  makeProvider: baseFactory,
45251
45695
  clock: () => new Date().toISOString(),
45252
- idSeq: () => randomUUID9(),
45696
+ idSeq: () => randomUUID10(),
45253
45697
  initial: baseUrl === undefined ? { provider, model } : { provider, model, baseUrl },
45254
45698
  selectProviderModel: realSelectProviderModel(baseUrl)
45255
45699
  };
@@ -45293,13 +45737,13 @@ async function shellCommand(args2) {
45293
45737
  modelId: model
45294
45738
  }),
45295
45739
  maxToolCalls: resolveAgentMaxToolCalls(),
45296
- idSeq: () => randomUUID9()
45740
+ idSeq: () => randomUUID10()
45297
45741
  };
45298
45742
  let resumeId = flags.resumeId;
45299
45743
  if (flags.resumePick === true && resumeId === undefined) {
45300
45744
  resumeId = latestSession(process.cwd())?.id;
45301
45745
  }
45302
- await runAgentRepl(sharedLines, { printPrompt }, agentDeps, metaprojectPort, {
45746
+ await runAgentRepl(sharedLines, { printPrompt, safeBoundary: io.onSafeBoundary }, agentDeps, metaprojectPort, {
45303
45747
  cwd: process.cwd(),
45304
45748
  ...flags.continueLast === true ? { continueLast: true } : {},
45305
45749
  ...resumeId !== undefined ? { resumeId } : {}
@@ -45319,6 +45763,7 @@ async function shellCommand(args2) {
45319
45763
  });
45320
45764
  }
45321
45765
  } finally {
45766
+ destroy();
45322
45767
  rl.close();
45323
45768
  }
45324
45769
  }
@@ -45463,7 +45908,7 @@ Shell:
45463
45908
  init_fs();
45464
45909
  import { readFile as readFile63 } from "fs/promises";
45465
45910
  import { stdin } from "process";
45466
- import path121 from "path";
45911
+ import path122 from "path";
45467
45912
  var MODULES = [
45468
45913
  { name: "gdgraph", flag: "--no-gdgraph", desc: "code graph, symbols, affected context", defaultEnabled: true },
45469
45914
  { name: "gdctx", flag: "--no-gdctx", desc: "token-aware command/read output", defaultEnabled: true },
@@ -45502,8 +45947,8 @@ async function modulesCommand(args2 = []) {
45502
45947
  return;
45503
45948
  }
45504
45949
  const wantsJson = args2.includes("--json") && (sub === undefined || sub === "status" || sub === "list" || sub === "--json");
45505
- const metaprojectRoot = path121.join(process.cwd(), ".metaproject");
45506
- const manifestPath = path121.join(metaprojectRoot, "metaproject.json");
45950
+ const metaprojectRoot = path122.join(process.cwd(), ".metaproject");
45951
+ const manifestPath = path122.join(metaprojectRoot, "metaproject.json");
45507
45952
  if (!await pathExists(manifestPath)) {
45508
45953
  if (wantsJson) {
45509
45954
  console.log(JSON.stringify({ schemaVersion: 1, error: "not-initialized", modules: [] }, null, 2));
@@ -45617,18 +46062,18 @@ function printHelp16() {
45617
46062
  }
45618
46063
 
45619
46064
  // src/commands/serve.ts
45620
- import { randomUUID as randomUUID12 } from "crypto";
46065
+ import { randomUUID as randomUUID13 } from "crypto";
45621
46066
 
45622
46067
  // src/lib/serve-config.ts
45623
46068
  init_config_dir();
45624
46069
  import { existsSync as existsSync24 } from "fs";
45625
- import path122 from "path";
46070
+ import path123 from "path";
45626
46071
  var SERVE_CONFIG_SCHEMA_VERSION = "1.0.0";
45627
46072
  var DEFAULT_SERVE_BIND_ADDRESS = "127.0.0.1";
45628
46073
  var DEFAULT_SERVE_PORT = 7377;
45629
46074
  var DEFAULT_SERVE_PROFILE = "remote-restricted";
45630
46075
  function serveConfigPath(dir) {
45631
- return path122.join(keryxConfigDir(dir), "serve.json");
46076
+ return path123.join(keryxConfigDir(dir), "serve.json");
45632
46077
  }
45633
46078
  function parseIpv4(value) {
45634
46079
  const parts = value.split(".");
@@ -45944,21 +46389,21 @@ function saveServeConfig(config, dir, onWarn) {
45944
46389
 
45945
46390
  // src/lib/serve-credential.ts
45946
46391
  init_config_dir();
45947
- import { createHash as createHash19, randomBytes as randomBytes2, randomUUID as randomUUID10 } from "crypto";
46392
+ import { createHash as createHash19, randomBytes as randomBytes2, randomUUID as randomUUID11 } from "crypto";
45948
46393
  import {
45949
46394
  chmodSync as chmodSync4,
45950
46395
  closeSync as closeSync3,
45951
46396
  existsSync as existsSync25,
45952
46397
  fsyncSync as fsyncSync2,
45953
46398
  openSync as openSync3,
45954
- renameSync as renameSync3,
46399
+ renameSync as renameSync4,
45955
46400
  statSync as statSync5,
45956
- unlinkSync as unlinkSync2,
46401
+ unlinkSync as unlinkSync3,
45957
46402
  writeFileSync as writeFileSync8
45958
46403
  } from "fs";
45959
- import path123 from "path";
46404
+ import path124 from "path";
45960
46405
  function serveCredentialPath(dir) {
45961
- return path123.join(keryxConfigDir(dir), "serve-credentials.json");
46406
+ return path124.join(keryxConfigDir(dir), "serve-credentials.json");
45962
46407
  }
45963
46408
  function constantTimeEqual(a, b) {
45964
46409
  const width = Math.max(a.length, b.length);
@@ -46028,7 +46473,7 @@ function readServeCredential(dir) {
46028
46473
  }
46029
46474
  function writeStore(store, dir) {
46030
46475
  const file = serveCredentialPath(dir);
46031
- const temp = `${file}.${randomUUID10()}.tmp`;
46476
+ const temp = `${file}.${randomUUID11()}.tmp`;
46032
46477
  try {
46033
46478
  ensureKeryxConfigDir(dir);
46034
46479
  const handle = openSync3(temp, "wx", 384);
@@ -46039,12 +46484,12 @@ function writeStore(store, dir) {
46039
46484
  } finally {
46040
46485
  closeSync3(handle);
46041
46486
  }
46042
- renameSync3(temp, file);
46487
+ renameSync4(temp, file);
46043
46488
  tighten2(file, 384);
46044
46489
  return true;
46045
46490
  } catch {
46046
46491
  try {
46047
- unlinkSync2(temp);
46492
+ unlinkSync3(temp);
46048
46493
  } catch {}
46049
46494
  return false;
46050
46495
  }
@@ -46068,7 +46513,7 @@ function mintRecord(now) {
46068
46513
  const salt = randomBytes2(32).toString("hex");
46069
46514
  return {
46070
46515
  token,
46071
- record: { id: randomUUID10(), algorithm: "sha256", salt, hash: hashToken(salt, token), createdAt: now }
46516
+ record: { id: randomUUID11(), algorithm: "sha256", salt, hash: hashToken(salt, token), createdAt: now }
46072
46517
  };
46073
46518
  }
46074
46519
  function issueServeToken(dir, now = () => new Date().toISOString(), onWaiting) {
@@ -46189,22 +46634,22 @@ class AuthFailureThrottle {
46189
46634
  init_config_dir();
46190
46635
  import { createHash as createHash20 } from "crypto";
46191
46636
  import { existsSync as existsSync26, readdirSync as readdirSync2, rmSync as rmSync2 } from "fs";
46192
- import path124 from "path";
46637
+ import path125 from "path";
46193
46638
  var MAX_TURN_EVENTS = 1e4;
46194
46639
  function turnsRoot(dir) {
46195
- return path124.join(keryxConfigDir(dir), "turns");
46640
+ return path125.join(keryxConfigDir(dir), "turns");
46196
46641
  }
46197
46642
  function turnDir(turnId, dir) {
46198
- return path124.join(turnsRoot(dir), turnId);
46643
+ return path125.join(turnsRoot(dir), turnId);
46199
46644
  }
46200
46645
  function keyPath(project, idempotencyKey, dir) {
46201
46646
  const projectBytes = Buffer.byteLength(project, "utf8");
46202
46647
  const digest = createHash20("sha256").update(`${projectBytes}:${project}\x00${idempotencyKey}`, "utf8").digest("hex");
46203
- return path124.join(turnsRoot(dir), "keys", `${digest}.json`);
46648
+ return path125.join(turnsRoot(dir), "keys", `${digest}.json`);
46204
46649
  }
46205
46650
  function legacyKeyPath(idempotencyKey, dir) {
46206
46651
  const digest = createHash20("sha256").update(idempotencyKey, "utf8").digest("hex");
46207
- return path124.join(turnsRoot(dir), "keys", `${digest}.json`);
46652
+ return path125.join(turnsRoot(dir), "keys", `${digest}.json`);
46208
46653
  }
46209
46654
  function adoptLegacyClaim(project, idempotencyKey, dir) {
46210
46655
  const legacy = legacyKeyPath(idempotencyKey, dir);
@@ -46268,7 +46713,7 @@ function ensureTurnDir(turnId, dir) {
46268
46713
  }
46269
46714
  function createTurnRecord(record, dir) {
46270
46715
  ensureTurnDir(record.turnId, dir);
46271
- writeOwnerOnlyFile(path124.join(turnDir(record.turnId, dir), "turn.json"), `${JSON.stringify(record, null, 2)}
46716
+ writeOwnerOnlyFile(path125.join(turnDir(record.turnId, dir), "turn.json"), `${JSON.stringify(record, null, 2)}
46272
46717
  `);
46273
46718
  }
46274
46719
  function appendTurnEvent(event, dir, opts) {
@@ -46277,12 +46722,12 @@ function appendTurnEvent(event, dir, opts) {
46277
46722
  }
46278
46723
  const line = JSON.stringify(event);
46279
46724
  try {
46280
- appendOwnerOnlyLine(path124.join(turnDir(event.turnId, dir), "events.jsonl"), line);
46725
+ appendOwnerOnlyLine(path125.join(turnDir(event.turnId, dir), "events.jsonl"), line);
46281
46726
  } catch (error) {
46282
46727
  if (error?.code !== "ENOENT") {
46283
46728
  throw error;
46284
46729
  }
46285
- appendOwnerOnlyLine(path124.join(ensureTurnDir(event.turnId, dir), "events.jsonl"), line);
46730
+ appendOwnerOnlyLine(path125.join(ensureTurnDir(event.turnId, dir), "events.jsonl"), line);
46286
46731
  }
46287
46732
  return true;
46288
46733
  }
@@ -46290,7 +46735,7 @@ function readTurnEvents(turnId, after = -1, dir) {
46290
46735
  if (!isTurnId(turnId)) {
46291
46736
  return { ok: false, reason: "not-a-turn-id" };
46292
46737
  }
46293
- const read = readTurnFile(path124.join(turnDir(turnId, dir), "events.jsonl"));
46738
+ const read = readTurnFile(path125.join(turnDir(turnId, dir), "events.jsonl"));
46294
46739
  if (!read.ok) {
46295
46740
  if (isDefiniteAbsence2(read.reason)) {
46296
46741
  return { ok: true, value: [] };
@@ -46318,7 +46763,7 @@ function readTurnRecord(turnId, dir) {
46318
46763
  if (!isTurnId(turnId)) {
46319
46764
  return { ok: false, reason: "not-a-turn-id" };
46320
46765
  }
46321
- const read = readTurnFile(path124.join(turnDir(turnId, dir), "turn.json"));
46766
+ const read = readTurnFile(path125.join(turnDir(turnId, dir), "turn.json"));
46322
46767
  if (!read.ok) {
46323
46768
  return { ok: false, reason: read.reason };
46324
46769
  }
@@ -46337,7 +46782,7 @@ function finishTurn(turnId, result, dir) {
46337
46782
  if (!record.ok) {
46338
46783
  return false;
46339
46784
  }
46340
- writeOwnerOnlyFile(path124.join(turnDir(turnId, dir), "turn.json"), `${JSON.stringify({ ...record.value, result }, null, 2)}
46785
+ writeOwnerOnlyFile(path125.join(turnDir(turnId, dir), "turn.json"), `${JSON.stringify({ ...record.value, result }, null, 2)}
46341
46786
  `);
46342
46787
  return true;
46343
46788
  }
@@ -46382,8 +46827,8 @@ function releaseIdempotencyKey(project, idempotencyKey, turnId, dir) {
46382
46827
  }
46383
46828
 
46384
46829
  // src/lib/serve-turn.ts
46385
- import { randomUUID as randomUUID11 } from "crypto";
46386
- import path125 from "path";
46830
+ import { randomUUID as randomUUID12 } from "crypto";
46831
+ import path126 from "path";
46387
46832
  init_service();
46388
46833
  var REMOTE_ORIGIN = "remote:http";
46389
46834
  var MAX_PROMPT_CHARS = 32000;
@@ -46452,9 +46897,9 @@ function isUuid(value) {
46452
46897
  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);
46453
46898
  }
46454
46899
  function resolveProject(declared, dir) {
46455
- const wanted = path125.resolve(declared);
46900
+ const wanted = path126.resolve(declared);
46456
46901
  for (const entry of listProjects(dir, () => {})) {
46457
- if (path125.resolve(entry.path) === wanted) {
46902
+ if (path126.resolve(entry.path) === wanted) {
46458
46903
  return { ok: true, project: entry.path };
46459
46904
  }
46460
46905
  }
@@ -46501,7 +46946,7 @@ async function redactOut(security, text) {
46501
46946
  async function runRemoteTurn(input2) {
46502
46947
  const scanRoot = input2.scanRoot;
46503
46948
  const security = createSecurityService(scanRoot);
46504
- const newId = input2.newId ?? (() => randomUUID11());
46949
+ const newId = input2.newId ?? (() => randomUUID12());
46505
46950
  const clock = input2.clock ?? (() => new Date().toISOString());
46506
46951
  const turnId = input2.turnId ?? newId();
46507
46952
  const sessionId = input2.request.sessionId ?? newId();
@@ -46644,7 +47089,7 @@ function outcomeOf(status, gate, unresolvedBlockerIds) {
46644
47089
  }
46645
47090
  function createSubmitTurn(deps) {
46646
47091
  return async (request, project) => {
46647
- const turnId = (deps.newId ?? (() => randomUUID11()))();
47092
+ const turnId = (deps.newId ?? (() => randomUUID12()))();
46648
47093
  const scanned = await scanPrompt(deps.dir, request.prompt);
46649
47094
  if (scanned.rejected) {
46650
47095
  return { kind: "rejected" };
@@ -47331,7 +47776,7 @@ function runConfig(args2) {
47331
47776
  return;
47332
47777
  }
47333
47778
  const credential = readServeCredential();
47334
- const credentialId = credential.status === "ok" ? credential.record.id : randomUUID12();
47779
+ const credentialId = credential.status === "ok" ? credential.record.id : randomUUID13();
47335
47780
  const config = defaultServeConfig(credentialId, {
47336
47781
  address: parsed.parsed.values.get("--bind") ?? DEFAULT_SERVE_BIND_ADDRESS,
47337
47782
  port: port ?? DEFAULT_SERVE_PORT,
@@ -47489,7 +47934,7 @@ function printHelp17() {
47489
47934
  import { spawn as spawn5 } from "child_process";
47490
47935
  import { chmod as chmod4, mkdir as mkdir45, readFile as readFile64, readdir as readdir20, writeFile as writeFile42 } from "fs/promises";
47491
47936
  import { access as access3, constants, existsSync as existsSync27 } from "fs";
47492
- import path126 from "path";
47937
+ import path127 from "path";
47493
47938
  import { fileURLToPath as fileURLToPath6 } from "url";
47494
47939
  init_config();
47495
47940
  init_config2();
@@ -47504,8 +47949,8 @@ async function updateCommand(args2 = []) {
47504
47949
  return;
47505
47950
  }
47506
47951
  const projectRoot = process.cwd();
47507
- const metaprojectRoot = path126.join(projectRoot, ".metaproject");
47508
- banner("keryx update", `Refreshing the .metaproject workspace in ${path126.basename(projectRoot)}/`);
47952
+ const metaprojectRoot = path127.join(projectRoot, ".metaproject");
47953
+ banner("keryx update", `Refreshing the .metaproject workspace in ${path127.basename(projectRoot)}/`);
47509
47954
  if (!await pathExists(metaprojectRoot)) {
47510
47955
  console.log(` ${style.red(symbols.cross)} Metaproject is not initialized.`);
47511
47956
  console.log(` ${style.cyan(symbols.arrow)} Run ${style.cyan("keryx init")} first.`);
@@ -47548,12 +47993,12 @@ async function updateCommand(args2 = []) {
47548
47993
  nextSteps(steps);
47549
47994
  }
47550
47995
  async function refreshServiceFiles(projectRoot, options) {
47551
- const metaprojectRoot = path126.join(projectRoot, ".metaproject");
47996
+ const metaprojectRoot = path127.join(projectRoot, ".metaproject");
47552
47997
  const manifestState = await readManifest5(metaprojectRoot);
47553
47998
  const manifest = manifestState.manifest;
47554
47999
  const recoveredManifest = !manifestState.exists || !manifestState.valid;
47555
48000
  if (manifestState.migrated) {
47556
- await writeFile42(path126.join(metaprojectRoot, "metaproject.json"), `${JSON.stringify(manifest, null, 2)}
48001
+ await writeFile42(path127.join(metaprojectRoot, "metaproject.json"), `${JSON.stringify(manifest, null, 2)}
47557
48002
  `, "utf8");
47558
48003
  }
47559
48004
  const enableGdgraph = moduleEnabled2(manifest, "gdgraph");
@@ -47588,11 +48033,11 @@ async function refreshServiceFiles(projectRoot, options) {
47588
48033
  enableTasks,
47589
48034
  enableSecurity
47590
48035
  });
47591
- await writeTextIfChanged4(path126.join(metaprojectRoot, "core", "README.md"), renderMetaprojectCoreReadme());
47592
- await writeTextIfChanged4(path126.join(metaprojectRoot, "hooks", "README.md"), renderHooksReadme());
47593
- await writeTextIfChanged4(path126.join(metaprojectRoot, "rules", "README.md"), renderProjectRulesReadme());
47594
- await writeTextIfChanged4(path126.join(metaprojectRoot, "skills", "project-rules", "README.md"), renderProjectRulesSkillReadme({ sources: ruleSources }));
47595
- await writeTextIfChanged4(path126.join(metaprojectRoot, "index.md"), renderIndexMarkdown({
48036
+ await writeTextIfChanged4(path127.join(metaprojectRoot, "core", "README.md"), renderMetaprojectCoreReadme());
48037
+ await writeTextIfChanged4(path127.join(metaprojectRoot, "hooks", "README.md"), renderHooksReadme());
48038
+ await writeTextIfChanged4(path127.join(metaprojectRoot, "rules", "README.md"), renderProjectRulesReadme());
48039
+ await writeTextIfChanged4(path127.join(metaprojectRoot, "skills", "project-rules", "README.md"), renderProjectRulesSkillReadme({ sources: ruleSources }));
48040
+ await writeTextIfChanged4(path127.join(metaprojectRoot, "index.md"), renderIndexMarkdown({
47596
48041
  enableGdgraph,
47597
48042
  enableGdctx,
47598
48043
  enableGdwiki,
@@ -47605,7 +48050,7 @@ async function refreshServiceFiles(projectRoot, options) {
47605
48050
  ruleSources,
47606
48051
  hasDistilledEntrypoints: await hasDistilledEntrypoints(metaprojectRoot)
47607
48052
  }));
47608
- await writeTextIfChanged4(path126.join(metaprojectRoot, "keryx-dashboard.html"), renderMetaprojectDashboardHtml({
48053
+ await writeTextIfChanged4(path127.join(metaprojectRoot, "keryx-dashboard.html"), renderMetaprojectDashboardHtml({
47609
48054
  enableGdgraph,
47610
48055
  enableGdctx,
47611
48056
  enableGdwiki,
@@ -47617,7 +48062,7 @@ async function refreshServiceFiles(projectRoot, options) {
47617
48062
  enableSecurity,
47618
48063
  data: dashboardData
47619
48064
  }));
47620
- await writeTextIfMissing4(path126.join(metaprojectRoot, "README.md"), renderMetaprojectReadme({
48065
+ await writeTextIfMissing4(path127.join(metaprojectRoot, "README.md"), renderMetaprojectReadme({
47621
48066
  enableGdgraph,
47622
48067
  enableGdctx,
47623
48068
  enableGdwiki,
@@ -47630,24 +48075,24 @@ async function refreshServiceFiles(projectRoot, options) {
47630
48075
  }));
47631
48076
  if (enableGdgraph) {
47632
48077
  await installGdgraphCoreScripts2(metaprojectRoot);
47633
- await writeTextIfChanged4(path126.join(metaprojectRoot, "modules", "gdgraph.md"), renderGdgraphManifest());
47634
- await writeTextIfChanged4(path126.join(metaprojectRoot, "core", "gdgraph", "README.md"), renderGdgraphCoreReadme());
47635
- await writeTextIfChanged4(path126.join(metaprojectRoot, "skills", "gdgraph", "SKILL.md"), renderGdgraphSkillReadme());
48078
+ await writeTextIfChanged4(path127.join(metaprojectRoot, "modules", "gdgraph.md"), renderGdgraphManifest());
48079
+ await writeTextIfChanged4(path127.join(metaprojectRoot, "core", "gdgraph", "README.md"), renderGdgraphCoreReadme());
48080
+ await writeTextIfChanged4(path127.join(metaprojectRoot, "skills", "gdgraph", "SKILL.md"), renderGdgraphSkillReadme());
47636
48081
  await seedAssetsLock(metaprojectRoot);
47637
48082
  if (manifest.modules?.gdgraph?.hooks?.gitPostCommit) {
47638
48083
  await installManagedHook2(projectRoot, "post-commit", "gdgraph-post-commit", renderGdgraphPostCommitHook());
47639
48084
  }
47640
48085
  }
47641
48086
  if (enableGdctx) {
47642
- await writeTextIfMissing4(path126.join(metaprojectRoot, "gdctx.config.json"), renderGdctxConfig());
47643
- await writeTextIfChanged4(path126.join(metaprojectRoot, "modules", "gdctx.md"), renderGdctxManifest());
47644
- await writeTextIfChanged4(path126.join(metaprojectRoot, "core", "gdctx", "README.md"), renderGdctxCoreReadme());
47645
- await writeTextIfChanged4(path126.join(metaprojectRoot, "skills", "gdctx", "SKILL.md"), renderGdctxSkillReadme());
48087
+ await writeTextIfMissing4(path127.join(metaprojectRoot, "gdctx.config.json"), renderGdctxConfig());
48088
+ await writeTextIfChanged4(path127.join(metaprojectRoot, "modules", "gdctx.md"), renderGdctxManifest());
48089
+ await writeTextIfChanged4(path127.join(metaprojectRoot, "core", "gdctx", "README.md"), renderGdctxCoreReadme());
48090
+ await writeTextIfChanged4(path127.join(metaprojectRoot, "skills", "gdctx", "SKILL.md"), renderGdctxSkillReadme());
47646
48091
  }
47647
48092
  if (enableGdwiki) {
47648
- await writeTextIfMissing4(path126.join(metaprojectRoot, "wiki", "templates", "page.md"), renderWikiPageTemplate());
47649
- await writeTextIfChanged4(path126.join(metaprojectRoot, "modules", "gdwiki.md"), renderGdwikiManifest());
47650
- await writeTextIfChanged4(path126.join(metaprojectRoot, "skills", "gdwiki", "SKILL.md"), renderGdwikiSkillReadme());
48093
+ await writeTextIfMissing4(path127.join(metaprojectRoot, "wiki", "templates", "page.md"), renderWikiPageTemplate());
48094
+ await writeTextIfChanged4(path127.join(metaprojectRoot, "modules", "gdwiki.md"), renderGdwikiManifest());
48095
+ await writeTextIfChanged4(path127.join(metaprojectRoot, "skills", "gdwiki", "SKILL.md"), renderGdwikiSkillReadme());
47651
48096
  if (manifest.modules?.gdgraph?.hooks?.gitPostCommit) {
47652
48097
  await installManagedHook2(projectRoot, "post-commit", "gdwiki-post-commit", renderGdwikiPostCommitHook());
47653
48098
  }
@@ -47659,25 +48104,25 @@ async function refreshServiceFiles(projectRoot, options) {
47659
48104
  }
47660
48105
  }
47661
48106
  if (enableHealth) {
47662
- await writeTextIfMissing4(path126.join(metaprojectRoot, "health.config.json"), renderHealthConfig());
47663
- await writeTextIfChanged4(path126.join(metaprojectRoot, "modules", "health.md"), renderHealthManifest());
47664
- await writeTextIfChanged4(path126.join(metaprojectRoot, "core", "health", "README.md"), renderHealthCoreReadme());
47665
- await writeTextIfChanged4(path126.join(metaprojectRoot, "skills", "health", "SKILL.md"), renderHealthSkillReadme());
48107
+ await writeTextIfMissing4(path127.join(metaprojectRoot, "health.config.json"), renderHealthConfig());
48108
+ await writeTextIfChanged4(path127.join(metaprojectRoot, "modules", "health.md"), renderHealthManifest());
48109
+ await writeTextIfChanged4(path127.join(metaprojectRoot, "core", "health", "README.md"), renderHealthCoreReadme());
48110
+ await writeTextIfChanged4(path127.join(metaprojectRoot, "skills", "health", "SKILL.md"), renderHealthSkillReadme());
47666
48111
  if (manifest.modules?.health?.hooks?.gitPostCommit) {
47667
48112
  await installManagedHook2(projectRoot, "post-commit", "health-post-commit", renderHealthPostCommitHook());
47668
48113
  }
47669
48114
  }
47670
48115
  if (enableTesting) {
47671
- await writeTextIfMissing4(path126.join(metaprojectRoot, "testing.config.json"), renderTestingConfig({
48116
+ await writeTextIfMissing4(path127.join(metaprojectRoot, "testing.config.json"), renderTestingConfig({
47672
48117
  postCommitRefresh: Boolean(manifest.modules?.testing?.hooks?.gitPostCommit),
47673
48118
  prePushGate: Boolean(manifest.modules?.testing?.hooks?.prePush)
47674
48119
  }));
47675
- await writeTextIfChanged4(path126.join(metaprojectRoot, "modules", "testing.md"), renderTestingManifest());
47676
- await writeTextIfChanged4(path126.join(metaprojectRoot, "core", "testing", "README.md"), renderTestingCoreReadme());
47677
- await writeTextIfChanged4(path126.join(metaprojectRoot, "skills", "testing", "SKILL.md"), renderTestingSkillReadme());
48120
+ await writeTextIfChanged4(path127.join(metaprojectRoot, "modules", "testing.md"), renderTestingManifest());
48121
+ await writeTextIfChanged4(path127.join(metaprojectRoot, "core", "testing", "README.md"), renderTestingCoreReadme());
48122
+ await writeTextIfChanged4(path127.join(metaprojectRoot, "skills", "testing", "SKILL.md"), renderTestingSkillReadme());
47678
48123
  if (enableGdwiki) {
47679
- await writeTextIfMissing4(path126.join(metaprojectRoot, "wiki", "testing", "README.md"), renderTestingWikiReadme());
47680
- await writeTextIfMissing4(path126.join(metaprojectRoot, "wiki", "testing", "conventions.md"), renderTestingWikiConventions());
48124
+ await writeTextIfMissing4(path127.join(metaprojectRoot, "wiki", "testing", "README.md"), renderTestingWikiReadme());
48125
+ await writeTextIfMissing4(path127.join(metaprojectRoot, "wiki", "testing", "conventions.md"), renderTestingWikiConventions());
47681
48126
  }
47682
48127
  if (manifest.modules?.testing?.hooks?.gitPostCommit) {
47683
48128
  await installManagedHook2(projectRoot, "post-commit", "testing-post-commit", renderTestingPostCommitHook());
@@ -47690,24 +48135,24 @@ async function refreshServiceFiles(projectRoot, options) {
47690
48135
  await installManagedHook2(projectRoot, "post-commit", "metaproject-dashboard-post-commit", renderMetaprojectDashboardPostCommitHook());
47691
48136
  }
47692
48137
  if (enableMemory) {
47693
- await writeTextIfMissing4(path126.join(metaprojectRoot, "memory.config.json"), renderMemoryConfig());
47694
- await writeTextIfMissing4(path126.join(metaprojectRoot, "memory", "templates", "entry.md"), renderMemoryEntryTemplate());
47695
- await writeTextIfChanged4(path126.join(metaprojectRoot, "modules", "memory.md"), renderMemoryManifest());
47696
- await writeTextIfChanged4(path126.join(metaprojectRoot, "core", "memory", "README.md"), renderMemoryCoreReadme());
47697
- await writeTextIfChanged4(path126.join(metaprojectRoot, "skills", "memory", "SKILL.md"), renderMemorySkillReadme());
48138
+ await writeTextIfMissing4(path127.join(metaprojectRoot, "memory.config.json"), renderMemoryConfig());
48139
+ await writeTextIfMissing4(path127.join(metaprojectRoot, "memory", "templates", "entry.md"), renderMemoryEntryTemplate());
48140
+ await writeTextIfChanged4(path127.join(metaprojectRoot, "modules", "memory.md"), renderMemoryManifest());
48141
+ await writeTextIfChanged4(path127.join(metaprojectRoot, "core", "memory", "README.md"), renderMemoryCoreReadme());
48142
+ await writeTextIfChanged4(path127.join(metaprojectRoot, "skills", "memory", "SKILL.md"), renderMemorySkillReadme());
47698
48143
  }
47699
48144
  if (enableTasks) {
47700
- await writeTextIfChanged4(path126.join(metaprojectRoot, "flows", "README.md"), renderFlowsReadme());
47701
- await writeTextIfChanged4(path126.join(metaprojectRoot, "modules", "tasks.md"), renderTasksManifest());
47702
- await writeTextIfChanged4(path126.join(metaprojectRoot, "skills", "flow", "SKILL.md"), renderFlowSkillRouter());
47703
- await writeTextIfChanged4(path126.join(metaprojectRoot, "skills", "flow", "init.md"), renderFlowInitSkill());
47704
- await writeTextIfChanged4(path126.join(metaprojectRoot, "skills", "flow", "manage.md"), renderFlowManageSkill());
47705
- await writeTextIfChanged4(path126.join(metaprojectRoot, "skills", "flow", "complete.md"), renderFlowCompleteSkill());
48145
+ await writeTextIfChanged4(path127.join(metaprojectRoot, "flows", "README.md"), renderFlowsReadme());
48146
+ await writeTextIfChanged4(path127.join(metaprojectRoot, "modules", "tasks.md"), renderTasksManifest());
48147
+ await writeTextIfChanged4(path127.join(metaprojectRoot, "skills", "flow", "SKILL.md"), renderFlowSkillRouter());
48148
+ await writeTextIfChanged4(path127.join(metaprojectRoot, "skills", "flow", "init.md"), renderFlowInitSkill());
48149
+ await writeTextIfChanged4(path127.join(metaprojectRoot, "skills", "flow", "manage.md"), renderFlowManageSkill());
48150
+ await writeTextIfChanged4(path127.join(metaprojectRoot, "skills", "flow", "complete.md"), renderFlowCompleteSkill());
47706
48151
  }
47707
48152
  if (enableSecurity) {
47708
- await writeTextIfMissing4(path126.join(metaprojectRoot, "security.config.json"), renderSecurityConfig());
47709
- await writeTextIfChanged4(path126.join(metaprojectRoot, "modules", "security.md"), renderSecurityManifest());
47710
- await writeTextIfChanged4(path126.join(metaprojectRoot, "core", "security", "README.md"), renderSecurityCoreReadme());
48153
+ await writeTextIfMissing4(path127.join(metaprojectRoot, "security.config.json"), renderSecurityConfig());
48154
+ await writeTextIfChanged4(path127.join(metaprojectRoot, "modules", "security.md"), renderSecurityManifest());
48155
+ await writeTextIfChanged4(path127.join(metaprojectRoot, "core", "security", "README.md"), renderSecurityCoreReadme());
47711
48156
  if (manifest.modules?.security?.hooks?.prePush) {
47712
48157
  await installManagedHook2(projectRoot, "pre-push", "security-pre-push", renderSecurityPrePushHook());
47713
48158
  }
@@ -47756,13 +48201,13 @@ async function refreshServiceFiles(projectRoot, options) {
47756
48201
  };
47757
48202
  }
47758
48203
  async function buildDashboard(projectRoot = process.cwd()) {
47759
- const metaprojectRoot = path126.join(projectRoot, ".metaproject");
48204
+ const metaprojectRoot = path127.join(projectRoot, ".metaproject");
47760
48205
  if (!await pathExists(metaprojectRoot)) {
47761
48206
  throw new Error("Metaproject is not initialized. Run: keryx init");
47762
48207
  }
47763
48208
  const manifest = (await readManifest5(metaprojectRoot)).manifest;
47764
48209
  const data = await collectDashboardData(metaprojectRoot);
47765
- const dashboardPath = path126.join(metaprojectRoot, "keryx-dashboard.html");
48210
+ const dashboardPath = path127.join(metaprojectRoot, "keryx-dashboard.html");
47766
48211
  await writeTextIfChanged4(dashboardPath, renderMetaprojectDashboardHtml({
47767
48212
  enableGdgraph: moduleEnabled2(manifest, "gdgraph"),
47768
48213
  enableGdctx: moduleEnabled2(manifest, "gdctx"),
@@ -47782,7 +48227,7 @@ async function shouldInstallDashboardPostCommitHook(projectRoot, manifest) {
47782
48227
  if (Object.values(modules).some((module) => Boolean(module.hooks?.gitPostCommit))) {
47783
48228
  return true;
47784
48229
  }
47785
- const hookPath = path126.join(projectRoot, ".git", "hooks", "post-commit");
48230
+ const hookPath = path127.join(projectRoot, ".git", "hooks", "post-commit");
47786
48231
  if (!await pathExists(hookPath)) {
47787
48232
  return false;
47788
48233
  }
@@ -47802,11 +48247,11 @@ async function collectDashboardData(metaprojectRoot) {
47802
48247
  if (testing) {
47803
48248
  data.testing = testing;
47804
48249
  }
47805
- const wiki = await collectMarkdownPages(path126.join(metaprojectRoot, "wiki"), "wiki");
48250
+ const wiki = await collectMarkdownPages(path127.join(metaprojectRoot, "wiki"), "wiki");
47806
48251
  if (wiki.length > 0) {
47807
48252
  data.wiki = { pages: wiki };
47808
48253
  }
47809
- const memory = await collectMarkdownPages(path126.join(metaprojectRoot, "memory"), "memory");
48254
+ const memory = await collectMarkdownPages(path127.join(metaprojectRoot, "memory"), "memory");
47810
48255
  if (memory.length > 0) {
47811
48256
  data.memory = { entries: memory };
47812
48257
  }
@@ -47821,7 +48266,7 @@ async function collectDashboardData(metaprojectRoot) {
47821
48266
  return data;
47822
48267
  }
47823
48268
  async function collectTasksDashboardData(metaprojectRoot) {
47824
- const flowsRoot2 = path126.join(metaprojectRoot, "flows");
48269
+ const flowsRoot2 = path127.join(metaprojectRoot, "flows");
47825
48270
  if (!await pathExists(flowsRoot2)) {
47826
48271
  return null;
47827
48272
  }
@@ -47833,7 +48278,7 @@ async function collectTasksDashboardData(metaprojectRoot) {
47833
48278
  }
47834
48279
  const flows = [];
47835
48280
  for (const dir of dirEntries) {
47836
- const flowPath = path126.join(flowsRoot2, dir, "flow.json");
48281
+ const flowPath = path127.join(flowsRoot2, dir, "flow.json");
47837
48282
  if (!await pathExists(flowPath)) {
47838
48283
  continue;
47839
48284
  }
@@ -47841,7 +48286,7 @@ async function collectTasksDashboardData(metaprojectRoot) {
47841
48286
  const flow = JSON.parse(await readFile64(flowPath, "utf8"));
47842
48287
  const tasks = Array.isArray(flow.tasks) ? flow.tasks : [];
47843
48288
  let acTotal = 0;
47844
- const acPath2 = path126.join(flowsRoot2, dir, "acceptance-criteria.md");
48289
+ const acPath2 = path127.join(flowsRoot2, dir, "acceptance-criteria.md");
47845
48290
  if (await pathExists(acPath2)) {
47846
48291
  const acContent = await readFile64(acPath2, "utf8");
47847
48292
  acTotal = (acContent.match(/^- AC\d+:/gm) ?? []).length;
@@ -47894,7 +48339,7 @@ async function collectDashboardDocs(metaprojectRoot, wiki, memory) {
47894
48339
  "data/testing/context.md"
47895
48340
  ];
47896
48341
  for (const href of staticHrefs) {
47897
- const filePath = path126.join(metaprojectRoot, ...href.split("/"));
48342
+ const filePath = path127.join(metaprojectRoot, ...href.split("/"));
47898
48343
  if (!await pathExists(filePath)) {
47899
48344
  continue;
47900
48345
  }
@@ -47911,7 +48356,7 @@ async function collectDashboardDocs(metaprojectRoot, wiki, memory) {
47911
48356
  return docs;
47912
48357
  }
47913
48358
  async function collectHealthDashboardData(metaprojectRoot) {
47914
- const reportPath2 = path126.join(metaprojectRoot, "data", "health", "artifacts", "latest.json");
48359
+ const reportPath2 = path127.join(metaprojectRoot, "data", "health", "artifacts", "latest.json");
47915
48360
  if (!await pathExists(reportPath2)) {
47916
48361
  return;
47917
48362
  }
@@ -48020,8 +48465,8 @@ function metricToScope(metric) {
48020
48465
  };
48021
48466
  }
48022
48467
  async function collectGraphDashboardData(metaprojectRoot) {
48023
- const nodesPath = path126.join(metaprojectRoot, "data", "gdgraph", "storage", "nodes.jsonl");
48024
- const edgesPath = path126.join(metaprojectRoot, "data", "gdgraph", "storage", "edges.jsonl");
48468
+ const nodesPath = path127.join(metaprojectRoot, "data", "gdgraph", "storage", "nodes.jsonl");
48469
+ const edgesPath = path127.join(metaprojectRoot, "data", "gdgraph", "storage", "edges.jsonl");
48025
48470
  if (!await pathExists(nodesPath) || !await pathExists(edgesPath)) {
48026
48471
  return;
48027
48472
  }
@@ -48072,8 +48517,8 @@ async function collectGraphDashboardData(metaprojectRoot) {
48072
48517
  };
48073
48518
  }
48074
48519
  async function collectTestingDashboardData(metaprojectRoot) {
48075
- const reportPath2 = path126.join(metaprojectRoot, "data", "testing", "artifacts", "latest.json");
48076
- const contextPath = path126.join(metaprojectRoot, "data", "testing", "context.md");
48520
+ const reportPath2 = path127.join(metaprojectRoot, "data", "testing", "artifacts", "latest.json");
48521
+ const contextPath = path127.join(metaprojectRoot, "data", "testing", "context.md");
48077
48522
  if (await pathExists(reportPath2)) {
48078
48523
  const report = JSON.parse(await readFile64(reportPath2, "utf8"));
48079
48524
  const totalTests = numberOrUndefined(report.total);
@@ -48102,7 +48547,7 @@ async function collectMarkdownPages(root, hrefPrefix) {
48102
48547
  const files = await listMarkdownFiles(root);
48103
48548
  const pages = [];
48104
48549
  for (const filePath of files.slice(0, 40)) {
48105
- const relativePath = path126.relative(root, filePath).split(path126.sep).join("/");
48550
+ const relativePath = path127.relative(root, filePath).split(path127.sep).join("/");
48106
48551
  if (relativePath === "index.md" || relativePath.startsWith("templates/")) {
48107
48552
  continue;
48108
48553
  }
@@ -48123,7 +48568,7 @@ async function listMarkdownFiles(root) {
48123
48568
  const entries = await readdir20(root, { withFileTypes: true });
48124
48569
  const files = [];
48125
48570
  for (const entry of entries) {
48126
- const fullPath = path126.join(root, entry.name);
48571
+ const fullPath = path127.join(root, entry.name);
48127
48572
  if (entry.isDirectory()) {
48128
48573
  files.push(...await listMarkdownFiles(fullPath));
48129
48574
  } else if (entry.isFile() && entry.name.endsWith(".md")) {
@@ -48171,7 +48616,7 @@ async function writeRecoveredManifest(metaprojectRoot, modules) {
48171
48616
  const manifest = {
48172
48617
  schemaVersion: 1,
48173
48618
  standardVersion: STANDARD_VERSION,
48174
- name: `${path126.basename(path126.dirname(metaprojectRoot))}-metaproject`,
48619
+ name: `${path127.basename(path127.dirname(metaprojectRoot))}-metaproject`,
48175
48620
  createdBy: "keryx",
48176
48621
  profiles: computeProfiles(enabledModuleKeys2),
48177
48622
  paths: {
@@ -48254,11 +48699,11 @@ async function writeRecoveredManifest(metaprojectRoot, modules) {
48254
48699
  metaproject: ".metaproject/index.md"
48255
48700
  }
48256
48701
  };
48257
- await writeFile42(path126.join(metaprojectRoot, "metaproject.json"), `${JSON.stringify(manifest, null, 2)}
48702
+ await writeFile42(path127.join(metaprojectRoot, "metaproject.json"), `${JSON.stringify(manifest, null, 2)}
48258
48703
  `, "utf8");
48259
48704
  }
48260
48705
  async function enableTasksInManifest(metaprojectRoot) {
48261
- const manifestPath = path126.join(metaprojectRoot, "metaproject.json");
48706
+ const manifestPath = path127.join(metaprojectRoot, "metaproject.json");
48262
48707
  if (!await pathExists(manifestPath)) {
48263
48708
  return;
48264
48709
  }
@@ -48281,7 +48726,7 @@ async function enableTasksInManifest(metaprojectRoot) {
48281
48726
  `, "utf8");
48282
48727
  }
48283
48728
  async function updateManifestAgentEntrypoints(metaprojectRoot, ruleSources) {
48284
- const manifestPath = path126.join(metaprojectRoot, "metaproject.json");
48729
+ const manifestPath = path127.join(metaprojectRoot, "metaproject.json");
48285
48730
  if (!await pathExists(manifestPath)) {
48286
48731
  return;
48287
48732
  }
@@ -48317,69 +48762,69 @@ async function updateRuntime(projectRoot) {
48317
48762
  }
48318
48763
  }
48319
48764
  async function findRuntimeRoot(projectRoot) {
48320
- const projectRuntime = path126.join(projectRoot, ".metaproject", "runtime", "keryx");
48321
- if (await pathExists(path126.join(projectRuntime, ".git"))) {
48765
+ const projectRuntime = path127.join(projectRoot, ".metaproject", "runtime", "keryx");
48766
+ if (await pathExists(path127.join(projectRuntime, ".git"))) {
48322
48767
  return projectRuntime;
48323
48768
  }
48324
48769
  const home = process.env.HOME;
48325
48770
  if (!home) {
48326
48771
  return null;
48327
48772
  }
48328
- const globalRuntime = path126.join(home, ".keryx", "keryx");
48329
- if (await pathExists(path126.join(globalRuntime, ".git"))) {
48773
+ const globalRuntime = path127.join(home, ".keryx", "keryx");
48774
+ if (await pathExists(path127.join(globalRuntime, ".git"))) {
48330
48775
  return globalRuntime;
48331
48776
  }
48332
48777
  return null;
48333
48778
  }
48334
48779
  async function createServiceDirs(metaprojectRoot, modules) {
48335
48780
  const dirs = [
48336
- path126.join(metaprojectRoot, "core"),
48337
- path126.join(metaprojectRoot, "hooks", "post-update.d"),
48338
- path126.join(metaprojectRoot, "modules"),
48339
- path126.join(metaprojectRoot, "rules"),
48340
- path126.join(metaprojectRoot, "skills", "project-rules"),
48781
+ path127.join(metaprojectRoot, "core"),
48782
+ path127.join(metaprojectRoot, "hooks", "post-update.d"),
48783
+ path127.join(metaprojectRoot, "modules"),
48784
+ path127.join(metaprojectRoot, "rules"),
48785
+ path127.join(metaprojectRoot, "skills", "project-rules"),
48341
48786
  ...modules.enableGdgraph ? [
48342
- path126.join(metaprojectRoot, "core", "gdgraph"),
48343
- path126.join(metaprojectRoot, "skills", "gdgraph")
48787
+ path127.join(metaprojectRoot, "core", "gdgraph"),
48788
+ path127.join(metaprojectRoot, "skills", "gdgraph")
48344
48789
  ] : [],
48345
48790
  ...modules.enableGdctx ? [
48346
- path126.join(metaprojectRoot, "core", "gdctx"),
48347
- path126.join(metaprojectRoot, "skills", "gdctx")
48791
+ path127.join(metaprojectRoot, "core", "gdctx"),
48792
+ path127.join(metaprojectRoot, "skills", "gdctx")
48348
48793
  ] : [],
48349
48794
  ...modules.enableGdwiki ? [
48350
- path126.join(metaprojectRoot, "skills", "gdwiki"),
48351
- path126.join(metaprojectRoot, "wiki", "templates")
48795
+ path127.join(metaprojectRoot, "skills", "gdwiki"),
48796
+ path127.join(metaprojectRoot, "wiki", "templates")
48352
48797
  ] : [],
48353
48798
  ...modules.enableHealth ? [
48354
- path126.join(metaprojectRoot, "core", "health"),
48355
- path126.join(metaprojectRoot, "skills", "health")
48799
+ path127.join(metaprojectRoot, "core", "health"),
48800
+ path127.join(metaprojectRoot, "skills", "health")
48356
48801
  ] : [],
48357
48802
  ...modules.enableTesting ? [
48358
- path126.join(metaprojectRoot, "core", "testing"),
48359
- path126.join(metaprojectRoot, "skills", "testing")
48803
+ path127.join(metaprojectRoot, "core", "testing"),
48804
+ path127.join(metaprojectRoot, "skills", "testing")
48360
48805
  ] : [],
48361
48806
  ...modules.enableMemory ? [
48362
- path126.join(metaprojectRoot, "core", "memory"),
48363
- path126.join(metaprojectRoot, "skills", "memory"),
48364
- path126.join(metaprojectRoot, "memory", "templates")
48807
+ path127.join(metaprojectRoot, "core", "memory"),
48808
+ path127.join(metaprojectRoot, "skills", "memory"),
48809
+ path127.join(metaprojectRoot, "memory", "templates")
48365
48810
  ] : [],
48366
48811
  ...modules.enableTasks ? [
48367
- path126.join(metaprojectRoot, "flows"),
48368
- path126.join(metaprojectRoot, "skills", "flow")
48812
+ path127.join(metaprojectRoot, "flows"),
48813
+ path127.join(metaprojectRoot, "skills", "flow")
48369
48814
  ] : [],
48370
48815
  ...modules.enableSecurity ? [
48371
- path126.join(metaprojectRoot, "core", "security")
48816
+ path127.join(metaprojectRoot, "core", "security")
48372
48817
  ] : []
48373
48818
  ];
48374
48819
  await Promise.all(dirs.map((dir) => mkdir45(dir, { recursive: true })));
48375
48820
  }
48376
48821
  async function installGdgraphCoreScripts2(metaprojectRoot) {
48377
- const gdgraphCoreRoot = path126.join(metaprojectRoot, "core", "gdgraph");
48822
+ const gdgraphCoreRoot = path127.join(metaprojectRoot, "core", "gdgraph");
48378
48823
  await mkdir45(gdgraphCoreRoot, { recursive: true });
48379
48824
  for (const file of GDGRAPH_CORE_SOURCES) {
48380
- await copyFileIfChanged2(runtimeSourcePath2(`../gdgraph/${file}`), path126.join(gdgraphCoreRoot, file));
48825
+ await copyFileIfChanged2(runtimeSourcePath2(`../gdgraph/${file}`), path127.join(gdgraphCoreRoot, file));
48381
48826
  }
48382
- await writeTextIfChanged4(path126.join(gdgraphCoreRoot, "cli.ts"), renderGdgraphCoreCli());
48827
+ await writeTextIfChanged4(path127.join(gdgraphCoreRoot, "cli.ts"), renderGdgraphCoreCli());
48383
48828
  }
48384
48829
  async function installManagedHook2(projectRoot, hookName, blockId, content) {
48385
48830
  const hooksRoot = await resolveGitHooksRoot(projectRoot);
@@ -48387,7 +48832,7 @@ async function installManagedHook2(projectRoot, hookName, blockId, content) {
48387
48832
  return;
48388
48833
  }
48389
48834
  await mkdir45(hooksRoot, { recursive: true });
48390
- const hookPath = path126.join(hooksRoot, hookName);
48835
+ const hookPath = path127.join(hooksRoot, hookName);
48391
48836
  const blockStart = `# keryx:${blockId}:begin`;
48392
48837
  const blockEnd = `# keryx:${blockId}:end`;
48393
48838
  const managedBlock = `${blockStart}
@@ -48408,7 +48853,7 @@ async function removeManagedHook2(projectRoot, hookName, blockId) {
48408
48853
  if (!hooksRoot) {
48409
48854
  return;
48410
48855
  }
48411
- const hookPath = path126.join(hooksRoot, hookName);
48856
+ const hookPath = path127.join(hooksRoot, hookName);
48412
48857
  if (!await pathExists(hookPath)) {
48413
48858
  return;
48414
48859
  }
@@ -48430,7 +48875,7 @@ async function prePushHasSecurityBlock2(projectRoot) {
48430
48875
  if (!hooksRoot) {
48431
48876
  return false;
48432
48877
  }
48433
- const hookPath = path126.join(hooksRoot, "pre-push");
48878
+ const hookPath = path127.join(hooksRoot, "pre-push");
48434
48879
  if (!await pathExists(hookPath)) {
48435
48880
  return false;
48436
48881
  }
@@ -48445,7 +48890,7 @@ async function agentSettingsHasSecuritySentinel2(projectRoot) {
48445
48890
  return (await readFile64(file, "utf8")).includes(AGENT_HOOKS_SENTINEL);
48446
48891
  }
48447
48892
  async function readManifest5(metaprojectRoot) {
48448
- const manifestPath = path126.join(metaprojectRoot, "metaproject.json");
48893
+ const manifestPath = path127.join(metaprojectRoot, "metaproject.json");
48449
48894
  if (!await pathExists(manifestPath)) {
48450
48895
  return {
48451
48896
  exists: false,
@@ -48513,7 +48958,7 @@ async function inferManifestFromExistingMetaproject(metaprojectRoot) {
48513
48958
  }
48514
48959
  async function anyPathExists(root, candidates) {
48515
48960
  for (const candidate of candidates) {
48516
- if (await pathExists(path126.join(root, candidate))) {
48961
+ if (await pathExists(path127.join(root, candidate))) {
48517
48962
  return true;
48518
48963
  }
48519
48964
  }
@@ -48534,13 +48979,13 @@ function parseUpdateArgs(args2) {
48534
48979
  };
48535
48980
  }
48536
48981
  async function runPostUpdateHooks(projectRoot) {
48537
- const hooksDir = path126.join(projectRoot, ".metaproject", "hooks", "post-update.d");
48982
+ const hooksDir = path127.join(projectRoot, ".metaproject", "hooks", "post-update.d");
48538
48983
  if (!await pathExists(hooksDir)) {
48539
48984
  return;
48540
48985
  }
48541
48986
  const entries = (await readdir20(hooksDir)).sort();
48542
48987
  for (const entry of entries) {
48543
- const hookPath = path126.join(hooksDir, entry);
48988
+ const hookPath = path127.join(hooksDir, entry);
48544
48989
  try {
48545
48990
  await accessExecutable(hookPath);
48546
48991
  } catch {
@@ -48581,14 +49026,14 @@ async function writeTextIfChanged4(filePath, content) {
48581
49026
  if (await pathExists(filePath) && await readFile64(filePath, "utf8") === content) {
48582
49027
  return;
48583
49028
  }
48584
- await mkdir45(path126.dirname(filePath), { recursive: true });
49029
+ await mkdir45(path127.dirname(filePath), { recursive: true });
48585
49030
  await writeFile42(filePath, content, "utf8");
48586
49031
  }
48587
49032
  async function writeTextIfMissing4(filePath, content) {
48588
49033
  if (await pathExists(filePath)) {
48589
49034
  return;
48590
49035
  }
48591
- await mkdir45(path126.dirname(filePath), { recursive: true });
49036
+ await mkdir45(path127.dirname(filePath), { recursive: true });
48592
49037
  await writeFile42(filePath, content, "utf8");
48593
49038
  }
48594
49039
  async function copyFileIfChanged2(from, to) {
@@ -48596,7 +49041,7 @@ async function copyFileIfChanged2(from, to) {
48596
49041
  if (await pathExists(to) && await readFile64(to, "utf8") === next) {
48597
49042
  return;
48598
49043
  }
48599
- await mkdir45(path126.dirname(to), { recursive: true });
49044
+ await mkdir45(path127.dirname(to), { recursive: true });
48600
49045
  await writeFile42(to, next, "utf8");
48601
49046
  }
48602
49047
  function runtimeSourcePath2(relativePath) {
@@ -48605,7 +49050,7 @@ function runtimeSourcePath2(relativePath) {
48605
49050
  return directPath;
48606
49051
  }
48607
49052
  if (relativePath.startsWith("../")) {
48608
- const packagedSourcePath = path126.join(path126.dirname(fileURLToPath6(import.meta.url)), "..", "src", relativePath.slice(3));
49053
+ const packagedSourcePath = path127.join(path127.dirname(fileURLToPath6(import.meta.url)), "..", "src", relativePath.slice(3));
48609
49054
  if (existsSync27(packagedSourcePath)) {
48610
49055
  return packagedSourcePath;
48611
49056
  }
@@ -48637,7 +49082,7 @@ function printHelp18() {
48637
49082
 
48638
49083
  // src/commands/dashboard.ts
48639
49084
  import { spawn as spawn6 } from "child_process";
48640
- import path127 from "path";
49085
+ import path128 from "path";
48641
49086
  init_args();
48642
49087
  async function dashboardCommand(args2 = []) {
48643
49088
  const options = parseOptions(args2);
@@ -48648,7 +49093,7 @@ async function dashboardCommand(args2 = []) {
48648
49093
  }
48649
49094
  if (subcommand === "build") {
48650
49095
  const result = await buildDashboard();
48651
- const rel = path127.relative(process.cwd(), result.path);
49096
+ const rel = path128.relative(process.cwd(), result.path);
48652
49097
  console.log(` ${style.green(symbols.ok)} Dashboard built ${style.cyan(symbols.arrow)} ${style.cyan(rel)}`);
48653
49098
  note(`Open it: keryx dashboard open`);
48654
49099
  return;
@@ -48656,7 +49101,7 @@ async function dashboardCommand(args2 = []) {
48656
49101
  if (subcommand === "open") {
48657
49102
  const result = await buildDashboard();
48658
49103
  await openFile(result.path);
48659
- const rel = path127.relative(process.cwd(), result.path);
49104
+ const rel = path128.relative(process.cwd(), result.path);
48660
49105
  console.log(` ${style.green(symbols.ok)} Opened ${style.cyan(rel)}`);
48661
49106
  return;
48662
49107
  }
@@ -48705,7 +49150,7 @@ import { readFileSync as readFileSync10 } from "fs";
48705
49150
  // src/agents/bootstrap.ts
48706
49151
  import { mkdir as mkdir46, readFile as readFile65, writeFile as writeFile43 } from "fs/promises";
48707
49152
  import { homedir as homedir6 } from "os";
48708
- import path128 from "path";
49153
+ import path129 from "path";
48709
49154
  init_fs();
48710
49155
  var AGENT_BOOTSTRAP_START = "<!-- keryx:global-bootstrap -->";
48711
49156
  var AGENT_BOOTSTRAP_END = "<!-- /keryx:global-bootstrap -->";
@@ -48715,35 +49160,35 @@ var AGENT_BOOTSTRAP_RUNTIMES = [
48715
49160
  aliases: ["claude-code"],
48716
49161
  label: "Claude Code",
48717
49162
  fileName: "CLAUDE.md",
48718
- filePath: (homeRoot) => path128.join(homeRoot, ".claude", "CLAUDE.md")
49163
+ filePath: (homeRoot) => path129.join(homeRoot, ".claude", "CLAUDE.md")
48719
49164
  },
48720
49165
  {
48721
49166
  id: "opencode",
48722
49167
  aliases: ["open-code"],
48723
49168
  label: "OpenCode",
48724
49169
  fileName: "AGENTS.md",
48725
- filePath: (homeRoot) => path128.join(homeRoot, ".config", "opencode", "AGENTS.md")
49170
+ filePath: (homeRoot) => path129.join(homeRoot, ".config", "opencode", "AGENTS.md")
48726
49171
  },
48727
49172
  {
48728
49173
  id: "zcode",
48729
49174
  aliases: ["zed", "zed-code"],
48730
49175
  label: "ZCode",
48731
49176
  fileName: "AGENTS.md",
48732
- filePath: (homeRoot) => path128.join(homeRoot, ".zcode", "AGENTS.md")
49177
+ filePath: (homeRoot) => path129.join(homeRoot, ".zcode", "AGENTS.md")
48733
49178
  },
48734
49179
  {
48735
49180
  id: "codex",
48736
49181
  aliases: [],
48737
49182
  label: "Codex",
48738
49183
  fileName: "AGENTS.md",
48739
- filePath: (homeRoot) => path128.join(homeRoot, ".codex", "AGENTS.md")
49184
+ filePath: (homeRoot) => path129.join(homeRoot, ".codex", "AGENTS.md")
48740
49185
  },
48741
49186
  {
48742
49187
  id: "antigravity",
48743
49188
  aliases: ["antigravuty", "antigravity-code"],
48744
49189
  label: "Antigravity",
48745
49190
  fileName: "AGENTS.md",
48746
- filePath: (homeRoot) => path128.join(homeRoot, ".config", "antigravity", "AGENTS.md")
49191
+ filePath: (homeRoot) => path129.join(homeRoot, ".config", "antigravity", "AGENTS.md")
48747
49192
  }
48748
49193
  ];
48749
49194
  function agentBootstrapRuntimeIds() {
@@ -48791,7 +49236,7 @@ async function installAgentBootstrap(runtime, options = {}) {
48791
49236
  const dryRun = options.dryRun === true;
48792
49237
  const wrote = next !== current;
48793
49238
  if (wrote && !dryRun) {
48794
- await mkdir46(path128.dirname(filePath), { recursive: true });
49239
+ await mkdir46(path129.dirname(filePath), { recursive: true });
48795
49240
  await writeFile43(filePath, next, "utf8");
48796
49241
  }
48797
49242
  const status = dryRun ? statusFromContent(runtime, filePath, exists2, next) : await agentBootstrapStatus(runtime, homeRoot);
@@ -49139,7 +49584,7 @@ function printBootstrapHelp() {
49139
49584
  // src/commands/metrics.ts
49140
49585
  init_args();
49141
49586
  import { readFile as readFile66 } from "fs/promises";
49142
- import path129 from "path";
49587
+ import path130 from "path";
49143
49588
 
49144
49589
  // src/metrics/benchmark.ts
49145
49590
  function createPairedBenchmarkTemplate(taskIds) {
@@ -49367,7 +49812,7 @@ async function metricsCommand(args2 = [], projectRoot = process.cwd()) {
49367
49812
  console.log("# metrics status");
49368
49813
  console.log("");
49369
49814
  console.log(`root: ${root}`);
49370
- console.log(`enabled: ${await Bun.file(path129.join(projectRoot, ".metaproject", "metaproject.json")).exists() ? "yes" : "no"}`);
49815
+ console.log(`enabled: ${await Bun.file(path130.join(projectRoot, ".metaproject", "metaproject.json")).exists() ? "yes" : "no"}`);
49371
49816
  const latest2 = await readLatestPointer(root);
49372
49817
  console.log(`latest: ${latest2.status}`);
49373
49818
  return;
@@ -49379,7 +49824,7 @@ async function metricsCommand(args2 = [], projectRoot = process.cwd()) {
49379
49824
  process.exitCode = 1;
49380
49825
  return;
49381
49826
  }
49382
- const record2 = JSON.parse(await readFile66(path129.resolve(projectRoot, file), "utf8"));
49827
+ const record2 = JSON.parse(await readFile66(path130.resolve(projectRoot, file), "utf8"));
49383
49828
  const result = validateRunRecord(record2);
49384
49829
  console.log(result.valid ? "valid: yes" : "valid: no");
49385
49830
  for (const error of result.errors)
@@ -49404,7 +49849,7 @@ async function metricsCommand(args2 = [], projectRoot = process.cwd()) {
49404
49849
  process.exitCode = 1;
49405
49850
  return;
49406
49851
  }
49407
- const file = path129.join(metricsRoot(projectRoot), "runs", `${runId}.json`);
49852
+ const file = path130.join(metricsRoot(projectRoot), "runs", `${runId}.json`);
49408
49853
  if (!await Bun.file(file).exists()) {
49409
49854
  console.error(`Run not found: ${runId}`);
49410
49855
  process.exitCode = 1;
@@ -49421,8 +49866,8 @@ async function metricsCommand(args2 = [], projectRoot = process.cwd()) {
49421
49866
  process.exitCode = 1;
49422
49867
  return;
49423
49868
  }
49424
- const a = JSON.parse(await readFile66(path129.join(metricsRoot(projectRoot), "runs", `${runA}.json`), "utf8"));
49425
- const b = JSON.parse(await readFile66(path129.join(metricsRoot(projectRoot), "runs", `${runB}.json`), "utf8"));
49869
+ const a = JSON.parse(await readFile66(path130.join(metricsRoot(projectRoot), "runs", `${runA}.json`), "utf8"));
49870
+ const b = JSON.parse(await readFile66(path130.join(metricsRoot(projectRoot), "runs", `${runB}.json`), "utf8"));
49426
49871
  const comparison = compareExecutionRuns(a, b);
49427
49872
  console.log(stableJson(comparison));
49428
49873
  return;
@@ -49456,8 +49901,8 @@ async function metricsCommand(args2 = [], projectRoot = process.cwd()) {
49456
49901
  return;
49457
49902
  }
49458
49903
  const template = createPairedBenchmarkTemplate(taskIds);
49459
- await Bun.write(path129.resolve(projectRoot, out), stableJson(template));
49460
- console.log(`manifest: ${path129.relative(projectRoot, path129.resolve(projectRoot, out))}`);
49904
+ await Bun.write(path130.resolve(projectRoot, out), stableJson(template));
49905
+ console.log(`manifest: ${path130.relative(projectRoot, path130.resolve(projectRoot, out))}`);
49461
49906
  return;
49462
49907
  }
49463
49908
  if (subcommand === "benchmark" && args2[1] === "validate") {
@@ -49467,7 +49912,7 @@ async function metricsCommand(args2 = [], projectRoot = process.cwd()) {
49467
49912
  process.exitCode = 1;
49468
49913
  return;
49469
49914
  }
49470
- const raw = JSON.parse(await readFile66(path129.resolve(projectRoot, file), "utf8"));
49915
+ const raw = JSON.parse(await readFile66(path130.resolve(projectRoot, file), "utf8"));
49471
49916
  const input2 = Array.isArray(raw) ? raw : raw.runs ?? [];
49472
49917
  const result = validatePairedBenchmark(input2);
49473
49918
  console.log(stableJson(result));
@@ -49485,7 +49930,7 @@ async function collect(projectRoot, args2) {
49485
49930
  process.exitCode = 1;
49486
49931
  return;
49487
49932
  }
49488
- const raw = JSON.parse(await readFile66(path129.resolve(projectRoot, eventFile), "utf8"));
49933
+ const raw = JSON.parse(await readFile66(path130.resolve(projectRoot, eventFile), "utf8"));
49489
49934
  const events2 = Array.isArray(raw) ? raw : raw.events;
49490
49935
  const startedAt = optionValue(args2, "--started-at") ?? events2[0]?.timestamp_utc ?? new Date().toISOString();
49491
49936
  const finishedAt = optionValue(args2, "--finished-at") ?? events2.at(-1)?.timestamp_utc ?? startedAt;
@@ -49501,11 +49946,11 @@ async function collect(projectRoot, args2) {
49501
49946
  parentRunId: optionValue(args2, "--parent-run-id") ?? null
49502
49947
  });
49503
49948
  const result = await writeRunArtifacts(metricsRoot(projectRoot), record2, { cwd: projectRoot });
49504
- console.log(`json: ${path129.relative(projectRoot, result.jsonPath)}`);
49505
- console.log(`markdown: ${path129.relative(projectRoot, result.markdownPath)}`);
49949
+ console.log(`json: ${path130.relative(projectRoot, result.jsonPath)}`);
49950
+ console.log(`markdown: ${path130.relative(projectRoot, result.markdownPath)}`);
49506
49951
  }
49507
49952
  function metricsRoot(projectRoot) {
49508
- return path129.join(projectRoot, ".metaproject", "data", "metrics");
49953
+ return path130.join(projectRoot, ".metaproject", "data", "metrics");
49509
49954
  }
49510
49955
  function printMetricsHelp() {
49511
49956
  console.log(`keryx metrics
@@ -49523,76 +49968,35 @@ Usage:
49523
49968
  keryx metrics benchmark validate <manifest.json>
49524
49969
  `);
49525
49970
  }
49526
- // package.json
49527
- var package_default = {
49528
- name: "@mrciphersmith/keryx",
49529
- version: "0.2.17",
49530
- description: "Version-controlled project context for AI coding agents: code graph, architecture wiki, project memory, relevant tests, quality signals, and task flows.",
49531
- private: false,
49532
- publishConfig: {
49533
- access: "public"
49534
- },
49535
- license: "MIT",
49536
- type: "module",
49537
- repository: {
49538
- type: "git",
49539
- url: "git+ssh://git@github.com/MrCipherSmith/keryx.git"
49540
- },
49541
- keywords: [
49542
- "ai-agents",
49543
- "coding-agents",
49544
- "agent-harness",
49545
- "agent-context",
49546
- "repository-context",
49547
- "code-graph",
49548
- "project-memory",
49549
- "test-impact-analysis",
49550
- "developer-tools",
49551
- "model-context-protocol",
49552
- "mcp",
49553
- "claude-code",
49554
- "cursor",
49555
- "codex",
49556
- "cli",
49557
- "bun"
49558
- ],
49559
- bin: {
49560
- keryx: "./dist/cli.js"
49561
- },
49562
- scripts: {
49563
- keryx: "bun ./src/cli.ts",
49564
- build: "bun build ./src/cli.ts --outdir ./dist --target bun --external @modelcontextprotocol/sdk --external web-tree-sitter --external @opentui/core && bun build ./src/harness/process/sandbox/proxy-worker.ts --outdir ./dist --target bun --external @modelcontextprotocol/sdk --external web-tree-sitter --external @opentui/core",
49565
- prepare: "bun run build",
49566
- typecheck: "tsc --noEmit",
49567
- test: "bun test",
49568
- check: "tsc --noEmit && bun test",
49569
- "check:doc-links": "bun scripts/check-doc-links.ts",
49570
- "test:guards": "bun test src/lib/config-dir.ast.test.ts src/lib/config-dir.readers.test.ts src/lib/production-graph.test.ts src/harness/policy/profiles.test.ts src/lib/serve-server.test.ts"
49571
- },
49572
- files: [
49573
- "dist",
49574
- "src/gdgraph",
49575
- "src/gdskills/bundled",
49576
- "src/gdskills/contracts",
49577
- "LICENSE",
49578
- "README.md",
49579
- "package.json"
49580
- ],
49581
- dependencies: {},
49582
- optionalDependencies: {
49583
- "@modelcontextprotocol/sdk": "^1.0.0",
49584
- "@opentui/core": "^0.4.5",
49585
- "web-tree-sitter": "^0.22.0"
49586
- },
49587
- devDependencies: {
49588
- "@types/bun": "latest",
49589
- "bun-types": "latest",
49590
- typescript: "^5"
49591
- },
49592
- engines: {
49593
- bun: ">=1.1.0"
49971
+
49972
+ // src/commands/version.ts
49973
+ async function versionCommand(args2, deps = {}) {
49974
+ const json = args2.length === 2 && args2[1] === "--json";
49975
+ if (args2[0] !== "check" || args2.length !== 1 && !json) {
49976
+ console.error("Usage: keryx version check [--json]");
49977
+ process.exitCode = 1;
49978
+ return;
49594
49979
  }
49595
- };
49980
+ const options = {
49981
+ currentVersion: deps.currentVersion ?? package_default.version,
49982
+ ...deps.fetch !== undefined ? { fetch: deps.fetch } : {},
49983
+ ...deps.cacheDir !== undefined ? { cacheDir: deps.cacheDir } : {},
49984
+ ...deps.now !== undefined ? { now: deps.now } : {}
49985
+ };
49986
+ const result = await (deps.check ?? checkVersion)(options);
49987
+ if (json) {
49988
+ console.log(JSON.stringify(result));
49989
+ return;
49990
+ }
49991
+ if (result.status === "update-available") {
49992
+ console.log(`Keryx ${result.currentVersion} \u2192 ${result.latestVersion}`);
49993
+ console.log(result.installCommand);
49994
+ } else if (result.status === "up-to-date") {
49995
+ console.log(`Keryx ${result.currentVersion} is up to date.`);
49996
+ } else {
49997
+ console.log(`Keryx version check unavailable (${result.reason}).`);
49998
+ }
49999
+ }
49596
50000
 
49597
50001
  // src/cli.ts
49598
50002
  var VERSION2 = package_default.version;
@@ -49627,7 +50031,8 @@ var CLI_ROUTES = {
49627
50031
  harness: harnessCommand,
49628
50032
  shell: shellCommand,
49629
50033
  sessions: sessionsCommand,
49630
- session: sessionsCommand
50034
+ session: sessionsCommand,
50035
+ version: versionCommand
49631
50036
  };
49632
50037
  async function main() {
49633
50038
  const args2 = process.argv.slice(2);
@@ -49658,6 +50063,7 @@ Usage:
49658
50063
  Start TUI agent shell (sessions are per-project)
49659
50064
  keryx sessions list|fork <id>|export <id>|path
49660
50065
  List / branch / export sessions for the current project
50066
+ keryx version check [--json] Check npm latest (advisory; never installs)
49661
50067
  keryx harness run --provider <fake|anthropic|ollama> --model <m> [--base-url <url>] [--record <path>] "<prompt>"
49662
50068
  keryx harness exec [--allow-env KEY]... [--max-runtime-ms N] [--allow-real-subprocess]
49663
50069
  [--allowed-domains a,b] [--mask-env NAME@host] [--tls-terminate] [--mask-mode auto|manual|off] [--auto-mask] -- <path> [args...]
@@ -49741,6 +50147,7 @@ Commands:
49741
50147
  shell Start the interactive TUI agent harness. Use --no-tui or --chat to opt out.
49742
50148
  Sessions: -c continue last in this project, -r [id] resume (per-project).
49743
50149
  sessions List or export per-project shell sessions
50150
+ version Check whether a newer npm release is available
49744
50151
  harness Run a single provider turn (harness run) and print structured events
49745
50152
  init Initialize .metaproject in the current project
49746
50153
  status Show local Metaproject status