@mrciphersmith/keryx 0.2.130 → 0.2.132
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 +138 -17
- package/dist/core.js +48 -4
- package/package.json +1 -1
- package/src/gdgraph/staleness.ts +21 -0
package/dist/cli.js
CHANGED
|
@@ -24338,6 +24338,49 @@ var init_trusted_wrap_up = __esm(() => {
|
|
|
24338
24338
|
consumed = new WeakSet;
|
|
24339
24339
|
});
|
|
24340
24340
|
|
|
24341
|
+
// src/sac/workspace-reference.ts
|
|
24342
|
+
function requireWorkspaceReference(raw) {
|
|
24343
|
+
const normalized = normalizeWorkspaceReference(raw);
|
|
24344
|
+
if (!normalized.ok)
|
|
24345
|
+
throw new Error(normalized.reason);
|
|
24346
|
+
return normalized.uri;
|
|
24347
|
+
}
|
|
24348
|
+
function normalizeWorkspaceReference(raw) {
|
|
24349
|
+
const trimmed = raw.trim();
|
|
24350
|
+
if (trimmed.length === 0) {
|
|
24351
|
+
return { ok: false, reason: 'a workspace reference must be a non-empty workspace-relative path, e.g. "./src/harness/search"' };
|
|
24352
|
+
}
|
|
24353
|
+
if (UNSAFE_ABSOLUTE.test(trimmed) || UNSAFE_SCHEME.test(trimmed) || trimmed.includes("\\")) {
|
|
24354
|
+
return {
|
|
24355
|
+
ok: false,
|
|
24356
|
+
reason: `"${trimmed}" is not a workspace-relative path \u2014 an absolute path, a URL and a Windows path are all refused. Use a path inside the project, e.g. "./src/harness/search".`
|
|
24357
|
+
};
|
|
24358
|
+
}
|
|
24359
|
+
const withoutDotSlash = trimmed.startsWith("./") ? trimmed.slice(2) : trimmed;
|
|
24360
|
+
const withoutTrailingSlash = withoutDotSlash.replace(/\/+$/, "");
|
|
24361
|
+
if (withoutTrailingSlash.length === 0) {
|
|
24362
|
+
return { ok: false, reason: `"${trimmed}" names the project root rather than a path inside it \u2014 name a directory or file, e.g. "./src/harness/search".` };
|
|
24363
|
+
}
|
|
24364
|
+
const segments = withoutTrailingSlash.split("/");
|
|
24365
|
+
if (segments.includes("..") || segments.includes(".")) {
|
|
24366
|
+
return { ok: false, reason: `"${trimmed}" contains a "." or ".." path segment \u2014 a workspace reference must stay inside the project.` };
|
|
24367
|
+
}
|
|
24368
|
+
const uri = `./${withoutTrailingSlash}`;
|
|
24369
|
+
if (!WORKSPACE_REFERENCE_PATTERN.test(uri)) {
|
|
24370
|
+
return {
|
|
24371
|
+
ok: false,
|
|
24372
|
+
reason: `"${trimmed}" is not a usable workspace reference. The stored form is "${uri}" if the characters are the problem: ` + 'each path segment may contain only letters, digits, ".", "_" and "-", and no ".." segment is allowed.'
|
|
24373
|
+
};
|
|
24374
|
+
}
|
|
24375
|
+
return { ok: true, uri };
|
|
24376
|
+
}
|
|
24377
|
+
var WORKSPACE_REFERENCE_PATTERN, UNSAFE_ABSOLUTE, UNSAFE_SCHEME;
|
|
24378
|
+
var init_workspace_reference = __esm(() => {
|
|
24379
|
+
WORKSPACE_REFERENCE_PATTERN = /^\.\/(?!(?:.*\/)?\.\.(?:\/|$))(?:[A-Za-z0-9._-]+\/)*[A-Za-z0-9._-]+$/;
|
|
24380
|
+
UNSAFE_ABSOLUTE = /^[/\\]/;
|
|
24381
|
+
UNSAFE_SCHEME = /^[a-z][a-z0-9+.-]*:/i;
|
|
24382
|
+
});
|
|
24383
|
+
|
|
24341
24384
|
// src/sac/index.ts
|
|
24342
24385
|
import { access as access2, readFile as readFile50, realpath as realpath6 } from "fs/promises";
|
|
24343
24386
|
import { createHash as createHash18 } from "crypto";
|
|
@@ -25085,11 +25128,12 @@ async function evaluateStrictSacGuard(input) {
|
|
|
25085
25128
|
}
|
|
25086
25129
|
var idPattern, subjectPattern, revisionPattern, correlationPattern, workspacePathPattern, receiptHashPattern, utcPattern, forbiddenPayloadKeys, normativeSchemaFiles, normativeSchemas, NORMATIVE_SCHEMA_DIR, SacReferenceError, trustedActors, roleRank;
|
|
25087
25130
|
var init_sac = __esm(() => {
|
|
25131
|
+
init_workspace_reference();
|
|
25088
25132
|
idPattern = /^[a-z][a-z0-9-]{2,63}$/;
|
|
25089
25133
|
subjectPattern = /^(?:user|team|service|agent):[a-z0-9][a-z0-9._-]{0,127}$/;
|
|
25090
25134
|
revisionPattern = /^[A-Za-z0-9][A-Za-z0-9._:-]{0,255}$/;
|
|
25091
25135
|
correlationPattern = /^[A-Za-z0-9][A-Za-z0-9._:-]{15,255}$/;
|
|
25092
|
-
workspacePathPattern =
|
|
25136
|
+
workspacePathPattern = WORKSPACE_REFERENCE_PATTERN;
|
|
25093
25137
|
receiptHashPattern = /^[a-f0-9]{64}$/;
|
|
25094
25138
|
utcPattern = /^(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2}):(\d{2})(?:\.(\d+))?Z$/;
|
|
25095
25139
|
forbiddenPayloadKeys = new Set(["prompt", "transcript", "hiddenReasoning", "secret", "secrets", "rawContent"]);
|
|
@@ -51085,7 +51129,7 @@ import path47 from "path";
|
|
|
51085
51129
|
// package.json
|
|
51086
51130
|
var package_default = {
|
|
51087
51131
|
name: "@mrciphersmith/keryx",
|
|
51088
|
-
version: "0.2.
|
|
51132
|
+
version: "0.2.132",
|
|
51089
51133
|
description: "Version-controlled project context for AI coding agents: code graph, architecture wiki, project memory, relevant tests, quality signals, and task flows.",
|
|
51090
51134
|
private: false,
|
|
51091
51135
|
publishConfig: {
|
|
@@ -59054,7 +59098,7 @@ init_provenance();
|
|
|
59054
59098
|
|
|
59055
59099
|
// src/sync/diff.ts
|
|
59056
59100
|
init_provenance();
|
|
59057
|
-
var CODE_EXT2 = /\.(ts|tsx|js|jsx|mjs|cjs|py|go|rs|java|rb|php|c|h|cc|cpp|hpp|cs|swift|kt|scala|sh|vue|svelte)$/;
|
|
59101
|
+
var CODE_EXT2 = /\.(ts|tsx|mts|cts|js|jsx|mjs|cjs|py|go|rs|java|rb|php|c|h|cc|cpp|hpp|cs|swift|kt|scala|sh|vue|svelte)$/;
|
|
59058
59102
|
function isCodeFile(file) {
|
|
59059
59103
|
return CODE_EXT2.test(file);
|
|
59060
59104
|
}
|
|
@@ -59176,7 +59220,22 @@ On git pull / branch switch, they run 'keryx sync' (advisory report). Run 'keryx
|
|
|
59176
59220
|
}
|
|
59177
59221
|
const code = codeOnly(diff);
|
|
59178
59222
|
if (totalChanges(code) === 0) {
|
|
59179
|
-
|
|
59223
|
+
if (apply && provenance.commit !== head.commit) {
|
|
59224
|
+
if (module === "gdwiki") {
|
|
59225
|
+
const gate = await resolveWikiSourceGate(cwd, HEAD_NOT_REQUESTED);
|
|
59226
|
+
if (gate.status !== "fresh") {
|
|
59227
|
+
printApplyOutcome({ recorded: false, gate }, "", ` up to date (built at ${provenance.commit.slice(0, 8)}); HEAD moved to ${head.commit.slice(0, 8)} with no code changes, but provenance NOT advanced`);
|
|
59228
|
+
console.log("");
|
|
59229
|
+
continue;
|
|
59230
|
+
}
|
|
59231
|
+
}
|
|
59232
|
+
await recordProvenance(cwd, module, at);
|
|
59233
|
+
console.log(` up to date (built at ${provenance.commit.slice(0, 8)}); no code changed \u2014 provenance advanced to ${head.commit.slice(0, 8)}`);
|
|
59234
|
+
} else if (provenance.commit !== head.commit) {
|
|
59235
|
+
console.log(` up to date (built at ${provenance.commit.slice(0, 8)}); HEAD moved to ${head.commit.slice(0, 8)} with no code changes \u2014 run \`keryx sync --apply\` to advance provenance`);
|
|
59236
|
+
} else {
|
|
59237
|
+
console.log(` up to date (built at ${provenance.commit.slice(0, 8)})`);
|
|
59238
|
+
}
|
|
59180
59239
|
console.log("");
|
|
59181
59240
|
continue;
|
|
59182
59241
|
}
|
|
@@ -78293,6 +78352,7 @@ function toMcpTools(ops = METAPROJECT_OPERATIONS, adapterFor = (cwd) => createMe
|
|
|
78293
78352
|
}
|
|
78294
78353
|
|
|
78295
78354
|
// src/sac/fwk-service.ts
|
|
78355
|
+
init_workspace_reference();
|
|
78296
78356
|
init_assembly();
|
|
78297
78357
|
init_sac();
|
|
78298
78358
|
init_workspace_service();
|
|
@@ -78303,6 +78363,7 @@ import { appendFile as appendFile7, mkdir as mkdir59, open as open2, readFile as
|
|
|
78303
78363
|
import path174 from "path";
|
|
78304
78364
|
|
|
78305
78365
|
// src/sac/policy-experiment.ts
|
|
78366
|
+
init_workspace_reference();
|
|
78306
78367
|
import { createHash as createHash34, createHmac as createHmac2 } from "crypto";
|
|
78307
78368
|
|
|
78308
78369
|
// src/sac/receipt-integrity.ts
|
|
@@ -78366,7 +78427,7 @@ function stableValue(value) {
|
|
|
78366
78427
|
}
|
|
78367
78428
|
var stableJson2 = (value) => JSON.stringify(stableValue(value));
|
|
78368
78429
|
var sha2564 = (value) => createHash34("sha256").update(stableJson2(value), "utf8").digest("hex");
|
|
78369
|
-
var workspacePathPattern2 =
|
|
78430
|
+
var workspacePathPattern2 = WORKSPACE_REFERENCE_PATTERN;
|
|
78370
78431
|
var trustedOutcomes = new WeakMap;
|
|
78371
78432
|
var trustedSandboxEvidence = new WeakMap;
|
|
78372
78433
|
var trustedSandboxExecutions = new WeakMap;
|
|
@@ -78458,7 +78519,7 @@ var metadataOnly = (value) => {
|
|
|
78458
78519
|
var nowIso2 = (now) => now().toISOString();
|
|
78459
78520
|
var reportHashPattern = /^[a-f0-9]{64}$/;
|
|
78460
78521
|
var immutableVersionPattern = /(?:^|[-_.:])(?:latest|next|head|main|develop)(?:$|[-_.:])/i;
|
|
78461
|
-
var workspacePathPattern3 =
|
|
78522
|
+
var workspacePathPattern3 = WORKSPACE_REFERENCE_PATTERN;
|
|
78462
78523
|
var policyExperimentConfigPath = [".metaproject", "context-operations", "policy-experiment", "config.json"];
|
|
78463
78524
|
var stableValue2 = (value) => {
|
|
78464
78525
|
if (Array.isArray(value))
|
|
@@ -79333,6 +79394,7 @@ async function reclaimStaleExternalSlates(cwd, now = () => new Date) {
|
|
|
79333
79394
|
init_slate();
|
|
79334
79395
|
init_workspace_resolve();
|
|
79335
79396
|
init_redact();
|
|
79397
|
+
init_workspace_reference();
|
|
79336
79398
|
|
|
79337
79399
|
// src/mcp/tools.ts
|
|
79338
79400
|
import { randomUUID as randomUUID19 } from "crypto";
|
|
@@ -79545,7 +79607,8 @@ function buildToolRegistry() {
|
|
|
79545
79607
|
const title = stringParam2(params, "title")?.trim() ?? "";
|
|
79546
79608
|
if (title.length === 0)
|
|
79547
79609
|
throw new Error("sac.workspaceCreate requires a non-empty 'title'");
|
|
79548
|
-
const
|
|
79610
|
+
const rawComponent = stringParam2(params, "component");
|
|
79611
|
+
const component = rawComponent === undefined ? undefined : requireWorkspaceReference(rawComponent);
|
|
79549
79612
|
const workspace = await sacWorkspaceService(cwd).create({ request: undefined, requestCorrelationId: randomUUID19(), id: newWorkspaceId(), title, ...component ? { component: { kind: "component", uri: component } } : {} });
|
|
79550
79613
|
return workspace;
|
|
79551
79614
|
}
|
|
@@ -87467,7 +87530,12 @@ function workspaceCreateTool(cwd, getSessionDir) {
|
|
|
87467
87530
|
const title = typeof input.title === "string" ? input.title.trim() : "";
|
|
87468
87531
|
if (title.length === 0)
|
|
87469
87532
|
return { output: "workspace_create requires a non-empty 'title'", isError: true };
|
|
87470
|
-
const
|
|
87533
|
+
const rawComponent = typeof input.component === "string" && input.component.length > 0 ? input.component : undefined;
|
|
87534
|
+
const normalizedComponent = rawComponent === undefined ? undefined : normalizeWorkspaceReference(rawComponent);
|
|
87535
|
+
if (normalizedComponent !== undefined && !normalizedComponent.ok) {
|
|
87536
|
+
return { output: "workspace_create: " + normalizedComponent.reason, isError: true };
|
|
87537
|
+
}
|
|
87538
|
+
const component = normalizedComponent?.ok === true ? normalizedComponent.uri : undefined;
|
|
87471
87539
|
try {
|
|
87472
87540
|
const workspace = await service5(cwd).create({
|
|
87473
87541
|
request: undefined,
|
|
@@ -93273,14 +93341,25 @@ async function classifySession(session) {
|
|
|
93273
93341
|
sessionId: session.id,
|
|
93274
93342
|
...workspaceId !== undefined ? { workspaceId } : {},
|
|
93275
93343
|
lastSeenAt: session.updatedAt,
|
|
93344
|
+
reason: "wrap-up-failed",
|
|
93276
93345
|
wrapUpOutcome: { trigger: wrapUpOutcome.trigger, generatedAt: wrapUpOutcome.generatedAt, groups: wrapUpOutcome.groups }
|
|
93277
93346
|
}
|
|
93278
93347
|
};
|
|
93279
93348
|
}
|
|
93280
93349
|
if (!await isSlateEngaged(dir))
|
|
93281
93350
|
return;
|
|
93282
|
-
const
|
|
93283
|
-
|
|
93351
|
+
const slateRead = await readSlateState(dir);
|
|
93352
|
+
const workspaceId = slateRead.state === "ok" ? slateRead.slate.workspaceId : undefined;
|
|
93353
|
+
return {
|
|
93354
|
+
kind: "unknown",
|
|
93355
|
+
item: {
|
|
93356
|
+
type: "unknown",
|
|
93357
|
+
sessionId: session.id,
|
|
93358
|
+
...workspaceId !== undefined ? { workspaceId } : {},
|
|
93359
|
+
lastSeenAt: session.updatedAt,
|
|
93360
|
+
reason: slateRead.state === "unreadable" ? "slate-unreadable" : "no-resolution-recorded"
|
|
93361
|
+
}
|
|
93362
|
+
};
|
|
93284
93363
|
}
|
|
93285
93364
|
async function readExternalUnboundCandidates(cwd) {
|
|
93286
93365
|
const extDir = externalSlatesDir(cwd);
|
|
@@ -93533,6 +93612,17 @@ async function isSlateEngaged(dir) {
|
|
|
93533
93612
|
return false;
|
|
93534
93613
|
}
|
|
93535
93614
|
}
|
|
93615
|
+
async function readSlateState(dir) {
|
|
93616
|
+
try {
|
|
93617
|
+
const slate = await readSlate(dir);
|
|
93618
|
+
return slate === undefined ? { state: "absent" } : { state: "ok", slate };
|
|
93619
|
+
} catch (error) {
|
|
93620
|
+
return isNotFound2(error) ? { state: "absent" } : { state: "unreadable" };
|
|
93621
|
+
}
|
|
93622
|
+
}
|
|
93623
|
+
function isNotFound2(error) {
|
|
93624
|
+
return typeof error === "object" && error !== null && error.code === "ENOENT";
|
|
93625
|
+
}
|
|
93536
93626
|
async function safeReadSlate(dir) {
|
|
93537
93627
|
try {
|
|
93538
93628
|
return await readSlate(dir);
|
|
@@ -94498,7 +94588,7 @@ function summarizeReviewItem(item) {
|
|
|
94498
94588
|
case "unbound-candidate":
|
|
94499
94589
|
return `${item.sessionId} \u2014 ${item.summary}`;
|
|
94500
94590
|
case "unknown":
|
|
94501
|
-
return `${item.sessionId} \u2014 last seen ${item.lastSeenAt}`;
|
|
94591
|
+
return `${item.sessionId} \u2014 ${unknownReasonLabel(item.reason)} (last seen ${item.lastSeenAt})`;
|
|
94502
94592
|
}
|
|
94503
94593
|
}
|
|
94504
94594
|
function formatReviewListLines(items, selected) {
|
|
@@ -94510,6 +94600,26 @@ function formatReviewListLines(items, selected) {
|
|
|
94510
94600
|
return `${mark} ${TYPE_LABEL[item.type].padEnd(8)} ${summarizeReviewItem(item)}`;
|
|
94511
94601
|
});
|
|
94512
94602
|
}
|
|
94603
|
+
function unknownReasonLabel(reason) {
|
|
94604
|
+
switch (reason) {
|
|
94605
|
+
case "wrap-up-failed":
|
|
94606
|
+
return "wrap-up failed";
|
|
94607
|
+
case "slate-unreadable":
|
|
94608
|
+
return "slate.json unreadable";
|
|
94609
|
+
case "no-resolution-recorded":
|
|
94610
|
+
return "no resolution recorded";
|
|
94611
|
+
}
|
|
94612
|
+
}
|
|
94613
|
+
function unknownReasonDetail(reason) {
|
|
94614
|
+
switch (reason) {
|
|
94615
|
+
case "wrap-up-failed":
|
|
94616
|
+
return "a wrap-up dispatch ran and every group failed - the per-kind reasons are above where recorded; re-run wrap-up after fixing them.";
|
|
94617
|
+
case "slate-unreadable":
|
|
94618
|
+
return "this session's slate.json exists but could not be read. Its record is DAMAGED, not absent: inspect the file before assuming nothing happened.";
|
|
94619
|
+
case "no-resolution-recorded":
|
|
94620
|
+
return "Slate engagement with no proposal, terminal state, unbound-candidate or wrap-up-outcome artifact recorded.";
|
|
94621
|
+
}
|
|
94622
|
+
}
|
|
94513
94623
|
function describeReviewItem(item) {
|
|
94514
94624
|
switch (item.type) {
|
|
94515
94625
|
case "proposal":
|
|
@@ -94551,7 +94661,7 @@ function describeReviewItem(item) {
|
|
|
94551
94661
|
] : [
|
|
94552
94662
|
`Session ${item.sessionId}${item.workspaceId !== undefined ? ` (workspace ${item.workspaceId})` : ""}`,
|
|
94553
94663
|
`Last seen ${item.lastSeenAt}`,
|
|
94554
|
-
"
|
|
94664
|
+
"Why unknown: " + unknownReasonDetail(item.reason),
|
|
94555
94665
|
"",
|
|
94556
94666
|
`Investigate: keryx sessions list / keryx shell -r ${item.sessionId}`
|
|
94557
94667
|
];
|
|
@@ -115928,7 +116038,8 @@ async function workspaceCommand(args) {
|
|
|
115928
116038
|
if (subcommand === "create") {
|
|
115929
116039
|
rejectUnknownOptions(args.slice(1), new Set(["--title", "--component"]));
|
|
115930
116040
|
const title = optionValue(args, "--title");
|
|
115931
|
-
const
|
|
116041
|
+
const rawComponent = optionValue(args, "--component");
|
|
116042
|
+
const component = rawComponent === undefined ? undefined : requireWorkspaceReference(rawComponent);
|
|
115932
116043
|
if (!title)
|
|
115933
116044
|
throw new Error("Usage: keryx workspace create --title <title> [--component <workspace-relative-ref>]");
|
|
115934
116045
|
const workspace = await service6().create({ request: undefined, requestCorrelationId: randomUUID40(), id: newWorkspaceId(), title, ...component ? { component: { kind: "component", uri: component } } : {} });
|
|
@@ -115957,11 +116068,11 @@ async function workspaceCommand(args) {
|
|
|
115957
116068
|
rejectUnknownOptions(args.slice(2), new Set(["--kind", "--uri", "--revision"]));
|
|
115958
116069
|
const workspaceId = args[1];
|
|
115959
116070
|
const kind = optionValue(args, "--kind");
|
|
115960
|
-
const
|
|
116071
|
+
const rawUri = optionValue(args, "--uri");
|
|
115961
116072
|
const revision = optionValue(args, "--revision");
|
|
115962
|
-
if (!workspaceId || !kind || !
|
|
116073
|
+
if (!workspaceId || !kind || !rawUri)
|
|
115963
116074
|
throw new Error("Usage: keryx workspace add-resource <workspace-id> --kind <kind> --uri <workspace-relative-ref> [--revision <revision>]");
|
|
115964
|
-
console.log(JSON.stringify(await service6().addResource({ request: undefined, requestCorrelationId: randomUUID40(), workspaceId, resource: { kind, uri, ...revision ? { revision } : {} } }), null, 2));
|
|
116075
|
+
console.log(JSON.stringify(await service6().addResource({ request: undefined, requestCorrelationId: randomUUID40(), workspaceId, resource: { kind, uri: requireWorkspaceReference(rawUri), ...revision ? { revision } : {} } }), null, 2));
|
|
115965
116076
|
return;
|
|
115966
116077
|
}
|
|
115967
116078
|
if (subcommand === "archive") {
|
|
@@ -116235,7 +116346,7 @@ function renderCatchUp(report, includeLifecycleFlags = true) {
|
|
|
116235
116346
|
sections.push(renderSection("Pending proposals", report.proposals, (item) => `- Accept, reject, or dismiss ${item.kind} proposal ${item.proposalId} in workspace ${item.workspaceId}` + `${item.note !== undefined ? `: "${item.note}"` : ""}? ` + `Recommendation: ${item.fresh ? "evidence is fresh \u2014 review now (`keryx workspace review " + item.workspaceId + " " + item.proposalId + " --decision <accepted|rejected|dismissed>`)" : "evidence has drifted since this proposal was created \u2014 treat as stale, re-run wrap-up before deciding"}.`));
|
|
116236
116347
|
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.`));
|
|
116237
116348
|
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}).`));
|
|
116238
|
-
sections.push(renderSection("Unknown (no resolution recorded)", report.unknown, (item) => `- Session ${item.sessionId} was last seen ${item.lastSeenAt}
|
|
116349
|
+
sections.push(renderSection("Unknown (no resolution recorded)", report.unknown, (item) => `- Session ${item.sessionId} was last seen ${item.lastSeenAt}: ${describeUnknownReason(item)}. Investigate, or ignore? ` + `Recommendation: \`keryx sessions list\` / \`keryx shell -r ${item.sessionId}\` to see what happened.`));
|
|
116239
116350
|
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.`));
|
|
116240
116351
|
if (includeLifecycleFlags) {
|
|
116241
116352
|
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.`));
|
|
@@ -116244,6 +116355,16 @@ function renderCatchUp(report, includeLifecycleFlags = true) {
|
|
|
116244
116355
|
|
|
116245
116356
|
`);
|
|
116246
116357
|
}
|
|
116358
|
+
function describeUnknownReason(item) {
|
|
116359
|
+
switch (item.reason) {
|
|
116360
|
+
case "wrap-up-failed":
|
|
116361
|
+
return "a wrap-up dispatch ran and every group failed (re-run it after fixing credentials/conflicts)";
|
|
116362
|
+
case "slate-unreadable":
|
|
116363
|
+
return "its slate.json is present but could not be read - the session's own record is damaged, not merely unrecorded";
|
|
116364
|
+
case "no-resolution-recorded":
|
|
116365
|
+
return "it has Slate engagement but no proposal, terminal state, unbound-candidate or wrap-up-outcome artifact";
|
|
116366
|
+
}
|
|
116367
|
+
}
|
|
116247
116368
|
function renderSection(title, items, describe) {
|
|
116248
116369
|
const lines = [`== ${title} ==`];
|
|
116249
116370
|
if (items.length === 0)
|
package/dist/core.js
CHANGED
|
@@ -20387,10 +20387,12 @@ __export(exports_service9, {
|
|
|
20387
20387
|
normalizeCollaborationResult: () => normalizeCollaborationResult,
|
|
20388
20388
|
normalizeFwkResult: () => normalizeFwkResult,
|
|
20389
20389
|
normalizeProposalLifecycleResult: () => normalizeProposalLifecycleResult,
|
|
20390
|
+
normalizeWorkspaceReference: () => normalizeWorkspaceReference,
|
|
20390
20391
|
proposalNotePath: () => proposalNotePath,
|
|
20391
20392
|
readExternalSlate: () => readExternalSlate,
|
|
20392
20393
|
reclaimStaleExternalSlates: () => reclaimStaleExternalSlates,
|
|
20393
20394
|
redactSensitiveText: () => redactSensitiveText,
|
|
20395
|
+
requireWorkspaceReference: () => requireWorkspaceReference,
|
|
20394
20396
|
resolveOrCreateWorkspace: () => resolveOrCreateWorkspace,
|
|
20395
20397
|
resolvePolicyExperiment: () => resolvePolicyExperiment,
|
|
20396
20398
|
rollbackPolicyExperiment: () => rollbackPolicyExperiment,
|
|
@@ -20401,8 +20403,50 @@ __export(exports_service9, {
|
|
|
20401
20403
|
});
|
|
20402
20404
|
|
|
20403
20405
|
// src/sac/fwk-service.ts
|
|
20404
|
-
init_assembly();
|
|
20405
20406
|
import { createHash as createHash15, randomUUID as randomUUID4 } from "crypto";
|
|
20407
|
+
|
|
20408
|
+
// src/sac/workspace-reference.ts
|
|
20409
|
+
var WORKSPACE_REFERENCE_PATTERN = /^\.\/(?!(?:.*\/)?\.\.(?:\/|$))(?:[A-Za-z0-9._-]+\/)*[A-Za-z0-9._-]+$/;
|
|
20410
|
+
var UNSAFE_ABSOLUTE = /^[/\\]/;
|
|
20411
|
+
var UNSAFE_SCHEME = /^[a-z][a-z0-9+.-]*:/i;
|
|
20412
|
+
function requireWorkspaceReference(raw) {
|
|
20413
|
+
const normalized = normalizeWorkspaceReference(raw);
|
|
20414
|
+
if (!normalized.ok)
|
|
20415
|
+
throw new Error(normalized.reason);
|
|
20416
|
+
return normalized.uri;
|
|
20417
|
+
}
|
|
20418
|
+
function normalizeWorkspaceReference(raw) {
|
|
20419
|
+
const trimmed = raw.trim();
|
|
20420
|
+
if (trimmed.length === 0) {
|
|
20421
|
+
return { ok: false, reason: 'a workspace reference must be a non-empty workspace-relative path, e.g. "./src/harness/search"' };
|
|
20422
|
+
}
|
|
20423
|
+
if (UNSAFE_ABSOLUTE.test(trimmed) || UNSAFE_SCHEME.test(trimmed) || trimmed.includes("\\")) {
|
|
20424
|
+
return {
|
|
20425
|
+
ok: false,
|
|
20426
|
+
reason: `"${trimmed}" is not a workspace-relative path \u2014 an absolute path, a URL and a Windows path are all refused. Use a path inside the project, e.g. "./src/harness/search".`
|
|
20427
|
+
};
|
|
20428
|
+
}
|
|
20429
|
+
const withoutDotSlash = trimmed.startsWith("./") ? trimmed.slice(2) : trimmed;
|
|
20430
|
+
const withoutTrailingSlash = withoutDotSlash.replace(/\/+$/, "");
|
|
20431
|
+
if (withoutTrailingSlash.length === 0) {
|
|
20432
|
+
return { ok: false, reason: `"${trimmed}" names the project root rather than a path inside it \u2014 name a directory or file, e.g. "./src/harness/search".` };
|
|
20433
|
+
}
|
|
20434
|
+
const segments = withoutTrailingSlash.split("/");
|
|
20435
|
+
if (segments.includes("..") || segments.includes(".")) {
|
|
20436
|
+
return { ok: false, reason: `"${trimmed}" contains a "." or ".." path segment \u2014 a workspace reference must stay inside the project.` };
|
|
20437
|
+
}
|
|
20438
|
+
const uri = `./${withoutTrailingSlash}`;
|
|
20439
|
+
if (!WORKSPACE_REFERENCE_PATTERN.test(uri)) {
|
|
20440
|
+
return {
|
|
20441
|
+
ok: false,
|
|
20442
|
+
reason: `"${trimmed}" is not a usable workspace reference. The stored form is "${uri}" if the characters are the problem: ` + 'each path segment may contain only letters, digits, ".", "_" and "-", and no ".." segment is allowed.'
|
|
20443
|
+
};
|
|
20444
|
+
}
|
|
20445
|
+
return { ok: true, uri };
|
|
20446
|
+
}
|
|
20447
|
+
|
|
20448
|
+
// src/sac/fwk-service.ts
|
|
20449
|
+
init_assembly();
|
|
20406
20450
|
import { appendFile as appendFile5, mkdir as mkdir28, open as open2, readFile as readFile40, rename as rename6, rm as rm5, stat as stat9, writeFile as writeFile21 } from "fs/promises";
|
|
20407
20451
|
import path78 from "path";
|
|
20408
20452
|
|
|
@@ -20415,7 +20459,7 @@ var idPattern = /^[a-z][a-z0-9-]{2,63}$/;
|
|
|
20415
20459
|
var subjectPattern = /^(?:user|team|service|agent):[a-z0-9][a-z0-9._-]{0,127}$/;
|
|
20416
20460
|
var revisionPattern = /^[A-Za-z0-9][A-Za-z0-9._:-]{0,255}$/;
|
|
20417
20461
|
var correlationPattern = /^[A-Za-z0-9][A-Za-z0-9._:-]{15,255}$/;
|
|
20418
|
-
var workspacePathPattern =
|
|
20462
|
+
var workspacePathPattern = WORKSPACE_REFERENCE_PATTERN;
|
|
20419
20463
|
var receiptHashPattern = /^[a-f0-9]{64}$/;
|
|
20420
20464
|
var utcPattern = /^(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2}):(\d{2})(?:\.(\d+))?Z$/;
|
|
20421
20465
|
var forbiddenPayloadKeys = new Set(["prompt", "transcript", "hiddenReasoning", "secret", "secrets", "rawContent"]);
|
|
@@ -21661,7 +21705,7 @@ var stableJson2 = (value) => JSON.stringify(stableValue(value));
|
|
|
21661
21705
|
var sha2562 = (value) => createHash14("sha256").update(stableJson2(value), "utf8").digest("hex");
|
|
21662
21706
|
var sha256Content = (value) => createHash14("sha256").update(value).digest("hex");
|
|
21663
21707
|
var pseudonym = (key, value) => createHmac2("sha256", key).update(value, "utf8").digest("hex");
|
|
21664
|
-
var workspacePathPattern2 =
|
|
21708
|
+
var workspacePathPattern2 = WORKSPACE_REFERENCE_PATTERN;
|
|
21665
21709
|
var trustedOutcomes = new WeakMap;
|
|
21666
21710
|
var trustedSandboxEvidence = new WeakMap;
|
|
21667
21711
|
var trustedSandboxExecutions = new WeakMap;
|
|
@@ -22138,7 +22182,7 @@ var metadataOnly = (value) => {
|
|
|
22138
22182
|
var nowIso = (now) => now().toISOString();
|
|
22139
22183
|
var reportHashPattern = /^[a-f0-9]{64}$/;
|
|
22140
22184
|
var immutableVersionPattern = /(?:^|[-_.:])(?:latest|next|head|main|develop)(?:$|[-_.:])/i;
|
|
22141
|
-
var workspacePathPattern3 =
|
|
22185
|
+
var workspacePathPattern3 = WORKSPACE_REFERENCE_PATTERN;
|
|
22142
22186
|
var policyExperimentConfigPath = [".metaproject", "context-operations", "policy-experiment", "config.json"];
|
|
22143
22187
|
var stableValue2 = (value) => {
|
|
22144
22188
|
if (Array.isArray(value))
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mrciphersmith/keryx",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.132",
|
|
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": {
|
package/src/gdgraph/staleness.ts
CHANGED
|
@@ -128,6 +128,27 @@ export async function checkGraphStaleness(cwd: string): Promise<StalenessCheck>
|
|
|
128
128
|
let gitFailed = false;
|
|
129
129
|
|
|
130
130
|
// --- new commit -----------------------------------------------------------
|
|
131
|
+
//
|
|
132
|
+
// Flow 280 (the graph-provenance stall): this comparison is intentionally a
|
|
133
|
+
// bare commit-hash check, and it stays one. The disagreement that flow
|
|
134
|
+
// fixed — a `.metaproject/`-only commit left `keryx sync` saying "nothing
|
|
135
|
+
// to rebuild" while THIS check said "stale" forever, because nothing ever
|
|
136
|
+
// advanced the provenance commit this reads — was not a defect in this
|
|
137
|
+
// comparison. `checkGraphStaleness` has no notion of "since provenance was
|
|
138
|
+
// last recorded" and no per-commit diff to reason from; it only ever sees
|
|
139
|
+
// the two endpoints (`provenance.commit`, `head.commit`). Rederiving "did
|
|
140
|
+
// any code file change between them" here would mean re-running the same
|
|
141
|
+
// diff `../sync/diff.ts` already computes, in a module whose contract is a
|
|
142
|
+
// cheap, dependency-light freshness probe (staleness.test.ts, AFC-10) —
|
|
143
|
+
// and would leave that diff computed twice, once here and once in sync's
|
|
144
|
+
// own loop, with the two free to drift. The fix instead makes the ONE place
|
|
145
|
+
// that already has the diff (`../commands/sync.ts`'s per-module loop)
|
|
146
|
+
// advance provenance to HEAD when that diff is empty, so this check's
|
|
147
|
+
// simple "did the commit move" question starts answering "no" again — which
|
|
148
|
+
// is also why it is correct for this to stay a hard equality check: once
|
|
149
|
+
// provenance is honestly advanced, an ADVANCING mismatch here really does
|
|
150
|
+
// mean the graph predates HEAD, and this is exactly the check the wiki gate
|
|
151
|
+
// (`../wiki/staleness.ts`) needs to keep trusting for that.
|
|
131
152
|
const head = await gitHead(cwd);
|
|
132
153
|
if (head === null) {
|
|
133
154
|
gitFailed = true;
|