@mrciphersmith/keryx 0.2.38 → 0.2.39

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (2) hide show
  1. package/dist/cli.js +1511 -351
  2. package/package.json +1 -1
package/dist/cli.js CHANGED
@@ -191,7 +191,9 @@ __export(exports_fs, {
191
191
  toPosix: () => toPosix,
192
192
  pathExists: () => pathExists,
193
193
  isPathInside: () => isPathInside,
194
- isNotFound: () => isNotFound
194
+ isNotFound: () => isNotFound,
195
+ isLockHeld: () => isLockHeld,
196
+ DEFAULT_LOCK_STALE_MS: () => DEFAULT_LOCK_STALE_MS
195
197
  });
196
198
  import { randomUUID as randomUUID4 } from "crypto";
197
199
  import { access, mkdir, readFile, rename, rm, stat, utimes, writeFile } from "fs/promises";
@@ -229,7 +231,7 @@ async function writeFileAtomic(filePath, content) {
229
231
  async function withFileLock2(lockPath, fn, options = {}) {
230
232
  const timeoutMs = options.timeoutMs ?? 5000;
231
233
  const retryMs = options.retryMs ?? 25;
232
- const staleMs = options.staleMs ?? 30000;
234
+ const staleMs = options.staleMs ?? DEFAULT_LOCK_STALE_MS;
233
235
  const heartbeatMs = options.heartbeatMs ?? Math.max(100, Math.floor(staleMs / 3));
234
236
  const startedAt = Date.now();
235
237
  const owner = { pid: process.pid, token: randomUUID4() };
@@ -270,6 +272,17 @@ async function withFileLock2(lockPath, fn, options = {}) {
270
272
  }
271
273
  }
272
274
  }
275
+ async function isLockHeld(lockPath, staleMs = DEFAULT_LOCK_STALE_MS) {
276
+ try {
277
+ const stats = await stat(lockPath);
278
+ if (Date.now() - stats.mtimeMs <= staleMs)
279
+ return true;
280
+ const owner = await readLockOwner(path5.join(lockPath, "owner.json"));
281
+ return owner !== undefined && processIsAlive(owner.pid);
282
+ } catch {
283
+ return false;
284
+ }
285
+ }
273
286
  function isAlreadyExistsError(error) {
274
287
  return typeof error === "object" && error !== null && "code" in error && error.code === "EEXIST";
275
288
  }
@@ -308,6 +321,7 @@ function processIsAlive(pid) {
308
321
  function delay(ms) {
309
322
  return new Promise((resolve) => setTimeout(resolve, ms));
310
323
  }
324
+ var DEFAULT_LOCK_STALE_MS = 30000;
311
325
  var init_fs = () => {};
312
326
 
313
327
  // src/lib/git-hooks.ts
@@ -14016,6 +14030,9 @@ function projectSessionsDir(projectPath, dataDir) {
14016
14030
  function sessionDir(projectPath, sessionId, dataDir) {
14017
14031
  return path8.join(projectSessionsDir(projectPath, dataDir), sessionId);
14018
14032
  }
14033
+ function resolveOneShotWrapUpSessionDir(cwd, mintSessionId) {
14034
+ return sessionDir(cwd, mintSessionId());
14035
+ }
14019
14036
 
14020
14037
  // src/harness/process/sandbox/network-run.ts
14021
14038
  import { Worker } from "worker_threads";
@@ -35531,6 +35548,9 @@ function createSacAuthorizationServer(input2) {
35531
35548
  return actor;
35532
35549
  } });
35533
35550
  }
35551
+ function isTrustedActorContext(actorContext) {
35552
+ return isRecord2(actorContext) && trustedActors.has(actorContext);
35553
+ }
35534
35554
  var roleRank = { viewer: 1, editor: 2, owner: 3 };
