@mrciphersmith/keryx 0.2.69 → 0.2.70
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 +951 -359
- package/package.json +1 -1
- package/src/gdgraph/build-lang.test.ts +10 -3
- package/src/gdgraph/build.ts +54 -9
- package/src/gdgraph/import-kind.test.ts +205 -0
- package/src/gdgraph/query.ts +6 -1
- package/src/gdgraph/types.ts +34 -0
- package/src/gdskills/bundled/rules/core/subagent-status-protocol.md +27 -1
- package/src/gdskills/bundled/skills/orchestration/flow-orchestrator/SKILL.md +88 -8
- package/src/gdskills/bundled/skills/orchestration/task-implementer/SKILL.md +1 -1
- package/src/gdskills/bundled/skills/review/review-orchestrator/SKILL.md +69 -43
package/dist/cli.js
CHANGED
|
@@ -5648,6 +5648,9 @@ var init_command = __esm(() => {
|
|
|
5648
5648
|
init_pull();
|
|
5649
5649
|
});
|
|
5650
5650
|
|
|
5651
|
+
// src/gdgraph/types.ts
|
|
5652
|
+
var UNKNOWN_IMPORT_KIND = "unknown-static";
|
|
5653
|
+
|
|
5651
5654
|
// src/gdgraph/config.ts
|
|
5652
5655
|
import path47 from "path";
|
|
5653
5656
|
function gdgraphConfigPath(cwd) {
|
|
@@ -6287,8 +6290,8 @@ async function buildGraph(projectRoot) {
|
|
|
6287
6290
|
const language = getLanguage(file);
|
|
6288
6291
|
const resolver = pickResolver(resolvers, language);
|
|
6289
6292
|
const isLanguageAware = language === "java" || language === "python";
|
|
6290
|
-
const
|
|
6291
|
-
for (const specifier of
|
|
6293
|
+
const records = extractImportRecords(content, language);
|
|
6294
|
+
for (const { specifier, kind: importKind } of records) {
|
|
6292
6295
|
const resolved = resolveImport(projectRoot, file, specifier, fileSet, resolver);
|
|
6293
6296
|
const asset = resolved ? null : resolveAssetImport(projectRoot, file, specifier, resolver);
|
|
6294
6297
|
const shouldTrackUnresolved = specifier.startsWith(".") || resolver.matchesAlias(specifier) || isLanguageAware;
|
|
@@ -6308,7 +6311,8 @@ async function buildGraph(projectRoot) {
|
|
|
6308
6311
|
from: file,
|
|
6309
6312
|
to: resolved ?? asset ?? specifier,
|
|
6310
6313
|
kind: resolved ? "imports" : asset ? "asset" : "unresolved",
|
|
6311
|
-
specifier
|
|
6314
|
+
specifier,
|
|
6315
|
+
importKind
|
|
6312
6316
|
});
|
|
6313
6317
|
}
|
|
6314
6318
|
}
|
|
@@ -6352,18 +6356,33 @@ async function collectSourceFiles(projectRoot) {
|
|
|
6352
6356
|
await walk3(".");
|
|
6353
6357
|
return { files: result.sort(), skippedDirectories: skippedDirectories.sort() };
|
|
6354
6358
|
}
|
|
6355
|
-
function
|
|
6359
|
+
function extractImportRecords(content, language) {
|
|
6356
6360
|
if (language === "java" || language === "python") {
|
|
6357
|
-
return extractImportSpecifiersFallback(content)
|
|
6361
|
+
return extractImportSpecifiersFallback(content).map((specifier) => ({
|
|
6362
|
+
specifier,
|
|
6363
|
+
kind: UNKNOWN_IMPORT_KIND
|
|
6364
|
+
}));
|
|
6358
6365
|
}
|
|
6359
6366
|
const scanned = scanImportsOrEmpty(content);
|
|
6360
6367
|
const fallback = extractImportSpecifiersFallback(content);
|
|
6361
|
-
|
|
6368
|
+
const kindBySpecifier = new Map;
|
|
6369
|
+
for (const { specifier, kind } of scanned) {
|
|
6370
|
+
const existing = kindBySpecifier.get(specifier);
|
|
6371
|
+
if (!existing || existing === "dynamic-import" && kind !== "dynamic-import") {
|
|
6372
|
+
kindBySpecifier.set(specifier, kind);
|
|
6373
|
+
}
|
|
6374
|
+
}
|
|
6375
|
+
for (const specifier of fallback) {
|
|
6376
|
+
if (!kindBySpecifier.has(specifier)) {
|
|
6377
|
+
kindBySpecifier.set(specifier, UNKNOWN_IMPORT_KIND);
|
|
6378
|
+
}
|
|
6379
|
+
}
|
|
6380
|
+
return [...kindBySpecifier.entries()].map(([specifier, kind]) => ({ specifier, kind })).sort((a, b) => a.specifier.localeCompare(b.specifier));
|
|
6362
6381
|
}
|
|
6363
6382
|
function scanImportsOrEmpty(content) {
|
|
6364
6383
|
try {
|
|
6365
6384
|
const scanner = new Bun.Transpiler({ loader: "tsx" });
|
|
6366
|
-
return scanner.scanImports(content).
|
|
6385
|
+
return scanner.scanImports(content).filter((entry) => typeof entry.path === "string" && entry.path.length > 0).map((entry) => ({ specifier: entry.path, kind: entry.kind }));
|
|
6367
6386
|
} catch {
|
|
6368
6387
|
return [];
|
|
6369
6388
|
}
|
|
@@ -6906,7 +6925,7 @@ function getCycles(graph) {
|
|
|
6906
6925
|
adjacency.set(node.path, []);
|
|
6907
6926
|
}
|
|
6908
6927
|
for (const edge of graph.edges) {
|
|
6909
|
-
if (edge.kind !== "imports") {
|
|
6928
|
+
if (edge.kind !== "imports" || edge.importKind === "dynamic-import") {
|
|
6910
6929
|
continue;
|
|
6911
6930
|
}
|
|
6912
6931
|
adjacency.get(edge.from)?.push(edge.to);
|
|
@@ -19200,8 +19219,8 @@ function buildAgentSystemInstruction(orient, ctx = {}) {
|
|
|
19200
19219
|
` + ` Do NOT thrash search_code/read_wiki instead of wiki enrich.
|
|
19201
19220
|
` + "- Optional prep: `keryx wiki collect` then enrich.\n" + "- Other keryx work (graph, health, memory, flow, testing): use the matching read tool above " + "(graph_affected/graph_query/graph_path/graph_symbol, health_status, memory_search, flow_status, test_related) " + "FIRST \u2014 they return the same data as the equivalent `keryx \u2026` CLI command without spending shell_exec's " + "scarce budget slot. Reach for `shell_exec` with the CLI only when the user wants to actually RUN a workflow " + `(mutate state, kick off a job) or needs an option no read tool covers.
|
|
19202
19221
|
|
|
19203
|
-
` + "ALWAYS use a tool to obtain facts instead of guessing; never fabricate paths, file " + "contents, or results.
|
|
19204
|
-
` + "give the shortest correct answer, prefer bullet points over prose, and omit preamble. " + "Do NOT paste large tool/command output back into your reply \u2014 the compact tool result " + "is already in context; reference it instead of repeating it.";
|
|
19222
|
+
` + "ALWAYS use a tool to obtain facts instead of guessing; never fabricate paths, file " + "contents, or results. " + "If the user asks you to run, inspect, or execute anything, call the relevant tool before " + `sending explanatory text.
|
|
19223
|
+
` + "Be economical with output LENGTH: lead with the conclusion, " + "give the shortest correct answer, prefer bullet points over prose, and omit preamble. " + "That economy governs prose only \u2014 never how many tools you call. " + "Do NOT paste large tool/command output back into your reply \u2014 the compact tool result " + "is already in context; reference it instead of repeating it. When a tool's result is " + "itself the deliverable you are about to report (e.g. a list of cycles, orphans, or " + "dependents) \u2014 not merely an input you go on to reason over \u2014 check it against source " + "before presenting it as fact; do not add this check to every call, only where the " + "result is the answer.";
|
|
19205
19224
|
const trimmed = orient?.trim() ?? "";
|
|
19206
19225
|
if (trimmed.length === 0) {
|
|
19207
19226
|
return base;
|
|
@@ -23554,7 +23573,66 @@ function assertTransition(from, to) {
|
|
|
23554
23573
|
].join(", ") || "(none)"}`);
|
|
23555
23574
|
}
|
|
23556
23575
|
}
|
|
23557
|
-
|
|
23576
|
+
function taskGateStatus(task) {
|
|
23577
|
+
if (task.status !== "done") {
|
|
23578
|
+
return "not-terminal";
|
|
23579
|
+
}
|
|
23580
|
+
return task.disposition === "failed" ? "terminal-fail" : "terminal-pass";
|
|
23581
|
+
}
|
|
23582
|
+
function isUnreasonedSkip(task) {
|
|
23583
|
+
return task.disposition === "skipped" && !task.dispositionReason?.trim();
|
|
23584
|
+
}
|
|
23585
|
+
function isBlockedTask(task) {
|
|
23586
|
+
return task.disposition === "blocked";
|
|
23587
|
+
}
|
|
23588
|
+
function isUnknownDisposition(task) {
|
|
23589
|
+
if (task.disposition === undefined || task.disposition === null) {
|
|
23590
|
+
return false;
|
|
23591
|
+
}
|
|
23592
|
+
if (task.disposition === "failed" || task.disposition === "blocked") {
|
|
23593
|
+
return false;
|
|
23594
|
+
}
|
|
23595
|
+
return !GATE_PASSING_DISPOSITIONS.has(task.disposition);
|
|
23596
|
+
}
|
|
23597
|
+
function evaluateTaskGate(tasks) {
|
|
23598
|
+
const open2 = [];
|
|
23599
|
+
const failed = [];
|
|
23600
|
+
const unreasonedSkips = [];
|
|
23601
|
+
const blocked = [];
|
|
23602
|
+
const unknownDisposition = [];
|
|
23603
|
+
for (const task of tasks) {
|
|
23604
|
+
const status = taskGateStatus(task);
|
|
23605
|
+
if (status === "not-terminal") {
|
|
23606
|
+
open2.push(task.id);
|
|
23607
|
+
continue;
|
|
23608
|
+
}
|
|
23609
|
+
if (status === "terminal-fail") {
|
|
23610
|
+
failed.push(task.id);
|
|
23611
|
+
continue;
|
|
23612
|
+
}
|
|
23613
|
+
if (isUnknownDisposition(task)) {
|
|
23614
|
+
unknownDisposition.push(task.id);
|
|
23615
|
+
continue;
|
|
23616
|
+
}
|
|
23617
|
+
if (isBlockedTask(task)) {
|
|
23618
|
+
blocked.push(task.id);
|
|
23619
|
+
continue;
|
|
23620
|
+
}
|
|
23621
|
+
if (isUnreasonedSkip(task)) {
|
|
23622
|
+
unreasonedSkips.push(task.id);
|
|
23623
|
+
}
|
|
23624
|
+
}
|
|
23625
|
+
return {
|
|
23626
|
+
passed: open2.length === 0 && failed.length === 0 && unreasonedSkips.length === 0 && blocked.length === 0 && unknownDisposition.length === 0,
|
|
23627
|
+
open: open2,
|
|
23628
|
+
failed,
|
|
23629
|
+
unreasonedSkips,
|
|
23630
|
+
blocked,
|
|
23631
|
+
unknownDisposition,
|
|
23632
|
+
total: tasks.length
|
|
23633
|
+
};
|
|
23634
|
+
}
|
|
23635
|
+
var TRANSITIONS, GATE_PASSING_DISPOSITIONS;
|
|
23558
23636
|
var init_machine = __esm(() => {
|
|
23559
23637
|
TRANSITIONS = {
|
|
23560
23638
|
initializing: ["ready", "blocked"],
|
|
@@ -23565,6 +23643,7 @@ var init_machine = __esm(() => {
|
|
|
23565
23643
|
blocked: [],
|
|
23566
23644
|
done: []
|
|
23567
23645
|
};
|
|
23646
|
+
GATE_PASSING_DISPOSITIONS = new Set(["completed", "skipped"]);
|
|
23568
23647
|
});
|
|
23569
23648
|
|
|
23570
23649
|
// src/flow/schema.ts
|
|
@@ -23603,6 +23682,17 @@ function flowStateSchema() {
|
|
|
23603
23682
|
}
|
|
23604
23683
|
},
|
|
23605
23684
|
acChecksum: { type: ["string", "null"], description: "SHA-256 of the frozen acceptance criteria." },
|
|
23685
|
+
gates: {
|
|
23686
|
+
type: "object",
|
|
23687
|
+
additionalProperties: true,
|
|
23688
|
+
description: "Which completion gates this package opted into at creation. Absent on packages created before a gate existed, which is how a gate is added without retroactively invalidating history: an absent flag reports the gate as skipped rather than passed.",
|
|
23689
|
+
properties: {
|
|
23690
|
+
tasks: {
|
|
23691
|
+
type: "boolean",
|
|
23692
|
+
description: "Written by `flow init`. When true, `flow complete` fails while any task is non-terminal."
|
|
23693
|
+
}
|
|
23694
|
+
}
|
|
23695
|
+
},
|
|
23606
23696
|
acConfirmed: {
|
|
23607
23697
|
type: "object",
|
|
23608
23698
|
description: "Per-AC confirmation records keyed by AC id (e.g. AC1).",
|
|
@@ -23692,6 +23782,10 @@ function flowStateSchema() {
|
|
|
23692
23782
|
enum: ["completed", "blocked", "failed", "skipped"],
|
|
23693
23783
|
description: "v2: terminal outcome when status is done (distinct from status)."
|
|
23694
23784
|
},
|
|
23785
|
+
dispositionReason: {
|
|
23786
|
+
type: "string",
|
|
23787
|
+
description: "Why the task ended this way. Required in practice for `skipped`: the task gate treats an unreasoned skip as non-terminal, because a skip nobody has to justify is a one-flag bypass of the gate."
|
|
23788
|
+
},
|
|
23695
23789
|
acRefs: { type: "array", items: { type: "string" }, description: "v2: AC ids this task addresses." },
|
|
23696
23790
|
evidenceRefs: { type: "array", items: { type: "string" }, description: "v2: artifact paths." },
|
|
23697
23791
|
budget: {
|
|
@@ -24047,6 +24141,7 @@ function createFlowService(deps) {
|
|
|
24047
24141
|
const createdAt = now();
|
|
24048
24142
|
const flow = {
|
|
24049
24143
|
schemaVersion: 1,
|
|
24144
|
+
gates: { tasks: true },
|
|
24050
24145
|
id,
|
|
24051
24146
|
slug: slug2,
|
|
24052
24147
|
title,
|
|
@@ -24132,7 +24227,7 @@ function createFlowService(deps) {
|
|
|
24132
24227
|
return save(input2.cwd, dir, flow, "task-added", `${task.id}: ${task.title}`);
|
|
24133
24228
|
});
|
|
24134
24229
|
},
|
|
24135
|
-
async taskDone({ cwd, id, taskId, disposition, evidenceRefs, runLink }) {
|
|
24230
|
+
async taskDone({ cwd, id, taskId, disposition, reason, evidenceRefs, runLink }) {
|
|
24136
24231
|
return mutate(cwd, id, async ({ dir, flow }) => {
|
|
24137
24232
|
await assertAcIntact(cwd, dir, flow);
|
|
24138
24233
|
const task = flow.tasks.find((item) => item.id.toUpperCase() === taskId.toUpperCase());
|
|
@@ -24140,7 +24235,14 @@ function createFlowService(deps) {
|
|
|
24140
24235
|
throw new Error(`Task not found: ${taskId}. Known: ${flow.tasks.map((t) => t.id).join(", ")}`);
|
|
24141
24236
|
}
|
|
24142
24237
|
task.status = "done";
|
|
24238
|
+
const previousDisposition = task.disposition;
|
|
24143
24239
|
task.disposition = disposition ?? task.disposition ?? "completed";
|
|
24240
|
+
if (disposition !== undefined && disposition !== previousDisposition) {
|
|
24241
|
+
delete task.dispositionReason;
|
|
24242
|
+
}
|
|
24243
|
+
if (reason?.trim()) {
|
|
24244
|
+
task.dispositionReason = reason.trim();
|
|
24245
|
+
}
|
|
24144
24246
|
if (evidenceRefs !== undefined) {
|
|
24145
24247
|
task.evidenceRefs = evidenceRefs;
|
|
24146
24248
|
}
|
|
@@ -24150,6 +24252,20 @@ function createFlowService(deps) {
|
|
|
24150
24252
|
return save(cwd, dir, flow, "task-done", `${task.id}: ${task.title}`);
|
|
24151
24253
|
});
|
|
24152
24254
|
},
|
|
24255
|
+
async taskAttempt({ cwd, id, taskId, outcome, detail }) {
|
|
24256
|
+
return mutate(cwd, id, async ({ dir, flow }) => {
|
|
24257
|
+
await assertAcIntact(cwd, dir, flow);
|
|
24258
|
+
const task = flow.tasks.find((item) => item.id.toUpperCase() === taskId.toUpperCase());
|
|
24259
|
+
if (!task) {
|
|
24260
|
+
throw new Error(`Task not found: ${taskId}. Known: ${flow.tasks.map((t) => t.id).join(", ")}`);
|
|
24261
|
+
}
|
|
24262
|
+
const attempts = task.attempts ?? { count: 0, log: [] };
|
|
24263
|
+
attempts.count += 1;
|
|
24264
|
+
attempts.log.push({ at: now(), outcome, ...detail?.trim() ? { detail: detail.trim() } : {} });
|
|
24265
|
+
task.attempts = attempts;
|
|
24266
|
+
return save(cwd, dir, flow, "task-attempt", `${task.id}: ${outcome} (attempt ${attempts.count})${detail?.trim() ? ` \u2014 ${detail.trim()}` : ""}`);
|
|
24267
|
+
});
|
|
24268
|
+
},
|
|
24153
24269
|
async acConfirm({ cwd, id, criterion, note: note2 }) {
|
|
24154
24270
|
return mutate(cwd, id, async ({ dir, flow }) => {
|
|
24155
24271
|
await assertAcIntact(cwd, dir, flow);
|
|
@@ -24243,6 +24359,7 @@ function createFlowService(deps) {
|
|
|
24243
24359
|
detail: "tracker unavailable; verify PR checks manually"
|
|
24244
24360
|
});
|
|
24245
24361
|
}
|
|
24362
|
+
gates.push(taskGate(flow));
|
|
24246
24363
|
try {
|
|
24247
24364
|
const health = await deps.healthGate(cwd);
|
|
24248
24365
|
gates.push(health.status === "fail" ? { name: "health", status: "fail", detail: health.reasons.join("; ") || "health gate failed" } : { name: "health", status: "pass", detail: `health gate: ${health.status}` });
|
|
@@ -24436,6 +24553,40 @@ async function appendIdMap(cwd, entry) {
|
|
|
24436
24553
|
await writeFileAtomic(file, `${JSON.stringify(entries, null, 2)}
|
|
24437
24554
|
`);
|
|
24438
24555
|
}
|
|
24556
|
+
function taskGate(flow) {
|
|
24557
|
+
if (!flow.gates?.tasks) {
|
|
24558
|
+
return {
|
|
24559
|
+
name: "tasks",
|
|
24560
|
+
status: "skipped",
|
|
24561
|
+
detail: "task gate not enabled for this package (created before the gate); " + "flows created by this keryx version opt in automatically"
|
|
24562
|
+
};
|
|
24563
|
+
}
|
|
24564
|
+
const verdict = evaluateTaskGate(flow.tasks);
|
|
24565
|
+
if (verdict.passed) {
|
|
24566
|
+
return {
|
|
24567
|
+
name: "tasks",
|
|
24568
|
+
status: "pass",
|
|
24569
|
+
detail: `${verdict.total} task(s) terminal`
|
|
24570
|
+
};
|
|
24571
|
+
}
|
|
24572
|
+
const reasons = [];
|
|
24573
|
+
if (verdict.open.length > 0) {
|
|
24574
|
+
reasons.push(`not done: ${verdict.open.join(", ")}`);
|
|
24575
|
+
}
|
|
24576
|
+
if (verdict.failed.length > 0) {
|
|
24577
|
+
reasons.push(`failed: ${verdict.failed.join(", ")}`);
|
|
24578
|
+
}
|
|
24579
|
+
if (verdict.unreasonedSkips.length > 0) {
|
|
24580
|
+
reasons.push(`skipped without a recorded reason: ${verdict.unreasonedSkips.join(", ")} ` + '(use: keryx flow task done <id> <Tn> --disposition skipped --reason "<why>")');
|
|
24581
|
+
}
|
|
24582
|
+
if (verdict.blocked.length > 0) {
|
|
24583
|
+
reasons.push(`blocked: ${verdict.blocked.join(", ")} ` + "(a blocked task did not get done; resolve it, or close it as skipped with a reason)");
|
|
24584
|
+
}
|
|
24585
|
+
if (verdict.unknownDisposition.length > 0) {
|
|
24586
|
+
reasons.push(`unrecognised disposition: ${verdict.unknownDisposition.join(", ")} ` + "(expected completed | blocked | failed | skipped)");
|
|
24587
|
+
}
|
|
24588
|
+
return { name: "tasks", status: "fail", detail: reasons.join("; ") };
|
|
24589
|
+
}
|
|
24439
24590
|
async function isPlaceholderAc(cwd, dir) {
|
|
24440
24591
|
const content = await Bun.file(acPath(cwd, dir)).text();
|
|
24441
24592
|
return content.includes("<replace with a hard, verifiable criterion");
|
|
@@ -40756,6 +40907,11 @@ init_github();
|
|
|
40756
40907
|
init_service6();
|
|
40757
40908
|
init_guard();
|
|
40758
40909
|
import path124 from "path";
|
|
40910
|
+
|
|
40911
|
+
// src/flow/types.ts
|
|
40912
|
+
var ATTEMPT_CLI_OUTCOMES = ["started", "failed", "blocked"];
|
|
40913
|
+
|
|
40914
|
+
// src/commands/flow.ts
|
|
40759
40915
|
var VALID_TASK_KINDS = ["context", "implement", "test", "verify", "review", "docs"];
|
|
40760
40916
|
function parseTaskKind(raw) {
|
|
40761
40917
|
if (raw === undefined) {
|
|
@@ -40766,6 +40922,26 @@ function parseTaskKind(raw) {
|
|
|
40766
40922
|
}
|
|
40767
40923
|
return raw;
|
|
40768
40924
|
}
|
|
40925
|
+
function positional(args2, index) {
|
|
40926
|
+
const value = args2[index];
|
|
40927
|
+
return value === undefined || value.startsWith("--") ? undefined : value;
|
|
40928
|
+
}
|
|
40929
|
+
var VALID_DISPOSITIONS = ["completed", "blocked", "failed", "skipped"];
|
|
40930
|
+
function parseDisposition(raw) {
|
|
40931
|
+
if (raw === undefined) {
|
|
40932
|
+
return;
|
|
40933
|
+
}
|
|
40934
|
+
if (!VALID_DISPOSITIONS.includes(raw)) {
|
|
40935
|
+
throw new Error(`Invalid --disposition "${raw}". Expected one of: ${VALID_DISPOSITIONS.join(", ")}`);
|
|
40936
|
+
}
|
|
40937
|
+
return raw;
|
|
40938
|
+
}
|
|
40939
|
+
function parseAttemptOutcome(raw) {
|
|
40940
|
+
if (raw === undefined || !ATTEMPT_CLI_OUTCOMES.includes(raw)) {
|
|
40941
|
+
throw new Error(`Invalid --outcome "${raw ?? ""}". Expected one of: ${ATTEMPT_CLI_OUTCOMES.join(", ")}`);
|
|
40942
|
+
}
|
|
40943
|
+
return raw;
|
|
40944
|
+
}
|
|
40769
40945
|
function flowStatusLabel(status) {
|
|
40770
40946
|
if (status === "done") {
|
|
40771
40947
|
return style.green(status);
|
|
@@ -40972,22 +41148,48 @@ async function runTask(args2) {
|
|
|
40972
41148
|
return;
|
|
40973
41149
|
}
|
|
40974
41150
|
if (sub === "done") {
|
|
40975
|
-
const id = args2
|
|
40976
|
-
const taskId = args2
|
|
41151
|
+
const id = positional(args2, 1);
|
|
41152
|
+
const taskId = positional(args2, 2);
|
|
40977
41153
|
if (!id || !taskId) {
|
|
40978
|
-
throw new Error(
|
|
41154
|
+
throw new Error('Usage: keryx flow task done <id> <taskId> [--disposition completed|blocked|failed|skipped] [--reason "<why>"]');
|
|
40979
41155
|
}
|
|
41156
|
+
const disposition = parseDisposition(optionValue(args2, "--disposition"));
|
|
41157
|
+
const reason = optionValue(args2, "--reason");
|
|
40980
41158
|
const flow = await getService3().taskDone({
|
|
40981
41159
|
cwd: process.cwd(),
|
|
40982
41160
|
id,
|
|
40983
41161
|
taskId,
|
|
40984
|
-
disposition
|
|
41162
|
+
disposition,
|
|
41163
|
+
reason
|
|
40985
41164
|
});
|
|
40986
41165
|
const done = flow.tasks.filter((task) => task.status === "done").length;
|
|
40987
41166
|
console.log(` ${style.green(symbols.ok)} Task ${style.bold(taskId.toUpperCase())} done ${style.dim(`(${done}/${flow.tasks.length})`)}`);
|
|
41167
|
+
if (disposition === "skipped" && !reason?.trim()) {
|
|
41168
|
+
note('A skipped task without --reason "<why>" fails the task gate at `keryx flow complete`. Re-run with a reason to record why the work was not needed.');
|
|
41169
|
+
}
|
|
41170
|
+
if (disposition === "blocked") {
|
|
41171
|
+
note("A blocked task fails the task gate at `keryx flow complete` \u2014 it is recorded as terminal, " + "but the work did not happen. Resolve it, or close it as skipped with a reason.");
|
|
41172
|
+
}
|
|
40988
41173
|
return;
|
|
40989
41174
|
}
|
|
40990
|
-
|
|
41175
|
+
if (sub === "attempt") {
|
|
41176
|
+
const id = positional(args2, 1);
|
|
41177
|
+
const taskId = positional(args2, 2);
|
|
41178
|
+
if (!id || !taskId) {
|
|
41179
|
+
throw new Error(`Usage: keryx flow task attempt <id> <taskId> --outcome ${ATTEMPT_CLI_OUTCOMES.join("|")} [--detail "<what happened>"]`);
|
|
41180
|
+
}
|
|
41181
|
+
const flow = await getService3().taskAttempt({
|
|
41182
|
+
cwd: process.cwd(),
|
|
41183
|
+
id,
|
|
41184
|
+
taskId,
|
|
41185
|
+
outcome: parseAttemptOutcome(optionValue(args2, "--outcome")),
|
|
41186
|
+
detail: optionValue(args2, "--detail")
|
|
41187
|
+
});
|
|
41188
|
+
const task = flow.tasks.find((item) => item.id.toUpperCase() === taskId.toUpperCase());
|
|
41189
|
+
console.log(` ${style.green(symbols.ok)} Attempt recorded on ${style.bold(taskId.toUpperCase())} ${style.dim(`(count ${task?.attempts?.count ?? 0})`)}`);
|
|
41190
|
+
return;
|
|
41191
|
+
}
|
|
41192
|
+
throw new Error("Usage: keryx flow task <add|done|attempt> ...");
|
|
40991
41193
|
}
|
|
40992
41194
|
async function runAc(args2) {
|
|
40993
41195
|
const sub = args2[0];
|
|
@@ -41118,7 +41320,8 @@ function printHelp10() {
|
|
|
41118
41320
|
"keryx flow freeze <id>",
|
|
41119
41321
|
"keryx flow start <id>",
|
|
41120
41322
|
'keryx flow task add <id> --title "<t>" [--kind context|implement|test|verify|review|docs] [--depends T1,T2]',
|
|
41121
|
-
|
|
41323
|
+
'keryx flow task done <id> <taskId> [--disposition completed|blocked|failed|skipped] [--reason "<why>"]',
|
|
41324
|
+
'keryx flow task attempt <id> <taskId> --outcome started|failed|blocked [--detail "<what happened>"]',
|
|
41122
41325
|
'keryx flow ac confirm <id> <ACn> [--note "<evidence>"]',
|
|
41123
41326
|
'keryx flow ac update <id> --reason "<why>"',
|
|
41124
41327
|
"keryx flow implemented <id> --pr <url>",
|
|
@@ -41136,16 +41339,17 @@ init_args();
|
|
|
41136
41339
|
|
|
41137
41340
|
// src/review/managed.ts
|
|
41138
41341
|
init_validator();
|
|
41139
|
-
init_fs();
|
|
41140
|
-
init_store2();
|
|
41141
41342
|
import { mkdir as mkdir48, readFile as readFile66, readdir as readdir20 } from "fs/promises";
|
|
41142
41343
|
import path125 from "path";
|
|
41344
|
+
init_fs();
|
|
41345
|
+
init_store2();
|
|
41143
41346
|
|
|
41144
41347
|
// src/review/types.ts
|
|
41145
41348
|
var MANAGED_REVIEW_MODES = ["attach-review", "review-flow", "ingest"];
|
|
41146
41349
|
var REVIEW_TARGET_KINDS = ["pr", "issue", "branch", "path", "report"];
|
|
41147
41350
|
var REVIEW_PACKAGE_STATUSES = ["draft", "reviewed", "decided", "learned", "closed"];
|
|
41148
41351
|
var REVIEW_COVERAGE_STATUSES = ["run", "skipped", "failed", "needs_context"];
|
|
41352
|
+
var REVIEW_FINDING_CONFIDENCES = ["high", "medium", "low"];
|
|
41149
41353
|
|
|
41150
41354
|
// src/review/managed.ts
|
|
41151
41355
|
var REQUIRED_ARTIFACTS = ["scope", "coverage", "report", "findings", "learning", "decisions"];
|
|
@@ -41174,7 +41378,14 @@ async function createManagedReviewPackage(input2) {
|
|
|
41174
41378
|
const packageDir = packagePath(input2.cwd, input2.mode, reviewId, flowMatch);
|
|
41175
41379
|
const coverage = normalizeCoverage(input2.coverage, input2.reviewers);
|
|
41176
41380
|
const report = await readReport(input2);
|
|
41177
|
-
const findings = normalizeFindings(
|
|
41381
|
+
const findings = await normalizeFindings({
|
|
41382
|
+
report,
|
|
41383
|
+
reportLabel: reportLabel(input2),
|
|
41384
|
+
mode: input2.mode,
|
|
41385
|
+
attachedToFlow: flowMatch !== null,
|
|
41386
|
+
source: input2.findings,
|
|
41387
|
+
reviewers: coverage.filter((entry) => entry.status === "run").map((entry) => entry.reviewer)
|
|
41388
|
+
});
|
|
41178
41389
|
const manifest = buildManifest2({
|
|
41179
41390
|
input: input2,
|
|
41180
41391
|
reviewId,
|
|
@@ -41191,13 +41402,17 @@ async function createManagedReviewPackage(input2) {
|
|
|
41191
41402
|
if (violations.length > 0) {
|
|
41192
41403
|
throw new Error(`Refusing to record findings that do not enumerate their class: ${violations.map((finding) => `${finding.id} (${finding.severity})`).join(", ")}. A blocker or major must carry class_scope with sites and enumeration_method \u2014 every site holding the shape, and how the set was derived.`);
|
|
41193
41404
|
}
|
|
41405
|
+
const contractErrors = await schemaErrors(findings);
|
|
41406
|
+
if (contractErrors.length > 0) {
|
|
41407
|
+
throw new Error(`Refusing to record findings that do not satisfy review-finding.schema.json: ${contractErrors.map((error2) => `${error2.path} ${error2.message}`).join("; ")}`);
|
|
41408
|
+
}
|
|
41194
41409
|
await mkdir48(packageDir, { recursive: true });
|
|
41195
41410
|
await writeFileAtomic(path125.join(packageDir, "manifest.json"), `${JSON.stringify(manifest, null, 2)}
|
|
41196
41411
|
`);
|
|
41197
41412
|
await writeFileAtomic(path125.join(packageDir, "scope.md"), renderScope(input2, flowMatch, at));
|
|
41198
41413
|
await writeFileAtomic(path125.join(packageDir, "coverage.md"), renderCoverage(coverage));
|
|
41199
41414
|
await writeFileAtomic(path125.join(packageDir, "report.md"), renderReport(report, input2.mode));
|
|
41200
|
-
await writeFileAtomic(path125.join(packageDir, "findings.json"), `${JSON.stringify(findings, null, 2)}
|
|
41415
|
+
await writeFileAtomic(path125.join(packageDir, "findings.json"), `${JSON.stringify(findings.map(toContractFinding), null, 2)}
|
|
41201
41416
|
`);
|
|
41202
41417
|
await writeFileAtomic(path125.join(packageDir, "learning.md"), renderLearning(findings));
|
|
41203
41418
|
await writeFileAtomic(path125.join(packageDir, "decisions.md"), renderDecisions(findings));
|
|
@@ -41354,7 +41569,147 @@ async function readReport(input2) {
|
|
|
41354
41569
|
No reviewer findings recorded yet.
|
|
41355
41570
|
`;
|
|
41356
41571
|
}
|
|
41357
|
-
function
|
|
41572
|
+
function reportLabel(input2) {
|
|
41573
|
+
return input2.reportPath ? path125.resolve(input2.cwd, input2.reportPath) : "the report text passed to keryx review";
|
|
41574
|
+
}
|
|
41575
|
+
async function normalizeFindings(args2) {
|
|
41576
|
+
if (args2.source !== undefined) {
|
|
41577
|
+
return triage(fromStructuredSource(args2.source, args2.reviewers), args2);
|
|
41578
|
+
}
|
|
41579
|
+
const structured = parseEmbeddedFindings(args2.report, args2.reportLabel);
|
|
41580
|
+
if (structured !== null) {
|
|
41581
|
+
return triage(fromStructuredSource(structured, args2.reviewers), args2);
|
|
41582
|
+
}
|
|
41583
|
+
return triage(parseLegacyReport(args2.report, args2.reviewers), args2);
|
|
41584
|
+
}
|
|
41585
|
+
function triage(findings, args2) {
|
|
41586
|
+
const classification = args2.mode === "ingest" ? "valid_followup" : "skill_learning_candidate";
|
|
41587
|
+
return findings.map((finding) => {
|
|
41588
|
+
const triaged = {
|
|
41589
|
+
...finding,
|
|
41590
|
+
classification,
|
|
41591
|
+
flow_relevance: args2.attachedToFlow ? "post_flow_feedback" : "standalone_review"
|
|
41592
|
+
};
|
|
41593
|
+
if (finding.learning_candidate === undefined && classification === "skill_learning_candidate") {
|
|
41594
|
+
triaged.learning_candidate = true;
|
|
41595
|
+
}
|
|
41596
|
+
return triaged;
|
|
41597
|
+
});
|
|
41598
|
+
}
|
|
41599
|
+
function toContractFinding(finding) {
|
|
41600
|
+
const record = {
|
|
41601
|
+
id: finding.id,
|
|
41602
|
+
reviewer: finding.reviewer,
|
|
41603
|
+
severity: finding.severity,
|
|
41604
|
+
problem: finding.problem,
|
|
41605
|
+
impact: finding.impact,
|
|
41606
|
+
suggested_fix: finding.suggested_fix,
|
|
41607
|
+
evidence: finding.evidence,
|
|
41608
|
+
confidence: finding.confidence
|
|
41609
|
+
};
|
|
41610
|
+
if (finding.file !== undefined) {
|
|
41611
|
+
record.file = finding.file;
|
|
41612
|
+
}
|
|
41613
|
+
if (finding.line !== undefined) {
|
|
41614
|
+
record.line = finding.line;
|
|
41615
|
+
}
|
|
41616
|
+
if (finding.symbol !== undefined) {
|
|
41617
|
+
record.symbol = finding.symbol;
|
|
41618
|
+
}
|
|
41619
|
+
if (finding.dedupe_key !== undefined) {
|
|
41620
|
+
record.dedupe_key = finding.dedupe_key;
|
|
41621
|
+
}
|
|
41622
|
+
if (finding.blocking_merge !== undefined) {
|
|
41623
|
+
record.blocking_merge = finding.blocking_merge;
|
|
41624
|
+
}
|
|
41625
|
+
if (finding.related_skill !== undefined) {
|
|
41626
|
+
record.related_skill = finding.related_skill;
|
|
41627
|
+
}
|
|
41628
|
+
if (finding.learning_candidate !== undefined) {
|
|
41629
|
+
record.learning_candidate = finding.learning_candidate;
|
|
41630
|
+
}
|
|
41631
|
+
if (finding.class_scope !== undefined) {
|
|
41632
|
+
record.class_scope = finding.class_scope;
|
|
41633
|
+
}
|
|
41634
|
+
return record;
|
|
41635
|
+
}
|
|
41636
|
+
var EMBEDDED_FINDINGS_FENCE = /^ {0,3}(`{3,}|~{3,})[^\n]*\bkeryx:findings\b[^\n]*$/gm;
|
|
41637
|
+
function parseEmbeddedFindings(report, reportLabel2) {
|
|
41638
|
+
const fences = [...report.matchAll(EMBEDDED_FINDINGS_FENCE)];
|
|
41639
|
+
if (fences.length === 0) {
|
|
41640
|
+
return null;
|
|
41641
|
+
}
|
|
41642
|
+
if (fences.length > 1) {
|
|
41643
|
+
throw new Error(`${reportLabel2} carries ${fences.length} keryx:findings blocks (at character ${fences.map((fence2) => String(fence2.index ?? 0)).join(" and ")}); exactly one is allowed. Concatenating one block per reviewer drops every block after the first \u2014 merge them into a single array.`);
|
|
41644
|
+
}
|
|
41645
|
+
const fence = fences[0];
|
|
41646
|
+
let parsed;
|
|
41647
|
+
try {
|
|
41648
|
+
parsed = JSON.parse(embeddedBlockBody(report, fence));
|
|
41649
|
+
} catch (error2) {
|
|
41650
|
+
throw new Error(`${reportLabel2} carries a keryx:findings block that is not valid JSON: ${error2 instanceof Error ? error2.message : String(error2)}`);
|
|
41651
|
+
}
|
|
41652
|
+
if (!Array.isArray(parsed) && !isReviewerResult(parsed)) {
|
|
41653
|
+
throw new Error(`${reportLabel2} carries a keryx:findings block that is ${describeJson(parsed)}, not an array of findings or a { reviewer, findings } result.`);
|
|
41654
|
+
}
|
|
41655
|
+
return parsed;
|
|
41656
|
+
}
|
|
41657
|
+
function embeddedBlockBody(report, fence) {
|
|
41658
|
+
const marker2 = fence[1] ?? "```";
|
|
41659
|
+
const char = marker2.startsWith("~") ? "~" : "`";
|
|
41660
|
+
const afterOpen = report.slice((fence.index ?? 0) + fence[0].length).replace(/^\r?\n/, "");
|
|
41661
|
+
const closing = afterOpen.match(new RegExp(`^ {0,3}\\${char}{${marker2.length},}\\s*$`, "m"));
|
|
41662
|
+
return closing?.index === undefined ? afterOpen : afterOpen.slice(0, closing.index);
|
|
41663
|
+
}
|
|
41664
|
+
function describeJson(value) {
|
|
41665
|
+
if (value === null) {
|
|
41666
|
+
return "JSON null";
|
|
41667
|
+
}
|
|
41668
|
+
return `a JSON ${typeof value}`;
|
|
41669
|
+
}
|
|
41670
|
+
function fromStructuredSource(source, reviewers) {
|
|
41671
|
+
const flattened = [];
|
|
41672
|
+
for (const entry of Array.isArray(source) ? source : [source]) {
|
|
41673
|
+
if (isReviewerResult(entry)) {
|
|
41674
|
+
for (const finding of entry.findings) {
|
|
41675
|
+
if (finding.reviewer || entry.reviewer === undefined) {
|
|
41676
|
+
flattened.push(finding);
|
|
41677
|
+
continue;
|
|
41678
|
+
}
|
|
41679
|
+
flattened.push({ ...finding, reviewer: entry.reviewer });
|
|
41680
|
+
}
|
|
41681
|
+
continue;
|
|
41682
|
+
}
|
|
41683
|
+
flattened.push(entry);
|
|
41684
|
+
}
|
|
41685
|
+
return flattened.map((finding) => coerceStructured(finding, reviewers));
|
|
41686
|
+
}
|
|
41687
|
+
function isReviewerResult(value) {
|
|
41688
|
+
return typeof value === "object" && value !== null && !Array.isArray(value) && Array.isArray(value.findings);
|
|
41689
|
+
}
|
|
41690
|
+
async function schemaErrors(findings) {
|
|
41691
|
+
const schema = await loadSchema("review-finding");
|
|
41692
|
+
const errors = [];
|
|
41693
|
+
for (const [index, finding] of findings.entries()) {
|
|
41694
|
+
for (const error2 of await validateJson(toContractFinding(finding), schema)) {
|
|
41695
|
+
errors.push({ path: error2.path.replace(/^\$/, `$[${index}]`), message: error2.message });
|
|
41696
|
+
}
|
|
41697
|
+
}
|
|
41698
|
+
return errors;
|
|
41699
|
+
}
|
|
41700
|
+
function coerceStructured(finding, reviewers) {
|
|
41701
|
+
const record = { ...finding };
|
|
41702
|
+
if (typeof record.reviewer !== "string" || record.reviewer === "") {
|
|
41703
|
+
record.reviewer = defaultReviewer(reviewers);
|
|
41704
|
+
}
|
|
41705
|
+
record.summary = typeof finding.problem === "string" ? finding.problem : "";
|
|
41706
|
+
record.class_scope_present = finding.class_scope !== undefined;
|
|
41707
|
+
return record;
|
|
41708
|
+
}
|
|
41709
|
+
function defaultReviewer(reviewers) {
|
|
41710
|
+
return reviewers.length === 1 && reviewers[0] ? reviewers[0] : "review-orchestrator";
|
|
41711
|
+
}
|
|
41712
|
+
function parseLegacyReport(report, reviewers) {
|
|
41358
41713
|
const findings = [];
|
|
41359
41714
|
const lines = report.split(`
|
|
41360
41715
|
`);
|
|
@@ -41366,18 +41721,128 @@ function normalizeFindings(report, mode, attachedToFlow) {
|
|
|
41366
41721
|
const id = heading2.id;
|
|
41367
41722
|
const summary = heading2.summary || "Review finding";
|
|
41368
41723
|
const block = findingBlock(lines, index);
|
|
41369
|
-
|
|
41724
|
+
const location = parseLocation(labelledField(block, ["file", "location"]));
|
|
41725
|
+
const classScope = parseClassScope(block);
|
|
41726
|
+
const finding = {
|
|
41370
41727
|
id,
|
|
41371
41728
|
severity: severityFor(line, block),
|
|
41372
|
-
reviewer:
|
|
41729
|
+
reviewer: legacyReviewer(block, reviewers),
|
|
41730
|
+
problem: labelledField(block, ["problem", "issue"]) ?? summary.replace(SEVERITY_PREFIX, ""),
|
|
41731
|
+
impact: labelledField(block, ["impact", "why it matters"]) ?? NOT_RECORDED("impact"),
|
|
41732
|
+
suggested_fix: labelledField(block, ["suggested fix", "suggested_fix", "fix", "recommendation"]) ?? NOT_RECORDED("suggested_fix"),
|
|
41733
|
+
evidence: labelledField(block, ["evidence", "proof"]) ?? NOT_RECORDED("evidence"),
|
|
41734
|
+
confidence: legacyConfidence(block),
|
|
41373
41735
|
summary,
|
|
41374
|
-
|
|
41375
|
-
|
|
41376
|
-
|
|
41377
|
-
|
|
41736
|
+
class_scope_present: classScope !== null
|
|
41737
|
+
};
|
|
41738
|
+
if (classScope !== null) {
|
|
41739
|
+
finding.class_scope = classScope;
|
|
41740
|
+
}
|
|
41741
|
+
if (location.file !== undefined) {
|
|
41742
|
+
finding.file = location.file;
|
|
41743
|
+
}
|
|
41744
|
+
if (location.line !== undefined) {
|
|
41745
|
+
finding.line = location.line;
|
|
41746
|
+
}
|
|
41747
|
+
findings.push(finding);
|
|
41378
41748
|
}
|
|
41379
41749
|
return findings;
|
|
41380
41750
|
}
|
|
41751
|
+
var SEVERITY_PREFIX = /^\s*(?:\*\*|__)?(?:blocker|major|minor|info)(?:\*\*|__)?\s*[:\u2014\u2013-]\s*/i;
|
|
41752
|
+
var NOT_RECORDED = (field3) => `not recorded: derived from a markdown review report, which carried no ${field3} field`;
|
|
41753
|
+
function labelledField(block, labels) {
|
|
41754
|
+
const lines = block.split(`
|
|
41755
|
+
`);
|
|
41756
|
+
for (const [index, line] of lines.entries()) {
|
|
41757
|
+
const match = line.match(LABEL_LINE);
|
|
41758
|
+
const label = match?.[1]?.trim().toLowerCase().replace(/\*\*|__|`/g, "");
|
|
41759
|
+
if (label === undefined || !labels.includes(label)) {
|
|
41760
|
+
continue;
|
|
41761
|
+
}
|
|
41762
|
+
const parts = [(match?.[2] ?? "").trim()];
|
|
41763
|
+
for (let i = index + 1;i < lines.length; i += 1) {
|
|
41764
|
+
const next = lines[i] ?? "";
|
|
41765
|
+
if (next.trim() === "" || /^\s*[-*+]\s/.test(next) || /^\s*(?:\*\*|__)/.test(next)) {
|
|
41766
|
+
break;
|
|
41767
|
+
}
|
|
41768
|
+
parts.push(next.trim());
|
|
41769
|
+
}
|
|
41770
|
+
const value = parts.join(" ").trim().replace(/^[`*_]+|[`*_]+$/g, "").trim();
|
|
41771
|
+
if (value !== "") {
|
|
41772
|
+
return value;
|
|
41773
|
+
}
|
|
41774
|
+
}
|
|
41775
|
+
return;
|
|
41776
|
+
}
|
|
41777
|
+
var LABEL_LINE = /^[\s>]*(?:[-*+]\s+)?((?:\*\*|__)?[A-Za-z][A-Za-z _-]*(?:\*\*|__)?)\s*[:=]\s*(.*)$/;
|
|
41778
|
+
function parseLocation(value) {
|
|
41779
|
+
if (value === undefined) {
|
|
41780
|
+
return {};
|
|
41781
|
+
}
|
|
41782
|
+
const match = value.replace(/`/g, "").trim().match(/^([^\s:]+?\.[A-Za-z0-9]+)(?::(\d+))?\b/);
|
|
41783
|
+
if (!match?.[1]) {
|
|
41784
|
+
return {};
|
|
41785
|
+
}
|
|
41786
|
+
const line = match[2] === undefined ? undefined : Number(match[2]);
|
|
41787
|
+
return line === undefined || Number.isNaN(line) ? { file: match[1] } : { file: match[1], line };
|
|
41788
|
+
}
|
|
41789
|
+
function legacyReviewer(block, reviewers) {
|
|
41790
|
+
const attribution = labelledField(block, ["reviewer", "found by", "found independently by", "reviewers"]);
|
|
41791
|
+
const named = attribution?.match(/\breview-[a-z0-9-]+\b/i);
|
|
41792
|
+
if (named?.[0]) {
|
|
41793
|
+
return named[0].toLowerCase();
|
|
41794
|
+
}
|
|
41795
|
+
return defaultReviewer(reviewers);
|
|
41796
|
+
}
|
|
41797
|
+
function legacyConfidence(block) {
|
|
41798
|
+
const declared = labelledField(block, ["confidence"])?.toLowerCase();
|
|
41799
|
+
if (declared !== undefined) {
|
|
41800
|
+
for (const value of REVIEW_FINDING_CONFIDENCES) {
|
|
41801
|
+
if (declared.startsWith(value)) {
|
|
41802
|
+
return value;
|
|
41803
|
+
}
|
|
41804
|
+
}
|
|
41805
|
+
}
|
|
41806
|
+
return "low";
|
|
41807
|
+
}
|
|
41808
|
+
function parseClassScope(block) {
|
|
41809
|
+
const lines = block.split(`
|
|
41810
|
+
`);
|
|
41811
|
+
const start = lines.findIndex((line) => /class[_ ]scope/i.test(line));
|
|
41812
|
+
if (start === -1) {
|
|
41813
|
+
return null;
|
|
41814
|
+
}
|
|
41815
|
+
const rest = lines.slice(start).join(`
|
|
41816
|
+
`);
|
|
41817
|
+
const sitesMatch = rest.match(/\bsites\b\s*[:=]\s*([\s\S]*?)(?=\benumeration_method\b|$)/i);
|
|
41818
|
+
const methodMatch = rest.match(/\benumeration_method\b\s*[:=]\s*([\s\S]*?)(?=\n\s*\n|$)/i);
|
|
41819
|
+
const sites = sitesMatch?.[1] === undefined ? [] : parseSites(sitesMatch[1]);
|
|
41820
|
+
const method = unquote(methodMatch?.[1]?.replace(/\s+/g, " ").trim() ?? "");
|
|
41821
|
+
if (sites.length === 0 || method === "") {
|
|
41822
|
+
return null;
|
|
41823
|
+
}
|
|
41824
|
+
return { sites, enumeration_method: method };
|
|
41825
|
+
}
|
|
41826
|
+
function unquote(value) {
|
|
41827
|
+
const trimmed = value.trim().replace(/[.;,]$/, "").trim();
|
|
41828
|
+
return /^(["'`])[\s\S]*\1$/.test(trimmed) ? trimmed.slice(1, -1).trim() : trimmed;
|
|
41829
|
+
}
|
|
41830
|
+
function parseSites(raw) {
|
|
41831
|
+
const text = raw.replace(/\s+/g, " ").trim().replace(/[;,]\s*$/, "");
|
|
41832
|
+
if (text === "") {
|
|
41833
|
+
return [];
|
|
41834
|
+
}
|
|
41835
|
+
if (text.startsWith("[")) {
|
|
41836
|
+
try {
|
|
41837
|
+
const parsed = JSON.parse(text.slice(0, text.lastIndexOf("]") + 1));
|
|
41838
|
+
if (Array.isArray(parsed)) {
|
|
41839
|
+
return parsed.map((item) => String(item)).filter((item) => item !== "");
|
|
41840
|
+
}
|
|
41841
|
+
} catch {}
|
|
41842
|
+
}
|
|
41843
|
+
const separator = text.includes(";") ? ";" : ",";
|
|
41844
|
+
return text.split(separator).map((item) => item.replace(/^[-*+\s`"]+|[\s`",]+$/g, "").trim()).filter((item) => item !== "");
|
|
41845
|
+
}
|
|
41381
41846
|
var HEADING_MARKER = /^(?:#{1,6}|[-*+])\s+/;
|
|
41382
41847
|
var FINDING_IDENTIFIER = /^(\[)?\s*(F-\d{3,})\b\s*(\])?/i;
|
|
41383
41848
|
var TITLE_SEPARATOR = /^\s*[:\u2014\u2013-]+\s*/;
|
|
@@ -41419,10 +41884,6 @@ function findingBlock(lines, headingIndex) {
|
|
|
41419
41884
|
return out.join(`
|
|
41420
41885
|
`);
|
|
41421
41886
|
}
|
|
41422
|
-
function hasClassScope(block) {
|
|
41423
|
-
const lower = block.toLowerCase();
|
|
41424
|
-
return (lower.includes("class_scope") || lower.includes("class scope")) && lower.includes("sites") && lower.includes("enumeration_method");
|
|
41425
|
-
}
|
|
41426
41887
|
function classScopeViolations(findings) {
|
|
41427
41888
|
return findings.filter((finding) => (finding.severity === "blocker" || finding.severity === "major") && finding.class_scope_present !== true);
|
|
41428
41889
|
}
|
|
@@ -41493,7 +41954,7 @@ function renderLearning(findings) {
|
|
|
41493
41954
|
|
|
41494
41955
|
## Skill Learning
|
|
41495
41956
|
|
|
41496
|
-
${candidates.map((finding) => `- \`
|
|
41957
|
+
${candidates.map((finding) => `- \`${finding.reviewer}\` <- ${finding.id}: ${finding.summary}`).join(`
|
|
41497
41958
|
`)}
|
|
41498
41959
|
`;
|
|
41499
41960
|
}
|
|
@@ -41506,7 +41967,7 @@ function renderDecisions(findings) {
|
|
|
41506
41967
|
}
|
|
41507
41968
|
return `# Decisions
|
|
41508
41969
|
|
|
41509
|
-
${findings.map((finding) => `- ${finding.id}: create follow-up task or learning proposal (${finding.classification}).`).join(`
|
|
41970
|
+
${findings.map((finding) => `- ${finding.id}: create follow-up task or learning proposal (${finding.classification}, ${finding.flow_relevance}).`).join(`
|
|
41510
41971
|
`)}
|
|
41511
41972
|
`;
|
|
41512
41973
|
}
|
|
@@ -42765,6 +43226,24 @@ var COMMAND_DESCRIPTORS = [
|
|
|
42765
43226
|
read: false,
|
|
42766
43227
|
sideEffects: ["renames .metaproject/flows/**", "writes .metaproject/flows/id-map.json"]
|
|
42767
43228
|
},
|
|
43229
|
+
{
|
|
43230
|
+
module: "sandbox",
|
|
43231
|
+
command: "sandbox status",
|
|
43232
|
+
summary: "OS sandbox launcher availability and the per-capability containment matrix for this platform. Report only \u2014 never runs a contained command, always exits 0.",
|
|
43233
|
+
intent: [
|
|
43234
|
+
"\u043F\u0440\u043E\u0432\u0435\u0440\u044C sandbox",
|
|
43235
|
+
"\u0441\u0442\u0430\u0442\u0443\u0441 \u043F\u0435\u0441\u043E\u0447\u043D\u0438\u0446\u044B",
|
|
43236
|
+
"sandbox status",
|
|
43237
|
+
"is bubblewrap installed",
|
|
43238
|
+
"\u0443\u0441\u0442\u0430\u043D\u043E\u0432\u043B\u0435\u043D \u043B\u0438 bubblewrap",
|
|
43239
|
+
"os containment status",
|
|
43240
|
+
"check os sandbox",
|
|
43241
|
+
"\u043A\u0430\u043A\u0430\u044F \u0438\u0437\u043E\u043B\u044F\u0446\u0438\u044F \u0434\u043E\u0441\u0442\u0443\u043F\u043D\u0430"
|
|
43242
|
+
],
|
|
43243
|
+
args: [{ name: "json", type: "bool", required: false, desc: "structured JSON report" }],
|
|
43244
|
+
json: true,
|
|
43245
|
+
read: true
|
|
43246
|
+
},
|
|
42768
43247
|
{
|
|
42769
43248
|
module: "security",
|
|
42770
43249
|
command: "security scan",
|
|
@@ -43709,14 +44188,14 @@ async function handlePolicy(cwd, args2) {
|
|
|
43709
44188
|
return;
|
|
43710
44189
|
}
|
|
43711
44190
|
const config = await loadSecurityConfig(cwd);
|
|
43712
|
-
const
|
|
44191
|
+
const schemaErrors2 = validateSecurityConfig(config);
|
|
43713
44192
|
const checksum = verifyConfigChecksum(config);
|
|
43714
44193
|
heading("keryx security policy validate");
|
|
43715
44194
|
console.log("");
|
|
43716
|
-
if (
|
|
44195
|
+
if (schemaErrors2.length === 0) {
|
|
43717
44196
|
console.log(` ${style.green(symbols.ok)} config schema: valid`);
|
|
43718
44197
|
} else {
|
|
43719
|
-
for (const error2 of
|
|
44198
|
+
for (const error2 of schemaErrors2) {
|
|
43720
44199
|
console.log(` ${style.red(symbols.cross)} ${error2}`);
|
|
43721
44200
|
}
|
|
43722
44201
|
}
|
|
@@ -43725,7 +44204,7 @@ async function handlePolicy(cwd, args2) {
|
|
|
43725
44204
|
} else {
|
|
43726
44205
|
console.log(` ${style.red(symbols.cross)} configChecksum: mismatch (expected ${checksum.expected})`);
|
|
43727
44206
|
}
|
|
43728
|
-
const ok =
|
|
44207
|
+
const ok = schemaErrors2.length === 0 && checksum.match;
|
|
43729
44208
|
process.exitCode = ok ? 0 : 1;
|
|
43730
44209
|
}
|
|
43731
44210
|
async function handleIncidents(cwd, args2) {
|
|
@@ -43940,14 +44419,347 @@ function printSecurityHelp() {
|
|
|
43940
44419
|
]);
|
|
43941
44420
|
}
|
|
43942
44421
|
|
|
44422
|
+
// src/harness/process/sandbox/detect.ts
|
|
44423
|
+
import { existsSync as realExistsSync } from "fs";
|
|
44424
|
+
import path133 from "path";
|
|
44425
|
+
|
|
44426
|
+
// src/harness/process/sandbox/seatbelt.ts
|
|
44427
|
+
var SANDBOX_EXEC_PATH = "/usr/bin/sandbox-exec";
|
|
44428
|
+
function sbplString(value) {
|
|
44429
|
+
return `"${value.replace(/\\/g, "\\\\").replace(/"/g, "\\\"")}"`;
|
|
44430
|
+
}
|
|
44431
|
+
var DEVICE_WRITE_LITERALS = [
|
|
44432
|
+
"/dev/null",
|
|
44433
|
+
"/dev/zero",
|
|
44434
|
+
"/dev/stdin",
|
|
44435
|
+
"/dev/stdout",
|
|
44436
|
+
"/dev/stderr",
|
|
44437
|
+
"/dev/tty",
|
|
44438
|
+
"/dev/dtracehelper",
|
|
44439
|
+
"/dev/random",
|
|
44440
|
+
"/dev/urandom"
|
|
44441
|
+
];
|
|
44442
|
+
function buildSeatbeltProfile(profile) {
|
|
44443
|
+
const lines = [
|
|
44444
|
+
"(version 1)",
|
|
44445
|
+
"(allow default)",
|
|
44446
|
+
"",
|
|
44447
|
+
";; --- filesystem writes: deny everything, then re-allow workspace roots ---",
|
|
44448
|
+
'(deny file-write* (subpath "/"))'
|
|
44449
|
+
];
|
|
44450
|
+
for (const root of profile.writableRoots) {
|
|
44451
|
+
lines.push(`(allow file-write* (subpath ${sbplString(root)}))`);
|
|
44452
|
+
}
|
|
44453
|
+
lines.push(`(allow file-write-data ${DEVICE_WRITE_LITERALS.map((d) => `(literal ${sbplString(d)})`).join(" ")})`);
|
|
44454
|
+
if (profile.readDenyList.length > 0) {
|
|
44455
|
+
lines.push("", ";; --- secret read-deny ---");
|
|
44456
|
+
for (const secret of profile.readDenyList) {
|
|
44457
|
+
lines.push(`(deny file-read* (subpath ${sbplString(secret)}))`);
|
|
44458
|
+
}
|
|
44459
|
+
}
|
|
44460
|
+
if (profile.network === "off") {
|
|
44461
|
+
lines.push("", ";; --- network off ---", "(deny network*)");
|
|
44462
|
+
} else if (profile.network === "restricted") {
|
|
44463
|
+
lines.push("", ";; --- network restricted to loopback allowlist proxy ---", "(deny network*)");
|
|
44464
|
+
if (profile.proxy) {
|
|
44465
|
+
lines.push(`(allow network-outbound (remote ip ${sbplString(`localhost:${profile.proxy.port}`)}))`);
|
|
44466
|
+
}
|
|
44467
|
+
}
|
|
44468
|
+
return `${lines.join(`
|
|
44469
|
+
`)}
|
|
44470
|
+
`;
|
|
44471
|
+
}
|
|
44472
|
+
function wrapSeatbelt(command, profile) {
|
|
44473
|
+
const profileText = buildSeatbeltProfile(profile);
|
|
44474
|
+
return {
|
|
44475
|
+
path: SANDBOX_EXEC_PATH,
|
|
44476
|
+
argv: ["sandbox-exec", "-p", profileText, command.path, ...command.argv.slice(1)],
|
|
44477
|
+
env: command.env,
|
|
44478
|
+
cwd: command.cwd
|
|
44479
|
+
};
|
|
44480
|
+
}
|
|
44481
|
+
|
|
44482
|
+
// src/harness/process/sandbox/bwrap.ts
|
|
44483
|
+
import { statSync as statSync4 } from "fs";
|
|
44484
|
+
var BWRAP_PROGRAM = "bwrap";
|
|
44485
|
+
function inspectMaskTarget(target) {
|
|
44486
|
+
try {
|
|
44487
|
+
return statSync4(target).isDirectory() ? "dir" : "file";
|
|
44488
|
+
} catch {
|
|
44489
|
+
return "missing";
|
|
44490
|
+
}
|
|
44491
|
+
}
|
|
44492
|
+
function buildBwrapArgs(profile, inspect = inspectMaskTarget) {
|
|
44493
|
+
const args2 = [
|
|
44494
|
+
"--ro-bind",
|
|
44495
|
+
"/",
|
|
44496
|
+
"/",
|
|
44497
|
+
"--dev",
|
|
44498
|
+
"/dev",
|
|
44499
|
+
"--proc",
|
|
44500
|
+
"/proc",
|
|
44501
|
+
"--tmpfs",
|
|
44502
|
+
"/tmp"
|
|
44503
|
+
];
|
|
44504
|
+
for (const root of profile.writableRoots) {
|
|
44505
|
+
args2.push("--bind", root, root);
|
|
44506
|
+
}
|
|
44507
|
+
for (const secret of profile.readDenyList) {
|
|
44508
|
+
const kind = inspect(secret);
|
|
44509
|
+
if (kind === "dir") {
|
|
44510
|
+
args2.push("--tmpfs", secret);
|
|
44511
|
+
} else if (kind === "file") {
|
|
44512
|
+
args2.push("--ro-bind", "/dev/null", secret);
|
|
44513
|
+
}
|
|
44514
|
+
}
|
|
44515
|
+
if (profile.network === "off") {
|
|
44516
|
+
args2.push("--unshare-net");
|
|
44517
|
+
}
|
|
44518
|
+
args2.push("--unshare-pid", "--unshare-ipc", "--die-with-parent", "--new-session");
|
|
44519
|
+
return args2;
|
|
44520
|
+
}
|
|
44521
|
+
function wrapBwrap(command, profile, launcherPath = BWRAP_PROGRAM) {
|
|
44522
|
+
return {
|
|
44523
|
+
path: launcherPath,
|
|
44524
|
+
argv: [BWRAP_PROGRAM, ...buildBwrapArgs(profile), "--", command.path, ...command.argv.slice(1)],
|
|
44525
|
+
env: command.env,
|
|
44526
|
+
cwd: command.cwd
|
|
44527
|
+
};
|
|
44528
|
+
}
|
|
44529
|
+
|
|
44530
|
+
// src/harness/process/sandbox/adapter.ts
|
|
44531
|
+
import { createHash as createHash21 } from "crypto";
|
|
44532
|
+
|
|
44533
|
+
// src/harness/process/sandbox/wrap.ts
|
|
44534
|
+
function wrapWithSandbox(command, profile, opts) {
|
|
44535
|
+
if (profile.mode === "danger-full-access") {
|
|
44536
|
+
return { ok: true, command, wrapped: false };
|
|
44537
|
+
}
|
|
44538
|
+
if (opts.platform === "darwin") {
|
|
44539
|
+
return { ok: true, command: wrapSeatbelt(command, profile), wrapped: true };
|
|
44540
|
+
}
|
|
44541
|
+
if (opts.platform === "linux") {
|
|
44542
|
+
if (profile.network === "restricted") {
|
|
44543
|
+
return {
|
|
44544
|
+
ok: false,
|
|
44545
|
+
reason: "network=restricted is not yet enforced on Linux (needs a network namespace + proxy relay); use network off/on or run inside a container."
|
|
44546
|
+
};
|
|
44547
|
+
}
|
|
44548
|
+
const wrapped = opts.bwrapPath ? wrapBwrap(command, profile, opts.bwrapPath) : wrapBwrap(command, profile);
|
|
44549
|
+
return { ok: true, command: wrapped, wrapped: true };
|
|
44550
|
+
}
|
|
44551
|
+
return {
|
|
44552
|
+
ok: false,
|
|
44553
|
+
reason: `OS sandbox is unsupported on platform "${opts.platform}"; run inside WSL2 or a container, or use an explicit danger-full-access override.`
|
|
44554
|
+
};
|
|
44555
|
+
}
|
|
44556
|
+
|
|
44557
|
+
// src/harness/process/sandbox/adapter.ts
|
|
44558
|
+
function spawnError(command, message2) {
|
|
44559
|
+
return {
|
|
44560
|
+
kind: "spawn-error",
|
|
44561
|
+
observedHash: createHash21("sha256").update(`${command.path}
|
|
44562
|
+
${command.argv.join(" ")}`, "utf8").digest("hex"),
|
|
44563
|
+
errorMessage: message2
|
|
44564
|
+
};
|
|
44565
|
+
}
|
|
44566
|
+
|
|
44567
|
+
class SandboxedProcessAdapter {
|
|
44568
|
+
opts;
|
|
44569
|
+
constructor(opts) {
|
|
44570
|
+
this.opts = opts;
|
|
44571
|
+
}
|
|
44572
|
+
spawn(command) {
|
|
44573
|
+
const { profile, inner, platform, launcherAvailable, bwrapPath } = this.opts;
|
|
44574
|
+
const failClosed = profile.required || profile.network === "restricted" || (this.opts.failIfUnavailable ?? true);
|
|
44575
|
+
if (profile.mode === "danger-full-access") {
|
|
44576
|
+
return inner.spawn(command);
|
|
44577
|
+
}
|
|
44578
|
+
if (!launcherAvailable) {
|
|
44579
|
+
if (failClosed) {
|
|
44580
|
+
return spawnError(command, `OS sandbox launcher unavailable on ${platform} for program "${command.path}"; failing closed (install bubblewrap on Linux, or relax failIfUnavailable to run unsandboxed).`);
|
|
44581
|
+
}
|
|
44582
|
+
return inner.spawn(command);
|
|
44583
|
+
}
|
|
44584
|
+
const wrap2 = wrapWithSandbox(command, profile, {
|
|
44585
|
+
platform,
|
|
44586
|
+
...bwrapPath !== undefined ? { bwrapPath } : {}
|
|
44587
|
+
});
|
|
44588
|
+
if (!wrap2.ok) {
|
|
44589
|
+
if (failClosed) {
|
|
44590
|
+
return spawnError(command, `sandbox wrap refused program "${command.path}" on ${platform}: ${wrap2.reason}`);
|
|
44591
|
+
}
|
|
44592
|
+
return inner.spawn(command);
|
|
44593
|
+
}
|
|
44594
|
+
const observation = inner.spawn(wrap2.command);
|
|
44595
|
+
if (observation.kind === "spawn-error") {
|
|
44596
|
+
const detail = observation.errorMessage ?? "unknown spawn error";
|
|
44597
|
+
return spawnError(command, `sandbox spawn failed for "${command.path}" via ${platform} launcher: ${detail}`);
|
|
44598
|
+
}
|
|
44599
|
+
if (observation.kind === "clean-exit" && observation.exitCode === 71 && wrap2.wrapped) {
|
|
44600
|
+
return spawnError(command, `sandbox launcher returned exit 71 (EX_OSERR) for "${command.path}" on ${platform}; often missing/non-executable helper or path denied inside the sandbox`);
|
|
44601
|
+
}
|
|
44602
|
+
return observation;
|
|
44603
|
+
}
|
|
44604
|
+
}
|
|
44605
|
+
|
|
44606
|
+
// src/harness/process/sandbox/detect.ts
|
|
44607
|
+
function detectSandboxLauncher(opts = {}) {
|
|
44608
|
+
const platform = opts.platform ?? process.platform;
|
|
44609
|
+
const exists2 = opts.existsSync ?? realExistsSync;
|
|
44610
|
+
if (platform === "darwin") {
|
|
44611
|
+
if (exists2(SANDBOX_EXEC_PATH)) {
|
|
44612
|
+
return { available: true, platform, path: SANDBOX_EXEC_PATH };
|
|
44613
|
+
}
|
|
44614
|
+
return { available: false, platform, reason: `${SANDBOX_EXEC_PATH} not found` };
|
|
44615
|
+
}
|
|
44616
|
+
if (platform === "linux") {
|
|
44617
|
+
const env = opts.env ?? process.env;
|
|
44618
|
+
const dirs = (env.PATH ?? "").split(path133.delimiter).filter(Boolean);
|
|
44619
|
+
for (const dir of dirs) {
|
|
44620
|
+
const candidate = path133.join(dir, BWRAP_PROGRAM);
|
|
44621
|
+
if (exists2(candidate)) {
|
|
44622
|
+
return { available: true, platform, path: candidate };
|
|
44623
|
+
}
|
|
44624
|
+
}
|
|
44625
|
+
return {
|
|
44626
|
+
available: false,
|
|
44627
|
+
platform,
|
|
44628
|
+
reason: "bubblewrap (bwrap) not found on PATH; install it (apt install bubblewrap / dnf install bubblewrap)"
|
|
44629
|
+
};
|
|
44630
|
+
}
|
|
44631
|
+
return { available: false, platform, reason: `OS sandbox unsupported on platform "${platform}"` };
|
|
44632
|
+
}
|
|
44633
|
+
function resolveSandboxAdapter(profile, inner, opts = {}) {
|
|
44634
|
+
const info = detectSandboxLauncher(opts);
|
|
44635
|
+
const adapter = new SandboxedProcessAdapter({
|
|
44636
|
+
profile,
|
|
44637
|
+
inner,
|
|
44638
|
+
platform: info.platform,
|
|
44639
|
+
launcherAvailable: info.available,
|
|
44640
|
+
...info.path !== undefined ? { bwrapPath: info.path } : {},
|
|
44641
|
+
...opts.failIfUnavailable !== undefined ? { failIfUnavailable: opts.failIfUnavailable } : {}
|
|
44642
|
+
});
|
|
44643
|
+
return { adapter, info };
|
|
44644
|
+
}
|
|
44645
|
+
|
|
44646
|
+
// src/harness/process/sandbox/capability-matrix.ts
|
|
44647
|
+
var SANDBOX_CAPABILITY_MATRIX = [
|
|
44648
|
+
{ capability: "Filesystem containment", linux: "supported", darwin: "supported" },
|
|
44649
|
+
{ capability: "Network OFF", linux: "supported", darwin: "supported" },
|
|
44650
|
+
{ capability: "Domain allowlist", flag: "--allowed-domains", linux: "not-implemented", darwin: "supported" },
|
|
44651
|
+
{ capability: "Credential masking", flag: "--mask-env", linux: "not-implemented", darwin: "supported" }
|
|
44652
|
+
];
|
|
44653
|
+
function capabilityStatusFor(row, platform) {
|
|
44654
|
+
return platform === "linux" ? row.linux : row.darwin;
|
|
44655
|
+
}
|
|
44656
|
+
function isKnownSandboxPlatform(platform) {
|
|
44657
|
+
return platform === "linux" || platform === "darwin";
|
|
44658
|
+
}
|
|
44659
|
+
|
|
44660
|
+
// src/commands/sandbox.ts
|
|
44661
|
+
var LAUNCHER_NAME = {
|
|
44662
|
+
linux: "bubblewrap (bwrap)",
|
|
44663
|
+
darwin: "Seatbelt (sandbox-exec)"
|
|
44664
|
+
};
|
|
44665
|
+
var LAUNCHER_NOT_INSTALLED = "launcher not installed";
|
|
44666
|
+
var NOT_IMPLEMENTED_ON_PLATFORM = "not implemented on this platform";
|
|
44667
|
+
function buildSandboxReport(deps = {}) {
|
|
44668
|
+
const info = detectSandboxLauncher(deps);
|
|
44669
|
+
const platform = info.platform;
|
|
44670
|
+
const known = isKnownSandboxPlatform(platform);
|
|
44671
|
+
const makeRow = (row, kind, status) => ({
|
|
44672
|
+
capability: row.capability,
|
|
44673
|
+
kind,
|
|
44674
|
+
status,
|
|
44675
|
+
...row.flag !== undefined ? { flag: row.flag } : {}
|
|
44676
|
+
});
|
|
44677
|
+
const capabilities = SANDBOX_CAPABILITY_MATRIX.map((row) => {
|
|
44678
|
+
if (!known) {
|
|
44679
|
+
return makeRow(row, "not-implemented", `${NOT_IMPLEMENTED_ON_PLATFORM} ("${platform}") \u2014 the OS sandbox has no support for this platform at all.`);
|
|
44680
|
+
}
|
|
44681
|
+
const status = capabilityStatusFor(row, platform);
|
|
44682
|
+
if (status === "not-implemented") {
|
|
44683
|
+
return makeRow(row, "not-implemented", `${NOT_IMPLEMENTED_ON_PLATFORM} \u2014 installing the OS sandbox launcher would not change this.`);
|
|
44684
|
+
}
|
|
44685
|
+
if (!info.available) {
|
|
44686
|
+
return makeRow(row, "launcher-missing", `requires ${LAUNCHER_NAME[platform]}; ${LAUNCHER_NOT_INSTALLED}.`);
|
|
44687
|
+
}
|
|
44688
|
+
return makeRow(row, "available", "available.");
|
|
44689
|
+
});
|
|
44690
|
+
return {
|
|
44691
|
+
platform,
|
|
44692
|
+
launcher: info,
|
|
44693
|
+
launcherName: known ? LAUNCHER_NAME[platform] : undefined,
|
|
44694
|
+
capabilities
|
|
44695
|
+
};
|
|
44696
|
+
}
|
|
44697
|
+
function renderSandboxReport(report) {
|
|
44698
|
+
const lines = [];
|
|
44699
|
+
lines.push("keryx sandbox status");
|
|
44700
|
+
lines.push("");
|
|
44701
|
+
lines.push(`Platform: ${report.platform}`);
|
|
44702
|
+
if (report.launcher.available) {
|
|
44703
|
+
lines.push(`Launcher: available (${report.launcherName ?? "unknown"}${report.launcher.path ? ` at ${report.launcher.path}` : ""})`);
|
|
44704
|
+
} else {
|
|
44705
|
+
lines.push(`Launcher: not found${report.launcherName ? ` (${report.launcherName})` : ""} \u2014 ${report.launcher.reason ?? "unavailable"}`);
|
|
44706
|
+
}
|
|
44707
|
+
lines.push("");
|
|
44708
|
+
lines.push("Capability matrix (this platform):");
|
|
44709
|
+
for (const cap of report.capabilities) {
|
|
44710
|
+
const flagNote = cap.flag ? ` (${cap.flag})` : "";
|
|
44711
|
+
lines.push(` - ${cap.capability}${flagNote}: ${cap.status}`);
|
|
44712
|
+
}
|
|
44713
|
+
lines.push("");
|
|
44714
|
+
lines.push("This is a report, not a gate \u2014 it always exits 0. A contained run still fails closed " + "regardless of what this prints; nothing here changes that behaviour.");
|
|
44715
|
+
return lines.join(`
|
|
44716
|
+
`);
|
|
44717
|
+
}
|
|
44718
|
+
function printHelp14() {
|
|
44719
|
+
console.log(`keryx sandbox status [--json] \u2014 OS sandbox launcher availability and the per-capability containment matrix
|
|
44720
|
+
|
|
44721
|
+
Usage:
|
|
44722
|
+
keryx sandbox status [--json]
|
|
44723
|
+
|
|
44724
|
+
Reports, for the current platform, whether the OS sandbox launcher (bubblewrap
|
|
44725
|
+
on Linux, Seatbelt on macOS) is installed, and for each containment capability
|
|
44726
|
+
(filesystem containment, network-off, domain allowlist, credential masking)
|
|
44727
|
+
whether it is available, blocked only on the launcher being missing, or not
|
|
44728
|
+
implemented on this platform at all \u2014 those are different findings and are
|
|
44729
|
+
never worded the same way. This command never runs a contained command and
|
|
44730
|
+
always exits 0: it is a report, not a gate. Run it any time; installation also
|
|
44731
|
+
prints this same information once, up front.
|
|
44732
|
+
`);
|
|
44733
|
+
}
|
|
44734
|
+
async function sandboxCommand(args2 = [], deps = {}) {
|
|
44735
|
+
const subcommand = args2[0];
|
|
44736
|
+
if (subcommand === "--help" || subcommand === "-h") {
|
|
44737
|
+
printHelp14();
|
|
44738
|
+
return;
|
|
44739
|
+
}
|
|
44740
|
+
if (subcommand !== undefined && subcommand !== "status" && subcommand !== "--json") {
|
|
44741
|
+
console.error(`Unknown sandbox command: ${subcommand}`);
|
|
44742
|
+
printHelp14();
|
|
44743
|
+
process.exitCode = 1;
|
|
44744
|
+
return;
|
|
44745
|
+
}
|
|
44746
|
+
const json = args2.includes("--json");
|
|
44747
|
+
const report = buildSandboxReport(deps);
|
|
44748
|
+
if (json) {
|
|
44749
|
+
console.log(JSON.stringify(report, null, 2));
|
|
44750
|
+
} else {
|
|
44751
|
+
console.log(renderSandboxReport(report));
|
|
44752
|
+
}
|
|
44753
|
+
}
|
|
44754
|
+
|
|
43943
44755
|
// src/commands/mcp.ts
|
|
43944
44756
|
init_args();
|
|
43945
|
-
import
|
|
44757
|
+
import path139 from "path";
|
|
43946
44758
|
|
|
43947
44759
|
// src/mcp/discovery.ts
|
|
43948
44760
|
init_fs();
|
|
43949
44761
|
init_json();
|
|
43950
|
-
import
|
|
44762
|
+
import path134 from "path";
|
|
43951
44763
|
var MODULE_MANIFEST_KEY = {
|
|
43952
44764
|
gdgraph: "gdgraph",
|
|
43953
44765
|
security: "security",
|
|
@@ -44009,7 +44821,7 @@ function buildDiscovery(manifest) {
|
|
|
44009
44821
|
};
|
|
44010
44822
|
}
|
|
44011
44823
|
async function loadDiscovery(cwd) {
|
|
44012
|
-
const manifestPath =
|
|
44824
|
+
const manifestPath = path134.join(cwd, ".metaproject", "metaproject.json");
|
|
44013
44825
|
if (!await pathExists(manifestPath)) {
|
|
44014
44826
|
return buildDiscovery({});
|
|
44015
44827
|
}
|
|
@@ -44037,8 +44849,8 @@ async function invokeStructured(op, port, params) {
|
|
|
44037
44849
|
switch (op.name) {
|
|
44038
44850
|
case "search_code": {
|
|
44039
44851
|
const pattern = stringParam(params, "pattern") ?? "";
|
|
44040
|
-
const
|
|
44041
|
-
return port.searchCode({ pattern, ...
|
|
44852
|
+
const path135 = stringParam(params, "path");
|
|
44853
|
+
return port.searchCode({ pattern, ...path135 !== undefined ? { path: path135 } : {} });
|
|
44042
44854
|
}
|
|
44043
44855
|
case "graph_affected": {
|
|
44044
44856
|
const target = stringParam(params, "file") ?? stringParam(params, "target") ?? "";
|
|
@@ -44053,8 +44865,8 @@ async function invokeStructured(op, port, params) {
|
|
|
44053
44865
|
return port.memorySearch({ query });
|
|
44054
44866
|
}
|
|
44055
44867
|
case "read_wiki": {
|
|
44056
|
-
const
|
|
44057
|
-
return port.readWiki({ path:
|
|
44868
|
+
const path135 = stringParam(params, "path") ?? "";
|
|
44869
|
+
return port.readWiki({ path: path135 });
|
|
44058
44870
|
}
|
|
44059
44871
|
default:
|
|
44060
44872
|
return op.invoke(port, params);
|
|
@@ -44080,17 +44892,17 @@ init_sac();
|
|
|
44080
44892
|
init_workspace_service();
|
|
44081
44893
|
init_store2();
|
|
44082
44894
|
init_fs();
|
|
44083
|
-
import { createHash as
|
|
44895
|
+
import { createHash as createHash24, randomUUID as randomUUID15 } from "crypto";
|
|
44084
44896
|
import { appendFile as appendFile5, mkdir as mkdir52, open as open2, readFile as readFile73, rename as rename6, rm as rm7, stat as stat6, writeFile as writeFile46 } from "fs/promises";
|
|
44085
|
-
import
|
|
44897
|
+
import path135 from "path";
|
|
44086
44898
|
|
|
44087
44899
|
// src/sac/policy-experiment.ts
|
|
44088
|
-
import { createHash as
|
|
44900
|
+
import { createHash as createHash23, createHmac as createHmac2 } from "crypto";
|
|
44089
44901
|
|
|
44090
44902
|
// src/sac/receipt-integrity.ts
|
|
44091
44903
|
init_sac();
|
|
44092
|
-
import { createHash as
|
|
44093
|
-
var sha2563 = (value) =>
|
|
44904
|
+
import { createHash as createHash22 } from "crypto";
|
|
44905
|
+
var sha2563 = (value) => createHash22("sha256").update(value, "utf8").digest("hex");
|
|
44094
44906
|
function bodyOf(receipt) {
|
|
44095
44907
|
const { integrity: _integrity, ...body } = receipt;
|
|
44096
44908
|
return body;
|
|
@@ -44147,7 +44959,7 @@ function stableValue(value) {
|
|
|
44147
44959
|
return value;
|
|
44148
44960
|
}
|
|
44149
44961
|
var stableJson2 = (value) => JSON.stringify(stableValue(value));
|
|
44150
|
-
var sha2564 = (value) =>
|
|
44962
|
+
var sha2564 = (value) => createHash23("sha256").update(stableJson2(value), "utf8").digest("hex");
|
|
44151
44963
|
var workspacePathPattern2 = /^\.\/(?!.*(?:^|\/)\.\.(?:\/|$))(?:[A-Za-z0-9._-]+\/)*[A-Za-z0-9._-]+$/;
|
|
44152
44964
|
var trustedOutcomes = new WeakMap;
|
|
44153
44965
|
var trustedSandboxEvidence = new WeakMap;
|
|
@@ -44167,7 +44979,7 @@ function policyCorpusDigest(manifest, rows, quarantine) {
|
|
|
44167
44979
|
function splitFor(receiptHash, outcome, split) {
|
|
44168
44980
|
if (outcome.caseClass === "adversarial")
|
|
44169
44981
|
return "adversarial";
|
|
44170
|
-
const byte = Number.parseInt(
|
|
44982
|
+
const byte = Number.parseInt(createHash23("sha256").update(`${split.seed}\x00${receiptHash}`).digest("hex").slice(0, 2), 16);
|
|
44171
44983
|
return byte / 256 * 100 < split.holdoutPercent ? "holdout" : "train";
|
|
44172
44984
|
}
|
|
44173
44985
|
function quarantineCounts(entries) {
|
|
@@ -44251,12 +45063,12 @@ var stableValue2 = (value) => {
|
|
|
44251
45063
|
return value;
|
|
44252
45064
|
};
|
|
44253
45065
|
var stableJson3 = (value) => JSON.stringify(stableValue2(value));
|
|
44254
|
-
var sha2565 = (value) =>
|
|
45066
|
+
var sha2565 = (value) => createHash24("sha256").update(stableJson3(value), "utf8").digest("hex");
|
|
44255
45067
|
var isImmutableVersion2 = (value) => typeof value === "string" && value.length >= 3 && value.length <= 256 && /^[A-Za-z0-9][A-Za-z0-9._:+-]*$/.test(value) && /\d/.test(value) && !immutableVersionPattern.test(value);
|
|
44256
45068
|
var receiptHashPattern2 = /^[a-f0-9]{64}$/;
|
|
44257
45069
|
var missingIdentity = Object.freeze({ ledgerBytes: 0, device: "0", inode: "0", modifiedNs: "0", changedNs: "0" });
|
|
44258
45070
|
var isMissing = (error2) => error2 instanceof Error && error2.code === "ENOENT";
|
|
44259
|
-
var checkpointHash = (body) =>
|
|
45071
|
+
var checkpointHash = (body) => createHash24("sha256").update(JSON.stringify(body), "utf8").digest("hex");
|
|
44260
45072
|
async function ledgerIdentity(ledger) {
|
|
44261
45073
|
const value = await stat6(ledger, { bigint: true });
|
|
44262
45074
|
if (value.size > BigInt(Number.MAX_SAFE_INTEGER))
|
|
@@ -44401,7 +45213,7 @@ var isConfigRecord = (value) => typeof value.enabled === "boolean" && typeof val
|
|
|
44401
45213
|
async function readPinnedJson(input2) {
|
|
44402
45214
|
const ref = await resolveWorkspaceReference({ workspaceRoot: input2.workspaceRoot, kind: "artifact", uri: input2.uri });
|
|
44403
45215
|
const raw = await readFile73(ref, "utf8");
|
|
44404
|
-
const digest2 =
|
|
45216
|
+
const digest2 = createHash24("sha256").update(raw).digest("hex");
|
|
44405
45217
|
if (digest2 !== input2.digest)
|
|
44406
45218
|
throw new Error("policy experiment pinned artifact digest mismatch");
|
|
44407
45219
|
return { artifact: JSON.parse(raw), digest: digest2 };
|
|
@@ -44416,7 +45228,7 @@ function hasReportIntegrity(report) {
|
|
|
44416
45228
|
return reportHashPattern.test(reportDigest) && sha2565(body) === reportDigest;
|
|
44417
45229
|
}
|
|
44418
45230
|
async function resolvePolicySelection(workspaceRoot, canonicalFallback) {
|
|
44419
|
-
const configPath3 =
|
|
45231
|
+
const configPath3 = path135.join(workspaceRoot, ...policyExperimentConfigPath);
|
|
44420
45232
|
let configJson;
|
|
44421
45233
|
try {
|
|
44422
45234
|
const raw = await readFile73(configPath3, "utf8").catch((error2) => {
|
|
@@ -44518,7 +45330,7 @@ async function diagnosePolicyReadiness(workspaceRoot) {
|
|
|
44518
45330
|
const integrityReady = steps.every((entry) => entry.step === "activation-flags" || entry.status === "pass");
|
|
44519
45331
|
return Object.freeze({ configPresent, enabled: enabled2, killSwitch: killSwitch2, integrityReady, candidateWouldActivate: integrityReady && enabled2 && !killSwitch2, steps: Object.freeze([...steps]) });
|
|
44520
45332
|
};
|
|
44521
|
-
const configPath3 =
|
|
45333
|
+
const configPath3 = path135.join(workspaceRoot, ...policyExperimentConfigPath);
|
|
44522
45334
|
let configJson;
|
|
44523
45335
|
try {
|
|
44524
45336
|
const raw = await readFile73(configPath3, "utf8").catch((error2) => {
|
|
@@ -44749,9 +45561,9 @@ class FwkReadService {
|
|
|
44749
45561
|
const recordedAt = nowIso2(now);
|
|
44750
45562
|
const id = `receipt-${randomUUID15().replace(/-/g, "").slice(0, 16)}`;
|
|
44751
45563
|
const base = { schemaVersion: "1.0", id, workspaceId, actor, action, decision, recordedAt, cost: { tokens: 0, toolCalls: 1, elapsedMs: 0 }, contextAssembly: { traceRef: assembly.traceRef, configurationRevision: assembly.configurationRevision, selected: assembly.selected.map((id2) => `./ids/${id2}`), omittedOptional: assembly.omittedOptional.map((id2) => `./ids/${id2}`) }, policy: { ref: assembly.policyRef, revision: assembly.policyRevision }, ...action === "resource" ? { resourceRef: `./ids/${resourceId ?? "unknown"}` } : {} };
|
|
44752
|
-
const ledger =
|
|
44753
|
-
const checkpointPath =
|
|
44754
|
-
await mkdir52(
|
|
45564
|
+
const ledger = path135.join(this.options.canonical.workspaceRoot, ".metaproject", "context-operations", "access-receipts.jsonl");
|
|
45565
|
+
const checkpointPath = path135.join(path135.dirname(ledger), "access-receipts.checkpoint.json");
|
|
45566
|
+
await mkdir52(path135.dirname(ledger), { recursive: true, mode: 448 });
|
|
44755
45567
|
return withFileLock2(`${ledger}.lock`, async () => {
|
|
44756
45568
|
const state = await resolveLedgerState(ledger, checkpointPath, this.options.verifyReceiptLedger ?? verifyAccessReceiptLedger);
|
|
44757
45569
|
const receipt = sealAccessReceipt(base, state.headHash);
|
|
@@ -44797,12 +45609,12 @@ function createLocalFwkReadService(cwd, opts) {
|
|
|
44797
45609
|
const flow = manifest.resources.find((resource) => resource.kind === "flow");
|
|
44798
45610
|
const facts = await Promise.all(manifest.resources.filter((resource) => resource.kind === "evidence").map(async (resource, index) => {
|
|
44799
45611
|
const raw = await workspaces.readResourceForActor({ actorContext, workspaceId, resource });
|
|
44800
|
-
const revision =
|
|
45612
|
+
const revision = createHash24("sha256").update(raw).digest("hex");
|
|
44801
45613
|
return { id: `fact-${index}`, uri: resource.uri, revision: resource.revision ?? revision, observedAt: manifest.updatedAt, expiresAt: "9999-12-31T23:59:59Z", trust: "primary", visible: true, statement: `Evidence reference ${resource.uri}`, status: resource.revision === revision || resource.revision === undefined ? "fresh" : "stale" };
|
|
44802
45614
|
}));
|
|
44803
45615
|
const knowHow = await Promise.all(manifest.resources.filter((resource) => resource.kind === "wiki" || resource.kind === "memory" || resource.kind === "skill").map(async (resource, index) => {
|
|
44804
45616
|
const raw = await workspaces.readResourceForActor({ actorContext, workspaceId, resource, encoding: "utf8" });
|
|
44805
|
-
const revision =
|
|
45617
|
+
const revision = createHash24("sha256").update(raw).digest("hex");
|
|
44806
45618
|
const accepted = /^Status:\s*(accepted|reviewed)\s*$/mi.test(raw);
|
|
44807
45619
|
return { id: `knowhow-${index}`, kind: resource.kind, uri: resource.uri, revision: resource.revision ?? revision, trust: "accepted", status: resource.revision === revision || resource.revision === undefined ? "fresh" : "stale", accepted, visible: true };
|
|
44808
45620
|
}));
|
|
@@ -44844,7 +45656,7 @@ init_workspace_service();
|
|
|
44844
45656
|
init_sac();
|
|
44845
45657
|
import { appendFile as appendFile6, mkdir as mkdir53, readFile as readFile74 } from "fs/promises";
|
|
44846
45658
|
import { randomUUID as randomUUID16 } from "crypto";
|
|
44847
|
-
import
|
|
45659
|
+
import path136 from "path";
|
|
44848
45660
|
|
|
44849
45661
|
class CollaborationServiceError extends Error {
|
|
44850
45662
|
code;
|
|
@@ -44860,7 +45672,7 @@ class CollaborationService {
|
|
|
44860
45672
|
this.input = input2;
|
|
44861
45673
|
}
|
|
44862
45674
|
file(id) {
|
|
44863
|
-
return
|
|
45675
|
+
return path136.join(this.input.workspaceRoot, ".metaproject", "workspaces", id, "activity.jsonl");
|
|
44864
45676
|
}
|
|
44865
45677
|
async overview(input2) {
|
|
44866
45678
|
const manifest = await this.input.workspaces.show(input2);
|
|
@@ -44885,7 +45697,7 @@ class CollaborationService {
|
|
|
44885
45697
|
if (!isWorkspaceOwner(manifest.members, actor.subject))
|
|
44886
45698
|
throw new CollaborationServiceError("access_denied", "owner authority is required");
|
|
44887
45699
|
const activity = this.validate({ schemaVersion: "1.0", id: `activity-${randomUUID16()}`, workspaceId: manifest.id, actorSubject: actor.subject, occurredAt: (this.input.now ?? (() => new Date))().toISOString(), ...input2.activity });
|
|
44888
|
-
await mkdir53(
|
|
45700
|
+
await mkdir53(path136.dirname(this.file(manifest.id)), { recursive: true, mode: 448 });
|
|
44889
45701
|
await appendFile6(this.file(manifest.id), `${JSON.stringify(activity)}
|
|
44890
45702
|
`, { mode: 384 });
|
|
44891
45703
|
return normalizeCollaborationResult(activity);
|
|
@@ -44919,9 +45731,9 @@ init_workspace_service();
|
|
|
44919
45731
|
init_fs();
|
|
44920
45732
|
init_machine_wrap_up();
|
|
44921
45733
|
import { mkdir as mkdir54, readdir as readdir23, readFile as readFile75 } from "fs/promises";
|
|
44922
|
-
import
|
|
45734
|
+
import path137 from "path";
|
|
44923
45735
|
function externalSlatesDir(cwd) {
|
|
44924
|
-
return
|
|
45736
|
+
return path137.join(cwd, ".keryx", "external-slates");
|
|
44925
45737
|
}
|
|
44926
45738
|
var EXTERNAL_SESSION_ID_PATTERN = /^[A-Za-z0-9_-]{1,128}$/;
|
|
44927
45739
|
function assertValidExternalSessionId(externalSessionId) {
|
|
@@ -44931,14 +45743,14 @@ function assertValidExternalSessionId(externalSessionId) {
|
|
|
44931
45743
|
}
|
|
44932
45744
|
function externalSlatePath(cwd, externalSessionId) {
|
|
44933
45745
|
assertValidExternalSessionId(externalSessionId);
|
|
44934
|
-
return
|
|
45746
|
+
return path137.join(externalSlatesDir(cwd), `${externalSessionId}.json`);
|
|
44935
45747
|
}
|
|
44936
45748
|
function externalSlateLockPath(cwd, externalSessionId) {
|
|
44937
45749
|
return `${externalSlatePath(cwd, externalSessionId)}.lock`;
|
|
44938
45750
|
}
|
|
44939
45751
|
function externalSlateEvidenceDir(cwd, externalSessionId) {
|
|
44940
45752
|
assertValidExternalSessionId(externalSessionId);
|
|
44941
|
-
return
|
|
45753
|
+
return path137.join(externalSlatesDir(cwd), externalSessionId);
|
|
44942
45754
|
}
|
|
44943
45755
|
async function readExternalSlate(cwd, externalSessionId) {
|
|
44944
45756
|
try {
|
|
@@ -45487,7 +46299,7 @@ function buildToolRegistry() {
|
|
|
45487
46299
|
|
|
45488
46300
|
// src/mcp/resources.ts
|
|
45489
46301
|
init_fs();
|
|
45490
|
-
import
|
|
46302
|
+
import path138 from "path";
|
|
45491
46303
|
import { readdir as readdir24, readFile as readFile77, stat as stat7 } from "fs/promises";
|
|
45492
46304
|
var URI_PREFIX = "metaproject://";
|
|
45493
46305
|
function mimeForPath(filePath) {
|
|
@@ -45500,13 +46312,13 @@ function mimeForPath(filePath) {
|
|
|
45500
46312
|
return "text/plain";
|
|
45501
46313
|
}
|
|
45502
46314
|
function dataRoot3(cwd) {
|
|
45503
|
-
return
|
|
46315
|
+
return path138.join(cwd, ".metaproject", "data");
|
|
45504
46316
|
}
|
|
45505
46317
|
function wikiRoot(cwd) {
|
|
45506
|
-
return
|
|
46318
|
+
return path138.join(cwd, ".metaproject", "wiki");
|
|
45507
46319
|
}
|
|
45508
46320
|
function memoryRoot2(cwd) {
|
|
45509
|
-
return
|
|
46321
|
+
return path138.join(cwd, ".metaproject", "memory");
|
|
45510
46322
|
}
|
|
45511
46323
|
async function walkFiles(root) {
|
|
45512
46324
|
if (!await pathExists(root)) {
|
|
@@ -45520,7 +46332,7 @@ async function walkFiles(root) {
|
|
|
45520
46332
|
return [];
|
|
45521
46333
|
}
|
|
45522
46334
|
for (const entry of entries) {
|
|
45523
|
-
const full =
|
|
46335
|
+
const full = path138.join(root, entry.name);
|
|
45524
46336
|
if (entry.isDirectory()) {
|
|
45525
46337
|
out.push(...await walkFiles(full));
|
|
45526
46338
|
} else if (entry.isFile()) {
|
|
@@ -45545,9 +46357,9 @@ async function listArtifacts(cwd) {
|
|
|
45545
46357
|
if (!moduleEntry.isDirectory()) {
|
|
45546
46358
|
continue;
|
|
45547
46359
|
}
|
|
45548
|
-
const artifactsDir2 =
|
|
46360
|
+
const artifactsDir2 = path138.join(base, moduleEntry.name, "artifacts");
|
|
45549
46361
|
for (const file of await walkFiles(artifactsDir2)) {
|
|
45550
|
-
const rel = toPosix(
|
|
46362
|
+
const rel = toPosix(path138.relative(artifactsDir2, file));
|
|
45551
46363
|
const relPath = `${moduleEntry.name}/${rel}`;
|
|
45552
46364
|
listings.push({
|
|
45553
46365
|
uri: `${URI_PREFIX}artifacts/${relPath}`,
|
|
@@ -45561,7 +46373,7 @@ async function listArtifacts(cwd) {
|
|
|
45561
46373
|
async function listUnderRoot(cwd, cls, root) {
|
|
45562
46374
|
const listings = [];
|
|
45563
46375
|
for (const file of await walkFiles(root)) {
|
|
45564
|
-
const rel = toPosix(
|
|
46376
|
+
const rel = toPosix(path138.relative(root, file));
|
|
45565
46377
|
listings.push({
|
|
45566
46378
|
uri: `${URI_PREFIX}${cls}/${rel}`,
|
|
45567
46379
|
name: rel,
|
|
@@ -45611,12 +46423,12 @@ function resolveConfined(cwd, cls, relPath) {
|
|
|
45611
46423
|
if (moduleName.includes("..") || moduleName.length === 0) {
|
|
45612
46424
|
return null;
|
|
45613
46425
|
}
|
|
45614
|
-
const root2 =
|
|
45615
|
-
const absolute2 =
|
|
46426
|
+
const root2 = path138.join(dataRoot3(cwd), moduleName, "artifacts");
|
|
46427
|
+
const absolute2 = path138.resolve(root2, rest);
|
|
45616
46428
|
return isPathInside(root2, absolute2) ? { root: root2, absolute: absolute2 } : null;
|
|
45617
46429
|
}
|
|
45618
46430
|
const root = cls === "wiki" ? wikiRoot(cwd) : memoryRoot2(cwd);
|
|
45619
|
-
const absolute =
|
|
46431
|
+
const absolute = path138.resolve(root, relPath);
|
|
45620
46432
|
return isPathInside(root, absolute) ? { root, absolute } : null;
|
|
45621
46433
|
}
|
|
45622
46434
|
async function readResource(cwd, roots, uri) {
|
|
@@ -45816,7 +46628,7 @@ async function mcpCommand(args2 = [], cwd = process.cwd()) {
|
|
|
45816
46628
|
}
|
|
45817
46629
|
if (!subcommand || subcommand === "serve") {
|
|
45818
46630
|
const http = args2.includes("--http");
|
|
45819
|
-
const projectRoot =
|
|
46631
|
+
const projectRoot = path139.resolve(optionValue(args2, "--cwd") ?? cwd);
|
|
45820
46632
|
try {
|
|
45821
46633
|
await serveMcp({ cwd: projectRoot, http });
|
|
45822
46634
|
} catch (error2) {
|
|
@@ -45850,7 +46662,7 @@ async function handleInstall2(cwd, args2) {
|
|
|
45850
46662
|
console.log(outcome.snippet ?? "");
|
|
45851
46663
|
continue;
|
|
45852
46664
|
}
|
|
45853
|
-
const rel =
|
|
46665
|
+
const rel = path139.relative(cwd, outcome.filePath);
|
|
45854
46666
|
if (outcome.errors.length > 0) {
|
|
45855
46667
|
for (const e of outcome.errors) {
|
|
45856
46668
|
console.log(` ${style.red(symbols.cross)} ${e}`);
|
|
@@ -45890,7 +46702,7 @@ async function handleUninstall2(cwd, args2) {
|
|
|
45890
46702
|
console.log(` ${style.gray(symbols.off)} ${outcome.id} ${style.dim("no file to change")}`);
|
|
45891
46703
|
continue;
|
|
45892
46704
|
}
|
|
45893
|
-
const rel =
|
|
46705
|
+
const rel = path139.relative(cwd, outcome.filePath);
|
|
45894
46706
|
console.log(` ${outcome.removed ? style.green(symbols.ok) : style.gray(symbols.off)} ${outcome.id} ${style.dim(outcome.removed ? `removed from ${rel}` : "nothing to remove")}`);
|
|
45895
46707
|
}
|
|
45896
46708
|
}
|
|
@@ -45920,14 +46732,14 @@ function printMcpHelp() {
|
|
|
45920
46732
|
// src/commands/status.ts
|
|
45921
46733
|
init_fs();
|
|
45922
46734
|
init_json();
|
|
45923
|
-
import
|
|
46735
|
+
import path140 from "path";
|
|
45924
46736
|
async function statusCommand(args2 = []) {
|
|
45925
46737
|
if (args2.includes("--help") || args2.includes("-h")) {
|
|
45926
|
-
|
|
46738
|
+
printHelp15();
|
|
45927
46739
|
return;
|
|
45928
46740
|
}
|
|
45929
|
-
const root =
|
|
45930
|
-
const manifestPath =
|
|
46741
|
+
const root = path140.join(process.cwd(), ".metaproject");
|
|
46742
|
+
const manifestPath = path140.join(root, "metaproject.json");
|
|
45931
46743
|
if (!await pathExists(root)) {
|
|
45932
46744
|
console.log("Metaproject: not initialized");
|
|
45933
46745
|
console.log("Run: keryx init");
|
|
@@ -45954,7 +46766,7 @@ async function statusCommand(args2 = []) {
|
|
|
45954
46766
|
console.log(` ${name}: ${moduleConfig.enabled ? "enabled" : "disabled"}`);
|
|
45955
46767
|
}
|
|
45956
46768
|
}
|
|
45957
|
-
function
|
|
46769
|
+
function printHelp15() {
|
|
45958
46770
|
console.log(`keryx status \u2014 whether this project has a .metaproject workspace, and which modules are on
|
|
45959
46771
|
|
|
45960
46772
|
Usage:
|
|
@@ -46023,7 +46835,7 @@ init_make_provider();
|
|
|
46023
46835
|
init_profiles();
|
|
46024
46836
|
|
|
46025
46837
|
// src/harness/run/run.ts
|
|
46026
|
-
import { createHash as
|
|
46838
|
+
import { createHash as createHash28 } from "crypto";
|
|
46027
46839
|
|
|
46028
46840
|
// src/harness/completion/gate.ts
|
|
46029
46841
|
var SCHEMA_VERSION2 = 1;
|
|
@@ -46074,10 +46886,10 @@ function evaluateCompletion(input2, deps) {
|
|
|
46074
46886
|
}
|
|
46075
46887
|
|
|
46076
46888
|
// src/harness/evidence/redaction.ts
|
|
46077
|
-
import { createHash as
|
|
46889
|
+
import { createHash as createHash25 } from "crypto";
|
|
46078
46890
|
var CLEAN_CATEGORY = "none";
|
|
46079
46891
|
function hashContent(content) {
|
|
46080
|
-
return
|
|
46892
|
+
return createHash25("sha256").update(content, "utf8").digest("hex");
|
|
46081
46893
|
}
|
|
46082
46894
|
function redactForPersistence(content, deps) {
|
|
46083
46895
|
const scan2 = deps.scan(content);
|
|
@@ -46128,7 +46940,7 @@ async function metaprojectBlastRadius(port, target) {
|
|
|
46128
46940
|
}
|
|
46129
46941
|
|
|
46130
46942
|
// src/harness/session/session.ts
|
|
46131
|
-
import { createHash as
|
|
46943
|
+
import { createHash as createHash26 } from "crypto";
|
|
46132
46944
|
var SCHEMA_VERSION3 = 1;
|
|
46133
46945
|
function canonicalStringify(value) {
|
|
46134
46946
|
if (Array.isArray(value)) {
|
|
@@ -46142,7 +46954,7 @@ function canonicalStringify(value) {
|
|
|
46142
46954
|
return JSON.stringify(value) ?? "null";
|
|
46143
46955
|
}
|
|
46144
46956
|
function sha2566(input2) {
|
|
46145
|
-
return
|
|
46957
|
+
return createHash26("sha256").update(input2, "utf8").digest("hex");
|
|
46146
46958
|
}
|
|
46147
46959
|
function deepFreeze(value) {
|
|
46148
46960
|
if (value !== null && typeof value === "object") {
|
|
@@ -46234,11 +47046,11 @@ class AppendOnlySession {
|
|
|
46234
47046
|
}
|
|
46235
47047
|
|
|
46236
47048
|
// src/harness/context/manifest.ts
|
|
46237
|
-
import { createHash as
|
|
47049
|
+
import { createHash as createHash27 } from "crypto";
|
|
46238
47050
|
var MAX_CONTEXT_BYTES = 2 * 1024 * 1024;
|
|
46239
47051
|
var MAX_CONTEXT_TOKENS = 200000;
|
|
46240
47052
|
function sha2567(input2) {
|
|
46241
|
-
return
|
|
47053
|
+
return createHash27("sha256").update(input2, "utf8").digest("hex");
|
|
46242
47054
|
}
|
|
46243
47055
|
function normalizeSource(value) {
|
|
46244
47056
|
const record = typeof value === "object" && value !== null ? value : {};
|
|
@@ -46364,7 +47176,7 @@ function canonicalize2(value) {
|
|
|
46364
47176
|
return `{${keys.map((key) => `${JSON.stringify(key)}:${canonicalize2(record[key])}`).join(",")}}`;
|
|
46365
47177
|
}
|
|
46366
47178
|
function sha2568(input2) {
|
|
46367
|
-
return
|
|
47179
|
+
return createHash28("sha256").update(input2, "utf8").digest("hex");
|
|
46368
47180
|
}
|
|
46369
47181
|
function uniqueInOrder2(values) {
|
|
46370
47182
|
const seen = new Set;
|
|
@@ -46802,7 +47614,7 @@ function replayOffline(fixture, run, deps) {
|
|
|
46802
47614
|
}
|
|
46803
47615
|
|
|
46804
47616
|
// src/harness/tool/registry.ts
|
|
46805
|
-
import { createHash as
|
|
47617
|
+
import { createHash as createHash29 } from "crypto";
|
|
46806
47618
|
function canonicalize3(value) {
|
|
46807
47619
|
if (value === null || typeof value !== "object") {
|
|
46808
47620
|
return JSON.stringify(value) ?? "null";
|
|
@@ -46816,7 +47628,7 @@ function canonicalize3(value) {
|
|
|
46816
47628
|
return `{${entries.join(",")}}`;
|
|
46817
47629
|
}
|
|
46818
47630
|
function sha2569(input2) {
|
|
46819
|
-
return
|
|
47631
|
+
return createHash29("sha256").update(input2, "utf8").digest("hex");
|
|
46820
47632
|
}
|
|
46821
47633
|
function definitionHash(definition) {
|
|
46822
47634
|
return sha2569(canonicalize3(definition));
|
|
@@ -46967,11 +47779,11 @@ function runContainedProcess(input2, deps) {
|
|
|
46967
47779
|
|
|
46968
47780
|
// src/harness/process/real-process-adapter.ts
|
|
46969
47781
|
import { spawnSync } from "child_process";
|
|
46970
|
-
import { createHash as
|
|
47782
|
+
import { createHash as createHash30 } from "crypto";
|
|
46971
47783
|
var DEFAULT_TIMEOUT_MS = 5000;
|
|
46972
47784
|
var DEFAULT_MAX_OUTPUT_BYTES = 10 * 1024 * 1024;
|
|
46973
47785
|
function sha256Hex4(input2) {
|
|
46974
|
-
return
|
|
47786
|
+
return createHash30("sha256").update(input2, "utf8").digest("hex");
|
|
46975
47787
|
}
|
|
46976
47788
|
function byteLength(chunk) {
|
|
46977
47789
|
if (chunk === null || chunk === undefined)
|
|
@@ -47040,7 +47852,7 @@ ${command.argv.join(" ")}`);
|
|
|
47040
47852
|
}
|
|
47041
47853
|
|
|
47042
47854
|
// src/harness/process/sandbox/profile.ts
|
|
47043
|
-
import
|
|
47855
|
+
import path141 from "path";
|
|
47044
47856
|
var DEFAULT_SECRET_SUBPATHS = [
|
|
47045
47857
|
".ssh",
|
|
47046
47858
|
".gnupg",
|
|
@@ -47065,7 +47877,7 @@ function defaultReadDenyList(home) {
|
|
|
47065
47877
|
if (!home) {
|
|
47066
47878
|
return [];
|
|
47067
47879
|
}
|
|
47068
|
-
return DEFAULT_SECRET_SUBPATHS.map((sub) =>
|
|
47880
|
+
return DEFAULT_SECRET_SUBPATHS.map((sub) => path141.join(home, sub));
|
|
47069
47881
|
}
|
|
47070
47882
|
function defaultSandboxProfile(cwd, tmpDir, home) {
|
|
47071
47883
|
return {
|
|
@@ -47081,230 +47893,6 @@ function dedupe2(values) {
|
|
|
47081
47893
|
return [...new Set(values.filter((v) => v.length > 0))];
|
|
47082
47894
|
}
|
|
47083
47895
|
|
|
47084
|
-
// src/harness/process/sandbox/detect.ts
|
|
47085
|
-
import { existsSync as realExistsSync } from "fs";
|
|
47086
|
-
import path141 from "path";
|
|
47087
|
-
|
|
47088
|
-
// src/harness/process/sandbox/seatbelt.ts
|
|
47089
|
-
var SANDBOX_EXEC_PATH = "/usr/bin/sandbox-exec";
|
|
47090
|
-
function sbplString(value) {
|
|
47091
|
-
return `"${value.replace(/\\/g, "\\\\").replace(/"/g, "\\\"")}"`;
|
|
47092
|
-
}
|
|
47093
|
-
var DEVICE_WRITE_LITERALS = [
|
|
47094
|
-
"/dev/null",
|
|
47095
|
-
"/dev/zero",
|
|
47096
|
-
"/dev/stdin",
|
|
47097
|
-
"/dev/stdout",
|
|
47098
|
-
"/dev/stderr",
|
|
47099
|
-
"/dev/tty",
|
|
47100
|
-
"/dev/dtracehelper",
|
|
47101
|
-
"/dev/random",
|
|
47102
|
-
"/dev/urandom"
|
|
47103
|
-
];
|
|
47104
|
-
function buildSeatbeltProfile(profile) {
|
|
47105
|
-
const lines = [
|
|
47106
|
-
"(version 1)",
|
|
47107
|
-
"(allow default)",
|
|
47108
|
-
"",
|
|
47109
|
-
";; --- filesystem writes: deny everything, then re-allow workspace roots ---",
|
|
47110
|
-
'(deny file-write* (subpath "/"))'
|
|
47111
|
-
];
|
|
47112
|
-
for (const root of profile.writableRoots) {
|
|
47113
|
-
lines.push(`(allow file-write* (subpath ${sbplString(root)}))`);
|
|
47114
|
-
}
|
|
47115
|
-
lines.push(`(allow file-write-data ${DEVICE_WRITE_LITERALS.map((d) => `(literal ${sbplString(d)})`).join(" ")})`);
|
|
47116
|
-
if (profile.readDenyList.length > 0) {
|
|
47117
|
-
lines.push("", ";; --- secret read-deny ---");
|
|
47118
|
-
for (const secret of profile.readDenyList) {
|
|
47119
|
-
lines.push(`(deny file-read* (subpath ${sbplString(secret)}))`);
|
|
47120
|
-
}
|
|
47121
|
-
}
|
|
47122
|
-
if (profile.network === "off") {
|
|
47123
|
-
lines.push("", ";; --- network off ---", "(deny network*)");
|
|
47124
|
-
} else if (profile.network === "restricted") {
|
|
47125
|
-
lines.push("", ";; --- network restricted to loopback allowlist proxy ---", "(deny network*)");
|
|
47126
|
-
if (profile.proxy) {
|
|
47127
|
-
lines.push(`(allow network-outbound (remote ip ${sbplString(`localhost:${profile.proxy.port}`)}))`);
|
|
47128
|
-
}
|
|
47129
|
-
}
|
|
47130
|
-
return `${lines.join(`
|
|
47131
|
-
`)}
|
|
47132
|
-
`;
|
|
47133
|
-
}
|
|
47134
|
-
function wrapSeatbelt(command, profile) {
|
|
47135
|
-
const profileText = buildSeatbeltProfile(profile);
|
|
47136
|
-
return {
|
|
47137
|
-
path: SANDBOX_EXEC_PATH,
|
|
47138
|
-
argv: ["sandbox-exec", "-p", profileText, command.path, ...command.argv.slice(1)],
|
|
47139
|
-
env: command.env,
|
|
47140
|
-
cwd: command.cwd
|
|
47141
|
-
};
|
|
47142
|
-
}
|
|
47143
|
-
|
|
47144
|
-
// src/harness/process/sandbox/bwrap.ts
|
|
47145
|
-
import { statSync as statSync4 } from "fs";
|
|
47146
|
-
var BWRAP_PROGRAM = "bwrap";
|
|
47147
|
-
function inspectMaskTarget(target) {
|
|
47148
|
-
try {
|
|
47149
|
-
return statSync4(target).isDirectory() ? "dir" : "file";
|
|
47150
|
-
} catch {
|
|
47151
|
-
return "missing";
|
|
47152
|
-
}
|
|
47153
|
-
}
|
|
47154
|
-
function buildBwrapArgs(profile, inspect = inspectMaskTarget) {
|
|
47155
|
-
const args2 = [
|
|
47156
|
-
"--ro-bind",
|
|
47157
|
-
"/",
|
|
47158
|
-
"/",
|
|
47159
|
-
"--dev",
|
|
47160
|
-
"/dev",
|
|
47161
|
-
"--proc",
|
|
47162
|
-
"/proc",
|
|
47163
|
-
"--tmpfs",
|
|
47164
|
-
"/tmp"
|
|
47165
|
-
];
|
|
47166
|
-
for (const root of profile.writableRoots) {
|
|
47167
|
-
args2.push("--bind", root, root);
|
|
47168
|
-
}
|
|
47169
|
-
for (const secret of profile.readDenyList) {
|
|
47170
|
-
const kind = inspect(secret);
|
|
47171
|
-
if (kind === "dir") {
|
|
47172
|
-
args2.push("--tmpfs", secret);
|
|
47173
|
-
} else if (kind === "file") {
|
|
47174
|
-
args2.push("--ro-bind", "/dev/null", secret);
|
|
47175
|
-
}
|
|
47176
|
-
}
|
|
47177
|
-
if (profile.network === "off") {
|
|
47178
|
-
args2.push("--unshare-net");
|
|
47179
|
-
}
|
|
47180
|
-
args2.push("--unshare-pid", "--unshare-ipc", "--die-with-parent", "--new-session");
|
|
47181
|
-
return args2;
|
|
47182
|
-
}
|
|
47183
|
-
function wrapBwrap(command, profile, launcherPath = BWRAP_PROGRAM) {
|
|
47184
|
-
return {
|
|
47185
|
-
path: launcherPath,
|
|
47186
|
-
argv: [BWRAP_PROGRAM, ...buildBwrapArgs(profile), "--", command.path, ...command.argv.slice(1)],
|
|
47187
|
-
env: command.env,
|
|
47188
|
-
cwd: command.cwd
|
|
47189
|
-
};
|
|
47190
|
-
}
|
|
47191
|
-
|
|
47192
|
-
// src/harness/process/sandbox/adapter.ts
|
|
47193
|
-
import { createHash as createHash30 } from "crypto";
|
|
47194
|
-
|
|
47195
|
-
// src/harness/process/sandbox/wrap.ts
|
|
47196
|
-
function wrapWithSandbox(command, profile, opts) {
|
|
47197
|
-
if (profile.mode === "danger-full-access") {
|
|
47198
|
-
return { ok: true, command, wrapped: false };
|
|
47199
|
-
}
|
|
47200
|
-
if (opts.platform === "darwin") {
|
|
47201
|
-
return { ok: true, command: wrapSeatbelt(command, profile), wrapped: true };
|
|
47202
|
-
}
|
|
47203
|
-
if (opts.platform === "linux") {
|
|
47204
|
-
if (profile.network === "restricted") {
|
|
47205
|
-
return {
|
|
47206
|
-
ok: false,
|
|
47207
|
-
reason: "network=restricted is not yet enforced on Linux (needs a network namespace + proxy relay); use network off/on or run inside a container."
|
|
47208
|
-
};
|
|
47209
|
-
}
|
|
47210
|
-
const wrapped = opts.bwrapPath ? wrapBwrap(command, profile, opts.bwrapPath) : wrapBwrap(command, profile);
|
|
47211
|
-
return { ok: true, command: wrapped, wrapped: true };
|
|
47212
|
-
}
|
|
47213
|
-
return {
|
|
47214
|
-
ok: false,
|
|
47215
|
-
reason: `OS sandbox is unsupported on platform "${opts.platform}"; run inside WSL2 or a container, or use an explicit danger-full-access override.`
|
|
47216
|
-
};
|
|
47217
|
-
}
|
|
47218
|
-
|
|
47219
|
-
// src/harness/process/sandbox/adapter.ts
|
|
47220
|
-
function spawnError(command, message2) {
|
|
47221
|
-
return {
|
|
47222
|
-
kind: "spawn-error",
|
|
47223
|
-
observedHash: createHash30("sha256").update(`${command.path}
|
|
47224
|
-
${command.argv.join(" ")}`, "utf8").digest("hex"),
|
|
47225
|
-
errorMessage: message2
|
|
47226
|
-
};
|
|
47227
|
-
}
|
|
47228
|
-
|
|
47229
|
-
class SandboxedProcessAdapter {
|
|
47230
|
-
opts;
|
|
47231
|
-
constructor(opts) {
|
|
47232
|
-
this.opts = opts;
|
|
47233
|
-
}
|
|
47234
|
-
spawn(command) {
|
|
47235
|
-
const { profile, inner, platform, launcherAvailable, bwrapPath } = this.opts;
|
|
47236
|
-
const failClosed = profile.required || profile.network === "restricted" || (this.opts.failIfUnavailable ?? true);
|
|
47237
|
-
if (profile.mode === "danger-full-access") {
|
|
47238
|
-
return inner.spawn(command);
|
|
47239
|
-
}
|
|
47240
|
-
if (!launcherAvailable) {
|
|
47241
|
-
if (failClosed) {
|
|
47242
|
-
return spawnError(command, `OS sandbox launcher unavailable on ${platform} for program "${command.path}"; failing closed (install bubblewrap on Linux, or relax failIfUnavailable to run unsandboxed).`);
|
|
47243
|
-
}
|
|
47244
|
-
return inner.spawn(command);
|
|
47245
|
-
}
|
|
47246
|
-
const wrap2 = wrapWithSandbox(command, profile, {
|
|
47247
|
-
platform,
|
|
47248
|
-
...bwrapPath !== undefined ? { bwrapPath } : {}
|
|
47249
|
-
});
|
|
47250
|
-
if (!wrap2.ok) {
|
|
47251
|
-
if (failClosed) {
|
|
47252
|
-
return spawnError(command, `sandbox wrap refused program "${command.path}" on ${platform}: ${wrap2.reason}`);
|
|
47253
|
-
}
|
|
47254
|
-
return inner.spawn(command);
|
|
47255
|
-
}
|
|
47256
|
-
const observation = inner.spawn(wrap2.command);
|
|
47257
|
-
if (observation.kind === "spawn-error") {
|
|
47258
|
-
const detail = observation.errorMessage ?? "unknown spawn error";
|
|
47259
|
-
return spawnError(command, `sandbox spawn failed for "${command.path}" via ${platform} launcher: ${detail}`);
|
|
47260
|
-
}
|
|
47261
|
-
if (observation.kind === "clean-exit" && observation.exitCode === 71 && wrap2.wrapped) {
|
|
47262
|
-
return spawnError(command, `sandbox launcher returned exit 71 (EX_OSERR) for "${command.path}" on ${platform}; often missing/non-executable helper or path denied inside the sandbox`);
|
|
47263
|
-
}
|
|
47264
|
-
return observation;
|
|
47265
|
-
}
|
|
47266
|
-
}
|
|
47267
|
-
|
|
47268
|
-
// src/harness/process/sandbox/detect.ts
|
|
47269
|
-
function detectSandboxLauncher(opts = {}) {
|
|
47270
|
-
const platform = opts.platform ?? process.platform;
|
|
47271
|
-
const exists2 = opts.existsSync ?? realExistsSync;
|
|
47272
|
-
if (platform === "darwin") {
|
|
47273
|
-
if (exists2(SANDBOX_EXEC_PATH)) {
|
|
47274
|
-
return { available: true, platform, path: SANDBOX_EXEC_PATH };
|
|
47275
|
-
}
|
|
47276
|
-
return { available: false, platform, reason: `${SANDBOX_EXEC_PATH} not found` };
|
|
47277
|
-
}
|
|
47278
|
-
if (platform === "linux") {
|
|
47279
|
-
const env = opts.env ?? process.env;
|
|
47280
|
-
const dirs = (env.PATH ?? "").split(path141.delimiter).filter(Boolean);
|
|
47281
|
-
for (const dir of dirs) {
|
|
47282
|
-
const candidate = path141.join(dir, BWRAP_PROGRAM);
|
|
47283
|
-
if (exists2(candidate)) {
|
|
47284
|
-
return { available: true, platform, path: candidate };
|
|
47285
|
-
}
|
|
47286
|
-
}
|
|
47287
|
-
return {
|
|
47288
|
-
available: false,
|
|
47289
|
-
platform,
|
|
47290
|
-
reason: "bubblewrap (bwrap) not found on PATH; install it (apt install bubblewrap / dnf install bubblewrap)"
|
|
47291
|
-
};
|
|
47292
|
-
}
|
|
47293
|
-
return { available: false, platform, reason: `OS sandbox unsupported on platform "${platform}"` };
|
|
47294
|
-
}
|
|
47295
|
-
function resolveSandboxAdapter(profile, inner, opts = {}) {
|
|
47296
|
-
const info = detectSandboxLauncher(opts);
|
|
47297
|
-
const adapter = new SandboxedProcessAdapter({
|
|
47298
|
-
profile,
|
|
47299
|
-
inner,
|
|
47300
|
-
platform: info.platform,
|
|
47301
|
-
launcherAvailable: info.available,
|
|
47302
|
-
...info.path !== undefined ? { bwrapPath: info.path } : {},
|
|
47303
|
-
...opts.failIfUnavailable !== undefined ? { failIfUnavailable: opts.failIfUnavailable } : {}
|
|
47304
|
-
});
|
|
47305
|
-
return { adapter, info };
|
|
47306
|
-
}
|
|
47307
|
-
|
|
47308
47896
|
// src/lib/sandbox-config.ts
|
|
47309
47897
|
init_config_dir();
|
|
47310
47898
|
init_shell_config();
|
|
@@ -47952,7 +48540,7 @@ function parseArgs2(args2) {
|
|
|
47952
48540
|
let unattended;
|
|
47953
48541
|
let goal;
|
|
47954
48542
|
let workspace;
|
|
47955
|
-
const
|
|
48543
|
+
const positional2 = [];
|
|
47956
48544
|
for (let i = 1;i < args2.length; i++) {
|
|
47957
48545
|
const arg = args2[i];
|
|
47958
48546
|
if (arg === "--provider") {
|
|
@@ -47972,10 +48560,10 @@ function parseArgs2(args2) {
|
|
|
47972
48560
|
const next = args2[i + 1];
|
|
47973
48561
|
workspace = next !== undefined && !KNOWN_HARNESS_RUN_FLAGS.has(next) ? args2[++i] : undefined;
|
|
47974
48562
|
} else if (arg !== undefined) {
|
|
47975
|
-
|
|
48563
|
+
positional2.push(arg);
|
|
47976
48564
|
}
|
|
47977
48565
|
}
|
|
47978
|
-
const parsed = { provider, model, prompt: goal !== undefined && goal.length > 0 ? goal :
|
|
48566
|
+
const parsed = { provider, model, prompt: goal !== undefined && goal.length > 0 ? goal : positional2.join(" ") };
|
|
47979
48567
|
if (baseUrl !== undefined)
|
|
47980
48568
|
parsed.baseUrl = baseUrl;
|
|
47981
48569
|
if (record !== undefined)
|
|
@@ -50682,7 +51270,7 @@ function suggestShellPatterns(command) {
|
|
|
50682
51270
|
exact,
|
|
50683
51271
|
prefix,
|
|
50684
51272
|
offerExact: !neverRemember && validateShellPattern(exact).ok,
|
|
50685
|
-
offerPrefix: !neverRemember && validateShellPattern(prefix).ok
|
|
51273
|
+
offerPrefix: !neverRemember && validateShellPattern(prefix).ok && isShellCommandAllowed(trimmed, [prefix])
|
|
50686
51274
|
};
|
|
50687
51275
|
}
|
|
50688
51276
|
function parseShellExecCommand(inputJson) {
|
|
@@ -53997,7 +54585,7 @@ import { spawnSync as spawnSync2 } from "child_process";
|
|
|
53997
54585
|
// package.json
|
|
53998
54586
|
var package_default = {
|
|
53999
54587
|
name: "@mrciphersmith/keryx",
|
|
54000
|
-
version: "0.2.
|
|
54588
|
+
version: "0.2.70",
|
|
54001
54589
|
description: "Version-controlled project context for AI coding agents: code graph, architecture wiki, project memory, relevant tests, quality signals, and task flows.",
|
|
54002
54590
|
private: false,
|
|
54003
54591
|
publishConfig: {
|
|
@@ -67679,7 +68267,7 @@ async function shellCommand(args2, runtime = {}) {
|
|
|
67679
68267
|
async function sessionsCommand(args2) {
|
|
67680
68268
|
const sub = args2[0] ?? "list";
|
|
67681
68269
|
if (sub === "--help" || sub === "-h" || sub === "help") {
|
|
67682
|
-
|
|
68270
|
+
printHelp16();
|
|
67683
68271
|
return;
|
|
67684
68272
|
}
|
|
67685
68273
|
const cwd = process.cwd();
|
|
@@ -67785,7 +68373,7 @@ async function sessionsCommand(args2) {
|
|
|
67785
68373
|
return;
|
|
67786
68374
|
}
|
|
67787
68375
|
console.error(`Unknown sessions subcommand: ${sub}`);
|
|
67788
|
-
|
|
68376
|
+
printHelp16();
|
|
67789
68377
|
process.exitCode = 1;
|
|
67790
68378
|
}
|
|
67791
68379
|
function pad(s, n) {
|
|
@@ -67794,7 +68382,7 @@ function pad(s, n) {
|
|
|
67794
68382
|
function clip6(s, n) {
|
|
67795
68383
|
return s.length > n ? `${s.slice(0, n - 1)}\u2026` : s;
|
|
67796
68384
|
}
|
|
67797
|
-
function
|
|
68385
|
+
function printHelp16() {
|
|
67798
68386
|
console.log(`keryx sessions
|
|
67799
68387
|
|
|
67800
68388
|
Per-project interactive shell sessions (isolated by git root / cwd).
|
|
@@ -67857,7 +68445,7 @@ function buildInitFlags(next, profile) {
|
|
|
67857
68445
|
async function modulesCommand(args2 = []) {
|
|
67858
68446
|
const sub = args2[0];
|
|
67859
68447
|
if (sub === "--help" || sub === "-h" || sub === "help") {
|
|
67860
|
-
|
|
68448
|
+
printHelp17();
|
|
67861
68449
|
return;
|
|
67862
68450
|
}
|
|
67863
68451
|
const wantsJson = args2.includes("--json") && (sub === undefined || sub === "status" || sub === "list" || sub === "--json");
|
|
@@ -67914,7 +68502,7 @@ async function modulesCommand(args2 = []) {
|
|
|
67914
68502
|
}
|
|
67915
68503
|
}
|
|
67916
68504
|
} else {
|
|
67917
|
-
|
|
68505
|
+
printHelp17();
|
|
67918
68506
|
process.exitCode = 1;
|
|
67919
68507
|
return;
|
|
67920
68508
|
}
|
|
@@ -67957,7 +68545,7 @@ function setsEqual(a, b) {
|
|
|
67957
68545
|
}
|
|
67958
68546
|
return true;
|
|
67959
68547
|
}
|
|
67960
|
-
function
|
|
68548
|
+
function printHelp17() {
|
|
67961
68549
|
helpTitle("keryx modules", "view and toggle Metaproject modules");
|
|
67962
68550
|
helpUsage([
|
|
67963
68551
|
"keryx modules",
|
|
@@ -69429,7 +70017,7 @@ var ENABLE_FLAG = "--enable";
|
|
|
69429
70017
|
var DISABLE_FLAG = "--disable";
|
|
69430
70018
|
async function serveCommand(args2 = []) {
|
|
69431
70019
|
if (args2.includes("--help") || args2.includes("-h") || args2[0] === "help") {
|
|
69432
|
-
|
|
70020
|
+
printHelp18();
|
|
69433
70021
|
return;
|
|
69434
70022
|
}
|
|
69435
70023
|
const sub = args2[0];
|
|
@@ -69450,14 +70038,14 @@ async function serveCommand(args2 = []) {
|
|
|
69450
70038
|
return;
|
|
69451
70039
|
}
|
|
69452
70040
|
console.error(`Unknown serve command: ${sanitizeForDisplay(sub)}`);
|
|
69453
|
-
|
|
70041
|
+
printHelp18();
|
|
69454
70042
|
process.exitCode = 1;
|
|
69455
70043
|
}
|
|
69456
70044
|
async function runServe(args2) {
|
|
69457
70045
|
const parsed = parseArgs3(args2, BIND_FLAGS, [ACK_FLAG]);
|
|
69458
70046
|
if (!parsed.ok) {
|
|
69459
70047
|
console.error(parsed.message);
|
|
69460
|
-
|
|
70048
|
+
printHelp18();
|
|
69461
70049
|
process.exitCode = 1;
|
|
69462
70050
|
return;
|
|
69463
70051
|
}
|
|
@@ -69559,7 +70147,7 @@ function runStatus6(args2) {
|
|
|
69559
70147
|
const parsed = parseArgs3(args2, [], ["--json"]);
|
|
69560
70148
|
if (!parsed.ok) {
|
|
69561
70149
|
console.error(parsed.message);
|
|
69562
|
-
|
|
70150
|
+
printHelp18();
|
|
69563
70151
|
process.exitCode = 1;
|
|
69564
70152
|
return;
|
|
69565
70153
|
}
|
|
@@ -69606,7 +70194,7 @@ function runToken(args2) {
|
|
|
69606
70194
|
const rest = parseArgs3(args2.slice(1), [], []);
|
|
69607
70195
|
if (!rest.ok) {
|
|
69608
70196
|
console.error(rest.message);
|
|
69609
|
-
|
|
70197
|
+
printHelp18();
|
|
69610
70198
|
process.exitCode = 1;
|
|
69611
70199
|
return;
|
|
69612
70200
|
}
|
|
@@ -69647,7 +70235,7 @@ function runToken(args2) {
|
|
|
69647
70235
|
return;
|
|
69648
70236
|
}
|
|
69649
70237
|
console.error(sub === undefined ? "Missing token subcommand" : `Unknown token command: ${sanitizeForDisplay(sub)}`);
|
|
69650
|
-
|
|
70238
|
+
printHelp18();
|
|
69651
70239
|
process.exitCode = 1;
|
|
69652
70240
|
}
|
|
69653
70241
|
function printTokenOnce(token) {
|
|
@@ -69672,7 +70260,7 @@ function runConfig(args2) {
|
|
|
69672
70260
|
const parsed = parseArgs3(args2.slice(1), BIND_FLAGS, [ACK_FLAG, FORCE_FLAG]);
|
|
69673
70261
|
if (!parsed.ok) {
|
|
69674
70262
|
console.error(parsed.message);
|
|
69675
|
-
|
|
70263
|
+
printHelp18();
|
|
69676
70264
|
process.exitCode = 1;
|
|
69677
70265
|
return;
|
|
69678
70266
|
}
|
|
@@ -69722,7 +70310,7 @@ function runConfig(args2) {
|
|
|
69722
70310
|
const parsed = parseArgs3(args2.slice(1), [], ["--json"]);
|
|
69723
70311
|
if (!parsed.ok) {
|
|
69724
70312
|
console.error(parsed.message);
|
|
69725
|
-
|
|
70313
|
+
printHelp18();
|
|
69726
70314
|
process.exitCode = 1;
|
|
69727
70315
|
return;
|
|
69728
70316
|
}
|
|
@@ -69739,14 +70327,14 @@ function runConfig(args2) {
|
|
|
69739
70327
|
return;
|
|
69740
70328
|
}
|
|
69741
70329
|
console.error(sub === undefined ? "Missing config subcommand" : `Unknown config command: ${sanitizeForDisplay(sub)}`);
|
|
69742
|
-
|
|
70330
|
+
printHelp18();
|
|
69743
70331
|
process.exitCode = 1;
|
|
69744
70332
|
}
|
|
69745
70333
|
function runConfigSet(args2) {
|
|
69746
70334
|
const parsed = parseArgs3(args2, BIND_FLAGS, [ACK_FLAG, NO_ACK_FLAG, ENABLE_FLAG, DISABLE_FLAG]);
|
|
69747
70335
|
if (!parsed.ok) {
|
|
69748
70336
|
console.error(parsed.message);
|
|
69749
|
-
|
|
70337
|
+
printHelp18();
|
|
69750
70338
|
process.exitCode = 1;
|
|
69751
70339
|
return;
|
|
69752
70340
|
}
|
|
@@ -69817,7 +70405,7 @@ function printConfig(config) {
|
|
|
69817
70405
|
console.log(` approvals: expire after ${config.approval.expirySeconds}s, max ${config.approval.maxPendingPerSession} pending per session`);
|
|
69818
70406
|
console.log(` non-loopback acknowledged: ${config.bind.acknowledgeNonLoopback === true}`);
|
|
69819
70407
|
}
|
|
69820
|
-
function
|
|
70408
|
+
function printHelp18() {
|
|
69821
70409
|
helpTitle("keryx serve", "loopback-bound HTTP entry over this install (off by default)");
|
|
69822
70410
|
helpUsage([
|
|
69823
70411
|
`keryx serve [--bind <addr>] [--port <n>] [--profile <name>] [${ACK_FLAG}]`,
|
|
@@ -69863,7 +70451,7 @@ init_git_hooks();
|
|
|
69863
70451
|
async function updateCommand(args2 = []) {
|
|
69864
70452
|
const options = parseUpdateArgs(args2);
|
|
69865
70453
|
if (options.help) {
|
|
69866
|
-
|
|
70454
|
+
printHelp19();
|
|
69867
70455
|
return;
|
|
69868
70456
|
}
|
|
69869
70457
|
const projectRoot = process.cwd();
|
|
@@ -71012,7 +71600,7 @@ function runtimeSourcePath2(relativePath) {
|
|
|
71012
71600
|
function escapeRegExp6(value) {
|
|
71013
71601
|
return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
71014
71602
|
}
|
|
71015
|
-
function
|
|
71603
|
+
function printHelp19() {
|
|
71016
71604
|
helpTitle("keryx update", "refresh .metaproject service files (data left untouched)");
|
|
71017
71605
|
helpUsage(["keryx update [--skip-runtime] [--hooks] [--no-tasks]"]);
|
|
71018
71606
|
heading("Default behavior");
|
|
@@ -71040,7 +71628,7 @@ async function dashboardCommand(args2 = []) {
|
|
|
71040
71628
|
const options = parseOptions(args2);
|
|
71041
71629
|
const subcommand = options.positionals[0];
|
|
71042
71630
|
if (!subcommand || options.help) {
|
|
71043
|
-
|
|
71631
|
+
printHelp20();
|
|
71044
71632
|
return;
|
|
71045
71633
|
}
|
|
71046
71634
|
if (subcommand === "build") {
|
|
@@ -71058,7 +71646,7 @@ async function dashboardCommand(args2 = []) {
|
|
|
71058
71646
|
return;
|
|
71059
71647
|
}
|
|
71060
71648
|
console.log(` ${style.red(symbols.cross)} Unknown dashboard command: ${subcommand}`);
|
|
71061
|
-
|
|
71649
|
+
printHelp20();
|
|
71062
71650
|
process.exitCode = 1;
|
|
71063
71651
|
}
|
|
71064
71652
|
function parseOptions(args2) {
|
|
@@ -71083,7 +71671,7 @@ async function openFile(filePath) {
|
|
|
71083
71671
|
});
|
|
71084
71672
|
});
|
|
71085
71673
|
}
|
|
71086
|
-
function
|
|
71674
|
+
function printHelp20() {
|
|
71087
71675
|
helpTitle("keryx dashboard", "build and open the human dashboard");
|
|
71088
71676
|
helpUsage([
|
|
71089
71677
|
"keryx dashboard build",
|
|
@@ -73656,7 +74244,7 @@ function service5() {
|
|
|
73656
74244
|
async function workspaceCommand(args2) {
|
|
73657
74245
|
const subcommand = args2[0];
|
|
73658
74246
|
if (!subcommand || subcommand === "help" || args2.includes("--help") || args2.includes("-h"))
|
|
73659
|
-
return
|
|
74247
|
+
return printHelp21();
|
|
73660
74248
|
try {
|
|
73661
74249
|
if (subcommand === "create") {
|
|
73662
74250
|
rejectUnknownOptions(args2.slice(1), new Set(["--title", "--component"]));
|
|
@@ -73906,7 +74494,7 @@ function booleanFlagDefaultTrue(args2, name) {
|
|
|
73906
74494
|
return false;
|
|
73907
74495
|
throw new Error(`Unknown value for ${name}: "${value}" \u2014 expected true or false`);
|
|
73908
74496
|
}
|
|
73909
|
-
function
|
|
74497
|
+
function printHelp21() {
|
|
73910
74498
|
console.log(`keryx workspace create --title <title> [--component <workspace-relative-ref>]
|
|
73911
74499
|
keryx workspace list [--include-archived]
|
|
73912
74500
|
keryx workspace show <workspace-id>
|
|
@@ -73978,6 +74566,7 @@ var CLI_ROUTES = {
|
|
|
73978
74566
|
standard: standardCommand,
|
|
73979
74567
|
commands: commandsCommand,
|
|
73980
74568
|
security: securityCommand,
|
|
74569
|
+
sandbox: sandboxCommand,
|
|
73981
74570
|
mcp: mcpCommand,
|
|
73982
74571
|
harness: harnessCommand,
|
|
73983
74572
|
shell: shellCommand,
|
|
@@ -73990,7 +74579,7 @@ async function main() {
|
|
|
73990
74579
|
const args2 = process.argv.slice(2);
|
|
73991
74580
|
const command = args2[0];
|
|
73992
74581
|
if (command === "--help" || command === "-h" || command === "help" || !command) {
|
|
73993
|
-
|
|
74582
|
+
printHelp22();
|
|
73994
74583
|
return;
|
|
73995
74584
|
}
|
|
73996
74585
|
if (command === "--version" || command === "-v") {
|
|
@@ -74003,10 +74592,10 @@ async function main() {
|
|
|
74003
74592
|
return;
|
|
74004
74593
|
}
|
|
74005
74594
|
console.error(`Unknown command: ${command}`);
|
|
74006
|
-
|
|
74595
|
+
printHelp22();
|
|
74007
74596
|
process.exitCode = 1;
|
|
74008
74597
|
}
|
|
74009
|
-
function
|
|
74598
|
+
function printHelp22() {
|
|
74010
74599
|
console.log(`keryx ${VERSION2}
|
|
74011
74600
|
|
|
74012
74601
|
Usage:
|
|
@@ -74084,6 +74673,8 @@ Usage:
|
|
|
74084
74673
|
keryx security scan <path> [--json]
|
|
74085
74674
|
keryx security scan-mcp <manifest|dir> [--json]
|
|
74086
74675
|
keryx security check-input [--source <kind>] [--file <path>]
|
|
74676
|
+
keryx sandbox status [--json]
|
|
74677
|
+
OS sandbox launcher availability + per-capability containment matrix (report, not a gate)
|
|
74087
74678
|
keryx security check-output [--target <kind>] [--file <path>]
|
|
74088
74679
|
keryx security redact <path> [--out <path>]
|
|
74089
74680
|
keryx security report [--since <ref>]
|
|
@@ -74125,6 +74716,7 @@ Commands:
|
|
|
74125
74716
|
standard Validate the workspace against the Metaproject Standard
|
|
74126
74717
|
commands Agent-callable command registry (intents, args, output, model usage)
|
|
74127
74718
|
security Policy-based scanning, redaction, guardrails and audit reports
|
|
74719
|
+
sandbox Report OS sandbox launcher availability and the per-capability containment matrix
|
|
74128
74720
|
mcp Expose Metaproject services over the Model Context Protocol (opt-in)
|
|
74129
74721
|
`);
|
|
74130
74722
|
}
|