@mrciphersmith/keryx 0.2.74 → 0.2.76
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 +860 -171
- package/package.json +1 -1
- package/src/gdskills/bundled/skills/review/review-orchestrator/SKILL.md +100 -10
- package/src/gdskills/bundled/skills/review/review-orchestrator/review-context.schema.json +175 -0
- package/src/gdskills/bundled/skills/review/review-orchestrator/reviewer-finding.schema.json +7 -0
- package/src/gdskills/contracts/review-finding.schema.json +4 -0
package/dist/cli.js
CHANGED
|
@@ -11214,9 +11214,9 @@ function resolvePointer(doc, fragment) {
|
|
|
11214
11214
|
if (!fragment.startsWith("/")) {
|
|
11215
11215
|
throw new Error(`Unsupported JSON Pointer fragment (must start with "/"): ${fragment}`);
|
|
11216
11216
|
}
|
|
11217
|
-
const
|
|
11217
|
+
const segments = fragment.split("/").slice(1).map(decodePointerSegment);
|
|
11218
11218
|
let node = doc;
|
|
11219
|
-
for (const segment of
|
|
11219
|
+
for (const segment of segments) {
|
|
11220
11220
|
if (node === null || typeof node !== "object" || Array.isArray(node)) {
|
|
11221
11221
|
throw new Error(`Pointer segment "${segment}" not resolvable in fragment #${fragment}`);
|
|
11222
11222
|
}
|
|
@@ -12495,12 +12495,12 @@ class FakeProvider {
|
|
|
12495
12495
|
}
|
|
12496
12496
|
const { attemptId } = opts;
|
|
12497
12497
|
let sequence = 0;
|
|
12498
|
-
const
|
|
12498
|
+
const emit2 = (body) => ({
|
|
12499
12499
|
...body,
|
|
12500
12500
|
sequence: sequence++,
|
|
12501
12501
|
attemptId
|
|
12502
12502
|
});
|
|
12503
|
-
yield
|
|
12503
|
+
yield emit2({ kind: "model_start" });
|
|
12504
12504
|
for (const raw of transcript.events) {
|
|
12505
12505
|
const payload = raw.payload ?? {};
|
|
12506
12506
|
switch (raw.kind) {
|
|
@@ -12513,13 +12513,13 @@ class FakeProvider {
|
|
|
12513
12513
|
if (extensions !== undefined) {
|
|
12514
12514
|
body.unknownExtensions = extensions;
|
|
12515
12515
|
}
|
|
12516
|
-
yield
|
|
12516
|
+
yield emit2(body);
|
|
12517
12517
|
break;
|
|
12518
12518
|
}
|
|
12519
12519
|
case "tool_call": {
|
|
12520
12520
|
const input2 = payload.input;
|
|
12521
12521
|
if (!isPlainObject6(input2)) {
|
|
12522
|
-
yield
|
|
12522
|
+
yield emit2({
|
|
12523
12523
|
kind: "provider_error",
|
|
12524
12524
|
error: {
|
|
12525
12525
|
kind: "malformed",
|
|
@@ -12538,21 +12538,21 @@ class FakeProvider {
|
|
|
12538
12538
|
if (typeof payload.toolName === "string") {
|
|
12539
12539
|
startBody.toolName = payload.toolName;
|
|
12540
12540
|
}
|
|
12541
|
-
yield
|
|
12542
|
-
yield
|
|
12541
|
+
yield emit2(startBody);
|
|
12542
|
+
yield emit2(endBody);
|
|
12543
12543
|
break;
|
|
12544
12544
|
}
|
|
12545
12545
|
case "finish": {
|
|
12546
12546
|
const usage = payload.usage;
|
|
12547
12547
|
if (isPlainObject6(usage)) {
|
|
12548
|
-
yield
|
|
12548
|
+
yield emit2({ kind: "usage_update", usage: mapUsage(usage) });
|
|
12549
12549
|
}
|
|
12550
12550
|
const body = { kind: "model_end" };
|
|
12551
12551
|
const extensions = extractUnknownExtensions(payload);
|
|
12552
12552
|
if (extensions !== undefined) {
|
|
12553
12553
|
body.unknownExtensions = extensions;
|
|
12554
12554
|
}
|
|
12555
|
-
yield
|
|
12555
|
+
yield emit2(body);
|
|
12556
12556
|
break;
|
|
12557
12557
|
}
|
|
12558
12558
|
case "error": {
|
|
@@ -12561,7 +12561,7 @@ class FakeProvider {
|
|
|
12561
12561
|
retryable: payload.retryable,
|
|
12562
12562
|
message: payload.message
|
|
12563
12563
|
};
|
|
12564
|
-
yield
|
|
12564
|
+
yield emit2({ kind: "provider_error", error });
|
|
12565
12565
|
return;
|
|
12566
12566
|
}
|
|
12567
12567
|
}
|
|
@@ -13660,7 +13660,10 @@ async function runModelTurn(input2) {
|
|
|
13660
13660
|
let reasoning = false;
|
|
13661
13661
|
const startedAt = performance.now();
|
|
13662
13662
|
let firstByteAt;
|
|
13663
|
-
for await (const event of port.stream(request, {
|
|
13663
|
+
for await (const event of port.stream(request, {
|
|
13664
|
+
attemptId: request.requestId,
|
|
13665
|
+
...input2.signal !== undefined ? { signal: input2.signal } : {}
|
|
13666
|
+
})) {
|
|
13664
13667
|
if (firstByteAt === undefined) {
|
|
13665
13668
|
firstByteAt = performance.now();
|
|
13666
13669
|
}
|
|
@@ -13849,14 +13852,14 @@ function hasUnquotedMetacharacter(command) {
|
|
|
13849
13852
|
return state.unbalanced || state.metacharacter;
|
|
13850
13853
|
}
|
|
13851
13854
|
function splitSegments(command) {
|
|
13852
|
-
const
|
|
13855
|
+
const segments = [];
|
|
13853
13856
|
let buf = "";
|
|
13854
13857
|
let pending;
|
|
13855
13858
|
let quote;
|
|
13856
13859
|
const flush = (next) => {
|
|
13857
13860
|
const raw = buf.trim();
|
|
13858
13861
|
if (raw.length > 0) {
|
|
13859
|
-
|
|
13862
|
+
segments.push({
|
|
13860
13863
|
raw,
|
|
13861
13864
|
words: splitWords(raw),
|
|
13862
13865
|
...pending !== undefined ? { precededBy: pending } : {}
|
|
@@ -13898,7 +13901,7 @@ function splitSegments(command) {
|
|
|
13898
13901
|
buf += ch;
|
|
13899
13902
|
}
|
|
13900
13903
|
flush();
|
|
13901
|
-
return
|
|
13904
|
+
return segments;
|
|
13902
13905
|
}
|
|
13903
13906
|
function separatorAt(ch, next) {
|
|
13904
13907
|
if (ch === "&" && next === "&")
|
|
@@ -14002,18 +14005,18 @@ function classifySegment(seg) {
|
|
|
14002
14005
|
function classifyCommand2(command) {
|
|
14003
14006
|
if (command.trim().length === 0)
|
|
14004
14007
|
return "shell";
|
|
14005
|
-
const
|
|
14006
|
-
for (const seg of
|
|
14008
|
+
const segments = splitSegments(command);
|
|
14009
|
+
for (const seg of segments) {
|
|
14007
14010
|
if (classifySegment(seg))
|
|
14008
14011
|
return "destructive";
|
|
14009
14012
|
}
|
|
14010
|
-
for (let i = 1;i <
|
|
14011
|
-
const seg =
|
|
14013
|
+
for (let i = 1;i < segments.length; i++) {
|
|
14014
|
+
const seg = segments[i];
|
|
14012
14015
|
if (seg.precededBy !== "|")
|
|
14013
14016
|
continue;
|
|
14014
14017
|
if (!INTERPRETERS.has(head(seg.words)))
|
|
14015
14018
|
continue;
|
|
14016
|
-
const upstream =
|
|
14019
|
+
const upstream = segments.slice(0, i);
|
|
14017
14020
|
if (upstream.some((s) => DOWNLOADERS.has(head(s.words))))
|
|
14018
14021
|
return "destructive";
|
|
14019
14022
|
}
|
|
@@ -16448,7 +16451,13 @@ function writeJsonl(file, history, ts) {
|
|
|
16448
16451
|
function redactHistory(history) {
|
|
16449
16452
|
return history.map((message) => ({
|
|
16450
16453
|
...message,
|
|
16451
|
-
content: redactSensitiveText(message.content)
|
|
16454
|
+
content: redactSensitiveText(message.content),
|
|
16455
|
+
...message.toolCalls === undefined ? {} : {
|
|
16456
|
+
toolCalls: message.toolCalls.map((call) => ({
|
|
16457
|
+
...call,
|
|
16458
|
+
arguments: redactSensitiveText(call.arguments)
|
|
16459
|
+
}))
|
|
16460
|
+
}
|
|
16452
16461
|
}));
|
|
16453
16462
|
}
|
|
16454
16463
|
function readJsonl2(file) {
|
|
@@ -17613,6 +17622,9 @@ async function createProjectSkill(projectRoot, options) {
|
|
|
17613
17622
|
if (!await pathExists(metaprojectRoot)) {
|
|
17614
17623
|
throw new Error("Metaproject is not initialized. Run: keryx init");
|
|
17615
17624
|
}
|
|
17625
|
+
if (!isRoutableSkillTarget(options.target)) {
|
|
17626
|
+
throw new Error(`keryx skills create: target ${JSON.stringify(options.target)} reads as prose, not a routing key. ` + 'A target is what `keryx skills route` matches against \u2014 a concept ("auth flow"), a symbol (`IResultDqReport`), ' + "or a path (`src/dq/DqScoreCard.tsx`). A sentence matches nothing and verifies as permanently stale.");
|
|
17627
|
+
}
|
|
17616
17628
|
const moduleName = slugify3(options.module ?? inferModule(options.target));
|
|
17617
17629
|
const skillName = slugify3(options.name ?? inferSkillName(options.target));
|
|
17618
17630
|
const format = options.format ?? "auto";
|
|
@@ -18605,6 +18617,23 @@ function authorityFor(manifest, subject) {
|
|
|
18605
18617
|
function normalizeProposalLifecycleResult(value) {
|
|
18606
18618
|
return JSON.parse(JSON.stringify(value));
|
|
18607
18619
|
}
|
|
18620
|
+
async function readProposalSecurityGate(cwd, workspaceId, proposalId) {
|
|
18621
|
+
const file = path86.join(cwd, ".metaproject", "workspaces", workspaceId, "proposals", `${proposalId}.json`);
|
|
18622
|
+
let parsed;
|
|
18623
|
+
try {
|
|
18624
|
+
parsed = JSON.parse(await readFile44(file, "utf8"));
|
|
18625
|
+
} catch {
|
|
18626
|
+
return;
|
|
18627
|
+
}
|
|
18628
|
+
if (typeof parsed !== "object" || parsed === null)
|
|
18629
|
+
return;
|
|
18630
|
+
const record = parsed;
|
|
18631
|
+
const gate = record.security?.gate;
|
|
18632
|
+
if (gate !== "pass" && gate !== "needs-approval")
|
|
18633
|
+
return;
|
|
18634
|
+
const evidenceRefs = Array.isArray(record.evidence) ? record.evidence.map((entry) => typeof entry === "object" && entry !== null ? entry.uri : undefined).filter((uri) => typeof uri === "string") : [];
|
|
18635
|
+
return { gate, evidenceRefs };
|
|
18636
|
+
}
|
|
18608
18637
|
var ProposalLifecycleError;
|
|
18609
18638
|
var init_proposal_lifecycle = __esm(() => {
|
|
18610
18639
|
init_fs();
|
|
@@ -18788,13 +18817,16 @@ async function digestLedgerPrefix(handle, byteLength) {
|
|
|
18788
18817
|
return digest2;
|
|
18789
18818
|
const buffer = Buffer.alloc(Math.min(ledgerDigestChunkBytes, byteLength));
|
|
18790
18819
|
let position = 0;
|
|
18791
|
-
|
|
18820
|
+
const maxChunks = Math.ceil(byteLength / buffer.length);
|
|
18821
|
+
for (let chunk = 0;chunk < maxChunks && position < byteLength; chunk += 1) {
|
|
18792
18822
|
const result = await handle.read(buffer, 0, Math.min(buffer.length, byteLength - position), position);
|
|
18793
18823
|
if (result.bytesRead === 0)
|
|
18794
18824
|
return;
|
|
18795
18825
|
digest2.update(buffer.subarray(0, result.bytesRead));
|
|
18796
18826
|
position += result.bytesRead;
|
|
18797
18827
|
}
|
|
18828
|
+
if (position < byteLength)
|
|
18829
|
+
return;
|
|
18798
18830
|
return digest2;
|
|
18799
18831
|
}
|
|
18800
18832
|
function parseCheckpoint(value) {
|
|
@@ -29216,6 +29248,32 @@ var init_metaproject_operations = __esm(() => {
|
|
|
29216
29248
|
|
|
29217
29249
|
// src/wiki/deep-enrich.ts
|
|
29218
29250
|
import { randomUUID as randomUUID15, createHash as createHash22 } from "crypto";
|
|
29251
|
+
function composeAbortSignals(external, timeout) {
|
|
29252
|
+
if (external === undefined) {
|
|
29253
|
+
return { signal: timeout, dispose: () => {} };
|
|
29254
|
+
}
|
|
29255
|
+
const controller = new AbortController;
|
|
29256
|
+
const abortFrom = (signal) => {
|
|
29257
|
+
if (!controller.signal.aborted) {
|
|
29258
|
+
controller.abort(signal.reason);
|
|
29259
|
+
}
|
|
29260
|
+
};
|
|
29261
|
+
const onExternalAbort = () => abortFrom(external);
|
|
29262
|
+
const onTimeoutAbort = () => abortFrom(timeout);
|
|
29263
|
+
external.addEventListener("abort", onExternalAbort, { once: true });
|
|
29264
|
+
timeout.addEventListener("abort", onTimeoutAbort, { once: true });
|
|
29265
|
+
if (external.aborted)
|
|
29266
|
+
abortFrom(external);
|
|
29267
|
+
if (timeout.aborted)
|
|
29268
|
+
abortFrom(timeout);
|
|
29269
|
+
return {
|
|
29270
|
+
signal: controller.signal,
|
|
29271
|
+
dispose: () => {
|
|
29272
|
+
external.removeEventListener("abort", onExternalAbort);
|
|
29273
|
+
timeout.removeEventListener("abort", onTimeoutAbort);
|
|
29274
|
+
}
|
|
29275
|
+
};
|
|
29276
|
+
}
|
|
29219
29277
|
function errorMessage2(cause) {
|
|
29220
29278
|
return cause instanceof Error ? cause.message : String(cause);
|
|
29221
29279
|
}
|
|
@@ -29254,6 +29312,9 @@ async function enrichPageDeep(input2) {
|
|
|
29254
29312
|
const idSeq = input2.idSeq ?? (() => randomUUID15());
|
|
29255
29313
|
const clock = input2.clock ?? (() => new Date().toISOString());
|
|
29256
29314
|
const toolCalls = [];
|
|
29315
|
+
if (input2.signal?.aborted) {
|
|
29316
|
+
return { fallback: true, reason: "deep enrich cancelled", toolCalls };
|
|
29317
|
+
}
|
|
29257
29318
|
try {
|
|
29258
29319
|
const env = envWithSavedApiKeys(input2.env ?? process.env);
|
|
29259
29320
|
const credentialAvailable = hasCredential(input2.provider, env);
|
|
@@ -29329,6 +29390,7 @@ async function enrichPageDeep(input2) {
|
|
|
29329
29390
|
let assistant = "";
|
|
29330
29391
|
let pending;
|
|
29331
29392
|
const abort = new AbortController;
|
|
29393
|
+
const composed = composeAbortSignals(input2.signal, abort.signal);
|
|
29332
29394
|
const io = {
|
|
29333
29395
|
write: (s) => {
|
|
29334
29396
|
assistant += s;
|
|
@@ -29360,11 +29422,12 @@ async function enrichPageDeep(input2) {
|
|
|
29360
29422
|
};
|
|
29361
29423
|
const history = [];
|
|
29362
29424
|
const userLine = buildDeepUserPrompt(input2.page, input2.original, input2.extraInstruction);
|
|
29363
|
-
const turn = runAgentTurn(io, deps, history, userLine, { signal:
|
|
29425
|
+
const turn = runAgentTurn(io, deps, history, userLine, { signal: composed.signal });
|
|
29364
29426
|
const deadlineMs = spawned.reservation.maxRuntimeMs;
|
|
29365
29427
|
if (deadlineMs <= 0) {
|
|
29366
29428
|
abort.abort();
|
|
29367
29429
|
turn.catch(() => {});
|
|
29430
|
+
composed.dispose();
|
|
29368
29431
|
releaseBudget();
|
|
29369
29432
|
return {
|
|
29370
29433
|
fallback: true,
|
|
@@ -29376,16 +29439,31 @@ async function enrichPageDeep(input2) {
|
|
|
29376
29439
|
const expired = new Promise((resolve2) => {
|
|
29377
29440
|
timer = setTimeout(() => resolve2("timeout"), deadlineMs);
|
|
29378
29441
|
});
|
|
29442
|
+
let onExternalAbort;
|
|
29443
|
+
const cancelled = new Promise((resolve2) => {
|
|
29444
|
+
if (input2.signal === undefined)
|
|
29445
|
+
return;
|
|
29446
|
+
onExternalAbort = () => resolve2("cancelled");
|
|
29447
|
+
if (input2.signal.aborted) {
|
|
29448
|
+
onExternalAbort();
|
|
29449
|
+
} else {
|
|
29450
|
+
input2.signal.addEventListener("abort", onExternalAbort, { once: true });
|
|
29451
|
+
}
|
|
29452
|
+
});
|
|
29379
29453
|
let outcome;
|
|
29380
29454
|
try {
|
|
29381
|
-
outcome = await Promise.race([turn.then(() => "done"), expired]);
|
|
29455
|
+
outcome = await Promise.race([turn.then(() => "done"), expired, cancelled]);
|
|
29382
29456
|
} finally {
|
|
29383
29457
|
if (timer !== undefined)
|
|
29384
29458
|
clearTimeout(timer);
|
|
29459
|
+
if (onExternalAbort !== undefined) {
|
|
29460
|
+
input2.signal?.removeEventListener("abort", onExternalAbort);
|
|
29461
|
+
}
|
|
29385
29462
|
}
|
|
29386
29463
|
if (outcome === "timeout") {
|
|
29387
29464
|
abort.abort();
|
|
29388
29465
|
turn.catch(() => {});
|
|
29466
|
+
composed.dispose();
|
|
29389
29467
|
releaseBudget();
|
|
29390
29468
|
const partial = assistant.trim();
|
|
29391
29469
|
return {
|
|
@@ -29395,7 +29473,15 @@ async function enrichPageDeep(input2) {
|
|
|
29395
29473
|
toolCalls
|
|
29396
29474
|
};
|
|
29397
29475
|
}
|
|
29476
|
+
composed.dispose();
|
|
29398
29477
|
releaseBudget();
|
|
29478
|
+
if (outcome === "cancelled" || input2.signal?.aborted) {
|
|
29479
|
+
return {
|
|
29480
|
+
fallback: true,
|
|
29481
|
+
reason: "deep enrich cancelled",
|
|
29482
|
+
toolCalls
|
|
29483
|
+
};
|
|
29484
|
+
}
|
|
29399
29485
|
const raw = assistant.trim().length > 0 ? assistant.trim() : history.filter((m) => m.role === "assistant").map((m) => m.content).join(`
|
|
29400
29486
|
`).trim();
|
|
29401
29487
|
if (raw.length === 0) {
|
|
@@ -29582,6 +29668,9 @@ function isWikiEnrichIntent(line) {
|
|
|
29582
29668
|
if (t.length === 0) {
|
|
29583
29669
|
return false;
|
|
29584
29670
|
}
|
|
29671
|
+
if (/^keryx\s+wiki\s+enrich(?:\s|$)/.test(t)) {
|
|
29672
|
+
return false;
|
|
29673
|
+
}
|
|
29585
29674
|
const ru = t.includes("\u0432\u0438\u043A\u0438") && (t.includes("\u043E\u0431\u043E\u0433\u0430\u0442") || t.includes("\u043E\u0431\u043E\u0433\u0430\u0449"));
|
|
29586
29675
|
const en = /\benrich\b/.test(t) && /\bwiki\b/.test(t) || /\bwiki\s+enrich\b/.test(t);
|
|
29587
29676
|
return ru || en;
|
|
@@ -29860,6 +29949,7 @@ async function wikiEnrich(input2) {
|
|
|
29860
29949
|
enriched: 0,
|
|
29861
29950
|
dryRun: 0,
|
|
29862
29951
|
skipped: 0,
|
|
29952
|
+
cancelled: 0,
|
|
29863
29953
|
failed: 0
|
|
29864
29954
|
};
|
|
29865
29955
|
if (input2.refreshGraph) {
|
|
@@ -29900,9 +29990,15 @@ async function wikiEnrich(input2) {
|
|
|
29900
29990
|
pageResults = await mapPool(pages, concurrency, async (page, i) => {
|
|
29901
29991
|
const index = i + 1;
|
|
29902
29992
|
const status = page.status ?? "draft";
|
|
29993
|
+
if (input2.signal?.aborted) {
|
|
29994
|
+
return { path: page.relativePath, action: "cancelled", reason: "enrichment cancelled" };
|
|
29995
|
+
}
|
|
29903
29996
|
onPage({ index, total, path: page.relativePath, status, phase: "start" });
|
|
29904
29997
|
try {
|
|
29905
29998
|
const originalRaw = await readFile62(page.absolutePath, "utf8");
|
|
29999
|
+
if (input2.signal?.aborted) {
|
|
30000
|
+
return { path: page.relativePath, action: "cancelled", reason: "enrichment cancelled" };
|
|
30001
|
+
}
|
|
29906
30002
|
const ensured = ensureWikiFrontmatter(originalRaw, {
|
|
29907
30003
|
title: page.title,
|
|
29908
30004
|
pageType: page.pageType
|
|
@@ -29919,8 +30015,12 @@ async function wikiEnrich(input2) {
|
|
|
29919
30015
|
env,
|
|
29920
30016
|
...input2.fetch ? { fetch: input2.fetch } : {},
|
|
29921
30017
|
...input2.baseUrl !== undefined ? { baseUrl: input2.baseUrl } : {},
|
|
29922
|
-
...input2.providerFactory ? { providerFactory: input2.providerFactory } : {}
|
|
30018
|
+
...input2.providerFactory ? { providerFactory: input2.providerFactory } : {},
|
|
30019
|
+
...input2.signal !== undefined ? { signal: input2.signal } : {}
|
|
29923
30020
|
});
|
|
30021
|
+
if (input2.signal?.aborted) {
|
|
30022
|
+
return { path: page.relativePath, action: "cancelled", reason: "enrichment cancelled" };
|
|
30023
|
+
}
|
|
29924
30024
|
if (turn.error) {
|
|
29925
30025
|
onPage({ index, total, path: page.relativePath, status, phase: "failed" });
|
|
29926
30026
|
return {
|
|
@@ -29957,7 +30057,16 @@ async function wikiEnrich(input2) {
|
|
|
29957
30057
|
const output2 = enriched.endsWith(`
|
|
29958
30058
|
`) ? enriched : `${enriched}
|
|
29959
30059
|
`;
|
|
29960
|
-
const materialized = prepareOutputForPersistence(await
|
|
30060
|
+
const materialized = prepareOutputForPersistence(await (input2.guardOutput ?? guardOutput)({
|
|
30061
|
+
cwd: input2.cwd,
|
|
30062
|
+
content: output2,
|
|
30063
|
+
target: "wiki",
|
|
30064
|
+
source: "generated",
|
|
30065
|
+
path: page.relativePath
|
|
30066
|
+
}), output2);
|
|
30067
|
+
if (input2.signal?.aborted) {
|
|
30068
|
+
return { path: page.relativePath, action: "cancelled", reason: "enrichment cancelled" };
|
|
30069
|
+
}
|
|
29961
30070
|
if (!materialized.allowed) {
|
|
29962
30071
|
onPage({ index, total, path: page.relativePath, status, phase: "failed" });
|
|
29963
30072
|
return { path: page.relativePath, action: "failed", reason: materialized.reason };
|
|
@@ -30009,6 +30118,8 @@ async function wikiEnrich(input2) {
|
|
|
30009
30118
|
}
|
|
30010
30119
|
} else if (entry.action === "dry-run") {
|
|
30011
30120
|
result.dryRun += 1;
|
|
30121
|
+
} else if (entry.action === "cancelled") {
|
|
30122
|
+
result.cancelled += 1;
|
|
30012
30123
|
} else if (entry.action === "failed") {
|
|
30013
30124
|
result.failed += 1;
|
|
30014
30125
|
resumeState.failed.push({ path: entry.path, reason: entry.reason ?? "failed" });
|
|
@@ -30079,6 +30190,9 @@ function finalizeEnrichedText(original, rawText, options) {
|
|
|
30079
30190
|
async function finishSuccess(ctx, page, originalRaw, content, keyFiles, extra) {
|
|
30080
30191
|
const index = ctx.pageIndex(page.relativePath);
|
|
30081
30192
|
const status = page.status ?? "draft";
|
|
30193
|
+
if (ctx.input.signal?.aborted) {
|
|
30194
|
+
return { path: page.relativePath, action: "cancelled", reason: "enrichment cancelled", tier: extra.tier };
|
|
30195
|
+
}
|
|
30082
30196
|
const nodeHash = keyFiles.length > 0 ? await computePageNodeHash(ctx.cwd, keyFiles, ctx.graph) : undefined;
|
|
30083
30197
|
if (ctx.input.dryRun) {
|
|
30084
30198
|
ctx.onPage({ index, total: ctx.total, path: page.relativePath, status, phase: "done" });
|
|
@@ -30095,7 +30209,16 @@ async function finishSuccess(ctx, page, originalRaw, content, keyFiles, extra) {
|
|
|
30095
30209
|
const output2 = content.endsWith(`
|
|
30096
30210
|
`) ? content : `${content}
|
|
30097
30211
|
`;
|
|
30098
|
-
const materialized = prepareOutputForPersistence(await
|
|
30212
|
+
const materialized = prepareOutputForPersistence(await (ctx.input.guardOutput ?? guardOutput)({
|
|
30213
|
+
cwd: ctx.cwd,
|
|
30214
|
+
content: output2,
|
|
30215
|
+
target: "wiki",
|
|
30216
|
+
source: "generated",
|
|
30217
|
+
path: page.relativePath
|
|
30218
|
+
}), output2);
|
|
30219
|
+
if (ctx.input.signal?.aborted) {
|
|
30220
|
+
return { path: page.relativePath, action: "cancelled", reason: "enrichment cancelled", tier: extra.tier };
|
|
30221
|
+
}
|
|
30099
30222
|
if (!materialized.allowed) {
|
|
30100
30223
|
ctx.onPage({ index, total: ctx.total, path: page.relativePath, status, phase: "failed" });
|
|
30101
30224
|
return {
|
|
@@ -30128,6 +30251,9 @@ async function runDeepSingle(ctx, item) {
|
|
|
30128
30251
|
const { page, original, originalRaw, keyFiles } = item;
|
|
30129
30252
|
const index = ctx.pageIndex(page.relativePath);
|
|
30130
30253
|
const status = page.status ?? "draft";
|
|
30254
|
+
if (ctx.input.signal?.aborted) {
|
|
30255
|
+
return { path: page.relativePath, action: "cancelled", reason: "enrichment cancelled", tier: "deep" };
|
|
30256
|
+
}
|
|
30131
30257
|
ctx.onPage({ index, total: ctx.total, path: page.relativePath, status, phase: "model" });
|
|
30132
30258
|
try {
|
|
30133
30259
|
const deepResult = await enrichPageDeep({
|
|
@@ -30140,11 +30266,15 @@ async function runDeepSingle(ctx, item) {
|
|
|
30140
30266
|
model: ctx.model,
|
|
30141
30267
|
maxToolCalls: ctx.wikiConfig.rlm.deep.maxToolCalls,
|
|
30142
30268
|
maxRuntimeMs: ctx.wikiConfig.rlm.deep.maxRuntimeMs,
|
|
30269
|
+
...ctx.input.signal !== undefined ? { signal: ctx.input.signal } : {},
|
|
30143
30270
|
env: ctx.env,
|
|
30144
30271
|
...ctx.input.fetch ? { fetch: ctx.input.fetch } : {},
|
|
30145
30272
|
...ctx.input.baseUrl !== undefined ? { baseUrl: ctx.input.baseUrl } : {},
|
|
30146
30273
|
...ctx.input.providerFactory ? { providerFactory: ctx.input.providerFactory } : {}
|
|
30147
30274
|
});
|
|
30275
|
+
if (ctx.input.signal?.aborted) {
|
|
30276
|
+
return { path: page.relativePath, action: "cancelled", reason: "enrichment cancelled", tier: "deep" };
|
|
30277
|
+
}
|
|
30148
30278
|
const deepToolCalls = deepResult.toolCalls.length;
|
|
30149
30279
|
let rawText = null;
|
|
30150
30280
|
if ("enriched" in deepResult) {
|
|
@@ -30222,9 +30352,9 @@ function batchGroupKey(item) {
|
|
|
30222
30352
|
const primaryKeyFile = item.keyFiles[0];
|
|
30223
30353
|
if (primaryKeyFile !== undefined) {
|
|
30224
30354
|
const dir = primaryKeyFile.split("/").slice(0, -1).join("/");
|
|
30225
|
-
const
|
|
30226
|
-
if (
|
|
30227
|
-
return
|
|
30355
|
+
const segments = dir.split("/").filter((segment) => segment.length > 0);
|
|
30356
|
+
if (segments.length > 0) {
|
|
30357
|
+
return segments.slice(0, 2).join("/");
|
|
30228
30358
|
}
|
|
30229
30359
|
}
|
|
30230
30360
|
return item.page.pageType;
|
|
@@ -30261,6 +30391,14 @@ function groupLightPagesIntoBatches(items, batchConfig, tokenBudget) {
|
|
|
30261
30391
|
return batches;
|
|
30262
30392
|
}
|
|
30263
30393
|
async function runLightBatch(ctx, items) {
|
|
30394
|
+
if (ctx.input.signal?.aborted) {
|
|
30395
|
+
return items.map((item) => ({
|
|
30396
|
+
path: item.page.relativePath,
|
|
30397
|
+
action: "cancelled",
|
|
30398
|
+
reason: "enrichment cancelled",
|
|
30399
|
+
tier: "light"
|
|
30400
|
+
}));
|
|
30401
|
+
}
|
|
30264
30402
|
for (const item of items) {
|
|
30265
30403
|
ctx.onPage({
|
|
30266
30404
|
index: ctx.pageIndex(item.page.relativePath),
|
|
@@ -30285,7 +30423,8 @@ async function runLightBatch(ctx, items) {
|
|
|
30285
30423
|
env: ctx.env,
|
|
30286
30424
|
...ctx.input.fetch ? { fetch: ctx.input.fetch } : {},
|
|
30287
30425
|
...ctx.input.baseUrl !== undefined ? { baseUrl: ctx.input.baseUrl } : {},
|
|
30288
|
-
...ctx.input.providerFactory ? { providerFactory: ctx.input.providerFactory } : {}
|
|
30426
|
+
...ctx.input.providerFactory ? { providerFactory: ctx.input.providerFactory } : {},
|
|
30427
|
+
...ctx.input.signal !== undefined ? { signal: ctx.input.signal } : {}
|
|
30289
30428
|
});
|
|
30290
30429
|
} catch (cause) {
|
|
30291
30430
|
const reason = cause instanceof Error ? cause.message : String(cause);
|
|
@@ -30305,6 +30444,14 @@ async function runLightBatch(ctx, items) {
|
|
|
30305
30444
|
};
|
|
30306
30445
|
});
|
|
30307
30446
|
}
|
|
30447
|
+
if (ctx.input.signal?.aborted) {
|
|
30448
|
+
return items.map((item) => ({
|
|
30449
|
+
path: item.page.relativePath,
|
|
30450
|
+
action: "cancelled",
|
|
30451
|
+
reason: "enrichment cancelled",
|
|
30452
|
+
tier: "light"
|
|
30453
|
+
}));
|
|
30454
|
+
}
|
|
30308
30455
|
if (turn.error) {
|
|
30309
30456
|
const errorReason = `${turn.error.kind}: ${turn.error.message}`;
|
|
30310
30457
|
return items.map((item) => {
|
|
@@ -30368,8 +30515,18 @@ async function runLightBatch(ctx, items) {
|
|
|
30368
30515
|
}
|
|
30369
30516
|
async function runRlmPipeline(ctxInput) {
|
|
30370
30517
|
const { pages, wikiConfig, input: input2 } = ctxInput;
|
|
30518
|
+
const cancelled = () => pages.map((page) => ({ path: page.relativePath, action: "cancelled", reason: "enrichment cancelled" }));
|
|
30519
|
+
if (input2.signal?.aborted) {
|
|
30520
|
+
return cancelled();
|
|
30521
|
+
}
|
|
30371
30522
|
const graph = await loadGraph(ctxInput.cwd);
|
|
30523
|
+
if (input2.signal?.aborted) {
|
|
30524
|
+
return cancelled();
|
|
30525
|
+
}
|
|
30372
30526
|
const gdgraphConfig = await loadGdgraphConfig(ctxInput.cwd);
|
|
30527
|
+
if (input2.signal?.aborted) {
|
|
30528
|
+
return cancelled();
|
|
30529
|
+
}
|
|
30373
30530
|
const fileNodes = graph.nodes.filter((node) => node.kind === "file").map((node) => node.path);
|
|
30374
30531
|
const rankEdges = buildRankEdges(graph, gdgraphConfig);
|
|
30375
30532
|
const ranked = personalizedPageRank(fileNodes, rankEdges, {
|
|
@@ -30400,11 +30557,21 @@ async function runRlmPipeline(ctxInput) {
|
|
|
30400
30557
|
};
|
|
30401
30558
|
const prepared = [];
|
|
30402
30559
|
for (const page of pages) {
|
|
30560
|
+
if (input2.signal?.aborted) {
|
|
30561
|
+
return cancelled();
|
|
30562
|
+
}
|
|
30403
30563
|
const index = pageIndex(page.relativePath);
|
|
30404
30564
|
const status = page.status ?? "draft";
|
|
30405
30565
|
ctx.onPage({ index, total: ctx.total, path: page.relativePath, status, phase: "start" });
|
|
30566
|
+
if (input2.signal?.aborted) {
|
|
30567
|
+
return cancelled();
|
|
30568
|
+
}
|
|
30569
|
+
input2.onRlmPreparation?.(page.relativePath);
|
|
30406
30570
|
try {
|
|
30407
30571
|
const originalRaw = await readFile62(page.absolutePath, "utf8");
|
|
30572
|
+
if (input2.signal?.aborted) {
|
|
30573
|
+
return cancelled();
|
|
30574
|
+
}
|
|
30408
30575
|
const ensured = ensureWikiFrontmatter(originalRaw, { title: page.title, pageType: page.pageType });
|
|
30409
30576
|
const original = ensured.markdown;
|
|
30410
30577
|
const keyFiles = keyFilesForPage(moduleKeyFilesIndex, page);
|
|
@@ -30412,6 +30579,9 @@ async function runRlmPipeline(ctxInput) {
|
|
|
30412
30579
|
let unchanged = false;
|
|
30413
30580
|
if (previousHash !== undefined && keyFiles.length > 0) {
|
|
30414
30581
|
const currentHash = await computePageNodeHash(ctx.cwd, keyFiles, graph);
|
|
30582
|
+
if (input2.signal?.aborted) {
|
|
30583
|
+
return cancelled();
|
|
30584
|
+
}
|
|
30415
30585
|
unchanged = isPageUnchangedSinceLastEnrich(page.relativePath, currentHash, resumeState.completedNodeHashes);
|
|
30416
30586
|
}
|
|
30417
30587
|
if (unchanged) {
|
|
@@ -30445,6 +30615,9 @@ async function runRlmPipeline(ctxInput) {
|
|
|
30445
30615
|
const item = { page, original, originalRaw, keyFiles };
|
|
30446
30616
|
prepared.push({ kind: tier === "deep" ? "deep" : "light", item });
|
|
30447
30617
|
} catch (cause) {
|
|
30618
|
+
if (input2.signal?.aborted) {
|
|
30619
|
+
return cancelled();
|
|
30620
|
+
}
|
|
30448
30621
|
ctx.onPage({ index, total: ctx.total, path: page.relativePath, status, phase: "failed" });
|
|
30449
30622
|
prepared.push({
|
|
30450
30623
|
kind: "resolved",
|
|
@@ -30456,6 +30629,9 @@ async function runRlmPipeline(ctxInput) {
|
|
|
30456
30629
|
});
|
|
30457
30630
|
}
|
|
30458
30631
|
}
|
|
30632
|
+
if (input2.signal?.aborted) {
|
|
30633
|
+
return cancelled();
|
|
30634
|
+
}
|
|
30459
30635
|
const units = [];
|
|
30460
30636
|
let pendingLight = [];
|
|
30461
30637
|
const flushLight = () => {
|
|
@@ -30485,6 +30661,9 @@ async function runRlmPipeline(ctxInput) {
|
|
|
30485
30661
|
}
|
|
30486
30662
|
}
|
|
30487
30663
|
flushLight();
|
|
30664
|
+
if (input2.signal?.aborted) {
|
|
30665
|
+
return cancelled();
|
|
30666
|
+
}
|
|
30488
30667
|
const unitResults = await mapPool(units, ctxInput.concurrency, async (unit) => {
|
|
30489
30668
|
try {
|
|
30490
30669
|
if (unit.kind === "resolved") {
|
|
@@ -30770,12 +30949,12 @@ async function narrate(opts) {
|
|
|
30770
30949
|
});
|
|
30771
30950
|
if (!turn.credentialAvailable) {
|
|
30772
30951
|
const message2 = formatMissingCredentialHint(turn.provider);
|
|
30773
|
-
|
|
30952
|
+
emit2(asJson, { provider: turn.provider, model: turn.model, credentialAvailable: false, error: message2 }, message2, true);
|
|
30774
30953
|
return;
|
|
30775
30954
|
}
|
|
30776
30955
|
if (turn.error) {
|
|
30777
30956
|
const message2 = `${turn.error.kind}: ${turn.error.message}`;
|
|
30778
|
-
|
|
30957
|
+
emit2(asJson, { provider: turn.provider, model: turn.model, error: message2 }, message2, true);
|
|
30779
30958
|
return;
|
|
30780
30959
|
}
|
|
30781
30960
|
const text = turn.text.trim();
|
|
@@ -30785,7 +30964,7 @@ async function narrate(opts) {
|
|
|
30785
30964
|
console.log(text);
|
|
30786
30965
|
}
|
|
30787
30966
|
}
|
|
30788
|
-
function
|
|
30967
|
+
function emit2(asJson, payload, message2, isError) {
|
|
30789
30968
|
if (asJson) {
|
|
30790
30969
|
console.log(JSON.stringify(payload, null, 2));
|
|
30791
30970
|
} else if (isError) {
|
|
@@ -32875,7 +33054,7 @@ var BUNDLED_GDSKILLS = [
|
|
|
32875
33054
|
"Use gdgraph affected context for exported symbols and shared surfaces.",
|
|
32876
33055
|
"Dispatch specialized review passes conceptually or as separate skill loads.",
|
|
32877
33056
|
"Report findings first, ordered by severity, with concrete file references."
|
|
32878
|
-
], ["review code", "full review", "review changes"]),
|
|
33057
|
+
], ["review", "\u0440\u0435\u0432\u044C\u044E", "review code", "full review", "review changes"]),
|
|
32879
33058
|
skill("review-logic", "review", ["recommended", "full"], "Review logic correctness, contracts, edge cases, nullability, and async behavior.", [
|
|
32880
33059
|
"Trace behavior through call sites and affected context.",
|
|
32881
33060
|
"Look for incorrect assumptions, missing branches, race conditions, and error paths.",
|
|
@@ -32890,7 +33069,13 @@ var BUNDLED_GDSKILLS = [
|
|
|
32890
33069
|
"Map inputs, trust boundaries, and sensitive outputs.",
|
|
32891
33070
|
"Check injection, auth, crypto, secrets, and unsafe filesystem/network behavior.",
|
|
32892
33071
|
"Prioritize exploitable findings with concrete remediation."
|
|
32893
|
-
], [
|
|
33072
|
+
], [
|
|
33073
|
+
"security review",
|
|
33074
|
+
"\u043F\u0440\u043E\u0432\u0435\u0440\u044C \u0431\u0435\u0437\u043E\u043F\u0430\u0441\u043D\u043E\u0441\u0442\u044C",
|
|
33075
|
+
"\u0431\u0435\u0437\u043E\u043F\u0430\u0441\u043D\u043E\u0441\u0442\u044C \u043A\u043E\u0434\u0430",
|
|
33076
|
+
"secure code",
|
|
33077
|
+
"vulnerability"
|
|
33078
|
+
]),
|
|
32894
33079
|
skill("review-performance", "review", ["recommended", "full"], "Review hot paths, unnecessary work, bundle/perf regressions, blocking operations, and memory risk.", [
|
|
32895
33080
|
"Find changed hot paths and repeated operations.",
|
|
32896
33081
|
"Check loops, rendering, async blocking, large imports, and caching behavior.",
|
|
@@ -33026,7 +33211,12 @@ var BUNDLED_GDSKILLS = [
|
|
|
33026
33211
|
"Detect deployment target and required pre-flight checks.",
|
|
33027
33212
|
"Run verification before deployment unless explicitly skipped.",
|
|
33028
33213
|
"Summarize deployment status, rollback path, and health checks."
|
|
33029
|
-
], [
|
|
33214
|
+
], [
|
|
33215
|
+
"deploy",
|
|
33216
|
+
"deployment",
|
|
33217
|
+
"ship",
|
|
33218
|
+
"release"
|
|
33219
|
+
]),
|
|
33030
33220
|
skill("commit", "quality", ["full"], "Prepare conventional commits with scope, summary, and verification notes.", [
|
|
33031
33221
|
"Inspect staged and unstaged changes.",
|
|
33032
33222
|
"Group related changes without staging unrelated work.",
|
|
@@ -33530,6 +33720,28 @@ async function validateValue(value, schema, valuePath, errors, rootSchema, schem
|
|
|
33530
33720
|
message: `Expected array length >= ${schema.minItems}`
|
|
33531
33721
|
});
|
|
33532
33722
|
}
|
|
33723
|
+
if (Array.isArray(value) && schema.maxItems !== undefined && value.length > schema.maxItems) {
|
|
33724
|
+
errors.push({
|
|
33725
|
+
path: valuePath,
|
|
33726
|
+
message: `Expected array length <= ${schema.maxItems}`
|
|
33727
|
+
});
|
|
33728
|
+
}
|
|
33729
|
+
if (typeof value === "number" && schema.exclusiveMinimum !== undefined && value <= schema.exclusiveMinimum) {
|
|
33730
|
+
errors.push({
|
|
33731
|
+
path: valuePath,
|
|
33732
|
+
message: `Expected number > ${schema.exclusiveMinimum}`
|
|
33733
|
+
});
|
|
33734
|
+
}
|
|
33735
|
+
if (schema.not !== undefined) {
|
|
33736
|
+
const negated = [];
|
|
33737
|
+
await validateValue(value, schema.not, valuePath, negated, rootSchema, schemaCache);
|
|
33738
|
+
if (negated.length === 0) {
|
|
33739
|
+
errors.push({
|
|
33740
|
+
path: valuePath,
|
|
33741
|
+
message: "Expected value NOT to match the prohibited subschema"
|
|
33742
|
+
});
|
|
33743
|
+
}
|
|
33744
|
+
}
|
|
33533
33745
|
if (typeof value === "string") {
|
|
33534
33746
|
if (schema.minLength !== undefined && value.length < schema.minLength) {
|
|
33535
33747
|
errors.push({
|
|
@@ -39475,8 +39687,56 @@ init_guard();
|
|
|
39475
39687
|
import { mkdir as mkdir16, readFile as readFile23, writeFile as writeFile18 } from "fs/promises";
|
|
39476
39688
|
import path46 from "path";
|
|
39477
39689
|
|
|
39690
|
+
// src/lib/bounded-stdin.ts
|
|
39691
|
+
async function readStdinBounded(deadlineMs) {
|
|
39692
|
+
if (process.stdin.isTTY) {
|
|
39693
|
+
return null;
|
|
39694
|
+
}
|
|
39695
|
+
const reader = Bun.stdin.stream().getReader();
|
|
39696
|
+
const timer = setTimeout(() => void reader.cancel().catch(() => {}), deadlineMs);
|
|
39697
|
+
timer.unref?.();
|
|
39698
|
+
const chunks = [];
|
|
39699
|
+
try {
|
|
39700
|
+
for (;; ) {
|
|
39701
|
+
const { done, value } = await reader.read();
|
|
39702
|
+
if (done)
|
|
39703
|
+
break;
|
|
39704
|
+
if (value)
|
|
39705
|
+
chunks.push(Buffer.from(value));
|
|
39706
|
+
}
|
|
39707
|
+
} catch {
|
|
39708
|
+
return null;
|
|
39709
|
+
} finally {
|
|
39710
|
+
clearTimeout(timer);
|
|
39711
|
+
reader.releaseLock?.();
|
|
39712
|
+
}
|
|
39713
|
+
return Buffer.concat(chunks).toString("utf8");
|
|
39714
|
+
}
|
|
39715
|
+
|
|
39478
39716
|
// src/ctx/hook-classify.ts
|
|
39479
39717
|
var ESCAPE_MARKER = /#\s*keryx:raw\b[ \t]*([^\n]*)/i;
|
|
39718
|
+
function escapeReasonOf(command) {
|
|
39719
|
+
let quote = null;
|
|
39720
|
+
for (let i = 0;i < command.length; i += 1) {
|
|
39721
|
+
const char = command[i];
|
|
39722
|
+
if (quote) {
|
|
39723
|
+
if (char === quote)
|
|
39724
|
+
quote = null;
|
|
39725
|
+
continue;
|
|
39726
|
+
}
|
|
39727
|
+
if (char === "'" || char === '"') {
|
|
39728
|
+
quote = char;
|
|
39729
|
+
continue;
|
|
39730
|
+
}
|
|
39731
|
+
if (char !== "#")
|
|
39732
|
+
continue;
|
|
39733
|
+
const match = ESCAPE_MARKER.exec(command.slice(i));
|
|
39734
|
+
if (match && match.index === 0) {
|
|
39735
|
+
return (match[1] ?? "").trim();
|
|
39736
|
+
}
|
|
39737
|
+
}
|
|
39738
|
+
return null;
|
|
39739
|
+
}
|
|
39480
39740
|
var LEADING_SKIP = new Set(["sudo", "command", "time", "nice", "env", "builtin"]);
|
|
39481
39741
|
var ALREADY_ROUTED = new Set(["keryx", "rtk"]);
|
|
39482
39742
|
var ROUTES = [
|
|
@@ -39484,11 +39744,118 @@ var ROUTES = [
|
|
|
39484
39744
|
{ names: /^(cat|head|tail)$/, suggestion: "keryx ctx read <file> --mode compact" }
|
|
39485
39745
|
];
|
|
39486
39746
|
var GIT_ROUTABLE = /^(diff|log|show)$/;
|
|
39487
|
-
function
|
|
39488
|
-
return command.split(
|
|
39747
|
+
function statements(command) {
|
|
39748
|
+
return command.split(/\|\||&&|;|\n/).map((s) => s.trim()).filter(Boolean);
|
|
39749
|
+
}
|
|
39750
|
+
function stages(command) {
|
|
39751
|
+
const out = [];
|
|
39752
|
+
for (const statement of statements(command)) {
|
|
39753
|
+
const parts = splitPipeline(statement);
|
|
39754
|
+
parts.forEach((text, index) => {
|
|
39755
|
+
const trimmed = text.trim();
|
|
39756
|
+
if (trimmed)
|
|
39757
|
+
out.push({ text: trimmed, isFirst: index === 0 });
|
|
39758
|
+
});
|
|
39759
|
+
}
|
|
39760
|
+
return out;
|
|
39761
|
+
}
|
|
39762
|
+
function splitPipeline(statement) {
|
|
39763
|
+
const parts = [];
|
|
39764
|
+
let current = "";
|
|
39765
|
+
let quote = null;
|
|
39766
|
+
for (const char of statement) {
|
|
39767
|
+
if (quote) {
|
|
39768
|
+
if (char === quote)
|
|
39769
|
+
quote = null;
|
|
39770
|
+
current += char;
|
|
39771
|
+
continue;
|
|
39772
|
+
}
|
|
39773
|
+
if (char === "'" || char === '"') {
|
|
39774
|
+
quote = char;
|
|
39775
|
+
current += char;
|
|
39776
|
+
continue;
|
|
39777
|
+
}
|
|
39778
|
+
if (char === "|") {
|
|
39779
|
+
parts.push(current);
|
|
39780
|
+
current = "";
|
|
39781
|
+
continue;
|
|
39782
|
+
}
|
|
39783
|
+
current += char;
|
|
39784
|
+
}
|
|
39785
|
+
parts.push(current);
|
|
39786
|
+
return parts;
|
|
39787
|
+
}
|
|
39788
|
+
function stageTokens(stage) {
|
|
39789
|
+
return (stage.match(/'[^']*'|"[^"]*"|\S+/g) ?? []).filter(Boolean);
|
|
39790
|
+
}
|
|
39791
|
+
var VALUE_FLAGS = {
|
|
39792
|
+
search: new Set(["-e", "--regexp", "-f", "--file", "-m", "--max-count", "-A", "-B", "-C", "--context"]),
|
|
39793
|
+
head: new Set(["-n", "--lines", "-c", "--bytes"]),
|
|
39794
|
+
tail: new Set(["-n", "--lines", "-c", "--bytes"]),
|
|
39795
|
+
sed: new Set(["-e", "--expression", "-f", "--file"]),
|
|
39796
|
+
awk: new Set(["-f", "-v"])
|
|
39797
|
+
};
|
|
39798
|
+
function valueFlagsFor(command, searchLike) {
|
|
39799
|
+
return searchLike ? VALUE_FLAGS.search : VALUE_FLAGS[command] ?? new Set;
|
|
39800
|
+
}
|
|
39801
|
+
function readsStdin(tokens, isFirst) {
|
|
39802
|
+
const [command, ...rest] = tokens;
|
|
39803
|
+
if (!command) {
|
|
39804
|
+
return true;
|
|
39805
|
+
}
|
|
39806
|
+
if (/^(find|ls|git)$/.test(command)) {
|
|
39807
|
+
return false;
|
|
39808
|
+
}
|
|
39809
|
+
if (/^(rg|ripgrep)$/.test(command) && isFirst) {
|
|
39810
|
+
return false;
|
|
39811
|
+
}
|
|
39812
|
+
const searchLike = /^(rg|grep|egrep|fgrep|ripgrep)$/.test(command);
|
|
39813
|
+
const valueFlags = valueFlagsFor(command, searchLike);
|
|
39814
|
+
let operandsAllowed = searchLike || /^(sed|awk)$/.test(command) ? 1 : 0;
|
|
39815
|
+
for (let i = 0;i < rest.length; i += 1) {
|
|
39816
|
+
const token = rest[i] ?? "";
|
|
39817
|
+
const [flag, attached] = splitFlagValue(token);
|
|
39818
|
+
if (operandsAllowed > 0 && PATTERN_FLAGS.has(flag)) {
|
|
39819
|
+
operandsAllowed = 0;
|
|
39820
|
+
if (attached === null)
|
|
39821
|
+
i += 1;
|
|
39822
|
+
continue;
|
|
39823
|
+
}
|
|
39824
|
+
if (valueFlags.has(flag)) {
|
|
39825
|
+
if (attached === null)
|
|
39826
|
+
i += 1;
|
|
39827
|
+
continue;
|
|
39828
|
+
}
|
|
39829
|
+
if (token.startsWith("-")) {
|
|
39830
|
+
if (searchLike && isRecursiveFlag(token)) {
|
|
39831
|
+
return false;
|
|
39832
|
+
}
|
|
39833
|
+
continue;
|
|
39834
|
+
}
|
|
39835
|
+
if (operandsAllowed > 0) {
|
|
39836
|
+
operandsAllowed -= 1;
|
|
39837
|
+
continue;
|
|
39838
|
+
}
|
|
39839
|
+
return false;
|
|
39840
|
+
}
|
|
39841
|
+
return true;
|
|
39842
|
+
}
|
|
39843
|
+
var PATTERN_FLAGS = new Set(["-e", "--regexp", "-f", "--file", "--expression"]);
|
|
39844
|
+
function splitFlagValue(token) {
|
|
39845
|
+
if (token.startsWith("--")) {
|
|
39846
|
+
const eq = token.indexOf("=");
|
|
39847
|
+
return eq === -1 ? [token, null] : [token.slice(0, eq), token.slice(eq + 1)];
|
|
39848
|
+
}
|
|
39849
|
+
if (token.startsWith("-") && token.length > 2) {
|
|
39850
|
+
return [token.slice(0, 2), token.slice(2)];
|
|
39851
|
+
}
|
|
39852
|
+
return [token, null];
|
|
39853
|
+
}
|
|
39854
|
+
function isRecursiveFlag(token) {
|
|
39855
|
+
return token === "--recursive" || token === "--dereference-recursive" || /^-[a-zA-Z]*[rR]/.test(token);
|
|
39489
39856
|
}
|
|
39490
39857
|
function leadingTokens(segment) {
|
|
39491
|
-
const tokens = segment
|
|
39858
|
+
const tokens = stageTokens(segment);
|
|
39492
39859
|
let i = 0;
|
|
39493
39860
|
while (i < tokens.length) {
|
|
39494
39861
|
const token = tokens[i] ?? "";
|
|
@@ -39504,12 +39871,15 @@ function classifyCommand(command) {
|
|
|
39504
39871
|
if (!command.trim()) {
|
|
39505
39872
|
return { block: false };
|
|
39506
39873
|
}
|
|
39507
|
-
const
|
|
39508
|
-
if (
|
|
39509
|
-
return { block: false, escapeReason
|
|
39874
|
+
const escapeReason = escapeReasonOf(command);
|
|
39875
|
+
if (escapeReason !== null) {
|
|
39876
|
+
return { block: false, escapeReason };
|
|
39510
39877
|
}
|
|
39511
|
-
for (const
|
|
39512
|
-
const tokens = leadingTokens(
|
|
39878
|
+
for (const stage of stages(command)) {
|
|
39879
|
+
const tokens = leadingTokens(stage.text);
|
|
39880
|
+
if (readsStdin(tokens, stage.isFirst)) {
|
|
39881
|
+
continue;
|
|
39882
|
+
}
|
|
39513
39883
|
const first = tokens[0];
|
|
39514
39884
|
if (!first || ALREADY_ROUTED.has(first)) {
|
|
39515
39885
|
continue;
|
|
@@ -39612,18 +39982,30 @@ function stripFromHookArray(settings, key) {
|
|
|
39612
39982
|
removeSentinel(settings);
|
|
39613
39983
|
return settings;
|
|
39614
39984
|
}
|
|
39615
|
-
function
|
|
39616
|
-
const
|
|
39617
|
-
const
|
|
39618
|
-
return
|
|
39619
|
-
|
|
39985
|
+
function groupContainer(settings, runtime) {
|
|
39986
|
+
const container = runtime.groupContainer ? settings[runtime.groupContainer] : settings.hooks;
|
|
39987
|
+
const list2 = container?.[runtime.groupKey];
|
|
39988
|
+
return Array.isArray(list2) ? list2 : [];
|
|
39989
|
+
}
|
|
39990
|
+
function managedGroupsFor(settings, runtime) {
|
|
39991
|
+
const command = hookCommand(runtime.id);
|
|
39992
|
+
return groupContainer(settings, runtime).filter((group) => {
|
|
39993
|
+
if (!isManagedGroup2(group))
|
|
39620
39994
|
return false;
|
|
39621
|
-
const
|
|
39622
|
-
if (
|
|
39623
|
-
return
|
|
39624
|
-
|
|
39995
|
+
const entry = group;
|
|
39996
|
+
if (runtime.groupShape === "flat") {
|
|
39997
|
+
return entry.command === command;
|
|
39998
|
+
}
|
|
39999
|
+
return Array.isArray(entry.hooks) && entry.hooks.some((hook) => hook?.command === command && hook?.type === "command");
|
|
39625
40000
|
});
|
|
39626
40001
|
}
|
|
40002
|
+
function hasRunnableGuard(settings, runtime) {
|
|
40003
|
+
return managedGroupsFor(settings, runtime).length > 0;
|
|
40004
|
+
}
|
|
40005
|
+
function hasStalePreToolUseMatcher(settings, runtime) {
|
|
40006
|
+
const expected = preToolUseMatcher(runtime);
|
|
40007
|
+
return managedGroupsFor(settings, runtime).some((group) => typeof group.matcher !== "string" || group.matcher !== expected);
|
|
40008
|
+
}
|
|
39627
40009
|
function parseJson(payload) {
|
|
39628
40010
|
try {
|
|
39629
40011
|
const parsed = JSON.parse(payload);
|
|
@@ -39642,6 +40024,22 @@ function parseToolInputCommand(payload) {
|
|
|
39642
40024
|
const command = input2.command;
|
|
39643
40025
|
return typeof command === "string" ? command : null;
|
|
39644
40026
|
}
|
|
40027
|
+
function parseToolName(payload) {
|
|
40028
|
+
const record = parseJson(payload);
|
|
40029
|
+
const name = record?.tool_name;
|
|
40030
|
+
return typeof name === "string" ? name : null;
|
|
40031
|
+
}
|
|
40032
|
+
function nativeSearchMessage(tool) {
|
|
40033
|
+
return [
|
|
40034
|
+
`[keryx ctx] The \`${tool}\` tool searches project code without the gdctx routing layer.`,
|
|
40035
|
+
`Use instead: keryx ctx rg "<pattern>" [path]`,
|
|
40036
|
+
`The routed form is compressed and recorded in the routing audit (ctx_used).`,
|
|
40037
|
+
`Structural question (callers, usages, blast radius)? Use gdgraph first.`,
|
|
40038
|
+
`If raw output is genuinely required, run it through Bash with an escape marker:`,
|
|
40039
|
+
` rg "<pattern>" <path> # keryx:raw <why raw is needed>`
|
|
40040
|
+
].join(`
|
|
40041
|
+
`);
|
|
40042
|
+
}
|
|
39645
40043
|
function parseCursorCommand(payload) {
|
|
39646
40044
|
const record = parseJson(payload);
|
|
39647
40045
|
const command = record?.command;
|
|
@@ -39712,41 +40110,73 @@ function antigravityAllow(_c) {
|
|
|
39712
40110
|
return { exitCode: 0, stdout: `${JSON.stringify({ allow_tool: true })}
|
|
39713
40111
|
` };
|
|
39714
40112
|
}
|
|
39715
|
-
function
|
|
40113
|
+
function preToolUseMatcher(runtime) {
|
|
40114
|
+
return ["Bash", ...runtime.nativeSearchTools ?? []].join("|");
|
|
40115
|
+
}
|
|
40116
|
+
function validatePreToolUse(settings, runtime) {
|
|
40117
|
+
const command = hookCommand(runtime.id);
|
|
40118
|
+
const expected = preToolUseMatcher(runtime);
|
|
40119
|
+
if (!hasRunnableGuard(settings, runtime)) {
|
|
40120
|
+
return [`${runtime.id}: missing PreToolUse(${expected}) guard`];
|
|
40121
|
+
}
|
|
40122
|
+
if (hasStalePreToolUseMatcher(settings, runtime)) {
|
|
40123
|
+
return [
|
|
40124
|
+
`${runtime.id}: PreToolUse guard does not match ${expected}; a tool it should cover bypasses it. ` + `Re-run \`keryx ctx install-hook --runtime ${runtime.id}\`.`
|
|
40125
|
+
];
|
|
40126
|
+
}
|
|
40127
|
+
return [];
|
|
40128
|
+
}
|
|
40129
|
+
function describeExistingGuard(settings, runtime) {
|
|
40130
|
+
const command = hookCommand(runtime.id);
|
|
40131
|
+
if (!hasRunnableGuard(settings, runtime))
|
|
40132
|
+
return null;
|
|
40133
|
+
if (!hasStalePreToolUseMatcher(settings, runtime))
|
|
40134
|
+
return null;
|
|
40135
|
+
const found = managedGroupsFor(settings, runtime).map((group) => typeof group.matcher === "string" ? group.matcher : "(no matcher)").join(", ");
|
|
40136
|
+
return `upgraded an existing guard: ${found} -> ${preToolUseMatcher(runtime)}`;
|
|
40137
|
+
}
|
|
40138
|
+
function preToolUseGroup(runtime) {
|
|
39716
40139
|
return {
|
|
39717
|
-
matcher:
|
|
39718
|
-
hooks: [{ type: "command", command: hookCommand(id) }],
|
|
40140
|
+
matcher: preToolUseMatcher(runtime),
|
|
40141
|
+
hooks: [{ type: "command", command: hookCommand(runtime.id) }],
|
|
39719
40142
|
[MANAGED_KEY2]: CTX_HOOK_SENTINEL
|
|
39720
40143
|
};
|
|
39721
40144
|
}
|
|
39722
40145
|
var CLAUDE_RUNTIME3 = {
|
|
39723
40146
|
id: "claude",
|
|
39724
|
-
label: ".claude/settings.json (PreToolUse
|
|
40147
|
+
label: ".claude/settings.json (PreToolUse)",
|
|
39725
40148
|
confidence: "verified",
|
|
40149
|
+
groupShape: "nested",
|
|
40150
|
+
groupKey: "PreToolUse",
|
|
40151
|
+
nativeSearchTools: ["Grep"],
|
|
39726
40152
|
parseCommand: parseToolInputCommand,
|
|
39727
40153
|
block: exitCodeBlock,
|
|
39728
40154
|
allow: exitCodeAllow,
|
|
39729
40155
|
locate: (root) => path44.join(root, ".claude", "settings.json"),
|
|
39730
|
-
merge: (s) => mergeIntoHookArray(s, "PreToolUse", preToolUseGroup(
|
|
40156
|
+
merge: (s) => mergeIntoHookArray(s, "PreToolUse", preToolUseGroup(CLAUDE_RUNTIME3)),
|
|
39731
40157
|
strip: (s) => stripFromHookArray(s, "PreToolUse"),
|
|
39732
|
-
validate: (s) =>
|
|
40158
|
+
validate: (s) => validatePreToolUse(s, CLAUDE_RUNTIME3)
|
|
39733
40159
|
};
|
|
39734
40160
|
var CODEX_RUNTIME = {
|
|
39735
40161
|
id: "codex",
|
|
39736
|
-
label: ".codex/hooks.json (PreToolUse
|
|
40162
|
+
label: ".codex/hooks.json (PreToolUse)",
|
|
39737
40163
|
confidence: "verified",
|
|
40164
|
+
groupShape: "nested",
|
|
40165
|
+
groupKey: "PreToolUse",
|
|
39738
40166
|
parseCommand: parseToolInputCommand,
|
|
39739
40167
|
block: exitCodeBlock,
|
|
39740
40168
|
allow: exitCodeAllow,
|
|
39741
40169
|
locate: (root) => path44.join(root, ".codex", "hooks.json"),
|
|
39742
|
-
merge: (s) => mergeIntoHookArray(s, "PreToolUse", preToolUseGroup(
|
|
40170
|
+
merge: (s) => mergeIntoHookArray(s, "PreToolUse", preToolUseGroup(CODEX_RUNTIME)),
|
|
39743
40171
|
strip: (s) => stripFromHookArray(s, "PreToolUse"),
|
|
39744
|
-
validate: (s) =>
|
|
40172
|
+
validate: (s) => validatePreToolUse(s, CODEX_RUNTIME)
|
|
39745
40173
|
};
|
|
39746
40174
|
var CURSOR_RUNTIME2 = {
|
|
39747
40175
|
id: "cursor",
|
|
39748
40176
|
label: ".cursor/hooks.json (beforeShellExecution)",
|
|
39749
40177
|
confidence: "verified",
|
|
40178
|
+
groupShape: "flat",
|
|
40179
|
+
groupKey: "beforeShellExecution",
|
|
39750
40180
|
parseCommand: parseCursorCommand,
|
|
39751
40181
|
block: cursorBlock,
|
|
39752
40182
|
allow: cursorAllow,
|
|
@@ -39759,12 +40189,14 @@ var CURSOR_RUNTIME2 = {
|
|
|
39759
40189
|
});
|
|
39760
40190
|
},
|
|
39761
40191
|
strip: (s) => stripFromHookArray(s, "beforeShellExecution"),
|
|
39762
|
-
validate: (s) =>
|
|
40192
|
+
validate: (s) => hasRunnableGuard(s, CURSOR_RUNTIME2) ? [] : ["cursor: missing beforeShellExecution guard"]
|
|
39763
40193
|
};
|
|
39764
40194
|
var WINDSURF_RUNTIME = {
|
|
39765
40195
|
id: "windsurf",
|
|
39766
40196
|
label: ".windsurf/hooks.json (pre_run_command)",
|
|
39767
40197
|
confidence: "verified",
|
|
40198
|
+
groupShape: "flat",
|
|
40199
|
+
groupKey: "pre_run_command",
|
|
39768
40200
|
parseCommand: parseWindsurfCommand,
|
|
39769
40201
|
block: exitCodeBlock,
|
|
39770
40202
|
allow: exitCodeAllow,
|
|
@@ -39775,12 +40207,15 @@ var WINDSURF_RUNTIME = {
|
|
|
39775
40207
|
[MANAGED_KEY2]: CTX_HOOK_SENTINEL
|
|
39776
40208
|
}),
|
|
39777
40209
|
strip: (s) => stripFromHookArray(s, "pre_run_command"),
|
|
39778
|
-
validate: (s) =>
|
|
40210
|
+
validate: (s) => hasRunnableGuard(s, WINDSURF_RUNTIME) ? [] : ["windsurf: missing pre_run_command guard"]
|
|
39779
40211
|
};
|
|
39780
40212
|
var ANTIGRAVITY_RUNTIME = {
|
|
39781
40213
|
id: "antigravity",
|
|
39782
40214
|
label: ".agents/hooks.json (PreToolUse/run_command)",
|
|
39783
40215
|
confidence: "experimental",
|
|
40216
|
+
groupShape: "nested",
|
|
40217
|
+
groupKey: "PreToolUse",
|
|
40218
|
+
groupContainer: "keryx-ctx-guard",
|
|
39784
40219
|
parseCommand: parseAntigravityCommand,
|
|
39785
40220
|
block: antigravityBlock,
|
|
39786
40221
|
allow: antigravityAllow,
|
|
@@ -39813,12 +40248,7 @@ var ANTIGRAVITY_RUNTIME = {
|
|
|
39813
40248
|
removeSentinel(s);
|
|
39814
40249
|
return s;
|
|
39815
40250
|
},
|
|
39816
|
-
validate: (s) =>
|
|
39817
|
-
const group = s["keryx-ctx-guard"];
|
|
39818
|
-
const list2 = Array.isArray(group?.PreToolUse) ? group?.PreToolUse : [];
|
|
39819
|
-
const ok = list2.some((g) => isManagedGroup2(g) && Array.isArray(g.hooks) && g.hooks.some((h) => h?.command === hookCommand("antigravity")));
|
|
39820
|
-
return ok ? [] : ["antigravity: missing run_command guard"];
|
|
39821
|
-
}
|
|
40251
|
+
validate: (s) => hasRunnableGuard(s, ANTIGRAVITY_RUNTIME) ? [] : ["antigravity: missing run_command guard"]
|
|
39822
40252
|
};
|
|
39823
40253
|
var OPENCODE_PLUGIN = `// keryx gdctx routing guard \u2014 generated by \`keryx ctx install-hook --runtime opencode\`.
|
|
39824
40254
|
// Bridges OpenCode's tool.execute.before to \`keryx ctx hook opencode\`.
|
|
@@ -39841,6 +40271,8 @@ var OPENCODE_RUNTIME2 = {
|
|
|
39841
40271
|
id: "opencode",
|
|
39842
40272
|
label: ".opencode/plugin/keryx-ctx-guard.js",
|
|
39843
40273
|
confidence: "experimental",
|
|
40274
|
+
groupShape: "nested",
|
|
40275
|
+
groupKey: "PreToolUse",
|
|
39844
40276
|
parseCommand: parseToolInputCommand,
|
|
39845
40277
|
block: exitCodeBlock,
|
|
39846
40278
|
allow: exitCodeAllow,
|
|
@@ -39896,15 +40328,9 @@ function resolveRuntimes2(ids) {
|
|
|
39896
40328
|
return { runtimes, unknown, unsupported };
|
|
39897
40329
|
}
|
|
39898
40330
|
// src/ctx/hook.ts
|
|
40331
|
+
var STDIN_DEADLINE_MS = 2000;
|
|
39899
40332
|
async function readStdin() {
|
|
39900
|
-
|
|
39901
|
-
return "";
|
|
39902
|
-
}
|
|
39903
|
-
const chunks = [];
|
|
39904
|
-
for await (const chunk of process.stdin) {
|
|
39905
|
-
chunks.push(Buffer.from(chunk));
|
|
39906
|
-
}
|
|
39907
|
-
return Buffer.concat(chunks).toString("utf8");
|
|
40333
|
+
return await readStdinBounded(STDIN_DEADLINE_MS) ?? "";
|
|
39908
40334
|
}
|
|
39909
40335
|
async function runCtxHook(runtimeId) {
|
|
39910
40336
|
const runtime = getRuntime2(runtimeId ?? "claude");
|
|
@@ -39914,10 +40340,17 @@ async function runCtxHook(runtimeId) {
|
|
|
39914
40340
|
const payload = await readStdin();
|
|
39915
40341
|
const command = runtime.parseCommand(payload);
|
|
39916
40342
|
if (command === null) {
|
|
40343
|
+
const nativeSearch = matchedNativeSearch(runtime.nativeSearchTools, payload);
|
|
40344
|
+
if (nativeSearch) {
|
|
40345
|
+
emit(refusalAction(runtime.id, nativeSearchMessage(nativeSearch)));
|
|
40346
|
+
}
|
|
39917
40347
|
return;
|
|
39918
40348
|
}
|
|
39919
40349
|
const classification = classifyCommand(command);
|
|
39920
40350
|
const action = classification.block ? runtime.block(command, classification) : runtime.allow(classification);
|
|
40351
|
+
emit(action);
|
|
40352
|
+
}
|
|
40353
|
+
function emit(action) {
|
|
39921
40354
|
if (action.stdout)
|
|
39922
40355
|
process.stdout.write(action.stdout);
|
|
39923
40356
|
if (action.stderr)
|
|
@@ -39925,6 +40358,13 @@ async function runCtxHook(runtimeId) {
|
|
|
39925
40358
|
if (action.exitCode)
|
|
39926
40359
|
process.exitCode = action.exitCode;
|
|
39927
40360
|
}
|
|
40361
|
+
function matchedNativeSearch(tools, payload) {
|
|
40362
|
+
if (!tools || tools.length === 0) {
|
|
40363
|
+
return null;
|
|
40364
|
+
}
|
|
40365
|
+
const name = parseToolName(payload);
|
|
40366
|
+
return name && tools.includes(name) ? name : null;
|
|
40367
|
+
}
|
|
39928
40368
|
|
|
39929
40369
|
// src/ctx/hook-install.ts
|
|
39930
40370
|
init_fs();
|
|
@@ -39961,9 +40401,10 @@ async function installRuntimeHook(projectRoot, runtime) {
|
|
|
39961
40401
|
return { path: file, errors: [`${runtime.id}: no installer defined`] };
|
|
39962
40402
|
}
|
|
39963
40403
|
const settings = await readSettings3(file);
|
|
40404
|
+
const upgraded = describeExistingGuard(settings, runtime);
|
|
39964
40405
|
await writeSettings3(file, merge(settings));
|
|
39965
40406
|
const errors = validate(await readSettings3(file));
|
|
39966
|
-
return { path: file, errors };
|
|
40407
|
+
return { path: file, errors, ...upgraded ? { upgraded } : {} };
|
|
39967
40408
|
}
|
|
39968
40409
|
async function uninstallRuntimeHook(projectRoot, runtime) {
|
|
39969
40410
|
if (runtime.customUninstall) {
|
|
@@ -40206,7 +40647,7 @@ async function handleInstallHook(args) {
|
|
|
40206
40647
|
console.log("escape: append `# keryx:raw <reason>` to run a raw command anyway");
|
|
40207
40648
|
console.log("");
|
|
40208
40649
|
for (const runtime of runtimes) {
|
|
40209
|
-
const { path: file, errors } = await installRuntimeHook(cwd, runtime);
|
|
40650
|
+
const { path: file, errors, upgraded } = await installRuntimeHook(cwd, runtime);
|
|
40210
40651
|
const tag = runtime.confidence === "experimental" ? " (experimental \u2014 verify on a live install)" : "";
|
|
40211
40652
|
if (errors.length > 0) {
|
|
40212
40653
|
for (const error of errors)
|
|
@@ -40214,6 +40655,8 @@ async function handleInstallHook(args) {
|
|
|
40214
40655
|
process.exitCode = 1;
|
|
40215
40656
|
} else {
|
|
40216
40657
|
console.log(` \u2713 ${runtime.id} -> ${path46.relative(cwd, file)}${tag}`);
|
|
40658
|
+
if (upgraded)
|
|
40659
|
+
console.log(` ${upgraded}`);
|
|
40217
40660
|
}
|
|
40218
40661
|
}
|
|
40219
40662
|
reportUnsupported(unsupported);
|
|
@@ -40350,7 +40793,7 @@ ${renderUntrackedFiles(untracked, config)}
|
|
|
40350
40793
|
` : ""}
|
|
40351
40794
|
## Risk Hints
|
|
40352
40795
|
|
|
40353
|
-
${renderRiskHints(risky, shape)}
|
|
40796
|
+
${renderRiskHints(risky, shape, shape.files)}
|
|
40354
40797
|
|
|
40355
40798
|
## Hunks
|
|
40356
40799
|
|
|
@@ -40723,13 +41166,19 @@ function renderDiffCounts(file) {
|
|
|
40723
41166
|
}
|
|
40724
41167
|
return file.changed !== undefined ? `: ~${file.changed} changed` : "";
|
|
40725
41168
|
}
|
|
40726
|
-
function renderRiskHints(risky, shape) {
|
|
41169
|
+
function renderRiskHints(risky, shape, files) {
|
|
40727
41170
|
const hints = risky.map((file) => `- ${file.path}`);
|
|
40728
41171
|
if (shape.pathsAbbreviated) {
|
|
40729
41172
|
hints.push("- (paths are abbreviated by `--stat`; risk matching is unreliable \u2014 re-run with `--numstat` or `--name-only` to check)");
|
|
40730
41173
|
}
|
|
40731
41174
|
if (hints.length === 0) {
|
|
40732
|
-
|
|
41175
|
+
if (shape.mode === "unenumerable") {
|
|
41176
|
+
return "- unknown: no file list in this output shape";
|
|
41177
|
+
}
|
|
41178
|
+
if (files.length === 0 && (shape.reportedFileCount ?? 0) > 0) {
|
|
41179
|
+
return `- unknown: ${shape.reportedFileCount} file(s) changed but this output shape lists no per-file rows \u2014 nothing was checked. Re-run with \`--numstat\` or \`--name-only\`.`;
|
|
41180
|
+
}
|
|
41181
|
+
return "- none";
|
|
40733
41182
|
}
|
|
40734
41183
|
return hints.join(`
|
|
40735
41184
|
`);
|
|
@@ -43662,6 +44111,47 @@ async function routeProjectSkills(args2) {
|
|
|
43662
44111
|
}
|
|
43663
44112
|
console.log(`Next: ${matches2[0]?.next}`);
|
|
43664
44113
|
}
|
|
44114
|
+
var INFLECTIONS = ["s", "es", "ed", "d", "ing", "er", "ers", "ion", "ions", "ation"];
|
|
44115
|
+
var MIN_STEM = 4;
|
|
44116
|
+
function containsPhrase(text, phrase) {
|
|
44117
|
+
const words = text.split(" ").filter(Boolean);
|
|
44118
|
+
const target = phrase.split(" ").filter(Boolean);
|
|
44119
|
+
if (target.length === 0)
|
|
44120
|
+
return false;
|
|
44121
|
+
for (let i = 0;i + target.length <= words.length; i += 1) {
|
|
44122
|
+
let matched = true;
|
|
44123
|
+
for (let j = 0;j < target.length; j += 1) {
|
|
44124
|
+
const word = words[i + j];
|
|
44125
|
+
const want = target[j];
|
|
44126
|
+
const last = j === target.length - 1;
|
|
44127
|
+
if (word === want)
|
|
44128
|
+
continue;
|
|
44129
|
+
if (last && matchesInflected(word, want))
|
|
44130
|
+
continue;
|
|
44131
|
+
matched = false;
|
|
44132
|
+
break;
|
|
44133
|
+
}
|
|
44134
|
+
if (matched)
|
|
44135
|
+
return true;
|
|
44136
|
+
}
|
|
44137
|
+
return false;
|
|
44138
|
+
}
|
|
44139
|
+
function matchesInflected(word, stem) {
|
|
44140
|
+
if (stem.length < MIN_STEM || !word.startsWith(stem))
|
|
44141
|
+
return false;
|
|
44142
|
+
return INFLECTIONS.includes(word.slice(stem.length));
|
|
44143
|
+
}
|
|
44144
|
+
function triggerFires(trigger, normalizedQuery, queryWords, queryTokens) {
|
|
44145
|
+
if (containsPhrase(normalizedQuery, trigger)) {
|
|
44146
|
+
return true;
|
|
44147
|
+
}
|
|
44148
|
+
const rawWords = trigger.split(" ").filter(Boolean);
|
|
44149
|
+
if (rawWords.length === 0) {
|
|
44150
|
+
return false;
|
|
44151
|
+
}
|
|
44152
|
+
const meaningful = routeTokens(trigger);
|
|
44153
|
+
return rawWords.every((word) => meaningful.has(word) ? queryTokens.has(word) : queryWords.has(word));
|
|
44154
|
+
}
|
|
43665
44155
|
function scoreBundledSkillRoute(entry, query) {
|
|
43666
44156
|
const normalizedQuery = normalizeRouteText(query);
|
|
43667
44157
|
if (!normalizedQuery) {
|
|
@@ -43684,18 +44174,13 @@ function scoreBundledSkillRoute(entry, query) {
|
|
|
43684
44174
|
score += 100;
|
|
43685
44175
|
reasons.push("exact skill");
|
|
43686
44176
|
}
|
|
43687
|
-
const
|
|
43688
|
-
|
|
43689
|
-
return true;
|
|
43690
|
-
}
|
|
43691
|
-
const triggerTokens = [...routeTokens(trigger)];
|
|
43692
|
-
return triggerTokens.length > 0 && triggerTokens.every((token) => queryTokens.has(token));
|
|
43693
|
-
});
|
|
44177
|
+
const queryWords = new Set(normalizedQuery.split(" ").filter(Boolean));
|
|
44178
|
+
const triggerHit = triggers.some((trigger) => triggerFires(trigger, normalizedQuery, queryWords, queryTokens));
|
|
43694
44179
|
if (triggerHit) {
|
|
43695
44180
|
score += 55;
|
|
43696
44181
|
reasons.push("trigger");
|
|
43697
44182
|
}
|
|
43698
|
-
if (name && normalizedQuery
|
|
44183
|
+
if (name && containsPhrase(normalizedQuery, name)) {
|
|
43699
44184
|
score += 30;
|
|
43700
44185
|
reasons.push("skill name");
|
|
43701
44186
|
}
|
|
@@ -43801,7 +44286,8 @@ var ROUTE_STOPWORDS = new Set([
|
|
|
43801
44286
|
var RU_SYNONYM_PREFIXES = [
|
|
43802
44287
|
["\u0440\u0435\u0432\u044C\u044E", ["review"]],
|
|
43803
44288
|
["\u0440\u0435\u0432\u044C\u044E\u0435\u0440", ["review", "reviewer"]],
|
|
43804
|
-
["\u043F\u0440\u043E\u0432\u0435\u0440\u043A", ["
|
|
44289
|
+
["\u043F\u0440\u043E\u0432\u0435\u0440\u043A", ["verify", "check"]],
|
|
44290
|
+
["\u043F\u0440\u043E\u0432\u0435\u0440", ["verify", "check"]],
|
|
43805
44291
|
["\u0440\u0435\u0430\u043B\u0438\u0437", ["implement"]],
|
|
43806
44292
|
["\u0438\u043C\u043F\u043B\u0435\u043C\u0435\u043D\u0442", ["implement"]],
|
|
43807
44293
|
["\u0432\u043D\u0435\u0434\u0440", ["implement"]],
|
|
@@ -43837,6 +44323,7 @@ var RU_SYNONYM_PREFIXES = [
|
|
|
43837
44323
|
["\u0441\u043E\u0437\u0434\u0430", ["create"]],
|
|
43838
44324
|
["\u0434\u0435\u043F\u043B\u043E\u0439", ["deploy"]],
|
|
43839
44325
|
["\u0440\u0430\u0437\u0432\u0435\u0440\u0442", ["deploy"]],
|
|
44326
|
+
["\u0440\u0430\u0437\u0432\u0435\u0440\u043D", ["deploy"]],
|
|
43840
44327
|
["\u0437\u0430\u0432\u0438\u0441\u0438\u043C", ["dependency", "dependencies"]],
|
|
43841
44328
|
["\u0438\u043D\u0442\u0435\u0440\u0432\u044C\u044E", ["interview"]],
|
|
43842
44329
|
["\u043E\u043F\u0440\u043E\u0441", ["interview"]],
|
|
@@ -43846,6 +44333,7 @@ var RU_SYNONYM_PREFIXES = [
|
|
|
43846
44333
|
["\u0444\u043B\u043E", ["flow"]],
|
|
43847
44334
|
["\u043E\u0440\u043A\u0435\u0441\u0442\u0440", ["orchestrator", "orchestrate"]],
|
|
43848
44335
|
["\u0430\u043D\u0430\u043B\u0438\u0437", ["analyze", "analysis"]],
|
|
44336
|
+
["\u043F\u0440\u043E\u0430\u043D\u0430\u043B\u0438\u0437", ["analyze", "analysis"]],
|
|
43849
44337
|
["\u0440\u0435\u0432\u044C\u044E\u0438\u0440", ["review"]]
|
|
43850
44338
|
];
|
|
43851
44339
|
function routeTokens(normalized, expand = false) {
|
|
@@ -46833,9 +47321,9 @@ var SNAPSHOT_DIRECTORIES = new Set(["__snapshots__"]);
|
|
|
46833
47321
|
var SNAPSHOT_SUFFIXES = [".snap"];
|
|
46834
47322
|
function classifyPath(path133, config = DEFAULT_REVIEW_SCOPE_CONFIG) {
|
|
46835
47323
|
const normalized = path133.replace(/\\/g, "/").replace(/^\.\//, "");
|
|
46836
|
-
const
|
|
46837
|
-
const basename =
|
|
46838
|
-
const directories =
|
|
47324
|
+
const segments = normalized.split("/").filter((segment) => segment.length > 0);
|
|
47325
|
+
const basename = segments.at(-1) ?? normalized;
|
|
47326
|
+
const directories = segments.slice(0, -1);
|
|
46839
47327
|
for (const segment of directories) {
|
|
46840
47328
|
if (VENDOR_DIRECTORIES.has(segment)) {
|
|
46841
47329
|
return { reason: "vendored", detail: `vendored: path segment "${segment}/"` };
|
|
@@ -49574,8 +50062,11 @@ init_project_skills();
|
|
|
49574
50062
|
import { readFile as readFile72, readdir as readdir24 } from "fs/promises";
|
|
49575
50063
|
import path135 from "path";
|
|
49576
50064
|
var PROJECT_REVIEWER_MODULE = "review";
|
|
50065
|
+
function escapeRegexLiteral(literal) {
|
|
50066
|
+
return literal.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
50067
|
+
}
|
|
49577
50068
|
function metadataLine(content, label) {
|
|
49578
|
-
const match = content.match(new RegExp(`^${label
|
|
50069
|
+
const match = content.match(new RegExp(`^${escapeRegexLiteral(label)}:\\s*(.+)$`, "m"));
|
|
49579
50070
|
return match?.[1]?.trim();
|
|
49580
50071
|
}
|
|
49581
50072
|
async function skillDirs(root) {
|
|
@@ -53429,9 +53920,9 @@ async function readContent(file) {
|
|
|
53429
53920
|
}
|
|
53430
53921
|
return readStdin2();
|
|
53431
53922
|
}
|
|
53432
|
-
function surfaceWarnings(warnings,
|
|
53923
|
+
function surfaceWarnings(warnings, emit3 = console.log) {
|
|
53433
53924
|
for (const warning of warnings) {
|
|
53434
|
-
|
|
53925
|
+
emit3(` ${style.yellow(symbols.bullet)} ${warning}`);
|
|
53435
53926
|
}
|
|
53436
53927
|
}
|
|
53437
53928
|
async function handleStatus(cwd) {
|
|
@@ -53775,14 +54266,14 @@ async function handleEval(cwd, args2) {
|
|
|
53775
54266
|
}
|
|
53776
54267
|
process.exitCode = gate.status === "fail" ? 1 : 0;
|
|
53777
54268
|
}
|
|
53778
|
-
function renderDecision(decision,
|
|
53779
|
-
|
|
53780
|
-
|
|
53781
|
-
|
|
53782
|
-
|
|
54269
|
+
function renderDecision(decision, emit3 = console.log) {
|
|
54270
|
+
emit3("");
|
|
54271
|
+
emit3(` gate: ${gateLabel(decision.gate)}`);
|
|
54272
|
+
emit3(` action: ${decision.action}`);
|
|
54273
|
+
emit3(` findings: ${decision.findings.length}`);
|
|
53783
54274
|
for (const finding of decision.findings.slice(0, 20)) {
|
|
53784
54275
|
const loc = finding.location?.line ? ` (line ${finding.location.line})` : "";
|
|
53785
|
-
|
|
54276
|
+
emit3(` ${severityMarker(finding.severity)} ${finding.category}/${finding.policyId} \u2192 ${finding.action}${loc}`);
|
|
53786
54277
|
}
|
|
53787
54278
|
}
|
|
53788
54279
|
function severityMarker(severity) {
|
|
@@ -53831,7 +54322,7 @@ function applyRuntimeDecision(args2, code, message2, decision, policyIsKnown) {
|
|
|
53831
54322
|
if (runtime === undefined) {
|
|
53832
54323
|
return code;
|
|
53833
54324
|
}
|
|
53834
|
-
const
|
|
54325
|
+
const emit3 = (action) => {
|
|
53835
54326
|
if (action.stdout !== undefined) {
|
|
53836
54327
|
process.stdout.write(action.stdout);
|
|
53837
54328
|
}
|
|
@@ -53843,9 +54334,9 @@ function applyRuntimeDecision(args2, code, message2, decision, policyIsKnown) {
|
|
|
53843
54334
|
const outcome = decideHookOutcome(code !== 0, decision, policyIsKnown);
|
|
53844
54335
|
switch (outcome.kind) {
|
|
53845
54336
|
case "refuse":
|
|
53846
|
-
return
|
|
54337
|
+
return emit3(refusalAction(runtime, message2));
|
|
53847
54338
|
case "approve":
|
|
53848
|
-
return
|
|
54339
|
+
return emit3(allowAction(runtime));
|
|
53849
54340
|
case "silent":
|
|
53850
54341
|
return 0;
|
|
53851
54342
|
}
|
|
@@ -63322,7 +63813,7 @@ import { spawnSync as spawnSync2 } from "child_process";
|
|
|
63322
63813
|
// package.json
|
|
63323
63814
|
var package_default = {
|
|
63324
63815
|
name: "@mrciphersmith/keryx",
|
|
63325
|
-
version: "0.2.
|
|
63816
|
+
version: "0.2.76",
|
|
63326
63817
|
description: "Version-controlled project context for AI coding agents: code graph, architecture wiki, project memory, relevant tests, quality signals, and task flows.",
|
|
63327
63818
|
private: false,
|
|
63328
63819
|
publishConfig: {
|
|
@@ -63818,9 +64309,9 @@ function createAssistantMessageStream(otui, renderer, parent) {
|
|
|
63818
64309
|
parent.add(container);
|
|
63819
64310
|
return { container, segmenter: createStreamSegmenter(), views: [], frozen: 0 };
|
|
63820
64311
|
};
|
|
63821
|
-
const paint = (m,
|
|
63822
|
-
for (let i = m.frozen;i <
|
|
63823
|
-
const segment =
|
|
64312
|
+
const paint = (m, segments, frozen) => {
|
|
64313
|
+
for (let i = m.frozen;i < segments.length; i++) {
|
|
64314
|
+
const segment = segments[i];
|
|
63824
64315
|
if (segment === undefined) {
|
|
63825
64316
|
continue;
|
|
63826
64317
|
}
|
|
@@ -63832,11 +64323,11 @@ function createAssistantMessageStream(otui, renderer, parent) {
|
|
|
63832
64323
|
existing?.destroy();
|
|
63833
64324
|
m.views[i] = createSegmentView(otui, renderer, m.container, segment);
|
|
63834
64325
|
}
|
|
63835
|
-
for (const stale of m.views.splice(
|
|
64326
|
+
for (const stale of m.views.splice(segments.length)) {
|
|
63836
64327
|
stale.destroy();
|
|
63837
64328
|
}
|
|
63838
64329
|
m.frozen = frozen;
|
|
63839
|
-
for (const segment of
|
|
64330
|
+
for (const segment of segments) {
|
|
63840
64331
|
if (segment.kind === "code") {
|
|
63841
64332
|
lastCode = { lang: segment.lang, body: segment.body };
|
|
63842
64333
|
}
|
|
@@ -63848,8 +64339,8 @@ function createAssistantMessageStream(otui, renderer, parent) {
|
|
|
63848
64339
|
return;
|
|
63849
64340
|
}
|
|
63850
64341
|
const m = message2 ??= start();
|
|
63851
|
-
const { segments
|
|
63852
|
-
paint(m,
|
|
64342
|
+
const { segments, frozen } = m.segmenter.push(chunk);
|
|
64343
|
+
paint(m, segments, frozen);
|
|
63853
64344
|
},
|
|
63854
64345
|
finalize: (text) => {
|
|
63855
64346
|
const m = message2 ?? start();
|
|
@@ -63857,9 +64348,9 @@ function createAssistantMessageStream(otui, renderer, parent) {
|
|
|
63857
64348
|
view.destroy();
|
|
63858
64349
|
}
|
|
63859
64350
|
m.frozen = 0;
|
|
63860
|
-
const
|
|
63861
|
-
paint(m,
|
|
63862
|
-
if (
|
|
64351
|
+
const segments = segmentMarkdown(text);
|
|
64352
|
+
paint(m, segments, segments.length);
|
|
64353
|
+
if (segments.length === 0) {
|
|
63863
64354
|
try {
|
|
63864
64355
|
parent.remove(m.container);
|
|
63865
64356
|
} catch {}
|
|
@@ -66642,20 +67133,20 @@ function buildContextUsage(source) {
|
|
|
66642
67133
|
const hasEstimate = source.estimateTokens !== undefined;
|
|
66643
67134
|
const total = hasEstimate ? source.estimateTokens ?? 0 : last ?? 0;
|
|
66644
67135
|
const estimated = hasEstimate || last === undefined;
|
|
66645
|
-
const
|
|
67136
|
+
const segments = [];
|
|
66646
67137
|
if (hasEstimate) {
|
|
66647
|
-
|
|
67138
|
+
segments.push({ id: "history", label: "history (est.)", tokens: source.estimateTokens ?? 0 });
|
|
66648
67139
|
}
|
|
66649
67140
|
if (lastIn !== undefined) {
|
|
66650
|
-
|
|
67141
|
+
segments.push({ id: "last-in", label: "last in", tokens: lastIn });
|
|
66651
67142
|
}
|
|
66652
67143
|
if (lastOut !== undefined) {
|
|
66653
|
-
|
|
67144
|
+
segments.push({ id: "last-out", label: "last out", tokens: lastOut });
|
|
66654
67145
|
}
|
|
66655
67146
|
return {
|
|
66656
67147
|
total,
|
|
66657
67148
|
estimated,
|
|
66658
|
-
segments
|
|
67149
|
+
segments,
|
|
66659
67150
|
bar: renderUsageBar(total),
|
|
66660
67151
|
note: total === 0 ? "No context usage yet." : "No model context window is known. The bar is relative to used tokens, not a billed limit."
|
|
66661
67152
|
};
|
|
@@ -68271,6 +68762,161 @@ function setProjectPermissionMode(projectPath, mode, dir) {
|
|
|
68271
68762
|
init_permission_mode();
|
|
68272
68763
|
init_enrich2();
|
|
68273
68764
|
|
|
68765
|
+
// src/tui/foreground-operation.ts
|
|
68766
|
+
class ForegroundOperationOwner {
|
|
68767
|
+
active;
|
|
68768
|
+
disposed = false;
|
|
68769
|
+
get signal() {
|
|
68770
|
+
if (this.active === undefined) {
|
|
68771
|
+
throw new Error("foreground operation has not started");
|
|
68772
|
+
}
|
|
68773
|
+
return this.active.controller.signal;
|
|
68774
|
+
}
|
|
68775
|
+
get isDisposed() {
|
|
68776
|
+
return this.disposed;
|
|
68777
|
+
}
|
|
68778
|
+
get isActive() {
|
|
68779
|
+
return this.active !== undefined;
|
|
68780
|
+
}
|
|
68781
|
+
accepts(token) {
|
|
68782
|
+
return !this.disposed && this.active?.token === token;
|
|
68783
|
+
}
|
|
68784
|
+
begin() {
|
|
68785
|
+
if (this.disposed) {
|
|
68786
|
+
throw new Error("foreground operation owner is disposed");
|
|
68787
|
+
}
|
|
68788
|
+
if (this.active !== undefined) {
|
|
68789
|
+
throw new Error("a foreground operation is already active");
|
|
68790
|
+
}
|
|
68791
|
+
const token = Symbol("foreground-operation");
|
|
68792
|
+
let resolveSettled;
|
|
68793
|
+
const settled = new Promise((resolve3) => {
|
|
68794
|
+
resolveSettled = resolve3;
|
|
68795
|
+
});
|
|
68796
|
+
this.active = { token, controller: new AbortController, settled, resolveSettled };
|
|
68797
|
+
return token;
|
|
68798
|
+
}
|
|
68799
|
+
cancel(reason) {
|
|
68800
|
+
const controller = this.active?.controller;
|
|
68801
|
+
if (controller !== undefined && !controller.signal.aborted) {
|
|
68802
|
+
controller.abort(reason);
|
|
68803
|
+
}
|
|
68804
|
+
}
|
|
68805
|
+
settled() {
|
|
68806
|
+
return this.active?.settled ?? Promise.resolve();
|
|
68807
|
+
}
|
|
68808
|
+
settle(token) {
|
|
68809
|
+
if (this.active?.token !== token) {
|
|
68810
|
+
return;
|
|
68811
|
+
}
|
|
68812
|
+
const active = this.active;
|
|
68813
|
+
this.active = undefined;
|
|
68814
|
+
active.resolveSettled();
|
|
68815
|
+
}
|
|
68816
|
+
dispose() {
|
|
68817
|
+
this.disposed = true;
|
|
68818
|
+
this.cancel("renderer disposed");
|
|
68819
|
+
}
|
|
68820
|
+
}
|
|
68821
|
+
function createForegroundOperationOwner() {
|
|
68822
|
+
return new ForegroundOperationOwner;
|
|
68823
|
+
}
|
|
68824
|
+
|
|
68825
|
+
class ForegroundForceHandoff {
|
|
68826
|
+
pending = [];
|
|
68827
|
+
awaitingSettlement = false;
|
|
68828
|
+
enqueue(item) {
|
|
68829
|
+
this.pending.push(item);
|
|
68830
|
+
if (this.awaitingSettlement)
|
|
68831
|
+
return false;
|
|
68832
|
+
this.awaitingSettlement = true;
|
|
68833
|
+
return true;
|
|
68834
|
+
}
|
|
68835
|
+
get isAwaitingSettlement() {
|
|
68836
|
+
return this.awaitingSettlement;
|
|
68837
|
+
}
|
|
68838
|
+
get hasPending() {
|
|
68839
|
+
return this.pending.length > 0;
|
|
68840
|
+
}
|
|
68841
|
+
takeAfterSettlement() {
|
|
68842
|
+
this.awaitingSettlement = false;
|
|
68843
|
+
return this.pending.shift();
|
|
68844
|
+
}
|
|
68845
|
+
takeNext() {
|
|
68846
|
+
return this.pending.shift();
|
|
68847
|
+
}
|
|
68848
|
+
}
|
|
68849
|
+
function createForegroundForceHandoff() {
|
|
68850
|
+
return new ForegroundForceHandoff;
|
|
68851
|
+
}
|
|
68852
|
+
function finalizeWikiForegroundOperation(state, callbacks) {
|
|
68853
|
+
if (state.disposed)
|
|
68854
|
+
return "disposed";
|
|
68855
|
+
callbacks.stopBusy();
|
|
68856
|
+
if (state.aborted)
|
|
68857
|
+
return "aborted";
|
|
68858
|
+
callbacks.complete();
|
|
68859
|
+
return "completed";
|
|
68860
|
+
}
|
|
68861
|
+
async function runAfterForegroundSettlement(owner, action) {
|
|
68862
|
+
await owner.settled();
|
|
68863
|
+
if (!owner.isDisposed) {
|
|
68864
|
+
action();
|
|
68865
|
+
}
|
|
68866
|
+
}
|
|
68867
|
+
function createForegroundAgentIoFacade(owner, token, io) {
|
|
68868
|
+
const accepts = () => owner.accepts(token);
|
|
68869
|
+
return {
|
|
68870
|
+
write: (text) => {
|
|
68871
|
+
if (accepts())
|
|
68872
|
+
io.write(text);
|
|
68873
|
+
},
|
|
68874
|
+
onHistoryChange: (kind2) => {
|
|
68875
|
+
if (accepts())
|
|
68876
|
+
io.onHistoryChange?.(kind2);
|
|
68877
|
+
},
|
|
68878
|
+
onAssistantText: (text) => {
|
|
68879
|
+
if (accepts())
|
|
68880
|
+
io.onAssistantText?.(text);
|
|
68881
|
+
},
|
|
68882
|
+
onReasoning: (text) => {
|
|
68883
|
+
if (accepts())
|
|
68884
|
+
io.onReasoning?.(text);
|
|
68885
|
+
},
|
|
68886
|
+
onUsage: (usage) => {
|
|
68887
|
+
if (accepts())
|
|
68888
|
+
io.onUsage?.(usage);
|
|
68889
|
+
},
|
|
68890
|
+
onToolCall: (name, input2) => {
|
|
68891
|
+
if (accepts())
|
|
68892
|
+
io.onToolCall?.(name, input2);
|
|
68893
|
+
},
|
|
68894
|
+
onToolResult: (name, result) => {
|
|
68895
|
+
if (accepts())
|
|
68896
|
+
io.onToolResult?.(name, result);
|
|
68897
|
+
},
|
|
68898
|
+
onSystem: (text) => {
|
|
68899
|
+
if (accepts())
|
|
68900
|
+
io.onSystem?.(text);
|
|
68901
|
+
},
|
|
68902
|
+
onTerminalState: (state) => {
|
|
68903
|
+
if (accepts())
|
|
68904
|
+
io.onTerminalState?.(state);
|
|
68905
|
+
},
|
|
68906
|
+
requestApproval: async (tool, input2, meta) => {
|
|
68907
|
+
if (!accepts())
|
|
68908
|
+
return false;
|
|
68909
|
+
const response = await io.requestApproval?.(tool, input2, meta);
|
|
68910
|
+
return accepts() ? response ?? false : false;
|
|
68911
|
+
},
|
|
68912
|
+
onAutoApproved: (tool, input2, meta) => {
|
|
68913
|
+
if (accepts())
|
|
68914
|
+
io.onAutoApproved?.(tool, input2, meta);
|
|
68915
|
+
},
|
|
68916
|
+
permissionMode: () => accepts() ? io.permissionMode?.() ?? "ask" : "ask"
|
|
68917
|
+
};
|
|
68918
|
+
}
|
|
68919
|
+
|
|
68274
68920
|
// src/tui/herdr-report.ts
|
|
68275
68921
|
import net from "net";
|
|
68276
68922
|
var SOURCE = "herdr:keryx";
|
|
@@ -72001,14 +72647,14 @@ function shortenCwd(cwd, max) {
|
|
|
72001
72647
|
if (full.length <= max) {
|
|
72002
72648
|
return full;
|
|
72003
72649
|
}
|
|
72004
|
-
const
|
|
72005
|
-
for (let i = 1;i <
|
|
72006
|
-
const candidate = `\u2026/${
|
|
72650
|
+
const segments = full.split("/").filter((s) => s.length > 0);
|
|
72651
|
+
for (let i = 1;i < segments.length; i++) {
|
|
72652
|
+
const candidate = `\u2026/${segments.slice(i).join("/")}`;
|
|
72007
72653
|
if (candidate.length <= max) {
|
|
72008
72654
|
return candidate;
|
|
72009
72655
|
}
|
|
72010
72656
|
}
|
|
72011
|
-
const last =
|
|
72657
|
+
const last = segments[segments.length - 1] ?? full;
|
|
72012
72658
|
return `\u2026${last.slice(last.length - (max - 1))}`;
|
|
72013
72659
|
}
|
|
72014
72660
|
function estimateContextTokens(history) {
|
|
@@ -72716,9 +73362,12 @@ async function launchTuiAgentShell(opts) {
|
|
|
72716
73362
|
let liveDeps;
|
|
72717
73363
|
let liveJobs;
|
|
72718
73364
|
let detachExternal;
|
|
73365
|
+
const foregroundOperation = createForegroundOperationOwner();
|
|
72719
73366
|
try {
|
|
72720
73367
|
const r = renderer = await createShellRenderer(otui, {
|
|
72721
73368
|
onDestroy: () => {
|
|
73369
|
+
foregroundOperation.cancel("renderer destroyed");
|
|
73370
|
+
foregroundOperation.dispose();
|
|
72722
73371
|
mountedChrome?.destroy();
|
|
72723
73372
|
setAskUserHost(undefined);
|
|
72724
73373
|
setSubagentFleetListener(undefined);
|
|
@@ -72782,7 +73431,6 @@ async function launchTuiAgentShell(opts) {
|
|
|
72782
73431
|
const stopBusy = () => {
|
|
72783
73432
|
chrome.stopBusy();
|
|
72784
73433
|
};
|
|
72785
|
-
let mainTurnAbortController;
|
|
72786
73434
|
const sidebar = chrome.sidebarTop;
|
|
72787
73435
|
sidebar.add(new otui.TextRenderable(r, { id: "sb-title", content: otui.t`${otui.bold("keryx")}` }));
|
|
72788
73436
|
sidebar.add(new otui.TextRenderable(r, { id: "sb-model-k", content: otui.t`${otui.dim("Model")}`, marginTop: 1 }));
|
|
@@ -73813,7 +74461,7 @@ Staying in the current session.
|
|
|
73813
74461
|
let mainQueue = [];
|
|
73814
74462
|
let mainQueueSeq = 0;
|
|
73815
74463
|
let pendingQueueEdit;
|
|
73816
|
-
|
|
74464
|
+
const forceHandoff = createForegroundForceHandoff();
|
|
73817
74465
|
let sideWorkerRunning = false;
|
|
73818
74466
|
let sideClearTimeout;
|
|
73819
74467
|
const showSideQueueStatus = () => {
|
|
@@ -73963,15 +74611,19 @@ Staying in the current session.
|
|
|
73963
74611
|
return;
|
|
73964
74612
|
mainQueue = removeMainQueueItem(mainQueue, index);
|
|
73965
74613
|
paintMainQueue();
|
|
73966
|
-
|
|
73967
|
-
|
|
73968
|
-
|
|
73969
|
-
mainTurnAbortController = undefined;
|
|
74614
|
+
(async () => {
|
|
74615
|
+
const waitsForSettlement = forceHandoff.enqueue(item);
|
|
74616
|
+
foregroundOperation.cancel("queue item forced");
|
|
73970
74617
|
io.onSystem?.(`\u25C7 main turn interrupted \u2014 q${index + 1} will run next.
|
|
73971
74618
|
`);
|
|
73972
|
-
|
|
73973
|
-
|
|
73974
|
-
|
|
74619
|
+
if (!waitsForSettlement)
|
|
74620
|
+
return;
|
|
74621
|
+
await runAfterForegroundSettlement(foregroundOperation, () => {
|
|
74622
|
+
const next = forceHandoff.takeAfterSettlement();
|
|
74623
|
+
if (next !== undefined)
|
|
74624
|
+
runLine(next.question);
|
|
74625
|
+
});
|
|
74626
|
+
})();
|
|
73975
74627
|
};
|
|
73976
74628
|
const handleQueueNavKey = (key) => {
|
|
73977
74629
|
if (!queueNavActive) {
|
|
@@ -74178,6 +74830,8 @@ Staying in the current session.
|
|
|
74178
74830
|
});
|
|
74179
74831
|
switch (decision) {
|
|
74180
74832
|
case "exit": {
|
|
74833
|
+
foregroundOperation.cancel("shell exit");
|
|
74834
|
+
foregroundOperation.dispose();
|
|
74181
74835
|
(async () => {
|
|
74182
74836
|
await closeSlateSession(slateSession, mintTimestampAttemptId);
|
|
74183
74837
|
await deps.sweepBackgroundJobs?.();
|
|
@@ -74198,8 +74852,8 @@ Staying in the current session.
|
|
|
74198
74852
|
return;
|
|
74199
74853
|
}
|
|
74200
74854
|
case "interrupt": {
|
|
74201
|
-
if (
|
|
74202
|
-
|
|
74855
|
+
if (foregroundOperation.isActive) {
|
|
74856
|
+
foregroundOperation.cancel("interrupted by user");
|
|
74203
74857
|
io.onSystem?.(`\u25C7 main turn interrupted.
|
|
74204
74858
|
`);
|
|
74205
74859
|
return;
|
|
@@ -74626,10 +75280,13 @@ ${formatThemeList(getThemeId())}`);
|
|
|
74626
75280
|
}));
|
|
74627
75281
|
if (isWikiEnrichIntent(line)) {
|
|
74628
75282
|
const startedAt2 = Date.now();
|
|
75283
|
+
const operation2 = foregroundOperation.begin();
|
|
74629
75284
|
(async () => {
|
|
74630
75285
|
try {
|
|
74631
75286
|
startBusy("planning wiki enrich\u2026");
|
|
74632
75287
|
const plan = await planWikiEnrich(process.cwd());
|
|
75288
|
+
if (foregroundOperation.signal.aborted || foregroundOperation.isDisposed)
|
|
75289
|
+
return;
|
|
74633
75290
|
stopBusy();
|
|
74634
75291
|
const maxList = 40;
|
|
74635
75292
|
const draftLines = plan.drafts.slice(0, maxList).map((p) => ` \xB7 ${p.relativePath}`);
|
|
@@ -74656,6 +75313,8 @@ ${formatThemeList(getThemeId())}`);
|
|
|
74656
75313
|
acceptedCount: plan.accepted.length,
|
|
74657
75314
|
total: plan.forceTargets.length
|
|
74658
75315
|
});
|
|
75316
|
+
if (foregroundOperation.signal.aborted || foregroundOperation.isDisposed)
|
|
75317
|
+
return;
|
|
74659
75318
|
input2.focus();
|
|
74660
75319
|
if (choice2 === "cancel") {
|
|
74661
75320
|
transcript.add(new otui.TextRenderable(r, {
|
|
@@ -74692,8 +75351,11 @@ ${formatThemeList(getThemeId())}`);
|
|
|
74692
75351
|
force,
|
|
74693
75352
|
provider: currentSel.provider,
|
|
74694
75353
|
model: currentSel.model,
|
|
75354
|
+
signal: foregroundOperation.signal,
|
|
74695
75355
|
concurrency: 2,
|
|
74696
75356
|
onPage: (info) => {
|
|
75357
|
+
if (foregroundOperation.signal.aborted || foregroundOperation.isDisposed)
|
|
75358
|
+
return;
|
|
74697
75359
|
setBusyPhase(`enrich ${info.index}/${info.total} [${info.phase}] ${info.path}`);
|
|
74698
75360
|
setMainAgent("running", `${info.index}/${info.total}`);
|
|
74699
75361
|
const status = info.phase === "done" ? "done" : info.phase === "failed" ? "failed" : "running";
|
|
@@ -74706,6 +75368,8 @@ ${formatThemeList(getThemeId())}`);
|
|
|
74706
75368
|
});
|
|
74707
75369
|
}
|
|
74708
75370
|
});
|
|
75371
|
+
if (foregroundOperation.signal.aborted || foregroundOperation.isDisposed)
|
|
75372
|
+
return;
|
|
74709
75373
|
stopBusy();
|
|
74710
75374
|
setMainAgent(result.failed > 0 && result.enriched === 0 ? "failed" : "done", `${result.enriched}ok/${result.failed}fail`);
|
|
74711
75375
|
const lines = [
|
|
@@ -74737,20 +75401,34 @@ ${formatThemeList(getThemeId())}`);
|
|
|
74737
75401
|
saveSession();
|
|
74738
75402
|
} catch {}
|
|
74739
75403
|
} catch (cause) {
|
|
75404
|
+
if (foregroundOperation.signal.aborted || foregroundOperation.isDisposed)
|
|
75405
|
+
return;
|
|
74740
75406
|
stopBusy();
|
|
74741
75407
|
transcript.add(new otui.TextRenderable(r, {
|
|
74742
75408
|
id: `we-err${uid++}`,
|
|
74743
75409
|
content: otui.t`${otui.red(`wiki enrich failed: ${cause instanceof Error ? cause.message : String(cause)}`)}`
|
|
74744
75410
|
}));
|
|
74745
75411
|
} finally {
|
|
74746
|
-
const
|
|
74747
|
-
|
|
74748
|
-
|
|
74749
|
-
|
|
74750
|
-
|
|
74751
|
-
|
|
74752
|
-
|
|
74753
|
-
|
|
75412
|
+
const operationAborted = foregroundOperation.signal.aborted;
|
|
75413
|
+
foregroundOperation.settle(operation2);
|
|
75414
|
+
finalizeWikiForegroundOperation({ aborted: operationAborted, disposed: foregroundOperation.isDisposed }, {
|
|
75415
|
+
stopBusy,
|
|
75416
|
+
complete: () => {
|
|
75417
|
+
const secs = ((Date.now() - startedAt2) / 1000).toFixed(1);
|
|
75418
|
+
transcript.add(new otui.TextRenderable(r, {
|
|
75419
|
+
id: `w${uid++}`,
|
|
75420
|
+
content: otui.t`${otui.dim(`worked for ${secs}s`)}`,
|
|
75421
|
+
marginTop: 1
|
|
75422
|
+
}));
|
|
75423
|
+
focusComposer();
|
|
75424
|
+
if (!forceHandoff.isAwaitingSettlement) {
|
|
75425
|
+
const next = forceHandoff.takeNext() ?? mainQueue.shift();
|
|
75426
|
+
paintMainQueue();
|
|
75427
|
+
if (next !== undefined)
|
|
75428
|
+
runLine(next.question);
|
|
75429
|
+
}
|
|
75430
|
+
}
|
|
75431
|
+
});
|
|
74754
75432
|
}
|
|
74755
75433
|
})();
|
|
74756
75434
|
return;
|
|
@@ -74758,6 +75436,7 @@ ${formatThemeList(getThemeId())}`);
|
|
|
74758
75436
|
fleet.clearMatching((w) => w.id !== MAIN_AGENT_ID && !isSideWorkerId(w.id));
|
|
74759
75437
|
sessions.clear();
|
|
74760
75438
|
deps.resetSubagentBudget?.();
|
|
75439
|
+
const operation = foregroundOperation.begin();
|
|
74761
75440
|
setMainAgent("running", "waiting");
|
|
74762
75441
|
startBusy("waiting for model");
|
|
74763
75442
|
const startedAt = Date.now();
|
|
@@ -74769,8 +75448,6 @@ ${formatThemeList(getThemeId())}`);
|
|
|
74769
75448
|
}
|
|
74770
75449
|
prevOnSystem?.(text);
|
|
74771
75450
|
};
|
|
74772
|
-
const controller = new AbortController;
|
|
74773
|
-
mainTurnAbortController = controller;
|
|
74774
75451
|
const suggestNextStep = async () => {
|
|
74775
75452
|
try {
|
|
74776
75453
|
const lastUser = [...history].reverse().find((m) => m.role === "user")?.content ?? "";
|
|
@@ -74793,11 +75470,14 @@ ${tail}`,
|
|
|
74793
75470
|
chrome.showSuggestion(result.text.trim().split(/\s+/).slice(0, 20).join(" "));
|
|
74794
75471
|
} catch {}
|
|
74795
75472
|
};
|
|
74796
|
-
|
|
74797
|
-
|
|
75473
|
+
const foregroundIo = createForegroundAgentIoFacade(foregroundOperation, operation, io);
|
|
75474
|
+
runAgentTurn(foregroundIo, deps, history, line, {
|
|
75475
|
+
signal: foregroundOperation.signal,
|
|
74798
75476
|
...slateSession !== undefined ? { slateSession } : {}
|
|
74799
75477
|
}).finally(() => {
|
|
74800
|
-
|
|
75478
|
+
foregroundOperation.settle(operation);
|
|
75479
|
+
if (foregroundOperation.isDisposed)
|
|
75480
|
+
return;
|
|
74801
75481
|
const secs = ((Date.now() - startedAt) / 1000).toFixed(1);
|
|
74802
75482
|
stopBusy();
|
|
74803
75483
|
refreshWorkspaceSidebar();
|
|
@@ -74813,15 +75493,11 @@ ${tail}`,
|
|
|
74813
75493
|
sbContext.content = otui.t`${otui.dim(`~${est.toLocaleString()} tokens (est)`)}`;
|
|
74814
75494
|
}
|
|
74815
75495
|
focusComposer();
|
|
74816
|
-
if (
|
|
75496
|
+
if (!forceHandoff.hasPending && mainQueue.length === 0 && !turnFailed) {
|
|
74817
75497
|
suggestNextStep();
|
|
74818
75498
|
}
|
|
74819
|
-
if (
|
|
74820
|
-
const next =
|
|
74821
|
-
priorityMainQuestion = undefined;
|
|
74822
|
-
runLine(next.question);
|
|
74823
|
-
} else if (mainQueue.length > 0) {
|
|
74824
|
-
const next = mainQueue.shift();
|
|
75499
|
+
if (!forceHandoff.isAwaitingSettlement) {
|
|
75500
|
+
const next = forceHandoff.takeNext() ?? mainQueue.shift();
|
|
74825
75501
|
paintMainQueue();
|
|
74826
75502
|
if (next !== undefined)
|
|
74827
75503
|
runLine(next.question);
|
|
@@ -77977,7 +78653,7 @@ async function runRemoteTurn(input2) {
|
|
|
77977
78653
|
let seq = 0;
|
|
77978
78654
|
createTurnRecord({ turnId, sessionId, project: input2.project, origin: REMOTE_ORIGIN, startedAt }, input2.dir);
|
|
77979
78655
|
let bounded2 = false;
|
|
77980
|
-
const
|
|
78656
|
+
const emit3 = (kind2, extra = {}) => {
|
|
77981
78657
|
const event = { schemaVersion: "1.0.0", turnId, seq, kind: kind2, at: clock(), ...extra };
|
|
77982
78658
|
if (appendTurnEvent(event, input2.dir)) {
|
|
77983
78659
|
seq += 1;
|
|
@@ -78016,7 +78692,7 @@ async function runRemoteTurn(input2) {
|
|
|
78016
78692
|
}
|
|
78017
78693
|
return { turnId, sessionId, result };
|
|
78018
78694
|
};
|
|
78019
|
-
|
|
78695
|
+
emit3("turn.started");
|
|
78020
78696
|
if (input2.profile.requiredControls.isolation === "required-fail-closed") {
|
|
78021
78697
|
const available = input2.containmentAvailable ?? (() => false);
|
|
78022
78698
|
if (!available()) {
|
|
@@ -78080,7 +78756,7 @@ async function runRemoteTurn(input2) {
|
|
|
78080
78756
|
if (event.kind === "text_delta" && typeof event.text === "string") {
|
|
78081
78757
|
const redacted = await redactOut(security, event.text);
|
|
78082
78758
|
assistantText += redacted;
|
|
78083
|
-
|
|
78759
|
+
emit3("assistant.delta", { text: redacted });
|
|
78084
78760
|
}
|
|
78085
78761
|
}
|
|
78086
78762
|
if (asked) {
|
|
@@ -78226,12 +78902,12 @@ function matchRoute(pathname) {
|
|
|
78226
78902
|
if (FIXED_ROUTES.has(pathname)) {
|
|
78227
78903
|
return { route: "fixed", pathname };
|
|
78228
78904
|
}
|
|
78229
|
-
const
|
|
78230
|
-
if (
|
|
78231
|
-
return { route: "turn", turnId:
|
|
78905
|
+
const segments = pathname.split("/");
|
|
78906
|
+
if (segments.length === 4 && segments[1] === "v1" && segments[2] === "turns") {
|
|
78907
|
+
return { route: "turn", turnId: segments[3] ?? "" };
|
|
78232
78908
|
}
|
|
78233
|
-
if (
|
|
78234
|
-
return { route: "turn-events", turnId:
|
|
78909
|
+
if (segments.length === 5 && segments[1] === "v1" && segments[2] === "turns" && segments[4] === "events") {
|
|
78910
|
+
return { route: "turn-events", turnId: segments[3] ?? "" };
|
|
78235
78911
|
}
|
|
78236
78912
|
return { route: "none" };
|
|
78237
78913
|
}
|
|
@@ -82895,13 +83571,26 @@ async function workspaceCommand(args2) {
|
|
|
82895
83571
|
return;
|
|
82896
83572
|
}
|
|
82897
83573
|
if (subcommand === "confirm-review") {
|
|
82898
|
-
rejectUnknownOptions(args2.slice(3), new Set);
|
|
83574
|
+
rejectUnknownOptions(args2.slice(3), new Set(["--acknowledge-security"]));
|
|
82899
83575
|
const workspaceId = args2[1];
|
|
82900
83576
|
const proposalId = args2[2];
|
|
82901
83577
|
if (!workspaceId || !proposalId)
|
|
82902
|
-
throw new Error("Usage: keryx workspace confirm-review <workspace-id> <proposal-id>");
|
|
82903
|
-
const
|
|
82904
|
-
|
|
83578
|
+
throw new Error("Usage: keryx workspace confirm-review <workspace-id> <proposal-id> [--acknowledge-security]");
|
|
83579
|
+
const security = await readProposalSecurityGate(process.cwd(), workspaceId, proposalId);
|
|
83580
|
+
if (!security)
|
|
83581
|
+
throw new Error(`keryx workspace confirm-review: cannot read proposal ${proposalId} in workspace ${workspaceId} \u2014 refusing to mint a token for a proposal whose security gate is unknown`);
|
|
83582
|
+
const acknowledgeSecurity = booleanFlag(args2, "--acknowledge-security");
|
|
83583
|
+
if (security.gate === "needs-approval" && !acknowledgeSecurity) {
|
|
83584
|
+
throw new Error([
|
|
83585
|
+
`keryx workspace confirm-review: proposal ${proposalId} is gated \`needs-approval\` \u2014 its evidence tripped the security scan.`,
|
|
83586
|
+
...security.evidenceRefs.length > 0 ? [`Evidence: ${security.evidenceRefs.join(", ")}`] : [],
|
|
83587
|
+
"Read the evidence, then re-run with --acknowledge-security to state that you have.",
|
|
83588
|
+
"Accepting without reading it is the thing this gate exists to prevent."
|
|
83589
|
+
].join(`
|
|
83590
|
+
`));
|
|
83591
|
+
}
|
|
83592
|
+
const { token, expiresAt } = await mintConfirmToken(process.cwd(), workspaceId, proposalId, { securityAcknowledged: security.gate === "needs-approval" && acknowledgeSecurity });
|
|
83593
|
+
console.log(JSON.stringify({ token, expiresAt, securityGate: security.gate, securityAcknowledged: security.gate === "needs-approval" && acknowledgeSecurity }, null, 2));
|
|
82905
83594
|
return;
|
|
82906
83595
|
}
|
|
82907
83596
|
if (subcommand === "review") {
|