@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/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": 1572,
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.123",
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 source = toLf(existing);
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 === source)
36624
- return repaired === source ? existing : inEolOf(existing, 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
- var MANAGED_REVIEW_MODES, REVIEW_TARGET_KINDS, REVIEW_PACKAGE_STATUSES, REVIEW_COVERAGE_STATUSES, 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;
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 kept = excludeLegacy ? opens.filter((open) => open.backfilled !== true && !isLegacy(open)) : opens;
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
- return `Export: ${summary.rows} decision${summary.rows === 1 ? "" : "s"}, ${summary.skipped} skipped, ${summary.blanked} invalid field${summary.blanked === 1 ? "" : "s"} blanked.`;
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>"\`` : `in keryx shell, call the review_override_reviewers tool (the operator answers a card); in a terminal, run \`keryx review override-reviewers --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?)`/g;
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
- return { operatorQuote: waiver.operatorQuote, time: waiver.time, files, ...via === undefined ? {} : { confirmedVia: via } };
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 ? { confirmedVia: confirmation.confirmedVia, confirmationQuote: confirmation.quote } : {}
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
- return ok(`${held.length} held finding(s), every one with an operator decision`);
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
- for (const { finding, disposition } of pending) {
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 nextStep(open) {
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 "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 `keryx review complete`.";
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 "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 `keryx review complete`.";
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 "Next: write or ingest the missing artifacts (`keryx review ingest`), then `keryx review complete`.";
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 "- none listed; run `keryx review complete` to close the package";
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 : ""}. ` + `\`keryx review complete\` still refuses on:
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 \`keryx review complete\` accepts the package, not when you stop writing. ` + `It still refuses on:
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. " + "Stop only after `review complete` succeeds, or report the one real blocker with its evidence."
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
- confirmationVia = typeof response === "object" && response.via === "remote" ? "remote" : "local";
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 { confirmedVia: confirmation.confirmedVia, quote: confirmation.quote };
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
- ...limited(files),
124908
- ...run.flaggedPaths.length > 0 ? [`FLAGGED PATHS (${run.flaggedPaths.length}) - repo plumbing, CI, hooks or agent config:`, ...limited(run.flaggedPaths.map((p) => ` ! ${cardSafe(p)}`))] : [],
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 MAX_LISTED = 12, SCOPES2, SCHEDULE_DESCRIPTIONS;
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 path286 from "path";
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(path286.join(packageDir, "manifest.json"), undefined);
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(path286.join(packageDir, "report.md"), "utf8").catch(() => "")).trim();
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(path286.join(packageDir, "findings.json"), []);
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(path286.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 } }, null, 2)}
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 path287 from "path";
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(path287.join(packageDir, FINDINGS_FILE), undefined);
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 ${path287.basename(packageDir)}/${FINDINGS_FILE}. Name a finding by its global_id or id.` };
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) : path287.resolve(packageDir);
125486
- if (realPrevious === realPackage || realPackage.startsWith(`${realPrevious}${path287.sep}`))
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(path287.join(realPrevious, FINDINGS_FILE), undefined);
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 = path287.join(packageDir, FINDINGS_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 readFileSync33 } from "fs";
125571
- import path288 from "path";
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 = path288.join(projectRoot, "routing.config.json");
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(readFileSync33(path288.join(packageDir, PLAN_CONFIRMATION_FILE), "utf8"));
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 = path288.join(packageDir, PLAN_CONFIRMATION_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
- path288.join(packageDir, "slices", "manifest.json"),
125716
- path288.join(packageDir, "slice-manifest.json"),
125717
- path288.join(root, ".metaproject", "data", "review", "slices", "manifest.json")
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 ?? path288.join(packageDir, "slices", "manifest.json") };
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(`${path288.relative(root, found.file)}: ${problem}`);
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 : path288.relative(root, found.file) };
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
- CONFIRMED_VIA = ["tui-card", "remote-card", "terminal"];
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 path289 from "path";
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
- at: confirmation.at
126101
+ ...cardPressFields(confirmation),
126102
+ at: confirmation.at,
126103
+ ...confirmation.hostQuestion === undefined ? {} : { hostQuestion: confirmation.hostQuestion }
125804
126104
  };
