@mrciphersmith/keryx 0.2.55 → 0.2.56
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 +441 -76
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -26111,6 +26111,10 @@ ${markdown.slice(4)}`;
|
|
|
26111
26111
|
}
|
|
26112
26112
|
return markdown;
|
|
26113
26113
|
}
|
|
26114
|
+
function extractFrontmatterStatus(markdown) {
|
|
26115
|
+
const bodyMatch = /\nStatus:\s*(\S+)/i.exec(markdown) ?? /^Status:\s*(\S+)/im.exec(markdown);
|
|
26116
|
+
return bodyMatch?.[1] ?? "draft";
|
|
26117
|
+
}
|
|
26114
26118
|
async function mapPool(items, concurrency, worker) {
|
|
26115
26119
|
const results = new Array(items.length);
|
|
26116
26120
|
let next = 0;
|
|
@@ -26137,7 +26141,6 @@ async function wikiEnrich(input2) {
|
|
|
26137
26141
|
const concurrency = Math.max(1, Math.min(MAX_CONCURRENCY, input2.concurrency ?? DEFAULT_CONCURRENCY));
|
|
26138
26142
|
const maxOutputTokens = input2.maxOutputTokens ?? DEFAULT_MAX_OUTPUT_TOKENS;
|
|
26139
26143
|
const validate = input2.validate !== false;
|
|
26140
|
-
const markAccepted = input2.keepStatus === true ? false : input2.markAccepted !== false;
|
|
26141
26144
|
const wikiConfig = await loadWikiConfig(input2.cwd);
|
|
26142
26145
|
const result = {
|
|
26143
26146
|
provider,
|
|
@@ -26231,9 +26234,7 @@ async function wikiEnrich(input2) {
|
|
|
26231
26234
|
return { path: page.relativePath, action: "failed", reason: `validation: ${structural}` };
|
|
26232
26235
|
}
|
|
26233
26236
|
}
|
|
26234
|
-
|
|
26235
|
-
enriched = setFrontmatterStatus(enriched, "accepted");
|
|
26236
|
-
}
|
|
26237
|
+
enriched = setFrontmatterStatus(enriched, extractFrontmatterStatus(original));
|
|
26237
26238
|
if (input2.dryRun) {
|
|
26238
26239
|
onPage({ index, total, path: page.relativePath, status, phase: "done" });
|
|
26239
26240
|
return {
|
|
@@ -26247,7 +26248,7 @@ async function wikiEnrich(input2) {
|
|
|
26247
26248
|
await writeFile36(page.absolutePath, `${enriched.endsWith(`
|
|
26248
26249
|
`) ? enriched : `${enriched}
|
|
26249
26250
|
`}`, "utf8");
|
|
26250
|
-
onPage({ index, total, path: page.relativePath, status
|
|
26251
|
+
onPage({ index, total, path: page.relativePath, status, phase: "done" });
|
|
26251
26252
|
return {
|
|
26252
26253
|
path: page.relativePath,
|
|
26253
26254
|
action: "enriched",
|
|
@@ -26273,7 +26274,6 @@ async function wikiEnrich(input2) {
|
|
|
26273
26274
|
model,
|
|
26274
26275
|
maxOutputTokens,
|
|
26275
26276
|
validate,
|
|
26276
|
-
markAccepted,
|
|
26277
26277
|
concurrency,
|
|
26278
26278
|
total,
|
|
26279
26279
|
onPage,
|
|
@@ -26356,8 +26356,8 @@ function finalizeEnrichedText(original, rawText, options) {
|
|
|
26356
26356
|
if (options.validate) {
|
|
26357
26357
|
structuralError = validateEnrichedMarkdown(original, content);
|
|
26358
26358
|
}
|
|
26359
|
-
if (structuralError === null
|
|
26360
|
-
content = setFrontmatterStatus(content,
|
|
26359
|
+
if (structuralError === null) {
|
|
26360
|
+
content = setFrontmatterStatus(content, extractFrontmatterStatus(original));
|
|
26361
26361
|
}
|
|
26362
26362
|
return { content, structuralError };
|
|
26363
26363
|
}
|
|
@@ -26384,7 +26384,7 @@ async function finishSuccess(ctx, page, originalRaw, content, keyFiles, extra) {
|
|
|
26384
26384
|
index,
|
|
26385
26385
|
total: ctx.total,
|
|
26386
26386
|
path: page.relativePath,
|
|
26387
|
-
status
|
|
26387
|
+
status,
|
|
26388
26388
|
phase: "done"
|
|
26389
26389
|
});
|
|
26390
26390
|
return {
|
|
@@ -26428,8 +26428,7 @@ async function runDeepSingle(ctx, item) {
|
|
|
26428
26428
|
let content = null;
|
|
26429
26429
|
if (rawText !== null) {
|
|
26430
26430
|
const finalized = finalizeEnrichedText(original, rawText, {
|
|
26431
|
-
validate: ctx.validate
|
|
26432
|
-
markAccepted: ctx.markAccepted
|
|
26431
|
+
validate: ctx.validate
|
|
26433
26432
|
});
|
|
26434
26433
|
if (finalized.structuralError === null) {
|
|
26435
26434
|
content = finalized.content;
|
|
@@ -26614,8 +26613,7 @@ async function runLightBatch(ctx, items) {
|
|
|
26614
26613
|
continue;
|
|
26615
26614
|
}
|
|
26616
26615
|
const finalized = finalizeEnrichedText(item.original, rawText, {
|
|
26617
|
-
validate: ctx.validate
|
|
26618
|
-
markAccepted: ctx.markAccepted
|
|
26616
|
+
validate: ctx.validate
|
|
26619
26617
|
});
|
|
26620
26618
|
if (finalized.structuralError !== null) {
|
|
26621
26619
|
ctx.onPage({ index, total: ctx.total, path: item.page.relativePath, status, phase: "failed" });
|
|
@@ -26666,7 +26664,6 @@ async function runRlmPipeline(ctxInput) {
|
|
|
26666
26664
|
model: ctxInput.model,
|
|
26667
26665
|
maxOutputTokens: ctxInput.maxOutputTokens,
|
|
26668
26666
|
validate: ctxInput.validate,
|
|
26669
|
-
markAccepted: ctxInput.markAccepted,
|
|
26670
26667
|
total: ctxInput.total,
|
|
26671
26668
|
onPage: ctxInput.onPage,
|
|
26672
26669
|
input: input2,
|
|
@@ -36675,7 +36672,6 @@ async function runEnrich(args2) {
|
|
|
36675
36672
|
const resume = args2.includes("--resume");
|
|
36676
36673
|
const refreshGraph = args2.includes("--refresh-graph");
|
|
36677
36674
|
const dryRun = args2.includes("--dry-run");
|
|
36678
|
-
const keepStatus = args2.includes("--keep-status");
|
|
36679
36675
|
const noValidate = args2.includes("--no-validate");
|
|
36680
36676
|
const valueFlags = new Set([
|
|
36681
36677
|
"--page",
|
|
@@ -36744,9 +36740,7 @@ async function runEnrich(args2) {
|
|
|
36744
36740
|
resume,
|
|
36745
36741
|
refreshGraph,
|
|
36746
36742
|
dryRun,
|
|
36747
|
-
keepStatus,
|
|
36748
36743
|
validate: !noValidate,
|
|
36749
|
-
markAccepted: !keepStatus,
|
|
36750
36744
|
...prompt ? { prompt } : {},
|
|
36751
36745
|
...provider ? { provider } : {},
|
|
36752
36746
|
...model ? { model } : {},
|
|
@@ -36823,10 +36817,12 @@ Usage:
|
|
|
36823
36817
|
keryx wiki validate
|
|
36824
36818
|
keryx wiki ask "<question>" [--k <n>] [--rerank]
|
|
36825
36819
|
keryx wiki enrich [<page>|--all] [--force] [--list] [--resume] [--limit N] [--concurrency N]
|
|
36826
|
-
[--refresh-graph] [--max-tokens N] [--
|
|
36820
|
+
[--refresh-graph] [--max-tokens N] [--no-validate]
|
|
36827
36821
|
[--prompt "<i>"] [--provider <p>] [--model <m>] [--dry-run] [--json]
|
|
36828
36822
|
# defaults: drafts only; provider/model from auth.json; validate on;
|
|
36829
|
-
#
|
|
36823
|
+
# concurrency 1 (raise for parallel page swarm)
|
|
36824
|
+
# rewrites prose only \u2014 Status is always left exactly as it was
|
|
36825
|
+
# before the run; enrich can never itself accept a page (issue #391)
|
|
36830
36826
|
keryx wiki context
|
|
36831
36827
|
keryx wiki backlinks <wiki-page-or-code-file>
|
|
36832
36828
|
|
|
@@ -49941,7 +49937,8 @@ var PREFIX_BANNED = new Set([
|
|
|
49941
49937
|
"osascript",
|
|
49942
49938
|
"open",
|
|
49943
49939
|
"tee",
|
|
49944
|
-
"cd"
|
|
49940
|
+
"cd",
|
|
49941
|
+
"keryx"
|
|
49945
49942
|
]);
|
|
49946
49943
|
var PREFIX_BANNED_READERS = new Set([
|
|
49947
49944
|
"cat",
|
|
@@ -50012,6 +50009,17 @@ function validateShellPattern(pattern) {
|
|
|
50012
50009
|
}
|
|
50013
50010
|
return { ok: true };
|
|
50014
50011
|
}
|
|
50012
|
+
function getRunningBinaryName() {
|
|
50013
|
+
try {
|
|
50014
|
+
const argv0 = process.argv0;
|
|
50015
|
+
if (!argv0)
|
|
50016
|
+
return "";
|
|
50017
|
+
const name = argv0.split(/[\\/]/).pop() ?? "";
|
|
50018
|
+
return name.toLowerCase();
|
|
50019
|
+
} catch {
|
|
50020
|
+
return "";
|
|
50021
|
+
}
|
|
50022
|
+
}
|
|
50015
50023
|
function bannedPrefixGrant(pattern, firstToken) {
|
|
50016
50024
|
const rest = pattern.slice(firstToken.length).trim();
|
|
50017
50025
|
const wildcardOnly = /^\*+$/.test(rest) || rest.length === 0 && /\*+$/.test(firstToken);
|
|
@@ -50024,6 +50032,13 @@ function bannedPrefixGrant(pattern, firstToken) {
|
|
|
50024
50032
|
reason: `\`${word} *\` grants arbitrary execution: ${word} is an interpreter or wrapper, so its first token does not constrain what runs`
|
|
50025
50033
|
};
|
|
50026
50034
|
}
|
|
50035
|
+
const runningBinary = getRunningBinaryName();
|
|
50036
|
+
if (runningBinary && word === runningBinary) {
|
|
50037
|
+
return {
|
|
50038
|
+
word,
|
|
50039
|
+
reason: `\`${word} *\` grants arbitrary execution: ${word} can run arbitrary subcommands, so remembering this grant would silently approve mutating/destructive operations forever`
|
|
50040
|
+
};
|
|
50041
|
+
}
|
|
50027
50042
|
if (PREFIX_BANNED_READERS.has(word)) {
|
|
50028
50043
|
return {
|
|
50029
50044
|
word,
|
|
@@ -53108,8 +53123,10 @@ init_slate();
|
|
|
53108
53123
|
init_workspace_service();
|
|
53109
53124
|
init_workspace_resolve();
|
|
53110
53125
|
init_service7();
|
|
53126
|
+
init_store2();
|
|
53111
53127
|
init_fs();
|
|
53112
53128
|
import path149 from "path";
|
|
53129
|
+
import { readFile as readFile79 } from "fs/promises";
|
|
53113
53130
|
var POSITIVE_INTEGER = /^[1-9][0-9]*$/;
|
|
53114
53131
|
var DEFAULT_AUTO_GOAL_ROUNDS = 8;
|
|
53115
53132
|
function parseGoalArgs(rest) {
|
|
@@ -53211,9 +53228,20 @@ async function autoProvisionFlow(cwd, goalText) {
|
|
|
53211
53228
|
await service5.start({ cwd, id: result.flow.id });
|
|
53212
53229
|
return result.flow.id;
|
|
53213
53230
|
}
|
|
53231
|
+
var ROUND_DONE_MARKER = "GOAL_ROUND_COMPLETE";
|
|
53232
|
+
function continuationRoundClaimsDone(history) {
|
|
53233
|
+
for (let i = history.length - 1;i >= 0; i--) {
|
|
53234
|
+
const message2 = history[i];
|
|
53235
|
+
if (message2?.role === "assistant") {
|
|
53236
|
+
return message2.content.includes(ROUND_DONE_MARKER);
|
|
53237
|
+
}
|
|
53238
|
+
}
|
|
53239
|
+
return false;
|
|
53240
|
+
}
|
|
53214
53241
|
async function buildContinuationMessage(cwd, slateSession, round4, roundsCap) {
|
|
53215
53242
|
const totalRounds = roundsCap + 1;
|
|
53216
|
-
const
|
|
53243
|
+
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.`;
|
|
53244
|
+
const generic = `Continue working toward the stated goal (round ${round4} of ${totalRounds}). ${doneInstruction}`;
|
|
53217
53245
|
const slate = await readSlate(slateSession.dir).catch(() => {
|
|
53218
53246
|
return;
|
|
53219
53247
|
});
|
|
@@ -53251,11 +53279,61 @@ function parseVerifierVerdict(output2) {
|
|
|
53251
53279
|
return;
|
|
53252
53280
|
}
|
|
53253
53281
|
}
|
|
53254
|
-
|
|
53282
|
+
var MAX_EVIDENCE_SEEDS = 10;
|
|
53283
|
+
function summarizeRecentSeeds(seeds) {
|
|
53284
|
+
return seeds.slice(-MAX_EVIDENCE_SEEDS).map((seed) => `- Seed${seed.kind !== undefined ? ` [${seed.kind}]` : ""}: ${seed.text}`);
|
|
53285
|
+
}
|
|
53286
|
+
function summarizeWorkspaceProposals(history) {
|
|
53287
|
+
const lines = [];
|
|
53288
|
+
for (const message2 of history) {
|
|
53289
|
+
if (message2.role !== "assistant" || message2.toolCalls === undefined) {
|
|
53290
|
+
continue;
|
|
53291
|
+
}
|
|
53292
|
+
for (const call of message2.toolCalls) {
|
|
53293
|
+
if (call.name !== "workspace_propose") {
|
|
53294
|
+
continue;
|
|
53295
|
+
}
|
|
53296
|
+
const resultMessage = history.find((candidate) => candidate.role === "tool" && candidate.toolCallId === call.id);
|
|
53297
|
+
let argSummary = call.arguments;
|
|
53298
|
+
try {
|
|
53299
|
+
const parsedArgs = JSON.parse(call.arguments);
|
|
53300
|
+
const kind2 = typeof parsedArgs.kind === "string" ? parsedArgs.kind : "?";
|
|
53301
|
+
const note2 = typeof parsedArgs.note === "string" ? parsedArgs.note : undefined;
|
|
53302
|
+
argSummary = `kind=${kind2}${note2 !== undefined ? `, note=${note2}` : ""}`;
|
|
53303
|
+
} catch {}
|
|
53304
|
+
const outcome = resultMessage !== undefined ? resultMessage.content : "(no result recorded this run)";
|
|
53305
|
+
lines.push(`- workspace_propose: ${argSummary} -> ${outcome}`);
|
|
53306
|
+
}
|
|
53307
|
+
}
|
|
53308
|
+
return lines;
|
|
53309
|
+
}
|
|
53310
|
+
async function flowDefersCompletionToVerifier(cwd, flowId) {
|
|
53311
|
+
try {
|
|
53312
|
+
const dir = await resolveFlowDir(cwd, flowId);
|
|
53313
|
+
const text = await readFile79(acPath(cwd, dir), "utf8");
|
|
53314
|
+
const normalized = text.replace(/\s+/g, " ");
|
|
53315
|
+
return normalized.includes("judged by the verifier subagent");
|
|
53316
|
+
} catch {
|
|
53317
|
+
return false;
|
|
53318
|
+
}
|
|
53319
|
+
}
|
|
53320
|
+
async function buildVerifierEvidence(cwd, slateSession, history) {
|
|
53321
|
+
const slate = await readSlate(slateSession.dir).catch(() => {
|
|
53322
|
+
return;
|
|
53323
|
+
});
|
|
53324
|
+
const lines = [...slate !== undefined ? summarizeRecentSeeds(slate.seeds) : [], ...summarizeWorkspaceProposals(history)];
|
|
53325
|
+
const evidenceText = lines.length > 0 ? lines.join(`
|
|
53326
|
+
`) : "(no Seeds or workspace_propose records were recorded this run)";
|
|
53327
|
+
const flowId = slate?.course.flowRef;
|
|
53328
|
+
const deferToVerifier = flowId !== undefined ? await flowDefersCompletionToVerifier(cwd, flowId) : false;
|
|
53329
|
+
return { evidenceText, deferToVerifier };
|
|
53330
|
+
}
|
|
53331
|
+
async function runGoalVerifier(deps, goalText, cwd, slateSession, history, io, mintCallId) {
|
|
53255
53332
|
const tool = deps.tools.find((candidate) => candidate.definition.name === "spawn_subagent");
|
|
53256
53333
|
if (tool === undefined) {
|
|
53257
53334
|
return;
|
|
53258
53335
|
}
|
|
53336
|
+
const { evidenceText, deferToVerifier } = await buildVerifierEvidence(cwd, slateSession, history);
|
|
53259
53337
|
const task = [
|
|
53260
53338
|
"Independently verify whether the following goal has ACTUALLY been achieved, based on the",
|
|
53261
53339
|
"current, real state of the repository (read the real files/tests \u2014 never trust a prior",
|
|
@@ -53263,17 +53341,49 @@ async function runGoalVerifier(deps, goalText) {
|
|
|
53263
53341
|
"",
|
|
53264
53342
|
`Goal: "${goalText}"`,
|
|
53265
53343
|
"",
|
|
53344
|
+
"Evidence this run already produced (recent Slate Seeds and workspace_propose records) \u2014",
|
|
53345
|
+
"weigh this as real evidence of what was actually done, not merely a claim:",
|
|
53346
|
+
evidenceText,
|
|
53347
|
+
"",
|
|
53348
|
+
...deferToVerifier ? [
|
|
53349
|
+
"This run's Task Manager flow acceptance criteria explicitly defer the completion",
|
|
53350
|
+
"judgment to THIS verifier check, not to the flow's own task checkboxes \u2014 its tasks",
|
|
53351
|
+
"are expected to remain unchecked even when the goal is genuinely achieved. Do NOT",
|
|
53352
|
+
"treat an incomplete/unchecked flow task list, by itself, as evidence the goal is NOT",
|
|
53353
|
+
"achieved; judge achievement from the real repository state and the evidence above.",
|
|
53354
|
+
""
|
|
53355
|
+
] : [],
|
|
53266
53356
|
"Reply with EXACTLY one JSON object and nothing else, no prose before or after it:",
|
|
53267
53357
|
'{"achieved": true or false, "gaps": ["specific reason it is not fully achieved", ...]}',
|
|
53268
53358
|
'"gaps" must be empty when "achieved" is true.'
|
|
53269
53359
|
].join(`
|
|
53270
53360
|
`);
|
|
53361
|
+
const input2 = { task, mode: "read_only", label: "goal-verifier" };
|
|
53362
|
+
const callId = mintCallId();
|
|
53363
|
+
io.onToolCall?.("spawn_subagent", JSON.stringify(input2));
|
|
53364
|
+
history.push({
|
|
53365
|
+
role: "assistant",
|
|
53366
|
+
content: "",
|
|
53367
|
+
provenance: "model",
|
|
53368
|
+
toolCalls: [{ id: callId, name: "spawn_subagent", arguments: JSON.stringify(input2) }]
|
|
53369
|
+
});
|
|
53370
|
+
io.onHistoryChange?.("tool");
|
|
53271
53371
|
let result;
|
|
53272
53372
|
try {
|
|
53273
|
-
result = await tool.invoke(
|
|
53274
|
-
} catch {
|
|
53373
|
+
result = await tool.invoke(input2);
|
|
53374
|
+
} catch (err) {
|
|
53375
|
+
const errorResult = {
|
|
53376
|
+
output: `spawn_subagent dispatch failed: ${err instanceof Error ? err.message : String(err)}`,
|
|
53377
|
+
isError: true
|
|
53378
|
+
};
|
|
53379
|
+
io.onToolResult?.("spawn_subagent", errorResult);
|
|
53380
|
+
history.push({ role: "tool", content: errorResult.output, provenance: "tool", toolCallId: callId });
|
|
53381
|
+
io.onHistoryChange?.("tool");
|
|
53275
53382
|
return;
|
|
53276
53383
|
}
|
|
53384
|
+
io.onToolResult?.("spawn_subagent", result);
|
|
53385
|
+
history.push({ role: "tool", content: result.output, provenance: "tool", toolCallId: callId });
|
|
53386
|
+
io.onHistoryChange?.("tool");
|
|
53277
53387
|
if (result.isError) {
|
|
53278
53388
|
return;
|
|
53279
53389
|
}
|
|
@@ -53363,10 +53473,21 @@ async function runGoalCommand(params) {
|
|
|
53363
53473
|
systemLine(io, `/goal --auto: round ${round4}/${roundsCap + 1} \u2014 continuing toward the goal.
|
|
53364
53474
|
`);
|
|
53365
53475
|
await runAgentTurn(io, deps, history, continuationText, turnOptions);
|
|
53476
|
+
if (continuationRoundClaimsDone(history)) {
|
|
53477
|
+
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.
|
|
53478
|
+
`);
|
|
53479
|
+
break;
|
|
53480
|
+
}
|
|
53366
53481
|
}
|
|
53367
53482
|
const wasOpenBeforeVerifier = slateSession.opened;
|
|
53368
|
-
const verdict = await runGoalVerifier(deps, parsed.text);
|
|
53369
|
-
if (verdict
|
|
53483
|
+
const verdict = await runGoalVerifier(deps, parsed.text, cwd, slateSession, history, io, mintAttemptId);
|
|
53484
|
+
if (verdict === undefined) {
|
|
53485
|
+
systemLine(io, `/goal --auto: verifier unavailable \u2014 outcome not independently checked.
|
|
53486
|
+
`);
|
|
53487
|
+
} else if (verdict.achieved) {
|
|
53488
|
+
systemLine(io, `/goal --auto: verifier confirmed the goal is achieved.
|
|
53489
|
+
`);
|
|
53490
|
+
} else {
|
|
53370
53491
|
systemLine(io, `/goal --auto: verifier found the goal not fully achieved${verdict.gaps.length > 0 ? ` \u2014 ${verdict.gaps.join("; ")}` : " (no specific gaps reported)"}
|
|
53371
53492
|
`);
|
|
53372
53493
|
if (roundsLeft > 0) {
|
|
@@ -53414,7 +53535,7 @@ import { spawnSync as spawnSync2 } from "child_process";
|
|
|
53414
53535
|
// package.json
|
|
53415
53536
|
var package_default = {
|
|
53416
53537
|
name: "@mrciphersmith/keryx",
|
|
53417
|
-
version: "0.2.
|
|
53538
|
+
version: "0.2.56",
|
|
53418
53539
|
description: "Version-controlled project context for AI coding agents: code graph, architecture wiki, project memory, relevant tests, quality signals, and task flows.",
|
|
53419
53540
|
private: false,
|
|
53420
53541
|
publishConfig: {
|
|
@@ -54674,7 +54795,7 @@ init_store3();
|
|
|
54674
54795
|
init_proposal_lifecycle();
|
|
54675
54796
|
init_workspace_service();
|
|
54676
54797
|
import { randomUUID as randomUUID24 } from "crypto";
|
|
54677
|
-
import { readdir as readdir26 } from "fs/promises";
|
|
54798
|
+
import { readdir as readdir26, stat as stat8 } from "fs/promises";
|
|
54678
54799
|
import path150 from "path";
|
|
54679
54800
|
|
|
54680
54801
|
// src/sac/lifecycle-flag.ts
|
|
@@ -54725,14 +54846,18 @@ async function computeLifecycleFlags(cwd, now = () => new Date) {
|
|
|
54725
54846
|
|
|
54726
54847
|
// src/sac/catch-up.ts
|
|
54727
54848
|
init_proposal_evidence();
|
|
54849
|
+
init_collect();
|
|
54850
|
+
init_store();
|
|
54728
54851
|
async function buildCatchUp(input2) {
|
|
54729
|
-
const [proposals, sessionCategories, lifecycleFlagsAll] = await Promise.all([
|
|
54852
|
+
const [proposals, sessionCategories, lifecycleFlagsAll, unreviewedPathsAll] = await Promise.all([
|
|
54730
54853
|
collectProposals(input2.cwd, input2.workspaceId),
|
|
54731
54854
|
collectSessionCategories(input2.cwd),
|
|
54732
|
-
computeLifecycleFlags(input2.cwd)
|
|
54855
|
+
computeLifecycleFlags(input2.cwd),
|
|
54856
|
+
detectUnreviewedSacPathChanges(input2.cwd, listSessions(input2.cwd))
|
|
54733
54857
|
]);
|
|
54734
54858
|
const lifecycleFlags = input2.workspaceId === undefined ? lifecycleFlagsAll : lifecycleFlagsAll.filter((flag) => flag.kind !== "workspace" || flag.ref === input2.workspaceId);
|
|
54735
|
-
|
|
54859
|
+
const unreviewedPaths = input2.workspaceId === undefined ? unreviewedPathsAll : unreviewedPathsAll.filter((item) => item.workspaceId === input2.workspaceId);
|
|
54860
|
+
return { proposals, ...sessionCategories, lifecycleFlags, unreviewedPaths };
|
|
54736
54861
|
}
|
|
54737
54862
|
async function collectProposals(cwd, workspaceId) {
|
|
54738
54863
|
const authorizationServer = localWorkspaceAuthorizationServer();
|
|
@@ -54795,8 +54920,82 @@ async function classifySession(session) {
|
|
|
54795
54920
|
const workspaceId = (await safeReadSlate(dir))?.workspaceId;
|
|
54796
54921
|
return { kind: "unknown", item: { type: "unknown", sessionId: session.id, ...workspaceId !== undefined ? { workspaceId } : {}, lastSeenAt: session.updatedAt } };
|
|
54797
54922
|
}
|
|
54923
|
+
async function readExternalUnboundCandidates(cwd) {
|
|
54924
|
+
const extDir = externalSlatesDir(cwd);
|
|
54925
|
+
let externalIds;
|
|
54926
|
+
try {
|
|
54927
|
+
const entries = await readdir26(extDir);
|
|
54928
|
+
externalIds = entries.filter((name) => name.endsWith(".json")).map((name) => name.slice(0, -".json".length));
|
|
54929
|
+
} catch {
|
|
54930
|
+
return [];
|
|
54931
|
+
}
|
|
54932
|
+
const candidates = [];
|
|
54933
|
+
for (const id of externalIds) {
|
|
54934
|
+
const slate = await readExternalSlate(cwd, id);
|
|
54935
|
+
if (!slate)
|
|
54936
|
+
continue;
|
|
54937
|
+
if (slate.closedAt === undefined || slate.workspaceId !== undefined)
|
|
54938
|
+
continue;
|
|
54939
|
+
const unbound2 = await readNewestUnboundCandidateForExternal(cwd, id);
|
|
54940
|
+
if (unbound2) {
|
|
54941
|
+
candidates.push({
|
|
54942
|
+
type: "unbound-candidate",
|
|
54943
|
+
externalSessionId: id,
|
|
54944
|
+
evidencePath: unbound2.evidencePath,
|
|
54945
|
+
summary: unbound2.summary
|
|
54946
|
+
});
|
|
54947
|
+
} else {
|
|
54948
|
+
const summary = summarizeUnboundCandidate((slate.seeds ?? []).reduce((groups, seed) => {
|
|
54949
|
+
const kind2 = seed.kind ?? "follow-up";
|
|
54950
|
+
const existing = groups.find((g) => g.kind === kind2);
|
|
54951
|
+
if (existing) {
|
|
54952
|
+
existing.seeds = (existing.seeds ?? []).concat([{ text: seed.text }]);
|
|
54953
|
+
} else {
|
|
54954
|
+
groups.push({ kind: kind2, seeds: [{ text: seed.text }] });
|
|
54955
|
+
}
|
|
54956
|
+
return groups;
|
|
54957
|
+
}, []));
|
|
54958
|
+
candidates.push({
|
|
54959
|
+
type: "unbound-candidate",
|
|
54960
|
+
externalSessionId: id,
|
|
54961
|
+
evidencePath: path150.join(extDir, `${id}.json`),
|
|
54962
|
+
summary
|
|
54963
|
+
});
|
|
54964
|
+
}
|
|
54965
|
+
}
|
|
54966
|
+
return candidates;
|
|
54967
|
+
}
|
|
54968
|
+
async function readNewestUnboundCandidateForExternal(cwd, externalSessionId) {
|
|
54969
|
+
const evidenceDir = path150.join(externalSlatesDir(cwd), externalSessionId);
|
|
54970
|
+
let entries;
|
|
54971
|
+
try {
|
|
54972
|
+
entries = (await readdir26(evidenceDir)).filter((name) => name.endsWith("-unbound-candidate.json"));
|
|
54973
|
+
} catch {
|
|
54974
|
+
return;
|
|
54975
|
+
}
|
|
54976
|
+
entries.sort();
|
|
54977
|
+
for (let i = entries.length - 1;i >= 0; i--) {
|
|
54978
|
+
const evidencePath = path150.join(evidenceDir, entries[i]);
|
|
54979
|
+
const result = readConfigFile(evidencePath);
|
|
54980
|
+
if (!result.ok) {
|
|
54981
|
+
continue;
|
|
54982
|
+
}
|
|
54983
|
+
try {
|
|
54984
|
+
const parsed = JSON.parse(result.text);
|
|
54985
|
+
if (parsed.recordType !== "unbound-candidate")
|
|
54986
|
+
continue;
|
|
54987
|
+
return { evidencePath, summary: summarizeUnboundCandidate(parsed.groups) };
|
|
54988
|
+
} catch {
|
|
54989
|
+
continue;
|
|
54990
|
+
}
|
|
54991
|
+
}
|
|
54992
|
+
return;
|
|
54993
|
+
}
|
|
54798
54994
|
async function collectSessionCategories(cwd) {
|
|
54799
|
-
const classified = await Promise.all(
|
|
54995
|
+
const [classified, externalUnboundCandidates] = await Promise.all([
|
|
54996
|
+
Promise.all(listSessions(cwd).map((session) => classifySession(session))),
|
|
54997
|
+
readExternalUnboundCandidates(cwd)
|
|
54998
|
+
]);
|
|
54800
54999
|
const blocked2 = [];
|
|
54801
55000
|
const unboundCandidates = [];
|
|
54802
55001
|
const unknown = [];
|
|
@@ -54810,8 +55009,151 @@ async function collectSessionCategories(cwd) {
|
|
|
54810
55009
|
else
|
|
54811
55010
|
unknown.push(category.item);
|
|
54812
55011
|
}
|
|
55012
|
+
unboundCandidates.push(...externalUnboundCandidates);
|
|
54813
55013
|
return { blocked: blocked2, unboundCandidates, unknown };
|
|
54814
55014
|
}
|
|
55015
|
+
var SESSION_ATTRIBUTION_SLACK_MS = 5 * 60000;
|
|
55016
|
+
var MTIME_CLOCK_SKEW_TOLERANCE_MS = 5000;
|
|
55017
|
+
async function collectReceiptTargets(cwd, owner) {
|
|
55018
|
+
const targets = new Set;
|
|
55019
|
+
const workspacesDir = path150.join(cwd, ".metaproject", "workspaces");
|
|
55020
|
+
let workspaceIds;
|
|
55021
|
+
try {
|
|
55022
|
+
workspaceIds = (await readdir26(workspacesDir, { withFileTypes: true })).filter((entry) => entry.isDirectory()).map((entry) => entry.name);
|
|
55023
|
+
} catch {
|
|
55024
|
+
return targets;
|
|
55025
|
+
}
|
|
55026
|
+
for (const workspaceId of workspaceIds) {
|
|
55027
|
+
const receiptsDir = path150.join(workspacesDir, workspaceId, `${owner}-write-receipts`);
|
|
55028
|
+
let files;
|
|
55029
|
+
try {
|
|
55030
|
+
files = (await readdir26(receiptsDir)).filter((name) => name.endsWith(".json"));
|
|
55031
|
+
} catch {
|
|
55032
|
+
continue;
|
|
55033
|
+
}
|
|
55034
|
+
for (const file of files) {
|
|
55035
|
+
const result = readConfigFile(path150.join(receiptsDir, file));
|
|
55036
|
+
if (!result.ok)
|
|
55037
|
+
continue;
|
|
55038
|
+
try {
|
|
55039
|
+
const receipt = JSON.parse(result.text);
|
|
55040
|
+
if (typeof receipt.targetRef === "string") {
|
|
55041
|
+
targets.add(receipt.targetRef.replace(/^\.\//, ""));
|
|
55042
|
+
}
|
|
55043
|
+
} catch {
|
|
55044
|
+
continue;
|
|
55045
|
+
}
|
|
55046
|
+
}
|
|
55047
|
+
}
|
|
55048
|
+
return targets;
|
|
55049
|
+
}
|
|
55050
|
+
async function attributeToSession(absolutePath, sessionsNewestFirst) {
|
|
55051
|
+
let mtimeMs;
|
|
55052
|
+
try {
|
|
55053
|
+
mtimeMs = (await stat8(absolutePath)).mtimeMs;
|
|
55054
|
+
} catch {
|
|
55055
|
+
return;
|
|
55056
|
+
}
|
|
55057
|
+
for (const session of sessionsNewestFirst) {
|
|
55058
|
+
const start = Date.parse(session.createdAt);
|
|
55059
|
+
const end = Date.parse(session.updatedAt);
|
|
55060
|
+
if (Number.isNaN(start) || Number.isNaN(end))
|
|
55061
|
+
continue;
|
|
55062
|
+
if (mtimeMs >= start - MTIME_CLOCK_SKEW_TOLERANCE_MS && mtimeMs <= end + SESSION_ATTRIBUTION_SLACK_MS) {
|
|
55063
|
+
const workspaceId = (await safeReadSlate(sessionDir(session.projectPath, session.id)))?.workspaceId;
|
|
55064
|
+
return { sessionId: session.id, workspaceId, changedAt: new Date(mtimeMs).toISOString() };
|
|
55065
|
+
}
|
|
55066
|
+
}
|
|
55067
|
+
return;
|
|
55068
|
+
}
|
|
55069
|
+
async function findSkillFiles(dir) {
|
|
55070
|
+
let entries;
|
|
55071
|
+
try {
|
|
55072
|
+
entries = await readdir26(dir, { withFileTypes: true });
|
|
55073
|
+
} catch {
|
|
55074
|
+
return [];
|
|
55075
|
+
}
|
|
55076
|
+
const found = [];
|
|
55077
|
+
for (const entry of entries) {
|
|
55078
|
+
const full = path150.join(dir, entry.name);
|
|
55079
|
+
if (entry.isDirectory()) {
|
|
55080
|
+
found.push(...await findSkillFiles(full));
|
|
55081
|
+
} else if (entry.isFile() && entry.name === "SKILL.md") {
|
|
55082
|
+
found.push(full);
|
|
55083
|
+
}
|
|
55084
|
+
}
|
|
55085
|
+
return found;
|
|
55086
|
+
}
|
|
55087
|
+
async function detectUnreviewedSacPathChanges(cwd, sessions) {
|
|
55088
|
+
const scoped = [...sessions].filter((session) => session.projectPath === cwd).sort((a, b) => Date.parse(b.updatedAt) - Date.parse(a.updatedAt));
|
|
55089
|
+
if (scoped.length === 0)
|
|
55090
|
+
return [];
|
|
55091
|
+
const [wikiReceipts, memoryReceipts, skillReceipts] = await Promise.all([
|
|
55092
|
+
collectReceiptTargets(cwd, "wiki"),
|
|
55093
|
+
collectReceiptTargets(cwd, "memory"),
|
|
55094
|
+
collectReceiptTargets(cwd, "skill")
|
|
55095
|
+
]);
|
|
55096
|
+
const items = [];
|
|
55097
|
+
const wikiPages = await collectPages(cwd).catch(() => []);
|
|
55098
|
+
for (const page of wikiPages) {
|
|
55099
|
+
if ((page.status ?? "").toLowerCase() !== "accepted")
|
|
55100
|
+
continue;
|
|
55101
|
+
const targetRef = `wiki/${page.relativePath}`;
|
|
55102
|
+
if (wikiReceipts.has(targetRef))
|
|
55103
|
+
continue;
|
|
55104
|
+
const attribution = await attributeToSession(page.absolutePath, scoped);
|
|
55105
|
+
if (attribution === undefined)
|
|
55106
|
+
continue;
|
|
55107
|
+
items.push({
|
|
55108
|
+
type: "unreviewed-sac-path",
|
|
55109
|
+
sessionId: attribution.sessionId,
|
|
55110
|
+
...attribution.workspaceId !== undefined ? { workspaceId: attribution.workspaceId } : {},
|
|
55111
|
+
owner: "wiki",
|
|
55112
|
+
path: targetRef,
|
|
55113
|
+
...page.status !== null ? { status: page.status } : {},
|
|
55114
|
+
changedAt: attribution.changedAt
|
|
55115
|
+
});
|
|
55116
|
+
}
|
|
55117
|
+
const memoryEntries = await collectEntries(cwd).catch(() => []);
|
|
55118
|
+
for (const entry of memoryEntries) {
|
|
55119
|
+
if (entry.status !== "accepted")
|
|
55120
|
+
continue;
|
|
55121
|
+
const targetRef = `memory/${entry.relativePath}`;
|
|
55122
|
+
if (memoryReceipts.has(targetRef))
|
|
55123
|
+
continue;
|
|
55124
|
+
const attribution = await attributeToSession(entry.absolutePath, scoped);
|
|
55125
|
+
if (attribution === undefined)
|
|
55126
|
+
continue;
|
|
55127
|
+
items.push({
|
|
55128
|
+
type: "unreviewed-sac-path",
|
|
55129
|
+
sessionId: attribution.sessionId,
|
|
55130
|
+
...attribution.workspaceId !== undefined ? { workspaceId: attribution.workspaceId } : {},
|
|
55131
|
+
owner: "memory",
|
|
55132
|
+
path: targetRef,
|
|
55133
|
+
status: entry.status,
|
|
55134
|
+
changedAt: attribution.changedAt
|
|
55135
|
+
});
|
|
55136
|
+
}
|
|
55137
|
+
const sacSkillsDir = path150.join(cwd, ".metaproject", "project-skills", "sac");
|
|
55138
|
+
const skillFiles = await findSkillFiles(sacSkillsDir);
|
|
55139
|
+
for (const absolutePath of skillFiles) {
|
|
55140
|
+
const targetRef = path150.relative(path150.join(cwd, ".metaproject"), absolutePath).split(path150.sep).join("/");
|
|
55141
|
+
if (skillReceipts.has(targetRef))
|
|
55142
|
+
continue;
|
|
55143
|
+
const attribution = await attributeToSession(absolutePath, scoped);
|
|
55144
|
+
if (attribution === undefined)
|
|
55145
|
+
continue;
|
|
55146
|
+
items.push({
|
|
55147
|
+
type: "unreviewed-sac-path",
|
|
55148
|
+
sessionId: attribution.sessionId,
|
|
55149
|
+
...attribution.workspaceId !== undefined ? { workspaceId: attribution.workspaceId } : {},
|
|
55150
|
+
owner: "skill",
|
|
55151
|
+
path: targetRef,
|
|
55152
|
+
changedAt: attribution.changedAt
|
|
55153
|
+
});
|
|
55154
|
+
}
|
|
55155
|
+
return items;
|
|
55156
|
+
}
|
|
54815
55157
|
async function isSlateEngaged(dir) {
|
|
54816
55158
|
if (await pathExists(path150.join(dir, "slate.json")))
|
|
54817
55159
|
return true;
|
|
@@ -55078,7 +55420,7 @@ async function loadInspectorCatchUp(cwd) {
|
|
|
55078
55420
|
try {
|
|
55079
55421
|
return await buildCatchUp({ cwd });
|
|
55080
55422
|
} catch {
|
|
55081
|
-
return { proposals: [], blocked: [], unboundCandidates: [], unknown: [], lifecycleFlags: [] };
|
|
55423
|
+
return { proposals: [], blocked: [], unboundCandidates: [], unknown: [], lifecycleFlags: [], unreviewedPaths: [] };
|
|
55082
55424
|
}
|
|
55083
55425
|
}
|
|
55084
55426
|
function catchUpItems(report) {
|
|
@@ -65153,6 +65495,22 @@ New session ${shortSessionId(live.summary.id)}.
|
|
|
65153
65495
|
slateSession,
|
|
65154
65496
|
mintAttemptId: mintTimestampAttemptId
|
|
65155
65497
|
});
|
|
65498
|
+
} else if (command === "/theme") {
|
|
65499
|
+
const wanted = rest.trim();
|
|
65500
|
+
if (wanted.length === 0) {
|
|
65501
|
+
agentIo.onSystem?.(formatThemeList(getThemeId()));
|
|
65502
|
+
} else {
|
|
65503
|
+
const next = parseThemeId(wanted);
|
|
65504
|
+
if (next === undefined) {
|
|
65505
|
+
agentIo.onSystem?.(`Unknown theme '${wanted}'.
|
|
65506
|
+
${formatThemeList(getThemeId())}`);
|
|
65507
|
+
} else {
|
|
65508
|
+
applyThemeId(next);
|
|
65509
|
+
persistThemeId(next);
|
|
65510
|
+
agentIo.onSystem?.(`Theme: ${themeLabel(next)}
|
|
65511
|
+
`);
|
|
65512
|
+
}
|
|
65513
|
+
}
|
|
65156
65514
|
} else {
|
|
65157
65515
|
agentIo.onSystem?.(describeUnavailableCommand(command, "agent") ?? `Unknown command: ${command}. Type /help.
|
|
65158
65516
|
`);
|
|
@@ -65417,6 +65775,12 @@ async function shellCommand(args2, runtime = {}) {
|
|
|
65417
65775
|
}
|
|
65418
65776
|
}
|
|
65419
65777
|
const rl = readline2.createInterface({ input: process.stdin });
|
|
65778
|
+
if (!process.stdin.isTTY) {
|
|
65779
|
+
process.on("SIGINT", () => {
|
|
65780
|
+
rl.close();
|
|
65781
|
+
process.exit(130);
|
|
65782
|
+
});
|
|
65783
|
+
}
|
|
65420
65784
|
const lineIterator = rl[Symbol.asyncIterator]();
|
|
65421
65785
|
const sharedLines = { [Symbol.asyncIterator]: () => lineIterator };
|
|
65422
65786
|
const { io, emitSystem, printHeader, printPrompt, destroy } = createRichIo(sharedLines, versionCheck);
|
|
@@ -65683,7 +66047,7 @@ Shell:
|
|
|
65683
66047
|
|
|
65684
66048
|
// src/commands/modules.ts
|
|
65685
66049
|
init_fs();
|
|
65686
|
-
import { readFile as
|
|
66050
|
+
import { readFile as readFile80 } from "fs/promises";
|
|
65687
66051
|
import { stdin } from "process";
|
|
65688
66052
|
import path153 from "path";
|
|
65689
66053
|
var MODULES = [
|
|
@@ -65745,7 +66109,7 @@ async function modulesCommand(args2 = []) {
|
|
|
65745
66109
|
}
|
|
65746
66110
|
let manifest = {};
|
|
65747
66111
|
try {
|
|
65748
|
-
manifest = JSON.parse(await
|
|
66112
|
+
manifest = JSON.parse(await readFile80(manifestPath, "utf8"));
|
|
65749
66113
|
} catch {}
|
|
65750
66114
|
const enabled = new Set(MODULES.filter((module) => manifest.modules?.[module.name]?.enabled === true).map((module) => module.name));
|
|
65751
66115
|
if (wantsJson) {
|
|
@@ -67719,7 +68083,7 @@ function printHelp17() {
|
|
|
67719
68083
|
|
|
67720
68084
|
// src/commands/update.ts
|
|
67721
68085
|
import { spawn as spawn5 } from "child_process";
|
|
67722
|
-
import { chmod as chmod4, mkdir as mkdir56, readFile as
|
|
68086
|
+
import { chmod as chmod4, mkdir as mkdir56, readFile as readFile81, readdir as readdir27, writeFile as writeFile49 } from "fs/promises";
|
|
67723
68087
|
import { access as access4, constants as constants2, existsSync as existsSync30 } from "fs";
|
|
67724
68088
|
import path158 from "path";
|
|
67725
68089
|
import { fileURLToPath as fileURLToPath7 } from "url";
|
|
@@ -68030,7 +68394,7 @@ async function shouldInstallDashboardPostCommitHook(projectRoot, manifest) {
|
|
|
68030
68394
|
if (!await pathExists(hookPath)) {
|
|
68031
68395
|
return false;
|
|
68032
68396
|
}
|
|
68033
|
-
return (await
|
|
68397
|
+
return (await readFile81(hookPath, "utf8")).includes("# keryx:");
|
|
68034
68398
|
}
|
|
68035
68399
|
async function collectDashboardData(metaprojectRoot) {
|
|
68036
68400
|
const data = {};
|
|
@@ -68082,12 +68446,12 @@ async function collectTasksDashboardData(metaprojectRoot) {
|
|
|
68082
68446
|
continue;
|
|
68083
68447
|
}
|
|
68084
68448
|
try {
|
|
68085
|
-
const flow = JSON.parse(await
|
|
68449
|
+
const flow = JSON.parse(await readFile81(flowPath, "utf8"));
|
|
68086
68450
|
const tasks = Array.isArray(flow.tasks) ? flow.tasks : [];
|
|
68087
68451
|
let acTotal = 0;
|
|
68088
68452
|
const acPath2 = path158.join(flowsRoot2, dir, "acceptance-criteria.md");
|
|
68089
68453
|
if (await pathExists(acPath2)) {
|
|
68090
|
-
const acContent = await
|
|
68454
|
+
const acContent = await readFile81(acPath2, "utf8");
|
|
68091
68455
|
acTotal = (acContent.match(/^- AC\d+:/gm) ?? []).length;
|
|
68092
68456
|
}
|
|
68093
68457
|
flows.push({
|
|
@@ -68143,7 +68507,7 @@ async function collectDashboardDocs(metaprojectRoot, wiki, memory) {
|
|
|
68143
68507
|
if (!await pathExists(filePath)) {
|
|
68144
68508
|
continue;
|
|
68145
68509
|
}
|
|
68146
|
-
const content = await
|
|
68510
|
+
const content = await readFile81(filePath, "utf8");
|
|
68147
68511
|
docs[href] = content.length > 40000 ? `${content.slice(0, 40000)}
|
|
68148
68512
|
|
|
68149
68513
|
\u2026truncated\u2026` : content;
|
|
@@ -68160,7 +68524,7 @@ async function collectHealthDashboardData(metaprojectRoot) {
|
|
|
68160
68524
|
if (!await pathExists(reportPath2)) {
|
|
68161
68525
|
return;
|
|
68162
68526
|
}
|
|
68163
|
-
const report = JSON.parse(await
|
|
68527
|
+
const report = JSON.parse(await readFile81(reportPath2, "utf8"));
|
|
68164
68528
|
const metrics = Array.isArray(report.metrics) ? report.metrics : [];
|
|
68165
68529
|
const findings = Array.isArray(report.findings) ? report.findings : [];
|
|
68166
68530
|
const project = metrics.find((metric) => metric.key === "project") ?? {};
|
|
@@ -68274,7 +68638,7 @@ async function collectGraphDashboardData(metaprojectRoot) {
|
|
|
68274
68638
|
let nodes = 0;
|
|
68275
68639
|
let files = 0;
|
|
68276
68640
|
let assets = 0;
|
|
68277
|
-
for (const node of parseJsonl2(await
|
|
68641
|
+
for (const node of parseJsonl2(await readFile81(nodesPath, "utf8"))) {
|
|
68278
68642
|
nodes += 1;
|
|
68279
68643
|
if (node.kind === "asset") {
|
|
68280
68644
|
assets += 1;
|
|
@@ -68290,7 +68654,7 @@ async function collectGraphDashboardData(metaprojectRoot) {
|
|
|
68290
68654
|
let imports = 0;
|
|
68291
68655
|
let assetEdges = 0;
|
|
68292
68656
|
let unresolved = 0;
|
|
68293
|
-
for (const edge of parseJsonl2(await
|
|
68657
|
+
for (const edge of parseJsonl2(await readFile81(edgesPath, "utf8"))) {
|
|
68294
68658
|
edges += 1;
|
|
68295
68659
|
if (edge.kind === "imports") {
|
|
68296
68660
|
imports += 1;
|
|
@@ -68320,7 +68684,7 @@ async function collectTestingDashboardData(metaprojectRoot) {
|
|
|
68320
68684
|
const reportPath2 = path158.join(metaprojectRoot, "data", "testing", "artifacts", "latest.json");
|
|
68321
68685
|
const contextPath = path158.join(metaprojectRoot, "data", "testing", "context.md");
|
|
68322
68686
|
if (await pathExists(reportPath2)) {
|
|
68323
|
-
const report = JSON.parse(await
|
|
68687
|
+
const report = JSON.parse(await readFile81(reportPath2, "utf8"));
|
|
68324
68688
|
const totalTests = numberOrUndefined(report.total);
|
|
68325
68689
|
const failedTests = Array.isArray(report.failures) ? report.failures.length : numberOrUndefined(report.failed);
|
|
68326
68690
|
return {
|
|
@@ -68351,7 +68715,7 @@ async function collectMarkdownPages(root, hrefPrefix) {
|
|
|
68351
68715
|
if (relativePath === "index.md" || relativePath.startsWith("templates/")) {
|
|
68352
68716
|
continue;
|
|
68353
68717
|
}
|
|
68354
|
-
const content = await
|
|
68718
|
+
const content = await readFile81(filePath, "utf8");
|
|
68355
68719
|
const embedded = content.length > 24000 ? `${content.slice(0, 24000)}
|
|
68356
68720
|
|
|
68357
68721
|
\u2026truncated\u2026` : content;
|
|
@@ -68527,7 +68891,7 @@ async function enableTasksInManifest(metaprojectRoot) {
|
|
|
68527
68891
|
}
|
|
68528
68892
|
let raw;
|
|
68529
68893
|
try {
|
|
68530
|
-
raw = JSON.parse(await
|
|
68894
|
+
raw = JSON.parse(await readFile81(manifestPath, "utf8"));
|
|
68531
68895
|
} catch {
|
|
68532
68896
|
return;
|
|
68533
68897
|
}
|
|
@@ -68550,7 +68914,7 @@ async function updateManifestAgentEntrypoints(metaprojectRoot, ruleSources) {
|
|
|
68550
68914
|
}
|
|
68551
68915
|
let raw;
|
|
68552
68916
|
try {
|
|
68553
|
-
raw = JSON.parse(await
|
|
68917
|
+
raw = JSON.parse(await readFile81(manifestPath, "utf8"));
|
|
68554
68918
|
} catch {
|
|
68555
68919
|
return;
|
|
68556
68920
|
}
|
|
@@ -68659,7 +69023,7 @@ async function installManagedHook2(projectRoot, hookName, blockId, content) {
|
|
|
68659
69023
|
const managedBlock = `${blockStart}
|
|
68660
69024
|
${content.trim()}
|
|
68661
69025
|
${blockEnd}`;
|
|
68662
|
-
const existing = await pathExists(hookPath) ? await
|
|
69026
|
+
const existing = await pathExists(hookPath) ? await readFile81(hookPath, "utf8") : `#!/usr/bin/env sh
|
|
68663
69027
|
`;
|
|
68664
69028
|
const blockPattern = new RegExp(`${escapeRegExp6(blockStart)}[\\s\\S]*?${escapeRegExp6(blockEnd)}`);
|
|
68665
69029
|
const next = blockPattern.test(existing) ? existing.replace(blockPattern, managedBlock) : `${existing.trimEnd()}
|
|
@@ -68678,7 +69042,7 @@ async function removeManagedHook2(projectRoot, hookName, blockId) {
|
|
|
68678
69042
|
if (!await pathExists(hookPath)) {
|
|
68679
69043
|
return;
|
|
68680
69044
|
}
|
|
68681
|
-
const existing = await
|
|
69045
|
+
const existing = await readFile81(hookPath, "utf8");
|
|
68682
69046
|
const blockStart = `# keryx:${blockId}:begin`;
|
|
68683
69047
|
const blockEnd = `# keryx:${blockId}:end`;
|
|
68684
69048
|
const blockPattern = new RegExp(`\\n*${escapeRegExp6(blockStart)}[\\s\\S]*?${escapeRegExp6(blockEnd)}\\n*`);
|
|
@@ -68700,7 +69064,7 @@ async function prePushHasSecurityBlock2(projectRoot) {
|
|
|
68700
69064
|
if (!await pathExists(hookPath)) {
|
|
68701
69065
|
return false;
|
|
68702
69066
|
}
|
|
68703
|
-
const hook = await
|
|
69067
|
+
const hook = await readFile81(hookPath, "utf8");
|
|
68704
69068
|
return hook.includes("# keryx:security-pre-push:begin");
|
|
68705
69069
|
}
|
|
68706
69070
|
async function agentSettingsHasSecuritySentinel2(projectRoot) {
|
|
@@ -68708,7 +69072,7 @@ async function agentSettingsHasSecuritySentinel2(projectRoot) {
|
|
|
68708
69072
|
if (!await pathExists(file)) {
|
|
68709
69073
|
return false;
|
|
68710
69074
|
}
|
|
68711
|
-
return (await
|
|
69075
|
+
return (await readFile81(file, "utf8")).includes(AGENT_HOOKS_SENTINEL);
|
|
68712
69076
|
}
|
|
68713
69077
|
async function readManifest5(metaprojectRoot) {
|
|
68714
69078
|
const manifestPath = path158.join(metaprojectRoot, "metaproject.json");
|
|
@@ -68721,7 +69085,7 @@ async function readManifest5(metaprojectRoot) {
|
|
|
68721
69085
|
};
|
|
68722
69086
|
}
|
|
68723
69087
|
try {
|
|
68724
|
-
const manifest = JSON.parse(await
|
|
69088
|
+
const manifest = JSON.parse(await readFile81(manifestPath, "utf8"));
|
|
68725
69089
|
const normalized = normalizeManifest(manifest);
|
|
68726
69090
|
return {
|
|
68727
69091
|
exists: true,
|
|
@@ -68845,7 +69209,7 @@ async function run(command, args2, cwd) {
|
|
|
68845
69209
|
});
|
|
68846
69210
|
}
|
|
68847
69211
|
async function writeTextIfChanged4(filePath, content) {
|
|
68848
|
-
if (await pathExists(filePath) && await
|
|
69212
|
+
if (await pathExists(filePath) && await readFile81(filePath, "utf8") === content) {
|
|
68849
69213
|
return;
|
|
68850
69214
|
}
|
|
68851
69215
|
await mkdir56(path158.dirname(filePath), { recursive: true });
|
|
@@ -68859,8 +69223,8 @@ async function writeTextIfMissing4(filePath, content) {
|
|
|
68859
69223
|
await writeFile49(filePath, content, "utf8");
|
|
68860
69224
|
}
|
|
68861
69225
|
async function copyFileIfChanged2(from, to) {
|
|
68862
|
-
const next = await
|
|
68863
|
-
if (await pathExists(to) && await
|
|
69226
|
+
const next = await readFile81(from, "utf8");
|
|
69227
|
+
if (await pathExists(to) && await readFile81(to, "utf8") === next) {
|
|
68864
69228
|
return;
|
|
68865
69229
|
}
|
|
68866
69230
|
await mkdir56(path158.dirname(to), { recursive: true });
|
|
@@ -68970,7 +69334,7 @@ function printHelp19() {
|
|
|
68970
69334
|
import { readFileSync as readFileSync10 } from "fs";
|
|
68971
69335
|
|
|
68972
69336
|
// src/agents/bootstrap.ts
|
|
68973
|
-
import { mkdir as mkdir57, readFile as
|
|
69337
|
+
import { mkdir as mkdir57, readFile as readFile82, writeFile as writeFile50 } from "fs/promises";
|
|
68974
69338
|
import { homedir as homedir7 } from "os";
|
|
68975
69339
|
import path160 from "path";
|
|
68976
69340
|
init_fs();
|
|
@@ -69043,7 +69407,7 @@ function resolveAgentBootstrapRuntimes(ids) {
|
|
|
69043
69407
|
async function agentBootstrapStatus(runtime, homeRoot = homedir7()) {
|
|
69044
69408
|
const filePath = runtime.filePath(homeRoot);
|
|
69045
69409
|
const exists2 = await pathExists(filePath);
|
|
69046
|
-
const content = exists2 ? await
|
|
69410
|
+
const content = exists2 ? await readFile82(filePath, "utf8") : "";
|
|
69047
69411
|
const expected = renderAgentBootstrapBlock(runtime.fileName).trim();
|
|
69048
69412
|
const installed = content.includes(AGENT_BOOTSTRAP_START);
|
|
69049
69413
|
const current = installed && extractManagedBlock(content)?.trim() === expected;
|
|
@@ -69053,7 +69417,7 @@ async function installAgentBootstrap(runtime, options = {}) {
|
|
|
69053
69417
|
const homeRoot = options.homeRoot ?? homedir7();
|
|
69054
69418
|
const filePath = runtime.filePath(homeRoot);
|
|
69055
69419
|
const exists2 = await pathExists(filePath);
|
|
69056
|
-
const current = exists2 ? await
|
|
69420
|
+
const current = exists2 ? await readFile82(filePath, "utf8") : "";
|
|
69057
69421
|
const next = upsertManagedBlock(current || defaultAgentFile(runtime), renderAgentBootstrapBlock(runtime.fileName));
|
|
69058
69422
|
const dryRun = options.dryRun === true;
|
|
69059
69423
|
const wrote = next !== current;
|
|
@@ -69068,7 +69432,7 @@ async function uninstallAgentBootstrap(runtime, options = {}) {
|
|
|
69068
69432
|
const homeRoot = options.homeRoot ?? homedir7();
|
|
69069
69433
|
const filePath = runtime.filePath(homeRoot);
|
|
69070
69434
|
const exists2 = await pathExists(filePath);
|
|
69071
|
-
const current = exists2 ? await
|
|
69435
|
+
const current = exists2 ? await readFile82(filePath, "utf8") : "";
|
|
69072
69436
|
const next = removeManagedBlock(current);
|
|
69073
69437
|
const dryRun = options.dryRun === true;
|
|
69074
69438
|
const removed = next !== current;
|
|
@@ -69594,7 +69958,7 @@ function printBootstrapHelp() {
|
|
|
69594
69958
|
|
|
69595
69959
|
// src/commands/metrics.ts
|
|
69596
69960
|
init_args();
|
|
69597
|
-
import { readFile as
|
|
69961
|
+
import { readFile as readFile83 } from "fs/promises";
|
|
69598
69962
|
import path162 from "path";
|
|
69599
69963
|
|
|
69600
69964
|
// src/metrics/benchmark.ts
|
|
@@ -70870,7 +71234,7 @@ async function metricsCommand(args2 = [], projectRoot = process.cwd()) {
|
|
|
70870
71234
|
process.exitCode = 1;
|
|
70871
71235
|
return;
|
|
70872
71236
|
}
|
|
70873
|
-
const record2 = JSON.parse(await
|
|
71237
|
+
const record2 = JSON.parse(await readFile83(path162.resolve(projectRoot, file), "utf8"));
|
|
70874
71238
|
const result = validateRunRecord(record2);
|
|
70875
71239
|
console.log(result.valid ? "valid: yes" : "valid: no");
|
|
70876
71240
|
for (const error2 of result.errors)
|
|
@@ -70901,7 +71265,7 @@ async function metricsCommand(args2 = [], projectRoot = process.cwd()) {
|
|
|
70901
71265
|
process.exitCode = 1;
|
|
70902
71266
|
return;
|
|
70903
71267
|
}
|
|
70904
|
-
console.log(await
|
|
71268
|
+
console.log(await readFile83(file, "utf8"));
|
|
70905
71269
|
return;
|
|
70906
71270
|
}
|
|
70907
71271
|
if (subcommand === "compare") {
|
|
@@ -70912,8 +71276,8 @@ async function metricsCommand(args2 = [], projectRoot = process.cwd()) {
|
|
|
70912
71276
|
process.exitCode = 1;
|
|
70913
71277
|
return;
|
|
70914
71278
|
}
|
|
70915
|
-
const a = JSON.parse(await
|
|
70916
|
-
const b = JSON.parse(await
|
|
71279
|
+
const a = JSON.parse(await readFile83(path162.join(metricsRoot(projectRoot), "runs", `${runA}.json`), "utf8"));
|
|
71280
|
+
const b = JSON.parse(await readFile83(path162.join(metricsRoot(projectRoot), "runs", `${runB}.json`), "utf8"));
|
|
70917
71281
|
const comparison = compareExecutionRuns(a, b);
|
|
70918
71282
|
console.log(stableJson(comparison));
|
|
70919
71283
|
return;
|
|
@@ -70962,7 +71326,7 @@ async function metricsCommand(args2 = [], projectRoot = process.cwd()) {
|
|
|
70962
71326
|
process.exitCode = 1;
|
|
70963
71327
|
return;
|
|
70964
71328
|
}
|
|
70965
|
-
const raw = JSON.parse(await
|
|
71329
|
+
const raw = JSON.parse(await readFile83(path162.resolve(projectRoot, file), "utf8"));
|
|
70966
71330
|
const input2 = Array.isArray(raw) ? raw : raw.runs ?? [];
|
|
70967
71331
|
const result = validatePairedBenchmark(input2);
|
|
70968
71332
|
console.log(stableJson(result));
|
|
@@ -70974,7 +71338,7 @@ async function metricsCommand(args2 = [], projectRoot = process.cwd()) {
|
|
|
70974
71338
|
process.exitCode = 1;
|
|
70975
71339
|
}
|
|
70976
71340
|
async function loadAffectedSets(projectRoot, file) {
|
|
70977
|
-
const raw = JSON.parse(await
|
|
71341
|
+
const raw = JSON.parse(await readFile83(path162.resolve(projectRoot, file), "utf8"));
|
|
70978
71342
|
const map = new Map;
|
|
70979
71343
|
for (const entry of raw.targets ?? []) {
|
|
70980
71344
|
if (typeof entry.target === "string")
|
|
@@ -71045,7 +71409,7 @@ async function runHarnessLayer(projectRoot, args2, ladder) {
|
|
|
71045
71409
|
let tasks;
|
|
71046
71410
|
let model;
|
|
71047
71411
|
try {
|
|
71048
|
-
const raw = JSON.parse(await
|
|
71412
|
+
const raw = JSON.parse(await readFile83(path162.resolve(projectRoot, resultsPath), "utf8"));
|
|
71049
71413
|
tasks = raw.tasks ?? [];
|
|
71050
71414
|
model = raw.model;
|
|
71051
71415
|
} catch (error2) {
|
|
@@ -71076,7 +71440,7 @@ async function runSafetyCompletionHonestyLayer(projectRoot, args2, ladder) {
|
|
|
71076
71440
|
let cases;
|
|
71077
71441
|
let model;
|
|
71078
71442
|
try {
|
|
71079
|
-
const raw = JSON.parse(await
|
|
71443
|
+
const raw = JSON.parse(await readFile83(path162.resolve(projectRoot, resultsPath), "utf8"));
|
|
71080
71444
|
cases = raw.cases ?? [];
|
|
71081
71445
|
model = raw.model;
|
|
71082
71446
|
} catch (error2) {
|
|
@@ -71101,7 +71465,7 @@ async function runSafetyFalsePremiseLayer(projectRoot, args2, ladder) {
|
|
|
71101
71465
|
let cases;
|
|
71102
71466
|
let model;
|
|
71103
71467
|
try {
|
|
71104
|
-
const raw = JSON.parse(await
|
|
71468
|
+
const raw = JSON.parse(await readFile83(path162.resolve(projectRoot, resultsPath), "utf8"));
|
|
71105
71469
|
cases = raw.cases ?? [];
|
|
71106
71470
|
model = raw.model;
|
|
71107
71471
|
} catch (error2) {
|
|
@@ -71126,7 +71490,7 @@ async function runSafetyContainmentLayer(projectRoot, args2, ladder, caseClass)
|
|
|
71126
71490
|
let cases;
|
|
71127
71491
|
let model;
|
|
71128
71492
|
try {
|
|
71129
|
-
const raw = JSON.parse(await
|
|
71493
|
+
const raw = JSON.parse(await readFile83(path162.resolve(projectRoot, resultsPath), "utf8"));
|
|
71130
71494
|
cases = raw.cases ?? [];
|
|
71131
71495
|
model = raw.model;
|
|
71132
71496
|
} catch (error2) {
|
|
@@ -71217,7 +71581,7 @@ async function runGdgraphLayer(projectRoot, args2, ladder) {
|
|
|
71217
71581
|
return allValid;
|
|
71218
71582
|
}
|
|
71219
71583
|
async function loadCoverageMap2(projectRoot, file) {
|
|
71220
|
-
const raw = JSON.parse(await
|
|
71584
|
+
const raw = JSON.parse(await readFile83(path162.resolve(projectRoot, file), "utf8"));
|
|
71221
71585
|
return raw.coverageMap ?? {};
|
|
71222
71586
|
}
|
|
71223
71587
|
async function runTestingLayer(projectRoot, args2, ladder) {
|
|
@@ -71254,7 +71618,7 @@ async function runTestingLayer(projectRoot, args2, ladder) {
|
|
|
71254
71618
|
return result.valid;
|
|
71255
71619
|
}
|
|
71256
71620
|
async function loadMemoryGoldK(projectRoot, file) {
|
|
71257
|
-
const raw = JSON.parse(await
|
|
71621
|
+
const raw = JSON.parse(await readFile83(path162.resolve(projectRoot, file), "utf8"));
|
|
71258
71622
|
return typeof raw.k === "number" && raw.k > 0 ? raw.k : 3;
|
|
71259
71623
|
}
|
|
71260
71624
|
async function runMemoryLayer(projectRoot, args2, ladder) {
|
|
@@ -71291,11 +71655,11 @@ async function runMemoryLayer(projectRoot, args2, ladder) {
|
|
|
71291
71655
|
return result.valid;
|
|
71292
71656
|
}
|
|
71293
71657
|
async function loadWikiGoldK(projectRoot, file) {
|
|
71294
|
-
const raw = JSON.parse(await
|
|
71658
|
+
const raw = JSON.parse(await readFile83(path162.resolve(projectRoot, file), "utf8"));
|
|
71295
71659
|
return typeof raw.k === "number" && raw.k > 0 ? raw.k : 5;
|
|
71296
71660
|
}
|
|
71297
71661
|
async function loadWikiGroundedness(projectRoot, file) {
|
|
71298
|
-
const raw = JSON.parse(await
|
|
71662
|
+
const raw = JSON.parse(await readFile83(path162.resolve(projectRoot, file), "utf8"));
|
|
71299
71663
|
const map = new Map;
|
|
71300
71664
|
for (const entry of raw.targets ?? []) {
|
|
71301
71665
|
if (typeof entry.target !== "string" || !Array.isArray(entry.scores) || entry.scores.length !== 3)
|
|
@@ -71351,7 +71715,7 @@ async function runWikiLayer(projectRoot, args2, ladder) {
|
|
|
71351
71715
|
return result.valid;
|
|
71352
71716
|
}
|
|
71353
71717
|
async function loadGdctxFacts(projectRoot, file) {
|
|
71354
|
-
const raw = JSON.parse(await
|
|
71718
|
+
const raw = JSON.parse(await readFile83(path162.resolve(projectRoot, file), "utf8"));
|
|
71355
71719
|
const inputs = [];
|
|
71356
71720
|
for (const entry of raw.inputs ?? []) {
|
|
71357
71721
|
if (typeof entry.input === "string") {
|
|
@@ -71389,7 +71753,7 @@ async function collect(projectRoot, args2) {
|
|
|
71389
71753
|
process.exitCode = 1;
|
|
71390
71754
|
return;
|
|
71391
71755
|
}
|
|
71392
|
-
const raw = JSON.parse(await
|
|
71756
|
+
const raw = JSON.parse(await readFile83(path162.resolve(projectRoot, eventFile), "utf8"));
|
|
71393
71757
|
const events2 = Array.isArray(raw) ? raw : raw.events;
|
|
71394
71758
|
const startedAt = optionValue(args2, "--started-at") ?? events2[0]?.timestamp_utc ?? new Date().toISOString();
|
|
71395
71759
|
const finishedAt = optionValue(args2, "--finished-at") ?? events2.at(-1)?.timestamp_utc ?? startedAt;
|
|
@@ -71789,6 +72153,7 @@ function renderCatchUp(report, includeLifecycleFlags = true) {
|
|
|
71789
72153
|
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
72154
|
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
72155
|
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.`));
|
|
72156
|
+
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
72157
|
if (includeLifecycleFlags) {
|
|
71793
72158
|
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
72159
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mrciphersmith/keryx",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.56",
|
|
4
4
|
"description": "Version-controlled project context for AI coding agents: code graph, architecture wiki, project memory, relevant tests, quality signals, and task flows.",
|
|
5
5
|
"private": false,
|
|
6
6
|
"publishConfig": {
|