@mrciphersmith/keryx 0.3.40 → 0.3.41

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 +7 -0
  2. package/dist/cli.js +174 -76
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -95,6 +95,13 @@ keryx auth login <provider> # subscription login (device code / OAuth) or API k
95
95
  Inside a running session, `/provider` reopens the same add/reconfigure wizard
96
96
  and `/connect` switches between providers you already configured.
97
97
 
98
+ ChatGPT / Codex model discovery reads the current stable Codex version from npm
99
+ metadata and caches it for one day. **Test** in `/connect` (or
100
+ `keryx providers test openai-codex`) always checks for a newer version before
101
+ listing models. This does not install Codex or poll npm in the background.
102
+ If npm is unavailable, a previously discovered version is reused; without a
103
+ cached version, discovery reports the problem instead of guessing a version.
104
+
98
105
  Once you have more than one provider connected, `keryx routing` (and, inside a
99
106
  session, `/routing`) maps a task category — `review`, `subagents`, and a
100
107
  catalogue of others — to a specific model, so reviews and subagent spawns can
package/dist/cli.js CHANGED
@@ -14119,7 +14119,7 @@ var package_default;
14119
14119
  var init_package = __esm(() => {
14120
14120
  package_default = {
14121
14121
  name: "@mrciphersmith/keryx",
14122
- version: "0.3.40",
14122
+ version: "0.3.41",
14123
14123
  description: "Version-controlled project context for AI coding agents: code graph, architecture wiki, project memory, relevant tests, quality signals, and task flows.",
14124
14124
  private: false,
14125
14125
  publishConfig: {
@@ -24043,6 +24043,90 @@ var init_openai_subscription = __esm(() => {
24043
24043
  };
24044
24044
  });
24045
24045
 
24046
+ // src/lib/oauth/codex-catalog-version.ts
24047
+ import { join as join2 } from "path";
24048
+ function validVersion(value) {
24049
+ return typeof value === "string" && value.length <= 64 && /^(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)$/.test(value);
24050
+ }
24051
+ function loadCache(file, now) {
24052
+ try {
24053
+ const content = readConfigFile(file);
24054
+ if (!content.ok)
24055
+ return;
24056
+ const cache = JSON.parse(content.text);
24057
+ if (typeof cache !== "object" || cache === null)
24058
+ return;
24059
+ const record = cache;
24060
+ if (!validVersion(record.version) || typeof record.fetchedAt !== "number" || !Number.isFinite(record.fetchedAt) || record.fetchedAt < 0 || record.fetchedAt > now)
24061
+ return;
24062
+ return { version: record.version, fetchedAt: record.fetchedAt };
24063
+ } catch {
24064
+ return;
24065
+ }
24066
+ }
24067
+ async function readMetadata(response) {
24068
+ const declared = response.headers.get("content-length");
24069
+ if (declared !== null && Number(declared) > RESPONSE_LIMIT_BYTES)
24070
+ return;
24071
+ if (response.body === null)
24072
+ return;
24073
+ const reader = response.body.getReader();
24074
+ const chunks = [];
24075
+ let size = 0;
24076
+ try {
24077
+ while (true) {
24078
+ const chunk = await reader.read();
24079
+ if (chunk.done)
24080
+ break;
24081
+ size += chunk.value.byteLength;
24082
+ if (size > RESPONSE_LIMIT_BYTES) {
24083
+ await reader.cancel().catch(() => {});
24084
+ return;
24085
+ }
24086
+ chunks.push(chunk.value);
24087
+ }
24088
+ return JSON.parse(Buffer.concat(chunks).toString("utf8"));
24089
+ } finally {
24090
+ reader.releaseLock();
24091
+ }
24092
+ }
24093
+ async function resolveCodexCatalogVersion(fetchFn, opts = {}) {
24094
+ if (opts.signal?.aborted)
24095
+ return;
24096
+ const now = (opts.now ?? Date.now)();
24097
+ const file = join2(keryxConfigDir(opts.configDir), CACHE_FILE);
24098
+ const cached = loadCache(file, now);
24099
+ if (!opts.forceRefresh && cached !== undefined && now - cached.fetchedAt < CODEX_VERSION_CACHE_TTL_MS)
24100
+ return cached.version;
24101
+ try {
24102
+ const timeout = AbortSignal.timeout(2000);
24103
+ const response = await fetchFn(CODEX_VERSION_REGISTRY_URL, {
24104
+ headers: { Accept: "application/json" },
24105
+ credentials: "omit",
24106
+ redirect: "error",
24107
+ signal: opts.signal === undefined ? timeout : AbortSignal.any([opts.signal, timeout])
24108
+ });
24109
+ if (response.ok) {
24110
+ const body = await readMetadata(response);
24111
+ if (typeof body === "object" && body !== null && "name" in body && body.name === "@openai/codex" && "version" in body && validVersion(body.version) && !opts.signal?.aborted) {
24112
+ const version = body.version;
24113
+ try {
24114
+ ensureKeryxConfigDir(opts.configDir);
24115
+ writeOwnerOnlyFileAtomic(file, JSON.stringify({ version, fetchedAt: now }));
24116
+ } catch {}
24117
+ return version;
24118
+ }
24119
+ }
24120
+ } catch {}
24121
+ return opts.signal?.aborted ? undefined : cached?.version;
24122
+ }
24123
+ var CODEX_VERSION_REGISTRY_URL = "https://registry.npmjs.org/@openai%2Fcodex/latest", CODEX_VERSION_CACHE_TTL_MS, CACHE_FILE = "codex-catalog-version.json", RESPONSE_LIMIT_BYTES;
24124
+ var init_codex_catalog_version = __esm(() => {
24125
+ init_config_dir();
24126
+ CODEX_VERSION_CACHE_TTL_MS = 24 * 60 * 60 * 1000;
24127
+ RESPONSE_LIMIT_BYTES = 64 * 1024;
24128
+ });
24129
+
24046
24130
  // src/commands/subscription-models.ts
24047
24131
  async function fetchOpenAiCodexModels(fetchFn, opts = {}) {
24048
24132
  const controller = new AbortController;
@@ -24051,7 +24135,17 @@ async function fetchOpenAiCodexModels(fetchFn, opts = {}) {
24051
24135
  try {
24052
24136
  const grant = await ensureOpenAiCodexGrant({ fetch: fetchFn, signal: controller.signal, ...opts.configDir !== undefined ? { configDir: opts.configDir } : {} });
24053
24137
  authenticated = true;
24054
- const response = await fetchFn(`${OPENAI_CODEX_PICKER.baseUrl}/models?client_version=${CODEX_CATALOG_CLIENT_VERSION}`, {
24138
+ const clientVersion = await resolveCodexCatalogVersion(fetchFn, {
24139
+ signal: controller.signal,
24140
+ ...opts.refreshCatalogVersion !== undefined ? { forceRefresh: opts.refreshCatalogVersion } : {},
24141
+ ...opts.configDir !== undefined ? { configDir: opts.configDir } : {}
24142
+ });
24143
+ if (clientVersion === undefined)
24144
+ return { models: [], source: "fallback", failure: {
24145
+ kind: "unreachable",
24146
+ detail: "could not discover the Codex catalog version from npm; no cached version is available"
24147
+ } };
24148
+ const response = await fetchFn(`${OPENAI_CODEX_PICKER.baseUrl}/models?client_version=${clientVersion}`, {
24055
24149
  headers: { Authorization: `Bearer ${grant.access}`, "ChatGPT-Account-ID": grant.accountId, originator: "keryx", Accept: "application/json" },
24056
24150
  signal: controller.signal,
24057
24151
  redirect: "error"
@@ -24075,9 +24169,10 @@ async function fetchOpenAiCodexModels(fetchFn, opts = {}) {
24075
24169
  clearTimeout(timer);
24076
24170
  }
24077
24171
  }
24078
- var OPENAI_CODEX_PICKER, CODEX_CATALOG_CLIENT_VERSION = "0.157.0";
24172
+ var OPENAI_CODEX_PICKER;
24079
24173
  var init_subscription_models = __esm(() => {
24080
24174
  init_openai_subscription();
24175
+ init_codex_catalog_version();
24081
24176
  OPENAI_CODEX_PICKER = {
24082
24177
  name: "openai-codex",
24083
24178
  label: "ChatGPT / Codex",
@@ -26678,7 +26773,10 @@ function providerApiKey(provider, env = process.env) {
26678
26773
  }
26679
26774
  async function testProviderConnection(provider, fetchFn = globalThis.fetch, env = process.env, profiles) {
26680
26775
  if (provider.name === "openai-codex")
26681
- return fetchOpenAiCodexModels(fetchFn, { ...profiles?.dir !== undefined ? { configDir: profiles.dir } : {} });
26776
+ return fetchOpenAiCodexModels(fetchFn, {
26777
+ refreshCatalogVersion: true,
26778
+ ...profiles?.dir !== undefined ? { configDir: profiles.dir } : {}
26779
+ });
26682
26780
  const apiKey = providerApiKey(provider, envWithSavedApiKeys(env, profiles?.dir)) ?? provider.apiKey;
26683
26781
  return fetchOpenAiCompatModelsDetailed(fetchFn, provider, apiKey, {
26684
26782
  timeoutMs: MODELS_FETCH_TIMEOUT_MS,
@@ -31306,7 +31404,7 @@ var init_wiki_freshness = () => {};
31306
31404
 
31307
31405
  // src/harness/tool/builtin/interactive-tools.ts
31308
31406
  import { readdir as readdir9 } from "fs/promises";
31309
- import { basename, dirname, isAbsolute, join as join2, relative, resolve as resolve2, sep } from "path";
31407
+ import { basename, dirname, isAbsolute, join as join3, relative, resolve as resolve2, sep } from "path";
31310
31408
  import { lstatSync as lstatSync5, realpathSync as realpathSync3 } from "fs";
31311
31409
  async function readFromLine(file, startLine, cap) {
31312
31410
  const reader = file.stream().getReader();
@@ -31391,7 +31489,7 @@ function realpathThroughAncestors(absolute) {
31391
31489
  for (;; ) {
31392
31490
  try {
31393
31491
  const real = realpathSync3(current);
31394
- return missing.length === 0 ? real : join2(real, ...missing.reverse());
31492
+ return missing.length === 0 ? real : join3(real, ...missing.reverse());
31395
31493
  } catch {
31396
31494
  try {
31397
31495
  lstatSync5(current);
@@ -52592,7 +52690,7 @@ var init_github = __esm(() => {
52592
52690
 
52593
52691
  // src/harness/tool/metaproject-adapter.ts
52594
52692
  import { lstat as lstat8, readdir as readdir21, readFile as readFile60, realpath as realpath11, stat as stat15 } from "fs/promises";
52595
- import { isAbsolute as isAbsolute2, join as join3, relative as relative2, resolve as resolve3 } from "path";
52693
+ import { isAbsolute as isAbsolute2, join as join4, relative as relative2, resolve as resolve3 } from "path";
52596
52694
  function isMissingExecutable(cause) {
52597
52695
  const message = cause instanceof Error ? cause.message : String(cause);
52598
52696
  return /Executable not found|\bENOENT\b|not found in \$?PATH/i.test(message);
@@ -52633,7 +52731,7 @@ function portableMemoryPath(candidate) {
52633
52731
  return !isAbsolute2(normalized) && normalized !== ".." && !normalized.startsWith("../") && !normalized.includes("/../") ? normalized : null;
52634
52732
  }
52635
52733
  function confineToWiki(cwd, candidate) {
52636
- const wikiRoot = join3(cwd, ".metaproject", "wiki");
52734
+ const wikiRoot = join4(cwd, ".metaproject", "wiki");
52637
52735
  const target = resolve3(wikiRoot, candidate);
52638
52736
  const rel = relative2(wikiRoot, target);
52639
52737
  if (rel === "") {
@@ -52676,7 +52774,7 @@ async function projectRemovalTrail(cwd, query) {
52676
52774
  async function detectMemoryStoreUnreadable(cwd) {
52677
52775
  const root = memoryRoot(cwd);
52678
52776
  for (const { folder } of MEMORY_TYPES) {
52679
- const dir = join3(root, folder);
52777
+ const dir = join4(root, folder);
52680
52778
  try {
52681
52779
  await readdir21(dir);
52682
52780
  } catch (error) {
@@ -52690,7 +52788,7 @@ async function detectMemoryStoreUnreadable(cwd) {
52690
52788
  return null;
52691
52789
  }
52692
52790
  function confineToSkills(cwd, candidate) {
52693
- const skillsRoot = join3(cwd, ".metaproject", "skills", "gdskills");
52791
+ const skillsRoot = join4(cwd, ".metaproject", "skills", "gdskills");
52694
52792
  const target = resolve3(cwd, candidate);
52695
52793
  const rel = relative2(skillsRoot, target);
52696
52794
  if (rel === "") {
@@ -52705,7 +52803,7 @@ async function parseCatalogSummaries(cwd) {
52705
52803
  const summaries = new Map;
52706
52804
  let content;
52707
52805
  try {
52708
- const bytes = await readContainedFile(join3(cwd, ".metaproject", "skills"), join3(cwd, ".metaproject", "skills", "catalog.md"), { maxBytes: 512 * 1024, requireRegularFile: true });
52806
+ const bytes = await readContainedFile(join4(cwd, ".metaproject", "skills"), join4(cwd, ".metaproject", "skills", "catalog.md"), { maxBytes: 512 * 1024, requireRegularFile: true });
52709
52807
  content = bytes.toString("utf8");
52710
52808
  } catch {
52711
52809
  return summaries;
@@ -52726,7 +52824,7 @@ async function parseCatalogSummaries(cwd) {
52726
52824
  return summaries;
52727
52825
  }
52728
52826
  async function walkSkillCatalog(cwd) {
52729
- const root = join3(cwd, ".metaproject", "skills", "gdskills");
52827
+ const root = join4(cwd, ".metaproject", "skills", "gdskills");
52730
52828
  const entries = [];
52731
52829
  const rootInfo = await lstat8(root).catch(() => null);
52732
52830
  if (!rootInfo || !rootInfo.isDirectory() || rootInfo.isSymbolicLink())
@@ -52744,7 +52842,7 @@ async function walkSkillCatalog(cwd) {
52744
52842
  for (const categoryDir of categoryDirs) {
52745
52843
  if (!categoryDir.isDirectory() && !categoryDir.isSymbolicLink())
52746
52844
  continue;
52747
- const categoryPath = join3(root, categoryDir.name);
52845
+ const categoryPath = join4(root, categoryDir.name);
52748
52846
  const categoryReal = await realpath11(categoryPath).catch(() => null);
52749
52847
  const categoryInfo = await stat15(categoryPath).catch(() => null);
52750
52848
  if (!categoryReal || !categoryInfo?.isDirectory() || !isPathInside(rootReal, categoryReal))
@@ -52756,12 +52854,12 @@ async function walkSkillCatalog(cwd) {
52756
52854
  continue;
52757
52855
  }
52758
52856
  for (const skillDir of skillDirs) {
52759
- const skillPath = join3(categoryPath, skillDir.name);
52857
+ const skillPath = join4(categoryPath, skillDir.name);
52760
52858
  const skillReal = await realpath11(skillPath).catch(() => null);
52761
52859
  const skillInfo = await stat15(skillPath).catch(() => null);
52762
52860
  if (!skillReal || !skillInfo?.isDirectory() || !isPathInside(rootReal, skillReal))
52763
52861
  continue;
52764
- const skillMdPath = join3(skillPath, "SKILL.md");
52862
+ const skillMdPath = join4(skillPath, "SKILL.md");
52765
52863
  let content;
52766
52864
  try {
52767
52865
  const bytes = await readContainedFile(root, skillMdPath, { maxBytes: 512 * 1024, requireRegularFile: true });
@@ -53027,7 +53125,7 @@ function createMetaprojectAdapter(cwd, overrides = {}) {
53027
53125
  exists = false;
53028
53126
  }
53029
53127
  if (!exists) {
53030
- const relativePath = toPosix(relative2(join3(cwd, ".metaproject", "wiki"), target));
53128
+ const relativePath = toPosix(relative2(join4(cwd, ".metaproject", "wiki"), target));
53031
53129
  const registry = await readSectionRegistryState(cwd);
53032
53130
  if (registry.state === "unreadable") {
53033
53131
  return {
@@ -53068,7 +53166,7 @@ function createMetaprojectAdapter(cwd, overrides = {}) {
53068
53166
  };
53069
53167
  }
53070
53168
  try {
53071
- const content = (await readContainedFile(join3(cwd, ".metaproject", "wiki"), target, {
53169
+ const content = (await readContainedFile(join4(cwd, ".metaproject", "wiki"), target, {
53072
53170
  maxBytes: 8 * 1024 * 1024,
53073
53171
  requireRegularFile: true
53074
53172
  })).toString("utf8");
@@ -53112,7 +53210,7 @@ function createMetaprojectAdapter(cwd, overrides = {}) {
53112
53210
  }
53113
53211
  let hasWikiIndex;
53114
53212
  try {
53115
- await readFile60(join3(cwd, ".metaproject", "wiki", "index.md"), "utf8");
53213
+ await readFile60(join4(cwd, ".metaproject", "wiki", "index.md"), "utf8");
53116
53214
  hasWikiIndex = true;
53117
53215
  } catch {
53118
53216
  hasWikiIndex = false;
@@ -53387,7 +53485,7 @@ function createMetaprojectAdapter(cwd, overrides = {}) {
53387
53485
  const byName = catalog.find((entry) => entry.name === input.name);
53388
53486
  if (byName !== undefined) {
53389
53487
  try {
53390
- const content = (await readContainedFile(join3(cwd, ".metaproject", "skills", "gdskills"), join3(cwd, byName.path), {
53488
+ const content = (await readContainedFile(join4(cwd, ".metaproject", "skills", "gdskills"), join4(cwd, byName.path), {
53391
53489
  maxBytes: 512 * 1024,
53392
53490
  requireRegularFile: true
53393
53491
  })).toString("utf8");
@@ -53400,12 +53498,12 @@ function createMetaprojectAdapter(cwd, overrides = {}) {
53400
53498
  if (confined === null) {
53401
53499
  return { name: input.name, path: "", content: "", found: false };
53402
53500
  }
53403
- const byPath = catalog.find((entry) => join3(cwd, entry.path) === confined);
53501
+ const byPath = catalog.find((entry) => join4(cwd, entry.path) === confined);
53404
53502
  if (byPath === undefined) {
53405
53503
  return { name: input.name, path: "", content: "", found: false };
53406
53504
  }
53407
53505
  try {
53408
- const content = (await readContainedFile(join3(cwd, ".metaproject", "skills", "gdskills"), confined, {
53506
+ const content = (await readContainedFile(join4(cwd, ".metaproject", "skills", "gdskills"), confined, {
53409
53507
  maxBytes: 512 * 1024,
53410
53508
  requireRegularFile: true
53411
53509
  })).toString("utf8");
@@ -112748,7 +112846,7 @@ var init_jev_rules = __esm(() => {
112748
112846
 
112749
112847
  // src/commands/review-jev-rules.ts
112750
112848
  import { readFile as readFile112, readdir as readdir41, stat as stat25 } from "fs/promises";
112751
- import { basename as basename2, dirname as dirname2, isAbsolute as isAbsolute3, join as join4, relative as relative3 } from "path";
112849
+ import { basename as basename2, dirname as dirname2, isAbsolute as isAbsolute3, join as join5, relative as relative3 } from "path";
112752
112850
  function rejectUnknownJevRulesFlags(args) {
112753
112851
  const unknown = args.filter((arg) => arg.startsWith("--") && !JEV_RULES_FLAGS.includes(arg.split("=")[0]));
112754
112852
  if (unknown.length > 0) {
@@ -112790,7 +112888,7 @@ async function walkMatching(root, suffixes) {
112790
112888
  return;
112791
112889
  }
112792
112890
  for (const entry of entries) {
112793
- const full = join4(dir, entry.name);
112891
+ const full = join5(dir, entry.name);
112794
112892
  if (entry.isDirectory()) {
112795
112893
  await walk(full);
112796
112894
  continue;
@@ -112818,7 +112916,7 @@ function stripKeryxManagedBlock(text) {
112818
112916
  }
112819
112917
  async function discoverRuleSources(cwd, explicitPaths) {
112820
112918
  const discovered = [];
112821
- for (const root of [join4(cwd, ".metaproject", "rules"), join4(cwd, "rules"), join4(cwd, ".claude", "rules")]) {
112919
+ for (const root of [join5(cwd, ".metaproject", "rules"), join5(cwd, "rules"), join5(cwd, ".claude", "rules")]) {
112822
112920
  for (const file of await walkMatching(root, [".md", ".mdc"])) {
112823
112921
  const text = await readIfExists(file);
112824
112922
  if (text === undefined)
@@ -112827,7 +112925,7 @@ async function discoverRuleSources(cwd, explicitPaths) {
112827
112925
  }
112828
112926
  }
112829
112927
  for (const name of ["CLAUDE.md", "AGENTS.md"]) {
112830
- const text = await readIfExists(join4(cwd, name));
112928
+ const text = await readIfExists(join5(cwd, name));
112831
112929
  if (text === undefined)
112832
112930
  continue;
112833
112931
  const body = stripKeryxManagedBlock(text);
@@ -112836,8 +112934,8 @@ async function discoverRuleSources(cwd, explicitPaths) {
112836
112934
  break;
112837
112935
  }
112838
112936
  const skillRoots = [
112839
- [join4(cwd, ".metaproject", "project-skills"), "project-skill"],
112840
- [join4(cwd, ".metaproject", "skills", "gdskills"), "gdskill"]
112937
+ [join5(cwd, ".metaproject", "project-skills"), "project-skill"],
112938
+ [join5(cwd, ".metaproject", "skills", "gdskills"), "gdskill"]
112841
112939
  ];
112842
112940
  for (const [root, kind] of skillRoots) {
112843
112941
  for (const skillMd of await walkMatching(root, ["SKILL.md"])) {
@@ -112862,7 +112960,7 @@ async function discoverRuleSources(cwd, explicitPaths) {
112862
112960
  const explicitFiles = [];
112863
112961
  const explicitDirSources = [];
112864
112962
  for (const raw of explicitPaths) {
112865
- const resolved = isAbsolute3(raw) ? raw : join4(cwd, raw);
112963
+ const resolved = isAbsolute3(raw) ? raw : join5(cwd, raw);
112866
112964
  const stats = await stat25(resolved).catch(() => {
112867
112965
  return;
112868
112966
  });
@@ -112905,11 +113003,11 @@ async function discoverRuleSources(cwd, explicitPaths) {
112905
113003
  return { sources: [...byPath.values()].sort((a, b) => a.path.localeCompare(b.path)), excluded: excluded.sort((a, b) => a.path.localeCompare(b.path)) };
112906
113004
  }
112907
113005
  async function fixturePrPort(dir) {
112908
- const raw = await readFile112(join4(dir, "pr.json"), "utf8");
113006
+ const raw = await readFile112(join5(dir, "pr.json"), "utf8");
112909
113007
  return createFixtureConformPrPort({ pr: JSON.parse(raw) });
112910
113008
  }
112911
113009
  async function fixtureJevFetch(dir) {
112912
- const raw = await readFile112(join4(dir, "jev-responses.json"), "utf8");
113010
+ const raw = await readFile112(join5(dir, "jev-responses.json"), "utf8");
112913
113011
  const responses = JSON.parse(raw);
112914
113012
  let index = 0;
112915
113013
  const fn = async () => {
@@ -115269,7 +115367,7 @@ var init_jev_scenarios = __esm(() => {
115269
115367
 
115270
115368
  // src/commands/review-jev-scenarios.ts
115271
115369
  import { readFile as readFile121, readdir as readdir43 } from "fs/promises";
115272
- import { join as join5, relative as relative4 } from "path";
115370
+ import { join as join6, relative as relative4 } from "path";
115273
115371
  function symbolsByPathFromRegions(regions) {
115274
115372
  const byPath = new Map;
115275
115373
  for (const region of regions) {
@@ -115322,7 +115420,7 @@ async function walkMarkdown2(root) {
115322
115420
  return;
115323
115421
  }
115324
115422
  for (const entry of entries) {
115325
- const full = join5(dir, entry.name);
115423
+ const full = join6(dir, entry.name);
115326
115424
  if (entry.isDirectory()) {
115327
115425
  await walk(full);
115328
115426
  continue;
@@ -115336,19 +115434,19 @@ async function walkMarkdown2(root) {
115336
115434
  }
115337
115435
  async function discoverScenarioSources(cwd) {
115338
115436
  const scenarios = [];
115339
- for (const file of await walkMarkdown2(join5(cwd, ".metaproject", "wiki", "user-scenarios"))) {
115437
+ for (const file of await walkMarkdown2(join6(cwd, ".metaproject", "wiki", "user-scenarios"))) {
115340
115438
  const text = await readIfExists2(file);
115341
115439
  if (text === undefined)
115342
115440
  continue;
115343
115441
  scenarios.push(scenarioFromWikiPage(relative4(cwd, file), text));
115344
115442
  }
115345
- for (const file of await walkMarkdown2(join5(cwd, "docs", "requirements"))) {
115443
+ for (const file of await walkMarkdown2(join6(cwd, "docs", "requirements"))) {
115346
115444
  const text = await readIfExists2(file);
115347
115445
  if (text === undefined)
115348
115446
  continue;
115349
115447
  scenarios.push(...scenariosFromPrd(relative4(cwd, file), text));
115350
115448
  }
115351
- const readmeLikeFiles = [join5(cwd, "README.md"), ...await walkMarkdown2(join5(cwd, "docs", "docs"))];
115449
+ const readmeLikeFiles = [join6(cwd, "README.md"), ...await walkMarkdown2(join6(cwd, "docs", "docs"))];
115352
115450
  for (const file of readmeLikeFiles) {
115353
115451
  const text = await readIfExists2(file);
115354
115452
  if (text === undefined)
@@ -115358,11 +115456,11 @@ async function discoverScenarioSources(cwd) {
115358
115456
  return scenarios;
115359
115457
  }
115360
115458
  async function fixturePrPort4(dir) {
115361
- const raw = await readFile121(join5(dir, "pr.json"), "utf8");
115459
+ const raw = await readFile121(join6(dir, "pr.json"), "utf8");
115362
115460
  return createFixtureConformPrPort({ pr: JSON.parse(raw) });
115363
115461
  }
115364
115462
  async function fixtureJevFetch5(dir) {
115365
- const raw = await readFile121(join5(dir, "jev-responses.json"), "utf8");
115463
+ const raw = await readFile121(join6(dir, "jev-responses.json"), "utf8");
115366
115464
  const responses = JSON.parse(raw);
115367
115465
  let index = 0;
115368
115466
  const fn = async () => {
@@ -115987,7 +116085,7 @@ var init_jev_docs = __esm(() => {
115987
116085
 
115988
116086
  // src/commands/review-jev-docs.ts
115989
116087
  import { readFile as readFile123, readdir as readdir44 } from "fs/promises";
115990
- import { join as join6, relative as relative5 } from "path";
116088
+ import { join as join7, relative as relative5 } from "path";
115991
116089
  function messageOf4(error) {
115992
116090
  return error instanceof Error ? error.message : String(error);
115993
116091
  }
@@ -116049,7 +116147,7 @@ async function walkMatching2(root, suffixes) {
116049
116147
  return;
116050
116148
  }
116051
116149
  for (const entry of entries) {
116052
- const full = join6(dir, entry.name);
116150
+ const full = join7(dir, entry.name);
116053
116151
  if (entry.isDirectory()) {
116054
116152
  await walk(full);
116055
116153
  continue;
@@ -116109,7 +116207,7 @@ async function walkAllDocFiles(dir) {
116109
116207
  return out;
116110
116208
  }
116111
116209
  for (const entry of entries) {
116112
- const full = join6(dir, entry.name);
116210
+ const full = join7(dir, entry.name);
116113
116211
  if (entry.isDirectory()) {
116114
116212
  if (INCLUDE_WALK_PRUNE_DIRS.has(entry.name))
116115
116213
  continue;
@@ -116123,19 +116221,19 @@ async function walkAllDocFiles(dir) {
116123
116221
  async function resolveIncludeGlob(cwd, glob) {
116124
116222
  const rootDir = includeGlobRootDir(glob);
116125
116223
  const regex = includeGlobToRegExp(glob);
116126
- const candidates = await walkAllDocFiles(join6(cwd, rootDir));
116224
+ const candidates = await walkAllDocFiles(join7(cwd, rootDir));
116127
116225
  return candidates.filter((file) => regex.test(relative5(cwd, file)));
116128
116226
  }
116129
116227
  async function discoverDocFiles(cwd, includeGlobs = []) {
116130
116228
  const files = new Set;
116131
- for (const file of await walkMatching2(join6(cwd, "docs"), [".md"]))
116229
+ for (const file of await walkMatching2(join7(cwd, "docs"), [".md"]))
116132
116230
  files.add(file);
116133
116231
  for (const name of await matchingFileNames(cwd, /^readme/i)) {
116134
116232
  if (/^changelog/i.test(name))
116135
116233
  continue;
116136
- files.add(join6(cwd, name));
116234
+ files.add(join7(cwd, name));
116137
116235
  }
116138
- for (const file of await walkMatching2(join6(cwd, ".metaproject", "wiki"), [".md"]))
116236
+ for (const file of await walkMatching2(join7(cwd, ".metaproject", "wiki"), [".md"]))
116139
116237
  files.add(file);
116140
116238
  for (const glob of includeGlobs) {
116141
116239
  for (const file of await resolveIncludeGlob(cwd, glob))
@@ -116163,11 +116261,11 @@ function changedCommandNamesFromRegions(regions) {
116163
116261
  return names;
116164
116262
  }
116165
116263
  async function fixturePrPort5(dir) {
116166
- const raw = await readFile123(join6(dir, "pr.json"), "utf8");
116264
+ const raw = await readFile123(join7(dir, "pr.json"), "utf8");
116167
116265
  return createFixtureConformPrPort({ pr: JSON.parse(raw) });
116168
116266
  }
116169
116267
  async function fixtureJevFetch6(dir) {
116170
- const raw = await readFile123(join6(dir, "jev-responses.json"), "utf8");
116268
+ const raw = await readFile123(join7(dir, "jev-responses.json"), "utf8");
116171
116269
  const responses = JSON.parse(raw);
116172
116270
  let index = 0;
116173
116271
  const fn = async () => {
@@ -116585,7 +116683,7 @@ var init_jev_comments = __esm(() => {
116585
116683
 
116586
116684
  // src/commands/review-jev-comments.ts
116587
116685
  import { readFile as readFile125 } from "fs/promises";
116588
- import { join as join7 } from "path";
116686
+ import { join as join8 } from "path";
116589
116687
  function rejectUnknownJevCommentsFlags(args) {
116590
116688
  const unknown = args.filter((arg) => arg.startsWith("--") && !JEV_COMMENTS_FLAGS.includes(arg.split("=")[0]));
116591
116689
  if (unknown.length > 0) {
@@ -116671,15 +116769,15 @@ function createFixtureJevCommentsFactsPort(facts) {
116671
116769
  };
116672
116770
  }
116673
116771
  async function fixturePrPort6(dir) {
116674
- const raw = await readFile125(join7(dir, "pr.json"), "utf8");
116772
+ const raw = await readFile125(join8(dir, "pr.json"), "utf8");
116675
116773
  return createFixtureConformPrPort({ pr: JSON.parse(raw) });
116676
116774
  }
116677
116775
  async function fixtureFactsPort(dir) {
116678
- const raw = await readFile125(join7(dir, "git-facts.json"), "utf8").catch(() => "{}");
116776
+ const raw = await readFile125(join8(dir, "git-facts.json"), "utf8").catch(() => "{}");
116679
116777
  return createFixtureJevCommentsFactsPort(JSON.parse(raw));
116680
116778
  }
116681
116779
  async function fixtureJevFetch7(dir) {
116682
- const raw = await readFile125(join7(dir, "jev-responses.json"), "utf8");
116780
+ const raw = await readFile125(join8(dir, "jev-responses.json"), "utf8");
116683
116781
  const responses = JSON.parse(raw);
116684
116782
  let index = 0;
116685
116783
  const fn = async () => {
@@ -116693,7 +116791,7 @@ async function fixtureJevFetch7(dir) {
116693
116791
  return fn;
116694
116792
  }
116695
116793
  function advisoryLabelsPath(cwd, repo, number) {
116696
- return join7(cwd, ".metaproject", "data", "review-jev-comments", `labels__${repo.replace(/\//g, "__")}__${number}.json`);
116794
+ return join8(cwd, ".metaproject", "data", "review-jev-comments", `labels__${repo.replace(/\//g, "__")}__${number}.json`);
116697
116795
  }
116698
116796
  async function readCommentAdvisoryLabels(cwd, repo, number) {
116699
116797
  const file = advisoryLabelsPath(cwd, repo, number);
@@ -127961,7 +128059,7 @@ var init_narrate = __esm(() => {
127961
128059
  // src/commands/memory.ts
127962
128060
  import { randomUUID as randomUUID34 } from "crypto";
127963
128061
  import { readdir as readdir45 } from "fs/promises";
127964
- import { join as join8 } from "path";
128062
+ import { join as join9 } from "path";
127965
128063
  function getService() {
127966
128064
  service4 ??= createMemoryService();
127967
128065
  return service4;
@@ -128068,7 +128166,7 @@ async function runIndex(args) {
128068
128166
  async function detectMemoryStoreUnreadable2(cwd) {
128069
128167
  const root = memoryRoot(cwd);
128070
128168
  for (const { folder } of MEMORY_TYPES) {
128071
- const dir = join8(root, folder);
128169
+ const dir = join9(root, folder);
128072
128170
  try {
128073
128171
  await readdir45(dir);
128074
128172
  } catch (error) {
@@ -152693,7 +152791,7 @@ function referencesHash(entry) {
152693
152791
  }
152694
152792
  return hash.digest("hex");
152695
152793
  }
152696
- function loadCache(cachePath) {
152794
+ function loadCache2(cachePath) {
152697
152795
  if (!existsSync51(cachePath))
152698
152796
  return {};
152699
152797
  try {
@@ -152821,7 +152919,7 @@ async function runStocktake(root, options = {}) {
152821
152919
  const now = options.now ?? (() => new Date);
152822
152920
  const { entries: catalog, unreadable } = loadSkillCatalogWithDiagnostics(root, { scope });
152823
152921
  const cachePath = options.cachePath ?? path295.join(root, ".metaproject", "data", "skills", "stocktake", "cache.json");
152824
- const cache = loadCache(cachePath);
152922
+ const cache = loadCache2(cachePath);
152825
152923
  const fingerprint = catalogFingerprint(catalog);
152826
152924
  let hits = 0;
152827
152925
  let misses = 0;
@@ -159572,7 +159670,7 @@ var init_post = __esm(() => {
159572
159670
 
159573
159671
  // src/commands/review-bot.ts
159574
159672
  import { readFile as readFile158 } from "fs/promises";
159575
- import { join as join9 } from "path";
159673
+ import { join as join10 } from "path";
159576
159674
  function reject(args, valued, boolean, usage) {
159577
159675
  const problems = [];
159578
159676
  for (let index = 0;index < args.length; index += 1) {
@@ -159601,7 +159699,7 @@ async function portFor(args) {
159601
159699
  return createGhPort();
159602
159700
  let pull;
159603
159701
  try {
159604
- pull = JSON.parse(await readFile158(join9(fixtures, "pull.json"), "utf8"));
159702
+ pull = JSON.parse(await readFile158(join10(fixtures, "pull.json"), "utf8"));
159605
159703
  } catch {
159606
159704
  pull = {};
159607
159705
  }
@@ -161041,7 +161139,7 @@ var init_floor = __esm(() => {
161041
161139
 
161042
161140
  // src/commands/review.ts
161043
161141
  import { mkdir as mkdir66, readdir as readdir58, readFile as readFile162 } from "fs/promises";
161044
- import path317, { join as join10 } from "path";
161142
+ import path317, { join as join11 } from "path";
161045
161143
  function reviewerList(args) {
161046
161144
  return (optionValue(args, "--reviewers") ?? "").split(",").map((item) => item.trim()).filter(Boolean);
161047
161145
  }
@@ -161716,7 +161814,7 @@ async function resolvePort(args) {
161716
161814
  }
161717
161815
  const files = {};
161718
161816
  for (const key of ["pull", "pull-comments", "pull-reviews", "issue-comments"]) {
161719
- const file = join10(fixtures, `${key}.json`);
161817
+ const file = join11(fixtures, `${key}.json`);
161720
161818
  try {
161721
161819
  files[key] = JSON.parse(await readFile162(file, "utf8"));
161722
161820
  } catch {
@@ -161726,13 +161824,13 @@ async function resolvePort(args) {
161726
161824
  return createFixturePort(files);
161727
161825
  }
161728
161826
  async function fixtureCiPort(dir) {
161729
- const runInfo = JSON.parse(await readFile162(join10(dir, "ci-run-info.json"), "utf8"));
161730
- const log = await readFile162(join10(dir, "ci-failed-log.txt"), "utf8").catch(() => "");
161731
- const history = await readFile162(join10(dir, "ci-history.json"), "utf8").then((raw) => JSON.parse(raw)).catch(() => ({}));
161732
- const attempts = await readFile162(join10(dir, "ci-attempts.json"), "utf8").then((raw) => JSON.parse(raw)).catch(() => ({}));
161733
- const changedFiles = await readFile162(join10(dir, "ci-changed-files.json"), "utf8").then((raw) => JSON.parse(raw)).catch(() => ({}));
161734
- const runsByHeadSha = await readFile162(join10(dir, "ci-runs-by-head-sha.json"), "utf8").then((raw) => JSON.parse(raw)).catch(() => ({}));
161735
- const related = await readFile162(join10(dir, "ci-related-runs.json"), "utf8").then((raw) => JSON.parse(raw)).catch(() => ({}));
161827
+ const runInfo = JSON.parse(await readFile162(join11(dir, "ci-run-info.json"), "utf8"));
161828
+ const log = await readFile162(join11(dir, "ci-failed-log.txt"), "utf8").catch(() => "");
161829
+ const history = await readFile162(join11(dir, "ci-history.json"), "utf8").then((raw) => JSON.parse(raw)).catch(() => ({}));
161830
+ const attempts = await readFile162(join11(dir, "ci-attempts.json"), "utf8").then((raw) => JSON.parse(raw)).catch(() => ({}));
161831
+ const changedFiles = await readFile162(join11(dir, "ci-changed-files.json"), "utf8").then((raw) => JSON.parse(raw)).catch(() => ({}));
161832
+ const runsByHeadSha = await readFile162(join11(dir, "ci-runs-by-head-sha.json"), "utf8").then((raw) => JSON.parse(raw)).catch(() => ({}));
161833
+ const related = await readFile162(join11(dir, "ci-related-runs.json"), "utf8").then((raw) => JSON.parse(raw)).catch(() => ({}));
161736
161834
  return createFixtureCiPort({
161737
161835
  runs: { [runInfo.runId]: runInfo, ...related.runs },
161738
161836
  logs: { [runInfo.runId]: log, ...related.logs },
@@ -161743,7 +161841,7 @@ async function fixtureCiPort(dir) {
161743
161841
  });
161744
161842
  }
161745
161843
  async function fixtureJevFetch9(dir) {
161746
- const body = await readFile162(join10(dir, "jev-response.json"), "utf8");
161844
+ const body = await readFile162(join11(dir, "jev-response.json"), "utf8");
161747
161845
  const fn = async () => new Response(body, { status: 200, headers: { "content-type": "application/json" } });
161748
161846
  return fn;
161749
161847
  }
@@ -161912,12 +162010,12 @@ async function runCiTriageEval(cwd, args, evalFile) {
161912
162010
  }
161913
162011
  }
161914
162012
  async function fixtureConformJevFetch(dir) {
161915
- const body = await readFile162(join10(dir, "jev-response.json"), "utf8");
162013
+ const body = await readFile162(join11(dir, "jev-response.json"), "utf8");
161916
162014
  const fn = async () => new Response(body, { status: 200, headers: { "content-type": "application/json" } });
161917
162015
  return fn;
161918
162016
  }
161919
162017
  async function fixtureConformPrPortFrom(dir) {
161920
- const pr = JSON.parse(await readFile162(join10(dir, "pr.json"), "utf8"));
162018
+ const pr = JSON.parse(await readFile162(join11(dir, "pr.json"), "utf8"));
161921
162019
  return createFixtureConformPrPort({ pr });
161922
162020
  }
161923
162021
  function accumulateJevUsage(acc, usage) {
@@ -161998,7 +162096,7 @@ async function explainConformVerdicts(cwd, refPath, verdicts, threshold, fixture
161998
162096
  return {};
161999
162097
  if (fixturesDir !== undefined) {
162000
162098
  try {
162001
- return JSON.parse(await readFile162(join10(fixturesDir, "explain-response.json"), "utf8"));
162099
+ return JSON.parse(await readFile162(join11(fixturesDir, "explain-response.json"), "utf8"));
162002
162100
  } catch {
162003
162101
  return {};
162004
162102
  }
@@ -162046,7 +162144,7 @@ function parsePositiveIntFlag(args, flag, fallback) {
162046
162144
  }
162047
162145
  async function readReportFindings2(reportDir) {
162048
162146
  try {
162049
- const raw = JSON.parse(await readFile162(join10(reportDir, "findings.json"), "utf8"));
162147
+ const raw = JSON.parse(await readFile162(join11(reportDir, "findings.json"), "utf8"));
162050
162148
  return Array.isArray(raw) ? raw : [];
162051
162149
  } catch {
162052
162150
  return [];
@@ -162118,7 +162216,7 @@ async function runConform(args) {
162118
162216
  const fetchFn = fixturesDir === undefined ? globalThis.fetch : await fixtureConformJevFetch(fixturesDir);
162119
162217
  const acc = { jevCalls: 0, inputTokens: 0, outputTokens: 0, costUsd: 0, sawUsage: false };
162120
162218
  const clauses = await resolveConformClauseTags(cwd, refPath, docText, rawClauses, fetchFn, model, acc);
162121
- await writeFileAtomic(join10(cwd, CONFORM_RECENTS_PATH), `${JSON.stringify(withRecentDoc(await readRecentConformDocs2(cwd), refPath), null, 2)}
162219
+ await writeFileAtomic(join11(cwd, CONFORM_RECENTS_PATH), `${JSON.stringify(withRecentDoc(await readRecentConformDocs2(cwd), refPath), null, 2)}
162122
162220
  `, { mode: 384 }).catch(() => {});
162123
162221
  const notCheckable = clauses.filter((c) => !c.checkable);
162124
162222
  const checkable = clauses.filter((c) => c.checkable);
@@ -162150,7 +162248,7 @@ async function runConform(args) {
162150
162248
  hunkTotalRegions = hunkResult.totalRegions;
162151
162249
  } else if (reportDir !== undefined) {
162152
162250
  target = { kind: "report", label: reportDir };
162153
- const reportMarkdown = await readFile162(join10(reportDir, "report.md"), "utf8").catch(() => "");
162251
+ const reportMarkdown = await readFile162(join11(reportDir, "report.md"), "utf8").catch(() => "");
162154
162252
  const findings = await readReportFindings2(reportDir);
162155
162253
  const facts = computeReportConformFacts(reportMarkdown, findings);
162156
162254
  const redacted = reportRedactedStateText(reportMarkdown);
@@ -162191,7 +162289,7 @@ async function runConform(args) {
162191
162289
  }
162192
162290
  async function readRecentConformDocs2(cwd) {
162193
162291
  try {
162194
- const raw = JSON.parse(await readFile162(join10(cwd, CONFORM_RECENTS_PATH), "utf8"));
162292
+ const raw = JSON.parse(await readFile162(join11(cwd, CONFORM_RECENTS_PATH), "utf8"));
162195
162293
  return Array.isArray(raw) ? raw : [];
162196
162294
  } catch {
162197
162295
  return [];
@@ -162440,7 +162538,7 @@ async function runStack(args) {
162440
162538
  console.log(renderStackScopingMarkdown(detected, decisions));
162441
162539
  }
162442
162540
  async function stackScopingForInstalledReviewers(cwd, detected) {
162443
- const reviewRoot = join10(cwd, ".metaproject", "skills", "gdskills", "review");
162541
+ const reviewRoot = join11(cwd, ".metaproject", "skills", "gdskills", "review");
162444
162542
  let entries;
162445
162543
  try {
162446
162544
  entries = await readdir58(reviewRoot, { withFileTypes: true });
@@ -162452,7 +162550,7 @@ async function stackScopingForInstalledReviewers(cwd, detected) {
162452
162550
  if (!entry.isDirectory()) {
162453
162551
  continue;
162454
162552
  }
162455
- const skillMdPath = join10(reviewRoot, entry.name, "SKILL.md");
162553
+ const skillMdPath = join11(reviewRoot, entry.name, "SKILL.md");
162456
162554
  let content;
162457
162555
  try {
162458
162556
  content = await readFile162(skillMdPath, "utf8");
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mrciphersmith/keryx",
3
- "version": "0.3.40",
3
+ "version": "0.3.41",
4
4
  "description": "Version-controlled project context for AI coding agents: code graph, architecture wiki, project memory, relevant tests, quality signals, and task flows.",
5
5
  "private": false,
6
6
  "publishConfig": {