125805
- await writeFileAtomic(path289.join(packageDir, PLAN_CONFIRMATION_FILE), `${JSON.stringify(record, null, 2)}
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 path290 from "path";
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 = path290.join(packageDir, EVIDENCE_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(path290.join(dir, name), "utf8")) });
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 = path290.join(packageDir, "findings.json");
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 path291 from "path";
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(path291.join(packageDir, PR_CONTEXT_JSON), "utf8"));
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 = path291.join(input.packageDir, PR_CONTEXT_JSON);
126057
- const md = path291.join(input.packageDir, PR_CONTEXT_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 path292 from "path";
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 = path292.join(inputDir, "raw");
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(path292.join(dir, name), "utf8") });
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(path292.join(packageDir, "findings.json"));
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(path292.join(packageDir, "research.json")));
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 = path292.join(packageDir, "research-evidence");
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 = path292.basename(loc.file).toLowerCase();
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 && path292.basename(evidence.file).toLowerCase() === base && evidence.line !== undefined && Math.abs(evidence.line - loc.line) <= LINE_WINDOW2;
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 path293 from "path";
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) => path293.join(dir, 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(path293.join(inputDir, "drivers"))) {
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: path293.basename(file), reviewer: driver.reviewer, unusable: slices.unusable });
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(path293.basename(driver.result));
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 = path293.resolve(root, candidate);
126640
+ const definition = path294.resolve(root, candidate);
126340
126641
  if (existsSync60(definition)) {
126341
- ruleFiles[driver.reviewer] = path293.relative(root, definition);
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 path294 from "path";
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 = path294.join(packageDir, DRIVERS_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(path294.join(dir, name), "utf8")));
126687
+ kept.set(name, JSON.parse(await readFile137(path295.join(dir, name), "utf8")));
126387
126688
  } catch {}
126388
- await rm23(path294.join(dir, name), { force: true });
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(path294.resolve(root, candidate)));
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: path294.resolve(sliceDir, entry.path), bytes: entry.bytes })),
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(path294.join(dir, name), `${JSON.stringify(driver, null, 2)}
126710
+ await writeFileAtomic(path295.join(dir, name), `${JSON.stringify(driver, null, 2)}
126410
126711
  `);
126411
- written.push(path294.join(dir, name));
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 path295 from "path";
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 : path295.resolve(cwd, value);
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: ${path295.join(plan.package, PLAN_FILE)}`
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 = path295.join(packageDir, "scope.json");
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(path295.join(packageDir, "scope-files.json"))) {
126847
+ if (!existsSync62(path296.join(packageDir, "scope-files.json"))) {
126547
126848
  const lines = scopeRecord?.counts?.changedLinesRetained;
126548
- await writeFileAtomic(path295.join(packageDir, "scope-files.json"), `${JSON.stringify({ version: 1, files: scopeFiles, ...typeof lines === "number" ? { changedLines: lines } : {} }, null, 2)}
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", path295.join(packageDir, "blast-radius.json"), ...baseRef === undefined ? [] : ["--ref", baseRef]]);
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 = path295.join(packageDir, "slices", "manifest.json");
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: path295.dirname(manifestPath), reviewers: await packageReviewers(packageDir, manifest) });
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(path295.join(packageDir, PLAN_FILE), `${JSON.stringify(plan, null, 2)}
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 path296 from "path";
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(path296.resolve(findingsFile), "utf8"));
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 path297 from "path";
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 = path297.join(path297.dirname(fileURLToPath5(import.meta.url)), "..", "src", "gdskills", "bundled", "skills", category, skillName);
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 = path297.join(skillsRoot, skillEntry.category);
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 = path297.join(categoryDir, skillEntry.name);
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 !== path297.join(root.realPath, skillEntry.category, skillEntry.name)) {
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(path297.join(bundledDir, entryName)))
127256
+ if (existsSync64(path298.join(bundledDir, entryName)))
126956
127257
  continue;
126957
- const installedPath = path297.join(installedDir, entryName);
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 path297.relative(projectRoot, target).split(path297.sep).join("/");
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 = path297.join(rulesTarget, retired.fileName);
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 path298 from "path";
127427
+ import path299 from "path";
127127
127428
  import { fileURLToPath as fileURLToPath6 } from "url";
127128
127429
  async function mkdirContainedOrWarn(metaprojectRoot, target) {
127129
- const rel = path298.relative(metaprojectRoot, target);
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 = path298.join(metaprojectRoot, "skills", "gdskills");
127143
- const dataRoot = path298.join(metaprojectRoot, "data", "gdskills");
127144
- const coreRoot = path298.join(metaprojectRoot, "core", "gdskills");
127145
- const contractsRoot = path298.join(coreRoot, "contracts");
127146
- const projectSkillsRoot = path298.join(metaprojectRoot, metaprojectRelative(PROJECT_SKILLS_DIR));
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
- path298.join(metaprojectRoot, "jobs"),
127150
- path298.join(metaprojectRoot, "modules"),
127450
+ path299.join(metaprojectRoot, "jobs"),
127451
+ path299.join(metaprojectRoot, "modules"),
127151
127452
  projectSkillsRoot,
127152
127453
  ...options.createDataDirs === false ? [] : [
127153
- path298.join(dataRoot, "artifacts"),
127154
- path298.join(metaprojectRoot, metaprojectRelative(PROJECT_SKILL_REPORTS_DIR)),
127155
- path298.join(dataRoot, "proposals")
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 = path298.join(skillsRoot, skillEntry.category, skillEntry.name);
127165
- const relSkillDir = path298.relative(metaprojectRoot, skillDir);
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, path298.join(relSkillDir, "SKILL.md"), renderBundledSkill(skillEntry));
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 = path298.join(metaprojectRoot, metaprojectRelative(PROJECT_SKILLS_CATALOG_PATH));
127195
- await writeContained(metaprojectRoot, path298.relative(metaprojectRoot, catalogPath), await preserveProjectSkillsSection(catalogPath, renderGdskillsCatalog(profile)));
127196
- const manifestPath = path298.join(metaprojectRoot, "modules", "gdskills.md");
127197
- await writeContained(metaprojectRoot, path298.relative(metaprojectRoot, manifestPath), renderGdskillsManifest(profile));
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 = path298.dirname(metaprojectRoot);
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 = path298.join(skillsRoot, "shared");
127222
- const relSharedTarget = path298.relative(metaprojectRoot, sharedTarget);
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 = path298.join(metaprojectRoot, "rules", "core");
127566
+ const rulesTarget = path299.join(metaprojectRoot, "rules", "core");
127266
127567
  if (!existsSync65(rulesSource)) {
127267
127568
  return { outcomes: [], warning: null };
127268
127569
  }
127269
- const relRulesTarget = path298.relative(metaprojectRoot, rulesTarget);
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, path298.relative(metaprojectRoot, 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 = path298.join(contractsRoot, contract.fileName);
127306
- const relDestination = path298.relative(metaprojectRoot, destination);
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 = path298.join(path298.dirname(fileURLToPath6(import.meta.url)), "..", "src", "gdskills", "contracts", fileName);
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 = path298.join(path298.dirname(fileURLToPath6(import.meta.url)), "..", "src", "gdskills", "bundled", "skills", "shared");
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 = path298.join(path298.dirname(fileURLToPath6(import.meta.url)), "..", "src", "gdskills", "bundled", "rules", "core");
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 path299 from "path";
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 = path299.relative(parent, child);
127781
- return relative !== "" && relative !== ".." && !relative.startsWith(`..${path299.sep}`) && !path299.isAbsolute(relative);
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 = path299.resolve(absolute);
127785
- const root = path299.resolve(projectRoot);
128085
+ const resolved = path300.resolve(absolute);
128086
+ const root = path300.resolve(projectRoot);
127786
128087
  if (isInside6(root, resolved)) {
127787
- return toPosix(path299.relative(root, resolved));
128088
+ return toPosix(path300.relative(root, resolved));
127788
128089
  }
127789
128090
  if (isInside6(home, resolved)) {
127790
- return `~/${toPosix(path299.relative(home, resolved))}`;
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 = path299.join(dir, "SKILL.md");
127854
- const name = path299.basename(dir);
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(path299.join(root, "SKILL.md"))) {
128208
+ if (await pathExists(path300.join(root, "SKILL.md"))) {
127908
128209
  return { dirs: [root], tree: false };
127909
128210
  }
127910
- const nestedSkills = path299.join(root, "skills");
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 = path299.join(scan, entry.name);
127918
- if (await pathExists(path299.join(dir, "SKILL.md"))) {
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 = path299.join(options.projectRoot, dest, "SKILL.md");
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(path299.join(dest, "SKILL.md")), options.allowFlagged === true);
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 = path299.join(options.projectRoot, entry.path, "SKILL.md");
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(path299.join(entry.path, "SKILL.md")), options.allowFlagged === true);
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(path299.join(created.skillPath, "SKILL.md")), gate.content);
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 = path299.dirname(sourcePath);
128555
+ let dir = path300.dirname(sourcePath);
128255
128556
  let notRegular;
128256
128557
  for (let depth = 0;depth <= 3; depth += 1) {
128257
- const candidate = path299.join(dir, "rules", ref);
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 = path299.dirname(dir);
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(path299.join(bundledRules, path299.basename(citation.ref)));
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(path299.join(options.projectRoot, existing), "utf8") : undefined;
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 = path299.join(path299.dirname(sourcePath), TEMPLATES_DIR);
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(path299.join(dir, entry.name), rel);
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 = path299.join(current, part);
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(path299.join(path299.dirname(pkg.sourcePath), TEMPLATES_DIR, file));
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 = path299.join(options.projectRoot, target);
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 = path299.basename(path299.dirname(filePath));
128862
+ const parent = path300.basename(path300.dirname(filePath));
128562
128863
  if (parent && parent !== "skills" && parent !== ".")
128563
128864
  return parent;
128564
- return path299.basename(filePath, path299.extname(filePath));
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 = path299.join(projectRoot, PROJECT_SKILLS_MANIFEST_PATH);
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 path300 from "path";
129292
+ import path301 from "path";
128992
129293
  async function readJevRulesEnabled(cwd) {
128993
129294
  try {
128994
- const parsed = JSON.parse(await readFile143(path300.join(cwd, REVIEW_GATE_CONFIG_PATH), "utf8"));
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 path301 from "path";
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 path301.join(cwd, JEV_RULES_CACHE_PATH);
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 path302 from "path";
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 path302.join(cwd, cacheRelPath);
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 path303 from "path";
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(path303.join(dir, "manifest.json"), "utf8"));
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(path303.join(dir, "findings.json"), "utf8"));
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 = path303.join(cwd, ".metaproject", "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(path303.join(reviews, name));
130736
+ dirs.push(path304.join(reviews, name));
130436
130737
  }
130437
- const flows = path303.join(cwd, ".metaproject", "flows");
130738
+ const flows = path304.join(cwd, ".metaproject", "flows");
130438
130739
  for (const flow of await subdirectories(flows)) {
130439
- const nested = path303.join(flows, flow, "reviews");
130740
+ const nested = path304.join(flows, flow, "reviews");
130440
130741
  for (const name of await subdirectories(nested))
130441
- dirs.push(path303.join(nested, name));
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 (path303.basename(dir) !== reviewId)
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 path304 from "path";
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 path304.join(cwd, ".metaproject", "reviews", "bot", `${repo.replace(/\//g, "__")}__${number}.json`);
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 path305 from "path";
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: path305.basename(cwd), target }) : NO_NOTIFIER);
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(path305.join(result.runDir, ARBITER_LOG_NAME)));
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: ${path305.join(result.runDir, "report.md")}`);
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 path306 from "path";
131919
+ import path307 from "path";
131619
131920
  async function readJevRiskEnabled(cwd) {
131620
131921
  try {
131621
- const parsed = JSON.parse(await readFile149(path306.join(cwd, REVIEW_GATE_CONFIG_PATH), "utf8"));
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
- var OPERATOR_COMMANDS, CARD;
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 run `keryx review complete`. Outside keryx shell, run this command in a terminal.");
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 run `keryx review complete`. Outside keryx shell, run this command in a terminal.");
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 completeManagedReview(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 } });
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
- init_routing_check();
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 { existsSync as existsSync75, statSync as statSync21 } from "fs";
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
- const shown = files.slice(0, LISTED_FILES).map((file) => ` ${file}`);
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. " + "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`.",
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). " + "Same effect as `keryx review publish`; the operator sees the target, the verdict and the start of the body first.",
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 bodyLines = result.body.trimEnd().split(/\r?\n/);
147808
- const preview = bodyLines.slice(0, BODY_PREVIEW_LINES).map((line) => ` ${cardSafe(line.length > BODY_PREVIEW_CHARS ? `${line.slice(0, BODY_PREVIEW_CHARS)}\u2026` : line)}`);
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; the first ${Math.min(BODY_PREVIEW_LINES, bodyLines.length)}:`,
147818
- ...preview,
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. " + "Same effect as `keryx review complete --waive-coverage`: records the waiver (the operator's yes, the time, the files); run `keryx review complete` afterwards to close the review.",
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. Run \`keryx review complete\` to close the review.` };
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). " + "Same effect as `keryx review complete --waive <item>`: records the waiver (the operator's yes, the time) in the manifest and the report header; run `keryx review complete` afterwards to close the review.",
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(", ")}. Run \`keryx review complete\` to close the review.` };
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. " + "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.",
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 reason = reasonOf2(input);
148499
+ const given = reasonOf2(input);
147976
148500
  if (ref === undefined)
