@mrciphersmith/keryx 0.2.55 → 0.2.57

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 +1116 -633
  2. package/package.json +1 -1
package/dist/cli.js CHANGED
@@ -14422,101 +14422,66 @@ var init_slate_course = __esm(() => {
14422
14422
  unbound = { state: "unbound" };
14423
14423
  });
14424
14424
 
14425
- // src/sac/secure-resource-read.ts
14426
- import { constants } from "fs";
14427
- import path71 from "path";
14428
- import { dlopen, FFIType, ptr } from "bun:ffi";
14429
- function readWorkspaceFileNoFollow(workspaceRoot, absolutePath) {
14430
- const relativePath = path71.relative(workspaceRoot, absolutePath);
14431
- const components = relativePath.split(path71.sep);
14432
- if (!relativePath || path71.isAbsolute(relativePath) || components.some((component) => !component || component === "." || component === "..")) {
14433
- throw new Error("safe source path is not workspace-relative");
14434
- }
14435
- const libc = loadPosixLibrary();
14436
- if (!libc || !Number.isInteger(constants.O_DIRECTORY) || !Number.isInteger(constants.O_NOFOLLOW)) {
14437
- throw new Error("safe descriptor source reads are unavailable on this platform");
14438
- }
14439
- const directoryFlags = constants.O_RDONLY | constants.O_DIRECTORY | constants.O_NOFOLLOW;
14440
- const fileFlags = constants.O_RDONLY | constants.O_NOFOLLOW;
14441
- const opened = [];
14442
- try {
14443
- let parent = openAt(libc, atFdcwd, workspaceRoot, directoryFlags);
14444
- opened.push(parent);
14445
- for (const component of components.slice(0, -1)) {
14446
- parent = openAt(libc, parent, component, directoryFlags);
14447
- opened.push(parent);
14425
+ // src/sac/trusted-wrap-up.ts
14426
+ import { createHash as createHash8, randomUUID as randomUUID6 } from "crypto";
14427
+ function createTrustedWrapUpAuthority(input2) {
14428
+ const now = input2.now ?? (() => new Date);
14429
+ return Object.freeze({
14430
+ async issue(request) {
14431
+ const resolved = await input2.resolveExplicitWrapUp(request);
14432
+ if (!resolved.summary.trim() || resolved.evidence.length === 0 || new Date(resolved.expiresAt).getTime() <= now().getTime())
14433
+ throw new Error("invalid trusted wrap-up issuance");
14434
+ const provenance = Object.freeze({ id: `wrapup-${randomUUID6().replace(/-/g, "").slice(0, 16)}`, source: request.source, sourceRef: request.sourceRef, sourceRevision: resolved.sourceRevision, workspaceId: resolved.workspaceId, actorSubject: request.actor.subject, summaryDigest: digest(resolved.summary), evidence: resolved.evidence.map((item) => ({ ...item })), issuedAt: now().toISOString(), expiresAt: resolved.expiresAt });
14435
+ issued.add(provenance);
14436
+ return provenance;
14437
+ },
14438
+ verify(provenance, request) {
14439
+ if (!issued.has(provenance))
14440
+ return "untrusted";
14441
+ if (consumed.has(provenance))
14442
+ return "replayed";
14443
+ if (new Date(provenance.expiresAt).getTime() <= now().getTime())
14444
+ return "expired";
14445
+ if (provenance.workspaceId !== request.workspaceId || provenance.actorSubject !== request.actor.subject)
14446
+ return "mismatch";
14447
+ return "ok";
14448
+ },
14449
+ consume(provenance, request) {
14450
+ const result = this.verify(provenance, request);
14451
+ if (result === "ok")
14452
+ consumed.add(provenance);
14453
+ return result;
14448
14454
  }
14449
- const name = components.at(-1);
14450
- if (!name)
14451
- throw new Error("safe source path has no final component");
14452
- const file = openAt(libc, parent, name, fileFlags);
14453
- opened.push(file);
14454
- return readAll(libc, file);
14455
- } finally {
14456
- for (const fd of opened.reverse())
14457
- libc.symbols.close(fd);
14458
- libc.close();
14459
- }
14460
- }
14461
- function loadPosixLibrary() {
14462
- for (const candidate of libcCandidates) {
14463
- try {
14464
- return dlopen(candidate, {
14465
- openat: { args: [FFIType.i32, FFIType.cstring, FFIType.i32, FFIType.i32], returns: FFIType.i32 },
14466
- read: { args: [FFIType.i32, FFIType.ptr, FFIType.u64], returns: FFIType.i64 },
14467
- close: { args: [FFIType.i32], returns: FFIType.i32 }
14468
- });
14469
- } catch {}
14470
- }
14471
- return;
14472
- }
14473
- function openAt(libc, directoryFd, component, flags) {
14474
- const name = Buffer.from(`${component}\x00`);
14475
- const fd = libc.symbols.openat(directoryFd, name, flags, 0);
14476
- if (fd < 0)
14477
- throw new Error("safe source descriptor open failed");
14478
- return fd;
14455
+ });
14479
14456
  }
14480
- function readAll(libc, fd) {
14481
- const chunks = [];
14482
- for (;; ) {
14483
- const chunk = Buffer.allocUnsafe(64 * 1024);
14484
- const read = libc.symbols.read(fd, ptr(chunk), chunk.length);
14485
- if (read < 0n)
14486
- throw new Error("safe source descriptor read failed");
14487
- if (read === 0n)
14488
- return Buffer.concat(chunks);
14489
- const length = Number(read);
14490
- if (!Number.isSafeInteger(length) || length > chunk.length)
14491
- throw new Error("safe source descriptor read returned an invalid length");
14492
- chunks.push(chunk.subarray(0, length));
14493
- }
14457
+ function digest(value) {
14458
+ return createHash8("sha256").update(value).digest("hex");
14494
14459
  }
14495
- var atFdcwd, libcCandidates;
14496
- var init_secure_resource_read = __esm(() => {
14497
- atFdcwd = process.platform === "darwin" ? -2 : -100;
14498
- libcCandidates = process.platform === "darwin" ? ["/usr/lib/libSystem.B.dylib"] : process.platform === "linux" ? ["/lib/aarch64-linux-gnu/libc.so.6", "/lib/x86_64-linux-gnu/libc.so.6", "/lib64/libc.so.6"] : [];
14460
+ var issued, consumed;
14461
+ var init_trusted_wrap_up = __esm(() => {
14462
+ issued = new WeakSet;
14463
+ consumed = new WeakSet;
14499
14464
  });
14500
14465
 
14501
14466
  // src/sac/index.ts
14502
14467
  import { access as access2, readFile as readFile33, realpath as realpath2 } from "fs/promises";
14503
- import { createHash as createHash8 } from "crypto";
14504
- import path72 from "path";
14468
+ import { createHash as createHash9 } from "crypto";
14469
+ import path71 from "path";
14505
14470
  import { fileURLToPath as fileURLToPath4 } from "url";
14506
14471
  async function resolveSacNormativeSchemaPath(fileName, searchFrom = [fileURLToPath4(new URL(".", import.meta.url)), process.cwd()]) {
14507
14472
  const seen = new Set;
14508
14473
  for (const start of searchFrom) {
14509
- let dir = path72.resolve(start);
14474
+ let dir = path71.resolve(start);
14510
14475
  for (let i = 0;i < 10; i++) {
14511
14476
  if (seen.has(dir))
14512
14477
  break;
14513
14478
  seen.add(dir);
14514
- const candidate = path72.join(dir, NORMATIVE_SCHEMA_DIR, fileName);
14479
+ const candidate = path71.join(dir, NORMATIVE_SCHEMA_DIR, fileName);
14515
14480
  try {
14516
14481
  await access2(candidate);
14517
14482
  return candidate;
14518
14483
  } catch {}
14519
- const parent = path72.dirname(dir);
14484
+ const parent = path71.dirname(dir);
14520
14485
  if (parent === dir)
14521
14486
  break;
14522
14487
  dir = parent;
@@ -15178,15 +15143,15 @@ async function validateSacContract(input2) {
15178
15143
  return { valid: errors.length === 0, errors };
15179
15144
  }
15180
15145
  function hashSacRecord(value) {
15181
- return createHash8("sha256").update(typeof value === "string" ? value : JSON.stringify(value)).digest("hex");
15146
+ return createHash9("sha256").update(typeof value === "string" ? value : JSON.stringify(value)).digest("hex");
15182
15147
  }
15183
15148
  async function resolveWorkspaceReference(input2) {
15184
- if (!workspacePathPattern.test(input2.uri) || path72.isAbsolute(input2.uri) || /^[a-z][a-z0-9+.-]*:/i.test(input2.uri) || input2.uri.includes("\\"))
15149
+ if (!workspacePathPattern.test(input2.uri) || path71.isAbsolute(input2.uri) || /^[a-z][a-z0-9+.-]*:/i.test(input2.uri) || input2.uri.includes("\\"))
15185
15150
  throw new SacReferenceError("unsafe workspace reference");
15186
- const lexicalRoot = path72.resolve(input2.workspaceRoot);
15151
+ const lexicalRoot = path71.resolve(input2.workspaceRoot);
15187
15152
  const root = await realpath2(lexicalRoot);
15188
- const candidate = path72.resolve(lexicalRoot, input2.uri.slice(2));
15189
- if (candidate !== lexicalRoot && !candidate.startsWith(`${lexicalRoot}${path72.sep}`))
15153
+ const candidate = path71.resolve(lexicalRoot, input2.uri.slice(2));
15154
+ if (candidate !== lexicalRoot && !candidate.startsWith(`${lexicalRoot}${path71.sep}`))
15190
15155
  throw new SacReferenceError("workspace reference escapes root");
15191
15156
  let resolved;
15192
15157
  try {
@@ -15194,7 +15159,7 @@ async function resolveWorkspaceReference(input2) {
15194
15159
  } catch {
15195
15160
  throw new SacReferenceError("workspace reference is not resolvable");
15196
15161
  }
15197
- if (resolved !== root && !resolved.startsWith(`${root}${path72.sep}`))
15162
+ if (resolved !== root && !resolved.startsWith(`${root}${path71.sep}`))
15198
15163
  throw new SacReferenceError("workspace reference escapes root after realpath");
15199
15164
  return candidate;
15200
15165
  }
@@ -15261,7 +15226,7 @@ var init_sac = __esm(() => {
15261
15226
  "review-decision": "review-decision.schema.json"
15262
15227
  };
15263
15228
  normativeSchemas = new Map;
15264
- NORMATIVE_SCHEMA_DIR = path72.join("docs", "requirements", "shared-agent-context", "schemas");
15229
+ NORMATIVE_SCHEMA_DIR = path71.join("docs", "requirements", "shared-agent-context", "schemas");
15265
15230
  SacReferenceError = class SacReferenceError extends Error {
15266
15231
  code = "unsafe_workspace_reference";
15267
15232
  };
@@ -15269,9 +15234,85 @@ var init_sac = __esm(() => {
15269
15234
  roleRank = { viewer: 1, editor: 2, owner: 3 };
15270
15235
  });
15271
15236
 
15237
+ // src/sac/secure-resource-read.ts
15238
+ import { constants } from "fs";
15239
+ import path72 from "path";
15240
+ import { dlopen, FFIType, ptr } from "bun:ffi";
15241
+ function readWorkspaceFileNoFollow(workspaceRoot, absolutePath) {
15242
+ const relativePath = path72.relative(workspaceRoot, absolutePath);
15243
+ const components = relativePath.split(path72.sep);
15244
+ if (!relativePath || path72.isAbsolute(relativePath) || components.some((component) => !component || component === "." || component === "..")) {
15245
+ throw new Error("safe source path is not workspace-relative");
15246
+ }
15247
+ const libc = loadPosixLibrary();
15248
+ if (!libc || !Number.isInteger(constants.O_DIRECTORY) || !Number.isInteger(constants.O_NOFOLLOW)) {
15249
+ throw new Error("safe descriptor source reads are unavailable on this platform");
15250
+ }
15251
+ const directoryFlags = constants.O_RDONLY | constants.O_DIRECTORY | constants.O_NOFOLLOW;
15252
+ const fileFlags = constants.O_RDONLY | constants.O_NOFOLLOW;
15253
+ const opened = [];
15254
+ try {
15255
+ let parent = openAt(libc, atFdcwd, workspaceRoot, directoryFlags);
15256
+ opened.push(parent);
15257
+ for (const component of components.slice(0, -1)) {
15258
+ parent = openAt(libc, parent, component, directoryFlags);
15259
+ opened.push(parent);
15260
+ }
15261
+ const name = components.at(-1);
15262
+ if (!name)
15263
+ throw new Error("safe source path has no final component");
15264
+ const file = openAt(libc, parent, name, fileFlags);
15265
+ opened.push(file);
15266
+ return readAll(libc, file);
15267
+ } finally {
15268
+ for (const fd of opened.reverse())
15269
+ libc.symbols.close(fd);
15270
+ libc.close();
15271
+ }
15272
+ }
15273
+ function loadPosixLibrary() {
15274
+ for (const candidate of libcCandidates) {
15275
+ try {
15276
+ return dlopen(candidate, {
15277
+ openat: { args: [FFIType.i32, FFIType.cstring, FFIType.i32, FFIType.i32], returns: FFIType.i32 },
15278
+ read: { args: [FFIType.i32, FFIType.ptr, FFIType.u64], returns: FFIType.i64 },
15279
+ close: { args: [FFIType.i32], returns: FFIType.i32 }
15280
+ });
15281
+ } catch {}
15282
+ }
15283
+ return;
15284
+ }
15285
+ function openAt(libc, directoryFd, component, flags) {
15286
+ const name = Buffer.from(`${component}\x00`);
15287
+ const fd = libc.symbols.openat(directoryFd, name, flags, 0);
15288
+ if (fd < 0)
15289
+ throw new Error("safe source descriptor open failed");
15290
+ return fd;
15291
+ }
15292
+ function readAll(libc, fd) {
15293
+ const chunks = [];
15294
+ for (;; ) {
15295
+ const chunk = Buffer.allocUnsafe(64 * 1024);
15296
+ const read = libc.symbols.read(fd, ptr(chunk), chunk.length);
15297
+ if (read < 0n)
15298
+ throw new Error("safe source descriptor read failed");
15299
+ if (read === 0n)
15300
+ return Buffer.concat(chunks);
15301
+ const length = Number(read);
15302
+ if (!Number.isSafeInteger(length) || length > chunk.length)
15303
+ throw new Error("safe source descriptor read returned an invalid length");
15304
+ chunks.push(chunk.subarray(0, length));
15305
+ }
15306
+ }
15307
+ var atFdcwd, libcCandidates;
15308
+ var init_secure_resource_read = __esm(() => {
15309
+ atFdcwd = process.platform === "darwin" ? -2 : -100;
15310
+ libcCandidates = process.platform === "darwin" ? ["/usr/lib/libSystem.B.dylib"] : process.platform === "linux" ? ["/lib/aarch64-linux-gnu/libc.so.6", "/lib/x86_64-linux-gnu/libc.so.6", "/lib64/libc.so.6"] : [];
15311
+ });
15312
+
15272
15313
  // src/sac/workspace-service.ts
15273
15314
  import { mkdir as mkdir27, readdir as readdir9, readFile as readFile34 } from "fs/promises";
15274
- import { randomUUID as randomUUID6 } from "crypto";
15315
+ import { randomUUID as randomUUID7 } from "crypto";
15275
15316
  import path73 from "path";
15276
15317
 
15277
15318
  class WorkspaceService {
@@ -15579,7 +15620,7 @@ function localWorkspaceAuthorizationServer(subject = `user:local-${process.getui
15579
15620
  return createSacAuthorizationServer({ authenticateRequest: async () => ({ subject, authenticationMethod: "local-os", roleRevision: "local-os-v1" }) });
15580
15621
  }
15581
15622
  function newWorkspaceId() {
15582
- return `workspace-${randomUUID6().replace(/-/g, "").slice(0, 16)}`;
15623
+ return `workspace-${randomUUID7().replace(/-/g, "").slice(0, 16)}`;
15583
15624
  }
15584
15625
  async function resolveWorkspaceForActor(cwd, workspaceId) {
15585
15626
  const service = new WorkspaceService({
@@ -15588,7 +15629,7 @@ async function resolveWorkspaceForActor(cwd, workspaceId) {
15588
15629
  strictGuard: { mode: "strict", availability: "available", decision: "pass", policyRevision: "local-offline-v1" }
15589
15630
  });
15590
15631
  try {
15591
- const manifest = await service.show({ request: undefined, requestCorrelationId: randomUUID6(), workspaceId });
15632
+ const manifest = await service.show({ request: undefined, requestCorrelationId: randomUUID7(), workspaceId });
15592
15633
  return { ok: true, manifest };
15593
15634
  } catch (error2) {
15594
15635
  if (error2 instanceof WorkspaceServiceError) {
@@ -15614,47 +15655,6 @@ var init_workspace_service = __esm(() => {
15614
15655
  };
15615
15656
  });
15616
15657
 
15617
- // src/sac/trusted-wrap-up.ts
15618
- import { createHash as createHash9, randomUUID as randomUUID7 } from "crypto";
15619
- function createTrustedWrapUpAuthority(input2) {
15620
- const now = input2.now ?? (() => new Date);
15621
- return Object.freeze({
15622
- async issue(request) {
15623
- const resolved = await input2.resolveExplicitWrapUp(request);
15624
- if (!resolved.summary.trim() || resolved.evidence.length === 0 || new Date(resolved.expiresAt).getTime() <= now().getTime())
15625
- throw new Error("invalid trusted wrap-up issuance");
15626
- const provenance = Object.freeze({ id: `wrapup-${randomUUID7().replace(/-/g, "").slice(0, 16)}`, source: request.source, sourceRef: request.sourceRef, sourceRevision: resolved.sourceRevision, workspaceId: resolved.workspaceId, actorSubject: request.actor.subject, summaryDigest: digest(resolved.summary), evidence: resolved.evidence.map((item) => ({ ...item })), issuedAt: now().toISOString(), expiresAt: resolved.expiresAt });
15627
- issued.add(provenance);
15628
- return provenance;
15629
- },
15630
- verify(provenance, request) {
15631
- if (!issued.has(provenance))
15632
- return "untrusted";
15633
- if (consumed.has(provenance))
15634
- return "replayed";
15635
- if (new Date(provenance.expiresAt).getTime() <= now().getTime())
15636
- return "expired";
15637
- if (provenance.workspaceId !== request.workspaceId || provenance.actorSubject !== request.actor.subject)
15638
- return "mismatch";
15639
- return "ok";
15640
- },
15641
- consume(provenance, request) {
15642
- const result = this.verify(provenance, request);
15643
- if (result === "ok")
15644
- consumed.add(provenance);
15645
- return result;
15646
- }
15647
- });
15648
- }
15649
- function digest(value) {
15650
- return createHash9("sha256").update(value).digest("hex");
15651
- }
15652
- var issued, consumed;
15653
- var init_trusted_wrap_up = __esm(() => {
15654
- issued = new WeakSet;
15655
- consumed = new WeakSet;
15656
- });
15657
-
15658
15658
  // src/sac/guarded-owner-writer.ts
15659
15659
  import { createHash as createHash10 } from "crypto";
15660
15660
  function createGuardedOwnerWriter(input2) {
@@ -16208,290 +16208,16 @@ var init_store3 = __esm(() => {
16208
16208
  };
16209
16209
  });
16210
16210
 
16211
- // src/sac/machine-wrap-up.ts
16212
- import { createHash as createHash11, randomUUID as randomUUID9 } from "crypto";
16213
- import { execFile } from "child_process";
16214
- import { mkdir as mkdir28 } from "fs/promises";
16215
- import path75 from "path";
16216
- import { promisify } from "util";
16217
- function describeSource(source) {
16218
- return source === "parent" ? "parent" : `child:${source.childDispatchId}`;
16219
- }
16220
- function dedupedAttributedSeeds(slate) {
16221
- const seen = new Set;
16222
- const result = [];
16223
- const take = (seeds, source) => {
16224
- for (const seed of dedupeSeeds(seeds)) {
16225
- const key = seed.text.trim();
16226
- if (seen.has(key))
16227
- continue;
16228
- seen.add(key);
16229
- result.push({ text: seed.text, kind: seed.kind ?? "follow-up", source });
16230
- }
16231
- };
16232
- take(slate.seeds, "parent");
16233
- const childDispatches = slate.childDispatches ?? {};
16234
- for (const [dispatchId, dispatch] of Object.entries(childDispatches)) {
16235
- take(dispatch.seeds, { childDispatchId: dispatchId });
16236
- }
16237
- return result;
16238
- }
16239
- function groupSeedsByKind(slate) {
16240
- const map = new Map;
16241
- for (const seed of dedupedAttributedSeeds(slate)) {
16242
- const bucket = map.get(seed.kind);
16243
- if (bucket)
16244
- bucket.push(seed);
16245
- else
16246
- map.set(seed.kind, [seed]);
16247
- }
16248
- return map;
16249
- }
16250
- function sha256(value) {
16251
- return createHash11("sha256").update(value).digest("hex");
16252
- }
16253
- async function gitDiff(cwd) {
16254
- try {
16255
- const { stdout: stdout2 } = await execFileAsync("git", ["diff"], { cwd, maxBuffer: 16 * 1024 * 1024 });
16256
- return stdout2;
16257
- } catch {
16258
- return "";
16259
- }
16260
- }
16261
- function diffStatLine(diffText) {
16262
- if (diffText.trim().length === 0)
16263
- return "no working-tree changes";
16264
- const added = (diffText.match(/^\+(?!\+\+)/gm) ?? []).length;
16265
- const removed = (diffText.match(/^-(?!--)/gm) ?? []).length;
16266
- return `working-tree diff: +${added}/-${removed} line(s)`;
16267
- }
16268
- function courseStatusLine(course) {
16269
- if (course.state !== "bound")
16270
- return "flow: unbound";
16271
- return `flow ${course.flowRef.uri} snapshot=${course.flowRef.snapshot} completed=${course.completed.length} next=${course.next.length} blocked=${course.blocked.length}`;
16272
- }
16273
- function mechanicalSummary(diffText, course) {
16274
- return `Mechanical wrap-up summary (model turn unavailable or timed out):
16275
- ${diffStatLine(diffText)}
16276
- ${courseStatusLine(course)}`;
16277
- }
16278
- async function resolveMachineWrapUp(input2) {
16279
- const now = input2.now ?? (() => new Date);
16280
- const diffText = await gitDiff(input2.cwd);
16281
- const course = await readCourse(input2.cwd, input2.slate.course.flowRef);
16282
- const seedsForKind = dedupedAttributedSeeds(input2.slate).filter((seed) => seed.kind === input2.kind);
16283
- const flowSnapshotJson = `${JSON.stringify(course, null, 2)}
16284
- `;
16285
- const seedsJson = `${JSON.stringify(seedsForKind.map((seed) => ({ text: seed.text, source: describeSource(seed.source) })), null, 2)}
16286
- `;
16287
- const sourceRevision = sha256([diffText, flowSnapshotJson, seedsJson].join("\x00"));
16288
- const shortHash = sourceRevision.slice(0, 16);
16289
- 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.";
16290
- const user = `--- git diff ---
16291
- ${diffText.length > 0 ? diffText : "(no working-tree changes)"}
16292
-
16293
- ` + `--- flow snapshot ---
16294
- ${flowSnapshotJson}
16295
- ` + `--- seeds (${input2.kind}) ---
16296
- ${seedsJson}`;
16297
- let modelResult;
16298
- const modelTurnTimeoutMs = input2.modelTurnTimeoutMs ?? DEFAULT_MODEL_TURN_TIMEOUT_MS;
16299
- const turn = runModelTurn({
16300
- system,
16301
- user,
16302
- requestId: `machine-wrap-up-${shortHash}`,
16303
- ...input2.env !== undefined ? { env: input2.env } : {},
16304
- ...input2.providerFactory !== undefined ? { providerFactory: input2.providerFactory } : {}
16305
- }).then((result) => {
16306
- modelResult = result;
16307
- return "done";
16308
- });
16309
- let timer;
16310
- const expired = new Promise((resolve) => {
16311
- timer = setTimeout(() => resolve("timeout"), modelTurnTimeoutMs);
16312
- });
16313
- let raceOutcome;
16314
- try {
16315
- raceOutcome = await Promise.race([turn, expired]);
16316
- } finally {
16317
- if (timer !== undefined)
16318
- clearTimeout(timer);
16319
- }
16320
- let summary;
16321
- if (raceOutcome === "timeout") {
16322
- turn.catch(() => {});
16323
- summary = mechanicalSummary(diffText, course);
16324
- } else {
16325
- const result = modelResult;
16326
- if (result.text.trim().length === 0 && !result.credentialAvailable) {
16327
- return { ok: false, code: "no_credential" };
16328
- }
16329
- summary = result.text.trim().length > 0 ? result.text.trim() : mechanicalSummary(diffText, course);
16330
- }
16331
- const evidenceDir = path75.join(input2.cwd, ".metaproject", "workspaces", input2.workspaceId, "machine-evidence");
16332
- await mkdir28(evidenceDir, { recursive: true });
16333
- const diffFile = `${input2.kind}.${shortHash}.diff.txt`;
16334
- const flowFile = `${input2.kind}.${shortHash}.flow.json`;
16335
- const seedsFile = `${input2.kind}.${shortHash}.seeds.json`;
16336
- await writeFileAtomic(path75.join(evidenceDir, diffFile), diffText);
16337
- await writeFileAtomic(path75.join(evidenceDir, flowFile), flowSnapshotJson);
16338
- await writeFileAtomic(path75.join(evidenceDir, seedsFile), seedsJson);
16339
- const observedAt = now().toISOString();
16340
- const relBase = `./.metaproject/workspaces/${input2.workspaceId}/machine-evidence`;
16341
- const evidence = [
16342
- { kind: "diff", uri: `${relBase}/${diffFile}`, revision: sha256(diffText), observedAt },
16343
- { kind: "flow", uri: `${relBase}/${flowFile}`, revision: sha256(flowSnapshotJson), observedAt },
16344
- { kind: "seeds", uri: `${relBase}/${seedsFile}`, revision: sha256(seedsJson), observedAt }
16345
- ];
16346
- return {
16347
- ok: true,
16348
- resolution: {
16349
- workspaceId: input2.workspaceId,
16350
- sourceRevision,
16351
- summary,
16352
- evidence,
16353
- expiresAt: new Date(now().getTime() + WRAP_UP_TTL_MS).toISOString()
16354
- }
16355
- };
16356
- }
16357
- async function writeUnboundCandidateArtifact(dir, trigger, now, grouped, nonEmptyKinds) {
16358
- const archiveDir = path75.join(dir, "slate-archive");
16359
- await mkdir28(archiveDir, { recursive: true });
16360
- const nowIso2 = now().toISOString();
16361
- const filename = `${nowIso2.replace(/[:.]/g, "-")}-unbound-candidate.json`;
16362
- const content = {
16363
- recordType: "unbound-candidate",
16364
- trigger,
16365
- generatedAt: nowIso2,
16366
- groups: nonEmptyKinds.map((kind) => ({
16367
- kind,
16368
- seeds: (grouped.get(kind) ?? []).map((seed) => ({ text: seed.text, source: describeSource(seed.source) }))
16369
- }))
16370
- };
16371
- await writeFileAtomic(path75.join(archiveDir, filename), `${JSON.stringify(content, null, 2)}
16372
- `);
16373
- }
16374
- async function writeWrapUpOutcomeArtifact(dir, trigger, now, groups) {
16375
- try {
16376
- const archiveDir = path75.join(dir, "slate-archive");
16377
- await mkdir28(archiveDir, { recursive: true });
16378
- const nowIso2 = now().toISOString();
16379
- const filename = `${nowIso2.replace(/[:.]/g, "-")}-wrap-up-outcome.json`;
16380
- const content = { recordType: "wrap-up-outcome", trigger, generatedAt: nowIso2, groups };
16381
- await writeFileAtomic(path75.join(archiveDir, filename), `${JSON.stringify(content, null, 2)}
16382
- `);
16383
- } catch {}
16384
- }
16385
- async function proposeOneGroup(params) {
16386
- const wrapUpSource = params.wrapUpSource ?? "flow";
16387
- try {
16388
- const resolved = await resolveMachineWrapUp({
16389
- cwd: params.cwd,
16390
- workspaceId: params.workspaceId,
16391
- slate: params.slate,
16392
- kind: params.kind,
16393
- now: params.now,
16394
- ...params.env !== undefined ? { env: params.env } : {},
16395
- ...params.providerFactory !== undefined ? { providerFactory: params.providerFactory } : {},
16396
- ...params.modelTurnTimeoutMs !== undefined ? { modelTurnTimeoutMs: params.modelTurnTimeoutMs } : {}
16397
- });
16398
- if (!resolved.ok)
16399
- return { kind: params.kind, outcome: "no_credential" };
16400
- const flowEvidence = resolved.resolution.evidence.find((item) => item.kind === "flow");
16401
- const sourceRef = (flowEvidence ?? resolved.resolution.evidence[0]).uri;
16402
- const flowRef = params.slate.course.flowRef ?? "";
16403
- const dedupHash = sha256(`${params.workspaceId}:${flowRef}:${resolved.resolution.sourceRevision}:${params.kind}`);
16404
- const proposalId = `wrapup-${dedupHash.slice(0, 32)}`;
16405
- const wrapUpAuthority = createTrustedWrapUpAuthority({
16406
- now: params.now,
16407
- resolveExplicitWrapUp: async (request) => {
16408
- if (request.source !== wrapUpSource) {
16409
- throw new Error(`machine-wrap-up only resolves "${wrapUpSource}" wrap-ups, got "${request.source}"`);
16410
- }
16411
- return resolved.resolution;
16412
- }
16413
- });
16414
- const { service, authorizationServer } = createHarnessProposalLifecycleService(params.cwd, {
16415
- workspaceId: params.workspaceId,
16416
- now: params.now
16417
- });
16418
- const requestCorrelationId = randomUUID9();
16419
- const actor = await authorizationServer.actorContextFor(undefined, requestCorrelationId);
16420
- if (!actor)
16421
- throw new Error("trusted ActorContext is required for a machine wrap-up propose");
16422
- const provenance = await wrapUpAuthority.issue({ actor, source: wrapUpSource, sourceRef });
16423
- try {
16424
- const proposal = await service.create({
16425
- request: undefined,
16426
- requestCorrelationId,
16427
- workspaceId: params.workspaceId,
16428
- id: proposalId,
16429
- proposalRevision: "1",
16430
- kind: params.kind,
16431
- wrapUp: provenance
16432
- });
16433
- return { kind: params.kind, outcome: "proposed", proposalId: proposal.id };
16434
- } catch (error2) {
16435
- if (error2 instanceof ProposalLifecycleError && error2.code === "conflict") {
16436
- return { kind: params.kind, outcome: "conflict" };
16437
- }
16438
- throw error2;
16439
- }
16440
- } catch (error2) {
16441
- const message = error2 instanceof Error ? error2.message : String(error2);
16442
- return { kind: params.kind, outcome: "error", message };
16443
- }
16444
- }
16445
- async function runWrapUp(input2) {
16446
- const now = input2.now ?? (() => new Date);
16447
- const grouped = groupSeedsByKind(input2.slate);
16448
- const nonEmptyKinds = [...grouped.keys()].filter((kind) => (grouped.get(kind)?.length ?? 0) > 0);
16449
- if (nonEmptyKinds.length === 0) {
16450
- return { groups: [] };
16451
- }
16452
- if (input2.slate.workspaceId === undefined) {
16453
- await writeUnboundCandidateArtifact(input2.dir, input2.trigger, now, grouped, nonEmptyKinds);
16454
- const groups2 = nonEmptyKinds.map((kind) => ({ kind, outcome: "unbound-candidate" }));
16455
- await writeWrapUpOutcomeArtifact(input2.dir, input2.trigger, now, groups2);
16456
- return { groups: groups2 };
16457
- }
16458
- const workspaceId = input2.slate.workspaceId;
16459
- const groups = await Promise.all(nonEmptyKinds.map((kind) => proposeOneGroup({
16460
- cwd: input2.cwd,
16461
- workspaceId,
16462
- slate: input2.slate,
16463
- kind,
16464
- now,
16465
- ...input2.wrapUpSource !== undefined ? { wrapUpSource: input2.wrapUpSource } : {},
16466
- ...input2.env !== undefined ? { env: input2.env } : {},
16467
- ...input2.providerFactory !== undefined ? { providerFactory: input2.providerFactory } : {},
16468
- ...input2.modelTurnTimeoutMs !== undefined ? { modelTurnTimeoutMs: input2.modelTurnTimeoutMs } : {}
16469
- })));
16470
- await writeWrapUpOutcomeArtifact(input2.dir, input2.trigger, now, groups);
16471
- return { groups };
16472
- }
16473
- var execFileAsync, WRAP_UP_TTL_MS, DEFAULT_MODEL_TURN_TIMEOUT_MS = 30000;
16474
- var init_machine_wrap_up = __esm(() => {
16475
- init_fs();
16476
- init_slate();
16477
- init_slate_course();
16478
- init_trusted_wrap_up();
16479
- init_proposal_lifecycle();
16480
- init_single_turn();
16481
- execFileAsync = promisify(execFile);
16482
- WRAP_UP_TTL_MS = 60 * 60 * 1000;
16483
- });
16484
-
16485
16211
  // src/sac/session-wrap-up.ts
16486
- import { createHash as createHash12 } from "crypto";
16487
- import { mkdir as mkdir29, writeFile as writeFile27 } from "fs/promises";
16488
- import path76 from "path";
16212
+ import { createHash as createHash11 } from "crypto";
16213
+ import { mkdir as mkdir28, writeFile as writeFile27 } from "fs/promises";
16214
+ import path75 from "path";
16489
16215
  function sessionEvidenceRef(workspaceId, sessionId) {
16490
16216
  return `./.metaproject/workspaces/${workspaceId}/session-evidence/${sessionId}.md`;
16491
16217
  }
16492
16218
  async function resolveSessionWrapUp(input2) {
16493
16219
  const now = input2.now ?? (() => new Date);
16494
- const sessionId = path76.posix.basename(input2.sourceRef, ".md");
16220
+ const sessionId = path75.posix.basename(input2.sourceRef, ".md");
16495
16221
  const summary = findSession(input2.cwd, sessionId);
16496
16222
  if (summary === undefined || input2.sourceRef !== sessionEvidenceRef(input2.workspaceId, summary.id)) {
16497
16223
  throw new SessionWrapUpError("session_not_found", `no session matching "${input2.sourceRef}" in this project \u2014 use \`keryx sessions list\``);
@@ -16508,9 +16234,9 @@ async function resolveSessionWrapUp(input2) {
16508
16234
  throw new SessionWrapUpError("session_unreadable", `session "${summary.id}" could not be read: ${cause.message}`);
16509
16235
  }
16510
16236
  const relPath = sessionEvidenceRef(input2.workspaceId, summary.id).slice(2);
16511
- const evidenceDir = path76.dirname(relPath);
16512
- await mkdir29(path76.join(input2.cwd, evidenceDir), { recursive: true });
16513
- await writeFile27(path76.join(input2.cwd, relPath), markdown, "utf8");
16237
+ const evidenceDir = path75.dirname(relPath);
16238
+ await mkdir28(path75.join(input2.cwd, evidenceDir), { recursive: true });
16239
+ await writeFile27(path75.join(input2.cwd, relPath), markdown, "utf8");
16514
16240
  const slate = await readSessionSlate(input2.cwd, summary.id);
16515
16241
  const diffText = await gitDiff(input2.cwd);
16516
16242
  const course = await readCourse(input2.cwd, slate?.course.flowRef);
@@ -16530,30 +16256,30 @@ async function resolveSessionWrapUp(input2) {
16530
16256
  ""
16531
16257
  ].join(`
16532
16258
  `);
16533
- const wrapUpRelPath = path76.join(evidenceDir, `${summary.id}.wrap-up.md`);
16534
- const diffRelPath = path76.join(evidenceDir, `${summary.id}.diff.txt`);
16535
- await writeFile27(path76.join(input2.cwd, wrapUpRelPath), wrapUpMarkdown, "utf8");
16536
- await writeFile27(path76.join(input2.cwd, diffRelPath), diffText, "utf8");
16259
+ const wrapUpRelPath = path75.join(evidenceDir, `${summary.id}.wrap-up.md`);
16260
+ const diffRelPath = path75.join(evidenceDir, `${summary.id}.diff.txt`);
16261
+ await writeFile27(path75.join(input2.cwd, wrapUpRelPath), wrapUpMarkdown, "utf8");
16262
+ await writeFile27(path75.join(input2.cwd, diffRelPath), diffText, "utf8");
16537
16263
  const observedAt = now().toISOString();
16538
16264
  const evidence = [
16539
- { kind: "wrap-up", uri: `./${wrapUpRelPath}`, revision: createHash12("sha256").update(wrapUpMarkdown).digest("hex"), observedAt },
16540
- { kind: "diff", uri: `./${diffRelPath}`, revision: createHash12("sha256").update(diffText).digest("hex"), observedAt },
16541
- { kind: "session", uri: `./${relPath}`, revision: createHash12("sha256").update(markdown).digest("hex"), observedAt }
16265
+ { kind: "wrap-up", uri: `./${wrapUpRelPath}`, revision: createHash11("sha256").update(wrapUpMarkdown).digest("hex"), observedAt },
16266
+ { kind: "diff", uri: `./${diffRelPath}`, revision: createHash11("sha256").update(diffText).digest("hex"), observedAt },
16267
+ { kind: "session", uri: `./${relPath}`, revision: createHash11("sha256").update(markdown).digest("hex"), observedAt }
16542
16268
  ];
16543
16269
  return {
16544
16270
  workspaceId: input2.workspaceId,
16545
16271
  sourceRevision: summary.updatedAt,
16546
16272
  summary: `Session "${summary.title}" (${summary.archiveMessageCount} messages${summary.model ? `, ${summary.provider ?? "?"}/${summary.model}` : ""})`,
16547
16273
  evidence,
16548
- expiresAt: new Date(now().getTime() + WRAP_UP_TTL_MS2).toISOString()
16274
+ expiresAt: new Date(now().getTime() + WRAP_UP_TTL_MS).toISOString()
16549
16275
  };
16550
16276
  }
16551
- var WRAP_UP_TTL_MS2, SessionWrapUpError;
16277
+ var WRAP_UP_TTL_MS, SessionWrapUpError;
16552
16278
  var init_session_wrap_up = __esm(() => {
16553
16279
  init_store3();
16554
16280
  init_machine_wrap_up();
16555
16281
  init_slate_course();
16556
- WRAP_UP_TTL_MS2 = 60 * 60 * 1000;
16282
+ WRAP_UP_TTL_MS = 60 * 60 * 1000;
16557
16283
  SessionWrapUpError = class SessionWrapUpError extends Error {
16558
16284
  code;
16559
16285
  constructor(code, message) {
@@ -16564,17 +16290,17 @@ var init_session_wrap_up = __esm(() => {
16564
16290
  });
16565
16291
 
16566
16292
  // src/sac/review-confirm-token.ts
16567
- import { createHash as createHash13, randomBytes as randomBytes2 } from "crypto";
16293
+ import { createHash as createHash12, randomBytes as randomBytes2 } from "crypto";
16568
16294
  import { readFile as readFile35 } from "fs/promises";
16569
- import path77 from "path";
16295
+ import path76 from "path";
16570
16296
  function confirmTokenPath(cwd, workspaceId, proposalId) {
16571
- return path77.join(cwd, ".metaproject", "workspaces", workspaceId, "proposals", `${proposalId}.confirm-token.json`);
16297
+ return path76.join(cwd, ".metaproject", "workspaces", workspaceId, "proposals", `${proposalId}.confirm-token.json`);
16572
16298
  }
16573
16299
  function confirmReceiptPath(cwd, workspaceId, proposalId, idempotencyKey) {
16574
- return path77.join(cwd, ".metaproject", "workspaces", workspaceId, "proposals", `${proposalId}.${sha2562(idempotencyKey)}.confirm-receipt.json`);
16300
+ return path76.join(cwd, ".metaproject", "workspaces", workspaceId, "proposals", `${proposalId}.${sha256(idempotencyKey)}.confirm-receipt.json`);
16575
16301
  }
16576
- function sha2562(text) {
16577
- return createHash13("sha256").update(text).digest("hex");
16302
+ function sha256(text) {
16303
+ return createHash12("sha256").update(text).digest("hex");
16578
16304
  }
16579
16305
  async function mintConfirmToken(cwd, workspaceId, proposalId, deps = {}) {
16580
16306
  const now = deps.now ?? (() => new Date);
@@ -16585,7 +16311,7 @@ async function mintConfirmToken(cwd, workspaceId, proposalId, deps = {}) {
16585
16311
  const file = confirmTokenPath(cwd, workspaceId, proposalId);
16586
16312
  const stored = {
16587
16313
  schemaVersion: 1,
16588
- hash: sha2562(token),
16314
+ hash: sha256(token),
16589
16315
  workspaceId,
16590
16316
  proposalId,
16591
16317
  mintedAt: mintedAt.toISOString(),
@@ -16612,7 +16338,7 @@ async function consumeConfirmToken(cwd, workspaceId, proposalId, idempotencyKey,
16612
16338
  } catch (error2) {
16613
16339
  return { ok: false, reason: isNotFound(error2) ? "token_required" : "token_invalid" };
16614
16340
  }
16615
- if (stored.usedAt !== undefined || new Date(stored.expiresAt).getTime() <= now().getTime() || stored.hash !== sha2562(token) || stored.workspaceId !== workspaceId || stored.proposalId !== proposalId) {
16341
+ if (stored.usedAt !== undefined || new Date(stored.expiresAt).getTime() <= now().getTime() || stored.hash !== sha256(token) || stored.workspaceId !== workspaceId || stored.proposalId !== proposalId) {
16616
16342
  return { ok: false, reason: "token_invalid" };
16617
16343
  }
16618
16344
  const consumed2 = { ...stored, usedAt: now().toISOString() };
@@ -16699,17 +16425,17 @@ function round2(value) {
16699
16425
  var init_dedup = () => {};
16700
16426
 
16701
16427
  // src/sac/proposal-evidence.ts
16702
- import { createHash as createHash14 } from "crypto";
16428
+ import { createHash as createHash13 } from "crypto";
16703
16429
  import { readFile as readFile36 } from "fs/promises";
16704
- import path78 from "path";
16430
+ import path77 from "path";
16705
16431
  function proposalPath(cwd, workspaceId, proposalId) {
16706
- return path78.join(cwd, ".metaproject", "workspaces", workspaceId, "proposals", `${proposalId}.json`);
16432
+ return path77.join(cwd, ".metaproject", "workspaces", workspaceId, "proposals", `${proposalId}.json`);
16707
16433
  }
16708
16434
  function ownerReceiptPath(cwd, owner, workspaceId, idempotencyKey) {
16709
- return path78.join(cwd, ".metaproject", "workspaces", workspaceId, `${owner}-write-receipts`, `${idempotencyKey}.json`);
16435
+ return path77.join(cwd, ".metaproject", "workspaces", workspaceId, `${owner}-write-receipts`, `${idempotencyKey}.json`);
16710
16436
  }
16711
16437
  function proposalNotePath(cwd, workspaceId, proposalId) {
16712
- return path78.join(cwd, ".metaproject", "workspaces", workspaceId, "proposals", `${proposalId}.note.txt`);
16438
+ return path77.join(cwd, ".metaproject", "workspaces", workspaceId, "proposals", `${proposalId}.note.txt`);
16713
16439
  }
16714
16440
  async function readVerifiedProposalEvidence(cwd, workspaceId, proposalId) {
16715
16441
  let proposal;
@@ -16723,11 +16449,11 @@ async function readVerifiedProposalEvidence(cwd, workspaceId, proposalId) {
16723
16449
  return { ok: false, code: "no_evidence_to_write" };
16724
16450
  let content;
16725
16451
  try {
16726
- content = await readFile36(path78.join(cwd, evidence.uri), "utf8");
16452
+ content = await readFile36(path77.join(cwd, evidence.uri), "utf8");
16727
16453
  } catch {
16728
16454
  return { ok: false, code: "evidence_file_unreadable" };
16729
16455
  }
16730
- if (createHash14("sha256").update(content).digest("hex") !== evidence.revision) {
16456
+ if (createHash13("sha256").update(content).digest("hex") !== evidence.revision) {
16731
16457
  return { ok: false, code: "evidence_revision_mismatch" };
16732
16458
  }
16733
16459
  return { proposal, evidence, content };
@@ -16741,8 +16467,8 @@ var init_proposal_evidence = () => {};
16741
16467
 
16742
16468
  // src/sac/decision-dedup.ts
16743
16469
  import { readFile as readFile37, readdir as readdir10 } from "fs/promises";
16744
- import { randomUUID as randomUUID10 } from "crypto";
16745
- import path79 from "path";
16470
+ import { randomUUID as randomUUID9 } from "crypto";
16471
+ import path78 from "path";
16746
16472
  function asMemoryStatus(value) {
16747
16473
  return MEMORY_STATUSES.includes(value) ? value : "draft";
16748
16474
  }
@@ -16769,14 +16495,14 @@ function extractHeaderField(content, field3) {
16769
16495
  async function resolveWorkspaceModule(cwd, workspaceId) {
16770
16496
  try {
16771
16497
  const service = new WorkspaceService({ workspaceRoot: cwd, authorizationServer: localWorkspaceAuthorizationServer(), strictGuard: { mode: "strict", availability: "available", decision: "pass", policyRevision: "local-offline-v1" } });
16772
- const manifest = await service.show({ request: undefined, requestCorrelationId: randomUUID10(), workspaceId });
16498
+ const manifest = await service.show({ request: undefined, requestCorrelationId: randomUUID9(), workspaceId });
16773
16499
  return manifest.resources.find((r) => r.kind === "component")?.uri ?? null;
16774
16500
  } catch {
16775
16501
  return null;
16776
16502
  }
16777
16503
  }
16778
16504
  async function collectWikiDecisionEntries(cwd) {
16779
- const dir = path79.join(cwd, ".metaproject", "wiki", "decisions");
16505
+ const dir = path78.join(cwd, ".metaproject", "wiki", "decisions");
16780
16506
  let files;
16781
16507
  try {
16782
16508
  files = (await readdir10(dir)).filter((name) => name.endsWith(".md"));
@@ -16785,12 +16511,12 @@ async function collectWikiDecisionEntries(cwd) {
16785
16511
  }
16786
16512
  const entries = [];
16787
16513
  for (const file of files) {
16788
- const absolutePath = path79.join(dir, file);
16514
+ const absolutePath = path78.join(dir, file);
16789
16515
  try {
16790
16516
  const content = await readFile37(absolutePath, "utf8");
16791
16517
  entries.push({
16792
16518
  absolutePath,
16793
- relativePath: path79.posix.join("decisions", file),
16519
+ relativePath: path78.posix.join("decisions", file),
16794
16520
  type: extractHeaderField(content, "Type") ?? "decision",
16795
16521
  title: extractTitle(content),
16796
16522
  version: extractHeaderField(content, "Version") ?? null,
@@ -16909,13 +16635,13 @@ var init_decision_dedup = __esm(() => {
16909
16635
  });
16910
16636
 
16911
16637
  // src/memory/write.ts
16912
- import { mkdir as mkdir30, open, readFile as readFile38, rename as rename3, rm as rm5, writeFile as writeFile28 } from "fs/promises";
16913
- import path80 from "path";
16638
+ import { mkdir as mkdir29, open, readFile as readFile38, rename as rename3, rm as rm5, writeFile as writeFile28 } from "fs/promises";
16639
+ import path79 from "path";
16914
16640
  async function resolveCanonicalEntryPath(cwd, relativePath) {
16915
- const root = path80.resolve(memoryRoot(cwd));
16916
- const absolutePath = path80.resolve(root, relativePath);
16917
- const normalized = toPosix2(path80.relative(root, absolutePath));
16918
- if (!normalized || normalized.startsWith("../") || path80.isAbsolute(normalized) || !normalized.endsWith(".md")) {
16641
+ const root = path79.resolve(memoryRoot(cwd));
16642
+ const absolutePath = path79.resolve(root, relativePath);
16643
+ const normalized = toPosix2(path79.relative(root, absolutePath));
16644
+ if (!normalized || normalized.startsWith("../") || path79.isAbsolute(normalized) || !normalized.endsWith(".md")) {
16919
16645
  return null;
16920
16646
  }
16921
16647
  return { absolutePath, relativePath: normalized };
@@ -17010,9 +16736,9 @@ function validateNextEntry(relativePath, content) {
17010
16736
  return null;
17011
16737
  }
17012
16738
  async function replaceAtomically(target, content) {
17013
- const dir = path80.dirname(target);
17014
- await mkdir30(dir, { recursive: true });
17015
- const tmp = path80.join(dir, `.${path80.basename(target)}.keryx-tmp-${process.pid}-${Math.random().toString(16).slice(2)}`);
16739
+ const dir = path79.dirname(target);
16740
+ await mkdir29(dir, { recursive: true });
16741
+ const tmp = path79.join(dir, `.${path79.basename(target)}.keryx-tmp-${process.pid}-${Math.random().toString(16).slice(2)}`);
17016
16742
  try {
17017
16743
  await writeFile28(tmp, content, "utf8");
17018
16744
  const file = await open(tmp, "r");
@@ -17041,14 +16767,14 @@ function asPair(result, prior) {
17041
16767
  return { status: "skipped", paths: [...prior, result.path], warnings: result.warnings, reason: result.reason };
17042
16768
  return { status: "error", paths: [...prior, result.path], warnings: result.warnings, error: result.error };
17043
16769
  }
17044
- function persistenceError(path81, cause, warnings) {
17045
- return { status: "error", path: path81, warnings, error: { code: "persistence-failed", message: message(cause) } };
16770
+ function persistenceError(path80, cause, warnings) {
16771
+ return { status: "error", path: path80, warnings, error: { code: "persistence-failed", message: message(cause) } };
17046
16772
  }
17047
16773
  function header(content, name) {
17048
16774
  return content.match(new RegExp(`^${name}:\\s*(.+)$`, "mi"))?.[1]?.trim() ?? null;
17049
16775
  }
17050
16776
  function toPosix2(value) {
17051
- return value.split(path80.sep).join("/");
16777
+ return value.split(path79.sep).join("/");
17052
16778
  }
17053
16779
  function message(cause) {
17054
16780
  return cause instanceof Error ? cause.message : String(cause);
@@ -17060,8 +16786,8 @@ var init_write = __esm(() => {
17060
16786
  });
17061
16787
 
17062
16788
  // src/sac/memory-owner-writer.ts
17063
- import { mkdir as mkdir31, readFile as readFile39, writeFile as writeFile29 } from "fs/promises";
17064
- import path81 from "path";
16789
+ import { mkdir as mkdir30, readFile as readFile39, writeFile as writeFile29 } from "fs/promises";
16790
+ import path80 from "path";
17065
16791
  function renderWrapUpMemoryEntry(input2) {
17066
16792
  const note2 = input2.note?.trim();
17067
16793
  return `# ${input2.title}
@@ -17146,7 +16872,7 @@ function createRealMemoryOwnerWriter(cwd, opts) {
17146
16872
  targetRef: `./memory/${result.path}`,
17147
16873
  completedAt: now().toISOString()
17148
16874
  };
17149
- await mkdir31(path81.dirname(ownerReceiptPath(cwd, "memory", intent.workspaceId, intent.idempotencyKey)), { recursive: true });
16875
+ await mkdir30(path80.dirname(ownerReceiptPath(cwd, "memory", intent.workspaceId, intent.idempotencyKey)), { recursive: true });
17150
16876
  await writeFile29(ownerReceiptPath(cwd, "memory", intent.workspaceId, intent.idempotencyKey), `${JSON.stringify(receipt, null, 2)}
17151
16877
  `, "utf8");
17152
16878
  return receipt;
@@ -17161,7 +16887,7 @@ var init_memory_owner_writer = __esm(() => {
17161
16887
 
17162
16888
  // src/sac/wiki-owner-writer.ts
17163
16889
  import { readFile as readFile40 } from "fs/promises";
17164
- import path82 from "path";
16890
+ import path81 from "path";
17165
16891
  function renderWrapUpDecisionPage(input2) {
17166
16892
  const note2 = input2.note?.trim();
17167
16893
  return `# ${input2.title}
@@ -17202,7 +16928,7 @@ export is the source of truth for what actually happened.
17202
16928
  `;
17203
16929
  }
17204
16930
  function wikiPageRelativePath(proposalId) {
17205
- return path82.posix.join("decisions", `sac-${proposalId}.md`);
16931
+ return path81.posix.join("decisions", `sac-${proposalId}.md`);
17206
16932
  }
17207
16933
  function createRealWikiOwnerWriter(cwd, opts) {
17208
16934
  const now = opts?.now ?? (() => new Date);
@@ -17239,7 +16965,7 @@ function createRealWikiOwnerWriter(cwd, opts) {
17239
16965
  const guard = await guardOutput({ cwd, content, target: "wiki", source: "tool-output", path: `wiki/${relativePath}` });
17240
16966
  if (!guard.allowed)
17241
16967
  return { ok: false, code: `security_gate_${guard.reason ?? "blocked"}` };
17242
- await writeFileAtomic(path82.join(cwd, ".metaproject", "wiki", relativePath), content);
16968
+ await writeFileAtomic(path81.join(cwd, ".metaproject", "wiki", relativePath), content);
17243
16969
  const receipt = {
17244
16970
  receiptRef: `./wiki/${relativePath.replace(/\.md$/, "")}.receipt.json`,
17245
16971
  targetRef: `./wiki/${relativePath}`,
@@ -17258,23 +16984,23 @@ var init_wiki_owner_writer = __esm(() => {
17258
16984
  });
17259
16985
 
17260
16986
  // src/gdskills/project-skills.ts
17261
- import { mkdir as mkdir32, readFile as readFile41, stat as stat4 } from "fs/promises";
17262
- import path83 from "path";
16987
+ import { mkdir as mkdir31, readFile as readFile41, stat as stat4 } from "fs/promises";
16988
+ import path82 from "path";
17263
16989
  async function createProjectSkill(projectRoot, options) {
17264
- const metaprojectRoot = path83.join(projectRoot, ".metaproject");
16990
+ const metaprojectRoot = path82.join(projectRoot, ".metaproject");
17265
16991
  if (!await pathExists(metaprojectRoot)) {
17266
16992
  throw new Error("Metaproject is not initialized. Run: keryx init");
17267
16993
  }
17268
16994
  const moduleName = slugify3(options.module ?? inferModule(options.target));
17269
16995
  const skillName = slugify3(options.name ?? inferSkillName(options.target));
17270
16996
  const format = options.format ?? "auto";
17271
- const packageRoot = path83.join(metaprojectRoot, "project-skills", moduleName, skillName);
17272
- const relativeSkillPath = toPosix(path83.relative(projectRoot, packageRoot));
16997
+ const packageRoot = path82.join(metaprojectRoot, "project-skills", moduleName, skillName);
16998
+ const relativeSkillPath = toPosix(path82.relative(projectRoot, packageRoot));
17273
16999
  const evidence = await collectEvidence(projectRoot, options.target);
17274
17000
  const warnings = collectWarnings(evidence, format);
17275
17001
  const files = filesForPackage(packageRoot, format);
17276
17002
  if (!options.dryRun) {
17277
- await withFileLock2(path83.join(metaprojectRoot, "data", "gdskills", "project-skills.lock"), async () => {
17003
+ await withFileLock2(path82.join(metaprojectRoot, "data", "gdskills", "project-skills.lock"), async () => {
17278
17004
  await writeProjectSkillPackage({
17279
17005
  projectRoot,
17280
17006
  packageRoot,
@@ -17301,7 +17027,7 @@ async function createProjectSkill(projectRoot, options) {
17301
17027
  name: skillName,
17302
17028
  target: options.target,
17303
17029
  skillPath: relativeSkillPath,
17304
- files: files.map((filePath) => toPosix(path83.relative(projectRoot, filePath))),
17030
+ files: files.map((filePath) => toPosix(path82.relative(projectRoot, filePath))),
17305
17031
  warnings,
17306
17032
  dryRun: options.dryRun === true
17307
17033
  };
@@ -17323,31 +17049,31 @@ async function writeProjectSkillPackage({
17323
17049
  }) {
17324
17050
  const packageFormat = format === "single" ? "single" : "package";
17325
17051
  const skillContent = renderProjectSkill({ moduleName, skillName, target, evidence, packageFormat });
17326
- const relativeSkillMdPath = toPosix(path83.join(path83.relative(projectRoot, packageRoot), "SKILL.md"));
17052
+ const relativeSkillMdPath = toPosix(path82.join(path82.relative(projectRoot, packageRoot), "SKILL.md"));
17327
17053
  const guard = await guardOutput({ cwd: projectRoot, content: skillContent, target: "skill", source: "generated", path: relativeSkillMdPath });
17328
17054
  if (!guard.allowed) {
17329
17055
  throw new Error(`Project skill blocked by the security gate: ${guard.reason ?? "policy violation"}`);
17330
17056
  }
17331
- await mkdir32(packageRoot, { recursive: true });
17332
- const skillPath = path83.join(packageRoot, "SKILL.md");
17057
+ await mkdir31(packageRoot, { recursive: true });
17058
+ const skillPath = path82.join(packageRoot, "SKILL.md");
17333
17059
  await writeFileAtomic(skillPath, skillContent);
17334
- const changelogPath = path83.join(packageRoot, "skill-changelog.md");
17060
+ const changelogPath = path82.join(packageRoot, "skill-changelog.md");
17335
17061
  if (!await pathExists(changelogPath)) {
17336
17062
  await writeFileAtomic(changelogPath, renderSkillChangelog({ moduleName, skillName, target }));
17337
17063
  }
17338
17064
  if (packageFormat === "package") {
17339
- await mkdir32(path83.join(packageRoot, "references"), { recursive: true });
17340
- await mkdir32(path83.join(packageRoot, "templates"), { recursive: true });
17341
- await writeFileAtomic(path83.join(packageRoot, "references", "context.md"), renderReferenceContext({ moduleName, skillName, target, evidence }));
17342
- await writeFileAtomic(path83.join(packageRoot, "templates", "README.md"), renderTemplatesReadme({ moduleName, skillName }));
17343
- await writeFileAtomic(path83.join(packageRoot, "verification.md"), renderVerification({ moduleName, skillName, evidence }));
17065
+ await mkdir31(path82.join(packageRoot, "references"), { recursive: true });
17066
+ await mkdir31(path82.join(packageRoot, "templates"), { recursive: true });
17067
+ await writeFileAtomic(path82.join(packageRoot, "references", "context.md"), renderReferenceContext({ moduleName, skillName, target, evidence }));
17068
+ await writeFileAtomic(path82.join(packageRoot, "templates", "README.md"), renderTemplatesReadme({ moduleName, skillName }));
17069
+ await writeFileAtomic(path82.join(packageRoot, "verification.md"), renderVerification({ moduleName, skillName, evidence }));
17344
17070
  }
17345
17071
  }
17346
17072
  async function collectEvidence(projectRoot, target) {
17347
- const absoluteTarget = path83.resolve(projectRoot, target);
17073
+ const absoluteTarget = path82.resolve(projectRoot, target);
17348
17074
  const targetExists = await pathExists(absoluteTarget);
17349
17075
  const targetKind = await classifyTarget(absoluteTarget, targetExists);
17350
- const maybeRelativeTarget = targetExists ? toPosix(path83.relative(projectRoot, absoluteTarget)) : undefined;
17076
+ const maybeRelativeTarget = targetExists ? toPosix(path82.relative(projectRoot, absoluteTarget)) : undefined;
17351
17077
  const graphArtifacts = await existingRelativePaths(projectRoot, [
17352
17078
  ".metaproject/data/gdgraph/artifacts/summary.md",
17353
17079
  ".metaproject/data/gdgraph/artifacts/module-map.json"
@@ -17377,7 +17103,7 @@ async function classifyTarget(absoluteTarget, targetExists) {
17377
17103
  async function existingRelativePaths(projectRoot, candidates) {
17378
17104
  const existing = [];
17379
17105
  for (const candidate of candidates) {
17380
- if (await pathExists(path83.join(projectRoot, candidate))) {
17106
+ if (await pathExists(path82.join(projectRoot, candidate))) {
17381
17107
  existing.push(candidate);
17382
17108
  }
17383
17109
  }
@@ -17404,17 +17130,17 @@ function collectWarnings(evidence, format) {
17404
17130
  }
17405
17131
  function filesForPackage(packageRoot, format) {
17406
17132
  const base = [
17407
- path83.join(packageRoot, "SKILL.md"),
17408
- path83.join(packageRoot, "skill-changelog.md")
17133
+ path82.join(packageRoot, "SKILL.md"),
17134
+ path82.join(packageRoot, "skill-changelog.md")
17409
17135
  ];
17410
17136
  if (format === "single") {
17411
17137
  return base;
17412
17138
  }
17413
17139
  return [
17414
17140
  ...base,
17415
- path83.join(packageRoot, "verification.md"),
17416
- path83.join(packageRoot, "references", "context.md"),
17417
- path83.join(packageRoot, "templates", "README.md")
17141
+ path82.join(packageRoot, "verification.md"),
17142
+ path82.join(packageRoot, "references", "context.md"),
17143
+ path82.join(packageRoot, "templates", "README.md")
17418
17144
  ];
17419
17145
  }
17420
17146
  function renderProjectSkill({
@@ -17633,7 +17359,7 @@ keryx skills verify ${moduleName}/${skillName}
17633
17359
  `;
17634
17360
  }
17635
17361
  async function updateManifest(projectRoot, entry) {
17636
- const manifestPath = path83.join(projectRoot, ".metaproject", "metaproject.json");
17362
+ const manifestPath = path82.join(projectRoot, ".metaproject", "metaproject.json");
17637
17363
  const manifest = await readJsonFileOr(manifestPath, {});
17638
17364
  manifest.modules ??= {};
17639
17365
  manifest.modules.gdskills ??= {};
@@ -17647,8 +17373,8 @@ async function updateManifest(projectRoot, entry) {
17647
17373
  `);
17648
17374
  }
17649
17375
  async function updateSkillsCatalog(projectRoot) {
17650
- const manifestPath = path83.join(projectRoot, ".metaproject", "metaproject.json");
17651
- const catalogPath = path83.join(projectRoot, ".metaproject", "skills", "catalog.md");
17376
+ const manifestPath = path82.join(projectRoot, ".metaproject", "metaproject.json");
17377
+ const catalogPath = path82.join(projectRoot, ".metaproject", "skills", "catalog.md");
17652
17378
  const manifest = await readJsonFileOr(manifestPath, {});
17653
17379
  const registry = manifest.modules?.gdskills?.projectSkillRegistry ?? [];
17654
17380
  const rows = registry.length > 0 ? registry.map((entry) => `| ${entry.module} | ${entry.name} | \`${entry.target}\` | ${entry.path}/SKILL.md |`).join(`
@@ -17688,7 +17414,7 @@ function inferModule(target) {
17688
17414
  }
17689
17415
  function inferSkillName(target) {
17690
17416
  const normalized = target.trim().replace(/[#:]+/g, "/");
17691
- const base = path83.basename(normalized).replace(/\.[^.]+$/, "");
17417
+ const base = path82.basename(normalized).replace(/\.[^.]+$/, "");
17692
17418
  return base || "entity";
17693
17419
  }
17694
17420
  function slugify3(value) {
@@ -17766,9 +17492,9 @@ var init_skill_owner_writer = __esm(() => {
17766
17492
  });
17767
17493
 
17768
17494
  // src/sac/proposal-lifecycle.ts
17769
- import { createHash as createHash15, randomUUID as randomUUID11 } from "crypto";
17770
- import { appendFile as appendFile3, mkdir as mkdir33, readdir as readdir11, readFile as readFile43 } from "fs/promises";
17771
- import path84 from "path";
17495
+ import { createHash as createHash14, randomUUID as randomUUID10 } from "crypto";
17496
+ import { appendFile as appendFile3, mkdir as mkdir32, readdir as readdir11, readFile as readFile43 } from "fs/promises";
17497
+ import path83 from "path";
17772
17498
 
17773
17499
  class ProposalLifecycleService {
17774
17500
  options;
@@ -17776,7 +17502,7 @@ class ProposalLifecycleService {
17776
17502
  now;
17777
17503
  constructor(options) {
17778
17504
  this.options = options;
17779
- this.root = path84.resolve(options.workspaceRoot);
17505
+ this.root = path83.resolve(options.workspaceRoot);
17780
17506
  this.now = options.now ?? (() => new Date);
17781
17507
  }
17782
17508
  async create(input2) {
@@ -17795,7 +17521,7 @@ class ProposalLifecycleService {
17795
17521
  if (manifest.status === "archived")
17796
17522
  throw new ProposalLifecycleError("guard_denied", "workspace is archived");
17797
17523
  const file = this.proposalPath(workspaceId, proposal.id);
17798
- await mkdir33(path84.dirname(file), { recursive: true, mode: 448 });
17524
+ await mkdir32(path83.dirname(file), { recursive: true, mode: 448 });
17799
17525
  return withFileLock2(`${file}.lock`, async () => {
17800
17526
  const consume = this.options.wrapUpAuthority.consume(input2.wrapUp, { actor, workspaceId });
17801
17527
  if (consume !== "ok")
@@ -17823,7 +17549,7 @@ class ProposalLifecycleService {
17823
17549
  return this.options.workspaces.withAuthorizedActor({ actorContext: actor, workspaceId: input2.workspaceId, action: "review", execute: async (manifest) => {
17824
17550
  const proposal = await this.loadProposal(input2.workspaceId, input2.proposalId);
17825
17551
  const ledger = this.ledgerPath(input2.workspaceId);
17826
- await mkdir33(path84.dirname(ledger), { recursive: true, mode: 448 });
17552
+ await mkdir32(path83.dirname(ledger), { recursive: true, mode: 448 });
17827
17553
  return withFileLock2(`${ledger}.lock`, async () => {
17828
17554
  const records = (await this.records(ledger)).filter((record) => record.proposalId === proposal.id);
17829
17555
  const events = records.filter((record) => record.recordType === "proposal-transition");
@@ -17864,7 +17590,7 @@ class ProposalLifecycleService {
17864
17590
  } });
17865
17591
  }
17866
17592
  async listProposedProposals(workspaceId) {
17867
- const proposalsDir = path84.join(this.root, ".metaproject", "workspaces", workspaceId, "proposals");
17593
+ const proposalsDir = path83.join(this.root, ".metaproject", "workspaces", workspaceId, "proposals");
17868
17594
  let entries;
17869
17595
  try {
17870
17596
  entries = (await readdir11(proposalsDir)).filter((name) => name.endsWith(".json"));
@@ -17876,7 +17602,7 @@ class ProposalLifecycleService {
17876
17602
  const proposals = [];
17877
17603
  for (const entry of entries) {
17878
17604
  try {
17879
- const parsed = JSON.parse(await readFile43(path84.join(proposalsDir, entry), "utf8"));
17605
+ const parsed = JSON.parse(await readFile43(path83.join(proposalsDir, entry), "utf8"));
17880
17606
  if (parsed.recordType === "proposal-created")
17881
17607
  proposals.push(parsed);
17882
17608
  } catch {}
@@ -17935,7 +17661,7 @@ class ProposalLifecycleService {
17935
17661
  }
17936
17662
  async transition(input2) {
17937
17663
  const previous = input2.records.at(-1);
17938
- const base = { schemaVersion: "1.0", recordType: "proposal-transition", eventId: `event-${randomUUID11().replace(/-/g, "").slice(0, 16)}`, proposalId: input2.proposal.id, proposalRevision: input2.proposal.proposalRevision, correlationId: input2.input.requestCorrelationId, workspaceId: input2.proposal.workspaceId, sequence: input2.records.length + 1, priorEventHash: previous ? recordHash(previous) : hash("GENESIS"), fromStatus: "proposed", toStatus: input2.outcome, occurredAt: this.timestamp(), idempotencyKey: input2.input.idempotencyKey };
17664
+ const base = { schemaVersion: "1.0", recordType: "proposal-transition", eventId: `event-${randomUUID10().replace(/-/g, "").slice(0, 16)}`, proposalId: input2.proposal.id, proposalRevision: input2.proposal.proposalRevision, correlationId: input2.input.requestCorrelationId, workspaceId: input2.proposal.workspaceId, sequence: input2.records.length + 1, priorEventHash: previous ? recordHash(previous) : hash("GENESIS"), fromStatus: "proposed", toStatus: input2.outcome, occurredAt: this.timestamp(), idempotencyKey: input2.input.idempotencyKey };
17939
17665
  if (input2.outcome === "accepted") {
17940
17666
  const write = input2.targetWrite;
17941
17667
  if (!write?.ok)
@@ -17982,7 +17708,7 @@ class ProposalLifecycleService {
17982
17708
  if (!isNotFound(error2))
17983
17709
  throw error2;
17984
17710
  }
17985
- const intent = { schemaVersion: "1.0", recordType: "proposal-write-intent", intentId: `intent-${randomUUID11().replace(/-/g, "").slice(0, 16)}`, proposalId: proposal.id, proposalRevision: proposal.proposalRevision, correlationId: input2.requestCorrelationId, workspaceId: proposal.workspaceId, sequence: records.length + 1, priorEventHash: records.length ? recordHash(records.at(-1)) : hash("GENESIS"), idempotencyKey: input2.idempotencyKey, reviewer: { subject: actor.subject, authority: reviewerAuthority, trustedPrincipalRef: "./principals/local" }, approvalRef, security: { gate: "pass", policyRef: this.options.policyRef, policyRevision }, evidence: proposal.evidence, createdAt: this.timestamp() };
17711
+ const intent = { schemaVersion: "1.0", recordType: "proposal-write-intent", intentId: `intent-${randomUUID10().replace(/-/g, "").slice(0, 16)}`, proposalId: proposal.id, proposalRevision: proposal.proposalRevision, correlationId: input2.requestCorrelationId, workspaceId: proposal.workspaceId, sequence: records.length + 1, priorEventHash: records.length ? recordHash(records.at(-1)) : hash("GENESIS"), idempotencyKey: input2.idempotencyKey, reviewer: { subject: actor.subject, authority: reviewerAuthority, trustedPrincipalRef: "./principals/local" }, approvalRef, security: { gate: "pass", policyRef: this.options.policyRef, policyRevision }, evidence: proposal.evidence, createdAt: this.timestamp() };
17986
17712
  await this.validateRecord(intent);
17987
17713
  await this.writeImmutable(this.intentPath(proposal.workspaceId, proposal.id, input2.idempotencyKey), intent);
17988
17714
  await appendFile3(ledger, `${JSON.stringify(intent)}
@@ -18050,7 +17776,7 @@ class ProposalLifecycleService {
18050
17776
  throw new ProposalLifecycleError("invalid_proposal", validation.errors.map((error2) => error2.code).join(","));
18051
17777
  }
18052
17778
  async reviewDecision(proposal, actor, reviewerAuthority, input2, decision, targetWrite, policyRevision) {
18053
- const base = { schemaVersion: "1.0", id: `review-${randomUUID11().replace(/-/g, "").slice(0, 16)}`, proposalId: proposal.id, proposalRevision: proposal.proposalRevision, correlationId: input2.requestCorrelationId, workspaceId: proposal.workspaceId, decision, reviewer: { subject: actor.subject, authority: reviewerAuthority, trustedPrincipalRef: "./principals/local" }, decidedAt: this.timestamp(), idempotencyKey: input2.idempotencyKey };
17779
+ const base = { schemaVersion: "1.0", id: `review-${randomUUID10().replace(/-/g, "").slice(0, 16)}`, proposalId: proposal.id, proposalRevision: proposal.proposalRevision, correlationId: input2.requestCorrelationId, workspaceId: proposal.workspaceId, decision, reviewer: { subject: actor.subject, authority: reviewerAuthority, trustedPrincipalRef: "./principals/local" }, decidedAt: this.timestamp(), idempotencyKey: input2.idempotencyKey };
18054
17780
  if (decision === "accepted" && targetWrite?.ok)
18055
17781
  Object.assign(base, { security: { gate: "pass", policyRef: this.options.policyRef, policyRevision }, freshness: { state: "fresh", verifiedAt: this.timestamp(), evidenceRevision: proposal.evidence[0].revision }, targetWrite: { receiptRef: targetWrite.receipt.receiptRef, targetRef: targetWrite.receipt.targetRef, completedAt: targetWrite.receipt.completedAt } });
18056
17782
  else
@@ -18082,28 +17808,28 @@ class ProposalLifecycleService {
18082
17808
  }
18083
17809
  }
18084
17810
  proposalPath(workspaceId, proposalId) {
18085
- return path84.join(this.root, ".metaproject", "workspaces", workspaceId, "proposals", `${proposalId}.json`);
17811
+ return path83.join(this.root, ".metaproject", "workspaces", workspaceId, "proposals", `${proposalId}.json`);
18086
17812
  }
18087
17813
  decisionRef(proposalId, key) {
18088
17814
  return `./proposals/${proposalId}.${hash(key)}.decision.json`;
18089
17815
  }
18090
17816
  decisionPath(workspaceId, proposalId, key) {
18091
- return path84.join(this.root, ".metaproject", "workspaces", workspaceId, "proposals", `${proposalId}.${hash(key)}.decision.json`);
17817
+ return path83.join(this.root, ".metaproject", "workspaces", workspaceId, "proposals", `${proposalId}.${hash(key)}.decision.json`);
18092
17818
  }
18093
17819
  approvalRef(proposalId, key) {
18094
17820
  return `./proposals/${proposalId}.${hash(key)}.approval.json`;
18095
17821
  }
18096
17822
  approvalPath(workspaceId, proposalId, key) {
18097
- return path84.join(this.root, ".metaproject", "workspaces", workspaceId, "proposals", `${proposalId}.${hash(key)}.approval.json`);
17823
+ return path83.join(this.root, ".metaproject", "workspaces", workspaceId, "proposals", `${proposalId}.${hash(key)}.approval.json`);
18098
17824
  }
18099
17825
  writeResultPath(workspaceId, proposalId, key) {
18100
- return path84.join(this.root, ".metaproject", "workspaces", workspaceId, "proposals", `${proposalId}.${hash(key)}.write-result.json`);
17826
+ return path83.join(this.root, ".metaproject", "workspaces", workspaceId, "proposals", `${proposalId}.${hash(key)}.write-result.json`);
18101
17827
  }
18102
17828
  intentRef(proposalId, key) {
18103
17829
  return `./proposals/${proposalId}.${hash(key)}.write-intent.json`;
18104
17830
  }
18105
17831
  intentPath(workspaceId, proposalId, key) {
18106
- return path84.join(this.root, ".metaproject", "workspaces", workspaceId, "proposals", `${proposalId}.${hash(key)}.write-intent.json`);
17832
+ return path83.join(this.root, ".metaproject", "workspaces", workspaceId, "proposals", `${proposalId}.${hash(key)}.write-intent.json`);
18107
17833
  }
18108
17834
  async loadWriteResult(workspaceId, proposalId, key) {
18109
17835
  try {
@@ -18130,14 +17856,14 @@ class ProposalLifecycleService {
18130
17856
  }
18131
17857
  }
18132
17858
  ledgerPath(workspaceId) {
18133
- return path84.join(this.root, ".metaproject", "workspaces", workspaceId, "activity.jsonl");
17859
+ return path83.join(this.root, ".metaproject", "workspaces", workspaceId, "activity.jsonl");
18134
17860
  }
18135
17861
  timestamp() {
18136
17862
  return this.now().toISOString();
18137
17863
  }
18138
17864
  }
18139
17865
  function hash(value) {
18140
- return createHash15("sha256").update(value).digest("hex");
17866
+ return createHash14("sha256").update(value).digest("hex");
18141
17867
  }
18142
17868
  function recordHash(value) {
18143
17869
  return hash(JSON.stringify(value));
@@ -18235,9 +17961,9 @@ var init_proposal_lifecycle = __esm(() => {
18235
17961
  });
18236
17962
 
18237
17963
  // src/harness/tool/builtin/workspace-lifecycle-tool.ts
18238
- import { randomUUID as randomUUID12 } from "crypto";
17964
+ import { randomUUID as randomUUID11 } from "crypto";
18239
17965
  import { writeFile as writeFile30 } from "fs/promises";
18240
- import path85 from "path";
17966
+ import path84 from "path";
18241
17967
  function service(cwd) {
18242
17968
  return new WorkspaceService({
18243
17969
  workspaceRoot: cwd,
@@ -18248,11 +17974,11 @@ function service(cwd) {
18248
17974
  function errorOutput(prefix, cause) {
18249
17975
  return { output: `${prefix}: ${cause instanceof Error ? cause.message : String(cause)}`, isError: true };
18250
17976
  }
18251
- function workspaceCreateTool(cwd) {
17977
+ function workspaceCreateTool(cwd, getSessionDir) {
18252
17978
  return {
18253
17979
  definition: {
18254
17980
  name: "workspace_create",
18255
- description: "Create a new Shared Agent Context (SAC) workspace to bind this session's work to. Call workspace_list FIRST and only create when no existing workspace already fits the current topic \u2014 a workspace is meant to persist and accumulate context across sessions, not to be created per-session. Input: { title: string, component?: string }. `component` is an optional workspace-relative path this workspace is scoped to.",
17981
+ description: "Create a new Shared Agent Context (SAC) workspace to bind this session's work to. Call workspace_list FIRST and only create when no existing workspace already fits the current topic \u2014 a workspace is meant to persist and accumulate context across sessions, not to be created per-session. The created workspace is BOUND to this session's slate (its workspaceId is written to the slate), so wrap-up can propose into it. Input: { title: string, component?: string }. `component` is an optional workspace-relative path this workspace is scoped to.",
18256
17982
  inputSchema: {
18257
17983
  type: "object",
18258
17984
  properties: { title: { type: "string" }, component: { type: "string" } },
@@ -18269,11 +17995,23 @@ function workspaceCreateTool(cwd) {
18269
17995
  try {
18270
17996
  const workspace = await service(cwd).create({
18271
17997
  request: undefined,
18272
- requestCorrelationId: randomUUID12(),
17998
+ requestCorrelationId: randomUUID11(),
18273
17999
  id: newWorkspaceId(),
18274
18000
  title,
18275
18001
  ...component ? { component: { kind: "component", uri: component } } : {}
18276
18002
  });
18003
+ const dir = getSessionDir?.();
18004
+ if (dir !== undefined) {
18005
+ try {
18006
+ await writeSlate(dir, (prev) => ({
18007
+ anchors: prev?.anchors ?? { root: "", touched: [] },
18008
+ course: prev?.course ?? {},
18009
+ seeds: prev?.seeds ?? [],
18010
+ ...prev !== undefined ? { workspaceId: prev.workspaceId } : {},
18011
+ workspaceId: workspace.id
18012
+ }));
18013
+ } catch {}
18014
+ }
18277
18015
  return { output: JSON.stringify(workspace, null, 2), isError: false };
18278
18016
  } catch (cause) {
18279
18017
  return errorOutput("workspace_create failed", cause);
@@ -18296,7 +18034,7 @@ function workspaceListTool(cwd) {
18296
18034
  invoke: async (input2) => {
18297
18035
  const includeArchived = input2.includeArchived === true;
18298
18036
  try {
18299
- const workspaces = await service(cwd).list({ request: undefined, requestCorrelationId: randomUUID12(), includeArchived });
18037
+ const workspaces = await service(cwd).list({ request: undefined, requestCorrelationId: randomUUID11(), includeArchived });
18300
18038
  return { output: JSON.stringify(workspaces, null, 2), isError: false };
18301
18039
  } catch (cause) {
18302
18040
  return errorOutput("workspace_list failed", cause);
@@ -18322,7 +18060,7 @@ function workspaceShowTool(cwd) {
18322
18060
  if (workspaceId.length === 0)
18323
18061
  return { output: "workspace_show requires a non-empty 'workspaceId'", isError: true };
18324
18062
  try {
18325
- const workspace = await service(cwd).show({ request: undefined, requestCorrelationId: randomUUID12(), workspaceId });
18063
+ const workspace = await service(cwd).show({ request: undefined, requestCorrelationId: randomUUID11(), workspaceId });
18326
18064
  return { output: JSON.stringify(workspace, null, 2), isError: false };
18327
18065
  } catch (cause) {
18328
18066
  return errorOutput("workspace_show failed", cause);
@@ -18358,7 +18096,7 @@ function workspaceProposeTool(cwd, getSessionDir) {
18358
18096
  return { output: `workspace_propose: unrecognized 'kind' \u2014 expected one of: ${PROPOSAL_KINDS.join(", ")}`, isError: true };
18359
18097
  }
18360
18098
  const explicitSessionId = typeof input2.sessionId === "string" && input2.sessionId.length > 0 ? input2.sessionId : undefined;
18361
- const sessionRef = explicitSessionId ?? path85.basename(getSessionDir() ?? "");
18099
+ const sessionRef = explicitSessionId ?? path84.basename(getSessionDir() ?? "");
18362
18100
  if (sessionRef.length === 0) {
18363
18101
  return { output: "workspace_propose: no 'sessionId' given and no active session in this run", isError: true };
18364
18102
  }
@@ -18367,7 +18105,7 @@ function workspaceProposeTool(cwd, getSessionDir) {
18367
18105
  if (!session)
18368
18106
  return { output: `workspace_propose: no session matching "${sessionRef}" in this project`, isError: true };
18369
18107
  const { service: lifecycle, wrapUpAuthority, authorizationServer } = createHarnessProposalLifecycleService(cwd, { workspaceId, ...note2 ? { note: note2 } : {} });
18370
- const requestCorrelationId = randomUUID12();
18108
+ const requestCorrelationId = randomUUID11();
18371
18109
  const actor = await authorizationServer.actorContextFor(undefined, requestCorrelationId);
18372
18110
  if (!actor)
18373
18111
  return { output: "workspace_propose: trusted ActorContext is required", isError: true };
@@ -18376,7 +18114,7 @@ function workspaceProposeTool(cwd, getSessionDir) {
18376
18114
  request: undefined,
18377
18115
  requestCorrelationId,
18378
18116
  workspaceId,
18379
- id: `proposal-${randomUUID12().replace(/-/g, "").slice(0, 16)}`,
18117
+ id: `proposal-${randomUUID11().replace(/-/g, "").slice(0, 16)}`,
18380
18118
  proposalRevision: "1",
18381
18119
  kind,
18382
18120
  wrapUp
@@ -18396,6 +18134,7 @@ var init_workspace_lifecycle_tool = __esm(() => {
18396
18134
  init_proposal_lifecycle();
18397
18135
  init_proposal_evidence();
18398
18136
  init_session_wrap_up();
18137
+ init_slate();
18399
18138
  init_store3();
18400
18139
  PROPOSAL_KINDS = ["decision", "wiki-update", "memory-entry", "follow-up", "contract-change", "risk"];
18401
18140
  });
@@ -18445,7 +18184,7 @@ ${topicHint}
18445
18184
  ${existing.map((w) => `${w.id}: ${w.title}`).join(`
18446
18185
  `)}`;
18447
18186
  let modelResult;
18448
- const modelTurnTimeoutMs = input2.modelTurnTimeoutMs ?? DEFAULT_MODEL_TURN_TIMEOUT_MS2;
18187
+ const modelTurnTimeoutMs = input2.modelTurnTimeoutMs ?? DEFAULT_MODEL_TURN_TIMEOUT_MS;
18449
18188
  const turn = runModelTurn({
18450
18189
  system,
18451
18190
  user,
@@ -18490,13 +18229,315 @@ ${existing.map((w) => `${w.id}: ${w.title}`).join(`
18490
18229
  const { id } = JSON.parse(created.output);
18491
18230
  return { ok: true, workspaceId: id, action: "created" };
18492
18231
  }
18493
- var DEFAULT_MODEL_TURN_TIMEOUT_MS2 = 15000, TOPIC_HINT_MAX_LENGTH = 200;
18232
+ var DEFAULT_MODEL_TURN_TIMEOUT_MS = 15000, TOPIC_HINT_MAX_LENGTH = 200;
18494
18233
  var init_workspace_resolve = __esm(() => {
18495
18234
  init_redact();
18496
18235
  init_workspace_lifecycle_tool();
18497
18236
  init_single_turn();
18498
18237
  });
18499
18238
 
18239
+ // src/sac/machine-wrap-up.ts
18240
+ import { createHash as createHash15, randomUUID as randomUUID12 } from "crypto";
18241
+ import { execFile } from "child_process";
18242
+ import { mkdir as mkdir33 } from "fs/promises";
18243
+ import path85 from "path";
18244
+ import { promisify } from "util";
18245
+ function describeSource(source) {
18246
+ return source === "parent" ? "parent" : `child:${source.childDispatchId}`;
18247
+ }
18248
+ function dedupedAttributedSeeds(slate) {
18249
+ const seen = new Set;
18250
+ const result = [];
18251
+ const take = (seeds, source) => {
18252
+ for (const seed of dedupeSeeds(seeds)) {
18253
+ const key = seed.text.trim();
18254
+ if (seen.has(key))
18255
+ continue;
18256
+ seen.add(key);
18257
+ result.push({ text: seed.text, kind: seed.kind ?? "follow-up", source });
18258
+ }
18259
+ };
18260
+ take(slate.seeds, "parent");
18261
+ const childDispatches = slate.childDispatches ?? {};
18262
+ for (const [dispatchId, dispatch] of Object.entries(childDispatches)) {
18263
+ take(dispatch.seeds, { childDispatchId: dispatchId });
18264
+ }
18265
+ return result;
18266
+ }
18267
+ function groupSeedsByKind(slate) {
18268
+ const map = new Map;
18269
+ for (const seed of dedupedAttributedSeeds(slate)) {
18270
+ const bucket = map.get(seed.kind);
18271
+ if (bucket)
18272
+ bucket.push(seed);
18273
+ else
18274
+ map.set(seed.kind, [seed]);
18275
+ }
18276
+ return map;
18277
+ }
18278
+ function sha2562(value) {
18279
+ return createHash15("sha256").update(value).digest("hex");
18280
+ }
18281
+ async function gitDiff(cwd) {
18282
+ try {
18283
+ const { stdout: stdout2 } = await execFileAsync("git", ["diff"], { cwd, maxBuffer: 16 * 1024 * 1024 });
18284
+ return stdout2;
18285
+ } catch {
18286
+ return "";
18287
+ }
18288
+ }
18289
+ function diffStatLine(diffText) {
18290
+ if (diffText.trim().length === 0)
18291
+ return "no working-tree changes";
18292
+ const added = (diffText.match(/^\+(?!\+\+)/gm) ?? []).length;
18293
+ const removed = (diffText.match(/^-(?!--)/gm) ?? []).length;
18294
+ return `working-tree diff: +${added}/-${removed} line(s)`;
18295
+ }
18296
+ function courseStatusLine(course) {
18297
+ if (course.state !== "bound")
18298
+ return "flow: unbound";
18299
+ return `flow ${course.flowRef.uri} snapshot=${course.flowRef.snapshot} completed=${course.completed.length} next=${course.next.length} blocked=${course.blocked.length}`;
18300
+ }
18301
+ function mechanicalSummary(diffText, course) {
18302
+ return `Mechanical wrap-up summary (model turn unavailable or timed out):
18303
+ ${diffStatLine(diffText)}
18304
+ ${courseStatusLine(course)}`;
18305
+ }
18306
+ async function resolveMachineWrapUp(input2) {
18307
+ const now = input2.now ?? (() => new Date);
18308
+ const diffText = await gitDiff(input2.cwd);
18309
+ const course = await readCourse(input2.cwd, input2.slate.course.flowRef);
18310
+ const seedsForKind = dedupedAttributedSeeds(input2.slate).filter((seed) => seed.kind === input2.kind);
18311
+ const flowSnapshotJson = `${JSON.stringify(course, null, 2)}
18312
+ `;
18313
+ const seedsJson = `${JSON.stringify(seedsForKind.map((seed) => ({ text: seed.text, source: describeSource(seed.source) })), null, 2)}
18314
+ `;
18315
+ const sourceRevision = sha2562([diffText, flowSnapshotJson, seedsJson].join("\x00"));
18316
+ const shortHash = sourceRevision.slice(0, 16);
18317
+ 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.";
18318
+ const user = `--- git diff ---
18319
+ ${diffText.length > 0 ? diffText : "(no working-tree changes)"}
18320
+
18321
+ ` + `--- flow snapshot ---
18322
+ ${flowSnapshotJson}
18323
+ ` + `--- seeds (${input2.kind}) ---
18324
+ ${seedsJson}`;
18325
+ let modelResult;
18326
+ const modelTurnTimeoutMs = input2.modelTurnTimeoutMs ?? DEFAULT_MODEL_TURN_TIMEOUT_MS2;
18327
+ const turn = runModelTurn({
18328
+ system,
18329
+ user,
18330
+ requestId: `machine-wrap-up-${shortHash}`,
18331
+ ...input2.env !== undefined ? { env: input2.env } : {},
18332
+ ...input2.providerFactory !== undefined ? { providerFactory: input2.providerFactory } : {}
18333
+ }).then((result) => {
18334
+ modelResult = result;
18335
+ return "done";
18336
+ });
18337
+ let timer;
18338
+ const expired = new Promise((resolve) => {
18339
+ timer = setTimeout(() => resolve("timeout"), modelTurnTimeoutMs);
18340
+ });
18341
+ let raceOutcome;
18342
+ try {
18343
+ raceOutcome = await Promise.race([turn, expired]);
18344
+ } finally {
18345
+ if (timer !== undefined)
18346
+ clearTimeout(timer);
18347
+ }
18348
+ let summary;
18349
+ if (raceOutcome === "timeout") {
18350
+ turn.catch(() => {});
18351
+ summary = mechanicalSummary(diffText, course);
18352
+ } else {
18353
+ const result = modelResult;
18354
+ if (result.text.trim().length === 0 && !result.credentialAvailable) {
18355
+ return { ok: false, code: "no_credential" };
18356
+ }
18357
+ summary = result.text.trim().length > 0 ? result.text.trim() : mechanicalSummary(diffText, course);
18358
+ }
18359
+ const evidenceDir = path85.join(input2.cwd, ".metaproject", "workspaces", input2.workspaceId, "machine-evidence");
18360
+ await mkdir33(evidenceDir, { recursive: true });
18361
+ const diffFile = `${input2.kind}.${shortHash}.diff.txt`;
18362
+ const flowFile = `${input2.kind}.${shortHash}.flow.json`;
18363
+ const seedsFile = `${input2.kind}.${shortHash}.seeds.json`;
18364
+ await writeFileAtomic(path85.join(evidenceDir, diffFile), diffText);
18365
+ await writeFileAtomic(path85.join(evidenceDir, flowFile), flowSnapshotJson);
18366
+ await writeFileAtomic(path85.join(evidenceDir, seedsFile), seedsJson);
18367
+ const observedAt = now().toISOString();
18368
+ const relBase = `./.metaproject/workspaces/${input2.workspaceId}/machine-evidence`;
18369
+ const evidence = [
18370
+ { kind: "diff", uri: `${relBase}/${diffFile}`, revision: sha2562(diffText), observedAt },
18371
+ { kind: "flow", uri: `${relBase}/${flowFile}`, revision: sha2562(flowSnapshotJson), observedAt },
18372
+ { kind: "seeds", uri: `${relBase}/${seedsFile}`, revision: sha2562(seedsJson), observedAt }
18373
+ ];
18374
+ return {
18375
+ ok: true,
18376
+ resolution: {
18377
+ workspaceId: input2.workspaceId,
18378
+ sourceRevision,
18379
+ summary,
18380
+ evidence,
18381
+ expiresAt: new Date(now().getTime() + WRAP_UP_TTL_MS2).toISOString()
18382
+ }
18383
+ };
18384
+ }
18385
+ async function writeUnboundCandidateArtifact(dir, trigger, now, grouped, nonEmptyKinds) {
18386
+ const archiveDir = path85.join(dir, "slate-archive");
18387
+ await mkdir33(archiveDir, { recursive: true });
18388
+ const nowIso2 = now().toISOString();
18389
+ const filename = `${nowIso2.replace(/[:.]/g, "-")}-unbound-candidate.json`;
18390
+ const content = {
18391
+ recordType: "unbound-candidate",
18392
+ trigger,
18393
+ generatedAt: nowIso2,
18394
+ groups: nonEmptyKinds.map((kind) => ({
18395
+ kind,
18396
+ seeds: (grouped.get(kind) ?? []).map((seed) => ({ text: seed.text, source: describeSource(seed.source) }))
18397
+ }))
18398
+ };
18399
+ await writeFileAtomic(path85.join(archiveDir, filename), `${JSON.stringify(content, null, 2)}
18400
+ `);
18401
+ }
18402
+ async function writeWrapUpOutcomeArtifact(dir, trigger, now, groups) {
18403
+ try {
18404
+ const archiveDir = path85.join(dir, "slate-archive");
18405
+ await mkdir33(archiveDir, { recursive: true });
18406
+ const nowIso2 = now().toISOString();
18407
+ const filename = `${nowIso2.replace(/[:.]/g, "-")}-wrap-up-outcome.json`;
18408
+ const content = { recordType: "wrap-up-outcome", trigger, generatedAt: nowIso2, groups };
18409
+ await writeFileAtomic(path85.join(archiveDir, filename), `${JSON.stringify(content, null, 2)}
18410
+ `);
18411
+ } catch {}
18412
+ }
18413
+ async function proposeOneGroup(params) {
18414
+ const wrapUpSource = params.wrapUpSource ?? "flow";
18415
+ try {
18416
+ const resolved = await resolveMachineWrapUp({
18417
+ cwd: params.cwd,
18418
+ workspaceId: params.workspaceId,
18419
+ slate: params.slate,
18420
+ kind: params.kind,
18421
+ now: params.now,
18422
+ ...params.env !== undefined ? { env: params.env } : {},
18423
+ ...params.providerFactory !== undefined ? { providerFactory: params.providerFactory } : {},
18424
+ ...params.modelTurnTimeoutMs !== undefined ? { modelTurnTimeoutMs: params.modelTurnTimeoutMs } : {}
18425
+ });
18426
+ if (!resolved.ok)
18427
+ return { kind: params.kind, outcome: "no_credential" };
18428
+ const flowEvidence = resolved.resolution.evidence.find((item) => item.kind === "flow");
18429
+ const sourceRef = (flowEvidence ?? resolved.resolution.evidence[0]).uri;
18430
+ const flowRef = params.slate.course.flowRef ?? "";
18431
+ const dedupHash = sha2562(`${params.workspaceId}:${flowRef}:${resolved.resolution.sourceRevision}:${params.kind}`);
18432
+ const proposalId = `wrapup-${dedupHash.slice(0, 32)}`;
18433
+ const wrapUpAuthority = createTrustedWrapUpAuthority({
18434
+ now: params.now,
18435
+ resolveExplicitWrapUp: async (request) => {
18436
+ if (request.source !== wrapUpSource) {
18437
+ throw new Error(`machine-wrap-up only resolves "${wrapUpSource}" wrap-ups, got "${request.source}"`);
18438
+ }
18439
+ return resolved.resolution;
18440
+ }
18441
+ });
18442
+ const { service: service2, authorizationServer } = createHarnessProposalLifecycleService(params.cwd, {
18443
+ workspaceId: params.workspaceId,
18444
+ now: params.now
18445
+ });
18446
+ const requestCorrelationId = randomUUID12();
18447
+ const actor = await authorizationServer.actorContextFor(undefined, requestCorrelationId);
18448
+ if (!actor)
18449
+ throw new Error("trusted ActorContext is required for a machine wrap-up propose");
18450
+ const provenance = await wrapUpAuthority.issue({ actor, source: wrapUpSource, sourceRef });
18451
+ try {
18452
+ const proposal = await service2.create({
18453
+ request: undefined,
18454
+ requestCorrelationId,
18455
+ workspaceId: params.workspaceId,
18456
+ id: proposalId,
18457
+ proposalRevision: "1",
18458
+ kind: params.kind,
18459
+ wrapUp: provenance
18460
+ });
18461
+ return { kind: params.kind, outcome: "proposed", proposalId: proposal.id };
18462
+ } catch (error2) {
18463
+ if (error2 instanceof ProposalLifecycleError && error2.code === "conflict") {
18464
+ return { kind: params.kind, outcome: "conflict" };
18465
+ }
18466
+ throw error2;
18467
+ }
18468
+ } catch (error2) {
18469
+ const message2 = error2 instanceof Error ? error2.message : String(error2);
18470
+ return { kind: params.kind, outcome: "error", message: message2 };
18471
+ }
18472
+ }
18473
+ async function runWrapUp(input2) {
18474
+ const now = input2.now ?? (() => new Date);
18475
+ const grouped = groupSeedsByKind(input2.slate);
18476
+ const nonEmptyKinds = [...grouped.keys()].filter((kind) => (grouped.get(kind)?.length ?? 0) > 0);
18477
+ if (nonEmptyKinds.length === 0) {
18478
+ return { groups: [] };
18479
+ }
18480
+ let workspaceId = input2.slate.workspaceId;
18481
+ if (workspaceId === undefined && input2.wrapUpSource === "external-slate") {
18482
+ await writeUnboundCandidateArtifact(input2.dir, input2.trigger, now, grouped, nonEmptyKinds);
18483
+ const groups2 = nonEmptyKinds.map((kind) => ({ kind, outcome: "unbound-candidate" }));
18484
+ await writeWrapUpOutcomeArtifact(input2.dir, input2.trigger, now, groups2);
18485
+ return { groups: groups2 };
18486
+ }
18487
+ if (workspaceId === undefined) {
18488
+ const topicHint = dedupedAttributedSeeds(input2.slate).map((seed) => seed.text).join("; ").trim().slice(0, 2000);
18489
+ const resolver = input2.resolveWorkspace ?? resolveOrCreateWorkspace;
18490
+ const resolved = await resolver({
18491
+ cwd: input2.cwd,
18492
+ topicHint: topicHint.length > 0 ? topicHint : "Untitled session wrap-up",
18493
+ ...input2.env !== undefined ? { env: input2.env } : {},
18494
+ ...input2.providerFactory !== undefined ? { providerFactory: input2.providerFactory } : {},
18495
+ ...input2.modelTurnTimeoutMs !== undefined ? { modelTurnTimeoutMs: input2.modelTurnTimeoutMs } : {}
18496
+ });
18497
+ if (!resolved.ok) {
18498
+ await writeUnboundCandidateArtifact(input2.dir, input2.trigger, now, grouped, nonEmptyKinds);
18499
+ const groups2 = nonEmptyKinds.map((kind) => ({ kind, outcome: "unbound-candidate" }));
18500
+ await writeWrapUpOutcomeArtifact(input2.dir, input2.trigger, now, groups2);
18501
+ return { groups: groups2 };
18502
+ }
18503
+ workspaceId = resolved.workspaceId;
18504
+ const boundWorkspaceId = resolved.workspaceId;
18505
+ try {
18506
+ await writeSlate(input2.dir, (prev) => ({
18507
+ anchors: prev?.anchors ?? { root: "", touched: [] },
18508
+ course: prev?.course ?? {},
18509
+ seeds: prev?.seeds ?? [],
18510
+ workspaceId: boundWorkspaceId
18511
+ }));
18512
+ } catch {}
18513
+ }
18514
+ const groups = await Promise.all(nonEmptyKinds.map((kind) => proposeOneGroup({
18515
+ cwd: input2.cwd,
18516
+ workspaceId,
18517
+ slate: input2.slate,
18518
+ kind,
18519
+ now,
18520
+ ...input2.wrapUpSource !== undefined ? { wrapUpSource: input2.wrapUpSource } : {},
18521
+ ...input2.env !== undefined ? { env: input2.env } : {},
18522
+ ...input2.providerFactory !== undefined ? { providerFactory: input2.providerFactory } : {},
18523
+ ...input2.modelTurnTimeoutMs !== undefined ? { modelTurnTimeoutMs: input2.modelTurnTimeoutMs } : {}
18524
+ })));
18525
+ await writeWrapUpOutcomeArtifact(input2.dir, input2.trigger, now, groups);
18526
+ return { groups };
18527
+ }
18528
+ var execFileAsync, WRAP_UP_TTL_MS2, DEFAULT_MODEL_TURN_TIMEOUT_MS2 = 30000;
18529
+ var init_machine_wrap_up = __esm(() => {
18530
+ init_fs();
18531
+ init_slate();
18532
+ init_slate_course();
18533
+ init_trusted_wrap_up();
18534
+ init_proposal_lifecycle();
18535
+ init_workspace_resolve();
18536
+ init_single_turn();
18537
+ execFileAsync = promisify(execFile);
18538
+ WRAP_UP_TTL_MS2 = 60 * 60 * 1000;
18539
+ });
18540
+
18500
18541
  // src/session/slate-lifecycle.ts
18501
18542
  import { execFile as execFile2 } from "child_process";
18502
18543
  import { promisify as promisify2 } from "util";
@@ -18870,8 +18911,8 @@ function buildAgentSystemInstruction(orient, ctx = {}) {
18870
18911
  ` + "- To find where a function/class/symbol is defined (or who calls it): call " + "**graph_symbol** with `{ name }` FIRST \u2014 it returns the exact file + line in one call. " + "read_file is capped at its first bytes only (see its own description) and cannot page " + "forward, so re-reading a large file to hunt for a symbol wastes calls without ever " + "reaching content past the cap; use graph_symbol (or search_code for a text pattern) " + `to get the location, THEN read_file only if you need surrounding context near it.
18871
18912
  ` + "- Prefer ONE correct shell_exec over many exploratory tool calls when the user asks " + `to run a known keryx workflow.
18872
18913
  ` + "- Tool-call budget: shell_exec, file-mutating shell, workspace_create/workspace_propose, and spawn_subagent " + "all share ONE small per-turn pool (distinct non-read actions), separate from the much larger read-tool pool. " + "search_code/graph_*/memory_search/read_wiki/wiki_*/test_related/health_status/flow_status/repomap/read_file/" + "list_dir do NOT touch it. Conserve the small pool: batch multiple shell steps into ONE call with `&&` instead " + "of issuing them one at a time, get a command's arguments right the first time instead of trying variants, and " + "for any check covered by a read tool above, use that tool instead of shelling out to the equivalent `keryx \u2026` " + `CLI command.
18873
- ` + "- This session has its own Slate (working-set scratch, not project knowledge): " + "**slate_read** shows the Course (if a Flow is bound) and Seeds recorded so far \u2014 nothing " + "here is auto-injected, so call it if you want to see it. **slate_write_seed** with " + "`{ text, kind? }` records a draft hypothesis/decision/follow-up worth a later human review " + "\u2014 use it for a real finding worth not losing (e.g. a root cause, a risk, a suggested " + `change), not for routine progress notes. A Seed is never accepted knowledge by itself.
18874
- ` + "- Shared Agent Context (SAC) workspaces hold accepted, evidence-backed project context " + "beyond this codebase. **workspace_list** with `{ includeArchived? }` shows every workspace " + "visible to you \u2014 call it first when the user references a shared team workspace or accepted " + "project context, or before creating a new workspace, to judge whether an existing one " + "already fits the current topic. **workspace_show** with `{ workspaceId }` shows one " + "workspace's manifest. **workspace_overview** with `{ workspaceId }`, then **workspace_read** " + "with `{ workspaceId, itemId }` for one specific item, reads its accepted Facts/Work/Know-how. " + "**workspace_create** with `{ title, component? }` creates a new workspace \u2014 only when " + "workspace_list found no fitting one; a workspace is meant to persist across sessions, so " + "prefer an existing one over creating another for the same topic. **workspace_propose** with " + "`{ workspaceId, kind, sessionId?, note? }` (sessionId defaults to this session) proposes a decision/wiki-update/memory-entry/" + "follow-up/contract-change/risk from this session for later human review \u2014 it never accepts " + "anything by itself; accepting always requires a human running `keryx workspace review` at a " + `real terminal, never this tool.
18914
+ ` + "- This session has its own Slate (working-set scratch, not project knowledge): " + "**slate_read** shows the Course (if a Flow is bound) and Seeds recorded so far \u2014 nothing " + "here is auto-injected, so call it if you want to see it. **slate_write_seed** with " + "`{ text, kind? }` records a draft hypothesis/decision/follow-up worth a later human " + "review. WRITE A SEED when you: found a root cause or a bug worth remembering; changed or " + "added code (summarize WHAT changed and WHY); took a design/architecture decision; " + "identified a risk; or discovered a constraint/lesson. Use the `kind` that fits: " + "`decision` (a choice made), `wiki-update` (something a wiki page should say), " + "`memory-entry` (a lesson/constraint), `follow-up` (a TODO for a later session), " + "`risk`, or `contract-change`. Keep each Seed to 2-3 sentences, concrete and specific. " + "Do NOT write Seeds for routine progress notes, one-shot operational requests (e.g. " + '"run git pull", "count files"), or trivia \u2014 those need no workspace and no proposal. ' + "Seeds are the ONLY input wrap-up proposes from: a session whose Slate has zero Seeds " + `produces zero proposals. A Seed is never accepted knowledge by itself.
18915
+ ` + "- Shared Agent Context (SAC) workspaces hold accepted, evidence-backed project context " + "beyond this codebase. **workspace_list** with `{ includeArchived? }` shows every workspace " + "visible to you \u2014 call it first when the user references a shared team workspace or accepted " + "project context, or before creating a new workspace, to judge whether an existing one " + "already fits the current topic. **workspace_show** with `{ workspaceId }` shows one " + "workspace's manifest. **workspace_overview** with `{ workspaceId }`, then **workspace_read** " + "with `{ workspaceId, itemId }` for one specific item, reads its accepted Facts/Work/Know-how. " + "**workspace_create** with `{ title, component? }` creates a new workspace AND binds it to " + "this session's slate (wrap-up then proposes into it) \u2014 only when workspace_list found no " + "fitting one and the session has real, durable results worth persisting; a workspace is " + "meant to persist across sessions, so prefer an existing one over creating another for the " + "same topic, and do NOT create one for one-shot operational requests. **workspace_propose** with " + "`{ workspaceId, kind, sessionId?, note? }` (sessionId defaults to this session) proposes a decision/wiki-update/memory-entry/" + "follow-up/contract-change/risk from this session for later human review \u2014 it never accepts " + "anything by itself; accepting always requires a human running `keryx workspace review` at a " + `real terminal, never this tool.
18875
18916
  ` + "- When you need a decision, interview step, or clarification: use **ask_user** with " + "2\u20136 options `{ id, label, description, recommended? }` (mark one recommended). " + `Do not dump long prose questions without options.
18876
18917
  ` + "- For a focused independent subtask (investigate X, review Y, research Z): use " + "**spawn_subagent** with `{ task, mode?: 'read_only'|'general', label? }`. " + "Default mode is read_only (no shell). Prefer spawn for work that can finish " + `without your intermediate turns; do not spawn for trivial one-line answers.
18877
18918
 
@@ -19114,22 +19155,6 @@ async function runAgentTurnCore(io, deps, history, userLine, options = {}) {
19114
19155
  if (freshSlate !== undefined) {
19115
19156
  history.push({ role: "user", content: renderAnchorsBlock(freshSlate.anchors), provenance: "project" });
19116
19157
  io.onHistoryChange?.("tool");
19117
- if (freshSlate.workspaceId === undefined) {
19118
- const resolver = options.resolveWorkspace ?? resolveOrCreateWorkspace;
19119
- const resolved = await resolver({
19120
- cwd: options.slateSession.cwd,
19121
- topicHint: userLine,
19122
- provider: deps.providerId,
19123
- model: deps.modelId
19124
- });
19125
- if (resolved.ok) {
19126
- await writeSlate(options.slateSession.dir, (prev) => {
19127
- if (!prev)
19128
- throw new Error(`SLATE-16 bind: no open slate in ${options.slateSession.dir}`);
19129
- return { ...prev, workspaceId: resolved.workspaceId };
19130
- });
19131
- }
19132
- }
19133
19158
  }
19134
19159
  }
19135
19160
  }
@@ -19710,7 +19735,6 @@ var init_agent = __esm(() => {
19710
19735
  init_scheduler();
19711
19736
  init_slate();
19712
19737
  init_slate_course();
19713
- init_workspace_resolve();
19714
19738
  init_machine_wrap_up();
19715
19739
  init_slate_lifecycle();
19716
19740
  init_slate_terminal_state();
@@ -26111,6 +26135,10 @@ ${markdown.slice(4)}`;
26111
26135
  }
26112
26136
  return markdown;
26113
26137
  }
26138
+ function extractFrontmatterStatus(markdown) {
26139
+ const bodyMatch = /\nStatus:\s*(\S+)/i.exec(markdown) ?? /^Status:\s*(\S+)/im.exec(markdown);
26140
+ return bodyMatch?.[1] ?? "draft";
26141
+ }
26114
26142
  async function mapPool(items, concurrency, worker) {
26115
26143
  const results = new Array(items.length);
26116
26144
  let next = 0;
@@ -26137,7 +26165,6 @@ async function wikiEnrich(input2) {
26137
26165
  const concurrency = Math.max(1, Math.min(MAX_CONCURRENCY, input2.concurrency ?? DEFAULT_CONCURRENCY));
26138
26166
  const maxOutputTokens = input2.maxOutputTokens ?? DEFAULT_MAX_OUTPUT_TOKENS;
26139
26167
  const validate = input2.validate !== false;
26140
- const markAccepted = input2.keepStatus === true ? false : input2.markAccepted !== false;
26141
26168
  const wikiConfig = await loadWikiConfig(input2.cwd);
26142
26169
  const result = {
26143
26170
  provider,
@@ -26231,9 +26258,7 @@ async function wikiEnrich(input2) {
26231
26258
  return { path: page.relativePath, action: "failed", reason: `validation: ${structural}` };
26232
26259
  }
26233
26260
  }
26234
- if (markAccepted) {
26235
- enriched = setFrontmatterStatus(enriched, "accepted");
26236
- }
26261
+ enriched = setFrontmatterStatus(enriched, extractFrontmatterStatus(original));
26237
26262
  if (input2.dryRun) {
26238
26263
  onPage({ index, total, path: page.relativePath, status, phase: "done" });
26239
26264
  return {
@@ -26247,7 +26272,7 @@ async function wikiEnrich(input2) {
26247
26272
  await writeFile36(page.absolutePath, `${enriched.endsWith(`
26248
26273
  `) ? enriched : `${enriched}
26249
26274
  `}`, "utf8");
26250
- onPage({ index, total, path: page.relativePath, status: markAccepted ? "accepted" : status, phase: "done" });
26275
+ onPage({ index, total, path: page.relativePath, status, phase: "done" });
26251
26276
  return {
26252
26277
  path: page.relativePath,
26253
26278
  action: "enriched",
@@ -26273,7 +26298,6 @@ async function wikiEnrich(input2) {
26273
26298
  model,
26274
26299
  maxOutputTokens,
26275
26300
  validate,
26276
- markAccepted,
26277
26301
  concurrency,
26278
26302
  total,
26279
26303
  onPage,
@@ -26356,8 +26380,8 @@ function finalizeEnrichedText(original, rawText, options) {
26356
26380
  if (options.validate) {
26357
26381
  structuralError = validateEnrichedMarkdown(original, content);
26358
26382
  }
26359
- if (structuralError === null && options.markAccepted) {
26360
- content = setFrontmatterStatus(content, "accepted");
26383
+ if (structuralError === null) {
26384
+ content = setFrontmatterStatus(content, extractFrontmatterStatus(original));
26361
26385
  }
26362
26386
  return { content, structuralError };
26363
26387
  }
@@ -26384,7 +26408,7 @@ async function finishSuccess(ctx, page, originalRaw, content, keyFiles, extra) {
26384
26408
  index,
26385
26409
  total: ctx.total,
26386
26410
  path: page.relativePath,
26387
- status: ctx.markAccepted ? "accepted" : status,
26411
+ status,
26388
26412
  phase: "done"
26389
26413
  });
26390
26414
  return {
@@ -26428,8 +26452,7 @@ async function runDeepSingle(ctx, item) {
26428
26452
  let content = null;
26429
26453
  if (rawText !== null) {
26430
26454
  const finalized = finalizeEnrichedText(original, rawText, {
26431
- validate: ctx.validate,
26432
- markAccepted: ctx.markAccepted
26455
+ validate: ctx.validate
26433
26456
  });
26434
26457
  if (finalized.structuralError === null) {
26435
26458
  content = finalized.content;
@@ -26614,8 +26637,7 @@ async function runLightBatch(ctx, items) {
26614
26637
  continue;
26615
26638
  }
26616
26639
  const finalized = finalizeEnrichedText(item.original, rawText, {
26617
- validate: ctx.validate,
26618
- markAccepted: ctx.markAccepted
26640
+ validate: ctx.validate
26619
26641
  });
26620
26642
  if (finalized.structuralError !== null) {
26621
26643
  ctx.onPage({ index, total: ctx.total, path: item.page.relativePath, status, phase: "failed" });
@@ -26666,7 +26688,6 @@ async function runRlmPipeline(ctxInput) {
26666
26688
  model: ctxInput.model,
26667
26689
  maxOutputTokens: ctxInput.maxOutputTokens,
26668
26690
  validate: ctxInput.validate,
26669
- markAccepted: ctxInput.markAccepted,
26670
26691
  total: ctxInput.total,
26671
26692
  onPage: ctxInput.onPage,
26672
26693
  input: input2,
@@ -36675,7 +36696,6 @@ async function runEnrich(args2) {
36675
36696
  const resume = args2.includes("--resume");
36676
36697
  const refreshGraph = args2.includes("--refresh-graph");
36677
36698
  const dryRun = args2.includes("--dry-run");
36678
- const keepStatus = args2.includes("--keep-status");
36679
36699
  const noValidate = args2.includes("--no-validate");
36680
36700
  const valueFlags = new Set([
36681
36701
  "--page",
@@ -36744,9 +36764,7 @@ async function runEnrich(args2) {
36744
36764
  resume,
36745
36765
  refreshGraph,
36746
36766
  dryRun,
36747
- keepStatus,
36748
36767
  validate: !noValidate,
36749
- markAccepted: !keepStatus,
36750
36768
  ...prompt ? { prompt } : {},
36751
36769
  ...provider ? { provider } : {},
36752
36770
  ...model ? { model } : {},
@@ -36823,10 +36841,12 @@ Usage:
36823
36841
  keryx wiki validate
36824
36842
  keryx wiki ask "<question>" [--k <n>] [--rerank]
36825
36843
  keryx wiki enrich [<page>|--all] [--force] [--list] [--resume] [--limit N] [--concurrency N]
36826
- [--refresh-graph] [--max-tokens N] [--keep-status] [--no-validate]
36844
+ [--refresh-graph] [--max-tokens N] [--no-validate]
36827
36845
  [--prompt "<i>"] [--provider <p>] [--model <m>] [--dry-run] [--json]
36828
36846
  # defaults: drafts only; provider/model from auth.json; validate on;
36829
- # mark Status: accepted; concurrency 1 (raise for parallel page swarm)
36847
+ # concurrency 1 (raise for parallel page swarm)
36848
+ # rewrites prose only \u2014 Status is always left exactly as it was
36849
+ # before the run; enrich can never itself accept a page (issue #391)
36830
36850
  keryx wiki context
36831
36851
  keryx wiki backlinks <wiki-page-or-code-file>
36832
36852
 
@@ -49836,7 +49856,7 @@ function buildInteractiveAgentTools(input2) {
49836
49856
  applyPatchTool(input2.cwd),
49837
49857
  workspaceOverviewTool(input2.cwd),
49838
49858
  workspaceReadTool(input2.cwd),
49839
- workspaceCreateTool(input2.cwd),
49859
+ workspaceCreateTool(input2.cwd, getSessionDir),
49840
49860
  workspaceListTool(input2.cwd),
49841
49861
  workspaceShowTool(input2.cwd),
49842
49862
  workspaceProposeTool(input2.cwd, getSessionDir),
@@ -49941,7 +49961,8 @@ var PREFIX_BANNED = new Set([
49941
49961
  "osascript",
49942
49962
  "open",
49943
49963
  "tee",
49944
- "cd"
49964
+ "cd",
49965
+ "keryx"
49945
49966
  ]);
49946
49967
  var PREFIX_BANNED_READERS = new Set([
49947
49968
  "cat",
@@ -50012,6 +50033,17 @@ function validateShellPattern(pattern) {
50012
50033
  }
50013
50034
  return { ok: true };
50014
50035
  }
50036
+ function getRunningBinaryName() {
50037
+ try {
50038
+ const argv0 = process.argv0;
50039
+ if (!argv0)
50040
+ return "";
50041
+ const name = argv0.split(/[\\/]/).pop() ?? "";
50042
+ return name.toLowerCase();
50043
+ } catch {
50044
+ return "";
50045
+ }
50046
+ }
50015
50047
  function bannedPrefixGrant(pattern, firstToken) {
50016
50048
  const rest = pattern.slice(firstToken.length).trim();
50017
50049
  const wildcardOnly = /^\*+$/.test(rest) || rest.length === 0 && /\*+$/.test(firstToken);
@@ -50024,6 +50056,13 @@ function bannedPrefixGrant(pattern, firstToken) {
50024
50056
  reason: `\`${word} *\` grants arbitrary execution: ${word} is an interpreter or wrapper, so its first token does not constrain what runs`
50025
50057
  };
50026
50058
  }
50059
+ const runningBinary = getRunningBinaryName();
50060
+ if (runningBinary && word === runningBinary) {
50061
+ return {
50062
+ word,
50063
+ reason: `\`${word} *\` grants arbitrary execution: ${word} can run arbitrary subcommands, so remembering this grant would silently approve mutating/destructive operations forever`
50064
+ };
50065
+ }
50027
50066
  if (PREFIX_BANNED_READERS.has(word)) {
50028
50067
  return {
50029
50068
  word,
@@ -53106,10 +53145,11 @@ init_agent();
53106
53145
  init_slate_lifecycle();
53107
53146
  init_slate();
53108
53147
  init_workspace_service();
53109
- init_workspace_resolve();
53110
53148
  init_service7();
53149
+ init_store2();
53111
53150
  init_fs();
53112
53151
  import path149 from "path";
53152
+ import { readFile as readFile79 } from "fs/promises";
53113
53153
  var POSITIVE_INTEGER = /^[1-9][0-9]*$/;
53114
53154
  var DEFAULT_AUTO_GOAL_ROUNDS = 8;
53115
53155
  function parseGoalArgs(rest) {
@@ -53211,9 +53251,20 @@ async function autoProvisionFlow(cwd, goalText) {
53211
53251
  await service5.start({ cwd, id: result.flow.id });
53212
53252
  return result.flow.id;
53213
53253
  }
53254
+ var ROUND_DONE_MARKER = "GOAL_ROUND_COMPLETE";
53255
+ function continuationRoundClaimsDone(history) {
53256
+ for (let i = history.length - 1;i >= 0; i--) {
53257
+ const message2 = history[i];
53258
+ if (message2?.role === "assistant") {
53259
+ return message2.content.includes(ROUND_DONE_MARKER);
53260
+ }
53261
+ }
53262
+ return false;
53263
+ }
53214
53264
  async function buildContinuationMessage(cwd, slateSession, round4, roundsCap) {
53215
53265
  const totalRounds = roundsCap + 1;
53216
- const generic = `Continue working toward the stated goal (round ${round4} of ${totalRounds}).`;
53266
+ const doneInstruction = `If \u2014 and only if \u2014 the stated goal is now FULLY achieved and there is nothing further to do this round, ` + `end your reply with the exact line ${ROUND_DONE_MARKER} on its own, and nothing else on that line. ` + `Otherwise do not include that line at all.`;
53267
+ const generic = `Continue working toward the stated goal (round ${round4} of ${totalRounds}). ${doneInstruction}`;
53217
53268
  const slate = await readSlate(slateSession.dir).catch(() => {
53218
53269
  return;
53219
53270
  });
@@ -53251,11 +53302,61 @@ function parseVerifierVerdict(output2) {
53251
53302
  return;
53252
53303
  }
53253
53304
  }
53254
- async function runGoalVerifier(deps, goalText) {
53305
+ var MAX_EVIDENCE_SEEDS = 10;
53306
+ function summarizeRecentSeeds(seeds) {
53307
+ return seeds.slice(-MAX_EVIDENCE_SEEDS).map((seed) => `- Seed${seed.kind !== undefined ? ` [${seed.kind}]` : ""}: ${seed.text}`);
53308
+ }
53309
+ function summarizeWorkspaceProposals(history) {
53310
+ const lines = [];
53311
+ for (const message2 of history) {
53312
+ if (message2.role !== "assistant" || message2.toolCalls === undefined) {
53313
+ continue;
53314
+ }
53315
+ for (const call of message2.toolCalls) {
53316
+ if (call.name !== "workspace_propose") {
53317
+ continue;
53318
+ }
53319
+ const resultMessage = history.find((candidate) => candidate.role === "tool" && candidate.toolCallId === call.id);
53320
+ let argSummary = call.arguments;
53321
+ try {
53322
+ const parsedArgs = JSON.parse(call.arguments);
53323
+ const kind2 = typeof parsedArgs.kind === "string" ? parsedArgs.kind : "?";
53324
+ const note2 = typeof parsedArgs.note === "string" ? parsedArgs.note : undefined;
53325
+ argSummary = `kind=${kind2}${note2 !== undefined ? `, note=${note2}` : ""}`;
53326
+ } catch {}
53327
+ const outcome = resultMessage !== undefined ? resultMessage.content : "(no result recorded this run)";
53328
+ lines.push(`- workspace_propose: ${argSummary} -> ${outcome}`);
53329
+ }
53330
+ }
53331
+ return lines;
53332
+ }
53333
+ async function flowDefersCompletionToVerifier(cwd, flowId) {
53334
+ try {
53335
+ const dir = await resolveFlowDir(cwd, flowId);
53336
+ const text = await readFile79(acPath(cwd, dir), "utf8");
53337
+ const normalized = text.replace(/\s+/g, " ");
53338
+ return normalized.includes("judged by the verifier subagent");
53339
+ } catch {
53340
+ return false;
53341
+ }
53342
+ }
53343
+ async function buildVerifierEvidence(cwd, slateSession, history) {
53344
+ const slate = await readSlate(slateSession.dir).catch(() => {
53345
+ return;
53346
+ });
53347
+ const lines = [...slate !== undefined ? summarizeRecentSeeds(slate.seeds) : [], ...summarizeWorkspaceProposals(history)];
53348
+ const evidenceText = lines.length > 0 ? lines.join(`
53349
+ `) : "(no Seeds or workspace_propose records were recorded this run)";
53350
+ const flowId = slate?.course.flowRef;
53351
+ const deferToVerifier = flowId !== undefined ? await flowDefersCompletionToVerifier(cwd, flowId) : false;
53352
+ return { evidenceText, deferToVerifier };
53353
+ }
53354
+ async function runGoalVerifier(deps, goalText, cwd, slateSession, history, io, mintCallId) {
53255
53355
  const tool = deps.tools.find((candidate) => candidate.definition.name === "spawn_subagent");
53256
53356
  if (tool === undefined) {
53257
53357
  return;
53258
53358
  }
53359
+ const { evidenceText, deferToVerifier } = await buildVerifierEvidence(cwd, slateSession, history);
53259
53360
  const task = [
53260
53361
  "Independently verify whether the following goal has ACTUALLY been achieved, based on the",
53261
53362
  "current, real state of the repository (read the real files/tests \u2014 never trust a prior",
@@ -53263,24 +53364,56 @@ async function runGoalVerifier(deps, goalText) {
53263
53364
  "",
53264
53365
  `Goal: "${goalText}"`,
53265
53366
  "",
53367
+ "Evidence this run already produced (recent Slate Seeds and workspace_propose records) \u2014",
53368
+ "weigh this as real evidence of what was actually done, not merely a claim:",
53369
+ evidenceText,
53370
+ "",
53371
+ ...deferToVerifier ? [
53372
+ "This run's Task Manager flow acceptance criteria explicitly defer the completion",
53373
+ "judgment to THIS verifier check, not to the flow's own task checkboxes \u2014 its tasks",
53374
+ "are expected to remain unchecked even when the goal is genuinely achieved. Do NOT",
53375
+ "treat an incomplete/unchecked flow task list, by itself, as evidence the goal is NOT",
53376
+ "achieved; judge achievement from the real repository state and the evidence above.",
53377
+ ""
53378
+ ] : [],
53266
53379
  "Reply with EXACTLY one JSON object and nothing else, no prose before or after it:",
53267
53380
  '{"achieved": true or false, "gaps": ["specific reason it is not fully achieved", ...]}',
53268
53381
  '"gaps" must be empty when "achieved" is true.'
53269
53382
  ].join(`
53270
53383
  `);
53384
+ const input2 = { task, mode: "read_only", label: "goal-verifier" };
53385
+ const callId = mintCallId();
53386
+ io.onToolCall?.("spawn_subagent", JSON.stringify(input2));
53387
+ history.push({
53388
+ role: "assistant",
53389
+ content: "",
53390
+ provenance: "model",
53391
+ toolCalls: [{ id: callId, name: "spawn_subagent", arguments: JSON.stringify(input2) }]
53392
+ });
53393
+ io.onHistoryChange?.("tool");
53271
53394
  let result;
53272
53395
  try {
53273
- result = await tool.invoke({ task, mode: "read_only", label: "goal-verifier" });
53274
- } catch {
53396
+ result = await tool.invoke(input2);
53397
+ } catch (err) {
53398
+ const errorResult = {
53399
+ output: `spawn_subagent dispatch failed: ${err instanceof Error ? err.message : String(err)}`,
53400
+ isError: true
53401
+ };
53402
+ io.onToolResult?.("spawn_subagent", errorResult);
53403
+ history.push({ role: "tool", content: errorResult.output, provenance: "tool", toolCallId: callId });
53404
+ io.onHistoryChange?.("tool");
53275
53405
  return;
53276
53406
  }
53407
+ io.onToolResult?.("spawn_subagent", result);
53408
+ history.push({ role: "tool", content: result.output, provenance: "tool", toolCallId: callId });
53409
+ io.onHistoryChange?.("tool");
53277
53410
  if (result.isError) {
53278
53411
  return;
53279
53412
  }
53280
53413
  return parseVerifierVerdict(result.output);
53281
53414
  }
53282
53415
  async function runGoalCommand(params) {
53283
- const { raw, cwd, io, deps, history, slateSession, mintAttemptId, resolveWorkspace } = params;
53416
+ const { raw, cwd, io, deps, history, slateSession, mintAttemptId } = params;
53284
53417
  const parsed = parseGoalArgs(raw);
53285
53418
  if ("error" in parsed) {
53286
53419
  systemLine(io, `/goal: ${parsed.error}
@@ -53314,19 +53447,6 @@ async function runGoalCommand(params) {
53314
53447
  const base = prev ?? { anchors: { root: "", touched: [] }, course: {}, seeds: [] };
53315
53448
  return { ...base, workspaceId };
53316
53449
  });
53317
- } else {
53318
- const current = await readSlate(slateSession.dir);
53319
- if (current !== undefined && current.workspaceId === undefined) {
53320
- const resolver = resolveWorkspace ?? resolveOrCreateWorkspace;
53321
- const resolved2 = await resolver({ cwd, topicHint: parsed.text, provider: deps.providerId, model: deps.modelId });
53322
- if (resolved2.ok) {
53323
- await writeSlate(slateSession.dir, (prev) => {
53324
- if (!prev)
53325
- throw new Error(`SLATE-16 bind: no open slate in ${slateSession.dir}`);
53326
- return { ...prev, workspaceId: resolved2.workspaceId };
53327
- });
53328
- }
53329
- }
53330
53450
  }
53331
53451
  if (parsed.auto !== undefined) {
53332
53452
  const forCourse = await readSlate(slateSession.dir);
@@ -53363,10 +53483,21 @@ async function runGoalCommand(params) {
53363
53483
  systemLine(io, `/goal --auto: round ${round4}/${roundsCap + 1} \u2014 continuing toward the goal.
53364
53484
  `);
53365
53485
  await runAgentTurn(io, deps, history, continuationText, turnOptions);
53486
+ if (continuationRoundClaimsDone(history)) {
53487
+ systemLine(io, `/goal --auto: model signaled this round's work is complete (round ${round4}/${roundsCap + 1}) \u2014 ` + `ending the round budget early; the verifier will confirm.
53488
+ `);
53489
+ break;
53490
+ }
53366
53491
  }
53367
53492
  const wasOpenBeforeVerifier = slateSession.opened;
53368
- const verdict = await runGoalVerifier(deps, parsed.text);
53369
- if (verdict !== undefined && !verdict.achieved) {
53493
+ const verdict = await runGoalVerifier(deps, parsed.text, cwd, slateSession, history, io, mintAttemptId);
53494
+ if (verdict === undefined) {
53495
+ systemLine(io, `/goal --auto: verifier unavailable \u2014 outcome not independently checked.
53496
+ `);
53497
+ } else if (verdict.achieved) {
53498
+ systemLine(io, `/goal --auto: verifier confirmed the goal is achieved.
53499
+ `);
53500
+ } else {
53370
53501
  systemLine(io, `/goal --auto: verifier found the goal not fully achieved${verdict.gaps.length > 0 ? ` \u2014 ${verdict.gaps.join("; ")}` : " (no specific gaps reported)"}
53371
53502
  `);
53372
53503
  if (roundsLeft > 0) {
@@ -53414,7 +53545,7 @@ import { spawnSync as spawnSync2 } from "child_process";
53414
53545
  // package.json
53415
53546
  var package_default = {
53416
53547
  name: "@mrciphersmith/keryx",
53417
- version: "0.2.55",
53548
+ version: "0.2.57",
53418
53549
  description: "Version-controlled project context for AI coding agents: code graph, architecture wiki, project memory, relevant tests, quality signals, and task flows.",
53419
53550
  private: false,
53420
53551
  publishConfig: {
@@ -54567,6 +54698,13 @@ function ensureHost(otui, chrome) {
54567
54698
  }
54568
54699
  const idx = state.tabs.findIndex((tab) => tab.id === state.active);
54569
54700
  const onStrip = focused !== null && containsNode(state.tabStrip, focused);
54701
+ const claimArrow = (direction) => state.input?.onArrowKeys?.(key, direction) === true;
54702
+ if (key.name === "left" && claimArrow("left")) {
54703
+ return;
54704
+ }
54705
+ if (key.name === "right" && claimArrow("right")) {
54706
+ return;
54707
+ }
54570
54708
  if (key.name === "left" || onStrip && key.name === "tab" && key.shift === true) {
54571
54709
  const prev = idx > 0 ? state.tabs[idx - 1] : undefined;
54572
54710
  if (prev !== undefined) {
@@ -54674,7 +54812,7 @@ init_store3();
54674
54812
  init_proposal_lifecycle();
54675
54813
  init_workspace_service();
54676
54814
  import { randomUUID as randomUUID24 } from "crypto";
54677
- import { readdir as readdir26 } from "fs/promises";
54815
+ import { readdir as readdir26, stat as stat8 } from "fs/promises";
54678
54816
  import path150 from "path";
54679
54817
 
54680
54818
  // src/sac/lifecycle-flag.ts
@@ -54725,14 +54863,18 @@ async function computeLifecycleFlags(cwd, now = () => new Date) {
54725
54863
 
54726
54864
  // src/sac/catch-up.ts
54727
54865
  init_proposal_evidence();
54866
+ init_collect();
54867
+ init_store();
54728
54868
  async function buildCatchUp(input2) {
54729
- const [proposals, sessionCategories, lifecycleFlagsAll] = await Promise.all([
54869
+ const [proposals, sessionCategories, lifecycleFlagsAll, unreviewedPathsAll] = await Promise.all([
54730
54870
  collectProposals(input2.cwd, input2.workspaceId),
54731
54871
  collectSessionCategories(input2.cwd),
54732
- computeLifecycleFlags(input2.cwd)
54872
+ computeLifecycleFlags(input2.cwd),
54873
+ detectUnreviewedSacPathChanges(input2.cwd, listSessions(input2.cwd))
54733
54874
  ]);
54734
54875
  const lifecycleFlags = input2.workspaceId === undefined ? lifecycleFlagsAll : lifecycleFlagsAll.filter((flag) => flag.kind !== "workspace" || flag.ref === input2.workspaceId);
54735
- return { proposals, ...sessionCategories, lifecycleFlags };
54876
+ const unreviewedPaths = input2.workspaceId === undefined ? unreviewedPathsAll : unreviewedPathsAll.filter((item) => item.workspaceId === input2.workspaceId);
54877
+ return { proposals, ...sessionCategories, lifecycleFlags, unreviewedPaths };
54736
54878
  }
54737
54879
  async function collectProposals(cwd, workspaceId) {
54738
54880
  const authorizationServer = localWorkspaceAuthorizationServer();
@@ -54795,8 +54937,82 @@ async function classifySession(session) {
54795
54937
  const workspaceId = (await safeReadSlate(dir))?.workspaceId;
54796
54938
  return { kind: "unknown", item: { type: "unknown", sessionId: session.id, ...workspaceId !== undefined ? { workspaceId } : {}, lastSeenAt: session.updatedAt } };
54797
54939
  }
54940
+ async function readExternalUnboundCandidates(cwd) {
54941
+ const extDir = externalSlatesDir(cwd);
54942
+ let externalIds;
54943
+ try {
54944
+ const entries = await readdir26(extDir);
54945
+ externalIds = entries.filter((name) => name.endsWith(".json")).map((name) => name.slice(0, -".json".length));
54946
+ } catch {
54947
+ return [];
54948
+ }
54949
+ const candidates = [];
54950
+ for (const id of externalIds) {
54951
+ const slate = await readExternalSlate(cwd, id);
54952
+ if (!slate)
54953
+ continue;
54954
+ if (slate.closedAt === undefined || slate.workspaceId !== undefined)
54955
+ continue;
54956
+ const unbound2 = await readNewestUnboundCandidateForExternal(cwd, id);
54957
+ if (unbound2) {
54958
+ candidates.push({
54959
+ type: "unbound-candidate",
54960
+ externalSessionId: id,
54961
+ evidencePath: unbound2.evidencePath,
54962
+ summary: unbound2.summary
54963
+ });
54964
+ } else {
54965
+ const summary = summarizeUnboundCandidate((slate.seeds ?? []).reduce((groups, seed) => {
54966
+ const kind2 = seed.kind ?? "follow-up";
54967
+ const existing = groups.find((g) => g.kind === kind2);
54968
+ if (existing) {
54969
+ existing.seeds = (existing.seeds ?? []).concat([{ text: seed.text }]);
54970
+ } else {
54971
+ groups.push({ kind: kind2, seeds: [{ text: seed.text }] });
54972
+ }
54973
+ return groups;
54974
+ }, []));
54975
+ candidates.push({
54976
+ type: "unbound-candidate",
54977
+ externalSessionId: id,
54978
+ evidencePath: path150.join(extDir, `${id}.json`),
54979
+ summary
54980
+ });
54981
+ }
54982
+ }
54983
+ return candidates;
54984
+ }
54985
+ async function readNewestUnboundCandidateForExternal(cwd, externalSessionId) {
54986
+ const evidenceDir = path150.join(externalSlatesDir(cwd), externalSessionId);
54987
+ let entries;
54988
+ try {
54989
+ entries = (await readdir26(evidenceDir)).filter((name) => name.endsWith("-unbound-candidate.json"));
54990
+ } catch {
54991
+ return;
54992
+ }
54993
+ entries.sort();
54994
+ for (let i = entries.length - 1;i >= 0; i--) {
54995
+ const evidencePath = path150.join(evidenceDir, entries[i]);
54996
+ const result = readConfigFile(evidencePath);
54997
+ if (!result.ok) {
54998
+ continue;
54999
+ }
55000
+ try {
55001
+ const parsed = JSON.parse(result.text);
55002
+ if (parsed.recordType !== "unbound-candidate")
55003
+ continue;
55004
+ return { evidencePath, summary: summarizeUnboundCandidate(parsed.groups) };
55005
+ } catch {
55006
+ continue;
55007
+ }
55008
+ }
55009
+ return;
55010
+ }
54798
55011
  async function collectSessionCategories(cwd) {
54799
- const classified = await Promise.all(listSessions(cwd).map((session) => classifySession(session)));
55012
+ const [classified, externalUnboundCandidates] = await Promise.all([
55013
+ Promise.all(listSessions(cwd).map((session) => classifySession(session))),
55014
+ readExternalUnboundCandidates(cwd)
55015
+ ]);
54800
55016
  const blocked2 = [];
54801
55017
  const unboundCandidates = [];
54802
55018
  const unknown = [];
@@ -54810,8 +55026,151 @@ async function collectSessionCategories(cwd) {
54810
55026
  else
54811
55027
  unknown.push(category.item);
54812
55028
  }
55029
+ unboundCandidates.push(...externalUnboundCandidates);
54813
55030
  return { blocked: blocked2, unboundCandidates, unknown };
54814
55031
  }
55032
+ var SESSION_ATTRIBUTION_SLACK_MS = 5 * 60000;
55033
+ var MTIME_CLOCK_SKEW_TOLERANCE_MS = 5000;
55034
+ async function collectReceiptTargets(cwd, owner) {
55035
+ const targets = new Set;
55036
+ const workspacesDir = path150.join(cwd, ".metaproject", "workspaces");
55037
+ let workspaceIds;
55038
+ try {
55039
+ workspaceIds = (await readdir26(workspacesDir, { withFileTypes: true })).filter((entry) => entry.isDirectory()).map((entry) => entry.name);
55040
+ } catch {
55041
+ return targets;
55042
+ }
55043
+ for (const workspaceId of workspaceIds) {
55044
+ const receiptsDir = path150.join(workspacesDir, workspaceId, `${owner}-write-receipts`);
55045
+ let files;
55046
+ try {
55047
+ files = (await readdir26(receiptsDir)).filter((name) => name.endsWith(".json"));
55048
+ } catch {
55049
+ continue;
55050
+ }
55051
+ for (const file of files) {
55052
+ const result = readConfigFile(path150.join(receiptsDir, file));
55053
+ if (!result.ok)
55054
+ continue;
55055
+ try {
55056
+ const receipt = JSON.parse(result.text);
55057
+ if (typeof receipt.targetRef === "string") {
55058
+ targets.add(receipt.targetRef.replace(/^\.\//, ""));
55059
+ }
55060
+ } catch {
55061
+ continue;
55062
+ }
55063
+ }
55064
+ }
55065
+ return targets;
55066
+ }
55067
+ async function attributeToSession(absolutePath, sessionsNewestFirst) {
55068
+ let mtimeMs;
55069
+ try {
55070
+ mtimeMs = (await stat8(absolutePath)).mtimeMs;
55071
+ } catch {
55072
+ return;
55073
+ }
55074
+ for (const session of sessionsNewestFirst) {
55075
+ const start = Date.parse(session.createdAt);
55076
+ const end = Date.parse(session.updatedAt);
55077
+ if (Number.isNaN(start) || Number.isNaN(end))
55078
+ continue;
55079
+ if (mtimeMs >= start - MTIME_CLOCK_SKEW_TOLERANCE_MS && mtimeMs <= end + SESSION_ATTRIBUTION_SLACK_MS) {
55080
+ const workspaceId = (await safeReadSlate(sessionDir(session.projectPath, session.id)))?.workspaceId;
55081
+ return { sessionId: session.id, workspaceId, changedAt: new Date(mtimeMs).toISOString() };
55082
+ }
55083
+ }
55084
+ return;
55085
+ }
55086
+ async function findSkillFiles(dir) {
55087
+ let entries;
55088
+ try {
55089
+ entries = await readdir26(dir, { withFileTypes: true });
55090
+ } catch {
55091
+ return [];
55092
+ }
55093
+ const found = [];
55094
+ for (const entry of entries) {
55095
+ const full = path150.join(dir, entry.name);
55096
+ if (entry.isDirectory()) {
55097
+ found.push(...await findSkillFiles(full));
55098
+ } else if (entry.isFile() && entry.name === "SKILL.md") {
55099
+ found.push(full);
55100
+ }
55101
+ }
55102
+ return found;
55103
+ }
55104
+ async function detectUnreviewedSacPathChanges(cwd, sessions) {
55105
+ const scoped = [...sessions].filter((session) => session.projectPath === cwd).sort((a, b) => Date.parse(b.updatedAt) - Date.parse(a.updatedAt));
55106
+ if (scoped.length === 0)
55107
+ return [];
55108
+ const [wikiReceipts, memoryReceipts, skillReceipts] = await Promise.all([
55109
+ collectReceiptTargets(cwd, "wiki"),
55110
+ collectReceiptTargets(cwd, "memory"),
55111
+ collectReceiptTargets(cwd, "skill")
55112
+ ]);
55113
+ const items = [];
55114
+ const wikiPages = await collectPages(cwd).catch(() => []);
55115
+ for (const page of wikiPages) {
55116
+ if ((page.status ?? "").toLowerCase() !== "accepted")
55117
+ continue;
55118
+ const targetRef = `wiki/${page.relativePath}`;
55119
+ if (wikiReceipts.has(targetRef))
55120
+ continue;
55121
+ const attribution = await attributeToSession(page.absolutePath, scoped);
55122
+ if (attribution === undefined)
55123
+ continue;
55124
+ items.push({
55125
+ type: "unreviewed-sac-path",
55126
+ sessionId: attribution.sessionId,
55127
+ ...attribution.workspaceId !== undefined ? { workspaceId: attribution.workspaceId } : {},
55128
+ owner: "wiki",
55129
+ path: targetRef,
55130
+ ...page.status !== null ? { status: page.status } : {},
55131
+ changedAt: attribution.changedAt
55132
+ });
55133
+ }
55134
+ const memoryEntries = await collectEntries(cwd).catch(() => []);
55135
+ for (const entry of memoryEntries) {
55136
+ if (entry.status !== "accepted")
55137
+ continue;
55138
+ const targetRef = `memory/${entry.relativePath}`;
55139
+ if (memoryReceipts.has(targetRef))
55140
+ continue;
55141
+ const attribution = await attributeToSession(entry.absolutePath, scoped);
55142
+ if (attribution === undefined)
55143
+ continue;
55144
+ items.push({
55145
+ type: "unreviewed-sac-path",
55146
+ sessionId: attribution.sessionId,
55147
+ ...attribution.workspaceId !== undefined ? { workspaceId: attribution.workspaceId } : {},
55148
+ owner: "memory",
55149
+ path: targetRef,
55150
+ status: entry.status,
55151
+ changedAt: attribution.changedAt
55152
+ });
55153
+ }
55154
+ const sacSkillsDir = path150.join(cwd, ".metaproject", "project-skills", "sac");
55155
+ const skillFiles = await findSkillFiles(sacSkillsDir);
55156
+ for (const absolutePath of skillFiles) {
55157
+ const targetRef = path150.relative(path150.join(cwd, ".metaproject"), absolutePath).split(path150.sep).join("/");
55158
+ if (skillReceipts.has(targetRef))
55159
+ continue;
55160
+ const attribution = await attributeToSession(absolutePath, scoped);
55161
+ if (attribution === undefined)
55162
+ continue;
55163
+ items.push({
55164
+ type: "unreviewed-sac-path",
55165
+ sessionId: attribution.sessionId,
55166
+ ...attribution.workspaceId !== undefined ? { workspaceId: attribution.workspaceId } : {},
55167
+ owner: "skill",
55168
+ path: targetRef,
55169
+ changedAt: attribution.changedAt
55170
+ });
55171
+ }
55172
+ return items;
55173
+ }
54815
55174
  async function isSlateEngaged(dir) {
54816
55175
  if (await pathExists(path150.join(dir, "slate.json")))
54817
55176
  return true;
@@ -55078,7 +55437,7 @@ async function loadInspectorCatchUp(cwd) {
55078
55437
  try {
55079
55438
  return await buildCatchUp({ cwd });
55080
55439
  } catch {
55081
- return { proposals: [], blocked: [], unboundCandidates: [], unknown: [], lifecycleFlags: [] };
55440
+ return { proposals: [], blocked: [], unboundCandidates: [], unknown: [], lifecycleFlags: [], unreviewedPaths: [] };
55082
55441
  }
55083
55442
  }
55084
55443
  function catchUpItems(report) {
@@ -55617,10 +55976,9 @@ function openWorkspace(otui, chrome, options) {
55617
55976
  var REVIEW_COMMAND = "/review";
55618
55977
  var REVIEW_FOOTER = [
55619
55978
  { key: "[/]", label: "item" },
55620
- { key: "a y", label: "accept proposal" },
55621
- { key: "d y", label: "decline proposal" },
55979
+ { key: "\u2190/\u2192 a d", label: "accept/decline" },
55980
+ { key: "enter y", label: "arm \u2192 confirm" },
55622
55981
  { key: "\u2191/\u2193", label: "scroll" },
55623
- { key: "\u2190/\u2192", label: "tabs" },
55624
55982
  { key: "esc", label: "close" }
55625
55983
  ];
55626
55984
  function isReviewCommand(line) {
@@ -55804,14 +56162,58 @@ function paintLines3(otui, renderer, body, lines, width) {
55804
56162
  parent.add(node);
55805
56163
  return node;
55806
56164
  }
56165
+ function paintActionButtons(otui, renderer, body, callbacks) {
56166
+ if (otui === undefined || otui === null || body === undefined || body === null) {
56167
+ return;
56168
+ }
56169
+ const parent = body;
56170
+ const boxCtor = otui.BoxRenderable;
56171
+ const textCtor = otui.TextRenderable;
56172
+ if (parent.add === undefined || boxCtor === undefined || textCtor === undefined) {
56173
+ return;
56174
+ }
56175
+ const BoxCtor = boxCtor;
56176
+ const TextCtor = textCtor;
56177
+ const addChild = (child) => parent.add?.(child);
56178
+ const theme = getTheme();
56179
+ const make = (label, id, color, onClick) => {
56180
+ const box = new BoxCtor(renderer, {
56181
+ id,
56182
+ flexShrink: 0,
56183
+ marginLeft: 1,
56184
+ paddingLeft: 1,
56185
+ paddingRight: 1,
56186
+ onMouseDown: (event) => {
56187
+ event.stopPropagation();
56188
+ onClick();
56189
+ }
56190
+ });
56191
+ const text = new TextCtor(renderer, { id: `${id}-t`, content: `[${label}]` });
56192
+ text.fg = color;
56193
+ box.add(text);
56194
+ addChild(box);
56195
+ const setActive = (active) => {
56196
+ box.backgroundColor = active ? theme.highlight : undefined;
56197
+ text.content = `[${label}]`;
56198
+ text.fg = color;
56199
+ };
56200
+ return { setActive };
56201
+ };
56202
+ return {
56203
+ accept: make("Accept", "review-accept", theme.ok, callbacks.onAccept),
56204
+ decline: make("Decline", "review-decline", theme.error, callbacks.onDecline)
56205
+ };
56206
+ }
55807
56207
  function presentReview(openModal2, otui, chrome, options) {
55808
56208
  const items = [...options.items];
55809
56209
  let selected = 0;
55810
56210
  let listScroll = 0;
55811
56211
  let detailScroll = 0;
55812
56212
  let status = { kind: "idle" };
56213
+ let focusedAction = "accept";
55813
56214
  let listNode;
55814
56215
  let detailNode;
56216
+ let actionButtons;
55815
56217
  let unsubscribeKey;
55816
56218
  const rendererHint = options.renderer ?? chrome?.renderer;
55817
56219
  const bodyRows = options.visibleRows ?? (typeof rendererHint?.width === "number" && typeof rendererHint.height === "number" ? modalBodyRows(resolveModalPanelSize(rendererHint.width, rendererHint.height).height) : 13);
@@ -55832,6 +56234,7 @@ function presentReview(openModal2, otui, chrome, options) {
55832
56234
  detailNode.content = windowLines3(detailLines(), detailScroll, bodyRows).join(`
55833
56235
  `);
55834
56236
  }
56237
+ updateButtons();
55835
56238
  };
55836
56239
  const moveSelection = (next) => {
55837
56240
  if (items.length === 0) {
@@ -55847,6 +56250,22 @@ function presentReview(openModal2, otui, chrome, options) {
55847
56250
  paintSelection();
55848
56251
  };
55849
56252
  const handlerFor = (decision) => decision === "accept" ? options.acceptProposal : options.declineProposal;
56253
+ const updateButtons = () => {
56254
+ if (actionButtons === undefined) {
56255
+ return;
56256
+ }
56257
+ const onProposal = items[selected]?.type === "proposal" && status.kind !== "done";
56258
+ const highlighted = status.kind === "armed" ? status.decision : focusedAction;
56259
+ actionButtons.accept.setActive(onProposal && highlighted === "accept");
56260
+ actionButtons.decline.setActive(onProposal && highlighted === "decline");
56261
+ };
56262
+ const armDecision = (decision) => {
56263
+ if (status.kind === "running") {
56264
+ return;
56265
+ }
56266
+ status = items[selected]?.type === "proposal" && handlerFor(decision) !== undefined ? { kind: "armed", decision } : { kind: "unavailable", decision };
56267
+ paintSelection();
56268
+ };
55850
56269
  const runDecision = (decision) => {
55851
56270
  const item = items[selected];
55852
56271
  const run = handlerFor(decision);
@@ -55871,16 +56290,43 @@ function presentReview(openModal2, otui, chrome, options) {
55871
56290
  ],
55872
56291
  initialTab: "list",
55873
56292
  footer: REVIEW_FOOTER,
56293
+ onArrowKeys: (key, direction) => {
56294
+ if (items[selected]?.type === "proposal" && status.kind !== "running" && status.kind !== "done" && handle?.activeTab() === "detail") {
56295
+ focusedAction = direction === "left" ? "accept" : "decline";
56296
+ status = { kind: "idle" };
56297
+ paintSelection();
56298
+ return true;
56299
+ }
56300
+ return false;
56301
+ },
55874
56302
  renderTab: (tabId, body, ctx) => {
55875
56303
  const renderer = options.renderer ?? chrome?.renderer;
55876
56304
  tabWidth = ctx?.width;
55877
56305
  if (tabId === "list") {
56306
+ detailNode = undefined;
56307
+ actionButtons = undefined;
55878
56308
  listScroll = scrollToReveal3(selected, listScroll, bodyRows);
55879
56309
  listNode = paintLines3(otui, renderer, body, windowLines3(listLines(), listScroll, bodyRows));
55880
56310
  return;
55881
56311
  }
56312
+ listNode = undefined;
55882
56313
  detailScroll = clampScroll3(detailScroll, detailLines().length, bodyRows);
55883
56314
  detailNode = paintLines3(otui, renderer, body, windowLines3(detailLines(), detailScroll, bodyRows), tabWidth);
56315
+ if (items[selected]?.type === "proposal") {
56316
+ actionButtons = paintActionButtons(otui, renderer, body, {
56317
+ onAccept: () => {
56318
+ focusedAction = "accept";
56319
+ armDecision("accept");
56320
+ },
56321
+ onDecline: () => {
56322
+ focusedAction = "decline";
56323
+ armDecision("decline");
56324
+ }
56325
+ });
56326
+ updateButtons();
56327
+ } else {
56328
+ actionButtons = undefined;
56329
+ }
55884
56330
  },
55885
56331
  onClose: () => {
55886
56332
  unsubscribeKey?.();
@@ -55897,7 +56343,7 @@ function presentReview(openModal2, otui, chrome, options) {
55897
56343
  }
55898
56344
  const onDetail = handle.activeTab() === "detail";
55899
56345
  if (onDetail && status.kind === "armed") {
55900
- if (token === "y") {
56346
+ if (token === "y" || token === "return" || token === "enter") {
55901
56347
  runDecision(status.decision);
55902
56348
  } else {
55903
56349
  status = { kind: "idle" };
@@ -55914,15 +56360,29 @@ function presentReview(openModal2, otui, chrome, options) {
55914
56360
  return;
55915
56361
  }
55916
56362
  if (token === "return" || token === "enter") {
56363
+ if (onDetail && items[selected]?.type === "proposal" && status.kind !== "running" && status.kind !== "done") {
56364
+ if (status.kind === "armed") {
56365
+ runDecision(status.decision);
56366
+ } else {
56367
+ armDecision(focusedAction);
56368
+ }
56369
+ return;
56370
+ }
55917
56371
  handle.setTab("detail");
55918
56372
  return;
55919
56373
  }
55920
- if (onDetail && (token === "a" || token === "d") && status.kind !== "running") {
55921
- const decision = token === "a" ? "accept" : "decline";
55922
- status = items[selected]?.type === "proposal" && handlerFor(decision) !== undefined ? { kind: "armed", decision } : { kind: "unavailable", decision };
56374
+ if (onDetail && (token === "left" || token === "right") && status.kind !== "running" && status.kind !== "done") {
56375
+ focusedAction = token === "left" ? "accept" : "decline";
56376
+ status = { kind: "idle" };
55923
56377
  paintSelection();
55924
56378
  return;
55925
56379
  }
56380
+ if (onDetail && (token === "a" || token === "d") && status.kind !== "running" && status.kind !== "done") {
56381
+ const decision = token === "a" ? "accept" : "decline";
56382
+ focusedAction = decision;
56383
+ armDecision(decision);
56384
+ return;
56385
+ }
55926
56386
  if (token === "up" || token === "k") {
55927
56387
  if (onDetail) {
55928
56388
  detailScroll = clampScroll3(detailScroll - 1, detailLines().length, bodyRows);
@@ -65153,6 +65613,22 @@ New session ${shortSessionId(live.summary.id)}.
65153
65613
  slateSession,
65154
65614
  mintAttemptId: mintTimestampAttemptId
65155
65615
  });
65616
+ } else if (command === "/theme") {
65617
+ const wanted = rest.trim();
65618
+ if (wanted.length === 0) {
65619
+ agentIo.onSystem?.(formatThemeList(getThemeId()));
65620
+ } else {
65621
+ const next = parseThemeId(wanted);
65622
+ if (next === undefined) {
65623
+ agentIo.onSystem?.(`Unknown theme '${wanted}'.
65624
+ ${formatThemeList(getThemeId())}`);
65625
+ } else {
65626
+ applyThemeId(next);
65627
+ persistThemeId(next);
65628
+ agentIo.onSystem?.(`Theme: ${themeLabel(next)}
65629
+ `);
65630
+ }
65631
+ }
65156
65632
  } else {
65157
65633
  agentIo.onSystem?.(describeUnavailableCommand(command, "agent") ?? `Unknown command: ${command}. Type /help.
65158
65634
  `);
@@ -65417,6 +65893,12 @@ async function shellCommand(args2, runtime = {}) {
65417
65893
  }
65418
65894
  }
65419
65895
  const rl = readline2.createInterface({ input: process.stdin });
65896
+ if (!process.stdin.isTTY) {
65897
+ process.on("SIGINT", () => {
65898
+ rl.close();
65899
+ process.exit(130);
65900
+ });
65901
+ }
65420
65902
  const lineIterator = rl[Symbol.asyncIterator]();
65421
65903
  const sharedLines = { [Symbol.asyncIterator]: () => lineIterator };
65422
65904
  const { io, emitSystem, printHeader, printPrompt, destroy } = createRichIo(sharedLines, versionCheck);
@@ -65683,7 +66165,7 @@ Shell:
65683
66165
 
65684
66166
  // src/commands/modules.ts
65685
66167
  init_fs();
65686
- import { readFile as readFile79 } from "fs/promises";
66168
+ import { readFile as readFile80 } from "fs/promises";
65687
66169
  import { stdin } from "process";
65688
66170
  import path153 from "path";
65689
66171
  var MODULES = [
@@ -65745,7 +66227,7 @@ async function modulesCommand(args2 = []) {
65745
66227
  }
65746
66228
  let manifest = {};
65747
66229
  try {
65748
- manifest = JSON.parse(await readFile79(manifestPath, "utf8"));
66230
+ manifest = JSON.parse(await readFile80(manifestPath, "utf8"));
65749
66231
  } catch {}
65750
66232
  const enabled = new Set(MODULES.filter((module) => manifest.modules?.[module.name]?.enabled === true).map((module) => module.name));
65751
66233
  if (wantsJson) {
@@ -67719,7 +68201,7 @@ function printHelp17() {
67719
68201
 
67720
68202
  // src/commands/update.ts
67721
68203
  import { spawn as spawn5 } from "child_process";
67722
- import { chmod as chmod4, mkdir as mkdir56, readFile as readFile80, readdir as readdir27, writeFile as writeFile49 } from "fs/promises";
68204
+ import { chmod as chmod4, mkdir as mkdir56, readFile as readFile81, readdir as readdir27, writeFile as writeFile49 } from "fs/promises";
67723
68205
  import { access as access4, constants as constants2, existsSync as existsSync30 } from "fs";
67724
68206
  import path158 from "path";
67725
68207
  import { fileURLToPath as fileURLToPath7 } from "url";
@@ -68030,7 +68512,7 @@ async function shouldInstallDashboardPostCommitHook(projectRoot, manifest) {
68030
68512
  if (!await pathExists(hookPath)) {
68031
68513
  return false;
68032
68514
  }
68033
- return (await readFile80(hookPath, "utf8")).includes("# keryx:");
68515
+ return (await readFile81(hookPath, "utf8")).includes("# keryx:");
68034
68516
  }
68035
68517
  async function collectDashboardData(metaprojectRoot) {
68036
68518
  const data = {};
@@ -68082,12 +68564,12 @@ async function collectTasksDashboardData(metaprojectRoot) {
68082
68564
  continue;
68083
68565
  }
68084
68566
  try {
68085
- const flow = JSON.parse(await readFile80(flowPath, "utf8"));
68567
+ const flow = JSON.parse(await readFile81(flowPath, "utf8"));
68086
68568
  const tasks = Array.isArray(flow.tasks) ? flow.tasks : [];
68087
68569
  let acTotal = 0;
68088
68570
  const acPath2 = path158.join(flowsRoot2, dir, "acceptance-criteria.md");
68089
68571
  if (await pathExists(acPath2)) {
68090
- const acContent = await readFile80(acPath2, "utf8");
68572
+ const acContent = await readFile81(acPath2, "utf8");
68091
68573
  acTotal = (acContent.match(/^- AC\d+:/gm) ?? []).length;
68092
68574
  }
68093
68575
  flows.push({
@@ -68143,7 +68625,7 @@ async function collectDashboardDocs(metaprojectRoot, wiki, memory) {
68143
68625
  if (!await pathExists(filePath)) {
68144
68626
  continue;
68145
68627
  }
68146
- const content = await readFile80(filePath, "utf8");
68628
+ const content = await readFile81(filePath, "utf8");
68147
68629
  docs[href] = content.length > 40000 ? `${content.slice(0, 40000)}
68148
68630
 
68149
68631
  \u2026truncated\u2026` : content;
@@ -68160,7 +68642,7 @@ async function collectHealthDashboardData(metaprojectRoot) {
68160
68642
  if (!await pathExists(reportPath2)) {
68161
68643
  return;
68162
68644
  }
68163
- const report = JSON.parse(await readFile80(reportPath2, "utf8"));
68645
+ const report = JSON.parse(await readFile81(reportPath2, "utf8"));
68164
68646
  const metrics = Array.isArray(report.metrics) ? report.metrics : [];
68165
68647
  const findings = Array.isArray(report.findings) ? report.findings : [];
68166
68648
  const project = metrics.find((metric) => metric.key === "project") ?? {};
@@ -68274,7 +68756,7 @@ async function collectGraphDashboardData(metaprojectRoot) {
68274
68756
  let nodes = 0;
68275
68757
  let files = 0;
68276
68758
  let assets = 0;
68277
- for (const node of parseJsonl2(await readFile80(nodesPath, "utf8"))) {
68759
+ for (const node of parseJsonl2(await readFile81(nodesPath, "utf8"))) {
68278
68760
  nodes += 1;
68279
68761
  if (node.kind === "asset") {
68280
68762
  assets += 1;
@@ -68290,7 +68772,7 @@ async function collectGraphDashboardData(metaprojectRoot) {
68290
68772
  let imports = 0;
68291
68773
  let assetEdges = 0;
68292
68774
  let unresolved = 0;
68293
- for (const edge of parseJsonl2(await readFile80(edgesPath, "utf8"))) {
68775
+ for (const edge of parseJsonl2(await readFile81(edgesPath, "utf8"))) {
68294
68776
  edges += 1;
68295
68777
  if (edge.kind === "imports") {
68296
68778
  imports += 1;
@@ -68320,7 +68802,7 @@ async function collectTestingDashboardData(metaprojectRoot) {
68320
68802
  const reportPath2 = path158.join(metaprojectRoot, "data", "testing", "artifacts", "latest.json");
68321
68803
  const contextPath = path158.join(metaprojectRoot, "data", "testing", "context.md");
68322
68804
  if (await pathExists(reportPath2)) {
68323
- const report = JSON.parse(await readFile80(reportPath2, "utf8"));
68805
+ const report = JSON.parse(await readFile81(reportPath2, "utf8"));
68324
68806
  const totalTests = numberOrUndefined(report.total);
68325
68807
  const failedTests = Array.isArray(report.failures) ? report.failures.length : numberOrUndefined(report.failed);
68326
68808
  return {
@@ -68351,7 +68833,7 @@ async function collectMarkdownPages(root, hrefPrefix) {
68351
68833
  if (relativePath === "index.md" || relativePath.startsWith("templates/")) {
68352
68834
  continue;
68353
68835
  }
68354
- const content = await readFile80(filePath, "utf8");
68836
+ const content = await readFile81(filePath, "utf8");
68355
68837
  const embedded = content.length > 24000 ? `${content.slice(0, 24000)}
68356
68838
 
68357
68839
  \u2026truncated\u2026` : content;
@@ -68527,7 +69009,7 @@ async function enableTasksInManifest(metaprojectRoot) {
68527
69009
  }
68528
69010
  let raw;
68529
69011
  try {
68530
- raw = JSON.parse(await readFile80(manifestPath, "utf8"));
69012
+ raw = JSON.parse(await readFile81(manifestPath, "utf8"));
68531
69013
  } catch {
68532
69014
  return;
68533
69015
  }
@@ -68550,7 +69032,7 @@ async function updateManifestAgentEntrypoints(metaprojectRoot, ruleSources) {
68550
69032
  }
68551
69033
  let raw;
68552
69034
  try {
68553
- raw = JSON.parse(await readFile80(manifestPath, "utf8"));
69035
+ raw = JSON.parse(await readFile81(manifestPath, "utf8"));
68554
69036
  } catch {
68555
69037
  return;
68556
69038
  }
@@ -68659,7 +69141,7 @@ async function installManagedHook2(projectRoot, hookName, blockId, content) {
68659
69141
  const managedBlock = `${blockStart}
68660
69142
  ${content.trim()}
68661
69143
  ${blockEnd}`;
68662
- const existing = await pathExists(hookPath) ? await readFile80(hookPath, "utf8") : `#!/usr/bin/env sh
69144
+ const existing = await pathExists(hookPath) ? await readFile81(hookPath, "utf8") : `#!/usr/bin/env sh
68663
69145
  `;
68664
69146
  const blockPattern = new RegExp(`${escapeRegExp6(blockStart)}[\\s\\S]*?${escapeRegExp6(blockEnd)}`);
68665
69147
  const next = blockPattern.test(existing) ? existing.replace(blockPattern, managedBlock) : `${existing.trimEnd()}
@@ -68678,7 +69160,7 @@ async function removeManagedHook2(projectRoot, hookName, blockId) {
68678
69160
  if (!await pathExists(hookPath)) {
68679
69161
  return;
68680
69162
  }
68681
- const existing = await readFile80(hookPath, "utf8");
69163
+ const existing = await readFile81(hookPath, "utf8");
68682
69164
  const blockStart = `# keryx:${blockId}:begin`;
68683
69165
  const blockEnd = `# keryx:${blockId}:end`;
68684
69166
  const blockPattern = new RegExp(`\\n*${escapeRegExp6(blockStart)}[\\s\\S]*?${escapeRegExp6(blockEnd)}\\n*`);
@@ -68700,7 +69182,7 @@ async function prePushHasSecurityBlock2(projectRoot) {
68700
69182
  if (!await pathExists(hookPath)) {
68701
69183
  return false;
68702
69184
  }
68703
- const hook = await readFile80(hookPath, "utf8");
69185
+ const hook = await readFile81(hookPath, "utf8");
68704
69186
  return hook.includes("# keryx:security-pre-push:begin");
68705
69187
  }
68706
69188
  async function agentSettingsHasSecuritySentinel2(projectRoot) {
@@ -68708,7 +69190,7 @@ async function agentSettingsHasSecuritySentinel2(projectRoot) {
68708
69190
  if (!await pathExists(file)) {
68709
69191
  return false;
68710
69192
  }
68711
- return (await readFile80(file, "utf8")).includes(AGENT_HOOKS_SENTINEL);
69193
+ return (await readFile81(file, "utf8")).includes(AGENT_HOOKS_SENTINEL);
68712
69194
  }
68713
69195
  async function readManifest5(metaprojectRoot) {
68714
69196
  const manifestPath = path158.join(metaprojectRoot, "metaproject.json");
@@ -68721,7 +69203,7 @@ async function readManifest5(metaprojectRoot) {
68721
69203
  };
68722
69204
  }
68723
69205
  try {
68724
- const manifest = JSON.parse(await readFile80(manifestPath, "utf8"));
69206
+ const manifest = JSON.parse(await readFile81(manifestPath, "utf8"));
68725
69207
  const normalized = normalizeManifest(manifest);
68726
69208
  return {
68727
69209
  exists: true,
@@ -68845,7 +69327,7 @@ async function run(command, args2, cwd) {
68845
69327
  });
68846
69328
  }
68847
69329
  async function writeTextIfChanged4(filePath, content) {
68848
- if (await pathExists(filePath) && await readFile80(filePath, "utf8") === content) {
69330
+ if (await pathExists(filePath) && await readFile81(filePath, "utf8") === content) {
68849
69331
  return;
68850
69332
  }
68851
69333
  await mkdir56(path158.dirname(filePath), { recursive: true });
@@ -68859,8 +69341,8 @@ async function writeTextIfMissing4(filePath, content) {
68859
69341
  await writeFile49(filePath, content, "utf8");
68860
69342
  }
68861
69343
  async function copyFileIfChanged2(from, to) {
68862
- const next = await readFile80(from, "utf8");
68863
- if (await pathExists(to) && await readFile80(to, "utf8") === next) {
69344
+ const next = await readFile81(from, "utf8");
69345
+ if (await pathExists(to) && await readFile81(to, "utf8") === next) {
68864
69346
  return;
68865
69347
  }
68866
69348
  await mkdir56(path158.dirname(to), { recursive: true });
@@ -68970,7 +69452,7 @@ function printHelp19() {
68970
69452
  import { readFileSync as readFileSync10 } from "fs";
68971
69453
 
68972
69454
  // src/agents/bootstrap.ts
68973
- import { mkdir as mkdir57, readFile as readFile81, writeFile as writeFile50 } from "fs/promises";
69455
+ import { mkdir as mkdir57, readFile as readFile82, writeFile as writeFile50 } from "fs/promises";
68974
69456
  import { homedir as homedir7 } from "os";
68975
69457
  import path160 from "path";
68976
69458
  init_fs();
@@ -69043,7 +69525,7 @@ function resolveAgentBootstrapRuntimes(ids) {
69043
69525
  async function agentBootstrapStatus(runtime, homeRoot = homedir7()) {
69044
69526
  const filePath = runtime.filePath(homeRoot);
69045
69527
  const exists2 = await pathExists(filePath);
69046
- const content = exists2 ? await readFile81(filePath, "utf8") : "";
69528
+ const content = exists2 ? await readFile82(filePath, "utf8") : "";
69047
69529
  const expected = renderAgentBootstrapBlock(runtime.fileName).trim();
69048
69530
  const installed = content.includes(AGENT_BOOTSTRAP_START);
69049
69531
  const current = installed && extractManagedBlock(content)?.trim() === expected;
@@ -69053,7 +69535,7 @@ async function installAgentBootstrap(runtime, options = {}) {
69053
69535
  const homeRoot = options.homeRoot ?? homedir7();
69054
69536
  const filePath = runtime.filePath(homeRoot);
69055
69537
  const exists2 = await pathExists(filePath);
69056
- const current = exists2 ? await readFile81(filePath, "utf8") : "";
69538
+ const current = exists2 ? await readFile82(filePath, "utf8") : "";
69057
69539
  const next = upsertManagedBlock(current || defaultAgentFile(runtime), renderAgentBootstrapBlock(runtime.fileName));
69058
69540
  const dryRun = options.dryRun === true;
69059
69541
  const wrote = next !== current;
@@ -69068,7 +69550,7 @@ async function uninstallAgentBootstrap(runtime, options = {}) {
69068
69550
  const homeRoot = options.homeRoot ?? homedir7();
69069
69551
  const filePath = runtime.filePath(homeRoot);
69070
69552
  const exists2 = await pathExists(filePath);
69071
- const current = exists2 ? await readFile81(filePath, "utf8") : "";
69553
+ const current = exists2 ? await readFile82(filePath, "utf8") : "";
69072
69554
  const next = removeManagedBlock(current);
69073
69555
  const dryRun = options.dryRun === true;
69074
69556
  const removed = next !== current;
@@ -69594,7 +70076,7 @@ function printBootstrapHelp() {
69594
70076
 
69595
70077
  // src/commands/metrics.ts
69596
70078
  init_args();
69597
- import { readFile as readFile82 } from "fs/promises";
70079
+ import { readFile as readFile83 } from "fs/promises";
69598
70080
  import path162 from "path";
69599
70081
 
69600
70082
  // src/metrics/benchmark.ts
@@ -70870,7 +71352,7 @@ async function metricsCommand(args2 = [], projectRoot = process.cwd()) {
70870
71352
  process.exitCode = 1;
70871
71353
  return;
70872
71354
  }
70873
- const record2 = JSON.parse(await readFile82(path162.resolve(projectRoot, file), "utf8"));
71355
+ const record2 = JSON.parse(await readFile83(path162.resolve(projectRoot, file), "utf8"));
70874
71356
  const result = validateRunRecord(record2);
70875
71357
  console.log(result.valid ? "valid: yes" : "valid: no");
70876
71358
  for (const error2 of result.errors)
@@ -70901,7 +71383,7 @@ async function metricsCommand(args2 = [], projectRoot = process.cwd()) {
70901
71383
  process.exitCode = 1;
70902
71384
  return;
70903
71385
  }
70904
- console.log(await readFile82(file, "utf8"));
71386
+ console.log(await readFile83(file, "utf8"));
70905
71387
  return;
70906
71388
  }
70907
71389
  if (subcommand === "compare") {
@@ -70912,8 +71394,8 @@ async function metricsCommand(args2 = [], projectRoot = process.cwd()) {
70912
71394
  process.exitCode = 1;
70913
71395
  return;
70914
71396
  }
70915
- const a = JSON.parse(await readFile82(path162.join(metricsRoot(projectRoot), "runs", `${runA}.json`), "utf8"));
70916
- const b = JSON.parse(await readFile82(path162.join(metricsRoot(projectRoot), "runs", `${runB}.json`), "utf8"));
71397
+ const a = JSON.parse(await readFile83(path162.join(metricsRoot(projectRoot), "runs", `${runA}.json`), "utf8"));
71398
+ const b = JSON.parse(await readFile83(path162.join(metricsRoot(projectRoot), "runs", `${runB}.json`), "utf8"));
70917
71399
  const comparison = compareExecutionRuns(a, b);
70918
71400
  console.log(stableJson(comparison));
70919
71401
  return;
@@ -70962,7 +71444,7 @@ async function metricsCommand(args2 = [], projectRoot = process.cwd()) {
70962
71444
  process.exitCode = 1;
70963
71445
  return;
70964
71446
  }
70965
- const raw = JSON.parse(await readFile82(path162.resolve(projectRoot, file), "utf8"));
71447
+ const raw = JSON.parse(await readFile83(path162.resolve(projectRoot, file), "utf8"));
70966
71448
  const input2 = Array.isArray(raw) ? raw : raw.runs ?? [];
70967
71449
  const result = validatePairedBenchmark(input2);
70968
71450
  console.log(stableJson(result));
@@ -70974,7 +71456,7 @@ async function metricsCommand(args2 = [], projectRoot = process.cwd()) {
70974
71456
  process.exitCode = 1;
70975
71457
  }
70976
71458
  async function loadAffectedSets(projectRoot, file) {
70977
- const raw = JSON.parse(await readFile82(path162.resolve(projectRoot, file), "utf8"));
71459
+ const raw = JSON.parse(await readFile83(path162.resolve(projectRoot, file), "utf8"));
70978
71460
  const map = new Map;
70979
71461
  for (const entry of raw.targets ?? []) {
70980
71462
  if (typeof entry.target === "string")
@@ -71045,7 +71527,7 @@ async function runHarnessLayer(projectRoot, args2, ladder) {
71045
71527
  let tasks;
71046
71528
  let model;
71047
71529
  try {
71048
- const raw = JSON.parse(await readFile82(path162.resolve(projectRoot, resultsPath), "utf8"));
71530
+ const raw = JSON.parse(await readFile83(path162.resolve(projectRoot, resultsPath), "utf8"));
71049
71531
  tasks = raw.tasks ?? [];
71050
71532
  model = raw.model;
71051
71533
  } catch (error2) {
@@ -71076,7 +71558,7 @@ async function runSafetyCompletionHonestyLayer(projectRoot, args2, ladder) {
71076
71558
  let cases;
71077
71559
  let model;
71078
71560
  try {
71079
- const raw = JSON.parse(await readFile82(path162.resolve(projectRoot, resultsPath), "utf8"));
71561
+ const raw = JSON.parse(await readFile83(path162.resolve(projectRoot, resultsPath), "utf8"));
71080
71562
  cases = raw.cases ?? [];
71081
71563
  model = raw.model;
71082
71564
  } catch (error2) {
@@ -71101,7 +71583,7 @@ async function runSafetyFalsePremiseLayer(projectRoot, args2, ladder) {
71101
71583
  let cases;
71102
71584
  let model;
71103
71585
  try {
71104
- const raw = JSON.parse(await readFile82(path162.resolve(projectRoot, resultsPath), "utf8"));
71586
+ const raw = JSON.parse(await readFile83(path162.resolve(projectRoot, resultsPath), "utf8"));
71105
71587
  cases = raw.cases ?? [];
71106
71588
  model = raw.model;
71107
71589
  } catch (error2) {
@@ -71126,7 +71608,7 @@ async function runSafetyContainmentLayer(projectRoot, args2, ladder, caseClass)
71126
71608
  let cases;
71127
71609
  let model;
71128
71610
  try {
71129
- const raw = JSON.parse(await readFile82(path162.resolve(projectRoot, resultsPath), "utf8"));
71611
+ const raw = JSON.parse(await readFile83(path162.resolve(projectRoot, resultsPath), "utf8"));
71130
71612
  cases = raw.cases ?? [];
71131
71613
  model = raw.model;
71132
71614
  } catch (error2) {
@@ -71217,7 +71699,7 @@ async function runGdgraphLayer(projectRoot, args2, ladder) {
71217
71699
  return allValid;
71218
71700
  }
71219
71701
  async function loadCoverageMap2(projectRoot, file) {
71220
- const raw = JSON.parse(await readFile82(path162.resolve(projectRoot, file), "utf8"));
71702
+ const raw = JSON.parse(await readFile83(path162.resolve(projectRoot, file), "utf8"));
71221
71703
  return raw.coverageMap ?? {};
71222
71704
  }
71223
71705
  async function runTestingLayer(projectRoot, args2, ladder) {
@@ -71254,7 +71736,7 @@ async function runTestingLayer(projectRoot, args2, ladder) {
71254
71736
  return result.valid;
71255
71737
  }
71256
71738
  async function loadMemoryGoldK(projectRoot, file) {
71257
- const raw = JSON.parse(await readFile82(path162.resolve(projectRoot, file), "utf8"));
71739
+ const raw = JSON.parse(await readFile83(path162.resolve(projectRoot, file), "utf8"));
71258
71740
  return typeof raw.k === "number" && raw.k > 0 ? raw.k : 3;
71259
71741
  }
71260
71742
  async function runMemoryLayer(projectRoot, args2, ladder) {
@@ -71291,11 +71773,11 @@ async function runMemoryLayer(projectRoot, args2, ladder) {
71291
71773
  return result.valid;
71292
71774
  }
71293
71775
  async function loadWikiGoldK(projectRoot, file) {
71294
- const raw = JSON.parse(await readFile82(path162.resolve(projectRoot, file), "utf8"));
71776
+ const raw = JSON.parse(await readFile83(path162.resolve(projectRoot, file), "utf8"));
71295
71777
  return typeof raw.k === "number" && raw.k > 0 ? raw.k : 5;
71296
71778
  }
71297
71779
  async function loadWikiGroundedness(projectRoot, file) {
71298
- const raw = JSON.parse(await readFile82(path162.resolve(projectRoot, file), "utf8"));
71780
+ const raw = JSON.parse(await readFile83(path162.resolve(projectRoot, file), "utf8"));
71299
71781
  const map = new Map;
71300
71782
  for (const entry of raw.targets ?? []) {
71301
71783
  if (typeof entry.target !== "string" || !Array.isArray(entry.scores) || entry.scores.length !== 3)
@@ -71351,7 +71833,7 @@ async function runWikiLayer(projectRoot, args2, ladder) {
71351
71833
  return result.valid;
71352
71834
  }
71353
71835
  async function loadGdctxFacts(projectRoot, file) {
71354
- const raw = JSON.parse(await readFile82(path162.resolve(projectRoot, file), "utf8"));
71836
+ const raw = JSON.parse(await readFile83(path162.resolve(projectRoot, file), "utf8"));
71355
71837
  const inputs = [];
71356
71838
  for (const entry of raw.inputs ?? []) {
71357
71839
  if (typeof entry.input === "string") {
@@ -71389,7 +71871,7 @@ async function collect(projectRoot, args2) {
71389
71871
  process.exitCode = 1;
71390
71872
  return;
71391
71873
  }
71392
- const raw = JSON.parse(await readFile82(path162.resolve(projectRoot, eventFile), "utf8"));
71874
+ const raw = JSON.parse(await readFile83(path162.resolve(projectRoot, eventFile), "utf8"));
71393
71875
  const events2 = Array.isArray(raw) ? raw : raw.events;
71394
71876
  const startedAt = optionValue(args2, "--started-at") ?? events2[0]?.timestamp_utc ?? new Date().toISOString();
71395
71877
  const finishedAt = optionValue(args2, "--finished-at") ?? events2.at(-1)?.timestamp_utc ?? startedAt;
@@ -71789,6 +72271,7 @@ function renderCatchUp(report, includeLifecycleFlags = true) {
71789
72271
  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.`));
71790
72272
  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}).`));
71791
72273
  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.`));
72274
+ sections.push(renderSection("Unreviewed SAC-owned changes (no proposal on record)", report.unreviewedPaths, (item) => `- Session ${item.sessionId} changed ${item.owner} path \`${item.path}\`${item.status !== undefined ? ` (Status: ${item.status})` : ""} at ${item.changedAt} with NO SAC proposal/receipt behind it \u2014 this looks like it bypassed review. Was this reviewed some other way, or should it be? ` + `Recommendation: \`keryx shell -r ${item.sessionId}\` to see what happened; if the content is good, route it through a real proposal (\`keryx workspace propose ...\`) before trusting it as durable knowledge.`));
71792
72275
  if (includeLifecycleFlags) {
71793
72276
  sections.push(renderSection("Lifecycle flags (component no longer in the graph)", report.lifecycleFlags, (item) => `- ${item.kind} \`${item.ref}\` scopes to \`${item.missingComponent}\`, which is no longer in the code graph (flagged ${item.flaggedAt}). Still relevant, or safe to clean up? ` + `Recommendation: this is report-only \u2014 nothing was archived/edited/removed automatically; ${item.kind === "workspace" ? "`keryx workspace archive " + item.ref + "`" : item.kind === "memory-entry" ? "`keryx memory supersede` or edit the entry directly" : "edit or remove the wiki page directly"} if you decide it's actually stale.`));
71794
72277
  }