@mrciphersmith/keryx 0.2.55 → 0.2.57
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli.js +1116 -633
- 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/
|
|
14426
|
-
import {
|
|
14427
|
-
|
|
14428
|
-
|
|
14429
|
-
|
|
14430
|
-
|
|
14431
|
-
|
|
14432
|
-
|
|
14433
|
-
|
|
14434
|
-
|
|
14435
|
-
|
|
14436
|
-
|
|
14437
|
-
|
|
14438
|
-
|
|
14439
|
-
|
|
14440
|
-
|
|
14441
|
-
|
|
14442
|
-
|
|
14443
|
-
|
|
14444
|
-
|
|
14445
|
-
|
|
14446
|
-
|
|
14447
|
-
|
|
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
|
-
|
|
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
|
|
14481
|
-
|
|
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
|
|
14496
|
-
var
|
|
14497
|
-
|
|
14498
|
-
|
|
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
|
|
14504
|
-
import
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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
|
|
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) ||
|
|
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 =
|
|
15151
|
+
const lexicalRoot = path71.resolve(input2.workspaceRoot);
|
|
15187
15152
|
const root = await realpath2(lexicalRoot);
|
|
15188
|
-
const candidate =
|
|
15189
|
-
if (candidate !== lexicalRoot && !candidate.startsWith(`${lexicalRoot}${
|
|
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}${
|
|
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 =
|
|
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
|
|
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-${
|
|
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:
|
|
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
|
|
16487
|
-
import { mkdir as
|
|
16488
|
-
import
|
|
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 =
|
|
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 =
|
|
16512
|
-
await
|
|
16513
|
-
await writeFile27(
|
|
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 =
|
|
16534
|
-
const diffRelPath =
|
|
16535
|
-
await writeFile27(
|
|
16536
|
-
await writeFile27(
|
|
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:
|
|
16540
|
-
{ kind: "diff", uri: `./${diffRelPath}`, revision:
|
|
16541
|
-
{ kind: "session", uri: `./${relPath}`, revision:
|
|
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() +
|
|
16274
|
+
expiresAt: new Date(now().getTime() + WRAP_UP_TTL_MS).toISOString()
|
|
16549
16275
|
};
|
|
16550
16276
|
}
|
|
16551
|
-
var
|
|
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
|
-
|
|
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
|
|
16293
|
+
import { createHash as createHash12, randomBytes as randomBytes2 } from "crypto";
|
|
16568
16294
|
import { readFile as readFile35 } from "fs/promises";
|
|
16569
|
-
import
|
|
16295
|
+
import path76 from "path";
|
|
16570
16296
|
function confirmTokenPath(cwd, workspaceId, proposalId) {
|
|
16571
|
-
return
|
|
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
|
|
16300
|
+
return path76.join(cwd, ".metaproject", "workspaces", workspaceId, "proposals", `${proposalId}.${sha256(idempotencyKey)}.confirm-receipt.json`);
|
|
16575
16301
|
}
|
|
16576
|
-
function
|
|
16577
|
-
return
|
|
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:
|
|
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 !==
|
|
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
|
|
16428
|
+
import { createHash as createHash13 } from "crypto";
|
|
16703
16429
|
import { readFile as readFile36 } from "fs/promises";
|
|
16704
|
-
import
|
|
16430
|
+
import path77 from "path";
|
|
16705
16431
|
function proposalPath(cwd, workspaceId, proposalId) {
|
|
16706
|
-
return
|
|
16432
|
+
return path77.join(cwd, ".metaproject", "workspaces", workspaceId, "proposals", `${proposalId}.json`);
|
|
16707
16433
|
}
|
|
16708
16434
|
function ownerReceiptPath(cwd, owner, workspaceId, idempotencyKey) {
|
|
16709
|
-
return
|
|
16435
|
+
return path77.join(cwd, ".metaproject", "workspaces", workspaceId, `${owner}-write-receipts`, `${idempotencyKey}.json`);
|
|
16710
16436
|
}
|
|
16711
16437
|
function proposalNotePath(cwd, workspaceId, proposalId) {
|
|
16712
|
-
return
|
|
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(
|
|
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 (
|
|
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
|
|
16745
|
-
import
|
|
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:
|
|
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 =
|
|
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 =
|
|
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:
|
|
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
|
|
16913
|
-
import
|
|
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 =
|
|
16916
|
-
const absolutePath =
|
|
16917
|
-
const normalized = toPosix2(
|
|
16918
|
-
if (!normalized || normalized.startsWith("../") ||
|
|
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 =
|
|
17014
|
-
await
|
|
17015
|
-
const tmp =
|
|
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(
|
|
17045
|
-
return { status: "error", path:
|
|
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(
|
|
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
|
|
17064
|
-
import
|
|
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
|
|
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
|
|
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
|
|
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(
|
|
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
|
|
17262
|
-
import
|
|
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 =
|
|
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 =
|
|
17272
|
-
const relativeSkillPath = toPosix(
|
|
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(
|
|
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(
|
|
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(
|
|
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
|
|
17332
|
-
const skillPath =
|
|
17057
|
+
await mkdir31(packageRoot, { recursive: true });
|
|
17058
|
+
const skillPath = path82.join(packageRoot, "SKILL.md");
|
|
17333
17059
|
await writeFileAtomic(skillPath, skillContent);
|
|
17334
|
-
const changelogPath =
|
|
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
|
|
17340
|
-
await
|
|
17341
|
-
await writeFileAtomic(
|
|
17342
|
-
await writeFileAtomic(
|
|
17343
|
-
await writeFileAtomic(
|
|
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 =
|
|
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(
|
|
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(
|
|
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
|
-
|
|
17408
|
-
|
|
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
|
-
|
|
17416
|
-
|
|
17417
|
-
|
|
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 =
|
|
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 =
|
|
17651
|
-
const catalogPath =
|
|
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 =
|
|
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
|
|
17770
|
-
import { appendFile as appendFile3, mkdir as
|
|
17771
|
-
import
|
|
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 =
|
|
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
|
|
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
|
|
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 =
|
|
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(
|
|
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-${
|
|
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-${
|
|
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-${
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
17964
|
+
import { randomUUID as randomUUID11 } from "crypto";
|
|
18239
17965
|
import { writeFile as writeFile30 } from "fs/promises";
|
|
18240
|
-
import
|
|
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:
|
|
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:
|
|
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:
|
|
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 ??
|
|
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 =
|
|
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-${
|
|
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 ??
|
|
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
|
|
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 " + "
|
|
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
|
|
18914
|
+
` + "- This session has its own Slate (working-set scratch, not project knowledge): " + "**slate_read** shows the Course (if a Flow is bound) and Seeds recorded so far \u2014 nothing " + "here is auto-injected, so call it if you want to see it. **slate_write_seed** with " + "`{ text, kind? }` records a draft hypothesis/decision/follow-up worth a later human " + "review. WRITE A SEED when you: found a root cause or a bug worth remembering; changed or " + "added code (summarize WHAT changed and WHY); took a design/architecture decision; " + "identified a risk; or discovered a constraint/lesson. Use the `kind` that fits: " + "`decision` (a choice made), `wiki-update` (something a wiki page should say), " + "`memory-entry` (a lesson/constraint), `follow-up` (a TODO for a later session), " + "`risk`, or `contract-change`. Keep each Seed to 2-3 sentences, concrete and specific. " + "Do NOT write Seeds for routine progress notes, one-shot operational requests (e.g. " + '"run git pull", "count files"), or trivia \u2014 those need no workspace and no proposal. ' + "Seeds are the ONLY input wrap-up proposes from: a session whose Slate has zero Seeds " + `produces zero proposals. A Seed is never accepted knowledge by itself.
|
|
18915
|
+
` + "- Shared Agent Context (SAC) workspaces hold accepted, evidence-backed project context " + "beyond this codebase. **workspace_list** with `{ includeArchived? }` shows every workspace " + "visible to you \u2014 call it first when the user references a shared team workspace or accepted " + "project context, or before creating a new workspace, to judge whether an existing one " + "already fits the current topic. **workspace_show** with `{ workspaceId }` shows one " + "workspace's manifest. **workspace_overview** with `{ workspaceId }`, then **workspace_read** " + "with `{ workspaceId, itemId }` for one specific item, reads its accepted Facts/Work/Know-how. " + "**workspace_create** with `{ title, component? }` creates a new workspace AND binds it to " + "this session's slate (wrap-up then proposes into it) \u2014 only when workspace_list found no " + "fitting one and the session has real, durable results worth persisting; a workspace is " + "meant to persist across sessions, so prefer an existing one over creating another for the " + "same topic, and do NOT create one for one-shot operational requests. **workspace_propose** with " + "`{ workspaceId, kind, sessionId?, note? }` (sessionId defaults to this session) proposes a decision/wiki-update/memory-entry/" + "follow-up/contract-change/risk from this session for later human review \u2014 it never accepts " + "anything by itself; accepting always requires a human running `keryx workspace review` at a " + `real terminal, never this tool.
|
|
18875
18916
|
` + "- When you need a decision, interview step, or clarification: use **ask_user** with " + "2\u20136 options `{ id, label, description, recommended? }` (mark one recommended). " + `Do not dump long prose questions without options.
|
|
18876
18917
|
` + "- For a focused independent subtask (investigate X, review Y, research Z): use " + "**spawn_subagent** with `{ task, mode?: 'read_only'|'general', label? }`. " + "Default mode is read_only (no shell). Prefer spawn for work that can finish " + `without your intermediate turns; do not spawn for trivial one-line answers.
|
|
18877
18918
|
|
|
@@ -19114,22 +19155,6 @@ async function runAgentTurnCore(io, deps, history, userLine, options = {}) {
|
|
|
19114
19155
|
if (freshSlate !== undefined) {
|
|
19115
19156
|
history.push({ role: "user", content: renderAnchorsBlock(freshSlate.anchors), provenance: "project" });
|
|
19116
19157
|
io.onHistoryChange?.("tool");
|
|
19117
|
-
if (freshSlate.workspaceId === undefined) {
|
|
19118
|
-
const resolver = options.resolveWorkspace ?? resolveOrCreateWorkspace;
|
|
19119
|
-
const resolved = await resolver({
|
|
19120
|
-
cwd: options.slateSession.cwd,
|
|
19121
|
-
topicHint: userLine,
|
|
19122
|
-
provider: deps.providerId,
|
|
19123
|
-
model: deps.modelId
|
|
19124
|
-
});
|
|
19125
|
-
if (resolved.ok) {
|
|
19126
|
-
await writeSlate(options.slateSession.dir, (prev) => {
|
|
19127
|
-
if (!prev)
|
|
19128
|
-
throw new Error(`SLATE-16 bind: no open slate in ${options.slateSession.dir}`);
|
|
19129
|
-
return { ...prev, workspaceId: resolved.workspaceId };
|
|
19130
|
-
});
|
|
19131
|
-
}
|
|
19132
|
-
}
|
|
19133
19158
|
}
|
|
19134
19159
|
}
|
|
19135
19160
|
}
|
|
@@ -19710,7 +19735,6 @@ var init_agent = __esm(() => {
|
|
|
19710
19735
|
init_scheduler();
|
|
19711
19736
|
init_slate();
|
|
19712
19737
|
init_slate_course();
|
|
19713
|
-
init_workspace_resolve();
|
|
19714
19738
|
init_machine_wrap_up();
|
|
19715
19739
|
init_slate_lifecycle();
|
|
19716
19740
|
init_slate_terminal_state();
|
|
@@ -26111,6 +26135,10 @@ ${markdown.slice(4)}`;
|
|
|
26111
26135
|
}
|
|
26112
26136
|
return markdown;
|
|
26113
26137
|
}
|
|
26138
|
+
function extractFrontmatterStatus(markdown) {
|
|
26139
|
+
const bodyMatch = /\nStatus:\s*(\S+)/i.exec(markdown) ?? /^Status:\s*(\S+)/im.exec(markdown);
|
|
26140
|
+
return bodyMatch?.[1] ?? "draft";
|
|
26141
|
+
}
|
|
26114
26142
|
async function mapPool(items, concurrency, worker) {
|
|
26115
26143
|
const results = new Array(items.length);
|
|
26116
26144
|
let next = 0;
|
|
@@ -26137,7 +26165,6 @@ async function wikiEnrich(input2) {
|
|
|
26137
26165
|
const concurrency = Math.max(1, Math.min(MAX_CONCURRENCY, input2.concurrency ?? DEFAULT_CONCURRENCY));
|
|
26138
26166
|
const maxOutputTokens = input2.maxOutputTokens ?? DEFAULT_MAX_OUTPUT_TOKENS;
|
|
26139
26167
|
const validate = input2.validate !== false;
|
|
26140
|
-
const markAccepted = input2.keepStatus === true ? false : input2.markAccepted !== false;
|
|
26141
26168
|
const wikiConfig = await loadWikiConfig(input2.cwd);
|
|
26142
26169
|
const result = {
|
|
26143
26170
|
provider,
|
|
@@ -26231,9 +26258,7 @@ async function wikiEnrich(input2) {
|
|
|
26231
26258
|
return { path: page.relativePath, action: "failed", reason: `validation: ${structural}` };
|
|
26232
26259
|
}
|
|
26233
26260
|
}
|
|
26234
|
-
|
|
26235
|
-
enriched = setFrontmatterStatus(enriched, "accepted");
|
|
26236
|
-
}
|
|
26261
|
+
enriched = setFrontmatterStatus(enriched, extractFrontmatterStatus(original));
|
|
26237
26262
|
if (input2.dryRun) {
|
|
26238
26263
|
onPage({ index, total, path: page.relativePath, status, phase: "done" });
|
|
26239
26264
|
return {
|
|
@@ -26247,7 +26272,7 @@ async function wikiEnrich(input2) {
|
|
|
26247
26272
|
await writeFile36(page.absolutePath, `${enriched.endsWith(`
|
|
26248
26273
|
`) ? enriched : `${enriched}
|
|
26249
26274
|
`}`, "utf8");
|
|
26250
|
-
onPage({ index, total, path: page.relativePath, status
|
|
26275
|
+
onPage({ index, total, path: page.relativePath, status, phase: "done" });
|
|
26251
26276
|
return {
|
|
26252
26277
|
path: page.relativePath,
|
|
26253
26278
|
action: "enriched",
|
|
@@ -26273,7 +26298,6 @@ async function wikiEnrich(input2) {
|
|
|
26273
26298
|
model,
|
|
26274
26299
|
maxOutputTokens,
|
|
26275
26300
|
validate,
|
|
26276
|
-
markAccepted,
|
|
26277
26301
|
concurrency,
|
|
26278
26302
|
total,
|
|
26279
26303
|
onPage,
|
|
@@ -26356,8 +26380,8 @@ function finalizeEnrichedText(original, rawText, options) {
|
|
|
26356
26380
|
if (options.validate) {
|
|
26357
26381
|
structuralError = validateEnrichedMarkdown(original, content);
|
|
26358
26382
|
}
|
|
26359
|
-
if (structuralError === null
|
|
26360
|
-
content = setFrontmatterStatus(content,
|
|
26383
|
+
if (structuralError === null) {
|
|
26384
|
+
content = setFrontmatterStatus(content, extractFrontmatterStatus(original));
|
|
26361
26385
|
}
|
|
26362
26386
|
return { content, structuralError };
|
|
26363
26387
|
}
|
|
@@ -26384,7 +26408,7 @@ async function finishSuccess(ctx, page, originalRaw, content, keyFiles, extra) {
|
|
|
26384
26408
|
index,
|
|
26385
26409
|
total: ctx.total,
|
|
26386
26410
|
path: page.relativePath,
|
|
26387
|
-
status
|
|
26411
|
+
status,
|
|
26388
26412
|
phase: "done"
|
|
26389
26413
|
});
|
|
26390
26414
|
return {
|
|
@@ -26428,8 +26452,7 @@ async function runDeepSingle(ctx, item) {
|
|
|
26428
26452
|
let content = null;
|
|
26429
26453
|
if (rawText !== null) {
|
|
26430
26454
|
const finalized = finalizeEnrichedText(original, rawText, {
|
|
26431
|
-
validate: ctx.validate
|
|
26432
|
-
markAccepted: ctx.markAccepted
|
|
26455
|
+
validate: ctx.validate
|
|
26433
26456
|
});
|
|
26434
26457
|
if (finalized.structuralError === null) {
|
|
26435
26458
|
content = finalized.content;
|
|
@@ -26614,8 +26637,7 @@ async function runLightBatch(ctx, items) {
|
|
|
26614
26637
|
continue;
|
|
26615
26638
|
}
|
|
26616
26639
|
const finalized = finalizeEnrichedText(item.original, rawText, {
|
|
26617
|
-
validate: ctx.validate
|
|
26618
|
-
markAccepted: ctx.markAccepted
|
|
26640
|
+
validate: ctx.validate
|
|
26619
26641
|
});
|
|
26620
26642
|
if (finalized.structuralError !== null) {
|
|
26621
26643
|
ctx.onPage({ index, total: ctx.total, path: item.page.relativePath, status, phase: "failed" });
|
|
@@ -26666,7 +26688,6 @@ async function runRlmPipeline(ctxInput) {
|
|
|
26666
26688
|
model: ctxInput.model,
|
|
26667
26689
|
maxOutputTokens: ctxInput.maxOutputTokens,
|
|
26668
26690
|
validate: ctxInput.validate,
|
|
26669
|
-
markAccepted: ctxInput.markAccepted,
|
|
26670
26691
|
total: ctxInput.total,
|
|
26671
26692
|
onPage: ctxInput.onPage,
|
|
26672
26693
|
input: input2,
|
|
@@ -36675,7 +36696,6 @@ async function runEnrich(args2) {
|
|
|
36675
36696
|
const resume = args2.includes("--resume");
|
|
36676
36697
|
const refreshGraph = args2.includes("--refresh-graph");
|
|
36677
36698
|
const dryRun = args2.includes("--dry-run");
|
|
36678
|
-
const keepStatus = args2.includes("--keep-status");
|
|
36679
36699
|
const noValidate = args2.includes("--no-validate");
|
|
36680
36700
|
const valueFlags = new Set([
|
|
36681
36701
|
"--page",
|
|
@@ -36744,9 +36764,7 @@ async function runEnrich(args2) {
|
|
|
36744
36764
|
resume,
|
|
36745
36765
|
refreshGraph,
|
|
36746
36766
|
dryRun,
|
|
36747
|
-
keepStatus,
|
|
36748
36767
|
validate: !noValidate,
|
|
36749
|
-
markAccepted: !keepStatus,
|
|
36750
36768
|
...prompt ? { prompt } : {},
|
|
36751
36769
|
...provider ? { provider } : {},
|
|
36752
36770
|
...model ? { model } : {},
|
|
@@ -36823,10 +36841,12 @@ Usage:
|
|
|
36823
36841
|
keryx wiki validate
|
|
36824
36842
|
keryx wiki ask "<question>" [--k <n>] [--rerank]
|
|
36825
36843
|
keryx wiki enrich [<page>|--all] [--force] [--list] [--resume] [--limit N] [--concurrency N]
|
|
36826
|
-
[--refresh-graph] [--max-tokens N] [--
|
|
36844
|
+
[--refresh-graph] [--max-tokens N] [--no-validate]
|
|
36827
36845
|
[--prompt "<i>"] [--provider <p>] [--model <m>] [--dry-run] [--json]
|
|
36828
36846
|
# defaults: drafts only; provider/model from auth.json; validate on;
|
|
36829
|
-
#
|
|
36847
|
+
# concurrency 1 (raise for parallel page swarm)
|
|
36848
|
+
# rewrites prose only \u2014 Status is always left exactly as it was
|
|
36849
|
+
# before the run; enrich can never itself accept a page (issue #391)
|
|
36830
36850
|
keryx wiki context
|
|
36831
36851
|
keryx wiki backlinks <wiki-page-or-code-file>
|
|
36832
36852
|
|
|
@@ -49836,7 +49856,7 @@ function buildInteractiveAgentTools(input2) {
|
|
|
49836
49856
|
applyPatchTool(input2.cwd),
|
|
49837
49857
|
workspaceOverviewTool(input2.cwd),
|
|
49838
49858
|
workspaceReadTool(input2.cwd),
|
|
49839
|
-
workspaceCreateTool(input2.cwd),
|
|
49859
|
+
workspaceCreateTool(input2.cwd, getSessionDir),
|
|
49840
49860
|
workspaceListTool(input2.cwd),
|
|
49841
49861
|
workspaceShowTool(input2.cwd),
|
|
49842
49862
|
workspaceProposeTool(input2.cwd, getSessionDir),
|
|
@@ -49941,7 +49961,8 @@ var PREFIX_BANNED = new Set([
|
|
|
49941
49961
|
"osascript",
|
|
49942
49962
|
"open",
|
|
49943
49963
|
"tee",
|
|
49944
|
-
"cd"
|
|
49964
|
+
"cd",
|
|
49965
|
+
"keryx"
|
|
49945
49966
|
]);
|
|
49946
49967
|
var PREFIX_BANNED_READERS = new Set([
|
|
49947
49968
|
"cat",
|
|
@@ -50012,6 +50033,17 @@ function validateShellPattern(pattern) {
|
|
|
50012
50033
|
}
|
|
50013
50034
|
return { ok: true };
|
|
50014
50035
|
}
|
|
50036
|
+
function getRunningBinaryName() {
|
|
50037
|
+
try {
|
|
50038
|
+
const argv0 = process.argv0;
|
|
50039
|
+
if (!argv0)
|
|
50040
|
+
return "";
|
|
50041
|
+
const name = argv0.split(/[\\/]/).pop() ?? "";
|
|
50042
|
+
return name.toLowerCase();
|
|
50043
|
+
} catch {
|
|
50044
|
+
return "";
|
|
50045
|
+
}
|
|
50046
|
+
}
|
|
50015
50047
|
function bannedPrefixGrant(pattern, firstToken) {
|
|
50016
50048
|
const rest = pattern.slice(firstToken.length).trim();
|
|
50017
50049
|
const wildcardOnly = /^\*+$/.test(rest) || rest.length === 0 && /\*+$/.test(firstToken);
|
|
@@ -50024,6 +50056,13 @@ function bannedPrefixGrant(pattern, firstToken) {
|
|
|
50024
50056
|
reason: `\`${word} *\` grants arbitrary execution: ${word} is an interpreter or wrapper, so its first token does not constrain what runs`
|
|
50025
50057
|
};
|
|
50026
50058
|
}
|
|
50059
|
+
const runningBinary = getRunningBinaryName();
|
|
50060
|
+
if (runningBinary && word === runningBinary) {
|
|
50061
|
+
return {
|
|
50062
|
+
word,
|
|
50063
|
+
reason: `\`${word} *\` grants arbitrary execution: ${word} can run arbitrary subcommands, so remembering this grant would silently approve mutating/destructive operations forever`
|
|
50064
|
+
};
|
|
50065
|
+
}
|
|
50027
50066
|
if (PREFIX_BANNED_READERS.has(word)) {
|
|
50028
50067
|
return {
|
|
50029
50068
|
word,
|
|
@@ -53106,10 +53145,11 @@ init_agent();
|
|
|
53106
53145
|
init_slate_lifecycle();
|
|
53107
53146
|
init_slate();
|
|
53108
53147
|
init_workspace_service();
|
|
53109
|
-
init_workspace_resolve();
|
|
53110
53148
|
init_service7();
|
|
53149
|
+
init_store2();
|
|
53111
53150
|
init_fs();
|
|
53112
53151
|
import path149 from "path";
|
|
53152
|
+
import { readFile as readFile79 } from "fs/promises";
|
|
53113
53153
|
var POSITIVE_INTEGER = /^[1-9][0-9]*$/;
|
|
53114
53154
|
var DEFAULT_AUTO_GOAL_ROUNDS = 8;
|
|
53115
53155
|
function parseGoalArgs(rest) {
|
|
@@ -53211,9 +53251,20 @@ async function autoProvisionFlow(cwd, goalText) {
|
|
|
53211
53251
|
await service5.start({ cwd, id: result.flow.id });
|
|
53212
53252
|
return result.flow.id;
|
|
53213
53253
|
}
|
|
53254
|
+
var ROUND_DONE_MARKER = "GOAL_ROUND_COMPLETE";
|
|
53255
|
+
function continuationRoundClaimsDone(history) {
|
|
53256
|
+
for (let i = history.length - 1;i >= 0; i--) {
|
|
53257
|
+
const message2 = history[i];
|
|
53258
|
+
if (message2?.role === "assistant") {
|
|
53259
|
+
return message2.content.includes(ROUND_DONE_MARKER);
|
|
53260
|
+
}
|
|
53261
|
+
}
|
|
53262
|
+
return false;
|
|
53263
|
+
}
|
|
53214
53264
|
async function buildContinuationMessage(cwd, slateSession, round4, roundsCap) {
|
|
53215
53265
|
const totalRounds = roundsCap + 1;
|
|
53216
|
-
const
|
|
53266
|
+
const doneInstruction = `If \u2014 and only if \u2014 the stated goal is now FULLY achieved and there is nothing further to do this round, ` + `end your reply with the exact line ${ROUND_DONE_MARKER} on its own, and nothing else on that line. ` + `Otherwise do not include that line at all.`;
|
|
53267
|
+
const generic = `Continue working toward the stated goal (round ${round4} of ${totalRounds}). ${doneInstruction}`;
|
|
53217
53268
|
const slate = await readSlate(slateSession.dir).catch(() => {
|
|
53218
53269
|
return;
|
|
53219
53270
|
});
|
|
@@ -53251,11 +53302,61 @@ function parseVerifierVerdict(output2) {
|
|
|
53251
53302
|
return;
|
|
53252
53303
|
}
|
|
53253
53304
|
}
|
|
53254
|
-
|
|
53305
|
+
var MAX_EVIDENCE_SEEDS = 10;
|
|
53306
|
+
function summarizeRecentSeeds(seeds) {
|
|
53307
|
+
return seeds.slice(-MAX_EVIDENCE_SEEDS).map((seed) => `- Seed${seed.kind !== undefined ? ` [${seed.kind}]` : ""}: ${seed.text}`);
|
|
53308
|
+
}
|
|
53309
|
+
function summarizeWorkspaceProposals(history) {
|
|
53310
|
+
const lines = [];
|
|
53311
|
+
for (const message2 of history) {
|
|
53312
|
+
if (message2.role !== "assistant" || message2.toolCalls === undefined) {
|
|
53313
|
+
continue;
|
|
53314
|
+
}
|
|
53315
|
+
for (const call of message2.toolCalls) {
|
|
53316
|
+
if (call.name !== "workspace_propose") {
|
|
53317
|
+
continue;
|
|
53318
|
+
}
|
|
53319
|
+
const resultMessage = history.find((candidate) => candidate.role === "tool" && candidate.toolCallId === call.id);
|
|
53320
|
+
let argSummary = call.arguments;
|
|
53321
|
+
try {
|
|
53322
|
+
const parsedArgs = JSON.parse(call.arguments);
|
|
53323
|
+
const kind2 = typeof parsedArgs.kind === "string" ? parsedArgs.kind : "?";
|
|
53324
|
+
const note2 = typeof parsedArgs.note === "string" ? parsedArgs.note : undefined;
|
|
53325
|
+
argSummary = `kind=${kind2}${note2 !== undefined ? `, note=${note2}` : ""}`;
|
|
53326
|
+
} catch {}
|
|
53327
|
+
const outcome = resultMessage !== undefined ? resultMessage.content : "(no result recorded this run)";
|
|
53328
|
+
lines.push(`- workspace_propose: ${argSummary} -> ${outcome}`);
|
|
53329
|
+
}
|
|
53330
|
+
}
|
|
53331
|
+
return lines;
|
|
53332
|
+
}
|
|
53333
|
+
async function flowDefersCompletionToVerifier(cwd, flowId) {
|
|
53334
|
+
try {
|
|
53335
|
+
const dir = await resolveFlowDir(cwd, flowId);
|
|
53336
|
+
const text = await readFile79(acPath(cwd, dir), "utf8");
|
|
53337
|
+
const normalized = text.replace(/\s+/g, " ");
|
|
53338
|
+
return normalized.includes("judged by the verifier subagent");
|
|
53339
|
+
} catch {
|
|
53340
|
+
return false;
|
|
53341
|
+
}
|
|
53342
|
+
}
|
|
53343
|
+
async function buildVerifierEvidence(cwd, slateSession, history) {
|
|
53344
|
+
const slate = await readSlate(slateSession.dir).catch(() => {
|
|
53345
|
+
return;
|
|
53346
|
+
});
|
|
53347
|
+
const lines = [...slate !== undefined ? summarizeRecentSeeds(slate.seeds) : [], ...summarizeWorkspaceProposals(history)];
|
|
53348
|
+
const evidenceText = lines.length > 0 ? lines.join(`
|
|
53349
|
+
`) : "(no Seeds or workspace_propose records were recorded this run)";
|
|
53350
|
+
const flowId = slate?.course.flowRef;
|
|
53351
|
+
const deferToVerifier = flowId !== undefined ? await flowDefersCompletionToVerifier(cwd, flowId) : false;
|
|
53352
|
+
return { evidenceText, deferToVerifier };
|
|
53353
|
+
}
|
|
53354
|
+
async function runGoalVerifier(deps, goalText, cwd, slateSession, history, io, mintCallId) {
|
|
53255
53355
|
const tool = deps.tools.find((candidate) => candidate.definition.name === "spawn_subagent");
|
|
53256
53356
|
if (tool === undefined) {
|
|
53257
53357
|
return;
|
|
53258
53358
|
}
|
|
53359
|
+
const { evidenceText, deferToVerifier } = await buildVerifierEvidence(cwd, slateSession, history);
|
|
53259
53360
|
const task = [
|
|
53260
53361
|
"Independently verify whether the following goal has ACTUALLY been achieved, based on the",
|
|
53261
53362
|
"current, real state of the repository (read the real files/tests \u2014 never trust a prior",
|
|
@@ -53263,24 +53364,56 @@ async function runGoalVerifier(deps, goalText) {
|
|
|
53263
53364
|
"",
|
|
53264
53365
|
`Goal: "${goalText}"`,
|
|
53265
53366
|
"",
|
|
53367
|
+
"Evidence this run already produced (recent Slate Seeds and workspace_propose records) \u2014",
|
|
53368
|
+
"weigh this as real evidence of what was actually done, not merely a claim:",
|
|
53369
|
+
evidenceText,
|
|
53370
|
+
"",
|
|
53371
|
+
...deferToVerifier ? [
|
|
53372
|
+
"This run's Task Manager flow acceptance criteria explicitly defer the completion",
|
|
53373
|
+
"judgment to THIS verifier check, not to the flow's own task checkboxes \u2014 its tasks",
|
|
53374
|
+
"are expected to remain unchecked even when the goal is genuinely achieved. Do NOT",
|
|
53375
|
+
"treat an incomplete/unchecked flow task list, by itself, as evidence the goal is NOT",
|
|
53376
|
+
"achieved; judge achievement from the real repository state and the evidence above.",
|
|
53377
|
+
""
|
|
53378
|
+
] : [],
|
|
53266
53379
|
"Reply with EXACTLY one JSON object and nothing else, no prose before or after it:",
|
|
53267
53380
|
'{"achieved": true or false, "gaps": ["specific reason it is not fully achieved", ...]}',
|
|
53268
53381
|
'"gaps" must be empty when "achieved" is true.'
|
|
53269
53382
|
].join(`
|
|
53270
53383
|
`);
|
|
53384
|
+
const input2 = { task, mode: "read_only", label: "goal-verifier" };
|
|
53385
|
+
const callId = mintCallId();
|
|
53386
|
+
io.onToolCall?.("spawn_subagent", JSON.stringify(input2));
|
|
53387
|
+
history.push({
|
|
53388
|
+
role: "assistant",
|
|
53389
|
+
content: "",
|
|
53390
|
+
provenance: "model",
|
|
53391
|
+
toolCalls: [{ id: callId, name: "spawn_subagent", arguments: JSON.stringify(input2) }]
|
|
53392
|
+
});
|
|
53393
|
+
io.onHistoryChange?.("tool");
|
|
53271
53394
|
let result;
|
|
53272
53395
|
try {
|
|
53273
|
-
result = await tool.invoke(
|
|
53274
|
-
} catch {
|
|
53396
|
+
result = await tool.invoke(input2);
|
|
53397
|
+
} catch (err) {
|
|
53398
|
+
const errorResult = {
|
|
53399
|
+
output: `spawn_subagent dispatch failed: ${err instanceof Error ? err.message : String(err)}`,
|
|
53400
|
+
isError: true
|
|
53401
|
+
};
|
|
53402
|
+
io.onToolResult?.("spawn_subagent", errorResult);
|
|
53403
|
+
history.push({ role: "tool", content: errorResult.output, provenance: "tool", toolCallId: callId });
|
|
53404
|
+
io.onHistoryChange?.("tool");
|
|
53275
53405
|
return;
|
|
53276
53406
|
}
|
|
53407
|
+
io.onToolResult?.("spawn_subagent", result);
|
|
53408
|
+
history.push({ role: "tool", content: result.output, provenance: "tool", toolCallId: callId });
|
|
53409
|
+
io.onHistoryChange?.("tool");
|
|
53277
53410
|
if (result.isError) {
|
|
53278
53411
|
return;
|
|
53279
53412
|
}
|
|
53280
53413
|
return parseVerifierVerdict(result.output);
|
|
53281
53414
|
}
|
|
53282
53415
|
async function runGoalCommand(params) {
|
|
53283
|
-
const { raw, cwd, io, deps, history, slateSession, mintAttemptId
|
|
53416
|
+
const { raw, cwd, io, deps, history, slateSession, mintAttemptId } = params;
|
|
53284
53417
|
const parsed = parseGoalArgs(raw);
|
|
53285
53418
|
if ("error" in parsed) {
|
|
53286
53419
|
systemLine(io, `/goal: ${parsed.error}
|
|
@@ -53314,19 +53447,6 @@ async function runGoalCommand(params) {
|
|
|
53314
53447
|
const base = prev ?? { anchors: { root: "", touched: [] }, course: {}, seeds: [] };
|
|
53315
53448
|
return { ...base, workspaceId };
|
|
53316
53449
|
});
|
|
53317
|
-
} else {
|
|
53318
|
-
const current = await readSlate(slateSession.dir);
|
|
53319
|
-
if (current !== undefined && current.workspaceId === undefined) {
|
|
53320
|
-
const resolver = resolveWorkspace ?? resolveOrCreateWorkspace;
|
|
53321
|
-
const resolved2 = await resolver({ cwd, topicHint: parsed.text, provider: deps.providerId, model: deps.modelId });
|
|
53322
|
-
if (resolved2.ok) {
|
|
53323
|
-
await writeSlate(slateSession.dir, (prev) => {
|
|
53324
|
-
if (!prev)
|
|
53325
|
-
throw new Error(`SLATE-16 bind: no open slate in ${slateSession.dir}`);
|
|
53326
|
-
return { ...prev, workspaceId: resolved2.workspaceId };
|
|
53327
|
-
});
|
|
53328
|
-
}
|
|
53329
|
-
}
|
|
53330
53450
|
}
|
|
53331
53451
|
if (parsed.auto !== undefined) {
|
|
53332
53452
|
const forCourse = await readSlate(slateSession.dir);
|
|
@@ -53363,10 +53483,21 @@ async function runGoalCommand(params) {
|
|
|
53363
53483
|
systemLine(io, `/goal --auto: round ${round4}/${roundsCap + 1} \u2014 continuing toward the goal.
|
|
53364
53484
|
`);
|
|
53365
53485
|
await runAgentTurn(io, deps, history, continuationText, turnOptions);
|
|
53486
|
+
if (continuationRoundClaimsDone(history)) {
|
|
53487
|
+
systemLine(io, `/goal --auto: model signaled this round's work is complete (round ${round4}/${roundsCap + 1}) \u2014 ` + `ending the round budget early; the verifier will confirm.
|
|
53488
|
+
`);
|
|
53489
|
+
break;
|
|
53490
|
+
}
|
|
53366
53491
|
}
|
|
53367
53492
|
const wasOpenBeforeVerifier = slateSession.opened;
|
|
53368
|
-
const verdict = await runGoalVerifier(deps, parsed.text);
|
|
53369
|
-
if (verdict
|
|
53493
|
+
const verdict = await runGoalVerifier(deps, parsed.text, cwd, slateSession, history, io, mintAttemptId);
|
|
53494
|
+
if (verdict === undefined) {
|
|
53495
|
+
systemLine(io, `/goal --auto: verifier unavailable \u2014 outcome not independently checked.
|
|
53496
|
+
`);
|
|
53497
|
+
} else if (verdict.achieved) {
|
|
53498
|
+
systemLine(io, `/goal --auto: verifier confirmed the goal is achieved.
|
|
53499
|
+
`);
|
|
53500
|
+
} else {
|
|
53370
53501
|
systemLine(io, `/goal --auto: verifier found the goal not fully achieved${verdict.gaps.length > 0 ? ` \u2014 ${verdict.gaps.join("; ")}` : " (no specific gaps reported)"}
|
|
53371
53502
|
`);
|
|
53372
53503
|
if (roundsLeft > 0) {
|
|
@@ -53414,7 +53545,7 @@ import { spawnSync as spawnSync2 } from "child_process";
|
|
|
53414
53545
|
// package.json
|
|
53415
53546
|
var package_default = {
|
|
53416
53547
|
name: "@mrciphersmith/keryx",
|
|
53417
|
-
version: "0.2.
|
|
53548
|
+
version: "0.2.57",
|
|
53418
53549
|
description: "Version-controlled project context for AI coding agents: code graph, architecture wiki, project memory, relevant tests, quality signals, and task flows.",
|
|
53419
53550
|
private: false,
|
|
53420
53551
|
publishConfig: {
|
|
@@ -54567,6 +54698,13 @@ function ensureHost(otui, chrome) {
|
|
|
54567
54698
|
}
|
|
54568
54699
|
const idx = state.tabs.findIndex((tab) => tab.id === state.active);
|
|
54569
54700
|
const onStrip = focused !== null && containsNode(state.tabStrip, focused);
|
|
54701
|
+
const claimArrow = (direction) => state.input?.onArrowKeys?.(key, direction) === true;
|
|
54702
|
+
if (key.name === "left" && claimArrow("left")) {
|
|
54703
|
+
return;
|
|
54704
|
+
}
|
|
54705
|
+
if (key.name === "right" && claimArrow("right")) {
|
|
54706
|
+
return;
|
|
54707
|
+
}
|
|
54570
54708
|
if (key.name === "left" || onStrip && key.name === "tab" && key.shift === true) {
|
|
54571
54709
|
const prev = idx > 0 ? state.tabs[idx - 1] : undefined;
|
|
54572
54710
|
if (prev !== undefined) {
|
|
@@ -54674,7 +54812,7 @@ init_store3();
|
|
|
54674
54812
|
init_proposal_lifecycle();
|
|
54675
54813
|
init_workspace_service();
|
|
54676
54814
|
import { randomUUID as randomUUID24 } from "crypto";
|
|
54677
|
-
import { readdir as readdir26 } from "fs/promises";
|
|
54815
|
+
import { readdir as readdir26, stat as stat8 } from "fs/promises";
|
|
54678
54816
|
import path150 from "path";
|
|
54679
54817
|
|
|
54680
54818
|
// src/sac/lifecycle-flag.ts
|
|
@@ -54725,14 +54863,18 @@ async function computeLifecycleFlags(cwd, now = () => new Date) {
|
|
|
54725
54863
|
|
|
54726
54864
|
// src/sac/catch-up.ts
|
|
54727
54865
|
init_proposal_evidence();
|
|
54866
|
+
init_collect();
|
|
54867
|
+
init_store();
|
|
54728
54868
|
async function buildCatchUp(input2) {
|
|
54729
|
-
const [proposals, sessionCategories, lifecycleFlagsAll] = await Promise.all([
|
|
54869
|
+
const [proposals, sessionCategories, lifecycleFlagsAll, unreviewedPathsAll] = await Promise.all([
|
|
54730
54870
|
collectProposals(input2.cwd, input2.workspaceId),
|
|
54731
54871
|
collectSessionCategories(input2.cwd),
|
|
54732
|
-
computeLifecycleFlags(input2.cwd)
|
|
54872
|
+
computeLifecycleFlags(input2.cwd),
|
|
54873
|
+
detectUnreviewedSacPathChanges(input2.cwd, listSessions(input2.cwd))
|
|
54733
54874
|
]);
|
|
54734
54875
|
const lifecycleFlags = input2.workspaceId === undefined ? lifecycleFlagsAll : lifecycleFlagsAll.filter((flag) => flag.kind !== "workspace" || flag.ref === input2.workspaceId);
|
|
54735
|
-
|
|
54876
|
+
const unreviewedPaths = input2.workspaceId === undefined ? unreviewedPathsAll : unreviewedPathsAll.filter((item) => item.workspaceId === input2.workspaceId);
|
|
54877
|
+
return { proposals, ...sessionCategories, lifecycleFlags, unreviewedPaths };
|
|
54736
54878
|
}
|
|
54737
54879
|
async function collectProposals(cwd, workspaceId) {
|
|
54738
54880
|
const authorizationServer = localWorkspaceAuthorizationServer();
|
|
@@ -54795,8 +54937,82 @@ async function classifySession(session) {
|
|
|
54795
54937
|
const workspaceId = (await safeReadSlate(dir))?.workspaceId;
|
|
54796
54938
|
return { kind: "unknown", item: { type: "unknown", sessionId: session.id, ...workspaceId !== undefined ? { workspaceId } : {}, lastSeenAt: session.updatedAt } };
|
|
54797
54939
|
}
|
|
54940
|
+
async function readExternalUnboundCandidates(cwd) {
|
|
54941
|
+
const extDir = externalSlatesDir(cwd);
|
|
54942
|
+
let externalIds;
|
|
54943
|
+
try {
|
|
54944
|
+
const entries = await readdir26(extDir);
|
|
54945
|
+
externalIds = entries.filter((name) => name.endsWith(".json")).map((name) => name.slice(0, -".json".length));
|
|
54946
|
+
} catch {
|
|
54947
|
+
return [];
|
|
54948
|
+
}
|
|
54949
|
+
const candidates = [];
|
|
54950
|
+
for (const id of externalIds) {
|
|
54951
|
+
const slate = await readExternalSlate(cwd, id);
|
|
54952
|
+
if (!slate)
|
|
54953
|
+
continue;
|
|
54954
|
+
if (slate.closedAt === undefined || slate.workspaceId !== undefined)
|
|
54955
|
+
continue;
|
|
54956
|
+
const unbound2 = await readNewestUnboundCandidateForExternal(cwd, id);
|
|
54957
|
+
if (unbound2) {
|
|
54958
|
+
candidates.push({
|
|
54959
|
+
type: "unbound-candidate",
|
|
54960
|
+
externalSessionId: id,
|
|
54961
|
+
evidencePath: unbound2.evidencePath,
|
|
54962
|
+
summary: unbound2.summary
|
|
54963
|
+
});
|
|
54964
|
+
} else {
|
|
54965
|
+
const summary = summarizeUnboundCandidate((slate.seeds ?? []).reduce((groups, seed) => {
|
|
54966
|
+
const kind2 = seed.kind ?? "follow-up";
|
|
54967
|
+
const existing = groups.find((g) => g.kind === kind2);
|
|
54968
|
+
if (existing) {
|
|
54969
|
+
existing.seeds = (existing.seeds ?? []).concat([{ text: seed.text }]);
|
|
54970
|
+
} else {
|
|
54971
|
+
groups.push({ kind: kind2, seeds: [{ text: seed.text }] });
|
|
54972
|
+
}
|
|
54973
|
+
return groups;
|
|
54974
|
+
}, []));
|
|
54975
|
+
candidates.push({
|
|
54976
|
+
type: "unbound-candidate",
|
|
54977
|
+
externalSessionId: id,
|
|
54978
|
+
evidencePath: path150.join(extDir, `${id}.json`),
|
|
54979
|
+
summary
|
|
54980
|
+
});
|
|
54981
|
+
}
|
|
54982
|
+
}
|
|
54983
|
+
return candidates;
|
|
54984
|
+
}
|
|
54985
|
+
async function readNewestUnboundCandidateForExternal(cwd, externalSessionId) {
|
|
54986
|
+
const evidenceDir = path150.join(externalSlatesDir(cwd), externalSessionId);
|
|
54987
|
+
let entries;
|
|
54988
|
+
try {
|
|
54989
|
+
entries = (await readdir26(evidenceDir)).filter((name) => name.endsWith("-unbound-candidate.json"));
|
|
54990
|
+
} catch {
|
|
54991
|
+
return;
|
|
54992
|
+
}
|
|
54993
|
+
entries.sort();
|
|
54994
|
+
for (let i = entries.length - 1;i >= 0; i--) {
|
|
54995
|
+
const evidencePath = path150.join(evidenceDir, entries[i]);
|
|
54996
|
+
const result = readConfigFile(evidencePath);
|
|
54997
|
+
if (!result.ok) {
|
|
54998
|
+
continue;
|
|
54999
|
+
}
|
|
55000
|
+
try {
|
|
55001
|
+
const parsed = JSON.parse(result.text);
|
|
55002
|
+
if (parsed.recordType !== "unbound-candidate")
|
|
55003
|
+
continue;
|
|
55004
|
+
return { evidencePath, summary: summarizeUnboundCandidate(parsed.groups) };
|
|
55005
|
+
} catch {
|
|
55006
|
+
continue;
|
|
55007
|
+
}
|
|
55008
|
+
}
|
|
55009
|
+
return;
|
|
55010
|
+
}
|
|
54798
55011
|
async function collectSessionCategories(cwd) {
|
|
54799
|
-
const classified = await Promise.all(
|
|
55012
|
+
const [classified, externalUnboundCandidates] = await Promise.all([
|
|
55013
|
+
Promise.all(listSessions(cwd).map((session) => classifySession(session))),
|
|
55014
|
+
readExternalUnboundCandidates(cwd)
|
|
55015
|
+
]);
|
|
54800
55016
|
const blocked2 = [];
|
|
54801
55017
|
const unboundCandidates = [];
|
|
54802
55018
|
const unknown = [];
|
|
@@ -54810,8 +55026,151 @@ async function collectSessionCategories(cwd) {
|
|
|
54810
55026
|
else
|
|
54811
55027
|
unknown.push(category.item);
|
|
54812
55028
|
}
|
|
55029
|
+
unboundCandidates.push(...externalUnboundCandidates);
|
|
54813
55030
|
return { blocked: blocked2, unboundCandidates, unknown };
|
|
54814
55031
|
}
|
|
55032
|
+
var SESSION_ATTRIBUTION_SLACK_MS = 5 * 60000;
|
|
55033
|
+
var MTIME_CLOCK_SKEW_TOLERANCE_MS = 5000;
|
|
55034
|
+
async function collectReceiptTargets(cwd, owner) {
|
|
55035
|
+
const targets = new Set;
|
|
55036
|
+
const workspacesDir = path150.join(cwd, ".metaproject", "workspaces");
|
|
55037
|
+
let workspaceIds;
|
|
55038
|
+
try {
|
|
55039
|
+
workspaceIds = (await readdir26(workspacesDir, { withFileTypes: true })).filter((entry) => entry.isDirectory()).map((entry) => entry.name);
|
|
55040
|
+
} catch {
|
|
55041
|
+
return targets;
|
|
55042
|
+
}
|
|
55043
|
+
for (const workspaceId of workspaceIds) {
|
|
55044
|
+
const receiptsDir = path150.join(workspacesDir, workspaceId, `${owner}-write-receipts`);
|
|
55045
|
+
let files;
|
|
55046
|
+
try {
|
|
55047
|
+
files = (await readdir26(receiptsDir)).filter((name) => name.endsWith(".json"));
|
|
55048
|
+
} catch {
|
|
55049
|
+
continue;
|
|
55050
|
+
}
|
|
55051
|
+
for (const file of files) {
|
|
55052
|
+
const result = readConfigFile(path150.join(receiptsDir, file));
|
|
55053
|
+
if (!result.ok)
|
|
55054
|
+
continue;
|
|
55055
|
+
try {
|
|
55056
|
+
const receipt = JSON.parse(result.text);
|
|
55057
|
+
if (typeof receipt.targetRef === "string") {
|
|
55058
|
+
targets.add(receipt.targetRef.replace(/^\.\//, ""));
|
|
55059
|
+
}
|
|
55060
|
+
} catch {
|
|
55061
|
+
continue;
|
|
55062
|
+
}
|
|
55063
|
+
}
|
|
55064
|
+
}
|
|
55065
|
+
return targets;
|
|
55066
|
+
}
|
|
55067
|
+
async function attributeToSession(absolutePath, sessionsNewestFirst) {
|
|
55068
|
+
let mtimeMs;
|
|
55069
|
+
try {
|
|
55070
|
+
mtimeMs = (await stat8(absolutePath)).mtimeMs;
|
|
55071
|
+
} catch {
|
|
55072
|
+
return;
|
|
55073
|
+
}
|
|
55074
|
+
for (const session of sessionsNewestFirst) {
|
|
55075
|
+
const start = Date.parse(session.createdAt);
|
|
55076
|
+
const end = Date.parse(session.updatedAt);
|
|
55077
|
+
if (Number.isNaN(start) || Number.isNaN(end))
|
|
55078
|
+
continue;
|
|
55079
|
+
if (mtimeMs >= start - MTIME_CLOCK_SKEW_TOLERANCE_MS && mtimeMs <= end + SESSION_ATTRIBUTION_SLACK_MS) {
|
|
55080
|
+
const workspaceId = (await safeReadSlate(sessionDir(session.projectPath, session.id)))?.workspaceId;
|
|
55081
|
+
return { sessionId: session.id, workspaceId, changedAt: new Date(mtimeMs).toISOString() };
|
|
55082
|
+
}
|
|
55083
|
+
}
|
|
55084
|
+
return;
|
|
55085
|
+
}
|
|
55086
|
+
async function findSkillFiles(dir) {
|
|
55087
|
+
let entries;
|
|
55088
|
+
try {
|
|
55089
|
+
entries = await readdir26(dir, { withFileTypes: true });
|
|
55090
|
+
} catch {
|
|
55091
|
+
return [];
|
|
55092
|
+
}
|
|
55093
|
+
const found = [];
|
|
55094
|
+
for (const entry of entries) {
|
|
55095
|
+
const full = path150.join(dir, entry.name);
|
|
55096
|
+
if (entry.isDirectory()) {
|
|
55097
|
+
found.push(...await findSkillFiles(full));
|
|
55098
|
+
} else if (entry.isFile() && entry.name === "SKILL.md") {
|
|
55099
|
+
found.push(full);
|
|
55100
|
+
}
|
|
55101
|
+
}
|
|
55102
|
+
return found;
|
|
55103
|
+
}
|
|
55104
|
+
async function detectUnreviewedSacPathChanges(cwd, sessions) {
|
|
55105
|
+
const scoped = [...sessions].filter((session) => session.projectPath === cwd).sort((a, b) => Date.parse(b.updatedAt) - Date.parse(a.updatedAt));
|
|
55106
|
+
if (scoped.length === 0)
|
|
55107
|
+
return [];
|
|
55108
|
+
const [wikiReceipts, memoryReceipts, skillReceipts] = await Promise.all([
|
|
55109
|
+
collectReceiptTargets(cwd, "wiki"),
|
|
55110
|
+
collectReceiptTargets(cwd, "memory"),
|
|
55111
|
+
collectReceiptTargets(cwd, "skill")
|
|
55112
|
+
]);
|
|
55113
|
+
const items = [];
|
|
55114
|
+
const wikiPages = await collectPages(cwd).catch(() => []);
|
|
55115
|
+
for (const page of wikiPages) {
|
|
55116
|
+
if ((page.status ?? "").toLowerCase() !== "accepted")
|
|
55117
|
+
continue;
|
|
55118
|
+
const targetRef = `wiki/${page.relativePath}`;
|
|
55119
|
+
if (wikiReceipts.has(targetRef))
|
|
55120
|
+
continue;
|
|
55121
|
+
const attribution = await attributeToSession(page.absolutePath, scoped);
|
|
55122
|
+
if (attribution === undefined)
|
|
55123
|
+
continue;
|
|
55124
|
+
items.push({
|
|
55125
|
+
type: "unreviewed-sac-path",
|
|
55126
|
+
sessionId: attribution.sessionId,
|
|
55127
|
+
...attribution.workspaceId !== undefined ? { workspaceId: attribution.workspaceId } : {},
|
|
55128
|
+
owner: "wiki",
|
|
55129
|
+
path: targetRef,
|
|
55130
|
+
...page.status !== null ? { status: page.status } : {},
|
|
55131
|
+
changedAt: attribution.changedAt
|
|
55132
|
+
});
|
|
55133
|
+
}
|
|
55134
|
+
const memoryEntries = await collectEntries(cwd).catch(() => []);
|
|
55135
|
+
for (const entry of memoryEntries) {
|
|
55136
|
+
if (entry.status !== "accepted")
|
|
55137
|
+
continue;
|
|
55138
|
+
const targetRef = `memory/${entry.relativePath}`;
|
|
55139
|
+
if (memoryReceipts.has(targetRef))
|
|
55140
|
+
continue;
|
|
55141
|
+
const attribution = await attributeToSession(entry.absolutePath, scoped);
|
|
55142
|
+
if (attribution === undefined)
|
|
55143
|
+
continue;
|
|
55144
|
+
items.push({
|
|
55145
|
+
type: "unreviewed-sac-path",
|
|
55146
|
+
sessionId: attribution.sessionId,
|
|
55147
|
+
...attribution.workspaceId !== undefined ? { workspaceId: attribution.workspaceId } : {},
|
|
55148
|
+
owner: "memory",
|
|
55149
|
+
path: targetRef,
|
|
55150
|
+
status: entry.status,
|
|
55151
|
+
changedAt: attribution.changedAt
|
|
55152
|
+
});
|
|
55153
|
+
}
|
|
55154
|
+
const sacSkillsDir = path150.join(cwd, ".metaproject", "project-skills", "sac");
|
|
55155
|
+
const skillFiles = await findSkillFiles(sacSkillsDir);
|
|
55156
|
+
for (const absolutePath of skillFiles) {
|
|
55157
|
+
const targetRef = path150.relative(path150.join(cwd, ".metaproject"), absolutePath).split(path150.sep).join("/");
|
|
55158
|
+
if (skillReceipts.has(targetRef))
|
|
55159
|
+
continue;
|
|
55160
|
+
const attribution = await attributeToSession(absolutePath, scoped);
|
|
55161
|
+
if (attribution === undefined)
|
|
55162
|
+
continue;
|
|
55163
|
+
items.push({
|
|
55164
|
+
type: "unreviewed-sac-path",
|
|
55165
|
+
sessionId: attribution.sessionId,
|
|
55166
|
+
...attribution.workspaceId !== undefined ? { workspaceId: attribution.workspaceId } : {},
|
|
55167
|
+
owner: "skill",
|
|
55168
|
+
path: targetRef,
|
|
55169
|
+
changedAt: attribution.changedAt
|
|
55170
|
+
});
|
|
55171
|
+
}
|
|
55172
|
+
return items;
|
|
55173
|
+
}
|
|
54815
55174
|
async function isSlateEngaged(dir) {
|
|
54816
55175
|
if (await pathExists(path150.join(dir, "slate.json")))
|
|
54817
55176
|
return true;
|
|
@@ -55078,7 +55437,7 @@ async function loadInspectorCatchUp(cwd) {
|
|
|
55078
55437
|
try {
|
|
55079
55438
|
return await buildCatchUp({ cwd });
|
|
55080
55439
|
} catch {
|
|
55081
|
-
return { proposals: [], blocked: [], unboundCandidates: [], unknown: [], lifecycleFlags: [] };
|
|
55440
|
+
return { proposals: [], blocked: [], unboundCandidates: [], unknown: [], lifecycleFlags: [], unreviewedPaths: [] };
|
|
55082
55441
|
}
|
|
55083
55442
|
}
|
|
55084
55443
|
function catchUpItems(report) {
|
|
@@ -55617,10 +55976,9 @@ function openWorkspace(otui, chrome, options) {
|
|
|
55617
55976
|
var REVIEW_COMMAND = "/review";
|
|
55618
55977
|
var REVIEW_FOOTER = [
|
|
55619
55978
|
{ key: "[/]", label: "item" },
|
|
55620
|
-
{ key: "a
|
|
55621
|
-
{ key: "
|
|
55979
|
+
{ key: "\u2190/\u2192 a d", label: "accept/decline" },
|
|
55980
|
+
{ key: "enter y", label: "arm \u2192 confirm" },
|
|
55622
55981
|
{ key: "\u2191/\u2193", label: "scroll" },
|
|
55623
|
-
{ key: "\u2190/\u2192", label: "tabs" },
|
|
55624
55982
|
{ key: "esc", label: "close" }
|
|
55625
55983
|
];
|
|
55626
55984
|
function isReviewCommand(line) {
|
|
@@ -55804,14 +56162,58 @@ function paintLines3(otui, renderer, body, lines, width) {
|
|
|
55804
56162
|
parent.add(node);
|
|
55805
56163
|
return node;
|
|
55806
56164
|
}
|
|
56165
|
+
function paintActionButtons(otui, renderer, body, callbacks) {
|
|
56166
|
+
if (otui === undefined || otui === null || body === undefined || body === null) {
|
|
56167
|
+
return;
|
|
56168
|
+
}
|
|
56169
|
+
const parent = body;
|
|
56170
|
+
const boxCtor = otui.BoxRenderable;
|
|
56171
|
+
const textCtor = otui.TextRenderable;
|
|
56172
|
+
if (parent.add === undefined || boxCtor === undefined || textCtor === undefined) {
|
|
56173
|
+
return;
|
|
56174
|
+
}
|
|
56175
|
+
const BoxCtor = boxCtor;
|
|
56176
|
+
const TextCtor = textCtor;
|
|
56177
|
+
const addChild = (child) => parent.add?.(child);
|
|
56178
|
+
const theme = getTheme();
|
|
56179
|
+
const make = (label, id, color, onClick) => {
|
|
56180
|
+
const box = new BoxCtor(renderer, {
|
|
56181
|
+
id,
|
|
56182
|
+
flexShrink: 0,
|
|
56183
|
+
marginLeft: 1,
|
|
56184
|
+
paddingLeft: 1,
|
|
56185
|
+
paddingRight: 1,
|
|
56186
|
+
onMouseDown: (event) => {
|
|
56187
|
+
event.stopPropagation();
|
|
56188
|
+
onClick();
|
|
56189
|
+
}
|
|
56190
|
+
});
|
|
56191
|
+
const text = new TextCtor(renderer, { id: `${id}-t`, content: `[${label}]` });
|
|
56192
|
+
text.fg = color;
|
|
56193
|
+
box.add(text);
|
|
56194
|
+
addChild(box);
|
|
56195
|
+
const setActive = (active) => {
|
|
56196
|
+
box.backgroundColor = active ? theme.highlight : undefined;
|
|
56197
|
+
text.content = `[${label}]`;
|
|
56198
|
+
text.fg = color;
|
|
56199
|
+
};
|
|
56200
|
+
return { setActive };
|
|
56201
|
+
};
|
|
56202
|
+
return {
|
|
56203
|
+
accept: make("Accept", "review-accept", theme.ok, callbacks.onAccept),
|
|
56204
|
+
decline: make("Decline", "review-decline", theme.error, callbacks.onDecline)
|
|
56205
|
+
};
|
|
56206
|
+
}
|
|
55807
56207
|
function presentReview(openModal2, otui, chrome, options) {
|
|
55808
56208
|
const items = [...options.items];
|
|
55809
56209
|
let selected = 0;
|
|
55810
56210
|
let listScroll = 0;
|
|
55811
56211
|
let detailScroll = 0;
|
|
55812
56212
|
let status = { kind: "idle" };
|
|
56213
|
+
let focusedAction = "accept";
|
|
55813
56214
|
let listNode;
|
|
55814
56215
|
let detailNode;
|
|
56216
|
+
let actionButtons;
|
|
55815
56217
|
let unsubscribeKey;
|
|
55816
56218
|
const rendererHint = options.renderer ?? chrome?.renderer;
|
|
55817
56219
|
const bodyRows = options.visibleRows ?? (typeof rendererHint?.width === "number" && typeof rendererHint.height === "number" ? modalBodyRows(resolveModalPanelSize(rendererHint.width, rendererHint.height).height) : 13);
|
|
@@ -55832,6 +56234,7 @@ function presentReview(openModal2, otui, chrome, options) {
|
|
|
55832
56234
|
detailNode.content = windowLines3(detailLines(), detailScroll, bodyRows).join(`
|
|
55833
56235
|
`);
|
|
55834
56236
|
}
|
|
56237
|
+
updateButtons();
|
|
55835
56238
|
};
|
|
55836
56239
|
const moveSelection = (next) => {
|
|
55837
56240
|
if (items.length === 0) {
|
|
@@ -55847,6 +56250,22 @@ function presentReview(openModal2, otui, chrome, options) {
|
|
|
55847
56250
|
paintSelection();
|
|
55848
56251
|
};
|
|
55849
56252
|
const handlerFor = (decision) => decision === "accept" ? options.acceptProposal : options.declineProposal;
|
|
56253
|
+
const updateButtons = () => {
|
|
56254
|
+
if (actionButtons === undefined) {
|
|
56255
|
+
return;
|
|
56256
|
+
}
|
|
56257
|
+
const onProposal = items[selected]?.type === "proposal" && status.kind !== "done";
|
|
56258
|
+
const highlighted = status.kind === "armed" ? status.decision : focusedAction;
|
|
56259
|
+
actionButtons.accept.setActive(onProposal && highlighted === "accept");
|
|
56260
|
+
actionButtons.decline.setActive(onProposal && highlighted === "decline");
|
|
56261
|
+
};
|
|
56262
|
+
const armDecision = (decision) => {
|
|
56263
|
+
if (status.kind === "running") {
|
|
56264
|
+
return;
|
|
56265
|
+
}
|
|
56266
|
+
status = items[selected]?.type === "proposal" && handlerFor(decision) !== undefined ? { kind: "armed", decision } : { kind: "unavailable", decision };
|
|
56267
|
+
paintSelection();
|
|
56268
|
+
};
|
|
55850
56269
|
const runDecision = (decision) => {
|
|
55851
56270
|
const item = items[selected];
|
|
55852
56271
|
const run = handlerFor(decision);
|
|
@@ -55871,16 +56290,43 @@ function presentReview(openModal2, otui, chrome, options) {
|
|
|
55871
56290
|
],
|
|
55872
56291
|
initialTab: "list",
|
|
55873
56292
|
footer: REVIEW_FOOTER,
|
|
56293
|
+
onArrowKeys: (key, direction) => {
|
|
56294
|
+
if (items[selected]?.type === "proposal" && status.kind !== "running" && status.kind !== "done" && handle?.activeTab() === "detail") {
|
|
56295
|
+
focusedAction = direction === "left" ? "accept" : "decline";
|
|
56296
|
+
status = { kind: "idle" };
|
|
56297
|
+
paintSelection();
|
|
56298
|
+
return true;
|
|
56299
|
+
}
|
|
56300
|
+
return false;
|
|
56301
|
+
},
|
|
55874
56302
|
renderTab: (tabId, body, ctx) => {
|
|
55875
56303
|
const renderer = options.renderer ?? chrome?.renderer;
|
|
55876
56304
|
tabWidth = ctx?.width;
|
|
55877
56305
|
if (tabId === "list") {
|
|
56306
|
+
detailNode = undefined;
|
|
56307
|
+
actionButtons = undefined;
|
|
55878
56308
|
listScroll = scrollToReveal3(selected, listScroll, bodyRows);
|
|
55879
56309
|
listNode = paintLines3(otui, renderer, body, windowLines3(listLines(), listScroll, bodyRows));
|
|
55880
56310
|
return;
|
|
55881
56311
|
}
|
|
56312
|
+
listNode = undefined;
|
|
55882
56313
|
detailScroll = clampScroll3(detailScroll, detailLines().length, bodyRows);
|
|
55883
56314
|
detailNode = paintLines3(otui, renderer, body, windowLines3(detailLines(), detailScroll, bodyRows), tabWidth);
|
|
56315
|
+
if (items[selected]?.type === "proposal") {
|
|
56316
|
+
actionButtons = paintActionButtons(otui, renderer, body, {
|
|
56317
|
+
onAccept: () => {
|
|
56318
|
+
focusedAction = "accept";
|
|
56319
|
+
armDecision("accept");
|
|
56320
|
+
},
|
|
56321
|
+
onDecline: () => {
|
|
56322
|
+
focusedAction = "decline";
|
|
56323
|
+
armDecision("decline");
|
|
56324
|
+
}
|
|
56325
|
+
});
|
|
56326
|
+
updateButtons();
|
|
56327
|
+
} else {
|
|
56328
|
+
actionButtons = undefined;
|
|
56329
|
+
}
|
|
55884
56330
|
},
|
|
55885
56331
|
onClose: () => {
|
|
55886
56332
|
unsubscribeKey?.();
|
|
@@ -55897,7 +56343,7 @@ function presentReview(openModal2, otui, chrome, options) {
|
|
|
55897
56343
|
}
|
|
55898
56344
|
const onDetail = handle.activeTab() === "detail";
|
|
55899
56345
|
if (onDetail && status.kind === "armed") {
|
|
55900
|
-
if (token === "y") {
|
|
56346
|
+
if (token === "y" || token === "return" || token === "enter") {
|
|
55901
56347
|
runDecision(status.decision);
|
|
55902
56348
|
} else {
|
|
55903
56349
|
status = { kind: "idle" };
|
|
@@ -55914,15 +56360,29 @@ function presentReview(openModal2, otui, chrome, options) {
|
|
|
55914
56360
|
return;
|
|
55915
56361
|
}
|
|
55916
56362
|
if (token === "return" || token === "enter") {
|
|
56363
|
+
if (onDetail && items[selected]?.type === "proposal" && status.kind !== "running" && status.kind !== "done") {
|
|
56364
|
+
if (status.kind === "armed") {
|
|
56365
|
+
runDecision(status.decision);
|
|
56366
|
+
} else {
|
|
56367
|
+
armDecision(focusedAction);
|
|
56368
|
+
}
|
|
56369
|
+
return;
|
|
56370
|
+
}
|
|
55917
56371
|
handle.setTab("detail");
|
|
55918
56372
|
return;
|
|
55919
56373
|
}
|
|
55920
|
-
if (onDetail && (token === "
|
|
55921
|
-
|
|
55922
|
-
status =
|
|
56374
|
+
if (onDetail && (token === "left" || token === "right") && status.kind !== "running" && status.kind !== "done") {
|
|
56375
|
+
focusedAction = token === "left" ? "accept" : "decline";
|
|
56376
|
+
status = { kind: "idle" };
|
|
55923
56377
|
paintSelection();
|
|
55924
56378
|
return;
|
|
55925
56379
|
}
|
|
56380
|
+
if (onDetail && (token === "a" || token === "d") && status.kind !== "running" && status.kind !== "done") {
|
|
56381
|
+
const decision = token === "a" ? "accept" : "decline";
|
|
56382
|
+
focusedAction = decision;
|
|
56383
|
+
armDecision(decision);
|
|
56384
|
+
return;
|
|
56385
|
+
}
|
|
55926
56386
|
if (token === "up" || token === "k") {
|
|
55927
56387
|
if (onDetail) {
|
|
55928
56388
|
detailScroll = clampScroll3(detailScroll - 1, detailLines().length, bodyRows);
|
|
@@ -65153,6 +65613,22 @@ New session ${shortSessionId(live.summary.id)}.
|
|
|
65153
65613
|
slateSession,
|
|
65154
65614
|
mintAttemptId: mintTimestampAttemptId
|
|
65155
65615
|
});
|
|
65616
|
+
} else if (command === "/theme") {
|
|
65617
|
+
const wanted = rest.trim();
|
|
65618
|
+
if (wanted.length === 0) {
|
|
65619
|
+
agentIo.onSystem?.(formatThemeList(getThemeId()));
|
|
65620
|
+
} else {
|
|
65621
|
+
const next = parseThemeId(wanted);
|
|
65622
|
+
if (next === undefined) {
|
|
65623
|
+
agentIo.onSystem?.(`Unknown theme '${wanted}'.
|
|
65624
|
+
${formatThemeList(getThemeId())}`);
|
|
65625
|
+
} else {
|
|
65626
|
+
applyThemeId(next);
|
|
65627
|
+
persistThemeId(next);
|
|
65628
|
+
agentIo.onSystem?.(`Theme: ${themeLabel(next)}
|
|
65629
|
+
`);
|
|
65630
|
+
}
|
|
65631
|
+
}
|
|
65156
65632
|
} else {
|
|
65157
65633
|
agentIo.onSystem?.(describeUnavailableCommand(command, "agent") ?? `Unknown command: ${command}. Type /help.
|
|
65158
65634
|
`);
|
|
@@ -65417,6 +65893,12 @@ async function shellCommand(args2, runtime = {}) {
|
|
|
65417
65893
|
}
|
|
65418
65894
|
}
|
|
65419
65895
|
const rl = readline2.createInterface({ input: process.stdin });
|
|
65896
|
+
if (!process.stdin.isTTY) {
|
|
65897
|
+
process.on("SIGINT", () => {
|
|
65898
|
+
rl.close();
|
|
65899
|
+
process.exit(130);
|
|
65900
|
+
});
|
|
65901
|
+
}
|
|
65420
65902
|
const lineIterator = rl[Symbol.asyncIterator]();
|
|
65421
65903
|
const sharedLines = { [Symbol.asyncIterator]: () => lineIterator };
|
|
65422
65904
|
const { io, emitSystem, printHeader, printPrompt, destroy } = createRichIo(sharedLines, versionCheck);
|
|
@@ -65683,7 +66165,7 @@ Shell:
|
|
|
65683
66165
|
|
|
65684
66166
|
// src/commands/modules.ts
|
|
65685
66167
|
init_fs();
|
|
65686
|
-
import { readFile as
|
|
66168
|
+
import { readFile as readFile80 } from "fs/promises";
|
|
65687
66169
|
import { stdin } from "process";
|
|
65688
66170
|
import path153 from "path";
|
|
65689
66171
|
var MODULES = [
|
|
@@ -65745,7 +66227,7 @@ async function modulesCommand(args2 = []) {
|
|
|
65745
66227
|
}
|
|
65746
66228
|
let manifest = {};
|
|
65747
66229
|
try {
|
|
65748
|
-
manifest = JSON.parse(await
|
|
66230
|
+
manifest = JSON.parse(await readFile80(manifestPath, "utf8"));
|
|
65749
66231
|
} catch {}
|
|
65750
66232
|
const enabled = new Set(MODULES.filter((module) => manifest.modules?.[module.name]?.enabled === true).map((module) => module.name));
|
|
65751
66233
|
if (wantsJson) {
|
|
@@ -67719,7 +68201,7 @@ function printHelp17() {
|
|
|
67719
68201
|
|
|
67720
68202
|
// src/commands/update.ts
|
|
67721
68203
|
import { spawn as spawn5 } from "child_process";
|
|
67722
|
-
import { chmod as chmod4, mkdir as mkdir56, readFile as
|
|
68204
|
+
import { chmod as chmod4, mkdir as mkdir56, readFile as readFile81, readdir as readdir27, writeFile as writeFile49 } from "fs/promises";
|
|
67723
68205
|
import { access as access4, constants as constants2, existsSync as existsSync30 } from "fs";
|
|
67724
68206
|
import path158 from "path";
|
|
67725
68207
|
import { fileURLToPath as fileURLToPath7 } from "url";
|
|
@@ -68030,7 +68512,7 @@ async function shouldInstallDashboardPostCommitHook(projectRoot, manifest) {
|
|
|
68030
68512
|
if (!await pathExists(hookPath)) {
|
|
68031
68513
|
return false;
|
|
68032
68514
|
}
|
|
68033
|
-
return (await
|
|
68515
|
+
return (await readFile81(hookPath, "utf8")).includes("# keryx:");
|
|
68034
68516
|
}
|
|
68035
68517
|
async function collectDashboardData(metaprojectRoot) {
|
|
68036
68518
|
const data = {};
|
|
@@ -68082,12 +68564,12 @@ async function collectTasksDashboardData(metaprojectRoot) {
|
|
|
68082
68564
|
continue;
|
|
68083
68565
|
}
|
|
68084
68566
|
try {
|
|
68085
|
-
const flow = JSON.parse(await
|
|
68567
|
+
const flow = JSON.parse(await readFile81(flowPath, "utf8"));
|
|
68086
68568
|
const tasks = Array.isArray(flow.tasks) ? flow.tasks : [];
|
|
68087
68569
|
let acTotal = 0;
|
|
68088
68570
|
const acPath2 = path158.join(flowsRoot2, dir, "acceptance-criteria.md");
|
|
68089
68571
|
if (await pathExists(acPath2)) {
|
|
68090
|
-
const acContent = await
|
|
68572
|
+
const acContent = await readFile81(acPath2, "utf8");
|
|
68091
68573
|
acTotal = (acContent.match(/^- AC\d+:/gm) ?? []).length;
|
|
68092
68574
|
}
|
|
68093
68575
|
flows.push({
|
|
@@ -68143,7 +68625,7 @@ async function collectDashboardDocs(metaprojectRoot, wiki, memory) {
|
|
|
68143
68625
|
if (!await pathExists(filePath)) {
|
|
68144
68626
|
continue;
|
|
68145
68627
|
}
|
|
68146
|
-
const content = await
|
|
68628
|
+
const content = await readFile81(filePath, "utf8");
|
|
68147
68629
|
docs[href] = content.length > 40000 ? `${content.slice(0, 40000)}
|
|
68148
68630
|
|
|
68149
68631
|
\u2026truncated\u2026` : content;
|
|
@@ -68160,7 +68642,7 @@ async function collectHealthDashboardData(metaprojectRoot) {
|
|
|
68160
68642
|
if (!await pathExists(reportPath2)) {
|
|
68161
68643
|
return;
|
|
68162
68644
|
}
|
|
68163
|
-
const report = JSON.parse(await
|
|
68645
|
+
const report = JSON.parse(await readFile81(reportPath2, "utf8"));
|
|
68164
68646
|
const metrics = Array.isArray(report.metrics) ? report.metrics : [];
|
|
68165
68647
|
const findings = Array.isArray(report.findings) ? report.findings : [];
|
|
68166
68648
|
const project = metrics.find((metric) => metric.key === "project") ?? {};
|
|
@@ -68274,7 +68756,7 @@ async function collectGraphDashboardData(metaprojectRoot) {
|
|
|
68274
68756
|
let nodes = 0;
|
|
68275
68757
|
let files = 0;
|
|
68276
68758
|
let assets = 0;
|
|
68277
|
-
for (const node of parseJsonl2(await
|
|
68759
|
+
for (const node of parseJsonl2(await readFile81(nodesPath, "utf8"))) {
|
|
68278
68760
|
nodes += 1;
|
|
68279
68761
|
if (node.kind === "asset") {
|
|
68280
68762
|
assets += 1;
|
|
@@ -68290,7 +68772,7 @@ async function collectGraphDashboardData(metaprojectRoot) {
|
|
|
68290
68772
|
let imports = 0;
|
|
68291
68773
|
let assetEdges = 0;
|
|
68292
68774
|
let unresolved = 0;
|
|
68293
|
-
for (const edge of parseJsonl2(await
|
|
68775
|
+
for (const edge of parseJsonl2(await readFile81(edgesPath, "utf8"))) {
|
|
68294
68776
|
edges += 1;
|
|
68295
68777
|
if (edge.kind === "imports") {
|
|
68296
68778
|
imports += 1;
|
|
@@ -68320,7 +68802,7 @@ async function collectTestingDashboardData(metaprojectRoot) {
|
|
|
68320
68802
|
const reportPath2 = path158.join(metaprojectRoot, "data", "testing", "artifacts", "latest.json");
|
|
68321
68803
|
const contextPath = path158.join(metaprojectRoot, "data", "testing", "context.md");
|
|
68322
68804
|
if (await pathExists(reportPath2)) {
|
|
68323
|
-
const report = JSON.parse(await
|
|
68805
|
+
const report = JSON.parse(await readFile81(reportPath2, "utf8"));
|
|
68324
68806
|
const totalTests = numberOrUndefined(report.total);
|
|
68325
68807
|
const failedTests = Array.isArray(report.failures) ? report.failures.length : numberOrUndefined(report.failed);
|
|
68326
68808
|
return {
|
|
@@ -68351,7 +68833,7 @@ async function collectMarkdownPages(root, hrefPrefix) {
|
|
|
68351
68833
|
if (relativePath === "index.md" || relativePath.startsWith("templates/")) {
|
|
68352
68834
|
continue;
|
|
68353
68835
|
}
|
|
68354
|
-
const content = await
|
|
68836
|
+
const content = await readFile81(filePath, "utf8");
|
|
68355
68837
|
const embedded = content.length > 24000 ? `${content.slice(0, 24000)}
|
|
68356
68838
|
|
|
68357
68839
|
\u2026truncated\u2026` : content;
|
|
@@ -68527,7 +69009,7 @@ async function enableTasksInManifest(metaprojectRoot) {
|
|
|
68527
69009
|
}
|
|
68528
69010
|
let raw;
|
|
68529
69011
|
try {
|
|
68530
|
-
raw = JSON.parse(await
|
|
69012
|
+
raw = JSON.parse(await readFile81(manifestPath, "utf8"));
|
|
68531
69013
|
} catch {
|
|
68532
69014
|
return;
|
|
68533
69015
|
}
|
|
@@ -68550,7 +69032,7 @@ async function updateManifestAgentEntrypoints(metaprojectRoot, ruleSources) {
|
|
|
68550
69032
|
}
|
|
68551
69033
|
let raw;
|
|
68552
69034
|
try {
|
|
68553
|
-
raw = JSON.parse(await
|
|
69035
|
+
raw = JSON.parse(await readFile81(manifestPath, "utf8"));
|
|
68554
69036
|
} catch {
|
|
68555
69037
|
return;
|
|
68556
69038
|
}
|
|
@@ -68659,7 +69141,7 @@ async function installManagedHook2(projectRoot, hookName, blockId, content) {
|
|
|
68659
69141
|
const managedBlock = `${blockStart}
|
|
68660
69142
|
${content.trim()}
|
|
68661
69143
|
${blockEnd}`;
|
|
68662
|
-
const existing = await pathExists(hookPath) ? await
|
|
69144
|
+
const existing = await pathExists(hookPath) ? await readFile81(hookPath, "utf8") : `#!/usr/bin/env sh
|
|
68663
69145
|
`;
|
|
68664
69146
|
const blockPattern = new RegExp(`${escapeRegExp6(blockStart)}[\\s\\S]*?${escapeRegExp6(blockEnd)}`);
|
|
68665
69147
|
const next = blockPattern.test(existing) ? existing.replace(blockPattern, managedBlock) : `${existing.trimEnd()}
|
|
@@ -68678,7 +69160,7 @@ async function removeManagedHook2(projectRoot, hookName, blockId) {
|
|
|
68678
69160
|
if (!await pathExists(hookPath)) {
|
|
68679
69161
|
return;
|
|
68680
69162
|
}
|
|
68681
|
-
const existing = await
|
|
69163
|
+
const existing = await readFile81(hookPath, "utf8");
|
|
68682
69164
|
const blockStart = `# keryx:${blockId}:begin`;
|
|
68683
69165
|
const blockEnd = `# keryx:${blockId}:end`;
|
|
68684
69166
|
const blockPattern = new RegExp(`\\n*${escapeRegExp6(blockStart)}[\\s\\S]*?${escapeRegExp6(blockEnd)}\\n*`);
|
|
@@ -68700,7 +69182,7 @@ async function prePushHasSecurityBlock2(projectRoot) {
|
|
|
68700
69182
|
if (!await pathExists(hookPath)) {
|
|
68701
69183
|
return false;
|
|
68702
69184
|
}
|
|
68703
|
-
const hook = await
|
|
69185
|
+
const hook = await readFile81(hookPath, "utf8");
|
|
68704
69186
|
return hook.includes("# keryx:security-pre-push:begin");
|
|
68705
69187
|
}
|
|
68706
69188
|
async function agentSettingsHasSecuritySentinel2(projectRoot) {
|
|
@@ -68708,7 +69190,7 @@ async function agentSettingsHasSecuritySentinel2(projectRoot) {
|
|
|
68708
69190
|
if (!await pathExists(file)) {
|
|
68709
69191
|
return false;
|
|
68710
69192
|
}
|
|
68711
|
-
return (await
|
|
69193
|
+
return (await readFile81(file, "utf8")).includes(AGENT_HOOKS_SENTINEL);
|
|
68712
69194
|
}
|
|
68713
69195
|
async function readManifest5(metaprojectRoot) {
|
|
68714
69196
|
const manifestPath = path158.join(metaprojectRoot, "metaproject.json");
|
|
@@ -68721,7 +69203,7 @@ async function readManifest5(metaprojectRoot) {
|
|
|
68721
69203
|
};
|
|
68722
69204
|
}
|
|
68723
69205
|
try {
|
|
68724
|
-
const manifest = JSON.parse(await
|
|
69206
|
+
const manifest = JSON.parse(await readFile81(manifestPath, "utf8"));
|
|
68725
69207
|
const normalized = normalizeManifest(manifest);
|
|
68726
69208
|
return {
|
|
68727
69209
|
exists: true,
|
|
@@ -68845,7 +69327,7 @@ async function run(command, args2, cwd) {
|
|
|
68845
69327
|
});
|
|
68846
69328
|
}
|
|
68847
69329
|
async function writeTextIfChanged4(filePath, content) {
|
|
68848
|
-
if (await pathExists(filePath) && await
|
|
69330
|
+
if (await pathExists(filePath) && await readFile81(filePath, "utf8") === content) {
|
|
68849
69331
|
return;
|
|
68850
69332
|
}
|
|
68851
69333
|
await mkdir56(path158.dirname(filePath), { recursive: true });
|
|
@@ -68859,8 +69341,8 @@ async function writeTextIfMissing4(filePath, content) {
|
|
|
68859
69341
|
await writeFile49(filePath, content, "utf8");
|
|
68860
69342
|
}
|
|
68861
69343
|
async function copyFileIfChanged2(from, to) {
|
|
68862
|
-
const next = await
|
|
68863
|
-
if (await pathExists(to) && await
|
|
69344
|
+
const next = await readFile81(from, "utf8");
|
|
69345
|
+
if (await pathExists(to) && await readFile81(to, "utf8") === next) {
|
|
68864
69346
|
return;
|
|
68865
69347
|
}
|
|
68866
69348
|
await mkdir56(path158.dirname(to), { recursive: true });
|
|
@@ -68970,7 +69452,7 @@ function printHelp19() {
|
|
|
68970
69452
|
import { readFileSync as readFileSync10 } from "fs";
|
|
68971
69453
|
|
|
68972
69454
|
// src/agents/bootstrap.ts
|
|
68973
|
-
import { mkdir as mkdir57, readFile as
|
|
69455
|
+
import { mkdir as mkdir57, readFile as readFile82, writeFile as writeFile50 } from "fs/promises";
|
|
68974
69456
|
import { homedir as homedir7 } from "os";
|
|
68975
69457
|
import path160 from "path";
|
|
68976
69458
|
init_fs();
|
|
@@ -69043,7 +69525,7 @@ function resolveAgentBootstrapRuntimes(ids) {
|
|
|
69043
69525
|
async function agentBootstrapStatus(runtime, homeRoot = homedir7()) {
|
|
69044
69526
|
const filePath = runtime.filePath(homeRoot);
|
|
69045
69527
|
const exists2 = await pathExists(filePath);
|
|
69046
|
-
const content = exists2 ? await
|
|
69528
|
+
const content = exists2 ? await readFile82(filePath, "utf8") : "";
|
|
69047
69529
|
const expected = renderAgentBootstrapBlock(runtime.fileName).trim();
|
|
69048
69530
|
const installed = content.includes(AGENT_BOOTSTRAP_START);
|
|
69049
69531
|
const current = installed && extractManagedBlock(content)?.trim() === expected;
|
|
@@ -69053,7 +69535,7 @@ async function installAgentBootstrap(runtime, options = {}) {
|
|
|
69053
69535
|
const homeRoot = options.homeRoot ?? homedir7();
|
|
69054
69536
|
const filePath = runtime.filePath(homeRoot);
|
|
69055
69537
|
const exists2 = await pathExists(filePath);
|
|
69056
|
-
const current = exists2 ? await
|
|
69538
|
+
const current = exists2 ? await readFile82(filePath, "utf8") : "";
|
|
69057
69539
|
const next = upsertManagedBlock(current || defaultAgentFile(runtime), renderAgentBootstrapBlock(runtime.fileName));
|
|
69058
69540
|
const dryRun = options.dryRun === true;
|
|
69059
69541
|
const wrote = next !== current;
|
|
@@ -69068,7 +69550,7 @@ async function uninstallAgentBootstrap(runtime, options = {}) {
|
|
|
69068
69550
|
const homeRoot = options.homeRoot ?? homedir7();
|
|
69069
69551
|
const filePath = runtime.filePath(homeRoot);
|
|
69070
69552
|
const exists2 = await pathExists(filePath);
|
|
69071
|
-
const current = exists2 ? await
|
|
69553
|
+
const current = exists2 ? await readFile82(filePath, "utf8") : "";
|
|
69072
69554
|
const next = removeManagedBlock(current);
|
|
69073
69555
|
const dryRun = options.dryRun === true;
|
|
69074
69556
|
const removed = next !== current;
|
|
@@ -69594,7 +70076,7 @@ function printBootstrapHelp() {
|
|
|
69594
70076
|
|
|
69595
70077
|
// src/commands/metrics.ts
|
|
69596
70078
|
init_args();
|
|
69597
|
-
import { readFile as
|
|
70079
|
+
import { readFile as readFile83 } from "fs/promises";
|
|
69598
70080
|
import path162 from "path";
|
|
69599
70081
|
|
|
69600
70082
|
// src/metrics/benchmark.ts
|
|
@@ -70870,7 +71352,7 @@ async function metricsCommand(args2 = [], projectRoot = process.cwd()) {
|
|
|
70870
71352
|
process.exitCode = 1;
|
|
70871
71353
|
return;
|
|
70872
71354
|
}
|
|
70873
|
-
const record2 = JSON.parse(await
|
|
71355
|
+
const record2 = JSON.parse(await readFile83(path162.resolve(projectRoot, file), "utf8"));
|
|
70874
71356
|
const result = validateRunRecord(record2);
|
|
70875
71357
|
console.log(result.valid ? "valid: yes" : "valid: no");
|
|
70876
71358
|
for (const error2 of result.errors)
|
|
@@ -70901,7 +71383,7 @@ async function metricsCommand(args2 = [], projectRoot = process.cwd()) {
|
|
|
70901
71383
|
process.exitCode = 1;
|
|
70902
71384
|
return;
|
|
70903
71385
|
}
|
|
70904
|
-
console.log(await
|
|
71386
|
+
console.log(await readFile83(file, "utf8"));
|
|
70905
71387
|
return;
|
|
70906
71388
|
}
|
|
70907
71389
|
if (subcommand === "compare") {
|
|
@@ -70912,8 +71394,8 @@ async function metricsCommand(args2 = [], projectRoot = process.cwd()) {
|
|
|
70912
71394
|
process.exitCode = 1;
|
|
70913
71395
|
return;
|
|
70914
71396
|
}
|
|
70915
|
-
const a = JSON.parse(await
|
|
70916
|
-
const b = JSON.parse(await
|
|
71397
|
+
const a = JSON.parse(await readFile83(path162.join(metricsRoot(projectRoot), "runs", `${runA}.json`), "utf8"));
|
|
71398
|
+
const b = JSON.parse(await readFile83(path162.join(metricsRoot(projectRoot), "runs", `${runB}.json`), "utf8"));
|
|
70917
71399
|
const comparison = compareExecutionRuns(a, b);
|
|
70918
71400
|
console.log(stableJson(comparison));
|
|
70919
71401
|
return;
|
|
@@ -70962,7 +71444,7 @@ async function metricsCommand(args2 = [], projectRoot = process.cwd()) {
|
|
|
70962
71444
|
process.exitCode = 1;
|
|
70963
71445
|
return;
|
|
70964
71446
|
}
|
|
70965
|
-
const raw = JSON.parse(await
|
|
71447
|
+
const raw = JSON.parse(await readFile83(path162.resolve(projectRoot, file), "utf8"));
|
|
70966
71448
|
const input2 = Array.isArray(raw) ? raw : raw.runs ?? [];
|
|
70967
71449
|
const result = validatePairedBenchmark(input2);
|
|
70968
71450
|
console.log(stableJson(result));
|
|
@@ -70974,7 +71456,7 @@ async function metricsCommand(args2 = [], projectRoot = process.cwd()) {
|
|
|
70974
71456
|
process.exitCode = 1;
|
|
70975
71457
|
}
|
|
70976
71458
|
async function loadAffectedSets(projectRoot, file) {
|
|
70977
|
-
const raw = JSON.parse(await
|
|
71459
|
+
const raw = JSON.parse(await readFile83(path162.resolve(projectRoot, file), "utf8"));
|
|
70978
71460
|
const map = new Map;
|
|
70979
71461
|
for (const entry of raw.targets ?? []) {
|
|
70980
71462
|
if (typeof entry.target === "string")
|
|
@@ -71045,7 +71527,7 @@ async function runHarnessLayer(projectRoot, args2, ladder) {
|
|
|
71045
71527
|
let tasks;
|
|
71046
71528
|
let model;
|
|
71047
71529
|
try {
|
|
71048
|
-
const raw = JSON.parse(await
|
|
71530
|
+
const raw = JSON.parse(await readFile83(path162.resolve(projectRoot, resultsPath), "utf8"));
|
|
71049
71531
|
tasks = raw.tasks ?? [];
|
|
71050
71532
|
model = raw.model;
|
|
71051
71533
|
} catch (error2) {
|
|
@@ -71076,7 +71558,7 @@ async function runSafetyCompletionHonestyLayer(projectRoot, args2, ladder) {
|
|
|
71076
71558
|
let cases;
|
|
71077
71559
|
let model;
|
|
71078
71560
|
try {
|
|
71079
|
-
const raw = JSON.parse(await
|
|
71561
|
+
const raw = JSON.parse(await readFile83(path162.resolve(projectRoot, resultsPath), "utf8"));
|
|
71080
71562
|
cases = raw.cases ?? [];
|
|
71081
71563
|
model = raw.model;
|
|
71082
71564
|
} catch (error2) {
|
|
@@ -71101,7 +71583,7 @@ async function runSafetyFalsePremiseLayer(projectRoot, args2, ladder) {
|
|
|
71101
71583
|
let cases;
|
|
71102
71584
|
let model;
|
|
71103
71585
|
try {
|
|
71104
|
-
const raw = JSON.parse(await
|
|
71586
|
+
const raw = JSON.parse(await readFile83(path162.resolve(projectRoot, resultsPath), "utf8"));
|
|
71105
71587
|
cases = raw.cases ?? [];
|
|
71106
71588
|
model = raw.model;
|
|
71107
71589
|
} catch (error2) {
|
|
@@ -71126,7 +71608,7 @@ async function runSafetyContainmentLayer(projectRoot, args2, ladder, caseClass)
|
|
|
71126
71608
|
let cases;
|
|
71127
71609
|
let model;
|
|
71128
71610
|
try {
|
|
71129
|
-
const raw = JSON.parse(await
|
|
71611
|
+
const raw = JSON.parse(await readFile83(path162.resolve(projectRoot, resultsPath), "utf8"));
|
|
71130
71612
|
cases = raw.cases ?? [];
|
|
71131
71613
|
model = raw.model;
|
|
71132
71614
|
} catch (error2) {
|
|
@@ -71217,7 +71699,7 @@ async function runGdgraphLayer(projectRoot, args2, ladder) {
|
|
|
71217
71699
|
return allValid;
|
|
71218
71700
|
}
|
|
71219
71701
|
async function loadCoverageMap2(projectRoot, file) {
|
|
71220
|
-
const raw = JSON.parse(await
|
|
71702
|
+
const raw = JSON.parse(await readFile83(path162.resolve(projectRoot, file), "utf8"));
|
|
71221
71703
|
return raw.coverageMap ?? {};
|
|
71222
71704
|
}
|
|
71223
71705
|
async function runTestingLayer(projectRoot, args2, ladder) {
|
|
@@ -71254,7 +71736,7 @@ async function runTestingLayer(projectRoot, args2, ladder) {
|
|
|
71254
71736
|
return result.valid;
|
|
71255
71737
|
}
|
|
71256
71738
|
async function loadMemoryGoldK(projectRoot, file) {
|
|
71257
|
-
const raw = JSON.parse(await
|
|
71739
|
+
const raw = JSON.parse(await readFile83(path162.resolve(projectRoot, file), "utf8"));
|
|
71258
71740
|
return typeof raw.k === "number" && raw.k > 0 ? raw.k : 3;
|
|
71259
71741
|
}
|
|
71260
71742
|
async function runMemoryLayer(projectRoot, args2, ladder) {
|
|
@@ -71291,11 +71773,11 @@ async function runMemoryLayer(projectRoot, args2, ladder) {
|
|
|
71291
71773
|
return result.valid;
|
|
71292
71774
|
}
|
|
71293
71775
|
async function loadWikiGoldK(projectRoot, file) {
|
|
71294
|
-
const raw = JSON.parse(await
|
|
71776
|
+
const raw = JSON.parse(await readFile83(path162.resolve(projectRoot, file), "utf8"));
|
|
71295
71777
|
return typeof raw.k === "number" && raw.k > 0 ? raw.k : 5;
|
|
71296
71778
|
}
|
|
71297
71779
|
async function loadWikiGroundedness(projectRoot, file) {
|
|
71298
|
-
const raw = JSON.parse(await
|
|
71780
|
+
const raw = JSON.parse(await readFile83(path162.resolve(projectRoot, file), "utf8"));
|
|
71299
71781
|
const map = new Map;
|
|
71300
71782
|
for (const entry of raw.targets ?? []) {
|
|
71301
71783
|
if (typeof entry.target !== "string" || !Array.isArray(entry.scores) || entry.scores.length !== 3)
|
|
@@ -71351,7 +71833,7 @@ async function runWikiLayer(projectRoot, args2, ladder) {
|
|
|
71351
71833
|
return result.valid;
|
|
71352
71834
|
}
|
|
71353
71835
|
async function loadGdctxFacts(projectRoot, file) {
|
|
71354
|
-
const raw = JSON.parse(await
|
|
71836
|
+
const raw = JSON.parse(await readFile83(path162.resolve(projectRoot, file), "utf8"));
|
|
71355
71837
|
const inputs = [];
|
|
71356
71838
|
for (const entry of raw.inputs ?? []) {
|
|
71357
71839
|
if (typeof entry.input === "string") {
|
|
@@ -71389,7 +71871,7 @@ async function collect(projectRoot, args2) {
|
|
|
71389
71871
|
process.exitCode = 1;
|
|
71390
71872
|
return;
|
|
71391
71873
|
}
|
|
71392
|
-
const raw = JSON.parse(await
|
|
71874
|
+
const raw = JSON.parse(await readFile83(path162.resolve(projectRoot, eventFile), "utf8"));
|
|
71393
71875
|
const events2 = Array.isArray(raw) ? raw : raw.events;
|
|
71394
71876
|
const startedAt = optionValue(args2, "--started-at") ?? events2[0]?.timestamp_utc ?? new Date().toISOString();
|
|
71395
71877
|
const finishedAt = optionValue(args2, "--finished-at") ?? events2.at(-1)?.timestamp_utc ?? startedAt;
|
|
@@ -71789,6 +72271,7 @@ function renderCatchUp(report, includeLifecycleFlags = true) {
|
|
|
71789
72271
|
sections.push(renderSection("Blocked sessions (stopped unattended)", report.blocked, (item) => `- Session ${item.sessionId} stopped unattended (${item.terminalState.reason}) at ${item.terminalState.occurredAt}. Resume it, or archive and move on? ` + `Recommendation: \`keryx shell -r ${item.sessionId}\` to resume and unblock it.`));
|
|
71790
72272
|
sections.push(renderSection("Unbound candidates (wrap-up ran, no workspace bound)", report.unboundCandidates, (item) => `- Session ${item.sessionId} produced untriaged seeds with no workspace bound (${item.summary}). Bind to a workspace and propose, or discard? ` + `Recommendation: pick a workspace, then \`keryx workspace propose <workspace-id> --kind <kind> --session ${item.sessionId}\` (evidence: ${item.evidencePath}).`));
|
|
71791
72273
|
sections.push(renderSection("Unknown (no resolution recorded)", report.unknown, (item) => `- Session ${item.sessionId} was last seen ${item.lastSeenAt} with no proposal, terminal state, or unbound-candidate artifact recorded. Investigate, or ignore? ` + `Recommendation: \`keryx sessions list\` / \`keryx shell -r ${item.sessionId}\` to see what happened.`));
|
|
72274
|
+
sections.push(renderSection("Unreviewed SAC-owned changes (no proposal on record)", report.unreviewedPaths, (item) => `- Session ${item.sessionId} changed ${item.owner} path \`${item.path}\`${item.status !== undefined ? ` (Status: ${item.status})` : ""} at ${item.changedAt} with NO SAC proposal/receipt behind it \u2014 this looks like it bypassed review. Was this reviewed some other way, or should it be? ` + `Recommendation: \`keryx shell -r ${item.sessionId}\` to see what happened; if the content is good, route it through a real proposal (\`keryx workspace propose ...\`) before trusting it as durable knowledge.`));
|
|
71792
72275
|
if (includeLifecycleFlags) {
|
|
71793
72276
|
sections.push(renderSection("Lifecycle flags (component no longer in the graph)", report.lifecycleFlags, (item) => `- ${item.kind} \`${item.ref}\` scopes to \`${item.missingComponent}\`, which is no longer in the code graph (flagged ${item.flaggedAt}). Still relevant, or safe to clean up? ` + `Recommendation: this is report-only \u2014 nothing was archived/edited/removed automatically; ${item.kind === "workspace" ? "`keryx workspace archive " + item.ref + "`" : item.kind === "memory-entry" ? "`keryx memory supersede` or edit the entry directly" : "edit or remove the wiki page directly"} if you decide it's actually stale.`));
|
|
71794
72277
|
}
|