147977
148501
  return { refusal: "package is required" };
147978
- if (reason === undefined)
147979
- return { refusal: "reason is required" };
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.slice(0, LISTED_FILES).join(", "))}${pending > LISTED_FILES ? ` \u2026 and ${pending - LISTED_FILES} more` : ""}`,
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.slice(0, LISTED_FILES).join(", ") || "none")}${missing.length > LISTED_FILES ? ` \u2026 and ${missing.length - LISTED_FILES} more` : ""}`,
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. " + "The operator sees every reply before anything is posted.",
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 Promise.resolve().then(() => init_review());
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 = requests.slice(0, REPLY_PREVIEW).map((request) => ` ${cardSafe((request.body.body ?? "").slice(0, BODY_PREVIEW_CHARS))}`);
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}; the first ${Math.min(REPLY_PREVIEW, requests.length)}:`, ...bodies, "yes: the replies are posted on the pull request and cannot be taken back from here"],
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 Promise.resolve().then(() => init_review());
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 commentsReplyPass([...prepared.argv], false);
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.slice(0, LISTED_FILES).map((role) => ` ${cardSafe(role.role)}: ${role.slices} slice(s), ${role.waves} wave(s)`),
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 reason = reasonOf2(input);
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 (reason === undefined)
148176
- return { refusal: "reason is required" };
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 LISTED_FILES = 15, BODY_PREVIEW_LINES = 8, BODY_PREVIEW_CHARS = 140, packageSchema, label3 = (dir, projectRoot) => path337.relative(projectRoot, dir) || ".", failed = (error) => ({ refusal: error instanceof Error ? error.message : String(error) }), reasonOf2 = (input) => {
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
- return typeof value === "string" && value.trim() !== "" ? value.trim() : undefined;
148233
- }, REPLY_PREVIEW = 6;
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
- if (token !== undefined)
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
- return { output: `${spec.name}: no operator-confirmed card for this request \u2014 nothing was changed`, isError: true };
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: ctx?.confirmationVia === "remote" ? "remote-card" : "tui-card",
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 createHash85 } from "crypto";
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 createHash85("sha256").update(canonical).digest("hex");
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 readFileSync39 } from "fs";
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(readFileSync39(path339.join(cwd, REVIEW_GATE_CONFIG_PATH), "utf8"));
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 createHash86 } from "crypto";
150279
- import { existsSync as existsSync77, readFileSync as readFileSync40 } from "fs";
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 = createHash86("sha256").update(JSON.stringify({ version, rules })).digest("hex").slice(0, 12);
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(readFileSync40(file, "utf8"));
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 createHash87 } from "crypto";
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 createHash87("sha256").update(JSON.stringify([question.kind, facts, question.optionCount, question.recommended])).digest("hex");
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 createHash88 } from "crypto";
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 createHash88("sha256").update(read.text, "utf8").digest("hex");
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 createHash89, randomUUID as randomUUID37 } from "crypto";
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 createHash89("sha256").update(text).digest("hex");
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 createHash90 } from "crypto";
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 createHash90("sha256").update(material).digest("hex").slice(0, 32);
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)`/g;
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 createHash91 } from "crypto";
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 createHash91("sha256").update(content).digest("hex");
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 createHash91("sha256").update([...lines].sort().join(`
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 ${clock2(history.at)} (${history.auto ? "restored automatically" : HISTORY_COMMAND}). Again: ${HISTORY_COMMAND} [N], or press h here.`;
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 clock2(at) {
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) => `${clock2(event.at)} ${event.kind.padEnd(8)} ${event.text}`);
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) => `${clock2(event.at)} ${event.text}`);
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 ${clock2(status.history.at).slice(0, 5)}`, width) };
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 createHash92 } from "crypto";
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 = createHash92("sha256").update(`${projectBytes}:${project}\x00${idempotencyKey}`, "utf8").digest("hex");
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 = createHash92("sha256").update(idempotencyKey, "utf8").digest("hex");
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 createHash93 } from "crypto";
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${createHash93("sha256").update(`${eventKey}
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${createHash93("sha256").update(seed).digest("hex").slice(0, 11)}`;
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 clock3(iso) {
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 ${clock3(nextPollAt)}`;
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 text = await reportText(cwd).catch(() => UNREADABLE);
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 label = `${live}${before}`;
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 errorText4(error) {
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: ${errorText4(error)}`);
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: ${errorText4(error)}`;
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: errorText4(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: ${errorText4(error)}. ${NOTHING_APPLIED}`;
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: ${errorText4(error)}`;
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 readFileSync41 } from "fs";
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(readFileSync41(file, "utf8"));
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 readFileSync42, readlinkSync as readlinkSync2 } from "fs";
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 = readFileSync42("/proc/self/stat", "utf8").trim();
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 createHash94 } from "crypto";
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 createHash94("sha256").update(text).digest("hex");
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 readFileSync43, writeFileSync as writeFileSync12 } from "fs";
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(readFileSync43(file, "utf8"));
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 clip7(s, max) {
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(` ${clip7(phase, 26)}`);
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 = clip7(w.label, 12);
191615
- const det = phase.length > 0 ? ` ${clip7(phase, 12)}` : "";
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 ? ` ${clip7(activity, 14)}` : "";
191646
- return `${marker}${statusGlyph} @${clip7(name, 10)}${detail}`;
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 clip8(s, max) {
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}${clip8(session.label, labelBudget)}`;
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 ? ` ${clip8(phase, detBudget)}` : "";
191785
- return clip8(`${glyph} ${label}${det}`, width);
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) => clip9(line, options.width ?? 0));
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 clip9(value, width) {
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 clip10(s, max) {
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 = clip10(entry.command, budget);
193204
- return clip10(`${glyph} ${cmd}`, width);
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 ? { approved: true, fingerprint: meta.fingerprint, via: "remote" } : true;
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 raceApproval({
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 ? { approved: true, fingerprint: meta.fingerprint, via: "remote" } : true;
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 readFileSync44, readlinkSync as readlinkSync3, statSync as statSync24 } from "fs";
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 readFileSync44(file, "utf8");
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: { projectRoot: cwd }
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 readFileSync45 } from "fs";
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 = readFileSync45(refPath, "utf8");
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 createHash95 } from "crypto";
212090
- import { existsSync as existsSync91, readdirSync as readdirSync21, readFileSync as readFileSync46, statSync as statSync25 } from "fs";
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 createHash95("sha256").update(parts.join(`
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 createHash95("sha256").update(readFileSync46(reportPath, "utf8")).digest("hex");
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 = createHash95("sha256");
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(readFileSync46(path415.join(referencesDir, rel)));
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(readFileSync46(cachePath, "utf8"));
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(readFileSync46(reportPath, "utf8"));
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 readFileSync47 } from "fs";
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(readFileSync47(file, "utf8"));
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 createHash96 } from "crypto";
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 createHash96("sha256").update(stableStringify3(rest)).digest("hex");
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(`^${escapeRegExp11(label)}:\\s*(.+)$`, "m"));
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 escapeRegExp11(value) {
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 createHash97 } from "crypto";
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 createHash97("sha256").update(canonical, "utf8").digest("hex");
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 createHash98 } from "crypto";
227336
+ import { createHash as createHash99 } from "crypto";
226344
227337
  function hashContent(content) {
226345
- return createHash98("sha256").update(content, "utf8").digest("hex");
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 createHash99 } from "crypto";
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 createHash99("sha256").update(input, "utf8").digest("hex");
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 createHash100 } from "crypto";
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 createHash100("sha256").update(input, "utf8").digest("hex");
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 createHash101 } from "crypto";
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 createHash101("sha256").update(input, "utf8").digest("hex");
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 createHash102 } from "crypto";
228321
+ import { createHash as createHash103 } from "crypto";
227329
228322
  function sha256Hex11(input) {
227330
- return createHash102("sha256").update(input, "utf8").digest("hex");
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 readFileSync48, writeFileSync as writeFileSync13 } from "fs";
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 = readFileSync48(file, "utf8");
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(readFileSync48(specPath, "utf8"));
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(clip11(model, 22), 24) + (s.parentSessionId !== undefined ? `\u21B3 ${s.title}` : s.title));
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 clip11(s, n) {
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 createHash103 } from "crypto";
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 createHash103("sha256").update(JSON.stringify(canonical)).digest("hex");
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 readFileSync49 } from "fs";
231189
+ import { readFileSync as readFileSync50 } from "fs";
230197
231190
  function loadAcpFixture(path) {
230198
- const raw = readFileSync49(path, "utf-8");
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 createHash104 } from "crypto";
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-${createHash104("sha256").update(machine.normalize("NFC")).digest("hex").slice(0, 6)}`;
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 createHash105 } from "crypto";
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 = createHash105("sha256").update(bindingKey).digest("hex").slice(0, 32);
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 clip12(text, max) {
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: ${clip12(event.title, TITLE_MAX).replace(/\s+/g, " ")}`,
239218
+ `Title: ${clip13(event.title, TITLE_MAX).replace(/\s+/g, " ")}`,
238226
239219
  `Allowed actions: ${input.allowed.join(", ")}`,
238227
239220
  "Text:",
238228
- clip12(event.body ?? "", BODY_MAX2)
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 readFileSync50, writeFileSync as writeFileSync14 } from "fs";
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(readFileSync50(packJsonPath, "utf8"));
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 ? readFileSync50(filePath, "utf8") : undefined;
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 readFileSync51 } from "fs";
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(readFileSync51(source, "utf8"));
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 readFileSync52 } from "fs";
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 = readFileSync52(payloadFileArg, "utf8");
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 = readFileSync52(filePath, "utf8");
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();