@mrciphersmith/keryx 0.3.124 → 0.3.126
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 +494 -43
- package/dist/core.js +59 -10
- 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 +10 -10
- package/src/gdskills/bundled/skills/review/review-orchestrator/SKILL.md +21 -22
- 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.126",
|
|
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
|
}
|
|
@@ -43752,12 +43766,23 @@ function cardPressFields(source) {
|
|
|
43752
43766
|
...source?.pressedAt === undefined ? {} : { pressedAt: source.pressedAt }
|
|
43753
43767
|
};
|
|
43754
43768
|
}
|
|
43755
|
-
|
|
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;
|
|
43756
43780
|
var init_types4 = __esm(() => {
|
|
43757
43781
|
MANAGED_REVIEW_MODES = ["attach-review", "review-flow", "ingest"];
|
|
43758
43782
|
REVIEW_TARGET_KINDS = ["pr", "issue", "branch", "path", "report"];
|
|
43759
43783
|
REVIEW_PACKAGE_STATUSES = ["draft", "reviewed", "decided", "learned", "closed"];
|
|
43760
43784
|
REVIEW_COVERAGE_STATUSES = ["run", "skipped", "failed", "needs_context"];
|
|
43785
|
+
HOST_QUESTION_SOURCES = ["claude-code", "codex", "keryx-shell", "helyx-poll", "other"];
|
|
43761
43786
|
REVIEW_FINDING_SEVERITIES = ["blocker", "major", "minor", "info"];
|
|
43762
43787
|
REVIEW_FINDING_CONFIDENCES = ["high", "medium", "low"];
|
|
43763
43788
|
FINDING_DISPOSITION_STATES = [
|
|
@@ -46688,7 +46713,7 @@ function resultOf(record) {
|
|
|
46688
46713
|
flow: record.flow
|
|
46689
46714
|
};
|
|
46690
46715
|
}
|
|
46691
|
-
function findOpenTwin(records, hash, session, now) {
|
|
46716
|
+
function findOpenTwin(records, hash, session, now, host) {
|
|
46692
46717
|
const answered = new Set;
|
|
46693
46718
|
for (const r of records)
|
|
46694
46719
|
if (r.kind === "answer")
|
|
@@ -46696,6 +46721,8 @@ function findOpenTwin(records, hash, session, now) {
|
|
|
46696
46721
|
for (const r of records) {
|
|
46697
46722
|
if (r.kind !== "open" || r.backfilled === true || answered.has(r.id))
|
|
46698
46723
|
continue;
|
|
46724
|
+
if (r.hostQuestion?.source !== host?.source || r.hostQuestion?.ref !== host?.ref || r.hostQuestion?.verb !== host?.verb)
|
|
46725
|
+
continue;
|
|
46699
46726
|
if ((r.session ?? undefined) !== (session !== undefined && session.length > 0 ? session : undefined))
|
|
46700
46727
|
continue;
|
|
46701
46728
|
const age = now.getTime() - Date.parse(r.at);
|
|
@@ -46730,14 +46757,14 @@ async function openDecision(input) {
|
|
|
46730
46757
|
}
|
|
46731
46758
|
async function openLocked(input, now, random, question, ids, recommendation) {
|
|
46732
46759
|
const records = await readRecords(input.cwd);
|
|
46733
|
-
const twin = findOpenTwin(records, questionHash(question, [...ids]), input.session, now);
|
|
46760
|
+
const twin = findOpenTwin(records, questionHash(question, [...ids]), input.session, now, input.hostQuestion);
|
|
46734
46761
|
if (twin !== undefined)
|
|
46735
46762
|
return resultOf(twin);
|
|
46736
46763
|
const config = await loadDecisionsConfig(input.cwd);
|
|
46737
46764
|
const tagged = input.action !== undefined && input.action.trim().length > 0;
|
|
46738
46765
|
const irreversible = input.irreversible === true || tagged || isIrreversible(config.irreversible, question, input.action, input.options);
|
|
46739
46766
|
const salt = input.salt ?? await loadRepoSalt(input.cwd);
|
|
46740
|
-
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;
|
|
46741
46768
|
const choice = chooseArm({
|
|
46742
46769
|
salt,
|
|
46743
46770
|
seq,
|
|
@@ -46786,6 +46813,7 @@ async function openLocked(input, now, random, question, ids, recommendation) {
|
|
|
46786
46813
|
...tagged ? { action: input.action?.trim() ?? "" } : {},
|
|
46787
46814
|
...blindRefused ? { blindRefused: true } : {},
|
|
46788
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) } },
|
|
46789
46817
|
...input.session !== undefined && input.session.length > 0 ? { session: input.session } : {}
|
|
46790
46818
|
};
|
|
46791
46819
|
await appendRecord(input.cwd, record);
|
|
@@ -46910,7 +46938,8 @@ var init_sources = __esm(() => {
|
|
|
46910
46938
|
roundLimit: "round-limit",
|
|
46911
46939
|
wikiEnrich: "tui-wiki-enrich",
|
|
46912
46940
|
queueRoute: "tui-queue-route",
|
|
46913
|
-
sessionLease: "tui-session-lease"
|
|
46941
|
+
sessionLease: "tui-session-lease",
|
|
46942
|
+
hostQuestion: "host-question"
|
|
46914
46943
|
};
|
|
46915
46944
|
WORK_DECISION_SOURCES = Object.values(DECISION_SOURCES);
|
|
46916
46945
|
});
|
|
@@ -47393,6 +47422,14 @@ var init_quality = __esm(() => {
|
|
|
47393
47422
|
CHILD_ENV_KEYS = ["PATH", "HOME", "LANG", "LC_ALL", "TMPDIR"];
|
|
47394
47423
|
});
|
|
47395
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
|
+
|
|
47396
47433
|
// src/decisions/report.ts
|
|
47397
47434
|
function emptyTally() {
|
|
47398
47435
|
return { answered: 0, matched: 0 };
|
|
@@ -47567,6 +47604,22 @@ function buildLegacy(opens, firstOf) {
|
|
|
47567
47604
|
out.matchShare = out.matchTally.answered === 0 ? null : out.matchTally.matched / out.matchTally.answered;
|
|
47568
47605
|
return out;
|
|
47569
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
|
+
}
|
|
47570
47623
|
function buildReport2(records, skipped = 0, options = {}) {
|
|
47571
47624
|
const opens = [];
|
|
47572
47625
|
const answers = new Map;
|
|
@@ -47588,7 +47641,9 @@ function buildReport2(records, skipped = 0, options = {}) {
|
|
|
47588
47641
|
}
|
|
47589
47642
|
const excludeLegacy = options.excludeLegacy === true;
|
|
47590
47643
|
const settings = options.settings ?? defaultSettings();
|
|
47591
|
-
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;
|
|
47592
47647
|
const live = kept.filter((open) => open.backfilled !== true);
|
|
47593
47648
|
const history = kept.filter((open) => open.backfilled === true);
|
|
47594
47649
|
const firstAnswer = (id) => [...answers.get(id) ?? []].sort((a, b) => a.seq - b.seq)[0];
|
|
@@ -47627,6 +47682,7 @@ function buildReport2(records, skipped = 0, options = {}) {
|
|
|
47627
47682
|
excludeLegacy,
|
|
47628
47683
|
quality: buildQualityMatrix(options.quality ?? [], known),
|
|
47629
47684
|
reasons: buildReasons(compared, headline, firstAnswer, reasons),
|
|
47685
|
+
hostQuestions: hostOpens.map((open) => hostQuestionRow(open, answers.get(open.id) ?? [])),
|
|
47630
47686
|
ineligible: {
|
|
47631
47687
|
decisions: randomized.filter((open) => !recordEligible(open)).length,
|
|
47632
47688
|
answered: randomized.filter((open) => !recordEligible(open) && firstAnswer(open.id) !== undefined).length
|
|
@@ -47807,15 +47863,40 @@ function renderLegacy(report) {
|
|
|
47807
47863
|
function seconds(ms) {
|
|
47808
47864
|
return `${(ms / 1000).toFixed(1)}s`;
|
|
47809
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
|
+
}
|
|
47810
47879
|
function renderReport(report) {
|
|
47811
47880
|
const skippedLine = report.skipped > 0 ? `Skipped ${report.skipped} unreadable journal record${report.skipped === 1 ? "" : "s"}.` : undefined;
|
|
47812
47881
|
const configLines = report.settings.invalid.length > 0 ? renderSettings(report.settings) : [];
|
|
47882
|
+
const hostLines = renderHostQuestions(report);
|
|
47813
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
|
+
}
|
|
47814
47893
|
return ["No decisions recorded yet.", ...configLines, ...skippedLine === undefined ? [] : [skippedLine]].join(`
|
|
47815
47894
|
`);
|
|
47816
47895
|
}
|
|
47817
47896
|
if (report.total === 0) {
|
|
47818
47897
|
const lines = ["No live decisions recorded yet.", "", ...renderBackfilled(report.backfilled)];
|
|
47898
|
+
if (hostLines.length > 0)
|
|
47899
|
+
lines.push("", ...hostLines);
|
|
47819
47900
|
if (configLines.length > 0)
|
|
47820
47901
|
lines.push("", ...configLines);
|
|
47821
47902
|
if (skippedLine !== undefined)
|
|
@@ -47880,6 +47961,8 @@ function renderReport(report) {
|
|
|
47880
47961
|
if (report.inferredFlow > 0) {
|
|
47881
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"}.`);
|
|
47882
47963
|
}
|
|
47964
|
+
if (hostLines.length > 0)
|
|
47965
|
+
lines.push("", ...hostLines);
|
|
47883
47966
|
if (report.backfilled.total > 0)
|
|
47884
47967
|
lines.push("", ...renderBackfilled(report.backfilled));
|
|
47885
47968
|
if (skippedLine !== undefined)
|
|
@@ -47918,7 +48001,8 @@ function toUtcIso(value) {
|
|
|
47918
48001
|
return ISO_OFFSET.test(value) ? new Date(at).toISOString() : null;
|
|
47919
48002
|
}
|
|
47920
48003
|
function exportSummaryLine(summary) {
|
|
47921
|
-
|
|
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;
|
|
47922
48006
|
}
|
|
47923
48007
|
|
|
47924
48008
|
class Validator {
|
|
@@ -48002,7 +48086,14 @@ function buildExportWithSummary(records, quality = [], options = {}) {
|
|
|
48002
48086
|
}
|
|
48003
48087
|
}
|
|
48004
48088
|
const rows = [];
|
|
48089
|
+
let hostQuestions = 0;
|
|
48005
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
|
+
}
|
|
48006
48097
|
const arm = exportArm(open);
|
|
48007
48098
|
if (arm === null || !Array.isArray(open.options) || !Array.isArray(open.order)) {
|
|
48008
48099
|
check.skipped += 1;
|
|
@@ -48047,7 +48138,7 @@ function buildExportWithSummary(records, quality = [], options = {}) {
|
|
|
48047
48138
|
ratings: ratingsOf(open.id, quality, check)
|
|
48048
48139
|
});
|
|
48049
48140
|
}
|
|
48050
|
-
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 } };
|
|
48051
48142
|
}
|
|
48052
48143
|
function ratingsOf(id, quality, check) {
|
|
48053
48144
|
const out = [];
|
|
@@ -48473,7 +48564,7 @@ async function latestAnsweredDecision(cwd, flow) {
|
|
|
48473
48564
|
const answered = new Set(records.filter((r) => r.kind === "answer").map((r) => r.id));
|
|
48474
48565
|
for (let i = records.length - 1;i >= 0; i -= 1) {
|
|
48475
48566
|
const record = records[i];
|
|
48476
|
-
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))
|
|
48477
48568
|
return record.id;
|
|
48478
48569
|
}
|
|
48479
48570
|
return;
|
|
@@ -48559,6 +48650,9 @@ async function reportLine(cwd) {
|
|
|
48559
48650
|
async function decisionCount(cwd) {
|
|
48560
48651
|
return (await loadReport(cwd)).total;
|
|
48561
48652
|
}
|
|
48653
|
+
async function hostQuestionCount(cwd) {
|
|
48654
|
+
return (await loadReport(cwd)).hostQuestions?.length ?? 0;
|
|
48655
|
+
}
|
|
48562
48656
|
async function annotatedCount(cwd) {
|
|
48563
48657
|
return (await loadReport(cwd)).annotated.length;
|
|
48564
48658
|
}
|
|
@@ -48932,17 +49026,20 @@ function validPremiseSubject(value) {
|
|
|
48932
49026
|
const subject = value;
|
|
48933
49027
|
return text(subject.severity) && text(subject.verdict) && text(subject.problemSha256);
|
|
48934
49028
|
}
|
|
49029
|
+
function viaConsistent(d) {
|
|
49030
|
+
return d.confirmedVia === "host-question" ? isHostQuestion(d.hostQuestion) : true;
|
|
49031
|
+
}
|
|
48935
49032
|
function validPremise(entry) {
|
|
48936
49033
|
if (entry === null || typeof entry !== "object")
|
|
48937
49034
|
return false;
|
|
48938
49035
|
const d = entry;
|
|
48939
|
-
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);
|
|
48940
49037
|
}
|
|
48941
49038
|
function validGateDecision(entry) {
|
|
48942
49039
|
if (entry === null || typeof entry !== "object")
|
|
48943
49040
|
return false;
|
|
48944
49041
|
const d = entry;
|
|
48945
|
-
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));
|
|
48946
49043
|
}
|
|
48947
49044
|
function validDecision(entry) {
|
|
48948
49045
|
return validGateDecision(entry) || validPremise(entry);
|
|
@@ -49001,7 +49098,8 @@ async function recordPremiseDecisionAfter(packageDir, input, applyFirst) {
|
|
|
49001
49098
|
confirmedVia: input.confirmedVia,
|
|
49002
49099
|
...cardPressFields(input),
|
|
49003
49100
|
subject: input.subject,
|
|
49004
|
-
...input.carriedFrom === undefined ? {} : { carriedFrom: input.carriedFrom }
|
|
49101
|
+
...input.carriedFrom === undefined ? {} : { carriedFrom: input.carriedFrom },
|
|
49102
|
+
...hostQuestionFields(input)
|
|
49005
49103
|
};
|
|
49006
49104
|
if (!validPremise(kept)) {
|
|
49007
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");
|
|
@@ -49045,9 +49143,9 @@ async function recordOperatorDecision(packageDir, decision) {
|
|
|
49045
49143
|
}
|
|
49046
49144
|
function decisionWriters(kind, packageArg = "<review-dir>") {
|
|
49047
49145
|
if (kind === PREMISE_DECISION_KIND) {
|
|
49048
|
-
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>")`;
|
|
49049
49147
|
}
|
|
49050
|
-
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>")`;
|
|
49051
49149
|
}
|
|
49052
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;
|
|
49053
49151
|
var init_operator_decisions = __esm(() => {
|
|
@@ -49055,7 +49153,7 @@ var init_operator_decisions = __esm(() => {
|
|
|
49055
49153
|
init_json();
|
|
49056
49154
|
init_types4();
|
|
49057
49155
|
OPERATOR_DECISION_KINDS = ["mutation-waiver", "reviewer-override"];
|
|
49058
|
-
VIAS = ["tui-card", "remote-card", "terminal"];
|
|
49156
|
+
VIAS = ["tui-card", "remote-card", "terminal", "host-question"];
|
|
49059
49157
|
});
|
|
49060
49158
|
|
|
49061
49159
|
// src/review/mutate.ts
|
|
@@ -74403,7 +74501,8 @@ function applyPremiseDecision(finding, decision) {
|
|
|
74403
74501
|
quote: decision.quote,
|
|
74404
74502
|
at: decision.at,
|
|
74405
74503
|
actor: decision.actor,
|
|
74406
|
-
confirmedVia: decision.confirmedVia
|
|
74504
|
+
confirmedVia: decision.confirmedVia,
|
|
74505
|
+
...hostQuestionFields(decision)
|
|
74407
74506
|
};
|
|
74408
74507
|
const { disposition: existing, ...rest } = record;
|
|
74409
74508
|
const current = isRecord9(existing) ? existing : undefined;
|
|
@@ -74412,7 +74511,7 @@ function applyPremiseDecision(finding, decision) {
|
|
|
74412
74511
|
return {
|
|
74413
74512
|
...ownDismissal ? rest : record,
|
|
74414
74513
|
premise: { ...premise, operator },
|
|
74415
|
-
...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}` } } : {}
|
|
74416
74515
|
};
|
|
74417
74516
|
}
|
|
74418
74517
|
function applyPremiseDecisions(findings, decisions) {
|
|
@@ -82100,10 +82199,43 @@ var init_patch_risk = __esm(() => {
|
|
|
82100
82199
|
NEW_FILE_HEADER = /^\+\+\+ (.+)$/;
|
|
82101
82200
|
});
|
|
82102
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
|
+
|
|
82103
82208
|
// src/trigger/unattended.ts
|
|
82104
82209
|
function isUnattendedExcludedTool(toolName) {
|
|
82105
82210
|
return UNATTENDED_EXCLUDED_TOOLS.includes(toolName) || OPERATOR_CONFIRM_TOOL_NAMES.includes(toolName);
|
|
82106
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
|
+
}
|
|
82107
82239
|
function isUnattendedProtectedPath(p) {
|
|
82108
82240
|
const normalized = p.replace(/\\/g, "/").trim();
|
|
82109
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);
|
|
@@ -82112,6 +82244,9 @@ function unattendedShellRefusal(command) {
|
|
|
82112
82244
|
const text = command.trim();
|
|
82113
82245
|
if (text.length === 0)
|
|
82114
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
|
+
}
|
|
82115
82250
|
for (const pattern of FORBIDDEN_FLOW_VERBS) {
|
|
82116
82251
|
const match = pattern.exec(text);
|
|
82117
82252
|
if (match !== null) {
|
|
@@ -82186,9 +82321,10 @@ function unattendedRefusal(toolName, input) {
|
|
|
82186
82321
|
}
|
|
82187
82322
|
return;
|
|
82188
82323
|
}
|
|
82189
|
-
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;
|
|
82190
82325
|
var init_unattended = __esm(() => {
|
|
82191
82326
|
init_patch_risk();
|
|
82327
|
+
init_host_question_verbs();
|
|
82192
82328
|
init_shell_syntax();
|
|
82193
82329
|
init_command_risk();
|
|
82194
82330
|
UNATTENDED_EXCLUDED_TOOLS = [
|
|
@@ -82240,6 +82376,10 @@ var init_unattended = __esm(() => {
|
|
|
82240
82376
|
/\blaunchctl\b/,
|
|
82241
82377
|
/\bloginctl\b/
|
|
82242
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"]);
|
|
82243
82383
|
FORBIDDEN_GIT_VERBS = new Set(["push", "merge", "tag", "update-ref"]);
|
|
82244
82384
|
FORBIDDEN_GIT_BRANCH_FLAGS = new Set(["-f", "--force", "-D", "-M", "--delete", "-d", "-m", "--move"]);
|
|
82245
82385
|
GH_API_BODY_FLAGS = new Set(["-f", "-F", "--field", "--raw-field", "--input"]);
|
|
@@ -87745,13 +87885,19 @@ function premiseDecisionsCheck(ctx) {
|
|
|
87745
87885
|
if (held.length === 0)
|
|
87746
87886
|
return ok("no finding is held by the premise judge");
|
|
87747
87887
|
const awaiting = awaitingOperator(records, decisions);
|
|
87748
|
-
if (awaiting.length === 0)
|
|
87749
|
-
|
|
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
|
+
}
|
|
87750
87896
|
const evidence = `${awaiting.length} of ${held.length} held finding(s) have no operator decision: ${some(awaiting)}`;
|
|
87751
87897
|
if (!ctx.premise.ingested) {
|
|
87752
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`);
|
|
87753
87899
|
}
|
|
87754
|
-
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>")`);
|
|
87755
87901
|
}
|
|
87756
87902
|
function quotesLocatedCheck(ctx) {
|
|
87757
87903
|
const flagged = ctx.findings.filter((finding) => asRecord2(finding.quote_unlocated) !== undefined);
|
|
@@ -125655,7 +125801,7 @@ async function carryPremiseDecisions(packageDir, previousDir, carriedIds, carrie
|
|
|
125655
125801
|
return carried;
|
|
125656
125802
|
}
|
|
125657
125803
|
async function answerPremiseQuestion(packageDir, input, registry = {}) {
|
|
125658
|
-
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 }, (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));
|
|
125659
125805
|
if (vouched === true)
|
|
125660
125806
|
reRecordRegistry(packageDir, registry);
|
|
125661
125807
|
return decision;
|
|
@@ -125814,7 +125960,7 @@ function isPlanConfirmation(value) {
|
|
|
125814
125960
|
if (value === null || typeof value !== "object" || Array.isArray(value))
|
|
125815
125961
|
return false;
|
|
125816
125962
|
const v = value;
|
|
125817
|
-
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 !== "";
|
|
125818
125964
|
}
|
|
125819
125965
|
function readPlanConfirmation(packageDir) {
|
|
125820
125966
|
try {
|
|
@@ -125923,7 +126069,8 @@ var init_estimate = __esm(() => {
|
|
|
125923
126069
|
init_slice_routing();
|
|
125924
126070
|
init_waves();
|
|
125925
126071
|
init_round_split();
|
|
125926
|
-
|
|
126072
|
+
init_types4();
|
|
126073
|
+
CONFIRMED_VIA = ["tui-card", "remote-card", "terminal", "host-question"];
|
|
125927
126074
|
});
|
|
125928
126075
|
|
|
125929
126076
|
// src/review/plan-confirmation.ts
|
|
@@ -125937,9 +126084,14 @@ function planKeyOf2(estimate) {
|
|
|
125937
126084
|
function estimatePlan(packageDir) {
|
|
125938
126085
|
return estimatePackage(packageDir);
|
|
125939
126086
|
}
|
|
126087
|
+
async function readPlanConfirmation2(packageDir) {
|
|
126088
|
+
return readPlanConfirmation(packageDir);
|
|
126089
|
+
}
|
|
125940
126090
|
async function recordPlanConfirmation(packageDir, estimate, confirmation) {
|
|
125941
126091
|
if (confirmation.quote.trim() === "")
|
|
125942
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");
|
|
125943
126095
|
const record = {
|
|
125944
126096
|
planKey: planKeyOf2(estimate),
|
|
125945
126097
|
dispatches: estimate.dispatches,
|
|
@@ -125947,7 +126099,8 @@ async function recordPlanConfirmation(packageDir, estimate, confirmation) {
|
|
|
125947
126099
|
quote: confirmation.quote.trim(),
|
|
125948
126100
|
confirmedVia: confirmation.confirmedVia,
|
|
125949
126101
|
...cardPressFields(confirmation),
|
|
125950
|
-
at: confirmation.at
|
|
126102
|
+
at: confirmation.at,
|
|
126103
|
+
...confirmation.hostQuestion === undefined ? {} : { hostQuestion: confirmation.hostQuestion }
|
|
125951
126104
|
};
|
|
125952
126105
|
await writeFileAtomic(path290.join(packageDir, PLAN_CONFIRMATION_FILE), `${JSON.stringify(record, null, 2)}
|
|
125953
126106
|
`);
|
|
@@ -134590,17 +134743,34 @@ function printOperatorCommandHelp(command) {
|
|
|
134590
134743
|
|
|
134591
134744
|
Records the operator's waiver of the mutation pass in <review-dir>/operator-decisions.json: the review may
|
|
134592
134745
|
record \`Not run: mutation pass\` with unrun mutants. Asks at the terminal; refused without a TTY. In keryx
|
|
134593
|
-
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")}`,
|
|
134594
134752
|
"override-reviewers": `keryx review override-reviewers --package <review-dir> --reason "<why>" [--selected a,b]
|
|
134595
134753
|
|
|
134596
134754
|
Records the operator's decision to run a smaller reviewer set than \`keryx review required\` names (an
|
|
134597
134755
|
override or mode diff) in <review-dir>/operator-decisions.json. Asks at the terminal; refused without a TTY.
|
|
134598
|
-
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")}`,
|
|
134599
134762
|
"confirm-plan": `keryx review confirm-plan --package <review-dir>
|
|
134600
134763
|
|
|
134601
134764
|
Shows the plan's dispatches and minutes (\`keryx review estimate\`) and records the operator's yes in
|
|
134602
134765
|
<review-dir>/plan-confirmation.json for exactly this plan. Asks at the terminal; refused without a TTY. In
|
|
134603
|
-
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")}`,
|
|
134604
134774
|
"decide-premise": `keryx review decide-premise --package <review-dir> --finding <id> --answer uphold|dismiss --reason "<why>"
|
|
134605
134775
|
|
|
134606
134776
|
Answers the premise question on a finding the premise judge held (verdict challenged or owner-decision) and
|
|
@@ -134608,7 +134778,14 @@ records it in <review-dir>/operator-decisions.json, with your typed words, and o
|
|
|
134608
134778
|
(so the next round and the published round block read the answer too). uphold returns the finding to kept at its original
|
|
134609
134779
|
severity; dismiss records it dismissed-incorrect with your reason as the evidence. Refused for an unknown
|
|
134610
134780
|
finding, a finding that is not held, or a missing reason. Asks at the terminal; refused without a TTY. In
|
|
134611
|
-
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")}`,
|
|
134612
134789
|
"verify-run": `keryx review verify-run --package <review-dir> --finding <id> [--timeout <seconds>] -- <command\u2026>
|
|
134613
134790
|
|
|
134614
134791
|
Runs a verification command in this (the parent's) session and saves its stdout, stderr and exit status to
|
|
@@ -134654,6 +134831,9 @@ async function runOperatorCommand(command, args, seams = {}) {
|
|
|
134654
134831
|
log("Hand that file to the verifier (context_files) and have it cite the file in the claim's evidence.");
|
|
134655
134832
|
return 0;
|
|
134656
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
|
+
}
|
|
134657
134837
|
const isTerminal = seams.isTerminal ?? (() => process.stdin.isTTY === true && process.stdout.isTTY === true);
|
|
134658
134838
|
requireTerminal(command, isTerminal);
|
|
134659
134839
|
const ask = seams.ask ?? terminalAsk;
|
|
@@ -134710,13 +134890,210 @@ async function runOperatorCommand(command, args, seams = {}) {
|
|
|
134710
134890
|
log(`${kind} recorded (terminal, ${at}) in operator-decisions.json`);
|
|
134711
134891
|
return 0;
|
|
134712
134892
|
}
|
|
134713
|
-
|
|
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);
|
|
134714
135088
|
var init_review_operator = __esm(() => {
|
|
135089
|
+
init_service6();
|
|
134715
135090
|
init_args();
|
|
134716
135091
|
init_coverage();
|
|
134717
135092
|
init_operator_decisions();
|
|
134718
135093
|
init_plan_confirmation();
|
|
134719
135094
|
init_premise_question();
|
|
135095
|
+
init_run_profile();
|
|
135096
|
+
init_types4();
|
|
134720
135097
|
init_verify_run();
|
|
134721
135098
|
init_review_layers();
|
|
134722
135099
|
OPERATOR_COMMANDS = ["waive-mutation", "override-reviewers", "confirm-plan", "decide-premise", "verify-run"];
|
|
@@ -134726,6 +135103,70 @@ var init_review_operator = __esm(() => {
|
|
|
134726
135103
|
"confirm-plan": "review_confirm_plan",
|
|
134727
135104
|
"decide-premise": "review_decide_premise"
|
|
134728
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
|
+
]);
|
|
134729
135170
|
});
|
|
134730
135171
|
|
|
134731
135172
|
// src/commands/review-chunks.ts
|
|
@@ -148354,7 +148795,7 @@ async function completionFacts(dir) {
|
|
|
148354
148795
|
for (const decision of await readOperatorDecisions(dir)) {
|
|
148355
148796
|
const items = decision.subject?.items ?? [];
|
|
148356
148797
|
const what = decision.kind === "mutation-waiver" ? "mutation pass not run" : "reviewer override, left out";
|
|
148357
|
-
waivers.push(`waiver: ${what}${items.length === 0 ? "" : ` ${cardSafe(items.join(", "))}`}: "${quoteWhole(decision.quote)}" (${decision.confirmedVia})`);
|
|
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)}`})`);
|
|
148358
148799
|
}
|
|
148359
148800
|
return {
|
|
148360
148801
|
reviewId: manifest.reviewId ?? path337.basename(dir),
|
|
@@ -176138,7 +176579,7 @@ function summarizeExtras(report) {
|
|
|
176138
176579
|
counts: ARMS.map((arm) => [arm, whole(counts[arm])])
|
|
176139
176580
|
};
|
|
176140
176581
|
}
|
|
176141
|
-
return { reasons, ineligible, progress };
|
|
176582
|
+
return { reasons, ineligible, progress, hostQuestions: Array.isArray(report.hostQuestions) ? report.hostQuestions.length : 0 };
|
|
176142
176583
|
}
|
|
176143
176584
|
function nonNegative(value) {
|
|
176144
176585
|
return typeof value === "number" && Number.isFinite(value) && value > 0 ? value : 0;
|
|
@@ -176218,6 +176659,8 @@ function extrasLines(extras) {
|
|
|
176218
176659
|
}
|
|
176219
176660
|
if (extras.ineligible !== null)
|
|
176220
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}`);
|
|
176221
176664
|
if (extras.progress !== null) {
|
|
176222
176665
|
const p = extras.progress;
|
|
176223
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(", ")}`);
|
|
@@ -176331,12 +176774,13 @@ async function openDecisionsArms(otui, chrome, options) {
|
|
|
176331
176774
|
const text = await armsText(cwd);
|
|
176332
176775
|
return presentDecisions((hostOtui, hostChrome, input) => openModal(hostOtui, hostChrome, input), otui, chrome, { ...rest, text, tabLabel: "Arms" });
|
|
176333
176776
|
}
|
|
176334
|
-
function projectDecisionsPanel(count, width, backfilled = 0, annotated = 0) {
|
|
176335
|
-
if (count <= 0 && backfilled <= 0)
|
|
176777
|
+
function projectDecisionsPanel(count, width, backfilled = 0, annotated = 0, hostQuestions = 0) {
|
|
176778
|
+
if (count <= 0 && backfilled <= 0 && hostQuestions <= 0)
|
|
176336
176779
|
return { visible: false, text: "" };
|
|
176337
176780
|
const live = count > 0 ? `${count} decision${count === 1 ? "" : "s"}` : "";
|
|
176338
176781
|
const before = backfilled > 0 ? `${count > 0 ? " + " : ""}${backfilled} before` : "";
|
|
176339
|
-
const
|
|
176782
|
+
const via = hostQuestions > 0 ? `${count > 0 || backfilled > 0 ? " + " : ""}${hostQuestions} via host` : "";
|
|
176783
|
+
const label = `${live}${before}${via}`;
|
|
176340
176784
|
const marked = annotated > 0 ? `${label} ${OWN_MARK}${annotated}` : label;
|
|
176341
176785
|
const candidates = [`${marked} \xB7 /decisions`, marked, `${label} \xB7 /decisions`, label];
|
|
176342
176786
|
const text = candidates.find((candidate) => candidate.length <= width) ?? label.slice(0, Math.max(1, width));
|
|
@@ -176362,6 +176806,7 @@ function mountDecisionsSidebar(options) {
|
|
|
176362
176806
|
const count = options.count ?? (() => decisionCount(options.cwd));
|
|
176363
176807
|
const backfilledTotal = options.backfilled ?? (() => backfilledCount(options.cwd));
|
|
176364
176808
|
const annotatedTotal = options.annotated ?? (() => annotatedCount(options.cwd));
|
|
176809
|
+
const hostTotal = options.hostQuestions ?? (() => hostQuestionCount(options.cwd));
|
|
176365
176810
|
const box = new core.BoxRenderable(r, { id: "sb-decisions", flexDirection: "column", flexShrink: 0 });
|
|
176366
176811
|
options.parent.add(box);
|
|
176367
176812
|
const armsBox = new core.BoxRenderable(r, { id: "sb-decisions-arms", flexDirection: "column", flexShrink: 0 });
|
|
@@ -176449,9 +176894,15 @@ function mountDecisionsSidebar(options) {
|
|
|
176449
176894
|
} catch {
|
|
176450
176895
|
marked = 0;
|
|
176451
176896
|
}
|
|
176897
|
+
let viaHost;
|
|
176898
|
+
try {
|
|
176899
|
+
viaHost = await hostTotal();
|
|
176900
|
+
} catch {
|
|
176901
|
+
viaHost = 0;
|
|
176902
|
+
}
|
|
176452
176903
|
if (disposed || generation !== refreshGeneration)
|
|
176453
176904
|
return;
|
|
176454
|
-
projected = projectDecisionsPanel(n, options.width, before, marked);
|
|
176905
|
+
projected = projectDecisionsPanel(n, options.width, before, marked, viaHost);
|
|
176455
176906
|
paint();
|
|
176456
176907
|
let nextArms;
|
|
176457
176908
|
try {
|
|
@@ -216271,7 +216722,7 @@ function parseSkillMetadata(skillMd, registryEntry) {
|
|
|
216271
216722
|
};
|
|
216272
216723
|
}
|
|
216273
216724
|
function readMetadataLine(content, label) {
|
|
216274
|
-
const match = content.match(new RegExp(`^${
|
|
216725
|
+
const match = content.match(new RegExp(`^${escapeRegExp12(label)}:\\s*(.+)$`, "m"));
|
|
216275
216726
|
return match?.[1]?.trim();
|
|
216276
216727
|
}
|
|
216277
216728
|
function updateLastVerified(skillMd, verifiedAt) {
|
|
@@ -216306,7 +216757,7 @@ function inferModuleFromPath(packageRoot) {
|
|
|
216306
216757
|
const next = parts[index + 1];
|
|
216307
216758
|
return index >= 0 && next ? next : "general";
|
|
216308
216759
|
}
|
|
216309
|
-
function
|
|
216760
|
+
function escapeRegExp12(value) {
|
|
216310
216761
|
return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
216311
216762
|
}
|
|
216312
216763
|
var init_verify2 = __esm(() => {
|