35535
35555
  function authorizationResult(allowed, code, baseline, required2, workspaceId) {
35536
35556
  return { allowed, code, authorizeAtUse: async (resolve2) => {
@@ -35697,6 +35717,15 @@ class WorkspaceService {
35697
35717
  async list(input2) {
35698
35718
  const actor = await this.requireActor(input2.request, input2.requestCorrelationId);
35699
35719
  await this.requireStrict("read");
35720
+ return this.enumerateVisible(actor, input2.includeArchived);
35721
+ }
35722
+ async listForActor(input2) {
35723
+ await this.requireStrict("read");
35724
+ if (!isTrustedActorContext(input2.actorContext))
35725
+ throw new WorkspaceServiceError("access_denied", "untrusted actor");
35726
+ return this.enumerateVisible(input2.actorContext, input2.includeArchived);
35727
+ }
35728
+ async enumerateVisible(actor, includeArchived) {
35700
35729
  try {
35701
35730
  await mkdir46(this.storageRoot, { recursive: true, mode: 448 });
35702
35731
  } catch {
@@ -35710,7 +35739,7 @@ class WorkspaceService {
35710
35739
  try {
35711
35740
  const manifest = await this.readManifest(entry.name);
35712
35741
  const role = currentRole(manifest, actor.subject);
35713
- if (role && (input2.includeArchived === true || manifest.status !== "archived"))
35742
+ if (role && (includeArchived === true || manifest.status !== "archived"))
35714
35743
  visible.push(manifest);
35715
35744
  } catch {}
35716
35745
  }
@@ -35951,6 +35980,25 @@ function localWorkspaceAuthorizationServer(subject = `user:local-${process.getui
35951
35980
  function newWorkspaceId() {
35952
35981
  return `workspace-${randomUUID7().replace(/-/g, "").slice(0, 16)}`;
35953
35982
  }
35983
+ async function resolveWorkspaceForActor(cwd, workspaceId) {
35984
+ const service4 = new WorkspaceService({
35985
+ workspaceRoot: cwd,
35986
+ authorizationServer: localWorkspaceAuthorizationServer(),
35987
+ strictGuard: { mode: "strict", availability: "available", decision: "pass", policyRevision: "local-offline-v1" }
35988
+ });
35989
+ try {
35990
+ const manifest = await service4.show({ request: undefined, requestCorrelationId: randomUUID7(), workspaceId });
35991
+ return { ok: true, manifest };
35992
+ } catch (error2) {
35993
+ if (error2 instanceof WorkspaceServiceError) {
35994
+ return { ok: false, error: error2 };
35995
+ }
35996
+ return {
35997
+ ok: false,
35998
+ error: new WorkspaceServiceError("not_found", error2 instanceof Error ? error2.message : "workspace could not be resolved")
35999
+ };
36000
+ }
36001
+ }
35954
36002
 
35955
36003
  // src/sac/fwk-service.ts
35956
36004
  init_store2();
@@ -36706,7 +36754,7 @@ function normalizeFwkResult(result) {
36706
36754
  // src/sac/proposal-lifecycle.ts
36707
36755
  init_fs();
36708
36756
  import { createHash as createHash18, randomUUID as randomUUID11 } from "crypto";
36709
- import { appendFile as appendFile5, mkdir as mkdir50, readFile as readFile69 } from "fs/promises";
36757
+ import { appendFile as appendFile5, mkdir as mkdir50, readdir as readdir20, readFile as readFile69 } from "fs/promises";
36710
36758
  import path124 from "path";
36711
36759
 
36712
36760
  // src/sac/trusted-wrap-up.ts
@@ -36945,7 +36993,9 @@ function readSummaryFile(file) {
36945
36993
  compactCount: typeof o.compactCount === "number" ? o.compactCount : 0,
36946
36994
  ...typeof o.provider === "string" ? { provider: o.provider } : {},
36947
36995
  ...typeof o.model === "string" ? { model: o.model } : {},
36948
- ...typeof o.parentSessionId === "string" ? { parentSessionId: o.parentSessionId } : {}
36996
+ ...typeof o.parentSessionId === "string" ? { parentSessionId: o.parentSessionId } : {},
36997
+ ...o.runMode === "interactive" || o.runMode === "unattended" ? { runMode: o.runMode } : {},
36998
+ ...o.courseStatus === "unbound" || o.courseStatus === "active" || o.courseStatus === "blocked" || o.courseStatus === "done" ? { courseStatus: o.courseStatus } : {}
36949
36999
  };
36950
37000
  } catch {
36951
37001
  return;
@@ -37703,6 +37753,51 @@ class ProposalLifecycleService {
37703
37753
  });
37704
37754
  } });
37705
37755
  }
37756
+ async listProposedProposals(workspaceId) {
37757
+ const proposalsDir = path124.join(this.root, ".metaproject", "workspaces", workspaceId, "proposals");
37758
+ let entries;
37759
+ try {
37760
+ entries = (await readdir20(proposalsDir)).filter((name) => name.endsWith(".json"));
37761
+ } catch (error2) {
37762
+ if (isNotFound(error2))
37763
+ return [];
37764
+ throw error2;
37765
+ }
37766
+ const proposals = [];
37767
+ for (const entry of entries) {
37768
+ try {
37769
+ const parsed = JSON.parse(await readFile69(path124.join(proposalsDir, entry), "utf8"));
37770
+ if (parsed.recordType === "proposal-created")
37771
+ proposals.push(parsed);
37772
+ } catch {}
37773
+ }
37774
+ const terminalIds = new Set((await this.records(this.ledgerPath(workspaceId))).filter((record) => record.recordType === "proposal-transition").map((record) => record.proposalId));
37775
+ return proposals.filter((proposal) => !terminalIds.has(proposal.id));
37776
+ }
37777
+ async listVisibleProposedProposals(actor) {
37778
+ const workspaces = await this.options.workspaces.listForActor({ actorContext: actor, includeArchived: true });
37779
+ const groups = [];
37780
+ for (const workspace of workspaces) {
37781
+ const proposals = await this.listProposedProposals(workspace.id);
37782
+ if (proposals.length > 0)
37783
+ groups.push({ workspace, proposals });
37784
+ }
37785
+ return groups;
37786
+ }
37787
+ async isEvidenceFresh(proposal, _actor) {
37788
+ const readEvidenceFile = this.options.readEvidenceFile ?? readWorkspaceFileNoFollow;
37789
+ for (const item of proposal.evidence) {
37790
+ try {
37791
+ const resolved = await resolveWorkspaceReference({ workspaceRoot: this.root, kind: item.kind, uri: item.uri });
37792
+ const content = readEvidenceFile(this.root, resolved).toString("utf8");
37793
+ if (hash(content) !== item.revision)
37794
+ return false;
37795
+ } catch {
37796
+ return false;
37797
+ }
37798
+ }
37799
+ return true;
37800
+ }
37706
37801
  async targetWriteOrStale(proposal, actor, reviewerAuthority, input2, approvalRef, writeIntent, policyRevision) {
37707
37802
  await this.options.beforeTargetWrite?.();
37708
37803
  try {
@@ -37940,14 +38035,23 @@ function recordHash(value) {
37940
38035
  function isPlatformUnavailableSecureReadError(error2) {
37941
38036
  return error2 instanceof Error && error2.message === "safe descriptor source reads are unavailable on this platform";
37942
38037
  }
37943
- function createHarnessProposalLifecycleService(cwd, opts) {
38038
+ function createLocalProposalLifecycleService(cwd) {
37944
38039
  const authorizationServer = localWorkspaceAuthorizationServer();
37945
38040
  const workspaces = new WorkspaceService({ workspaceRoot: cwd, authorizationServer, strictGuard: { mode: "strict", availability: "available", decision: "pass", policyRevision: "local-offline-v1" } });
38041
+ return new ProposalLifecycleService({ workspaceRoot: cwd, workspaces, authorizationServer, guard: { mode: "strict", availability: "available", decision: "pass", policyRevision: "local-offline-v1" }, policyRef: "./security/policy/local", policyRevision: "local-offline-v1", targetWriters: createLocalOwnerWriterAdapters(), wrapUpAuthority: createTrustedWrapUpAuthority({ now: () => new Date(0), resolveExplicitWrapUp: async () => {
38042
+ throw new Error("trusted wrap-up boundary unavailable");
38043
+ } }) });
38044
+ }
38045
+ function createHarnessProposalLifecycleService(cwd, opts) {
38046
+ const authorizationServer = localWorkspaceAuthorizationServer();
38047
+ const nowOpt = opts.now !== undefined ? { now: opts.now } : {};
38048
+ const workspaces = new WorkspaceService({ workspaceRoot: cwd, authorizationServer, strictGuard: { mode: "strict", availability: "available", decision: "pass", policyRevision: "local-offline-v1" }, ...nowOpt });
37946
38049
  const wrapUpAuthority = createTrustedWrapUpAuthority({
38050
+ ...nowOpt,
37947
38051
  resolveExplicitWrapUp: async (request) => {
37948
38052
  if (request.source !== "session")
37949
38053
  throw new Error(`this composition only resolves "session" wrap-ups, got "${request.source}"`);
37950
- return resolveSessionWrapUp({ cwd, workspaceId: opts.workspaceId, sourceRef: request.sourceRef });
38054
+ return resolveSessionWrapUp({ cwd, workspaceId: opts.workspaceId, sourceRef: request.sourceRef, ...nowOpt });
37951
38055
  }
37952
38056
  });
37953
38057
  const noteOpt = opts.note !== undefined ? { note: opts.note } : {};
@@ -37957,7 +38061,7 @@ function createHarnessProposalLifecycleService(cwd, opts) {
37957
38061
  wiki: createWikiGuardedTargetWriter(createRealWikiOwnerWriter(cwd, noteOpt)),
37958
38062
  skill: createSkillGuardedTargetWriter(createRealSkillOwnerWriter(cwd, noteOpt))
37959
38063
  };
37960
- const service4 = new ProposalLifecycleService({ workspaceRoot: cwd, workspaces, authorizationServer, guard: { mode: "strict", availability: "available", decision: "pass", policyRevision: "local-offline-v1" }, policyRef: "./security/policy/local", policyRevision: "local-offline-v1", targetWriters, wrapUpAuthority });
38064
+ const service4 = new ProposalLifecycleService({ workspaceRoot: cwd, workspaces, authorizationServer, guard: { mode: "strict", availability: "available", decision: "pass", policyRevision: "local-offline-v1" }, policyRef: "./security/policy/local", policyRevision: "local-offline-v1", targetWriters, wrapUpAuthority, ...nowOpt });
37961
38065
  return { service: service4, wrapUpAuthority, authorizationServer };
37962
38066
  }
37963
38067
  function createWikiGuardedTargetWriter(input2) {
@@ -38361,7 +38465,7 @@ function buildToolRegistry() {
38361
38465
  // src/mcp/resources.ts
38362
38466
  init_fs();
38363
38467
  import path126 from "path";
38364
- import { readdir as readdir20, readFile as readFile72, stat as stat7 } from "fs/promises";
38468
+ import { readdir as readdir21, readFile as readFile72, stat as stat7 } from "fs/promises";
38365
38469
  var URI_PREFIX = "metaproject://";
38366
38470
  function mimeForPath(filePath) {
38367
38471
  if (filePath.endsWith(".json") || filePath.endsWith(".jsonl")) {
@@ -38388,7 +38492,7 @@ async function walkFiles(root) {
38388
38492
  const out = [];
38389
38493
  let entries;
38390
38494
  try {
38391
- entries = await readdir20(root, { withFileTypes: true });
38495
+ entries = await readdir21(root, { withFileTypes: true });
38392
38496
  } catch {
38393
38497
  return [];
38394
38498
  }
@@ -38410,7 +38514,7 @@ async function listArtifacts(cwd) {
38410
38514
  const listings = [];
38411
38515
  let modules;
38412
38516
  try {
38413
- modules = await readdir20(base, { withFileTypes: true });
38517
+ modules = await readdir21(base, { withFileTypes: true });
38414
38518
  } catch {
38415
38519
  return [];
38416
38520
  }
@@ -38840,8 +38944,8 @@ Reports the workspace root and one enabled/disabled line per module. Use
38840
38944
 
38841
38945
  // src/commands/harness.ts
38842
38946
  import { readFileSync as readFileSync8, writeFileSync as writeFileSync7 } from "fs";
38843
- import path132 from "path";
38844
- import { randomUUID as randomUUID14 } from "crypto";
38947
+ import path134 from "path";
38948
+ import { randomUUID as randomUUID15 } from "crypto";
38845
38949
 
38846
38950
  // src/security/harness-scan.ts
38847
38951
  init_guard();
@@ -40782,6 +40886,427 @@ function resolveAllowedDomains(env, projectRoot) {
40782
40886
 
40783
40887
  // src/commands/harness.ts
40784
40888
  init_providers();
40889
+
40890
+ // src/sac/machine-wrap-up.ts
40891
+ init_fs();
40892
+ import { createHash as createHash27, randomUUID as randomUUID14 } from "crypto";
40893
+ import { execFile } from "child_process";
40894
+ import { mkdir as mkdir53 } from "fs/promises";
40895
+ import path133 from "path";
40896
+ import { promisify } from "util";
40897
+
40898
+ // src/session/slate.ts
40899
+ init_fs();
40900
+ import { mkdir as mkdir52, readFile as readFile73, rm as rm7 } from "fs/promises";
40901
+ import path132 from "path";
40902
+ init_repomap();
40903
+ init_redact();
40904
+ function slatePath(dir) {
40905
+ return path132.join(dir, "slate.json");
40906
+ }
40907
+ function slateLockPath(dir) {
40908
+ return `${slatePath(dir)}.lock`;
40909
+ }
40910
+ async function readSlate(dir) {
40911
+ try {
40912
+ const raw = await readFile73(slatePath(dir), "utf8");
40913
+ return JSON.parse(raw);
40914
+ } catch (error2) {
40915
+ if (isNotFound(error2))
40916
+ return;
40917
+ throw error2;
40918
+ }
40919
+ }
40920
+ async function writeSlate(dir, update) {
40921
+ await mkdir52(dir, { recursive: true });
40922
+ return withFileLock2(slateLockPath(dir), async () => {
40923
+ const prev = await readSlate(dir);
40924
+ const next = update(prev);
40925
+ await writeFileAtomic(slatePath(dir), `${JSON.stringify(next, null, 2)}
40926
+ `);
40927
+ return next;
40928
+ });
40929
+ }
40930
+ async function archiveSlate(dir, attemptId) {
40931
+ if (!/^[A-Za-z0-9._-]+$/.test(attemptId))
40932
+ throw new Error(`invalid attemptId: ${JSON.stringify(attemptId)}`);
40933
+ await mkdir52(dir, { recursive: true });
40934
+ return withFileLock2(slateLockPath(dir), () => archiveIfExistsLocked(dir, attemptId));
40935
+ }
40936
+ async function archiveIfExistsLocked(dir, attemptId) {
40937
+ let raw;
40938
+ try {
40939
+ raw = await readFile73(slatePath(dir), "utf8");
40940
+ } catch (error2) {
40941
+ if (isNotFound(error2))
40942
+ return;
40943
+ throw error2;
40944
+ }
40945
+ const archiveDir = path132.join(dir, "slate-archive");
40946
+ await mkdir52(archiveDir, { recursive: true });
40947
+ await writeFileAtomic(path132.join(archiveDir, `${attemptId}.json`), raw);
40948
+ await rm7(slatePath(dir), { force: true });
40949
+ }
40950
+ async function openSlateAtomic(dir, mintAttemptId, build) {
40951
+ await mkdir52(dir, { recursive: true });
40952
+ return withFileLock2(slateLockPath(dir), async () => {
40953
+ const existing = await readSlate(dir);
40954
+ if (existing !== undefined) {
40955
+ await archiveIfExistsLocked(dir, mintAttemptId());
40956
+ }
40957
+ const next = build();
40958
+ await writeFileAtomic(slatePath(dir), `${JSON.stringify(next, null, 2)}
40959
+ `);
40960
+ await rm7(path132.join(dir, "terminal-state.json"), { force: true });
40961
+ return next;
40962
+ });
40963
+ }
40964
+ async function appendSeed(dir, seed) {
40965
+ return writeSlate(dir, (prev) => {
40966
+ if (!prev)
40967
+ throw new Error(`appendSeed: no open slate in ${dir}`);
40968
+ return { ...prev, seeds: [...prev.seeds, seed] };
40969
+ });
40970
+ }
40971
+ function dedupeSeeds(seeds) {
40972
+ const seen = new Set;
40973
+ const result = [];
40974
+ for (const seed of seeds) {
40975
+ const key = seed.text.trim();
40976
+ if (seen.has(key))
40977
+ continue;
40978
+ seen.add(key);
40979
+ result.push(seed);
40980
+ }
40981
+ return result;
40982
+ }
40983
+ var DEFAULT_RENDER_MAX_TOKENS = 2000;
40984
+ function redactAndBoundTouched(touched, budget, ctx) {
40985
+ const recentFirstRedacted = [...touched].reverse().map((entry) => redactSensitiveText(entry));
40986
+ const candidates = recentFirstRedacted.map((text, i) => ({
40987
+ id: `touched:${i}`,
40988
+ required: false,
40989
+ tokens: estimateTokens(text)
40990
+ }));
40991
+ const assembly = assembleContext({
40992
+ candidates,
40993
+ maxItems: candidates.length,
40994
+ maxTokens: Math.max(0, budget),
40995
+ ...ctx
40996
+ });
40997
+ const selectedIds = new Set("code" in assembly ? [] : assembly.selected);
40998
+ return recentFirstRedacted.map((text, i) => ({ id: `touched:${i}`, text })).filter((entry) => selectedIds.has(entry.id)).reverse().map((entry) => entry.text);
40999
+ }
41000
+ function renderAnchorsBlock(anchors, opts) {
41001
+ const maxTokens = opts?.maxTokens ?? DEFAULT_RENDER_MAX_TOKENS;
41002
+ const rootLine = `root: ${anchors.root}`;
41003
+ const treeLine = anchors.tree !== undefined ? `tree: ${anchors.tree}` : undefined;
41004
+ const runtimeLine = anchors.runtime !== undefined ? `runtime: ${anchors.runtime.provider}/${anchors.runtime.model}` : undefined;
41005
+ const headEntries = [{ id: "root", text: rootLine, required: true }];
41006
+ if (treeLine !== undefined)
41007
+ headEntries.push({ id: "tree", text: treeLine, required: false });
41008
+ if (runtimeLine !== undefined)
41009
+ headEntries.push({ id: "runtime", text: runtimeLine, required: false });
41010
+ const headCandidates = headEntries.map((entry) => ({
41011
+ id: entry.id,
41012
+ required: entry.required,
41013
+ tokens: estimateTokens(entry.text)
41014
+ }));
41015
+ const headAssembly = assembleContext({
41016
+ candidates: headCandidates,
41017
+ maxItems: headCandidates.length,
41018
+ maxTokens,
41019
+ traceRef: "slate-anchors",
41020
+ configurationRevision: "slate-anchors-v1",
41021
+ policyRef: "slate-anchors",
41022
+ policyRevision: "v1"
41023
+ });
41024
+ const headSelectedIds = "code" in headAssembly ? new Set(["root"]) : new Set(headAssembly.selected);
41025
+ const headTokensUsed = "code" in headAssembly ? 0 : headCandidates.filter((c) => headSelectedIds.has(c.id)).reduce((sum, c) => sum + c.tokens, 0);
41026
+ const touchedBudget = "code" in headAssembly ? 0 : Math.max(0, maxTokens - headTokensUsed);
41027
+ const touchedLines = redactAndBoundTouched(anchors.touched, touchedBudget, {
41028
+ traceRef: "slate-anchors-touched",
41029
+ configurationRevision: "slate-anchors-v1",
41030
+ policyRef: "slate-anchors",
41031
+ policyRevision: "v1"
41032
+ }).map((text) => `- ${text}`);
41033
+ const lines = ["Anchors:", rootLine];
41034
+ if (treeLine !== undefined && headSelectedIds.has("tree"))
41035
+ lines.push(treeLine);
41036
+ if (runtimeLine !== undefined && headSelectedIds.has("runtime"))
41037
+ lines.push(runtimeLine);
41038
+ if (touchedLines.length > 0) {
41039
+ lines.push("touched:", ...touchedLines);
41040
+ }
41041
+ return lines.join(`
41042
+ `);
41043
+ }
41044
+
41045
+ // src/session/slate-course.ts
41046
+ init_store2();
41047
+ var unbound = { state: "unbound" };
41048
+ async function readCourse(cwd, flowRef) {
41049
+ if (!flowRef)
41050
+ return unbound;
41051
+ try {
41052
+ const dir = await resolveFlowDir(cwd, flowRef);
41053
+ const flow = await readFlow(cwd, dir);
41054
+ return { state: "bound", ...deriveFlowWork(flow, flowRef) };
41055
+ } catch {
41056
+ return unbound;
41057
+ }
41058
+ }
41059
+ async function courseFromSlate(cwd, slate) {
41060
+ return readCourse(cwd, slate?.course.flowRef);
41061
+ }
41062
+
41063
+ // src/sac/machine-wrap-up.ts
41064
+ init_single_turn();
41065
+ var execFileAsync = promisify(execFile);
41066
+ var WRAP_UP_TTL_MS2 = 60 * 60 * 1000;
41067
+ var DEFAULT_MODEL_TURN_TIMEOUT_MS = 30000;
41068
+ function describeSource(source) {
41069
+ return source === "parent" ? "parent" : `child:${source.childDispatchId}`;
41070
+ }
41071
+ function dedupedAttributedSeeds(slate) {
41072
+ const seen = new Set;
41073
+ const result = [];
41074
+ const take = (seeds, source) => {
41075
+ for (const seed of dedupeSeeds(seeds)) {
41076
+ const key = seed.text.trim();
41077
+ if (seen.has(key))
41078
+ continue;
41079
+ seen.add(key);
41080
+ result.push({ text: seed.text, kind: seed.kind ?? "follow-up", source });
41081
+ }
41082
+ };
41083
+ take(slate.seeds, "parent");
41084
+ const childDispatches = slate.childDispatches ?? {};
41085
+ for (const [dispatchId, dispatch] of Object.entries(childDispatches)) {
41086
+ take(dispatch.seeds, { childDispatchId: dispatchId });
41087
+ }
41088
+ return result;
41089
+ }
41090
+ function groupSeedsByKind(slate) {
41091
+ const map = new Map;
41092
+ for (const seed of dedupedAttributedSeeds(slate)) {
41093
+ const bucket = map.get(seed.kind);
41094
+ if (bucket)
41095
+ bucket.push(seed);
41096
+ else
41097
+ map.set(seed.kind, [seed]);
41098
+ }
41099
+ return map;
41100
+ }
41101
+ function sha2568(value) {
41102
+ return createHash27("sha256").update(value).digest("hex");
41103
+ }
41104
+ async function gitDiff(cwd) {
41105
+ try {
41106
+ const { stdout: stdout2 } = await execFileAsync("git", ["diff"], { cwd, maxBuffer: 16 * 1024 * 1024 });
41107
+ return stdout2;
41108
+ } catch {
41109
+ return "";
41110
+ }
41111
+ }
41112
+ function diffStatLine(diffText) {
41113
+ if (diffText.trim().length === 0)
41114
+ return "no working-tree changes";
41115
+ const added = (diffText.match(/^\+(?!\+\+)/gm) ?? []).length;
41116
+ const removed = (diffText.match(/^-(?!--)/gm) ?? []).length;
41117
+ return `working-tree diff: +${added}/-${removed} line(s)`;
41118
+ }
41119
+ function courseStatusLine(course) {
41120
+ if (course.state !== "bound")
41121
+ return "flow: unbound";
41122
+ return `flow ${course.flowRef.uri} snapshot=${course.flowRef.snapshot} completed=${course.completed.length} next=${course.next.length} blocked=${course.blocked.length}`;
41123
+ }
41124
+ function mechanicalSummary(diffText, course) {
41125
+ return `Mechanical wrap-up summary (model turn unavailable or timed out):
41126
+ ${diffStatLine(diffText)}
41127
+ ${courseStatusLine(course)}`;
41128
+ }
41129
+ async function resolveMachineWrapUp(input2) {
41130
+ const now = input2.now ?? (() => new Date);
41131
+ const diffText = await gitDiff(input2.cwd);
41132
+ const course = await readCourse(input2.cwd, input2.slate.course.flowRef);
41133
+ const seedsForKind = dedupedAttributedSeeds(input2.slate).filter((seed) => seed.kind === input2.kind);
41134
+ const flowSnapshotJson = `${JSON.stringify(course, null, 2)}
41135
+ `;
41136
+ const seedsJson = `${JSON.stringify(seedsForKind.map((seed) => ({ text: seed.text, source: describeSource(seed.source) })), null, 2)}
41137
+ `;
41138
+ const sourceRevision = sha2568([diffText, flowSnapshotJson, seedsJson].join("\x00"));
41139
+ const shortHash = sourceRevision.slice(0, 16);
41140
+ const system = "Summarize ONLY the machine evidence provided below \u2014 a git diff, a Flow snapshot, and the Seeds captured " + "this session for one proposal kind. Never invent facts that are not present in the evidence.";
41141
+ const user = `--- git diff ---
41142
+ ${diffText.length > 0 ? diffText : "(no working-tree changes)"}
41143
+
41144
+ ` + `--- flow snapshot ---
41145
+ ${flowSnapshotJson}
41146
+ ` + `--- seeds (${input2.kind}) ---
41147
+ ${seedsJson}`;
41148
+ let modelResult;
41149
+ const modelTurnTimeoutMs = input2.modelTurnTimeoutMs ?? DEFAULT_MODEL_TURN_TIMEOUT_MS;
41150
+ const turn = runModelTurn({
41151
+ system,
41152
+ user,
41153
+ requestId: `machine-wrap-up-${shortHash}`,
41154
+ ...input2.env !== undefined ? { env: input2.env } : {},
41155
+ ...input2.providerFactory !== undefined ? { providerFactory: input2.providerFactory } : {}
41156
+ }).then((result) => {
41157
+ modelResult = result;
41158
+ return "done";
41159
+ });
41160
+ let timer;
41161
+ const expired = new Promise((resolve2) => {
41162
+ timer = setTimeout(() => resolve2("timeout"), modelTurnTimeoutMs);
41163
+ });
41164
+ let raceOutcome;
41165
+ try {
41166
+ raceOutcome = await Promise.race([turn, expired]);
41167
+ } finally {
41168
+ if (timer !== undefined)
41169
+ clearTimeout(timer);
41170
+ }
41171
+ let summary;
41172
+ if (raceOutcome === "timeout") {
41173
+ turn.catch(() => {});
41174
+ summary = mechanicalSummary(diffText, course);
41175
+ } else {
41176
+ const result = modelResult;
41177
+ if (result.text.trim().length === 0 && !result.credentialAvailable) {
41178
+ return { ok: false, code: "no_credential" };
41179
+ }
41180
+ summary = result.text.trim().length > 0 ? result.text.trim() : mechanicalSummary(diffText, course);
41181
+ }
41182
+ const evidenceDir = path133.join(input2.cwd, ".metaproject", "workspaces", input2.workspaceId, "machine-evidence");
41183
+ await mkdir53(evidenceDir, { recursive: true });
41184
+ const diffFile = `${input2.kind}.${shortHash}.diff.txt`;
41185
+ const flowFile = `${input2.kind}.${shortHash}.flow.json`;
41186
+ const seedsFile = `${input2.kind}.${shortHash}.seeds.json`;
41187
+ await writeFileAtomic(path133.join(evidenceDir, diffFile), diffText);
41188
+ await writeFileAtomic(path133.join(evidenceDir, flowFile), flowSnapshotJson);
41189
+ await writeFileAtomic(path133.join(evidenceDir, seedsFile), seedsJson);
41190
+ const observedAt = now().toISOString();
41191
+ const relBase = `./.metaproject/workspaces/${input2.workspaceId}/machine-evidence`;
41192
+ const evidence = [
41193
+ { kind: "diff", uri: `${relBase}/${diffFile}`, revision: sha2568(diffText), observedAt },
41194
+ { kind: "flow", uri: `${relBase}/${flowFile}`, revision: sha2568(flowSnapshotJson), observedAt },
41195
+ { kind: "seeds", uri: `${relBase}/${seedsFile}`, revision: sha2568(seedsJson), observedAt }
41196
+ ];
41197
+ return {
41198
+ ok: true,
41199
+ resolution: {
41200
+ workspaceId: input2.workspaceId,
41201
+ sourceRevision,
41202
+ summary,
41203
+ evidence,
41204
+ expiresAt: new Date(now().getTime() + WRAP_UP_TTL_MS2).toISOString()
41205
+ }
41206
+ };
41207
+ }
41208
+ async function writeUnboundCandidateArtifact(dir, trigger, now, grouped, nonEmptyKinds) {
41209
+ const archiveDir = path133.join(dir, "slate-archive");
41210
+ await mkdir53(archiveDir, { recursive: true });
41211
+ const nowIso3 = now().toISOString();
41212
+ const filename = `${nowIso3.replace(/[:.]/g, "-")}-unbound-candidate.json`;
41213
+ const content = {
41214
+ recordType: "unbound-candidate",
41215
+ trigger,
41216
+ generatedAt: nowIso3,
41217
+ groups: nonEmptyKinds.map((kind) => ({
41218
+ kind,
41219
+ seeds: (grouped.get(kind) ?? []).map((seed) => ({ text: seed.text, source: describeSource(seed.source) }))
41220
+ }))
41221
+ };
41222
+ await writeFileAtomic(path133.join(archiveDir, filename), `${JSON.stringify(content, null, 2)}
41223
+ `);
41224
+ }
41225
+ async function proposeOneGroup(params) {
41226
+ try {
41227
+ const resolved = await resolveMachineWrapUp({
41228
+ cwd: params.cwd,
41229
+ workspaceId: params.workspaceId,
41230
+ slate: params.slate,
41231
+ kind: params.kind,
41232
+ now: params.now,
41233
+ ...params.env !== undefined ? { env: params.env } : {},
41234
+ ...params.providerFactory !== undefined ? { providerFactory: params.providerFactory } : {},
41235
+ ...params.modelTurnTimeoutMs !== undefined ? { modelTurnTimeoutMs: params.modelTurnTimeoutMs } : {}
41236
+ });
41237
+ if (!resolved.ok)
41238
+ return { kind: params.kind, outcome: "no_credential" };
41239
+ const flowEvidence = resolved.resolution.evidence.find((item) => item.kind === "flow");
41240
+ const sourceRef = (flowEvidence ?? resolved.resolution.evidence[0]).uri;
41241
+ const flowRef = params.slate.course.flowRef ?? "";
41242
+ const dedupHash = sha2568(`${params.workspaceId}:${flowRef}:${resolved.resolution.sourceRevision}:${params.kind}`);
41243
+ const proposalId = `wrapup-${dedupHash.slice(0, 32)}`;
41244
+ const wrapUpAuthority = createTrustedWrapUpAuthority({
41245
+ now: params.now,
41246
+ resolveExplicitWrapUp: async (request) => {
41247
+ if (request.source !== "flow") {
41248
+ throw new Error(`machine-wrap-up only resolves "flow" wrap-ups, got "${request.source}"`);
41249
+ }
41250
+ return resolved.resolution;
41251
+ }
41252
+ });
41253
+ const { service: service4, authorizationServer } = createHarnessProposalLifecycleService(params.cwd, {
41254
+ workspaceId: params.workspaceId,
41255
+ now: params.now
41256
+ });
41257
+ const requestCorrelationId = randomUUID14();
41258
+ const actor = await authorizationServer.actorContextFor(undefined, requestCorrelationId);
41259
+ if (!actor)
41260
+ throw new Error("trusted ActorContext is required for a machine wrap-up propose");
41261
+ const provenance = await wrapUpAuthority.issue({ actor, source: "flow", sourceRef });
41262
+ try {
41263
+ const proposal = await service4.create({
41264
+ request: undefined,
41265
+ requestCorrelationId,
41266
+ workspaceId: params.workspaceId,
41267
+ id: proposalId,
41268
+ proposalRevision: "1",
41269
+ kind: params.kind,
41270
+ wrapUp: provenance
41271
+ });
41272
+ return { kind: params.kind, outcome: "proposed", proposalId: proposal.id };
41273
+ } catch (error2) {
41274
+ if (error2 instanceof ProposalLifecycleError && error2.code === "conflict") {
41275
+ return { kind: params.kind, outcome: "conflict" };
41276
+ }
41277
+ throw error2;
41278
+ }
41279
+ } catch (error2) {
41280
+ const message2 = error2 instanceof Error ? error2.message : String(error2);
41281
+ return { kind: params.kind, outcome: "error", message: message2 };
41282
+ }
41283
+ }
41284
+ async function runWrapUp(input2) {
41285
+ const now = input2.now ?? (() => new Date);
41286
+ const grouped = groupSeedsByKind(input2.slate);
41287
+ const nonEmptyKinds = [...grouped.keys()].filter((kind) => (grouped.get(kind)?.length ?? 0) > 0);
41288
+ if (nonEmptyKinds.length === 0) {
41289
+ return { groups: [] };
41290
+ }
41291
+ if (input2.slate.workspaceId === undefined) {
41292
+ await writeUnboundCandidateArtifact(input2.dir, input2.trigger, now, grouped, nonEmptyKinds);
41293
+ return { groups: nonEmptyKinds.map((kind) => ({ kind, outcome: "unbound-candidate" })) };
41294
+ }
41295
+ const workspaceId = input2.slate.workspaceId;
41296
+ const groups = await Promise.all(nonEmptyKinds.map((kind) => proposeOneGroup({
41297
+ cwd: input2.cwd,
41298
+ workspaceId,
41299
+ slate: input2.slate,
41300
+ kind,
41301
+ now,
41302
+ ...input2.env !== undefined ? { env: input2.env } : {},
41303
+ ...input2.providerFactory !== undefined ? { providerFactory: input2.providerFactory } : {},
41304
+ ...input2.modelTurnTimeoutMs !== undefined ? { modelTurnTimeoutMs: input2.modelTurnTimeoutMs } : {}
41305
+ })));
41306
+ return { groups };
41307
+ }
41308
+
41309
+ // src/commands/harness.ts
40785
41310
  init_shell_config();
40786
41311
  import { realpathSync as realpathSync3 } from "fs";
40787
41312
  import { homedir as homedir3, tmpdir as tmpdir2 } from "os";
@@ -41201,7 +41726,7 @@ function resolveRuntime(deps) {
41201
41726
  const env = deps?.env ?? process.env;
41202
41727
  const clock = deps?.clock ?? (() => new Date().toISOString());
41203
41728
  let idCounter = 0;
41204
- const idSeq = deps?.idSeq ?? (() => `${randomUUID14()}-${idCounter++}`);
41729
+ const idSeq = deps?.idSeq ?? (() => `${randomUUID15()}-${idCounter++}`);
41205
41730
  return { env, clock, idSeq };
41206
41731
  }
41207
41732
  function shellAllowProfile() {
@@ -41225,12 +41750,23 @@ var denyingExecutor = {
41225
41750
  throw new Error(`no tool executor is configured for the harness CLI: ${invocation.call.toolName}`);
41226
41751
  }
41227
41752
  };
41753
+ var KNOWN_HARNESS_RUN_FLAGS = new Set([
41754
+ "--provider",
41755
+ "--model",
41756
+ "--base-url",
41757
+ "--record",
41758
+ "--unattended",
41759
+ "--goal",
41760
+ "--workspace"
41761
+ ]);
41228
41762
  function parseArgs2(args) {
41229
41763
  let provider = "";
41230
41764
  let model = "";
41231
41765
  let baseUrl;
41232
41766
  let record;
41233
41767
  let unattended;
41768
+ let goal;
41769
+ let workspace;
41234
41770
  const positional = [];
41235
41771
  for (let i = 1;i < args.length; i++) {
41236
41772
  const arg = args[i];
@@ -41244,17 +41780,27 @@ function parseArgs2(args) {
41244
41780
  record = args[++i];
41245
41781
  } else if (arg === "--unattended") {
41246
41782
  unattended = true;
41783
+ } else if (arg === "--goal") {
41784
+ const next = args[i + 1];
41785
+ goal = next !== undefined && !KNOWN_HARNESS_RUN_FLAGS.has(next) ? args[++i] : undefined;
41786
+ } else if (arg === "--workspace") {
41787
+ const next = args[i + 1];
41788
+ workspace = next !== undefined && !KNOWN_HARNESS_RUN_FLAGS.has(next) ? args[++i] : undefined;
41247
41789
  } else if (arg !== undefined) {
41248
41790
  positional.push(arg);
41249
41791
  }
41250
41792
  }
41251
- const parsed = { provider, model, prompt: positional.join(" ") };
41793
+ const parsed = { provider, model, prompt: goal !== undefined && goal.length > 0 ? goal : positional.join(" ") };
41252
41794
  if (baseUrl !== undefined)
41253
41795
  parsed.baseUrl = baseUrl;
41254
41796
  if (record !== undefined)
41255
41797
  parsed.record = record;
41256
41798
  if (unattended !== undefined)
41257
41799
  parsed.unattended = unattended;
41800
+ if (goal !== undefined)
41801
+ parsed.goal = goal;
41802
+ if (workspace !== undefined)
41803
+ parsed.workspace = workspace;
41258
41804
  return parsed;
41259
41805
  }
41260
41806
  function toStructured(result) {
@@ -41288,16 +41834,27 @@ async function harnessCommand(args, deps) {
41288
41834
  console.log(USAGE);
41289
41835
  return;
41290
41836
  }
41291
- const { provider, model, baseUrl, prompt, record, unattended } = parseArgs2(args);
41837
+ const { provider, model, baseUrl, prompt, record, unattended, workspace } = parseArgs2(args);
41292
41838
  const validProviders = new Set(HARNESS_PROVIDER_OPTIONS);
41293
41839
  if (!validProviders.has(provider) || prompt.length === 0) {
41294
41840
  console.log(USAGE);
41295
41841
  return;
41296
41842
  }
41843
+ if (args.includes("--workspace") && (workspace === undefined || workspace.length === 0)) {
41844
+ console.log('--workspace requires a value, e.g. keryx harness run --provider <p> --model <m> --workspace <id> "<prompt>". No run was started.');
41845
+ return;
41846
+ }
41847
+ if (workspace !== undefined && workspace.length > 0) {
41848
+ const resolved = await resolveWorkspaceForActor(process.cwd(), workspace);
41849
+ if (!resolved.ok) {
41850
+ console.log(`--workspace "${workspace}" was rejected (${resolved.error.code}): ${resolved.error.message}. No run was started.`);
41851
+ return;
41852
+ }
41853
+ }
41297
41854
  const env = deps?.env ?? process.env;
41298
41855
  const clock = deps?.clock ?? (() => new Date().toISOString());
41299
41856
  let idCounter = 0;
41300
- const idSeq = deps?.idSeq ?? (() => `${randomUUID14()}-${idCounter++}`);
41857
+ const idSeq = deps?.idSeq ?? (() => `${randomUUID15()}-${idCounter++}`);
41301
41858
  const fetchImpl = deps?.fetch ?? globalThis.fetch;
41302
41859
  if (provider === "anthropic") {
41303
41860
  const apiKey = env.ANTHROPIC_API_KEY;
@@ -41363,6 +41920,16 @@ async function harnessCommand(args, deps) {
41363
41920
  };
41364
41921
  }
41365
41922
  console.log(JSON.stringify(structured));
41923
+ try {
41924
+ const wrapUpDir = resolveOneShotWrapUpSessionDir(process.cwd(), idSeq);
41925
+ const prior = await readSlate(wrapUpDir);
41926
+ const wrapUpSlate = prior ?? { anchors: { root: process.cwd(), touched: [] }, course: {}, seeds: [] };
41927
+ if (workspace !== undefined && workspace.length > 0)
41928
+ wrapUpSlate.workspaceId = workspace;
41929
+ await runWrapUp({ trigger: "process-termination", cwd: process.cwd(), dir: wrapUpDir, slate: wrapUpSlate });
41930
+ } catch (error2) {
41931
+ console.error(`harness run: wrap-up trigger failed (ignored): ${error2 instanceof Error ? error2.message : String(error2)}`);
41932
+ }
41366
41933
  }
41367
41934
  function parseReplayArgs(args) {
41368
41935
  const parsed = { json: false };
@@ -41414,7 +41981,7 @@ function harnessReplay(args, deps) {
41414
41981
  }
41415
41982
  const clock = deps?.clock ?? (() => new Date().toISOString());
41416
41983
  let idCounter = 0;
41417
- const idSeq = deps?.idSeq ?? (() => `${randomUUID14()}-${idCounter++}`);
41984
+ const idSeq = deps?.idSeq ?? (() => `${randomUUID15()}-${idCounter++}`);
41418
41985
  const fixture = (() => {
41419
41986
  if (parsed.fixture === undefined || parsed.fixture.length === 0) {
41420
41987
  return { ok: true, value: buildReplayFixture(run, { idSeq }), built: true };
@@ -41665,7 +42232,7 @@ async function harnessExec(args, deps) {
41665
42232
  env: commandEnv,
41666
42233
  cwd
41667
42234
  };
41668
- const worktreeRoot = path132.parse(path132.resolve(cwd, commandPath)).root || cwd;
42235
+ const worktreeRoot = path134.parse(path134.resolve(cwd, commandPath)).root || cwd;
41669
42236
  const budget = {
41670
42237
  reservationId: idSeq(),
41671
42238
  maxRuntimeMs: maxRuntimeMs ?? EXEC_DEFAULT_RUNTIME_MS
@@ -41854,7 +42421,7 @@ function harnessWave(args, deps) {
41854
42421
  // src/commands/shell.ts
41855
42422
  init_make_provider();
41856
42423
  init_orient();
41857
- import { randomUUID as randomUUID18 } from "crypto";
42424
+ import { randomUUID as randomUUID20 } from "crypto";
41858
42425
  import * as readline2 from "readline";
41859
42426
 
41860
42427
  // src/commands/agent-approval-context.ts
@@ -41896,6 +42463,9 @@ async function buildApprovalContext(port, command) {
41896
42463
  `);
41897
42464
  }
41898
42465
 
42466
+ // src/commands/interactive-agent-tools.ts
42467
+ import { randomUUID as randomUUID17 } from "crypto";
42468
+
41899
42469
  // src/harness/tool/builtin/ask-user-tool.ts
41900
42470
  function createAskUserTool(ask) {
41901
42471
  return {
@@ -41988,7 +42558,7 @@ function createAskUserTool(ask) {
41988
42558
  }
41989
42559
 
41990
42560
  // src/harness/tool/builtin/interactive-tools.ts
41991
- import { readdir as readdir21 } from "fs/promises";
42561
+ import { readdir as readdir22 } from "fs/promises";
41992
42562
  import { isAbsolute as isAbsolute2, relative as relative2, resolve as resolve2, sep } from "path";
41993
42563
  import { realpathSync as realpathSync4 } from "fs";
41994
42564
  var MAX_READ_BYTES = 20000;
@@ -42040,7 +42610,7 @@ function builtinReadOnlyTools(root) {
42040
42610
  return { output: `path escapes the project root: ${requested}`, isError: true };
42041
42611
  }
42042
42612
  try {
42043
- const entries = await readdir21(target, { withFileTypes: true });
42613
+ const entries = await readdir22(target, { withFileTypes: true });
42044
42614
  const lines = entries.map((e) => e.isDirectory() ? `${e.name}/` : e.name).sort();
42045
42615
  return { output: lines.length > 0 ? lines.join(`
42046
42616
  `) : "(empty)", isError: false };
@@ -42189,13 +42759,13 @@ function builtinMetaprojectTools(root, run = makeKeryxRunner(root), port) {
42189
42759
  if ("error" in pattern) {
42190
42760
  return pattern.error;
42191
42761
  }
42192
- const path133 = typeof input2.path === "string" && input2.path.length > 0 ? input2.path : undefined;
42762
+ const path135 = typeof input2.path === "string" && input2.path.length > 0 ? input2.path : undefined;
42193
42763
  const args = ["ctx", "rg", pattern.value];
42194
- if (path133 !== undefined) {
42195
- const confined = confineToRoot(root, path133);
42764
+ if (path135 !== undefined) {
42765
+ const confined = confineToRoot(root, path135);
42196
42766
  if (confined === null) {
42197
42767
  return {
42198
- output: `search_code: path escapes the project root: ${path133}`,
42768
+ output: `search_code: path escapes the project root: ${path135}`,
42199
42769
  isError: true
42200
42770
  };
42201
42771
  }
@@ -42485,6 +43055,100 @@ function shellExecTool(root, run = makeCommandRunner(root)) {
42485
43055
  };
42486
43056
  }
42487
43057
 
43058
+ // src/harness/tool/builtin/slate-tool.ts
43059
+ init_redact();
43060
+ var SEED_TEXT_MAX_LENGTH = 4000;
43061
+ var SLATE_SEED_KINDS = [
43062
+ "decision",
43063
+ "wiki-update",
43064
+ "memory-entry",
43065
+ "follow-up",
43066
+ "contract-change",
43067
+ "risk"
43068
+ ];
43069
+ function isSlateSeedKind(value) {
43070
+ return typeof value === "string" && SLATE_SEED_KINDS.includes(value);
43071
+ }
43072
+ function slateReadTool(cwd, getSessionDir) {
43073
+ return {
43074
+ definition: {
43075
+ name: "slate_read",
43076
+ description: "Read this session's Slate: the live Course projection (derived from the bound Flow, if any) and the Seeds recorded so far (draft hypotheses, not yet accepted). Input: {} (no arguments). Course/Seeds are NEVER auto-injected into the conversation \u2014 this tool is the only way to see them.",
43077
+ inputSchema: { type: "object", properties: {}, additionalProperties: false },
43078
+ risk: "read"
43079
+ },
43080
+ invoke: async () => {
43081
+ const dir = getSessionDir();
43082
+ if (dir === undefined) {
43083
+ return { output: "slate_read: no active session in this run", isError: true };
43084
+ }
43085
+ try {
43086
+ const slate = await readSlate(dir);
43087
+ const course = await courseFromSlate(cwd, slate);
43088
+ return {
43089
+ output: JSON.stringify({ course, seeds: slate?.seeds ?? [], workspaceId: slate?.workspaceId }, null, 2),
43090
+ isError: false
43091
+ };
43092
+ } catch (cause) {
43093
+ return {
43094
+ output: `slate_read failed: ${cause instanceof Error ? cause.message : String(cause)}`,
43095
+ isError: true
43096
+ };
43097
+ }
43098
+ }
43099
+ };
43100
+ }
43101
+ function slateWriteSeedTool(getSessionDir, idSeq, clock) {
43102
+ return {
43103
+ definition: {
43104
+ name: "slate_write_seed",
43105
+ description: "Record a Seed: a draft hypothesis, decision, or follow-up worth surfacing to a later review \u2014 never accepted project knowledge by itself. Input: { text: string, kind?: 'decision'|'wiki-update'|'memory-entry'|'follow-up'|'contract-change'|'risk' }.",
43106
+ inputSchema: {
43107
+ type: "object",
43108
+ properties: {
43109
+ text: { type: "string", maxLength: SEED_TEXT_MAX_LENGTH },
43110
+ kind: { type: "string", enum: [...SLATE_SEED_KINDS] }
43111
+ },
43112
+ required: ["text"],
43113
+ additionalProperties: false
43114
+ },
43115
+ risk: "read"
43116
+ },
43117
+ invoke: async (input2) => {
43118
+ const rawText = typeof input2.text === "string" ? input2.text : undefined;
43119
+ const text = rawText?.trim();
43120
+ if (text === undefined || text.length === 0) {
43121
+ return { output: "slate_write_seed requires a non-empty 'text'", isError: true };
43122
+ }
43123
+ let kind;
43124
+ if (input2.kind !== undefined) {
43125
+ if (!isSlateSeedKind(input2.kind)) {
43126
+ return {
43127
+ output: `slate_write_seed: unrecognized 'kind': ${JSON.stringify(input2.kind)}`,
43128
+ isError: true
43129
+ };
43130
+ }
43131
+ kind = input2.kind;
43132
+ }
43133
+ const dir = getSessionDir();
43134
+ if (dir === undefined) {
43135
+ return { output: "slate_write_seed: no active session in this run", isError: true };
43136
+ }
43137
+ try {
43138
+ const redactedText = redactSensitiveText(text);
43139
+ const seed = { id: idSeq(), text: redactedText, ts: clock(), ...kind !== undefined ? { kind } : {} };
43140
+ await appendSeed(dir, seed);
43141
+ return { output: JSON.stringify({ appended: seed }, null, 2), isError: false };
43142
+ } catch (cause) {
43143
+ return {
43144
+ output: `slate_write_seed failed: ${cause instanceof Error ? cause.message : String(cause)}`,
43145
+ isError: true
43146
+ };
43147
+ }
43148
+ }
43149
+ };
43150
+ }
43151
+
42488
43152
  // src/harness/web/sandboxed-web-transport.ts
42489
43153
  import { lookup as systemLookup } from "dns/promises";
42490
43154
 
@@ -42936,7 +43600,7 @@ function webSearchTool(service4) {
42936
43600
  }
42937
43601
 
42938
43602
  // src/harness/tool/builtin/workspace-context-tool.ts
42939
- import { randomUUID as randomUUID15 } from "crypto";
43603
+ import { randomUUID as randomUUID16 } from "crypto";
42940
43604
  function parseBudget(input2, defaultMaxItems) {
42941
43605
  const maxItems = input2.maxItems === undefined ? defaultMaxItems : input2.maxItems;
42942
43606
  const maxTokens = input2.maxTokens === undefined ? 4096 : input2.maxTokens;
@@ -42966,7 +43630,7 @@ function workspaceOverviewTool(cwd) {
42966
43630
  const budget = parseBudget(input2, 32);
42967
43631
  if ("error" in budget)
42968
43632
  return { output: budget.error, isError: true };
42969
- const result = await createLocalFwkReadService(cwd).overview({ workspaceId, request: undefined, requestCorrelationId: randomUUID15(), budget });
43633
+ const result = await createLocalFwkReadService(cwd).overview({ workspaceId, request: undefined, requestCorrelationId: randomUUID16(), budget });
42970
43634
  return { output: JSON.stringify(normalizeFwkResult(result), null, 2), isError: false };
42971
43635
  }
42972
43636
  };
@@ -42994,7 +43658,7 @@ function workspaceReadTool(cwd) {
42994
43658
  const budget = parseBudget(input2, 1);
42995
43659
  if ("error" in budget)
42996
43660
  return { output: budget.error, isError: true };
42997
- const result = await createLocalFwkReadService(cwd).read({ workspaceId, itemId, request: undefined, requestCorrelationId: randomUUID15(), budget });
43661
+ const result = await createLocalFwkReadService(cwd).read({ workspaceId, itemId, request: undefined, requestCorrelationId: randomUUID16(), budget });
42998
43662
  return { output: JSON.stringify(normalizeFwkResult(result), null, 2), isError: false };
42999
43663
  }
43000
43664
  };
@@ -43014,6 +43678,11 @@ async function invokeAskUserHost(request) {
43014
43678
 
43015
43679
  // src/commands/interactive-agent-tools.ts
43016
43680
  function buildInteractiveAgentTools(input2) {
43681
+ const getSessionDir = input2.getSessionDir ?? (() => {
43682
+ return;
43683
+ });
43684
+ const idSeq = input2.idSeq ?? (() => randomUUID17());
43685
+ const clock = input2.clock ?? (() => new Date().toISOString());
43017
43686
  return [
43018
43687
  ...builtinReadOnlyTools(input2.cwd),
43019
43688
  ...builtinMetaprojectTools(input2.cwd, makeKeryxRunner(input2.cwd), input2.metaprojectPort),
@@ -43023,6 +43692,8 @@ function buildInteractiveAgentTools(input2) {
43023
43692
  workspaceOverviewTool(input2.cwd),
43024
43693
  workspaceReadTool(input2.cwd),
43025
43694
  createAskUserTool(invokeAskUserHost),
43695
+ slateReadTool(input2.cwd, getSessionDir),
43696
+ slateWriteSeedTool(getSessionDir, idSeq, clock),
43026
43697
  input2.spawnTool
43027
43698
  ];
43028
43699
  }
@@ -43031,7 +43702,7 @@ function buildInteractiveAgentTools(input2) {
43031
43702
  init_config_dir();
43032
43703
  init_shell_config();
43033
43704
  import { existsSync as existsSync24 } from "fs";
43034
- import path133 from "path";
43705
+ import path135 from "path";
43035
43706
 
43036
43707
  // src/lib/shell-syntax.ts
43037
43708
  var METACHARACTERS = new Set([";", "&", "|", "<", ">", `
@@ -43344,7 +44015,7 @@ function touchesAgentCredentials(command) {
43344
44015
  }
43345
44016
 
43346
44017
  // src/lib/shell-permissions.ts
43347
- import { createHash as createHash27 } from "crypto";
44018
+ import { createHash as createHash28 } from "crypto";
43348
44019
  var PREFIX_BANNED = new Set([
43349
44020
  "sh",
43350
44021
  "bash",
@@ -43523,7 +44194,7 @@ function emptyShellPermissions() {
43523
44194
  return { allow: [] };
43524
44195
  }
43525
44196
  function shellPermissionsPath(dir) {
43526
- return path133.join(path133.dirname(shellConfigPath(dir)), "permissions.json");
44197
+ return path135.join(path135.dirname(shellConfigPath(dir)), "permissions.json");
43527
44198
  }
43528
44199
  function loadShellPermissionsWithAudit(dir) {
43529
44200
  try {
@@ -43561,7 +44232,7 @@ function loadShellPermissions(dir) {
43561
44232
  function saveShellPermissions(perms, dir, options = {}) {
43562
44233
  try {
43563
44234
  const file = shellPermissionsPath(dir);
43564
- ensureKeryxConfigDir(path133.dirname(file));
44235
+ ensureKeryxConfigDir(path135.dirname(file));
43565
44236
  const cleaned = Array.from(new Set(perms.allow.map((p) => p.trim()).filter((p) => p.length > 0)));
43566
44237
  const body = {
43567
44238
  allow: options.skipValidation === true ? cleaned : cleaned.filter((p) => validateShellPattern(p).ok)
@@ -43636,7 +44307,7 @@ function shellPermissionsFingerprint(dir) {
43636
44307
  if (!read.ok) {
43637
44308
  return "";
43638
44309
  }
43639
- return createHash27("sha256").update(read.text, "utf8").digest("hex");
44310
+ return createHash28("sha256").update(read.text, "utf8").digest("hex");
43640
44311
  } catch {
43641
44312
  return "";
43642
44313
  }
@@ -43870,12 +44541,12 @@ function remoteRequest(providerId, endpoint, query, key) {
43870
44541
  // src/lib/search-config.ts
43871
44542
  init_config_dir();
43872
44543
  import { existsSync as existsSync25 } from "fs";
43873
- import path134 from "path";
44544
+ import path136 from "path";
43874
44545
  function searchConfigPath(dir) {
43875
- return path134.join(keryxConfigDir(dir), "search-providers.json");
44546
+ return path136.join(keryxConfigDir(dir), "search-providers.json");
43876
44547
  }
43877
44548
  function searchCredentialPath(dir) {
43878
- return path134.join(keryxConfigDir(dir), "search-credentials.json");
44549
+ return path136.join(keryxConfigDir(dir), "search-credentials.json");
43879
44550
  }
43880
44551
  function readJson(file) {
43881
44552
  try {
@@ -44001,7 +44672,10 @@ function createDefaultSearchProviderController(configDir) {
44001
44672
  return new SearchProviderController(registry, configDir);
44002
44673
  }
44003
44674
  // src/harness/tool/builtin/spawn-subagent-tool.ts
44004
- import { createHash as createHash28, randomUUID as randomUUID16 } from "crypto";
44675
+ import { createHash as createHash29, randomUUID as randomUUID18 } from "crypto";
44676
+ import { mkdtemp, rm as rm8 } from "fs/promises";
44677
+ import { tmpdir as tmpdir4 } from "os";
44678
+ import path138 from "path";
44005
44679
 
44006
44680
  // src/harness/child/ledger.ts
44007
44681
  function decrement(remaining, reservation) {
@@ -44315,85 +44989,13 @@ function foldChildSummary(summary) {
44315
44989
  init_validator();
44316
44990
  init_redact();
44317
44991
 
44318
- // src/session/slate.ts
44319
- init_fs();
44320
- import { mkdir as mkdir52, readFile as readFile74, rm as rm7 } from "fs/promises";
44321
- import path135 from "path";
44322
- function slatePath(dir) {
44323
- return path135.join(dir, "slate.json");
44324
- }
44325
- function slateLockPath(dir) {
44326
- return `${slatePath(dir)}.lock`;
44327
- }
44328
- async function readSlate(dir) {
44329
- try {
44330
- const raw = await readFile74(slatePath(dir), "utf8");
44331
- return JSON.parse(raw);
44332
- } catch (error2) {
44333
- if (isNotFound(error2))
44334
- return;
44335
- throw error2;
44336
- }
44337
- }
44338
- async function archiveSlate(dir, attemptId) {
44339
- if (!/^[A-Za-z0-9._-]+$/.test(attemptId))
44340
- throw new Error(`invalid attemptId: ${JSON.stringify(attemptId)}`);
44341
- await mkdir52(dir, { recursive: true });
44342
- return withFileLock2(slateLockPath(dir), () => archiveIfExistsLocked(dir, attemptId));
44343
- }
44344
- async function archiveIfExistsLocked(dir, attemptId) {
44345
- let raw;
44346
- try {
44347
- raw = await readFile74(slatePath(dir), "utf8");
44348
- } catch (error2) {
44349
- if (isNotFound(error2))
44350
- return;
44351
- throw error2;
44352
- }
44353
- const archiveDir = path135.join(dir, "slate-archive");
44354
- await mkdir52(archiveDir, { recursive: true });
44355
- await writeFileAtomic(path135.join(archiveDir, `${attemptId}.json`), raw);
44356
- await rm7(slatePath(dir), { force: true });
44357
- }
44358
- async function openSlateAtomic(dir, mintAttemptId, build) {
44359
- await mkdir52(dir, { recursive: true });
44360
- return withFileLock2(slateLockPath(dir), async () => {
44361
- const existing = await readSlate(dir);
44362
- if (existing !== undefined) {
44363
- await archiveIfExistsLocked(dir, mintAttemptId());
44364
- }
44365
- const next = build();
44366
- await writeFileAtomic(slatePath(dir), `${JSON.stringify(next, null, 2)}
44367
- `);
44368
- return next;
44369
- });
44370
- }
44371
-
44372
- // src/session/slate-course.ts
44373
- init_store2();
44374
- var unbound = { state: "unbound" };
44375
- async function readCourse(cwd, flowRef) {
44376
- if (!flowRef)
44377
- return unbound;
44378
- try {
44379
- const dir = await resolveFlowDir(cwd, flowRef);
44380
- const flow = await readFlow(cwd, dir);
44381
- return { state: "bound", ...deriveFlowWork(flow, flowRef) };
44382
- } catch {
44383
- return unbound;
44384
- }
44385
- }
44386
- async function courseFromSlate(cwd, slate) {
44387
- return readCourse(cwd, slate?.course.flowRef);
44388
- }
44389
-
44390
44992
  // src/session/slate-lifecycle.ts
44391
- import { execFile } from "child_process";
44392
- import { promisify } from "util";
44393
- var execFileAsync = promisify(execFile);
44993
+ import { execFile as execFile2 } from "child_process";
44994
+ import { promisify as promisify2 } from "util";
44995
+ var execFileAsync2 = promisify2(execFile2);
44394
44996
  async function resolveTree(root) {
44395
44997
  try {
44396
- const { stdout: stdout2 } = await execFileAsync("git", ["rev-parse", "--abbrev-ref", "HEAD"], { cwd: root });
44998
+ const { stdout: stdout2 } = await execFileAsync2("git", ["rev-parse", "--abbrev-ref", "HEAD"], { cwd: root });
44397
44999
  const branch = stdout2.trim();
44398
45000
  return branch.length > 0 ? branch : undefined;
44399
45001
  } catch {
@@ -44503,12 +45105,80 @@ function isClosePhrase(text) {
44503
45105
  return true;
44504
45106
  });
44505
45107
  }
45108
+ async function recordSlateTouch(dir, touched, extra) {
45109
+ let changed = false;
45110
+ const slate = await writeSlate(dir, (prev) => {
45111
+ if (!prev)
45112
+ throw new Error(`recordSlateTouch: no open slate in ${dir}`);
45113
+ const existing = new Set(prev.anchors.touched);
45114
+ const additions = touched.filter((entry) => !existing.has(entry));
45115
+ const treeChanged = extra?.tree !== undefined && extra.tree !== prev.anchors.tree;
45116
+ const runtimeChanged = extra?.runtime !== undefined && (prev.anchors.runtime === undefined || prev.anchors.runtime.provider !== extra.runtime.provider || prev.anchors.runtime.model !== extra.runtime.model);
45117
+ changed = additions.length > 0 || treeChanged || runtimeChanged;
45118
+ if (!changed) {
45119
+ return prev;
45120
+ }
45121
+ const nextAnchors = {
45122
+ ...prev.anchors,
45123
+ touched: additions.length > 0 ? [...prev.anchors.touched, ...additions] : prev.anchors.touched
45124
+ };
45125
+ if (extra?.tree !== undefined)
45126
+ nextAnchors.tree = extra.tree;
45127
+ if (extra?.runtime !== undefined)
45128
+ nextAnchors.runtime = extra.runtime;
45129
+ return { ...prev, anchors: nextAnchors };
45130
+ });
45131
+ return { changed, slate };
45132
+ }
44506
45133
  var timestampMintCounter = 0;
44507
45134
  function mintTimestampAttemptId(now = new Date) {
44508
45135
  timestampMintCounter += 1;
44509
45136
  return `${now.toISOString().replace(/:/g, "-")}-${timestampMintCounter}`;
44510
45137
  }
44511
45138
 
45139
+ // src/session/slate-terminal-state.ts
45140
+ init_fs();
45141
+ init_repomap();
45142
+ init_redact();
45143
+ import path137 from "path";
45144
+ var DEFAULT_TERMINAL_STATE_MAX_TOKENS = 2000;
45145
+ function boundedRedactedAnchorsSnapshot(anchors, maxTokens) {
45146
+ const fence = anchors.fence?.map((entry) => redactSensitiveText(entry));
45147
+ const base = {
45148
+ root: anchors.root,
45149
+ ...anchors.tree !== undefined ? { tree: anchors.tree } : {},
45150
+ ...anchors.runtime !== undefined ? { runtime: anchors.runtime } : {},
45151
+ ...fence !== undefined ? { fence } : {},
45152
+ touched: []
45153
+ };
45154
+ const baseTokens = estimateTokens(JSON.stringify(base));
45155
+ const touchedBudget = Math.max(0, maxTokens - baseTokens);
45156
+ const touched = redactAndBoundTouched(anchors.touched, touchedBudget, {
45157
+ traceRef: "slate-terminal-state-anchors",
45158
+ configurationRevision: "slate-terminal-state-anchors-v1",
45159
+ policyRef: "slate-terminal-state-anchors",
45160
+ policyRevision: "v1"
45161
+ });
45162
+ return { ...base, touched };
45163
+ }
45164
+ function renderTerminalStateBlock(state, opts) {
45165
+ const maxTokens = opts?.maxTokens ?? DEFAULT_TERMINAL_STATE_MAX_TOKENS;
45166
+ const anchorsSnapshot = boundedRedactedAnchorsSnapshot(state.anchorsSnapshot, maxTokens);
45167
+ return [
45168
+ "KERYX_TERMINAL_STATE",
45169
+ `status: ${state.status}`,
45170
+ `reason: ${state.reason}`,
45171
+ `occurredAt: ${state.occurredAt}`,
45172
+ `courseSnapshot: ${JSON.stringify(state.courseSnapshot)}`,
45173
+ `anchorsSnapshot: ${JSON.stringify(anchorsSnapshot)}`
45174
+ ].join(`
45175
+ `);
45176
+ }
45177
+ async function writeTerminalState(dir, state) {
45178
+ await writeFileAtomic(path137.join(dir, "terminal-state.json"), `${JSON.stringify(state, null, 2)}
45179
+ `);
45180
+ }
45181
+
44512
45182
  // src/commands/agent.ts
44513
45183
  var DEFAULT_MAX_TOOL_CALLS = 48;
44514
45184
  var ENV_AGENT_MAX_TOOL_CALLS = "KERYX_AGENT_MAX_TOOL_CALLS";
@@ -44692,6 +45362,25 @@ function parseToolInput(raw) {
44692
45362
  return {};
44693
45363
  }
44694
45364
  }
45365
+ function extractTouchedFromToolInput(name, input2) {
45366
+ const pathLikeFields = ["path", "file", "dir", "target"];
45367
+ const out = [];
45368
+ for (const field3 of pathLikeFields) {
45369
+ const value = input2[field3];
45370
+ if (typeof value === "string" && value.trim().length > 0) {
45371
+ out.push(value.trim());
45372
+ }
45373
+ }
45374
+ if (name === "spawn_subagent") {
45375
+ const label = typeof input2.label === "string" ? input2.label.trim() : "";
45376
+ const task = typeof input2.task === "string" ? input2.task.trim() : "";
45377
+ const marker2 = label.length > 0 ? label : task.length > 0 ? task.slice(0, 40) : "";
45378
+ if (marker2.length > 0) {
45379
+ out.push(`subagent:${marker2}`);
45380
+ }
45381
+ }
45382
+ return out;
45383
+ }
44695
45384
  function stableStringify2(value) {
44696
45385
  if (value === null || typeof value !== "object") {
44697
45386
  return JSON.stringify(value);
@@ -44766,6 +45455,46 @@ function reserveToolAttempt(state, name, input2, risk) {
44766
45455
  state.attempts.set(hash2, attempt);
44767
45456
  return { ok: true, hash: hash2, attempt, chargedNew: isNew };
44768
45457
  }
45458
+ async function resolveTerminalStateSnapshots(options) {
45459
+ const ref = options.slateSession;
45460
+ if (ref !== undefined && ref.opened) {
45461
+ try {
45462
+ const slate = await readSlate(ref.dir);
45463
+ if (slate !== undefined) {
45464
+ return { courseSnapshot: slate.course, anchorsSnapshot: slate.anchors };
45465
+ }
45466
+ } catch {}
45467
+ }
45468
+ return { courseSnapshot: {}, anchorsSnapshot: { root: "", touched: [] } };
45469
+ }
45470
+ async function emitTerminalState(io, deps, options, reason) {
45471
+ const { courseSnapshot, anchorsSnapshot } = await resolveTerminalStateSnapshots(options);
45472
+ const now = deps.now ?? (() => new Date().toISOString());
45473
+ const state = {
45474
+ status: "blocked",
45475
+ reason,
45476
+ courseSnapshot,
45477
+ anchorsSnapshot,
45478
+ occurredAt: now()
45479
+ };
45480
+ io.onTerminalState?.(state);
45481
+ const ref = options.slateSession;
45482
+ if (ref !== undefined && ref.opened) {
45483
+ try {
45484
+ await writeTerminalState(ref.dir, state);
45485
+ } catch {}
45486
+ }
45487
+ const block = renderTerminalStateBlock(state);
45488
+ if (io.onSystem !== undefined) {
45489
+ io.onSystem(`
45490
+ ${block}
45491
+ `);
45492
+ } else {
45493
+ io.write(`
45494
+ ${block}
45495
+ `);
45496
+ }
45497
+ }
44769
45498
  async function runAgentTurn(io, deps, history, userLine, options = {}) {
44770
45499
  try {
44771
45500
  await runAgentTurnCore(io, deps, history, userLine, options);
@@ -44810,13 +45539,21 @@ async function runAgentTurnCore(io, deps, history, userLine, options = {}) {
44810
45539
  const actionRequest = isActionRequest(userLine);
44811
45540
  if (options.slateSession !== undefined) {
44812
45541
  try {
44813
- if (isClosePhrase(userLine)) {
45542
+ if (options.skipCloseTrigger !== true && isClosePhrase(userLine)) {
44814
45543
  await closeSlateSession(options.slateSession, () => deps.idSeq());
44815
45544
  } else if (actionRequest) {
45545
+ const wasOpened = options.slateSession.opened;
44816
45546
  await ensureSlateOpened(options.slateSession, () => deps.idSeq(), {
44817
45547
  provider: deps.providerId,
44818
45548
  model: deps.modelId
44819
45549
  });
45550
+ if (!wasOpened && options.slateSession.opened) {
45551
+ const freshSlate = await readSlate(options.slateSession.dir);
45552
+ if (freshSlate !== undefined) {
45553
+ history.push({ role: "user", content: renderAnchorsBlock(freshSlate.anchors), provenance: "project" });
45554
+ io.onHistoryChange?.("tool");
45555
+ }
45556
+ }
44820
45557
  }
44821
45558
  } catch (err) {
44822
45559
  io.onSystem?.(`slate open/close check failed (ignored): ${err instanceof Error ? err.message : String(err)}
@@ -44984,6 +45721,10 @@ async function runAgentTurnCore(io, deps, history, userLine, options = {}) {
44984
45721
  `);
44985
45722
  return;
44986
45723
  }
45724
+ if (deps.unattended === true && call.name === "ask_user") {
45725
+ await emitTerminalState(io, deps, options, "ask_user_unanswerable");
45726
+ return;
45727
+ }
44987
45728
  if (untrustedContentSeen || batchContainsUntrustedWeb && call.name !== "web_fetch" && call.name !== "web_search") {
44988
45729
  const result2 = {
44989
45730
  output: "tool blocked: external web content cannot authorize further tool calls in this turn",
@@ -45026,6 +45767,21 @@ ${modelOutput}` : modelOutput,
45026
45767
  if (result.untrusted === true && !result.isError) {
45027
45768
  untrustedContentSeen = true;
45028
45769
  }
45770
+ if (options.slateSession !== undefined && options.slateSession.opened === true) {
45771
+ try {
45772
+ const touchedPaths = extractTouchedFromToolInput(call.name, parseToolInput(call.input));
45773
+ const touch = await recordSlateTouch(options.slateSession.dir, touchedPaths, {
45774
+ runtime: { provider: deps.providerId, model: deps.modelId }
45775
+ });
45776
+ if (touch.changed) {
45777
+ history.push({ role: "user", content: renderAnchorsBlock(touch.slate.anchors), provenance: "project" });
45778
+ io.onHistoryChange?.("tool");
45779
+ }
45780
+ } catch (err) {
45781
+ io.onSystem?.(`slate touch update failed (ignored): ${err instanceof Error ? err.message : String(err)}
45782
+ `);
45783
+ }
45784
+ }
45029
45785
  const shortIn = call.input.length > 80 ? `${call.input.slice(0, 77)}\u2026` : call.input;
45030
45786
  const riskUsage = risk === "read" ? `, read ${readBudgetUsed(budget)}/${maxReadToolCalls}` : `, non-read ${nonReadBudgetUsed(budget)}/${maxNonReadToolCalls}`;
45031
45787
  toolLog.push(`${call.name}(${shortIn}) \u2192 ${result.isError ? "error" : "ok"} [attempt ${reservation.attempt}/${maxAttempts}, unique ${budgetUsed(budget)}/${maxToolCalls}${riskUsage}]`);
@@ -45050,6 +45806,10 @@ ${hint}
45050
45806
  }
45051
45807
  const noProgress = !executedAny && calls.length > 0;
45052
45808
  if (exhaustedBudget !== undefined || noProgress) {
45809
+ if (deps.unattended === true) {
45810
+ await emitTerminalState(io, deps, options, "budget_exhausted");
45811
+ return;
45812
+ }
45053
45813
  await finishWithBudgetSummary(io, deps, history, parentRunId, {
45054
45814
  maxUnique: maxToolCalls,
45055
45815
  maxAttempts,
@@ -45206,6 +45966,7 @@ function emitSubagentFleet(event) {
45206
45966
  }
45207
45967
 
45208
45968
  // src/harness/tool/builtin/spawn-subagent-tool.ts
45969
+ init_fs();
45209
45970
  var MAX_CHILD_SUMMARY_CHARS = 16000;
45210
45971
  var ENV_SUBAGENT_TIMEOUT_MS = "KERYX_SUBAGENT_TIMEOUT_MS";
45211
45972
  function resolveSubagentTimeoutMs(reservationMs, env = process.env) {
@@ -45230,13 +45991,13 @@ function boundSummary(text) {
45230
45991
  return `${text.slice(0, MAX_CHILD_SUMMARY_CHARS)}
45231
45992
  \u2026(truncated: ${dropped} more characters from the subagent)`;
45232
45993
  }
45233
- function sha2568(text) {
45234
- return createHash28("sha256").update(text).digest("hex");
45994
+ function sha2569(text) {
45995
+ return createHash29("sha256").update(text).digest("hex");
45235
45996
  }
45236
45997
  var parentShellPolicy = shellParentProfile;
45237
45998
  var childReadOnlyPolicy = shellChildReadOnlyProfile;
45238
45999
  function createSpawnSubagentTool(deps) {
45239
- const idSeq = deps.idSeq ?? (() => randomUUID16());
46000
+ const idSeq = deps.idSeq ?? (() => randomUUID18());
45240
46001
  const clock = deps.clock ?? (() => new Date().toISOString());
45241
46002
  const parentRunId = deps.parentRunId ?? idSeq();
45242
46003
  const parentSessionId = deps.parentSessionId ?? idSeq();
@@ -45281,7 +46042,7 @@ function createSpawnSubagentTool(deps) {
45281
46042
  parentRunId,
45282
46043
  parentSessionId,
45283
46044
  parentProvenance,
45284
- contextManifestHash: sha2568(`${parentRunId}:${parentSessionId}`),
46045
+ contextManifestHash: sha2569(`${parentRunId}:${parentSessionId}`),
45285
46046
  canonicalContractVersion: "1.0.0",
45286
46047
  parentModel: { providerId: parent.providerId, modelId: parent.modelId },
45287
46048
  parentPolicy: parentShellPolicy(),
@@ -45292,7 +46053,7 @@ function createSpawnSubagentTool(deps) {
45292
46053
  const attemptId = idSeq();
45293
46054
  const branchId = idSeq();
45294
46055
  const reservationId = idSeq();
45295
- const artifactHash = sha2568(task);
46056
+ const artifactHash = sha2569(task);
45296
46057
  const spawned = spawnSubagent({
45297
46058
  attempt: { attemptId, number: childSeq },
45298
46059
  branchId,
@@ -45343,7 +46104,32 @@ function createSpawnSubagentTool(deps) {
45343
46104
  ...builtinReadOnlyTools(cwd),
45344
46105
  ...builtinMetaprojectTools(cwd, makeKeryxRunner(cwd), createMetaprojectAdapter(cwd))
45345
46106
  ];
45346
- const provider = deps.makeProvider(runModel.provider, runModel.model, parent.baseUrl);
46107
+ let ephemeralDir;
46108
+ let openedChildSlate;
46109
+ let dispatchId = "";
46110
+ let closing = false;
46111
+ try {
46112
+ ephemeralDir = await mkdtemp(path138.join(tmpdir4(), "keryx-subagent-slate-"));
46113
+ dispatchId = path138.basename(ephemeralDir);
46114
+ openedChildSlate = await openSlate({
46115
+ dir: ephemeralDir,
46116
+ cwd,
46117
+ mintAttemptId: () => idSeq(),
46118
+ runtime: { provider: runModel.provider, model: runModel.model }
46119
+ });
46120
+ tools.push(slateWriteSeedTool(() => closing ? undefined : ephemeralDir, idSeq, clock));
46121
+ } catch {
46122
+ openedChildSlate = undefined;
46123
+ }
46124
+ let provider;
46125
+ try {
46126
+ provider = deps.makeProvider(runModel.provider, runModel.model, parent.baseUrl);
46127
+ } catch (cause) {
46128
+ if (ephemeralDir !== undefined) {
46129
+ await rm8(ephemeralDir, { recursive: true, force: true }).catch(() => {});
46130
+ }
46131
+ throw cause;
46132
+ }
45347
46133
  const childDeps = {
45348
46134
  provider,
45349
46135
  providerId: runModel.provider,
@@ -45417,8 +46203,63 @@ function createSpawnSubagentTool(deps) {
45417
46203
  maxToolCalls: childToolCalls
45418
46204
  });
45419
46205
  };
46206
+ const foldChildSlateAndCleanup = async (status) => {
46207
+ closing = true;
46208
+ if (ephemeralDir === undefined) {
46209
+ return;
46210
+ }
46211
+ const dirToClean = ephemeralDir;
46212
+ try {
46213
+ await withFileLock2(slateLockPath(dirToClean), async () => {
46214
+ try {
46215
+ const slateSession = deps.getSlateSession?.();
46216
+ if (openedChildSlate !== undefined && slateSession !== undefined) {
46217
+ try {
46218
+ const childSlate = await readSlate(dirToClean);
46219
+ const dispatch = {
46220
+ anchors: childSlate?.anchors ?? openedChildSlate.anchors,
46221
+ course: childSlate?.course ?? openedChildSlate.course,
46222
+ seeds: childSlate?.seeds ?? [],
46223
+ status
46224
+ };
46225
+ const parentDir = slateSession.dir;
46226
+ await writeSlate(parentDir, (prev) => {
46227
+ if (prev === undefined) {
46228
+ throw new Error(`foldChildSlateAndCleanup: no open parent slate in ${parentDir}`);
46229
+ }
46230
+ return {
46231
+ ...prev,
46232
+ childDispatches: { ...prev.childDispatches ?? {}, [dispatchId]: dispatch }
46233
+ };
46234
+ });
46235
+ } catch (foldCause) {
46236
+ const foldMsg = foldCause instanceof Error ? foldCause.message : String(foldCause);
46237
+ emitSubagentFleet({
46238
+ kind: "log",
46239
+ id: workerId,
46240
+ entry: { kind: "system", text: `slate fold failed (ignored): ${foldMsg}` }
46241
+ });
46242
+ }
46243
+ }
46244
+ } finally {
46245
+ await rm8(dirToClean, { recursive: true, force: true }).catch(() => {});
46246
+ }
46247
+ });
46248
+ } catch (lockCause) {
46249
+ const lockMsg = lockCause instanceof Error ? lockCause.message : String(lockCause);
46250
+ emitSubagentFleet({
46251
+ kind: "log",
46252
+ id: workerId,
46253
+ entry: { kind: "system", text: `slate cleanup lock failed (ignored): ${lockMsg}` }
46254
+ });
46255
+ await rm8(dirToClean, { recursive: true, force: true }).catch(() => {});
46256
+ }
46257
+ };
45420
46258
  try {
45421
46259
  const history = [];
46260
+ if (openedChildSlate !== undefined) {
46261
+ history.push({ role: "user", content: renderAnchorsBlock(openedChildSlate.anchors), provenance: "project" });
46262
+ }
45422
46263
  const userLine = `## Subagent task (${mode})
45423
46264
  ` + `${task}
45424
46265
 
@@ -45442,6 +46283,7 @@ function createSpawnSubagentTool(deps) {
45442
46283
  turn.catch(() => {});
45443
46284
  releaseBudget();
45444
46285
  emitSubagentFleet({ kind: "upsert", id: workerId, label, status: "failed", detail: "timeout", task });
46286
+ await foldChildSlateAndCleanup("incomplete");
45445
46287
  const partial = assistant.trim();
45446
46288
  return {
45447
46289
  output: `subagent ${label} (${workerId}) timed out after ${deadlineMs}ms and was abandoned ` + `(tighten or disable with ${ENV_SUBAGENT_TIMEOUT_MS})` + (partial.length > 0 ? `
@@ -45467,6 +46309,7 @@ ${boundSummary(partial)}` : ""),
45467
46309
  model: `${runModel.provider}/${runModel.model}`,
45468
46310
  task
45469
46311
  });
46312
+ await foldChildSlateAndCleanup("completed");
45470
46313
  return {
45471
46314
  output: `subagent ${label} (${workerId}) ${mode} via ${runModel.provider}/${runModel.model}
45472
46315
  ` + `MAE reservation: tools\u2264${spawned.reservation.maxToolCalls ?? maxToolCalls} ` + `runtime\u2264${spawned.reservation.maxRuntimeMs}ms children=${ledger.childCount}
@@ -45487,6 +46330,7 @@ ${boundSummary(folded.text)}`,
45487
46330
  detail: "error",
45488
46331
  task
45489
46332
  });
46333
+ await foldChildSlateAndCleanup("incomplete");
45490
46334
  return { output: `subagent ${label} failed: ${msg}`, isError: true };
45491
46335
  }
45492
46336
  }
@@ -45497,12 +46341,12 @@ ${boundSummary(folded.text)}`,
45497
46341
  import { homedir as homedir6 } from "os";
45498
46342
  var ESC = "\x1B";
45499
46343
  var CSI = `${ESC}[`;
45500
- function collapseHome(path136) {
46344
+ function collapseHome(path139) {
45501
46345
  const home = homedir6();
45502
- if (home.length > 0 && (path136 === home || path136.startsWith(`${home}/`))) {
45503
- return `~${path136.slice(home.length)}`;
46346
+ if (home.length > 0 && (path139 === home || path139.startsWith(`${home}/`))) {
46347
+ return `~${path139.slice(home.length)}`;
45504
46348
  }
45505
- return path136;
46349
+ return path139;
45506
46350
  }
45507
46351
 
45508
46352
  // src/lib/live-render.ts
@@ -45605,12 +46449,85 @@ class LiveMarkdownBlock {
45605
46449
  }
45606
46450
  }
45607
46451
 
46452
+ // src/commands/goal-command.ts
46453
+ function parseGoalArgs(rest) {
46454
+ const trimmed = rest.trim();
46455
+ if (trimmed.length === 0) {
46456
+ return { error: "a goal <text> is required, e.g. /goal implement the login flow [--workspace <id>]" };
46457
+ }
46458
+ const tokens = trimmed.split(/\s+/);
46459
+ const lastToken = tokens[tokens.length - 1];
46460
+ const secondLastToken = tokens[tokens.length - 2];
46461
+ let workspaceId;
46462
+ let textTokens = tokens;
46463
+ if (lastToken === "--workspace") {
46464
+ return { error: "--workspace requires a value, e.g. /goal <text> --workspace <id>" };
46465
+ }
46466
+ if (secondLastToken === "--workspace") {
46467
+ workspaceId = lastToken;
46468
+ textTokens = tokens.slice(0, tokens.length - 2);
46469
+ }
46470
+ const text = textTokens.join(" ").trim();
46471
+ if (text.length === 0) {
46472
+ return { error: "a goal <text> is required, e.g. /goal implement the login flow [--workspace <id>]" };
46473
+ }
46474
+ return workspaceId !== undefined ? { text, workspaceId } : { text };
46475
+ }
46476
+ function systemLine(io, text) {
46477
+ if (io.onSystem !== undefined) {
46478
+ io.onSystem(text);
46479
+ } else {
46480
+ io.write(text);
46481
+ }
46482
+ }
46483
+ async function runGoalCommand(params) {
46484
+ const { raw, cwd, io, deps, history, slateSession, mintAttemptId } = params;
46485
+ const parsed = parseGoalArgs(raw);
46486
+ if ("error" in parsed) {
46487
+ systemLine(io, `/goal: ${parsed.error}
46488
+ `);
46489
+ return;
46490
+ }
46491
+ if (parsed.workspaceId !== undefined) {
46492
+ const resolved = await resolveWorkspaceForActor(cwd, parsed.workspaceId);
46493
+ if (!resolved.ok) {
46494
+ systemLine(io, `/goal: --workspace "${parsed.workspaceId}" was rejected (${resolved.error.code}): ${resolved.error.message}. ` + `The slate was not opened and the goal was not run.
46495
+ `);
46496
+ return;
46497
+ }
46498
+ }
46499
+ if (slateSession !== undefined) {
46500
+ try {
46501
+ const wasOpened = slateSession.opened;
46502
+ await ensureSlateOpened(slateSession, mintAttemptId, { provider: deps.providerId, model: deps.modelId });
46503
+ if (!wasOpened && slateSession.opened) {
46504
+ const freshSlate = await readSlate(slateSession.dir);
46505
+ if (freshSlate !== undefined) {
46506
+ history.push({ role: "user", content: renderAnchorsBlock(freshSlate.anchors), provenance: "project" });
46507
+ io.onHistoryChange?.("tool");
46508
+ }
46509
+ }
46510
+ if (parsed.workspaceId !== undefined) {
46511
+ const workspaceId = parsed.workspaceId;
46512
+ await writeSlate(slateSession.dir, (prev) => {
46513
+ const base = prev ?? { anchors: { root: "", touched: [] }, course: {}, seeds: [] };
46514
+ return { ...base, workspaceId };
46515
+ });
46516
+ }
46517
+ } catch (err) {
46518
+ systemLine(io, `/goal: slate bookkeeping failed (ignored): ${err instanceof Error ? err.message : String(err)}
46519
+ `);
46520
+ }
46521
+ }
46522
+ await runAgentTurn(io, deps, history, parsed.text, slateSession !== undefined ? { slateSession, skipCloseTrigger: true } : {});
46523
+ }
46524
+
45608
46525
  // src/tui/tui-shell.ts
45609
46526
  import { spawnSync as spawnSync2 } from "child_process";
45610
46527
  // package.json
45611
46528
  var package_default = {
45612
46529
  name: "@mrciphersmith/keryx",
45613
- version: "0.2.38",
46530
+ version: "0.2.39",
45614
46531
  description: "Version-controlled project context for AI coding agents: code graph, architecture wiki, project memory, relevant tests, quality signals, and task flows.",
45615
46532
  private: false,
45616
46533
  publishConfig: {
@@ -46135,7 +47052,7 @@ function openFlows(otui, chrome, options) {
46135
47052
 
46136
47053
  // src/tui/inspector-sources.ts
46137
47054
  init_store2();
46138
- import { randomUUID as randomUUID17 } from "crypto";
47055
+ import { randomUUID as randomUUID19 } from "crypto";
46139
47056
  function workspaceFromManifest(manifest) {
46140
47057
  return {
46141
47058
  id: manifest.id,
@@ -46205,7 +47122,7 @@ async function loadInspectorWorkspaces(cwd) {
46205
47122
  policyRevision: "local-offline-v1"
46206
47123
  }
46207
47124
  });
46208
- const listed = await service4.list({ request: undefined, requestCorrelationId: randomUUID17() });
47125
+ const listed = await service4.list({ request: undefined, requestCorrelationId: randomUUID19() });
46209
47126
  return listed.map(workspaceFromManifest);
46210
47127
  } catch {
46211
47128
  return [];
@@ -46563,6 +47480,11 @@ var AGENT_SLASH_COMMANDS = [
46563
47480
  modes: AGENT_ONLY
46564
47481
  },
46565
47482
  { name: "/new", description: "Start a new session (old kept on disk)", modes: BOTH },
47483
+ {
47484
+ name: "/goal",
47485
+ description: "Deterministically start a goal \u2014 /goal <text> [--workspace <id>]",
47486
+ modes: AGENT_ONLY
47487
+ },
46566
47488
  { name: "/resume", description: "Resume a prior session in this project", modes: AGENT_ONLY },
46567
47489
  { name: "/sessions", description: "Open the session list and switch to one", modes: AGENT_ONLY },
46568
47490
  {
@@ -46839,7 +47761,7 @@ function showComposerChoice(otui, r, dock, request) {
46839
47761
  // src/lib/version-check.ts
46840
47762
  init_config_dir();
46841
47763
  init_fs();
46842
- import path136 from "path";
47764
+ import path139 from "path";
46843
47765
  var REGISTRY_URL = "https://registry.npmjs.org/@mrciphersmith%2Fkeryx/latest";
46844
47766
  var FIXED_INSTALL_COMMAND = "npm install -g @mrciphersmith/keryx@latest";
46845
47767
  var RESPONSE_BODY_LIMIT_BYTES = 64 * 1024;
@@ -47041,7 +47963,7 @@ async function checkVersion(options) {
47041
47963
  const now = options.now ?? Date.now;
47042
47964
  const timestamp = now();
47043
47965
  const configDir = ensureKeryxConfigDir(options.cacheDir);
47044
- const cacheFile = path136.join(configDir, "version-check.json");
47966
+ const cacheFile = path139.join(configDir, "version-check.json");
47045
47967
  const cache = parseCache(cacheFile);
47046
47968
  if (cache?.latestVersion !== undefined && cache.successAt !== undefined && timestamp - cache.successAt >= 0 && timestamp - cache.successAt < SUCCESS_CACHE_TTL_MS) {
47047
47969
  return resultFor(options.currentVersion, current, cache.latestVersion, "cache");
@@ -49252,6 +50174,22 @@ async function pickShellApproval(otui, r, dock, command, loadContext2, destructi
49252
50174
  }
49253
50175
  return "deny";
49254
50176
  }
50177
+ async function applyRuntimeSwitchToSlate(params) {
50178
+ if (params.slateSession === undefined || !params.slateSession.opened) {
50179
+ return false;
50180
+ }
50181
+ const result = await recordSlateTouch(params.slateSession.dir, [], { runtime: params.runtime });
50182
+ if (!result.changed) {
50183
+ return false;
50184
+ }
50185
+ params.history.push({
50186
+ role: "user",
50187
+ content: renderAnchorsBlock(result.slate.anchors),
50188
+ provenance: "project"
50189
+ });
50190
+ params.onHistoryChange?.("tool");
50191
+ return true;
50192
+ }
49255
50193
  function fmtTokens(n) {
49256
50194
  return n >= 1000 ? `${(n / 1000).toFixed(1)}K` : String(n);
49257
50195
  }
@@ -49687,7 +50625,7 @@ async function launchTuiAgentShell(opts) {
49687
50625
  }
49688
50626
  saveShellConfig(sel.baseUrl === undefined ? { provider: sel.provider, model: sel.model } : { provider: sel.provider, model: sel.model, baseUrl: sel.baseUrl });
49689
50627
  let currentSel = sel;
49690
- let deps = await opts.makeAgentDeps(sel);
50628
+ let deps = await opts.makeAgentDeps(sel, () => slateSession);
49691
50629
  const FOOTER_IDLE = "/ commands \xB7 Ctrl+O blocks \xB7 Ctrl+C to exit";
49692
50630
  const FOOTER_NAV = "blocks \xB7 \u2191/\u2193 move \xB7 Enter toggle \xB7 y copy \xB7 Esc exit";
49693
50631
  const chrome = await createShellChrome(otui, r, {
@@ -50277,7 +51215,7 @@ Staying in the current session.
50277
51215
  };
50278
51216
  const switchTo = async (ns) => {
50279
51217
  currentSel = ns;
50280
- deps = await opts.makeAgentDeps(ns);
51218
+ deps = await opts.makeAgentDeps(ns, () => slateSession);
50281
51219
  saveShellConfig(ns.baseUrl === undefined ? { provider: ns.provider, model: ns.model } : { provider: ns.provider, model: ns.model, baseUrl: ns.baseUrl });
50282
51220
  updateModelLabels();
50283
51221
  input2.focus();
@@ -50375,7 +51313,7 @@ Staying in the current session.
50375
51313
  });
50376
51314
  let answer = "";
50377
51315
  try {
50378
- const base = await opts.makeAgentDeps(currentSel);
51316
+ const base = await opts.makeAgentDeps(currentSel, () => slateSession);
50379
51317
  const tools = base.tools.filter((t) => t.definition.risk === "read");
50380
51318
  const sideDeps = {
50381
51319
  ...base,
@@ -50528,6 +51466,20 @@ Staying in the current session.
50528
51466
  })();
50529
51467
  return;
50530
51468
  }
51469
+ if (command.name === "/goal") {
51470
+ (async () => {
51471
+ await runGoalCommand({
51472
+ raw: line.slice(command.name.length).trim(),
51473
+ cwd: sessionCwd,
51474
+ io,
51475
+ deps,
51476
+ history,
51477
+ slateSession,
51478
+ mintAttemptId: mintTimestampAttemptId
51479
+ });
51480
+ })();
51481
+ return;
51482
+ }
50531
51483
  if (command.name === "/resume" || command.name === "/sessions") {
50532
51484
  resumeSessionInteractive();
50533
51485
  return;
@@ -50657,6 +51609,17 @@ Staying in the current session.
50657
51609
  const chosen = await chrome.withOverlay(() => pickModelInTui(otui, r, models));
50658
51610
  if (chosen !== undefined) {
50659
51611
  await switchTo(currentSel.baseUrl === undefined ? { provider: currentSel.provider, model: chosen } : { provider: currentSel.provider, model: chosen, baseUrl: currentSel.baseUrl });
51612
+ try {
51613
+ await applyRuntimeSwitchToSlate({
51614
+ slateSession,
51615
+ runtime: { provider: currentSel.provider, model: currentSel.model },
51616
+ history,
51617
+ onHistoryChange: io.onHistoryChange
51618
+ });
51619
+ } catch (err) {
51620
+ io.onSystem?.(`slate anchors update failed (ignored): ${err instanceof Error ? err.message : String(err)}
51621
+ `);
51622
+ }
50660
51623
  } else {
50661
51624
  input2.focus();
50662
51625
  }
@@ -51468,6 +52431,7 @@ var READLINE_AGENT_COMMANDS = [
51468
52431
  "/search-provider",
51469
52432
  "/search-connect",
51470
52433
  "/new",
52434
+ "/goal",
51471
52435
  "/clear",
51472
52436
  "/compact",
51473
52437
  "/status",
@@ -51990,7 +52954,7 @@ function formatUsage(usage) {
51990
52954
  }
51991
52955
  return style.dim(`${parts.join(" ")} tokens`);
51992
52956
  }
51993
- async function runAgentRepl(lines, rich, deps, metaprojectPort, sessionOpts) {
52957
+ async function runAgentRepl(lines, rich, deps, metaprojectPort, sessionOpts, slateSessionBox = { current: undefined }) {
51994
52958
  const out = (s) => {
51995
52959
  process.stdout.write(s);
51996
52960
  };
@@ -52238,6 +53202,7 @@ New session ${shortSessionId(live.summary.id)}.
52238
53202
  }
52239
53203
  }
52240
53204
  slateSession = live !== undefined ? { dir: live.dir, cwd: sessionCwd, opened: false } : undefined;
53205
+ slateSessionBox.current = slateSession;
52241
53206
  const save = () => {
52242
53207
  if (live === undefined) {
52243
53208
  return;
@@ -52344,6 +53309,7 @@ New session ${shortSessionId(live.summary.id)}.
52344
53309
  archive = [];
52345
53310
  nextArchiveIndex = 0;
52346
53311
  slateSession = { dir: live.dir, cwd: sessionCwd, opened: false };
53312
+ slateSessionBox.current = slateSession;
52347
53313
  agentIo.onSystem?.(`New session ${shortSessionId(live.summary.id)} (previous kept on disk)
52348
53314
  `);
52349
53315
  } else {
@@ -52432,6 +53398,16 @@ New session ${shortSessionId(live.summary.id)}.
52432
53398
  }
52433
53399
  agentIo.onSystem?.(`Search provider '${providerId}' selected.
52434
53400
  `);
53401
+ } else if (command === "/goal") {
53402
+ await runGoalCommand({
53403
+ raw: rest,
53404
+ cwd: sessionCwd,
53405
+ io: agentIo,
53406
+ deps,
53407
+ history,
53408
+ slateSession,
53409
+ mintAttemptId: mintTimestampAttemptId
53410
+ });
52435
53411
  } else {
52436
53412
  agentIo.onSystem?.(describeUnavailableCommand(command, "agent") ?? `Unknown command: ${command}. Type /help.
52437
53413
  `);
@@ -52566,7 +53542,8 @@ async function shellCommand(args2, runtime = {}) {
52566
53542
  const cwd = process.cwd();
52567
53543
  const tuiProviderFactory = realMakeProvider(() => {});
52568
53544
  const searchProviderController = createDefaultSearchProviderController();
52569
- const makeAgentDeps = async (sel) => {
53545
+ const makeAgentDeps = async (sel, getSlateSession) => {
53546
+ const getSessionDir = () => getSlateSession()?.dir;
52570
53547
  const agentProvider = tuiProviderFactory(sel.provider, sel.model, sel.baseUrl);
52571
53548
  let orient = "";
52572
53549
  try {
@@ -52589,7 +53566,8 @@ async function shellCommand(args2, runtime = {}) {
52589
53566
  names.add(d.name);
52590
53567
  }
52591
53568
  return [...names].map((name) => ({ name }));
52592
- }
53569
+ },
53570
+ getSlateSession
52593
53571
  });
52594
53572
  return {
52595
53573
  provider: agentProvider,
@@ -52599,14 +53577,15 @@ async function shellCommand(args2, runtime = {}) {
52599
53577
  cwd,
52600
53578
  metaprojectPort,
52601
53579
  searchController: searchProviderController,
52602
- spawnTool
53580
+ spawnTool,
53581
+ getSessionDir
52603
53582
  }),
52604
53583
  systemInstruction: buildAgentSystemInstruction(orient, {
52605
53584
  providerId: sel.provider,
52606
53585
  modelId: sel.model
52607
53586
  }),
52608
53587
  maxToolCalls: resolveAgentMaxToolCalls(),
52609
- idSeq: () => randomUUID18()
53588
+ idSeq: () => randomUUID20()
52610
53589
  };
52611
53590
  };
52612
53591
  const redetect = () => detectProviders({
@@ -52637,7 +53616,7 @@ async function shellCommand(args2, runtime = {}) {
52637
53616
  makeShellDeps: (sel) => ({
52638
53617
  makeProvider: chatFactory,
52639
53618
  clock: () => new Date().toISOString(),
52640
- idSeq: () => randomUUID18(),
53619
+ idSeq: () => randomUUID20(),
52641
53620
  initial: sel,
52642
53621
  session: {
52643
53622
  cwd,
@@ -52707,7 +53686,7 @@ async function shellCommand(args2, runtime = {}) {
52707
53686
  const deps = {
52708
53687
  makeProvider: baseFactory,
52709
53688
  clock: () => new Date().toISOString(),
52710
- idSeq: () => randomUUID18(),
53689
+ idSeq: () => randomUUID20(),
52711
53690
  initial: baseUrl2 === undefined ? { provider, model } : { provider, model, baseUrl: baseUrl2 },
52712
53691
  selectProviderModel: realSelectProviderModel(baseUrl2)
52713
53692
  };
@@ -52726,6 +53705,7 @@ async function shellCommand(args2, runtime = {}) {
52726
53705
  const metaprojectPort = createMetaprojectAdapter(process.cwd());
52727
53706
  const agentCwd = process.cwd();
52728
53707
  const searchProviderController = createDefaultSearchProviderController();
53708
+ const slateSessionBox = { current: undefined };
52729
53709
  const spawnTool = createSpawnSubagentTool({
52730
53710
  cwd: agentCwd,
52731
53711
  getParentModel: () => ({
@@ -52734,7 +53714,8 @@ async function shellCommand(args2, runtime = {}) {
52734
53714
  ...baseUrl2 !== undefined ? { baseUrl: baseUrl2 } : {}
52735
53715
  }),
52736
53716
  makeProvider: (providerId, modelId, childBaseUrl) => baseFactory(providerId, modelId, childBaseUrl ?? baseUrl2),
52737
- getDetectedProviders: () => [{ name: provider }]
53717
+ getDetectedProviders: () => [{ name: provider }],
53718
+ getSlateSession: () => slateSessionBox.current
52738
53719
  });
52739
53720
  const agentDeps = {
52740
53721
  provider: agentProvider,
@@ -52744,14 +53725,15 @@ async function shellCommand(args2, runtime = {}) {
52744
53725
  cwd: agentCwd,
52745
53726
  metaprojectPort,
52746
53727
  searchController: searchProviderController,
52747
- spawnTool
53728
+ spawnTool,
53729
+ getSessionDir: () => slateSessionBox.current?.dir
52748
53730
  }),
52749
53731
  systemInstruction: buildAgentSystemInstruction(orient, {
52750
53732
  providerId: provider,
52751
53733
  modelId: model
52752
53734
  }),
52753
53735
  maxToolCalls: resolveAgentMaxToolCalls(),
52754
- idSeq: () => randomUUID18()
53736
+ idSeq: () => randomUUID20()
52755
53737
  };
52756
53738
  let resumeId = flags.resumeId;
52757
53739
  if (flags.resumePick === true && resumeId === undefined) {
@@ -52761,7 +53743,7 @@ async function shellCommand(args2, runtime = {}) {
52761
53743
  cwd: process.cwd(),
52762
53744
  ...flags.continueLast === true ? { continueLast: true } : {},
52763
53745
  ...resumeId !== undefined ? { resumeId } : {}
52764
- });
53746
+ }, slateSessionBox);
52765
53747
  } else {
52766
53748
  let resumeId = flags.resumeId;
52767
53749
  if (flags.resumePick === true && resumeId === undefined) {
@@ -52922,7 +53904,7 @@ Shell:
52922
53904
  init_fs();
52923
53905
  import { readFile as readFile75 } from "fs/promises";
52924
53906
  import { stdin } from "process";
52925
- import path137 from "path";
53907
+ import path140 from "path";
52926
53908
  var MODULES = [
52927
53909
  { name: "gdgraph", flag: "--no-gdgraph", desc: "code graph, symbols, affected context", defaultEnabled: true },
52928
53910
  { name: "gdctx", flag: "--no-gdctx", desc: "token-aware command/read output", defaultEnabled: true },
@@ -52968,8 +53950,8 @@ async function modulesCommand(args2 = []) {
52968
53950
  return;
52969
53951
  }
52970
53952
  const wantsJson = args2.includes("--json") && (sub === undefined || sub === "status" || sub === "list" || sub === "--json");
52971
- const metaprojectRoot = path137.join(process.cwd(), ".metaproject");
52972
- const manifestPath = path137.join(metaprojectRoot, "metaproject.json");
53953
+ const metaprojectRoot = path140.join(process.cwd(), ".metaproject");
53954
+ const manifestPath = path140.join(metaprojectRoot, "metaproject.json");
52973
53955
  if (!await pathExists(manifestPath)) {
52974
53956
  if (wantsJson) {
52975
53957
  console.log(JSON.stringify({ schemaVersion: 1, error: "not-initialized", modules: [] }, null, 2));
@@ -53083,18 +54065,18 @@ function printHelp16() {
53083
54065
  }
53084
54066
 
53085
54067
  // src/commands/serve.ts
53086
- import { randomUUID as randomUUID21 } from "crypto";
54068
+ import { randomUUID as randomUUID23 } from "crypto";
53087
54069
 
53088
54070
  // src/lib/serve-config.ts
53089
54071
  init_config_dir();
53090
54072
  import { existsSync as existsSync26 } from "fs";
53091
- import path138 from "path";
54073
+ import path141 from "path";
53092
54074
  var SERVE_CONFIG_SCHEMA_VERSION = "1.0.0";
53093
54075
  var DEFAULT_SERVE_BIND_ADDRESS = "127.0.0.1";
53094
54076
  var DEFAULT_SERVE_PORT = 7377;
53095
54077
  var DEFAULT_SERVE_PROFILE = "remote-restricted";
53096
54078
  function serveConfigPath(dir) {
53097
- return path138.join(keryxConfigDir(dir), "serve.json");
54079
+ return path141.join(keryxConfigDir(dir), "serve.json");
53098
54080
  }
53099
54081
  function parseIpv4(value) {
53100
54082
  const parts = value.split(".");
@@ -53410,7 +54392,7 @@ function saveServeConfig(config, dir, onWarn) {
53410
54392
 
53411
54393
  // src/lib/serve-credential.ts
53412
54394
  init_config_dir();
53413
- import { createHash as createHash29, randomBytes as randomBytes2, randomUUID as randomUUID19 } from "crypto";
54395
+ import { createHash as createHash30, randomBytes as randomBytes2, randomUUID as randomUUID21 } from "crypto";
53414
54396
  import {
53415
54397
  chmodSync as chmodSync4,
53416
54398
  closeSync as closeSync3,
@@ -53422,9 +54404,9 @@ import {
53422
54404
  unlinkSync as unlinkSync3,
53423
54405
  writeFileSync as writeFileSync8
53424
54406
  } from "fs";
53425
- import path139 from "path";
54407
+ import path142 from "path";
53426
54408
  function serveCredentialPath(dir) {
53427
- return path139.join(keryxConfigDir(dir), "serve-credentials.json");
54409
+ return path142.join(keryxConfigDir(dir), "serve-credentials.json");
53428
54410
  }
53429
54411
  function constantTimeEqual(a, b) {
53430
54412
  const width = Math.max(a.length, b.length);
@@ -53437,10 +54419,10 @@ function constantTimeEqual(a, b) {
53437
54419
  return difference === 0;
53438
54420
  }
53439
54421
  function hashToken(salt, token) {
53440
- return createHash29("sha256").update(Buffer.from(salt, "hex")).update(Buffer.from(token, "utf8")).digest("hex");
54422
+ return createHash30("sha256").update(Buffer.from(salt, "hex")).update(Buffer.from(token, "utf8")).digest("hex");
53441
54423
  }
53442
54424
  function credentialFingerprint(record) {
53443
- return createHash29("sha256").update(`keryx-serve-fingerprint:${record.hash}`).digest("hex").slice(0, 8);
54425
+ return createHash30("sha256").update(`keryx-serve-fingerprint:${record.hash}`).digest("hex").slice(0, 8);
53444
54426
  }
53445
54427
  function isValidRecord(value) {
53446
54428
  if (typeof value !== "object" || value === null) {
@@ -53494,7 +54476,7 @@ function readServeCredential(dir) {
53494
54476
  }
53495
54477
  function writeStore(store, dir) {
53496
54478
  const file = serveCredentialPath(dir);
53497
- const temp = `${file}.${randomUUID19()}.tmp`;
54479
+ const temp = `${file}.${randomUUID21()}.tmp`;
53498
54480
  try {
53499
54481
  ensureKeryxConfigDir(dir);
53500
54482
  const handle = openSync3(temp, "wx", 384);
@@ -53534,7 +54516,7 @@ function mintRecord(now) {
53534
54516
  const salt = randomBytes2(32).toString("hex");
53535
54517
  return {
53536
54518
  token,
53537
- record: { id: randomUUID19(), algorithm: "sha256", salt, hash: hashToken(salt, token), createdAt: now }
54519
+ record: { id: randomUUID21(), algorithm: "sha256", salt, hash: hashToken(salt, token), createdAt: now }
53538
54520
  };
53539
54521
  }
53540
54522
  function issueServeToken(dir, now = () => new Date().toISOString(), onWaiting) {
@@ -53653,24 +54635,24 @@ class AuthFailureThrottle {
53653
54635
 
53654
54636
  // src/lib/serve-turn-store.ts
53655
54637
  init_config_dir();
53656
- import { createHash as createHash30 } from "crypto";
54638
+ import { createHash as createHash31 } from "crypto";
53657
54639
  import { existsSync as existsSync28, readdirSync as readdirSync2, rmSync as rmSync2 } from "fs";
53658
- import path140 from "path";
54640
+ import path143 from "path";
53659
54641
  var MAX_TURN_EVENTS = 1e4;
53660
54642
  function turnsRoot(dir) {
53661
- return path140.join(keryxConfigDir(dir), "turns");
54643
+ return path143.join(keryxConfigDir(dir), "turns");
53662
54644
  }
53663
54645
  function turnDir(turnId, dir) {
53664
- return path140.join(turnsRoot(dir), turnId);
54646
+ return path143.join(turnsRoot(dir), turnId);
53665
54647
  }
53666
54648
  function keyPath(project, idempotencyKey, dir) {
53667
54649
  const projectBytes = Buffer.byteLength(project, "utf8");
53668
- const digest2 = createHash30("sha256").update(`${projectBytes}:${project}\x00${idempotencyKey}`, "utf8").digest("hex");
53669
- return path140.join(turnsRoot(dir), "keys", `${digest2}.json`);
54650
+ const digest2 = createHash31("sha256").update(`${projectBytes}:${project}\x00${idempotencyKey}`, "utf8").digest("hex");
54651
+ return path143.join(turnsRoot(dir), "keys", `${digest2}.json`);
53670
54652
  }
53671
54653
  function legacyKeyPath(idempotencyKey, dir) {
53672
- const digest2 = createHash30("sha256").update(idempotencyKey, "utf8").digest("hex");
53673
- return path140.join(turnsRoot(dir), "keys", `${digest2}.json`);
54654
+ const digest2 = createHash31("sha256").update(idempotencyKey, "utf8").digest("hex");
54655
+ return path143.join(turnsRoot(dir), "keys", `${digest2}.json`);
53674
54656
  }
53675
54657
  function adoptLegacyClaim(project, idempotencyKey, dir) {
53676
54658
  const legacy = legacyKeyPath(idempotencyKey, dir);
@@ -53734,7 +54716,7 @@ function ensureTurnDir(turnId, dir) {
53734
54716
  }
53735
54717
  function createTurnRecord(record, dir) {
53736
54718
  ensureTurnDir(record.turnId, dir);
53737
- writeOwnerOnlyFile(path140.join(turnDir(record.turnId, dir), "turn.json"), `${JSON.stringify(record, null, 2)}
54719
+ writeOwnerOnlyFile(path143.join(turnDir(record.turnId, dir), "turn.json"), `${JSON.stringify(record, null, 2)}
53738
54720
  `);
53739
54721
  }
53740
54722
  function appendTurnEvent(event, dir, opts) {
@@ -53743,12 +54725,12 @@ function appendTurnEvent(event, dir, opts) {
53743
54725
  }
53744
54726
  const line = JSON.stringify(event);
53745
54727
  try {
53746
- appendOwnerOnlyLine(path140.join(turnDir(event.turnId, dir), "events.jsonl"), line);
54728
+ appendOwnerOnlyLine(path143.join(turnDir(event.turnId, dir), "events.jsonl"), line);
53747
54729
  } catch (error2) {
53748
54730
  if (error2?.code !== "ENOENT") {
53749
54731
  throw error2;
53750
54732
  }
53751
- appendOwnerOnlyLine(path140.join(ensureTurnDir(event.turnId, dir), "events.jsonl"), line);
54733
+ appendOwnerOnlyLine(path143.join(ensureTurnDir(event.turnId, dir), "events.jsonl"), line);
53752
54734
  }
53753
54735
  return true;
53754
54736
  }
@@ -53756,7 +54738,7 @@ function readTurnEvents(turnId, after = -1, dir) {
53756
54738
  if (!isTurnId(turnId)) {
53757
54739
  return { ok: false, reason: "not-a-turn-id" };
53758
54740
  }
53759
- const read = readTurnFile(path140.join(turnDir(turnId, dir), "events.jsonl"));
54741
+ const read = readTurnFile(path143.join(turnDir(turnId, dir), "events.jsonl"));
53760
54742
  if (!read.ok) {
53761
54743
  if (isDefiniteAbsence2(read.reason)) {
53762
54744
  return { ok: true, value: [] };
@@ -53784,7 +54766,7 @@ function readTurnRecord(turnId, dir) {
53784
54766
  if (!isTurnId(turnId)) {
53785
54767
  return { ok: false, reason: "not-a-turn-id" };
53786
54768
  }
53787
- const read = readTurnFile(path140.join(turnDir(turnId, dir), "turn.json"));
54769
+ const read = readTurnFile(path143.join(turnDir(turnId, dir), "turn.json"));
53788
54770
  if (!read.ok) {
53789
54771
  return { ok: false, reason: read.reason };
53790
54772
  }
@@ -53803,7 +54785,7 @@ function finishTurn(turnId, result, dir) {
53803
54785
  if (!record.ok) {
53804
54786
  return false;
53805
54787
  }
53806
- writeOwnerOnlyFile(path140.join(turnDir(turnId, dir), "turn.json"), `${JSON.stringify({ ...record.value, result }, null, 2)}
54788
+ writeOwnerOnlyFile(path143.join(turnDir(turnId, dir), "turn.json"), `${JSON.stringify({ ...record.value, result }, null, 2)}
53807
54789
  `);
53808
54790
  return true;
53809
54791
  }
@@ -53848,8 +54830,8 @@ function releaseIdempotencyKey(project, idempotencyKey, turnId, dir) {
53848
54830
  }
53849
54831
 
53850
54832
  // src/lib/serve-turn.ts
53851
- import { randomUUID as randomUUID20 } from "crypto";
53852
- import path141 from "path";
54833
+ import { randomUUID as randomUUID22 } from "crypto";
54834
+ import path144 from "path";
53853
54835
  init_service();
53854
54836
  var REMOTE_ORIGIN = "remote:http";
53855
54837
  var MAX_PROMPT_CHARS = 32000;
@@ -53918,9 +54900,9 @@ function isUuid(value) {
53918
54900
  return typeof value === "string" && /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/.test(value);
53919
54901
  }
53920
54902
  function resolveProject(declared, dir) {
53921
- const wanted = path141.resolve(declared);
54903
+ const wanted = path144.resolve(declared);
53922
54904
  for (const entry of listProjects(dir, () => {})) {
53923
- if (path141.resolve(entry.path) === wanted) {
54905
+ if (path144.resolve(entry.path) === wanted) {
53924
54906
  return { ok: true, project: entry.path };
53925
54907
  }
53926
54908
  }
@@ -53967,7 +54949,7 @@ async function redactOut(security, text) {
53967
54949
  async function runRemoteTurn(input2) {
53968
54950
  const scanRoot = input2.scanRoot;
53969
54951
  const security = createSecurityService(scanRoot);
53970
- const newId = input2.newId ?? (() => randomUUID20());
54952
+ const newId = input2.newId ?? (() => randomUUID22());
53971
54953
  const clock = input2.clock ?? (() => new Date().toISOString());
53972
54954
  const turnId = input2.turnId ?? newId();
53973
54955
  const sessionId = input2.request.sessionId ?? newId();
@@ -54110,7 +55092,7 @@ function outcomeOf(status, gate, unresolvedBlockerIds) {
54110
55092
  }
54111
55093
  function createSubmitTurn(deps) {
54112
55094
  return async (request, project) => {
54113
- const turnId = (deps.newId ?? (() => randomUUID20()))();
55095
+ const turnId = (deps.newId ?? (() => randomUUID22()))();
54114
55096
  const scanned = await scanPrompt(deps.dir, request.prompt);
54115
55097
  if (scanned.rejected) {
54116
55098
  return { kind: "rejected" };
@@ -54797,7 +55779,7 @@ function runConfig(args2) {
54797
55779
  return;
54798
55780
  }
54799
55781
  const credential2 = readServeCredential();
54800
- const credentialId = credential2.status === "ok" ? credential2.record.id : randomUUID21();
55782
+ const credentialId = credential2.status === "ok" ? credential2.record.id : randomUUID23();
54801
55783
  const config = defaultServeConfig(credentialId, {
54802
55784
  address: parsed.parsed.values.get("--bind") ?? DEFAULT_SERVE_BIND_ADDRESS,
54803
55785
  port: port ?? DEFAULT_SERVE_PORT,
@@ -54953,9 +55935,9 @@ function printHelp17() {
54953
55935
 
54954
55936
  // src/commands/update.ts
54955
55937
  import { spawn as spawn5 } from "child_process";
54956
- import { chmod as chmod4, mkdir as mkdir53, readFile as readFile76, readdir as readdir22, writeFile as writeFile47 } from "fs/promises";
55938
+ import { chmod as chmod4, mkdir as mkdir54, readFile as readFile76, readdir as readdir23, writeFile as writeFile47 } from "fs/promises";
54957
55939
  import { access as access4, constants as constants2, existsSync as existsSync29 } from "fs";
54958
- import path142 from "path";
55940
+ import path145 from "path";
54959
55941
  import { fileURLToPath as fileURLToPath7 } from "url";
54960
55942
  init_config();
54961
55943
  init_config2();
@@ -54970,8 +55952,8 @@ async function updateCommand(args2 = []) {
54970
55952
  return;
54971
55953
  }
54972
55954
  const projectRoot = process.cwd();
54973
- const metaprojectRoot = path142.join(projectRoot, ".metaproject");
54974
- banner("keryx update", `Refreshing the .metaproject workspace in ${path142.basename(projectRoot)}/`);
55955
+ const metaprojectRoot = path145.join(projectRoot, ".metaproject");
55956
+ banner("keryx update", `Refreshing the .metaproject workspace in ${path145.basename(projectRoot)}/`);
54975
55957
  if (!await pathExists(metaprojectRoot)) {
54976
55958
  console.log(` ${style.red(symbols.cross)} Metaproject is not initialized.`);
54977
55959
  console.log(` ${style.cyan(symbols.arrow)} Run ${style.cyan("keryx init")} first.`);
@@ -55017,12 +55999,12 @@ async function updateCommand(args2 = []) {
55017
55999
  nextSteps(steps);
55018
56000
  }
55019
56001
  async function refreshServiceFiles(projectRoot, options) {
55020
- const metaprojectRoot = path142.join(projectRoot, ".metaproject");
56002
+ const metaprojectRoot = path145.join(projectRoot, ".metaproject");
55021
56003
  const manifestState = await readManifest5(metaprojectRoot);
55022
56004
  const manifest = manifestState.manifest;
55023
56005
  const recoveredManifest = !manifestState.exists || !manifestState.valid;
55024
56006
  if (manifestState.migrated) {
55025
- await writeFile47(path142.join(metaprojectRoot, "metaproject.json"), `${JSON.stringify(manifest, null, 2)}
56007
+ await writeFile47(path145.join(metaprojectRoot, "metaproject.json"), `${JSON.stringify(manifest, null, 2)}
55026
56008
  `, "utf8");
55027
56009
  }
55028
56010
  const enableGdgraph = moduleEnabled2(manifest, "gdgraph");
@@ -55059,11 +56041,11 @@ async function refreshServiceFiles(projectRoot, options) {
55059
56041
  enableSecurity,
55060
56042
  enableSac
55061
56043
  });
55062
- await writeTextIfChanged4(path142.join(metaprojectRoot, "core", "README.md"), renderMetaprojectCoreReadme());
55063
- await writeTextIfChanged4(path142.join(metaprojectRoot, "hooks", "README.md"), renderHooksReadme());
55064
- await writeTextIfChanged4(path142.join(metaprojectRoot, "rules", "README.md"), renderProjectRulesReadme());
55065
- await writeTextIfChanged4(path142.join(metaprojectRoot, "skills", "project-rules", "README.md"), renderProjectRulesSkillReadme({ sources: ruleSources }));
55066
- await writeTextIfChanged4(path142.join(metaprojectRoot, "index.md"), renderIndexMarkdown({
56044
+ await writeTextIfChanged4(path145.join(metaprojectRoot, "core", "README.md"), renderMetaprojectCoreReadme());
56045
+ await writeTextIfChanged4(path145.join(metaprojectRoot, "hooks", "README.md"), renderHooksReadme());
56046
+ await writeTextIfChanged4(path145.join(metaprojectRoot, "rules", "README.md"), renderProjectRulesReadme());
56047
+ await writeTextIfChanged4(path145.join(metaprojectRoot, "skills", "project-rules", "README.md"), renderProjectRulesSkillReadme({ sources: ruleSources }));
56048
+ await writeTextIfChanged4(path145.join(metaprojectRoot, "index.md"), renderIndexMarkdown({
55067
56049
  enableGdgraph,
55068
56050
  enableGdctx,
55069
56051
  enableGdwiki,
@@ -55076,7 +56058,7 @@ async function refreshServiceFiles(projectRoot, options) {
55076
56058
  ruleSources,
55077
56059
  hasDistilledEntrypoints: await hasDistilledEntrypoints(metaprojectRoot)
55078
56060
  }));
55079
- await writeTextIfChanged4(path142.join(metaprojectRoot, "keryx-dashboard.html"), renderMetaprojectDashboardHtml({
56061
+ await writeTextIfChanged4(path145.join(metaprojectRoot, "keryx-dashboard.html"), renderMetaprojectDashboardHtml({
55080
56062
  enableGdgraph,
55081
56063
  enableGdctx,
55082
56064
  enableGdwiki,
@@ -55088,7 +56070,7 @@ async function refreshServiceFiles(projectRoot, options) {
55088
56070
  enableSecurity,
55089
56071
  data: dashboardData
55090
56072
  }));
55091
- await writeTextIfMissing4(path142.join(metaprojectRoot, "README.md"), renderMetaprojectReadme({
56073
+ await writeTextIfMissing4(path145.join(metaprojectRoot, "README.md"), renderMetaprojectReadme({
55092
56074
  enableGdgraph,
55093
56075
  enableGdctx,
55094
56076
  enableGdwiki,
@@ -55101,31 +56083,31 @@ async function refreshServiceFiles(projectRoot, options) {
55101
56083
  }));
55102
56084
  if (enableGdgraph) {
55103
56085
  await installGdgraphCoreScripts2(metaprojectRoot);
55104
- await writeTextIfChanged4(path142.join(metaprojectRoot, "modules", "gdgraph.md"), renderGdgraphManifest());
55105
- await writeTextIfChanged4(path142.join(metaprojectRoot, "core", "gdgraph", "README.md"), renderGdgraphCoreReadme());
55106
- await writeTextIfChanged4(path142.join(metaprojectRoot, "skills", "gdgraph", "SKILL.md"), renderGdgraphSkillReadme());
56086
+ await writeTextIfChanged4(path145.join(metaprojectRoot, "modules", "gdgraph.md"), renderGdgraphManifest());
56087
+ await writeTextIfChanged4(path145.join(metaprojectRoot, "core", "gdgraph", "README.md"), renderGdgraphCoreReadme());
56088
+ await writeTextIfChanged4(path145.join(metaprojectRoot, "skills", "gdgraph", "SKILL.md"), renderGdgraphSkillReadme());
55107
56089
  await seedAssetsLock(metaprojectRoot);
55108
56090
  if (manifest.modules?.gdgraph?.hooks?.gitPostCommit) {
55109
56091
  await installManagedHook2(projectRoot, "post-commit", "gdgraph-post-commit", renderGdgraphPostCommitHook());
55110
56092
  }
55111
56093
  }
55112
56094
  if (enableGdctx) {
55113
- await writeTextIfMissing4(path142.join(metaprojectRoot, "gdctx.config.json"), renderGdctxConfig());
55114
- await writeTextIfChanged4(path142.join(metaprojectRoot, "modules", "gdctx.md"), renderGdctxManifest());
55115
- await writeTextIfChanged4(path142.join(metaprojectRoot, "core", "gdctx", "README.md"), renderGdctxCoreReadme());
55116
- await writeTextIfChanged4(path142.join(metaprojectRoot, "skills", "gdctx", "SKILL.md"), renderGdctxSkillReadme());
56095
+ await writeTextIfMissing4(path145.join(metaprojectRoot, "gdctx.config.json"), renderGdctxConfig());
56096
+ await writeTextIfChanged4(path145.join(metaprojectRoot, "modules", "gdctx.md"), renderGdctxManifest());
56097
+ await writeTextIfChanged4(path145.join(metaprojectRoot, "core", "gdctx", "README.md"), renderGdctxCoreReadme());
56098
+ await writeTextIfChanged4(path145.join(metaprojectRoot, "skills", "gdctx", "SKILL.md"), renderGdctxSkillReadme());
55117
56099
  }
55118
56100
  if (enableGdwiki) {
55119
- await writeTextIfMissing4(path142.join(metaprojectRoot, "wiki", "templates", "page.md"), renderWikiPageTemplate());
55120
- await writeTextIfChanged4(path142.join(metaprojectRoot, "modules", "gdwiki.md"), renderGdwikiManifest());
55121
- await writeTextIfChanged4(path142.join(metaprojectRoot, "skills", "gdwiki", "SKILL.md"), renderGdwikiSkillReadme());
56101
+ await writeTextIfMissing4(path145.join(metaprojectRoot, "wiki", "templates", "page.md"), renderWikiPageTemplate());
56102
+ await writeTextIfChanged4(path145.join(metaprojectRoot, "modules", "gdwiki.md"), renderGdwikiManifest());
56103
+ await writeTextIfChanged4(path145.join(metaprojectRoot, "skills", "gdwiki", "SKILL.md"), renderGdwikiSkillReadme());
55122
56104
  if (manifest.modules?.gdgraph?.hooks?.gitPostCommit) {
55123
56105
  await installManagedHook2(projectRoot, "post-commit", "gdwiki-post-commit", renderGdwikiPostCommitHook());
55124
56106
  }
55125
56107
  }
55126
56108
  if (enableSac) {
55127
- await writeTextIfMissing4(path142.join(metaprojectRoot, "modules", "sac.md"), renderSacManifest());
55128
- await writeTextIfChanged4(path142.join(metaprojectRoot, "skills", "sac", "SKILL.md"), renderSacSkillReadme());
56109
+ await writeTextIfMissing4(path145.join(metaprojectRoot, "modules", "sac.md"), renderSacManifest());
56110
+ await writeTextIfChanged4(path145.join(metaprojectRoot, "skills", "sac", "SKILL.md"), renderSacSkillReadme());
55129
56111
  }
55130
56112
  if (enableGdskills) {
55131
56113
  await installGdskills(metaprojectRoot, gdskillsProfile, { createDataDirs: false });
@@ -55134,25 +56116,25 @@ async function refreshServiceFiles(projectRoot, options) {
55134
56116
  }
55135
56117
  }
55136
56118
  if (enableHealth) {
55137
- await writeTextIfMissing4(path142.join(metaprojectRoot, "health.config.json"), renderHealthConfig());
55138
- await writeTextIfChanged4(path142.join(metaprojectRoot, "modules", "health.md"), renderHealthManifest());
55139
- await writeTextIfChanged4(path142.join(metaprojectRoot, "core", "health", "README.md"), renderHealthCoreReadme());
55140
- await writeTextIfChanged4(path142.join(metaprojectRoot, "skills", "health", "SKILL.md"), renderHealthSkillReadme());
56119
+ await writeTextIfMissing4(path145.join(metaprojectRoot, "health.config.json"), renderHealthConfig());
56120
+ await writeTextIfChanged4(path145.join(metaprojectRoot, "modules", "health.md"), renderHealthManifest());
56121
+ await writeTextIfChanged4(path145.join(metaprojectRoot, "core", "health", "README.md"), renderHealthCoreReadme());
56122
+ await writeTextIfChanged4(path145.join(metaprojectRoot, "skills", "health", "SKILL.md"), renderHealthSkillReadme());
55141
56123
  if (manifest.modules?.health?.hooks?.gitPostCommit) {
55142
56124
  await installManagedHook2(projectRoot, "post-commit", "health-post-commit", renderHealthPostCommitHook());
55143
56125
  }
55144
56126
  }
55145
56127
  if (enableTesting) {
55146
- await writeTextIfMissing4(path142.join(metaprojectRoot, "testing.config.json"), renderTestingConfig({
56128
+ await writeTextIfMissing4(path145.join(metaprojectRoot, "testing.config.json"), renderTestingConfig({
55147
56129
  postCommitRefresh: Boolean(manifest.modules?.testing?.hooks?.gitPostCommit),
55148
56130
  prePushGate: Boolean(manifest.modules?.testing?.hooks?.prePush)
55149
56131
  }));
55150
- await writeTextIfChanged4(path142.join(metaprojectRoot, "modules", "testing.md"), renderTestingManifest());
55151
- await writeTextIfChanged4(path142.join(metaprojectRoot, "core", "testing", "README.md"), renderTestingCoreReadme());
55152
- await writeTextIfChanged4(path142.join(metaprojectRoot, "skills", "testing", "SKILL.md"), renderTestingSkillReadme());
56132
+ await writeTextIfChanged4(path145.join(metaprojectRoot, "modules", "testing.md"), renderTestingManifest());
56133
+ await writeTextIfChanged4(path145.join(metaprojectRoot, "core", "testing", "README.md"), renderTestingCoreReadme());
56134
+ await writeTextIfChanged4(path145.join(metaprojectRoot, "skills", "testing", "SKILL.md"), renderTestingSkillReadme());
55153
56135
  if (enableGdwiki) {
55154
- await writeTextIfMissing4(path142.join(metaprojectRoot, "wiki", "testing", "README.md"), renderTestingWikiReadme());
55155
- await writeTextIfMissing4(path142.join(metaprojectRoot, "wiki", "testing", "conventions.md"), renderTestingWikiConventions());
56136
+ await writeTextIfMissing4(path145.join(metaprojectRoot, "wiki", "testing", "README.md"), renderTestingWikiReadme());
56137
+ await writeTextIfMissing4(path145.join(metaprojectRoot, "wiki", "testing", "conventions.md"), renderTestingWikiConventions());
55156
56138
  }
55157
56139
  if (manifest.modules?.testing?.hooks?.gitPostCommit) {
55158
56140
  await installManagedHook2(projectRoot, "post-commit", "testing-post-commit", renderTestingPostCommitHook());
@@ -55165,24 +56147,24 @@ async function refreshServiceFiles(projectRoot, options) {
55165
56147
  await installManagedHook2(projectRoot, "post-commit", "metaproject-dashboard-post-commit", renderMetaprojectDashboardPostCommitHook());
55166
56148
  }
55167
56149
  if (enableMemory) {
55168
- await writeTextIfMissing4(path142.join(metaprojectRoot, "memory.config.json"), renderMemoryConfig());
55169
- await writeTextIfMissing4(path142.join(metaprojectRoot, "memory", "templates", "entry.md"), renderMemoryEntryTemplate());
55170
- await writeTextIfChanged4(path142.join(metaprojectRoot, "modules", "memory.md"), renderMemoryManifest());
55171
- await writeTextIfChanged4(path142.join(metaprojectRoot, "core", "memory", "README.md"), renderMemoryCoreReadme());
55172
- await writeTextIfChanged4(path142.join(metaprojectRoot, "skills", "memory", "SKILL.md"), renderMemorySkillReadme());
56150
+ await writeTextIfMissing4(path145.join(metaprojectRoot, "memory.config.json"), renderMemoryConfig());
56151
+ await writeTextIfMissing4(path145.join(metaprojectRoot, "memory", "templates", "entry.md"), renderMemoryEntryTemplate());
56152
+ await writeTextIfChanged4(path145.join(metaprojectRoot, "modules", "memory.md"), renderMemoryManifest());
56153
+ await writeTextIfChanged4(path145.join(metaprojectRoot, "core", "memory", "README.md"), renderMemoryCoreReadme());
56154
+ await writeTextIfChanged4(path145.join(metaprojectRoot, "skills", "memory", "SKILL.md"), renderMemorySkillReadme());
55173
56155
  }
55174
56156
  if (enableTasks) {
55175
- await writeTextIfChanged4(path142.join(metaprojectRoot, "flows", "README.md"), renderFlowsReadme());
55176
- await writeTextIfChanged4(path142.join(metaprojectRoot, "modules", "tasks.md"), renderTasksManifest());
55177
- await writeTextIfChanged4(path142.join(metaprojectRoot, "skills", "flow", "SKILL.md"), renderFlowSkillRouter());
55178
- await writeTextIfChanged4(path142.join(metaprojectRoot, "skills", "flow", "init.md"), renderFlowInitSkill());
55179
- await writeTextIfChanged4(path142.join(metaprojectRoot, "skills", "flow", "manage.md"), renderFlowManageSkill());
55180
- await writeTextIfChanged4(path142.join(metaprojectRoot, "skills", "flow", "complete.md"), renderFlowCompleteSkill());
56157
+ await writeTextIfChanged4(path145.join(metaprojectRoot, "flows", "README.md"), renderFlowsReadme());
56158
+ await writeTextIfChanged4(path145.join(metaprojectRoot, "modules", "tasks.md"), renderTasksManifest());
56159
+ await writeTextIfChanged4(path145.join(metaprojectRoot, "skills", "flow", "SKILL.md"), renderFlowSkillRouter());
56160
+ await writeTextIfChanged4(path145.join(metaprojectRoot, "skills", "flow", "init.md"), renderFlowInitSkill());
56161
+ await writeTextIfChanged4(path145.join(metaprojectRoot, "skills", "flow", "manage.md"), renderFlowManageSkill());
56162
+ await writeTextIfChanged4(path145.join(metaprojectRoot, "skills", "flow", "complete.md"), renderFlowCompleteSkill());
55181
56163
  }
55182
56164
  if (enableSecurity) {
55183
- await writeTextIfMissing4(path142.join(metaprojectRoot, "security.config.json"), renderSecurityConfig());
55184
- await writeTextIfChanged4(path142.join(metaprojectRoot, "modules", "security.md"), renderSecurityManifest());
55185
- await writeTextIfChanged4(path142.join(metaprojectRoot, "core", "security", "README.md"), renderSecurityCoreReadme());
56165
+ await writeTextIfMissing4(path145.join(metaprojectRoot, "security.config.json"), renderSecurityConfig());
56166
+ await writeTextIfChanged4(path145.join(metaprojectRoot, "modules", "security.md"), renderSecurityManifest());
56167
+ await writeTextIfChanged4(path145.join(metaprojectRoot, "core", "security", "README.md"), renderSecurityCoreReadme());
55186
56168
  if (manifest.modules?.security?.hooks?.prePush) {
55187
56169
  await installManagedHook2(projectRoot, "pre-push", "security-pre-push", renderSecurityPrePushHook());
55188
56170
  }
@@ -55233,13 +56215,13 @@ async function refreshServiceFiles(projectRoot, options) {
55233
56215
  };
55234
56216
  }
55235
56217
  async function buildDashboard(projectRoot = process.cwd()) {
55236
- const metaprojectRoot = path142.join(projectRoot, ".metaproject");
56218
+ const metaprojectRoot = path145.join(projectRoot, ".metaproject");
55237
56219
  if (!await pathExists(metaprojectRoot)) {
55238
56220
  throw new Error("Metaproject is not initialized. Run: keryx init");
55239
56221
  }
55240
56222
  const manifest = (await readManifest5(metaprojectRoot)).manifest;
55241
56223
  const data = await collectDashboardData(metaprojectRoot);
55242
- const dashboardPath = path142.join(metaprojectRoot, "keryx-dashboard.html");
56224
+ const dashboardPath = path145.join(metaprojectRoot, "keryx-dashboard.html");
55243
56225
  await writeTextIfChanged4(dashboardPath, renderMetaprojectDashboardHtml({
55244
56226
  enableGdgraph: moduleEnabled2(manifest, "gdgraph"),
55245
56227
  enableGdctx: moduleEnabled2(manifest, "gdctx"),
@@ -55259,7 +56241,7 @@ async function shouldInstallDashboardPostCommitHook(projectRoot, manifest) {
55259
56241
  if (Object.values(modules).some((module) => Boolean(module.hooks?.gitPostCommit))) {
55260
56242
  return true;
55261
56243
  }
55262
- const hookPath = path142.join(projectRoot, ".git", "hooks", "post-commit");
56244
+ const hookPath = path145.join(projectRoot, ".git", "hooks", "post-commit");
55263
56245
  if (!await pathExists(hookPath)) {
55264
56246
  return false;
55265
56247
  }
@@ -55279,11 +56261,11 @@ async function collectDashboardData(metaprojectRoot) {
55279
56261
  if (testing) {
55280
56262
  data.testing = testing;
55281
56263
  }
55282
- const wiki = await collectMarkdownPages(path142.join(metaprojectRoot, "wiki"), "wiki");
56264
+ const wiki = await collectMarkdownPages(path145.join(metaprojectRoot, "wiki"), "wiki");
55283
56265
  if (wiki.length > 0) {
55284
56266
  data.wiki = { pages: wiki };
55285
56267
  }
55286
- const memory = await collectMarkdownPages(path142.join(metaprojectRoot, "memory"), "memory");
56268
+ const memory = await collectMarkdownPages(path145.join(metaprojectRoot, "memory"), "memory");
55287
56269
  if (memory.length > 0) {
55288
56270
  data.memory = { entries: memory };
55289
56271
  }
@@ -55298,19 +56280,19 @@ async function collectDashboardData(metaprojectRoot) {
55298
56280
  return data;
55299
56281
  }
55300
56282
  async function collectTasksDashboardData(metaprojectRoot) {
55301
- const flowsRoot2 = path142.join(metaprojectRoot, "flows");
56283
+ const flowsRoot2 = path145.join(metaprojectRoot, "flows");
55302
56284
  if (!await pathExists(flowsRoot2)) {
55303
56285
  return null;
55304
56286
  }
55305
56287
  let dirEntries;
55306
56288
  try {
55307
- dirEntries = (await readdir22(flowsRoot2, { withFileTypes: true })).filter((entry) => entry.isDirectory() && /^\d{3}-/.test(entry.name)).map((entry) => entry.name).sort();
56289
+ dirEntries = (await readdir23(flowsRoot2, { withFileTypes: true })).filter((entry) => entry.isDirectory() && /^\d{3}-/.test(entry.name)).map((entry) => entry.name).sort();
55308
56290
  } catch {
55309
56291
  return null;
55310
56292
  }
55311
56293
  const flows = [];
55312
56294
  for (const dir of dirEntries) {
55313
- const flowPath = path142.join(flowsRoot2, dir, "flow.json");
56295
+ const flowPath = path145.join(flowsRoot2, dir, "flow.json");
55314
56296
  if (!await pathExists(flowPath)) {
55315
56297
  continue;
55316
56298
  }
@@ -55318,7 +56300,7 @@ async function collectTasksDashboardData(metaprojectRoot) {
55318
56300
  const flow = JSON.parse(await readFile76(flowPath, "utf8"));
55319
56301
  const tasks = Array.isArray(flow.tasks) ? flow.tasks : [];
55320
56302
  let acTotal = 0;
55321
- const acPath2 = path142.join(flowsRoot2, dir, "acceptance-criteria.md");
56303
+ const acPath2 = path145.join(flowsRoot2, dir, "acceptance-criteria.md");
55322
56304
  if (await pathExists(acPath2)) {
55323
56305
  const acContent = await readFile76(acPath2, "utf8");
55324
56306
  acTotal = (acContent.match(/^- AC\d+:/gm) ?? []).length;
@@ -55372,7 +56354,7 @@ async function collectDashboardDocs(metaprojectRoot, wiki, memory) {
55372
56354
  "data/testing/context.md"
55373
56355
  ];
55374
56356
  for (const href of staticHrefs) {
55375
- const filePath = path142.join(metaprojectRoot, ...href.split("/"));
56357
+ const filePath = path145.join(metaprojectRoot, ...href.split("/"));
55376
56358
  if (!await pathExists(filePath)) {
55377
56359
  continue;
55378
56360
  }
@@ -55389,7 +56371,7 @@ async function collectDashboardDocs(metaprojectRoot, wiki, memory) {
55389
56371
  return docs;
55390
56372
  }
55391
56373
  async function collectHealthDashboardData(metaprojectRoot) {
55392
- const reportPath2 = path142.join(metaprojectRoot, "data", "health", "artifacts", "latest.json");
56374
+ const reportPath2 = path145.join(metaprojectRoot, "data", "health", "artifacts", "latest.json");
55393
56375
  if (!await pathExists(reportPath2)) {
55394
56376
  return;
55395
56377
  }
@@ -55498,8 +56480,8 @@ function metricToScope(metric) {
55498
56480
  };
55499
56481
  }
55500
56482
  async function collectGraphDashboardData(metaprojectRoot) {
55501
- const nodesPath = path142.join(metaprojectRoot, "data", "gdgraph", "storage", "nodes.jsonl");
55502
- const edgesPath = path142.join(metaprojectRoot, "data", "gdgraph", "storage", "edges.jsonl");
56483
+ const nodesPath = path145.join(metaprojectRoot, "data", "gdgraph", "storage", "nodes.jsonl");
56484
+ const edgesPath = path145.join(metaprojectRoot, "data", "gdgraph", "storage", "edges.jsonl");
55503
56485
  if (!await pathExists(nodesPath) || !await pathExists(edgesPath)) {
55504
56486
  return;
55505
56487
  }
@@ -55550,8 +56532,8 @@ async function collectGraphDashboardData(metaprojectRoot) {
55550
56532
  };
55551
56533
  }
55552
56534
  async function collectTestingDashboardData(metaprojectRoot) {
55553
- const reportPath2 = path142.join(metaprojectRoot, "data", "testing", "artifacts", "latest.json");
55554
- const contextPath = path142.join(metaprojectRoot, "data", "testing", "context.md");
56535
+ const reportPath2 = path145.join(metaprojectRoot, "data", "testing", "artifacts", "latest.json");
56536
+ const contextPath = path145.join(metaprojectRoot, "data", "testing", "context.md");
55555
56537
  if (await pathExists(reportPath2)) {
55556
56538
  const report = JSON.parse(await readFile76(reportPath2, "utf8"));
55557
56539
  const totalTests = numberOrUndefined(report.total);
@@ -55580,7 +56562,7 @@ async function collectMarkdownPages(root, hrefPrefix) {
55580
56562
  const files = await listMarkdownFiles(root);
55581
56563
  const pages = [];
55582
56564
  for (const filePath of files.slice(0, 40)) {
55583
- const relativePath = path142.relative(root, filePath).split(path142.sep).join("/");
56565
+ const relativePath = path145.relative(root, filePath).split(path145.sep).join("/");
55584
56566
  if (relativePath === "index.md" || relativePath.startsWith("templates/")) {
55585
56567
  continue;
55586
56568
  }
@@ -55598,10 +56580,10 @@ async function collectMarkdownPages(root, hrefPrefix) {
55598
56580
  return pages;
55599
56581
  }
55600
56582
  async function listMarkdownFiles(root) {
55601
- const entries = await readdir22(root, { withFileTypes: true });
56583
+ const entries = await readdir23(root, { withFileTypes: true });
55602
56584
  const files = [];
55603
56585
  for (const entry of entries) {
55604
- const fullPath = path142.join(root, entry.name);
56586
+ const fullPath = path145.join(root, entry.name);
55605
56587
  if (entry.isDirectory()) {
55606
56588
  files.push(...await listMarkdownFiles(fullPath));
55607
56589
  } else if (entry.isFile() && entry.name.endsWith(".md")) {
@@ -55649,7 +56631,7 @@ async function writeRecoveredManifest(metaprojectRoot, modules) {
55649
56631
  const manifest = {
55650
56632
  schemaVersion: 1,
55651
56633
  standardVersion: STANDARD_VERSION,
55652
- name: `${path142.basename(path142.dirname(metaprojectRoot))}-metaproject`,
56634
+ name: `${path145.basename(path145.dirname(metaprojectRoot))}-metaproject`,
55653
56635
  createdBy: "keryx",
55654
56636
  profiles: computeProfiles(enabledModuleKeys2),
55655
56637
  paths: {
@@ -55750,11 +56732,11 @@ async function writeRecoveredManifest(metaprojectRoot, modules) {
55750
56732
  metaproject: ".metaproject/index.md"
55751
56733
  }
55752
56734
  };
55753
- await writeFile47(path142.join(metaprojectRoot, "metaproject.json"), `${JSON.stringify(manifest, null, 2)}
56735
+ await writeFile47(path145.join(metaprojectRoot, "metaproject.json"), `${JSON.stringify(manifest, null, 2)}
55754
56736
  `, "utf8");
55755
56737
  }
55756
56738
  async function enableTasksInManifest(metaprojectRoot) {
55757
- const manifestPath = path142.join(metaprojectRoot, "metaproject.json");
56739
+ const manifestPath = path145.join(metaprojectRoot, "metaproject.json");
55758
56740
  if (!await pathExists(manifestPath)) {
55759
56741
  return;
55760
56742
  }
@@ -55777,7 +56759,7 @@ async function enableTasksInManifest(metaprojectRoot) {
55777
56759
  `, "utf8");
55778
56760
  }
55779
56761
  async function updateManifestAgentEntrypoints(metaprojectRoot, ruleSources) {
55780
- const manifestPath = path142.join(metaprojectRoot, "metaproject.json");
56762
+ const manifestPath = path145.join(metaprojectRoot, "metaproject.json");
55781
56763
  if (!await pathExists(manifestPath)) {
55782
56764
  return;
55783
56765
  }
@@ -55813,80 +56795,80 @@ async function updateRuntime(projectRoot) {
55813
56795
  }
55814
56796
  }
55815
56797
  async function findRuntimeRoot(projectRoot) {
55816
- const projectRuntime = path142.join(projectRoot, ".metaproject", "runtime", "keryx");
55817
- if (await pathExists(path142.join(projectRuntime, ".git"))) {
56798
+ const projectRuntime = path145.join(projectRoot, ".metaproject", "runtime", "keryx");
56799
+ if (await pathExists(path145.join(projectRuntime, ".git"))) {
55818
56800
  return projectRuntime;
55819
56801
  }
55820
56802
  const home = process.env.HOME;
55821
56803
  if (!home) {
55822
56804
  return null;
55823
56805
  }
55824
- const globalRuntime = path142.join(home, ".keryx", "keryx");
55825
- if (await pathExists(path142.join(globalRuntime, ".git"))) {
56806
+ const globalRuntime = path145.join(home, ".keryx", "keryx");
56807
+ if (await pathExists(path145.join(globalRuntime, ".git"))) {
55826
56808
  return globalRuntime;
55827
56809
  }
55828
56810
  return null;
55829
56811
  }
55830
56812
  async function createServiceDirs(metaprojectRoot, modules) {
55831
56813
  const dirs = [
55832
- path142.join(metaprojectRoot, "core"),
55833
- path142.join(metaprojectRoot, "hooks", "post-update.d"),
55834
- path142.join(metaprojectRoot, "modules"),
55835
- path142.join(metaprojectRoot, "rules"),
55836
- path142.join(metaprojectRoot, "skills", "project-rules"),
56814
+ path145.join(metaprojectRoot, "core"),
56815
+ path145.join(metaprojectRoot, "hooks", "post-update.d"),
56816
+ path145.join(metaprojectRoot, "modules"),
56817
+ path145.join(metaprojectRoot, "rules"),
56818
+ path145.join(metaprojectRoot, "skills", "project-rules"),
55837
56819
  ...modules.enableGdgraph ? [
55838
- path142.join(metaprojectRoot, "core", "gdgraph"),
55839
- path142.join(metaprojectRoot, "skills", "gdgraph")
56820
+ path145.join(metaprojectRoot, "core", "gdgraph"),
56821
+ path145.join(metaprojectRoot, "skills", "gdgraph")
55840
56822
  ] : [],
55841
56823
  ...modules.enableGdctx ? [
55842
- path142.join(metaprojectRoot, "core", "gdctx"),
55843
- path142.join(metaprojectRoot, "skills", "gdctx")
56824
+ path145.join(metaprojectRoot, "core", "gdctx"),
56825
+ path145.join(metaprojectRoot, "skills", "gdctx")
55844
56826
  ] : [],
55845
56827
  ...modules.enableGdwiki ? [
55846
- path142.join(metaprojectRoot, "skills", "gdwiki"),
55847
- path142.join(metaprojectRoot, "wiki", "templates")
56828
+ path145.join(metaprojectRoot, "skills", "gdwiki"),
56829
+ path145.join(metaprojectRoot, "wiki", "templates")
55848
56830
  ] : [],
55849
56831
  ...modules.enableHealth ? [
55850
- path142.join(metaprojectRoot, "core", "health"),
55851
- path142.join(metaprojectRoot, "skills", "health")
56832
+ path145.join(metaprojectRoot, "core", "health"),
56833
+ path145.join(metaprojectRoot, "skills", "health")
55852
56834
  ] : [],
55853
56835
  ...modules.enableTesting ? [
55854
- path142.join(metaprojectRoot, "core", "testing"),
55855
- path142.join(metaprojectRoot, "skills", "testing")
56836
+ path145.join(metaprojectRoot, "core", "testing"),
56837
+ path145.join(metaprojectRoot, "skills", "testing")
55856
56838
  ] : [],
55857
56839
  ...modules.enableMemory ? [
55858
- path142.join(metaprojectRoot, "core", "memory"),
55859
- path142.join(metaprojectRoot, "skills", "memory"),
55860
- path142.join(metaprojectRoot, "memory", "templates")
56840
+ path145.join(metaprojectRoot, "core", "memory"),
56841
+ path145.join(metaprojectRoot, "skills", "memory"),
56842
+ path145.join(metaprojectRoot, "memory", "templates")
55861
56843
  ] : [],
55862
56844
  ...modules.enableTasks ? [
55863
- path142.join(metaprojectRoot, "flows"),
55864
- path142.join(metaprojectRoot, "skills", "flow")
56845
+ path145.join(metaprojectRoot, "flows"),
56846
+ path145.join(metaprojectRoot, "skills", "flow")
55865
56847
  ] : [],
55866
56848
  ...modules.enableSecurity ? [
55867
- path142.join(metaprojectRoot, "core", "security")
56849
+ path145.join(metaprojectRoot, "core", "security")
55868
56850
  ] : [],
55869
56851
  ...modules.enableSac ? [
55870
- path142.join(metaprojectRoot, "skills", "sac")
56852
+ path145.join(metaprojectRoot, "skills", "sac")
55871
56853
  ] : []
55872
56854
  ];
55873
- await Promise.all(dirs.map((dir) => mkdir53(dir, { recursive: true })));
56855
+ await Promise.all(dirs.map((dir) => mkdir54(dir, { recursive: true })));
55874
56856
  }
55875
56857
  async function installGdgraphCoreScripts2(metaprojectRoot) {
55876
- const gdgraphCoreRoot = path142.join(metaprojectRoot, "core", "gdgraph");
55877
- await mkdir53(gdgraphCoreRoot, { recursive: true });
56858
+ const gdgraphCoreRoot = path145.join(metaprojectRoot, "core", "gdgraph");
56859
+ await mkdir54(gdgraphCoreRoot, { recursive: true });
55878
56860
  for (const file of GDGRAPH_CORE_SOURCES) {
55879
- await copyFileIfChanged2(runtimeSourcePath2(`../gdgraph/${file}`), path142.join(gdgraphCoreRoot, file));
56861
+ await copyFileIfChanged2(runtimeSourcePath2(`../gdgraph/${file}`), path145.join(gdgraphCoreRoot, file));
55880
56862
  }
55881
- await writeTextIfChanged4(path142.join(gdgraphCoreRoot, "cli.ts"), renderGdgraphCoreCli());
56863
+ await writeTextIfChanged4(path145.join(gdgraphCoreRoot, "cli.ts"), renderGdgraphCoreCli());
55882
56864
  }
55883
56865
  async function installManagedHook2(projectRoot, hookName, blockId, content) {
55884
56866
  const hooksRoot = await resolveGitHooksRoot(projectRoot);
55885
56867
  if (!hooksRoot) {
55886
56868
  return;
55887
56869
  }
55888
- await mkdir53(hooksRoot, { recursive: true });
55889
- const hookPath = path142.join(hooksRoot, hookName);
56870
+ await mkdir54(hooksRoot, { recursive: true });
56871
+ const hookPath = path145.join(hooksRoot, hookName);
55890
56872
  const blockStart = `# keryx:${blockId}:begin`;
55891
56873
  const blockEnd = `# keryx:${blockId}:end`;
55892
56874
  const managedBlock = `${blockStart}
@@ -55907,7 +56889,7 @@ async function removeManagedHook2(projectRoot, hookName, blockId) {
55907
56889
  if (!hooksRoot) {
55908
56890
  return;
55909
56891
  }
55910
- const hookPath = path142.join(hooksRoot, hookName);
56892
+ const hookPath = path145.join(hooksRoot, hookName);
55911
56893
  if (!await pathExists(hookPath)) {
55912
56894
  return;
55913
56895
  }
@@ -55929,7 +56911,7 @@ async function prePushHasSecurityBlock2(projectRoot) {
55929
56911
  if (!hooksRoot) {
55930
56912
  return false;
55931
56913
  }
55932
- const hookPath = path142.join(hooksRoot, "pre-push");
56914
+ const hookPath = path145.join(hooksRoot, "pre-push");
55933
56915
  if (!await pathExists(hookPath)) {
55934
56916
  return false;
55935
56917
  }
@@ -55944,7 +56926,7 @@ async function agentSettingsHasSecuritySentinel2(projectRoot) {
55944
56926
  return (await readFile76(file, "utf8")).includes(AGENT_HOOKS_SENTINEL);
55945
56927
  }
55946
56928
  async function readManifest5(metaprojectRoot) {
55947
- const manifestPath = path142.join(metaprojectRoot, "metaproject.json");
56929
+ const manifestPath = path145.join(metaprojectRoot, "metaproject.json");
55948
56930
  if (!await pathExists(manifestPath)) {
55949
56931
  return {
55950
56932
  exists: false,
@@ -56013,7 +56995,7 @@ async function inferManifestFromExistingMetaproject(metaprojectRoot) {
56013
56995
  }
56014
56996
  async function anyPathExists(root, candidates) {
56015
56997
  for (const candidate of candidates) {
56016
- if (await pathExists(path142.join(root, candidate))) {
56998
+ if (await pathExists(path145.join(root, candidate))) {
56017
56999
  return true;
56018
57000
  }
56019
57001
  }
@@ -56034,13 +57016,13 @@ function parseUpdateArgs(args2) {
56034
57016
  };
56035
57017
  }
56036
57018
  async function runPostUpdateHooks(projectRoot) {
56037
- const hooksDir = path142.join(projectRoot, ".metaproject", "hooks", "post-update.d");
57019
+ const hooksDir = path145.join(projectRoot, ".metaproject", "hooks", "post-update.d");
56038
57020
  if (!await pathExists(hooksDir)) {
56039
57021
  return;
56040
57022
  }
56041
- const entries = (await readdir22(hooksDir)).sort();
57023
+ const entries = (await readdir23(hooksDir)).sort();
56042
57024
  for (const entry of entries) {
56043
- const hookPath = path142.join(hooksDir, entry);
57025
+ const hookPath = path145.join(hooksDir, entry);
56044
57026
  try {
56045
57027
  await accessExecutable(hookPath);
56046
57028
  } catch {
@@ -56081,14 +57063,14 @@ async function writeTextIfChanged4(filePath, content) {
56081
57063
  if (await pathExists(filePath) && await readFile76(filePath, "utf8") === content) {
56082
57064
  return;
56083
57065
  }
56084
- await mkdir53(path142.dirname(filePath), { recursive: true });
57066
+ await mkdir54(path145.dirname(filePath), { recursive: true });
56085
57067
  await writeFile47(filePath, content, "utf8");
56086
57068
  }
56087
57069
  async function writeTextIfMissing4(filePath, content) {
56088
57070
  if (await pathExists(filePath)) {
56089
57071
  return;
56090
57072
  }
56091
- await mkdir53(path142.dirname(filePath), { recursive: true });
57073
+ await mkdir54(path145.dirname(filePath), { recursive: true });
56092
57074
  await writeFile47(filePath, content, "utf8");
56093
57075
  }
56094
57076
  async function copyFileIfChanged2(from, to) {
@@ -56096,7 +57078,7 @@ async function copyFileIfChanged2(from, to) {
56096
57078
  if (await pathExists(to) && await readFile76(to, "utf8") === next) {
56097
57079
  return;
56098
57080
  }
56099
- await mkdir53(path142.dirname(to), { recursive: true });
57081
+ await mkdir54(path145.dirname(to), { recursive: true });
56100
57082
  await writeFile47(to, next, "utf8");
56101
57083
  }
56102
57084
  function runtimeSourcePath2(relativePath) {
@@ -56105,7 +57087,7 @@ function runtimeSourcePath2(relativePath) {
56105
57087
  return directPath;
56106
57088
  }
56107
57089
  if (relativePath.startsWith("../")) {
56108
- const packagedSourcePath = path142.join(path142.dirname(fileURLToPath7(import.meta.url)), "..", "src", relativePath.slice(3));
57090
+ const packagedSourcePath = path145.join(path145.dirname(fileURLToPath7(import.meta.url)), "..", "src", relativePath.slice(3));
56109
57091
  if (existsSync29(packagedSourcePath)) {
56110
57092
  return packagedSourcePath;
56111
57093
  }
@@ -56137,7 +57119,7 @@ function printHelp18() {
56137
57119
 
56138
57120
  // src/commands/dashboard.ts
56139
57121
  import { spawn as spawn6 } from "child_process";
56140
- import path143 from "path";
57122
+ import path146 from "path";
56141
57123
  init_args();
56142
57124
  async function dashboardCommand(args2 = []) {
56143
57125
  const options = parseOptions(args2);
@@ -56148,7 +57130,7 @@ async function dashboardCommand(args2 = []) {
56148
57130
  }
56149
57131
  if (subcommand === "build") {
56150
57132
  const result = await buildDashboard();
56151
- const rel = path143.relative(process.cwd(), result.path);
57133
+ const rel = path146.relative(process.cwd(), result.path);
56152
57134
  console.log(` ${style.green(symbols.ok)} Dashboard built ${style.cyan(symbols.arrow)} ${style.cyan(rel)}`);
56153
57135
  note(`Open it: keryx dashboard open`);
56154
57136
  return;
@@ -56156,7 +57138,7 @@ async function dashboardCommand(args2 = []) {
56156
57138
  if (subcommand === "open") {
56157
57139
  const result = await buildDashboard();
56158
57140
  await openFile(result.path);
56159
- const rel = path143.relative(process.cwd(), result.path);
57141
+ const rel = path146.relative(process.cwd(), result.path);
56160
57142
  console.log(` ${style.green(symbols.ok)} Opened ${style.cyan(rel)}`);
56161
57143
  return;
56162
57144
  }
@@ -56203,9 +57185,9 @@ function printHelp19() {
56203
57185
  import { readFileSync as readFileSync10 } from "fs";
56204
57186
 
56205
57187
  // src/agents/bootstrap.ts
56206
- import { mkdir as mkdir54, readFile as readFile77, writeFile as writeFile48 } from "fs/promises";
57188
+ import { mkdir as mkdir55, readFile as readFile77, writeFile as writeFile48 } from "fs/promises";
56207
57189
  import { homedir as homedir7 } from "os";
56208
- import path144 from "path";
57190
+ import path147 from "path";
56209
57191
  init_fs();
56210
57192
  var AGENT_BOOTSTRAP_START = "<!-- keryx:global-bootstrap -->";
56211
57193
  var AGENT_BOOTSTRAP_END = "<!-- /keryx:global-bootstrap -->";
@@ -56215,35 +57197,35 @@ var AGENT_BOOTSTRAP_RUNTIMES = [
56215
57197
  aliases: ["claude-code"],
56216
57198
  label: "Claude Code",
56217
57199
  fileName: "CLAUDE.md",
56218
- filePath: (homeRoot) => path144.join(homeRoot, ".claude", "CLAUDE.md")
57200
+ filePath: (homeRoot) => path147.join(homeRoot, ".claude", "CLAUDE.md")
56219
57201
  },
56220
57202
  {
56221
57203
  id: "opencode",
56222
57204
  aliases: ["open-code"],
56223
57205
  label: "OpenCode",
56224
57206
  fileName: "AGENTS.md",
56225
- filePath: (homeRoot) => path144.join(homeRoot, ".config", "opencode", "AGENTS.md")
57207
+ filePath: (homeRoot) => path147.join(homeRoot, ".config", "opencode", "AGENTS.md")
56226
57208
  },
56227
57209
  {
56228
57210
  id: "zcode",
56229
57211
  aliases: ["zed", "zed-code"],
56230
57212
  label: "ZCode",
56231
57213
  fileName: "AGENTS.md",
56232
- filePath: (homeRoot) => path144.join(homeRoot, ".zcode", "AGENTS.md")
57214
+ filePath: (homeRoot) => path147.join(homeRoot, ".zcode", "AGENTS.md")
56233
57215
  },
56234
57216
  {
56235
57217
  id: "codex",
56236
57218
  aliases: [],
56237
57219
  label: "Codex",
56238
57220
  fileName: "AGENTS.md",
56239
- filePath: (homeRoot) => path144.join(homeRoot, ".codex", "AGENTS.md")
57221
+ filePath: (homeRoot) => path147.join(homeRoot, ".codex", "AGENTS.md")
56240
57222
  },
56241
57223
  {
56242
57224
  id: "antigravity",
56243
57225
  aliases: ["antigravuty", "antigravity-code"],
56244
57226
  label: "Antigravity",
56245
57227
  fileName: "AGENTS.md",
56246
- filePath: (homeRoot) => path144.join(homeRoot, ".config", "antigravity", "AGENTS.md")
57228
+ filePath: (homeRoot) => path147.join(homeRoot, ".config", "antigravity", "AGENTS.md")
56247
57229
  }
56248
57230
  ];
56249
57231
  function agentBootstrapRuntimeIds() {
@@ -56291,7 +57273,7 @@ async function installAgentBootstrap(runtime, options = {}) {
56291
57273
  const dryRun = options.dryRun === true;
56292
57274
  const wrote = next !== current;
56293
57275
  if (wrote && !dryRun) {
56294
- await mkdir54(path144.dirname(filePath), { recursive: true });
57276
+ await mkdir55(path147.dirname(filePath), { recursive: true });
56295
57277
  await writeFile48(filePath, next, "utf8");
56296
57278
  }
56297
57279
  const status = dryRun ? statusFromContent(runtime, filePath, exists2, next) : await agentBootstrapStatus(runtime, homeRoot);
@@ -56639,7 +57621,7 @@ function printBootstrapHelp() {
56639
57621
  // src/commands/metrics.ts
56640
57622
  init_args();
56641
57623
  import { readFile as readFile78 } from "fs/promises";
56642
- import path146 from "path";
57624
+ import path149 from "path";
56643
57625
 
56644
57626
  // src/metrics/benchmark.ts
56645
57627
  var RELIABILITIES2 = new Set(["exact", "estimated", "unknown"]);
@@ -57409,8 +58391,8 @@ function buildContainmentManifest(inputs, options = {}) {
57409
58391
  }
57410
58392
 
57411
58393
  // src/metrics/oracle-runner.ts
57412
- import { mkdir as mkdir55, writeFile as writeFile49 } from "fs/promises";
57413
- import path145 from "path";
58394
+ import { mkdir as mkdir56, writeFile as writeFile49 } from "fs/promises";
58395
+ import path148 from "path";
57414
58396
 
57415
58397
  // src/metrics/ir.ts
57416
58398
  function toIdSet(ids) {
@@ -57852,9 +58834,9 @@ function buildEvidenceBundle(input2, options = {}) {
57852
58834
  async function persistEvidenceBundle(outDir, bundle, ladder = "metastore") {
57853
58835
  const safeTarget = bundle.target.replace(/[^A-Za-z0-9._/-]/g, "_");
57854
58836
  const safeCase = bundle.caseId.replace(/[^A-Za-z0-9._-]/g, "_");
57855
- const dir = path145.join(outDir, "bench", ladder, safeTarget, safeCase, bundle.variant, String(bundle.seed));
57856
- await mkdir55(dir, { recursive: true });
57857
- const write = (name, value) => writeFile49(path145.join(dir, name), `${JSON.stringify(value, null, 2)}
58837
+ const dir = path148.join(outDir, "bench", ladder, safeTarget, safeCase, bundle.variant, String(bundle.seed));
58838
+ await mkdir56(dir, { recursive: true });
58839
+ const write = (name, value) => writeFile49(path148.join(dir, name), `${JSON.stringify(value, null, 2)}
57858
58840
  `, "utf8");
57859
58841
  await Promise.all([
57860
58842
  write("inputs.json", bundle.inputs),
@@ -57894,7 +58876,7 @@ async function metricsCommand(args2 = [], projectRoot = process.cwd()) {
57894
58876
  console.log("# metrics status");
57895
58877
  console.log("");
57896
58878
  console.log(`root: ${root}`);
57897
- console.log(`enabled: ${await Bun.file(path146.join(projectRoot, ".metaproject", "metaproject.json")).exists() ? "yes" : "no"}`);
58879
+ console.log(`enabled: ${await Bun.file(path149.join(projectRoot, ".metaproject", "metaproject.json")).exists() ? "yes" : "no"}`);
57898
58880
  const latest2 = await readLatestPointer(root);
57899
58881
  console.log(`latest: ${latest2.status}`);
57900
58882
  return;
@@ -57906,7 +58888,7 @@ async function metricsCommand(args2 = [], projectRoot = process.cwd()) {
57906
58888
  process.exitCode = 1;
57907
58889
  return;
57908
58890
  }
57909
- const record2 = JSON.parse(await readFile78(path146.resolve(projectRoot, file), "utf8"));
58891
+ const record2 = JSON.parse(await readFile78(path149.resolve(projectRoot, file), "utf8"));
57910
58892
  const result = validateRunRecord(record2);
57911
58893
  console.log(result.valid ? "valid: yes" : "valid: no");
57912
58894
  for (const error2 of result.errors)
@@ -57931,7 +58913,7 @@ async function metricsCommand(args2 = [], projectRoot = process.cwd()) {
57931
58913
  process.exitCode = 1;
57932
58914
  return;
57933
58915
  }
57934
- const file = path146.join(metricsRoot(projectRoot), "runs", `${runId}.json`);
58916
+ const file = path149.join(metricsRoot(projectRoot), "runs", `${runId}.json`);
57935
58917
  if (!await Bun.file(file).exists()) {
57936
58918
  console.error(`Run not found: ${runId}`);
57937
58919
  process.exitCode = 1;
@@ -57948,8 +58930,8 @@ async function metricsCommand(args2 = [], projectRoot = process.cwd()) {
57948
58930
  process.exitCode = 1;
57949
58931
  return;
57950
58932
  }
57951
- const a = JSON.parse(await readFile78(path146.join(metricsRoot(projectRoot), "runs", `${runA}.json`), "utf8"));
57952
- const b = JSON.parse(await readFile78(path146.join(metricsRoot(projectRoot), "runs", `${runB}.json`), "utf8"));
58933
+ const a = JSON.parse(await readFile78(path149.join(metricsRoot(projectRoot), "runs", `${runA}.json`), "utf8"));
58934
+ const b = JSON.parse(await readFile78(path149.join(metricsRoot(projectRoot), "runs", `${runB}.json`), "utf8"));
57953
58935
  const comparison = compareExecutionRuns(a, b);
57954
58936
  console.log(stableJson(comparison));
57955
58937
  return;
@@ -57983,8 +58965,8 @@ async function metricsCommand(args2 = [], projectRoot = process.cwd()) {
57983
58965
  return;
57984
58966
  }
57985
58967
  const template = createPairedBenchmarkTemplate(taskIds);
57986
- await Bun.write(path146.resolve(projectRoot, out), stableJson(template));
57987
- console.log(`manifest: ${path146.relative(projectRoot, path146.resolve(projectRoot, out))}`);
58968
+ await Bun.write(path149.resolve(projectRoot, out), stableJson(template));
58969
+ console.log(`manifest: ${path149.relative(projectRoot, path149.resolve(projectRoot, out))}`);
57988
58970
  return;
57989
58971
  }
57990
58972
  if (subcommand === "benchmark" && args2[1] === "run") {
@@ -57998,7 +58980,7 @@ async function metricsCommand(args2 = [], projectRoot = process.cwd()) {
57998
58980
  process.exitCode = 1;
57999
58981
  return;
58000
58982
  }
58001
- const raw = JSON.parse(await readFile78(path146.resolve(projectRoot, file), "utf8"));
58983
+ const raw = JSON.parse(await readFile78(path149.resolve(projectRoot, file), "utf8"));
58002
58984
  const input2 = Array.isArray(raw) ? raw : raw.runs ?? [];
58003
58985
  const result = validatePairedBenchmark(input2);
58004
58986
  console.log(stableJson(result));
@@ -58010,7 +58992,7 @@ async function metricsCommand(args2 = [], projectRoot = process.cwd()) {
58010
58992
  process.exitCode = 1;
58011
58993
  }
58012
58994
  async function loadAffectedSets(projectRoot, file) {
58013
- const raw = JSON.parse(await readFile78(path146.resolve(projectRoot, file), "utf8"));
58995
+ const raw = JSON.parse(await readFile78(path149.resolve(projectRoot, file), "utf8"));
58014
58996
  const map = new Map;
58015
58997
  for (const entry of raw.targets ?? []) {
58016
58998
  if (typeof entry.target === "string")
@@ -58081,7 +59063,7 @@ async function runHarnessLayer(projectRoot, args2, ladder) {
58081
59063
  let tasks;
58082
59064
  let model;
58083
59065
  try {
58084
- const raw = JSON.parse(await readFile78(path146.resolve(projectRoot, resultsPath), "utf8"));
59066
+ const raw = JSON.parse(await readFile78(path149.resolve(projectRoot, resultsPath), "utf8"));
58085
59067
  tasks = raw.tasks ?? [];
58086
59068
  model = raw.model;
58087
59069
  } catch (error2) {
@@ -58112,7 +59094,7 @@ async function runSafetyCompletionHonestyLayer(projectRoot, args2, ladder) {
58112
59094
  let cases;
58113
59095
  let model;
58114
59096
  try {
58115
- const raw = JSON.parse(await readFile78(path146.resolve(projectRoot, resultsPath), "utf8"));
59097
+ const raw = JSON.parse(await readFile78(path149.resolve(projectRoot, resultsPath), "utf8"));
58116
59098
  cases = raw.cases ?? [];
58117
59099
  model = raw.model;
58118
59100
  } catch (error2) {
@@ -58137,7 +59119,7 @@ async function runSafetyFalsePremiseLayer(projectRoot, args2, ladder) {
58137
59119
  let cases;
58138
59120
  let model;
58139
59121
  try {
58140
- const raw = JSON.parse(await readFile78(path146.resolve(projectRoot, resultsPath), "utf8"));
59122
+ const raw = JSON.parse(await readFile78(path149.resolve(projectRoot, resultsPath), "utf8"));
58141
59123
  cases = raw.cases ?? [];
58142
59124
  model = raw.model;
58143
59125
  } catch (error2) {
@@ -58162,7 +59144,7 @@ async function runSafetyContainmentLayer(projectRoot, args2, ladder, caseClass)
58162
59144
  let cases;
58163
59145
  let model;
58164
59146
  try {
58165
- const raw = JSON.parse(await readFile78(path146.resolve(projectRoot, resultsPath), "utf8"));
59147
+ const raw = JSON.parse(await readFile78(path149.resolve(projectRoot, resultsPath), "utf8"));
58166
59148
  cases = raw.cases ?? [];
58167
59149
  model = raw.model;
58168
59150
  } catch (error2) {
@@ -58224,12 +59206,12 @@ async function runGdgraphLayer(projectRoot, args2, ladder) {
58224
59206
  }
58225
59207
  const manifests = buildOracleManifestsByGold(inputs, { ladder });
58226
59208
  if (outDir) {
58227
- const resolvedOut = path146.resolve(projectRoot, outDir);
59209
+ const resolvedOut = path149.resolve(projectRoot, outDir);
58228
59210
  for (const input2 of inputs) {
58229
59211
  for (const named of input2.golds) {
58230
59212
  const bundle = buildEvidenceBundle({ target: input2.target, system: input2.system, gold: named.gold }, { ladder, goldReference: goldPathFor(named.kind), timestamp: new Date().toISOString() });
58231
- const dir = await persistEvidenceBundle(path146.join(resolvedOut, named.kind), bundle, ladder);
58232
- console.error(`bundle[${named.kind}]: ${path146.relative(projectRoot, dir)}`);
59213
+ const dir = await persistEvidenceBundle(path149.join(resolvedOut, named.kind), bundle, ladder);
59214
+ console.error(`bundle[${named.kind}]: ${path149.relative(projectRoot, dir)}`);
58233
59215
  }
58234
59216
  }
58235
59217
  }
@@ -58253,7 +59235,7 @@ async function runGdgraphLayer(projectRoot, args2, ladder) {
58253
59235
  return allValid;
58254
59236
  }
58255
59237
  async function loadCoverageMap2(projectRoot, file) {
58256
- const raw = JSON.parse(await readFile78(path146.resolve(projectRoot, file), "utf8"));
59238
+ const raw = JSON.parse(await readFile78(path149.resolve(projectRoot, file), "utf8"));
58257
59239
  return raw.coverageMap ?? {};
58258
59240
  }
58259
59241
  async function runTestingLayer(projectRoot, args2, ladder) {
@@ -58290,7 +59272,7 @@ async function runTestingLayer(projectRoot, args2, ladder) {
58290
59272
  return result.valid;
58291
59273
  }
58292
59274
  async function loadMemoryGoldK(projectRoot, file) {
58293
- const raw = JSON.parse(await readFile78(path146.resolve(projectRoot, file), "utf8"));
59275
+ const raw = JSON.parse(await readFile78(path149.resolve(projectRoot, file), "utf8"));
58294
59276
  return typeof raw.k === "number" && raw.k > 0 ? raw.k : 3;
58295
59277
  }
58296
59278
  async function runMemoryLayer(projectRoot, args2, ladder) {
@@ -58327,11 +59309,11 @@ async function runMemoryLayer(projectRoot, args2, ladder) {
58327
59309
  return result.valid;
58328
59310
  }
58329
59311
  async function loadWikiGoldK(projectRoot, file) {
58330
- const raw = JSON.parse(await readFile78(path146.resolve(projectRoot, file), "utf8"));
59312
+ const raw = JSON.parse(await readFile78(path149.resolve(projectRoot, file), "utf8"));
58331
59313
  return typeof raw.k === "number" && raw.k > 0 ? raw.k : 5;
58332
59314
  }
58333
59315
  async function loadWikiGroundedness(projectRoot, file) {
58334
- const raw = JSON.parse(await readFile78(path146.resolve(projectRoot, file), "utf8"));
59316
+ const raw = JSON.parse(await readFile78(path149.resolve(projectRoot, file), "utf8"));
58335
59317
  const map = new Map;
58336
59318
  for (const entry of raw.targets ?? []) {
58337
59319
  if (typeof entry.target !== "string" || !Array.isArray(entry.scores) || entry.scores.length !== 3)
@@ -58387,7 +59369,7 @@ async function runWikiLayer(projectRoot, args2, ladder) {
58387
59369
  return result.valid;
58388
59370
  }
58389
59371
  async function loadGdctxFacts(projectRoot, file) {
58390
- const raw = JSON.parse(await readFile78(path146.resolve(projectRoot, file), "utf8"));
59372
+ const raw = JSON.parse(await readFile78(path149.resolve(projectRoot, file), "utf8"));
58391
59373
  const inputs = [];
58392
59374
  for (const entry of raw.inputs ?? []) {
58393
59375
  if (typeof entry.input === "string") {
@@ -58425,7 +59407,7 @@ async function collect(projectRoot, args2) {
58425
59407
  process.exitCode = 1;
58426
59408
  return;
58427
59409
  }
58428
- const raw = JSON.parse(await readFile78(path146.resolve(projectRoot, eventFile), "utf8"));
59410
+ const raw = JSON.parse(await readFile78(path149.resolve(projectRoot, eventFile), "utf8"));
58429
59411
  const events2 = Array.isArray(raw) ? raw : raw.events;
58430
59412
  const startedAt = optionValue(args2, "--started-at") ?? events2[0]?.timestamp_utc ?? new Date().toISOString();
58431
59413
  const finishedAt = optionValue(args2, "--finished-at") ?? events2.at(-1)?.timestamp_utc ?? startedAt;
@@ -58441,11 +59423,11 @@ async function collect(projectRoot, args2) {
58441
59423
  parentRunId: optionValue(args2, "--parent-run-id") ?? null
58442
59424
  });
58443
59425
  const result = await writeRunArtifacts(metricsRoot(projectRoot), record2, { cwd: projectRoot });
58444
- console.log(`json: ${path146.relative(projectRoot, result.jsonPath)}`);
58445
- console.log(`markdown: ${path146.relative(projectRoot, result.markdownPath)}`);
59426
+ console.log(`json: ${path149.relative(projectRoot, result.jsonPath)}`);
59427
+ console.log(`markdown: ${path149.relative(projectRoot, result.markdownPath)}`);
58446
59428
  }
58447
59429
  function metricsRoot(projectRoot) {
58448
- return path146.join(projectRoot, ".metaproject", "data", "metrics");
59430
+ return path149.join(projectRoot, ".metaproject", "data", "metrics");
58449
59431
  }
58450
59432
  function printMetricsHelp() {
58451
59433
  console.log(`keryx metrics
@@ -58496,7 +59478,7 @@ async function versionCommand(args2, deps = {}) {
58496
59478
 
58497
59479
  // src/commands/workspace.ts
58498
59480
  init_args();
58499
- import { randomUUID as randomUUID22 } from "crypto";
59481
+ import { randomUUID as randomUUID25 } from "crypto";
58500
59482
  import { writeFile as writeFile50 } from "fs/promises";
58501
59483
 
58502
59484
  // src/sac/fwk-explain.ts
@@ -58544,6 +59526,136 @@ function formatFwkExplain(result) {
58544
59526
  `);
58545
59527
  }
58546
59528
 
59529
+ // src/sac/catch-up.ts
59530
+ init_fs();
59531
+ init_config_dir();
59532
+ import { randomUUID as randomUUID24 } from "crypto";
59533
+ import { readdir as readdir24 } from "fs/promises";
59534
+ import path150 from "path";
59535
+ async function buildCatchUp(input2) {
59536
+ const [proposals, sessionCategories] = await Promise.all([
59537
+ collectProposals(input2.cwd, input2.workspaceId),
59538
+ collectSessionCategories(input2.cwd)
59539
+ ]);
59540
+ return { proposals, ...sessionCategories };
59541
+ }
59542
+ async function collectProposals(cwd, workspaceId) {
59543
+ const authorizationServer = localWorkspaceAuthorizationServer();
59544
+ const actor = await authorizationServer.actorContextFor(undefined, randomUUID24());
59545
+ if (!actor)
59546
+ throw new Error("trusted ActorContext is required for catch-up");
59547
+ const proposalService = createLocalProposalLifecycleService(cwd);
59548
+ const groups = await proposalService.listVisibleProposedProposals(actor);
59549
+ const scoped = workspaceId === undefined ? groups : groups.filter((group) => group.workspace.id === workspaceId);
59550
+ const flattened = scoped.flatMap((group) => group.proposals.map((proposal) => ({ group, proposal })));
59551
+ return Promise.all(flattened.map(async ({ group, proposal }) => {
59552
+ const fresh = await proposalService.isEvidenceFresh(proposal, actor);
59553
+ return { type: "proposal", workspaceId: group.workspace.id, proposalId: proposal.id, fresh };
59554
+ }));
59555
+ }
59556
+ async function classifySession(session) {
59557
+ const dir = sessionDir(session.projectPath, session.id);
59558
+ if (await isLockHeld(slateLockPath(dir)))
59559
+ return;
59560
+ const terminalState = await readTerminalState(dir);
59561
+ if (terminalState !== undefined) {
59562
+ const workspaceId2 = (await safeReadSlate(dir))?.workspaceId;
59563
+ return { kind: "blocked", item: { type: "blocked", sessionId: session.id, ...workspaceId2 !== undefined ? { workspaceId: workspaceId2 } : {}, terminalState } };
59564
+ }
59565
+ const unboundCandidate = await readNewestUnboundCandidate(dir);
59566
+ if (unboundCandidate !== undefined) {
59567
+ return {
59568
+ kind: "unbound-candidate",
59569
+ item: { type: "unbound-candidate", sessionId: session.id, evidencePath: unboundCandidate.evidencePath, summary: unboundCandidate.summary }
59570
+ };
59571
+ }
59572
+ if (!await isSlateEngaged(dir))
59573
+ return;
59574
+ const workspaceId = (await safeReadSlate(dir))?.workspaceId;
59575
+ return { kind: "unknown", item: { type: "unknown", sessionId: session.id, ...workspaceId !== undefined ? { workspaceId } : {}, lastSeenAt: session.updatedAt } };
59576
+ }
59577
+ async function collectSessionCategories(cwd) {
59578
+ const classified = await Promise.all(listSessions(cwd).map((session) => classifySession(session)));
59579
+ const blocked2 = [];
59580
+ const unboundCandidates = [];
59581
+ const unknown = [];
59582
+ for (const category of classified) {
59583
+ if (category === undefined)
59584
+ continue;
59585
+ if (category.kind === "blocked")
59586
+ blocked2.push(category.item);
59587
+ else if (category.kind === "unbound-candidate")
59588
+ unboundCandidates.push(category.item);
59589
+ else
59590
+ unknown.push(category.item);
59591
+ }
59592
+ return { blocked: blocked2, unboundCandidates, unknown };
59593
+ }
59594
+ async function isSlateEngaged(dir) {
59595
+ if (await pathExists(path150.join(dir, "slate.json")))
59596
+ return true;
59597
+ if (await pathExists(path150.join(dir, "terminal-state.json")))
59598
+ return true;
59599
+ try {
59600
+ const entries = await readdir24(path150.join(dir, "slate-archive"));
59601
+ return entries.length > 0;
59602
+ } catch {
59603
+ return false;
59604
+ }
59605
+ }
59606
+ async function safeReadSlate(dir) {
59607
+ try {
59608
+ return await readSlate(dir);
59609
+ } catch {
59610
+ return;
59611
+ }
59612
+ }
59613
+ async function readTerminalState(dir) {
59614
+ const result = readConfigFile(path150.join(dir, "terminal-state.json"));
59615
+ if (!result.ok) {
59616
+ return;
59617
+ }
59618
+ try {
59619
+ return JSON.parse(result.text);
59620
+ } catch {
59621
+ return;
59622
+ }
59623
+ }
59624
+ async function readNewestUnboundCandidate(dir) {
59625
+ const archiveDir = path150.join(dir, "slate-archive");
59626
+ let entries;
59627
+ try {
59628
+ entries = (await readdir24(archiveDir)).filter((name) => name.endsWith("-unbound-candidate.json"));
59629
+ } catch {
59630
+ return;
59631
+ }
59632
+ entries.sort();
59633
+ for (let i = entries.length - 1;i >= 0; i--) {
59634
+ const evidencePath = path150.join(archiveDir, entries[i]);
59635
+ const result = readConfigFile(evidencePath);
59636
+ if (!result.ok) {
59637
+ continue;
59638
+ }
59639
+ try {
59640
+ const parsed = JSON.parse(result.text);
59641
+ if (parsed.recordType !== "unbound-candidate")
59642
+ continue;
59643
+ return { evidencePath, summary: summarizeUnboundCandidate(parsed.groups) };
59644
+ } catch {
59645
+ continue;
59646
+ }
59647
+ }
59648
+ return;
59649
+ }
59650
+ function summarizeUnboundCandidate(groups) {
59651
+ const safeGroups = groups ?? [];
59652
+ if (safeGroups.length === 0)
59653
+ return "no seeds captured";
59654
+ const seedCount = safeGroups.reduce((sum, group) => sum + (group.seeds?.length ?? 0), 0);
59655
+ const kinds = safeGroups.map((group) => typeof group.kind === "string" ? group.kind : "unknown").join(", ");
59656
+ return `${seedCount} untriaged seed(s) across ${safeGroups.length} kind(s) (${kinds})`;
59657
+ }
59658
+
58547
59659
  // src/commands/workspace.ts
58548
59660
  var PROPOSAL_KINDS = ["decision", "wiki-update", "memory-entry", "follow-up", "contract-change", "risk"];
58549
59661
  function service4() {
@@ -58564,14 +59676,14 @@ async function workspaceCommand(args2) {
58564
59676
  const component = optionValue(args2, "--component");
58565
59677
  if (!title)
58566
59678
  throw new Error("Usage: keryx workspace create --title <title> [--component <workspace-relative-ref>]");
58567
- const workspace = await service4().create({ request: undefined, requestCorrelationId: randomUUID22(), id: newWorkspaceId(), title, ...component ? { component: { kind: "component", uri: component } } : {} });
59679
+ const workspace = await service4().create({ request: undefined, requestCorrelationId: randomUUID25(), id: newWorkspaceId(), title, ...component ? { component: { kind: "component", uri: component } } : {} });
58568
59680
  console.log(JSON.stringify(workspace, null, 2));
58569
59681
  return;
58570
59682
  }
58571
59683
  if (subcommand === "list") {
58572
59684
  rejectUnknownOptions(args2.slice(1), new Set(["--include-archived"]));
58573
59685
  const includeArchived = booleanFlag(args2, "--include-archived");
58574
- console.log(JSON.stringify(await service4().list({ request: undefined, requestCorrelationId: randomUUID22(), includeArchived }), null, 2));
59686
+ console.log(JSON.stringify(await service4().list({ request: undefined, requestCorrelationId: randomUUID25(), includeArchived }), null, 2));
58575
59687
  return;
58576
59688
  }
58577
59689
  if (subcommand === "show") {
@@ -58579,7 +59691,7 @@ async function workspaceCommand(args2) {
58579
59691
  const id = args2[1];
58580
59692
  if (!id)
58581
59693
  throw new Error("Usage: keryx workspace show <workspace-id>");
58582
- console.log(JSON.stringify(await service4().show({ request: undefined, requestCorrelationId: randomUUID22(), workspaceId: id }), null, 2));
59694
+ console.log(JSON.stringify(await service4().show({ request: undefined, requestCorrelationId: randomUUID25(), workspaceId: id }), null, 2));
58583
59695
  return;
58584
59696
  }
58585
59697
  if (subcommand === "add-resource") {
@@ -58590,7 +59702,7 @@ async function workspaceCommand(args2) {
58590
59702
  const revision = optionValue(args2, "--revision");
58591
59703
  if (!workspaceId || !kind || !uri)
58592
59704
  throw new Error("Usage: keryx workspace add-resource <workspace-id> --kind <kind> --uri <workspace-relative-ref> [--revision <revision>]");
58593
- console.log(JSON.stringify(await service4().addResource({ request: undefined, requestCorrelationId: randomUUID22(), workspaceId, resource: { kind, uri, ...revision ? { revision } : {} } }), null, 2));
59705
+ console.log(JSON.stringify(await service4().addResource({ request: undefined, requestCorrelationId: randomUUID25(), workspaceId, resource: { kind, uri, ...revision ? { revision } : {} } }), null, 2));
58594
59706
  return;
58595
59707
  }
58596
59708
  if (subcommand === "archive") {
@@ -58598,7 +59710,7 @@ async function workspaceCommand(args2) {
58598
59710
  const workspaceId = args2[1];
58599
59711
  if (!workspaceId)
58600
59712
  throw new Error("Usage: keryx workspace archive <workspace-id>");
58601
- console.log(JSON.stringify(await service4().archive({ request: undefined, requestCorrelationId: randomUUID22(), workspaceId }), null, 2));
59713
+ console.log(JSON.stringify(await service4().archive({ request: undefined, requestCorrelationId: randomUUID25(), workspaceId }), null, 2));
58602
59714
  return;
58603
59715
  }
58604
59716
  if (subcommand === "remove-resource") {
@@ -58607,7 +59719,7 @@ async function workspaceCommand(args2) {
58607
59719
  const uri = optionValue(args2, "--uri");
58608
59720
  if (!workspaceId || !uri)
58609
59721
  throw new Error("Usage: keryx workspace remove-resource <workspace-id> --uri <workspace-relative-ref>");
58610
- console.log(JSON.stringify(await service4().removeResource({ request: undefined, requestCorrelationId: randomUUID22(), workspaceId, uri }), null, 2));
59722
+ console.log(JSON.stringify(await service4().removeResource({ request: undefined, requestCorrelationId: randomUUID25(), workspaceId, uri }), null, 2));
58611
59723
  return;
58612
59724
  }
58613
59725
  if (subcommand === "rename") {
@@ -58616,7 +59728,7 @@ async function workspaceCommand(args2) {
58616
59728
  const title = optionValue(args2, "--title");
58617
59729
  if (!workspaceId || !title)
58618
59730
  throw new Error("Usage: keryx workspace rename <workspace-id> --title <title>");
58619
- console.log(JSON.stringify(await service4().rename({ request: undefined, requestCorrelationId: randomUUID22(), workspaceId, title }), null, 2));
59731
+ console.log(JSON.stringify(await service4().rename({ request: undefined, requestCorrelationId: randomUUID25(), workspaceId, title }), null, 2));
58620
59732
  return;
58621
59733
  }
58622
59734
  if (subcommand === "overview") {
@@ -58628,7 +59740,7 @@ async function workspaceCommand(args2) {
58628
59740
  const maxTokens = Number(optionValue(args2, "--max-tokens") ?? "4096");
58629
59741
  if (!Number.isInteger(maxItems) || !Number.isInteger(maxTokens) || maxItems < 0 || maxTokens < 0)
58630
59742
  throw new Error("--max-items and --max-tokens must be non-negative integers");
58631
- const result = await createLocalFwkReadService(process.cwd()).overview({ workspaceId, request: undefined, requestCorrelationId: randomUUID22(), budget: { maxItems, maxTokens } });
59743
+ const result = await createLocalFwkReadService(process.cwd()).overview({ workspaceId, request: undefined, requestCorrelationId: randomUUID25(), budget: { maxItems, maxTokens } });
58632
59744
  const normalized = normalizeFwkResult(result);
58633
59745
  console.log(JSON.stringify(normalized, null, 2));
58634
59746
  if (args2.includes("--explain"))
@@ -58645,7 +59757,7 @@ async function workspaceCommand(args2) {
58645
59757
  const maxTokens = Number(optionValue(args2, "--max-tokens") ?? "4096");
58646
59758
  if (!Number.isInteger(maxItems) || !Number.isInteger(maxTokens) || maxItems < 0 || maxTokens < 0)
58647
59759
  throw new Error("--max-items and --max-tokens must be non-negative integers");
58648
- const result = await createLocalFwkReadService(process.cwd()).read({ workspaceId, itemId, request: undefined, requestCorrelationId: randomUUID22(), budget: { maxItems, maxTokens } });
59760
+ const result = await createLocalFwkReadService(process.cwd()).read({ workspaceId, itemId, request: undefined, requestCorrelationId: randomUUID25(), budget: { maxItems, maxTokens } });
58649
59761
  const normalized = normalizeFwkResult(result);
58650
59762
  console.log(JSON.stringify(normalized, null, 2));
58651
59763
  if (args2.includes("--explain"))
@@ -58668,12 +59780,12 @@ async function workspaceCommand(args2) {
58668
59780
  if (!session)
58669
59781
  throw new Error(`no session matching "${sessionRef}" in this project \u2014 use \`keryx sessions list\``);
58670
59782
  const { service: service5, wrapUpAuthority, authorizationServer } = createHarnessProposalLifecycleService(cwd, { workspaceId, ...note2 ? { note: note2 } : {} });
58671
- const requestCorrelationId = randomUUID22();
59783
+ const requestCorrelationId = randomUUID25();
58672
59784
  const actor = await authorizationServer.actorContextFor(undefined, requestCorrelationId);
58673
59785
  if (!actor)
58674
59786
  throw new Error("trusted ActorContext is required");
58675
59787
  const wrapUp = await wrapUpAuthority.issue({ actor, source: "session", sourceRef: sessionEvidenceRef(workspaceId, session.id) });
58676
- const proposal = await service5.create({ request: undefined, requestCorrelationId, workspaceId, id: `proposal-${randomUUID22().replace(/-/g, "").slice(0, 16)}`, proposalRevision, kind, wrapUp });
59788
+ const proposal = await service5.create({ request: undefined, requestCorrelationId, workspaceId, id: `proposal-${randomUUID25().replace(/-/g, "").slice(0, 16)}`, proposalRevision, kind, wrapUp });
58677
59789
  if (note2)
58678
59790
  await writeFile50(proposalNotePath(cwd, workspaceId, proposal.id), note2, "utf8");
58679
59791
  console.log(JSON.stringify(normalizeProposalLifecycleResult(proposal), null, 2));
@@ -58685,10 +59797,10 @@ async function workspaceCommand(args2) {
58685
59797
  const proposalId = args2[2];
58686
59798
  const decision = optionValue(args2, "--decision");
58687
59799
  const reason = optionValue(args2, "--reason");
58688
- const idempotencyKey = optionValue(args2, "--idempotency-key") ?? randomUUID22();
59800
+ const idempotencyKey = optionValue(args2, "--idempotency-key") ?? randomUUID25();
58689
59801
  if (!workspaceId || !proposalId || !decision)
58690
59802
  throw new Error("Usage: keryx workspace review <workspace-id> <proposal-id> --decision <accepted|rejected|dismissed> [--reason <reason>] [--idempotency-key <key>]");
58691
- const result = await createHarnessProposalLifecycleService(process.cwd(), { workspaceId }).service.review({ request: undefined, requestCorrelationId: randomUUID22(), workspaceId, proposalId, decision, idempotencyKey, interactive: true, ...reason ? { reason } : {} });
59803
+ const result = await createHarnessProposalLifecycleService(process.cwd(), { workspaceId }).service.review({ request: undefined, requestCorrelationId: randomUUID25(), workspaceId, proposalId, decision, idempotencyKey, interactive: true, ...reason ? { reason } : {} });
58692
59804
  console.log(JSON.stringify(normalizeProposalLifecycleResult(result), null, 2));
58693
59805
  return;
58694
59806
  }
@@ -58697,7 +59809,7 @@ async function workspaceCommand(args2) {
58697
59809
  const workspaceId = args2[1];
58698
59810
  if (!workspaceId)
58699
59811
  throw new Error("Usage: keryx workspace collaboration <workspace-id>");
58700
- console.log(JSON.stringify(await createLocalCollaborationService(process.cwd()).overview({ workspaceId, request: undefined, requestCorrelationId: randomUUID22() }), null, 2));
59812
+ console.log(JSON.stringify(await createLocalCollaborationService(process.cwd()).overview({ workspaceId, request: undefined, requestCorrelationId: randomUUID25() }), null, 2));
58701
59813
  return;
58702
59814
  }
58703
59815
  if (subcommand === "policy-readiness") {
@@ -58708,6 +59820,32 @@ async function workspaceCommand(args2) {
58708
59820
  process.exitCode = 1;
58709
59821
  return;
58710
59822
  }
59823
+ if (subcommand === "catch-up") {
59824
+ rejectUnknownOptions(args2.slice(1), new Set(["--workspace", "--json"]));
59825
+ const workspaceId = optionValue(args2, "--workspace");
59826
+ const report = await buildCatchUp({ cwd: process.cwd(), ...workspaceId ? { workspaceId } : {} });
59827
+ if (args2.includes("--json"))
59828
+ console.log(JSON.stringify(report, null, 2));
59829
+ else
59830
+ console.log(renderCatchUp(report));
59831
+ return;
59832
+ }
59833
+ if (subcommand === "list-proposals") {
59834
+ rejectUnknownOptions(args2.slice(2), new Set);
59835
+ const workspaceId = args2[1];
59836
+ const cwd = process.cwd();
59837
+ const authorizationServer = localWorkspaceAuthorizationServer();
59838
+ const actor = await authorizationServer.actorContextFor(undefined, randomUUID25());
59839
+ if (!actor)
59840
+ throw new Error("trusted ActorContext is required");
59841
+ if (workspaceId) {
59842
+ await service4().showForActor({ actorContext: actor, workspaceId });
59843
+ console.log(JSON.stringify(await createLocalProposalLifecycleService(cwd).listProposedProposals(workspaceId), null, 2));
59844
+ return;
59845
+ }
59846
+ console.log(JSON.stringify(await createLocalProposalLifecycleService(cwd).listVisibleProposedProposals(actor), null, 2));
59847
+ return;
59848
+ }
58711
59849
  throw new Error(`Unknown workspace command: ${subcommand}`);
58712
59850
  } catch (error2) {
58713
59851
  console.error(error2 instanceof Error ? error2.message : String(error2));
@@ -58752,7 +59890,29 @@ keryx workspace read <workspace-id> <item-id> [--max-items N] [--max-tokens N] [
58752
59890
  keryx workspace propose <workspace-id> --kind <` + PROPOSAL_KINDS.join("|") + `> --session <session-id> [--note <one-line note>]
58753
59891
  keryx workspace review <workspace-id> <proposal-id> --decision <accepted|rejected|dismissed> [--reason <reason>] [--idempotency-key <key>]
58754
59892
  keryx workspace collaboration <workspace-id>
58755
- keryx workspace policy-readiness`);
59893
+ keryx workspace policy-readiness
59894
+ keryx workspace catch-up [--workspace <workspace-id>] [--json]
59895
+ keryx workspace list-proposals [<workspace-id>]`);
59896
+ }
59897
+ function renderCatchUp(report) {
59898
+ const sections = [];
59899
+ sections.push(renderSection("Pending proposals", report.proposals, (item) => `- Accept, reject, or dismiss proposal ${item.proposalId} in workspace ${item.workspaceId}? ` + `Recommendation: ${item.fresh ? "evidence is fresh \u2014 review now (`keryx workspace review " + item.workspaceId + " " + item.proposalId + " --decision <accepted|rejected|dismissed>`)" : "evidence has drifted since this proposal was created \u2014 treat as stale, re-run wrap-up before deciding"}.`));
59900
+ sections.push(renderSection("Blocked sessions (stopped unattended)", report.blocked, (item) => `- Session ${item.sessionId} stopped unattended (${item.terminalState.reason}) at ${item.terminalState.occurredAt}. Resume it, or archive and move on? ` + `Recommendation: \`keryx shell -r ${item.sessionId}\` to resume and unblock it.`));
59901
+ sections.push(renderSection("Unbound candidates (wrap-up ran, no workspace bound)", report.unboundCandidates, (item) => `- Session ${item.sessionId} produced untriaged seeds with no workspace bound (${item.summary}). Bind to a workspace and propose, or discard? ` + `Recommendation: pick a workspace, then \`keryx workspace propose <workspace-id> --kind <kind> --session ${item.sessionId}\` (evidence: ${item.evidencePath}).`));
59902
+ sections.push(renderSection("Unknown (no resolution recorded)", report.unknown, (item) => `- Session ${item.sessionId} was last seen ${item.lastSeenAt} with no proposal, terminal state, or unbound-candidate artifact recorded. Investigate, or ignore? ` + `Recommendation: \`keryx sessions list\` / \`keryx shell -r ${item.sessionId}\` to see what happened.`));
59903
+ return sections.join(`
59904
+
59905
+ `);
59906
+ }
59907
+ function renderSection(title, items, describe) {
59908
+ const lines = [`== ${title} ==`];
59909
+ if (items.length === 0)
59910
+ lines.push("(none)");
59911
+ else
59912
+ for (const item of items)
59913
+ lines.push(describe(item));
59914
+ return lines.join(`
59915
+ `);
58756
59916
  }
58757
59917
 
58758
59918
  // src/cli.ts