@mrciphersmith/keryx 0.3.123 → 0.3.125
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/README.md +4 -0
- package/dist/cli.js +1549 -556
- package/dist/core.js +85 -17
- package/package.json +1 -1
- package/src/gdskills/bundled/skills/orchestration/flow-orchestrator/SKILL.md +1 -1
- package/src/gdskills/bundled/skills/orchestration/job-orchestrator/SKILL.md +5 -5
- package/src/gdskills/bundled/skills/review/review-orchestrator/SKILL.detail.md +11 -11
- package/src/gdskills/bundled/skills/review/review-orchestrator/SKILL.md +25 -26
- package/src/gdskills/bundled/skills/review/review-premise/SKILL.md +2 -2
- package/src/gdskills/contracts/review-finding.schema.json +14 -1
package/dist/cli.js
CHANGED
|
@@ -19499,7 +19499,7 @@ var init_skill_length_ceilings = __esm(() => {
|
|
|
19499
19499
|
"review/review-jev-scenarios": 182,
|
|
19500
19500
|
"review/review-layout": 237,
|
|
19501
19501
|
"review/review-logic": 365,
|
|
19502
|
-
"review/review-orchestrator":
|
|
19502
|
+
"review/review-orchestrator": 1571,
|
|
19503
19503
|
"review/review-performance": 354,
|
|
19504
19504
|
"review/review-pr-contract": 191,
|
|
19505
19505
|
"review/review-pr-feedback": 893,
|
|
@@ -23312,7 +23312,7 @@ var package_default;
|
|
|
23312
23312
|
var init_package = __esm(() => {
|
|
23313
23313
|
package_default = {
|
|
23314
23314
|
name: "@mrciphersmith/keryx",
|
|
23315
|
-
version: "0.3.
|
|
23315
|
+
version: "0.3.125",
|
|
23316
23316
|
description: "Version-controlled project context for AI coding agents: code graph, architecture wiki, project memory, relevant tests, quality signals, and task flows.",
|
|
23317
23317
|
private: false,
|
|
23318
23318
|
publishConfig: {
|
|
@@ -36527,6 +36527,19 @@ function leadingYamlBlock(markdown) {
|
|
|
36527
36527
|
`, close + 4);
|
|
36528
36528
|
return markdown.slice(0, lineEnd < 0 ? markdown.length : lineEnd + 1);
|
|
36529
36529
|
}
|
|
36530
|
+
function unwrapFencedPage(markdown) {
|
|
36531
|
+
const source = toLf(markdown);
|
|
36532
|
+
const meta = leadingYamlBlock(source);
|
|
36533
|
+
if (meta === null)
|
|
36534
|
+
return markdown;
|
|
36535
|
+
const body = source.slice(meta.length);
|
|
36536
|
+
const unwrapped = unwrapFencedReply(body);
|
|
36537
|
+
if (unwrapped === body)
|
|
36538
|
+
return markdown;
|
|
36539
|
+
const innerMeta = leadingYamlBlock(unwrapped);
|
|
36540
|
+
const inner = innerMeta === null ? unwrapped : unwrapped.slice(innerMeta.length);
|
|
36541
|
+
return inEolOf(markdown, `${meta}${inner.replace(/^\n+/, "")}`);
|
|
36542
|
+
}
|
|
36530
36543
|
function preserveEnrichInvariants(original, enriched, entryNote) {
|
|
36531
36544
|
const source = toLf(original);
|
|
36532
36545
|
let next = toLf(enriched);
|
|
@@ -36569,7 +36582,8 @@ function isDeclaredGenerated(heading) {
|
|
|
36569
36582
|
return isReferenceHeading(heading) || /^related wiki$/i.test(heading);
|
|
36570
36583
|
}
|
|
36571
36584
|
function mergeGeneratedSections(existing, generated, entryNote) {
|
|
36572
|
-
const
|
|
36585
|
+
const original = toLf(existing);
|
|
36586
|
+
const source = toLf(unwrapFencedPage(original));
|
|
36573
36587
|
const generatedLf = toLf(generated);
|
|
36574
36588
|
const fresh = new Map(sectionsOf(generatedLf).map((section) => [section.heading.toLowerCase(), section]));
|
|
36575
36589
|
const enriched = sectionsOf(source).some((section) => !fresh.has(section.heading.toLowerCase()) && !/^changelog$/i.test(section.heading));
|
|
@@ -36620,8 +36634,8 @@ ${replacement.lines.join(`
|
|
|
36620
36634
|
}
|
|
36621
36635
|
}
|
|
36622
36636
|
const repaired = repairVersionAndOrder(next);
|
|
36623
|
-
if (next ===
|
|
36624
|
-
return repaired ===
|
|
36637
|
+
if (next === original)
|
|
36638
|
+
return repaired === original ? existing : inEolOf(existing, repaired);
|
|
36625
36639
|
const version = nextPageVersion(repaired);
|
|
36626
36640
|
return inEolOf(existing, prependChangelogEntry(setPageVersion(repaired, version), `- ${version} - ${entryNote}`));
|
|
36627
36641
|
}
|
|
@@ -43746,12 +43760,29 @@ var init_scope = __esm(() => {
|
|
|
43746
43760
|
});
|
|
43747
43761
|
|
|
43748
43762
|
// src/review/types.ts
|
|
43749
|
-
|
|
43763
|
+
function cardPressFields(source) {
|
|
43764
|
+
return {
|
|
43765
|
+
...source?.pressedBy === undefined ? {} : { pressedBy: source.pressedBy },
|
|
43766
|
+
...source?.pressedAt === undefined ? {} : { pressedAt: source.pressedAt }
|
|
43767
|
+
};
|
|
43768
|
+
}
|
|
43769
|
+
function isHostQuestion(value) {
|
|
43770
|
+
if (value === null || typeof value !== "object")
|
|
43771
|
+
return false;
|
|
43772
|
+
const hq = value;
|
|
43773
|
+
const filled = (field) => typeof field === "string" && field.trim() !== "";
|
|
43774
|
+
return HOST_QUESTION_SOURCES.includes(hq["source"]) && filled(hq["ref"]) && filled(hq["questionText"]) && filled(hq["answerText"]) && filled(hq["time"]);
|
|
43775
|
+
}
|
|
43776
|
+
function hostQuestionFields(source) {
|
|
43777
|
+
return source?.hostQuestion === undefined ? {} : { hostQuestion: source.hostQuestion };
|
|
43778
|
+
}
|
|
43779
|
+
var MANAGED_REVIEW_MODES, REVIEW_TARGET_KINDS, REVIEW_PACKAGE_STATUSES, REVIEW_COVERAGE_STATUSES, HOST_QUESTION_SOURCES, REVIEW_FINDING_SEVERITIES, REVIEW_FINDING_CONFIDENCES, FINDING_DISPOSITION_STATES, FINDING_DISMISSAL_STATES, EXTERNAL_TERMINAL_DISPOSITIONS, VERIFICATION_VERDICTS, VERIFICATION_METHODS, REASONING_CAPPED_VERDICT = "unverifiable", VERIFICATION_MODES, DEFAULT_VERIFICATION_MODE = "annotate", PREMISE_VERDICTS, HELD_PREMISE_VERDICTS;
|
|
43750
43780
|
var init_types4 = __esm(() => {
|
|
43751
43781
|
MANAGED_REVIEW_MODES = ["attach-review", "review-flow", "ingest"];
|
|
43752
43782
|
REVIEW_TARGET_KINDS = ["pr", "issue", "branch", "path", "report"];
|
|
43753
43783
|
REVIEW_PACKAGE_STATUSES = ["draft", "reviewed", "decided", "learned", "closed"];
|
|
43754
43784
|
REVIEW_COVERAGE_STATUSES = ["run", "skipped", "failed", "needs_context"];
|
|
43785
|
+
HOST_QUESTION_SOURCES = ["claude-code", "codex", "keryx-shell", "helyx-poll", "other"];
|
|
43755
43786
|
REVIEW_FINDING_SEVERITIES = ["blocker", "major", "minor", "info"];
|
|
43756
43787
|
REVIEW_FINDING_CONFIDENCES = ["high", "medium", "low"];
|
|
43757
43788
|
FINDING_DISPOSITION_STATES = [
|
|
@@ -46682,7 +46713,7 @@ function resultOf(record) {
|
|
|
46682
46713
|
flow: record.flow
|
|
46683
46714
|
};
|
|
46684
46715
|
}
|
|
46685
|
-
function findOpenTwin(records, hash, session, now) {
|
|
46716
|
+
function findOpenTwin(records, hash, session, now, host) {
|
|
46686
46717
|
const answered = new Set;
|
|
46687
46718
|
for (const r of records)
|
|
46688
46719
|
if (r.kind === "answer")
|
|
@@ -46690,6 +46721,8 @@ function findOpenTwin(records, hash, session, now) {
|
|
|
46690
46721
|
for (const r of records) {
|
|
46691
46722
|
if (r.kind !== "open" || r.backfilled === true || answered.has(r.id))
|
|
46692
46723
|
continue;
|
|
46724
|
+
if (r.hostQuestion?.source !== host?.source || r.hostQuestion?.ref !== host?.ref || r.hostQuestion?.verb !== host?.verb)
|
|
46725
|
+
continue;
|
|
46693
46726
|
if ((r.session ?? undefined) !== (session !== undefined && session.length > 0 ? session : undefined))
|
|
46694
46727
|
continue;
|
|
46695
46728
|
const age = now.getTime() - Date.parse(r.at);
|
|
@@ -46724,14 +46757,14 @@ async function openDecision(input) {
|
|
|
46724
46757
|
}
|
|
46725
46758
|
async function openLocked(input, now, random, question, ids, recommendation) {
|
|
46726
46759
|
const records = await readRecords(input.cwd);
|
|
46727
|
-
const twin = findOpenTwin(records, questionHash(question, [...ids]), input.session, now);
|
|
46760
|
+
const twin = findOpenTwin(records, questionHash(question, [...ids]), input.session, now, input.hostQuestion);
|
|
46728
46761
|
if (twin !== undefined)
|
|
46729
46762
|
return resultOf(twin);
|
|
46730
46763
|
const config = await loadDecisionsConfig(input.cwd);
|
|
46731
46764
|
const tagged = input.action !== undefined && input.action.trim().length > 0;
|
|
46732
46765
|
const irreversible = input.irreversible === true || tagged || isIrreversible(config.irreversible, question, input.action, input.options);
|
|
46733
46766
|
const salt = input.salt ?? await loadRepoSalt(input.cwd);
|
|
46734
|
-
const seq = input.seq ?? records.filter((r) => r.kind === "open").length + 1;
|
|
46767
|
+
const seq = input.seq ?? records.filter((r) => r.kind === "open" && r.hostQuestion === undefined).length + 1;
|
|
46735
46768
|
const choice = chooseArm({
|
|
46736
46769
|
salt,
|
|
46737
46770
|
seq,
|
|
@@ -46780,6 +46813,7 @@ async function openLocked(input, now, random, question, ids, recommendation) {
|
|
|
46780
46813
|
...tagged ? { action: input.action?.trim() ?? "" } : {},
|
|
46781
46814
|
...blindRefused ? { blindRefused: true } : {},
|
|
46782
46815
|
...input.source !== undefined && input.source.trim().length > 0 ? { source: oneLine(input.source) } : {},
|
|
46816
|
+
...input.hostQuestion === undefined ? {} : { hostQuestion: { source: oneLine(input.hostQuestion.source), ref: oneLine(input.hostQuestion.ref), verb: oneLine(input.hostQuestion.verb), answerText: storedOperatorText(input.hostQuestion.answerText) } },
|
|
46783
46817
|
...input.session !== undefined && input.session.length > 0 ? { session: input.session } : {}
|
|
46784
46818
|
};
|
|
46785
46819
|
await appendRecord(input.cwd, record);
|
|
@@ -46904,7 +46938,8 @@ var init_sources = __esm(() => {
|
|
|
46904
46938
|
roundLimit: "round-limit",
|
|
46905
46939
|
wikiEnrich: "tui-wiki-enrich",
|
|
46906
46940
|
queueRoute: "tui-queue-route",
|
|
46907
|
-
sessionLease: "tui-session-lease"
|
|
46941
|
+
sessionLease: "tui-session-lease",
|
|
46942
|
+
hostQuestion: "host-question"
|
|
46908
46943
|
};
|
|
46909
46944
|
WORK_DECISION_SOURCES = Object.values(DECISION_SOURCES);
|
|
46910
46945
|
});
|
|
@@ -47387,6 +47422,14 @@ var init_quality = __esm(() => {
|
|
|
47387
47422
|
CHILD_ENV_KEYS = ["PATH", "HOME", "LANG", "LC_ALL", "TMPDIR"];
|
|
47388
47423
|
});
|
|
47389
47424
|
|
|
47425
|
+
// src/decisions/types.ts
|
|
47426
|
+
function isHostQuestionEntry(record) {
|
|
47427
|
+
return record !== null && record !== undefined && record.hostQuestion !== undefined && record.hostQuestion !== null;
|
|
47428
|
+
}
|
|
47429
|
+
function hostQuestionLabel(host) {
|
|
47430
|
+
return `answered via host question (${host.source}:${host.ref})`;
|
|
47431
|
+
}
|
|
47432
|
+
|
|
47390
47433
|
// src/decisions/report.ts
|
|
47391
47434
|
function emptyTally() {
|
|
47392
47435
|
return { answered: 0, matched: 0 };
|
|
@@ -47561,6 +47604,22 @@ function buildLegacy(opens, firstOf) {
|
|
|
47561
47604
|
out.matchShare = out.matchTally.answered === 0 ? null : out.matchTally.matched / out.matchTally.answered;
|
|
47562
47605
|
return out;
|
|
47563
47606
|
}
|
|
47607
|
+
function hostQuestionRow(open, answered) {
|
|
47608
|
+
const latest = [...answered].sort((a, b) => a.seq - b.seq).pop();
|
|
47609
|
+
const host = open.hostQuestion;
|
|
47610
|
+
return {
|
|
47611
|
+
id: open.id,
|
|
47612
|
+
label: hostQuestionLabel({ source: host?.source ?? "", ref: host?.ref ?? "" }),
|
|
47613
|
+
verb: host?.verb ?? "",
|
|
47614
|
+
question: open.question,
|
|
47615
|
+
options: open.options.map((option) => ({ id: option.id, label: option.label })),
|
|
47616
|
+
choice: latest?.choice ?? "",
|
|
47617
|
+
answerText: host?.answerText ?? "",
|
|
47618
|
+
source: host?.source ?? "",
|
|
47619
|
+
ref: host?.ref ?? "",
|
|
47620
|
+
at: open.at
|
|
47621
|
+
};
|
|
47622
|
+
}
|
|
47564
47623
|
function buildReport2(records, skipped = 0, options = {}) {
|
|
47565
47624
|
const opens = [];
|
|
47566
47625
|
const answers = new Map;
|
|
@@ -47582,7 +47641,9 @@ function buildReport2(records, skipped = 0, options = {}) {
|
|
|
47582
47641
|
}
|
|
47583
47642
|
const excludeLegacy = options.excludeLegacy === true;
|
|
47584
47643
|
const settings = options.settings ?? defaultSettings();
|
|
47585
|
-
const
|
|
47644
|
+
const hostOpens = opens.filter((open) => isHostQuestionEntry(open));
|
|
47645
|
+
const drawn = opens.filter((open) => !isHostQuestionEntry(open));
|
|
47646
|
+
const kept = excludeLegacy ? drawn.filter((open) => open.backfilled !== true && !isLegacy(open)) : drawn;
|
|
47586
47647
|
const live = kept.filter((open) => open.backfilled !== true);
|
|
47587
47648
|
const history = kept.filter((open) => open.backfilled === true);
|
|
47588
47649
|
const firstAnswer = (id) => [...answers.get(id) ?? []].sort((a, b) => a.seq - b.seq)[0];
|
|
@@ -47621,6 +47682,7 @@ function buildReport2(records, skipped = 0, options = {}) {
|
|
|
47621
47682
|
excludeLegacy,
|
|
47622
47683
|
quality: buildQualityMatrix(options.quality ?? [], known),
|
|
47623
47684
|
reasons: buildReasons(compared, headline, firstAnswer, reasons),
|
|
47685
|
+
hostQuestions: hostOpens.map((open) => hostQuestionRow(open, answers.get(open.id) ?? [])),
|
|
47624
47686
|
ineligible: {
|
|
47625
47687
|
decisions: randomized.filter((open) => !recordEligible(open)).length,
|
|
47626
47688
|
answered: randomized.filter((open) => !recordEligible(open) && firstAnswer(open.id) !== undefined).length
|
|
@@ -47801,15 +47863,40 @@ function renderLegacy(report) {
|
|
|
47801
47863
|
function seconds(ms) {
|
|
47802
47864
|
return `${(ms / 1000).toFixed(1)}s`;
|
|
47803
47865
|
}
|
|
47866
|
+
function renderHostQuestions(report) {
|
|
47867
|
+
const rows = report.hostQuestions ?? [];
|
|
47868
|
+
if (rows.length === 0)
|
|
47869
|
+
return [];
|
|
47870
|
+
const lines = [`Host questions (asked through the host's own tool, recorded by a keryx review verb; counted apart from the decisions above, in no arm or match number): ${rows.length}`];
|
|
47871
|
+
for (const row of rows) {
|
|
47872
|
+
lines.push(` ${oneLine(row.id)} [host-question, ${oneLine(row.label)}, ${oneLine(row.verb)}, ${oneLine(row.at)}]`);
|
|
47873
|
+
lines.push(` ${oneLine(row.question, FULL_TEXT)}`);
|
|
47874
|
+
lines.push(` options: ${row.options.map((option) => oneLine(option.id)).join(", ")}; chose ${row.choice === "" ? "(not answered)" : oneLine(row.choice)}`);
|
|
47875
|
+
lines.push(` answer: ${oneLine(row.answerText, FULL_TEXT)}`);
|
|
47876
|
+
}
|
|
47877
|
+
return lines;
|
|
47878
|
+
}
|
|
47804
47879
|
function renderReport(report) {
|
|
47805
47880
|
const skippedLine = report.skipped > 0 ? `Skipped ${report.skipped} unreadable journal record${report.skipped === 1 ? "" : "s"}.` : undefined;
|
|
47806
47881
|
const configLines = report.settings.invalid.length > 0 ? renderSettings(report.settings) : [];
|
|
47882
|
+
const hostLines = renderHostQuestions(report);
|
|
47807
47883
|
if (report.total === 0 && report.backfilled.total === 0) {
|
|
47884
|
+
if (hostLines.length > 0) {
|
|
47885
|
+
const lines = ["No live decisions recorded yet.", "", ...hostLines];
|
|
47886
|
+
if (configLines.length > 0)
|
|
47887
|
+
lines.push("", ...configLines);
|
|
47888
|
+
if (skippedLine !== undefined)
|
|
47889
|
+
lines.push("", skippedLine);
|
|
47890
|
+
return lines.join(`
|
|
47891
|
+
`);
|
|
47892
|
+
}
|
|
47808
47893
|
return ["No decisions recorded yet.", ...configLines, ...skippedLine === undefined ? [] : [skippedLine]].join(`
|
|
47809
47894
|
`);
|
|
47810
47895
|
}
|
|
47811
47896
|
if (report.total === 0) {
|
|
47812
47897
|
const lines = ["No live decisions recorded yet.", "", ...renderBackfilled(report.backfilled)];
|
|
47898
|
+
if (hostLines.length > 0)
|
|
47899
|
+
lines.push("", ...hostLines);
|
|
47813
47900
|
if (configLines.length > 0)
|
|
47814
47901
|
lines.push("", ...configLines);
|
|
47815
47902
|
if (skippedLine !== undefined)
|
|
@@ -47874,6 +47961,8 @@ function renderReport(report) {
|
|
|
47874
47961
|
if (report.inferredFlow > 0) {
|
|
47875
47962
|
lines.push("", `Flow attribution inferred (the one flow in progress, not named by KERYX_FLOW or the branch): ${report.inferredFlow} decision${report.inferredFlow === 1 ? "" : "s"}.`);
|
|
47876
47963
|
}
|
|
47964
|
+
if (hostLines.length > 0)
|
|
47965
|
+
lines.push("", ...hostLines);
|
|
47877
47966
|
if (report.backfilled.total > 0)
|
|
47878
47967
|
lines.push("", ...renderBackfilled(report.backfilled));
|
|
47879
47968
|
if (skippedLine !== undefined)
|
|
@@ -47912,7 +48001,8 @@ function toUtcIso(value) {
|
|
|
47912
48001
|
return ISO_OFFSET.test(value) ? new Date(at).toISOString() : null;
|
|
47913
48002
|
}
|
|
47914
48003
|
function exportSummaryLine(summary) {
|
|
47915
|
-
|
|
48004
|
+
const line = `Export: ${summary.rows} decision${summary.rows === 1 ? "" : "s"}, ${summary.skipped} skipped, ${summary.blanked} invalid field${summary.blanked === 1 ? "" : "s"} blanked.`;
|
|
48005
|
+
return summary.hostQuestions > 0 ? `${line} ${summary.hostQuestions} answered via host question, not exported as rows.` : line;
|
|
47916
48006
|
}
|
|
47917
48007
|
|
|
47918
48008
|
class Validator {
|
|
@@ -47996,7 +48086,14 @@ function buildExportWithSummary(records, quality = [], options = {}) {
|
|
|
47996
48086
|
}
|
|
47997
48087
|
}
|
|
47998
48088
|
const rows = [];
|
|
48089
|
+
let hostQuestions = 0;
|
|
47999
48090
|
for (const open of opens) {
|
|
48091
|
+
if (isHostQuestionEntry(open)) {
|
|
48092
|
+
const at = toUtcIso(open.at);
|
|
48093
|
+
if (options.since === undefined || at !== null && Date.parse(at) >= options.since.getTime())
|
|
48094
|
+
hostQuestions += 1;
|
|
48095
|
+
continue;
|
|
48096
|
+
}
|
|
48000
48097
|
const arm = exportArm(open);
|
|
48001
48098
|
if (arm === null || !Array.isArray(open.options) || !Array.isArray(open.order)) {
|
|
48002
48099
|
check.skipped += 1;
|
|
@@ -48041,7 +48138,7 @@ function buildExportWithSummary(records, quality = [], options = {}) {
|
|
|
48041
48138
|
ratings: ratingsOf(open.id, quality, check)
|
|
48042
48139
|
});
|
|
48043
48140
|
}
|
|
48044
|
-
return { rows, summary: { rows: rows.length, skipped: check.skipped, blanked: check.blanked } };
|
|
48141
|
+
return { rows, summary: { rows: rows.length, skipped: check.skipped, blanked: check.blanked, hostQuestions } };
|
|
48045
48142
|
}
|
|
48046
48143
|
function ratingsOf(id, quality, check) {
|
|
48047
48144
|
const out = [];
|
|
@@ -48467,7 +48564,7 @@ async function latestAnsweredDecision(cwd, flow) {
|
|
|
48467
48564
|
const answered = new Set(records.filter((r) => r.kind === "answer").map((r) => r.id));
|
|
48468
48565
|
for (let i = records.length - 1;i >= 0; i -= 1) {
|
|
48469
48566
|
const record = records[i];
|
|
48470
|
-
if (record !== undefined && record.kind === "open" && record.backfilled !== true && answered.has(record.id) && (flow === undefined || record.flow === flow))
|
|
48567
|
+
if (record !== undefined && record.kind === "open" && record.backfilled !== true && !isHostQuestionEntry(record) && answered.has(record.id) && (flow === undefined || record.flow === flow))
|
|
48471
48568
|
return record.id;
|
|
48472
48569
|
}
|
|
48473
48570
|
return;
|
|
@@ -48553,6 +48650,9 @@ async function reportLine(cwd) {
|
|
|
48553
48650
|
async function decisionCount(cwd) {
|
|
48554
48651
|
return (await loadReport(cwd)).total;
|
|
48555
48652
|
}
|
|
48653
|
+
async function hostQuestionCount(cwd) {
|
|
48654
|
+
return (await loadReport(cwd)).hostQuestions?.length ?? 0;
|
|
48655
|
+
}
|
|
48556
48656
|
async function annotatedCount(cwd) {
|
|
48557
48657
|
return (await loadReport(cwd)).annotated.length;
|
|
48558
48658
|
}
|
|
@@ -48926,17 +49026,20 @@ function validPremiseSubject(value) {
|
|
|
48926
49026
|
const subject = value;
|
|
48927
49027
|
return text(subject.severity) && text(subject.verdict) && text(subject.problemSha256);
|
|
48928
49028
|
}
|
|
49029
|
+
function viaConsistent(d) {
|
|
49030
|
+
return d.confirmedVia === "host-question" ? isHostQuestion(d.hostQuestion) : true;
|
|
49031
|
+
}
|
|
48929
49032
|
function validPremise(entry) {
|
|
48930
49033
|
if (entry === null || typeof entry !== "object")
|
|
48931
49034
|
return false;
|
|
48932
49035
|
const d = entry;
|
|
48933
|
-
return d.kind === PREMISE_DECISION_KIND && text(d.finding) && (d.answer === "uphold" || d.answer === "dismiss") && text(d.reason) && text(d.quote) && text(d.at) && text(d.actor) && VIAS.includes(d.confirmedVia) && validPremiseSubject(d.subject);
|
|
49036
|
+
return d.kind === PREMISE_DECISION_KIND && text(d.finding) && (d.answer === "uphold" || d.answer === "dismiss") && text(d.reason) && text(d.quote) && text(d.at) && text(d.actor) && VIAS.includes(d.confirmedVia) && viaConsistent(d) && validPremiseSubject(d.subject);
|
|
48934
49037
|
}
|
|
48935
49038
|
function validGateDecision(entry) {
|
|
48936
49039
|
if (entry === null || typeof entry !== "object")
|
|
48937
49040
|
return false;
|
|
48938
49041
|
const d = entry;
|
|
48939
|
-
return OPERATOR_DECISION_KINDS.includes(d.kind) && text(d.quote) && text(d.reason) && VIAS.includes(d.confirmedVia) && text(d.at) && (d.subject === undefined || validSubject(d.subject));
|
|
49042
|
+
return OPERATOR_DECISION_KINDS.includes(d.kind) && text(d.quote) && text(d.reason) && VIAS.includes(d.confirmedVia) && viaConsistent(d) && text(d.at) && (d.subject === undefined || validSubject(d.subject));
|
|
48940
49043
|
}
|
|
48941
49044
|
function validDecision(entry) {
|
|
48942
49045
|
return validGateDecision(entry) || validPremise(entry);
|
|
@@ -48993,8 +49096,10 @@ async function recordPremiseDecisionAfter(packageDir, input, applyFirst) {
|
|
|
48993
49096
|
at: input.at,
|
|
48994
49097
|
actor: PREMISE_DECISION_ACTOR,
|
|
48995
49098
|
confirmedVia: input.confirmedVia,
|
|
49099
|
+
...cardPressFields(input),
|
|
48996
49100
|
subject: input.subject,
|
|
48997
|
-
...input.carriedFrom === undefined ? {} : { carriedFrom: input.carriedFrom }
|
|
49101
|
+
...input.carriedFrom === undefined ? {} : { carriedFrom: input.carriedFrom },
|
|
49102
|
+
...hostQuestionFields(input)
|
|
48998
49103
|
};
|
|
48999
49104
|
if (!validPremise(kept)) {
|
|
49000
49105
|
throw new Error("a premise decision needs the finding, an answer of uphold or dismiss, a reason, the operator's words, the time, how it was confirmed and the finding it was shown");
|
|
@@ -49032,22 +49137,23 @@ async function recordOperatorDecision(packageDir, decision) {
|
|
|
49032
49137
|
if (!validGateDecision(decision))
|
|
49033
49138
|
throw new Error("an operator decision needs a kind, the operator's words, a reason, how it was confirmed and the time");
|
|
49034
49139
|
const subject = decision.subject === undefined ? undefined : { ...decision.subject.head === undefined ? {} : { head: decision.subject.head }, items: [...new Set(decision.subject.items)].sort() };
|
|
49035
|
-
const entry = { ...decision, quote: decision.quote.trim(), reason: decision.reason.trim(), ...subject === undefined ? {} : { subject } };
|
|
49140
|
+
const entry = { ...decision, quote: decision.quote.trim(), reason: decision.reason.trim(), ...cardPressFields(decision), ...subject === undefined ? {} : { subject } };
|
|
49036
49141
|
await withOperatorDecisionsLock(packageDir, () => appendDecision(packageDir, entry));
|
|
49037
49142
|
return decision;
|
|
49038
49143
|
}
|
|
49039
49144
|
function decisionWriters(kind, packageArg = "<review-dir>") {
|
|
49040
49145
|
if (kind === PREMISE_DECISION_KIND) {
|
|
49041
|
-
return `in keryx shell, call the review_decide_premise tool (the operator answers a card); in a terminal, run \`keryx review decide-premise --package ${packageArg} --finding <id> --answer uphold|dismiss --reason "<why>"
|
|
49146
|
+
return `in keryx shell, call the review_decide_premise tool (the operator answers a card); elsewhere ask the operator with the host's question tool, or in a terminal, run \`keryx review decide-premise --package ${packageArg} --finding <id> --answer uphold|dismiss --reason "<why>"\` (from a host question add --answered-by <source>:<ref> --answer-text "<their words>" --question-text "<the question>")`;
|
|
49042
49147
|
}
|
|
49043
|
-
return kind === "mutation-waiver" ? `in keryx shell, call the review_waive_mutation tool (the operator answers a card); in a terminal, run \`keryx review waive-mutation --package ${packageArg} --reason "<why>"
|
|
49148
|
+
return kind === "mutation-waiver" ? `in keryx shell, call the review_waive_mutation tool (the operator answers a card); elsewhere ask the operator with the host's question tool, or in a terminal, run \`keryx review waive-mutation --package ${packageArg} --reason "<why>"\` (from a host question add --answered-by <source>:<ref> --answer-text "<their words>" --question-text "<the question>")` : `in keryx shell, call the review_override_reviewers tool (the operator answers a card); elsewhere ask the operator with the host's question tool, or in a terminal, run \`keryx review override-reviewers --package ${packageArg} --reason "<why>"\` (from a host question add --answered-by <source>:<ref> --answer-text "<their words>" --question-text "<the question>")`;
|
|
49044
49149
|
}
|
|
49045
49150
|
var OPERATOR_DECISIONS_FILE = "operator-decisions.json", OPERATOR_DECISIONS_LOCK = ".operator-decisions.lock", OPERATOR_DECISION_KINDS, PREMISE_DECISION_KIND = "premise-question", VIAS, PREMISE_DECISION_ACTOR = "operator", text = (value) => typeof value === "string" && value.trim().length > 0;
|
|
49046
49151
|
var init_operator_decisions = __esm(() => {
|
|
49047
49152
|
init_fs();
|
|
49048
49153
|
init_json();
|
|
49154
|
+
init_types4();
|
|
49049
49155
|
OPERATOR_DECISION_KINDS = ["mutation-waiver", "reviewer-override"];
|
|
49050
|
-
VIAS = ["tui-card", "remote-card", "terminal"];
|
|
49156
|
+
VIAS = ["tui-card", "remote-card", "terminal", "host-question"];
|
|
49051
49157
|
});
|
|
49052
49158
|
|
|
49053
49159
|
// src/review/mutate.ts
|
|
@@ -51490,14 +51596,14 @@ function recordChunkEstimate(packageDir, estimate, mode) {
|
|
|
51490
51596
|
return record;
|
|
51491
51597
|
});
|
|
51492
51598
|
}
|
|
51493
|
-
async function answerChunkEstimate(packageDir, value, quote, now = new Date, confirmedVia) {
|
|
51599
|
+
async function answerChunkEstimate(packageDir, value, quote, now = new Date, confirmedVia, press) {
|
|
51494
51600
|
if (quote.trim() === "")
|
|
51495
51601
|
throw new Error("The answer needs the operator's own words (--quote).");
|
|
51496
51602
|
return withChunkStateLock(packageDir, async () => {
|
|
51497
51603
|
const previous = await readChunkEstimate(packageDir);
|
|
51498
51604
|
if (previous === undefined)
|
|
51499
51605
|
throw new Error("No chunk estimate is recorded; run `keryx review chunks estimate` first.");
|
|
51500
|
-
const record = { ...previous, asked: true, answer: { value, quote: quote.trim(), at: now.toISOString(), ...confirmedVia === undefined ? {} : { confirmedVia } } };
|
|
51606
|
+
const record = { ...previous, asked: true, answer: { value, quote: quote.trim(), at: now.toISOString(), ...confirmedVia === undefined ? {} : { confirmedVia }, ...cardPressFields(press) } };
|
|
51501
51607
|
await writeFileAtomic(path117.join(packageDir, CHUNK_ESTIMATE_FILE), `${JSON.stringify(record, null, 2)}
|
|
51502
51608
|
`);
|
|
51503
51609
|
return record;
|
|
@@ -51543,6 +51649,7 @@ var init_chunks_estimate = __esm(() => {
|
|
|
51543
51649
|
init_chunks();
|
|
51544
51650
|
init_blast_radius();
|
|
51545
51651
|
init_json();
|
|
51652
|
+
init_types4();
|
|
51546
51653
|
DEFAULT_CHUNK_LIMITS = { maxPasses: DEFAULT_MAX_PASSES, inputUsdPerMTok: 3, outputUsdPerMTok: 15, secondsPerPass: 150, source: "default" };
|
|
51547
51654
|
});
|
|
51548
51655
|
|
|
@@ -68822,7 +68929,7 @@ async function collectProjectRules(projectRoot, warnings) {
|
|
|
68822
68929
|
function selectRules(candidates, files, taskType, bundled) {
|
|
68823
68930
|
const selected = [];
|
|
68824
68931
|
for (const candidate of candidates) {
|
|
68825
|
-
const shadowsPath = candidate.refs.map((ref) => bundled.get(ref)).find((found) => found !== undefined);
|
|
68932
|
+
const shadowsPath = candidate.refs.map((ref) => bundled.get(ref) ?? bundled.get(withoutCorePrefix(ref))).find((found) => found !== undefined);
|
|
68826
68933
|
const matching = candidate.front.globs.filter((glob) => files.some((file) => matchesAnyGlobWithBraces(file, [glob])));
|
|
68827
68934
|
const base = { candidate, ...shadowsPath !== undefined ? { shadows: shadowsPath } : {} };
|
|
68828
68935
|
if (matching.length > 0) {
|
|
@@ -69095,7 +69202,7 @@ var init_context_resolver = __esm(() => {
|
|
|
69095
69202
|
service_api: ["code-style-patterns", "nestjs-dto", "api-contracts"]
|
|
69096
69203
|
};
|
|
69097
69204
|
TEST_RULES = ["tdd-workflow", "testing-conventions"];
|
|
69098
|
-
CITATION = /`([^`\s]+\.mdc?)
|
|
69205
|
+
CITATION = /`([^`\s#]+\.mdc?)(?:#[^`\s]*)?`/g;
|
|
69099
69206
|
});
|
|
69100
69207
|
|
|
69101
69208
|
// src/memory/relevant.ts
|
|
@@ -73358,7 +73465,9 @@ function readCoverageWaiver(dispatch) {
|
|
|
73358
73465
|
return;
|
|
73359
73466
|
const files = Array.isArray(waiver.files) ? waiver.files.filter(text2) : [];
|
|
73360
73467
|
const via = waiver.confirmedVia === "tui-card" || waiver.confirmedVia === "remote-card" ? waiver.confirmedVia : undefined;
|
|
73361
|
-
|
|
73468
|
+
const pressedBy = typeof waiver.pressedBy === "number" && Number.isSafeInteger(waiver.pressedBy) ? waiver.pressedBy : undefined;
|
|
73469
|
+
const pressedAt = text2(waiver.pressedAt) ? waiver.pressedAt : undefined;
|
|
73470
|
+
return { operatorQuote: waiver.operatorQuote, time: waiver.time, files, ...via === undefined ? {} : { confirmedVia: via }, ...cardPressFields({ pressedBy, pressedAt }) };
|
|
73362
73471
|
}
|
|
73363
73472
|
async function computeCoverage(packageDir, options = {}) {
|
|
73364
73473
|
const research = options.research ?? await readJson4(path172.join(packageDir, "research.json"));
|
|
@@ -73458,7 +73567,7 @@ function coverageLine(report) {
|
|
|
73458
73567
|
const { inScope, covered, omitted, uncovered } = report.counts;
|
|
73459
73568
|
return `coverage: in scope ${inScope}, covered ${covered}, omitted ${omitted}${reasons === "" ? "" : ` (${reasons})`}, uncovered ${uncovered}`;
|
|
73460
73569
|
}
|
|
73461
|
-
async function recordCoverageWaiver(packageDir, operatorQuote, now = new Date, confirmedVia) {
|
|
73570
|
+
async function recordCoverageWaiver(packageDir, operatorQuote, now = new Date, confirmedVia, press) {
|
|
73462
73571
|
const quote = operatorQuote.replace(/-{0,2}waive[- ]?coverage/gi, "").trim();
|
|
73463
73572
|
if (quote.length < MIN_WAIVER_QUOTE_CHARS || !/[\p{L}\p{N}]/u.test(quote)) {
|
|
73464
73573
|
throw new Error(`--waive-coverage needs the operator's own words as its value: at least ${MIN_WAIVER_QUOTE_CHARS} characters, with letters or digits in them.`);
|
|
@@ -73472,7 +73581,7 @@ async function recordCoverageWaiver(packageDir, operatorQuote, now = new Date, c
|
|
|
73472
73581
|
const uncovered = report.files.filter((entry) => entry.status === "uncovered").map((entry) => entry.file);
|
|
73473
73582
|
if (!report.tracked || uncovered.length === 0)
|
|
73474
73583
|
return;
|
|
73475
|
-
const waiver = { operatorQuote: quote, time: now.toISOString(), files: uncovered, ...confirmedVia === undefined ? {} : { confirmedVia } };
|
|
73584
|
+
const waiver = { operatorQuote: quote, time: now.toISOString(), files: uncovered, ...confirmedVia === undefined ? {} : { confirmedVia }, ...cardPressFields(press) };
|
|
73476
73585
|
await writeFileAtomic(file, `${JSON.stringify({ ...research, dispatch: { ...dispatch, coverageWaiver: waiver } }, null, 2)}
|
|
73477
73586
|
`);
|
|
73478
73587
|
return waiver;
|
|
@@ -73503,6 +73612,7 @@ var init_coverage_complete = __esm(() => {
|
|
|
73503
73612
|
init_chunks();
|
|
73504
73613
|
init_blast_radius();
|
|
73505
73614
|
init_coverage();
|
|
73615
|
+
init_types4();
|
|
73506
73616
|
init_json();
|
|
73507
73617
|
});
|
|
73508
73618
|
|
|
@@ -74391,7 +74501,8 @@ function applyPremiseDecision(finding, decision) {
|
|
|
74391
74501
|
quote: decision.quote,
|
|
74392
74502
|
at: decision.at,
|
|
74393
74503
|
actor: decision.actor,
|
|
74394
|
-
confirmedVia: decision.confirmedVia
|
|
74504
|
+
confirmedVia: decision.confirmedVia,
|
|
74505
|
+
...hostQuestionFields(decision)
|
|
74395
74506
|
};
|
|
74396
74507
|
const { disposition: existing, ...rest } = record;
|
|
74397
74508
|
const current = isRecord9(existing) ? existing : undefined;
|
|
@@ -74400,7 +74511,7 @@ function applyPremiseDecision(finding, decision) {
|
|
|
74400
74511
|
return {
|
|
74401
74512
|
...ownDismissal ? rest : record,
|
|
74402
74513
|
premise: { ...premise, operator },
|
|
74403
|
-
...decision.answer === "dismiss" && open ? { disposition: { state: "dismissed-incorrect", evidence: `operator (${decision.confirmedVia}, ${decision.at}) on the premise question: ${decision.reason}` } } : {}
|
|
74514
|
+
...decision.answer === "dismiss" && open ? { disposition: { state: "dismissed-incorrect", evidence: `operator (${decision.confirmedVia}${decision.hostQuestion === undefined ? "" : ` ${decision.hostQuestion.source}:${decision.hostQuestion.ref}`}, ${decision.at}) on the premise question: ${decision.reason}` } } : {}
|
|
74404
74515
|
};
|
|
74405
74516
|
}
|
|
74406
74517
|
function applyPremiseDecisions(findings, decisions) {
|
|
@@ -77566,7 +77677,9 @@ function flowStateSchema() {
|
|
|
77566
77677
|
description: "The completion target the token was minted for: `pr:<url>` or `merged`."
|
|
77567
77678
|
}
|
|
77568
77679
|
}
|
|
77569
|
-
}
|
|
77680
|
+
},
|
|
77681
|
+
pressedBy: { type: "integer", description: "Flow 464: on a remote-card token, the Telegram user id that pressed the card." },
|
|
77682
|
+
pressedAt: { type: "string", description: "Flow 464: on a remote-card token, the shell's clock when the press arrived." }
|
|
77570
77683
|
}
|
|
77571
77684
|
}
|
|
77572
77685
|
}
|
|
@@ -77828,7 +77941,9 @@ async function mintConfirmationToken(cwd, dir, binding, deps) {
|
|
|
77828
77941
|
mintedAt: mintedAt.toISOString(),
|
|
77829
77942
|
expiresAt: new Date(mintedAt.getTime() + CONFIRMATION_TOKEN_TTL_MS).toISOString(),
|
|
77830
77943
|
...binding.confirmedVia !== undefined ? { confirmedVia: binding.confirmedVia } : {},
|
|
77831
|
-
...binding.confirmationQuote !== undefined ? { confirmationQuote: binding.confirmationQuote } : {}
|
|
77944
|
+
...binding.confirmationQuote !== undefined ? { confirmationQuote: binding.confirmationQuote } : {},
|
|
77945
|
+
...binding.pressedBy !== undefined ? { pressedBy: binding.pressedBy } : {},
|
|
77946
|
+
...binding.pressedAt !== undefined ? { pressedAt: binding.pressedAt } : {}
|
|
77832
77947
|
};
|
|
77833
77948
|
await writeFileAtomic(confirmTokenPath2(cwd, dir), `${JSON.stringify(stored, null, 2)}
|
|
77834
77949
|
`);
|
|
@@ -77886,7 +78001,9 @@ async function consumeConfirmationToken(cwd, dir, stored, now) {
|
|
|
77886
78001
|
tokenRef: stored.tokenRef,
|
|
77887
78002
|
mintedAt: stored.mintedAt,
|
|
77888
78003
|
consumedAt,
|
|
77889
|
-
boundTo: { kind: stored.kind, acChecksum: stored.acChecksum, target: stored.target }
|
|
78004
|
+
boundTo: { kind: stored.kind, acChecksum: stored.acChecksum, target: stored.target },
|
|
78005
|
+
...stored.pressedBy !== undefined ? { pressedBy: stored.pressedBy } : {},
|
|
78006
|
+
...stored.pressedAt !== undefined ? { pressedAt: stored.pressedAt } : {}
|
|
77890
78007
|
};
|
|
77891
78008
|
}
|
|
77892
78009
|
function describeConfirmationFailure(reason, flowId) {
|
|
@@ -80494,7 +80611,12 @@ function createFlowService(deps) {
|
|
|
80494
80611
|
flowId: flow.id,
|
|
80495
80612
|
acChecksum: flow.acChecksum,
|
|
80496
80613
|
target: completionTarget({ merged: merged === true, prUrl: flow.pr.url }),
|
|
80497
|
-
...confirmation !== undefined ? {
|
|
80614
|
+
...confirmation !== undefined ? {
|
|
80615
|
+
confirmedVia: confirmation.confirmedVia,
|
|
80616
|
+
confirmationQuote: confirmation.quote,
|
|
80617
|
+
...confirmation.pressedBy !== undefined ? { pressedBy: confirmation.pressedBy } : {},
|
|
80618
|
+
...confirmation.pressedAt !== undefined ? { pressedAt: confirmation.pressedAt } : {}
|
|
80619
|
+
} : {}
|
|
80498
80620
|
}, { now: deps.now });
|
|
80499
80621
|
flow = await save(cwd, dir, flow, "confirmation-minted", `token ${stored.tokenRef}, expires ${stored.expiresAt}`);
|
|
80500
80622
|
return {
|
|
@@ -82077,10 +82199,43 @@ var init_patch_risk = __esm(() => {
|
|
|
82077
82199
|
NEW_FILE_HEADER = /^\+\+\+ (.+)$/;
|
|
82078
82200
|
});
|
|
82079
82201
|
|
|
82202
|
+
// src/review/host-question-verbs.ts
|
|
82203
|
+
var HOST_QUESTION_VERBS;
|
|
82204
|
+
var init_host_question_verbs = __esm(() => {
|
|
82205
|
+
HOST_QUESTION_VERBS = ["waive-mutation", "override-reviewers", "confirm-plan", "decide-premise"];
|
|
82206
|
+
});
|
|
82207
|
+
|
|
82080
82208
|
// src/trigger/unattended.ts
|
|
82081
82209
|
function isUnattendedExcludedTool(toolName) {
|
|
82082
82210
|
return UNATTENDED_EXCLUDED_TOOLS.includes(toolName) || OPERATOR_CONFIRM_TOOL_NAMES.includes(toolName);
|
|
82083
82211
|
}
|
|
82212
|
+
function recordsHostQuestion(text, depth = 0) {
|
|
82213
|
+
const joined = text.replace(/\\\r?\n/g, "").replace(/\$(?=["'])/g, "");
|
|
82214
|
+
const segments = splitSegments(joined);
|
|
82215
|
+
const feedsShell = segments.some((s) => s.precededBy === "|" && SHELL_WRAPPERS.has(commandWord(s.words)));
|
|
82216
|
+
const inner = (words) => depth < 3 && words.some((w) => /\s/.test(w) && recordsHostQuestion(w, depth + 1));
|
|
82217
|
+
for (const segment of segments) {
|
|
82218
|
+
const words = stripAssignments(segment.words).map((w) => w.replace(/\\/g, "").replace(/^[$(`{]+/, ""));
|
|
82219
|
+
const cmd = commandWord(words);
|
|
82220
|
+
if (PRINTING_COMMANDS.has(cmd)) {
|
|
82221
|
+
if (feedsShell && inner(words))
|
|
82222
|
+
return true;
|
|
82223
|
+
continue;
|
|
82224
|
+
}
|
|
82225
|
+
if (SHELL_WRAPPERS.has(cmd) && inner(words))
|
|
82226
|
+
return true;
|
|
82227
|
+
const reviewAt = words.findIndex((w, i) => w === "review" && words.slice(0, i).some((p) => KERYX_ENTRY.test(p)));
|
|
82228
|
+
if (reviewAt < 0)
|
|
82229
|
+
continue;
|
|
82230
|
+
const args = words.slice(reviewAt + 1);
|
|
82231
|
+
const verbAt = args.findIndex((w) => !w.startsWith("-"));
|
|
82232
|
+
if (verbAt < 0 || !HOST_QUESTION_VERB_SET.has(args[verbAt]))
|
|
82233
|
+
continue;
|
|
82234
|
+
if (args.slice(verbAt + 1).some((w) => w === "--answered-by" || w.startsWith("--answered-by=")))
|
|
82235
|
+
return true;
|
|
82236
|
+
}
|
|
82237
|
+
return false;
|
|
82238
|
+
}
|
|
82084
82239
|
function isUnattendedProtectedPath(p) {
|
|
82085
82240
|
const normalized = p.replace(/\\/g, "/").trim();
|
|
82086
82241
|
return /(^|\/)flow\.json$/.test(normalized) || /(^|\/)acceptance-criteria\.md$/.test(normalized) || /(^|\/)\.metaproject\/triggers\.json$/.test(normalized) || /(^|\/)\.metaproject\/data\/trigger(\/|$)/.test(normalized) || /confirm-token\.json$/.test(normalized);
|
|
@@ -82089,6 +82244,9 @@ function unattendedShellRefusal(command) {
|
|
|
82089
82244
|
const text = command.trim();
|
|
82090
82245
|
if (text.length === 0)
|
|
82091
82246
|
return;
|
|
82247
|
+
if (recordsHostQuestion(text)) {
|
|
82248
|
+
return "`--answered-by` records the operator's answer to a question the host asked \u2014 an unattended run has no operator to ask, so it never records one";
|
|
82249
|
+
}
|
|
82092
82250
|
for (const pattern of FORBIDDEN_FLOW_VERBS) {
|
|
82093
82251
|
const match = pattern.exec(text);
|
|
82094
82252
|
if (match !== null) {
|
|
@@ -82163,9 +82321,10 @@ function unattendedRefusal(toolName, input) {
|
|
|
82163
82321
|
}
|
|
82164
82322
|
return;
|
|
82165
82323
|
}
|
|
82166
|
-
var UNATTENDED_EXCLUDED_TOOLS, OPERATOR_CONFIRM_TOOL_NAMES, FORBIDDEN_FLOW_VERBS, FORBIDDEN_GIT_VERBS, FORBIDDEN_GIT_BRANCH_FLAGS, GH_API_BODY_FLAGS, RAW_GIT, RAW_PUBLISH, PROTECTED_TEXT_MARKERS;
|
|
82324
|
+
var UNATTENDED_EXCLUDED_TOOLS, OPERATOR_CONFIRM_TOOL_NAMES, FORBIDDEN_FLOW_VERBS, HOST_QUESTION_VERB_SET, KERYX_ENTRY, SHELL_WRAPPERS, PRINTING_COMMANDS, FORBIDDEN_GIT_VERBS, FORBIDDEN_GIT_BRANCH_FLAGS, GH_API_BODY_FLAGS, RAW_GIT, RAW_PUBLISH, PROTECTED_TEXT_MARKERS;
|
|
82167
82325
|
var init_unattended = __esm(() => {
|
|
82168
82326
|
init_patch_risk();
|
|
82327
|
+
init_host_question_verbs();
|
|
82169
82328
|
init_shell_syntax();
|
|
82170
82329
|
init_command_risk();
|
|
82171
82330
|
UNATTENDED_EXCLUDED_TOOLS = [
|
|
@@ -82187,6 +82346,7 @@ var init_unattended = __esm(() => {
|
|
|
82187
82346
|
"review_comments_reply",
|
|
82188
82347
|
"review_confirm_plan",
|
|
82189
82348
|
"review_decide_premise",
|
|
82349
|
+
"review_complete",
|
|
82190
82350
|
"flow_confirm",
|
|
82191
82351
|
"external_apply",
|
|
82192
82352
|
"learn_accept",
|
|
@@ -82216,6 +82376,10 @@ var init_unattended = __esm(() => {
|
|
|
82216
82376
|
/\blaunchctl\b/,
|
|
82217
82377
|
/\bloginctl\b/
|
|
82218
82378
|
];
|
|
82379
|
+
HOST_QUESTION_VERB_SET = new Set(HOST_QUESTION_VERBS);
|
|
82380
|
+
KERYX_ENTRY = /(?:^|\/)(?:keryx|cli\.[cm]?[jt]s)$/;
|
|
82381
|
+
SHELL_WRAPPERS = new Set(["sh", "bash", "zsh", "dash", "ksh", "eval"]);
|
|
82382
|
+
PRINTING_COMMANDS = new Set(["echo", "printf", "grep", "egrep", "fgrep", "rg", "ag", "cat"]);
|
|
82219
82383
|
FORBIDDEN_GIT_VERBS = new Set(["push", "merge", "tag", "update-ref"]);
|
|
82220
82384
|
FORBIDDEN_GIT_BRANCH_FLAGS = new Set(["-f", "--force", "-D", "-M", "--delete", "-d", "-m", "--move"]);
|
|
82221
82385
|
GH_API_BODY_FLAGS = new Set(["-f", "-F", "--field", "--raw-field", "--input"]);
|
|
@@ -84680,6 +84844,10 @@ var init_schedule_verify = __esm(() => {
|
|
|
84680
84844
|
function cardSafe(text) {
|
|
84681
84845
|
return text.replace(/\u001b\[[0-?]*[ -/]*[@-~]/g, "").replace(/\u001b[\]P^_][\s\S]*?(?:\u0007|\u001b\\)/g, "").replace(/\r\n|\r|\n/g, " \u23CE ").replace(/[\u0000-\u0008\u000b-\u001f\u007f-\u009f\u200e\u200f\u202a-\u202e\u2066-\u2069]/g, "").replace(/\t/g, " ");
|
|
84682
84846
|
}
|
|
84847
|
+
function hiddenInCard(text) {
|
|
84848
|
+
return cardSafe(text) !== text || /[\p{Cf}\p{Zl}\p{Zp}]/u.test(text);
|
|
84849
|
+
}
|
|
84850
|
+
var HIDDEN_IN_CARD = "holds control, escape, newline, bidi or zero-width characters the card would hide or rewrite; send plain single-line text";
|
|
84683
84851
|
|
|
84684
84852
|
// src/trigger/schedules.ts
|
|
84685
84853
|
import { execFile as execFile4 } from "child_process";
|
|
@@ -85180,6 +85348,9 @@ function parseEvidenceLevel(report) {
|
|
|
85180
85348
|
}
|
|
85181
85349
|
return { ok: false, reason: "the evidence level must start with `static only` or `ran`" };
|
|
85182
85350
|
}
|
|
85351
|
+
function evidenceLevelLine(report) {
|
|
85352
|
+
return withoutFences(report).find((line) => LINE.test(line))?.trim();
|
|
85353
|
+
}
|
|
85183
85354
|
var REPORT_FIELD_EVIDENCE_LEVEL = "evidence-level", LINE, SEPARATORS, PLACEHOLDER;
|
|
85184
85355
|
var init_evidence_level = __esm(() => {
|
|
85185
85356
|
LINE = /^\s*(?:[-*+]\s+)?(?:\*\*|__)?evidence level\s*:?\s*(?:\*\*|__)?\s*:?\s*(.*)$/i;
|
|
@@ -87714,13 +87885,19 @@ function premiseDecisionsCheck(ctx) {
|
|
|
87714
87885
|
if (held.length === 0)
|
|
87715
87886
|
return ok("no finding is held by the premise judge");
|
|
87716
87887
|
const awaiting = awaitingOperator(records, decisions);
|
|
87717
|
-
if (awaiting.length === 0)
|
|
87718
|
-
|
|
87888
|
+
if (awaiting.length === 0) {
|
|
87889
|
+
const hostQuestions = held.flatMap((finding) => {
|
|
87890
|
+
const decision = latestPremiseDecision(finding, decisions);
|
|
87891
|
+
return decision?.confirmedVia === "host-question" && decision.hostQuestion !== undefined ? [`${decision.hostQuestion.source}:${decision.hostQuestion.ref}`] : [];
|
|
87892
|
+
});
|
|
87893
|
+
const viaHost = hostQuestions.length === 0 ? "" : `; ${hostQuestions.length} confirmed via host-question (${hostQuestions.join(", ")}), not by a card`;
|
|
87894
|
+
return ok(`${held.length} held finding(s), every one with an operator decision${viaHost}`);
|
|
87895
|
+
}
|
|
87719
87896
|
const evidence = `${awaiting.length} of ${held.length} held finding(s) have no operator decision: ${some(awaiting)}`;
|
|
87720
87897
|
if (!ctx.premise.ingested) {
|
|
87721
87898
|
return bad(`${evidence} (not ingested yet)`, `ingest the round with its premise results first (\`keryx review ingest ... --premises ${ctx.arg}/research-evidence/premises.json\`), then ask the operator to answer each held finding`);
|
|
87722
87899
|
}
|
|
87723
|
-
return bad(evidence, `ask the operator: in keryx shell call the review_decide_premise tool (a card); in a terminal run keryx review decide-premise --package ${ctx.arg} --finding <id> --answer uphold|dismiss --reason "<why>"`);
|
|
87900
|
+
return bad(evidence, `ask the operator: in keryx shell call the review_decide_premise tool (a card); elsewhere ask with the host's question tool, or in a terminal run keryx review decide-premise --package ${ctx.arg} --finding <id> --answer uphold|dismiss --reason "<why>" (from a host question add --answered-by <source>:<ref> --answer-text "<their words>" --question-text "<the question>")`);
|
|
87724
87901
|
}
|
|
87725
87902
|
function quotesLocatedCheck(ctx) {
|
|
87726
87903
|
const flagged = ctx.findings.filter((finding) => asRecord2(finding.quote_unlocated) !== undefined);
|
|
@@ -87995,7 +88172,7 @@ async function waiveReadiness(packageDir, selectors, operatorQuote, options = {}
|
|
|
87995
88172
|
const manifest = JSON.parse(await readFile109(manifestPath, "utf8"));
|
|
87996
88173
|
const time = (options.now ?? new Date).toISOString();
|
|
87997
88174
|
const kept = (manifest.readiness_waivers ?? []).filter((waiver) => !ids.includes(waiver.item));
|
|
87998
|
-
const added = ids.map((item) => ({ item, operator_quote: quote, time, ...options.confirmedVia === undefined ? {} : { confirmedVia: options.confirmedVia } }));
|
|
88175
|
+
const added = ids.map((item) => ({ item, operator_quote: quote, time, ...options.confirmedVia === undefined ? {} : { confirmedVia: options.confirmedVia }, ...cardPressFields(options) }));
|
|
87999
88176
|
const all = [...kept, ...added].sort((a, b) => READINESS_ITEMS.findIndex((i) => i.id === a.item) - READINESS_ITEMS.findIndex((i) => i.id === b.item));
|
|
88000
88177
|
await writeFileAtomic(manifestPath, `${JSON.stringify({ ...manifest, readiness_waivers: all }, null, 2)}
|
|
88001
88178
|
`);
|
|
@@ -89927,6 +90104,18 @@ async function recordDispositions(packageDir, records) {
|
|
|
89927
90104
|
async function recordDispositionsLocked(packageDir, records) {
|
|
89928
90105
|
const findingsPath = path211.join(packageDir, "findings.json");
|
|
89929
90106
|
const findings = JSON.parse(await readFile110(findingsPath, "utf8"));
|
|
90107
|
+
const pending = await resolveDispositions(findings, records);
|
|
90108
|
+
for (const { finding, disposition } of pending) {
|
|
90109
|
+
finding.disposition = disposition;
|
|
90110
|
+
}
|
|
90111
|
+
await writeFileAtomic(findingsPath, `${JSON.stringify(findings, null, 2)}
|
|
90112
|
+
`);
|
|
90113
|
+
}
|
|
90114
|
+
async function checkDispositions(packageDir, records) {
|
|
90115
|
+
const findings = JSON.parse(await readFile110(path211.join(packageDir, "findings.json"), "utf8"));
|
|
90116
|
+
await resolveDispositions(findings, records);
|
|
90117
|
+
}
|
|
90118
|
+
async function resolveDispositions(findings, records) {
|
|
89930
90119
|
const schema = await loadSchema("review-finding");
|
|
89931
90120
|
const dispositionSchema = schema.properties?.["disposition"];
|
|
89932
90121
|
if (!dispositionSchema) {
|
|
@@ -89958,11 +90147,7 @@ async function recordDispositionsLocked(packageDir, records) {
|
|
|
89958
90147
|
}
|
|
89959
90148
|
pending.push({ finding, disposition });
|
|
89960
90149
|
}
|
|
89961
|
-
|
|
89962
|
-
finding.disposition = disposition;
|
|
89963
|
-
}
|
|
89964
|
-
await writeFileAtomic(findingsPath, `${JSON.stringify(findings, null, 2)}
|
|
89965
|
-
`);
|
|
90150
|
+
return pending;
|
|
89966
90151
|
}
|
|
89967
90152
|
function embeddedFence(tag) {
|
|
89968
90153
|
return new RegExp("^ {0,3}(`{3,}|~{3,})[^\\n]*\\bkeryx:" + tag + "\\b[^\\n]*$", "gm");
|
|
@@ -91073,22 +91258,25 @@ function sessionStartedAt(history) {
|
|
|
91073
91258
|
}
|
|
91074
91259
|
return Date.now();
|
|
91075
91260
|
}
|
|
91076
|
-
function
|
|
91261
|
+
function closeStep(card) {
|
|
91262
|
+
return card ? "call the `review_complete` card (the operator answers it; you never run `keryx review complete` yourself)" : "run `keryx review complete`";
|
|
91263
|
+
}
|
|
91264
|
+
function nextStep(open, card) {
|
|
91077
91265
|
if (open.packageDir === "") {
|
|
91078
|
-
return
|
|
91266
|
+
return `Next: to continue a review opened in an earlier session, name its package (\`keryx review status <review-id-or-path>\`); otherwise \`keryx review start --pr <n> --report <path>\` opens the package; then dispatch the reviewers per slice, save each result, \`keryx review ledger build <input-dir> --package <review-dir>\`, \`keryx review ingest ... --research <review-dir>/research.json\`, then ${closeStep(card)}.`;
|
|
91079
91267
|
}
|
|
91080
91268
|
if (open.blockers.some((b) => /research|dispatch/.test(b))) {
|
|
91081
|
-
return
|
|
91269
|
+
return `Next: dispatch the reviewers per slice, build the research ledger with \`keryx review ledger build <input-dir> --package <review-dir>\` (it names every gap), \`keryx review ingest ... --research <review-dir>/research.json\`, then ${closeStep(card)}.`;
|
|
91082
91270
|
}
|
|
91083
|
-
return
|
|
91271
|
+
return `Next: write or ingest the missing artifacts (\`keryx review ingest\`), then ${closeStep(card)}.`;
|
|
91084
91272
|
}
|
|
91085
91273
|
function nudgeLine(blocker) {
|
|
91086
91274
|
const flat = blocker.replace(/[\u0000-\u001f\u007f-\u009f\u2028\u2029]+/g, " ").trim();
|
|
91087
91275
|
return flat.length > MAX_NUDGE_BLOCKER_CHARS ? `${flat.slice(0, MAX_NUDGE_BLOCKER_CHARS)}\u2026` : flat;
|
|
91088
91276
|
}
|
|
91089
|
-
function describeBlockers(open) {
|
|
91277
|
+
function describeBlockers(open, card = true) {
|
|
91090
91278
|
if (open.blockers.length === 0)
|
|
91091
|
-
return
|
|
91279
|
+
return `- none listed; ${card ? "call the `review_complete` card" : "run `keryx review complete`"} to close the package`;
|
|
91092
91280
|
const shown = open.blockers.slice(0, MAX_NUDGE_BLOCKERS).map((b) => `- ${nudgeLine(b)}`);
|
|
91093
91281
|
const more = open.blockers.length - shown.length;
|
|
91094
91282
|
return [...shown, ...more > 0 ? [`- \u2026 and ${more} more (\`keryx review complete\` lists them all)`] : []].join(`
|
|
@@ -91098,22 +91286,23 @@ function decideReviewGate(input) {
|
|
|
91098
91286
|
const { open } = input;
|
|
91099
91287
|
if (open === null || !input.runSeen)
|
|
91100
91288
|
return { action: "accept" };
|
|
91289
|
+
const card = input.cardAvailable !== false;
|
|
91101
91290
|
const stalls = input.lastSignature === open.signature ? input.stalls + 1 : 0;
|
|
91102
91291
|
if (input.continues >= MAX_REVIEW_GATE_CONTINUES || stalls >= MAX_REVIEW_GATE_STALLS) {
|
|
91103
91292
|
return {
|
|
91104
91293
|
action: "stop",
|
|
91105
|
-
report: `Review ${open.reviewId} is not complete after ${input.continues} continue(s)` + `${stalls >= MAX_REVIEW_GATE_STALLS ? " and no new artifact in the last " + stalls : ""}. ` +
|
|
91106
|
-
${describeBlockers(open)}`
|
|
91294
|
+
report: `Review ${open.reviewId} is not complete after ${input.continues} continue(s)` + `${stalls >= MAX_REVIEW_GATE_STALLS ? " and no new artifact in the last " + stalls : ""}. ` + `${card ? "The review_complete card" : "`keryx review complete`"} would still be refused on:
|
|
91295
|
+
${describeBlockers(open, card)}`
|
|
91107
91296
|
};
|
|
91108
91297
|
}
|
|
91109
91298
|
return {
|
|
91110
91299
|
action: "continue",
|
|
91111
91300
|
stalls,
|
|
91112
91301
|
signature: open.signature,
|
|
91113
|
-
message: `The managed review ${open.reviewId} is not finished: it is finished when
|
|
91114
|
-
${describeBlockers(open)}
|
|
91115
|
-
` + `${nextStep(open)}
|
|
91116
|
-
` + "Do the next missing step now. Step 1 (Build Review Context Pack) has no CLI command; it is a prose step, so mark it done and move on. " + "A command that is refused is not a blocker: run `keryx review --help` for the accepted usage and retry with the right flags. " + "A plan item you marked blocked for such a reason is not blocked: mark it in_progress. " + "A tool loop guard or an empty child result is not a reason to ask the operator to resend the request or to continue: change the arguments, retry the failed pass on a smaller slice, and keep going. " +
|
|
91302
|
+
message: `The managed review ${open.reviewId} is not finished: it is finished when the package is closed (${card ? "the operator answers the `review_complete` card and its gates pass" : "`keryx review complete` accepts it"}), not when you stop writing. ` + `It still refuses on:
|
|
91303
|
+
${describeBlockers(open, card)}
|
|
91304
|
+
` + `${nextStep(open, card)}
|
|
91305
|
+
` + "Do the next missing step now. Step 1 (Build Review Context Pack) has no CLI command; it is a prose step, so mark it done and move on. " + "A command that is refused is not a blocker: run `keryx review --help` for the accepted usage and retry with the right flags. " + "A plan item you marked blocked for such a reason is not blocked: mark it in_progress. " + "A tool loop guard or an empty child result is not a reason to ask the operator to resend the request or to continue: change the arguments, retry the failed pass on a smaller slice, and keep going. " + `Stop only after ${card ? "the `review_complete` card has" : "`keryx review complete` has"} closed the package, or report the one real blocker with its evidence.`
|
|
91117
91306
|
};
|
|
91118
91307
|
}
|
|
91119
91308
|
var MAX_REVIEW_GATE_CONTINUES = 20, MAX_REVIEW_GATE_STALLS = 3, MAX_HASHED_FILES = 4096, RACY_WINDOW_MS = 2000, fileHashes, METAPROJECT = ".metaproject", REVIEWS = "reviews", PREFIX_DELIMITERS, MAX_PREFIX_CHARS = 1024, MAX_NAME_CHARS = 255, MAX_SESSION_REVIEW_ROOTS = 8, MAX_SESSION_REVIEW_DIRS = 32, MAX_SESSION_REVIEW_IDS = 32, MAX_PENDING_CALLS = 64, SAFE_REVIEW_ID, isPathDelimiter = (ch) => PREFIX_DELIMITERS.has(ch) || /\s/.test(ch), isSeparator = (ch) => ch === "/" || ch === "\\", OPENING_SUBCOMMANDS, PACKAGE_ARG_SUBCOMMANDS, VALUELESS_FLAGS, OWN_OUTPUT_LINE, KERYX_PACKAGE, PIPE_FILTERS, NO_VALUE_OPTIONS, HEAD_TAIL_VALUE_OPTIONS, GREP_VALUE_OPTIONS, READER_VALUE_OPTIONS, GREP_PATTERN_OPTIONS, GREP_TREE_OPTION, KERYX_PACKAGE_NAMES, nextIs = (line, at, chars) => at + 1 < line.length && chars.includes(line[at + 1]), baseName2 = (word) => path212.posix.basename(word.replace(/\\/g, "/")), trackers, SCAN_CONCURRENCY = 16, NO_PACKAGE_BLOCKER = "no review package this session opened or named: `keryx review start` has not opened one, and no `keryx review` command in this session names an open package by its path or review id", MAX_NUDGE_BLOCKERS = 20, MAX_NUDGE_BLOCKER_CHARS = 300;
|
|
@@ -105865,7 +106054,8 @@ ${line}`;
|
|
|
105865
106054
|
runSeen,
|
|
105866
106055
|
continues: reviewGateContinues,
|
|
105867
106056
|
stalls: reviewGateStalls,
|
|
105868
|
-
lastSignature: lastReviewGateSignature
|
|
106057
|
+
lastSignature: lastReviewGateSignature,
|
|
106058
|
+
cardAvailable: deps.tools.some((tool) => tool.definition.name === "review_complete")
|
|
105869
106059
|
});
|
|
105870
106060
|
if (gate.action === "continue") {
|
|
105871
106061
|
reviewGateContinues += 1;
|
|
@@ -107270,6 +107460,8 @@ async function executeCallUnbounded(call, toolByName, requestApproval, permissio
|
|
|
107270
107460
|
const hookInteractive = hooks?.runtime.interactive ?? true;
|
|
107271
107461
|
let confirmationToken;
|
|
107272
107462
|
let confirmationVia;
|
|
107463
|
+
let confirmationPressedBy;
|
|
107464
|
+
let confirmationPressedAt;
|
|
107273
107465
|
if (tool.confirmation !== undefined && risk !== "write") {
|
|
107274
107466
|
return { output: `tool "${call.name}" needs operator confirmation but is not a write tool; refused`, isError: true };
|
|
107275
107467
|
}
|
|
@@ -107458,8 +107650,12 @@ async function executeCallUnbounded(call, toolByName, requestApproval, permissio
|
|
|
107458
107650
|
isError: true
|
|
107459
107651
|
};
|
|
107460
107652
|
}
|
|
107461
|
-
if (card !== undefined)
|
|
107462
|
-
|
|
107653
|
+
if (card !== undefined) {
|
|
107654
|
+
const remote = typeof response === "object" && response.via === "remote" ? response : undefined;
|
|
107655
|
+
confirmationVia = remote !== undefined ? "remote" : "local";
|
|
107656
|
+
confirmationPressedBy = remote?.pressedBy;
|
|
107657
|
+
confirmationPressedAt = remote?.pressedAt;
|
|
107658
|
+
}
|
|
107463
107659
|
if (hookResult?.decisions.some((d) => d.hookId === IMPACT_EVIDENCE_HOOK_ID && d.decision === "ask")) {
|
|
107464
107660
|
hooks?.runtime.acknowledgeImpactEvidence?.(extractFilePathsFromToolInput(input));
|
|
107465
107661
|
}
|
|
@@ -107501,6 +107697,8 @@ async function executeCallUnbounded(call, toolByName, requestApproval, permissio
|
|
|
107501
107697
|
...signal !== undefined ? { signal } : {},
|
|
107502
107698
|
...confirmationToken !== undefined ? { confirmationToken } : {},
|
|
107503
107699
|
...confirmationToken !== undefined && confirmationVia !== undefined ? { confirmationVia } : {},
|
|
107700
|
+
...confirmationToken !== undefined && confirmationVia === "remote" && confirmationPressedBy !== undefined ? { confirmationPressedBy } : {},
|
|
107701
|
+
...confirmationToken !== undefined && confirmationVia === "remote" && confirmationPressedAt !== undefined ? { confirmationPressedAt } : {},
|
|
107504
107702
|
...untrustedContentSeen ? { untrustedContentSeen: true } : {}
|
|
107505
107703
|
});
|
|
107506
107704
|
if (review !== undefined && call.name === "shell_exec" && typeof input.command === "string") {
|
|
@@ -115600,7 +115798,7 @@ async function landWriteRun(input, deps = {}) {
|
|
|
115600
115798
|
decision: "landed",
|
|
115601
115799
|
branch,
|
|
115602
115800
|
commit,
|
|
115603
|
-
...input.confirmation !== undefined ? { confirmedVia: input.confirmation.confirmedVia, quote: input.confirmation.quote } : {}
|
|
115801
|
+
...input.confirmation !== undefined ? { confirmedVia: input.confirmation.confirmedVia, quote: input.confirmation.quote, ...input.confirmation.pressedBy !== undefined ? { pressedBy: input.confirmation.pressedBy } : {}, ...input.confirmation.pressedAt !== undefined ? { pressedAt: input.confirmation.pressedAt } : {} } : {}
|
|
115604
115802
|
}));
|
|
115605
115803
|
if (!won) {
|
|
115606
115804
|
await git(["branch", "-D", branch], repo).catch(() => {
|
|
@@ -122586,7 +122784,7 @@ class RemoteClient {
|
|
|
122586
122784
|
}
|
|
122587
122785
|
async askApproval(prompt, timeoutMs, options = {}) {
|
|
122588
122786
|
if (!this.connected || options.signal?.aborted === true) {
|
|
122589
|
-
return { decision: "deny" };
|
|
122787
|
+
return { decision: "deny", undelivered: true };
|
|
122590
122788
|
}
|
|
122591
122789
|
const body = {
|
|
122592
122790
|
sessionId: this.options.sessionId,
|
|
@@ -122598,15 +122796,15 @@ class RemoteClient {
|
|
|
122598
122796
|
try {
|
|
122599
122797
|
const response = await this.post("approval", body);
|
|
122600
122798
|
if (!response.ok) {
|
|
122601
|
-
return { decision: "deny" };
|
|
122799
|
+
return { decision: "deny", undelivered: true };
|
|
122602
122800
|
}
|
|
122603
122801
|
const id = (await response.json())?.approvalId;
|
|
122604
122802
|
if (typeof id !== "string" || id.length === 0) {
|
|
122605
|
-
return { decision: "deny" };
|
|
122803
|
+
return { decision: "deny", undelivered: true };
|
|
122606
122804
|
}
|
|
122607
122805
|
approvalId = id;
|
|
122608
122806
|
} catch {
|
|
122609
|
-
return { decision: "deny" };
|
|
122807
|
+
return { decision: "deny", undelivered: true };
|
|
122610
122808
|
}
|
|
122611
122809
|
const early = this.earlyDecisions.get(approvalId);
|
|
122612
122810
|
if (early !== undefined) {
|
|
@@ -124734,13 +124932,13 @@ function sha25610(value) {
|
|
|
124734
124932
|
function contentDigest(value) {
|
|
124735
124933
|
return sha25610(JSON.stringify(value ?? null));
|
|
124736
124934
|
}
|
|
124737
|
-
function limited(lines) {
|
|
124738
|
-
if (lines.length <= MAX_LISTED)
|
|
124739
|
-
return [...lines];
|
|
124740
|
-
return [...lines.slice(0, MAX_LISTED), ` ... and ${lines.length - MAX_LISTED} more`];
|
|
124741
|
-
}
|
|
124742
124935
|
function record4(confirmation) {
|
|
124743
|
-
return {
|
|
124936
|
+
return {
|
|
124937
|
+
confirmedVia: confirmation.confirmedVia,
|
|
124938
|
+
quote: confirmation.quote,
|
|
124939
|
+
...confirmation.pressedBy === undefined ? {} : { pressedBy: confirmation.pressedBy },
|
|
124940
|
+
...confirmation.pressedAt === undefined ? {} : { pressedAt: confirmation.pressedAt }
|
|
124941
|
+
};
|
|
124744
124942
|
}
|
|
124745
124943
|
async function captureConsole(fn) {
|
|
124746
124944
|
const lines = [];
|
|
@@ -124904,8 +125102,8 @@ function externalApplySpec(seams) {
|
|
|
124904
125102
|
`branch: ${branch} (new, local; cut from base commit ${cardSafe(run.baseCommit)})`,
|
|
124905
125103
|
`patch hash (sha256): ${hash}`,
|
|
124906
125104
|
`changed files: ${run.files.length}`,
|
|
124907
|
-
...
|
|
124908
|
-
...run.flaggedPaths.length > 0 ? [`FLAGGED PATHS (${run.flaggedPaths.length}) - repo plumbing, CI, hooks or agent config:`, ...
|
|
125105
|
+
...files,
|
|
125106
|
+
...run.flaggedPaths.length > 0 ? [`FLAGGED PATHS (${run.flaggedPaths.length}) - repo plumbing, CI, hooks or agent config:`, ...run.flaggedPaths.map((p) => ` ! ${cardSafe(p)}`)] : [],
|
|
124909
125107
|
"effect: your current branch, HEAD, index and working tree are never touched"
|
|
124910
125108
|
],
|
|
124911
125109
|
subject: `${branch} @ sha256:${hash}`,
|
|
@@ -125270,7 +125468,7 @@ function otherConfirmSpecs(seams = {}) {
|
|
|
125270
125468
|
scheduleSpec("remove", seams)
|
|
125271
125469
|
];
|
|
125272
125470
|
}
|
|
125273
|
-
var
|
|
125471
|
+
var SCOPES2, SCHEDULE_DESCRIPTIONS;
|
|
125274
125472
|
var init_operator_confirm_other = __esm(() => {
|
|
125275
125473
|
init_service11();
|
|
125276
125474
|
init_record2();
|
|
@@ -125287,9 +125485,101 @@ var init_operator_confirm_other = __esm(() => {
|
|
|
125287
125485
|
};
|
|
125288
125486
|
});
|
|
125289
125487
|
|
|
125488
|
+
// src/review/routing-check.ts
|
|
125489
|
+
import { readFileSync as readFileSync33 } from "fs";
|
|
125490
|
+
import path286 from "path";
|
|
125491
|
+
function narrowedSlices(manifest, readText, fix) {
|
|
125492
|
+
const originals = manifest.slices.filter((entry) => entry.retryOf === undefined && Array.isArray(entry.roles));
|
|
125493
|
+
const changedFiles = [...new Set(manifest.slices.filter((entry) => entry.retryOf === undefined).flatMap((entry) => entry.files))];
|
|
125494
|
+
const narrowed = [];
|
|
125495
|
+
const unreadable = [];
|
|
125496
|
+
for (const entry of originals) {
|
|
125497
|
+
const text = readText(entry);
|
|
125498
|
+
if (text === undefined) {
|
|
125499
|
+
unreadable.push(entry.id);
|
|
125500
|
+
continue;
|
|
125501
|
+
}
|
|
125502
|
+
const needed = routeSliceRegions(entry.id, parseScopedDiff(text), { changedFiles });
|
|
125503
|
+
const backedFix = fixClaimBacked(entry, fix, text);
|
|
125504
|
+
const backedSecondLook = secondLookClaimBacked(entry, fix, text);
|
|
125505
|
+
const roles = backedSecondLook ? [...SECOND_LOOK_ROLES] : backedFix ? expectedFixRoles(entry, fix, text) : needed.roles;
|
|
125506
|
+
const recorded = new Set(entry.roles ?? []);
|
|
125507
|
+
const missingRoles = roles.filter((role) => !recorded.has(role)).sort();
|
|
125508
|
+
const shallow = (backedFix || backedSecondLook || needed.depth === "deep") && entry.depth !== "deep";
|
|
125509
|
+
if (missingRoles.length > 0 || shallow) {
|
|
125510
|
+
narrowed.push({ id: entry.id, missingRoles, ...shallow ? { depth: { recorded: entry.depth ?? "unset", needed: "deep" } } : {} });
|
|
125511
|
+
}
|
|
125512
|
+
}
|
|
125513
|
+
return { narrowed, unreadable };
|
|
125514
|
+
}
|
|
125515
|
+
function sliceTextReader(manifestDir) {
|
|
125516
|
+
return (entry) => {
|
|
125517
|
+
try {
|
|
125518
|
+
return readFileSync33(path286.resolve(manifestDir, entry.path), "utf8");
|
|
125519
|
+
} catch {
|
|
125520
|
+
return;
|
|
125521
|
+
}
|
|
125522
|
+
};
|
|
125523
|
+
}
|
|
125524
|
+
function packageRoutingRefusal(packageDir, projectRoot, options = {}) {
|
|
125525
|
+
const shared = path286.join(projectRoot, ".metaproject", "data", "review", "slices");
|
|
125526
|
+
const own = path286.join(packageDir, "slices");
|
|
125527
|
+
for (const [file, dirs] of [[path286.join(own, "manifest.json"), [own]], [path286.join(packageDir, "slice-manifest.json"), [own, shared]]]) {
|
|
125528
|
+
let manifest;
|
|
125529
|
+
try {
|
|
125530
|
+
const parsed = JSON.parse(readFileSync33(file, "utf8"));
|
|
125531
|
+
manifest = Array.isArray(parsed.slices) ? { slices: parsed.slices } : undefined;
|
|
125532
|
+
} catch {
|
|
125533
|
+
manifest = undefined;
|
|
125534
|
+
}
|
|
125535
|
+
if (manifest === undefined)
|
|
125536
|
+
continue;
|
|
125537
|
+
const readers = dirs.map((dir) => sliceTextReader(dir));
|
|
125538
|
+
const { narrowed, unreadable } = narrowedSlices(manifest, (entry) => readers.map((read) => read(entry)).find((text) => text !== undefined), fixContextOf(packageDir));
|
|
125539
|
+
const label = path286.relative(projectRoot, file) || file;
|
|
125540
|
+
const refusal = routingRefusal(label, narrowed);
|
|
125541
|
+
if (refusal !== undefined || options.refuseUnreadable !== true || unreadable.length === 0)
|
|
125542
|
+
return refusal;
|
|
125543
|
+
return `${label} lists ${unreadable.length} slice(s) whose text is missing (${unreadable.slice(0, 8).join(", ")}${unreadable.length > 8 ? ", \u2026" : ""}), so their routing cannot be checked against their content. ` + "Restore the slice files next to the manifest, or re-run `keryx review slice` and the round";
|
|
125544
|
+
}
|
|
125545
|
+
return;
|
|
125546
|
+
}
|
|
125547
|
+
function routingRefusal(manifestLabel, narrowed) {
|
|
125548
|
+
if (narrowed.length === 0)
|
|
125549
|
+
return;
|
|
125550
|
+
const lines = narrowed.slice(0, 8).map((entry) => `${entry.id}: ${[
|
|
125551
|
+
entry.missingRoles.length > 0 ? `roles ${entry.missingRoles.join(", ")} are missing` : undefined,
|
|
125552
|
+
entry.depth !== undefined ? `recorded ${entry.depth.recorded}, its content needs deep` : undefined
|
|
125553
|
+
].filter(Boolean).join("; ")}`);
|
|
125554
|
+
return `${manifestLabel} routes ${narrowed.length} slice(s) narrower than their content needs (${lines.join(" | ")}${narrowed.length > 8 ? ` | \u2026 and ${narrowed.length - 8} more` : ""}). ` + "Routing is recomputed from each slice's text and the manifest may only be wider. Re-run `keryx review slice` to route the slices again, and do not edit roles or depth by hand.";
|
|
125555
|
+
}
|
|
125556
|
+
var init_routing_check = __esm(() => {
|
|
125557
|
+
init_slice();
|
|
125558
|
+
init_slice_routing();
|
|
125559
|
+
init_round_split();
|
|
125560
|
+
});
|
|
125561
|
+
|
|
125562
|
+
// src/review/complete-gated.ts
|
|
125563
|
+
async function completeReviewGated(cwd, ref, options) {
|
|
125564
|
+
const packageDir = await resolveReviewPackagePath(cwd, ref);
|
|
125565
|
+
const profileMissing = runProfileRefusal(packageDir);
|
|
125566
|
+
if (profileMissing !== undefined)
|
|
125567
|
+
throw new Error(`review complete refused: ${profileMissing}`);
|
|
125568
|
+
const narrowedRouting = packageRoutingRefusal(packageDir, resolveProjectRoot2(cwd), { refuseUnreadable: true });
|
|
125569
|
+
if (narrowedRouting !== undefined)
|
|
125570
|
+
throw new Error(`review complete refused: ${narrowedRouting}`);
|
|
125571
|
+
return completeManagedReview(cwd, ref, options);
|
|
125572
|
+
}
|
|
125573
|
+
var init_complete_gated = __esm(() => {
|
|
125574
|
+
init_contained_path();
|
|
125575
|
+
init_managed();
|
|
125576
|
+
init_run_profile();
|
|
125577
|
+
init_routing_check();
|
|
125578
|
+
});
|
|
125579
|
+
|
|
125290
125580
|
// src/review/publish.ts
|
|
125291
125581
|
import { readFile as readFile131 } from "fs/promises";
|
|
125292
|
-
import
|
|
125582
|
+
import path287 from "path";
|
|
125293
125583
|
function reviewEvent(value) {
|
|
125294
125584
|
const wanted = (value ?? "COMMENT").trim().toUpperCase();
|
|
125295
125585
|
const found = REVIEW_EVENTS.find((event) => event === wanted);
|
|
@@ -125385,7 +125675,7 @@ function buildRoundBlock(input) {
|
|
|
125385
125675
|
return { ...base, findings: kept, ...kept.length < listed.length ? { truncated: listed.length - kept.length } : {} };
|
|
125386
125676
|
}
|
|
125387
125677
|
async function publishReview(packageDir, options) {
|
|
125388
|
-
const manifest = await readJsonFileOr(
|
|
125678
|
+
const manifest = await readJsonFileOr(path287.join(packageDir, "manifest.json"), undefined);
|
|
125389
125679
|
if (manifest === undefined)
|
|
125390
125680
|
throw new Error(`Managed review package has no readable manifest.json: ${packageDir}`);
|
|
125391
125681
|
if (!isPrBound(manifest.target)) {
|
|
@@ -125400,7 +125690,7 @@ async function publishReview(packageDir, options) {
|
|
|
125400
125690
|
if (partial && event === "APPROVE") {
|
|
125401
125691
|
throw new Error(`A partial review cannot approve: ${failing.length} readiness item(s) are failing (${failing.map((item) => item.id).join(", ")}). Use --event COMMENT or REQUEST_CHANGES, or repair the items.`);
|
|
125402
125692
|
}
|
|
125403
|
-
const report = (await readFile131(
|
|
125693
|
+
const report = (await readFile131(path287.join(packageDir, "report.md"), "utf8").catch(() => "")).trim();
|
|
125404
125694
|
if (report === "")
|
|
125405
125695
|
throw new Error(`Review ${manifest.reviewId} has no report.md body to publish.`);
|
|
125406
125696
|
const head = `${partial ? `${partialPublishHeader(readiness).join(`
|
|
@@ -125411,7 +125701,7 @@ async function publishReview(packageDir, options) {
|
|
|
125411
125701
|
const minors = minorSection((await computeVerdict(packageDir)).minors, report, PUBLISHED_BODY_LIMIT - head.length - foot.length - ROUND_BLOCK_LIMIT);
|
|
125412
125702
|
const visible = `${head}${minors}${foot}`;
|
|
125413
125703
|
const reviewedHead = manifest.target.head ?? null;
|
|
125414
|
-
const findings = await readJsonFileOr(
|
|
125704
|
+
const findings = await readJsonFileOr(path287.join(packageDir, "findings.json"), []);
|
|
125415
125705
|
const roundBlock = buildRoundBlock({ body: visible, round: readRoundNumber(packageDir) ?? 1, reviewId: manifest.reviewId, reviewedHead, findings: Array.isArray(findings) ? findings : [] });
|
|
125416
125706
|
const body = `${visible}
|
|
125417
125707
|
${renderRoundBlock(roundBlock)}
|
|
@@ -125421,7 +125711,7 @@ ${renderRoundBlock(roundBlock)}
|
|
|
125421
125711
|
const result = await options.post(manifest.target.ref, event, body);
|
|
125422
125712
|
if (!result.ok)
|
|
125423
125713
|
throw new Error(`GitHub refused the review: ${result.detail}`);
|
|
125424
|
-
await writeFileAtomic(
|
|
125714
|
+
await writeFileAtomic(path287.join(packageDir, "published.json"), `${JSON.stringify({ pr: manifest.target.ref, reviewId: manifest.reviewId, head: reviewedHead, round: roundBlock.round, event, partial, failing: failing.map((item) => item.id), time: (options.now?.() ?? new Date).toISOString(), ...options.confirmedVia === undefined ? {} : { confirmedVia: options.confirmedVia }, ...cardPressFields(options) }, null, 2)}
|
|
125425
125715
|
`);
|
|
125426
125716
|
const registered = recordFinishedRound(packageDir, "publish");
|
|
125427
125717
|
return { readiness, body, event, partial, posted: true, ...registered.error === undefined ? {} : { registryWarning: `round not recorded in the local round registry: ${registered.error}` } };
|
|
@@ -125437,6 +125727,7 @@ var init_publish = __esm(() => {
|
|
|
125437
125727
|
init_round_registry();
|
|
125438
125728
|
init_verdict();
|
|
125439
125729
|
init_readiness();
|
|
125730
|
+
init_types4();
|
|
125440
125731
|
REVIEW_EVENTS = ["COMMENT", "REQUEST_CHANGES", "APPROVE"];
|
|
125441
125732
|
});
|
|
125442
125733
|
|
|
@@ -125444,16 +125735,16 @@ var init_publish = __esm(() => {
|
|
|
125444
125735
|
import { createHash as createHash81 } from "crypto";
|
|
125445
125736
|
import { existsSync as existsSync56, realpathSync as realpathSync19 } from "fs";
|
|
125446
125737
|
import { readFile as readFile132 } from "fs/promises";
|
|
125447
|
-
import
|
|
125738
|
+
import path288 from "path";
|
|
125448
125739
|
async function findHeldFinding(packageDir, ref) {
|
|
125449
125740
|
const wanted = ref.trim();
|
|
125450
125741
|
if (wanted === "")
|
|
125451
125742
|
return { ok: false, refusal: "a finding id is required" };
|
|
125452
|
-
const findings = await readJsonFileOr(
|
|
125743
|
+
const findings = await readJsonFileOr(path288.join(packageDir, FINDINGS_FILE), undefined);
|
|
125453
125744
|
const records = Array.isArray(findings) ? findings.map(obj).filter((entry) => entry !== undefined) : [];
|
|
125454
125745
|
const named = records.filter((record) => str3(record["global_id"]) === wanted || str3(record["id"]) === wanted);
|
|
125455
125746
|
if (named.length === 0)
|
|
125456
|
-
return { ok: false, refusal: `No finding "${wanted}" in ${
|
|
125747
|
+
return { ok: false, refusal: `No finding "${wanted}" in ${path288.basename(packageDir)}/${FINDINGS_FILE}. Name a finding by its global_id or id.` };
|
|
125457
125748
|
const keys = new Set(named.map((record) => str3(record["global_id"]) ?? str3(record["id"]) ?? wanted));
|
|
125458
125749
|
if (keys.size > 1)
|
|
125459
125750
|
return { ok: false, refusal: `"${wanted}" names ${keys.size} findings (${[...keys].join(", ")}); name one by its global_id.` };
|
|
@@ -125482,13 +125773,13 @@ async function carryPremiseDecisions(packageDir, previousDir, carriedIds, carrie
|
|
|
125482
125773
|
if (carriedIds.length === 0 || !isReviewPackageDir(previousDir, options.projectRoot))
|
|
125483
125774
|
return [];
|
|
125484
125775
|
const realPrevious = realpathSync19(previousDir);
|
|
125485
|
-
const realPackage = existsSync56(packageDir) ? realpathSync19(packageDir) :
|
|
125486
|
-
if (realPrevious === realPackage || realPackage.startsWith(`${realPrevious}${
|
|
125776
|
+
const realPackage = existsSync56(packageDir) ? realpathSync19(packageDir) : path288.resolve(packageDir);
|
|
125777
|
+
if (realPrevious === realPackage || realPackage.startsWith(`${realPrevious}${path288.sep}`))
|
|
125487
125778
|
return [];
|
|
125488
125779
|
const previous = await readPremiseDecisions(realPrevious);
|
|
125489
125780
|
if (previous.length === 0)
|
|
125490
125781
|
return [];
|
|
125491
|
-
const findings = await readJsonFileOr(
|
|
125782
|
+
const findings = await readJsonFileOr(path288.join(realPrevious, FINDINGS_FILE), undefined);
|
|
125492
125783
|
const records = Array.isArray(findings) ? findings.map(obj).filter((entry) => entry !== undefined) : [];
|
|
125493
125784
|
const have = await readPremiseDecisions(packageDir);
|
|
125494
125785
|
const carried = [];
|
|
@@ -125510,13 +125801,13 @@ async function carryPremiseDecisions(packageDir, previousDir, carriedIds, carrie
|
|
|
125510
125801
|
return carried;
|
|
125511
125802
|
}
|
|
125512
125803
|
async function answerPremiseQuestion(packageDir, input, registry = {}) {
|
|
125513
|
-
const { decision, applied: vouched } = await recordPremiseDecisionAfter(packageDir, { finding: input.finding.key, answer: input.answer, reason: input.reason, quote: input.quote, at: input.at, confirmedVia: input.confirmedVia, subject: input.finding.subject }, (kept) => applyAnswerToFindings(packageDir, kept, registry));
|
|
125804
|
+
const { decision, applied: vouched } = await recordPremiseDecisionAfter(packageDir, { finding: input.finding.key, answer: input.answer, reason: input.reason, quote: input.quote, at: input.at, confirmedVia: input.confirmedVia, ...cardPressFields(input), subject: input.finding.subject, ...input.hostQuestion === undefined ? {} : { hostQuestion: input.hostQuestion } }, (kept) => applyAnswerToFindings(packageDir, kept, registry));
|
|
125514
125805
|
if (vouched === true)
|
|
125515
125806
|
reRecordRegistry(packageDir, registry);
|
|
125516
125807
|
return decision;
|
|
125517
125808
|
}
|
|
125518
125809
|
async function applyAnswerToFindings(packageDir, kept, registry) {
|
|
125519
|
-
const file =
|
|
125810
|
+
const file = path288.join(packageDir, FINDINGS_FILE);
|
|
125520
125811
|
const before = await readFile132(file, "utf8").catch(() => {
|
|
125521
125812
|
return;
|
|
125522
125813
|
});
|
|
@@ -125561,14 +125852,15 @@ var init_premise_question = __esm(() => {
|
|
|
125561
125852
|
init_json();
|
|
125562
125853
|
init_operator_decisions();
|
|
125563
125854
|
init_package_location();
|
|
125855
|
+
init_types4();
|
|
125564
125856
|
init_premise();
|
|
125565
125857
|
init_round_registry();
|
|
125566
125858
|
});
|
|
125567
125859
|
|
|
125568
125860
|
// src/review/estimate.ts
|
|
125569
125861
|
import { createHash as createHash82 } from "crypto";
|
|
125570
|
-
import { existsSync as existsSync57, readFileSync as
|
|
125571
|
-
import
|
|
125862
|
+
import { existsSync as existsSync57, readFileSync as readFileSync34 } from "fs";
|
|
125863
|
+
import path289 from "path";
|
|
125572
125864
|
function planKeyOf(estimate) {
|
|
125573
125865
|
const roles = [...estimate.perRole].sort((a, b) => a.role.localeCompare(b.role)).map((entry) => ({ role: entry.role, waves: entry.waves }));
|
|
125574
125866
|
const slices = [...estimate.slicePlan].sort((a, b) => a.id.localeCompare(b.id)).map((entry) => ({ id: entry.id, roles: [...entry.roles].sort(), depth: entry.depth ?? null }));
|
|
@@ -125648,7 +125940,7 @@ function estimateRound(roles, manifest, depth = "unrecorded", thresholds = { max
|
|
|
125648
125940
|
}
|
|
125649
125941
|
async function readPlanThresholds(projectRoot) {
|
|
125650
125942
|
const defaults = { maxDispatches: DEFAULT_MAX_DISPATCHES, maxMinutes: DEFAULT_MAX_MINUTES, source: "default" };
|
|
125651
|
-
const file =
|
|
125943
|
+
const file = path289.join(projectRoot, "routing.config.json");
|
|
125652
125944
|
if (!await pathExists(file))
|
|
125653
125945
|
return defaults;
|
|
125654
125946
|
const read = await readJsonObjectFile(file);
|
|
@@ -125668,11 +125960,11 @@ function isPlanConfirmation(value) {
|
|
|
125668
125960
|
if (value === null || typeof value !== "object" || Array.isArray(value))
|
|
125669
125961
|
return false;
|
|
125670
125962
|
const v = value;
|
|
125671
|
-
return typeof v.planKey === "string" && v.planKey !== "" && typeof v.dispatches === "number" && Number.isFinite(v.dispatches) && Array.isArray(v.minutes) && v.minutes.length === 2 && v.minutes.every((m) => typeof m === "number" && Number.isFinite(m)) && typeof v.quote === "string" && v.quote.trim() !== "" && typeof v.confirmedVia === "string" && CONFIRMED_VIA.includes(v.confirmedVia) && typeof v.at === "string" && v.at !== "";
|
|
125963
|
+
return typeof v.planKey === "string" && v.planKey !== "" && typeof v.dispatches === "number" && Number.isFinite(v.dispatches) && Array.isArray(v.minutes) && v.minutes.length === 2 && v.minutes.every((m) => typeof m === "number" && Number.isFinite(m)) && typeof v.quote === "string" && v.quote.trim() !== "" && typeof v.confirmedVia === "string" && CONFIRMED_VIA.includes(v.confirmedVia) && (v.confirmedVia !== "host-question" || isHostQuestion(v.hostQuestion)) && typeof v.at === "string" && v.at !== "";
|
|
125672
125964
|
}
|
|
125673
125965
|
function readPlanConfirmation(packageDir) {
|
|
125674
125966
|
try {
|
|
125675
|
-
const parsed = JSON.parse(
|
|
125967
|
+
const parsed = JSON.parse(readFileSync34(path289.join(packageDir, PLAN_CONFIRMATION_FILE), "utf8"));
|
|
125676
125968
|
return isPlanConfirmation(parsed) ? parsed : undefined;
|
|
125677
125969
|
} catch {
|
|
125678
125970
|
return;
|
|
@@ -125687,7 +125979,7 @@ function planConfirmationFor(estimate, quote, confirmedVia, now = new Date) {
|
|
|
125687
125979
|
return { planKey: planKeyOf(estimate), dispatches: estimate.dispatches, minutes: [estimate.minutes.min, estimate.minutes.max], quote: quote.trim(), confirmedVia, at: now.toISOString() };
|
|
125688
125980
|
}
|
|
125689
125981
|
async function writePlanConfirmation(packageDir, confirmation) {
|
|
125690
|
-
const file =
|
|
125982
|
+
const file = path289.join(packageDir, PLAN_CONFIRMATION_FILE);
|
|
125691
125983
|
await writeFileAtomic(file, `${JSON.stringify(confirmation, null, 2)}
|
|
125692
125984
|
`);
|
|
125693
125985
|
return file;
|
|
@@ -125712,9 +126004,9 @@ async function packagePlanGate(packageDir, options = {}) {
|
|
|
125712
126004
|
}
|
|
125713
126005
|
function manifestCandidates(packageDir, root) {
|
|
125714
126006
|
return [
|
|
125715
|
-
|
|
125716
|
-
|
|
125717
|
-
|
|
126007
|
+
path289.join(packageDir, "slices", "manifest.json"),
|
|
126008
|
+
path289.join(packageDir, "slice-manifest.json"),
|
|
126009
|
+
path289.join(root, ".metaproject", "data", "review", "slices", "manifest.json")
|
|
125718
126010
|
];
|
|
125719
126011
|
}
|
|
125720
126012
|
async function readManifest3(candidates) {
|
|
@@ -125737,15 +126029,15 @@ async function estimatePackage(packageDir, options = {}) {
|
|
|
125737
126029
|
depth: recorded?.depth
|
|
125738
126030
|
});
|
|
125739
126031
|
const read = await readManifest3(options.manifest === undefined ? manifestCandidates(packageDir, root) : [options.manifest]);
|
|
125740
|
-
const found = options.manifestValue === undefined ? read : { manifest: options.manifestValue, file: read?.file ?? options.manifest ??
|
|
126032
|
+
const found = options.manifestValue === undefined ? read : { manifest: options.manifestValue, file: read?.file ?? options.manifest ?? path289.join(packageDir, "slices", "manifest.json") };
|
|
125741
126033
|
if (options.manifest !== undefined && found === undefined)
|
|
125742
126034
|
throw new Error(`${options.manifest} is not a slice manifest (no readable JSON with a slices array); run \`keryx review slice\` first`);
|
|
125743
126035
|
const problem = found === undefined ? undefined : manifestSizeProblem(found.manifest);
|
|
125744
126036
|
if (problem !== undefined)
|
|
125745
|
-
throw new Error(`${
|
|
126037
|
+
throw new Error(`${path289.relative(root, found.file)}: ${problem}`);
|
|
125746
126038
|
const thresholds = await readPlanThresholds(root);
|
|
125747
126039
|
const roles = [...new Set([...set.required, ...found === undefined ? [] : manifestRosterRoles(found.manifest)])].sort();
|
|
125748
|
-
return { ...estimateRound(roles, found?.manifest, recorded?.depth ?? "unrecorded", thresholds), manifest: found === undefined ? undefined :
|
|
126040
|
+
return { ...estimateRound(roles, found?.manifest, recorded?.depth ?? "unrecorded", thresholds), manifest: found === undefined ? undefined : path289.relative(root, found.file) };
|
|
125749
126041
|
}
|
|
125750
126042
|
function renderEstimate(estimate) {
|
|
125751
126043
|
const lines = [
|
|
@@ -125777,11 +126069,12 @@ var init_estimate = __esm(() => {
|
|
|
125777
126069
|
init_slice_routing();
|
|
125778
126070
|
init_waves();
|
|
125779
126071
|
init_round_split();
|
|
125780
|
-
|
|
126072
|
+
init_types4();
|
|
126073
|
+
CONFIRMED_VIA = ["tui-card", "remote-card", "terminal", "host-question"];
|
|
125781
126074
|
});
|
|
125782
126075
|
|
|
125783
126076
|
// src/review/plan-confirmation.ts
|
|
125784
|
-
import
|
|
126077
|
+
import path290 from "path";
|
|
125785
126078
|
function planOverThreshold(estimate) {
|
|
125786
126079
|
return estimate.overThreshold;
|
|
125787
126080
|
}
|
|
@@ -125791,30 +126084,38 @@ function planKeyOf2(estimate) {
|
|
|
125791
126084
|
function estimatePlan(packageDir) {
|
|
125792
126085
|
return estimatePackage(packageDir);
|
|
125793
126086
|
}
|
|
126087
|
+
async function readPlanConfirmation2(packageDir) {
|
|
126088
|
+
return readPlanConfirmation(packageDir);
|
|
126089
|
+
}
|
|
125794
126090
|
async function recordPlanConfirmation(packageDir, estimate, confirmation) {
|
|
125795
126091
|
if (confirmation.quote.trim() === "")
|
|
125796
126092
|
throw new Error("a plan confirmation needs the operator's own words");
|
|
126093
|
+
if (confirmation.confirmedVia === "host-question" && confirmation.hostQuestion === undefined)
|
|
126094
|
+
throw new Error("a host-question plan confirmation needs the question and the answer");
|
|
125797
126095
|
const record = {
|
|
125798
126096
|
planKey: planKeyOf2(estimate),
|
|
125799
126097
|
dispatches: estimate.dispatches,
|
|
125800
126098
|
minutes: [estimate.minutes.min, estimate.minutes.max],
|
|
125801
126099
|
quote: confirmation.quote.trim(),
|
|
125802
126100
|
confirmedVia: confirmation.confirmedVia,
|
|
125803
|
-
|
|
126101
|
+
...cardPressFields(confirmation),
|
|
126102
|
+
at: confirmation.at,
|
|
126103
|
+
...confirmation.hostQuestion === undefined ? {} : { hostQuestion: confirmation.hostQuestion }
|
|
125804
126104
|
};
|
|
125805
|
-
await writeFileAtomic(
|
|
126105
|
+
await writeFileAtomic(path290.join(packageDir, PLAN_CONFIRMATION_FILE), `${JSON.stringify(record, null, 2)}
|
|
125806
126106
|
`);
|
|
125807
126107
|
return record;
|
|
125808
126108
|
}
|
|
125809
126109
|
var init_plan_confirmation = __esm(() => {
|
|
125810
126110
|
init_fs();
|
|
125811
126111
|
init_estimate();
|
|
126112
|
+
init_types4();
|
|
125812
126113
|
});
|
|
125813
126114
|
|
|
125814
126115
|
// src/review/severity-audit.ts
|
|
125815
126116
|
import { existsSync as existsSync58 } from "fs";
|
|
125816
126117
|
import { readFile as readFile133, readdir as readdir48, writeFile as writeFile43 } from "fs/promises";
|
|
125817
|
-
import
|
|
126118
|
+
import path291 from "path";
|
|
125818
126119
|
function cited(finding) {
|
|
125819
126120
|
const evidence = typeof finding.evidence === "string" ? finding.evidence : "";
|
|
125820
126121
|
return [...new Set([...evidence.matchAll(CITATION2)].map((m) => m[1]))];
|
|
@@ -125892,14 +126193,14 @@ function auditSeverity(findings, records, options = {}) {
|
|
|
125892
126193
|
return { findings: audited, changes };
|
|
125893
126194
|
}
|
|
125894
126195
|
async function loadMutationRecords(packageDir) {
|
|
125895
|
-
const dir =
|
|
126196
|
+
const dir = path291.join(packageDir, EVIDENCE_DIR);
|
|
125896
126197
|
if (!existsSync58(dir))
|
|
125897
126198
|
return [];
|
|
125898
126199
|
const names = (await readdir48(dir)).filter((n) => /^mutation-\d+\.json$/.test(n)).sort((a, b) => Number(/\d+/.exec(a)[0]) - Number(/\d+/.exec(b)[0]));
|
|
125899
126200
|
const out = [];
|
|
125900
126201
|
for (const name of names) {
|
|
125901
126202
|
try {
|
|
125902
|
-
out.push({ name, record: JSON.parse(await readFile133(
|
|
126203
|
+
out.push({ name, record: JSON.parse(await readFile133(path291.join(dir, name), "utf8")) });
|
|
125903
126204
|
} catch {
|
|
125904
126205
|
continue;
|
|
125905
126206
|
}
|
|
@@ -125907,7 +126208,7 @@ async function loadMutationRecords(packageDir) {
|
|
|
125907
126208
|
return out;
|
|
125908
126209
|
}
|
|
125909
126210
|
async function auditPackage(packageDir, options = {}) {
|
|
125910
|
-
const file =
|
|
126211
|
+
const file = path291.join(packageDir, "findings.json");
|
|
125911
126212
|
const raw = JSON.parse(await readFile133(file, "utf8"));
|
|
125912
126213
|
if (!Array.isArray(raw))
|
|
125913
126214
|
throw new Error(`${file} must hold an array of findings`);
|
|
@@ -125928,7 +126229,7 @@ var init_severity_audit = __esm(() => {
|
|
|
125928
126229
|
|
|
125929
126230
|
// src/review/context-collect.ts
|
|
125930
126231
|
import { mkdir as mkdir70, readFile as readFile134, writeFile as writeFile44 } from "fs/promises";
|
|
125931
|
-
import
|
|
126232
|
+
import path292 from "path";
|
|
125932
126233
|
function extractBackendPins(texts) {
|
|
125933
126234
|
const pins = [];
|
|
125934
126235
|
const seen = new Set;
|
|
@@ -126031,7 +126332,7 @@ function renderPrContext(ctx) {
|
|
|
126031
126332
|
}
|
|
126032
126333
|
async function readPrContext(packageDir) {
|
|
126033
126334
|
try {
|
|
126034
|
-
const parsed = JSON.parse(await readFile134(
|
|
126335
|
+
const parsed = JSON.parse(await readFile134(path292.join(packageDir, PR_CONTEXT_JSON), "utf8"));
|
|
126035
126336
|
return parsed?.schemaVersion === 1 ? parsed : undefined;
|
|
126036
126337
|
} catch {
|
|
126037
126338
|
return;
|
|
@@ -126053,8 +126354,8 @@ async function collectPrContext(input) {
|
|
|
126053
126354
|
]);
|
|
126054
126355
|
const context = buildPrContext({ repo: input.repo, number: input.number, pull: pullText(pull), comments: collected.comments, checks: input.checks, previous, now: input.now });
|
|
126055
126356
|
await mkdir70(input.packageDir, { recursive: true });
|
|
126056
|
-
const json =
|
|
126057
|
-
const md =
|
|
126357
|
+
const json = path292.join(input.packageDir, PR_CONTEXT_JSON);
|
|
126358
|
+
const md = path292.join(input.packageDir, PR_CONTEXT_MD);
|
|
126058
126359
|
await writeFile44(json, `${JSON.stringify(context, null, 2)}
|
|
126059
126360
|
`, "utf8");
|
|
126060
126361
|
await writeFile44(md, renderPrContext(context), "utf8");
|
|
@@ -126086,7 +126387,7 @@ var init_context_collect = __esm(() => {
|
|
|
126086
126387
|
// src/review/compare-reference.ts
|
|
126087
126388
|
import { existsSync as existsSync59 } from "fs";
|
|
126088
126389
|
import { readFile as readFile135, readdir as readdir49 } from "fs/promises";
|
|
126089
|
-
import
|
|
126390
|
+
import path293 from "path";
|
|
126090
126391
|
function locationsOf(text) {
|
|
126091
126392
|
const found = [];
|
|
126092
126393
|
const pattern = /`?([A-Za-z0-9_./-]+\.(?:tsx?|jsx?|css|scss|md|json|ya?ml))(?::(\d+)(?:-(\d+))?)?`?/g;
|
|
@@ -126164,17 +126465,17 @@ async function readJson11(file) {
|
|
|
126164
126465
|
}
|
|
126165
126466
|
}
|
|
126166
126467
|
async function rawReplies(inputDir) {
|
|
126167
|
-
const dir =
|
|
126468
|
+
const dir = path293.join(inputDir, "raw");
|
|
126168
126469
|
if (!existsSync59(dir))
|
|
126169
126470
|
return [];
|
|
126170
126471
|
const out = [];
|
|
126171
126472
|
for (const name of (await readdir49(dir)).filter((n) => n.endsWith(".txt")).sort()) {
|
|
126172
|
-
out.push({ id: `raw/${name}`, text: await readFile135(
|
|
126473
|
+
out.push({ id: `raw/${name}`, text: await readFile135(path293.join(dir, name), "utf8") });
|
|
126173
126474
|
}
|
|
126174
126475
|
return out;
|
|
126175
126476
|
}
|
|
126176
126477
|
async function loadPackageEvidence(packageDir, inputDir) {
|
|
126177
|
-
const findingsRaw = await readJson11(
|
|
126478
|
+
const findingsRaw = await readJson11(path293.join(packageDir, "findings.json"));
|
|
126178
126479
|
const findings = (Array.isArray(findingsRaw) ? findingsRaw : []).map((f) => ({
|
|
126179
126480
|
id: asText(f.id),
|
|
126180
126481
|
severity: asText(f.severity) || undefined,
|
|
@@ -126184,7 +126485,7 @@ async function loadPackageEvidence(packageDir, inputDir) {
|
|
|
126184
126485
|
`)
|
|
126185
126486
|
}));
|
|
126186
126487
|
const canonicalIds = new Set(findings.map((f) => f.id));
|
|
126187
|
-
const research = asRecord2(await readJson11(
|
|
126488
|
+
const research = asRecord2(await readJson11(path293.join(packageDir, "research.json")));
|
|
126188
126489
|
const dispatch = asRecord2(research?.dispatch);
|
|
126189
126490
|
const rows = (value) => Array.isArray(value) ? value.filter((r) => r !== null && typeof r === "object") : [];
|
|
126190
126491
|
const selected = Array.isArray(dispatch?.selected) ? dispatch.selected : [];
|
|
@@ -126202,7 +126503,7 @@ async function loadPackageEvidence(packageDir, inputDir) {
|
|
|
126202
126503
|
}
|
|
126203
126504
|
if (inputDir !== undefined)
|
|
126204
126505
|
lost.push(...await rawReplies(inputDir));
|
|
126205
|
-
const evidenceDir =
|
|
126506
|
+
const evidenceDir = path293.join(packageDir, "research-evidence");
|
|
126206
126507
|
const mutationPass = existsSync59(evidenceDir) && (await readdir49(evidenceDir)).some((n) => /^mutation-\d+\.(json|md)$/i.test(n));
|
|
126207
126508
|
return { ran: [...ran].sort(), findings, lost, mutationPass };
|
|
126208
126509
|
}
|
|
@@ -126213,14 +126514,14 @@ function score2(item, evidence) {
|
|
|
126213
126514
|
if (haystack.includes(identifier))
|
|
126214
126515
|
points += 1;
|
|
126215
126516
|
for (const loc of item.locations) {
|
|
126216
|
-
const base =
|
|
126517
|
+
const base = path293.basename(loc.file).toLowerCase();
|
|
126217
126518
|
if (!haystack.includes(base))
|
|
126218
126519
|
continue;
|
|
126219
126520
|
if (loc.line === undefined) {
|
|
126220
126521
|
points += 1;
|
|
126221
126522
|
continue;
|
|
126222
126523
|
}
|
|
126223
|
-
const near = [...haystack.matchAll(new RegExp(`${base.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}:(\\d+)`, "g"))].some((m) => Math.abs(Number(m[1]) - loc.line) <= LINE_WINDOW2) || evidence.file !== undefined &&
|
|
126524
|
+
const near = [...haystack.matchAll(new RegExp(`${base.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}:(\\d+)`, "g"))].some((m) => Math.abs(Number(m[1]) - loc.line) <= LINE_WINDOW2) || evidence.file !== undefined && path293.basename(evidence.file).toLowerCase() === base && evidence.line !== undefined && Math.abs(evidence.line - loc.line) <= LINE_WINDOW2;
|
|
126224
126525
|
points += near ? 2 : 1;
|
|
126225
126526
|
}
|
|
126226
126527
|
return points;
|
|
@@ -126285,7 +126586,7 @@ var init_compare_reference = __esm(() => {
|
|
|
126285
126586
|
// src/review/driver-read.ts
|
|
126286
126587
|
import { existsSync as existsSync60 } from "fs";
|
|
126287
126588
|
import { readFile as readFile136, readdir as readdir50 } from "fs/promises";
|
|
126288
|
-
import
|
|
126589
|
+
import path294 from "path";
|
|
126289
126590
|
async function readJson12(file) {
|
|
126290
126591
|
try {
|
|
126291
126592
|
return JSON.parse(await readFile136(file, "utf8"));
|
|
@@ -126298,7 +126599,7 @@ function definitionCandidates(driver) {
|
|
|
126298
126599
|
return [driver.definition, driver.skill_path, payload.definition, payload.skill_path].filter((v) => typeof v === "string" && v.trim() !== "");
|
|
126299
126600
|
}
|
|
126300
126601
|
async function jsonFiles(dir) {
|
|
126301
|
-
return existsSync60(dir) ? (await readdir50(dir)).filter((n) => n.endsWith(".json")).sort().map((n) =>
|
|
126602
|
+
return existsSync60(dir) ? (await readdir50(dir)).filter((n) => n.endsWith(".json")).sort().map((n) => path294.join(dir, n)) : [];
|
|
126302
126603
|
}
|
|
126303
126604
|
async function readDrivers(inputDir, root) {
|
|
126304
126605
|
const payloadSlices = {};
|
|
@@ -126310,7 +126611,7 @@ async function readDrivers(inputDir, root) {
|
|
|
126310
126611
|
const replyFiles = new Set;
|
|
126311
126612
|
const replyReviewers = new Set;
|
|
126312
126613
|
const dropped = [];
|
|
126313
|
-
for (const file of await jsonFiles(
|
|
126614
|
+
for (const file of await jsonFiles(path294.join(inputDir, "drivers"))) {
|
|
126314
126615
|
const driver = await readJson12(file);
|
|
126315
126616
|
if (typeof driver?.reviewer !== "string")
|
|
126316
126617
|
continue;
|
|
@@ -126319,11 +126620,11 @@ async function readDrivers(inputDir, root) {
|
|
|
126319
126620
|
const slices = readSliceIds(driver.slices, "assigned");
|
|
126320
126621
|
const complete = Array.isArray(driver.slices) && slices.unusable === 0;
|
|
126321
126622
|
if (slices.unusable > 0)
|
|
126322
|
-
dropped.push({ file:
|
|
126623
|
+
dropped.push({ file: path294.basename(file), reviewer: driver.reviewer, unusable: slices.unusable });
|
|
126323
126624
|
if (complete)
|
|
126324
126625
|
(payloadSlices[driver.reviewer] ??= []).push(...slices.ids);
|
|
126325
126626
|
if (typeof driver.result === "string") {
|
|
126326
|
-
replyFiles.add(
|
|
126627
|
+
replyFiles.add(path294.basename(driver.result));
|
|
126327
126628
|
if (complete) {
|
|
126328
126629
|
const key = `${driver.reviewer}\x00${driver.result}`;
|
|
126329
126630
|
const matches = assignments.get(key) ?? [];
|
|
@@ -126336,9 +126637,9 @@ async function readDrivers(inputDir, root) {
|
|
|
126336
126637
|
if (typeof driver.model === "string" && driver.model.trim() !== "")
|
|
126337
126638
|
models[driver.reviewer] = driver.model.trim();
|
|
126338
126639
|
for (const candidate of definitionCandidates(driver)) {
|
|
126339
|
-
const definition =
|
|
126640
|
+
const definition = path294.resolve(root, candidate);
|
|
126340
126641
|
if (existsSync60(definition)) {
|
|
126341
|
-
ruleFiles[driver.reviewer] =
|
|
126642
|
+
ruleFiles[driver.reviewer] = path294.relative(root, definition);
|
|
126342
126643
|
break;
|
|
126343
126644
|
}
|
|
126344
126645
|
}
|
|
@@ -126352,7 +126653,7 @@ var init_driver_read = __esm(() => {
|
|
|
126352
126653
|
// src/review/wave-drivers.ts
|
|
126353
126654
|
import { existsSync as existsSync61 } from "fs";
|
|
126354
126655
|
import { mkdir as mkdir71, readFile as readFile137, readdir as readdir51, rm as rm23 } from "fs/promises";
|
|
126355
|
-
import
|
|
126656
|
+
import path295 from "path";
|
|
126356
126657
|
async function packageReviewers(packageDir, manifest, options = {}) {
|
|
126357
126658
|
const root = projectRootOfPackage(packageDir);
|
|
126358
126659
|
const names = new Set;
|
|
@@ -126376,21 +126677,21 @@ async function packageReviewers(packageDir, manifest, options = {}) {
|
|
|
126376
126677
|
async function writeWaveDrivers(input) {
|
|
126377
126678
|
const { packageDir, manifest, sliceDir } = input;
|
|
126378
126679
|
const root = projectRootOfPackage(packageDir);
|
|
126379
|
-
const dir =
|
|
126680
|
+
const dir = path295.join(packageDir, DRIVERS_DIR);
|
|
126380
126681
|
await mkdir71(dir, { recursive: true });
|
|
126381
126682
|
const kept = new Map;
|
|
126382
126683
|
for (const name of existsSync61(dir) ? await readdir51(dir) : []) {
|
|
126383
126684
|
if (!/-w\d+\.json$/.test(name))
|
|
126384
126685
|
continue;
|
|
126385
126686
|
try {
|
|
126386
|
-
kept.set(name, JSON.parse(await readFile137(
|
|
126687
|
+
kept.set(name, JSON.parse(await readFile137(path295.join(dir, name), "utf8")));
|
|
126387
126688
|
} catch {}
|
|
126388
|
-
await rm23(
|
|
126689
|
+
await rm23(path295.join(dir, name), { force: true });
|
|
126389
126690
|
}
|
|
126390
126691
|
const written = [];
|
|
126391
126692
|
for (const reviewer of [...input.reviewers].sort()) {
|
|
126392
126693
|
const waves = roleDispatchWaves(manifest, reviewer);
|
|
126393
|
-
const definition = DEFINITION_CANDIDATES(reviewer).find((candidate) => existsSync61(
|
|
126694
|
+
const definition = DEFINITION_CANDIDATES(reviewer).find((candidate) => existsSync61(path295.resolve(root, candidate)));
|
|
126394
126695
|
for (const [at, wave] of waves.entries()) {
|
|
126395
126696
|
const number = at + 1;
|
|
126396
126697
|
const name = `${reviewer}-w${number}.json`;
|
|
@@ -126400,15 +126701,15 @@ async function writeWaveDrivers(input) {
|
|
|
126400
126701
|
wave: number,
|
|
126401
126702
|
waves: waves.length,
|
|
126402
126703
|
slices: wave.map((entry) => entry.id),
|
|
126403
|
-
slicePaths: wave.map((entry) => ({ id: entry.id, path:
|
|
126704
|
+
slicePaths: wave.map((entry) => ({ id: entry.id, path: path295.resolve(sliceDir, entry.path), bytes: entry.bytes })),
|
|
126404
126705
|
result: `${RAW_DIR}/${reviewer}-w${number}.txt`,
|
|
126405
126706
|
...definition === undefined ? {} : { definition },
|
|
126406
126707
|
...typeof prior?.model === "string" && prior.model.trim() !== "" ? { model: prior.model } : {},
|
|
126407
126708
|
...typeof prior?.toolLog === "string" && prior.toolLog.trim() !== "" ? { toolLog: prior.toolLog } : {}
|
|
126408
126709
|
};
|
|
126409
|
-
await writeFileAtomic(
|
|
126710
|
+
await writeFileAtomic(path295.join(dir, name), `${JSON.stringify(driver, null, 2)}
|
|
126410
126711
|
`);
|
|
126411
|
-
written.push(
|
|
126712
|
+
written.push(path295.join(dir, name));
|
|
126412
126713
|
}
|
|
126413
126714
|
}
|
|
126414
126715
|
return written;
|
|
@@ -126428,7 +126729,7 @@ var init_wave_drivers = __esm(() => {
|
|
|
126428
126729
|
// src/commands/review-plan.ts
|
|
126429
126730
|
import { existsSync as existsSync62 } from "fs";
|
|
126430
126731
|
import { readFile as readFile138 } from "fs/promises";
|
|
126431
|
-
import
|
|
126732
|
+
import path296 from "path";
|
|
126432
126733
|
function printPlanHelp() {
|
|
126433
126734
|
console.log(`keryx review plan --target <pr-url|branch> [--ref <base>] [--head <sha>] [--host ${REVIEW_HOSTS.join("|")}]
|
|
126434
126735
|
[--memory-query "<query>"] [--unattended] [--json]
|
|
@@ -126461,7 +126762,7 @@ function packagePathOf(startOutput, cwd) {
|
|
|
126461
126762
|
if (line === undefined)
|
|
126462
126763
|
return;
|
|
126463
126764
|
const value = line.slice("path: ".length).trim();
|
|
126464
|
-
return value === "" ? undefined :
|
|
126765
|
+
return value === "" ? undefined : path296.resolve(cwd, value);
|
|
126465
126766
|
}
|
|
126466
126767
|
function slicesByKind(manifest) {
|
|
126467
126768
|
const originals = manifest.slices.filter((entry) => entry.retryOf === undefined);
|
|
@@ -126485,7 +126786,7 @@ function renderPlanSummary(plan) {
|
|
|
126485
126786
|
`estimate: ${plan.dispatches} dispatches, ${plan.minutes.min}-${plan.minutes.max} min${plan.overThreshold ? ` \u2014 over threshold (${plan.overReasons.join("; ")})` : ""}`,
|
|
126486
126787
|
...plan.warnings.map((warning) => `warning: ${warning}`),
|
|
126487
126788
|
`next: ${plan.next}`,
|
|
126488
|
-
`written: ${
|
|
126789
|
+
`written: ${path296.join(plan.package, PLAN_FILE)}`
|
|
126489
126790
|
];
|
|
126490
126791
|
return lines.join(`
|
|
126491
126792
|
`);
|
|
@@ -126532,7 +126833,7 @@ async function runPlan2(args, deps) {
|
|
|
126532
126833
|
const scoped = await deps.run(["scope", "--json", ...baseRef === undefined ? [] : ["--ref", baseRef, ...head === undefined ? [] : ["--head", head]]]);
|
|
126533
126834
|
if (scoped.exitCode !== 0)
|
|
126534
126835
|
fail2("scope", scoped);
|
|
126535
|
-
const scopeFile =
|
|
126836
|
+
const scopeFile = path296.join(packageDir, "scope.json");
|
|
126536
126837
|
await writeFileAtomic(scopeFile, scoped.stdout.endsWith(`
|
|
126537
126838
|
`) ? scoped.stdout : `${scoped.stdout}
|
|
126538
126839
|
`);
|
|
@@ -126543,19 +126844,19 @@ async function runPlan2(args, deps) {
|
|
|
126543
126844
|
throw new Error("review plan: `review scope --json` printed no JSON; the plan stops here.");
|
|
126544
126845
|
}
|
|
126545
126846
|
const scopeFiles = Array.isArray(scopeRecord?.files) ? scopeRecord.files.filter((file) => typeof file === "string") : [];
|
|
126546
|
-
if (!existsSync62(
|
|
126847
|
+
if (!existsSync62(path296.join(packageDir, "scope-files.json"))) {
|
|
126547
126848
|
const lines = scopeRecord?.counts?.changedLinesRetained;
|
|
126548
|
-
await writeFileAtomic(
|
|
126849
|
+
await writeFileAtomic(path296.join(packageDir, "scope-files.json"), `${JSON.stringify({ version: 1, files: scopeFiles, ...typeof lines === "number" ? { changedLines: lines } : {} }, null, 2)}
|
|
126549
126850
|
`);
|
|
126550
126851
|
}
|
|
126551
|
-
const radius = await deps.run(["blast-radius", "--package", packageDir, "--json", "--out",
|
|
126852
|
+
const radius = await deps.run(["blast-radius", "--package", packageDir, "--json", "--out", path296.join(packageDir, "blast-radius.json"), ...baseRef === undefined ? [] : ["--ref", baseRef]]);
|
|
126552
126853
|
if (radius.exitCode !== 0)
|
|
126553
126854
|
warnings.push(`blast radius not computed (${(radius.stderr.trim() || radius.stdout.trim()).split(`
|
|
126554
126855
|
`).pop() ?? `exit ${radius.exitCode}`})`);
|
|
126555
126856
|
const sliced = await deps.run(["slice", "--package", packageDir, ...baseRef === undefined || delta !== undefined ? [] : ["--ref", baseRef], ...head === undefined ? [] : ["--head", head], ...host === undefined ? [] : ["--host", host], "--json"]);
|
|
126556
126857
|
if (sliced.exitCode !== 0)
|
|
126557
126858
|
fail2("slice", sliced);
|
|
126558
|
-
const manifestPath =
|
|
126859
|
+
const manifestPath = path296.join(packageDir, "slices", "manifest.json");
|
|
126559
126860
|
const manifest = JSON.parse(await readFile138(manifestPath, "utf8"));
|
|
126560
126861
|
const required = await deps.run(["required", "--depth", "auto", "--scope", scopeFile, "--package", packageDir, "--json"]);
|
|
126561
126862
|
if (required.exitCode !== 0)
|
|
@@ -126568,7 +126869,7 @@ async function runPlan2(args, deps) {
|
|
|
126568
126869
|
throw new Error("review plan: `review required --json` printed no JSON; the plan stops here.");
|
|
126569
126870
|
}
|
|
126570
126871
|
const estimate = await estimatePackage(packageDir);
|
|
126571
|
-
const drivers = await writeWaveDrivers({ packageDir, manifest, sliceDir:
|
|
126872
|
+
const drivers = await writeWaveDrivers({ packageDir, manifest, sliceDir: path296.dirname(manifestPath), reviewers: await packageReviewers(packageDir, manifest) });
|
|
126572
126873
|
if (drivers.length !== estimate.dispatches)
|
|
126573
126874
|
warnings.push(`${drivers.length} driver(s) written for ${estimate.dispatches} planned dispatch(es); re-run \`keryx review slice --package ${packageDir}\``);
|
|
126574
126875
|
const confirmed = planConfirmed(packageDir, estimate);
|
|
@@ -126600,7 +126901,7 @@ async function runPlan2(args, deps) {
|
|
|
126600
126901
|
warnings,
|
|
126601
126902
|
next: nextStepOf(estimate, confirmed)
|
|
126602
126903
|
};
|
|
126603
|
-
await writeFileAtomic(
|
|
126904
|
+
await writeFileAtomic(path296.join(packageDir, PLAN_FILE), `${JSON.stringify(plan, null, 2)}
|
|
126604
126905
|
`);
|
|
126605
126906
|
console.log(args.includes("--json") ? JSON.stringify(plan, null, 2) : renderPlanSummary(plan));
|
|
126606
126907
|
}
|
|
@@ -126750,7 +127051,7 @@ var init_plan_eval = __esm(() => {
|
|
|
126750
127051
|
// src/commands/review-plan-eval.ts
|
|
126751
127052
|
import { existsSync as existsSync63 } from "fs";
|
|
126752
127053
|
import { readFile as readFile139 } from "fs/promises";
|
|
126753
|
-
import
|
|
127054
|
+
import path297 from "path";
|
|
126754
127055
|
function printPlanEvalHelp() {
|
|
126755
127056
|
console.log(`keryx review plan-eval \u2014 usage:
|
|
126756
127057
|
|
|
@@ -126810,7 +127111,7 @@ async function runPlanEval(args, deps) {
|
|
|
126810
127111
|
throw new Error("plan-eval needs --diff <file|base..head>. See `keryx review plan-eval --help`.");
|
|
126811
127112
|
const diff = await readDiff(source);
|
|
126812
127113
|
const findingsFile = optionValue(args, "--findings");
|
|
126813
|
-
const findings = findingsFile === undefined ? undefined : parseGroundTruth(await readFile139(
|
|
127114
|
+
const findings = findingsFile === undefined ? undefined : parseGroundTruth(await readFile139(path297.resolve(findingsFile), "utf8"));
|
|
126814
127115
|
const rolesFlag = optionValue(args, "--roles");
|
|
126815
127116
|
let roles = rolesFlag?.split(",").map((role) => role.trim()).filter(Boolean);
|
|
126816
127117
|
if (roles === undefined) {
|
|
@@ -126868,7 +127169,7 @@ var init_retired_rules = __esm(() => {
|
|
|
126868
127169
|
import { cp as cp2, lstat as lstat19, readFile as readFile140, readdir as readdir52, realpath as realpath26, unlink as unlink2 } from "fs/promises";
|
|
126869
127170
|
import { existsSync as existsSync64 } from "fs";
|
|
126870
127171
|
import { createHash as createHash83 } from "crypto";
|
|
126871
|
-
import
|
|
127172
|
+
import path298 from "path";
|
|
126872
127173
|
import { fileURLToPath as fileURLToPath5 } from "url";
|
|
126873
127174
|
function isErrnoException(error) {
|
|
126874
127175
|
return error instanceof Error && "code" in error;
|
|
@@ -126884,7 +127185,7 @@ function bundledSkillSourcePath(category, skillName) {
|
|
|
126884
127185
|
if (existsSync64(directPath)) {
|
|
126885
127186
|
return directPath;
|
|
126886
127187
|
}
|
|
126887
|
-
const packagedSourcePath =
|
|
127188
|
+
const packagedSourcePath = path298.join(path298.dirname(fileURLToPath5(import.meta.url)), "..", "src", "gdskills", "bundled", "skills", category, skillName);
|
|
126888
127189
|
if (existsSync64(packagedSourcePath)) {
|
|
126889
127190
|
return packagedSourcePath;
|
|
126890
127191
|
}
|
|
@@ -126905,7 +127206,7 @@ async function removeStaleRuntimeBuilds(skillsRoot, fsOps = { unlink: unlink2 })
|
|
|
126905
127206
|
}
|
|
126906
127207
|
const categories = new Map;
|
|
126907
127208
|
for (const skillEntry of BUNDLED_GDSKILLS) {
|
|
126908
|
-
const categoryDir =
|
|
127209
|
+
const categoryDir = path298.join(skillsRoot, skillEntry.category);
|
|
126909
127210
|
let category = categories.get(categoryDir);
|
|
126910
127211
|
if (!category) {
|
|
126911
127212
|
category = await resolveSweepableDir(categoryDir);
|
|
@@ -126916,7 +127217,7 @@ async function removeStaleRuntimeBuilds(skillsRoot, fsOps = { unlink: unlink2 })
|
|
|
126916
127217
|
}
|
|
126917
127218
|
if (category.kind !== "usable")
|
|
126918
127219
|
continue;
|
|
126919
|
-
const installedDir =
|
|
127220
|
+
const installedDir = path298.join(categoryDir, skillEntry.name);
|
|
126920
127221
|
const skillDir = await resolveSweepableDir(installedDir);
|
|
126921
127222
|
if (skillDir.kind === "missing") {
|
|
126922
127223
|
continue;
|
|
@@ -126925,7 +127226,7 @@ async function removeStaleRuntimeBuilds(skillsRoot, fsOps = { unlink: unlink2 })
|
|
|
126925
127226
|
outcomes.push(skillDir.outcome);
|
|
126926
127227
|
continue;
|
|
126927
127228
|
}
|
|
126928
|
-
if (skillDir.realPath !==
|
|
127229
|
+
if (skillDir.realPath !== path298.join(root.realPath, skillEntry.category, skillEntry.name)) {
|
|
126929
127230
|
outcomes.push({
|
|
126930
127231
|
path: installedDir,
|
|
126931
127232
|
blockedAt: installedDir,
|
|
@@ -126952,9 +127253,9 @@ async function removeStaleRuntimeBuilds(skillsRoot, fsOps = { unlink: unlink2 })
|
|
|
126952
127253
|
for (const entryName of entries) {
|
|
126953
127254
|
if (!RUNTIME_BUILD_FILE_NAMES.includes(entryName))
|
|
126954
127255
|
continue;
|
|
126955
|
-
if (existsSync64(
|
|
127256
|
+
if (existsSync64(path298.join(bundledDir, entryName)))
|
|
126956
127257
|
continue;
|
|
126957
|
-
const installedPath =
|
|
127258
|
+
const installedPath = path298.join(installedDir, entryName);
|
|
126958
127259
|
try {
|
|
126959
127260
|
const stats = await lstat19(installedPath);
|
|
126960
127261
|
if (!stats.isFile()) {
|
|
@@ -127042,12 +127343,12 @@ function unsweepableDirCause(outcome, projectRoot) {
|
|
|
127042
127343
|
}
|
|
127043
127344
|
}
|
|
127044
127345
|
function displayPath2(target, projectRoot) {
|
|
127045
|
-
return
|
|
127346
|
+
return path298.relative(projectRoot, target).split(path298.sep).join("/");
|
|
127046
127347
|
}
|
|
127047
127348
|
async function removeUnmodifiedRetiredRules(rulesTarget, fsOps = { unlink: unlink2 }) {
|
|
127048
127349
|
const outcomes = [];
|
|
127049
127350
|
for (const retired of RETIRED_RULES) {
|
|
127050
|
-
const installedPath =
|
|
127351
|
+
const installedPath = path298.join(rulesTarget, retired.fileName);
|
|
127051
127352
|
let stage = "lstat";
|
|
127052
127353
|
try {
|
|
127053
127354
|
const stats = await lstat19(installedPath);
|
|
@@ -127123,10 +127424,10 @@ var init_guarded_fs_ops = __esm(() => {
|
|
|
127123
127424
|
// src/gdskills/install.ts
|
|
127124
127425
|
import { lstat as lstat20, readFile as readFile141, readlink as readlink2 } from "fs/promises";
|
|
127125
127426
|
import { existsSync as existsSync65 } from "fs";
|
|
127126
|
-
import
|
|
127427
|
+
import path299 from "path";
|
|
127127
127428
|
import { fileURLToPath as fileURLToPath6 } from "url";
|
|
127128
127429
|
async function mkdirContainedOrWarn(metaprojectRoot, target) {
|
|
127129
|
-
const rel =
|
|
127430
|
+
const rel = path299.relative(metaprojectRoot, target);
|
|
127130
127431
|
try {
|
|
127131
127432
|
await mkdirContained(metaprojectRoot, rel);
|
|
127132
127433
|
return null;
|
|
@@ -127139,20 +127440,20 @@ async function mkdirContainedOrWarn(metaprojectRoot, target) {
|
|
|
127139
127440
|
}
|
|
127140
127441
|
async function installGdskills(metaprojectRoot, profile, options = {}) {
|
|
127141
127442
|
const skills = getBundledSkillsForProfile(profile);
|
|
127142
|
-
const skillsRoot =
|
|
127143
|
-
const dataRoot =
|
|
127144
|
-
const coreRoot =
|
|
127145
|
-
const contractsRoot =
|
|
127146
|
-
const projectSkillsRoot =
|
|
127443
|
+
const skillsRoot = path299.join(metaprojectRoot, "skills", "gdskills");
|
|
127444
|
+
const dataRoot = path299.join(metaprojectRoot, "data", "gdskills");
|
|
127445
|
+
const coreRoot = path299.join(metaprojectRoot, "core", "gdskills");
|
|
127446
|
+
const contractsRoot = path299.join(coreRoot, "contracts");
|
|
127447
|
+
const projectSkillsRoot = path299.join(metaprojectRoot, metaprojectRelative(PROJECT_SKILLS_DIR));
|
|
127147
127448
|
const skippedDestinationWarnings = [];
|
|
127148
127449
|
for (const dir of [
|
|
127149
|
-
|
|
127150
|
-
|
|
127450
|
+
path299.join(metaprojectRoot, "jobs"),
|
|
127451
|
+
path299.join(metaprojectRoot, "modules"),
|
|
127151
127452
|
projectSkillsRoot,
|
|
127152
127453
|
...options.createDataDirs === false ? [] : [
|
|
127153
|
-
|
|
127154
|
-
|
|
127155
|
-
|
|
127454
|
+
path299.join(dataRoot, "artifacts"),
|
|
127455
|
+
path299.join(metaprojectRoot, metaprojectRelative(PROJECT_SKILL_REPORTS_DIR)),
|
|
127456
|
+
path299.join(dataRoot, "proposals")
|
|
127156
127457
|
]
|
|
127157
127458
|
]) {
|
|
127158
127459
|
const warning = await mkdirContainedOrWarn(metaprojectRoot, dir);
|
|
@@ -127161,8 +127462,8 @@ async function installGdskills(metaprojectRoot, profile, options = {}) {
|
|
|
127161
127462
|
}
|
|
127162
127463
|
let installedSkills = 0;
|
|
127163
127464
|
for (const skillEntry of skills) {
|
|
127164
|
-
const skillDir =
|
|
127165
|
-
const relSkillDir =
|
|
127465
|
+
const skillDir = path299.join(skillsRoot, skillEntry.category, skillEntry.name);
|
|
127466
|
+
const relSkillDir = path299.relative(metaprojectRoot, skillDir);
|
|
127166
127467
|
const destination = await checkInstallDestination(skillDir, relSkillDir);
|
|
127167
127468
|
if (!destination.usable) {
|
|
127168
127469
|
skippedDestinationWarnings.push(destination.warning);
|
|
@@ -127177,7 +127478,7 @@ async function installGdskills(metaprojectRoot, profile, options = {}) {
|
|
|
127177
127478
|
if (existsSync65(bundledSkillPath)) {
|
|
127178
127479
|
await copyDirectoryContained(bundledSkillPath, skillDir);
|
|
127179
127480
|
} else {
|
|
127180
|
-
await writeContained(metaprojectRoot,
|
|
127481
|
+
await writeContained(metaprojectRoot, path299.join(relSkillDir, "SKILL.md"), renderBundledSkill(skillEntry));
|
|
127181
127482
|
}
|
|
127182
127483
|
installedSkills += 1;
|
|
127183
127484
|
}
|
|
@@ -127191,12 +127492,12 @@ async function installGdskills(metaprojectRoot, profile, options = {}) {
|
|
|
127191
127492
|
skippedDestinationWarnings.push(rulesInstall.warning);
|
|
127192
127493
|
}
|
|
127193
127494
|
const retiredRuleOutcomes = rulesInstall.outcomes;
|
|
127194
|
-
const catalogPath =
|
|
127195
|
-
await writeContained(metaprojectRoot,
|
|
127196
|
-
const manifestPath =
|
|
127197
|
-
await writeContained(metaprojectRoot,
|
|
127495
|
+
const catalogPath = path299.join(metaprojectRoot, metaprojectRelative(PROJECT_SKILLS_CATALOG_PATH));
|
|
127496
|
+
await writeContained(metaprojectRoot, path299.relative(metaprojectRoot, catalogPath), await preserveProjectSkillsSection(catalogPath, renderGdskillsCatalog(profile)));
|
|
127497
|
+
const manifestPath = path299.join(metaprojectRoot, "modules", "gdskills.md");
|
|
127498
|
+
await writeContained(metaprojectRoot, path299.relative(metaprojectRoot, manifestPath), renderGdskillsManifest(profile));
|
|
127198
127499
|
skippedDestinationWarnings.push(...await installContracts(contractsRoot, metaprojectRoot));
|
|
127199
|
-
const projectRoot =
|
|
127500
|
+
const projectRoot = path299.dirname(metaprojectRoot);
|
|
127200
127501
|
const warnings = [
|
|
127201
127502
|
...skippedDestinationWarnings,
|
|
127202
127503
|
...retiredRuleOutcomes.map(retiredRuleWarning).filter((warning) => warning !== null),
|
|
@@ -127218,8 +127519,8 @@ async function installBundledSharedSkills(skillsRoot, metaprojectRoot) {
|
|
|
127218
127519
|
if (!existsSync65(sharedSource)) {
|
|
127219
127520
|
return null;
|
|
127220
127521
|
}
|
|
127221
|
-
const sharedTarget =
|
|
127222
|
-
const relSharedTarget =
|
|
127522
|
+
const sharedTarget = path299.join(skillsRoot, "shared");
|
|
127523
|
+
const relSharedTarget = path299.relative(metaprojectRoot, sharedTarget);
|
|
127223
127524
|
const destination = await checkInstallDestination(sharedTarget, relSharedTarget);
|
|
127224
127525
|
if (!destination.usable) {
|
|
127225
127526
|
return destination.warning;
|
|
@@ -127262,11 +127563,11 @@ async function checkInstallDestination(destination, displayPath) {
|
|
|
127262
127563
|
}
|
|
127263
127564
|
async function installBundledRules(metaprojectRoot) {
|
|
127264
127565
|
const rulesSource = bundledRulesSourcePath();
|
|
127265
|
-
const rulesTarget =
|
|
127566
|
+
const rulesTarget = path299.join(metaprojectRoot, "rules", "core");
|
|
127266
127567
|
if (!existsSync65(rulesSource)) {
|
|
127267
127568
|
return { outcomes: [], warning: null };
|
|
127268
127569
|
}
|
|
127269
|
-
const relRulesTarget =
|
|
127570
|
+
const relRulesTarget = path299.relative(metaprojectRoot, rulesTarget);
|
|
127270
127571
|
const destination = await checkInstallDestination(rulesTarget, relRulesTarget);
|
|
127271
127572
|
if (!destination.usable) {
|
|
127272
127573
|
return { outcomes: [], warning: destination.warning };
|
|
@@ -127297,13 +127598,13 @@ ${section}
|
|
|
127297
127598
|
`;
|
|
127298
127599
|
}
|
|
127299
127600
|
async function installContracts(contractsRoot, metaprojectRoot) {
|
|
127300
|
-
const rootCheck = await checkInstallDestination(contractsRoot,
|
|
127601
|
+
const rootCheck = await checkInstallDestination(contractsRoot, path299.relative(metaprojectRoot, contractsRoot));
|
|
127301
127602
|
if (!rootCheck.usable) {
|
|
127302
127603
|
return [rootCheck.warning];
|
|
127303
127604
|
}
|
|
127304
127605
|
const results = await Promise.all(CONTRACTS.map(async (contract) => {
|
|
127305
|
-
const destination =
|
|
127306
|
-
const relDestination =
|
|
127606
|
+
const destination = path299.join(contractsRoot, contract.fileName);
|
|
127607
|
+
const relDestination = path299.relative(metaprojectRoot, destination);
|
|
127307
127608
|
const check = await checkInstallFile(destination, relDestination);
|
|
127308
127609
|
if (!check.usable) {
|
|
127309
127610
|
return check.warning;
|
|
@@ -127348,7 +127649,7 @@ function contractSourcePath(fileName) {
|
|
|
127348
127649
|
if (existsSync65(directPath)) {
|
|
127349
127650
|
return directPath;
|
|
127350
127651
|
}
|
|
127351
|
-
const packagedSourcePath =
|
|
127652
|
+
const packagedSourcePath = path299.join(path299.dirname(fileURLToPath6(import.meta.url)), "..", "src", "gdskills", "contracts", fileName);
|
|
127352
127653
|
if (existsSync65(packagedSourcePath)) {
|
|
127353
127654
|
return packagedSourcePath;
|
|
127354
127655
|
}
|
|
@@ -127359,7 +127660,7 @@ function bundledSharedSourcePath() {
|
|
|
127359
127660
|
if (existsSync65(directPath)) {
|
|
127360
127661
|
return directPath;
|
|
127361
127662
|
}
|
|
127362
|
-
const packagedSourcePath =
|
|
127663
|
+
const packagedSourcePath = path299.join(path299.dirname(fileURLToPath6(import.meta.url)), "..", "src", "gdskills", "bundled", "skills", "shared");
|
|
127363
127664
|
if (existsSync65(packagedSourcePath)) {
|
|
127364
127665
|
return packagedSourcePath;
|
|
127365
127666
|
}
|
|
@@ -127370,7 +127671,7 @@ function bundledRulesSourcePath() {
|
|
|
127370
127671
|
if (existsSync65(directPath)) {
|
|
127371
127672
|
return directPath;
|
|
127372
127673
|
}
|
|
127373
|
-
const packagedSourcePath =
|
|
127674
|
+
const packagedSourcePath = path299.join(path299.dirname(fileURLToPath6(import.meta.url)), "..", "src", "gdskills", "bundled", "rules", "core");
|
|
127374
127675
|
if (existsSync65(packagedSourcePath)) {
|
|
127375
127676
|
return packagedSourcePath;
|
|
127376
127677
|
}
|
|
@@ -127388,7 +127689,7 @@ var init_install2 = __esm(() => {
|
|
|
127388
127689
|
// src/gdskills/import-skills.ts
|
|
127389
127690
|
import { lstat as lstat21, readdir as readdir53, readFile as readFile142, stat as stat33 } from "fs/promises";
|
|
127390
127691
|
import { homedir as homedir11 } from "os";
|
|
127391
|
-
import
|
|
127692
|
+
import path300 from "path";
|
|
127392
127693
|
function emptyOnlyError(label) {
|
|
127393
127694
|
return new Error(`${label}: --only needs a glob; an empty value selects nothing. Pass a package directory name or a glob over them (\`*\`, \`?\`), or drop --only.`);
|
|
127394
127695
|
}
|
|
@@ -127777,17 +128078,17 @@ function refuseSharedDestinations(label, picked) {
|
|
|
127777
128078
|
}
|
|
127778
128079
|
}
|
|
127779
128080
|
function isInside6(parent, child) {
|
|
127780
|
-
const relative =
|
|
127781
|
-
return relative !== "" && relative !== ".." && !relative.startsWith(`..${
|
|
128081
|
+
const relative = path300.relative(parent, child);
|
|
128082
|
+
return relative !== "" && relative !== ".." && !relative.startsWith(`..${path300.sep}`) && !path300.isAbsolute(relative);
|
|
127782
128083
|
}
|
|
127783
128084
|
function portableOriginRef(absolute, projectRoot, home = homedir11()) {
|
|
127784
|
-
const resolved =
|
|
127785
|
-
const root =
|
|
128085
|
+
const resolved = path300.resolve(absolute);
|
|
128086
|
+
const root = path300.resolve(projectRoot);
|
|
127786
128087
|
if (isInside6(root, resolved)) {
|
|
127787
|
-
return toPosix(
|
|
128088
|
+
return toPosix(path300.relative(root, resolved));
|
|
127788
128089
|
}
|
|
127789
128090
|
if (isInside6(home, resolved)) {
|
|
127790
|
-
return `~/${toPosix(
|
|
128091
|
+
return `~/${toPosix(path300.relative(home, resolved))}`;
|
|
127791
128092
|
}
|
|
127792
128093
|
return resolved;
|
|
127793
128094
|
}
|
|
@@ -127850,8 +128151,8 @@ async function sourceFromDirectory(options, absolute) {
|
|
|
127850
128151
|
const { dirs, tree } = await skillPackageDirs(absolute);
|
|
127851
128152
|
const candidates = [];
|
|
127852
128153
|
for (const dir of dirs) {
|
|
127853
|
-
const skillMd =
|
|
127854
|
-
const name =
|
|
128154
|
+
const skillMd = path300.join(dir, "SKILL.md");
|
|
128155
|
+
const name = path300.basename(dir);
|
|
127855
128156
|
candidates.push({ name, slug: projectSkillSlug(name), skillMd, content: await readFile142(skillMd, "utf8") });
|
|
127856
128157
|
}
|
|
127857
128158
|
candidates.sort((a, b) => a.name.localeCompare(b.name));
|
|
@@ -127904,18 +128205,18 @@ ${candidateList(selected)}`);
|
|
|
127904
128205
|
return picked.map(({ source }) => source).sort((a, b) => projectSkillKey(a).localeCompare(projectSkillKey(b)));
|
|
127905
128206
|
}
|
|
127906
128207
|
async function skillPackageDirs(root) {
|
|
127907
|
-
if (await pathExists(
|
|
128208
|
+
if (await pathExists(path300.join(root, "SKILL.md"))) {
|
|
127908
128209
|
return { dirs: [root], tree: false };
|
|
127909
128210
|
}
|
|
127910
|
-
const nestedSkills =
|
|
128211
|
+
const nestedSkills = path300.join(root, "skills");
|
|
127911
128212
|
const scan = await pathExists(nestedSkills) ? nestedSkills : root;
|
|
127912
128213
|
const entries = await readdir53(scan, { withFileTypes: true });
|
|
127913
128214
|
const dirs = [];
|
|
127914
128215
|
for (const entry of entries) {
|
|
127915
128216
|
if (!entry.isDirectory())
|
|
127916
128217
|
continue;
|
|
127917
|
-
const dir =
|
|
127918
|
-
if (await pathExists(
|
|
128218
|
+
const dir = path300.join(scan, entry.name);
|
|
128219
|
+
if (await pathExists(path300.join(dir, "SKILL.md"))) {
|
|
127919
128220
|
dirs.push(dir);
|
|
127920
128221
|
}
|
|
127921
128222
|
}
|
|
@@ -128027,7 +128328,7 @@ function refusedSkillRow(base, verdict, dryRun) {
|
|
|
128027
128328
|
}
|
|
128028
128329
|
async function planOne(options, source) {
|
|
128029
128330
|
const dest = projectSkillPackagePath(source.module, source.name);
|
|
128030
|
-
const destAbs =
|
|
128331
|
+
const destAbs = path300.join(options.projectRoot, dest, "SKILL.md");
|
|
128031
128332
|
const exists = await pathExists(destAbs);
|
|
128032
128333
|
const bundled = BUNDLED_NAMES.has(source.name);
|
|
128033
128334
|
if (skippedAsDeprecated(source)) {
|
|
@@ -128067,7 +128368,7 @@ async function planOne(options, source) {
|
|
|
128067
128368
|
}
|
|
128068
128369
|
};
|
|
128069
128370
|
}
|
|
128070
|
-
const verdict = await gateText(options.projectRoot, plannedSkillText(source.content, { name: source.name, module: source.module, origin: source.origin }), toPosix(
|
|
128371
|
+
const verdict = await gateText(options.projectRoot, plannedSkillText(source.content, { name: source.name, module: source.module, origin: source.origin }), toPosix(path300.join(dest, "SKILL.md")), options.allowFlagged === true);
|
|
128071
128372
|
if (verdict.kind !== "write") {
|
|
128072
128373
|
return {
|
|
128073
128374
|
row: refusedSkillRow({ name: source.name, module: source.module, path: dest, origin: source.origin }, verdict, options.dryRun === true)
|
|
@@ -128120,7 +128421,7 @@ function mergeNotes(notes, parts) {
|
|
|
128120
128421
|
};
|
|
128121
128422
|
}
|
|
128122
128423
|
async function planUpdate(options, entry) {
|
|
128123
|
-
const skillMd =
|
|
128424
|
+
const skillMd = path300.join(options.projectRoot, entry.path, "SKILL.md");
|
|
128124
128425
|
const current = await readFile142(skillMd, "utf8");
|
|
128125
128426
|
let origin = options.from ?? metadataLine2(current, "Origin");
|
|
128126
128427
|
const skippedRow = (reason, recorded) => ({
|
|
@@ -128147,7 +128448,7 @@ async function planUpdate(options, entry) {
|
|
|
128147
128448
|
sourcePath = resolved;
|
|
128148
128449
|
origin = portableOriginRef(resolved, options.projectRoot);
|
|
128149
128450
|
}
|
|
128150
|
-
const verdict = await gateText(options.projectRoot, plannedSkillText(content, { name: entry.name, module: entry.module, origin }), toPosix(
|
|
128451
|
+
const verdict = await gateText(options.projectRoot, plannedSkillText(content, { name: entry.name, module: entry.module, origin }), toPosix(path300.join(entry.path, "SKILL.md")), options.allowFlagged === true);
|
|
128151
128452
|
if (verdict.kind !== "write") {
|
|
128152
128453
|
return {
|
|
128153
128454
|
row: refusedSkillRow({ name: entry.name, module: entry.module, path: entry.path, origin }, verdict, options.dryRun === true)
|
|
@@ -128191,7 +128492,7 @@ async function writeUpdate(options, { entry, origin, content, gate }) {
|
|
|
128191
128492
|
};
|
|
128192
128493
|
}
|
|
128193
128494
|
async function writeGatedSkill(projectRoot, created, gate) {
|
|
128194
|
-
await writeContained(projectRoot, toPosix(
|
|
128495
|
+
await writeContained(projectRoot, toPosix(path300.join(created.skillPath, "SKILL.md")), gate.content);
|
|
128195
128496
|
}
|
|
128196
128497
|
function stampImportHeader(source, header) {
|
|
128197
128498
|
if (!header)
|
|
@@ -128251,15 +128552,15 @@ function versionOption(content) {
|
|
|
128251
128552
|
return version ? { version } : {};
|
|
128252
128553
|
}
|
|
128253
128554
|
async function findOverlayRule(sourcePath, ref) {
|
|
128254
|
-
let dir =
|
|
128555
|
+
let dir = path300.dirname(sourcePath);
|
|
128255
128556
|
let notRegular;
|
|
128256
128557
|
for (let depth = 0;depth <= 3; depth += 1) {
|
|
128257
|
-
const candidate =
|
|
128558
|
+
const candidate = path300.join(dir, "rules", ref);
|
|
128258
128559
|
if (await isRegularFile(candidate))
|
|
128259
128560
|
return { found: candidate };
|
|
128260
128561
|
if (notRegular === undefined && await pathExists(candidate))
|
|
128261
128562
|
notRegular = candidate;
|
|
128262
|
-
const parent =
|
|
128563
|
+
const parent = path300.dirname(dir);
|
|
128263
128564
|
if (parent === dir)
|
|
128264
128565
|
break;
|
|
128265
128566
|
dir = parent;
|
|
@@ -128333,7 +128634,7 @@ async function decideRule(options, citation, bundledRules) {
|
|
|
128333
128634
|
const base = { ref: citation.ref, citedBy: citation.citedBy };
|
|
128334
128635
|
const resolution = await resolveRuleReference(options.projectRoot, citation.ref);
|
|
128335
128636
|
const existing = resolution.resolved;
|
|
128336
|
-
const shipsWithKeryx = citation.ref.startsWith(`${KERYX_RULES_DIR}/`) && await pathExists(
|
|
128637
|
+
const shipsWithKeryx = citation.ref.startsWith(`${KERYX_RULES_DIR}/`) && await pathExists(path300.join(bundledRules, path300.basename(citation.ref)));
|
|
128337
128638
|
const notRegularFiles = resolution.notRegularFiles ?? [];
|
|
128338
128639
|
const notRegularReason = (candidate) => `${candidate} is not a regular file; nothing was written there`;
|
|
128339
128640
|
let found;
|
|
@@ -128354,7 +128655,7 @@ async function decideRule(options, citation, bundledRules) {
|
|
|
128354
128655
|
}
|
|
128355
128656
|
const origin = portableOriginRef(found, options.projectRoot);
|
|
128356
128657
|
const content = await readFile142(found, "utf8");
|
|
128357
|
-
const existingContent = existing !== undefined ? await readFile142(
|
|
128658
|
+
const existingContent = existing !== undefined ? await readFile142(path300.join(options.projectRoot, existing), "utf8") : undefined;
|
|
128358
128659
|
if (existing !== undefined && existingContent === content) {
|
|
128359
128660
|
return { row: { ...base, status: "present", origin, existing } };
|
|
128360
128661
|
}
|
|
@@ -128410,7 +128711,7 @@ async function decideReferencedRules(options, sources, refusedPackages) {
|
|
|
128410
128711
|
return decisions;
|
|
128411
128712
|
}
|
|
128412
128713
|
async function listTemplateFiles(sourcePath) {
|
|
128413
|
-
const root =
|
|
128714
|
+
const root = path300.join(path300.dirname(sourcePath), TEMPLATES_DIR);
|
|
128414
128715
|
try {
|
|
128415
128716
|
if (!(await lstat21(root)).isDirectory())
|
|
128416
128717
|
return [];
|
|
@@ -128423,7 +128724,7 @@ async function listTemplateFiles(sourcePath) {
|
|
|
128423
128724
|
for (const entry of entries) {
|
|
128424
128725
|
const rel = prefix === "" ? entry.name : `${prefix}/${entry.name}`;
|
|
128425
128726
|
if (entry.isDirectory())
|
|
128426
|
-
await walk(
|
|
128727
|
+
await walk(path300.join(dir, entry.name), rel);
|
|
128427
128728
|
else if (entry.isFile())
|
|
128428
128729
|
files.push(rel);
|
|
128429
128730
|
}
|
|
@@ -128435,7 +128736,7 @@ async function fileInTheWay(projectRoot, target) {
|
|
|
128435
128736
|
let current = projectRoot;
|
|
128436
128737
|
let relative = "";
|
|
128437
128738
|
for (const part of target.split("/").slice(0, -1)) {
|
|
128438
|
-
current =
|
|
128739
|
+
current = path300.join(current, part);
|
|
128439
128740
|
relative = relative === "" ? part : `${relative}/${part}`;
|
|
128440
128741
|
try {
|
|
128441
128742
|
if (!(await stat33(current)).isDirectory())
|
|
@@ -128472,13 +128773,13 @@ async function planTemplates(options, pkg, replace) {
|
|
|
128472
128773
|
for (const file of await listTemplateFiles(pkg.sourcePath)) {
|
|
128473
128774
|
const target = `${packagePath}/${TEMPLATES_DIR}/${file}`;
|
|
128474
128775
|
const base = { package: packageKey, file, path: target };
|
|
128475
|
-
const bytes = await readFile142(
|
|
128776
|
+
const bytes = await readFile142(path300.join(path300.dirname(pkg.sourcePath), TEMPLATES_DIR, file));
|
|
128476
128777
|
const text = bytes.toString("utf8");
|
|
128477
128778
|
if (!Buffer.from(text, "utf8").equals(bytes)) {
|
|
128478
128779
|
plans.push({ row: { ...base, status: "skipped", reason: "not UTF-8 text; the importer copies text templates only" } });
|
|
128479
128780
|
continue;
|
|
128480
128781
|
}
|
|
128481
|
-
const targetAbs =
|
|
128782
|
+
const targetAbs = path300.join(options.projectRoot, target);
|
|
128482
128783
|
const blocker = await fileInTheWay(options.projectRoot, target);
|
|
128483
128784
|
if (blocker !== undefined) {
|
|
128484
128785
|
plans.push({ row: { ...base, status: "skipped", reason: `${blocker} is a file, not a directory; nothing was written there` } });
|
|
@@ -128558,10 +128859,10 @@ function inferNameFromPath(filePath, content) {
|
|
|
128558
128859
|
const fromFrontmatter = frontmatterName(content);
|
|
128559
128860
|
if (fromFrontmatter)
|
|
128560
128861
|
return fromFrontmatter;
|
|
128561
|
-
const parent =
|
|
128862
|
+
const parent = path300.basename(path300.dirname(filePath));
|
|
128562
128863
|
if (parent && parent !== "skills" && parent !== ".")
|
|
128563
128864
|
return parent;
|
|
128564
|
-
return
|
|
128865
|
+
return path300.basename(filePath, path300.extname(filePath));
|
|
128565
128866
|
}
|
|
128566
128867
|
function inferNameFromUrl(url) {
|
|
128567
128868
|
const cleaned = url.replace(/\/SKILL\.md$/i, "");
|
|
@@ -128588,7 +128889,7 @@ function metadataLine2(content, label) {
|
|
|
128588
128889
|
}
|
|
128589
128890
|
async function readRegistry(projectRoot) {
|
|
128590
128891
|
await Promise.resolve().then(() => init_json());
|
|
128591
|
-
const manifestPath =
|
|
128892
|
+
const manifestPath = path300.join(projectRoot, PROJECT_SKILLS_MANIFEST_PATH);
|
|
128592
128893
|
const manifest = await readJsonFileOr(manifestPath, {});
|
|
128593
128894
|
return manifest.modules?.gdskills?.projectSkillRegistry ?? [];
|
|
128594
128895
|
}
|
|
@@ -128988,10 +129289,10 @@ var init_import_reviewers = __esm(() => {
|
|
|
128988
129289
|
|
|
128989
129290
|
// src/review/jev-rules-config.ts
|
|
128990
129291
|
import { readFile as readFile143 } from "fs/promises";
|
|
128991
|
-
import
|
|
129292
|
+
import path301 from "path";
|
|
128992
129293
|
async function readJevRulesEnabled(cwd) {
|
|
128993
129294
|
try {
|
|
128994
|
-
const parsed = JSON.parse(await readFile143(
|
|
129295
|
+
const parsed = JSON.parse(await readFile143(path301.join(cwd, REVIEW_GATE_CONFIG_PATH), "utf8"));
|
|
128995
129296
|
if (typeof parsed !== "object" || parsed === null)
|
|
128996
129297
|
return false;
|
|
128997
129298
|
const review = parsed["review"];
|
|
@@ -129011,7 +129312,7 @@ var init_jev_rules_config = __esm(() => {
|
|
|
129011
129312
|
|
|
129012
129313
|
// src/review/jev-rules-cache.ts
|
|
129013
129314
|
import { readFile as readFile144 } from "fs/promises";
|
|
129014
|
-
import
|
|
129315
|
+
import path302 from "path";
|
|
129015
129316
|
function hashCacheKey(input) {
|
|
129016
129317
|
let hash = 2166136261;
|
|
129017
129318
|
for (let i = 0;i < input.length; i += 1) {
|
|
@@ -129024,7 +129325,7 @@ function cacheKeyFor(ruleId, clauseId, regionPath, regionStartLine, regionEndLin
|
|
|
129024
129325
|
return hashCacheKey(`${ruleId}::${clauseId}::${regionPath}:${regionStartLine}-${regionEndLine}::${clauseText}`);
|
|
129025
129326
|
}
|
|
129026
129327
|
function cacheFilePath(cwd) {
|
|
129027
|
-
return
|
|
129328
|
+
return path302.join(cwd, JEV_RULES_CACHE_PATH);
|
|
129028
129329
|
}
|
|
129029
129330
|
async function readJevRulesCache(cwd) {
|
|
129030
129331
|
const file = cacheFilePath(cwd);
|
|
@@ -129854,14 +130155,14 @@ var init_jev_rules = __esm(() => {
|
|
|
129854
130155
|
});
|
|
129855
130156
|
|
|
129856
130157
|
// src/review/conform-tag-cache.ts
|
|
129857
|
-
import
|
|
130158
|
+
import path303 from "path";
|
|
129858
130159
|
import { readFile as readFile145 } from "fs/promises";
|
|
129859
130160
|
function hashConformDocContent(content) {
|
|
129860
130161
|
return hashOriginContent(`clause-tag-schema:v${CLAUSE_TAG_SCHEMA_VERSION}
|
|
129861
130162
|
${content}`);
|
|
129862
130163
|
}
|
|
129863
130164
|
function cacheFilePath2(cwd, cacheRelPath) {
|
|
129864
|
-
return
|
|
130165
|
+
return path303.join(cwd, cacheRelPath);
|
|
129865
130166
|
}
|
|
129866
130167
|
async function readClauseTagCache(cwd, cacheRelPath = CONFORM_TAG_CACHE_PATH) {
|
|
129867
130168
|
const file = cacheFilePath2(cwd, cacheRelPath);
|
|
@@ -130386,7 +130687,7 @@ function checkPullOpen(facts) {
|
|
|
130386
130687
|
|
|
130387
130688
|
// src/review/bot/packages.ts
|
|
130388
130689
|
import { readdir as readdir55, readFile as readFile147 } from "fs/promises";
|
|
130389
|
-
import
|
|
130690
|
+
import path304 from "path";
|
|
130390
130691
|
async function subdirectories(dir) {
|
|
130391
130692
|
try {
|
|
130392
130693
|
const entries = await readdir55(dir, { withFileTypes: true });
|
|
@@ -130409,12 +130710,12 @@ function pullFromTarget(target) {
|
|
|
130409
130710
|
}
|
|
130410
130711
|
async function readPackage(dir) {
|
|
130411
130712
|
try {
|
|
130412
|
-
const manifest = JSON.parse(await readFile147(
|
|
130713
|
+
const manifest = JSON.parse(await readFile147(path304.join(dir, "manifest.json"), "utf8"));
|
|
130413
130714
|
if (manifest.target?.kind !== "pr")
|
|
130414
130715
|
return null;
|
|
130415
130716
|
let findings = [];
|
|
130416
130717
|
try {
|
|
130417
|
-
const parsed = JSON.parse(await readFile147(
|
|
130718
|
+
const parsed = JSON.parse(await readFile147(path304.join(dir, "findings.json"), "utf8"));
|
|
130418
130719
|
if (Array.isArray(parsed))
|
|
130419
130720
|
findings = parsed;
|
|
130420
130721
|
} catch {
|
|
@@ -130429,16 +130730,16 @@ async function readPackage(dir) {
|
|
|
130429
130730
|
function candidateDirs2(cwd) {
|
|
130430
130731
|
return (async () => {
|
|
130431
130732
|
const dirs = [];
|
|
130432
|
-
const reviews =
|
|
130733
|
+
const reviews = path304.join(cwd, ".metaproject", "reviews");
|
|
130433
130734
|
for (const name of await subdirectories(reviews)) {
|
|
130434
130735
|
if (name !== "bot")
|
|
130435
|
-
dirs.push(
|
|
130736
|
+
dirs.push(path304.join(reviews, name));
|
|
130436
130737
|
}
|
|
130437
|
-
const flows =
|
|
130738
|
+
const flows = path304.join(cwd, ".metaproject", "flows");
|
|
130438
130739
|
for (const flow of await subdirectories(flows)) {
|
|
130439
|
-
const nested =
|
|
130740
|
+
const nested = path304.join(flows, flow, "reviews");
|
|
130440
130741
|
for (const name of await subdirectories(nested))
|
|
130441
|
-
dirs.push(
|
|
130742
|
+
dirs.push(path304.join(nested, name));
|
|
130442
130743
|
}
|
|
130443
130744
|
return dirs;
|
|
130444
130745
|
})();
|
|
@@ -130455,7 +130756,7 @@ async function listReviewPackages(cwd) {
|
|
|
130455
130756
|
}
|
|
130456
130757
|
async function loadReviewPackage(cwd, reviewId) {
|
|
130457
130758
|
for (const dir of await candidateDirs2(cwd)) {
|
|
130458
|
-
if (
|
|
130759
|
+
if (path304.basename(dir) !== reviewId)
|
|
130459
130760
|
continue;
|
|
130460
130761
|
const listed = await readPackage(dir);
|
|
130461
130762
|
if (listed !== null && listed.reviewId === reviewId)
|
|
@@ -130466,7 +130767,7 @@ async function loadReviewPackage(cwd, reviewId) {
|
|
|
130466
130767
|
var init_packages = () => {};
|
|
130467
130768
|
|
|
130468
130769
|
// src/review/bot/run.ts
|
|
130469
|
-
import
|
|
130770
|
+
import path305 from "path";
|
|
130470
130771
|
function capDiff(diff, maxBytes) {
|
|
130471
130772
|
const totalBytes = Buffer.byteLength(diff);
|
|
130472
130773
|
if (totalBytes <= maxBytes)
|
|
@@ -130511,7 +130812,7 @@ function parseVerifierVerdict(reply) {
|
|
|
130511
130812
|
return unverifiable;
|
|
130512
130813
|
}
|
|
130513
130814
|
function botStatePath(cwd, repo, number) {
|
|
130514
|
-
return
|
|
130815
|
+
return path305.join(cwd, ".metaproject", "reviews", "bot", `${repo.replace(/\//g, "__")}__${number}.json`);
|
|
130515
130816
|
}
|
|
130516
130817
|
function emptyBotState(repo, number) {
|
|
130517
130818
|
return { schemaVersion: 1, repo, number, reviews: [], pull: null };
|
|
@@ -131422,7 +131723,7 @@ var init_review_bot = __esm(() => {
|
|
|
131422
131723
|
});
|
|
131423
131724
|
|
|
131424
131725
|
// src/commands/review-run.ts
|
|
131425
|
-
import
|
|
131726
|
+
import path306 from "path";
|
|
131426
131727
|
function positive3(args, name, integer) {
|
|
131427
131728
|
const raw = optionValue(args, name);
|
|
131428
131729
|
if (raw === undefined) {
|
|
@@ -131527,7 +131828,7 @@ async function runReviewRunCommand(args, deps = {}) {
|
|
|
131527
131828
|
}
|
|
131528
131829
|
const cwd = deps.cwd ?? process.cwd();
|
|
131529
131830
|
const target = parsed.target;
|
|
131530
|
-
const notifier = deps.notifier ?? (parsed.notify ? createTelegramNotifier({ project:
|
|
131831
|
+
const notifier = deps.notifier ?? (parsed.notify ? createTelegramNotifier({ project: path306.basename(cwd), target }) : NO_NOTIFIER);
|
|
131531
131832
|
const run = deps.run ?? runAutonomousReview;
|
|
131532
131833
|
const heartbeat = createHeartbeat(error, deps.now, undefined, deps.keepAliveMs ?? HEARTBEAT_KEEPALIVE_MS);
|
|
131533
131834
|
const stop = new AbortController;
|
|
@@ -131550,14 +131851,14 @@ async function runReviewRunCommand(args, deps = {}) {
|
|
|
131550
131851
|
...parsed.model === undefined ? {} : { model: parsed.model }
|
|
131551
131852
|
});
|
|
131552
131853
|
await notifier.finished(endLine(result.summary));
|
|
131553
|
-
const escalated = arbiterEscalateLine(result.summary, readArbiterLog(
|
|
131854
|
+
const escalated = arbiterEscalateLine(result.summary, readArbiterLog(path306.join(result.runDir, ARBITER_LOG_NAME)));
|
|
131554
131855
|
if (escalated !== undefined)
|
|
131555
131856
|
await notifier.finished(escalated);
|
|
131556
131857
|
if (parsed.json)
|
|
131557
131858
|
log(JSON.stringify(result.summary, null, 2));
|
|
131558
131859
|
else {
|
|
131559
131860
|
log(`${result.reused ? "reused" : result.status}: ${result.summary.reason}`);
|
|
131560
|
-
log(`report: ${
|
|
131861
|
+
log(`report: ${path306.join(result.runDir, "report.md")}`);
|
|
131561
131862
|
}
|
|
131562
131863
|
return result.exitCode;
|
|
131563
131864
|
} catch (problem) {
|
|
@@ -131615,10 +131916,10 @@ A run that really stalls is still stopped by --stall-minutes (exit 5), and the l
|
|
|
131615
131916
|
|
|
131616
131917
|
// src/review/jev-risk-config.ts
|
|
131617
131918
|
import { readFile as readFile149 } from "fs/promises";
|
|
131618
|
-
import
|
|
131919
|
+
import path307 from "path";
|
|
131619
131920
|
async function readJevRiskEnabled(cwd) {
|
|
131620
131921
|
try {
|
|
131621
|
-
const parsed = JSON.parse(await readFile149(
|
|
131922
|
+
const parsed = JSON.parse(await readFile149(path307.join(cwd, REVIEW_GATE_CONFIG_PATH), "utf8"));
|
|
131622
131923
|
if (typeof parsed !== "object" || parsed === null)
|
|
131623
131924
|
return false;
|
|
131624
131925
|
const review = parsed["review"];
|
|
@@ -131846,80 +132147,6 @@ var init_review_jev_risk = __esm(() => {
|
|
|
131846
132147
|
JEV_RISK_FLAGS = ["--diff", "--pr", "--scope", "--max-calls", "--threshold", "--model", "--repo", "--fixtures", "--json"];
|
|
131847
132148
|
});
|
|
131848
132149
|
|
|
131849
|
-
// src/review/routing-check.ts
|
|
131850
|
-
import { readFileSync as readFileSync34 } from "fs";
|
|
131851
|
-
import path307 from "path";
|
|
131852
|
-
function narrowedSlices(manifest, readText, fix) {
|
|
131853
|
-
const originals = manifest.slices.filter((entry) => entry.retryOf === undefined && Array.isArray(entry.roles));
|
|
131854
|
-
const changedFiles = [...new Set(manifest.slices.filter((entry) => entry.retryOf === undefined).flatMap((entry) => entry.files))];
|
|
131855
|
-
const narrowed = [];
|
|
131856
|
-
const unreadable = [];
|
|
131857
|
-
for (const entry of originals) {
|
|
131858
|
-
const text = readText(entry);
|
|
131859
|
-
if (text === undefined) {
|
|
131860
|
-
unreadable.push(entry.id);
|
|
131861
|
-
continue;
|
|
131862
|
-
}
|
|
131863
|
-
const needed = routeSliceRegions(entry.id, parseScopedDiff(text), { changedFiles });
|
|
131864
|
-
const backedFix = fixClaimBacked(entry, fix, text);
|
|
131865
|
-
const backedSecondLook = secondLookClaimBacked(entry, fix, text);
|
|
131866
|
-
const roles = backedSecondLook ? [...SECOND_LOOK_ROLES] : backedFix ? expectedFixRoles(entry, fix, text) : needed.roles;
|
|
131867
|
-
const recorded = new Set(entry.roles ?? []);
|
|
131868
|
-
const missingRoles = roles.filter((role) => !recorded.has(role)).sort();
|
|
131869
|
-
const shallow = (backedFix || backedSecondLook || needed.depth === "deep") && entry.depth !== "deep";
|
|
131870
|
-
if (missingRoles.length > 0 || shallow) {
|
|
131871
|
-
narrowed.push({ id: entry.id, missingRoles, ...shallow ? { depth: { recorded: entry.depth ?? "unset", needed: "deep" } } : {} });
|
|
131872
|
-
}
|
|
131873
|
-
}
|
|
131874
|
-
return { narrowed, unreadable };
|
|
131875
|
-
}
|
|
131876
|
-
function sliceTextReader(manifestDir) {
|
|
131877
|
-
return (entry) => {
|
|
131878
|
-
try {
|
|
131879
|
-
return readFileSync34(path307.resolve(manifestDir, entry.path), "utf8");
|
|
131880
|
-
} catch {
|
|
131881
|
-
return;
|
|
131882
|
-
}
|
|
131883
|
-
};
|
|
131884
|
-
}
|
|
131885
|
-
function packageRoutingRefusal(packageDir, projectRoot, options = {}) {
|
|
131886
|
-
const shared = path307.join(projectRoot, ".metaproject", "data", "review", "slices");
|
|
131887
|
-
const own = path307.join(packageDir, "slices");
|
|
131888
|
-
for (const [file, dirs] of [[path307.join(own, "manifest.json"), [own]], [path307.join(packageDir, "slice-manifest.json"), [own, shared]]]) {
|
|
131889
|
-
let manifest;
|
|
131890
|
-
try {
|
|
131891
|
-
const parsed = JSON.parse(readFileSync34(file, "utf8"));
|
|
131892
|
-
manifest = Array.isArray(parsed.slices) ? { slices: parsed.slices } : undefined;
|
|
131893
|
-
} catch {
|
|
131894
|
-
manifest = undefined;
|
|
131895
|
-
}
|
|
131896
|
-
if (manifest === undefined)
|
|
131897
|
-
continue;
|
|
131898
|
-
const readers = dirs.map((dir) => sliceTextReader(dir));
|
|
131899
|
-
const { narrowed, unreadable } = narrowedSlices(manifest, (entry) => readers.map((read) => read(entry)).find((text) => text !== undefined), fixContextOf(packageDir));
|
|
131900
|
-
const label = path307.relative(projectRoot, file) || file;
|
|
131901
|
-
const refusal = routingRefusal(label, narrowed);
|
|
131902
|
-
if (refusal !== undefined || options.refuseUnreadable !== true || unreadable.length === 0)
|
|
131903
|
-
return refusal;
|
|
131904
|
-
return `${label} lists ${unreadable.length} slice(s) whose text is missing (${unreadable.slice(0, 8).join(", ")}${unreadable.length > 8 ? ", \u2026" : ""}), so their routing cannot be checked against their content. ` + "Restore the slice files next to the manifest, or re-run `keryx review slice` and the round";
|
|
131905
|
-
}
|
|
131906
|
-
return;
|
|
131907
|
-
}
|
|
131908
|
-
function routingRefusal(manifestLabel, narrowed) {
|
|
131909
|
-
if (narrowed.length === 0)
|
|
131910
|
-
return;
|
|
131911
|
-
const lines = narrowed.slice(0, 8).map((entry) => `${entry.id}: ${[
|
|
131912
|
-
entry.missingRoles.length > 0 ? `roles ${entry.missingRoles.join(", ")} are missing` : undefined,
|
|
131913
|
-
entry.depth !== undefined ? `recorded ${entry.depth.recorded}, its content needs deep` : undefined
|
|
131914
|
-
].filter(Boolean).join("; ")}`);
|
|
131915
|
-
return `${manifestLabel} routes ${narrowed.length} slice(s) narrower than their content needs (${lines.join(" | ")}${narrowed.length > 8 ? ` | \u2026 and ${narrowed.length - 8} more` : ""}). ` + "Routing is recomputed from each slice's text and the manifest may only be wider. Re-run `keryx review slice` to route the slices again, and do not edit roles or depth by hand.";
|
|
131916
|
-
}
|
|
131917
|
-
var init_routing_check = __esm(() => {
|
|
131918
|
-
init_slice();
|
|
131919
|
-
init_slice_routing();
|
|
131920
|
-
init_round_split();
|
|
131921
|
-
});
|
|
131922
|
-
|
|
131923
132150
|
// src/commands/review-ledger.ts
|
|
131924
132151
|
import { existsSync as existsSync66 } from "fs";
|
|
131925
132152
|
import { createHash as createHash84 } from "crypto";
|
|
@@ -134516,17 +134743,34 @@ function printOperatorCommandHelp(command) {
|
|
|
134516
134743
|
|
|
134517
134744
|
Records the operator's waiver of the mutation pass in <review-dir>/operator-decisions.json: the review may
|
|
134518
134745
|
record \`Not run: mutation pass\` with unrun mutants. Asks at the terminal; refused without a TTY. In keryx
|
|
134519
|
-
shell the review_waive_mutation tool asks the same as a card
|
|
134746
|
+
shell the review_waive_mutation tool asks the same as a card.
|
|
134747
|
+
|
|
134748
|
+
Without a TTY, record the operator's answer to a question you asked through the host's own question tool:
|
|
134749
|
+
keryx review waive-mutation --package <review-dir> --reason "<why>" --answered-by <source>:<ref> \\
|
|
134750
|
+
--answer-text "<the operator's words>" --question-text "<the question as asked>"
|
|
134751
|
+
${hostHelpTail("an --answer-text that is not a plain yes (a decline, a negation, the word keep, no yes at all), a blank or oversize text, a missing --reason")}`,
|
|
134520
134752
|
"override-reviewers": `keryx review override-reviewers --package <review-dir> --reason "<why>" [--selected a,b]
|
|
134521
134753
|
|
|
134522
134754
|
Records the operator's decision to run a smaller reviewer set than \`keryx review required\` names (an
|
|
134523
134755
|
override or mode diff) in <review-dir>/operator-decisions.json. Asks at the terminal; refused without a TTY.
|
|
134524
|
-
In keryx shell the review_override_reviewers tool asks the same as a card
|
|
134756
|
+
In keryx shell the review_override_reviewers tool asks the same as a card.
|
|
134757
|
+
|
|
134758
|
+
Without a TTY, record the operator's answer to a question you asked through the host's own question tool:
|
|
134759
|
+
keryx review override-reviewers --package <review-dir> --reason "<why>" [--selected a,b] --answered-by <source>:<ref> \\
|
|
134760
|
+
--answer-text "<the operator's words>" --question-text "<the question as asked>"
|
|
134761
|
+
${hostHelpTail("an --answer-text that is not a plain yes (a decline, a negation, the word keep, no yes at all), a blank or oversize text, a missing --reason")}`,
|
|
134525
134762
|
"confirm-plan": `keryx review confirm-plan --package <review-dir>
|
|
134526
134763
|
|
|
134527
134764
|
Shows the plan's dispatches and minutes (\`keryx review estimate\`) and records the operator's yes in
|
|
134528
134765
|
<review-dir>/plan-confirmation.json for exactly this plan. Asks at the terminal; refused without a TTY. In
|
|
134529
|
-
keryx shell the review_confirm_plan tool asks the same as a card
|
|
134766
|
+
keryx shell the review_confirm_plan tool asks the same as a card.
|
|
134767
|
+
|
|
134768
|
+
Without a TTY, record the operator's answer to a question you asked through the host's own question tool:
|
|
134769
|
+
keryx review confirm-plan --package <review-dir> --answered-by <source>:<ref> \\
|
|
134770
|
+
--answer-text "<the operator's words>" --question-text "<the question as asked>"
|
|
134771
|
+
The question must show this plan's dispatches and minutes (as \`keryx review estimate\` prints them): a question that
|
|
134772
|
+
does not is refused as a stale plan, so ask again with the current numbers.
|
|
134773
|
+
${hostHelpTail("an --answer-text that is not a plain yes (a decline, a negation, no yes at all), a blank or oversize text")}`,
|
|
134530
134774
|
"decide-premise": `keryx review decide-premise --package <review-dir> --finding <id> --answer uphold|dismiss --reason "<why>"
|
|
134531
134775
|
|
|
134532
134776
|
Answers the premise question on a finding the premise judge held (verdict challenged or owner-decision) and
|
|
@@ -134534,7 +134778,14 @@ records it in <review-dir>/operator-decisions.json, with your typed words, and o
|
|
|
134534
134778
|
(so the next round and the published round block read the answer too). uphold returns the finding to kept at its original
|
|
134535
134779
|
severity; dismiss records it dismissed-incorrect with your reason as the evidence. Refused for an unknown
|
|
134536
134780
|
finding, a finding that is not held, or a missing reason. Asks at the terminal; refused without a TTY. In
|
|
134537
|
-
keryx shell the review_decide_premise tool asks the same as a card
|
|
134781
|
+
keryx shell the review_decide_premise tool asks the same as a card.
|
|
134782
|
+
|
|
134783
|
+
Without a TTY, record the operator's answer to a question you asked through the host's own question tool:
|
|
134784
|
+
keryx review decide-premise --package <review-dir> --finding <id> --answer uphold|dismiss --reason "<why>" \\
|
|
134785
|
+
--answered-by <source>:<ref> --answer-text "<the operator's words>" --question-text "<the question as asked>"
|
|
134786
|
+
The operator's words must name the same choice as --answer (uphold or dismiss, or the same in Russian) and no other;
|
|
134787
|
+
the finding changes as on the terminal path.
|
|
134788
|
+
${hostHelpTail("words that name the other choice or none, a blank or oversize text, a missing --reason, an unknown or not-held finding")}`,
|
|
134538
134789
|
"verify-run": `keryx review verify-run --package <review-dir> --finding <id> [--timeout <seconds>] -- <command\u2026>
|
|
134539
134790
|
|
|
134540
134791
|
Runs a verification command in this (the parent's) session and saves its stdout, stderr and exit status to
|
|
@@ -134580,6 +134831,9 @@ async function runOperatorCommand(command, args, seams = {}) {
|
|
|
134580
134831
|
log("Hand that file to the verifier (context_files) and have it cite the file in the claim's evidence.");
|
|
134581
134832
|
return 0;
|
|
134582
134833
|
}
|
|
134834
|
+
if (own.some((arg) => arg === "--answered-by" || arg.startsWith("--answered-by="))) {
|
|
134835
|
+
return recordHostQuestion(command, own, packageDir, { cwd, log, env: seams.env ?? process.env });
|
|
134836
|
+
}
|
|
134583
134837
|
const isTerminal = seams.isTerminal ?? (() => process.stdin.isTTY === true && process.stdout.isTTY === true);
|
|
134584
134838
|
requireTerminal(command, isTerminal);
|
|
134585
134839
|
const ask = seams.ask ?? terminalAsk;
|
|
@@ -134636,13 +134890,210 @@ async function runOperatorCommand(command, args, seams = {}) {
|
|
|
134636
134890
|
log(`${kind} recorded (terminal, ${at}) in operator-decisions.json`);
|
|
134637
134891
|
return 0;
|
|
134638
134892
|
}
|
|
134639
|
-
|
|
134893
|
+
function parseAnsweredBy(raw, command) {
|
|
134894
|
+
const refuse = (why) => {
|
|
134895
|
+
throw new Error(`--answered-by must be <source>:<ref>: <source> one of ${HOST_QUESTION_SOURCES.join(", ")} and <ref> the host's own id for the question, not empty, at most ${REF_MAX_LENGTH} characters, no control characters` + `${raw === undefined ? " (the flag has no value)" : ` (got ${shortened(raw)}${why})`}. See \`keryx review ${command} --help\`.`);
|
|
134896
|
+
};
|
|
134897
|
+
if (raw === undefined)
|
|
134898
|
+
return refuse("");
|
|
134899
|
+
const colon = raw.indexOf(":");
|
|
134900
|
+
const source = colon <= 0 ? "" : raw.slice(0, colon);
|
|
134901
|
+
const ref = colon <= 0 ? "" : raw.slice(colon + 1).trim();
|
|
134902
|
+
if (!HOST_QUESTION_SOURCES.includes(source) || ref === "")
|
|
134903
|
+
return refuse("");
|
|
134904
|
+
if (ref.length > REF_MAX_LENGTH)
|
|
134905
|
+
return refuse(`: the ref is ${ref.length} characters`);
|
|
134906
|
+
if ([...ref].some((char) => /\p{Cc}/u.test(char)))
|
|
134907
|
+
return refuse(": the ref holds a control character");
|
|
134908
|
+
return { source, ref };
|
|
134909
|
+
}
|
|
134910
|
+
function refuseNonAffirmative(answerText, verb) {
|
|
134911
|
+
const words = wordsOf(answerText);
|
|
134912
|
+
const choices = HOST_OPTIONS[verb];
|
|
134913
|
+
const fix = `Nothing was recorded. Ask the operator again and pass their own words, which must say yes (for example "yes, ${choices.confirm} it") and nothing against it. See \`keryx review ${verb} --help\`.`;
|
|
134914
|
+
if (words.length === 0 || LEAD_DECLINE.has(words[0])) {
|
|
134915
|
+
throw new Error(`--answer-text reads as a decline (${shortened(answerText)}): a declined question records nothing. ${fix}`);
|
|
134916
|
+
}
|
|
134917
|
+
if (words.some((word) => NEGATIONS.has(word)))
|
|
134918
|
+
throw new Error(`--answer-text reads as a decline (${shortened(answerText)}): it holds a negation, so it is not a yes. ${fix}`);
|
|
134919
|
+
if (words.includes(choices.decline))
|
|
134920
|
+
throw new Error(`--answer-text reads as a decline (${shortened(answerText)}): it names the other option, "${choices.decline}". ${fix}`);
|
|
134921
|
+
if (!words.some((word) => AFFIRMATIVE.has(word) || word === choices.confirm || choices.alsoAffirms?.includes(word) === true)) {
|
|
134922
|
+
throw new Error(`--answer-text is not a yes (${shortened(answerText)}): free text that does not say yes is not a decision. ${fix}`);
|
|
134923
|
+
}
|
|
134924
|
+
}
|
|
134925
|
+
function refuseOtherChoice(answerText, answer) {
|
|
134926
|
+
const clauses = answerText.toLowerCase().split(/[,.;:!?()\n]+/u).map(wordsOf).filter((clause) => clause.length > 0);
|
|
134927
|
+
const other = answer === "uphold" ? "dismiss" : "uphold";
|
|
134928
|
+
const chosen = CHOICE_WORDS[answer];
|
|
134929
|
+
const words = clauses.flat();
|
|
134930
|
+
const named = words.some((word) => chosen.has(word));
|
|
134931
|
+
const namesOther = words.some((word) => CHOICE_WORDS[other].has(word));
|
|
134932
|
+
const negated = clauses.some((clause) => clause.some((word, index) => (chosen.has(word) || CHOICE_WORDS[other].has(word)) && NEGATIONS.has(clause[index - 1] ?? "")));
|
|
134933
|
+
if (!named || namesOther || negated) {
|
|
134934
|
+
throw new Error(`--answer-text does not name the same choice as --answer ${answer}: the operator's words must say ${answer} (and not ${other}, and not behind a "not"; a bare yes or no is not a choice here). ` + `Nothing was recorded; ask the operator again, or pass the --answer they gave.`);
|
|
134935
|
+
}
|
|
134936
|
+
}
|
|
134937
|
+
function requireText(flag, value, command) {
|
|
134938
|
+
if (value === undefined || value.trim() === "")
|
|
134939
|
+
throw new Error(`${flag} "<text>" is required and must not be blank: it holds ${flag === "--answer-text" ? "the operator's own words" : "the question as it was asked"}. See \`keryx review ${command} --help\`.`);
|
|
134940
|
+
if (value.length > MAX_OPERATOR_TEXT_LENGTH)
|
|
134941
|
+
throw new Error(`${flag} is ${value.length} characters; at most ${MAX_OPERATOR_TEXT_LENGTH} are recorded. Pass the operator's words or the question as asked, shortened. See \`keryx review ${command} --help\`.`);
|
|
134942
|
+
return value;
|
|
134943
|
+
}
|
|
134944
|
+
function requireReason(own, command) {
|
|
134945
|
+
const reason = optionValue(own, "--reason");
|
|
134946
|
+
if (reason === undefined || reason.trim() === "")
|
|
134947
|
+
throw new Error(`Missing --reason "<why>". See \`keryx review ${command} --help\`.`);
|
|
134948
|
+
return reason.trim();
|
|
134949
|
+
}
|
|
134950
|
+
function refuseHostQuestionHere(command, packageDir, env) {
|
|
134951
|
+
if (env["KERYX_TOOL_CALL"] === "1") {
|
|
134952
|
+
throw new Error(`keryx review ${command} --answered-by is refused: this keryx was started from an agent's shell (KERYX_TOOL_CALL=1), and an agent can type into it. ` + `Nothing was recorded. The operator answers the ${CARD[command]} card instead.`);
|
|
134953
|
+
}
|
|
134954
|
+
if (isAutonomousEnv(env) || readRunProfile(packageDir)?.mode === "unattended") {
|
|
134955
|
+
throw new Error(`keryx review ${command} --answered-by is refused in an unattended run: nobody is there to be asked, so nothing was recorded.`);
|
|
134956
|
+
}
|
|
134957
|
+
}
|
|
134958
|
+
function refuseRepeat(command, host, held) {
|
|
134959
|
+
if (held === undefined || held.source !== host.hostQuestion.source || held.ref !== host.hostQuestion.ref)
|
|
134960
|
+
return;
|
|
134961
|
+
throw new Error(`keryx review ${command}: the host question ${held.source}:${held.ref} is already recorded here, so this call recorded nothing again. ` + `If this is a new question, give it its own <ref> in --answered-by.`);
|
|
134962
|
+
}
|
|
134963
|
+
function refuseUnboundQuestion(command, questionText, subject, tokens, countAlso) {
|
|
134964
|
+
if (tokens.length === 0)
|
|
134965
|
+
return;
|
|
134966
|
+
if (tokens.every((token) => namesToken(questionText, token)))
|
|
134967
|
+
return;
|
|
134968
|
+
if (countAlso && namesToken(questionText, String(tokens.length)))
|
|
134969
|
+
return;
|
|
134970
|
+
throw new Error(`--question-text does not name the ${subject}: ${tokens.length === 1 ? tokens[0] : `${tokens.slice(0, 6).join(", ")}${tokens.length > 6 ? ", ..." : ""}`}${countAlso ? ` (or their count, ${tokens.length})` : ""}. ` + `An answer to a question that does not say what it is about is not recorded. Ask the operator again with the ${subject} in the question. Nothing was recorded. See \`keryx review ${command} --help\`.`);
|
|
134971
|
+
}
|
|
134972
|
+
async function prepareConfirmPlan(packageDir, host) {
|
|
134973
|
+
const estimate = await estimatePlan(packageDir);
|
|
134974
|
+
const shown = new Set(host.questionText.match(/\d+/g) ?? []);
|
|
134975
|
+
if (![estimate.dispatches, estimate.minutes.min, estimate.minutes.max].every((value) => shown.has(String(value)))) {
|
|
134976
|
+
throw new Error(`The plan changed, or the question does not show it: the plan now is ${estimate.dispatches} reviewer dispatches (${estimate.minutes.min}-${estimate.minutes.max} min) ` + `and --question-text names other numbers. Ask the operator again with the current numbers in the question. Nothing was recorded.`);
|
|
134977
|
+
}
|
|
134978
|
+
refuseNonAffirmative(host.answerText, "confirm-plan");
|
|
134979
|
+
refuseRepeat("confirm-plan", host, (await readPlanConfirmation2(packageDir))?.hostQuestion);
|
|
134980
|
+
const commit = async () => {
|
|
134981
|
+
const record = await recordPlanConfirmation(packageDir, estimate, { quote: `${host.questionText}: ${host.answerText}`, confirmedVia: "host-question", at: host.at, hostQuestion: host.hostQuestion });
|
|
134982
|
+
return `plan confirmed (${host.where}): ${record.dispatches} dispatch(es), ${record.minutes[0]}-${record.minutes[1]} min`;
|
|
134983
|
+
};
|
|
134984
|
+
return { commit, choice: HOST_OPTIONS["confirm-plan"].confirm };
|
|
134985
|
+
}
|
|
134986
|
+
async function preparePremise(packageDir, host, input, deps) {
|
|
134987
|
+
const held = await findHeldFinding(packageDir, input.ref);
|
|
134988
|
+
if (!held.ok)
|
|
134989
|
+
throw new Error(held.refusal);
|
|
134990
|
+
const finding = held.finding;
|
|
134991
|
+
const shortId = finding.key.split("#").pop();
|
|
134992
|
+
if (!namesToken(host.questionText, finding.key))
|
|
134993
|
+
refuseUnboundQuestion("decide-premise", host.questionText, "finding id", [shortId], false);
|
|
134994
|
+
refuseOtherChoice(host.answerText, input.answer);
|
|
134995
|
+
const prior = (await readPremiseDecisions(packageDir)).find((decision) => decision.finding === finding.key && decision.hostQuestion?.source === host.hostQuestion.source && decision.hostQuestion.ref === host.hostQuestion.ref);
|
|
134996
|
+
refuseRepeat("decide-premise", host, prior?.hostQuestion);
|
|
134997
|
+
const commit = async () => {
|
|
134998
|
+
await answerPremiseQuestion(packageDir, { finding, answer: input.answer, reason: input.reason, quote: host.answerText, at: host.at, confirmedVia: "host-question", hostQuestion: host.hostQuestion }, { cwd: deps.cwd, warn: (line) => deps.log(`warning: ${line}`) });
|
|
134999
|
+
return `premise-question ${input.answer} recorded for ${finding.key} (${host.where}, ${host.at}) in operator-decisions.json and findings.json`;
|
|
135000
|
+
};
|
|
135001
|
+
return { commit, choice: input.answer };
|
|
135002
|
+
}
|
|
135003
|
+
async function prepareGate(packageDir, host, input) {
|
|
135004
|
+
const kind = input.command === "waive-mutation" ? "mutation-waiver" : "reviewer-override";
|
|
135005
|
+
const subject = await operatorDecisionSubject(packageDir, kind, kind === "mutation-waiver" ? undefined : input.selected);
|
|
135006
|
+
refuseUnboundQuestion(input.command, host.questionText, kind === "mutation-waiver" ? "unrun mutants" : "reviewers left out", subject.items, true);
|
|
135007
|
+
refuseNonAffirmative(host.answerText, input.command);
|
|
135008
|
+
const prior = (await readOperatorDecisions(packageDir)).find((decision) => decision.kind === kind && decision.hostQuestion?.source === host.hostQuestion.source && decision.hostQuestion.ref === host.hostQuestion.ref);
|
|
135009
|
+
refuseRepeat(input.command, host, prior?.hostQuestion);
|
|
135010
|
+
const commit = async () => {
|
|
135011
|
+
await recordOperatorDecision(packageDir, { kind, quote: host.answerText, reason: input.reason, confirmedVia: "host-question", at: host.at, subject, hostQuestion: host.hostQuestion });
|
|
135012
|
+
return `${kind} recorded (${host.where}, ${host.at}) in operator-decisions.json`;
|
|
135013
|
+
};
|
|
135014
|
+
return { commit, choice: HOST_OPTIONS[input.command].confirm };
|
|
135015
|
+
}
|
|
135016
|
+
function refuseOtherAnswer(own, command) {
|
|
135017
|
+
const choices = HOST_OPTIONS[command];
|
|
135018
|
+
const answerArg = optionValue(own, "--answer");
|
|
135019
|
+
if (answerArg === undefined || answerArg === choices.confirm)
|
|
135020
|
+
return;
|
|
135021
|
+
throw new Error(answerArg === choices.decline ? `--answer ${answerArg} is a decline: a declined question records nothing. See \`keryx review ${command} --help\`.` : `--answer must be ${choices.confirm} (the option this verb records) or left out; got ${JSON.stringify(answerArg.slice(0, 40))}. See \`keryx review ${command} --help\`.`);
|
|
135022
|
+
}
|
|
135023
|
+
async function prepareVerb(command, own, packageDir, host, deps) {
|
|
135024
|
+
switch (command) {
|
|
135025
|
+
case "decide-premise": {
|
|
135026
|
+
const answer = optionValue(own, "--answer");
|
|
135027
|
+
if (answer !== "uphold" && answer !== "dismiss")
|
|
135028
|
+
throw new Error("--answer must be uphold or dismiss. See `keryx review decide-premise --help`.");
|
|
135029
|
+
const reason = requireReason(own, command);
|
|
135030
|
+
const ref = optionValue(own, "--finding");
|
|
135031
|
+
if (ref === undefined || ref.trim() === "")
|
|
135032
|
+
throw new Error("Missing --finding <id>. See `keryx review decide-premise --help`.");
|
|
135033
|
+
return preparePremise(packageDir, host, { ref, answer, reason }, deps);
|
|
135034
|
+
}
|
|
135035
|
+
case "confirm-plan":
|
|
135036
|
+
refuseOtherAnswer(own, command);
|
|
135037
|
+
return prepareConfirmPlan(packageDir, host);
|
|
135038
|
+
case "waive-mutation":
|
|
135039
|
+
case "override-reviewers": {
|
|
135040
|
+
refuseOtherAnswer(own, command);
|
|
135041
|
+
const reason = requireReason(own, command);
|
|
135042
|
+
const selectedArg = optionValue(own, "--selected");
|
|
135043
|
+
const selected = selectedArg === undefined ? undefined : selectedArg.split(",").map((item) => item.trim()).filter(Boolean);
|
|
135044
|
+
return prepareGate(packageDir, host, { command, reason, selected });
|
|
135045
|
+
}
|
|
135046
|
+
}
|
|
135047
|
+
}
|
|
135048
|
+
async function recordHostQuestion(command, own, packageDir, context) {
|
|
135049
|
+
const { cwd, log, env } = context;
|
|
135050
|
+
refuseHostQuestionHere(command, packageDir, env);
|
|
135051
|
+
const by = parseAnsweredBy(optionValue(own, "--answered-by"), command);
|
|
135052
|
+
const answerText = requireText("--answer-text", optionValue(own, "--answer-text"), command);
|
|
135053
|
+
const questionText = requireText("--question-text", optionValue(own, "--question-text"), command);
|
|
135054
|
+
const at = new Date().toISOString();
|
|
135055
|
+
const host = { hostQuestion: { source: by.source, ref: by.ref, questionText, answerText, time: at }, answerText, questionText, at, where: `host-question, ${by.source}:${by.ref}` };
|
|
135056
|
+
const { commit, choice } = await prepareVerb(command, own, packageDir, host, { cwd, log });
|
|
135057
|
+
log(await commit());
|
|
135058
|
+
await journalHostQuestion(command, host, choice, { cwd, log, env });
|
|
135059
|
+
return 0;
|
|
135060
|
+
}
|
|
135061
|
+
async function journalHostQuestion(command, host, choice, context) {
|
|
135062
|
+
const { cwd, log, env } = context;
|
|
135063
|
+
const { source, ref } = host.hostQuestion;
|
|
135064
|
+
const choices = HOST_OPTIONS[command];
|
|
135065
|
+
try {
|
|
135066
|
+
const flow = await resolveFlowContext(cwd, env);
|
|
135067
|
+
const opened = await openDecision({
|
|
135068
|
+
cwd,
|
|
135069
|
+
question: host.questionText,
|
|
135070
|
+
options: choices.options.map((option) => ({ ...option, description: SYNTHESIZED_OPTION })),
|
|
135071
|
+
stage: `review ${command}`,
|
|
135072
|
+
flow: flow.flow,
|
|
135073
|
+
flowSource: flow.flowSource,
|
|
135074
|
+
channel: source,
|
|
135075
|
+
source: DECISION_SOURCES.hostQuestion,
|
|
135076
|
+
hostQuestion: { source, ref, verb: command, answerText: host.answerText },
|
|
135077
|
+
salt: "host-question"
|
|
135078
|
+
});
|
|
135079
|
+
await answerDecision({ cwd, id: opened.id, choice });
|
|
135080
|
+
} catch (error) {
|
|
135081
|
+
log(`warning: the answer is recorded, but the keryx decisions journal was not updated (${error instanceof Error ? error.message : String(error)})`);
|
|
135082
|
+
}
|
|
135083
|
+
}
|
|
135084
|
+
var OPERATOR_COMMANDS, CARD, hostHelpTail = (refusedFor) => `<source> is ${HOST_QUESTION_SOURCES.join(", ")}; <ref> is the host's own id for the question (a poll or message id, at most ${REF_MAX_LENGTH} characters).
|
|
135085
|
+
The record says confirmedVia host-question with both texts (each at most ${MAX_OPERATOR_TEXT_LENGTH} characters), and \`keryx decisions\` keeps the question.
|
|
135086
|
+
keryx cannot see the question: it records what you report. Refused, with nothing recorded, for ${refusedFor}, the same host question recorded twice,
|
|
135087
|
+
in an unattended run and under KERYX_TOOL_CALL=1.`, REF_MAX_LENGTH = 200, SYNTHESIZED_OPTION = "Synthesized by keryx from the verb: not an option the host offered.", HOST_OPTIONS, LEAD_DECLINE, CHOICE_WORDS, NEGATIONS, AFFIRMATIVE, wordsOf = (text) => text.toLowerCase().split(/[^\p{L}\p{N}]+/u).filter((word) => word !== ""), shortened = (text) => JSON.stringify(text.trim().slice(0, 80)), escapeRegExp11 = (value) => value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"), namesToken = (text, token) => new RegExp(`(?<![\\p{L}\\p{N}_-])${escapeRegExp11(token)}(?![\\p{L}\\p{N}_-])`, "iu").test(text);
|
|
134640
135088
|
var init_review_operator = __esm(() => {
|
|
135089
|
+
init_service6();
|
|
134641
135090
|
init_args();
|
|
134642
135091
|
init_coverage();
|
|
134643
135092
|
init_operator_decisions();
|
|
134644
135093
|
init_plan_confirmation();
|
|
134645
135094
|
init_premise_question();
|
|
135095
|
+
init_run_profile();
|
|
135096
|
+
init_types4();
|
|
134646
135097
|
init_verify_run();
|
|
134647
135098
|
init_review_layers();
|
|
134648
135099
|
OPERATOR_COMMANDS = ["waive-mutation", "override-reviewers", "confirm-plan", "decide-premise", "verify-run"];
|
|
@@ -134652,6 +135103,70 @@ var init_review_operator = __esm(() => {
|
|
|
134652
135103
|
"confirm-plan": "review_confirm_plan",
|
|
134653
135104
|
"decide-premise": "review_decide_premise"
|
|
134654
135105
|
};
|
|
135106
|
+
HOST_OPTIONS = {
|
|
135107
|
+
"decide-premise": {
|
|
135108
|
+
confirm: "uphold",
|
|
135109
|
+
decline: "dismiss",
|
|
135110
|
+
options: [
|
|
135111
|
+
{ id: "uphold", label: "Uphold: the finding stands at its original severity" },
|
|
135112
|
+
{ id: "dismiss", label: "Dismiss: the finding is incorrect" }
|
|
135113
|
+
]
|
|
135114
|
+
},
|
|
135115
|
+
"confirm-plan": {
|
|
135116
|
+
confirm: "yes",
|
|
135117
|
+
decline: "no",
|
|
135118
|
+
alsoAffirms: ["run"],
|
|
135119
|
+
options: [
|
|
135120
|
+
{ id: "yes", label: "Yes: run the plan as estimated" },
|
|
135121
|
+
{ id: "no", label: "No: do not run it" }
|
|
135122
|
+
]
|
|
135123
|
+
},
|
|
135124
|
+
"waive-mutation": {
|
|
135125
|
+
confirm: "waive",
|
|
135126
|
+
decline: "keep",
|
|
135127
|
+
options: [
|
|
135128
|
+
{ id: "waive", label: "Waive: skip the mutation pass" },
|
|
135129
|
+
{ id: "keep", label: "Keep: run the mutation pass" }
|
|
135130
|
+
]
|
|
135131
|
+
},
|
|
135132
|
+
"override-reviewers": {
|
|
135133
|
+
confirm: "override",
|
|
135134
|
+
decline: "keep",
|
|
135135
|
+
options: [
|
|
135136
|
+
{ id: "override", label: "Override: run the smaller reviewer set" },
|
|
135137
|
+
{ id: "keep", label: "Keep: run the required reviewers" }
|
|
135138
|
+
]
|
|
135139
|
+
}
|
|
135140
|
+
};
|
|
135141
|
+
LEAD_DECLINE = new Set(["n", "no", "nope", "nah", "not", "never", "cancel", "stop", "decline", "declined", "reject", "\u043D\u0435\u0442", "\u043D\u0435", "\u043E\u0442\u043C\u0435\u043D\u0430"]);
|
|
135142
|
+
CHOICE_WORDS = {
|
|
135143
|
+
uphold: new Set(["uphold", "upheld", "upholds", "keep", "kept", "\u043E\u0441\u0442\u0430\u0432\u0438\u0442\u044C", "\u043E\u0441\u0442\u0430\u0432\u043B\u044F\u0435\u043C", "\u043E\u0441\u0442\u0430\u0432\u043B\u044F\u044E", "\u043F\u043E\u0434\u0442\u0432\u0435\u0440\u0434\u0438\u0442\u044C", "\u043F\u043E\u0434\u0442\u0432\u0435\u0440\u0436\u0434\u0430\u044E", "\u043F\u043E\u0434\u0434\u0435\u0440\u0436\u0430\u0442\u044C"]),
|
|
135144
|
+
dismiss: new Set(["dismiss", "dismissed", "dismisses", "drop", "dropped", "\u043E\u0442\u043A\u043B\u043E\u043D\u0438\u0442\u044C", "\u043E\u0442\u043A\u043B\u043E\u043D\u044F\u044E", "\u043E\u0442\u043A\u043B\u043E\u043D\u044F\u0435\u043C", "\u0441\u043D\u044F\u0442\u044C", "\u0437\u0430\u043A\u0440\u044B\u0442\u044C"])
|
|
135145
|
+
};
|
|
135146
|
+
NEGATIONS = new Set(["not", "never", "no", "nope", "nah", "dont", "don", "t", "nt", "cannot", "cant", "\u043D\u0435\u0442", "\u043D\u0435", "\u043D\u0438", "\u0431\u0435\u0437"]);
|
|
135147
|
+
AFFIRMATIVE = new Set([
|
|
135148
|
+
"yes",
|
|
135149
|
+
"y",
|
|
135150
|
+
"yep",
|
|
135151
|
+
"yeah",
|
|
135152
|
+
"ok",
|
|
135153
|
+
"okay",
|
|
135154
|
+
"sure",
|
|
135155
|
+
"confirm",
|
|
135156
|
+
"confirmed",
|
|
135157
|
+
"approve",
|
|
135158
|
+
"approved",
|
|
135159
|
+
"agree",
|
|
135160
|
+
"agreed",
|
|
135161
|
+
"go",
|
|
135162
|
+
"\u0434\u0430",
|
|
135163
|
+
"\u043E\u043A",
|
|
135164
|
+
"\u043E\u043A\u0435\u0439",
|
|
135165
|
+
"\u043F\u043E\u0434\u0442\u0432\u0435\u0440\u0436\u0434\u0430\u044E",
|
|
135166
|
+
"\u0441\u043E\u0433\u043B\u0430\u0441\u0435\u043D",
|
|
135167
|
+
"\u0434\u0430\u0432\u0430\u0439",
|
|
135168
|
+
"\u0437\u0430\u043F\u0443\u0441\u043A\u0430\u0439"
|
|
135169
|
+
]);
|
|
134655
135170
|
});
|
|
134656
135171
|
|
|
134657
135172
|
// src/commands/review-chunks.ts
|
|
@@ -146555,19 +147070,12 @@ async function runComplete2(args, seams) {
|
|
|
146555
147070
|
}
|
|
146556
147071
|
const isTerminal = seams?.isTerminal ?? (() => process.stdin.isTTY === true && process.stdout.isTTY === true);
|
|
146557
147072
|
if (waiver !== undefined && !isTerminal()) {
|
|
146558
|
-
throw new Error("--waive-coverage needs a terminal: the waiver is the operator's own statement and is refused when stdin or stdout is not a TTY. Inside keryx shell, call the review_waive_coverage tool: the operator answers it as a card, then
|
|
147073
|
+
throw new Error("--waive-coverage needs a terminal: the waiver is the operator's own statement and is refused when stdin or stdout is not a TTY. Inside keryx shell, call the review_waive_coverage tool: the operator answers it as a card, then call the review_complete card. Outside keryx shell, run this command in a terminal.");
|
|
146559
147074
|
}
|
|
146560
147075
|
const readinessWaivers = parseReadinessWaivers(args);
|
|
146561
147076
|
if (readinessWaivers.length > 0 && !isTerminal()) {
|
|
146562
|
-
throw new Error("--waive needs a terminal: the waiver is the operator's own statement and is refused when stdin or stdout is not a TTY. Inside keryx shell, call the review_waive_readiness tool: the operator answers it as a card, then
|
|
147077
|
+
throw new Error("--waive needs a terminal: the waiver is the operator's own statement and is refused when stdin or stdout is not a TTY. Inside keryx shell, call the review_waive_readiness tool: the operator answers it as a card, then call the review_complete card. Outside keryx shell, run this command in a terminal.");
|
|
146563
147078
|
}
|
|
146564
|
-
const completeDir = await resolveReviewPackagePath(process.cwd(), ref);
|
|
146565
|
-
const profileMissing = runProfileRefusal(completeDir);
|
|
146566
|
-
if (profileMissing !== undefined)
|
|
146567
|
-
throw new Error(`review complete refused: ${profileMissing}`);
|
|
146568
|
-
const narrowedRouting = packageRoutingRefusal(completeDir, resolveProjectRoot2(process.cwd()), { refuseUnreadable: true });
|
|
146569
|
-
if (narrowedRouting !== undefined)
|
|
146570
|
-
throw new Error(`review complete refused: ${narrowedRouting}`);
|
|
146571
147079
|
const {
|
|
146572
147080
|
manifest,
|
|
146573
147081
|
reviewNotes,
|
|
@@ -146575,7 +147083,7 @@ async function runComplete2(args, seams) {
|
|
|
146575
147083
|
coverageWaiver,
|
|
146576
147084
|
readiness,
|
|
146577
147085
|
warnings
|
|
146578
|
-
} = await
|
|
147086
|
+
} = await completeReviewGated(process.cwd(), ref, { dispositions, prLookup: seams?.prLookup ?? trackerPrLookup, prFiles: seams?.prFiles ?? githubPrFiles, ...readinessWaivers.length === 0 ? {} : { waiveReadiness: readinessWaivers }, ...waiver?.value === undefined ? {} : { waiveCoverage: waiver.value }, ...headFlag?.value === undefined ? {} : { head: headFlag.value } });
|
|
146579
147087
|
for (const warning of warnings)
|
|
146580
147088
|
console.error(`warning: ${warning}`);
|
|
146581
147089
|
if (coverageWaiver !== undefined) {
|
|
@@ -147431,7 +147939,7 @@ var init_review = __esm(() => {
|
|
|
147431
147939
|
init_host_profile();
|
|
147432
147940
|
init_skill_drift();
|
|
147433
147941
|
init_run_profile();
|
|
147434
|
-
|
|
147942
|
+
init_complete_gated();
|
|
147435
147943
|
init_review_operator();
|
|
147436
147944
|
init_review_chunks();
|
|
147437
147945
|
init_review_layers();
|
|
@@ -147688,8 +148196,22 @@ process; past the bound the package is still created, the command returns, and t
|
|
|
147688
148196
|
});
|
|
147689
148197
|
|
|
147690
148198
|
// src/commands/operator-confirm-review.ts
|
|
147691
|
-
import {
|
|
148199
|
+
import { createHash as createHash85 } from "crypto";
|
|
148200
|
+
import { existsSync as existsSync75, readFileSync as readFileSync39, statSync as statSync21 } from "fs";
|
|
147692
148201
|
import path337 from "path";
|
|
148202
|
+
async function defaultReplyPass(args, dryRun) {
|
|
148203
|
+
await Promise.resolve().then(() => init_review());
|
|
148204
|
+
return commentsReplyPass(args, dryRun);
|
|
148205
|
+
}
|
|
148206
|
+
function shownBody(text) {
|
|
148207
|
+
const lines = text.split(/\r?\n/).map((line) => line.replace(/\t/g, " "));
|
|
148208
|
+
while (lines.length > 1 && lines[lines.length - 1] === "")
|
|
148209
|
+
lines.pop();
|
|
148210
|
+
const bad = lines.findIndex((line) => hiddenInCard(line));
|
|
148211
|
+
if (bad !== -1)
|
|
148212
|
+
return { refusal: `holds control, escape, bidi or zero-width characters on line ${bad + 1} that the card would hide or rewrite; it is not shown, so it is not asked` };
|
|
148213
|
+
return { lines: lines.map((line) => ` ${line}`) };
|
|
148214
|
+
}
|
|
147693
148215
|
async function trackerPrLookup2(ref) {
|
|
147694
148216
|
if (!await githubAdapter.detect())
|
|
147695
148217
|
return null;
|
|
@@ -147708,13 +148230,12 @@ async function resolvePackage(ref, projectRoot) {
|
|
|
147708
148230
|
return resolveReviewPackagePath(projectRoot, ref);
|
|
147709
148231
|
}
|
|
147710
148232
|
function listFiles3(files) {
|
|
147711
|
-
|
|
147712
|
-
return files.length > LISTED_FILES ? [...shown, ` \u2026 and ${files.length - LISTED_FILES} more`] : shown;
|
|
148233
|
+
return files.map((file) => ` ${file}`);
|
|
147713
148234
|
}
|
|
147714
148235
|
function chunksAnswerSpec() {
|
|
147715
148236
|
return {
|
|
147716
148237
|
name: "review_chunks_answer",
|
|
147717
|
-
description: "Record the operator's answer to the chunk-run question: a blast-radius chunk run needs more review passes than the ceiling allows.
|
|
148238
|
+
description: "Record the operator's answer to the chunk-run question: a blast-radius chunk run needs more review passes than the ceiling allows. Pass the review package and the answer you are asking for (yes lifts the ceiling for this plan, no stops the run). Same effect as `keryx review chunks estimate --answer`.",
|
|
147718
148239
|
inputSchema: {
|
|
147719
148240
|
type: "object",
|
|
147720
148241
|
properties: { package: packageSchema, answer: { type: "string", enum: ["yes", "no"], description: "The answer to record if the operator confirms." } },
|
|
@@ -147764,7 +148285,7 @@ function chunksAnswerSpec() {
|
|
|
147764
148285
|
return { output: "The chunk plan changed after the card was shown; nothing was recorded. Ask again.", isError: true };
|
|
147765
148286
|
}
|
|
147766
148287
|
await recordChunkEstimate(prepared.dir, estimate, chunkModeOf(prepared.dir));
|
|
147767
|
-
const record = await answerChunkEstimate(prepared.dir, prepared.answer, confirmation.quote, new Date(confirmation.at), confirmation.confirmedVia);
|
|
148288
|
+
const record = await answerChunkEstimate(prepared.dir, prepared.answer, confirmation.quote, new Date(confirmation.at), confirmation.confirmedVia, cardPressFields(confirmation));
|
|
147768
148289
|
return { output: `chunk run answered ${record.answer?.value ?? prepared.answer} by the operator (${confirmation.confirmedVia}, ${confirmation.at}); ${estimate.chunks} pass(es) against a ceiling of ${estimate.maxPasses}` };
|
|
147769
148290
|
}
|
|
147770
148291
|
};
|
|
@@ -147774,7 +148295,7 @@ function publishSpec(seams) {
|
|
|
147774
148295
|
const post = () => seams.postReview ?? githubPostReview;
|
|
147775
148296
|
return {
|
|
147776
148297
|
name: "review_publish",
|
|
147777
|
-
description: "Post a finished review to its pull request on GitHub. Pass the review package, optionally the event (COMMENT, REQUEST_CHANGES, APPROVE; default COMMENT) and partial=true to post a review whose readiness checklist still has failing items (the page then says what is missing).
|
|
148298
|
+
description: "Post a finished review to its pull request on GitHub. Pass the review package, optionally the event (COMMENT, REQUEST_CHANGES, APPROVE; default COMMENT) and partial=true to post a review whose readiness checklist still has failing items (the page then says what is missing). Same effect as `keryx review publish`; the operator sees the target, the verdict and the whole body first (a body with hidden or control characters is refused; a card over 3000 characters is answered in the shell, not in the Telegram topic).",
|
|
147778
148299
|
inputSchema: {
|
|
147779
148300
|
type: "object",
|
|
147780
148301
|
properties: {
|
|
@@ -147804,8 +148325,10 @@ function publishSpec(seams) {
|
|
|
147804
148325
|
return false;
|
|
147805
148326
|
}
|
|
147806
148327
|
});
|
|
147807
|
-
const
|
|
147808
|
-
|
|
148328
|
+
const shownReview = shownBody(result.body);
|
|
148329
|
+
if ("refusal" in shownReview)
|
|
148330
|
+
return { refusal: `The review body ${shownReview.refusal}` };
|
|
148331
|
+
const bodyLines = shownReview.lines;
|
|
147809
148332
|
const failing = result.readiness.items.filter((item) => item.status === "fail").map((item) => item.id);
|
|
147810
148333
|
const name = label3(dir, ctx.projectRoot);
|
|
147811
148334
|
return {
|
|
@@ -147814,8 +148337,8 @@ function publishSpec(seams) {
|
|
|
147814
148337
|
`target: ${cardSafe(target)}`,
|
|
147815
148338
|
`verdict: ${result.event}`,
|
|
147816
148339
|
result.partial ? `partial: yes \u2014 readiness is failing: ${cardSafe(failing.join(", "))}` : "partial: no \u2014 the readiness checklist passes",
|
|
147817
|
-
`body: ${result.body.length} characters, ${bodyLines.length} lines
|
|
147818
|
-
...
|
|
148340
|
+
`body: ${result.body.length} characters, ${bodyLines.length} lines, shown whole:`,
|
|
148341
|
+
...bodyLines,
|
|
147819
148342
|
"yes: the review is posted to GitHub and cannot be taken back from here"
|
|
147820
148343
|
],
|
|
147821
148344
|
subject: `${cardSafe(target)} (package ${name})`,
|
|
@@ -147836,6 +148359,7 @@ function publishSpec(seams) {
|
|
|
147836
148359
|
post: post(),
|
|
147837
148360
|
now: () => new Date(confirmation.at),
|
|
147838
148361
|
confirmedVia: confirmation.confirmedVia,
|
|
148362
|
+
...cardPressFields(confirmation),
|
|
147839
148363
|
confirm: async (body, targetRef) => {
|
|
147840
148364
|
changed = body !== prepared.body || targetRef !== prepared.ref;
|
|
147841
148365
|
return !changed;
|
|
@@ -147851,7 +148375,7 @@ function publishSpec(seams) {
|
|
|
147851
148375
|
function waiveCoverageSpec() {
|
|
147852
148376
|
return {
|
|
147853
148377
|
name: "review_waive_coverage",
|
|
147854
|
-
description: "Accept that the in-scope files no review slice or chunk covered stay unreviewed, so `keryx review complete` passes its coverage gate. Pass the review package.
|
|
148378
|
+
description: "Accept that the in-scope files no review slice or chunk covered stay unreviewed, so `keryx review complete` passes its coverage gate. Pass the review package. Same effect as `keryx review complete --waive-coverage`: records the waiver (the operator's yes, the time, the files); call review_complete afterwards to close the review.",
|
|
147855
148379
|
inputSchema: { type: "object", properties: { package: packageSchema }, required: ["package"], additionalProperties: false },
|
|
147856
148380
|
async buildCard(input, ctx) {
|
|
147857
148381
|
const ref = packageRef(input);
|
|
@@ -147887,10 +148411,10 @@ function waiveCoverageSpec() {
|
|
|
147887
148411
|
if (now.length !== prepared.files.length || now.some((file, index) => file !== prepared.files[index])) {
|
|
147888
148412
|
return { output: "The uncovered files changed after the card was shown; nothing was recorded. Ask again.", isError: true };
|
|
147889
148413
|
}
|
|
147890
|
-
const waiver = await recordCoverageWaiver(prepared.dir, confirmation.quote, new Date(confirmation.at), confirmation.confirmedVia);
|
|
148414
|
+
const waiver = await recordCoverageWaiver(prepared.dir, confirmation.quote, new Date(confirmation.at), confirmation.confirmedVia, cardPressFields(confirmation));
|
|
147891
148415
|
if (waiver === undefined)
|
|
147892
148416
|
return { output: "No coverage waiver was recorded: the package holds no dispatch record or nothing is uncovered.", isError: true };
|
|
147893
|
-
return { output: `coverage waived by the operator (${waiver.confirmedVia ?? "unknown"}, ${waiver.time}): ${waiver.files.length} file(s) not reviewed.
|
|
148417
|
+
return { output: `coverage waived by the operator (${waiver.confirmedVia ?? "unknown"}, ${waiver.time}): ${waiver.files.length} file(s) not reviewed. Call review_complete to close the review.` };
|
|
147894
148418
|
}
|
|
147895
148419
|
};
|
|
147896
148420
|
}
|
|
@@ -147898,7 +148422,7 @@ function waiveReadinessSpec(seams) {
|
|
|
147898
148422
|
const readiness = () => ({ prLookup: seams.prLookup ?? trackerPrLookup2, prFiles: seams.prFiles ?? githubPrFiles });
|
|
147899
148423
|
return {
|
|
147900
148424
|
name: "review_waive_readiness",
|
|
147901
|
-
description: "Accept one failing item of the review readiness checklist as it is, so the review can complete or be published as a partial review. Pass the review package and the item (its number or id).
|
|
148425
|
+
description: "Accept one failing item of the review readiness checklist as it is, so the review can complete or be published as a partial review. Pass the review package and the item (its number or id). Same effect as `keryx review complete --waive <item>`: records the waiver (the operator's yes, the time) in the manifest and the report header; call review_complete afterwards to close the review.",
|
|
147902
148426
|
inputSchema: {
|
|
147903
148427
|
type: "object",
|
|
147904
148428
|
properties: { package: packageSchema, item: { type: "string", description: `The readiness item: its number (1-${READINESS_ITEMS.length}) or id, e.g. head, pr-files or quotes-located. Item ${READINESS_ITEMS.find((entry) => entry.id === "premise-decisions")?.number ?? "premise-decisions"} (premise-decisions) is never waived: each held finding is answered with review_decide_premise.` } },
|
|
@@ -147950,8 +148474,8 @@ function waiveReadinessSpec(seams) {
|
|
|
147950
148474
|
if (current === undefined || current.status !== "fail" || current.evidence !== prepared.evidence) {
|
|
147951
148475
|
return { output: "The readiness item changed after the card was shown; nothing was recorded. Ask again.", isError: true };
|
|
147952
148476
|
}
|
|
147953
|
-
const added = await waiveReadiness(prepared.dir, [prepared.item], confirmation.quote, { ...readiness(), now: new Date(confirmation.at), confirmedVia: confirmation.confirmedVia });
|
|
147954
|
-
return { output: `readiness item waived by the operator (${confirmation.confirmedVia}, ${confirmation.at}): ${added.map((waiver) => waiver.item).join(", ")}.
|
|
148477
|
+
const added = await waiveReadiness(prepared.dir, [prepared.item], confirmation.quote, { ...readiness(), now: new Date(confirmation.at), confirmedVia: confirmation.confirmedVia, ...cardPressFields(confirmation) });
|
|
148478
|
+
return { output: `readiness item waived by the operator (${confirmation.confirmedVia}, ${confirmation.at}): ${added.map((waiver) => waiver.item).join(", ")}. Call review_complete to close the review.` };
|
|
147955
148479
|
}
|
|
147956
148480
|
};
|
|
147957
148481
|
}
|
|
@@ -147959,7 +148483,7 @@ function decisionSpec(kind) {
|
|
|
147959
148483
|
const mutation = kind === "mutation-waiver";
|
|
147960
148484
|
return {
|
|
147961
148485
|
name: mutation ? "review_waive_mutation" : "review_override_reviewers",
|
|
147962
|
-
description: mutation ? "Record the operator's waiver of the mutation pass: the proposed mutants stay unrun and the review records `Not run: mutation pass`. Pass the review package and the reason.
|
|
148486
|
+
description: mutation ? "Record the operator's waiver of the mutation pass: the proposed mutants stay unrun and the review records `Not run: mutation pass`. Pass the review package and the reason. Same effect as `keryx review waive-mutation` at a terminal. A mutationWaiver quote you write into the dispatch is not accepted on its own." : "Record the operator's decision to run a smaller reviewer set than `keryx review required` names (an override, or mode diff). Pass the review package, the reason and, optionally, the reviewers that will run. Same effect as `keryx review override-reviewers` at a terminal. An override quote you write into the dispatch is not accepted on its own.",
|
|
147963
148487
|
inputSchema: {
|
|
147964
148488
|
type: "object",
|
|
147965
148489
|
properties: {
|
|
@@ -147972,11 +148496,12 @@ function decisionSpec(kind) {
|
|
|
147972
148496
|
},
|
|
147973
148497
|
async buildCard(input, ctx) {
|
|
147974
148498
|
const ref = packageRef(input);
|
|
147975
|
-
const
|
|
148499
|
+
const given = reasonOf2(input);
|
|
147976
148500
|
if (ref === undefined)
|
|
147977
148501
|
return { refusal: "package is required" };
|
|
147978
|
-
if (
|
|
147979
|
-
return
|
|
148502
|
+
if ("refusal" in given)
|
|
148503
|
+
return given;
|
|
148504
|
+
const { reason } = given;
|
|
147980
148505
|
try {
|
|
147981
148506
|
const dir = await resolvePackage(ref, ctx.projectRoot);
|
|
147982
148507
|
const name = label3(dir, ctx.projectRoot);
|
|
@@ -147988,7 +148513,7 @@ function decisionSpec(kind) {
|
|
|
147988
148513
|
return {
|
|
147989
148514
|
title: `Waive the mutation pass for ${name}: ${pending} proposed mutant(s) stay unrun?`,
|
|
147990
148515
|
lines: [
|
|
147991
|
-
`unrun mutants: ${pending} (research-evidence/mutants.json): ${cardSafe(subject.items.
|
|
148516
|
+
`unrun mutants: ${pending} (research-evidence/mutants.json): ${cardSafe(subject.items.join(", "))}`,
|
|
147992
148517
|
`head: ${cardSafe(subject.head ?? "not recorded")}`,
|
|
147993
148518
|
`reason: ${cardSafe(reason)}`,
|
|
147994
148519
|
"yes: the review records `Not run: mutation pass` with your words for these mutants at this head; a new head or a new mutant asks again"
|
|
@@ -148007,7 +148532,7 @@ function decisionSpec(kind) {
|
|
|
148007
148532
|
`required (mode all): ${required.length} reviewer(s)`,
|
|
148008
148533
|
`head: ${cardSafe(subject.head ?? "not recorded")}`,
|
|
148009
148534
|
selected === undefined ? "selected: not named" : `selected: ${cardSafe(selected.join(", "))}`,
|
|
148010
|
-
`not run: ${cardSafe(missing.
|
|
148535
|
+
`not run: ${cardSafe(missing.join(", ") || "none")}`,
|
|
148011
148536
|
`reason: ${cardSafe(reason)}`,
|
|
148012
148537
|
"yes: the gate accepts leaving out exactly these reviewers at this head; dropping another one or a new head asks again"
|
|
148013
148538
|
],
|
|
@@ -148026,7 +148551,7 @@ function decisionSpec(kind) {
|
|
|
148026
148551
|
if (JSON.stringify(again) !== JSON.stringify(prepared.subject)) {
|
|
148027
148552
|
return { output: "The head, the unrun mutants or the reviewer set changed after the card was shown; nothing was recorded. Ask again.", isError: true };
|
|
148028
148553
|
}
|
|
148029
|
-
await recordOperatorDecision(prepared.dir, { kind, quote: confirmation.quote, reason: prepared.reason, confirmedVia: confirmation.confirmedVia, at: confirmation.at, subject: prepared.subject });
|
|
148554
|
+
await recordOperatorDecision(prepared.dir, { kind, quote: confirmation.quote, reason: prepared.reason, confirmedVia: confirmation.confirmedVia, ...cardPressFields(confirmation), at: confirmation.at, subject: prepared.subject });
|
|
148030
148555
|
return { output: `${mutation ? "mutation pass waived" : "reviewer-set override recorded"} by the operator (${confirmation.confirmedVia}, ${confirmation.at}) in operator-decisions.json` };
|
|
148031
148556
|
}
|
|
148032
148557
|
};
|
|
@@ -148051,10 +148576,11 @@ function replyArgv(input) {
|
|
|
148051
148576
|
argv.push("--round", String(input["round"]));
|
|
148052
148577
|
return argv;
|
|
148053
148578
|
}
|
|
148054
|
-
function replySpec() {
|
|
148579
|
+
function replySpec(seams) {
|
|
148580
|
+
const pass = seams.replyPass ?? defaultReplyPass;
|
|
148055
148581
|
return {
|
|
148056
148582
|
name: "review_comments_reply",
|
|
148057
|
-
description: "Post the final review-comment replies on a pull request (`keryx review comments reply --final`). Pass the repo, the PR number, its head sha, the outcomes file and the review package or result.
|
|
148583
|
+
description: "Post the final review-comment replies on a pull request (`keryx review comments reply --final`). Pass the repo, the PR number, its head sha, the outcomes file and the review package or result. The operator sees every reply whole before anything is posted (a reply with hidden or control characters is refused; a card over 3000 characters is answered in the shell, not in the Telegram topic).",
|
|
148058
148584
|
inputSchema: {
|
|
148059
148585
|
type: "object",
|
|
148060
148586
|
properties: {
|
|
@@ -148075,15 +148601,20 @@ function replySpec() {
|
|
|
148075
148601
|
if (!Array.isArray(argv))
|
|
148076
148602
|
return argv;
|
|
148077
148603
|
try {
|
|
148078
|
-
await
|
|
148079
|
-
const dry = await commentsReplyPass([...argv], true);
|
|
148604
|
+
const dry = await pass([...argv], true);
|
|
148080
148605
|
const requests = dry.result.requests;
|
|
148081
148606
|
if (requests.length === 0)
|
|
148082
148607
|
return { refusal: "The reply pass has nothing to post." };
|
|
148083
|
-
const bodies =
|
|
148608
|
+
const bodies = [];
|
|
148609
|
+
for (const [index, request] of requests.entries()) {
|
|
148610
|
+
const shown = shownBody(request.body.body ?? "");
|
|
148611
|
+
if ("refusal" in shown)
|
|
148612
|
+
return { refusal: `Reply ${index + 1} ${shown.refusal}` };
|
|
148613
|
+
bodies.push(`reply ${index + 1}:`, ...shown.lines);
|
|
148614
|
+
}
|
|
148084
148615
|
return {
|
|
148085
148616
|
title: `Post ${requests.length} reply(ies) on ${cardSafe(dry.repo)}#${dry.number}?`,
|
|
148086
|
-
lines: [`replies: ${requests.length}
|
|
148617
|
+
lines: [`replies: ${requests.length}, each shown whole:`, ...bodies, "yes: the replies are posted on the pull request and cannot be taken back from here"],
|
|
148087
148618
|
subject: `${cardSafe(dry.repo)}#${dry.number}`,
|
|
148088
148619
|
prepared: { argv, requests: JSON.stringify(requests) }
|
|
148089
148620
|
};
|
|
@@ -148094,11 +148625,10 @@ function replySpec() {
|
|
|
148094
148625
|
async apply(_input, _ctx, confirmation, prepared) {
|
|
148095
148626
|
if (prepared === undefined)
|
|
148096
148627
|
return { output: "review_comments_reply: nothing prepared for this card", isError: true };
|
|
148097
|
-
await
|
|
148098
|
-
const again = await commentsReplyPass([...prepared.argv], true);
|
|
148628
|
+
const again = await pass([...prepared.argv], true);
|
|
148099
148629
|
if (JSON.stringify(again.result.requests) !== prepared.requests)
|
|
148100
148630
|
return { output: "The replies changed after the card was shown; nothing was posted. Ask again.", isError: true };
|
|
148101
|
-
const posted = await
|
|
148631
|
+
const posted = await pass([...prepared.argv], false);
|
|
148102
148632
|
return { output: `posted ${posted.result.posted.length} reply(ies) (${confirmation.confirmedVia}, ${confirmation.at}); skipped ${posted.result.skipped.length}` };
|
|
148103
148633
|
}
|
|
148104
148634
|
};
|
|
@@ -148125,7 +148655,7 @@ function confirmPlanSpec() {
|
|
|
148125
148655
|
`dispatches: ${estimate.dispatches} (${estimate.roles} roles, ${estimate.slices} slices, ${estimate.sequentialBatches} batch(es) one after another)`,
|
|
148126
148656
|
`expected duration: ${estimate.minutes.min}-${estimate.minutes.max} min`,
|
|
148127
148657
|
`threshold: ${estimate.thresholds.maxDispatches} dispatches or ${estimate.thresholds.maxMinutes} min (${estimate.overReasons.join("; ")})`,
|
|
148128
|
-
...estimate.perRole.
|
|
148658
|
+
...estimate.perRole.map((role) => ` ${cardSafe(role.role)}: ${role.slices} slice(s), ${role.waves} wave(s)`),
|
|
148129
148659
|
"yes: this plan may run; a re-cut plan asks again"
|
|
148130
148660
|
],
|
|
148131
148661
|
subject: `package ${name}`,
|
|
@@ -148141,7 +148671,7 @@ function confirmPlanSpec() {
|
|
|
148141
148671
|
const estimate = await estimatePlan(prepared.dir);
|
|
148142
148672
|
if (planKeyOf2(estimate) !== prepared.planKey)
|
|
148143
148673
|
return { output: "The plan changed after the card was shown; nothing was recorded. Ask again.", isError: true };
|
|
148144
|
-
const record = await recordPlanConfirmation(prepared.dir, estimate, { quote: confirmation.quote, confirmedVia: confirmation.confirmedVia, at: confirmation.at });
|
|
148674
|
+
const record = await recordPlanConfirmation(prepared.dir, estimate, { quote: confirmation.quote, confirmedVia: confirmation.confirmedVia, ...cardPressFields(confirmation), at: confirmation.at });
|
|
148145
148675
|
return { output: `plan confirmed by the operator (${record.confirmedVia}, ${record.at}): ${record.dispatches} dispatch(es), ${record.minutes[0]}-${record.minutes[1]} min` };
|
|
148146
148676
|
}
|
|
148147
148677
|
};
|
|
@@ -148163,7 +148693,7 @@ function decidePremiseSpec() {
|
|
|
148163
148693
|
},
|
|
148164
148694
|
async buildCard(input, ctx) {
|
|
148165
148695
|
const ref = packageRef(input);
|
|
148166
|
-
const
|
|
148696
|
+
const given = reasonOf2(input);
|
|
148167
148697
|
const finding = typeof input["finding"] === "string" ? input["finding"].trim() : "";
|
|
148168
148698
|
const answer = input["answer"];
|
|
148169
148699
|
if (ref === undefined)
|
|
@@ -148172,8 +148702,9 @@ function decidePremiseSpec() {
|
|
|
148172
148702
|
return { refusal: "finding is required" };
|
|
148173
148703
|
if (answer !== "uphold" && answer !== "dismiss")
|
|
148174
148704
|
return { refusal: 'answer must be "uphold" or "dismiss"' };
|
|
148175
|
-
if (
|
|
148176
|
-
return
|
|
148705
|
+
if ("refusal" in given)
|
|
148706
|
+
return given;
|
|
148707
|
+
const { reason } = given;
|
|
148177
148708
|
try {
|
|
148178
148709
|
const dir = await resolvePackage(ref, ctx.projectRoot);
|
|
148179
148710
|
const held = await findHeldFinding(dir, finding);
|
|
@@ -148205,12 +148736,196 @@ function decidePremiseSpec() {
|
|
|
148205
148736
|
return { output: "The finding changed after the card was shown; nothing was recorded. Ask again.", isError: true };
|
|
148206
148737
|
}
|
|
148207
148738
|
const warnings = [];
|
|
148208
|
-
await answerPremiseQuestion(prepared.dir, { finding: again.finding, answer: prepared.answer, reason: prepared.reason, quote: confirmation.quote, at: confirmation.at, confirmedVia: confirmation.confirmedVia }, { cwd: ctx.projectRoot, warn: (line) => warnings.push(`warning: ${line}`) });
|
|
148739
|
+
await answerPremiseQuestion(prepared.dir, { finding: again.finding, answer: prepared.answer, reason: prepared.reason, quote: confirmation.quote, at: confirmation.at, confirmedVia: confirmation.confirmedVia, ...cardPressFields(confirmation) }, { cwd: ctx.projectRoot, warn: (line) => warnings.push(`warning: ${line}`) });
|
|
148209
148740
|
return { output: [`premise question on ${prepared.finding} answered ${prepared.answer} by the operator (${confirmation.confirmedVia}, ${confirmation.at}) in operator-decisions.json and findings.json`, ...warnings].join(`
|
|
148210
148741
|
`) };
|
|
148211
148742
|
}
|
|
148212
148743
|
};
|
|
148213
148744
|
}
|
|
148745
|
+
function completionDispositions(input) {
|
|
148746
|
+
const raw = input["dispositions"];
|
|
148747
|
+
if (raw === undefined)
|
|
148748
|
+
return [];
|
|
148749
|
+
if (!Array.isArray(raw))
|
|
148750
|
+
return { refusal: "dispositions must be an array of { finding, disposition, evidence? }" };
|
|
148751
|
+
const records = [];
|
|
148752
|
+
for (const [index, entry] of raw.entries()) {
|
|
148753
|
+
const item = typeof entry === "object" && entry !== null ? entry : {};
|
|
148754
|
+
const finding = typeof item["finding"] === "string" ? item["finding"].trim() : "";
|
|
148755
|
+
const state = item["disposition"];
|
|
148756
|
+
const evidence = item["evidence"];
|
|
148757
|
+
if (finding === "")
|
|
148758
|
+
return { refusal: `dispositions[${index}]: finding is required` };
|
|
148759
|
+
if (typeof state !== "string" || !FINDING_DISPOSITION_STATES.includes(state)) {
|
|
148760
|
+
return { refusal: `dispositions[${index}]: disposition must be one of ${FINDING_DISPOSITION_STATES.join(", ")}` };
|
|
148761
|
+
}
|
|
148762
|
+
if (evidence !== undefined && typeof evidence !== "string")
|
|
148763
|
+
return { refusal: `dispositions[${index}]: evidence must be text` };
|
|
148764
|
+
const text = evidence?.trim();
|
|
148765
|
+
if (finding.length > MAX_DISPOSITION_FINDING_CHARS)
|
|
148766
|
+
return { refusal: `dispositions[${index}]: finding is longer than ${MAX_DISPOSITION_FINDING_CHARS} characters` };
|
|
148767
|
+
if (text !== undefined && text.length > MAX_DISPOSITION_EVIDENCE_CHARS) {
|
|
148768
|
+
return { refusal: `dispositions[${index}]: evidence is ${text.length} characters; the card shows it whole, so the limit is ${MAX_DISPOSITION_EVIDENCE_CHARS}` };
|
|
148769
|
+
}
|
|
148770
|
+
for (const [field, value] of [["finding", finding], ["evidence", text ?? ""]]) {
|
|
148771
|
+
if (hiddenInCard(value))
|
|
148772
|
+
return { refusal: `dispositions[${index}]: ${field} ${HIDDEN_IN_CARD}` };
|
|
148773
|
+
}
|
|
148774
|
+
records.push({ finding, state, ...text === undefined || text === "" ? {} : { evidence: text } });
|
|
148775
|
+
}
|
|
148776
|
+
return records;
|
|
148777
|
+
}
|
|
148778
|
+
async function completionFacts(dir) {
|
|
148779
|
+
const manifest = JSON.parse(readFileSync39(path337.join(dir, "manifest.json"), "utf8"));
|
|
148780
|
+
const recorded = typeof manifest.target?.head === "string" && manifest.target.head !== "" ? manifest.target.head : undefined;
|
|
148781
|
+
const report = readFileSync39(path337.join(dir, "report.md"), "utf8");
|
|
148782
|
+
const evidenceLine = evidenceLevelLine(report);
|
|
148783
|
+
const waivers = (manifest.readiness_waivers ?? []).map((waiver) => `waiver: readiness item ${cardSafe(waiver.item)}: "${quoteWhole(waiver.operator_quote)}"`);
|
|
148784
|
+
let coverage;
|
|
148785
|
+
try {
|
|
148786
|
+
coverage = readCoverageWaiver(JSON.parse(readFileSync39(path337.join(dir, "research.json"), "utf8")).dispatch);
|
|
148787
|
+
} catch (error) {
|
|
148788
|
+
if (error.code !== "ENOENT") {
|
|
148789
|
+
waivers.push(`waiver: coverage, could not read research.json (${cardSafe(error instanceof Error ? error.message : String(error))}); the research gate will refuse`);
|
|
148790
|
+
}
|
|
148791
|
+
coverage = undefined;
|
|
148792
|
+
}
|
|
148793
|
+
if (coverage !== undefined)
|
|
148794
|
+
waivers.push(`waiver: coverage, ${coverage.files.length} file(s) not reviewed: "${quoteWhole(coverage.operatorQuote)}"`);
|
|
148795
|
+
for (const decision of await readOperatorDecisions(dir)) {
|
|
148796
|
+
const items = decision.subject?.items ?? [];
|
|
148797
|
+
const what = decision.kind === "mutation-waiver" ? "mutation pass not run" : "reviewer override, left out";
|
|
148798
|
+
waivers.push(`waiver: ${what}${items.length === 0 ? "" : ` ${cardSafe(items.join(", "))}`}: "${quoteWhole(decision.quote)}" (${decision.confirmedVia}${decision.hostQuestion === undefined ? "" : ` ${cardSafe(decision.hostQuestion.source)}:${cardSafe(decision.hostQuestion.ref)}`})`);
|
|
148799
|
+
}
|
|
148800
|
+
return {
|
|
148801
|
+
reviewId: manifest.reviewId ?? path337.basename(dir),
|
|
148802
|
+
head: recorded === undefined ? undefined : cardSafe(recorded),
|
|
148803
|
+
evidence: evidenceLine === undefined ? "evidence level: the report has no `Evidence level:` line; the gate will refuse" : cardSafe(evidenceLine),
|
|
148804
|
+
waivers,
|
|
148805
|
+
status: manifest.status ?? ""
|
|
148806
|
+
};
|
|
148807
|
+
}
|
|
148808
|
+
function quoteWhole(text) {
|
|
148809
|
+
return hiddenInCard(text) ? `${cardSafe(text)} [the stored quote holds characters this card hides or rewrites]` : text;
|
|
148810
|
+
}
|
|
148811
|
+
function completeSpec(seams) {
|
|
148812
|
+
return {
|
|
148813
|
+
name: "review_complete",
|
|
148814
|
+
description: "Close a review round: the operator sees the reviewed head SHA, the report's evidence level and every waiver the package carries (readiness, coverage, mutation, reviewer override), and says yes before the round is closed. Pass the review package; the round closes against the head the package recorded. Same gates and same effect as `keryx review complete` at a terminal; record the waivers first with review_waive_readiness or review_waive_coverage. A fix round passes `dispositions` (finding, disposition, evidence) for the earlier findings; the card lists each one. A refused gate closes nothing.",
|
|
148815
|
+
inputSchema: {
|
|
148816
|
+
type: "object",
|
|
148817
|
+
properties: {
|
|
148818
|
+
package: packageSchema,
|
|
148819
|
+
dispositions: dispositionsSchema
|
|
148820
|
+
},
|
|
148821
|
+
required: ["package"],
|
|
148822
|
+
additionalProperties: false
|
|
148823
|
+
},
|
|
148824
|
+
async buildCard(input, ctx) {
|
|
148825
|
+
const ref = packageRef(input);
|
|
148826
|
+
if (ref === undefined)
|
|
148827
|
+
return { refusal: "package is required" };
|
|
148828
|
+
const dispositions = completionDispositions(input);
|
|
148829
|
+
if ("refusal" in dispositions)
|
|
148830
|
+
return dispositions;
|
|
148831
|
+
try {
|
|
148832
|
+
const dir = await resolvePackage(ref, ctx.projectRoot);
|
|
148833
|
+
const facts = await completionFacts(dir);
|
|
148834
|
+
if (facts.status === "closed")
|
|
148835
|
+
return { refusal: `${facts.reviewId} is already complete` };
|
|
148836
|
+
if (dispositions.length > 0)
|
|
148837
|
+
await checkDispositions(dir, dispositions);
|
|
148838
|
+
const name = label3(dir, ctx.projectRoot);
|
|
148839
|
+
const at = facts.head === undefined ? "" : ` at ${facts.head.slice(0, 12)}`;
|
|
148840
|
+
return {
|
|
148841
|
+
title: `Complete review ${cardSafe(facts.reviewId)}${at}?`,
|
|
148842
|
+
lines: [
|
|
148843
|
+
facts.head === undefined ? "reviewed head: not recorded (the package does not carry a commit)" : `reviewed head: ${facts.head}`,
|
|
148844
|
+
facts.evidence,
|
|
148845
|
+
...facts.waivers.length === 0 ? ["waivers: none"] : facts.waivers,
|
|
148846
|
+
...dispositions.map(dispositionLine),
|
|
148847
|
+
"yes: the head, evidence-level, research and readiness gates run now; a refusal closes nothing"
|
|
148848
|
+
],
|
|
148849
|
+
subject: `package ${name}${facts.head === undefined ? "" : `, head ${facts.head.slice(0, 12)}`}`,
|
|
148850
|
+
prepared: { dir, head: facts.head, digest: factsDigest(facts), dispositions }
|
|
148851
|
+
};
|
|
148852
|
+
} catch (error) {
|
|
148853
|
+
return failed(error);
|
|
148854
|
+
}
|
|
148855
|
+
},
|
|
148856
|
+
async apply(_input, ctx, confirmation, prepared) {
|
|
148857
|
+
if (prepared === undefined)
|
|
148858
|
+
return { output: "review_complete: nothing prepared for this card", isError: true };
|
|
148859
|
+
let facts;
|
|
148860
|
+
try {
|
|
148861
|
+
facts = await completionFacts(prepared.dir);
|
|
148862
|
+
} catch (error) {
|
|
148863
|
+
return { output: `review_complete refused: ${errorText4(error)} \u2014 nothing was recorded`, isError: true };
|
|
148864
|
+
}
|
|
148865
|
+
if (factsDigest(facts) !== prepared.digest) {
|
|
148866
|
+
return { output: "The head, the evidence level or the waivers changed after the card was shown; nothing was recorded. Ask again.", isError: true };
|
|
148867
|
+
}
|
|
148868
|
+
const run = await runCompletionGates(ctx.projectRoot, prepared, confirmation.at, seams);
|
|
148869
|
+
if (!run.closed)
|
|
148870
|
+
return { output: `review_complete refused: ${errorText4(run.gateError)} \u2014 nothing was recorded`, isError: true };
|
|
148871
|
+
const recordError = await writeCompletionRecord(prepared.dir, {
|
|
148872
|
+
quote: confirmation.quote,
|
|
148873
|
+
confirmedVia: confirmation.confirmedVia,
|
|
148874
|
+
...cardPressFields(confirmation),
|
|
148875
|
+
at: confirmation.at,
|
|
148876
|
+
head: prepared.head,
|
|
148877
|
+
evidence: facts.evidence,
|
|
148878
|
+
waivers: facts.waivers,
|
|
148879
|
+
dispositions: prepared.dispositions
|
|
148880
|
+
});
|
|
148881
|
+
return completionOutput(`review ${facts.reviewId} complete${prepared.head === undefined ? "" : ` at ${prepared.head.slice(0, 12)}`} (${confirmation.confirmedVia}, ${confirmation.at})`, run, recordError);
|
|
148882
|
+
}
|
|
148883
|
+
};
|
|
148884
|
+
}
|
|
148885
|
+
async function runCompletionGates(projectRoot, prepared, at, seams) {
|
|
148886
|
+
let done;
|
|
148887
|
+
let gateError;
|
|
148888
|
+
try {
|
|
148889
|
+
done = await completeReviewGated(projectRoot, prepared.dir, {
|
|
148890
|
+
dispositions: prepared.dispositions,
|
|
148891
|
+
prLookup: seams.prLookup ?? trackerPrLookup2,
|
|
148892
|
+
prFiles: seams.prFiles ?? githubPrFiles,
|
|
148893
|
+
now: new Date(at)
|
|
148894
|
+
});
|
|
148895
|
+
} catch (error) {
|
|
148896
|
+
gateError = error;
|
|
148897
|
+
}
|
|
148898
|
+
const closed = done !== undefined || (await completionFacts(prepared.dir).catch(() => {
|
|
148899
|
+
return;
|
|
148900
|
+
}))?.status === "closed";
|
|
148901
|
+
return { done, gateError, closed };
|
|
148902
|
+
}
|
|
148903
|
+
async function writeCompletionRecord(dir, record) {
|
|
148904
|
+
try {
|
|
148905
|
+
await writeFileAtomic(path337.join(dir, COMPLETION_FILE), `${JSON.stringify(record, null, 2)}
|
|
148906
|
+
`);
|
|
148907
|
+
return;
|
|
148908
|
+
} catch (error) {
|
|
148909
|
+
return error;
|
|
148910
|
+
}
|
|
148911
|
+
}
|
|
148912
|
+
function completionOutput(closedLine, run, recordError) {
|
|
148913
|
+
const problems = [];
|
|
148914
|
+
if (recordError !== undefined)
|
|
148915
|
+
problems.push(`the round is closed but the confirmation record could not be written: ${errorText4(recordError)}`);
|
|
148916
|
+
if (run.gateError !== undefined)
|
|
148917
|
+
problems.push(`the round is closed but its bookkeeping stopped: ${errorText4(run.gateError)}`);
|
|
148918
|
+
return {
|
|
148919
|
+
output: [
|
|
148920
|
+
closedLine,
|
|
148921
|
+
...problems,
|
|
148922
|
+
...run.done?.readiness === undefined ? [] : [readinessLine(run.done.readiness)],
|
|
148923
|
+
...(run.done?.warnings ?? []).map((warning) => `warning: ${warning}`)
|
|
148924
|
+
].join(`
|
|
148925
|
+
`),
|
|
148926
|
+
...problems.length === 0 ? {} : { isError: true }
|
|
148927
|
+
};
|
|
148928
|
+
}
|
|
148214
148929
|
function buildReviewConfirmSpecs(seams = {}) {
|
|
148215
148930
|
return [
|
|
148216
148931
|
chunksAnswerSpec(),
|
|
@@ -148219,25 +148934,33 @@ function buildReviewConfirmSpecs(seams = {}) {
|
|
|
148219
148934
|
waiveReadinessSpec(seams),
|
|
148220
148935
|
decisionSpec("mutation-waiver"),
|
|
148221
148936
|
decisionSpec("reviewer-override"),
|
|
148222
|
-
replySpec(),
|
|
148937
|
+
replySpec(seams),
|
|
148223
148938
|
confirmPlanSpec(),
|
|
148224
|
-
decidePremiseSpec()
|
|
148939
|
+
decidePremiseSpec(),
|
|
148940
|
+
completeSpec(seams)
|
|
148225
148941
|
];
|
|
148226
148942
|
}
|
|
148227
148943
|
function reviewConfirmSpecs() {
|
|
148228
148944
|
return buildReviewConfirmSpecs();
|
|
148229
148945
|
}
|
|
148230
|
-
var
|
|
148946
|
+
var packageSchema, label3 = (dir, projectRoot) => path337.relative(projectRoot, dir) || ".", failed = (error) => ({ refusal: error instanceof Error ? error.message : String(error) }), reasonOf2 = (input) => {
|
|
148231
148947
|
const value = input["reason"];
|
|
148232
|
-
|
|
148233
|
-
|
|
148948
|
+
const text = typeof value === "string" ? value.trim() : "";
|
|
148949
|
+
if (text === "")
|
|
148950
|
+
return { refusal: "reason is required" };
|
|
148951
|
+
return hiddenInCard(text) ? { refusal: `reason ${HIDDEN_IN_CARD}` } : { reason: text };
|
|
148952
|
+
}, COMPLETION_FILE = "completion-confirmation.json", MAX_DISPOSITION_FINDING_CHARS = 200, MAX_DISPOSITION_EVIDENCE_CHARS = 300, dispositionsSchema, dispositionLine = (record) => `disposition: ${cardSafe(record.finding)} \u2192 ${record.state}${record.evidence === undefined ? "" : `: "${cardSafe(record.evidence)}"`}`, factsDigest = (facts) => createHash85("sha256").update(JSON.stringify(facts)).digest("hex"), errorText4 = (error) => error instanceof Error ? error.message : String(error);
|
|
148234
148953
|
var init_operator_confirm_review = __esm(() => {
|
|
148954
|
+
init_types4();
|
|
148235
148955
|
init_service11();
|
|
148236
148956
|
init_review_path_flags();
|
|
148237
148957
|
init_blast_radius();
|
|
148238
148958
|
init_chunks();
|
|
148239
148959
|
init_chunks_estimate();
|
|
148960
|
+
init_complete_gated();
|
|
148240
148961
|
init_coverage_complete();
|
|
148962
|
+
init_evidence_level();
|
|
148963
|
+
init_fs();
|
|
148241
148964
|
init_managed();
|
|
148242
148965
|
init_publish();
|
|
148243
148966
|
init_readiness();
|
|
@@ -148246,6 +148969,24 @@ var init_operator_confirm_review = __esm(() => {
|
|
|
148246
148969
|
init_premise_question();
|
|
148247
148970
|
init_plan_confirmation();
|
|
148248
148971
|
packageSchema = { type: "string", description: "The review package: its directory or its review id." };
|
|
148972
|
+
dispositionsSchema = {
|
|
148973
|
+
type: "array",
|
|
148974
|
+
description: "Optional. How each earlier finding was handled in this fix round, recorded when the round closes (the same as `review complete --finding \u2026 --disposition \u2026 --evidence \u2026`). The operator sees every entry on the card, whole, so a long list makes a long card (a card over the Telegram limit is answered in the shell). A finding the package does not hold, and any state but `unknown` without evidence, refuse the card.",
|
|
148975
|
+
items: {
|
|
148976
|
+
type: "object",
|
|
148977
|
+
properties: {
|
|
148978
|
+
finding: { type: "string", maxLength: MAX_DISPOSITION_FINDING_CHARS, description: "The finding's global id or display id; it must be a finding this package holds." },
|
|
148979
|
+
disposition: { type: "string", enum: [...FINDING_DISPOSITION_STATES] },
|
|
148980
|
+
evidence: {
|
|
148981
|
+
type: "string",
|
|
148982
|
+
maxLength: MAX_DISPOSITION_EVIDENCE_CHARS,
|
|
148983
|
+
description: `The commit or reasoning behind the disposition; required for every state except \`unknown\`. At most ${MAX_DISPOSITION_EVIDENCE_CHARS} characters of plain single-line text; the card shows it whole.`
|
|
148984
|
+
}
|
|
148985
|
+
},
|
|
148986
|
+
required: ["finding", "disposition"],
|
|
148987
|
+
additionalProperties: false
|
|
148988
|
+
}
|
|
148989
|
+
};
|
|
148249
148990
|
});
|
|
148250
148991
|
|
|
148251
148992
|
// src/commands/operator-confirm-tools.ts
|
|
@@ -148297,7 +149038,7 @@ function operatorConfirmTool(spec, binding) {
|
|
|
148297
149038
|
const card = { title: built.title, lines: [...built.lines], subject: built.subject };
|
|
148298
149039
|
const shown = renderOperatorCard(card);
|
|
148299
149040
|
const token = randomUUID32();
|
|
148300
|
-
pending.set(token, { input: key(input), card, shown, prepared: built.prepared });
|
|
149041
|
+
pending.set(token, { input: key(input), card, shown, prepared: built.prepared, issuedAt: now().getTime() });
|
|
148301
149042
|
return { card: shown, title: card.title, token };
|
|
148302
149043
|
},
|
|
148303
149044
|
confirmationDeclined: (token) => {
|
|
@@ -148306,16 +149047,35 @@ function operatorConfirmTool(spec, binding) {
|
|
|
148306
149047
|
invoke: async (input, ctx) => {
|
|
148307
149048
|
const token = ctx?.confirmationToken;
|
|
148308
149049
|
const entry = token === undefined ? undefined : pending.get(token);
|
|
148309
|
-
|
|
148310
|
-
pending.delete(token);
|
|
149050
|
+
const refuse = (why) => ({ output: `${spec.name}: ${why} \u2014 nothing was changed`, isError: true });
|
|
148311
149051
|
if (entry === undefined || entry.input !== key(input)) {
|
|
148312
|
-
|
|
149052
|
+
if (token !== undefined)
|
|
149053
|
+
pending.delete(token);
|
|
149054
|
+
return refuse("no operator-confirmed card for this request");
|
|
149055
|
+
}
|
|
149056
|
+
pending.delete(token);
|
|
149057
|
+
const remote = ctx?.confirmationVia === "remote";
|
|
149058
|
+
let pressedBy;
|
|
149059
|
+
let pressedAt;
|
|
149060
|
+
if (remote) {
|
|
149061
|
+
pressedBy = ctx?.confirmationPressedBy;
|
|
149062
|
+
pressedAt = ctx?.confirmationPressedAt;
|
|
149063
|
+
const allowed = binding.allowedUserIds?.();
|
|
149064
|
+
if (pressedBy === undefined || pressedAt === undefined)
|
|
149065
|
+
return refuse("the remote answer did not say who pressed it");
|
|
149066
|
+
if (allowed === undefined || !allowed.includes(pressedBy)) {
|
|
149067
|
+
return refuse(`Telegram user ${pressedBy} is not on the allowed list`);
|
|
149068
|
+
}
|
|
149069
|
+
if (now().getTime() - entry.issuedAt > REMOTE_CARD_TTL_MS) {
|
|
149070
|
+
return refuse("the card is older than 1 hour; ask again");
|
|
149071
|
+
}
|
|
148313
149072
|
}
|
|
148314
149073
|
const confirmation = {
|
|
148315
149074
|
quote: `${entry.card.title}: yes`,
|
|
148316
149075
|
answer: "yes",
|
|
148317
149076
|
at: now().toISOString(),
|
|
148318
|
-
confirmedVia:
|
|
149077
|
+
confirmedVia: remote ? "remote-card" : "tui-card",
|
|
149078
|
+
...remote ? { pressedBy, pressedAt } : {},
|
|
148319
149079
|
subject: entry.card.subject,
|
|
148320
149080
|
card: entry.shown
|
|
148321
149081
|
};
|
|
@@ -148334,10 +149094,11 @@ function operatorConfirmSpecs() {
|
|
|
148334
149094
|
function operatorConfirmTools(binding, specs = operatorConfirmSpecs()) {
|
|
148335
149095
|
return specs.map((spec) => operatorConfirmTool(spec, binding));
|
|
148336
149096
|
}
|
|
148337
|
-
var OPERATOR_CONFIRM_NOTE;
|
|
149097
|
+
var REMOTE_CARD_TTL_MS, OPERATOR_CONFIRM_NOTE;
|
|
148338
149098
|
var init_operator_confirm_tools = __esm(() => {
|
|
148339
149099
|
init_operator_confirm_other();
|
|
148340
149100
|
init_operator_confirm_review();
|
|
149101
|
+
REMOTE_CARD_TTL_MS = 60 * 60 * 1000;
|
|
148341
149102
|
OPERATOR_CONFIRM_NOTE = " The operator ALWAYS sees a yes/no card in the shell and must answer it \u2014 you cannot confirm it, and you " + "must not ask them to run a command in a terminal instead.";
|
|
148342
149103
|
});
|
|
148343
149104
|
|
|
@@ -149520,7 +150281,7 @@ var init_workspace_lifecycle_tool = __esm(() => {
|
|
|
149520
150281
|
});
|
|
149521
150282
|
|
|
149522
150283
|
// src/mcp-servers/catalog.ts
|
|
149523
|
-
import { createHash as
|
|
150284
|
+
import { createHash as createHash86 } from "crypto";
|
|
149524
150285
|
function canonicalJson4(value) {
|
|
149525
150286
|
if (value === undefined)
|
|
149526
150287
|
return "null";
|
|
@@ -149538,7 +150299,7 @@ function toolDefinitionFingerprint(entry) {
|
|
|
149538
150299
|
description: entry.description ?? null,
|
|
149539
150300
|
inputSchema: entry.inputSchema ?? null
|
|
149540
150301
|
});
|
|
149541
|
-
return
|
|
150302
|
+
return createHash86("sha256").update(canonical).digest("hex");
|
|
149542
150303
|
}
|
|
149543
150304
|
function buildFqn(server, rawName) {
|
|
149544
150305
|
return `${server}${FQN_DELIMITER}${rawName}`;
|
|
@@ -149786,11 +150547,11 @@ var init_tools2 = __esm(() => {
|
|
|
149786
150547
|
});
|
|
149787
150548
|
|
|
149788
150549
|
// src/commands/review-arbiter-config.ts
|
|
149789
|
-
import { readFileSync as
|
|
150550
|
+
import { readFileSync as readFileSync40 } from "fs";
|
|
149790
150551
|
import path339 from "path";
|
|
149791
150552
|
function reviewArbiterBlock(cwd) {
|
|
149792
150553
|
try {
|
|
149793
|
-
const parsed = JSON.parse(
|
|
150554
|
+
const parsed = JSON.parse(readFileSync40(path339.join(cwd, REVIEW_GATE_CONFIG_PATH), "utf8"));
|
|
149794
150555
|
if (typeof parsed !== "object" || parsed === null)
|
|
149795
150556
|
return;
|
|
149796
150557
|
const review = parsed["review"];
|
|
@@ -150275,8 +151036,8 @@ var init_arbiter_rules = __esm(() => {
|
|
|
150275
151036
|
});
|
|
150276
151037
|
|
|
150277
151038
|
// src/review/arbiter-rules.ts
|
|
150278
|
-
import { createHash as
|
|
150279
|
-
import { existsSync as existsSync77, readFileSync as
|
|
151039
|
+
import { createHash as createHash87 } from "crypto";
|
|
151040
|
+
import { existsSync as existsSync77, readFileSync as readFileSync41 } from "fs";
|
|
150280
151041
|
import path340 from "path";
|
|
150281
151042
|
function isArbiterKind(value) {
|
|
150282
151043
|
return typeof value === "string" && ARBITER_KINDS.includes(value);
|
|
@@ -150347,7 +151108,7 @@ function parseArbiterRules(raw, source) {
|
|
|
150347
151108
|
});
|
|
150348
151109
|
if (errors.length > 0)
|
|
150349
151110
|
return { ok: false, errors };
|
|
150350
|
-
const hash =
|
|
151111
|
+
const hash = createHash87("sha256").update(JSON.stringify({ version, rules })).digest("hex").slice(0, 12);
|
|
150351
151112
|
return { ok: true, rules: { version, rules, source, hash } };
|
|
150352
151113
|
}
|
|
150353
151114
|
function parseWhen2(raw, where, errors) {
|
|
@@ -150426,7 +151187,7 @@ function loadArbiterRules(cwd) {
|
|
|
150426
151187
|
return { rules: bundledArbiterRules() };
|
|
150427
151188
|
let raw;
|
|
150428
151189
|
try {
|
|
150429
|
-
raw = JSON.parse(
|
|
151190
|
+
raw = JSON.parse(readFileSync41(file, "utf8"));
|
|
150430
151191
|
} catch (error) {
|
|
150431
151192
|
return { error: `${PROJECT_RULES_PATH} cannot be read as JSON: ${error instanceof Error ? error.message : String(error)}` };
|
|
150432
151193
|
}
|
|
@@ -150471,7 +151232,7 @@ var init_arbiter_rules2 = __esm(() => {
|
|
|
150471
151232
|
});
|
|
150472
151233
|
|
|
150473
151234
|
// src/commands/review-arbiter.ts
|
|
150474
|
-
import { createHash as
|
|
151235
|
+
import { createHash as createHash88 } from "crypto";
|
|
150475
151236
|
function parseQuestion(request) {
|
|
150476
151237
|
const policy = request.policy;
|
|
150477
151238
|
if (policy === undefined)
|
|
@@ -150592,7 +151353,7 @@ function checkModelAnswer(answer, question, rules) {
|
|
|
150592
151353
|
}
|
|
150593
151354
|
function cacheKey(question) {
|
|
150594
151355
|
const facts = Object.keys(question.facts).sort().map((name) => [name, question.facts[name]]);
|
|
150595
|
-
return
|
|
151356
|
+
return createHash88("sha256").update(JSON.stringify([question.kind, facts, question.optionCount, question.recommended])).digest("hex");
|
|
150596
151357
|
}
|
|
150597
151358
|
function costOf2(prices, modelId, inputTokens, outputTokens) {
|
|
150598
151359
|
const price = prices[modelId];
|
|
@@ -151068,7 +151829,7 @@ var init_shell_events = __esm(() => {
|
|
|
151068
151829
|
// src/lib/shell-permissions.ts
|
|
151069
151830
|
import { existsSync as existsSync78 } from "fs";
|
|
151070
151831
|
import path341 from "path";
|
|
151071
|
-
import { createHash as
|
|
151832
|
+
import { createHash as createHash89 } from "crypto";
|
|
151072
151833
|
function validateShellPattern(pattern) {
|
|
151073
151834
|
const trimmed = pattern.trim();
|
|
151074
151835
|
if (trimmed.length === 0) {
|
|
@@ -151303,7 +152064,7 @@ function shellPermissionsFingerprint(dir) {
|
|
|
151303
152064
|
if (!read.ok) {
|
|
151304
152065
|
return "";
|
|
151305
152066
|
}
|
|
151306
|
-
return
|
|
152067
|
+
return createHash89("sha256").update(read.text, "utf8").digest("hex");
|
|
151307
152068
|
} catch {
|
|
151308
152069
|
return "";
|
|
151309
152070
|
}
|
|
@@ -153394,7 +154155,7 @@ var init_child_model_request = __esm(() => {
|
|
|
153394
154155
|
});
|
|
153395
154156
|
|
|
153396
154157
|
// src/harness/tool/builtin/spawn-subagent-tool.ts
|
|
153397
|
-
import { createHash as
|
|
154158
|
+
import { createHash as createHash90, randomUUID as randomUUID37 } from "crypto";
|
|
153398
154159
|
import { execFile as execFile14 } from "child_process";
|
|
153399
154160
|
import { lstat as lstat22, mkdir as mkdir78, mkdtemp as mkdtemp10, readFile as readFile182, realpath as realpath29, rm as rm26, stat as stat42, utimes as utimes2, writeFile as writeFile49 } from "fs/promises";
|
|
153400
154161
|
import { tmpdir as tmpdir13 } from "os";
|
|
@@ -153700,7 +154461,7 @@ function readExternalRuntimeRequest(input) {
|
|
|
153700
154461
|
return agent === block.agent ? block : { ...block, agent };
|
|
153701
154462
|
}
|
|
153702
154463
|
function sha25611(text) {
|
|
153703
|
-
return
|
|
154464
|
+
return createHash90("sha256").update(text).digest("hex");
|
|
153704
154465
|
}
|
|
153705
154466
|
async function resolveSubagentCwd(rawCwd, parentCwd) {
|
|
153706
154467
|
if (rawCwd === undefined) {
|
|
@@ -155121,7 +155882,7 @@ var init_http_headers = __esm(() => {
|
|
|
155121
155882
|
});
|
|
155122
155883
|
|
|
155123
155884
|
// src/mcp-servers/trust.ts
|
|
155124
|
-
import { createHash as
|
|
155885
|
+
import { createHash as createHash91 } from "crypto";
|
|
155125
155886
|
import path348 from "path";
|
|
155126
155887
|
function trustFile(configDir) {
|
|
155127
155888
|
return path348.join(configDir ?? ensureKeryxConfigDir(), "mcp-servers-trust.json");
|
|
@@ -155144,7 +155905,7 @@ function serverFingerprint(server) {
|
|
|
155144
155905
|
}
|
|
155145
155906
|
}
|
|
155146
155907
|
});
|
|
155147
|
-
return
|
|
155908
|
+
return createHash91("sha256").update(material).digest("hex").slice(0, 32);
|
|
155148
155909
|
}
|
|
155149
155910
|
function sortedKeys(values) {
|
|
155150
155911
|
return Object.entries(values ?? {}).sort(([a], [b]) => a.localeCompare(b));
|
|
@@ -158382,7 +159143,7 @@ var init_references_check = __esm(() => {
|
|
|
158382
159143
|
init_project_skills();
|
|
158383
159144
|
init_references_config();
|
|
158384
159145
|
init_references_section();
|
|
158385
|
-
DOCUMENT_CITATION = /`([^`\s]+\.md)
|
|
159146
|
+
DOCUMENT_CITATION = /`([^`\s#]+\.md)(?:#[^`\s]*)?`/g;
|
|
158386
159147
|
});
|
|
158387
159148
|
|
|
158388
159149
|
// src/gdskills/project-context-status.ts
|
|
@@ -160475,7 +161236,7 @@ var init_help_groups = __esm(() => {
|
|
|
160475
161236
|
kind: "slash",
|
|
160476
161237
|
name: "/decisions",
|
|
160477
161238
|
group: "Automation",
|
|
160478
|
-
summary: "Recommendation journal report: match share by mode and stage, the deviations with their reasons, and your own answers and typed reasons in full (marked \u270D in the sidebar row)."
|
|
161239
|
+
summary: "Recommendation journal report: match share by mode and stage, the deviations with their reasons, and your own answers and typed reasons in full (marked \u270D in the sidebar row), and the Telegram review cards waiting or answered (review_confirm_plan, review_decide_premise, review_waive_readiness, review_waive_coverage, review_complete)."
|
|
160479
161240
|
},
|
|
160480
161241
|
{
|
|
160481
161242
|
kind: "slash",
|
|
@@ -160493,7 +161254,7 @@ var init_help_groups = __esm(() => {
|
|
|
160493
161254
|
kind: "slash",
|
|
160494
161255
|
name: "/remote-control",
|
|
160495
161256
|
group: "Automation",
|
|
160496
|
-
summary: 'Drive this session from a Telegram topic: send lines, get replies, answer approvals and questions (an option button, or "\u270D \u0421\u0432\u043E\u0439 \u043E\u0442\u0432\u0435\u0442" and a reply with your own text). Off by default; [name|off|status].'
|
|
161257
|
+
summary: 'Drive this session from a Telegram topic: send lines, get replies, answer approvals and questions (an option button, or "\u270D \u0421\u0432\u043E\u0439 \u043E\u0442\u0432\u0435\u0442" and a reply with your own text). Off by default; [name|off|status]. Also takes the review cards (review_confirm_plan, review_decide_premise, review_waive_readiness, review_waive_coverage, review_complete): each pending one shows with its age and the time left of its 1-hour token, and who pressed.'
|
|
160497
161258
|
},
|
|
160498
161259
|
{
|
|
160499
161260
|
kind: "slash",
|
|
@@ -164653,7 +165414,7 @@ var init_extract3 = __esm(() => {
|
|
|
164653
165414
|
});
|
|
164654
165415
|
|
|
164655
165416
|
// src/product/corpus.ts
|
|
164656
|
-
import { createHash as
|
|
165417
|
+
import { createHash as createHash92 } from "crypto";
|
|
164657
165418
|
import { readdir as readdir67, readFile as readFile191 } from "fs/promises";
|
|
164658
165419
|
import path366 from "path";
|
|
164659
165420
|
function flowsDir(cwd) {
|
|
@@ -164698,7 +165459,7 @@ function digestOf4(content) {
|
|
|
164698
165459
|
return "absent";
|
|
164699
165460
|
if (content === "unreadable")
|
|
164700
165461
|
return "unreadable";
|
|
164701
|
-
return
|
|
165462
|
+
return createHash92("sha256").update(content).digest("hex");
|
|
164702
165463
|
}
|
|
164703
165464
|
async function readBytes(file) {
|
|
164704
165465
|
try {
|
|
@@ -164732,7 +165493,7 @@ async function corpusFingerprint(cwd) {
|
|
|
164732
165493
|
const specification = await readBytes(path366.join(dir, "specification.md"));
|
|
164733
165494
|
lines.push(`file\x00docs/requirements/${name}/specification.md\x00${digestOf4(specification)}`);
|
|
164734
165495
|
}
|
|
164735
|
-
return
|
|
165496
|
+
return createHash92("sha256").update([...lines].sort().join(`
|
|
164736
165497
|
`)).digest("hex");
|
|
164737
165498
|
}
|
|
164738
165499
|
function idOrder(intent) {
|
|
@@ -166909,7 +167670,8 @@ function mapApprovalAnswer(got, offered) {
|
|
|
166909
167670
|
decision: yes ? "allow" : "deny",
|
|
166910
167671
|
always: o.decision === "always" && offered,
|
|
166911
167672
|
...approvalId === undefined ? {} : { approvalId },
|
|
166912
|
-
...fromId === undefined ? {} : { fromId }
|
|
167673
|
+
...fromId === undefined ? {} : { fromId },
|
|
167674
|
+
...!yes && o.undelivered === true ? { undelivered: true } : {}
|
|
166913
167675
|
};
|
|
166914
167676
|
}
|
|
166915
167677
|
function composeApprovalPrompt(prompt) {
|
|
@@ -166966,6 +167728,13 @@ function operatorCardTooLongForTopic(tool, inputJson, meta) {
|
|
|
166966
167728
|
return false;
|
|
166967
167729
|
return redactSensitiveText(describeApprovalForTopic(tool, inputJson, meta)).trim().length > MAX_APPROVAL_PROMPT_CHARS;
|
|
166968
167730
|
}
|
|
167731
|
+
function mirrorUndeliveredNote(bridge) {
|
|
167732
|
+
if (bridge === undefined || !bridge.active)
|
|
167733
|
+
return `${MIRROR_NOT_DELIVERED}: remote control is not on in this shell. Answer it here.`;
|
|
167734
|
+
if (!bridge.connected)
|
|
167735
|
+
return `${MIRROR_NOT_DELIVERED}: the remote channel is not connected. Answer it here.`;
|
|
167736
|
+
return;
|
|
167737
|
+
}
|
|
166969
167738
|
|
|
166970
167739
|
class RemoteBridge {
|
|
166971
167740
|
options;
|
|
@@ -167070,6 +167839,9 @@ class RemoteBridge {
|
|
|
167070
167839
|
get active() {
|
|
167071
167840
|
return this.client !== undefined;
|
|
167072
167841
|
}
|
|
167842
|
+
get connected() {
|
|
167843
|
+
return this.client?.connected === true;
|
|
167844
|
+
}
|
|
167073
167845
|
get telegramTurnActive() {
|
|
167074
167846
|
return this.tgTurn;
|
|
167075
167847
|
}
|
|
@@ -167484,7 +168256,7 @@ class RemoteBridge {
|
|
|
167484
168256
|
const client = this.client;
|
|
167485
168257
|
if (client === undefined || !client.connected) {
|
|
167486
168258
|
this.push("approval", "denied: not connected");
|
|
167487
|
-
return { decision: "deny", always: false };
|
|
168259
|
+
return { decision: "deny", always: false, undelivered: true };
|
|
167488
168260
|
}
|
|
167489
168261
|
this.push("approval", "asked in the topic");
|
|
167490
168262
|
let answer;
|
|
@@ -167563,7 +168335,7 @@ class RemoteBridge {
|
|
|
167563
168335
|
await this.client?.reportApprovalResult?.(approvalId, remembered);
|
|
167564
168336
|
}
|
|
167565
168337
|
}
|
|
167566
|
-
var TG_SOURCE = "tg", REMOTE_EVENT_LIMIT = 20, REMOTE_REPLY_MAX_CHARS = 16000, EVENT_PREVIEW_CHARS = 80, HISTORY_PACE_MS = 2000;
|
|
168338
|
+
var TG_SOURCE = "tg", REMOTE_EVENT_LIMIT = 20, REMOTE_REPLY_MAX_CHARS = 16000, EVENT_PREVIEW_CHARS = 80, HISTORY_PACE_MS = 2000, MIRROR_NOT_DELIVERED = "Telegram mirror not delivered";
|
|
167567
168339
|
var init_shell_bridge = __esm(() => {
|
|
167568
168340
|
init_approval_cause();
|
|
167569
168341
|
init_service2();
|
|
@@ -167574,45 +168346,6 @@ var init_shell_bridge = __esm(() => {
|
|
|
167574
168346
|
init_history2();
|
|
167575
168347
|
});
|
|
167576
168348
|
|
|
167577
|
-
// src/tui/approval-mirror.ts
|
|
167578
|
-
function isTopicPress(answer) {
|
|
167579
|
-
return answer.decision === "allow" || answer.fromId !== undefined;
|
|
167580
|
-
}
|
|
167581
|
-
async function raceApproval(options) {
|
|
167582
|
-
const dockAbort = new AbortController;
|
|
167583
|
-
const topicAbort = new AbortController;
|
|
167584
|
-
let topicAnswer;
|
|
167585
|
-
let settled = false;
|
|
167586
|
-
let started = false;
|
|
167587
|
-
const race = {
|
|
167588
|
-
signal: AbortSignal.any([options.turnSignal, dockAbort.signal]),
|
|
167589
|
-
opened: () => {
|
|
167590
|
-
if (started || settled)
|
|
167591
|
-
return;
|
|
167592
|
-
started = true;
|
|
167593
|
-
options.topic(topicAbort.signal).then((answer) => {
|
|
167594
|
-
if (settled || !isTopicPress(answer))
|
|
167595
|
-
return;
|
|
167596
|
-
topicAnswer = answer;
|
|
167597
|
-
dockAbort.abort("topic");
|
|
167598
|
-
}).catch(() => {
|
|
167599
|
-
return;
|
|
167600
|
-
});
|
|
167601
|
-
},
|
|
167602
|
-
topicWon: () => topicAnswer !== undefined
|
|
167603
|
-
};
|
|
167604
|
-
try {
|
|
167605
|
-
const result = await options.dock(race);
|
|
167606
|
-
if (topicAnswer !== undefined)
|
|
167607
|
-
return { from: "topic", answer: topicAnswer };
|
|
167608
|
-
return { from: "dock", result };
|
|
167609
|
-
} finally {
|
|
167610
|
-
settled = true;
|
|
167611
|
-
if (topicAnswer === undefined)
|
|
167612
|
-
topicAbort.abort(options.turnSignal.aborted ? "cancelled" : "shell");
|
|
167613
|
-
}
|
|
167614
|
-
}
|
|
167615
|
-
|
|
167616
168349
|
// src/remote/history-host.ts
|
|
167617
168350
|
function sessionEntryOf(resumed) {
|
|
167618
168351
|
return { resumed, kind: resumed ? "resumed" : "new" };
|
|
@@ -168974,6 +169707,212 @@ var init_channels_surface = __esm(() => {
|
|
|
168974
169707
|
TOKEN_CHAR = /^[A-Za-z0-9_:-]$/;
|
|
168975
169708
|
});
|
|
168976
169709
|
|
|
169710
|
+
// src/tui/approval-mirror.ts
|
|
169711
|
+
function isTopicPress(answer) {
|
|
169712
|
+
return answer.decision === "allow" || answer.fromId !== undefined;
|
|
169713
|
+
}
|
|
169714
|
+
async function raceApproval(options) {
|
|
169715
|
+
const dockAbort = new AbortController;
|
|
169716
|
+
const topicAbort = new AbortController;
|
|
169717
|
+
let topicAnswer;
|
|
169718
|
+
let settled = false;
|
|
169719
|
+
let started = false;
|
|
169720
|
+
const race = {
|
|
169721
|
+
signal: AbortSignal.any([options.turnSignal, dockAbort.signal]),
|
|
169722
|
+
opened: () => {
|
|
169723
|
+
if (started || settled)
|
|
169724
|
+
return;
|
|
169725
|
+
started = true;
|
|
169726
|
+
options.topic(topicAbort.signal).then((answer) => {
|
|
169727
|
+
if (settled)
|
|
169728
|
+
return;
|
|
169729
|
+
if (answer.undelivered === true)
|
|
169730
|
+
options.onUndelivered?.();
|
|
169731
|
+
else if (!isTopicPress(answer) && !topicAbort.signal.aborted)
|
|
169732
|
+
options.onTopicEnded?.();
|
|
169733
|
+
if (!isTopicPress(answer))
|
|
169734
|
+
return;
|
|
169735
|
+
topicAnswer = answer;
|
|
169736
|
+
dockAbort.abort("topic");
|
|
169737
|
+
}).catch(() => {
|
|
169738
|
+
return;
|
|
169739
|
+
});
|
|
169740
|
+
},
|
|
169741
|
+
topicWon: () => topicAnswer !== undefined
|
|
169742
|
+
};
|
|
169743
|
+
try {
|
|
169744
|
+
const result = await options.dock(race);
|
|
169745
|
+
if (topicAnswer !== undefined)
|
|
169746
|
+
return { from: "topic", answer: topicAnswer };
|
|
169747
|
+
return { from: "dock", result };
|
|
169748
|
+
} finally {
|
|
169749
|
+
settled = true;
|
|
169750
|
+
if (topicAnswer === undefined)
|
|
169751
|
+
topicAbort.abort(options.turnSignal.aborted ? "cancelled" : "shell");
|
|
169752
|
+
}
|
|
169753
|
+
}
|
|
169754
|
+
|
|
169755
|
+
// src/tui/operator-confirmations.ts
|
|
169756
|
+
class OperatorConfirmationLog {
|
|
169757
|
+
now;
|
|
169758
|
+
items = [];
|
|
169759
|
+
nextId = 1;
|
|
169760
|
+
listeners = new Set;
|
|
169761
|
+
constructor(now = () => Date.now()) {
|
|
169762
|
+
this.now = now;
|
|
169763
|
+
}
|
|
169764
|
+
ask(question, mirrored) {
|
|
169765
|
+
const id = this.nextId++;
|
|
169766
|
+
this.items.push({ id, question, askedAt: this.now(), mirrored });
|
|
169767
|
+
this.changed();
|
|
169768
|
+
return id;
|
|
169769
|
+
}
|
|
169770
|
+
answer(id, result) {
|
|
169771
|
+
const at = this.items.findIndex((item) => item.id === id);
|
|
169772
|
+
const entry = this.items[at];
|
|
169773
|
+
if (entry === undefined || entry.answered !== undefined)
|
|
169774
|
+
return;
|
|
169775
|
+
this.items[at] = {
|
|
169776
|
+
...entry,
|
|
169777
|
+
answered: { place: result.place, yes: result.yes, at: this.now(), ...result.pressedBy === undefined ? {} : { pressedBy: result.pressedBy } }
|
|
169778
|
+
};
|
|
169779
|
+
const answered = this.items.filter((item) => item.answered !== undefined);
|
|
169780
|
+
for (const old of answered.slice(0, Math.max(0, answered.length - ANSWERED_CARDS_KEPT))) {
|
|
169781
|
+
this.items.splice(this.items.indexOf(old), 1);
|
|
169782
|
+
}
|
|
169783
|
+
this.changed();
|
|
169784
|
+
}
|
|
169785
|
+
settle(id, outcome, turnAborted) {
|
|
169786
|
+
if (outcome === undefined)
|
|
169787
|
+
this.answer(id, { place: "withdrawn", yes: false });
|
|
169788
|
+
else if (outcome.from === "dock")
|
|
169789
|
+
this.answer(id, { place: turnAborted ? "withdrawn" : "shell", yes: outcome.result !== false });
|
|
169790
|
+
else
|
|
169791
|
+
this.answer(id, { place: "telegram", yes: outcome.answer.decision === "allow", ...outcome.answer.fromId === undefined ? {} : { pressedBy: outcome.answer.fromId } });
|
|
169792
|
+
}
|
|
169793
|
+
markUndelivered(id, reason = "undelivered") {
|
|
169794
|
+
const at = this.items.findIndex((item) => item.id === id);
|
|
169795
|
+
const entry = this.items[at];
|
|
169796
|
+
if (entry === undefined || entry.answered !== undefined || !entry.mirrored)
|
|
169797
|
+
return;
|
|
169798
|
+
this.items[at] = { ...entry, mirrored: false, ...reason === "ended" ? { topicEnded: true } : {} };
|
|
169799
|
+
this.changed();
|
|
169800
|
+
}
|
|
169801
|
+
waiting() {
|
|
169802
|
+
const now = this.now();
|
|
169803
|
+
return this.items.filter((item) => item.answered === undefined && item.mirrored && now - item.askedAt <= REMOTE_CARD_TTL_MS);
|
|
169804
|
+
}
|
|
169805
|
+
entries() {
|
|
169806
|
+
const open = this.items.filter((item) => item.answered === undefined);
|
|
169807
|
+
const done = this.items.filter((item) => item.answered !== undefined).reverse();
|
|
169808
|
+
return [...open, ...done];
|
|
169809
|
+
}
|
|
169810
|
+
subscribe(listener) {
|
|
169811
|
+
this.listeners.add(listener);
|
|
169812
|
+
return () => {
|
|
169813
|
+
this.listeners.delete(listener);
|
|
169814
|
+
};
|
|
169815
|
+
}
|
|
169816
|
+
changed() {
|
|
169817
|
+
for (const listener of [...this.listeners])
|
|
169818
|
+
listener();
|
|
169819
|
+
}
|
|
169820
|
+
}
|
|
169821
|
+
function age(ms) {
|
|
169822
|
+
const seconds = Math.max(0, Math.round(ms / 1000));
|
|
169823
|
+
if (seconds < 60)
|
|
169824
|
+
return `${seconds}s`;
|
|
169825
|
+
const minutes = Math.floor(seconds / 60);
|
|
169826
|
+
if (minutes < 60)
|
|
169827
|
+
return `${minutes}m`;
|
|
169828
|
+
return `${Math.floor(minutes / 60)}h${String(minutes % 60).padStart(2, "0")}m`;
|
|
169829
|
+
}
|
|
169830
|
+
function clock2(at) {
|
|
169831
|
+
return new Date(at).toISOString().slice(11, 19);
|
|
169832
|
+
}
|
|
169833
|
+
function clip7(text, max) {
|
|
169834
|
+
const flat = text.replace(/\s+/g, " ").trim();
|
|
169835
|
+
return flat.length <= max ? flat : `${flat.slice(0, Math.max(0, max - 1))}\u2026`;
|
|
169836
|
+
}
|
|
169837
|
+
function pendingConfirmationLine(entry, now) {
|
|
169838
|
+
const elapsed = now - entry.askedAt;
|
|
169839
|
+
const left = REMOTE_CARD_TTL_MS - elapsed;
|
|
169840
|
+
const token = left > 0 ? `${age(left)} left of the 1-hour token` : "Telegram token expired, answer in the shell";
|
|
169841
|
+
const where = entry.mirrored ? "" : entry.topicEnded === true ? " \xB7 Telegram is no longer waiting for this card, answer in the shell" : " \xB7 Telegram mirror not delivered, answer in the shell";
|
|
169842
|
+
return `${clip7(entry.question, 80)} \xB7 asked ${age(elapsed)} ago \xB7 ${token}${where}`;
|
|
169843
|
+
}
|
|
169844
|
+
function answeredConfirmationLine(entry) {
|
|
169845
|
+
const done = entry.answered;
|
|
169846
|
+
if (done === undefined)
|
|
169847
|
+
return clip7(entry.question, 80);
|
|
169848
|
+
const verdict = done.yes ? "yes" : "no";
|
|
169849
|
+
if (done.place === "telegram") {
|
|
169850
|
+
const who = done.pressedBy === undefined ? "a Telegram user" : `Telegram user ${done.pressedBy}`;
|
|
169851
|
+
return `${clip7(entry.question, 80)} \xB7 ${verdict} \xB7 pressed by ${who} at ${clock2(done.at)}`;
|
|
169852
|
+
}
|
|
169853
|
+
if (done.place === "withdrawn")
|
|
169854
|
+
return `${clip7(entry.question, 80)} \xB7 withdrawn (the turn stopped) at ${clock2(done.at)}`;
|
|
169855
|
+
return `${clip7(entry.question, 80)} \xB7 ${verdict} \xB7 answered in the shell at ${clock2(done.at)}`;
|
|
169856
|
+
}
|
|
169857
|
+
function confirmationLines(entries, now, heading = "Operator confirmations (Telegram cards)") {
|
|
169858
|
+
if (entries.length === 0)
|
|
169859
|
+
return [];
|
|
169860
|
+
const waiting = entries.filter((entry) => entry.answered === undefined);
|
|
169861
|
+
const answered = entries.filter((entry) => entry.answered !== undefined);
|
|
169862
|
+
const lines = [heading];
|
|
169863
|
+
for (const entry of waiting)
|
|
169864
|
+
lines.push(` waiting ${pendingConfirmationLine(entry, now)}`);
|
|
169865
|
+
for (const entry of answered)
|
|
169866
|
+
lines.push(` answered ${answeredConfirmationLine(entry)}`);
|
|
169867
|
+
return lines;
|
|
169868
|
+
}
|
|
169869
|
+
function waitingCardsText(waiting) {
|
|
169870
|
+
if (waiting <= 0)
|
|
169871
|
+
return;
|
|
169872
|
+
return `${waiting} card${waiting === 1 ? "" : "s"} waiting`;
|
|
169873
|
+
}
|
|
169874
|
+
function remoteApproval(fingerprint, fromId, now = () => new Date) {
|
|
169875
|
+
return { approved: true, fingerprint, via: "remote", ...fromId !== undefined ? { pressedBy: fromId } : {}, pressedAt: now().toISOString() };
|
|
169876
|
+
}
|
|
169877
|
+
async function askUnmirroredCard(log, question, turnSignal, ask) {
|
|
169878
|
+
const id = log.ask(question, false);
|
|
169879
|
+
let verdict;
|
|
169880
|
+
let answered = false;
|
|
169881
|
+
try {
|
|
169882
|
+
verdict = await ask();
|
|
169883
|
+
answered = true;
|
|
169884
|
+
return verdict;
|
|
169885
|
+
} finally {
|
|
169886
|
+
log.answer(id, { place: turnSignal.aborted || !answered ? "withdrawn" : "shell", yes: answered && verdict !== false });
|
|
169887
|
+
}
|
|
169888
|
+
}
|
|
169889
|
+
async function raceMirroredCard(options) {
|
|
169890
|
+
const id = options.question === undefined ? undefined : options.log.ask(options.question, true);
|
|
169891
|
+
let outcome;
|
|
169892
|
+
try {
|
|
169893
|
+
outcome = await raceApproval({
|
|
169894
|
+
turnSignal: options.turnSignal,
|
|
169895
|
+
...id === undefined ? {} : {
|
|
169896
|
+
onUndelivered: () => {
|
|
169897
|
+
options.log.markUndelivered(id);
|
|
169898
|
+
options.onUndelivered?.();
|
|
169899
|
+
},
|
|
169900
|
+
onTopicEnded: () => options.log.markUndelivered(id, "ended")
|
|
169901
|
+
},
|
|
169902
|
+
dock: options.dock,
|
|
169903
|
+
topic: options.topic
|
|
169904
|
+
});
|
|
169905
|
+
return outcome;
|
|
169906
|
+
} finally {
|
|
169907
|
+
if (id !== undefined)
|
|
169908
|
+
options.log.settle(id, outcome, options.turnSignal.aborted);
|
|
169909
|
+
}
|
|
169910
|
+
}
|
|
169911
|
+
var ANSWERED_CARDS_KEPT = 5;
|
|
169912
|
+
var init_operator_confirmations = __esm(() => {
|
|
169913
|
+
init_operator_confirm_tools();
|
|
169914
|
+
});
|
|
169915
|
+
|
|
168977
169916
|
// src/tui/remote-control-surface.ts
|
|
168978
169917
|
function commandEchoText(line, source) {
|
|
168979
169918
|
return source === TG_SOURCE ? `${TG_ECHO_MARKER} ${line}` : `\u276F ${line}`;
|
|
@@ -169023,9 +169962,9 @@ function formatHistoryStatus(status) {
|
|
|
169023
169962
|
const history = status.history;
|
|
169024
169963
|
if (history === undefined)
|
|
169025
169964
|
return `History: not posted yet. Use ${HISTORY_COMMAND} [N], or press h here.`;
|
|
169026
|
-
return `History: ${history.count} message${history.count === 1 ? "" : "s"} posted at ${
|
|
169965
|
+
return `History: ${history.count} message${history.count === 1 ? "" : "s"} posted at ${clock3(history.at)} (${history.auto ? "restored automatically" : HISTORY_COMMAND}). Again: ${HISTORY_COMMAND} [N], or press h here.`;
|
|
169027
169966
|
}
|
|
169028
|
-
function formatRemoteStatusLines(status, posture) {
|
|
169967
|
+
function formatRemoteStatusLines(status, posture, confirmations) {
|
|
169029
169968
|
const lines = [`Remote control: ${STATE_WORDS[status.state]}`];
|
|
169030
169969
|
if (status.state === "off") {
|
|
169031
169970
|
lines.push("Topic: none");
|
|
@@ -169041,25 +169980,27 @@ function formatRemoteStatusLines(status, posture) {
|
|
|
169041
169980
|
const n = status.unconfirmedApprovals;
|
|
169042
169981
|
lines.push(`Approvals not confirmed: ${n}. The topic shows ${n === 1 ? "it" : "them"} as "not confirmed" until serve takes the shell's ack.`);
|
|
169043
169982
|
}
|
|
169983
|
+
if (confirmations !== undefined && confirmations.length > 0)
|
|
169984
|
+
lines.push("", ...confirmations);
|
|
169044
169985
|
lines.push(`Turn it off with ${REMOTE_CONTROL_COMMAND} off, or press o here. Rename: ${REMOTE_CONTROL_COMMAND} off, then ${REMOTE_CONTROL_COMMAND} <name>.`);
|
|
169045
169986
|
if (posture !== undefined && posture.length > 0) {
|
|
169046
169987
|
lines.push("", "Telegram permissions (change with /remote-policy):", ...posture.map((line) => ` ${line}`));
|
|
169047
169988
|
}
|
|
169048
169989
|
return lines;
|
|
169049
169990
|
}
|
|
169050
|
-
function
|
|
169991
|
+
function clock3(at) {
|
|
169051
169992
|
return new Date(at).toISOString().slice(11, 19);
|
|
169052
169993
|
}
|
|
169053
169994
|
function formatRemoteEventLines(status) {
|
|
169054
169995
|
if (status.events.length === 0)
|
|
169055
169996
|
return ["No remote events yet."];
|
|
169056
|
-
return [...status.events].reverse().slice(0, REMOTE_EVENT_LIMIT).map((event) => `${
|
|
169997
|
+
return [...status.events].reverse().slice(0, REMOTE_EVENT_LIMIT).map((event) => `${clock3(event.at)} ${event.kind.padEnd(8)} ${event.text}`);
|
|
169057
169998
|
}
|
|
169058
169999
|
function formatRemoteCommandLines(status) {
|
|
169059
170000
|
const commands = status.events.filter((event) => event.kind === "command");
|
|
169060
170001
|
if (commands.length === 0)
|
|
169061
170002
|
return ["No remote commands yet."];
|
|
169062
|
-
return [...commands].reverse().slice(0, REMOTE_EVENT_LIMIT).map((event) => `${
|
|
170003
|
+
return [...commands].reverse().slice(0, REMOTE_EVENT_LIMIT).map((event) => `${clock3(event.at)} ${event.text}`);
|
|
169063
170004
|
}
|
|
169064
170005
|
function renderRemoteControlText(status) {
|
|
169065
170006
|
return `${[...formatRemoteStatusLines(status), "", "Recent events:", ...formatRemoteEventLines(status)].join(`
|
|
@@ -169105,7 +170046,7 @@ function presentRemoteControl(open, otui, chrome, options) {
|
|
|
169105
170046
|
return formatRemoteEventLines(status);
|
|
169106
170047
|
if (tab === "commands")
|
|
169107
170048
|
return formatRemoteCommandLines(status);
|
|
169108
|
-
const lines = formatRemoteStatusLines(status, options.getPostureLines?.());
|
|
170049
|
+
const lines = formatRemoteStatusLines(status, options.getPostureLines?.(), options.getConfirmationLines?.());
|
|
169109
170050
|
if (historyNote !== undefined) {
|
|
169110
170051
|
const at = lines.findIndex((line) => line.startsWith("History:"));
|
|
169111
170052
|
lines.splice(at < 0 ? lines.length : at + 1, 0, historyNote);
|
|
@@ -169196,14 +170137,17 @@ function fit3(text, width) {
|
|
|
169196
170137
|
return "";
|
|
169197
170138
|
return text.length <= width ? text : `${text.slice(0, Math.max(0, width - 1))}\u2026`;
|
|
169198
170139
|
}
|
|
169199
|
-
function projectRemoteRow(status, width) {
|
|
170140
|
+
function projectRemoteRow(status, width, waitingCards = 0) {
|
|
169200
170141
|
if (status === undefined || status.state === "off")
|
|
169201
170142
|
return { text: fit3("off", width), role: "muted", action: "open" };
|
|
169202
|
-
const detail = status.history === undefined ? {} : { detail: fit3(`history ${status.history.count} \xB7 ${
|
|
170143
|
+
const detail = status.history === undefined ? {} : { detail: fit3(`history ${status.history.count} \xB7 ${clock3(status.history.at).slice(0, 5)}`, width) };
|
|
169203
170144
|
if (status.state === "offline")
|
|
169204
170145
|
return { text: fit3("offline", width), role: "attention", action: "open", ...detail };
|
|
169205
170146
|
const topic = status.name ?? "connecting";
|
|
169206
170147
|
const unconfirmed = status.unconfirmedApprovals ?? 0;
|
|
170148
|
+
const cards = waitingCardsText(waitingCards);
|
|
170149
|
+
if (cards !== undefined)
|
|
170150
|
+
return { text: fit3(`${topic} \xB7 ${cards}`, width), role: "attention", action: "open", ...detail };
|
|
169207
170151
|
if (unconfirmed > 0)
|
|
169208
170152
|
return { text: fit3(`${topic} \xB7 ${unconfirmed} unconfirmed`, width), role: "attention", action: "open", ...detail };
|
|
169209
170153
|
return { text: fit3(topic, width), role: "ok", action: "open", ...detail };
|
|
@@ -169232,7 +170176,7 @@ function mountRemotePanel(otui, renderer, parent, options) {
|
|
|
169232
170176
|
box.add(detail);
|
|
169233
170177
|
const posture = new core.TextRenderable(renderer, { id: "sb-remote-p", content: "" });
|
|
169234
170178
|
box.add(posture);
|
|
169235
|
-
const currentRow = () => projectRemoteRow(options.getStatus(), options.width);
|
|
170179
|
+
const currentRow = () => projectRemoteRow(options.getStatus(), options.width, options.getWaitingCards?.() ?? 0);
|
|
169236
170180
|
const draw = () => {
|
|
169237
170181
|
if (disposed)
|
|
169238
170182
|
return;
|
|
@@ -169277,6 +170221,7 @@ var init_remote_control_surface = __esm(() => {
|
|
|
169277
170221
|
init_history2();
|
|
169278
170222
|
init_history_host();
|
|
169279
170223
|
init_shell_bridge();
|
|
170224
|
+
init_operator_confirmations();
|
|
169280
170225
|
init_flow_inspector();
|
|
169281
170226
|
init_modal_host();
|
|
169282
170227
|
init_theme_repaint();
|
|
@@ -169513,7 +170458,7 @@ var init_agent_commands = __esm(() => {
|
|
|
169513
170458
|
},
|
|
169514
170459
|
{
|
|
169515
170460
|
name: "/remote-control",
|
|
169516
|
-
description: "Drive this session from a Telegram topic: /remote-control [name|off|status]. Off by default",
|
|
170461
|
+
description: "Drive this session from a Telegram topic: /remote-control [name|off|status]. Off by default. Shows the review cards (review_confirm_plan, review_decide_premise, review_waive_readiness, review_waive_coverage, review_complete) waiting for a press",
|
|
169517
170462
|
modes: AGENT_ONLY
|
|
169518
170463
|
},
|
|
169519
170464
|
{
|
|
@@ -169598,7 +170543,7 @@ var init_agent_commands = __esm(() => {
|
|
|
169598
170543
|
},
|
|
169599
170544
|
{
|
|
169600
170545
|
name: "/decisions",
|
|
169601
|
-
description: "Recommendation journal: how often you follow the agent's recommendation, by mode and stage, and the deviations; /decisions arms: how often it was followed in each arm; /decisions reason <why> and /decisions change <option> follow up on the last answer",
|
|
170546
|
+
description: "Recommendation journal: how often you follow the agent's recommendation, by mode and stage, and the deviations; /decisions arms: how often it was followed in each arm; /decisions reason <why> and /decisions change <option> follow up on the last answer; also lists the Telegram review cards waiting or answered (review_confirm_plan, review_decide_premise, review_waive_readiness, review_waive_coverage, review_complete)",
|
|
169602
170547
|
modes: AGENT_ONLY
|
|
169603
170548
|
},
|
|
169604
170549
|
{
|
|
@@ -172314,7 +173259,7 @@ function approvalsText(views, now, options) {
|
|
|
172314
173259
|
var RECENT_RESOLVED_LIMIT = 10;
|
|
172315
173260
|
|
|
172316
173261
|
// src/lib/serve-turn-store.ts
|
|
172317
|
-
import { createHash as
|
|
173262
|
+
import { createHash as createHash93 } from "crypto";
|
|
172318
173263
|
import { existsSync as existsSync84, readdirSync as readdirSync16, rmSync as rmSync6 } from "fs";
|
|
172319
173264
|
import path376 from "path";
|
|
172320
173265
|
function turnsRoot(dir) {
|
|
@@ -172325,11 +173270,11 @@ function turnDir(turnId, dir) {
|
|
|
172325
173270
|
}
|
|
172326
173271
|
function keyPath(project, idempotencyKey, dir) {
|
|
172327
173272
|
const projectBytes = Buffer.byteLength(project, "utf8");
|
|
172328
|
-
const digest =
|
|
173273
|
+
const digest = createHash93("sha256").update(`${projectBytes}:${project}\x00${idempotencyKey}`, "utf8").digest("hex");
|
|
172329
173274
|
return path376.join(turnsRoot(dir), "keys", `${digest}.json`);
|
|
172330
173275
|
}
|
|
172331
173276
|
function legacyKeyPath(idempotencyKey, dir) {
|
|
172332
|
-
const digest =
|
|
173277
|
+
const digest = createHash93("sha256").update(idempotencyKey, "utf8").digest("hex");
|
|
172333
173278
|
return path376.join(turnsRoot(dir), "keys", `${digest}.json`);
|
|
172334
173279
|
}
|
|
172335
173280
|
function adoptLegacyClaim(project, idempotencyKey, dir) {
|
|
@@ -173583,7 +174528,7 @@ var init_types10 = __esm(() => {
|
|
|
173583
174528
|
});
|
|
173584
174529
|
|
|
173585
174530
|
// src/intake/store.ts
|
|
173586
|
-
import { createHash as
|
|
174531
|
+
import { createHash as createHash94 } from "crypto";
|
|
173587
174532
|
import { appendFile as appendFile17, mkdir as mkdir81, readFile as readFile199, writeFile as writeFile50 } from "fs/promises";
|
|
173588
174533
|
import path380 from "path";
|
|
173589
174534
|
async function ensureIntakeDataIgnored(root) {
|
|
@@ -173606,11 +174551,11 @@ async function locked2(root, lock, fn) {
|
|
|
173606
174551
|
return withFileLock(lock, fn, { timeoutMs: 5000 });
|
|
173607
174552
|
}
|
|
173608
174553
|
function cardIdFor(eventKey, stamp) {
|
|
173609
|
-
return `c${
|
|
174554
|
+
return `c${createHash94("sha256").update(`${eventKey}
|
|
173610
174555
|
${stamp}`).digest("hex").slice(0, 11)}`;
|
|
173611
174556
|
}
|
|
173612
174557
|
function overflowCardId(seed) {
|
|
173613
|
-
return `o${
|
|
174558
|
+
return `o${createHash94("sha256").update(seed).digest("hex").slice(0, 11)}`;
|
|
173614
174559
|
}
|
|
173615
174560
|
function emptyIntakeState() {
|
|
173616
174561
|
return { version: 1, paused: false, seen: {}, baselined: [], delivery: {}, recent: [], pendingEdits: {}, pendingEditTries: {} };
|
|
@@ -174993,7 +175938,7 @@ function summary(c) {
|
|
|
174993
175938
|
...c.remindAt !== undefined ? { remindAt: c.remindAt } : {}
|
|
174994
175939
|
};
|
|
174995
175940
|
}
|
|
174996
|
-
function
|
|
175941
|
+
function clock4(iso) {
|
|
174997
175942
|
if (iso === null)
|
|
174998
175943
|
return "\u0435\u0449\u0451 \u043D\u0435 \u043E\u043F\u0440\u0430\u0448\u0438\u0432\u0430\u043B\u0441\u044F";
|
|
174999
175944
|
const d = new Date(iso);
|
|
@@ -175013,7 +175958,7 @@ async function buildIntakeStatus(root, deps = {}) {
|
|
|
175013
175958
|
const nextPollAt = nextIntakePollAt(config, state);
|
|
175014
175959
|
const ghAccount = ghEnvForProject(root, env)["GH_ACCOUNT"] === "work" ? "work" : "personal";
|
|
175015
175960
|
const disabledReason = intakeDisabledReason(file);
|
|
175016
|
-
const line = !file.present ? "Intake: \u043D\u0435 \u043D\u0430\u0441\u0442\u0440\u043E\u0435\u043D" : !config.enabled ? "Intake: \u0432\u044B\u043A\u043B" : state.paused ? `Intake: ${waiting.length} \u0436\u0434\u0443\u0442 | \u043F\u0430\u0443\u0437\u0430` : `Intake: ${waiting.length} \u0436\u0434\u0443\u0442 | \u0441\u043B\u0435\u0434\u0443\u044E\u0449\u0438\u0439 \u043E\u043F\u0440\u043E\u0441 ${
|
|
175961
|
+
const line = !file.present ? "Intake: \u043D\u0435 \u043D\u0430\u0441\u0442\u0440\u043E\u0435\u043D" : !config.enabled ? "Intake: \u0432\u044B\u043A\u043B" : state.paused ? `Intake: ${waiting.length} \u0436\u0434\u0443\u0442 | \u043F\u0430\u0443\u0437\u0430` : `Intake: ${waiting.length} \u0436\u0434\u0443\u0442 | \u0441\u043B\u0435\u0434\u0443\u044E\u0449\u0438\u0439 \u043E\u043F\u0440\u043E\u0441 ${clock4(nextPollAt)}`;
|
|
175017
175962
|
return {
|
|
175018
175963
|
configured: file.present,
|
|
175019
175964
|
enabled: config.enabled,
|
|
@@ -175634,7 +176579,7 @@ function summarizeExtras(report) {
|
|
|
175634
176579
|
counts: ARMS.map((arm) => [arm, whole(counts[arm])])
|
|
175635
176580
|
};
|
|
175636
176581
|
}
|
|
175637
|
-
return { reasons, ineligible, progress };
|
|
176582
|
+
return { reasons, ineligible, progress, hostQuestions: Array.isArray(report.hostQuestions) ? report.hostQuestions.length : 0 };
|
|
175638
176583
|
}
|
|
175639
176584
|
function nonNegative(value) {
|
|
175640
176585
|
return typeof value === "number" && Number.isFinite(value) && value > 0 ? value : 0;
|
|
@@ -175714,6 +176659,8 @@ function extrasLines(extras) {
|
|
|
175714
176659
|
}
|
|
175715
176660
|
if (extras.ineligible !== null)
|
|
175716
176661
|
lines.push(`Not in the comparison (ineligible): ${extras.ineligible.decisions}, ${extras.ineligible.answered} answered`);
|
|
176662
|
+
if (extras.hostQuestions > 0)
|
|
176663
|
+
lines.push(`Answered via host question (a separate kind, not in the comparison): ${extras.hostQuestions}`);
|
|
175717
176664
|
if (extras.progress !== null) {
|
|
175718
176665
|
const p = extras.progress;
|
|
175719
176666
|
lines.push(`Progress, flow 392 AC11: ${p.decisions}/${p.decisionsTarget} decisions, ${p.blind}/${p.blindTarget} blind`, `Progress per arm (threshold ${p.threshold}): ${p.counts.map(([arm, n]) => `${arm} ${n}`).join(", ")}`);
|
|
@@ -175811,21 +176758,29 @@ function presentDecisions(open, otui, chrome, options) {
|
|
|
175811
176758
|
return handle;
|
|
175812
176759
|
}
|
|
175813
176760
|
async function openDecisions(otui, chrome, options) {
|
|
175814
|
-
const { cwd, ...rest } = options;
|
|
175815
|
-
const
|
|
176761
|
+
const { cwd, confirmationLines, ...rest } = options;
|
|
176762
|
+
const report = await reportText(cwd).catch(() => UNREADABLE);
|
|
176763
|
+
const text = withConfirmationLines(report, confirmationLines?.() ?? []);
|
|
175816
176764
|
return presentDecisions((hostOtui, hostChrome, input) => openModal(hostOtui, hostChrome, input), otui, chrome, { ...rest, text });
|
|
175817
176765
|
}
|
|
176766
|
+
function withConfirmationLines(report, cards) {
|
|
176767
|
+
return cards.length === 0 ? report : `${report}
|
|
176768
|
+
|
|
176769
|
+
${cards.join(`
|
|
176770
|
+
`)}`;
|
|
176771
|
+
}
|
|
175818
176772
|
async function openDecisionsArms(otui, chrome, options) {
|
|
175819
|
-
const { cwd, ...rest } = options;
|
|
176773
|
+
const { cwd, confirmationLines: _cards, ...rest } = options;
|
|
175820
176774
|
const text = await armsText(cwd);
|
|
175821
176775
|
return presentDecisions((hostOtui, hostChrome, input) => openModal(hostOtui, hostChrome, input), otui, chrome, { ...rest, text, tabLabel: "Arms" });
|
|
175822
176776
|
}
|
|
175823
|
-
function projectDecisionsPanel(count, width, backfilled = 0, annotated = 0) {
|
|
175824
|
-
if (count <= 0 && backfilled <= 0)
|
|
176777
|
+
function projectDecisionsPanel(count, width, backfilled = 0, annotated = 0, hostQuestions = 0) {
|
|
176778
|
+
if (count <= 0 && backfilled <= 0 && hostQuestions <= 0)
|
|
175825
176779
|
return { visible: false, text: "" };
|
|
175826
176780
|
const live = count > 0 ? `${count} decision${count === 1 ? "" : "s"}` : "";
|
|
175827
176781
|
const before = backfilled > 0 ? `${count > 0 ? " + " : ""}${backfilled} before` : "";
|
|
175828
|
-
const
|
|
176782
|
+
const via = hostQuestions > 0 ? `${count > 0 || backfilled > 0 ? " + " : ""}${hostQuestions} via host` : "";
|
|
176783
|
+
const label = `${live}${before}${via}`;
|
|
175829
176784
|
const marked = annotated > 0 ? `${label} ${OWN_MARK}${annotated}` : label;
|
|
175830
176785
|
const candidates = [`${marked} \xB7 /decisions`, marked, `${label} \xB7 /decisions`, label];
|
|
175831
176786
|
const text = candidates.find((candidate) => candidate.length <= width) ?? label.slice(0, Math.max(1, width));
|
|
@@ -175851,6 +176806,7 @@ function mountDecisionsSidebar(options) {
|
|
|
175851
176806
|
const count = options.count ?? (() => decisionCount(options.cwd));
|
|
175852
176807
|
const backfilledTotal = options.backfilled ?? (() => backfilledCount(options.cwd));
|
|
175853
176808
|
const annotatedTotal = options.annotated ?? (() => annotatedCount(options.cwd));
|
|
176809
|
+
const hostTotal = options.hostQuestions ?? (() => hostQuestionCount(options.cwd));
|
|
175854
176810
|
const box = new core.BoxRenderable(r, { id: "sb-decisions", flexDirection: "column", flexShrink: 0 });
|
|
175855
176811
|
options.parent.add(box);
|
|
175856
176812
|
const armsBox = new core.BoxRenderable(r, { id: "sb-decisions-arms", flexDirection: "column", flexShrink: 0 });
|
|
@@ -175909,6 +176865,7 @@ function mountDecisionsSidebar(options) {
|
|
|
175909
176865
|
modal?.close({ restoreFocus: false });
|
|
175910
176866
|
modal = await open(core, chrome, {
|
|
175911
176867
|
cwd: options.cwd,
|
|
176868
|
+
...options.confirmationLines !== undefined ? { confirmationLines: options.confirmationLines } : {},
|
|
175912
176869
|
...options.onKeypress !== undefined ? { onKeypress: options.onKeypress } : {}
|
|
175913
176870
|
});
|
|
175914
176871
|
return modal;
|
|
@@ -175937,9 +176894,15 @@ function mountDecisionsSidebar(options) {
|
|
|
175937
176894
|
} catch {
|
|
175938
176895
|
marked = 0;
|
|
175939
176896
|
}
|
|
176897
|
+
let viaHost;
|
|
176898
|
+
try {
|
|
176899
|
+
viaHost = await hostTotal();
|
|
176900
|
+
} catch {
|
|
176901
|
+
viaHost = 0;
|
|
176902
|
+
}
|
|
175940
176903
|
if (disposed || generation !== refreshGeneration)
|
|
175941
176904
|
return;
|
|
175942
|
-
projected = projectDecisionsPanel(n, options.width, before, marked);
|
|
176905
|
+
projected = projectDecisionsPanel(n, options.width, before, marked, viaHost);
|
|
175943
176906
|
paint();
|
|
175944
176907
|
let nextArms;
|
|
175945
176908
|
try {
|
|
@@ -176766,7 +177729,7 @@ function discardResultText(result, runId) {
|
|
|
176766
177729
|
return `Discarded run ${shortId(runId)}. Its stored patch was deleted.`;
|
|
176767
177730
|
return `Not discarded (${result.reason}): ${clean3(result.detail)}`;
|
|
176768
177731
|
}
|
|
176769
|
-
function
|
|
177732
|
+
function errorText5(error) {
|
|
176770
177733
|
return clean3(error instanceof Error ? error.message : String(error));
|
|
176771
177734
|
}
|
|
176772
177735
|
function orderedFiles(record) {
|
|
@@ -176798,7 +177761,7 @@ function externalDiffSlashText(deps) {
|
|
|
176798
177761
|
}
|
|
176799
177762
|
}
|
|
176800
177763
|
} catch (error) {
|
|
176801
|
-
lines.push(`Could not read the write runs: ${
|
|
177764
|
+
lines.push(`Could not read the write runs: ${errorText5(error)}`);
|
|
176802
177765
|
}
|
|
176803
177766
|
lines.push("Use keryx agents external review <run-id> and keryx agents external apply <run-id> in a terminal.");
|
|
176804
177767
|
return `${lines.join(`
|
|
@@ -176823,7 +177786,7 @@ function createExternalDiffController(options) {
|
|
|
176823
177786
|
runs = deps.list();
|
|
176824
177787
|
} catch (error) {
|
|
176825
177788
|
runs = [];
|
|
176826
|
-
notice = `Could not read the write runs: ${
|
|
177789
|
+
notice = `Could not read the write runs: ${errorText5(error)}`;
|
|
176827
177790
|
}
|
|
176828
177791
|
const ids = new Set(runs.map((run) => run.runId));
|
|
176829
177792
|
for (const id of [...views.keys()]) {
|
|
@@ -176853,7 +177816,7 @@ function createExternalDiffController(options) {
|
|
|
176853
177816
|
const view = deps.view(run.runId);
|
|
176854
177817
|
entry = view === undefined ? {} : { view };
|
|
176855
177818
|
} catch (error) {
|
|
176856
|
-
entry = { error:
|
|
177819
|
+
entry = { error: errorText5(error) };
|
|
176857
177820
|
}
|
|
176858
177821
|
views.set(run.runId, entry);
|
|
176859
177822
|
return entry;
|
|
@@ -176951,7 +177914,7 @@ function createExternalDiffController(options) {
|
|
|
176951
177914
|
try {
|
|
176952
177915
|
text = landResultText(await deps.land({ runId, confirmedPatchHash: hash, allowFlagged }));
|
|
176953
177916
|
} catch (error) {
|
|
176954
|
-
text = `Apply failed: ${
|
|
177917
|
+
text = `Apply failed: ${errorText5(error)}. ${NOTHING_APPLIED}`;
|
|
176955
177918
|
}
|
|
176956
177919
|
allowFlaggedRun = undefined;
|
|
176957
177920
|
views.clear();
|
|
@@ -176972,7 +177935,7 @@ function createExternalDiffController(options) {
|
|
|
176972
177935
|
try {
|
|
176973
177936
|
notice = discardResultText(deps.discard(run.runId), run.runId);
|
|
176974
177937
|
} catch (error) {
|
|
176975
|
-
notice = `Discard failed: ${
|
|
177938
|
+
notice = `Discard failed: ${errorText5(error)}`;
|
|
176976
177939
|
}
|
|
176977
177940
|
allowFlaggedRun = undefined;
|
|
176978
177941
|
views.clear();
|
|
@@ -177257,7 +178220,7 @@ var init_external_diff_sidebar = __esm(() => {
|
|
|
177257
178220
|
|
|
177258
178221
|
// src/tui/autoreview-surface.ts
|
|
177259
178222
|
import { spawn as nodeSpawn4 } from "child_process";
|
|
177260
|
-
import { existsSync as existsSync87, readdirSync as readdirSync18, readFileSync as
|
|
178223
|
+
import { existsSync as existsSync87, readdirSync as readdirSync18, readFileSync as readFileSync42 } from "fs";
|
|
177261
178224
|
import path386 from "path";
|
|
177262
178225
|
function parseAutoReviewArgs(line) {
|
|
177263
178226
|
const words = line.trim().split(/\s+/).slice(1);
|
|
@@ -177306,7 +178269,7 @@ function parseAutoReviewArgs(line) {
|
|
|
177306
178269
|
}
|
|
177307
178270
|
function readJson15(file) {
|
|
177308
178271
|
try {
|
|
177309
|
-
return JSON.parse(
|
|
178272
|
+
return JSON.parse(readFileSync42(file, "utf8"));
|
|
177310
178273
|
} catch {
|
|
177311
178274
|
return;
|
|
177312
178275
|
}
|
|
@@ -178707,7 +179670,7 @@ var init_stdin_guard = __esm(() => {
|
|
|
178707
179670
|
|
|
178708
179671
|
// src/tui/renderer-debug.ts
|
|
178709
179672
|
import { spawnSync as spawnSync7 } from "child_process";
|
|
178710
|
-
import { readFileSync as
|
|
179673
|
+
import { readFileSync as readFileSync43, readlinkSync as readlinkSync2 } from "fs";
|
|
178711
179674
|
function routeProcessWarnings(write = (text) => {
|
|
178712
179675
|
process.stderr.write(text);
|
|
178713
179676
|
}) {
|
|
@@ -178744,7 +179707,7 @@ function eofDiagnostics() {
|
|
|
178744
179707
|
}
|
|
178745
179708
|
let stat;
|
|
178746
179709
|
try {
|
|
178747
|
-
stat =
|
|
179710
|
+
stat = readFileSync43("/proc/self/stat", "utf8").trim();
|
|
178748
179711
|
} catch {
|
|
178749
179712
|
stat = "";
|
|
178750
179713
|
}
|
|
@@ -188619,12 +189582,12 @@ function maxOverFiles(files, values) {
|
|
|
188619
189582
|
}
|
|
188620
189583
|
|
|
188621
189584
|
// src/wiki/deep-enrich.ts
|
|
188622
|
-
import { randomUUID as randomUUID45, createHash as
|
|
189585
|
+
import { randomUUID as randomUUID45, createHash as createHash95 } from "crypto";
|
|
188623
189586
|
function errorMessage5(cause) {
|
|
188624
189587
|
return cause instanceof Error ? cause.message : String(cause);
|
|
188625
189588
|
}
|
|
188626
189589
|
function sha256Hex10(text) {
|
|
188627
|
-
return
|
|
189590
|
+
return createHash95("sha256").update(text).digest("hex");
|
|
188628
189591
|
}
|
|
188629
189592
|
function buildDeepEnrichTools(port) {
|
|
188630
189593
|
const deepOps = METAPROJECT_OPERATIONS.filter((op) => DEEP_ENRICH_OPS.includes(op.name));
|
|
@@ -188863,7 +189826,7 @@ var init_deep_enrich = __esm(() => {
|
|
|
188863
189826
|
});
|
|
188864
189827
|
|
|
188865
189828
|
// src/wiki/enrich.ts
|
|
188866
|
-
import { existsSync as existsSync88, mkdirSync as mkdirSync14, readFileSync as
|
|
189829
|
+
import { existsSync as existsSync88, mkdirSync as mkdirSync14, readFileSync as readFileSync44, writeFileSync as writeFileSync12 } from "fs";
|
|
188867
189830
|
import { readFile as readFile207 } from "fs/promises";
|
|
188868
189831
|
import path393 from "path";
|
|
188869
189832
|
function resolveEnrichProviderModel(input) {
|
|
@@ -188881,7 +189844,7 @@ function loadResumeState(cwd) {
|
|
|
188881
189844
|
if (!existsSync88(file)) {
|
|
188882
189845
|
return { updatedAt: new Date().toISOString(), completed: [], failed: [] };
|
|
188883
189846
|
}
|
|
188884
|
-
const raw = JSON.parse(
|
|
189847
|
+
const raw = JSON.parse(readFileSync44(file, "utf8"));
|
|
188885
189848
|
if (raw === null || typeof raw !== "object") {
|
|
188886
189849
|
return { updatedAt: new Date().toISOString(), completed: [], failed: [] };
|
|
188887
189850
|
}
|
|
@@ -191485,7 +192448,7 @@ var init_turn_loop = __esm(() => {
|
|
|
191485
192448
|
});
|
|
191486
192449
|
|
|
191487
192450
|
// src/tui/worker-fleet.ts
|
|
191488
|
-
function
|
|
192451
|
+
function clip8(s, max) {
|
|
191489
192452
|
if (s.length <= max) {
|
|
191490
192453
|
return s;
|
|
191491
192454
|
}
|
|
@@ -191576,7 +192539,7 @@ function formatFleetSidebar(workers, maxLines = 14) {
|
|
|
191576
192539
|
if (main !== undefined) {
|
|
191577
192540
|
lines.push(mainHeadline(main.status));
|
|
191578
192541
|
const phase = humanFleetPhase(main.status, main.detail);
|
|
191579
|
-
lines.push(` ${
|
|
192542
|
+
lines.push(` ${clip8(phase, 26)}`);
|
|
191580
192543
|
if (main.status === "blocked") {
|
|
191581
192544
|
lines.push(" \u2191 pick menu above input");
|
|
191582
192545
|
}
|
|
@@ -191611,8 +192574,8 @@ function formatFleetSidebar(workers, maxLines = 14) {
|
|
|
191611
192574
|
for (const w of shown) {
|
|
191612
192575
|
const g = STATUS_GLYPH2[w.status];
|
|
191613
192576
|
const phase = humanFleetPhase(w.status, w.detail);
|
|
191614
|
-
const label =
|
|
191615
|
-
const det = phase.length > 0 ? ` ${
|
|
192577
|
+
const label = clip8(w.label, 12);
|
|
192578
|
+
const det = phase.length > 0 ? ` ${clip8(phase, 12)}` : "";
|
|
191616
192579
|
lines.push(`${g} ${label}${det}`);
|
|
191617
192580
|
}
|
|
191618
192581
|
if (sorted.length > shown.length) {
|
|
@@ -191642,8 +192605,8 @@ function formatPeerLine(peer) {
|
|
|
191642
192605
|
const statusGlyph = PEER_STATUS_GLYPH[peer.status] ?? "?";
|
|
191643
192606
|
const name = displaySafe(peer.name);
|
|
191644
192607
|
const activity = displaySafe(peer.activity).trim();
|
|
191645
|
-
const detail = activity.length > 0 ? ` ${
|
|
191646
|
-
return `${marker}${statusGlyph} @${
|
|
192608
|
+
const detail = activity.length > 0 ? ` ${clip8(activity, 14)}` : "";
|
|
192609
|
+
return `${marker}${statusGlyph} @${clip8(name, 10)}${detail}`;
|
|
191647
192610
|
}
|
|
191648
192611
|
function formatFleetSidebarWithPeers(workers, peers = [], maxLines = 14) {
|
|
191649
192612
|
const base = formatFleetSidebar(workers, maxLines);
|
|
@@ -191746,7 +192709,7 @@ var init_worker_fleet = __esm(() => {
|
|
|
191746
192709
|
});
|
|
191747
192710
|
|
|
191748
192711
|
// src/tui/subagent-session.ts
|
|
191749
|
-
function
|
|
192712
|
+
function clip9(s, max) {
|
|
191750
192713
|
if (s.length <= max) {
|
|
191751
192714
|
return s;
|
|
191752
192715
|
}
|
|
@@ -191778,11 +192741,11 @@ function formatSubagentRow(session, width = SIDEBAR_TEXT_WIDTH) {
|
|
|
191778
192741
|
const mark = session.runtime === "external" ? EXTERNAL_SUBAGENT_MARK : "";
|
|
191779
192742
|
const budget = Math.max(4, width - glyph.length - 1);
|
|
191780
192743
|
const labelBudget = Math.max(1, Math.min(12, budget) - mark.length);
|
|
191781
|
-
const label = `${mark}${
|
|
192744
|
+
const label = `${mark}${clip9(session.label, labelBudget)}`;
|
|
191782
192745
|
const used = glyph.length + 1 + label.length;
|
|
191783
192746
|
const detBudget = width - used - 1;
|
|
191784
|
-
const det = detBudget >= 3 && phase.length > 0 ? ` ${
|
|
191785
|
-
return
|
|
192747
|
+
const det = detBudget >= 3 && phase.length > 0 ? ` ${clip9(phase, detBudget)}` : "";
|
|
192748
|
+
return clip9(`${glyph} ${label}${det}`, width);
|
|
191786
192749
|
}
|
|
191787
192750
|
function formatSubagentListHeader(count) {
|
|
191788
192751
|
return `Subagents ${count}`;
|
|
@@ -192877,7 +193840,7 @@ function renderExternalTranscript(events, options = {}) {
|
|
|
192877
193840
|
if (options.summary !== false && events.length > 0) {
|
|
192878
193841
|
lines.push("", formatTranscriptFooter(foldExternalTranscript(events)));
|
|
192879
193842
|
}
|
|
192880
|
-
return options.width === undefined ? lines : lines.map((line) =>
|
|
193843
|
+
return options.width === undefined ? lines : lines.map((line) => clip10(line, options.width ?? 0));
|
|
192881
193844
|
}
|
|
192882
193845
|
function formatExternalWork(events, options = {}) {
|
|
192883
193846
|
return renderExternalTranscript(events, options).join(`
|
|
@@ -193042,7 +194005,7 @@ function describeSkips(skippedLines) {
|
|
|
193042
194005
|
function plural5(count, singular, pluralForm) {
|
|
193043
194006
|
return `${count} ${count === 1 ? singular : pluralForm ?? `${singular}s`}`;
|
|
193044
194007
|
}
|
|
193045
|
-
function
|
|
194008
|
+
function clip10(value, width) {
|
|
193046
194009
|
if (width <= 0 || value.length <= width)
|
|
193047
194010
|
return value;
|
|
193048
194011
|
return `${value.slice(0, Math.max(1, width - 1))}\u2026`;
|
|
@@ -193185,7 +194148,7 @@ function appendBounded(output, chunk) {
|
|
|
193185
194148
|
const combined = output + chunk;
|
|
193186
194149
|
return combined.length > MAX_BACKGROUND_JOB_OUTPUT_CHARS ? combined.slice(-MAX_BACKGROUND_JOB_OUTPUT_CHARS) : combined;
|
|
193187
194150
|
}
|
|
193188
|
-
function
|
|
194151
|
+
function clip11(s, max) {
|
|
193189
194152
|
if (max <= 0) {
|
|
193190
194153
|
return "";
|
|
193191
194154
|
}
|
|
@@ -193200,8 +194163,8 @@ function formatJobListHeader(count) {
|
|
|
193200
194163
|
function formatJobRow(entry, width = SIDEBAR_TEXT_WIDTH) {
|
|
193201
194164
|
const glyph = STATUS_GLYPH4[entry.status];
|
|
193202
194165
|
const budget = Math.max(1, width - glyph.length - 1);
|
|
193203
|
-
const cmd =
|
|
193204
|
-
return
|
|
194166
|
+
const cmd = clip11(entry.command, budget);
|
|
194167
|
+
return clip11(`${glyph} ${cmd}`, width);
|
|
193205
194168
|
}
|
|
193206
194169
|
function formatJobMeta(entry, now = Date.now()) {
|
|
193207
194170
|
const rows = [
|
|
@@ -194981,6 +195944,9 @@ function shouldAutoApproveReadOnlySpawn(mode, hookAsk, untrustedOrigin) {
|
|
|
194981
195944
|
function isUserPromptApprovalCard(tool) {
|
|
194982
195945
|
return tool === "user_prompt";
|
|
194983
195946
|
}
|
|
195947
|
+
function withMirrorNote(card, note) {
|
|
195948
|
+
return card.length === 0 ? [note] : [...card.slice(0, -1), note, card[card.length - 1]];
|
|
195949
|
+
}
|
|
194984
195950
|
function operatorCardDialog(cardTitle) {
|
|
194985
195951
|
if (cardTitle === undefined) {
|
|
194986
195952
|
return {
|
|
@@ -196431,6 +197397,8 @@ async function launchTuiAgentShell(opts) {
|
|
|
196431
197397
|
let liveReviewsPanel;
|
|
196432
197398
|
let remoteBridge;
|
|
196433
197399
|
let liveRemotePanel;
|
|
197400
|
+
const operatorCards = new OperatorConfirmationLog;
|
|
197401
|
+
const operatorCardLines = () => confirmationLines(operatorCards.entries(), Date.now());
|
|
196434
197402
|
let liveChannelsPanel;
|
|
196435
197403
|
const channelsClient = new ChannelsClient;
|
|
196436
197404
|
let liveApprovals;
|
|
@@ -196785,6 +197753,7 @@ async function launchTuiAgentShell(opts) {
|
|
|
196785
197753
|
liveRemotePanel = mountRemotePanel(otui, r, sidebar, {
|
|
196786
197754
|
width: SIDEBAR_TEXT_WIDTH,
|
|
196787
197755
|
getStatus: remoteStatus,
|
|
197756
|
+
getWaitingCards: () => operatorCards.waiting().length,
|
|
196788
197757
|
onOpen: () => showRemoteControl(),
|
|
196789
197758
|
onOpenPermissions: () => showPermissions(),
|
|
196790
197759
|
getPosture: () => {
|
|
@@ -196826,7 +197795,8 @@ async function launchTuiAgentShell(opts) {
|
|
|
196826
197795
|
notice: (text) => io.onSystem?.(`\u25C7 ${text}
|
|
196827
197796
|
`),
|
|
196828
197797
|
lastDecisionId: lastAskUserDecisionId,
|
|
196829
|
-
session: askUserSessionId()
|
|
197798
|
+
session: askUserSessionId(),
|
|
197799
|
+
confirmationLines: operatorCardLines
|
|
196830
197800
|
});
|
|
196831
197801
|
liveDecisions = decisionsPanel;
|
|
196832
197802
|
liveResearchSync = mountResearchSyncPanel(otui, r, sidebar, {
|
|
@@ -197824,10 +198794,16 @@ async function launchTuiAgentShell(opts) {
|
|
|
197824
198794
|
`);
|
|
197825
198795
|
if (decision !== "allow")
|
|
197826
198796
|
return false;
|
|
197827
|
-
return meta?.fingerprint !== undefined ?
|
|
198797
|
+
return meta?.fingerprint !== undefined ? remoteApproval(meta.fingerprint, answer.fromId) : true;
|
|
197828
198798
|
};
|
|
197829
198799
|
const mirrorApprovalToTopic = (inner) => async (tool, inputJson, meta) => {
|
|
197830
198800
|
const bridge = remoteBridge;
|
|
198801
|
+
const isCard = meta?.alwaysAsk === true && meta.card !== undefined && meta.card.length > 0 && !isUserPromptApprovalCard(tool);
|
|
198802
|
+
const unmirrored = isCard ? mirrorUndeliveredNote(bridge) : undefined;
|
|
198803
|
+
if (unmirrored !== undefined && meta?.card !== undefined) {
|
|
198804
|
+
const card = withMirrorNote(meta.card, unmirrored);
|
|
198805
|
+
return askUnmirroredCard(operatorCards, meta.card[0] ?? tool, foregroundOperation.signal, () => inner(tool, inputJson, { ...meta, card }));
|
|
198806
|
+
}
|
|
197831
198807
|
if (bridge === undefined || !bridge.active) {
|
|
197832
198808
|
return inner(tool, inputJson, meta);
|
|
197833
198809
|
}
|
|
@@ -197836,8 +198812,12 @@ async function launchTuiAgentShell(opts) {
|
|
|
197836
198812
|
return inner(tool, inputJson, meta);
|
|
197837
198813
|
}
|
|
197838
198814
|
const brief = tool.length > 60 ? `${tool.slice(0, 59)}\u2026` : tool;
|
|
197839
|
-
const outcome = await
|
|
198815
|
+
const outcome = await raceMirroredCard({
|
|
198816
|
+
log: operatorCards,
|
|
198817
|
+
question: isCard && meta?.card !== undefined ? meta.card[0] ?? tool : undefined,
|
|
197840
198818
|
turnSignal: foregroundOperation.signal,
|
|
198819
|
+
onUndelivered: () => io.onSystem?.(`\u25C7 Telegram mirror not delivered: the topic did not take the card. Answer it here.
|
|
198820
|
+
`),
|
|
197841
198821
|
dock: (race) => inner(tool, inputJson, meta, race),
|
|
197842
198822
|
topic: (signal) => bridge.askApproval(describeApprovalForTopic(tool, inputJson, meta), { signal, timeoutMs: MAX_APPROVAL_TIMEOUT_MS })
|
|
197843
198823
|
});
|
|
@@ -197851,7 +198831,7 @@ async function launchTuiAgentShell(opts) {
|
|
|
197851
198831
|
`);
|
|
197852
198832
|
if (!allowed)
|
|
197853
198833
|
return false;
|
|
197854
|
-
return meta?.fingerprint !== undefined ?
|
|
198834
|
+
return meta?.fingerprint !== undefined ? remoteApproval(meta.fingerprint, outcome.answer.fromId) : true;
|
|
197855
198835
|
};
|
|
197856
198836
|
io.requestApproval = trackApproval(mirrorApprovalToTopic(requestApprovalLocally));
|
|
197857
198837
|
const askUserInteractive = async (req) => {
|
|
@@ -198698,9 +199678,14 @@ Staying in the current session.
|
|
|
198698
199678
|
});
|
|
198699
199679
|
};
|
|
198700
199680
|
const remoteModalListeners = new Set;
|
|
199681
|
+
operatorCards.subscribe(() => {
|
|
199682
|
+
liveRemotePanel?.refresh();
|
|
199683
|
+
remoteModalListeners.forEach((listener) => listener());
|
|
199684
|
+
});
|
|
198701
199685
|
const showRemoteControl = () => {
|
|
198702
199686
|
openRemoteControl(otui, chrome, {
|
|
198703
199687
|
getStatus: remoteStatus,
|
|
199688
|
+
getConfirmationLines: operatorCardLines,
|
|
198704
199689
|
subscribeChange: (listener) => {
|
|
198705
199690
|
remoteModalListeners.add(listener);
|
|
198706
199691
|
return () => {
|
|
@@ -201276,6 +202261,7 @@ var init_tui_shell = __esm(() => {
|
|
|
201276
202261
|
init_approvals_sidebar();
|
|
201277
202262
|
init_intake_surface();
|
|
201278
202263
|
init_decisions_surface();
|
|
202264
|
+
init_operator_confirmations();
|
|
201279
202265
|
init_research_sync_panel();
|
|
201280
202266
|
init_external_diff_sidebar();
|
|
201281
202267
|
init_autoreview_surface();
|
|
@@ -201836,7 +202822,7 @@ var init_chat_shell = __esm(() => {
|
|
|
201836
202822
|
|
|
201837
202823
|
// src/tui/debug-watcher.ts
|
|
201838
202824
|
import { spawn as spawn12, spawnSync as spawnSync9 } from "child_process";
|
|
201839
|
-
import { closeSync as closeSync12, constants as constants7, openSync as openSync13, readdirSync as readdirSync19, readFileSync as
|
|
202825
|
+
import { closeSync as closeSync12, constants as constants7, openSync as openSync13, readdirSync as readdirSync19, readFileSync as readFileSync45, readlinkSync as readlinkSync3, statSync as statSync24 } from "fs";
|
|
201840
202826
|
import path396 from "path";
|
|
201841
202827
|
function parseEpollFdinfo(text) {
|
|
201842
202828
|
const out = [];
|
|
@@ -201881,7 +202867,7 @@ function isAlive(pid) {
|
|
|
201881
202867
|
}
|
|
201882
202868
|
function readText7(file) {
|
|
201883
202869
|
try {
|
|
201884
|
-
return
|
|
202870
|
+
return readFileSync45(file, "utf8");
|
|
201885
202871
|
} catch {
|
|
201886
202872
|
return;
|
|
201887
202873
|
}
|
|
@@ -204928,7 +205914,13 @@ Example: keryx shell --provider ollama --model llama3.1:latest`);
|
|
|
204928
205914
|
...flags.denyTools !== undefined ? { denyTools: flags.denyTools } : {},
|
|
204929
205915
|
...bus !== undefined ? { bus } : {},
|
|
204930
205916
|
schedules: { projectRoot: cwd, defaults: () => ({ provider: sel.provider, model: sel.model }) },
|
|
204931
|
-
operatorConfirm: {
|
|
205917
|
+
operatorConfirm: {
|
|
205918
|
+
projectRoot: cwd,
|
|
205919
|
+
allowedUserIds: () => {
|
|
205920
|
+
const loaded = loadRemoteConfig();
|
|
205921
|
+
return loaded.ok ? loaded.value.allowedUserIds : undefined;
|
|
205922
|
+
}
|
|
205923
|
+
}
|
|
204932
205924
|
}),
|
|
204933
205925
|
mcpRuntime: () => mcpRuntime,
|
|
204934
205926
|
maxOutputTokens: resolveAgentMaxOutputTokens({
|
|
@@ -205379,6 +206371,7 @@ var init_shell = __esm(() => {
|
|
|
205379
206371
|
init_reviews_inspector();
|
|
205380
206372
|
init_remote_control_surface();
|
|
205381
206373
|
init_channels_client();
|
|
206374
|
+
init_config11();
|
|
205382
206375
|
init_channels_surface();
|
|
205383
206376
|
init_intake_surface();
|
|
205384
206377
|
init_session_info();
|
|
@@ -211940,7 +212933,7 @@ var init_judge_recordings = __esm(() => {
|
|
|
211940
212933
|
});
|
|
211941
212934
|
|
|
211942
212935
|
// src/gdskills/governance/authoring-lint.ts
|
|
211943
|
-
import { existsSync as existsSync90, readdirSync as readdirSync20, readFileSync as
|
|
212936
|
+
import { existsSync as existsSync90, readdirSync as readdirSync20, readFileSync as readFileSync46 } from "fs";
|
|
211944
212937
|
import path414 from "path";
|
|
211945
212938
|
function skillDirectoryName(skillMdPath) {
|
|
211946
212939
|
return path414.basename(path414.dirname(skillMdPath));
|
|
@@ -211965,7 +212958,7 @@ function deepReferenceFindings(skillMdPath) {
|
|
|
211965
212958
|
if (!entry.isFile() || !entry.name.endsWith(".md"))
|
|
211966
212959
|
continue;
|
|
211967
212960
|
const refPath = path414.join(referencesDir, entry.name);
|
|
211968
|
-
const content =
|
|
212961
|
+
const content = readFileSync46(refPath, "utf8");
|
|
211969
212962
|
const linksAnotherReference = /\]\([^)]*\.md\)/.test(content) || /\breferences\/[\w.-]+\.md\b/.test(content);
|
|
211970
212963
|
if (linksAnotherReference) {
|
|
211971
212964
|
findings.push({
|
|
@@ -212086,15 +213079,15 @@ var init_authoring_lint = __esm(() => {
|
|
|
212086
213079
|
});
|
|
212087
213080
|
|
|
212088
213081
|
// src/gdskills/governance/stocktake.ts
|
|
212089
|
-
import { createHash as
|
|
212090
|
-
import { existsSync as existsSync91, readdirSync as readdirSync21, readFileSync as
|
|
213082
|
+
import { createHash as createHash96 } from "crypto";
|
|
213083
|
+
import { existsSync as existsSync91, readdirSync as readdirSync21, readFileSync as readFileSync47, statSync as statSync25 } from "fs";
|
|
212091
213084
|
import path415 from "path";
|
|
212092
213085
|
function cacheKeyMatches(cached, inputs, quick) {
|
|
212093
213086
|
return cached.algorithmVersion === ALGORITHM_VERSION && cached.quick === quick && cached.sha256 === inputs.sha256 && cached.catalogFingerprint === inputs.catalogFingerprint && cached.verificationReportHash === inputs.verificationReportHash && cached.referencesHash === inputs.referencesHash;
|
|
212094
213087
|
}
|
|
212095
213088
|
function catalogFingerprint(catalog) {
|
|
212096
213089
|
const parts = [...catalog].map((entry) => `${entry.id}:${entry.sha256}`).sort();
|
|
212097
|
-
return
|
|
213090
|
+
return createHash96("sha256").update(parts.join(`
|
|
212098
213091
|
`)).digest("hex");
|
|
212099
213092
|
}
|
|
212100
213093
|
function verificationReportHash(root, entry) {
|
|
@@ -212102,7 +213095,7 @@ function verificationReportHash(root, entry) {
|
|
|
212102
213095
|
if (!existsSync91(reportPath))
|
|
212103
213096
|
return null;
|
|
212104
213097
|
try {
|
|
212105
|
-
return
|
|
213098
|
+
return createHash96("sha256").update(readFileSync47(reportPath, "utf8")).digest("hex");
|
|
212106
213099
|
} catch {
|
|
212107
213100
|
return null;
|
|
212108
213101
|
}
|
|
@@ -212124,11 +213117,11 @@ function referencesHash(entry) {
|
|
|
212124
213117
|
}
|
|
212125
213118
|
};
|
|
212126
213119
|
walk(referencesDir, "");
|
|
212127
|
-
const hash =
|
|
213120
|
+
const hash = createHash96("sha256");
|
|
212128
213121
|
for (const rel of files) {
|
|
212129
213122
|
hash.update(rel);
|
|
212130
213123
|
hash.update("\x00");
|
|
212131
|
-
hash.update(
|
|
213124
|
+
hash.update(readFileSync47(path415.join(referencesDir, rel)));
|
|
212132
213125
|
hash.update("\x00");
|
|
212133
213126
|
}
|
|
212134
213127
|
return hash.digest("hex");
|
|
@@ -212137,7 +213130,7 @@ function loadCache2(cachePath) {
|
|
|
212137
213130
|
if (!existsSync91(cachePath))
|
|
212138
213131
|
return {};
|
|
212139
213132
|
try {
|
|
212140
|
-
return JSON.parse(
|
|
213133
|
+
return JSON.parse(readFileSync47(cachePath, "utf8"));
|
|
212141
213134
|
} catch {
|
|
212142
213135
|
return {};
|
|
212143
213136
|
}
|
|
@@ -212154,7 +213147,7 @@ function checkFreshness(root, entry) {
|
|
|
212154
213147
|
if (!existsSync91(reportPath))
|
|
212155
213148
|
return;
|
|
212156
213149
|
try {
|
|
212157
|
-
const report = JSON.parse(
|
|
213150
|
+
const report = JSON.parse(readFileSync47(reportPath, "utf8"));
|
|
212158
213151
|
if (report.status === "stale" || report.status === "needs-review" || report.status === "blocked") {
|
|
212159
213152
|
return {
|
|
212160
213153
|
skillId: entry.id,
|
|
@@ -213436,7 +214429,7 @@ var init_install_manifest_schema = __esm(() => {
|
|
|
213436
214429
|
});
|
|
213437
214430
|
|
|
213438
214431
|
// src/gdskills/manifest/manifest.ts
|
|
213439
|
-
import { existsSync as existsSync92, readFileSync as
|
|
214432
|
+
import { existsSync as existsSync92, readFileSync as readFileSync48 } from "fs";
|
|
213440
214433
|
import path417 from "path";
|
|
213441
214434
|
import { fileURLToPath as fileURLToPath9 } from "url";
|
|
213442
214435
|
function thisModuleDir() {
|
|
@@ -213468,7 +214461,7 @@ function parseInstallManifest(fileLabel, raw) {
|
|
|
213468
214461
|
}
|
|
213469
214462
|
function loadBundledManifest() {
|
|
213470
214463
|
const file = bundledManifestPath();
|
|
213471
|
-
const parsed = JSON.parse(
|
|
214464
|
+
const parsed = JSON.parse(readFileSync48(file, "utf8"));
|
|
213472
214465
|
return parseInstallManifest(file, parsed);
|
|
213473
214466
|
}
|
|
213474
214467
|
var HARNESS_IDS, BUNDLED_TREE, InvalidInstallManifestError;
|
|
@@ -213572,7 +214565,7 @@ var init_glob = __esm(() => {
|
|
|
213572
214565
|
});
|
|
213573
214566
|
|
|
213574
214567
|
// src/gdskills/manifest/plan.ts
|
|
213575
|
-
import { createHash as
|
|
214568
|
+
import { createHash as createHash97 } from "crypto";
|
|
213576
214569
|
import path419 from "path";
|
|
213577
214570
|
function destinationRootsForTarget(target) {
|
|
213578
214571
|
if (target === "claude")
|
|
@@ -213584,7 +214577,7 @@ function destinationRootsForTarget(target) {
|
|
|
213584
214577
|
}
|
|
213585
214578
|
function stackInputsSha256(stack) {
|
|
213586
214579
|
const { detectedAt: _detectedAt, ...rest } = stack;
|
|
213587
|
-
return
|
|
214580
|
+
return createHash97("sha256").update(stableStringify3(rest)).digest("hex");
|
|
213588
214581
|
}
|
|
213589
214582
|
function stableStringify3(value) {
|
|
213590
214583
|
if (Array.isArray(value)) {
|
|
@@ -215729,7 +216722,7 @@ function parseSkillMetadata(skillMd, registryEntry) {
|
|
|
215729
216722
|
};
|
|
215730
216723
|
}
|
|
215731
216724
|
function readMetadataLine(content, label) {
|
|
215732
|
-
const match = content.match(new RegExp(`^${
|
|
216725
|
+
const match = content.match(new RegExp(`^${escapeRegExp12(label)}:\\s*(.+)$`, "m"));
|
|
215733
216726
|
return match?.[1]?.trim();
|
|
215734
216727
|
}
|
|
215735
216728
|
function updateLastVerified(skillMd, verifiedAt) {
|
|
@@ -215764,7 +216757,7 @@ function inferModuleFromPath(packageRoot) {
|
|
|
215764
216757
|
const next = parts[index + 1];
|
|
215765
216758
|
return index >= 0 && next ? next : "general";
|
|
215766
216759
|
}
|
|
215767
|
-
function
|
|
216760
|
+
function escapeRegExp12(value) {
|
|
215768
216761
|
return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
215769
216762
|
}
|
|
215770
216763
|
var init_verify2 = __esm(() => {
|
|
@@ -226040,7 +227033,7 @@ var init_detect3 = __esm(() => {
|
|
|
226040
227033
|
});
|
|
226041
227034
|
|
|
226042
227035
|
// src/stack/service.ts
|
|
226043
|
-
import { createHash as
|
|
227036
|
+
import { createHash as createHash98 } from "crypto";
|
|
226044
227037
|
import { readFile as readFile233 } from "fs/promises";
|
|
226045
227038
|
import path446 from "path";
|
|
226046
227039
|
function stackJsonPath(root) {
|
|
@@ -226066,7 +227059,7 @@ function serializeStackDetection(doc) {
|
|
|
226066
227059
|
}
|
|
226067
227060
|
function fingerprintOf(core) {
|
|
226068
227061
|
const canonical = JSON.stringify(canonicalize2(core));
|
|
226069
|
-
return
|
|
227062
|
+
return createHash98("sha256").update(canonical, "utf8").digest("hex");
|
|
226070
227063
|
}
|
|
226071
227064
|
async function readStackDetection(root) {
|
|
226072
227065
|
try {
|
|
@@ -226340,9 +227333,9 @@ function evaluateCompletion(input, deps) {
|
|
|
226340
227333
|
var SCHEMA_VERSION3 = 1;
|
|
226341
227334
|
|
|
226342
227335
|
// src/harness/evidence/redaction.ts
|
|
226343
|
-
import { createHash as
|
|
227336
|
+
import { createHash as createHash99 } from "crypto";
|
|
226344
227337
|
function hashContent(content) {
|
|
226345
|
-
return
|
|
227338
|
+
return createHash99("sha256").update(content, "utf8").digest("hex");
|
|
226346
227339
|
}
|
|
226347
227340
|
function redactForPersistence(content, deps) {
|
|
226348
227341
|
const scan = deps.scan(content);
|
|
@@ -226392,7 +227385,7 @@ async function metaprojectBlastRadius(port, target) {
|
|
|
226392
227385
|
}
|
|
226393
227386
|
|
|
226394
227387
|
// src/harness/session/session.ts
|
|
226395
|
-
import { createHash as
|
|
227388
|
+
import { createHash as createHash100 } from "crypto";
|
|
226396
227389
|
function canonicalStringify(value) {
|
|
226397
227390
|
if (Array.isArray(value)) {
|
|
226398
227391
|
return `[${value.map(canonicalStringify).join(",")}]`;
|
|
@@ -226405,7 +227398,7 @@ function canonicalStringify(value) {
|
|
|
226405
227398
|
return JSON.stringify(value) ?? "null";
|
|
226406
227399
|
}
|
|
226407
227400
|
function sha25612(input) {
|
|
226408
|
-
return
|
|
227401
|
+
return createHash100("sha256").update(input, "utf8").digest("hex");
|
|
226409
227402
|
}
|
|
226410
227403
|
function deepFreeze(value) {
|
|
226411
227404
|
if (value !== null && typeof value === "object") {
|
|
@@ -226549,7 +227542,7 @@ var init_startup = __esm(() => {
|
|
|
226549
227542
|
});
|
|
226550
227543
|
|
|
226551
227544
|
// src/harness/run/run.ts
|
|
226552
|
-
import { createHash as
|
|
227545
|
+
import { createHash as createHash101 } from "crypto";
|
|
226553
227546
|
function aliasRunHookToolName(keryxToolName) {
|
|
226554
227547
|
return RUN_HOOK_TOOL_NAME_ALIASES[keryxToolName] ?? keryxToolName;
|
|
226555
227548
|
}
|
|
@@ -226565,7 +227558,7 @@ function canonicalize3(value) {
|
|
|
226565
227558
|
return `{${keys.map((key) => `${JSON.stringify(key)}:${canonicalize3(record[key])}`).join(",")}}`;
|
|
226566
227559
|
}
|
|
226567
227560
|
function sha25613(input) {
|
|
226568
|
-
return
|
|
227561
|
+
return createHash101("sha256").update(input, "utf8").digest("hex");
|
|
226569
227562
|
}
|
|
226570
227563
|
function uniqueInOrder2(values) {
|
|
226571
227564
|
const seen = new Set;
|
|
@@ -227158,7 +228151,7 @@ var init_replay = __esm(() => {
|
|
|
227158
228151
|
});
|
|
227159
228152
|
|
|
227160
228153
|
// src/harness/tool/registry.ts
|
|
227161
|
-
import { createHash as
|
|
228154
|
+
import { createHash as createHash102 } from "crypto";
|
|
227162
228155
|
function canonicalize4(value) {
|
|
227163
228156
|
if (value === null || typeof value !== "object") {
|
|
227164
228157
|
return JSON.stringify(value) ?? "null";
|
|
@@ -227172,7 +228165,7 @@ function canonicalize4(value) {
|
|
|
227172
228165
|
return `{${entries.join(",")}}`;
|
|
227173
228166
|
}
|
|
227174
228167
|
function sha25614(input) {
|
|
227175
|
-
return
|
|
228168
|
+
return createHash102("sha256").update(input, "utf8").digest("hex");
|
|
227176
228169
|
}
|
|
227177
228170
|
function definitionHash(definition) {
|
|
227178
228171
|
return sha25614(canonicalize4(definition));
|
|
@@ -227325,9 +228318,9 @@ var init_executor = __esm(() => {
|
|
|
227325
228318
|
|
|
227326
228319
|
// src/harness/process/real-process-adapter.ts
|
|
227327
228320
|
import { spawnSync as spawnSync10 } from "child_process";
|
|
227328
|
-
import { createHash as
|
|
228321
|
+
import { createHash as createHash103 } from "crypto";
|
|
227329
228322
|
function sha256Hex11(input) {
|
|
227330
|
-
return
|
|
228323
|
+
return createHash103("sha256").update(input, "utf8").digest("hex");
|
|
227331
228324
|
}
|
|
227332
228325
|
function byteLength3(chunk) {
|
|
227333
228326
|
if (chunk === null || chunk === undefined)
|
|
@@ -227641,7 +228634,7 @@ function checkApproval(input) {
|
|
|
227641
228634
|
}
|
|
227642
228635
|
|
|
227643
228636
|
// src/commands/harness.ts
|
|
227644
|
-
import { readFileSync as
|
|
228637
|
+
import { readFileSync as readFileSync49, writeFileSync as writeFileSync13 } from "fs";
|
|
227645
228638
|
import path448 from "path";
|
|
227646
228639
|
import { randomUUID as randomUUID49 } from "crypto";
|
|
227647
228640
|
import { realpathSync as realpathSync21 } from "fs";
|
|
@@ -227884,7 +228877,7 @@ function parseReplayArgs(args) {
|
|
|
227884
228877
|
function readJsonFile3(file) {
|
|
227885
228878
|
let text;
|
|
227886
228879
|
try {
|
|
227887
|
-
text =
|
|
228880
|
+
text = readFileSync49(file, "utf8");
|
|
227888
228881
|
} catch (error) {
|
|
227889
228882
|
return { ok: false, reason: error instanceof Error ? error.message : String(error) };
|
|
227890
228883
|
}
|
|
@@ -228248,7 +229241,7 @@ function readSpecArg(args) {
|
|
|
228248
229241
|
if (specPath === undefined) {
|
|
228249
229242
|
throw new Error("keryx harness: a --spec <path> argument is required when no spec is injected.");
|
|
228250
229243
|
}
|
|
228251
|
-
return JSON.parse(
|
|
229244
|
+
return JSON.parse(readFileSync49(specPath, "utf8"));
|
|
228252
229245
|
}
|
|
228253
229246
|
function harnessExtension(args, deps) {
|
|
228254
229247
|
const { clock, idSeq } = resolveRuntime(deps);
|
|
@@ -228431,7 +229424,7 @@ async function sessionsCommand(args) {
|
|
|
228431
229424
|
console.log(pad2("ID", 10) + pad2("UPDATED", 22) + pad2("MSGS", 6) + pad2("LIVE", 7) + pad2("MODEL", 24) + "TITLE");
|
|
228432
229425
|
for (const { summary: s, lease } of listed) {
|
|
228433
229426
|
const model = s.provider !== undefined && s.model !== undefined ? `${s.provider}/${s.model}` : s.model ?? "-";
|
|
228434
|
-
console.log(pad2(shortSessionId(s.id), 10) + pad2(s.updatedAt.slice(0, 19).replace("T", " "), 22) + pad2(String(s.messageCount), 6) + pad2(lease ?? "", 7) + pad2(
|
|
229427
|
+
console.log(pad2(shortSessionId(s.id), 10) + pad2(s.updatedAt.slice(0, 19).replace("T", " "), 22) + pad2(String(s.messageCount), 6) + pad2(lease ?? "", 7) + pad2(clip12(model, 22), 24) + (s.parentSessionId !== undefined ? `\u21B3 ${s.title}` : s.title));
|
|
228435
229428
|
if (s.remote !== undefined) {
|
|
228436
229429
|
console.log(`${" ".repeat(10)}\u21C4 ${describeRemote(s.remote)}`);
|
|
228437
229430
|
}
|
|
@@ -228537,7 +229530,7 @@ function leaseColumn(cwd, sessionId) {
|
|
|
228537
229530
|
function pad2(s, n) {
|
|
228538
229531
|
return s.length >= n ? `${s.slice(0, n - 1)} ` : s + " ".repeat(n - s.length);
|
|
228539
229532
|
}
|
|
228540
|
-
function
|
|
229533
|
+
function clip12(s, n) {
|
|
228541
229534
|
return s.length > n ? `${s.slice(0, n - 1)}\u2026` : s;
|
|
228542
229535
|
}
|
|
228543
229536
|
function printHelp20() {
|
|
@@ -229107,7 +230100,7 @@ var init_session3 = __esm(() => {
|
|
|
229107
230100
|
});
|
|
229108
230101
|
|
|
229109
230102
|
// src/acp/session-mcp.ts
|
|
229110
|
-
import { createHash as
|
|
230103
|
+
import { createHash as createHash104 } from "crypto";
|
|
229111
230104
|
function isRecord17(value) {
|
|
229112
230105
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
229113
230106
|
}
|
|
@@ -229234,7 +230227,7 @@ function acpMcpSetKey(parsed) {
|
|
|
229234
230227
|
})),
|
|
229235
230228
|
refused: parsed.refused
|
|
229236
230229
|
};
|
|
229237
|
-
return
|
|
230230
|
+
return createHash104("sha256").update(JSON.stringify(canonical)).digest("hex");
|
|
229238
230231
|
}
|
|
229239
230232
|
function startAcpSessionMcp(parsed, options = {}) {
|
|
229240
230233
|
const refused = parsed.refused.map((problem) => ({ name: problem.name, reason: scrub(problem.reason, parsed.secrets) }));
|
|
@@ -230193,9 +231186,9 @@ var init_server2 = __esm(() => {
|
|
|
230193
231186
|
});
|
|
230194
231187
|
|
|
230195
231188
|
// src/acp/fixture-provider.ts
|
|
230196
|
-
import { readFileSync as
|
|
231189
|
+
import { readFileSync as readFileSync50 } from "fs";
|
|
230197
231190
|
function loadAcpFixture(path) {
|
|
230198
|
-
const raw =
|
|
231191
|
+
const raw = readFileSync50(path, "utf-8");
|
|
230199
231192
|
const parsed = JSON.parse(raw);
|
|
230200
231193
|
const turns = parsed.turns;
|
|
230201
231194
|
const models = parsed.models !== undefined && parsed.models.length > 0 ? [...parsed.models] : ["fixture-model"];
|
|
@@ -232278,7 +233271,7 @@ var init_serve_runner = __esm(() => {
|
|
|
232278
233271
|
});
|
|
232279
233272
|
|
|
232280
233273
|
// src/remote/naming.ts
|
|
232281
|
-
import { createHash as
|
|
233274
|
+
import { createHash as createHash105 } from "crypto";
|
|
232282
233275
|
import { hostname as hostname5 } from "os";
|
|
232283
233276
|
import path451 from "path";
|
|
232284
233277
|
function nameKey(name) {
|
|
@@ -232308,7 +233301,7 @@ function idChars(sessionId) {
|
|
|
232308
233301
|
}
|
|
232309
233302
|
function machineSlug(machine) {
|
|
232310
233303
|
const slug = machine.normalize("NFKD").replace(/[^A-Za-z0-9._-]+/g, "-").replace(/-+/g, "-").replace(/^[-.]+|[-.]+$/g, "").slice(0, MAX_MACHINE_SLUG);
|
|
232311
|
-
return slug.length > 0 ? slug : `host-${
|
|
233304
|
+
return slug.length > 0 ? slug : `host-${createHash105("sha256").update(machine.normalize("NFC")).digest("hex").slice(0, 6)}`;
|
|
232312
233305
|
}
|
|
232313
233306
|
function localMachineName(read = hostname5) {
|
|
232314
233307
|
let name = "";
|
|
@@ -233710,7 +234703,7 @@ var init_durable_log = __esm(() => {
|
|
|
233710
234703
|
});
|
|
233711
234704
|
|
|
233712
234705
|
// src/remote/inbound.ts
|
|
233713
|
-
import { createHash as
|
|
234706
|
+
import { createHash as createHash106 } from "crypto";
|
|
233714
234707
|
import { unlinkSync as unlinkSync11 } from "fs";
|
|
233715
234708
|
import path452 from "path";
|
|
233716
234709
|
function parseEntry6(value) {
|
|
@@ -233743,7 +234736,7 @@ class InboundQueues {
|
|
|
233743
234736
|
this.directory = ensureRemoteDir(this.dir, INBOUND_DIRNAME);
|
|
233744
234737
|
}
|
|
233745
234738
|
fileFor(bindingKey) {
|
|
233746
|
-
const name =
|
|
234739
|
+
const name = createHash106("sha256").update(bindingKey).digest("hex").slice(0, 32);
|
|
233747
234740
|
return path452.join(this.directory, `${name}.jsonl`);
|
|
233748
234741
|
}
|
|
233749
234742
|
log(bindingKey) {
|
|
@@ -238214,7 +239207,7 @@ var init_serve_research_sync = __esm(() => {
|
|
|
238214
239207
|
|
|
238215
239208
|
// src/intake/assess.ts
|
|
238216
239209
|
import { randomUUID as randomUUID58 } from "crypto";
|
|
238217
|
-
function
|
|
239210
|
+
function clip13(text, max) {
|
|
238218
239211
|
const flat = redactSensitiveText(text).replace(/[\u0000-\u0008\u000b\u000c\u000e-\u001f\u007f]/g, " ").trim();
|
|
238219
239212
|
return flat.length > max ? `${flat.slice(0, max)}\u2026` : flat;
|
|
238220
239213
|
}
|
|
@@ -238222,10 +239215,10 @@ function promptFor(input) {
|
|
|
238222
239215
|
const { event } = input;
|
|
238223
239216
|
return [
|
|
238224
239217
|
`Kind: ${event.kind}`,
|
|
238225
|
-
`Title: ${
|
|
239218
|
+
`Title: ${clip13(event.title, TITLE_MAX).replace(/\s+/g, " ")}`,
|
|
238226
239219
|
`Allowed actions: ${input.allowed.join(", ")}`,
|
|
238227
239220
|
"Text:",
|
|
238228
|
-
|
|
239221
|
+
clip13(event.body ?? "", BODY_MAX2)
|
|
238229
239222
|
].join(`
|
|
238230
239223
|
`);
|
|
238231
239224
|
}
|
|
@@ -241288,7 +242281,7 @@ var init_agents_external = __esm(() => {
|
|
|
241288
242281
|
});
|
|
241289
242282
|
|
|
241290
242283
|
// src/commands/agents-catalog.ts
|
|
241291
|
-
import { existsSync as existsSync94, lstatSync as lstatSync10, readFileSync as
|
|
242284
|
+
import { existsSync as existsSync94, lstatSync as lstatSync10, readFileSync as readFileSync51, writeFileSync as writeFileSync14 } from "fs";
|
|
241292
242285
|
import path464 from "path";
|
|
241293
242286
|
function resolveDeps2(deps) {
|
|
241294
242287
|
return {
|
|
@@ -241569,7 +242562,7 @@ function printVerifyHelp2() {
|
|
|
241569
242562
|
function readStackPackForGeneration(packJsonPath, stackId) {
|
|
241570
242563
|
let raw;
|
|
241571
242564
|
try {
|
|
241572
|
-
raw = JSON.parse(
|
|
242565
|
+
raw = JSON.parse(readFileSync51(packJsonPath, "utf8"));
|
|
241573
242566
|
} catch {
|
|
241574
242567
|
return;
|
|
241575
242568
|
}
|
|
@@ -241694,7 +242687,7 @@ function generateCommand(args, depsIn) {
|
|
|
241694
242687
|
}
|
|
241695
242688
|
const outcomes = [];
|
|
241696
242689
|
for (const { file, filePath, existed } of prepared) {
|
|
241697
|
-
const previous = existed ?
|
|
242690
|
+
const previous = existed ? readFileSync51(filePath, "utf8") : undefined;
|
|
241698
242691
|
const changed = previous !== file.content;
|
|
241699
242692
|
if (!check && changed) {
|
|
241700
242693
|
writeFileSync14(filePath, file.content, "utf8");
|
|
@@ -241818,7 +242811,7 @@ var init_reduce = __esm(() => {
|
|
|
241818
242811
|
});
|
|
241819
242812
|
|
|
241820
242813
|
// src/commands/agents.ts
|
|
241821
|
-
import { readFileSync as
|
|
242814
|
+
import { readFileSync as readFileSync52 } from "fs";
|
|
241822
242815
|
async function agentsCommand(args = []) {
|
|
241823
242816
|
const subcommand = args[0];
|
|
241824
242817
|
if (!subcommand || subcommand === "--help" || subcommand === "-h") {
|
|
@@ -241870,7 +242863,7 @@ function monitorCommand(args) {
|
|
|
241870
242863
|
}
|
|
241871
242864
|
let events;
|
|
241872
242865
|
try {
|
|
241873
|
-
events = parseAgentEvents(
|
|
242866
|
+
events = parseAgentEvents(readFileSync52(source, "utf8"));
|
|
241874
242867
|
} catch (error) {
|
|
241875
242868
|
console.error(`Failed to read/parse agent-event source "${source}": ${error.message}`);
|
|
241876
242869
|
process.exitCode = 1;
|
|
@@ -246190,7 +247183,7 @@ var init_product = __esm(() => {
|
|
|
246190
247183
|
});
|
|
246191
247184
|
|
|
246192
247185
|
// src/commands/hooks.ts
|
|
246193
|
-
import { existsSync as existsSync95, readFileSync as
|
|
247186
|
+
import { existsSync as existsSync95, readFileSync as readFileSync53 } from "fs";
|
|
246194
247187
|
import path469 from "path";
|
|
246195
247188
|
function resolveHomeDir(deps) {
|
|
246196
247189
|
return resolveHooksHomeDir(process.env, deps.homeDir);
|
|
@@ -246615,7 +247608,7 @@ async function runTest(args, deps) {
|
|
|
246615
247608
|
if (payloadFileArg !== undefined) {
|
|
246616
247609
|
let raw;
|
|
246617
247610
|
try {
|
|
246618
|
-
raw =
|
|
247611
|
+
raw = readFileSync53(payloadFileArg, "utf8");
|
|
246619
247612
|
} catch (err) {
|
|
246620
247613
|
fail8(`Could not read --payload-file "${payloadFileArg}": ${err instanceof Error ? err.message : String(err)}`);
|
|
246621
247614
|
}
|
|
@@ -246755,7 +247748,7 @@ function emptyDoc() {
|
|
|
246755
247748
|
function readDoc(filePath) {
|
|
246756
247749
|
let raw;
|
|
246757
247750
|
try {
|
|
246758
|
-
raw =
|
|
247751
|
+
raw = readFileSync53(filePath, "utf8");
|
|
246759
247752
|
} catch (err) {
|
|
246760
247753
|
if (err instanceof Error && "code" in err && err.code === "ENOENT") {
|
|
246761
247754
|
return emptyDoc();
|