@mrciphersmith/keryx 0.2.56 → 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 +676 -558
  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();
@@ -49832,7 +49856,7 @@ function buildInteractiveAgentTools(input2) {
49832
49856
  applyPatchTool(input2.cwd),
49833
49857
  workspaceOverviewTool(input2.cwd),
49834
49858
  workspaceReadTool(input2.cwd),
49835
- workspaceCreateTool(input2.cwd),
49859
+ workspaceCreateTool(input2.cwd, getSessionDir),
49836
49860
  workspaceListTool(input2.cwd),
49837
49861
  workspaceShowTool(input2.cwd),
49838
49862
  workspaceProposeTool(input2.cwd, getSessionDir),
@@ -53121,7 +53145,6 @@ init_agent();
53121
53145
  init_slate_lifecycle();
53122
53146
  init_slate();
53123
53147
  init_workspace_service();
53124
- init_workspace_resolve();
53125
53148
  init_service7();
53126
53149
  init_store2();
53127
53150
  init_fs();
@@ -53390,7 +53413,7 @@ async function runGoalVerifier(deps, goalText, cwd, slateSession, history, io, m
53390
53413
  return parseVerifierVerdict(result.output);
53391
53414
  }
53392
53415
  async function runGoalCommand(params) {
53393
- const { raw, cwd, io, deps, history, slateSession, mintAttemptId, resolveWorkspace } = params;
53416
+ const { raw, cwd, io, deps, history, slateSession, mintAttemptId } = params;
53394
53417
  const parsed = parseGoalArgs(raw);
53395
53418
  if ("error" in parsed) {
53396
53419
  systemLine(io, `/goal: ${parsed.error}
@@ -53424,19 +53447,6 @@ async function runGoalCommand(params) {
53424
53447
  const base = prev ?? { anchors: { root: "", touched: [] }, course: {}, seeds: [] };
53425
53448
  return { ...base, workspaceId };
53426
53449
  });
53427
- } else {
53428
- const current = await readSlate(slateSession.dir);
53429
- if (current !== undefined && current.workspaceId === undefined) {
53430
- const resolver = resolveWorkspace ?? resolveOrCreateWorkspace;
53431
- const resolved2 = await resolver({ cwd, topicHint: parsed.text, provider: deps.providerId, model: deps.modelId });
53432
- if (resolved2.ok) {
53433
- await writeSlate(slateSession.dir, (prev) => {
53434
- if (!prev)
53435
- throw new Error(`SLATE-16 bind: no open slate in ${slateSession.dir}`);
53436
- return { ...prev, workspaceId: resolved2.workspaceId };
53437
- });
53438
- }
53439
- }
53440
53450
  }
53441
53451
  if (parsed.auto !== undefined) {
53442
53452
  const forCourse = await readSlate(slateSession.dir);
@@ -53535,7 +53545,7 @@ import { spawnSync as spawnSync2 } from "child_process";
53535
53545
  // package.json
53536
53546
  var package_default = {
53537
53547
  name: "@mrciphersmith/keryx",
53538
- version: "0.2.56",
53548
+ version: "0.2.57",
53539
53549
  description: "Version-controlled project context for AI coding agents: code graph, architecture wiki, project memory, relevant tests, quality signals, and task flows.",
53540
53550
  private: false,
53541
53551
  publishConfig: {
@@ -54688,6 +54698,13 @@ function ensureHost(otui, chrome) {
54688
54698
  }
54689
54699
  const idx = state.tabs.findIndex((tab) => tab.id === state.active);
54690
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
+ }
54691
54708
  if (key.name === "left" || onStrip && key.name === "tab" && key.shift === true) {
54692
54709
  const prev = idx > 0 ? state.tabs[idx - 1] : undefined;
54693
54710
  if (prev !== undefined) {
@@ -55959,10 +55976,9 @@ function openWorkspace(otui, chrome, options) {
55959
55976
  var REVIEW_COMMAND = "/review";
55960
55977
  var REVIEW_FOOTER = [
55961
55978
  { key: "[/]", label: "item" },
55962
- { key: "a y", label: "accept proposal" },
55963
- { key: "d y", label: "decline proposal" },
55979
+ { key: "\u2190/\u2192 a d", label: "accept/decline" },
55980
+ { key: "enter y", label: "arm \u2192 confirm" },
55964
55981
  { key: "\u2191/\u2193", label: "scroll" },
55965
- { key: "\u2190/\u2192", label: "tabs" },
55966
55982
  { key: "esc", label: "close" }
55967
55983
  ];
55968
55984
  function isReviewCommand(line) {
@@ -56146,14 +56162,58 @@ function paintLines3(otui, renderer, body, lines, width) {
56146
56162
  parent.add(node);
56147
56163
  return node;
56148
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
+ }
56149
56207
  function presentReview(openModal2, otui, chrome, options) {
56150
56208
  const items = [...options.items];
56151
56209
  let selected = 0;
56152
56210
  let listScroll = 0;
56153
56211
  let detailScroll = 0;
56154
56212
  let status = { kind: "idle" };
56213
+ let focusedAction = "accept";
56155
56214
  let listNode;
56156
56215
  let detailNode;
56216
+ let actionButtons;
56157
56217
  let unsubscribeKey;
56158
56218
  const rendererHint = options.renderer ?? chrome?.renderer;
56159
56219
  const bodyRows = options.visibleRows ?? (typeof rendererHint?.width === "number" && typeof rendererHint.height === "number" ? modalBodyRows(resolveModalPanelSize(rendererHint.width, rendererHint.height).height) : 13);
@@ -56174,6 +56234,7 @@ function presentReview(openModal2, otui, chrome, options) {
56174
56234
  detailNode.content = windowLines3(detailLines(), detailScroll, bodyRows).join(`
56175
56235
  `);
56176
56236
  }
56237
+ updateButtons();
56177
56238
  };
56178
56239
  const moveSelection = (next) => {
56179
56240
  if (items.length === 0) {
@@ -56189,6 +56250,22 @@ function presentReview(openModal2, otui, chrome, options) {
56189
56250
  paintSelection();
56190
56251
  };
56191
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
+ };
56192
56269
  const runDecision = (decision) => {
56193
56270
  const item = items[selected];
56194
56271
  const run = handlerFor(decision);
@@ -56213,16 +56290,43 @@ function presentReview(openModal2, otui, chrome, options) {
56213
56290
  ],
56214
56291
  initialTab: "list",
56215
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
+ },
56216
56302
  renderTab: (tabId, body, ctx) => {
56217
56303
  const renderer = options.renderer ?? chrome?.renderer;
56218
56304
  tabWidth = ctx?.width;
56219
56305
  if (tabId === "list") {
56306
+ detailNode = undefined;
56307
+ actionButtons = undefined;
56220
56308
  listScroll = scrollToReveal3(selected, listScroll, bodyRows);
56221
56309
  listNode = paintLines3(otui, renderer, body, windowLines3(listLines(), listScroll, bodyRows));
56222
56310
  return;
56223
56311
  }
56312
+ listNode = undefined;
56224
56313
  detailScroll = clampScroll3(detailScroll, detailLines().length, bodyRows);
56225
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
+ }
56226
56330
  },
56227
56331
  onClose: () => {
56228
56332
  unsubscribeKey?.();
@@ -56239,7 +56343,7 @@ function presentReview(openModal2, otui, chrome, options) {
56239
56343
  }
56240
56344
  const onDetail = handle.activeTab() === "detail";
56241
56345
  if (onDetail && status.kind === "armed") {
56242
- if (token === "y") {
56346
+ if (token === "y" || token === "return" || token === "enter") {
56243
56347
  runDecision(status.decision);
56244
56348
  } else {
56245
56349
  status = { kind: "idle" };
@@ -56256,15 +56360,29 @@ function presentReview(openModal2, otui, chrome, options) {
56256
56360
  return;
56257
56361
  }
56258
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
+ }
56259
56371
  handle.setTab("detail");
56260
56372
  return;
56261
56373
  }
56262
- if (onDetail && (token === "a" || token === "d") && status.kind !== "running") {
56263
- const decision = token === "a" ? "accept" : "decline";
56264
- 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" };
56265
56377
  paintSelection();
56266
56378
  return;
56267
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
+ }
56268
56386
  if (token === "up" || token === "k") {
56269
56387
  if (onDetail) {
56270
56388
  detailScroll = clampScroll3(detailScroll - 1, detailLines().length, bodyRows);