@mrciphersmith/keryx 0.3.43 → 0.3.46

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/core.js CHANGED
@@ -7067,6 +7067,9 @@ async function assertContained(root, rel, opts = {}) {
7067
7067
  const resolvedPath = targetReal ?? targetPath;
7068
7068
  return { targetPath, dir: path31.dirname(targetPath), resolvedPath, resolvedDir: path31.dirname(resolvedPath) };
7069
7069
  }
7070
+ async function assertWritableContained(root, rel, kind = "file") {
7071
+ await assertContained(root, rel, kind === "file" ? { requireRegularIfExists: true } : { requireDirectoryIfExists: true });
7072
+ }
7070
7073
  async function pathExistsLstat(p) {
7071
7074
  try {
7072
7075
  await lstat5(p);
@@ -7369,9 +7372,212 @@ var init_codecs = __esm(() => {
7369
7372
  allow: (_runtimeId) => ({ exitCode: 0 })
7370
7373
  };
7371
7374
  });
7375
+ // src/lib/git-worktrees.ts
7376
+ var init_git_worktrees = () => {};
7372
7377
 
7373
- // src/integrations/surfaces.ts
7378
+ // src/rules/entrypoint-targets.ts
7379
+ function localRootEntry(runtime, source = DEFAULT_CODEX_SOURCE) {
7380
+ return runtime === "claude" ? { runtime, path: LOCAL_ROOT_PATH.claude, scope: "local" } : { runtime, path: LOCAL_ROOT_PATH.codex, scope: "local", mode: "override", source };
7381
+ }
7382
+ function sharedRootEntry(runtime, entryPath = SHARED_ROOT_PATH[runtime]) {
7383
+ return { runtime, path: entryPath, scope: "shared" };
7384
+ }
7385
+ function localClaudeSettingsTarget() {
7386
+ return { path: LOCAL_SETTINGS_PATH, scope: "local" };
7387
+ }
7388
+ function sharedClaudeSettingsTarget() {
7389
+ return { path: SHARED_SETTINGS_PATH, scope: "shared" };
7390
+ }
7391
+ function normalizeEntrypointTargets(agentEntrypoints) {
7392
+ const raw = isRecord(agentEntrypoints) ? agentEntrypoints : {};
7393
+ const rawRoot = Array.isArray(raw.root) ? raw.root : [];
7394
+ const root = [];
7395
+ const legacy = [];
7396
+ const ignored = [];
7397
+ const seen = new Set;
7398
+ for (const item of rawRoot) {
7399
+ const fromString = typeof item === "string" ? rootEntryFromLegacyString(item) : undefined;
7400
+ const entry = fromString ?? (isRecord(item) ? rootEntryFromRecord(item) : undefined);
7401
+ if (entry === undefined || seen.has(entry.runtime)) {
7402
+ ignored.push(item);
7403
+ continue;
7404
+ }
7405
+ seen.add(entry.runtime);
7406
+ root.push(entry);
7407
+ if (fromString !== undefined && entry.scope === "shared") {
7408
+ legacy.push({ kind: "root", runtime: entry.runtime, path: entry.path });
7409
+ }
7410
+ }
7411
+ for (const runtime of RUNTIMES) {
7412
+ if (seen.has(runtime))
7413
+ continue;
7414
+ const entry = sharedRootEntry(runtime);
7415
+ root.push(entry);
7416
+ legacy.push({ kind: "root", runtime, path: entry.path });
7417
+ }
7418
+ let claudeSettings = claudeSettingsFromValue(raw.claudeSettings);
7419
+ if (claudeSettings === undefined) {
7420
+ claudeSettings = sharedClaudeSettingsTarget();
7421
+ legacy.push({ kind: "claudeSettings", path: claudeSettings.path });
7422
+ }
7423
+ const targets = { root, claudeSettings };
7424
+ const needsRewrite = JSON.stringify(raw.root) !== JSON.stringify(targets.root) || JSON.stringify(raw.claudeSettings) !== JSON.stringify(targets.claudeSettings);
7425
+ return { targets, legacy, ignored, needsRewrite };
7426
+ }
7427
+ function isRecord(value) {
7428
+ return typeof value === "object" && value !== null && !Array.isArray(value);
7429
+ }
7430
+ function safeRelativePath(value) {
7431
+ if (typeof value !== "string" || value.length === 0 || /[\u0000-\u001f\u007f]/.test(value))
7432
+ return;
7433
+ if (value.startsWith("/") || value.startsWith("\\") || /^[A-Za-z]:/.test(value))
7434
+ return;
7435
+ if (value.startsWith("!") || value.startsWith("#"))
7436
+ return;
7437
+ if (value.split(/[\\/]/).includes(".."))
7438
+ return;
7439
+ return value;
7440
+ }
7441
+ function scopeOf(value) {
7442
+ return value === "local" || value === "shared" ? value : undefined;
7443
+ }
7444
+ function runtimeOf(value) {
7445
+ return value === "claude" || value === "codex" ? value : undefined;
7446
+ }
7447
+ function rootEntryFromLegacyString(value) {
7448
+ const lower = value.toLowerCase();
7449
+ for (const runtime of RUNTIMES) {
7450
+ if (lower === SHARED_ROOT_PATH[runtime].toLowerCase())
7451
+ return sharedRootEntry(runtime, value);
7452
+ if (lower === LOCAL_ROOT_PATH[runtime].toLowerCase())
7453
+ return localRootEntry(runtime);
7454
+ }
7455
+ return;
7456
+ }
7457
+ function rootEntryFromRecord(value) {
7458
+ const runtime = runtimeOf(value.runtime);
7459
+ if (runtime === undefined)
7460
+ return;
7461
+ const statedPath = safeRelativePath(value.path);
7462
+ if (value.path !== undefined && statedPath === undefined)
7463
+ return;
7464
+ const statedScope = scopeOf(value.scope);
7465
+ if (statedPath === undefined && statedScope === undefined)
7466
+ return;
7467
+ const scope = statedScope ?? (statedPath?.toLowerCase() === LOCAL_ROOT_PATH[runtime].toLowerCase() ? "local" : "shared");
7468
+ if (scope === "shared")
7469
+ return sharedRootEntry(runtime, statedPath ?? SHARED_ROOT_PATH[runtime]);
7470
+ if (runtime === "claude")
7471
+ return localRootEntry(runtime);
7472
+ return {
7473
+ ...localRootEntry(runtime, safeRelativePath(value.source) ?? DEFAULT_CODEX_SOURCE),
7474
+ mode: value.mode === "skip" ? "skip" : "override"
7475
+ };
7476
+ }
7477
+ function claudeSettingsFromValue(value) {
7478
+ if (!isRecord(value))
7479
+ return;
7480
+ const statedPath = safeRelativePath(value.path);
7481
+ if (value.path !== undefined && statedPath === undefined)
7482
+ return;
7483
+ const statedScope = scopeOf(value.scope);
7484
+ if (statedPath === undefined && statedScope === undefined)
7485
+ return;
7486
+ const scope = statedScope ?? (statedPath === LOCAL_SETTINGS_PATH ? "local" : "shared");
7487
+ return scope === "local" ? localClaudeSettingsTarget() : sharedClaudeSettingsTarget();
7488
+ }
7489
+ var RUNTIMES, SHARED_ROOT_PATH, LOCAL_ROOT_PATH, SHARED_SETTINGS_PATH = ".claude/settings.json", LOCAL_SETTINGS_PATH = ".claude/settings.local.json", DEFAULT_CODEX_SOURCE;
7490
+ var init_entrypoint_targets = __esm(() => {
7491
+ RUNTIMES = ["claude", "codex"];
7492
+ SHARED_ROOT_PATH = { claude: "CLAUDE.md", codex: "AGENTS.md" };
7493
+ LOCAL_ROOT_PATH = { claude: "CLAUDE.local.md", codex: "AGENTS.override.md" };
7494
+ DEFAULT_CODEX_SOURCE = SHARED_ROOT_PATH.codex;
7495
+ });
7496
+ // src/rules/managed-index-block.ts
7497
+ var init_managed_index_block = () => {};
7498
+
7499
+ // src/rules/entrypoint-migration.ts
7500
+ function settingsTextHasManagedHooks(text) {
7501
+ let parsed;
7502
+ try {
7503
+ parsed = JSON.parse(text);
7504
+ } catch {
7505
+ return false;
7506
+ }
7507
+ if (!isRecord2(parsed) || !isRecord2(parsed.hooks))
7508
+ return false;
7509
+ return Object.values(parsed.hooks).some((groups) => Array.isArray(groups) && groups.some((group) => isRecord2(group) && typeof group[MANAGED_KEY] === "string"));
7510
+ }
7511
+ function isRecord2(value) {
7512
+ return typeof value === "object" && value !== null && !Array.isArray(value);
7513
+ }
7514
+ var init_entrypoint_migration = __esm(() => {
7515
+ init_settings_json();
7516
+ init_git_worktrees();
7517
+ init_entrypoint_targets();
7518
+ init_managed_index_block();
7519
+ });
7520
+
7521
+ // src/integrations/claude-settings.ts
7522
+ import { readFileSync as readFileSync2 } from "fs";
7374
7523
  import path32 from "path";
7524
+ function readTextSync(file) {
7525
+ try {
7526
+ return readFileSync2(file, "utf8");
7527
+ } catch {
7528
+ return;
7529
+ }
7530
+ }
7531
+ function statedClaudeSettingsTarget(projectRoot) {
7532
+ const text = readTextSync(path32.join(projectRoot, ...MANIFEST_REL.split("/")));
7533
+ if (text === undefined)
7534
+ return;
7535
+ let manifest;
7536
+ try {
7537
+ manifest = JSON.parse(text);
7538
+ } catch {
7539
+ return;
7540
+ }
7541
+ const agentEntrypoints = typeof manifest === "object" && manifest !== null ? manifest.agentEntrypoints : undefined;
7542
+ const normalized = normalizeEntrypointTargets(agentEntrypoints);
7543
+ if (normalized.legacy.some((entry) => entry.kind === "claudeSettings"))
7544
+ return;
7545
+ return normalized.targets.claudeSettings.scope === "shared" ? sharedClaudeSettingsTarget() : localClaudeSettingsTarget();
7546
+ }
7547
+ function claudeSettingsFileHoldsManagedHooks(projectRoot, relativePath) {
7548
+ const text = readTextSync(path32.join(projectRoot, ...relativePath.split("/")));
7549
+ return text !== undefined && settingsTextHasManagedHooks(text);
7550
+ }
7551
+ function resolveClaudeSettingsTarget(projectRoot) {
7552
+ const stated = statedClaudeSettingsTarget(projectRoot);
7553
+ if (stated?.scope === "shared")
7554
+ return stated;
7555
+ if (claudeSettingsFileHoldsManagedHooks(projectRoot, CLAUDE_SHARED_SETTINGS_PATH))
7556
+ return sharedClaudeSettingsTarget();
7557
+ return localClaudeSettingsTarget();
7558
+ }
7559
+ function claudeSettingsRelativePath(projectRoot) {
7560
+ return resolveClaudeSettingsTarget(projectRoot).path;
7561
+ }
7562
+ function claudeSettingsLocation() {
7563
+ return {
7564
+ relativePath: CLAUDE_LOCAL_SETTINGS_PATH,
7565
+ relativePathCandidates: CLAUDE_SETTINGS_PATHS,
7566
+ relativePathFor: claudeSettingsRelativePath,
7567
+ settingsFile: (projectRoot) => path32.join(projectRoot, ...claudeSettingsRelativePath(projectRoot).split("/"))
7568
+ };
7569
+ }
7570
+ var CLAUDE_LOCAL_SETTINGS_PATH, CLAUDE_SHARED_SETTINGS_PATH, CLAUDE_SETTINGS_PATHS, MANIFEST_REL = ".metaproject/metaproject.json";
7571
+ var init_claude_settings = __esm(() => {
7572
+ init_entrypoint_migration();
7573
+ init_entrypoint_targets();
7574
+ CLAUDE_LOCAL_SETTINGS_PATH = localClaudeSettingsTarget().path;
7575
+ CLAUDE_SHARED_SETTINGS_PATH = sharedClaudeSettingsTarget().path;
7576
+ CLAUDE_SETTINGS_PATHS = [CLAUDE_LOCAL_SETTINGS_PATH, CLAUDE_SHARED_SETTINGS_PATH];
7577
+ });
7578
+
7579
+ // src/integrations/surfaces.ts
7580
+ import path33 from "path";
7375
7581
  function ctxHookCommand(id) {
7376
7582
  return `keryx ctx hook ${id}`;
7377
7583
  }
@@ -7416,7 +7622,7 @@ function nestedCtxSurface(opts) {
7416
7622
  confidence,
7417
7623
  sourceDocs,
7418
7624
  ...riskNotes !== undefined ? { riskNotes } : {},
7419
- settingsFile: (root) => path32.join(root, ...relativePath.split("/")),
7625
+ settingsFile: (root) => path33.join(root, ...relativePath.split("/")),
7420
7626
  relativePath,
7421
7627
  slots: [
7422
7628
  { key: container ?? "hooks", type: "object", access: "owns" },
@@ -7530,7 +7736,7 @@ function flatSecuritySurface(harnessId, on, relativePath) {
7530
7736
  "`securityHooks` matches no documented hook contract for this harness; it is keryx's own invention (tracked as OQ-3)."
7531
7737
  ],
7532
7738
  sourceDocs: ["src/security/agent-hooks/runtimes.ts"],
7533
- settingsFile: (root) => path32.join(root, ...relativePath.split("/")),
7739
+ settingsFile: (root) => path33.join(root, ...relativePath.split("/")),
7534
7740
  relativePath,
7535
7741
  slots: [
7536
7742
  { key: SECURITY_HOOKS_KEY, type: "array", access: "owns" },
@@ -7587,14 +7793,18 @@ export const KeryxCtxGuard = async () => ({
7587
7793
  var init_surfaces = __esm(() => {
7588
7794
  init_settings_json();
7589
7795
  init_codecs();
7590
- CTX_GUARD_CLAUDE = nestedCtxSurface({
7591
- id: "claude",
7592
- relativePath: ".claude/settings.json",
7593
- confidence: "verified",
7594
- nativeSearchTools: ["Grep"],
7595
- key: "PreToolUse",
7596
- sourceDocs: ["src/ctx/runtimes.ts"]
7597
- });
7796
+ init_claude_settings();
7797
+ CTX_GUARD_CLAUDE = {
7798
+ ...nestedCtxSurface({
7799
+ id: "claude",
7800
+ relativePath: CLAUDE_LOCAL_SETTINGS_PATH,
7801
+ confidence: "verified",
7802
+ nativeSearchTools: ["Grep"],
7803
+ key: "PreToolUse",
7804
+ sourceDocs: ["src/ctx/runtimes.ts"]
7805
+ }),
7806
+ ...claudeSettingsLocation()
7807
+ };
7598
7808
  CTX_GUARD_CODEX = nestedCtxSurface({
7599
7809
  id: "codex",
7600
7810
  relativePath: ".codex/hooks.json",
@@ -7609,7 +7819,7 @@ var init_surfaces = __esm(() => {
7609
7819
  sentinel: CTX_HOOK_SENTINEL,
7610
7820
  confidence: "verified",
7611
7821
  sourceDocs: ["src/ctx/runtimes.ts"],
7612
- settingsFile: (root) => path32.join(root, ".cursor", "hooks.json"),
7822
+ settingsFile: (root) => path33.join(root, ".cursor", "hooks.json"),
7613
7823
  relativePath: ".cursor/hooks.json",
7614
7824
  slots: [
7615
7825
  { key: "hooks", type: "object", access: "owns" },
@@ -7636,7 +7846,7 @@ var init_surfaces = __esm(() => {
7636
7846
  sentinel: CTX_HOOK_SENTINEL,
7637
7847
  confidence: "verified",
7638
7848
  sourceDocs: ["src/ctx/runtimes.ts"],
7639
- settingsFile: (root) => path32.join(root, ".windsurf", "hooks.json"),
7849
+ settingsFile: (root) => path33.join(root, ".windsurf", "hooks.json"),
7640
7850
  relativePath: ".windsurf/hooks.json",
7641
7851
  slots: [
7642
7852
  { key: "hooks", type: "object", access: "owns" },
@@ -7660,7 +7870,7 @@ var init_surfaces = __esm(() => {
7660
7870
  confidence: "experimental",
7661
7871
  riskNotes: ["No first-party docs confirm this hook contract; verify on a live install."],
7662
7872
  sourceDocs: ["src/ctx/runtimes.ts"],
7663
- settingsFile: (root) => path32.join(root, ".agents", "hooks.json"),
7873
+ settingsFile: (root) => path33.join(root, ".agents", "hooks.json"),
7664
7874
  relativePath: ".agents/hooks.json",
7665
7875
  slots: [
7666
7876
  { key: ANTIGRAVITY_CONTAINER, type: "object", access: "owns" },
@@ -7713,7 +7923,7 @@ var init_surfaces = __esm(() => {
7713
7923
  confidence: "experimental",
7714
7924
  riskNotes: ["Bridged via a generated plugin file rather than a documented hook config."],
7715
7925
  sourceDocs: ["src/ctx/runtimes.ts"],
7716
- settingsFile: (root) => path32.join(root, ...OPENCODE_PLUGIN_RELATIVE_PATH.split("/")),
7926
+ settingsFile: (root) => path33.join(root, ...OPENCODE_PLUGIN_RELATIVE_PATH.split("/")),
7717
7927
  relativePath: OPENCODE_PLUGIN_RELATIVE_PATH,
7718
7928
  slots: [],
7719
7929
  label: OPENCODE_PLUGIN_RELATIVE_PATH,
@@ -7750,8 +7960,7 @@ var init_surfaces = __esm(() => {
7750
7960
  sentinel: ORIENT_SENTINEL,
7751
7961
  confidence: "verified",
7752
7962
  sourceDocs: ["src/ctx/orient-runtimes.ts"],
7753
- settingsFile: (root) => path32.join(root, ".claude", "settings.json"),
7754
- relativePath: ".claude/settings.json",
7963
+ ...claudeSettingsLocation(),
7755
7964
  slots: [
7756
7965
  { key: "hooks", type: "object", access: "owns" },
7757
7966
  { key: "_keryxManaged", type: "array", access: "owns" },
@@ -7760,7 +7969,7 @@ var init_surfaces = __esm(() => {
7760
7969
  merge: (s) => mergeIntoHookArray(s, "UserPromptSubmit", { hooks: [{ type: "command", command: orientHookCommand("claude") }], [MANAGED_KEY]: ORIENT_SENTINEL }, ORIENT_SENTINEL),
7761
7970
  strip: (s) => stripFromHookArray(s, "UserPromptSubmit", ORIENT_SENTINEL),
7762
7971
  validate: orientValidate("claude", "UserPromptSubmit", "claude: missing UserPromptSubmit orientation hook"),
7763
- label: ".claude/settings.json (UserPromptSubmit)",
7972
+ label: `${CLAUDE_LOCAL_SETTINGS_PATH} (UserPromptSubmit)`,
7764
7973
  groupKey: "UserPromptSubmit",
7765
7974
  groupContainer: "hooks"
7766
7975
  };
@@ -7771,7 +7980,7 @@ var init_surfaces = __esm(() => {
7771
7980
  sentinel: ORIENT_SENTINEL,
7772
7981
  confidence: "verified",
7773
7982
  sourceDocs: ["src/ctx/orient-runtimes.ts"],
7774
- settingsFile: (root) => path32.join(root, ".codex", "hooks.json"),
7983
+ settingsFile: (root) => path33.join(root, ".codex", "hooks.json"),
7775
7984
  relativePath: ".codex/hooks.json",
7776
7985
  slots: [
7777
7986
  { key: "hooks", type: "object", access: "owns" },
@@ -7792,7 +8001,7 @@ var init_surfaces = __esm(() => {
7792
8001
  sentinel: ORIENT_SENTINEL,
7793
8002
  confidence: "verified",
7794
8003
  sourceDocs: ["src/ctx/orient-runtimes.ts"],
7795
- settingsFile: (root) => path32.join(root, ".cursor", "hooks.json"),
8004
+ settingsFile: (root) => path33.join(root, ".cursor", "hooks.json"),
7796
8005
  relativePath: ".cursor/hooks.json",
7797
8006
  slots: [
7798
8007
  { key: "hooks", type: "object", access: "owns" },
@@ -7823,8 +8032,7 @@ var init_surfaces = __esm(() => {
7823
8032
  sentinel: AGENT_HOOKS_SENTINEL,
7824
8033
  confidence: "verified",
7825
8034
  sourceDocs: ["src/security/agent-hooks/runtimes.ts"],
7826
- settingsFile: (root) => path32.join(root, ".claude", "settings.json"),
7827
- relativePath: ".claude/settings.json",
8035
+ ...claudeSettingsLocation(),
7828
8036
  slots: [
7829
8037
  { key: "hooks", type: "object", access: "owns" },
7830
8038
  { key: "_keryxManaged", type: "array", access: "owns" },
@@ -7843,8 +8051,7 @@ var init_surfaces = __esm(() => {
7843
8051
  sentinel: AGENT_HOOKS_SENTINEL,
7844
8052
  confidence: "verified",
7845
8053
  sourceDocs: ["src/security/agent-hooks/runtimes.ts"],
7846
- settingsFile: (root) => path32.join(root, ".claude", "settings.json"),
7847
- relativePath: ".claude/settings.json",
8054
+ ...claudeSettingsLocation(),
7848
8055
  slots: [
7849
8056
  { key: "hooks", type: "object", access: "owns" },
7850
8057
  { key: "_keryxManaged", type: "array", access: "owns" },
@@ -7870,7 +8077,7 @@ var init_surfaces = __esm(() => {
7870
8077
 
7871
8078
  // src/integrations/markdown-block.ts
7872
8079
  import { readFile as readFile15 } from "fs/promises";
7873
- import path33 from "path";
8080
+ import path34 from "path";
7874
8081
  function dominantEol(content) {
7875
8082
  const crlf = (content.match(/\r\n/g) ?? []).length;
7876
8083
  const totalLf = (content.match(/\n/g) ?? []).length;
@@ -7922,7 +8129,7 @@ function allIndices(haystack, needle) {
7922
8129
  }
7923
8130
  return out;
7924
8131
  }
7925
- function computeFencedRanges(content) {
8132
+ function computeFencedRanges2(content) {
7926
8133
  const ranges = [];
7927
8134
  const eolRe = /\r\n|\n/g;
7928
8135
  let lineStart = 0;
@@ -7952,7 +8159,7 @@ function isWithinRanges(offset, ranges) {
7952
8159
  return ranges.some(([s, e]) => offset >= s && offset < e);
7953
8160
  }
7954
8161
  function endsInsideOpenFence(content) {
7955
- return computeFencedRanges(content).endsInOpenFence;
8162
+ return computeFencedRanges2(content).endsInOpenFence;
7956
8163
  }
7957
8164
  function unclosedFenceMessage(relativePath) {
7958
8165
  return `${relativePath}: ends inside an unclosed code fence \u2014 close it (or add the Keryx block by hand) before installing`;
@@ -7961,7 +8168,7 @@ function parseBlocks(content, relativePath, spec) {
7961
8168
  const fail = () => {
7962
8169
  throw new UnterminatedInstructionsBlockError(`${relativePath}: unterminated ${spec.startMarker} block \u2014 fix it by hand`);
7963
8170
  };
7964
- const { ranges: fenced } = computeFencedRanges(content);
8171
+ const { ranges: fenced } = computeFencedRanges2(content);
7965
8172
  const starts = allIndices(content, spec.startMarker);
7966
8173
  const ends = allIndices(content, spec.endMarker);
7967
8174
  if (starts.some((i) => isWithinRanges(i, fenced)) || ends.some((i) => isWithinRanges(i, fenced)))
@@ -8012,7 +8219,7 @@ function appendBlock(content, block, eol) {
8012
8219
  return endsWithEol(content) ? `${content}${eol}${block}` : `${content}${eol}${eol}${block}`;
8013
8220
  }
8014
8221
  function fileFor(root, relativePath) {
8015
- return path33.join(root, ...relativePath.split("/"));
8222
+ return path34.join(root, ...relativePath.split("/"));
8016
8223
  }
8017
8224
  async function installMarkdownBlock(root, relativePath, frontMatter, spec = INSTRUCTIONS_BLOCK_SPEC) {
8018
8225
  const symlinkRefusal = await refuseEscapingSymlink(root, relativePath);
@@ -8124,7 +8331,7 @@ var init_markdown_block = __esm(() => {
8124
8331
  });
8125
8332
 
8126
8333
  // src/integrations/surfaces-w5b.ts
8127
- import path34 from "path";
8334
+ import path35 from "path";
8128
8335
  function kiroHookEntry() {
8129
8336
  return {
8130
8337
  name: KIRO_HOOK_ID,
@@ -8151,13 +8358,36 @@ function copilotHookEntry() {
8151
8358
  };
8152
8359
  }
8153
8360
  async function agentsMdHasKeryxBlock(root) {
8154
- const file = path34.join(root, "AGENTS.md");
8361
+ const file = path35.join(root, "AGENTS.md");
8155
8362
  if (!await pathExists(file))
8156
8363
  return false;
8157
8364
  const { readFile } = await import("fs/promises");
8158
8365
  const content = await readFile(file, "utf8");
8159
8366
  return content.includes("<!-- keryx:index -->");
8160
8367
  }
8368
+ async function localCodexEntryPath(root) {
8369
+ const { readFile } = await import("fs/promises");
8370
+ let manifest;
8371
+ try {
8372
+ manifest = JSON.parse(await readFile(path35.join(root, ".metaproject", "metaproject.json"), "utf8"));
8373
+ } catch {
8374
+ return;
8375
+ }
8376
+ const agentEntrypoints = typeof manifest === "object" && manifest !== null ? manifest.agentEntrypoints : undefined;
8377
+ const codex = normalizeEntrypointTargets(agentEntrypoints).targets.root.find((entry) => entry.runtime === "codex");
8378
+ return codex?.scope === "local" ? codex.path : undefined;
8379
+ }
8380
+ async function probeZedInstructions(root) {
8381
+ if (await agentsMdHasKeryxBlock(root))
8382
+ return [];
8383
+ const localCodex = await localCodexEntryPath(root);
8384
+ if (localCodex !== undefined) {
8385
+ return [
8386
+ `zed: AGENTS.md carries no Keryx block \u2014 the codex entry is local, so the block goes to ${localCodex}, which Zed does not read. To route Zed through Keryx, set the codex entry's scope to "shared" under agentEntrypoints.root in .metaproject/metaproject.json and run \`keryx update\`.`
8387
+ ];
8388
+ }
8389
+ return ["zed: AGENTS.md is missing or missing Keryx's block \u2014 run `keryx update`"];
8390
+ }
8161
8391
  function shellHookSurface(id, description) {
8162
8392
  return {
8163
8393
  id,
@@ -8179,6 +8409,7 @@ inclusion: always
8179
8409
 
8180
8410
  `, INSTRUCTIONS_KIRO, COPILOT_SOURCE_DOCS, COPILOT_KEY = "preToolUse", CTX_GUARD_GITHUB_COPILOT_AGENT, INSTRUCTIONS_GITHUB_COPILOT_AGENT, ACP_PERMISSION_ZED, ZED_SHADOWING_RULES_FILES, INSTRUCTIONS_ZED, KERYX_SHELL_UNSUPPORTED_REASON = "Registered by W6's keryx shell hook runtime; not installed by keryx integrations yet.", KERYX_SHELL_UNSUPPORTED_FLAGS, KERYX_SHELL_UNSUPPORTED, KERYX_SHELL_SOURCE_DOCS, SHELL_HOOKS_BLOCK, SHELL_HOOKS_PROMPT_GATE, SHELL_HOOKS_PRE_TOOL_CONTEXT, SHELL_HOOKS_INJECT_CONTEXT, SHELL_HOOKS_OBSERVE, SHELL_HOOKS_POST_TOOL, SHELL_HOOKS_SESSION_START, SHELL_HOOKS_STOP, KERYX_SHELL_SURFACES;
8181
8411
  var init_surfaces_w5b = __esm(() => {
8412
+ init_entrypoint_targets();
8182
8413
  init_settings_json();
8183
8414
  init_codecs();
8184
8415
  init_surfaces();
@@ -8209,7 +8440,7 @@ var init_surfaces_w5b = __esm(() => {
8209
8440
  confidence: "experimental",
8210
8441
  riskNotes: ["Whether Gemini CLI actually reads GEMINI.md end-to-end (beyond the documented context.fileName option) is not independently confirmed here."],
8211
8442
  sourceDocs: ["https://github.com/google-gemini/gemini-cli/blob/main/docs/cli/gemini-md.md"],
8212
- settingsFile: (root) => path34.join(root, "GEMINI.md"),
8443
+ settingsFile: (root) => path35.join(root, "GEMINI.md"),
8213
8444
  relativePath: "GEMINI.md",
8214
8445
  slots: [],
8215
8446
  customInstall: (root) => installMarkdownBlock(root, "GEMINI.md"),
@@ -8233,7 +8464,7 @@ var init_surfaces_w5b = __esm(() => {
8233
8464
  "Kiro's hook stdin field names and its shell tool's name are third-party-reported only, not confirmed by first-party docs \u2014 the matcher above is a best guess and may gate nothing on a real install; verify before relying on it."
8234
8465
  ],
8235
8466
  sourceDocs: KIRO_SOURCE_DOCS,
8236
- settingsFile: (root) => path34.join(root, ".kiro", "hooks", "keryx-ctx-guard.json"),
8467
+ settingsFile: (root) => path35.join(root, ".kiro", "hooks", "keryx-ctx-guard.json"),
8237
8468
  relativePath: ".kiro/hooks/keryx-ctx-guard.json",
8238
8469
  slots: [
8239
8470
  { key: "version", type: "string", access: "owns" },
@@ -8278,7 +8509,7 @@ var init_surfaces_w5b = __esm(() => {
8278
8509
  "Open Kiro issues report that steering file `inclusion` modes are not always honoured, even with `inclusion: always` set \u2014 verify on a live install."
8279
8510
  ],
8280
8511
  sourceDocs: ["https://kiro.dev/docs/steering/"],
8281
- settingsFile: (root) => path34.join(root, ".kiro", "steering", "keryx.md"),
8512
+ settingsFile: (root) => path35.join(root, ".kiro", "steering", "keryx.md"),
8282
8513
  relativePath: ".kiro/steering/keryx.md",
8283
8514
  slots: [],
8284
8515
  customInstall: (root) => installMarkdownBlock(root, ".kiro/steering/keryx.md", KIRO_STEERING_FRONT_MATTER),
@@ -8301,7 +8532,7 @@ var init_surfaces_w5b = __esm(() => {
8301
8532
  "The shell tool's name Copilot's hook payload carries is not documented; this guard parses any tool call whose payload carries `toolArgs.command`, whatever `toolName` is."
8302
8533
  ],
8303
8534
  sourceDocs: COPILOT_SOURCE_DOCS,
8304
- settingsFile: (root) => path34.join(root, ".github", "hooks", "keryx-ctx-guard.json"),
8535
+ settingsFile: (root) => path35.join(root, ".github", "hooks", "keryx-ctx-guard.json"),
8305
8536
  relativePath: ".github/hooks/keryx-ctx-guard.json",
8306
8537
  slots: [
8307
8538
  { key: "version", type: "number", access: "owns" },
@@ -8345,7 +8576,7 @@ var init_surfaces_w5b = __esm(() => {
8345
8576
  "https://docs.github.com/en/copilot/how-tos/configure-custom-instructions-in-your-ide/add-repository-instructions-in-your-ide",
8346
8577
  "https://github.blog/changelog/2025-08-28-copilot-coding-agent-now-supports-agents-md-custom-instructions/"
8347
8578
  ],
8348
- settingsFile: (root) => path34.join(root, ".github", "copilot-instructions.md"),
8579
+ settingsFile: (root) => path35.join(root, ".github", "copilot-instructions.md"),
8349
8580
  relativePath: ".github/copilot-instructions.md",
8350
8581
  slots: [],
8351
8582
  customInstall: (root) => installMarkdownBlock(root, ".github/copilot-instructions.md"),
@@ -8384,10 +8615,10 @@ var init_surfaces_w5b = __esm(() => {
8384
8615
  `Zed uses the first matching rules file among ${ZED_SHADOWING_RULES_FILES.join(", ")}, AGENT.md, AGENTS.md, CLAUDE.md, GEMINI.md, ... \u2014 an earlier file in that list shadows AGENTS.md even when AGENTS.md itself carries Keryx's block.`
8385
8616
  ],
8386
8617
  sourceDocs: ["https://github.com/zed-industries/zed/blob/main/docs/src/ai/rules.md"],
8387
- settingsFile: (root) => path34.join(root, "AGENTS.md"),
8618
+ settingsFile: (root) => path35.join(root, "AGENTS.md"),
8388
8619
  relativePath: "AGENTS.md",
8389
8620
  slots: [],
8390
- probe: async (root) => await agentsMdHasKeryxBlock(root) ? [] : ["zed: AGENTS.md is missing or missing Keryx's block \u2014 run `keryx update`"]
8621
+ probe: probeZedInstructions
8391
8622
  };
8392
8623
  KERYX_SHELL_UNSUPPORTED_FLAGS = ["skills", "agents", "instructions", "mcp"];
8393
8624
  KERYX_SHELL_UNSUPPORTED = Object.fromEntries(KERYX_SHELL_UNSUPPORTED_FLAGS.map((flag) => [flag, KERYX_SHELL_UNSUPPORTED_REASON]));
@@ -8465,142 +8696,185 @@ var init_route_tokens = __esm(() => {
8465
8696
  });
8466
8697
 
8467
8698
  // src/gdskills/skill-frontmatter.ts
8468
- function stripSkillFieldQuotes(value) {
8469
- if (value.length >= 2) {
8470
- const first = value[0];
8471
- const last = value[value.length - 1];
8472
- if (first === '"' && last === '"' || first === "'" && last === "'") {
8473
- return value.slice(1, -1);
8474
- }
8475
- }
8476
- return value;
8699
+ function frontmatterLines(content) {
8700
+ const text = content.startsWith("\uFEFF") ? content.slice(1) : content;
8701
+ const lines = text.split(/\r?\n/);
8702
+ if (lines[0]?.trimEnd() !== "---")
8703
+ return;
8704
+ const close = lines.findIndex((line, index) => index > 0 && line.trimEnd() === "---");
8705
+ return close === -1 ? undefined : lines.slice(1, close);
8477
8706
  }
8478
- function splitHarnessScalar(value) {
8479
- return value.split(",").map((entry) => entry.trim()).filter((entry) => entry.length > 0);
8707
+ function readFrontmatter(content) {
8708
+ const lines = frontmatterLines(content);
8709
+ return lines === undefined ? undefined : readMapping(lines, 0);
8480
8710
  }
8481
- function parseInlineHarnesses(value) {
8482
- if (value.length === 0)
8483
- return;
8484
- const flowList = /^\[(.*)\]$/.exec(value);
8485
- if (flowList !== null) {
8486
- return splitHarnessScalar((flowList[1] ?? "").replace(/["']/g, ""));
8487
- }
8488
- return splitHarnessScalar(stripSkillFieldQuotes(value));
8711
+ function scalarText(value) {
8712
+ return value?.kind === "scalar" || value?.kind === "block" ? value.text : undefined;
8489
8713
  }
8490
- function parseSkillFrontmatter(content) {
8491
- if (!content.startsWith("---")) {
8492
- return {};
8493
- }
8494
- const end = content.indexOf(`
8495
- ---`, 3);
8496
- if (end === -1) {
8497
- return {};
8498
- }
8499
- const lines = content.slice(3, end).split(`
8500
- `);
8501
- let name;
8502
- let description;
8503
- const triggers = [];
8504
- let inTriggers = false;
8505
- let inMetadata = false;
8506
- let metadataCategory;
8507
- let metadataVersion;
8508
- let metadataOrigin;
8509
- let compatibleHarnesses;
8510
- let awaitingHarnessesList = false;
8511
- let descriptionBlock = null;
8512
- const foldBlock = (block) => block.join(" ").replace(/\s+/g, " ").trim();
8513
- for (const line of lines) {
8514
- if (descriptionBlock !== null) {
8515
- if (line.trim() === "" || /^\s/.test(line)) {
8516
- descriptionBlock.push(line.trim());
8517
- continue;
8518
- }
8519
- description = foldBlock(descriptionBlock);
8520
- descriptionBlock = null;
8521
- }
8522
- const nameMatch = /^name:\s*(.*)$/.exec(line);
8523
- if (nameMatch !== null && nameMatch[1] !== undefined) {
8524
- name = stripSkillFieldQuotes(nameMatch[1].trim());
8525
- inTriggers = false;
8526
- inMetadata = false;
8527
- awaitingHarnessesList = false;
8714
+ function listItems(value) {
8715
+ if (value?.kind === "list")
8716
+ return [...value.items];
8717
+ if (value?.kind === "scalar")
8718
+ return splitOutsideQuotes(value.text).map(unquote).filter(Boolean);
8719
+ return [];
8720
+ }
8721
+ function indentOf(line) {
8722
+ return /^[ \t]*/.exec(line)?.[0].length ?? 0;
8723
+ }
8724
+ function isFiller(line) {
8725
+ const trimmed = line.trim();
8726
+ return trimmed === "" || trimmed.startsWith("#");
8727
+ }
8728
+ function readMapping(lines, indent) {
8729
+ const fields = new Map;
8730
+ let index = 0;
8731
+ while (index < lines.length) {
8732
+ const line = lines[index];
8733
+ index += 1;
8734
+ if (isFiller(line) || indentOf(line) !== indent)
8528
8735
  continue;
8529
- }
8530
- const descMatch = /^description:\s*(.*)$/.exec(line);
8531
- if (descMatch !== null && descMatch[1] !== undefined) {
8532
- const value = descMatch[1].trim();
8533
- if (/^[|>][-+]?$/.test(value)) {
8534
- descriptionBlock = [];
8736
+ const match = KEY_LINE.exec(line.slice(indent));
8737
+ if (match === null)
8738
+ continue;
8739
+ const start = index;
8740
+ while (index < lines.length) {
8741
+ const next = lines[index];
8742
+ const nextIndent = indentOf(next);
8743
+ if (isFiller(next) || nextIndent > indent || nextIndent === indent && LIST_ITEM.test(next.slice(nextIndent))) {
8744
+ index += 1;
8535
8745
  } else {
8536
- description = stripSkillFieldQuotes(value);
8746
+ break;
8537
8747
  }
8538
- inTriggers = false;
8539
- inMetadata = false;
8540
- awaitingHarnessesList = false;
8541
- continue;
8542
8748
  }
8543
- if (/^triggers:\s*$/.test(line)) {
8544
- inTriggers = true;
8545
- inMetadata = false;
8546
- awaitingHarnessesList = false;
8749
+ const key = unquote(match[1]);
8750
+ if (!fields.has(key))
8751
+ fields.set(key, readValue(match[2] ?? "", lines.slice(start, index), indent));
8752
+ }
8753
+ return fields;
8754
+ }
8755
+ function readValue(rest, children, keyIndent) {
8756
+ const text = stripTrailingComment(rest);
8757
+ if (BLOCK_SCALAR.test(text)) {
8758
+ const body = children.filter((line) => line.trim() === "" || indentOf(line) > keyIndent);
8759
+ return { kind: "block", text: body.map((line) => line.trim()).join(" ").replace(/\s+/g, " ").trim() };
8760
+ }
8761
+ if (text.startsWith("["))
8762
+ return readFlowList(text);
8763
+ if (text.startsWith("{") || text.startsWith("&") || text.startsWith("*") || text.startsWith("!")) {
8764
+ return { kind: "unsupported" };
8765
+ }
8766
+ if (text !== "")
8767
+ return { kind: "scalar", text: unquote(text) };
8768
+ const first = children.find((line) => !isFiller(line));
8769
+ if (first === undefined)
8770
+ return { kind: "scalar", text: "" };
8771
+ const firstIndent = indentOf(first);
8772
+ if (LIST_ITEM.test(first.slice(firstIndent)))
8773
+ return readBlockList(children, keyIndent);
8774
+ if (!KEY_LINE.test(first.slice(firstIndent)))
8775
+ return { kind: "unsupported" };
8776
+ return { kind: "mapping", fields: readMapping(children, firstIndent) };
8777
+ }
8778
+ function readBlockList(children, keyIndent) {
8779
+ const items = [];
8780
+ for (const line of children) {
8781
+ if (isFiller(line))
8547
8782
  continue;
8548
- }
8549
- if (inTriggers) {
8550
- const itemMatch = /^\s+-\s*(.+)$/.exec(line);
8551
- if (itemMatch !== null && itemMatch[1] !== undefined) {
8552
- triggers.push(stripSkillFieldQuotes(itemMatch[1].trim()));
8553
- continue;
8554
- }
8555
- inTriggers = false;
8556
- }
8557
- if (awaitingHarnessesList) {
8558
- const itemMatch = /^\s+-\s*(.+)$/.exec(line);
8559
- if (itemMatch !== null && itemMatch[1] !== undefined) {
8560
- (compatibleHarnesses ??= []).push(stripSkillFieldQuotes(itemMatch[1].trim()));
8561
- continue;
8783
+ const lineIndent = indentOf(line);
8784
+ const item = lineIndent >= keyIndent ? LIST_ITEM.exec(line.slice(lineIndent)) : null;
8785
+ if (item === null)
8786
+ break;
8787
+ const text = stripTrailingComment(item[1] ?? "");
8788
+ if (text.startsWith("[") || text.startsWith("{"))
8789
+ return { kind: "unsupported" };
8790
+ const entry = unquote(text);
8791
+ if (entry !== "")
8792
+ items.push(entry);
8793
+ }
8794
+ return { kind: "list", items };
8795
+ }
8796
+ function readFlowList(text) {
8797
+ if (!text.endsWith("]"))
8798
+ return { kind: "unsupported" };
8799
+ const inner = text.slice(1, -1);
8800
+ const entries = splitOutsideQuotes(inner);
8801
+ if (entries.some((entry) => /^[[{]/.test(entry.trim())))
8802
+ return { kind: "unsupported" };
8803
+ return { kind: "list", items: entries.map(unquote).filter(Boolean) };
8804
+ }
8805
+ function atTokenStart(text, index) {
8806
+ return index === 0 || /[\s[{,]/.test(text[index - 1]);
8807
+ }
8808
+ function stripTrailingComment(value) {
8809
+ let quote;
8810
+ for (let index = 0;index < value.length; index += 1) {
8811
+ const char = value[index];
8812
+ if (quote !== undefined) {
8813
+ if (quote === '"' && char === "\\")
8814
+ index += 1;
8815
+ else if (char === quote) {
8816
+ if (quote === "'" && value[index + 1] === "'")
8817
+ index += 1;
8818
+ else
8819
+ quote = undefined;
8562
8820
  }
8563
- awaitingHarnessesList = false;
8564
- }
8565
- if (/^metadata:\s*$/.test(line)) {
8566
- inMetadata = true;
8567
8821
  continue;
8568
8822
  }
8569
- if (inMetadata) {
8570
- if (line.trim() !== "" && !/^\s/.test(line)) {
8571
- inMetadata = false;
8572
- } else {
8573
- const categoryMatch = /^\s+category:\s*(.+)$/.exec(line);
8574
- if (categoryMatch !== null && categoryMatch[1] !== undefined) {
8575
- metadataCategory = stripSkillFieldQuotes(categoryMatch[1].trim());
8576
- continue;
8577
- }
8578
- const versionMatch = /^\s+version:\s*(.+)$/.exec(line);
8579
- if (versionMatch !== null && versionMatch[1] !== undefined) {
8580
- metadataVersion = stripSkillFieldQuotes(versionMatch[1].trim());
8581
- continue;
8582
- }
8583
- const originMatch = /^\s+origin:\s*(.+)$/.exec(line);
8584
- if (originMatch !== null && originMatch[1] !== undefined) {
8585
- metadataOrigin = stripSkillFieldQuotes(originMatch[1].trim());
8586
- continue;
8587
- }
8588
- const harnessesMatch = /^\s+compatible_harnesses:\s*(.*)$/.exec(line);
8589
- if (harnessesMatch !== null) {
8590
- const inline = parseInlineHarnesses((harnessesMatch[1] ?? "").trim());
8591
- if (inline !== undefined) {
8592
- compatibleHarnesses = inline;
8593
- } else {
8594
- awaitingHarnessesList = true;
8595
- }
8596
- continue;
8597
- }
8598
- }
8823
+ if ((char === '"' || char === "'") && atTokenStart(value, index))
8824
+ quote = char;
8825
+ else if (char === "#" && (index === 0 || /\s/.test(value[index - 1])))
8826
+ return value.slice(0, index).trim();
8827
+ }
8828
+ return value.trim();
8829
+ }
8830
+ function splitOutsideQuotes(text) {
8831
+ const parts = [];
8832
+ let quote;
8833
+ let current = "";
8834
+ for (let index = 0;index < text.length; index += 1) {
8835
+ const char = text[index];
8836
+ if (quote !== undefined) {
8837
+ if (char === quote)
8838
+ quote = undefined;
8839
+ } else if ((char === '"' || char === "'") && current.trim() === "") {
8840
+ quote = char;
8841
+ } else if (char === ",") {
8842
+ parts.push(current);
8843
+ current = "";
8844
+ continue;
8599
8845
  }
8846
+ current += char;
8600
8847
  }
8601
- if (descriptionBlock !== null) {
8602
- description = foldBlock(descriptionBlock);
8848
+ parts.push(current);
8849
+ return parts;
8850
+ }
8851
+ function unquote(value) {
8852
+ const trimmed = value.trim();
8853
+ if (trimmed.length >= 2) {
8854
+ const first = trimmed[0];
8855
+ if ((first === '"' || first === "'") && trimmed.endsWith(first))
8856
+ return trimmed.slice(1, -1);
8603
8857
  }
8858
+ return trimmed;
8859
+ }
8860
+ function nonEmpty(value) {
8861
+ return value === undefined || value === "" ? undefined : value;
8862
+ }
8863
+ function parseSkillFrontmatter(content) {
8864
+ const top = readFrontmatter(content);
8865
+ if (top === undefined)
8866
+ return {};
8867
+ const metadataValue = top.get("metadata");
8868
+ const metadata = metadataValue?.kind === "mapping" ? metadataValue.fields : new Map;
8869
+ const name = scalarText(top.get("name"));
8870
+ const description = scalarText(top.get("description"));
8871
+ const triggersValue = top.get("triggers");
8872
+ const triggers = triggersValue?.kind === "list" ? [...triggersValue.items] : [];
8873
+ const metadataCategory = nonEmpty(scalarText(metadata.get("category")));
8874
+ const metadataVersion = nonEmpty(scalarText(metadata.get("version")));
8875
+ const metadataOrigin = nonEmpty(scalarText(metadata.get("origin")));
8876
+ const harnessesValue = metadata.get("compatible_harnesses");
8877
+ const compatibleHarnesses = harnessesValue?.kind === "list" || harnessesValue?.kind === "scalar" && harnessesValue.text !== "" ? listItems(harnessesValue) : undefined;
8604
8878
  return {
8605
8879
  ...name !== undefined ? { name } : {},
8606
8880
  ...description !== undefined ? { description } : {},
@@ -8611,10 +8885,16 @@ function parseSkillFrontmatter(content) {
8611
8885
  ...compatibleHarnesses !== undefined ? { compatibleHarnesses } : {}
8612
8886
  };
8613
8887
  }
8888
+ var KEY_LINE, LIST_ITEM, BLOCK_SCALAR;
8889
+ var init_skill_frontmatter = __esm(() => {
8890
+ KEY_LINE = /^("[^"]*"|'[^']*'|[^\s#"'\-[\]{},:][^:]*?):(?:[ \t]+(.*))?$/;
8891
+ LIST_ITEM = /^-(?:[ \t]+(.*))?$/;
8892
+ BLOCK_SCALAR = /^[|>][-+]?[0-9]?$/;
8893
+ });
8614
8894
 
8615
8895
  // src/gdskills/catalog.ts
8616
- import { existsSync as existsSync2, readFileSync as readFileSync2 } from "fs";
8617
- import path35 from "path";
8896
+ import { existsSync as existsSync2, readFileSync as readFileSync3 } from "fs";
8897
+ import path36 from "path";
8618
8898
  import { fileURLToPath } from "url";
8619
8899
  function skill(name, category, profiles, purpose, workflow) {
8620
8900
  let routing;
@@ -8637,10 +8917,10 @@ function renderedSkill(name, category, profiles, purpose, workflow, triggers, de
8637
8917
  return { name, category, description, purpose, workflow, triggers, profiles };
8638
8918
  }
8639
8919
  function bundledSkillMarkdownPath(category, name) {
8640
- const here = path35.dirname(fileURLToPath(import.meta.url));
8920
+ const here = path36.dirname(fileURLToPath(import.meta.url));
8641
8921
  return [
8642
- path35.join(here, "bundled", "skills", category, name, "SKILL.md"),
8643
- path35.join(here, "..", "src", "gdskills", "bundled", "skills", category, name, "SKILL.md")
8922
+ path36.join(here, "bundled", "skills", category, name, "SKILL.md"),
8923
+ path36.join(here, "..", "src", "gdskills", "bundled", "skills", category, name, "SKILL.md")
8644
8924
  ].find((candidate) => existsSync2(candidate));
8645
8925
  }
8646
8926
  function readBundledSkillRouting(category, name) {
@@ -8648,7 +8928,7 @@ function readBundledSkillRouting(category, name) {
8648
8928
  if (file === undefined) {
8649
8929
  throw new Error(`bundled skill "${category}/${name}" ships no SKILL.md under src/gdskills/bundled/skills, and BUNDLED_GDSKILLS reads its description and triggers from there. Declare it with renderedSkill() if it has no file.`);
8650
8930
  }
8651
- const { description, triggers } = parseSkillFrontmatter(readFileSync2(file, "utf8"));
8931
+ const { description, triggers } = parseSkillFrontmatter(readFileSync3(file, "utf8"));
8652
8932
  if (description === undefined || description.length === 0 || triggers === undefined || triggers.length === 0) {
8653
8933
  throw new Error(`${file}: frontmatter must declare a non-empty \`description\` and \`triggers:\` list; the skill router scores both.`);
8654
8934
  }
@@ -8656,6 +8936,7 @@ function readBundledSkillRouting(category, name) {
8656
8936
  }
8657
8937
  var BUNDLED_GDSKILLS, BUNDLED_TREE_MARKER;
8658
8938
  var init_catalog = __esm(() => {
8939
+ init_skill_frontmatter();
8659
8940
  BUNDLED_GDSKILLS = [
8660
8941
  renderedSkill("metaproject-router", "core", ["minimal", "recommended", "full"], "Choose which Metaproject module, working skill, or project-skill should be used for a user request.", [
8661
8942
  "Read `.metaproject/index.md` first.",
@@ -8699,7 +8980,7 @@ var init_catalog = __esm(() => {
8699
8980
  "Write the reviewer against the orchestrated review contract; point at the canonical severity rubric instead of inventing one.",
8700
8981
  "Drop the persona, keep the method, and state the reason beside every rule kept.",
8701
8982
  "Confirm with `keryx review reviewers` \u2014 creating files is not registration, and registration is not discovery.",
8702
- "For existing SKILL.md packages, run `keryx skills import --from <dir|file|https-url> --module <module>`; `keryx review import` is the review-shaped alias with the review-vantage-* prefix."
8983
+ "For existing SKILL.md packages, run `keryx skills import --from <dir|file|https-url> --module <module>`; `keryx review import` is the review-shaped spelling (module implied). A tree of several packages needs `--only <glob>` to say which ones."
8703
8984
  ]),
8704
8985
  renderedSkill("entity-skill-verifier", "core", ["minimal", "recommended", "full"], "Run `keryx skills verify` to check a project-skill's required files, SKILL.md metadata, manifest registration, target-path existence, and evidence artifacts (gdgraph, gdctx, validated gdwiki, Code Health, canonical accepted memory), then classify it as fresh, needs-review, stale, or blocked. The command does not read the skill's prose or compare it against current code \u2014 that comparison is a manual agent step.", [
8705
8986
  "Resolve the target project-skill through gdgraph affected context or `keryx skills route <target>`.",
@@ -9105,10 +9386,10 @@ var init_catalog = __esm(() => {
9105
9386
  "Report gaps and recommended follow-up pages."
9106
9387
  ]),
9107
9388
  renderedSkill("agent-entrypoint-manager", "platform", ["minimal", "recommended", "full"], "Maintain AGENTS.md, CLAUDE.md, and local-first Metaproject references.", [
9108
- "Find existing root agent entrypoints.",
9109
- "Keep managed Metaproject blocks idempotent.",
9389
+ "Find existing root agent entrypoints and the block targets `agentEntrypoints.root` names: `CLAUDE.local.md` and `AGENTS.override.md` (scope `local`, the default; Codex mode `override` or `skip`), or `CLAUDE.md` / `AGENTS.md` (scope `shared`).",
9390
+ "Keep managed Metaproject blocks idempotent: refresh them with `keryx update` or `keryx rules sync`, which also moves a block out of a tracked entrypoint and regenerates `AGENTS.override.md` from `AGENTS.md` \u2014 never write a bare block there.",
9110
9391
  "Ensure local `.metaproject/index.md` and skill catalog are first-class references."
9111
- ], ["agents.md", "claude.md", "entrypoint"], "Use when AGENTS.md or CLAUDE.md needs its managed Metaproject block added, refreshed, or kept idempotent. NOT for: splitting an oversized entrypoint into rules and project-skills (see agent-entrypoint-distiller)."),
9392
+ ], ["agents.md", "claude.md", "entrypoint"], "Use when AGENTS.md or CLAUDE.md needs its managed Metaproject block added, refreshed, or kept idempotent \u2014 the block lives in CLAUDE.local.md / AGENTS.override.md unless scope is shared. NOT for: splitting an oversized entrypoint into rules and project-skills (see agent-entrypoint-distiller)."),
9112
9393
  skill("agent-entrypoint-distiller", "platform", ["minimal", "recommended", "full"], "Split large AGENTS.md/CLAUDE.md files into high-priority Metaproject rules and project-specific skills.", [
9113
9394
  "Run `keryx rules distill` when the user asks to decompose a large CLAUDE.md/AGENTS.md.",
9114
9395
  "Keep root entrypoints compact: non-project/highest-priority instructions plus `.metaproject/index.md` routing.",
@@ -9150,7 +9431,7 @@ var init_catalog = __esm(() => {
9150
9431
  "Sync only selected skills and report changed files."
9151
9432
  ], ["sync skills", "install runtime skills", "global skill sync"], "Use when already-exported runtime skills need pushing to configured local runtimes, and only when sync is explicitly enabled. NOT for: producing the runtime export itself (see skill-runtime-exporter).")
9152
9433
  ];
9153
- BUNDLED_TREE_MARKER = path35.join("src", "gdskills", "bundled");
9434
+ BUNDLED_TREE_MARKER = path36.join("src", "gdskills", "bundled");
9154
9435
  });
9155
9436
 
9156
9437
  // src/gdskills/resolve.ts
@@ -9283,13 +9564,13 @@ var init_skill_length_ceilings = __esm(() => {
9283
9564
  });
9284
9565
 
9285
9566
  // src/gdskills/bundled-eval.ts
9286
- import { existsSync as existsSync3, readFileSync as readFileSync3, readdirSync } from "fs";
9287
- import path36 from "path";
9567
+ import { existsSync as existsSync3, readFileSync as readFileSync4, readdirSync } from "fs";
9568
+ import path37 from "path";
9288
9569
  function defaultBundledRoot() {
9289
- const directPath = path36.join(import.meta.dir, "bundled");
9570
+ const directPath = path37.join(import.meta.dir, "bundled");
9290
9571
  if (existsSync3(directPath))
9291
9572
  return directPath;
9292
- const packagedPath = path36.join(import.meta.dir, "..", "src", "gdskills", "bundled");
9573
+ const packagedPath = path37.join(import.meta.dir, "..", "src", "gdskills", "bundled");
9293
9574
  if (existsSync3(packagedPath))
9294
9575
  return packagedPath;
9295
9576
  return directPath;
@@ -9300,6 +9581,7 @@ var init_bundled_eval = __esm(() => {
9300
9581
  init_catalog();
9301
9582
  init_export();
9302
9583
  init_model_tier();
9584
+ init_skill_frontmatter();
9303
9585
  init_skill_length_ceilings();
9304
9586
  PLACEHOLDER_ACCOUNTS = new Set([
9305
9587
  "dev",
@@ -9486,7 +9768,7 @@ var init_bundled_eval = __esm(() => {
9486
9768
  KNOWN_SKILL_COMPANION_DOCUMENTS = new Map([
9487
9769
  [
9488
9770
  "SKILL.detail.md",
9489
- "overflow reference linked from its skill's SKILL.md (`orchestration/feature-analyzer`, `review/review-orchestrator`); carries no frontmatter and is not addressed by any runtime"
9771
+ "overflow reference linked from its skill's SKILL.md (`core/reviewer-skill-creator`, `orchestration/feature-analyzer`, `review/review-orchestrator`); carries no frontmatter and is not addressed by any runtime"
9490
9772
  ],
9491
9773
  [
9492
9774
  "orchestrator-prompt.md",
@@ -9805,18 +10087,18 @@ var init_schema = __esm(() => {
9805
10087
  });
9806
10088
 
9807
10089
  // src/agents/catalog.ts
9808
- import { existsSync as existsSync4, readdirSync as readdirSync2, readFileSync as readFileSync4 } from "fs";
9809
- import path37 from "path";
10090
+ import { existsSync as existsSync4, readdirSync as readdirSync2, readFileSync as readFileSync5 } from "fs";
10091
+ import path38 from "path";
9810
10092
  function agentDefinitionFiles(dir) {
9811
10093
  if (!existsSync4(dir)) {
9812
10094
  return [];
9813
10095
  }
9814
- return readdirSync2(dir, { withFileTypes: true }).filter((entry) => entry.isFile() && entry.name.endsWith(".md")).map((entry) => path37.join(dir, entry.name)).sort();
10096
+ return readdirSync2(dir, { withFileTypes: true }).filter((entry) => entry.isFile() && entry.name.endsWith(".md")).map((entry) => path38.join(dir, entry.name)).sort();
9815
10097
  }
9816
10098
  function loadOne(filePath, kind) {
9817
10099
  let raw;
9818
10100
  try {
9819
- raw = readFileSync4(filePath, "utf8");
10101
+ raw = readFileSync5(filePath, "utf8");
9820
10102
  } catch (cause) {
9821
10103
  const detail = cause instanceof Error ? cause.message : String(cause);
9822
10104
  return { error: { reason: "unreadable-file", path: filePath, message: `could not read ${filePath}: ${detail}` } };
@@ -9843,7 +10125,7 @@ function loadOne(filePath, kind) {
9843
10125
  };
9844
10126
  }
9845
10127
  const definition = buildAgentDefinition(parsed.result.data, parsed.result.body);
9846
- const stem = path37.basename(filePath, ".md");
10128
+ const stem = path38.basename(filePath, ".md");
9847
10129
  if (stem !== definition.name) {
9848
10130
  return {
9849
10131
  error: {
@@ -9877,8 +10159,8 @@ function loadSource(dir, kind, byName, errors) {
9877
10159
  }
9878
10160
  }
9879
10161
  function loadAgentCatalog(projectRoot, options = {}) {
9880
- const bundledRoot = options.bundledRoot ?? path37.join(defaultBundledRoot(), "agents");
9881
- const projectDir = path37.join(projectRoot, ".metaproject", "agents");
10162
+ const bundledRoot = options.bundledRoot ?? path38.join(defaultBundledRoot(), "agents");
10163
+ const projectDir = path38.join(projectRoot, ".metaproject", "agents");
9882
10164
  const byName = new Map;
9883
10165
  const errors = [];
9884
10166
  loadSource(bundledRoot, "bundled", byName, errors);
@@ -10671,7 +10953,7 @@ var init_compile = __esm(() => {
10671
10953
 
10672
10954
  // src/agents/export.ts
10673
10955
  import { readFile as readFile16, readdir as readdir8, lstat as lstat6, realpath as realpath7 } from "fs/promises";
10674
- import path38 from "path";
10956
+ import path39 from "path";
10675
10957
  function matrixStateToExportLevel(state) {
10676
10958
  return state === "native" || state === "adapter" ? state : "instruction-only";
10677
10959
  }
@@ -10683,7 +10965,7 @@ function proseExportRelativePath(runtime, name) {
10683
10965
  return `${PROSE_EXPORT_ROOT}/${runtime}/${name}.md`;
10684
10966
  }
10685
10967
  async function readExistingFileContent(projectRoot, relativePath) {
10686
- const absolute = path38.join(projectRoot, ...relativePath.split("/"));
10968
+ const absolute = path39.join(projectRoot, ...relativePath.split("/"));
10687
10969
  try {
10688
10970
  return await readFile16(absolute, "utf8");
10689
10971
  } catch (error) {
@@ -10696,7 +10978,7 @@ async function checkExportPathSafety(projectRoot, relativePath) {
10696
10978
  const segments = relativePath.split("/");
10697
10979
  let current = projectRoot;
10698
10980
  for (const segment of segments) {
10699
- current = path38.join(current, segment);
10981
+ current = path39.join(current, segment);
10700
10982
  let stats;
10701
10983
  try {
10702
10984
  stats = await lstat6(current);
@@ -10706,12 +10988,12 @@ async function checkExportPathSafety(projectRoot, relativePath) {
10706
10988
  throw error;
10707
10989
  }
10708
10990
  if (stats.isSymbolicLink()) {
10709
- return `refusing to write ${relativePath} \u2014 ${path38.relative(projectRoot, current) || "."} is a symlink; this exporter never writes through a symlink`;
10991
+ return `refusing to write ${relativePath} \u2014 ${path39.relative(projectRoot, current) || "."} is a symlink; this exporter never writes through a symlink`;
10710
10992
  }
10711
10993
  }
10712
- let ancestor = path38.dirname(path38.join(projectRoot, ...segments));
10994
+ let ancestor = path39.dirname(path39.join(projectRoot, ...segments));
10713
10995
  for (let guard = 0;guard < segments.length + 1 && !await pathExists(ancestor); guard += 1) {
10714
- const parent = path38.dirname(ancestor);
10996
+ const parent = path39.dirname(ancestor);
10715
10997
  if (parent === ancestor)
10716
10998
  break;
10717
10999
  ancestor = parent;
@@ -10744,7 +11026,7 @@ function decideAction(existing, generated, format, expectedName) {
10744
11026
  return { action: "update" };
10745
11027
  }
10746
11028
  async function readClaudeSubagentAliasesConfig(projectRoot) {
10747
- const file = path38.join(projectRoot, CLAUDE_SUBAGENT_ALIASES_CONFIG_PATH);
11029
+ const file = path39.join(projectRoot, CLAUDE_SUBAGENT_ALIASES_CONFIG_PATH);
10748
11030
  if (!await pathExists(file)) {
10749
11031
  return { enabled: true };
10750
11032
  }
@@ -10886,7 +11168,7 @@ async function writeAgentExport(projectRoot, plan, opts = {}) {
10886
11168
  }
10887
11169
  async function scanManagedAgentExports(projectRoot, runtime) {
10888
11170
  const dirRelative = HOST_AGENTS_DIR[runtime];
10889
- const dirAbsolute = path38.join(projectRoot, ...dirRelative.split("/"));
11171
+ const dirAbsolute = path39.join(projectRoot, ...dirRelative.split("/"));
10890
11172
  let dirStats;
10891
11173
  try {
10892
11174
  dirStats = await lstat6(dirAbsolute);
@@ -10907,7 +11189,7 @@ async function scanManagedAgentExports(projectRoot, runtime) {
10907
11189
  if (!entry.isFile())
10908
11190
  continue;
10909
11191
  const relativePath = `${dirRelative}/${entry.name}`;
10910
- const absolute = path38.join(projectRoot, ...relativePath.split("/"));
11192
+ const absolute = path39.join(projectRoot, ...relativePath.split("/"));
10911
11193
  let entryStats;
10912
11194
  try {
10913
11195
  entryStats = await lstat6(absolute);
@@ -10922,7 +11204,7 @@ async function scanManagedAgentExports(projectRoot, runtime) {
10922
11204
  } catch {
10923
11205
  continue;
10924
11206
  }
10925
- const stem = entry.name.slice(0, entry.name.length - path38.extname(entry.name).length);
11207
+ const stem = entry.name.slice(0, entry.name.length - path39.extname(entry.name).length);
10926
11208
  const format = agentSentinelFormatOf(relativePath);
10927
11209
  if (!isStructurallyManaged(content, format, stem))
10928
11210
  continue;
@@ -11092,7 +11374,7 @@ var init_surfaces_agents = __esm(() => {
11092
11374
 
11093
11375
  // src/rules/export-render.ts
11094
11376
  import { readFile as readFile17, readdir as readdir9 } from "fs/promises";
11095
- import path39 from "path";
11377
+ import path40 from "path";
11096
11378
  function isUnsafeRulePath(relativePath) {
11097
11379
  return UNSAFE_RULE_PATH_PATTERN.test(relativePath) || relativePath.includes("<!--") || relativePath.includes("-->");
11098
11380
  }
@@ -11135,7 +11417,7 @@ function firstHeading(body) {
11135
11417
  return match ? match[1].trim() : undefined;
11136
11418
  }
11137
11419
  function titleFromFileName(relativePath) {
11138
- const base = path39.basename(relativePath).replace(/\.(mdc|md)$/i, "");
11420
+ const base = path40.basename(relativePath).replace(/\.(mdc|md)$/i, "");
11139
11421
  return base.split(/[-_]+/).filter((part) => part.length > 0).map((part) => part.charAt(0).toUpperCase() + part.slice(1)).join(" ");
11140
11422
  }
11141
11423
  function parseRuleFile(fileName, content) {
@@ -11148,7 +11430,7 @@ function parseRuleFile(fileName, content) {
11148
11430
  return { title: neutralise(collapseSingleLine(title, 120)), description };
11149
11431
  }
11150
11432
  async function collectCanonicalRules(root) {
11151
- const rulesDir = path39.join(root, ".metaproject", "rules");
11433
+ const rulesDir = path40.join(root, ".metaproject", "rules");
11152
11434
  if (!await pathExists(rulesDir))
11153
11435
  return { rules: [], skipped: [] };
11154
11436
  const rules = [];
@@ -11162,14 +11444,14 @@ async function collectCanonicalRules(root) {
11162
11444
  }
11163
11445
  for (const dirent of dirents) {
11164
11446
  const childRelative = dirRelative ? `${dirRelative}/${dirent.name}` : dirent.name;
11165
- const childAbsolute = path39.join(dirAbsolute, dirent.name);
11447
+ const childAbsolute = path40.join(dirAbsolute, dirent.name);
11166
11448
  if (dirent.isDirectory()) {
11167
11449
  await walk(childAbsolute, childRelative);
11168
11450
  continue;
11169
11451
  }
11170
11452
  if (!dirent.isFile())
11171
11453
  continue;
11172
- const ext = path39.extname(dirent.name).toLowerCase();
11454
+ const ext = path40.extname(dirent.name).toLowerCase();
11173
11455
  if (!RULE_EXTENSIONS.has(ext))
11174
11456
  continue;
11175
11457
  if (dirent.name.toLowerCase() === README_BASENAME)
@@ -11220,7 +11502,7 @@ var init_export_render = __esm(() => {
11220
11502
  });
11221
11503
 
11222
11504
  // src/integrations/surfaces-rules.ts
11223
- import path40 from "path";
11505
+ import path41 from "path";
11224
11506
  async function rulesSpec(root) {
11225
11507
  const { rules, skipped } = await collectCanonicalRules(root);
11226
11508
  const body = renderRulesBlockBody(rules);
@@ -11265,7 +11547,7 @@ function rulesExportSurface(params) {
11265
11547
  ...riskNotes !== undefined ? { riskNotes } : {},
11266
11548
  sourceDocs,
11267
11549
  optIn: true,
11268
- settingsFile: (root) => path40.join(root, ...relativePath.split("/")),
11550
+ settingsFile: (root) => path41.join(root, ...relativePath.split("/")),
11269
11551
  relativePath,
11270
11552
  label: relativePath,
11271
11553
  slots: [],
@@ -11348,7 +11630,6 @@ var init_surfaces_rules = __esm(() => {
11348
11630
  });
11349
11631
 
11350
11632
  // src/integrations/surfaces-learning.ts
11351
- import path41 from "path";
11352
11633
  function learningObserverCommand() {
11353
11634
  return "keryx learn observe --hook claude";
11354
11635
  }
@@ -11377,6 +11658,7 @@ function learningObserverValidate(settings) {
11377
11658
  var LEARNING_OBSERVER_SENTINEL = "learning-observer-hooks", LEARNING_OBSERVER_EVENTS, TOOL_EVENTS, TOOL_MATCHER = "*", HOOK_TIMEOUT_SECONDS = 5, LEARNING_OBSERVER_CLAUDE;
11378
11659
  var init_surfaces_learning = __esm(() => {
11379
11660
  init_settings_json();
11661
+ init_claude_settings();
11380
11662
  LEARNING_OBSERVER_EVENTS = [
11381
11663
  "PreToolUse",
11382
11664
  "PostToolUse",
@@ -11403,8 +11685,7 @@ var init_surfaces_learning = __esm(() => {
11403
11685
  "docs/requirements/keryx-agent-platform-expansion/workstreams/W3-self-learning.md",
11404
11686
  "src/learning/observe.ts"
11405
11687
  ],
11406
- settingsFile: (root) => path41.join(root, ".claude", "settings.json"),
11407
- relativePath: ".claude/settings.json",
11688
+ ...claudeSettingsLocation(),
11408
11689
  slots: [
11409
11690
  { key: "hooks", type: "object", access: "owns" },
11410
11691
  { key: "_keryxManaged", type: "array", access: "owns" },
@@ -11425,7 +11706,7 @@ var init_surfaces_learning = __esm(() => {
11425
11706
  return next;
11426
11707
  },
11427
11708
  validate: learningObserverValidate,
11428
- label: ".claude/settings.json (learning observer)",
11709
+ label: `${CLAUDE_LOCAL_SETTINGS_PATH} (learning observer)`,
11429
11710
  groupShape: "nested",
11430
11711
  groupKey: "PreToolUse",
11431
11712
  groupContainer: "hooks"
@@ -11497,6 +11778,9 @@ function getHarnessAdapter(id) {
11497
11778
  function surfacesOf(adapter, query = {}) {
11498
11779
  return adapter.surfaces.filter((s) => (query.flag === undefined || s.flag === query.flag) && (query.subsystem === undefined || s.subsystem === query.subsystem));
11499
11780
  }
11781
+ function surfacePaths(surface) {
11782
+ return surface.relativePathCandidates ?? (surface.relativePath ? [surface.relativePath] : []);
11783
+ }
11500
11784
  function assertRegistryCoherent(adapters = HARNESS_ADAPTERS) {
11501
11785
  for (const adapter of adapters) {
11502
11786
  const seenIds = new Set;
@@ -11510,34 +11794,34 @@ function assertRegistryCoherent(adapters = HARNESS_ADAPTERS) {
11510
11794
  const surfaceOwnerByFile = new Map;
11511
11795
  for (const adapter of adapters) {
11512
11796
  for (const surface of adapter.surfaces) {
11513
- if (!surface.relativePath)
11514
- continue;
11515
- let owningAdapterById = surfaceOwnerByFile.get(surface.relativePath);
11516
- if (!owningAdapterById) {
11517
- owningAdapterById = new Map;
11518
- surfaceOwnerByFile.set(surface.relativePath, owningAdapterById);
11519
- }
11520
- const existingOwner = owningAdapterById.get(surface.id);
11521
- if (existingOwner !== undefined && existingOwner !== adapter.id) {
11522
- throw new Error(`integrations registry: "${surface.relativePath}" has surface id "${surface.id}" registered by both ` + `"${existingOwner}" and "${adapter.id}" \u2014 a SettingsFileOwner keys its surfaces by id per file, so this ` + `would clobber one adapter's surface with the other's`);
11797
+ for (const relativePath of surfacePaths(surface)) {
11798
+ let owningAdapterById = surfaceOwnerByFile.get(relativePath);
11799
+ if (!owningAdapterById) {
11800
+ owningAdapterById = new Map;
11801
+ surfaceOwnerByFile.set(relativePath, owningAdapterById);
11802
+ }
11803
+ const existingOwner = owningAdapterById.get(surface.id);
11804
+ if (existingOwner !== undefined && existingOwner !== adapter.id) {
11805
+ throw new Error(`integrations registry: "${relativePath}" has surface id "${surface.id}" registered by both ` + `"${existingOwner}" and "${adapter.id}" \u2014 a SettingsFileOwner keys its surfaces by id per file, so this ` + `would clobber one adapter's surface with the other's`);
11806
+ }
11807
+ owningAdapterById.set(surface.id, adapter.id);
11523
11808
  }
11524
- owningAdapterById.set(surface.id, adapter.id);
11525
11809
  }
11526
11810
  }
11527
11811
  const slotsByFile = new Map;
11528
11812
  for (const adapter of adapters) {
11529
11813
  for (const surface of adapter.surfaces) {
11530
- if (!surface.relativePath)
11531
- continue;
11532
- let byKey = slotsByFile.get(surface.relativePath);
11533
- if (!byKey) {
11534
- byKey = new Map;
11535
- slotsByFile.set(surface.relativePath, byKey);
11536
- }
11537
- for (const slot of surface.slots) {
11538
- const records = byKey.get(slot.key) ?? [];
11539
- records.push({ owner: `${adapter.id}/${surface.id}`, type: slot.type, access: slot.access });
11540
- byKey.set(slot.key, records);
11814
+ for (const relativePath of surfacePaths(surface)) {
11815
+ let byKey = slotsByFile.get(relativePath);
11816
+ if (!byKey) {
11817
+ byKey = new Map;
11818
+ slotsByFile.set(relativePath, byKey);
11819
+ }
11820
+ for (const slot of surface.slots) {
11821
+ const records = byKey.get(slot.key) ?? [];
11822
+ records.push({ owner: `${adapter.id}/${surface.id}`, type: slot.type, access: slot.access });
11823
+ byKey.set(slot.key, records);
11824
+ }
11541
11825
  }
11542
11826
  }
11543
11827
  }
@@ -11736,11 +12020,13 @@ var init_registry = __esm(() => {
11736
12020
  const byPath = new Map;
11737
12021
  for (const adapter of HARNESS_ADAPTERS) {
11738
12022
  for (const surface of adapter.surfaces) {
11739
- if (!surface.relativePath || !surface.merge || !surface.strip)
12023
+ if (!surface.merge || !surface.strip)
11740
12024
  continue;
11741
- const list = byPath.get(surface.relativePath) ?? [];
11742
- list.push(surface);
11743
- byPath.set(surface.relativePath, list);
12025
+ for (const relativePath of surfacePaths(surface)) {
12026
+ const list = byPath.get(relativePath) ?? [];
12027
+ list.push(surface);
12028
+ byPath.set(relativePath, list);
12029
+ }
11744
12030
  }
11745
12031
  }
11746
12032
  return [...byPath.entries()].map(([relativePath, surfaces]) => createSettingsFileOwner(relativePath, surfaces));
@@ -11786,7 +12072,68 @@ var init_rules_export = __esm(() => {
11786
12072
  });
11787
12073
 
11788
12074
  // src/integrations/jev-edit-guard-surface.ts
12075
+ function editGuardHookCommand() {
12076
+ return "keryx review jev-edit-guard --hook claude";
12077
+ }
12078
+ function editGuardPostToolUseGroup() {
12079
+ return {
12080
+ matcher: EDIT_GUARD_HOOK_MATCHER,
12081
+ hooks: [{ type: "command", command: editGuardHookCommand() }],
12082
+ [MANAGED_KEY]: EDIT_GUARD_HOOK_SENTINEL
12083
+ };
12084
+ }
12085
+ function validateEditGuardSurface(settings) {
12086
+ const present = managedGroups(settings, {
12087
+ sentinel: EDIT_GUARD_HOOK_SENTINEL,
12088
+ container: "hooks",
12089
+ key: "PostToolUse",
12090
+ shape: "nested",
12091
+ commandMatches: (c) => c === editGuardHookCommand()
12092
+ });
12093
+ if (present.length === 0)
12094
+ return [`jev-edit-guard: missing PostToolUse(${EDIT_GUARD_HOOK_MATCHER}) guard`];
12095
+ const stale = present.some((g) => typeof g.matcher !== "string" || g.matcher !== EDIT_GUARD_HOOK_MATCHER);
12096
+ if (stale) {
12097
+ return [
12098
+ `jev-edit-guard: PostToolUse guard does not match ${EDIT_GUARD_HOOK_MATCHER}; an edit tool it should cover bypasses it. ` + "Re-run `keryx review jev-edit-guard install`."
12099
+ ];
12100
+ }
12101
+ return [];
12102
+ }
12103
+ var EDIT_GUARD_HOOK_SENTINEL = "jev-edit-guard-hooks", EDIT_GUARD_HOOK_MATCHER = "Edit|Write|MultiEdit", JEV_EDIT_GUARD_SURFACE;
11789
12104
  var init_jev_edit_guard_surface = __esm(() => {
12105
+ init_claude_settings();
12106
+ init_settings_json();
12107
+ JEV_EDIT_GUARD_SURFACE = {
12108
+ id: "jev-edit-guard",
12109
+ flag: "post-tool",
12110
+ subsystem: "jev-edit-guard",
12111
+ sentinel: EDIT_GUARD_HOOK_SENTINEL,
12112
+ confidence: "verified",
12113
+ sourceDocs: ["src/commands/review-jev-edit-guard.ts"],
12114
+ ...claudeSettingsLocation(),
12115
+ slots: [
12116
+ { key: "hooks", type: "object", access: "owns" },
12117
+ { key: "_keryxManaged", type: "array", access: "owns" },
12118
+ { key: "unmigratedHooks", type: "array", access: "owns" }
12119
+ ],
12120
+ merge: (s) => mergeIntoHookArray(s, "PostToolUse", editGuardPostToolUseGroup(), EDIT_GUARD_HOOK_SENTINEL),
12121
+ strip: (s) => stripFromHookArray(s, "PostToolUse", EDIT_GUARD_HOOK_SENTINEL),
12122
+ validate: validateEditGuardSurface
12123
+ };
12124
+ });
12125
+
12126
+ // src/integrations/claude-settings-migration.ts
12127
+ var init_claude_settings_migration = __esm(() => {
12128
+ init_contained_write();
12129
+ init_git_worktrees();
12130
+ init_symlink_safety();
12131
+ init_entrypoint_migration();
12132
+ init_entrypoint_targets();
12133
+ init_claude_settings();
12134
+ init_install_state();
12135
+ init_jev_edit_guard_surface();
12136
+ init_registry();
11790
12137
  init_settings_json();
11791
12138
  });
11792
12139
 
@@ -11799,6 +12146,8 @@ var init_service = __esm(() => {
11799
12146
  init_surfaces();
11800
12147
  init_rules_export();
11801
12148
  init_jev_edit_guard_surface();
12149
+ init_claude_settings();
12150
+ init_claude_settings_migration();
11802
12151
  init_surfaces_w5b();
11803
12152
  init_surfaces_learning();
11804
12153
  init_markdown_block();
@@ -11981,7 +12330,9 @@ var init_surfaces2 = __esm(() => {
11981
12330
  init_integrations();
11982
12331
  INSTRUCTION_FILENAMES = [
11983
12332
  "AGENTS.md",
12333
+ "AGENTS.override.md",
11984
12334
  "CLAUDE.md",
12335
+ "CLAUDE.local.md",
11985
12336
  "GEMINI.md",
11986
12337
  ".github/copilot-instructions.md",
11987
12338
  ".cursor/rules/keryx-rules.mdc",
@@ -12657,7 +13008,7 @@ function isReportedSpeechContext(content, start) {
12657
13008
  return REPORTED_SPEECH_RE.test(before);
12658
13009
  }
12659
13010
  function isDowngradedInjectionContext(content, origStart, origEnd) {
12660
- if (isInsideFence(computeFencedRanges2(content), origStart))
13011
+ if (isInsideFence(computeFencedRanges3(content), origStart))
12661
13012
  return true;
12662
13013
  if (isQuotedExample(content, origStart, origEnd))
12663
13014
  return true;
@@ -13032,7 +13383,7 @@ function checkHookRemoteExec(relativePath, hookCommand, pointer) {
13032
13383
  }
13033
13384
  ];
13034
13385
  }
13035
- function computeFencedRanges2(content) {
13386
+ function computeFencedRanges3(content) {
13036
13387
  const ranges = [];
13037
13388
  let offset = 0;
13038
13389
  let fenceStart;
@@ -13063,7 +13414,7 @@ function isInsideFence(ranges, index) {
13063
13414
  function checkRemoteExecInText(surface, relativePath, content) {
13064
13415
  const matches = matchAllRemoteExecShapes(content);
13065
13416
  if (matches.length > 0) {
13066
- const fencedRanges = computeFencedRanges2(content);
13417
+ const fencedRanges = computeFencedRanges3(content);
13067
13418
  const unfenced = matches.find((m) => !isInsideFence(fencedRanges, m.index));
13068
13419
  const reported = unfenced ?? matches[0];
13069
13420
  const fenced = unfenced === undefined;
@@ -17437,7 +17788,7 @@ function describeJsonValue(value) {
17437
17788
  }
17438
17789
  return `a ${typeof value}`;
17439
17790
  }
17440
- function isRecord(value) {
17791
+ function isRecord3(value) {
17441
17792
  return typeof value === "object" && value !== null && !Array.isArray(value);
17442
17793
  }
17443
17794
  function stringOr(value, fallback) {
@@ -17447,7 +17798,7 @@ function nullableString(value) {
17447
17798
  return typeof value === "string" ? value : null;
17448
17799
  }
17449
17800
  function parseEntry2(value) {
17450
- if (!isRecord(value)) {
17801
+ if (!isRecord3(value)) {
17451
17802
  return { ok: false };
17452
17803
  }
17453
17804
  const kind = value["kind"];
@@ -17469,7 +17820,7 @@ function parseEntry2(value) {
17469
17820
  }
17470
17821
  function parseTombstone(value) {
17471
17822
  const base = parseEntry2(value);
17472
- if (!base.ok || !isRecord(value)) {
17823
+ if (!base.ok || !isRecord3(value)) {
17473
17824
  return { ok: false };
17474
17825
  }
17475
17826
  const removedAt = value["removedAt"];
@@ -17483,7 +17834,7 @@ function parseTombstone(value) {
17483
17834
  }
17484
17835
  function parseLifted(value) {
17485
17836
  const base = parseTombstone(value);
17486
- if (!base.ok || !isRecord(value)) {
17837
+ if (!base.ok || !isRecord3(value)) {
17487
17838
  return { ok: false };
17488
17839
  }
17489
17840
  const restoredAt = value["restoredAt"];
@@ -21621,6 +21972,201 @@ var init_wiki_layer = __esm(() => {
21621
21972
  init_section_index();
21622
21973
  });
21623
21974
 
21975
+ // src/gdskills/contracts.ts
21976
+ import { readFile as readFile46 } from "fs/promises";
21977
+ import { existsSync as existsSync14 } from "fs";
21978
+ import path98 from "path";
21979
+ import { fileURLToPath as fileURLToPath2 } from "url";
21980
+ async function loadSchema(name) {
21981
+ const contract = CONTRACTS.find((entry) => entry.name === name);
21982
+ if (!contract) {
21983
+ throw new Error(`Unknown contract schema: ${name}`);
21984
+ }
21985
+ const raw = await readFile46(contractPath(contract), "utf8");
21986
+ return JSON.parse(raw);
21987
+ }
21988
+ function contractPath(contract) {
21989
+ const here = path98.dirname(fileURLToPath2(import.meta.url));
21990
+ if (contract.sourcePath) {
21991
+ for (const root of [path98.join(here, "..", ".."), path98.join(here, ".."), here]) {
21992
+ const candidate = path98.resolve(root, contract.sourcePath);
21993
+ if (existsSync14(candidate)) {
21994
+ return candidate;
21995
+ }
21996
+ }
21997
+ }
21998
+ const directPath = fileURLToPath2(new URL(`./contracts/${contract.fileName}`, import.meta.url));
21999
+ if (existsSync14(directPath)) {
22000
+ return directPath;
22001
+ }
22002
+ const packagedSourcePath = path98.join(here, "..", "src", "gdskills", "contracts", contract.fileName);
22003
+ if (existsSync14(packagedSourcePath)) {
22004
+ return packagedSourcePath;
22005
+ }
22006
+ return directPath;
22007
+ }
22008
+ var CONTRACTS, SCHEMA_ROOTS;
22009
+ var init_contracts = __esm(() => {
22010
+ CONTRACTS = [
22011
+ {
22012
+ name: "agent-event",
22013
+ fileName: "agent-event.schema.json",
22014
+ description: "Append-only lifecycle event emitted by orchestrators and subagents.",
22015
+ enforcement: {
22016
+ kind: "none",
22017
+ reason: "Events are appended by whichever agent is running, in that agent's own runtime. No keryx process sits between the emitter and the log, so there is no point at which a malformed event could be refused rather than written."
22018
+ }
22019
+ },
22020
+ {
22021
+ name: "flow-orchestrator-input",
22022
+ fileName: "flow-orchestrator-input-contract.schema.json",
22023
+ description: "Dispatch payload handed to flow-orchestrator (request + base branch + completion outcome + constraints).",
22024
+ sourcePath: "src/gdskills/bundled/skills/orchestration/flow-orchestrator/input-contract.schema.json",
22025
+ enforcement: {
22026
+ kind: "none",
22027
+ reason: "This payload is handed from one AGENT to another. In a session driven by a host agent's own dispatch tool, no keryx process is on that path, so nothing can refuse a malformed dispatch. Same structural position as reviewer-input, recorded for the same reason. Registering it made `keryx skills contracts validate --schema flow-orchestrator-input` possible, which is a validator an agent must remember to run, not an enforcement."
22028
+ }
22029
+ },
22030
+ {
22031
+ name: "review-pr-feedback-input",
22032
+ fileName: "review-pr-feedback-input-contract.schema.json",
22033
+ description: "Request handed to review-pr-feedback (PR reference, --fix, and the operator confirmation --fix requires).",
22034
+ sourcePath: "src/gdskills/bundled/skills/review/review-pr-feedback/input-contract.schema.json",
22035
+ enforcement: {
22036
+ kind: "none",
22037
+ reason: "The request is what an operator or a host agent hands the skill before any keryx command runs, so keryx is not yet in the path when the payload would have to be refused. This is the one where that hurts most: `--fix` merges third-party review comments into somebody else's pull request, and `operator_confirmed` is the fence. The conditional is expressed in the schema and `keryx skills contracts validate --schema review-pr-feedback-input` will apply it \u2014 but only when something invokes it."
22038
+ }
22039
+ },
22040
+ {
22041
+ name: "review-pr-feedback-output",
22042
+ fileName: "review-pr-feedback-output-contract.schema.json",
22043
+ description: "Result review-pr-feedback returns: verdict counts, the injection-screen record, and what the fix run merged.",
22044
+ sourcePath: "src/gdskills/bundled/skills/review/review-pr-feedback/output-contract.schema.json",
22045
+ enforcement: {
22046
+ kind: "opt-in",
22047
+ module: "src/commands/review.ts",
22048
+ switchedOnBy: "keryx review comments reply --result <file>",
22049
+ refuses: "A result that contradicts itself, before the pass is built and before any network call: an analyze-mode run reporting a branch and a merge, or a record saying the injection screen never ran while claiming it excluded comments."
22050
+ }
22051
+ },
22052
+ {
22053
+ name: "job-orchestrator-state",
22054
+ fileName: "job-orchestrator-state.schema.json",
22055
+ description: "Persisted job package state (.metaproject/jobs/<name>/state.json), written by `keryx job`.",
22056
+ sourcePath: "src/gdskills/bundled/skills/orchestration/job-orchestrator/state.schema.json",
22057
+ enforcement: {
22058
+ kind: "production",
22059
+ module: "src/job/store.ts",
22060
+ refuses: "`writeJob` refuses to write a state file that does not satisfy the schema, so an invalid job package cannot reach disk."
22061
+ }
22062
+ },
22063
+ {
22064
+ name: "orchestrator-state",
22065
+ fileName: "orchestrator-state.schema.json",
22066
+ description: "Persisted resumable orchestrator state.",
22067
+ enforcement: {
22068
+ kind: "none",
22069
+ reason: "No keryx command reads or writes this file. It describes state an orchestrator agent persists for itself, in its own runtime, so there is no keryx-owned write path to refuse at. Contrast job-orchestrator-state, which looks similar and IS enforced for exactly one reason: `keryx job` writes that one."
22070
+ }
22071
+ },
22072
+ {
22073
+ name: "review-finding",
22074
+ fileName: "review-finding.schema.json",
22075
+ description: "Normalized reviewer finding consumed by review-orchestrator and learning flows.",
22076
+ enforcement: {
22077
+ kind: "production",
22078
+ module: "src/review/managed.ts",
22079
+ refuses: "`createManagedReviewPackage` refuses to record findings that do not satisfy the schema, and the disposition sub-schema is applied the same way, so a review round cannot be written with a malformed finding or an unreadable outcome."
22080
+ }
22081
+ },
22082
+ {
22083
+ name: "subagent-dispatch",
22084
+ fileName: "subagent-dispatch.schema.json",
22085
+ description: "Orchestrator-to-subagent dispatch payload.",
22086
+ enforcement: {
22087
+ kind: "none",
22088
+ reason: "The dispatch is built and consumed inside the harness without the canonical schema being loaded: `subagent-dispatch` appears only as a label on the extension metadata. Its sibling `subagent-result` IS validated, because the harness parses a child's reply back and has to decide whether it is well-formed; nothing performs the equivalent act on the way out."
22089
+ }
22090
+ },
22091
+ {
22092
+ name: "subagent-result",
22093
+ fileName: "subagent-result.schema.json",
22094
+ description: "Subagent-to-orchestrator result payload.",
22095
+ enforcement: {
22096
+ kind: "production",
22097
+ module: "src/harness/external/runtime.ts",
22098
+ refuses: "A child's reply that does not parse as a well-formed result is rejected when the harness reads it back, so a malformed result cannot be persisted as if the child had succeeded."
22099
+ }
22100
+ },
22101
+ {
22102
+ name: "task-implementer-input",
22103
+ fileName: "task-implementer-input-contract.schema.json",
22104
+ description: "Task request handed to task-implementer (task + workspace + automation), validated before dispatch.",
22105
+ sourcePath: "src/gdskills/bundled/skills/orchestration/task-implementer/input-contract.schema.json",
22106
+ enforcement: {
22107
+ kind: "none",
22108
+ reason: "Dispatched agent-to-agent, like flow-orchestrator-input. The skill's Phase 1.4 lists five `ASSERT \u2026 \u2192 ABORT(\u2026)` refusals; registering the contract is what lets `keryx skills contracts validate` perform them, and nothing forces that call. The comment beside this registration has said so since it was written."
22109
+ }
22110
+ },
22111
+ {
22112
+ name: "task-implementer-output",
22113
+ fileName: "task-implementer-output-contract.schema.json",
22114
+ description: "JSON result task-implementer writes in Phase 6.1 before it emits its STATUS line.",
22115
+ sourcePath: "src/gdskills/bundled/skills/orchestration/task-implementer/output-contract.schema.json",
22116
+ enforcement: {
22117
+ kind: "none",
22118
+ reason: "The skill writes this file itself, in its own runtime, and no keryx command reads it back. Contrast subagent-result, which is the same shape of thing and IS enforced for one reason: the harness reads that one back and must decide whether it is well-formed."
22119
+ }
22120
+ }
22121
+ ];
22122
+ SCHEMA_ROOTS = [
22123
+ fileURLToPath2(new URL("./contracts/", import.meta.url)),
22124
+ fileURLToPath2(new URL("./bundled/skills/review/review-orchestrator/", import.meta.url))
22125
+ ];
22126
+ });
22127
+
22128
+ // src/lib/config-dir.ts
22129
+ import {
22130
+ appendFileSync,
22131
+ chmodSync,
22132
+ mkdirSync,
22133
+ readFileSync as readFileSync6,
22134
+ renameSync,
22135
+ statSync as statSync2,
22136
+ unlinkSync,
22137
+ writeFileSync
22138
+ } from "fs";
22139
+ function readBoundedFile(file, maxBytes) {
22140
+ let stats;
22141
+ try {
22142
+ stats = statSync2(file);
22143
+ } catch {
22144
+ return { ok: false, reason: "absent" };
22145
+ }
22146
+ if (!stats.isFile()) {
22147
+ return { ok: false, reason: "not-regular" };
22148
+ }
22149
+ if (stats.size > maxBytes) {
22150
+ return { ok: false, reason: "too-large" };
22151
+ }
22152
+ try {
22153
+ return { ok: true, text: readFileSync6(file, "utf8") };
22154
+ } catch {
22155
+ return { ok: false, reason: "unreadable" };
22156
+ }
22157
+ }
22158
+ function readConfigFile(file) {
22159
+ return readBoundedFile(file, MAX_CONFIG_FILE_BYTES);
22160
+ }
22161
+ function readTranscriptFile(file) {
22162
+ return readBoundedFile(file, MAX_TRANSCRIPT_FILE_BYTES);
22163
+ }
22164
+ var MAX_CONFIG_FILE_BYTES = 1e6, MAX_CONTENT_FILE_BYTES, MAX_TRANSCRIPT_FILE_BYTES;
22165
+ var init_config_dir = __esm(() => {
22166
+ MAX_CONTENT_FILE_BYTES = 64 * 1024 * 1024;
22167
+ MAX_TRANSCRIPT_FILE_BYTES = MAX_CONTENT_FILE_BYTES;
22168
+ });
22169
+
21624
22170
  // src/flow/service.ts
21625
22171
  init_fs();
21626
22172
  init_validator();
@@ -26317,7 +26863,7 @@ async function resolveTsconfigOptions(configPath, visited, depth, projectRoot) {
26317
26863
  const options = parsed.compilerOptions ?? {};
26318
26864
  const ownBaseUrl = typeof options.baseUrl === "string" ? toProjectRelativeDir(projectRoot, path71.resolve(configDir, options.baseUrl)) : null;
26319
26865
  const baseUrl = ownBaseUrl ?? inherited?.baseUrl ?? null;
26320
- const ownPaths = isRecord2(options.paths) ? Object.entries(options.paths).filter((entry) => Array.isArray(entry[1])).map(([pattern, targets]) => ({
26866
+ const ownPaths = isRecord4(options.paths) ? Object.entries(options.paths).filter((entry) => Array.isArray(entry[1])).map(([pattern, targets]) => ({
26321
26867
  pattern,
26322
26868
  targets: targets.filter((target) => typeof target === "string")
26323
26869
  })) : null;
@@ -26413,7 +26959,7 @@ function stripJsonComments(source) {
26413
26959
  }
26414
26960
  return out;
26415
26961
  }
26416
- function isRecord2(value) {
26962
+ function isRecord4(value) {
26417
26963
  return typeof value === "object" && value !== null && !Array.isArray(value);
26418
26964
  }
26419
26965
  async function writeGraph(projectRoot, graph) {
@@ -31725,161 +32271,10 @@ function defaultPlan(intent) {
31725
32271
 
31726
32272
  // src/job/store.ts
31727
32273
  init_validator();
32274
+ init_contracts();
32275
+ init_fs();
31728
32276
  import { appendFile as appendFile5, mkdir as mkdir27, readFile as readFile47, readdir as readdir16 } from "fs/promises";
31729
32277
  import path99 from "path";
31730
-
31731
- // src/gdskills/contracts.ts
31732
- import { readFile as readFile46 } from "fs/promises";
31733
- import { existsSync as existsSync14 } from "fs";
31734
- import path98 from "path";
31735
- import { fileURLToPath as fileURLToPath2 } from "url";
31736
- var CONTRACTS = [
31737
- {
31738
- name: "agent-event",
31739
- fileName: "agent-event.schema.json",
31740
- description: "Append-only lifecycle event emitted by orchestrators and subagents.",
31741
- enforcement: {
31742
- kind: "none",
31743
- reason: "Events are appended by whichever agent is running, in that agent's own runtime. No keryx process sits between the emitter and the log, so there is no point at which a malformed event could be refused rather than written."
31744
- }
31745
- },
31746
- {
31747
- name: "flow-orchestrator-input",
31748
- fileName: "flow-orchestrator-input-contract.schema.json",
31749
- description: "Dispatch payload handed to flow-orchestrator (request + base branch + completion outcome + constraints).",
31750
- sourcePath: "src/gdskills/bundled/skills/orchestration/flow-orchestrator/input-contract.schema.json",
31751
- enforcement: {
31752
- kind: "none",
31753
- reason: "This payload is handed from one AGENT to another. In a session driven by a host agent's own dispatch tool, no keryx process is on that path, so nothing can refuse a malformed dispatch. Same structural position as reviewer-input, recorded for the same reason. Registering it made `keryx skills contracts validate --schema flow-orchestrator-input` possible, which is a validator an agent must remember to run, not an enforcement."
31754
- }
31755
- },
31756
- {
31757
- name: "review-pr-feedback-input",
31758
- fileName: "review-pr-feedback-input-contract.schema.json",
31759
- description: "Request handed to review-pr-feedback (PR reference, --fix, and the operator confirmation --fix requires).",
31760
- sourcePath: "src/gdskills/bundled/skills/review/review-pr-feedback/input-contract.schema.json",
31761
- enforcement: {
31762
- kind: "none",
31763
- reason: "The request is what an operator or a host agent hands the skill before any keryx command runs, so keryx is not yet in the path when the payload would have to be refused. This is the one where that hurts most: `--fix` merges third-party review comments into somebody else's pull request, and `operator_confirmed` is the fence. The conditional is expressed in the schema and `keryx skills contracts validate --schema review-pr-feedback-input` will apply it \u2014 but only when something invokes it."
31764
- }
31765
- },
31766
- {
31767
- name: "review-pr-feedback-output",
31768
- fileName: "review-pr-feedback-output-contract.schema.json",
31769
- description: "Result review-pr-feedback returns: verdict counts, the injection-screen record, and what the fix run merged.",
31770
- sourcePath: "src/gdskills/bundled/skills/review/review-pr-feedback/output-contract.schema.json",
31771
- enforcement: {
31772
- kind: "opt-in",
31773
- module: "src/commands/review.ts",
31774
- switchedOnBy: "keryx review comments reply --result <file>",
31775
- refuses: "A result that contradicts itself, before the pass is built and before any network call: an analyze-mode run reporting a branch and a merge, or a record saying the injection screen never ran while claiming it excluded comments."
31776
- }
31777
- },
31778
- {
31779
- name: "job-orchestrator-state",
31780
- fileName: "job-orchestrator-state.schema.json",
31781
- description: "Persisted job package state (.metaproject/jobs/<name>/state.json), written by `keryx job`.",
31782
- sourcePath: "src/gdskills/bundled/skills/orchestration/job-orchestrator/state.schema.json",
31783
- enforcement: {
31784
- kind: "production",
31785
- module: "src/job/store.ts",
31786
- refuses: "`writeJob` refuses to write a state file that does not satisfy the schema, so an invalid job package cannot reach disk."
31787
- }
31788
- },
31789
- {
31790
- name: "orchestrator-state",
31791
- fileName: "orchestrator-state.schema.json",
31792
- description: "Persisted resumable orchestrator state.",
31793
- enforcement: {
31794
- kind: "none",
31795
- reason: "No keryx command reads or writes this file. It describes state an orchestrator agent persists for itself, in its own runtime, so there is no keryx-owned write path to refuse at. Contrast job-orchestrator-state, which looks similar and IS enforced for exactly one reason: `keryx job` writes that one."
31796
- }
31797
- },
31798
- {
31799
- name: "review-finding",
31800
- fileName: "review-finding.schema.json",
31801
- description: "Normalized reviewer finding consumed by review-orchestrator and learning flows.",
31802
- enforcement: {
31803
- kind: "production",
31804
- module: "src/review/managed.ts",
31805
- refuses: "`createManagedReviewPackage` refuses to record findings that do not satisfy the schema, and the disposition sub-schema is applied the same way, so a review round cannot be written with a malformed finding or an unreadable outcome."
31806
- }
31807
- },
31808
- {
31809
- name: "subagent-dispatch",
31810
- fileName: "subagent-dispatch.schema.json",
31811
- description: "Orchestrator-to-subagent dispatch payload.",
31812
- enforcement: {
31813
- kind: "none",
31814
- reason: "The dispatch is built and consumed inside the harness without the canonical schema being loaded: `subagent-dispatch` appears only as a label on the extension metadata. Its sibling `subagent-result` IS validated, because the harness parses a child's reply back and has to decide whether it is well-formed; nothing performs the equivalent act on the way out."
31815
- }
31816
- },
31817
- {
31818
- name: "subagent-result",
31819
- fileName: "subagent-result.schema.json",
31820
- description: "Subagent-to-orchestrator result payload.",
31821
- enforcement: {
31822
- kind: "production",
31823
- module: "src/harness/external/runtime.ts",
31824
- refuses: "A child's reply that does not parse as a well-formed result is rejected when the harness reads it back, so a malformed result cannot be persisted as if the child had succeeded."
31825
- }
31826
- },
31827
- {
31828
- name: "task-implementer-input",
31829
- fileName: "task-implementer-input-contract.schema.json",
31830
- description: "Task request handed to task-implementer (task + workspace + automation), validated before dispatch.",
31831
- sourcePath: "src/gdskills/bundled/skills/orchestration/task-implementer/input-contract.schema.json",
31832
- enforcement: {
31833
- kind: "none",
31834
- reason: "Dispatched agent-to-agent, like flow-orchestrator-input. The skill's Phase 1.4 lists five `ASSERT \u2026 \u2192 ABORT(\u2026)` refusals; registering the contract is what lets `keryx skills contracts validate` perform them, and nothing forces that call. The comment beside this registration has said so since it was written."
31835
- }
31836
- },
31837
- {
31838
- name: "task-implementer-output",
31839
- fileName: "task-implementer-output-contract.schema.json",
31840
- description: "JSON result task-implementer writes in Phase 6.1 before it emits its STATUS line.",
31841
- sourcePath: "src/gdskills/bundled/skills/orchestration/task-implementer/output-contract.schema.json",
31842
- enforcement: {
31843
- kind: "none",
31844
- reason: "The skill writes this file itself, in its own runtime, and no keryx command reads it back. Contrast subagent-result, which is the same shape of thing and IS enforced for one reason: the harness reads that one back and must decide whether it is well-formed."
31845
- }
31846
- }
31847
- ];
31848
- async function loadSchema(name) {
31849
- const contract = CONTRACTS.find((entry) => entry.name === name);
31850
- if (!contract) {
31851
- throw new Error(`Unknown contract schema: ${name}`);
31852
- }
31853
- const raw = await readFile46(contractPath(contract), "utf8");
31854
- return JSON.parse(raw);
31855
- }
31856
- function contractPath(contract) {
31857
- const here = path98.dirname(fileURLToPath2(import.meta.url));
31858
- if (contract.sourcePath) {
31859
- for (const root of [path98.join(here, "..", ".."), path98.join(here, ".."), here]) {
31860
- const candidate = path98.resolve(root, contract.sourcePath);
31861
- if (existsSync14(candidate)) {
31862
- return candidate;
31863
- }
31864
- }
31865
- }
31866
- const directPath = fileURLToPath2(new URL(`./contracts/${contract.fileName}`, import.meta.url));
31867
- if (existsSync14(directPath)) {
31868
- return directPath;
31869
- }
31870
- const packagedSourcePath = path98.join(here, "..", "src", "gdskills", "contracts", contract.fileName);
31871
- if (existsSync14(packagedSourcePath)) {
31872
- return packagedSourcePath;
31873
- }
31874
- return directPath;
31875
- }
31876
- var SCHEMA_ROOTS = [
31877
- fileURLToPath2(new URL("./contracts/", import.meta.url)),
31878
- fileURLToPath2(new URL("./bundled/skills/review/review-orchestrator/", import.meta.url))
31879
- ];
31880
-
31881
- // src/job/store.ts
31882
- init_fs();
31883
32278
  function jobsRoot(cwd) {
31884
32279
  return path99.join(cwd, ".metaproject", "jobs");
31885
32280
  }
@@ -32322,7 +32717,7 @@ function compareStrictUtc(left, right) {
32322
32717
  return 0;
32323
32718
  return leftFraction < rightFraction ? -1 : 1;
32324
32719
  }
32325
- function isRecord3(value) {
32720
+ function isRecord5(value) {
32326
32721
  return typeof value === "object" && value !== null && !Array.isArray(value);
32327
32722
  }
32328
32723
  async function loadNormativeSchema(schema) {
@@ -32338,18 +32733,18 @@ function resolveSchemaRef(root, ref) {
32338
32733
  return;
32339
32734
  let current = root;
32340
32735
  for (const part of ref.slice(2).split("/")) {
32341
- if (!isRecord3(current))
32736
+ if (!isRecord5(current))
32342
32737
  return;
32343
32738
  current = current[part.replace(/~1/g, "/").replace(/~0/g, "~")];
32344
32739
  }
32345
- return isRecord3(current) ? current : undefined;
32740
+ return isRecord5(current) ? current : undefined;
32346
32741
  }
32347
32742
  function jsonEqual(left, right) {
32348
32743
  if (Object.is(left, right))
32349
32744
  return true;
32350
32745
  if (Array.isArray(left) && Array.isArray(right))
32351
32746
  return left.length === right.length && left.every((item, index) => jsonEqual(item, right[index]));
32352
- if (isRecord3(left) && isRecord3(right)) {
32747
+ if (isRecord5(left) && isRecord5(right)) {
32353
32748
  const leftKeys = Object.keys(left).sort();
32354
32749
  const rightKeys = Object.keys(right).sort();
32355
32750
  return leftKeys.length === rightKeys.length && leftKeys.every((key, index) => key === rightKeys[index] && jsonEqual(left[key], right[key]));
@@ -32369,7 +32764,7 @@ function validateNormativeSchema(root, schema, value, errors, field) {
32369
32764
  if (Array.isArray(schema.oneOf)) {
32370
32765
  const candidates = schema.oneOf.map((candidate) => {
32371
32766
  const candidateErrors = [];
32372
- const valid = isRecord3(candidate) && validateNormativeSchema(root, candidate, value, candidateErrors, field);
32767
+ const valid = isRecord5(candidate) && validateNormativeSchema(root, candidate, value, candidateErrors, field);
32373
32768
  return { valid, errors: candidateErrors };
32374
32769
  });
32375
32770
  const matched = candidates.filter((candidate) => candidate.valid).length;
@@ -32381,22 +32776,22 @@ function validateNormativeSchema(root, schema, value, errors, field) {
32381
32776
  }
32382
32777
  if (Array.isArray(schema.allOf)) {
32383
32778
  for (const candidate of schema.allOf)
32384
- if (isRecord3(candidate))
32779
+ if (isRecord5(candidate))
32385
32780
  validateNormativeSchema(root, candidate, value, errors, field);
32386
32781
  }
32387
- if (isRecord3(schema.if)) {
32782
+ if (isRecord5(schema.if)) {
32388
32783
  const conditionErrors = [];
32389
32784
  const branch = validateNormativeSchema(root, schema.if, value, conditionErrors, field) ? schema.then : schema.else;
32390
- if (isRecord3(branch))
32785
+ if (isRecord5(branch))
32391
32786
  validateNormativeSchema(root, branch, value, errors, field);
32392
32787
  }
32393
- if (isRecord3(schema.not)) {
32788
+ if (isRecord5(schema.not)) {
32394
32789
  const prohibitedErrors = [];
32395
32790
  if (validateNormativeSchema(root, schema.not, value, prohibitedErrors, field))
32396
32791
  error(errors, "schema_not", field, "matches a prohibited normative schema branch");
32397
32792
  }
32398
32793
  if (Array.isArray(schema.anyOf)) {
32399
- const matched = schema.anyOf.some((candidate) => isRecord3(candidate) && validateNormativeSchema(root, candidate, value, [], field));
32794
+ const matched = schema.anyOf.some((candidate) => isRecord5(candidate) && validateNormativeSchema(root, candidate, value, [], field));
32400
32795
  if (!matched)
32401
32796
  error(errors, "schema_any_of", field, "must match a normative schema branch");
32402
32797
  }
@@ -32404,12 +32799,12 @@ function validateNormativeSchema(root, schema, value, errors, field) {
32404
32799
  error(errors, "schema_const", field, "does not match normative const");
32405
32800
  if (Array.isArray(schema.enum) && !schema.enum.some((candidate) => value === candidate))
32406
32801
  error(errors, "schema_enum", field, "does not match normative enum");
32407
- if (schema.type === "object" && !isRecord3(value)) {
32802
+ if (schema.type === "object" && !isRecord5(value)) {
32408
32803
  error(errors, "schema_type", field, "must be an object");
32409
32804
  return false;
32410
32805
  }
32411
- if (isRecord3(value) && (schema.type === "object" || isRecord3(schema.properties) || Array.isArray(schema.required) || schema.additionalProperties === false)) {
32412
- const properties = isRecord3(schema.properties) ? schema.properties : {};
32806
+ if (isRecord5(value) && (schema.type === "object" || isRecord5(schema.properties) || Array.isArray(schema.required) || schema.additionalProperties === false)) {
32807
+ const properties = isRecord5(schema.properties) ? schema.properties : {};
32413
32808
  if (Array.isArray(schema.required)) {
32414
32809
  for (const key of schema.required)
32415
32810
  if (typeof key === "string" && !(key in value))
@@ -32421,7 +32816,7 @@ function validateNormativeSchema(root, schema, value, errors, field) {
32421
32816
  error(errors, "additional_property", `${field}.${key}`, "is not allowed by normative schema");
32422
32817
  }
32423
32818
  for (const [key, propertySchema] of Object.entries(properties))
32424
- if (key in value && isRecord3(propertySchema))
32819
+ if (key in value && isRecord5(propertySchema))
32425
32820
  validateNormativeSchema(root, propertySchema, value[key], errors, `${field}.${key}`);
32426
32821
  }
32427
32822
  if (schema.type === "array") {
@@ -32437,9 +32832,9 @@ function validateNormativeSchema(root, schema, value, errors, field) {
32437
32832
  if (jsonEqual(value[left], value[right]))
32438
32833
  error(errors, "schema_unique_items", `${field}[${right}]`, "duplicates an earlier item prohibited by the normative schema");
32439
32834
  }
32440
- if (isRecord3(schema.items))
32835
+ if (isRecord5(schema.items))
32441
32836
  value.forEach((item, index) => validateNormativeSchema(root, schema.items, item, errors, `${field}[${index}]`));
32442
- if (isRecord3(schema.contains)) {
32837
+ if (isRecord5(schema.contains)) {
32443
32838
  const matches = value.filter((item) => validateNormativeSchema(root, schema.contains, item, [], field)).length;
32444
32839
  const minimum = typeof schema.minContains === "number" ? schema.minContains : 1;
32445
32840
  if (matches < minimum)
@@ -32476,7 +32871,7 @@ function error(errors, code, field, message) {
32476
32871
  errors.push({ code, path: field, message });
32477
32872
  }
32478
32873
  function requireObject(value, errors, field) {
32479
- if (!isRecord3(value)) {
32874
+ if (!isRecord5(value)) {
32480
32875
  error(errors, "schema_type", field, "must be an object");
32481
32876
  return false;
32482
32877
  }
@@ -32730,7 +33125,7 @@ function validateWriteIntent(document, errors) {
32730
33125
  workspaceUri(document.approvalRef, errors, "$.approvalRef");
32731
33126
  if (!requireObject(document.security, errors, "$.security") || document.security.gate !== "pass")
32732
33127
  error(errors, "security_gate_failed", "$.security.gate", "write intent requires pass");
32733
- if (isRecord3(document.security)) {
33128
+ if (isRecord5(document.security)) {
32734
33129
  workspaceUri(document.security.policyRef, errors, "$.security.policyRef");
32735
33130
  stringMatch(document.security.policyRevision, revisionPattern, errors, "$.security.policyRevision");
32736
33131
  }
@@ -32771,13 +33166,13 @@ function validateTransition(document, errors) {
32771
33166
  error(errors, "idempotency_mismatch", "$.acceptance.idempotencyKey", "must match the transition key");
32772
33167
  if (!requireObject(acceptance.security, errors, "$.acceptance.security") || acceptance.security.gate !== "pass")
32773
33168
  error(errors, "security_gate_failed", "$.acceptance.security.gate", "acceptance requires pass");
32774
- if (isRecord3(acceptance.security)) {
33169
+ if (isRecord5(acceptance.security)) {
32775
33170
  workspaceUri(acceptance.security.policyRef, errors, "$.acceptance.security.policyRef");
32776
33171
  stringMatch(acceptance.security.policyRevision, revisionPattern, errors, "$.acceptance.security.policyRevision");
32777
33172
  }
32778
33173
  if (!requireObject(acceptance.freshness, errors, "$.acceptance.freshness") || acceptance.freshness.state !== "fresh")
32779
33174
  error(errors, "stale_evidence", "$.acceptance.freshness.state", "acceptance requires fresh evidence");
32780
- if (isRecord3(acceptance.freshness))
33175
+ if (isRecord5(acceptance.freshness))
32781
33176
  utc(acceptance.freshness.verifiedAt, errors, "$.acceptance.freshness.verifiedAt");
32782
33177
  if (!requireObject(acceptance.targetWrite, errors, "$.acceptance.targetWrite"))
32783
33178
  error(errors, "missing_target_write", "$.acceptance.targetWrite", "acceptance requires a guarded write receipt");
@@ -32806,7 +33201,7 @@ function validateTransition(document, errors) {
32806
33201
  stringMatch(binding.policyRevision, revisionPattern, errors, "$.acceptance.targetWrite.binding.policyRevision");
32807
33202
  if (!["owner", "editor"].includes(binding.reviewerAuthority))
32808
33203
  error(errors, "schema_enum", "$.acceptance.targetWrite.binding.reviewerAuthority", "must be owner or editor");
32809
- if (binding.intentRef !== acceptance.writeIntentRef || binding.proposalId !== document.proposalId || binding.proposalRevision !== document.proposalRevision || binding.workspaceId !== document.workspaceId || binding.correlationId !== document.correlationId || binding.idempotencyKey !== document.idempotencyKey || binding.reviewerSubject !== (isRecord3(acceptance.reviewer) ? acceptance.reviewer.subject : undefined) || binding.reviewerAuthority !== (isRecord3(acceptance.reviewer) ? acceptance.reviewer.authority : undefined) || binding.policyRevision !== (isRecord3(acceptance.security) ? acceptance.security.policyRevision : undefined))
33204
+ if (binding.intentRef !== acceptance.writeIntentRef || binding.proposalId !== document.proposalId || binding.proposalRevision !== document.proposalRevision || binding.workspaceId !== document.workspaceId || binding.correlationId !== document.correlationId || binding.idempotencyKey !== document.idempotencyKey || binding.reviewerSubject !== (isRecord5(acceptance.reviewer) ? acceptance.reviewer.subject : undefined) || binding.reviewerAuthority !== (isRecord5(acceptance.reviewer) ? acceptance.reviewer.authority : undefined) || binding.policyRevision !== (isRecord5(acceptance.security) ? acceptance.security.policyRevision : undefined))
32810
33205
  error(errors, "receipt_binding_mismatch", "$.acceptance.targetWrite.binding", "must bind this exact accepted transition");
32811
33206
  else if (binding.bindingHash !== hashSacRecord({ owner: binding.owner, intentRef: binding.intentRef, proposalId: binding.proposalId, proposalRevision: binding.proposalRevision, workspaceId: binding.workspaceId, correlationId: binding.correlationId, idempotencyKey: binding.idempotencyKey, reviewerSubject: binding.reviewerSubject, reviewerAuthority: binding.reviewerAuthority, policyRevision: binding.policyRevision }))
32812
33207
  error(errors, "receipt_binding_hash_mismatch", "$.acceptance.targetWrite.binding.bindingHash", "must hash the retained owner binding");
@@ -32817,10 +33212,10 @@ function validateTransition(document, errors) {
32817
33212
  else
32818
33213
  acceptance.evidence.forEach((item, index) => {
32819
33214
  validateEvidence(item, errors, `$.acceptance.evidence[${index}]`, false);
32820
- if (isRecord3(acceptance.freshness))
32821
- ordered(isRecord3(item) ? item.observedAt : undefined, acceptance.freshness.verifiedAt, errors, `$.acceptance.evidence[${index}].observedAt`);
33215
+ if (isRecord5(acceptance.freshness))
33216
+ ordered(isRecord5(item) ? item.observedAt : undefined, acceptance.freshness.verifiedAt, errors, `$.acceptance.evidence[${index}].observedAt`);
32822
33217
  });
32823
- if (isRecord3(acceptance.freshness) && isRecord3(acceptance.targetWrite)) {
33218
+ if (isRecord5(acceptance.freshness) && isRecord5(acceptance.targetWrite)) {
32824
33219
  ordered(acceptance.freshness.verifiedAt, acceptance.targetWrite.completedAt, errors, "$.acceptance.freshness/targetWrite");
32825
33220
  ordered(acceptance.targetWrite.completedAt, document.occurredAt, errors, "$.acceptance.targetWrite/occurredAt");
32826
33221
  }
@@ -32838,11 +33233,11 @@ function validateReview(document, errors) {
32838
33233
  if (!["accepted", "rejected", "dismissed"].includes(document.decision))
32839
33234
  error(errors, "schema_enum", "$.decision", "is invalid");
32840
33235
  if (document.decision === "accepted") {
32841
- if (!isRecord3(document.security) || document.security.gate !== "pass")
33236
+ if (!isRecord5(document.security) || document.security.gate !== "pass")
32842
33237
  error(errors, "security_gate_failed", "$.security", "accepted requires a passing gate");
32843
- if (!isRecord3(document.freshness) || document.freshness.state !== "fresh")
33238
+ if (!isRecord5(document.freshness) || document.freshness.state !== "fresh")
32844
33239
  error(errors, "stale_evidence", "$.freshness", "accepted requires fresh evidence");
32845
- if (!isRecord3(document.targetWrite))
33240
+ if (!isRecord5(document.targetWrite))
32846
33241
  error(errors, "missing_target_write", "$.targetWrite", "accepted requires target write");
32847
33242
  } else if (typeof document.reason !== "string" || document.reason.length === 0 || document.targetWrite !== undefined || document.freshness !== undefined)
32848
33243
  error(errors, "schema_conditional", "$.reason", "non-accepted decisions require only a reason");
@@ -32980,7 +33375,7 @@ function createSacAuthorizationServer(input) {
32980
33375
  } });
32981
33376
  }
32982
33377
  function isTrustedActorContext(actorContext) {
32983
- return isRecord3(actorContext) && trustedActors.has(actorContext);
33378
+ return isRecord5(actorContext) && trustedActors.has(actorContext);
32984
33379
  }
32985
33380
  var roleRank = { viewer: 1, editor: 2, owner: 3 };
32986
33381
  function authorizationResult(allowed, code, baseline, required, workspaceId) {
@@ -32998,7 +33393,7 @@ function authorizationResult(allowed, code, baseline, required, workspaceId) {
32998
33393
  }
32999
33394
  async function authorizeSacUse(input) {
33000
33395
  const required = input.action === "read" ? 1 : 2;
33001
- if (!isRecord3(input.actorContext) || !trustedActors.has(input.actorContext))
33396
+ if (!isRecord5(input.actorContext) || !trustedActors.has(input.actorContext))
33002
33397
  return authorizationResult(false, "untrusted_actor", { role: "revoked", revision: "invalid", workspaceId: input.workspaceId }, required, input.workspaceId);
33003
33398
  const current = await input.resolveCurrentRole(input.actorContext.subject, input.workspaceId);
33004
33399
  if (current.workspaceId !== input.workspaceId)
@@ -34153,11 +34548,11 @@ async function resolveLedgerState(ledger, checkpointPath, verifier) {
34153
34548
  return auditLedger(ledger, checkpointPath, verifier);
34154
34549
  }
34155
34550
  var policyRefPattern = /^\.\/.+/;
34156
- var isRecord4 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
34551
+ var isRecord6 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
34157
34552
  var asString2 = (value) => typeof value === "string" && value.length > 0;
34158
34553
  var isBaselineArtifact = (artifact) => artifact.schemaVersion === "1.0" && artifact.kind === "deterministic-baseline" && isImmutableVersion2(artifact.version) && artifact.selection === "eligible-ids-in-input-order";
34159
34554
  var isCandidateArtifact = (artifact) => artifact.schemaVersion === "1.0" && artifact.kind === "offline-selection-advisor" && isImmutableVersion2(artifact.version) && asString2(artifact.output) && typeof artifact.mutations === "boolean";
34160
- var isEvaluationReport = (report) => report.schemaVersion === "1.0" && asString2(report.status) && asString2(report.baselineVersion) && asString2(report.baselineDigest) && reportHashPattern.test(report.baselineDigest) && asString2(report.candidateVersion) && asString2(report.candidateDigest) && reportHashPattern.test(report.candidateDigest) && asString2(report.corpusVersion) && asString2(report.corpusDigest) && reportHashPattern.test(report.corpusDigest) && asString2(report.reportDigest) && reportHashPattern.test(report.reportDigest) && asString2(report.sandboxProfileDigest) && reportHashPattern.test(report.sandboxProfileDigest) && isRecord4(report.train) && typeof report.train.status === "string" && typeof report.train.cases === "number" && isRecord4(report.holdout) && typeof report.holdout.status === "string" && typeof report.holdout.cases === "number" && isRecord4(report.adversarial) && typeof report.adversarial.status === "string" && typeof report.adversarial.cases === "number" && Array.isArray(report.candidateSelectedIds) && Array.isArray(report.reasons);
34555
+ var isEvaluationReport = (report) => report.schemaVersion === "1.0" && asString2(report.status) && asString2(report.baselineVersion) && asString2(report.baselineDigest) && reportHashPattern.test(report.baselineDigest) && asString2(report.candidateVersion) && asString2(report.candidateDigest) && reportHashPattern.test(report.candidateDigest) && asString2(report.corpusVersion) && asString2(report.corpusDigest) && reportHashPattern.test(report.corpusDigest) && asString2(report.reportDigest) && reportHashPattern.test(report.reportDigest) && asString2(report.sandboxProfileDigest) && reportHashPattern.test(report.sandboxProfileDigest) && isRecord6(report.train) && typeof report.train.status === "string" && typeof report.train.cases === "number" && isRecord6(report.holdout) && typeof report.holdout.status === "string" && typeof report.holdout.cases === "number" && isRecord6(report.adversarial) && typeof report.adversarial.status === "string" && typeof report.adversarial.cases === "number" && Array.isArray(report.candidateSelectedIds) && Array.isArray(report.reasons);
34161
34556
  var isConfigRecord = (value) => typeof value.enabled === "boolean" && typeof value.killSwitch === "boolean";
34162
34557
  async function readPinnedJson(input) {
34163
34558
  const ref = await resolveWorkspaceReference({ workspaceRoot: input.workspaceRoot, kind: "artifact", uri: input.uri });
@@ -34188,7 +34583,7 @@ async function resolvePolicySelection(workspaceRoot, canonicalFallback) {
34188
34583
  if (raw === undefined)
34189
34584
  return canonicalFallback;
34190
34585
  const parsed = JSON.parse(raw);
34191
- if (!isRecord4(parsed) || !isConfigRecord(parsed))
34586
+ if (!isRecord6(parsed) || !isConfigRecord(parsed))
34192
34587
  return canonicalFallback;
34193
34588
  configJson = parsed;
34194
34589
  } catch {
@@ -34267,7 +34662,7 @@ async function resolvePolicySelectionSafely(workspaceRoot, canonicalFallback) {
34267
34662
  }
34268
34663
  }
34269
34664
  function isCompleteCorpus(value) {
34270
- return isRecord4(value) && isRecord4(value.manifest) && Array.isArray(value.rows) && Array.isArray(value.quarantine) && verifyPolicyCorpus(value);
34665
+ return isRecord6(value) && isRecord6(value.manifest) && Array.isArray(value.rows) && Array.isArray(value.quarantine) && verifyPolicyCorpus(value);
34271
34666
  }
34272
34667
  var validHash = (value) => reportHashPattern.test(value);
34273
34668
 
@@ -34480,7 +34875,7 @@ function normalizeFwkResult(result) {
34480
34875
  // src/sac/proposal-lifecycle.ts
34481
34876
  init_fs();
34482
34877
  import { createHash as createHash29, randomUUID as randomUUID7 } from "crypto";
34483
- import { appendFile as appendFile7, mkdir as mkdir34, readdir as readdir19, readFile as readFile59 } from "fs/promises";
34878
+ import { appendFile as appendFile7, mkdir as mkdir33, readdir as readdir19, readFile as readFile59 } from "fs/promises";
34484
34879
  import path114 from "path";
34485
34880
 
34486
34881
  // src/sac/trusted-wrap-up.ts
@@ -34556,6 +34951,7 @@ import { mkdir as mkdir32, writeFile as writeFile21 } from "fs/promises";
34556
34951
  import path108 from "path";
34557
34952
 
34558
34953
  // src/session/store.ts
34954
+ init_config_dir();
34559
34955
  import {
34560
34956
  chmodSync as chmodSync2,
34561
34957
  existsSync as existsSync16,
@@ -34566,46 +34962,6 @@ import {
34566
34962
  } from "fs";
34567
34963
  import path107 from "path";
34568
34964
 
34569
- // src/lib/config-dir.ts
34570
- import {
34571
- appendFileSync,
34572
- chmodSync,
34573
- mkdirSync,
34574
- readFileSync as readFileSync5,
34575
- renameSync,
34576
- statSync as statSync2,
34577
- unlinkSync,
34578
- writeFileSync
34579
- } from "fs";
34580
- var MAX_CONFIG_FILE_BYTES = 1e6;
34581
- var MAX_CONTENT_FILE_BYTES = 64 * 1024 * 1024;
34582
- var MAX_TRANSCRIPT_FILE_BYTES = MAX_CONTENT_FILE_BYTES;
34583
- function readBoundedFile(file, maxBytes) {
34584
- let stats;
34585
- try {
34586
- stats = statSync2(file);
34587
- } catch {
34588
- return { ok: false, reason: "absent" };
34589
- }
34590
- if (!stats.isFile()) {
34591
- return { ok: false, reason: "not-regular" };
34592
- }
34593
- if (stats.size > maxBytes) {
34594
- return { ok: false, reason: "too-large" };
34595
- }
34596
- try {
34597
- return { ok: true, text: readFileSync5(file, "utf8") };
34598
- } catch {
34599
- return { ok: false, reason: "unreadable" };
34600
- }
34601
- }
34602
- function readConfigFile(file) {
34603
- return readBoundedFile(file, MAX_CONFIG_FILE_BYTES);
34604
- }
34605
- function readTranscriptFile(file) {
34606
- return readBoundedFile(file, MAX_TRANSCRIPT_FILE_BYTES);
34607
- }
34608
-
34609
34965
  // src/session/paths.ts
34610
34966
  import { existsSync as existsSync15 } from "fs";
34611
34967
  import { homedir } from "os";
@@ -35817,14 +36173,42 @@ function createRealWikiOwnerWriter(cwd, opts) {
35817
36173
  import { readFile as readFile58 } from "fs/promises";
35818
36174
 
35819
36175
  // src/gdskills/project-skills.ts
36176
+ init_contained_write();
35820
36177
  init_fs();
35821
36178
  init_json();
35822
36179
  init_guard();
35823
36180
  import { createHash as createHash28 } from "crypto";
35824
36181
  import { homedir as homedir2 } from "os";
35825
- import { mkdir as mkdir33, readFile as readFile57, stat as stat14 } from "fs/promises";
36182
+ import { readFile as readFile57, stat as stat14 } from "fs/promises";
35826
36183
  import path113 from "path";
35827
36184
  var VERSION = "0.1.0";
36185
+ var PROJECT_SKILLS_DIR = ".metaproject/project-skills";
36186
+ var PROJECT_SKILLS_MANIFEST_PATH = ".metaproject/metaproject.json";
36187
+ var PROJECT_SKILLS_CATALOG_PATH = ".metaproject/skills/catalog.md";
36188
+ var PROJECT_SKILLS_LOCK_PATH = ".metaproject/data/gdskills/project-skills.lock";
36189
+ var PROJECT_SKILLS_CATALOG_START = "<!-- gdskills:project-skills:start -->";
36190
+ var PROJECT_SKILLS_CATALOG_END = "<!-- gdskills:project-skills:end -->";
36191
+ var PROJECT_SKILLS_CATALOG_EMPTY_ROW = "| _none_ | _none_ | _none_ | - |";
36192
+ function projectSkillPackagePath(moduleName, skillName) {
36193
+ return `${PROJECT_SKILLS_DIR}/${moduleName}/${skillName}`;
36194
+ }
36195
+ function projectSkillKey(entry) {
36196
+ return `${entry.module}/${entry.name}`;
36197
+ }
36198
+ function projectSkillCatalogRow(entry) {
36199
+ return `| ${entry.module} | ${entry.name} | \`${entry.target}\` | ${entry.path}/SKILL.md |`;
36200
+ }
36201
+ function projectSkillWritePaths(moduleName, skillName, format) {
36202
+ const packagePath = projectSkillPackagePath(moduleName, skillName);
36203
+ const files = format === "single" ? ["SKILL.md", "skill-changelog.md"] : ["SKILL.md", "skill-changelog.md", "references/context.md", "templates/README.md", "verification.md"];
36204
+ return [...files.map((file) => `${packagePath}/${file}`), PROJECT_SKILLS_MANIFEST_PATH, PROJECT_SKILLS_CATALOG_PATH];
36205
+ }
36206
+ async function assertProjectWritesContained(projectRoot, paths) {
36207
+ await assertWritableContained(projectRoot, PROJECT_SKILLS_LOCK_PATH, "directory");
36208
+ for (const rel of [...new Set(paths)]) {
36209
+ await assertWritableContained(projectRoot, rel);
36210
+ }
36211
+ }
35828
36212
  async function createProjectSkill(projectRoot, options) {
35829
36213
  const metaprojectRoot = path113.join(projectRoot, ".metaproject");
35830
36214
  if (!await pathExists(metaprojectRoot)) {
@@ -35833,11 +36217,12 @@ async function createProjectSkill(projectRoot, options) {
35833
36217
  if (!isRoutableSkillTarget(options.target)) {
35834
36218
  throw new Error(`keryx skills create: target ${JSON.stringify(options.target)} reads as prose, not a routing key. ` + 'A target is what `keryx skills route` matches against \u2014 a concept ("auth flow"), a symbol (`IResultDqReport`), ' + "or a path (`src/dq/DqScoreCard.tsx`). A sentence matches nothing and verifies as permanently stale.");
35835
36219
  }
35836
- const moduleName = slugify5(options.module ?? inferModule(options.target));
35837
- const skillName = slugify5(options.name ?? inferSkillName(options.target));
36220
+ const moduleName = projectSkillSlug(options.module ?? inferModule(options.target));
36221
+ const skillName = projectSkillSlug(options.name ?? inferSkillName(options.target));
35838
36222
  const format = options.format ?? "auto";
35839
- const packageRoot = path113.join(metaprojectRoot, "project-skills", moduleName, skillName);
35840
- const relativeSkillPath = toPosix(path113.relative(projectRoot, packageRoot));
36223
+ const relativeSkillPath = projectSkillPackagePath(moduleName, skillName);
36224
+ const packageRoot = path113.join(projectRoot, relativeSkillPath);
36225
+ await assertProjectWritesContained(projectRoot, projectSkillWritePaths(moduleName, skillName, format));
35841
36226
  const origin = options.origin ? options.originContent !== undefined ? {
35842
36227
  ref: options.origin.trim(),
35843
36228
  hash: hashOriginContent(options.originContent),
@@ -35847,7 +36232,7 @@ async function createProjectSkill(projectRoot, options) {
35847
36232
  const warnings = collectWarnings(evidence, format);
35848
36233
  const files = filesForPackage(packageRoot, format);
35849
36234
  if (!options.dryRun) {
35850
- await withFileLock(path113.join(metaprojectRoot, "data", "gdskills", "project-skills.lock"), async () => {
36235
+ await withFileLock(path113.join(projectRoot, PROJECT_SKILLS_LOCK_PATH), async () => {
35851
36236
  await writeProjectSkillPackage({
35852
36237
  projectRoot,
35853
36238
  packageRoot,
@@ -35931,19 +36316,16 @@ async function writeProjectSkillPackage({
35931
36316
  if (!output.allowed) {
35932
36317
  throw new Error(`Project skill blocked by the security gate: ${output.reason}`);
35933
36318
  }
35934
- await mkdir33(packageRoot, { recursive: true });
35935
- const skillPath = path113.join(packageRoot, "SKILL.md");
35936
- await writeFileAtomic(skillPath, output.content);
35937
- const changelogPath = path113.join(packageRoot, "skill-changelog.md");
35938
- if (!await pathExists(changelogPath)) {
35939
- await writeFileAtomic(changelogPath, renderSkillChangelog({ moduleName, skillName, target }));
36319
+ const packageRel = toPosix(path113.relative(projectRoot, packageRoot));
36320
+ const write = (file, content) => writeContained(projectRoot, `${packageRel}/${file}`, content);
36321
+ await write("SKILL.md", output.content);
36322
+ if (!await pathExists(path113.join(packageRoot, "skill-changelog.md"))) {
36323
+ await write("skill-changelog.md", renderSkillChangelog({ moduleName, skillName, target }));
35940
36324
  }
35941
36325
  if (packageFormat === "package") {
35942
- await mkdir33(path113.join(packageRoot, "references"), { recursive: true });
35943
- await mkdir33(path113.join(packageRoot, "templates"), { recursive: true });
35944
- await writeFileAtomic(path113.join(packageRoot, "references", "context.md"), renderReferenceContext({ moduleName, skillName, target, evidence }));
35945
- await writeFileAtomic(path113.join(packageRoot, "templates", "README.md"), renderTemplatesReadme({ moduleName, skillName }));
35946
- await writeFileAtomic(path113.join(packageRoot, "verification.md"), renderVerification({ moduleName, skillName, evidence }));
36326
+ await write("references/context.md", renderReferenceContext({ moduleName, skillName, target, evidence }));
36327
+ await write("templates/README.md", renderTemplatesReadme({ moduleName, skillName }));
36328
+ await write("verification.md", renderVerification({ moduleName, skillName, evidence }));
35947
36329
  }
35948
36330
  }
35949
36331
  async function collectEvidence(projectRoot, target) {
@@ -36245,7 +36627,7 @@ keryx skills verify ${moduleName}/${skillName}
36245
36627
  `;
36246
36628
  }
36247
36629
  async function updateManifest(projectRoot, entry) {
36248
- const manifestPath = path113.join(projectRoot, ".metaproject", "metaproject.json");
36630
+ const manifestPath = path113.join(projectRoot, PROJECT_SKILLS_MANIFEST_PATH);
36249
36631
  const manifest = await readJsonFileOr(manifestPath, {});
36250
36632
  manifest.modules ??= {};
36251
36633
  manifest.modules.gdskills ??= {};
@@ -36253,29 +36635,29 @@ async function updateManifest(projectRoot, entry) {
36253
36635
  const nextRegistry = [
36254
36636
  ...registry.filter((existing) => existing.path !== entry.path),
36255
36637
  entry
36256
- ].sort((a, b) => `${a.module}/${a.name}`.localeCompare(`${b.module}/${b.name}`));
36638
+ ].sort((a, b) => projectSkillKey(a).localeCompare(projectSkillKey(b)));
36257
36639
  manifest.modules.gdskills.projectSkillRegistry = nextRegistry;
36258
- await writeFileAtomic(manifestPath, `${JSON.stringify(manifest, null, 2)}
36640
+ await writeContained(projectRoot, PROJECT_SKILLS_MANIFEST_PATH, `${JSON.stringify(manifest, null, 2)}
36259
36641
  `);
36260
36642
  }
36261
36643
  async function updateSkillsCatalog(projectRoot) {
36262
- const manifestPath = path113.join(projectRoot, ".metaproject", "metaproject.json");
36263
- const catalogPath = path113.join(projectRoot, ".metaproject", "skills", "catalog.md");
36644
+ const manifestPath = path113.join(projectRoot, PROJECT_SKILLS_MANIFEST_PATH);
36645
+ const catalogPath = path113.join(projectRoot, PROJECT_SKILLS_CATALOG_PATH);
36264
36646
  const manifest = await readJsonFileOr(manifestPath, {});
36265
36647
  const registry = manifest.modules?.gdskills?.projectSkillRegistry ?? [];
36266
- const rows = registry.length > 0 ? registry.map((entry) => `| ${entry.module} | ${entry.name} | \`${entry.target}\` | ${entry.path}/SKILL.md |`).join(`
36267
- `) : "| _none_ | _none_ | _none_ | - |";
36268
- const section = `<!-- gdskills:project-skills:start -->
36648
+ const rows = registry.length > 0 ? registry.map(projectSkillCatalogRow).join(`
36649
+ `) : PROJECT_SKILLS_CATALOG_EMPTY_ROW;
36650
+ const section = `${PROJECT_SKILLS_CATALOG_START}
36269
36651
  ## Project Skills
36270
36652
 
36271
36653
  | Module | Skill | Target | Entry |
36272
36654
  |---|---|---|---|
36273
36655
  ${rows}
36274
- <!-- gdskills:project-skills:end -->`;
36656
+ ${PROJECT_SKILLS_CATALOG_END}`;
36275
36657
  const current = await pathExists(catalogPath) ? await readFile57(catalogPath, "utf8") : `# Metaproject Skills Catalog
36276
36658
  `;
36277
- const start = "<!-- gdskills:project-skills:start -->";
36278
- const end = "<!-- gdskills:project-skills:end -->";
36659
+ const start = PROJECT_SKILLS_CATALOG_START;
36660
+ const end = PROJECT_SKILLS_CATALOG_END;
36279
36661
  const startIndex = current.indexOf(start);
36280
36662
  const endIndex = current.indexOf(end);
36281
36663
  const next = startIndex >= 0 && endIndex > startIndex ? `${current.slice(0, startIndex).trimEnd()}
@@ -36285,7 +36667,7 @@ ${current.slice(endIndex + end.length).trimStart()}` : `${current.trimEnd()}
36285
36667
 
36286
36668
  ${section}
36287
36669
  `;
36288
- await writeFileAtomic(catalogPath, next);
36670
+ await writeContained(projectRoot, PROJECT_SKILLS_CATALOG_PATH, next);
36289
36671
  }
36290
36672
  function inferModule(target) {
36291
36673
  const normalized = toPosix(target).replace(/^\.\//, "");
@@ -36303,9 +36685,11 @@ function inferSkillName(target) {
36303
36685
  const base = path113.basename(normalized).replace(/\.[^.]+$/, "");
36304
36686
  return base || "entity";
36305
36687
  }
36306
- function slugify5(value) {
36307
- const slug = value.trim().toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "");
36308
- return slug || "entity";
36688
+ function projectSkillSlug(value) {
36689
+ return slugOf(value) || "entity";
36690
+ }
36691
+ function slugOf(value) {
36692
+ return value.trim().toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "");
36309
36693
  }
36310
36694
  function titleize(value) {
36311
36695
  return value.split("-").filter(Boolean).map((part) => part.charAt(0).toUpperCase() + part.slice(1)).join(" ");
@@ -36422,7 +36806,7 @@ class ProposalLifecycleService {
36422
36806
  if (manifest.status === "archived")
36423
36807
  throw new ProposalLifecycleError("guard_denied", "workspace is archived");
36424
36808
  const file = this.proposalPath(workspaceId, proposal.id);
36425
- await mkdir34(path114.dirname(file), { recursive: true, mode: 448 });
36809
+ await mkdir33(path114.dirname(file), { recursive: true, mode: 448 });
36426
36810
  return withFileLock(`${file}.lock`, async () => {
36427
36811
  const consume = this.options.wrapUpAuthority.consume(input.wrapUp, { actor, workspaceId });
36428
36812
  if (consume !== "ok")
@@ -36450,7 +36834,7 @@ class ProposalLifecycleService {
36450
36834
  return this.options.workspaces.withAuthorizedActor({ actorContext: actor, workspaceId: input.workspaceId, action: "review", execute: async (manifest) => {
36451
36835
  const proposal = await this.loadProposal(input.workspaceId, input.proposalId);
36452
36836
  const ledger = this.ledgerPath(input.workspaceId);
36453
- await mkdir34(path114.dirname(ledger), { recursive: true, mode: 448 });
36837
+ await mkdir33(path114.dirname(ledger), { recursive: true, mode: 448 });
36454
36838
  return withFileLock(`${ledger}.lock`, async () => {
36455
36839
  const records = (await this.records(ledger)).filter((record) => record.proposalId === proposal.id);
36456
36840
  const events = records.filter((record) => record.recordType === "proposal-transition");
@@ -36902,7 +37286,7 @@ function normalizeProposalLifecycleResult(value) {
36902
37286
  return JSON.parse(JSON.stringify(value));
36903
37287
  }
36904
37288
  // src/sac/collaboration-service.ts
36905
- import { appendFile as appendFile8, mkdir as mkdir35, readFile as readFile60 } from "fs/promises";
37289
+ import { appendFile as appendFile8, mkdir as mkdir34, readFile as readFile60 } from "fs/promises";
36906
37290
  import { randomUUID as randomUUID8 } from "crypto";
36907
37291
  import path115 from "path";
36908
37292
  class CollaborationServiceError extends Error {
@@ -36956,7 +37340,7 @@ class CollaborationService {
36956
37340
  occurredAt: (this.input.now ?? (() => new Date))().toISOString(),
36957
37341
  ...suppliedHandoff ? { handoff: { ...suppliedHandoff, from: actor.subject } } : {}
36958
37342
  });
36959
- await mkdir35(path115.dirname(this.file(manifest.id)), { recursive: true, mode: 448 });
37343
+ await mkdir34(path115.dirname(this.file(manifest.id)), { recursive: true, mode: 448 });
36960
37344
  await appendFile8(this.file(manifest.id), `${JSON.stringify(activity)}
36961
37345
  `, { mode: 384 });
36962
37346
  return normalizeCollaborationResult(activity);
@@ -37005,13 +37389,13 @@ function normalizeCollaborationResult(value) {
37005
37389
  }
37006
37390
  // src/session/external-slate.ts
37007
37391
  init_fs();
37008
- import { mkdir as mkdir37, readdir as readdir20, readFile as readFile61 } from "fs/promises";
37392
+ import { mkdir as mkdir36, readdir as readdir20, readFile as readFile61 } from "fs/promises";
37009
37393
  import path117 from "path";
37010
37394
 
37011
37395
  // src/sac/machine-wrap-up.ts
37012
37396
  init_fs();
37013
37397
  import { createHash as createHash30, randomUUID as randomUUID10 } from "crypto";
37014
- import { mkdir as mkdir36 } from "fs/promises";
37398
+ import { mkdir as mkdir35 } from "fs/promises";
37015
37399
  import path116 from "path";
37016
37400
 
37017
37401
  // src/sac/workspace-resolve.ts
@@ -37212,7 +37596,7 @@ ${safeSeeds.content}`;
37212
37596
  summary = result.text.trim().length > 0 ? result.text.trim() : mechanicalSummary(summaryDiff, course);
37213
37597
  }
37214
37598
  const evidenceDir = path116.join(input.cwd, ".metaproject", "workspaces", input.workspaceId, "machine-evidence");
37215
- await mkdir36(evidenceDir, { recursive: true });
37599
+ await mkdir35(evidenceDir, { recursive: true });
37216
37600
  const diffFile = `${input.kind}.${shortHash}.diff.txt`;
37217
37601
  const flowFile = `${input.kind}.${shortHash}.flow.json`;
37218
37602
  const seedsFile = `${input.kind}.${shortHash}.seeds.json`;
@@ -37255,7 +37639,7 @@ async function writeFlooredArchiveArtifact(params) {
37255
37639
  return { ok: false, code: "security_denied", detail: floor.reason };
37256
37640
  const body = floor.bodies[0];
37257
37641
  const archiveDir = path116.join(params.dir, "slate-archive");
37258
- await mkdir36(archiveDir, { recursive: true });
37642
+ await mkdir35(archiveDir, { recursive: true });
37259
37643
  await writeFileAtomic(path116.join(archiveDir, params.filename), body.content);
37260
37644
  if (floor.notice !== undefined) {
37261
37645
  await writeFileAtomic(path116.join(archiveDir, `${params.filename}${ARCHIVE_REDACTION_NOTICE_SUFFIX}`), floor.notice);
@@ -37295,7 +37679,7 @@ async function writeWrapUpOutcomeArtifact(cwd, dir, trigger, now, groups) {
37295
37679
  const refusalIso = now().toISOString();
37296
37680
  const refusalFilename = `${refusalIso.replace(/[:.]/g, "-")}-wrap-up-outcome.json`;
37297
37681
  const archiveDir = path116.join(dir, "slate-archive");
37298
- await mkdir36(archiveDir, { recursive: true });
37682
+ await mkdir35(archiveDir, { recursive: true });
37299
37683
  await writeFileAtomic(path116.join(archiveDir, refusalFilename), `${JSON.stringify({
37300
37684
  recordType: "wrap-up-outcome",
37301
37685
  trigger,
@@ -37462,7 +37846,7 @@ async function readExternalSlate(cwd, externalSessionId) {
37462
37846
  }
37463
37847
  }
37464
37848
  async function writeExternalSlate(cwd, externalSessionId, update) {
37465
- await mkdir37(externalSlatesDir(cwd), { recursive: true });
37849
+ await mkdir36(externalSlatesDir(cwd), { recursive: true });
37466
37850
  return withFileLock(externalSlateLockPath(cwd, externalSessionId), async () => {
37467
37851
  const prev = await readExternalSlate(cwd, externalSessionId);
37468
37852
  const next = update(prev);
@@ -37488,7 +37872,7 @@ function isExternalSlateStale(slate, now = () => new Date) {
37488
37872
  return now().getTime() - lastWriteMs > DEFAULT_LOCK_STALE_MS;
37489
37873
  }
37490
37874
  async function closeExternalSlate(cwd, externalSessionId, trigger, now = () => new Date) {
37491
- await mkdir37(externalSlatesDir(cwd), { recursive: true });
37875
+ await mkdir36(externalSlatesDir(cwd), { recursive: true });
37492
37876
  return withFileLock(externalSlateLockPath(cwd, externalSessionId), async () => {
37493
37877
  const external = await readExternalSlate(cwd, externalSessionId);
37494
37878
  if (!external || external.closedAt !== undefined)
@@ -37689,18 +38073,32 @@ var SCHEMA_REGISTRY2 = {
37689
38073
 
37690
38074
  // src/standard/profiles.ts
37691
38075
  init_fs();
38076
+ init_entrypoint_targets();
37692
38077
  import path118 from "path";
37693
38078
  import { readFile as readFile62 } from "fs/promises";
37694
38079
  var AGENT_MODULES = ["gdgraph", "gdctx", "gdskills", "gdwiki", "memory"];
37695
38080
  var CI_MODULES = ["health", "testing"];
37696
- var ROOT_ENTRYPOINTS = ["AGENTS.md", "agents.md", "CLAUDE.md", "claude.md"];
37697
38081
  var INDEX_LINK = ".metaproject/index.md";
38082
+ function namedEntrypoints(manifest) {
38083
+ const agentEntrypoints = manifest.agentEntrypoints;
38084
+ const named = [];
38085
+ const seen = new Set;
38086
+ for (const entry of normalizeEntrypointTargets(agentEntrypoints).targets.root) {
38087
+ if (entry.runtime === "codex" && entry.scope === "local" && entry.mode === "skip")
38088
+ continue;
38089
+ if (seen.has(entry.path.toLowerCase()))
38090
+ continue;
38091
+ seen.add(entry.path.toLowerCase());
38092
+ named.push({ path: entry.path, scope: entry.scope });
38093
+ }
38094
+ return named;
38095
+ }
37698
38096
  function enabledModuleKeys(manifest) {
37699
38097
  const modules = manifest.modules ?? {};
37700
38098
  return Object.entries(modules).filter(([, entry]) => entry?.enabled === true).map(([key]) => key);
37701
38099
  }
37702
- async function rootEntrypointLinksIndex(cwd) {
37703
- for (const name of ROOT_ENTRYPOINTS) {
38100
+ async function rootEntrypointLinksIndex(cwd, manifest) {
38101
+ for (const { path: name } of namedEntrypoints(manifest)) {
37704
38102
  const filePath = path118.join(cwd, name);
37705
38103
  if (!await pathExists(filePath)) {
37706
38104
  continue;
@@ -37748,7 +38146,7 @@ async function evaluateProfiles(cwd, manifest) {
37748
38146
  satisfied.push("minimal");
37749
38147
  }
37750
38148
  const hasAgentModule = AGENT_MODULES.some((key) => new Set(enabledModuleKeys(manifest)).has(key));
37751
- const agent = minimal && hasAgentModule && await rootEntrypointLinksIndex(cwd) && await pathExists(path118.join(cwd, ".metaproject", "rules"));
38149
+ const agent = minimal && hasAgentModule && await rootEntrypointLinksIndex(cwd, manifest) && await pathExists(path118.join(cwd, ".metaproject", "rules"));
37752
38150
  if (agent) {
37753
38151
  satisfied.push("agent");
37754
38152
  }
@@ -37895,7 +38293,6 @@ function validateBranch(value, schema, rootSchema) {
37895
38293
  walk4(value, schema, "$", rootSchema, errors);
37896
38294
  return errors;
37897
38295
  }
37898
- var ROOT_ENTRYPOINTS2 = ["AGENTS.md", "agents.md", "CLAUDE.md", "claude.md"];
37899
38296
  var INDEX_LINK2 = ".metaproject/index.md";
37900
38297
  var MODULE_PATH_FIELDS = [
37901
38298
  "core",
@@ -37988,14 +38385,14 @@ async function validateWorkspace2(cwd) {
37988
38385
  }
37989
38386
  }
37990
38387
  }
37991
- for (const name of ROOT_ENTRYPOINTS2) {
38388
+ for (const { path: name, scope } of namedEntrypoints(manifest)) {
37992
38389
  const filePath = path119.join(cwd, name);
37993
38390
  if (!await pathExists(filePath)) {
37994
38391
  continue;
37995
38392
  }
37996
38393
  const content = await readFile63(filePath, "utf8");
37997
38394
  if (!content.includes(INDEX_LINK2)) {
37998
- errors.push(issue("entrypoint-missing-index-link", `Root entrypoint ${name} does not link ${INDEX_LINK2}`, `Add a reference to ${INDEX_LINK2} in ${name} (\`keryx rules sync\` does this).`));
38395
+ errors.push(issue("entrypoint-missing-index-link", `Root entrypoint ${name} does not link ${INDEX_LINK2}`, scope === "local" ? `Run \`keryx update\` to put the managed block back into ${name}.` : `Add a reference to ${INDEX_LINK2} in ${name} (\`keryx rules sync\` does this).`));
37999
38396
  }
38000
38397
  }
38001
38398
  const profileEval = await evaluateProfiles(cwd, manifest);
@@ -38148,6 +38545,12 @@ var HELP_GROUPS = [
38148
38545
  group: "Look and feel",
38149
38546
  summary: "Open the theme picker \u2014 /theme [name] applies immediately."
38150
38547
  },
38548
+ {
38549
+ kind: "slash",
38550
+ name: "/settings",
38551
+ group: "Look and feel",
38552
+ summary: "Every setting in one place, each with its On/Off or value buttons."
38553
+ },
38151
38554
  { kind: "slash", name: "/model", group: "Look and feel", summary: "Switch the model." },
38152
38555
  {
38153
38556
  kind: "slash",
@@ -38505,6 +38908,12 @@ var HELP_GROUPS = [
38505
38908
  group: "External agents, ACP and MCP",
38506
38909
  summary: "Hand a task to an external agent CLI \u2014 /delegate <agent> <task>."
38507
38910
  },
38911
+ {
38912
+ kind: "slash",
38913
+ name: "/external-agents",
38914
+ group: "External agents, ACP and MCP",
38915
+ summary: "Turn the external agent runtime on or off \u2014 /external-agents [on|off]; not the /external privacy switch."
38916
+ },
38508
38917
  {
38509
38918
  kind: "slash",
38510
38919
  name: "/demote",