@mrciphersmith/keryx 0.3.123 → 0.3.124

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
@@ -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.124",
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: {
@@ -43746,6 +43746,12 @@ var init_scope = __esm(() => {
43746
43746
  });
43747
43747
 
43748
43748
  // src/review/types.ts
43749
+ function cardPressFields(source) {
43750
+ return {
43751
+ ...source?.pressedBy === undefined ? {} : { pressedBy: source.pressedBy },
43752
+ ...source?.pressedAt === undefined ? {} : { pressedAt: source.pressedAt }
43753
+ };
43754
+ }
43749
43755
  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;
43750
43756
  var init_types4 = __esm(() => {
43751
43757
  MANAGED_REVIEW_MODES = ["attach-review", "review-flow", "ingest"];
@@ -48993,6 +48999,7 @@ async function recordPremiseDecisionAfter(packageDir, input, applyFirst) {
48993
48999
  at: input.at,
48994
49000
  actor: PREMISE_DECISION_ACTOR,
48995
49001
  confirmedVia: input.confirmedVia,
49002
+ ...cardPressFields(input),
48996
49003
  subject: input.subject,
48997
49004
  ...input.carriedFrom === undefined ? {} : { carriedFrom: input.carriedFrom }
48998
49005
  };
@@ -49032,7 +49039,7 @@ async function recordOperatorDecision(packageDir, decision) {
49032
49039
  if (!validGateDecision(decision))
49033
49040
  throw new Error("an operator decision needs a kind, the operator's words, a reason, how it was confirmed and the time");
49034
49041
  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 } };
49042
+ const entry = { ...decision, quote: decision.quote.trim(), reason: decision.reason.trim(), ...cardPressFields(decision), ...subject === undefined ? {} : { subject } };
49036
49043
  await withOperatorDecisionsLock(packageDir, () => appendDecision(packageDir, entry));
49037
49044
  return decision;
49038
49045
  }
@@ -49046,6 +49053,7 @@ var OPERATOR_DECISIONS_FILE = "operator-decisions.json", OPERATOR_DECISIONS_LOCK
49046
49053
  var init_operator_decisions = __esm(() => {
49047
49054
  init_fs();
49048
49055
  init_json();
49056
+ init_types4();
49049
49057
  OPERATOR_DECISION_KINDS = ["mutation-waiver", "reviewer-override"];
49050
49058
  VIAS = ["tui-card", "remote-card", "terminal"];
49051
49059
  });
@@ -51490,14 +51498,14 @@ function recordChunkEstimate(packageDir, estimate, mode) {
51490
51498
  return record;
51491
51499
  });
51492
51500
  }
51493
- async function answerChunkEstimate(packageDir, value, quote, now = new Date, confirmedVia) {
51501
+ async function answerChunkEstimate(packageDir, value, quote, now = new Date, confirmedVia, press) {
51494
51502
  if (quote.trim() === "")
51495
51503
  throw new Error("The answer needs the operator's own words (--quote).");
51496
51504
  return withChunkStateLock(packageDir, async () => {
51497
51505
  const previous = await readChunkEstimate(packageDir);
51498
51506
  if (previous === undefined)
51499
51507
  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 } } };
51508
+ const record = { ...previous, asked: true, answer: { value, quote: quote.trim(), at: now.toISOString(), ...confirmedVia === undefined ? {} : { confirmedVia }, ...cardPressFields(press) } };
51501
51509
  await writeFileAtomic(path117.join(packageDir, CHUNK_ESTIMATE_FILE), `${JSON.stringify(record, null, 2)}
51502
51510
  `);
51503
51511
  return record;
@@ -51543,6 +51551,7 @@ var init_chunks_estimate = __esm(() => {
51543
51551
  init_chunks();
51544
51552
  init_blast_radius();
51545
51553
  init_json();
51554
+ init_types4();
51546
51555
  DEFAULT_CHUNK_LIMITS = { maxPasses: DEFAULT_MAX_PASSES, inputUsdPerMTok: 3, outputUsdPerMTok: 15, secondsPerPass: 150, source: "default" };
51547
51556
  });
51548
51557
 
@@ -68822,7 +68831,7 @@ async function collectProjectRules(projectRoot, warnings) {
68822
68831
  function selectRules(candidates, files, taskType, bundled) {
68823
68832
  const selected = [];
68824
68833
  for (const candidate of candidates) {
68825
- const shadowsPath = candidate.refs.map((ref) => bundled.get(ref)).find((found) => found !== undefined);
68834
+ const shadowsPath = candidate.refs.map((ref) => bundled.get(ref) ?? bundled.get(withoutCorePrefix(ref))).find((found) => found !== undefined);
68826
68835
  const matching = candidate.front.globs.filter((glob) => files.some((file) => matchesAnyGlobWithBraces(file, [glob])));
68827
68836
  const base = { candidate, ...shadowsPath !== undefined ? { shadows: shadowsPath } : {} };
68828
68837
  if (matching.length > 0) {
@@ -69095,7 +69104,7 @@ var init_context_resolver = __esm(() => {
69095
69104
  service_api: ["code-style-patterns", "nestjs-dto", "api-contracts"]
69096
69105
  };
69097
69106
  TEST_RULES = ["tdd-workflow", "testing-conventions"];
69098
- CITATION = /`([^`\s]+\.mdc?)`/g;
69107
+ CITATION = /`([^`\s#]+\.mdc?)(?:#[^`\s]*)?`/g;
69099
69108
  });
69100
69109
 
69101
69110
  // src/memory/relevant.ts
@@ -73358,7 +73367,9 @@ function readCoverageWaiver(dispatch) {
73358
73367
  return;
73359
73368
  const files = Array.isArray(waiver.files) ? waiver.files.filter(text2) : [];
73360
73369
  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 } };
73370
+ const pressedBy = typeof waiver.pressedBy === "number" && Number.isSafeInteger(waiver.pressedBy) ? waiver.pressedBy : undefined;
73371
+ const pressedAt = text2(waiver.pressedAt) ? waiver.pressedAt : undefined;
73372
+ return { operatorQuote: waiver.operatorQuote, time: waiver.time, files, ...via === undefined ? {} : { confirmedVia: via }, ...cardPressFields({ pressedBy, pressedAt }) };
73362
73373
  }
73363
73374
  async function computeCoverage(packageDir, options = {}) {
73364
73375
  const research = options.research ?? await readJson4(path172.join(packageDir, "research.json"));
@@ -73458,7 +73469,7 @@ function coverageLine(report) {
73458
73469
  const { inScope, covered, omitted, uncovered } = report.counts;
73459
73470
  return `coverage: in scope ${inScope}, covered ${covered}, omitted ${omitted}${reasons === "" ? "" : ` (${reasons})`}, uncovered ${uncovered}`;
73460
73471
  }
73461
- async function recordCoverageWaiver(packageDir, operatorQuote, now = new Date, confirmedVia) {
73472
+ async function recordCoverageWaiver(packageDir, operatorQuote, now = new Date, confirmedVia, press) {
73462
73473
  const quote = operatorQuote.replace(/-{0,2}waive[- ]?coverage/gi, "").trim();
73463
73474
  if (quote.length < MIN_WAIVER_QUOTE_CHARS || !/[\p{L}\p{N}]/u.test(quote)) {
73464
73475
  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 +73483,7 @@ async function recordCoverageWaiver(packageDir, operatorQuote, now = new Date, c
73472
73483
  const uncovered = report.files.filter((entry) => entry.status === "uncovered").map((entry) => entry.file);
73473
73484
  if (!report.tracked || uncovered.length === 0)
73474
73485
  return;
73475
- const waiver = { operatorQuote: quote, time: now.toISOString(), files: uncovered, ...confirmedVia === undefined ? {} : { confirmedVia } };
73486
+ const waiver = { operatorQuote: quote, time: now.toISOString(), files: uncovered, ...confirmedVia === undefined ? {} : { confirmedVia }, ...cardPressFields(press) };
73476
73487
  await writeFileAtomic(file, `${JSON.stringify({ ...research, dispatch: { ...dispatch, coverageWaiver: waiver } }, null, 2)}
73477
73488
  `);
73478
73489
  return waiver;
@@ -73503,6 +73514,7 @@ var init_coverage_complete = __esm(() => {
73503
73514
  init_chunks();
73504
73515
  init_blast_radius();
73505
73516
  init_coverage();
73517
+ init_types4();
73506
73518
  init_json();
73507
73519
  });
73508
73520
 
@@ -77566,7 +77578,9 @@ function flowStateSchema() {
77566
77578
  description: "The completion target the token was minted for: `pr:<url>` or `merged`."
77567
77579
  }
77568
77580
  }
77569
- }
77581
+ },
77582
+ pressedBy: { type: "integer", description: "Flow 464: on a remote-card token, the Telegram user id that pressed the card." },
77583
+ pressedAt: { type: "string", description: "Flow 464: on a remote-card token, the shell's clock when the press arrived." }
77570
77584
  }
77571
77585
  }
77572
77586
  }
@@ -77828,7 +77842,9 @@ async function mintConfirmationToken(cwd, dir, binding, deps) {
77828
77842
  mintedAt: mintedAt.toISOString(),
77829
77843
  expiresAt: new Date(mintedAt.getTime() + CONFIRMATION_TOKEN_TTL_MS).toISOString(),
77830
77844
  ...binding.confirmedVia !== undefined ? { confirmedVia: binding.confirmedVia } : {},
77831
- ...binding.confirmationQuote !== undefined ? { confirmationQuote: binding.confirmationQuote } : {}
77845
+ ...binding.confirmationQuote !== undefined ? { confirmationQuote: binding.confirmationQuote } : {},
77846
+ ...binding.pressedBy !== undefined ? { pressedBy: binding.pressedBy } : {},
77847
+ ...binding.pressedAt !== undefined ? { pressedAt: binding.pressedAt } : {}
77832
77848
  };
77833
77849
  await writeFileAtomic(confirmTokenPath2(cwd, dir), `${JSON.stringify(stored, null, 2)}
77834
77850
  `);
@@ -77886,7 +77902,9 @@ async function consumeConfirmationToken(cwd, dir, stored, now) {
77886
77902
  tokenRef: stored.tokenRef,
77887
77903
  mintedAt: stored.mintedAt,
77888
77904
  consumedAt,
77889
- boundTo: { kind: stored.kind, acChecksum: stored.acChecksum, target: stored.target }
77905
+ boundTo: { kind: stored.kind, acChecksum: stored.acChecksum, target: stored.target },
77906
+ ...stored.pressedBy !== undefined ? { pressedBy: stored.pressedBy } : {},
77907
+ ...stored.pressedAt !== undefined ? { pressedAt: stored.pressedAt } : {}
77890
77908
  };
77891
77909
  }
77892
77910
  function describeConfirmationFailure(reason, flowId) {
@@ -80494,7 +80512,12 @@ function createFlowService(deps) {
80494
80512
  flowId: flow.id,
80495
80513
  acChecksum: flow.acChecksum,
80496
80514
  target: completionTarget({ merged: merged === true, prUrl: flow.pr.url }),
80497
- ...confirmation !== undefined ? { confirmedVia: confirmation.confirmedVia, confirmationQuote: confirmation.quote } : {}
80515
+ ...confirmation !== undefined ? {
80516
+ confirmedVia: confirmation.confirmedVia,
80517
+ confirmationQuote: confirmation.quote,
80518
+ ...confirmation.pressedBy !== undefined ? { pressedBy: confirmation.pressedBy } : {},
80519
+ ...confirmation.pressedAt !== undefined ? { pressedAt: confirmation.pressedAt } : {}
80520
+ } : {}
80498
80521
  }, { now: deps.now });
80499
80522
  flow = await save(cwd, dir, flow, "confirmation-minted", `token ${stored.tokenRef}, expires ${stored.expiresAt}`);
80500
80523
  return {
@@ -82187,6 +82210,7 @@ var init_unattended = __esm(() => {
82187
82210
  "review_comments_reply",
82188
82211
  "review_confirm_plan",
82189
82212
  "review_decide_premise",
82213
+ "review_complete",
82190
82214
  "flow_confirm",
82191
82215
  "external_apply",
82192
82216
  "learn_accept",
@@ -84680,6 +84704,10 @@ var init_schedule_verify = __esm(() => {
84680
84704
  function cardSafe(text) {
84681
84705
  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
84706
  }
84707
+ function hiddenInCard(text) {
84708
+ return cardSafe(text) !== text || /[\p{Cf}\p{Zl}\p{Zp}]/u.test(text);
84709
+ }
84710
+ 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
84711
 
84684
84712
  // src/trigger/schedules.ts
84685
84713
  import { execFile as execFile4 } from "child_process";
@@ -85180,6 +85208,9 @@ function parseEvidenceLevel(report) {
85180
85208
  }
85181
85209
  return { ok: false, reason: "the evidence level must start with `static only` or `ran`" };
85182
85210
  }
85211
+ function evidenceLevelLine(report) {
85212
+ return withoutFences(report).find((line) => LINE.test(line))?.trim();
85213
+ }
85183
85214
  var REPORT_FIELD_EVIDENCE_LEVEL = "evidence-level", LINE, SEPARATORS, PLACEHOLDER;
85184
85215
  var init_evidence_level = __esm(() => {
85185
85216
  LINE = /^\s*(?:[-*+]\s+)?(?:\*\*|__)?evidence level\s*:?\s*(?:\*\*|__)?\s*:?\s*(.*)$/i;
@@ -87995,7 +88026,7 @@ async function waiveReadiness(packageDir, selectors, operatorQuote, options = {}
87995
88026
  const manifest = JSON.parse(await readFile109(manifestPath, "utf8"));
87996
88027
  const time = (options.now ?? new Date).toISOString();
87997
88028
  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 } }));
88029
+ const added = ids.map((item) => ({ item, operator_quote: quote, time, ...options.confirmedVia === undefined ? {} : { confirmedVia: options.confirmedVia }, ...cardPressFields(options) }));
87999
88030
  const all = [...kept, ...added].sort((a, b) => READINESS_ITEMS.findIndex((i) => i.id === a.item) - READINESS_ITEMS.findIndex((i) => i.id === b.item));
88000
88031
  await writeFileAtomic(manifestPath, `${JSON.stringify({ ...manifest, readiness_waivers: all }, null, 2)}
88001
88032
  `);
@@ -89927,6 +89958,18 @@ async function recordDispositions(packageDir, records) {
89927
89958
  async function recordDispositionsLocked(packageDir, records) {
89928
89959
  const findingsPath = path211.join(packageDir, "findings.json");
89929
89960
  const findings = JSON.parse(await readFile110(findingsPath, "utf8"));
89961
+ const pending = await resolveDispositions(findings, records);
89962
+ for (const { finding, disposition } of pending) {
89963
+ finding.disposition = disposition;
89964
+ }
89965
+ await writeFileAtomic(findingsPath, `${JSON.stringify(findings, null, 2)}
89966
+ `);
89967
+ }
89968
+ async function checkDispositions(packageDir, records) {
89969
+ const findings = JSON.parse(await readFile110(path211.join(packageDir, "findings.json"), "utf8"));
89970
+ await resolveDispositions(findings, records);
89971
+ }
89972
+ async function resolveDispositions(findings, records) {
89930
89973
  const schema = await loadSchema("review-finding");
89931
89974
  const dispositionSchema = schema.properties?.["disposition"];
89932
89975
  if (!dispositionSchema) {
@@ -89958,11 +90001,7 @@ async function recordDispositionsLocked(packageDir, records) {
89958
90001
  }
89959
90002
  pending.push({ finding, disposition });
89960
90003
  }
89961
- for (const { finding, disposition } of pending) {
89962
- finding.disposition = disposition;
89963
- }
89964
- await writeFileAtomic(findingsPath, `${JSON.stringify(findings, null, 2)}
89965
- `);
90004
+ return pending;
89966
90005
  }
89967
90006
  function embeddedFence(tag) {
89968
90007
  return new RegExp("^ {0,3}(`{3,}|~{3,})[^\\n]*\\bkeryx:" + tag + "\\b[^\\n]*$", "gm");
@@ -91073,22 +91112,25 @@ function sessionStartedAt(history) {
91073
91112
  }
91074
91113
  return Date.now();
91075
91114
  }
91076
- function nextStep(open) {
91115
+ function closeStep(card) {
91116
+ return card ? "call the `review_complete` card (the operator answers it; you never run `keryx review complete` yourself)" : "run `keryx review complete`";
91117
+ }
91118
+ function nextStep(open, card) {
91077
91119
  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`.";
91120
+ 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
91121
  }
91080
91122
  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`.";
91123
+ 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
91124
  }
91083
- return "Next: write or ingest the missing artifacts (`keryx review ingest`), then `keryx review complete`.";
91125
+ return `Next: write or ingest the missing artifacts (\`keryx review ingest\`), then ${closeStep(card)}.`;
91084
91126
  }
91085
91127
  function nudgeLine(blocker) {
91086
91128
  const flat = blocker.replace(/[\u0000-\u001f\u007f-\u009f\u2028\u2029]+/g, " ").trim();
91087
91129
  return flat.length > MAX_NUDGE_BLOCKER_CHARS ? `${flat.slice(0, MAX_NUDGE_BLOCKER_CHARS)}\u2026` : flat;
91088
91130
  }
91089
- function describeBlockers(open) {
91131
+ function describeBlockers(open, card = true) {
91090
91132
  if (open.blockers.length === 0)
91091
- return "- none listed; run `keryx review complete` to close the package";
91133
+ return `- none listed; ${card ? "call the `review_complete` card" : "run `keryx review complete`"} to close the package`;
91092
91134
  const shown = open.blockers.slice(0, MAX_NUDGE_BLOCKERS).map((b) => `- ${nudgeLine(b)}`);
91093
91135
  const more = open.blockers.length - shown.length;
91094
91136
  return [...shown, ...more > 0 ? [`- \u2026 and ${more} more (\`keryx review complete\` lists them all)`] : []].join(`
@@ -91098,22 +91140,23 @@ function decideReviewGate(input) {
91098
91140
  const { open } = input;
91099
91141
  if (open === null || !input.runSeen)
91100
91142
  return { action: "accept" };
91143
+ const card = input.cardAvailable !== false;
91101
91144
  const stalls = input.lastSignature === open.signature ? input.stalls + 1 : 0;
91102
91145
  if (input.continues >= MAX_REVIEW_GATE_CONTINUES || stalls >= MAX_REVIEW_GATE_STALLS) {
91103
91146
  return {
91104
91147
  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)}`
91148
+ 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:
91149
+ ${describeBlockers(open, card)}`
91107
91150
  };
91108
91151
  }
91109
91152
  return {
91110
91153
  action: "continue",
91111
91154
  stalls,
91112
91155
  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."
91156
+ 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:
91157
+ ${describeBlockers(open, card)}
91158
+ ` + `${nextStep(open, card)}
91159
+ ` + "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
91160
  };
91118
91161
  }
91119
91162
  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 +105908,8 @@ ${line}`;
105865
105908
  runSeen,
105866
105909
  continues: reviewGateContinues,
105867
105910
  stalls: reviewGateStalls,
105868
- lastSignature: lastReviewGateSignature
105911
+ lastSignature: lastReviewGateSignature,
105912
+ cardAvailable: deps.tools.some((tool) => tool.definition.name === "review_complete")
105869
105913
  });
105870
105914
  if (gate.action === "continue") {
105871
105915
  reviewGateContinues += 1;
@@ -107270,6 +107314,8 @@ async function executeCallUnbounded(call, toolByName, requestApproval, permissio
107270
107314
  const hookInteractive = hooks?.runtime.interactive ?? true;
107271
107315
  let confirmationToken;
107272
107316
  let confirmationVia;
107317
+ let confirmationPressedBy;
107318
+ let confirmationPressedAt;
107273
107319
  if (tool.confirmation !== undefined && risk !== "write") {
107274
107320
  return { output: `tool "${call.name}" needs operator confirmation but is not a write tool; refused`, isError: true };
107275
107321
  }
@@ -107458,8 +107504,12 @@ async function executeCallUnbounded(call, toolByName, requestApproval, permissio
107458
107504
  isError: true
107459
107505
  };
107460
107506
  }
107461
- if (card !== undefined)
107462
- confirmationVia = typeof response === "object" && response.via === "remote" ? "remote" : "local";
107507
+ if (card !== undefined) {
107508
+ const remote = typeof response === "object" && response.via === "remote" ? response : undefined;
107509
+ confirmationVia = remote !== undefined ? "remote" : "local";
107510
+ confirmationPressedBy = remote?.pressedBy;
107511
+ confirmationPressedAt = remote?.pressedAt;
107512
+ }
107463
107513
  if (hookResult?.decisions.some((d) => d.hookId === IMPACT_EVIDENCE_HOOK_ID && d.decision === "ask")) {
107464
107514
  hooks?.runtime.acknowledgeImpactEvidence?.(extractFilePathsFromToolInput(input));
107465
107515
  }
@@ -107501,6 +107551,8 @@ async function executeCallUnbounded(call, toolByName, requestApproval, permissio
107501
107551
  ...signal !== undefined ? { signal } : {},
107502
107552
  ...confirmationToken !== undefined ? { confirmationToken } : {},
107503
107553
  ...confirmationToken !== undefined && confirmationVia !== undefined ? { confirmationVia } : {},
107554
+ ...confirmationToken !== undefined && confirmationVia === "remote" && confirmationPressedBy !== undefined ? { confirmationPressedBy } : {},
107555
+ ...confirmationToken !== undefined && confirmationVia === "remote" && confirmationPressedAt !== undefined ? { confirmationPressedAt } : {},
107504
107556
  ...untrustedContentSeen ? { untrustedContentSeen: true } : {}
107505
107557
  });
107506
107558
  if (review !== undefined && call.name === "shell_exec" && typeof input.command === "string") {
@@ -115600,7 +115652,7 @@ async function landWriteRun(input, deps = {}) {
115600
115652
  decision: "landed",
115601
115653
  branch,
115602
115654
  commit,
115603
- ...input.confirmation !== undefined ? { confirmedVia: input.confirmation.confirmedVia, quote: input.confirmation.quote } : {}
115655
+ ...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
115656
  }));
115605
115657
  if (!won) {
115606
115658
  await git(["branch", "-D", branch], repo).catch(() => {
@@ -122586,7 +122638,7 @@ class RemoteClient {
122586
122638
  }
122587
122639
  async askApproval(prompt, timeoutMs, options = {}) {
122588
122640
  if (!this.connected || options.signal?.aborted === true) {
122589
- return { decision: "deny" };
122641
+ return { decision: "deny", undelivered: true };
122590
122642
  }
122591
122643
  const body = {
122592
122644
  sessionId: this.options.sessionId,
@@ -122598,15 +122650,15 @@ class RemoteClient {
122598
122650
  try {
122599
122651
  const response = await this.post("approval", body);
122600
122652
  if (!response.ok) {
122601
- return { decision: "deny" };
122653
+ return { decision: "deny", undelivered: true };
122602
122654
  }
122603
122655
  const id = (await response.json())?.approvalId;
122604
122656
  if (typeof id !== "string" || id.length === 0) {
122605
- return { decision: "deny" };
122657
+ return { decision: "deny", undelivered: true };
122606
122658
  }
122607
122659
  approvalId = id;
122608
122660
  } catch {
122609
- return { decision: "deny" };
122661
+ return { decision: "deny", undelivered: true };
122610
122662
  }
122611
122663
  const early = this.earlyDecisions.get(approvalId);
122612
122664
  if (early !== undefined) {
@@ -124734,13 +124786,13 @@ function sha25610(value) {
124734
124786
  function contentDigest(value) {
124735
124787
  return sha25610(JSON.stringify(value ?? null));
124736
124788
  }
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
124789
  function record4(confirmation) {
124743
- return { confirmedVia: confirmation.confirmedVia, quote: confirmation.quote };
124790
+ return {
124791
+ confirmedVia: confirmation.confirmedVia,
124792
+ quote: confirmation.quote,
124793
+ ...confirmation.pressedBy === undefined ? {} : { pressedBy: confirmation.pressedBy },
124794
+ ...confirmation.pressedAt === undefined ? {} : { pressedAt: confirmation.pressedAt }
124795
+ };
124744
124796
  }
124745
124797
  async function captureConsole(fn) {
124746
124798
  const lines = [];
@@ -124904,8 +124956,8 @@ function externalApplySpec(seams) {
124904
124956
  `branch: ${branch} (new, local; cut from base commit ${cardSafe(run.baseCommit)})`,
124905
124957
  `patch hash (sha256): ${hash}`,
124906
124958
  `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)}`))] : [],
124959
+ ...files,
124960
+ ...run.flaggedPaths.length > 0 ? [`FLAGGED PATHS (${run.flaggedPaths.length}) - repo plumbing, CI, hooks or agent config:`, ...run.flaggedPaths.map((p) => ` ! ${cardSafe(p)}`)] : [],
124909
124961
  "effect: your current branch, HEAD, index and working tree are never touched"
124910
124962
  ],
124911
124963
  subject: `${branch} @ sha256:${hash}`,
@@ -125270,7 +125322,7 @@ function otherConfirmSpecs(seams = {}) {
125270
125322
  scheduleSpec("remove", seams)
125271
125323
  ];
125272
125324
  }
125273
- var MAX_LISTED = 12, SCOPES2, SCHEDULE_DESCRIPTIONS;
125325
+ var SCOPES2, SCHEDULE_DESCRIPTIONS;
125274
125326
  var init_operator_confirm_other = __esm(() => {
125275
125327
  init_service11();
125276
125328
  init_record2();
@@ -125287,9 +125339,101 @@ var init_operator_confirm_other = __esm(() => {
125287
125339
  };
125288
125340
  });
125289
125341
 
125342
+ // src/review/routing-check.ts
125343
+ import { readFileSync as readFileSync33 } from "fs";
125344
+ import path286 from "path";
125345
+ function narrowedSlices(manifest, readText, fix) {
125346
+ const originals = manifest.slices.filter((entry) => entry.retryOf === undefined && Array.isArray(entry.roles));
125347
+ const changedFiles = [...new Set(manifest.slices.filter((entry) => entry.retryOf === undefined).flatMap((entry) => entry.files))];
125348
+ const narrowed = [];
125349
+ const unreadable = [];
125350
+ for (const entry of originals) {
125351
+ const text = readText(entry);
125352
+ if (text === undefined) {
125353
+ unreadable.push(entry.id);
125354
+ continue;
125355
+ }
125356
+ const needed = routeSliceRegions(entry.id, parseScopedDiff(text), { changedFiles });
125357
+ const backedFix = fixClaimBacked(entry, fix, text);
125358
+ const backedSecondLook = secondLookClaimBacked(entry, fix, text);
125359
+ const roles = backedSecondLook ? [...SECOND_LOOK_ROLES] : backedFix ? expectedFixRoles(entry, fix, text) : needed.roles;
125360
+ const recorded = new Set(entry.roles ?? []);
125361
+ const missingRoles = roles.filter((role) => !recorded.has(role)).sort();
125362
+ const shallow = (backedFix || backedSecondLook || needed.depth === "deep") && entry.depth !== "deep";
125363
+ if (missingRoles.length > 0 || shallow) {
125364
+ narrowed.push({ id: entry.id, missingRoles, ...shallow ? { depth: { recorded: entry.depth ?? "unset", needed: "deep" } } : {} });
125365
+ }
125366
+ }
125367
+ return { narrowed, unreadable };
125368
+ }
125369
+ function sliceTextReader(manifestDir) {
125370
+ return (entry) => {
125371
+ try {
125372
+ return readFileSync33(path286.resolve(manifestDir, entry.path), "utf8");
125373
+ } catch {
125374
+ return;
125375
+ }
125376
+ };
125377
+ }
125378
+ function packageRoutingRefusal(packageDir, projectRoot, options = {}) {
125379
+ const shared = path286.join(projectRoot, ".metaproject", "data", "review", "slices");
125380
+ const own = path286.join(packageDir, "slices");
125381
+ for (const [file, dirs] of [[path286.join(own, "manifest.json"), [own]], [path286.join(packageDir, "slice-manifest.json"), [own, shared]]]) {
125382
+ let manifest;
125383
+ try {
125384
+ const parsed = JSON.parse(readFileSync33(file, "utf8"));
125385
+ manifest = Array.isArray(parsed.slices) ? { slices: parsed.slices } : undefined;
125386
+ } catch {
125387
+ manifest = undefined;
125388
+ }
125389
+ if (manifest === undefined)
125390
+ continue;
125391
+ const readers = dirs.map((dir) => sliceTextReader(dir));
125392
+ const { narrowed, unreadable } = narrowedSlices(manifest, (entry) => readers.map((read) => read(entry)).find((text) => text !== undefined), fixContextOf(packageDir));
125393
+ const label = path286.relative(projectRoot, file) || file;
125394
+ const refusal = routingRefusal(label, narrowed);
125395
+ if (refusal !== undefined || options.refuseUnreadable !== true || unreadable.length === 0)
125396
+ return refusal;
125397
+ 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";
125398
+ }
125399
+ return;
125400
+ }
125401
+ function routingRefusal(manifestLabel, narrowed) {
125402
+ if (narrowed.length === 0)
125403
+ return;
125404
+ const lines = narrowed.slice(0, 8).map((entry) => `${entry.id}: ${[
125405
+ entry.missingRoles.length > 0 ? `roles ${entry.missingRoles.join(", ")} are missing` : undefined,
125406
+ entry.depth !== undefined ? `recorded ${entry.depth.recorded}, its content needs deep` : undefined
125407
+ ].filter(Boolean).join("; ")}`);
125408
+ 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.";
125409
+ }
125410
+ var init_routing_check = __esm(() => {
125411
+ init_slice();
125412
+ init_slice_routing();
125413
+ init_round_split();
125414
+ });
125415
+
125416
+ // src/review/complete-gated.ts
125417
+ async function completeReviewGated(cwd, ref, options) {
125418
+ const packageDir = await resolveReviewPackagePath(cwd, ref);
125419
+ const profileMissing = runProfileRefusal(packageDir);
125420
+ if (profileMissing !== undefined)
125421
+ throw new Error(`review complete refused: ${profileMissing}`);
125422
+ const narrowedRouting = packageRoutingRefusal(packageDir, resolveProjectRoot2(cwd), { refuseUnreadable: true });
125423
+ if (narrowedRouting !== undefined)
125424
+ throw new Error(`review complete refused: ${narrowedRouting}`);
125425
+ return completeManagedReview(cwd, ref, options);
125426
+ }
125427
+ var init_complete_gated = __esm(() => {
125428
+ init_contained_path();
125429
+ init_managed();
125430
+ init_run_profile();
125431
+ init_routing_check();
125432
+ });
125433
+
125290
125434
  // src/review/publish.ts
125291
125435
  import { readFile as readFile131 } from "fs/promises";
125292
- import path286 from "path";
125436
+ import path287 from "path";
125293
125437
  function reviewEvent(value) {
125294
125438
  const wanted = (value ?? "COMMENT").trim().toUpperCase();
125295
125439
  const found = REVIEW_EVENTS.find((event) => event === wanted);
@@ -125385,7 +125529,7 @@ function buildRoundBlock(input) {
125385
125529
  return { ...base, findings: kept, ...kept.length < listed.length ? { truncated: listed.length - kept.length } : {} };
125386
125530
  }
125387
125531
  async function publishReview(packageDir, options) {
125388
- const manifest = await readJsonFileOr(path286.join(packageDir, "manifest.json"), undefined);
125532
+ const manifest = await readJsonFileOr(path287.join(packageDir, "manifest.json"), undefined);
125389
125533
  if (manifest === undefined)
125390
125534
  throw new Error(`Managed review package has no readable manifest.json: ${packageDir}`);
125391
125535
  if (!isPrBound(manifest.target)) {
@@ -125400,7 +125544,7 @@ async function publishReview(packageDir, options) {
125400
125544
  if (partial && event === "APPROVE") {
125401
125545
  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
125546
  }
125403
- const report = (await readFile131(path286.join(packageDir, "report.md"), "utf8").catch(() => "")).trim();
125547
+ const report = (await readFile131(path287.join(packageDir, "report.md"), "utf8").catch(() => "")).trim();
125404
125548
  if (report === "")
125405
125549
  throw new Error(`Review ${manifest.reviewId} has no report.md body to publish.`);
125406
125550
  const head = `${partial ? `${partialPublishHeader(readiness).join(`
@@ -125411,7 +125555,7 @@ async function publishReview(packageDir, options) {
125411
125555
  const minors = minorSection((await computeVerdict(packageDir)).minors, report, PUBLISHED_BODY_LIMIT - head.length - foot.length - ROUND_BLOCK_LIMIT);
125412
125556
  const visible = `${head}${minors}${foot}`;
125413
125557
  const reviewedHead = manifest.target.head ?? null;
125414
- const findings = await readJsonFileOr(path286.join(packageDir, "findings.json"), []);
125558
+ const findings = await readJsonFileOr(path287.join(packageDir, "findings.json"), []);
125415
125559
  const roundBlock = buildRoundBlock({ body: visible, round: readRoundNumber(packageDir) ?? 1, reviewId: manifest.reviewId, reviewedHead, findings: Array.isArray(findings) ? findings : [] });
125416
125560
  const body = `${visible}
125417
125561
  ${renderRoundBlock(roundBlock)}
@@ -125421,7 +125565,7 @@ ${renderRoundBlock(roundBlock)}
125421
125565
  const result = await options.post(manifest.target.ref, event, body);
125422
125566
  if (!result.ok)
125423
125567
  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)}
125568
+ 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
125569
  `);
125426
125570
  const registered = recordFinishedRound(packageDir, "publish");
125427
125571
  return { readiness, body, event, partial, posted: true, ...registered.error === undefined ? {} : { registryWarning: `round not recorded in the local round registry: ${registered.error}` } };
@@ -125437,6 +125581,7 @@ var init_publish = __esm(() => {
125437
125581
  init_round_registry();
125438
125582
  init_verdict();
125439
125583
  init_readiness();
125584
+ init_types4();
125440
125585
  REVIEW_EVENTS = ["COMMENT", "REQUEST_CHANGES", "APPROVE"];
125441
125586
  });
125442
125587
 
@@ -125444,16 +125589,16 @@ var init_publish = __esm(() => {
125444
125589
  import { createHash as createHash81 } from "crypto";
125445
125590
  import { existsSync as existsSync56, realpathSync as realpathSync19 } from "fs";
125446
125591
  import { readFile as readFile132 } from "fs/promises";
125447
- import path287 from "path";
125592
+ import path288 from "path";
125448
125593
  async function findHeldFinding(packageDir, ref) {
125449
125594
  const wanted = ref.trim();
125450
125595
  if (wanted === "")
125451
125596
  return { ok: false, refusal: "a finding id is required" };
125452
- const findings = await readJsonFileOr(path287.join(packageDir, FINDINGS_FILE), undefined);
125597
+ const findings = await readJsonFileOr(path288.join(packageDir, FINDINGS_FILE), undefined);
125453
125598
  const records = Array.isArray(findings) ? findings.map(obj).filter((entry) => entry !== undefined) : [];
125454
125599
  const named = records.filter((record) => str3(record["global_id"]) === wanted || str3(record["id"]) === wanted);
125455
125600
  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.` };
125601
+ return { ok: false, refusal: `No finding "${wanted}" in ${path288.basename(packageDir)}/${FINDINGS_FILE}. Name a finding by its global_id or id.` };
125457
125602
  const keys = new Set(named.map((record) => str3(record["global_id"]) ?? str3(record["id"]) ?? wanted));
125458
125603
  if (keys.size > 1)
125459
125604
  return { ok: false, refusal: `"${wanted}" names ${keys.size} findings (${[...keys].join(", ")}); name one by its global_id.` };
@@ -125482,13 +125627,13 @@ async function carryPremiseDecisions(packageDir, previousDir, carriedIds, carrie
125482
125627
  if (carriedIds.length === 0 || !isReviewPackageDir(previousDir, options.projectRoot))
125483
125628
  return [];
125484
125629
  const realPrevious = realpathSync19(previousDir);
125485
- const realPackage = existsSync56(packageDir) ? realpathSync19(packageDir) : path287.resolve(packageDir);
125486
- if (realPrevious === realPackage || realPackage.startsWith(`${realPrevious}${path287.sep}`))
125630
+ const realPackage = existsSync56(packageDir) ? realpathSync19(packageDir) : path288.resolve(packageDir);
125631
+ if (realPrevious === realPackage || realPackage.startsWith(`${realPrevious}${path288.sep}`))
125487
125632
  return [];
125488
125633
  const previous = await readPremiseDecisions(realPrevious);
125489
125634
  if (previous.length === 0)
125490
125635
  return [];
125491
- const findings = await readJsonFileOr(path287.join(realPrevious, FINDINGS_FILE), undefined);
125636
+ const findings = await readJsonFileOr(path288.join(realPrevious, FINDINGS_FILE), undefined);
125492
125637
  const records = Array.isArray(findings) ? findings.map(obj).filter((entry) => entry !== undefined) : [];
125493
125638
  const have = await readPremiseDecisions(packageDir);
125494
125639
  const carried = [];
@@ -125510,13 +125655,13 @@ async function carryPremiseDecisions(packageDir, previousDir, carriedIds, carrie
125510
125655
  return carried;
125511
125656
  }
125512
125657
  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));
125658
+ const { decision, applied: vouched } = await recordPremiseDecisionAfter(packageDir, { finding: input.finding.key, answer: input.answer, reason: input.reason, quote: input.quote, at: input.at, confirmedVia: input.confirmedVia, ...cardPressFields(input), subject: input.finding.subject }, (kept) => applyAnswerToFindings(packageDir, kept, registry));
125514
125659
  if (vouched === true)
125515
125660
  reRecordRegistry(packageDir, registry);
125516
125661
  return decision;
125517
125662
  }
125518
125663
  async function applyAnswerToFindings(packageDir, kept, registry) {
125519
- const file = path287.join(packageDir, FINDINGS_FILE);
125664
+ const file = path288.join(packageDir, FINDINGS_FILE);
125520
125665
  const before = await readFile132(file, "utf8").catch(() => {
125521
125666
  return;
125522
125667
  });
@@ -125561,14 +125706,15 @@ var init_premise_question = __esm(() => {
125561
125706
  init_json();
125562
125707
  init_operator_decisions();
125563
125708
  init_package_location();
125709
+ init_types4();
125564
125710
  init_premise();
125565
125711
  init_round_registry();
125566
125712
  });
125567
125713
 
125568
125714
  // src/review/estimate.ts
125569
125715
  import { createHash as createHash82 } from "crypto";
125570
- import { existsSync as existsSync57, readFileSync as readFileSync33 } from "fs";
125571
- import path288 from "path";
125716
+ import { existsSync as existsSync57, readFileSync as readFileSync34 } from "fs";
125717
+ import path289 from "path";
125572
125718
  function planKeyOf(estimate) {
125573
125719
  const roles = [...estimate.perRole].sort((a, b) => a.role.localeCompare(b.role)).map((entry) => ({ role: entry.role, waves: entry.waves }));
125574
125720
  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 +125794,7 @@ function estimateRound(roles, manifest, depth = "unrecorded", thresholds = { max
125648
125794
  }
125649
125795
  async function readPlanThresholds(projectRoot) {
125650
125796
  const defaults = { maxDispatches: DEFAULT_MAX_DISPATCHES, maxMinutes: DEFAULT_MAX_MINUTES, source: "default" };
125651
- const file = path288.join(projectRoot, "routing.config.json");
125797
+ const file = path289.join(projectRoot, "routing.config.json");
125652
125798
  if (!await pathExists(file))
125653
125799
  return defaults;
125654
125800
  const read = await readJsonObjectFile(file);
@@ -125672,7 +125818,7 @@ function isPlanConfirmation(value) {
125672
125818
  }
125673
125819
  function readPlanConfirmation(packageDir) {
125674
125820
  try {
125675
- const parsed = JSON.parse(readFileSync33(path288.join(packageDir, PLAN_CONFIRMATION_FILE), "utf8"));
125821
+ const parsed = JSON.parse(readFileSync34(path289.join(packageDir, PLAN_CONFIRMATION_FILE), "utf8"));
125676
125822
  return isPlanConfirmation(parsed) ? parsed : undefined;
125677
125823
  } catch {
125678
125824
  return;
@@ -125687,7 +125833,7 @@ function planConfirmationFor(estimate, quote, confirmedVia, now = new Date) {
125687
125833
  return { planKey: planKeyOf(estimate), dispatches: estimate.dispatches, minutes: [estimate.minutes.min, estimate.minutes.max], quote: quote.trim(), confirmedVia, at: now.toISOString() };
125688
125834
  }
125689
125835
  async function writePlanConfirmation(packageDir, confirmation) {
125690
- const file = path288.join(packageDir, PLAN_CONFIRMATION_FILE);
125836
+ const file = path289.join(packageDir, PLAN_CONFIRMATION_FILE);
125691
125837
  await writeFileAtomic(file, `${JSON.stringify(confirmation, null, 2)}
125692
125838
  `);
125693
125839
  return file;
@@ -125712,9 +125858,9 @@ async function packagePlanGate(packageDir, options = {}) {
125712
125858
  }
125713
125859
  function manifestCandidates(packageDir, root) {
125714
125860
  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")
125861
+ path289.join(packageDir, "slices", "manifest.json"),
125862
+ path289.join(packageDir, "slice-manifest.json"),
125863
+ path289.join(root, ".metaproject", "data", "review", "slices", "manifest.json")
125718
125864
  ];
125719
125865
  }
125720
125866
  async function readManifest3(candidates) {
@@ -125737,15 +125883,15 @@ async function estimatePackage(packageDir, options = {}) {
125737
125883
  depth: recorded?.depth
125738
125884
  });
125739
125885
  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") };
125886
+ const found = options.manifestValue === undefined ? read : { manifest: options.manifestValue, file: read?.file ?? options.manifest ?? path289.join(packageDir, "slices", "manifest.json") };
125741
125887
  if (options.manifest !== undefined && found === undefined)
125742
125888
  throw new Error(`${options.manifest} is not a slice manifest (no readable JSON with a slices array); run \`keryx review slice\` first`);
125743
125889
  const problem = found === undefined ? undefined : manifestSizeProblem(found.manifest);
125744
125890
  if (problem !== undefined)
125745
- throw new Error(`${path288.relative(root, found.file)}: ${problem}`);
125891
+ throw new Error(`${path289.relative(root, found.file)}: ${problem}`);
125746
125892
  const thresholds = await readPlanThresholds(root);
125747
125893
  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) };
125894
+ return { ...estimateRound(roles, found?.manifest, recorded?.depth ?? "unrecorded", thresholds), manifest: found === undefined ? undefined : path289.relative(root, found.file) };
125749
125895
  }
125750
125896
  function renderEstimate(estimate) {
125751
125897
  const lines = [
@@ -125781,7 +125927,7 @@ var init_estimate = __esm(() => {
125781
125927
  });
125782
125928
 
125783
125929
  // src/review/plan-confirmation.ts
125784
- import path289 from "path";
125930
+ import path290 from "path";
125785
125931
  function planOverThreshold(estimate) {
125786
125932
  return estimate.overThreshold;
125787
125933
  }
@@ -125800,21 +125946,23 @@ async function recordPlanConfirmation(packageDir, estimate, confirmation) {
125800
125946
  minutes: [estimate.minutes.min, estimate.minutes.max],
125801
125947
  quote: confirmation.quote.trim(),
125802
125948
  confirmedVia: confirmation.confirmedVia,
125949
+ ...cardPressFields(confirmation),
125803
125950
  at: confirmation.at
125804
125951
  };
125805
- await writeFileAtomic(path289.join(packageDir, PLAN_CONFIRMATION_FILE), `${JSON.stringify(record, null, 2)}
125952
+ await writeFileAtomic(path290.join(packageDir, PLAN_CONFIRMATION_FILE), `${JSON.stringify(record, null, 2)}
125806
125953
  `);
125807
125954
  return record;
125808
125955
  }
125809
125956
  var init_plan_confirmation = __esm(() => {
125810
125957
  init_fs();
125811
125958
  init_estimate();
125959
+ init_types4();
125812
125960
  });
125813
125961
 
125814
125962
  // src/review/severity-audit.ts
125815
125963
  import { existsSync as existsSync58 } from "fs";
125816
125964
  import { readFile as readFile133, readdir as readdir48, writeFile as writeFile43 } from "fs/promises";
125817
- import path290 from "path";
125965
+ import path291 from "path";
125818
125966
  function cited(finding) {
125819
125967
  const evidence = typeof finding.evidence === "string" ? finding.evidence : "";
125820
125968
  return [...new Set([...evidence.matchAll(CITATION2)].map((m) => m[1]))];
@@ -125892,14 +126040,14 @@ function auditSeverity(findings, records, options = {}) {
125892
126040
  return { findings: audited, changes };
125893
126041
  }
125894
126042
  async function loadMutationRecords(packageDir) {
125895
- const dir = path290.join(packageDir, EVIDENCE_DIR);
126043
+ const dir = path291.join(packageDir, EVIDENCE_DIR);
125896
126044
  if (!existsSync58(dir))
125897
126045
  return [];
125898
126046
  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
126047
  const out = [];
125900
126048
  for (const name of names) {
125901
126049
  try {
125902
- out.push({ name, record: JSON.parse(await readFile133(path290.join(dir, name), "utf8")) });
126050
+ out.push({ name, record: JSON.parse(await readFile133(path291.join(dir, name), "utf8")) });
125903
126051
  } catch {
125904
126052
  continue;
125905
126053
  }
@@ -125907,7 +126055,7 @@ async function loadMutationRecords(packageDir) {
125907
126055
  return out;
125908
126056
  }
125909
126057
  async function auditPackage(packageDir, options = {}) {
125910
- const file = path290.join(packageDir, "findings.json");
126058
+ const file = path291.join(packageDir, "findings.json");
125911
126059
  const raw = JSON.parse(await readFile133(file, "utf8"));
125912
126060
  if (!Array.isArray(raw))
125913
126061
  throw new Error(`${file} must hold an array of findings`);
@@ -125928,7 +126076,7 @@ var init_severity_audit = __esm(() => {
125928
126076
 
125929
126077
  // src/review/context-collect.ts
125930
126078
  import { mkdir as mkdir70, readFile as readFile134, writeFile as writeFile44 } from "fs/promises";
125931
- import path291 from "path";
126079
+ import path292 from "path";
125932
126080
  function extractBackendPins(texts) {
125933
126081
  const pins = [];
125934
126082
  const seen = new Set;
@@ -126031,7 +126179,7 @@ function renderPrContext(ctx) {
126031
126179
  }
126032
126180
  async function readPrContext(packageDir) {
126033
126181
  try {
126034
- const parsed = JSON.parse(await readFile134(path291.join(packageDir, PR_CONTEXT_JSON), "utf8"));
126182
+ const parsed = JSON.parse(await readFile134(path292.join(packageDir, PR_CONTEXT_JSON), "utf8"));
126035
126183
  return parsed?.schemaVersion === 1 ? parsed : undefined;
126036
126184
  } catch {
126037
126185
  return;
@@ -126053,8 +126201,8 @@ async function collectPrContext(input) {
126053
126201
  ]);
126054
126202
  const context = buildPrContext({ repo: input.repo, number: input.number, pull: pullText(pull), comments: collected.comments, checks: input.checks, previous, now: input.now });
126055
126203
  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);
126204
+ const json = path292.join(input.packageDir, PR_CONTEXT_JSON);
126205
+ const md = path292.join(input.packageDir, PR_CONTEXT_MD);
126058
126206
  await writeFile44(json, `${JSON.stringify(context, null, 2)}
126059
126207
  `, "utf8");
126060
126208
  await writeFile44(md, renderPrContext(context), "utf8");
@@ -126086,7 +126234,7 @@ var init_context_collect = __esm(() => {
126086
126234
  // src/review/compare-reference.ts
126087
126235
  import { existsSync as existsSync59 } from "fs";
126088
126236
  import { readFile as readFile135, readdir as readdir49 } from "fs/promises";
126089
- import path292 from "path";
126237
+ import path293 from "path";
126090
126238
  function locationsOf(text) {
126091
126239
  const found = [];
126092
126240
  const pattern = /`?([A-Za-z0-9_./-]+\.(?:tsx?|jsx?|css|scss|md|json|ya?ml))(?::(\d+)(?:-(\d+))?)?`?/g;
@@ -126164,17 +126312,17 @@ async function readJson11(file) {
126164
126312
  }
126165
126313
  }
126166
126314
  async function rawReplies(inputDir) {
126167
- const dir = path292.join(inputDir, "raw");
126315
+ const dir = path293.join(inputDir, "raw");
126168
126316
  if (!existsSync59(dir))
126169
126317
  return [];
126170
126318
  const out = [];
126171
126319
  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") });
126320
+ out.push({ id: `raw/${name}`, text: await readFile135(path293.join(dir, name), "utf8") });
126173
126321
  }
126174
126322
  return out;
126175
126323
  }
126176
126324
  async function loadPackageEvidence(packageDir, inputDir) {
126177
- const findingsRaw = await readJson11(path292.join(packageDir, "findings.json"));
126325
+ const findingsRaw = await readJson11(path293.join(packageDir, "findings.json"));
126178
126326
  const findings = (Array.isArray(findingsRaw) ? findingsRaw : []).map((f) => ({
126179
126327
  id: asText(f.id),
126180
126328
  severity: asText(f.severity) || undefined,
@@ -126184,7 +126332,7 @@ async function loadPackageEvidence(packageDir, inputDir) {
126184
126332
  `)
126185
126333
  }));
126186
126334
  const canonicalIds = new Set(findings.map((f) => f.id));
126187
- const research = asRecord2(await readJson11(path292.join(packageDir, "research.json")));
126335
+ const research = asRecord2(await readJson11(path293.join(packageDir, "research.json")));
126188
126336
  const dispatch = asRecord2(research?.dispatch);
126189
126337
  const rows = (value) => Array.isArray(value) ? value.filter((r) => r !== null && typeof r === "object") : [];
126190
126338
  const selected = Array.isArray(dispatch?.selected) ? dispatch.selected : [];
@@ -126202,7 +126350,7 @@ async function loadPackageEvidence(packageDir, inputDir) {
126202
126350
  }
126203
126351
  if (inputDir !== undefined)
126204
126352
  lost.push(...await rawReplies(inputDir));
126205
- const evidenceDir = path292.join(packageDir, "research-evidence");
126353
+ const evidenceDir = path293.join(packageDir, "research-evidence");
126206
126354
  const mutationPass = existsSync59(evidenceDir) && (await readdir49(evidenceDir)).some((n) => /^mutation-\d+\.(json|md)$/i.test(n));
126207
126355
  return { ran: [...ran].sort(), findings, lost, mutationPass };
126208
126356
  }
@@ -126213,14 +126361,14 @@ function score2(item, evidence) {
126213
126361
  if (haystack.includes(identifier))
126214
126362
  points += 1;
126215
126363
  for (const loc of item.locations) {
126216
- const base = path292.basename(loc.file).toLowerCase();
126364
+ const base = path293.basename(loc.file).toLowerCase();
126217
126365
  if (!haystack.includes(base))
126218
126366
  continue;
126219
126367
  if (loc.line === undefined) {
126220
126368
  points += 1;
126221
126369
  continue;
126222
126370
  }
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;
126371
+ 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
126372
  points += near ? 2 : 1;
126225
126373
  }
126226
126374
  return points;
@@ -126285,7 +126433,7 @@ var init_compare_reference = __esm(() => {
126285
126433
  // src/review/driver-read.ts
126286
126434
  import { existsSync as existsSync60 } from "fs";
126287
126435
  import { readFile as readFile136, readdir as readdir50 } from "fs/promises";
126288
- import path293 from "path";
126436
+ import path294 from "path";
126289
126437
  async function readJson12(file) {
126290
126438
  try {
126291
126439
  return JSON.parse(await readFile136(file, "utf8"));
@@ -126298,7 +126446,7 @@ function definitionCandidates(driver) {
126298
126446
  return [driver.definition, driver.skill_path, payload.definition, payload.skill_path].filter((v) => typeof v === "string" && v.trim() !== "");
126299
126447
  }
126300
126448
  async function jsonFiles(dir) {
126301
- return existsSync60(dir) ? (await readdir50(dir)).filter((n) => n.endsWith(".json")).sort().map((n) => path293.join(dir, n)) : [];
126449
+ return existsSync60(dir) ? (await readdir50(dir)).filter((n) => n.endsWith(".json")).sort().map((n) => path294.join(dir, n)) : [];
126302
126450
  }
126303
126451
  async function readDrivers(inputDir, root) {
126304
126452
  const payloadSlices = {};
@@ -126310,7 +126458,7 @@ async function readDrivers(inputDir, root) {
126310
126458
  const replyFiles = new Set;
126311
126459
  const replyReviewers = new Set;
126312
126460
  const dropped = [];
126313
- for (const file of await jsonFiles(path293.join(inputDir, "drivers"))) {
126461
+ for (const file of await jsonFiles(path294.join(inputDir, "drivers"))) {
126314
126462
  const driver = await readJson12(file);
126315
126463
  if (typeof driver?.reviewer !== "string")
126316
126464
  continue;
@@ -126319,11 +126467,11 @@ async function readDrivers(inputDir, root) {
126319
126467
  const slices = readSliceIds(driver.slices, "assigned");
126320
126468
  const complete = Array.isArray(driver.slices) && slices.unusable === 0;
126321
126469
  if (slices.unusable > 0)
126322
- dropped.push({ file: path293.basename(file), reviewer: driver.reviewer, unusable: slices.unusable });
126470
+ dropped.push({ file: path294.basename(file), reviewer: driver.reviewer, unusable: slices.unusable });
126323
126471
  if (complete)
126324
126472
  (payloadSlices[driver.reviewer] ??= []).push(...slices.ids);
126325
126473
  if (typeof driver.result === "string") {
126326
- replyFiles.add(path293.basename(driver.result));
126474
+ replyFiles.add(path294.basename(driver.result));
126327
126475
  if (complete) {
126328
126476
  const key = `${driver.reviewer}\x00${driver.result}`;
126329
126477
  const matches = assignments.get(key) ?? [];
@@ -126336,9 +126484,9 @@ async function readDrivers(inputDir, root) {
126336
126484
  if (typeof driver.model === "string" && driver.model.trim() !== "")
126337
126485
  models[driver.reviewer] = driver.model.trim();
126338
126486
  for (const candidate of definitionCandidates(driver)) {
126339
- const definition = path293.resolve(root, candidate);
126487
+ const definition = path294.resolve(root, candidate);
126340
126488
  if (existsSync60(definition)) {
126341
- ruleFiles[driver.reviewer] = path293.relative(root, definition);
126489
+ ruleFiles[driver.reviewer] = path294.relative(root, definition);
126342
126490
  break;
126343
126491
  }
126344
126492
  }
@@ -126352,7 +126500,7 @@ var init_driver_read = __esm(() => {
126352
126500
  // src/review/wave-drivers.ts
126353
126501
  import { existsSync as existsSync61 } from "fs";
126354
126502
  import { mkdir as mkdir71, readFile as readFile137, readdir as readdir51, rm as rm23 } from "fs/promises";
126355
- import path294 from "path";
126503
+ import path295 from "path";
126356
126504
  async function packageReviewers(packageDir, manifest, options = {}) {
126357
126505
  const root = projectRootOfPackage(packageDir);
126358
126506
  const names = new Set;
@@ -126376,21 +126524,21 @@ async function packageReviewers(packageDir, manifest, options = {}) {
126376
126524
  async function writeWaveDrivers(input) {
126377
126525
  const { packageDir, manifest, sliceDir } = input;
126378
126526
  const root = projectRootOfPackage(packageDir);
126379
- const dir = path294.join(packageDir, DRIVERS_DIR);
126527
+ const dir = path295.join(packageDir, DRIVERS_DIR);
126380
126528
  await mkdir71(dir, { recursive: true });
126381
126529
  const kept = new Map;
126382
126530
  for (const name of existsSync61(dir) ? await readdir51(dir) : []) {
126383
126531
  if (!/-w\d+\.json$/.test(name))
126384
126532
  continue;
126385
126533
  try {
126386
- kept.set(name, JSON.parse(await readFile137(path294.join(dir, name), "utf8")));
126534
+ kept.set(name, JSON.parse(await readFile137(path295.join(dir, name), "utf8")));
126387
126535
  } catch {}
126388
- await rm23(path294.join(dir, name), { force: true });
126536
+ await rm23(path295.join(dir, name), { force: true });
126389
126537
  }
126390
126538
  const written = [];
126391
126539
  for (const reviewer of [...input.reviewers].sort()) {
126392
126540
  const waves = roleDispatchWaves(manifest, reviewer);
126393
- const definition = DEFINITION_CANDIDATES(reviewer).find((candidate) => existsSync61(path294.resolve(root, candidate)));
126541
+ const definition = DEFINITION_CANDIDATES(reviewer).find((candidate) => existsSync61(path295.resolve(root, candidate)));
126394
126542
  for (const [at, wave] of waves.entries()) {
126395
126543
  const number = at + 1;
126396
126544
  const name = `${reviewer}-w${number}.json`;
@@ -126400,15 +126548,15 @@ async function writeWaveDrivers(input) {
126400
126548
  wave: number,
126401
126549
  waves: waves.length,
126402
126550
  slices: wave.map((entry) => entry.id),
126403
- slicePaths: wave.map((entry) => ({ id: entry.id, path: path294.resolve(sliceDir, entry.path), bytes: entry.bytes })),
126551
+ slicePaths: wave.map((entry) => ({ id: entry.id, path: path295.resolve(sliceDir, entry.path), bytes: entry.bytes })),
126404
126552
  result: `${RAW_DIR}/${reviewer}-w${number}.txt`,
126405
126553
  ...definition === undefined ? {} : { definition },
126406
126554
  ...typeof prior?.model === "string" && prior.model.trim() !== "" ? { model: prior.model } : {},
126407
126555
  ...typeof prior?.toolLog === "string" && prior.toolLog.trim() !== "" ? { toolLog: prior.toolLog } : {}
126408
126556
  };
126409
- await writeFileAtomic(path294.join(dir, name), `${JSON.stringify(driver, null, 2)}
126557
+ await writeFileAtomic(path295.join(dir, name), `${JSON.stringify(driver, null, 2)}
126410
126558
  `);
126411
- written.push(path294.join(dir, name));
126559
+ written.push(path295.join(dir, name));
126412
126560
  }
126413
126561
  }
126414
126562
  return written;
@@ -126428,7 +126576,7 @@ var init_wave_drivers = __esm(() => {
126428
126576
  // src/commands/review-plan.ts
126429
126577
  import { existsSync as existsSync62 } from "fs";
126430
126578
  import { readFile as readFile138 } from "fs/promises";
126431
- import path295 from "path";
126579
+ import path296 from "path";
126432
126580
  function printPlanHelp() {
126433
126581
  console.log(`keryx review plan --target <pr-url|branch> [--ref <base>] [--head <sha>] [--host ${REVIEW_HOSTS.join("|")}]
126434
126582
  [--memory-query "<query>"] [--unattended] [--json]
@@ -126461,7 +126609,7 @@ function packagePathOf(startOutput, cwd) {
126461
126609
  if (line === undefined)
126462
126610
  return;
126463
126611
  const value = line.slice("path: ".length).trim();
126464
- return value === "" ? undefined : path295.resolve(cwd, value);
126612
+ return value === "" ? undefined : path296.resolve(cwd, value);
126465
126613
  }
126466
126614
  function slicesByKind(manifest) {
126467
126615
  const originals = manifest.slices.filter((entry) => entry.retryOf === undefined);
@@ -126485,7 +126633,7 @@ function renderPlanSummary(plan) {
126485
126633
  `estimate: ${plan.dispatches} dispatches, ${plan.minutes.min}-${plan.minutes.max} min${plan.overThreshold ? ` \u2014 over threshold (${plan.overReasons.join("; ")})` : ""}`,
126486
126634
  ...plan.warnings.map((warning) => `warning: ${warning}`),
126487
126635
  `next: ${plan.next}`,
126488
- `written: ${path295.join(plan.package, PLAN_FILE)}`
126636
+ `written: ${path296.join(plan.package, PLAN_FILE)}`
126489
126637
  ];
126490
126638
  return lines.join(`
126491
126639
  `);
@@ -126532,7 +126680,7 @@ async function runPlan2(args, deps) {
126532
126680
  const scoped = await deps.run(["scope", "--json", ...baseRef === undefined ? [] : ["--ref", baseRef, ...head === undefined ? [] : ["--head", head]]]);
126533
126681
  if (scoped.exitCode !== 0)
126534
126682
  fail2("scope", scoped);
126535
- const scopeFile = path295.join(packageDir, "scope.json");
126683
+ const scopeFile = path296.join(packageDir, "scope.json");
126536
126684
  await writeFileAtomic(scopeFile, scoped.stdout.endsWith(`
126537
126685
  `) ? scoped.stdout : `${scoped.stdout}
126538
126686
  `);
@@ -126543,19 +126691,19 @@ async function runPlan2(args, deps) {
126543
126691
  throw new Error("review plan: `review scope --json` printed no JSON; the plan stops here.");
126544
126692
  }
126545
126693
  const scopeFiles = Array.isArray(scopeRecord?.files) ? scopeRecord.files.filter((file) => typeof file === "string") : [];
126546
- if (!existsSync62(path295.join(packageDir, "scope-files.json"))) {
126694
+ if (!existsSync62(path296.join(packageDir, "scope-files.json"))) {
126547
126695
  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)}
126696
+ await writeFileAtomic(path296.join(packageDir, "scope-files.json"), `${JSON.stringify({ version: 1, files: scopeFiles, ...typeof lines === "number" ? { changedLines: lines } : {} }, null, 2)}
126549
126697
  `);
126550
126698
  }
126551
- const radius = await deps.run(["blast-radius", "--package", packageDir, "--json", "--out", path295.join(packageDir, "blast-radius.json"), ...baseRef === undefined ? [] : ["--ref", baseRef]]);
126699
+ const radius = await deps.run(["blast-radius", "--package", packageDir, "--json", "--out", path296.join(packageDir, "blast-radius.json"), ...baseRef === undefined ? [] : ["--ref", baseRef]]);
126552
126700
  if (radius.exitCode !== 0)
126553
126701
  warnings.push(`blast radius not computed (${(radius.stderr.trim() || radius.stdout.trim()).split(`
126554
126702
  `).pop() ?? `exit ${radius.exitCode}`})`);
126555
126703
  const sliced = await deps.run(["slice", "--package", packageDir, ...baseRef === undefined || delta !== undefined ? [] : ["--ref", baseRef], ...head === undefined ? [] : ["--head", head], ...host === undefined ? [] : ["--host", host], "--json"]);
126556
126704
  if (sliced.exitCode !== 0)
126557
126705
  fail2("slice", sliced);
126558
- const manifestPath = path295.join(packageDir, "slices", "manifest.json");
126706
+ const manifestPath = path296.join(packageDir, "slices", "manifest.json");
126559
126707
  const manifest = JSON.parse(await readFile138(manifestPath, "utf8"));
126560
126708
  const required = await deps.run(["required", "--depth", "auto", "--scope", scopeFile, "--package", packageDir, "--json"]);
126561
126709
  if (required.exitCode !== 0)
@@ -126568,7 +126716,7 @@ async function runPlan2(args, deps) {
126568
126716
  throw new Error("review plan: `review required --json` printed no JSON; the plan stops here.");
126569
126717
  }
126570
126718
  const estimate = await estimatePackage(packageDir);
126571
- const drivers = await writeWaveDrivers({ packageDir, manifest, sliceDir: path295.dirname(manifestPath), reviewers: await packageReviewers(packageDir, manifest) });
126719
+ const drivers = await writeWaveDrivers({ packageDir, manifest, sliceDir: path296.dirname(manifestPath), reviewers: await packageReviewers(packageDir, manifest) });
126572
126720
  if (drivers.length !== estimate.dispatches)
126573
126721
  warnings.push(`${drivers.length} driver(s) written for ${estimate.dispatches} planned dispatch(es); re-run \`keryx review slice --package ${packageDir}\``);
126574
126722
  const confirmed = planConfirmed(packageDir, estimate);
@@ -126600,7 +126748,7 @@ async function runPlan2(args, deps) {
126600
126748
  warnings,
126601
126749
  next: nextStepOf(estimate, confirmed)
126602
126750
  };
126603
- await writeFileAtomic(path295.join(packageDir, PLAN_FILE), `${JSON.stringify(plan, null, 2)}
126751
+ await writeFileAtomic(path296.join(packageDir, PLAN_FILE), `${JSON.stringify(plan, null, 2)}
126604
126752
  `);
126605
126753
  console.log(args.includes("--json") ? JSON.stringify(plan, null, 2) : renderPlanSummary(plan));
126606
126754
  }
@@ -126750,7 +126898,7 @@ var init_plan_eval = __esm(() => {
126750
126898
  // src/commands/review-plan-eval.ts
126751
126899
  import { existsSync as existsSync63 } from "fs";
126752
126900
  import { readFile as readFile139 } from "fs/promises";
126753
- import path296 from "path";
126901
+ import path297 from "path";
126754
126902
  function printPlanEvalHelp() {
126755
126903
  console.log(`keryx review plan-eval \u2014 usage:
126756
126904
 
@@ -126810,7 +126958,7 @@ async function runPlanEval(args, deps) {
126810
126958
  throw new Error("plan-eval needs --diff <file|base..head>. See `keryx review plan-eval --help`.");
126811
126959
  const diff = await readDiff(source);
126812
126960
  const findingsFile = optionValue(args, "--findings");
126813
- const findings = findingsFile === undefined ? undefined : parseGroundTruth(await readFile139(path296.resolve(findingsFile), "utf8"));
126961
+ const findings = findingsFile === undefined ? undefined : parseGroundTruth(await readFile139(path297.resolve(findingsFile), "utf8"));
126814
126962
  const rolesFlag = optionValue(args, "--roles");
126815
126963
  let roles = rolesFlag?.split(",").map((role) => role.trim()).filter(Boolean);
126816
126964
  if (roles === undefined) {
@@ -126868,7 +127016,7 @@ var init_retired_rules = __esm(() => {
126868
127016
  import { cp as cp2, lstat as lstat19, readFile as readFile140, readdir as readdir52, realpath as realpath26, unlink as unlink2 } from "fs/promises";
126869
127017
  import { existsSync as existsSync64 } from "fs";
126870
127018
  import { createHash as createHash83 } from "crypto";
126871
- import path297 from "path";
127019
+ import path298 from "path";
126872
127020
  import { fileURLToPath as fileURLToPath5 } from "url";
126873
127021
  function isErrnoException(error) {
126874
127022
  return error instanceof Error && "code" in error;
@@ -126884,7 +127032,7 @@ function bundledSkillSourcePath(category, skillName) {
126884
127032
  if (existsSync64(directPath)) {
126885
127033
  return directPath;
126886
127034
  }
126887
- const packagedSourcePath = path297.join(path297.dirname(fileURLToPath5(import.meta.url)), "..", "src", "gdskills", "bundled", "skills", category, skillName);
127035
+ const packagedSourcePath = path298.join(path298.dirname(fileURLToPath5(import.meta.url)), "..", "src", "gdskills", "bundled", "skills", category, skillName);
126888
127036
  if (existsSync64(packagedSourcePath)) {
126889
127037
  return packagedSourcePath;
126890
127038
  }
@@ -126905,7 +127053,7 @@ async function removeStaleRuntimeBuilds(skillsRoot, fsOps = { unlink: unlink2 })
126905
127053
  }
126906
127054
  const categories = new Map;
126907
127055
  for (const skillEntry of BUNDLED_GDSKILLS) {
126908
- const categoryDir = path297.join(skillsRoot, skillEntry.category);
127056
+ const categoryDir = path298.join(skillsRoot, skillEntry.category);
126909
127057
  let category = categories.get(categoryDir);
126910
127058
  if (!category) {
126911
127059
  category = await resolveSweepableDir(categoryDir);
@@ -126916,7 +127064,7 @@ async function removeStaleRuntimeBuilds(skillsRoot, fsOps = { unlink: unlink2 })
126916
127064
  }
126917
127065
  if (category.kind !== "usable")
126918
127066
  continue;
126919
- const installedDir = path297.join(categoryDir, skillEntry.name);
127067
+ const installedDir = path298.join(categoryDir, skillEntry.name);
126920
127068
  const skillDir = await resolveSweepableDir(installedDir);
126921
127069
  if (skillDir.kind === "missing") {
126922
127070
  continue;
@@ -126925,7 +127073,7 @@ async function removeStaleRuntimeBuilds(skillsRoot, fsOps = { unlink: unlink2 })
126925
127073
  outcomes.push(skillDir.outcome);
126926
127074
  continue;
126927
127075
  }
126928
- if (skillDir.realPath !== path297.join(root.realPath, skillEntry.category, skillEntry.name)) {
127076
+ if (skillDir.realPath !== path298.join(root.realPath, skillEntry.category, skillEntry.name)) {
126929
127077
  outcomes.push({
126930
127078
  path: installedDir,
126931
127079
  blockedAt: installedDir,
@@ -126952,9 +127100,9 @@ async function removeStaleRuntimeBuilds(skillsRoot, fsOps = { unlink: unlink2 })
126952
127100
  for (const entryName of entries) {
126953
127101
  if (!RUNTIME_BUILD_FILE_NAMES.includes(entryName))
126954
127102
  continue;
126955
- if (existsSync64(path297.join(bundledDir, entryName)))
127103
+ if (existsSync64(path298.join(bundledDir, entryName)))
126956
127104
  continue;
126957
- const installedPath = path297.join(installedDir, entryName);
127105
+ const installedPath = path298.join(installedDir, entryName);
126958
127106
  try {
126959
127107
  const stats = await lstat19(installedPath);
126960
127108
  if (!stats.isFile()) {
@@ -127042,12 +127190,12 @@ function unsweepableDirCause(outcome, projectRoot) {
127042
127190
  }
127043
127191
  }
127044
127192
  function displayPath2(target, projectRoot) {
127045
- return path297.relative(projectRoot, target).split(path297.sep).join("/");
127193
+ return path298.relative(projectRoot, target).split(path298.sep).join("/");
127046
127194
  }
127047
127195
  async function removeUnmodifiedRetiredRules(rulesTarget, fsOps = { unlink: unlink2 }) {
127048
127196
  const outcomes = [];
127049
127197
  for (const retired of RETIRED_RULES) {
127050
- const installedPath = path297.join(rulesTarget, retired.fileName);
127198
+ const installedPath = path298.join(rulesTarget, retired.fileName);
127051
127199
  let stage = "lstat";
127052
127200
  try {
127053
127201
  const stats = await lstat19(installedPath);
@@ -127123,10 +127271,10 @@ var init_guarded_fs_ops = __esm(() => {
127123
127271
  // src/gdskills/install.ts
127124
127272
  import { lstat as lstat20, readFile as readFile141, readlink as readlink2 } from "fs/promises";
127125
127273
  import { existsSync as existsSync65 } from "fs";
127126
- import path298 from "path";
127274
+ import path299 from "path";
127127
127275
  import { fileURLToPath as fileURLToPath6 } from "url";
127128
127276
  async function mkdirContainedOrWarn(metaprojectRoot, target) {
127129
- const rel = path298.relative(metaprojectRoot, target);
127277
+ const rel = path299.relative(metaprojectRoot, target);
127130
127278
  try {
127131
127279
  await mkdirContained(metaprojectRoot, rel);
127132
127280
  return null;
@@ -127139,20 +127287,20 @@ async function mkdirContainedOrWarn(metaprojectRoot, target) {
127139
127287
  }
127140
127288
  async function installGdskills(metaprojectRoot, profile, options = {}) {
127141
127289
  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));
127290
+ const skillsRoot = path299.join(metaprojectRoot, "skills", "gdskills");
127291
+ const dataRoot = path299.join(metaprojectRoot, "data", "gdskills");
127292
+ const coreRoot = path299.join(metaprojectRoot, "core", "gdskills");
127293
+ const contractsRoot = path299.join(coreRoot, "contracts");
127294
+ const projectSkillsRoot = path299.join(metaprojectRoot, metaprojectRelative(PROJECT_SKILLS_DIR));
127147
127295
  const skippedDestinationWarnings = [];
127148
127296
  for (const dir of [
127149
- path298.join(metaprojectRoot, "jobs"),
127150
- path298.join(metaprojectRoot, "modules"),
127297
+ path299.join(metaprojectRoot, "jobs"),
127298
+ path299.join(metaprojectRoot, "modules"),
127151
127299
  projectSkillsRoot,
127152
127300
  ...options.createDataDirs === false ? [] : [
127153
- path298.join(dataRoot, "artifacts"),
127154
- path298.join(metaprojectRoot, metaprojectRelative(PROJECT_SKILL_REPORTS_DIR)),
127155
- path298.join(dataRoot, "proposals")
127301
+ path299.join(dataRoot, "artifacts"),
127302
+ path299.join(metaprojectRoot, metaprojectRelative(PROJECT_SKILL_REPORTS_DIR)),
127303
+ path299.join(dataRoot, "proposals")
127156
127304
  ]
127157
127305
  ]) {
127158
127306
  const warning = await mkdirContainedOrWarn(metaprojectRoot, dir);
@@ -127161,8 +127309,8 @@ async function installGdskills(metaprojectRoot, profile, options = {}) {
127161
127309
  }
127162
127310
  let installedSkills = 0;
127163
127311
  for (const skillEntry of skills) {
127164
- const skillDir = path298.join(skillsRoot, skillEntry.category, skillEntry.name);
127165
- const relSkillDir = path298.relative(metaprojectRoot, skillDir);
127312
+ const skillDir = path299.join(skillsRoot, skillEntry.category, skillEntry.name);
127313
+ const relSkillDir = path299.relative(metaprojectRoot, skillDir);
127166
127314
  const destination = await checkInstallDestination(skillDir, relSkillDir);
127167
127315
  if (!destination.usable) {
127168
127316
  skippedDestinationWarnings.push(destination.warning);
@@ -127177,7 +127325,7 @@ async function installGdskills(metaprojectRoot, profile, options = {}) {
127177
127325
  if (existsSync65(bundledSkillPath)) {
127178
127326
  await copyDirectoryContained(bundledSkillPath, skillDir);
127179
127327
  } else {
127180
- await writeContained(metaprojectRoot, path298.join(relSkillDir, "SKILL.md"), renderBundledSkill(skillEntry));
127328
+ await writeContained(metaprojectRoot, path299.join(relSkillDir, "SKILL.md"), renderBundledSkill(skillEntry));
127181
127329
  }
127182
127330
  installedSkills += 1;
127183
127331
  }
@@ -127191,12 +127339,12 @@ async function installGdskills(metaprojectRoot, profile, options = {}) {
127191
127339
  skippedDestinationWarnings.push(rulesInstall.warning);
127192
127340
  }
127193
127341
  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));
127342
+ const catalogPath = path299.join(metaprojectRoot, metaprojectRelative(PROJECT_SKILLS_CATALOG_PATH));
127343
+ await writeContained(metaprojectRoot, path299.relative(metaprojectRoot, catalogPath), await preserveProjectSkillsSection(catalogPath, renderGdskillsCatalog(profile)));
127344
+ const manifestPath = path299.join(metaprojectRoot, "modules", "gdskills.md");
127345
+ await writeContained(metaprojectRoot, path299.relative(metaprojectRoot, manifestPath), renderGdskillsManifest(profile));
127198
127346
  skippedDestinationWarnings.push(...await installContracts(contractsRoot, metaprojectRoot));
127199
- const projectRoot = path298.dirname(metaprojectRoot);
127347
+ const projectRoot = path299.dirname(metaprojectRoot);
127200
127348
  const warnings = [
127201
127349
  ...skippedDestinationWarnings,
127202
127350
  ...retiredRuleOutcomes.map(retiredRuleWarning).filter((warning) => warning !== null),
@@ -127218,8 +127366,8 @@ async function installBundledSharedSkills(skillsRoot, metaprojectRoot) {
127218
127366
  if (!existsSync65(sharedSource)) {
127219
127367
  return null;
127220
127368
  }
127221
- const sharedTarget = path298.join(skillsRoot, "shared");
127222
- const relSharedTarget = path298.relative(metaprojectRoot, sharedTarget);
127369
+ const sharedTarget = path299.join(skillsRoot, "shared");
127370
+ const relSharedTarget = path299.relative(metaprojectRoot, sharedTarget);
127223
127371
  const destination = await checkInstallDestination(sharedTarget, relSharedTarget);
127224
127372
  if (!destination.usable) {
127225
127373
  return destination.warning;
@@ -127262,11 +127410,11 @@ async function checkInstallDestination(destination, displayPath) {
127262
127410
  }
127263
127411
  async function installBundledRules(metaprojectRoot) {
127264
127412
  const rulesSource = bundledRulesSourcePath();
127265
- const rulesTarget = path298.join(metaprojectRoot, "rules", "core");
127413
+ const rulesTarget = path299.join(metaprojectRoot, "rules", "core");
127266
127414
  if (!existsSync65(rulesSource)) {
127267
127415
  return { outcomes: [], warning: null };
127268
127416
  }
127269
- const relRulesTarget = path298.relative(metaprojectRoot, rulesTarget);
127417
+ const relRulesTarget = path299.relative(metaprojectRoot, rulesTarget);
127270
127418
  const destination = await checkInstallDestination(rulesTarget, relRulesTarget);
127271
127419
  if (!destination.usable) {
127272
127420
  return { outcomes: [], warning: destination.warning };
@@ -127297,13 +127445,13 @@ ${section}
127297
127445
  `;
127298
127446
  }
127299
127447
  async function installContracts(contractsRoot, metaprojectRoot) {
127300
- const rootCheck = await checkInstallDestination(contractsRoot, path298.relative(metaprojectRoot, contractsRoot));
127448
+ const rootCheck = await checkInstallDestination(contractsRoot, path299.relative(metaprojectRoot, contractsRoot));
127301
127449
  if (!rootCheck.usable) {
127302
127450
  return [rootCheck.warning];
127303
127451
  }
127304
127452
  const results = await Promise.all(CONTRACTS.map(async (contract) => {
127305
- const destination = path298.join(contractsRoot, contract.fileName);
127306
- const relDestination = path298.relative(metaprojectRoot, destination);
127453
+ const destination = path299.join(contractsRoot, contract.fileName);
127454
+ const relDestination = path299.relative(metaprojectRoot, destination);
127307
127455
  const check = await checkInstallFile(destination, relDestination);
127308
127456
  if (!check.usable) {
127309
127457
  return check.warning;
@@ -127348,7 +127496,7 @@ function contractSourcePath(fileName) {
127348
127496
  if (existsSync65(directPath)) {
127349
127497
  return directPath;
127350
127498
  }
127351
- const packagedSourcePath = path298.join(path298.dirname(fileURLToPath6(import.meta.url)), "..", "src", "gdskills", "contracts", fileName);
127499
+ const packagedSourcePath = path299.join(path299.dirname(fileURLToPath6(import.meta.url)), "..", "src", "gdskills", "contracts", fileName);
127352
127500
  if (existsSync65(packagedSourcePath)) {
127353
127501
  return packagedSourcePath;
127354
127502
  }
@@ -127359,7 +127507,7 @@ function bundledSharedSourcePath() {
127359
127507
  if (existsSync65(directPath)) {
127360
127508
  return directPath;
127361
127509
  }
127362
- const packagedSourcePath = path298.join(path298.dirname(fileURLToPath6(import.meta.url)), "..", "src", "gdskills", "bundled", "skills", "shared");
127510
+ const packagedSourcePath = path299.join(path299.dirname(fileURLToPath6(import.meta.url)), "..", "src", "gdskills", "bundled", "skills", "shared");
127363
127511
  if (existsSync65(packagedSourcePath)) {
127364
127512
  return packagedSourcePath;
127365
127513
  }
@@ -127370,7 +127518,7 @@ function bundledRulesSourcePath() {
127370
127518
  if (existsSync65(directPath)) {
127371
127519
  return directPath;
127372
127520
  }
127373
- const packagedSourcePath = path298.join(path298.dirname(fileURLToPath6(import.meta.url)), "..", "src", "gdskills", "bundled", "rules", "core");
127521
+ const packagedSourcePath = path299.join(path299.dirname(fileURLToPath6(import.meta.url)), "..", "src", "gdskills", "bundled", "rules", "core");
127374
127522
  if (existsSync65(packagedSourcePath)) {
127375
127523
  return packagedSourcePath;
127376
127524
  }
@@ -127388,7 +127536,7 @@ var init_install2 = __esm(() => {
127388
127536
  // src/gdskills/import-skills.ts
127389
127537
  import { lstat as lstat21, readdir as readdir53, readFile as readFile142, stat as stat33 } from "fs/promises";
127390
127538
  import { homedir as homedir11 } from "os";
127391
- import path299 from "path";
127539
+ import path300 from "path";
127392
127540
  function emptyOnlyError(label) {
127393
127541
  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
127542
  }
@@ -127777,17 +127925,17 @@ function refuseSharedDestinations(label, picked) {
127777
127925
  }
127778
127926
  }
127779
127927
  function isInside6(parent, child) {
127780
- const relative = path299.relative(parent, child);
127781
- return relative !== "" && relative !== ".." && !relative.startsWith(`..${path299.sep}`) && !path299.isAbsolute(relative);
127928
+ const relative = path300.relative(parent, child);
127929
+ return relative !== "" && relative !== ".." && !relative.startsWith(`..${path300.sep}`) && !path300.isAbsolute(relative);
127782
127930
  }
127783
127931
  function portableOriginRef(absolute, projectRoot, home = homedir11()) {
127784
- const resolved = path299.resolve(absolute);
127785
- const root = path299.resolve(projectRoot);
127932
+ const resolved = path300.resolve(absolute);
127933
+ const root = path300.resolve(projectRoot);
127786
127934
  if (isInside6(root, resolved)) {
127787
- return toPosix(path299.relative(root, resolved));
127935
+ return toPosix(path300.relative(root, resolved));
127788
127936
  }
127789
127937
  if (isInside6(home, resolved)) {
127790
- return `~/${toPosix(path299.relative(home, resolved))}`;
127938
+ return `~/${toPosix(path300.relative(home, resolved))}`;
127791
127939
  }
127792
127940
  return resolved;
127793
127941
  }
@@ -127850,8 +127998,8 @@ async function sourceFromDirectory(options, absolute) {
127850
127998
  const { dirs, tree } = await skillPackageDirs(absolute);
127851
127999
  const candidates = [];
127852
128000
  for (const dir of dirs) {
127853
- const skillMd = path299.join(dir, "SKILL.md");
127854
- const name = path299.basename(dir);
128001
+ const skillMd = path300.join(dir, "SKILL.md");
128002
+ const name = path300.basename(dir);
127855
128003
  candidates.push({ name, slug: projectSkillSlug(name), skillMd, content: await readFile142(skillMd, "utf8") });
127856
128004
  }
127857
128005
  candidates.sort((a, b) => a.name.localeCompare(b.name));
@@ -127904,18 +128052,18 @@ ${candidateList(selected)}`);
127904
128052
  return picked.map(({ source }) => source).sort((a, b) => projectSkillKey(a).localeCompare(projectSkillKey(b)));
127905
128053
  }
127906
128054
  async function skillPackageDirs(root) {
127907
- if (await pathExists(path299.join(root, "SKILL.md"))) {
128055
+ if (await pathExists(path300.join(root, "SKILL.md"))) {
127908
128056
  return { dirs: [root], tree: false };
127909
128057
  }
127910
- const nestedSkills = path299.join(root, "skills");
128058
+ const nestedSkills = path300.join(root, "skills");
127911
128059
  const scan = await pathExists(nestedSkills) ? nestedSkills : root;
127912
128060
  const entries = await readdir53(scan, { withFileTypes: true });
127913
128061
  const dirs = [];
127914
128062
  for (const entry of entries) {
127915
128063
  if (!entry.isDirectory())
127916
128064
  continue;
127917
- const dir = path299.join(scan, entry.name);
127918
- if (await pathExists(path299.join(dir, "SKILL.md"))) {
128065
+ const dir = path300.join(scan, entry.name);
128066
+ if (await pathExists(path300.join(dir, "SKILL.md"))) {
127919
128067
  dirs.push(dir);
127920
128068
  }
127921
128069
  }
@@ -128027,7 +128175,7 @@ function refusedSkillRow(base, verdict, dryRun) {
128027
128175
  }
128028
128176
  async function planOne(options, source) {
128029
128177
  const dest = projectSkillPackagePath(source.module, source.name);
128030
- const destAbs = path299.join(options.projectRoot, dest, "SKILL.md");
128178
+ const destAbs = path300.join(options.projectRoot, dest, "SKILL.md");
128031
128179
  const exists = await pathExists(destAbs);
128032
128180
  const bundled = BUNDLED_NAMES.has(source.name);
128033
128181
  if (skippedAsDeprecated(source)) {
@@ -128067,7 +128215,7 @@ async function planOne(options, source) {
128067
128215
  }
128068
128216
  };
128069
128217
  }
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);
128218
+ 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
128219
  if (verdict.kind !== "write") {
128072
128220
  return {
128073
128221
  row: refusedSkillRow({ name: source.name, module: source.module, path: dest, origin: source.origin }, verdict, options.dryRun === true)
@@ -128120,7 +128268,7 @@ function mergeNotes(notes, parts) {
128120
128268
  };
128121
128269
  }
128122
128270
  async function planUpdate(options, entry) {
128123
- const skillMd = path299.join(options.projectRoot, entry.path, "SKILL.md");
128271
+ const skillMd = path300.join(options.projectRoot, entry.path, "SKILL.md");
128124
128272
  const current = await readFile142(skillMd, "utf8");
128125
128273
  let origin = options.from ?? metadataLine2(current, "Origin");
128126
128274
  const skippedRow = (reason, recorded) => ({
@@ -128147,7 +128295,7 @@ async function planUpdate(options, entry) {
128147
128295
  sourcePath = resolved;
128148
128296
  origin = portableOriginRef(resolved, options.projectRoot);
128149
128297
  }
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);
128298
+ 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
128299
  if (verdict.kind !== "write") {
128152
128300
  return {
128153
128301
  row: refusedSkillRow({ name: entry.name, module: entry.module, path: entry.path, origin }, verdict, options.dryRun === true)
@@ -128191,7 +128339,7 @@ async function writeUpdate(options, { entry, origin, content, gate }) {
128191
128339
  };
128192
128340
  }
128193
128341
  async function writeGatedSkill(projectRoot, created, gate) {
128194
- await writeContained(projectRoot, toPosix(path299.join(created.skillPath, "SKILL.md")), gate.content);
128342
+ await writeContained(projectRoot, toPosix(path300.join(created.skillPath, "SKILL.md")), gate.content);
128195
128343
  }
128196
128344
  function stampImportHeader(source, header) {
128197
128345
  if (!header)
@@ -128251,15 +128399,15 @@ function versionOption(content) {
128251
128399
  return version ? { version } : {};
128252
128400
  }
128253
128401
  async function findOverlayRule(sourcePath, ref) {
128254
- let dir = path299.dirname(sourcePath);
128402
+ let dir = path300.dirname(sourcePath);
128255
128403
  let notRegular;
128256
128404
  for (let depth = 0;depth <= 3; depth += 1) {
128257
- const candidate = path299.join(dir, "rules", ref);
128405
+ const candidate = path300.join(dir, "rules", ref);
128258
128406
  if (await isRegularFile(candidate))
128259
128407
  return { found: candidate };
128260
128408
  if (notRegular === undefined && await pathExists(candidate))
128261
128409
  notRegular = candidate;
128262
- const parent = path299.dirname(dir);
128410
+ const parent = path300.dirname(dir);
128263
128411
  if (parent === dir)
128264
128412
  break;
128265
128413
  dir = parent;
@@ -128333,7 +128481,7 @@ async function decideRule(options, citation, bundledRules) {
128333
128481
  const base = { ref: citation.ref, citedBy: citation.citedBy };
128334
128482
  const resolution = await resolveRuleReference(options.projectRoot, citation.ref);
128335
128483
  const existing = resolution.resolved;
128336
- const shipsWithKeryx = citation.ref.startsWith(`${KERYX_RULES_DIR}/`) && await pathExists(path299.join(bundledRules, path299.basename(citation.ref)));
128484
+ const shipsWithKeryx = citation.ref.startsWith(`${KERYX_RULES_DIR}/`) && await pathExists(path300.join(bundledRules, path300.basename(citation.ref)));
128337
128485
  const notRegularFiles = resolution.notRegularFiles ?? [];
128338
128486
  const notRegularReason = (candidate) => `${candidate} is not a regular file; nothing was written there`;
128339
128487
  let found;
@@ -128354,7 +128502,7 @@ async function decideRule(options, citation, bundledRules) {
128354
128502
  }
128355
128503
  const origin = portableOriginRef(found, options.projectRoot);
128356
128504
  const content = await readFile142(found, "utf8");
128357
- const existingContent = existing !== undefined ? await readFile142(path299.join(options.projectRoot, existing), "utf8") : undefined;
128505
+ const existingContent = existing !== undefined ? await readFile142(path300.join(options.projectRoot, existing), "utf8") : undefined;
128358
128506
  if (existing !== undefined && existingContent === content) {
128359
128507
  return { row: { ...base, status: "present", origin, existing } };
128360
128508
  }
@@ -128410,7 +128558,7 @@ async function decideReferencedRules(options, sources, refusedPackages) {
128410
128558
  return decisions;
128411
128559
  }
128412
128560
  async function listTemplateFiles(sourcePath) {
128413
- const root = path299.join(path299.dirname(sourcePath), TEMPLATES_DIR);
128561
+ const root = path300.join(path300.dirname(sourcePath), TEMPLATES_DIR);
128414
128562
  try {
128415
128563
  if (!(await lstat21(root)).isDirectory())
128416
128564
  return [];
@@ -128423,7 +128571,7 @@ async function listTemplateFiles(sourcePath) {
128423
128571
  for (const entry of entries) {
128424
128572
  const rel = prefix === "" ? entry.name : `${prefix}/${entry.name}`;
128425
128573
  if (entry.isDirectory())
128426
- await walk(path299.join(dir, entry.name), rel);
128574
+ await walk(path300.join(dir, entry.name), rel);
128427
128575
  else if (entry.isFile())
128428
128576
  files.push(rel);
128429
128577
  }
@@ -128435,7 +128583,7 @@ async function fileInTheWay(projectRoot, target) {
128435
128583
  let current = projectRoot;
128436
128584
  let relative = "";
128437
128585
  for (const part of target.split("/").slice(0, -1)) {
128438
- current = path299.join(current, part);
128586
+ current = path300.join(current, part);
128439
128587
  relative = relative === "" ? part : `${relative}/${part}`;
128440
128588
  try {
128441
128589
  if (!(await stat33(current)).isDirectory())
@@ -128472,13 +128620,13 @@ async function planTemplates(options, pkg, replace) {
128472
128620
  for (const file of await listTemplateFiles(pkg.sourcePath)) {
128473
128621
  const target = `${packagePath}/${TEMPLATES_DIR}/${file}`;
128474
128622
  const base = { package: packageKey, file, path: target };
128475
- const bytes = await readFile142(path299.join(path299.dirname(pkg.sourcePath), TEMPLATES_DIR, file));
128623
+ const bytes = await readFile142(path300.join(path300.dirname(pkg.sourcePath), TEMPLATES_DIR, file));
128476
128624
  const text = bytes.toString("utf8");
128477
128625
  if (!Buffer.from(text, "utf8").equals(bytes)) {
128478
128626
  plans.push({ row: { ...base, status: "skipped", reason: "not UTF-8 text; the importer copies text templates only" } });
128479
128627
  continue;
128480
128628
  }
128481
- const targetAbs = path299.join(options.projectRoot, target);
128629
+ const targetAbs = path300.join(options.projectRoot, target);
128482
128630
  const blocker = await fileInTheWay(options.projectRoot, target);
128483
128631
  if (blocker !== undefined) {
128484
128632
  plans.push({ row: { ...base, status: "skipped", reason: `${blocker} is a file, not a directory; nothing was written there` } });
@@ -128558,10 +128706,10 @@ function inferNameFromPath(filePath, content) {
128558
128706
  const fromFrontmatter = frontmatterName(content);
128559
128707
  if (fromFrontmatter)
128560
128708
  return fromFrontmatter;
128561
- const parent = path299.basename(path299.dirname(filePath));
128709
+ const parent = path300.basename(path300.dirname(filePath));
128562
128710
  if (parent && parent !== "skills" && parent !== ".")
128563
128711
  return parent;
128564
- return path299.basename(filePath, path299.extname(filePath));
128712
+ return path300.basename(filePath, path300.extname(filePath));
128565
128713
  }
128566
128714
  function inferNameFromUrl(url) {
128567
128715
  const cleaned = url.replace(/\/SKILL\.md$/i, "");
@@ -128588,7 +128736,7 @@ function metadataLine2(content, label) {
128588
128736
  }
128589
128737
  async function readRegistry(projectRoot) {
128590
128738
  await Promise.resolve().then(() => init_json());
128591
- const manifestPath = path299.join(projectRoot, PROJECT_SKILLS_MANIFEST_PATH);
128739
+ const manifestPath = path300.join(projectRoot, PROJECT_SKILLS_MANIFEST_PATH);
128592
128740
  const manifest = await readJsonFileOr(manifestPath, {});
128593
128741
  return manifest.modules?.gdskills?.projectSkillRegistry ?? [];
128594
128742
  }
@@ -128988,10 +129136,10 @@ var init_import_reviewers = __esm(() => {
128988
129136
 
128989
129137
  // src/review/jev-rules-config.ts
128990
129138
  import { readFile as readFile143 } from "fs/promises";
128991
- import path300 from "path";
129139
+ import path301 from "path";
128992
129140
  async function readJevRulesEnabled(cwd) {
128993
129141
  try {
128994
- const parsed = JSON.parse(await readFile143(path300.join(cwd, REVIEW_GATE_CONFIG_PATH), "utf8"));
129142
+ const parsed = JSON.parse(await readFile143(path301.join(cwd, REVIEW_GATE_CONFIG_PATH), "utf8"));
128995
129143
  if (typeof parsed !== "object" || parsed === null)
128996
129144
  return false;
128997
129145
  const review = parsed["review"];
@@ -129011,7 +129159,7 @@ var init_jev_rules_config = __esm(() => {
129011
129159
 
129012
129160
  // src/review/jev-rules-cache.ts
129013
129161
  import { readFile as readFile144 } from "fs/promises";
129014
- import path301 from "path";
129162
+ import path302 from "path";
129015
129163
  function hashCacheKey(input) {
129016
129164
  let hash = 2166136261;
129017
129165
  for (let i = 0;i < input.length; i += 1) {
@@ -129024,7 +129172,7 @@ function cacheKeyFor(ruleId, clauseId, regionPath, regionStartLine, regionEndLin
129024
129172
  return hashCacheKey(`${ruleId}::${clauseId}::${regionPath}:${regionStartLine}-${regionEndLine}::${clauseText}`);
129025
129173
  }
129026
129174
  function cacheFilePath(cwd) {
129027
- return path301.join(cwd, JEV_RULES_CACHE_PATH);
129175
+ return path302.join(cwd, JEV_RULES_CACHE_PATH);
129028
129176
  }
129029
129177
  async function readJevRulesCache(cwd) {
129030
129178
  const file = cacheFilePath(cwd);
@@ -129854,14 +130002,14 @@ var init_jev_rules = __esm(() => {
129854
130002
  });
129855
130003
 
129856
130004
  // src/review/conform-tag-cache.ts
129857
- import path302 from "path";
130005
+ import path303 from "path";
129858
130006
  import { readFile as readFile145 } from "fs/promises";
129859
130007
  function hashConformDocContent(content) {
129860
130008
  return hashOriginContent(`clause-tag-schema:v${CLAUSE_TAG_SCHEMA_VERSION}
129861
130009
  ${content}`);
129862
130010
  }
129863
130011
  function cacheFilePath2(cwd, cacheRelPath) {
129864
- return path302.join(cwd, cacheRelPath);
130012
+ return path303.join(cwd, cacheRelPath);
129865
130013
  }
129866
130014
  async function readClauseTagCache(cwd, cacheRelPath = CONFORM_TAG_CACHE_PATH) {
129867
130015
  const file = cacheFilePath2(cwd, cacheRelPath);
@@ -130386,7 +130534,7 @@ function checkPullOpen(facts) {
130386
130534
 
130387
130535
  // src/review/bot/packages.ts
130388
130536
  import { readdir as readdir55, readFile as readFile147 } from "fs/promises";
130389
- import path303 from "path";
130537
+ import path304 from "path";
130390
130538
  async function subdirectories(dir) {
130391
130539
  try {
130392
130540
  const entries = await readdir55(dir, { withFileTypes: true });
@@ -130409,12 +130557,12 @@ function pullFromTarget(target) {
130409
130557
  }
130410
130558
  async function readPackage(dir) {
130411
130559
  try {
130412
- const manifest = JSON.parse(await readFile147(path303.join(dir, "manifest.json"), "utf8"));
130560
+ const manifest = JSON.parse(await readFile147(path304.join(dir, "manifest.json"), "utf8"));
130413
130561
  if (manifest.target?.kind !== "pr")
130414
130562
  return null;
130415
130563
  let findings = [];
130416
130564
  try {
130417
- const parsed = JSON.parse(await readFile147(path303.join(dir, "findings.json"), "utf8"));
130565
+ const parsed = JSON.parse(await readFile147(path304.join(dir, "findings.json"), "utf8"));
130418
130566
  if (Array.isArray(parsed))
130419
130567
  findings = parsed;
130420
130568
  } catch {
@@ -130429,16 +130577,16 @@ async function readPackage(dir) {
130429
130577
  function candidateDirs2(cwd) {
130430
130578
  return (async () => {
130431
130579
  const dirs = [];
130432
- const reviews = path303.join(cwd, ".metaproject", "reviews");
130580
+ const reviews = path304.join(cwd, ".metaproject", "reviews");
130433
130581
  for (const name of await subdirectories(reviews)) {
130434
130582
  if (name !== "bot")
130435
- dirs.push(path303.join(reviews, name));
130583
+ dirs.push(path304.join(reviews, name));
130436
130584
  }
130437
- const flows = path303.join(cwd, ".metaproject", "flows");
130585
+ const flows = path304.join(cwd, ".metaproject", "flows");
130438
130586
  for (const flow of await subdirectories(flows)) {
130439
- const nested = path303.join(flows, flow, "reviews");
130587
+ const nested = path304.join(flows, flow, "reviews");
130440
130588
  for (const name of await subdirectories(nested))
130441
- dirs.push(path303.join(nested, name));
130589
+ dirs.push(path304.join(nested, name));
130442
130590
  }
130443
130591
  return dirs;
130444
130592
  })();
@@ -130455,7 +130603,7 @@ async function listReviewPackages(cwd) {
130455
130603
  }
130456
130604
  async function loadReviewPackage(cwd, reviewId) {
130457
130605
  for (const dir of await candidateDirs2(cwd)) {
130458
- if (path303.basename(dir) !== reviewId)
130606
+ if (path304.basename(dir) !== reviewId)
130459
130607
  continue;
130460
130608
  const listed = await readPackage(dir);
130461
130609
  if (listed !== null && listed.reviewId === reviewId)
@@ -130466,7 +130614,7 @@ async function loadReviewPackage(cwd, reviewId) {
130466
130614
  var init_packages = () => {};
130467
130615
 
130468
130616
  // src/review/bot/run.ts
130469
- import path304 from "path";
130617
+ import path305 from "path";
130470
130618
  function capDiff(diff, maxBytes) {
130471
130619
  const totalBytes = Buffer.byteLength(diff);
130472
130620
  if (totalBytes <= maxBytes)
@@ -130511,7 +130659,7 @@ function parseVerifierVerdict(reply) {
130511
130659
  return unverifiable;
130512
130660
  }
130513
130661
  function botStatePath(cwd, repo, number) {
130514
- return path304.join(cwd, ".metaproject", "reviews", "bot", `${repo.replace(/\//g, "__")}__${number}.json`);
130662
+ return path305.join(cwd, ".metaproject", "reviews", "bot", `${repo.replace(/\//g, "__")}__${number}.json`);
130515
130663
  }
130516
130664
  function emptyBotState(repo, number) {
130517
130665
  return { schemaVersion: 1, repo, number, reviews: [], pull: null };
@@ -131422,7 +131570,7 @@ var init_review_bot = __esm(() => {
131422
131570
  });
131423
131571
 
131424
131572
  // src/commands/review-run.ts
131425
- import path305 from "path";
131573
+ import path306 from "path";
131426
131574
  function positive3(args, name, integer) {
131427
131575
  const raw = optionValue(args, name);
131428
131576
  if (raw === undefined) {
@@ -131527,7 +131675,7 @@ async function runReviewRunCommand(args, deps = {}) {
131527
131675
  }
131528
131676
  const cwd = deps.cwd ?? process.cwd();
131529
131677
  const target = parsed.target;
131530
- const notifier = deps.notifier ?? (parsed.notify ? createTelegramNotifier({ project: path305.basename(cwd), target }) : NO_NOTIFIER);
131678
+ const notifier = deps.notifier ?? (parsed.notify ? createTelegramNotifier({ project: path306.basename(cwd), target }) : NO_NOTIFIER);
131531
131679
  const run = deps.run ?? runAutonomousReview;
131532
131680
  const heartbeat = createHeartbeat(error, deps.now, undefined, deps.keepAliveMs ?? HEARTBEAT_KEEPALIVE_MS);
131533
131681
  const stop = new AbortController;
@@ -131550,14 +131698,14 @@ async function runReviewRunCommand(args, deps = {}) {
131550
131698
  ...parsed.model === undefined ? {} : { model: parsed.model }
131551
131699
  });
131552
131700
  await notifier.finished(endLine(result.summary));
131553
- const escalated = arbiterEscalateLine(result.summary, readArbiterLog(path305.join(result.runDir, ARBITER_LOG_NAME)));
131701
+ const escalated = arbiterEscalateLine(result.summary, readArbiterLog(path306.join(result.runDir, ARBITER_LOG_NAME)));
131554
131702
  if (escalated !== undefined)
131555
131703
  await notifier.finished(escalated);
131556
131704
  if (parsed.json)
131557
131705
  log(JSON.stringify(result.summary, null, 2));
131558
131706
  else {
131559
131707
  log(`${result.reused ? "reused" : result.status}: ${result.summary.reason}`);
131560
- log(`report: ${path305.join(result.runDir, "report.md")}`);
131708
+ log(`report: ${path306.join(result.runDir, "report.md")}`);
131561
131709
  }
131562
131710
  return result.exitCode;
131563
131711
  } catch (problem) {
@@ -131615,10 +131763,10 @@ A run that really stalls is still stopped by --stall-minutes (exit 5), and the l
131615
131763
 
131616
131764
  // src/review/jev-risk-config.ts
131617
131765
  import { readFile as readFile149 } from "fs/promises";
131618
- import path306 from "path";
131766
+ import path307 from "path";
131619
131767
  async function readJevRiskEnabled(cwd) {
131620
131768
  try {
131621
- const parsed = JSON.parse(await readFile149(path306.join(cwd, REVIEW_GATE_CONFIG_PATH), "utf8"));
131769
+ const parsed = JSON.parse(await readFile149(path307.join(cwd, REVIEW_GATE_CONFIG_PATH), "utf8"));
131622
131770
  if (typeof parsed !== "object" || parsed === null)
131623
131771
  return false;
131624
131772
  const review = parsed["review"];
@@ -131846,80 +131994,6 @@ var init_review_jev_risk = __esm(() => {
131846
131994
  JEV_RISK_FLAGS = ["--diff", "--pr", "--scope", "--max-calls", "--threshold", "--model", "--repo", "--fixtures", "--json"];
131847
131995
  });
131848
131996
 
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
131997
  // src/commands/review-ledger.ts
131924
131998
  import { existsSync as existsSync66 } from "fs";
131925
131999
  import { createHash as createHash84 } from "crypto";
@@ -146555,19 +146629,12 @@ async function runComplete2(args, seams) {
146555
146629
  }
146556
146630
  const isTerminal = seams?.isTerminal ?? (() => process.stdin.isTTY === true && process.stdout.isTTY === true);
146557
146631
  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.");
146632
+ 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
146633
  }
146560
146634
  const readinessWaivers = parseReadinessWaivers(args);
146561
146635
  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.");
146636
+ 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
146637
  }
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
146638
  const {
146572
146639
  manifest,
146573
146640
  reviewNotes,
@@ -146575,7 +146642,7 @@ async function runComplete2(args, seams) {
146575
146642
  coverageWaiver,
146576
146643
  readiness,
146577
146644
  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 } });
146645
+ } = 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
146646
  for (const warning of warnings)
146580
146647
  console.error(`warning: ${warning}`);
146581
146648
  if (coverageWaiver !== undefined) {
@@ -147431,7 +147498,7 @@ var init_review = __esm(() => {
147431
147498
  init_host_profile();
147432
147499
  init_skill_drift();
147433
147500
  init_run_profile();
147434
- init_routing_check();
147501
+ init_complete_gated();
147435
147502
  init_review_operator();
147436
147503
  init_review_chunks();
147437
147504
  init_review_layers();
@@ -147688,8 +147755,22 @@ process; past the bound the package is still created, the command returns, and t
147688
147755
  });
147689
147756
 
147690
147757
  // src/commands/operator-confirm-review.ts
147691
- import { existsSync as existsSync75, statSync as statSync21 } from "fs";
147758
+ import { createHash as createHash85 } from "crypto";
147759
+ import { existsSync as existsSync75, readFileSync as readFileSync39, statSync as statSync21 } from "fs";
147692
147760
  import path337 from "path";
147761
+ async function defaultReplyPass(args, dryRun) {
147762
+ await Promise.resolve().then(() => init_review());
147763
+ return commentsReplyPass(args, dryRun);
147764
+ }
147765
+ function shownBody(text) {
147766
+ const lines = text.split(/\r?\n/).map((line) => line.replace(/\t/g, " "));
147767
+ while (lines.length > 1 && lines[lines.length - 1] === "")
147768
+ lines.pop();
147769
+ const bad = lines.findIndex((line) => hiddenInCard(line));
147770
+ if (bad !== -1)
147771
+ 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` };
147772
+ return { lines: lines.map((line) => ` ${line}`) };
147773
+ }
147693
147774
  async function trackerPrLookup2(ref) {
147694
147775
  if (!await githubAdapter.detect())
147695
147776
  return null;
@@ -147708,13 +147789,12 @@ async function resolvePackage(ref, projectRoot) {
147708
147789
  return resolveReviewPackagePath(projectRoot, ref);
147709
147790
  }
147710
147791
  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;
147792
+ return files.map((file) => ` ${file}`);
147713
147793
  }
147714
147794
  function chunksAnswerSpec() {
147715
147795
  return {
147716
147796
  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`.",
147797
+ 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
147798
  inputSchema: {
147719
147799
  type: "object",
147720
147800
  properties: { package: packageSchema, answer: { type: "string", enum: ["yes", "no"], description: "The answer to record if the operator confirms." } },
@@ -147764,7 +147844,7 @@ function chunksAnswerSpec() {
147764
147844
  return { output: "The chunk plan changed after the card was shown; nothing was recorded. Ask again.", isError: true };
147765
147845
  }
147766
147846
  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);
147847
+ const record = await answerChunkEstimate(prepared.dir, prepared.answer, confirmation.quote, new Date(confirmation.at), confirmation.confirmedVia, cardPressFields(confirmation));
147768
147848
  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
147849
  }
147770
147850
  };
@@ -147774,7 +147854,7 @@ function publishSpec(seams) {
147774
147854
  const post = () => seams.postReview ?? githubPostReview;
147775
147855
  return {
147776
147856
  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.",
147857
+ 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
147858
  inputSchema: {
147779
147859
  type: "object",
147780
147860
  properties: {
@@ -147804,8 +147884,10 @@ function publishSpec(seams) {
147804
147884
  return false;
147805
147885
  }
147806
147886
  });
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)}`);
147887
+ const shownReview = shownBody(result.body);
147888
+ if ("refusal" in shownReview)
147889
+ return { refusal: `The review body ${shownReview.refusal}` };
147890
+ const bodyLines = shownReview.lines;
147809
147891
  const failing = result.readiness.items.filter((item) => item.status === "fail").map((item) => item.id);
147810
147892
  const name = label3(dir, ctx.projectRoot);
147811
147893
  return {
@@ -147814,8 +147896,8 @@ function publishSpec(seams) {
147814
147896
  `target: ${cardSafe(target)}`,
147815
147897
  `verdict: ${result.event}`,
147816
147898
  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,
147899
+ `body: ${result.body.length} characters, ${bodyLines.length} lines, shown whole:`,
147900
+ ...bodyLines,
147819
147901
  "yes: the review is posted to GitHub and cannot be taken back from here"
147820
147902
  ],
147821
147903
  subject: `${cardSafe(target)} (package ${name})`,
@@ -147836,6 +147918,7 @@ function publishSpec(seams) {
147836
147918
  post: post(),
147837
147919
  now: () => new Date(confirmation.at),
147838
147920
  confirmedVia: confirmation.confirmedVia,
147921
+ ...cardPressFields(confirmation),
147839
147922
  confirm: async (body, targetRef) => {
147840
147923
  changed = body !== prepared.body || targetRef !== prepared.ref;
147841
147924
  return !changed;
@@ -147851,7 +147934,7 @@ function publishSpec(seams) {
147851
147934
  function waiveCoverageSpec() {
147852
147935
  return {
147853
147936
  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.",
147937
+ 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
147938
  inputSchema: { type: "object", properties: { package: packageSchema }, required: ["package"], additionalProperties: false },
147856
147939
  async buildCard(input, ctx) {
147857
147940
  const ref = packageRef(input);
@@ -147887,10 +147970,10 @@ function waiveCoverageSpec() {
147887
147970
  if (now.length !== prepared.files.length || now.some((file, index) => file !== prepared.files[index])) {
147888
147971
  return { output: "The uncovered files changed after the card was shown; nothing was recorded. Ask again.", isError: true };
147889
147972
  }
147890
- const waiver = await recordCoverageWaiver(prepared.dir, confirmation.quote, new Date(confirmation.at), confirmation.confirmedVia);
147973
+ const waiver = await recordCoverageWaiver(prepared.dir, confirmation.quote, new Date(confirmation.at), confirmation.confirmedVia, cardPressFields(confirmation));
147891
147974
  if (waiver === undefined)
147892
147975
  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.` };
147976
+ 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
147977
  }
147895
147978
  };
147896
147979
  }
@@ -147898,7 +147981,7 @@ function waiveReadinessSpec(seams) {
147898
147981
  const readiness = () => ({ prLookup: seams.prLookup ?? trackerPrLookup2, prFiles: seams.prFiles ?? githubPrFiles });
147899
147982
  return {
147900
147983
  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.",
147984
+ 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
147985
  inputSchema: {
147903
147986
  type: "object",
147904
147987
  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 +148033,8 @@ function waiveReadinessSpec(seams) {
147950
148033
  if (current === undefined || current.status !== "fail" || current.evidence !== prepared.evidence) {
147951
148034
  return { output: "The readiness item changed after the card was shown; nothing was recorded. Ask again.", isError: true };
147952
148035
  }
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.` };
148036
+ const added = await waiveReadiness(prepared.dir, [prepared.item], confirmation.quote, { ...readiness(), now: new Date(confirmation.at), confirmedVia: confirmation.confirmedVia, ...cardPressFields(confirmation) });
148037
+ 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
148038
  }
147956
148039
  };
147957
148040
  }
@@ -147959,7 +148042,7 @@ function decisionSpec(kind) {
147959
148042
  const mutation = kind === "mutation-waiver";
147960
148043
  return {
147961
148044
  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.",
148045
+ 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
148046
  inputSchema: {
147964
148047
  type: "object",
147965
148048
  properties: {
@@ -147972,11 +148055,12 @@ function decisionSpec(kind) {
147972
148055
  },
147973
148056
  async buildCard(input, ctx) {
147974
148057
  const ref = packageRef(input);
147975
- const reason = reasonOf2(input);
148058
+ const given = reasonOf2(input);
147976
148059
  if (ref === undefined)
147977
148060
  return { refusal: "package is required" };
147978
- if (reason === undefined)
147979
- return { refusal: "reason is required" };
148061
+ if ("refusal" in given)
148062
+ return given;
148063
+ const { reason } = given;
147980
148064
  try {
147981
148065
  const dir = await resolvePackage(ref, ctx.projectRoot);
147982
148066
  const name = label3(dir, ctx.projectRoot);
@@ -147988,7 +148072,7 @@ function decisionSpec(kind) {
147988
148072
  return {
147989
148073
  title: `Waive the mutation pass for ${name}: ${pending} proposed mutant(s) stay unrun?`,
147990
148074
  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` : ""}`,
148075
+ `unrun mutants: ${pending} (research-evidence/mutants.json): ${cardSafe(subject.items.join(", "))}`,
147992
148076
  `head: ${cardSafe(subject.head ?? "not recorded")}`,
147993
148077
  `reason: ${cardSafe(reason)}`,
147994
148078
  "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 +148091,7 @@ function decisionSpec(kind) {
148007
148091
  `required (mode all): ${required.length} reviewer(s)`,
148008
148092
  `head: ${cardSafe(subject.head ?? "not recorded")}`,
148009
148093
  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` : ""}`,
148094
+ `not run: ${cardSafe(missing.join(", ") || "none")}`,
148011
148095
  `reason: ${cardSafe(reason)}`,
148012
148096
  "yes: the gate accepts leaving out exactly these reviewers at this head; dropping another one or a new head asks again"
148013
148097
  ],
@@ -148026,7 +148110,7 @@ function decisionSpec(kind) {
148026
148110
  if (JSON.stringify(again) !== JSON.stringify(prepared.subject)) {
148027
148111
  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
148112
  }
148029
- await recordOperatorDecision(prepared.dir, { kind, quote: confirmation.quote, reason: prepared.reason, confirmedVia: confirmation.confirmedVia, at: confirmation.at, subject: prepared.subject });
148113
+ await recordOperatorDecision(prepared.dir, { kind, quote: confirmation.quote, reason: prepared.reason, confirmedVia: confirmation.confirmedVia, ...cardPressFields(confirmation), at: confirmation.at, subject: prepared.subject });
148030
148114
  return { output: `${mutation ? "mutation pass waived" : "reviewer-set override recorded"} by the operator (${confirmation.confirmedVia}, ${confirmation.at}) in operator-decisions.json` };
148031
148115
  }
148032
148116
  };
@@ -148051,10 +148135,11 @@ function replyArgv(input) {
148051
148135
  argv.push("--round", String(input["round"]));
148052
148136
  return argv;
148053
148137
  }
148054
- function replySpec() {
148138
+ function replySpec(seams) {
148139
+ const pass = seams.replyPass ?? defaultReplyPass;
148055
148140
  return {
148056
148141
  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.",
148142
+ 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
148143
  inputSchema: {
148059
148144
  type: "object",
148060
148145
  properties: {
@@ -148075,15 +148160,20 @@ function replySpec() {
148075
148160
  if (!Array.isArray(argv))
148076
148161
  return argv;
148077
148162
  try {
148078
- await Promise.resolve().then(() => init_review());
148079
- const dry = await commentsReplyPass([...argv], true);
148163
+ const dry = await pass([...argv], true);
148080
148164
  const requests = dry.result.requests;
148081
148165
  if (requests.length === 0)
148082
148166
  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))}`);
148167
+ const bodies = [];
148168
+ for (const [index, request] of requests.entries()) {
148169
+ const shown = shownBody(request.body.body ?? "");
148170
+ if ("refusal" in shown)
148171
+ return { refusal: `Reply ${index + 1} ${shown.refusal}` };
148172
+ bodies.push(`reply ${index + 1}:`, ...shown.lines);
148173
+ }
148084
148174
  return {
148085
148175
  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"],
148176
+ 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
148177
  subject: `${cardSafe(dry.repo)}#${dry.number}`,
148088
148178
  prepared: { argv, requests: JSON.stringify(requests) }
148089
148179
  };
@@ -148094,11 +148184,10 @@ function replySpec() {
148094
148184
  async apply(_input, _ctx, confirmation, prepared) {
148095
148185
  if (prepared === undefined)
148096
148186
  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);
148187
+ const again = await pass([...prepared.argv], true);
148099
148188
  if (JSON.stringify(again.result.requests) !== prepared.requests)
148100
148189
  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);
148190
+ const posted = await pass([...prepared.argv], false);
148102
148191
  return { output: `posted ${posted.result.posted.length} reply(ies) (${confirmation.confirmedVia}, ${confirmation.at}); skipped ${posted.result.skipped.length}` };
148103
148192
  }
148104
148193
  };
@@ -148125,7 +148214,7 @@ function confirmPlanSpec() {
148125
148214
  `dispatches: ${estimate.dispatches} (${estimate.roles} roles, ${estimate.slices} slices, ${estimate.sequentialBatches} batch(es) one after another)`,
148126
148215
  `expected duration: ${estimate.minutes.min}-${estimate.minutes.max} min`,
148127
148216
  `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)`),
148217
+ ...estimate.perRole.map((role) => ` ${cardSafe(role.role)}: ${role.slices} slice(s), ${role.waves} wave(s)`),
148129
148218
  "yes: this plan may run; a re-cut plan asks again"
148130
148219
  ],
148131
148220
  subject: `package ${name}`,
@@ -148141,7 +148230,7 @@ function confirmPlanSpec() {
148141
148230
  const estimate = await estimatePlan(prepared.dir);
148142
148231
  if (planKeyOf2(estimate) !== prepared.planKey)
148143
148232
  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 });
148233
+ const record = await recordPlanConfirmation(prepared.dir, estimate, { quote: confirmation.quote, confirmedVia: confirmation.confirmedVia, ...cardPressFields(confirmation), at: confirmation.at });
148145
148234
  return { output: `plan confirmed by the operator (${record.confirmedVia}, ${record.at}): ${record.dispatches} dispatch(es), ${record.minutes[0]}-${record.minutes[1]} min` };
148146
148235
  }
148147
148236
  };
@@ -148163,7 +148252,7 @@ function decidePremiseSpec() {
148163
148252
  },
148164
148253
  async buildCard(input, ctx) {
148165
148254
  const ref = packageRef(input);
148166
- const reason = reasonOf2(input);
148255
+ const given = reasonOf2(input);
148167
148256
  const finding = typeof input["finding"] === "string" ? input["finding"].trim() : "";
148168
148257
  const answer = input["answer"];
148169
148258
  if (ref === undefined)
@@ -148172,8 +148261,9 @@ function decidePremiseSpec() {
148172
148261
  return { refusal: "finding is required" };
148173
148262
  if (answer !== "uphold" && answer !== "dismiss")
148174
148263
  return { refusal: 'answer must be "uphold" or "dismiss"' };
148175
- if (reason === undefined)
148176
- return { refusal: "reason is required" };
148264
+ if ("refusal" in given)
148265
+ return given;
148266
+ const { reason } = given;
148177
148267
  try {
148178
148268
  const dir = await resolvePackage(ref, ctx.projectRoot);
148179
148269
  const held = await findHeldFinding(dir, finding);
@@ -148205,12 +148295,196 @@ function decidePremiseSpec() {
148205
148295
  return { output: "The finding changed after the card was shown; nothing was recorded. Ask again.", isError: true };
148206
148296
  }
148207
148297
  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}`) });
148298
+ 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
148299
  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
148300
  `) };
148211
148301
  }
148212
148302
  };
148213
148303
  }
148304
+ function completionDispositions(input) {
148305
+ const raw = input["dispositions"];
148306
+ if (raw === undefined)
148307
+ return [];
148308
+ if (!Array.isArray(raw))
148309
+ return { refusal: "dispositions must be an array of { finding, disposition, evidence? }" };
148310
+ const records = [];
148311
+ for (const [index, entry] of raw.entries()) {
148312
+ const item = typeof entry === "object" && entry !== null ? entry : {};
148313
+ const finding = typeof item["finding"] === "string" ? item["finding"].trim() : "";
148314
+ const state = item["disposition"];
148315
+ const evidence = item["evidence"];
148316
+ if (finding === "")
148317
+ return { refusal: `dispositions[${index}]: finding is required` };
148318
+ if (typeof state !== "string" || !FINDING_DISPOSITION_STATES.includes(state)) {
148319
+ return { refusal: `dispositions[${index}]: disposition must be one of ${FINDING_DISPOSITION_STATES.join(", ")}` };
148320
+ }
148321
+ if (evidence !== undefined && typeof evidence !== "string")
148322
+ return { refusal: `dispositions[${index}]: evidence must be text` };
148323
+ const text = evidence?.trim();
148324
+ if (finding.length > MAX_DISPOSITION_FINDING_CHARS)
148325
+ return { refusal: `dispositions[${index}]: finding is longer than ${MAX_DISPOSITION_FINDING_CHARS} characters` };
148326
+ if (text !== undefined && text.length > MAX_DISPOSITION_EVIDENCE_CHARS) {
148327
+ return { refusal: `dispositions[${index}]: evidence is ${text.length} characters; the card shows it whole, so the limit is ${MAX_DISPOSITION_EVIDENCE_CHARS}` };
148328
+ }
148329
+ for (const [field, value] of [["finding", finding], ["evidence", text ?? ""]]) {
148330
+ if (hiddenInCard(value))
148331
+ return { refusal: `dispositions[${index}]: ${field} ${HIDDEN_IN_CARD}` };
148332
+ }
148333
+ records.push({ finding, state, ...text === undefined || text === "" ? {} : { evidence: text } });
148334
+ }
148335
+ return records;
148336
+ }
148337
+ async function completionFacts(dir) {
148338
+ const manifest = JSON.parse(readFileSync39(path337.join(dir, "manifest.json"), "utf8"));
148339
+ const recorded = typeof manifest.target?.head === "string" && manifest.target.head !== "" ? manifest.target.head : undefined;
148340
+ const report = readFileSync39(path337.join(dir, "report.md"), "utf8");
148341
+ const evidenceLine = evidenceLevelLine(report);
148342
+ const waivers = (manifest.readiness_waivers ?? []).map((waiver) => `waiver: readiness item ${cardSafe(waiver.item)}: "${quoteWhole(waiver.operator_quote)}"`);
148343
+ let coverage;
148344
+ try {
148345
+ coverage = readCoverageWaiver(JSON.parse(readFileSync39(path337.join(dir, "research.json"), "utf8")).dispatch);
148346
+ } catch (error) {
148347
+ if (error.code !== "ENOENT") {
148348
+ waivers.push(`waiver: coverage, could not read research.json (${cardSafe(error instanceof Error ? error.message : String(error))}); the research gate will refuse`);
148349
+ }
148350
+ coverage = undefined;
148351
+ }
148352
+ if (coverage !== undefined)
148353
+ waivers.push(`waiver: coverage, ${coverage.files.length} file(s) not reviewed: "${quoteWhole(coverage.operatorQuote)}"`);
148354
+ for (const decision of await readOperatorDecisions(dir)) {
148355
+ const items = decision.subject?.items ?? [];
148356
+ const what = decision.kind === "mutation-waiver" ? "mutation pass not run" : "reviewer override, left out";
148357
+ waivers.push(`waiver: ${what}${items.length === 0 ? "" : ` ${cardSafe(items.join(", "))}`}: "${quoteWhole(decision.quote)}" (${decision.confirmedVia})`);
148358
+ }
148359
+ return {
148360
+ reviewId: manifest.reviewId ?? path337.basename(dir),
148361
+ head: recorded === undefined ? undefined : cardSafe(recorded),
148362
+ evidence: evidenceLine === undefined ? "evidence level: the report has no `Evidence level:` line; the gate will refuse" : cardSafe(evidenceLine),
148363
+ waivers,
148364
+ status: manifest.status ?? ""
148365
+ };
148366
+ }
148367
+ function quoteWhole(text) {
148368
+ return hiddenInCard(text) ? `${cardSafe(text)} [the stored quote holds characters this card hides or rewrites]` : text;
148369
+ }
148370
+ function completeSpec(seams) {
148371
+ return {
148372
+ name: "review_complete",
148373
+ 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.",
148374
+ inputSchema: {
148375
+ type: "object",
148376
+ properties: {
148377
+ package: packageSchema,
148378
+ dispositions: dispositionsSchema
148379
+ },
148380
+ required: ["package"],
148381
+ additionalProperties: false
148382
+ },
148383
+ async buildCard(input, ctx) {
148384
+ const ref = packageRef(input);
148385
+ if (ref === undefined)
148386
+ return { refusal: "package is required" };
148387
+ const dispositions = completionDispositions(input);
148388
+ if ("refusal" in dispositions)
148389
+ return dispositions;
148390
+ try {
148391
+ const dir = await resolvePackage(ref, ctx.projectRoot);
148392
+ const facts = await completionFacts(dir);
148393
+ if (facts.status === "closed")
148394
+ return { refusal: `${facts.reviewId} is already complete` };
148395
+ if (dispositions.length > 0)
148396
+ await checkDispositions(dir, dispositions);
148397
+ const name = label3(dir, ctx.projectRoot);
148398
+ const at = facts.head === undefined ? "" : ` at ${facts.head.slice(0, 12)}`;
148399
+ return {
148400
+ title: `Complete review ${cardSafe(facts.reviewId)}${at}?`,
148401
+ lines: [
148402
+ facts.head === undefined ? "reviewed head: not recorded (the package does not carry a commit)" : `reviewed head: ${facts.head}`,
148403
+ facts.evidence,
148404
+ ...facts.waivers.length === 0 ? ["waivers: none"] : facts.waivers,
148405
+ ...dispositions.map(dispositionLine),
148406
+ "yes: the head, evidence-level, research and readiness gates run now; a refusal closes nothing"
148407
+ ],
148408
+ subject: `package ${name}${facts.head === undefined ? "" : `, head ${facts.head.slice(0, 12)}`}`,
148409
+ prepared: { dir, head: facts.head, digest: factsDigest(facts), dispositions }
148410
+ };
148411
+ } catch (error) {
148412
+ return failed(error);
148413
+ }
148414
+ },
148415
+ async apply(_input, ctx, confirmation, prepared) {
148416
+ if (prepared === undefined)
148417
+ return { output: "review_complete: nothing prepared for this card", isError: true };
148418
+ let facts;
148419
+ try {
148420
+ facts = await completionFacts(prepared.dir);
148421
+ } catch (error) {
148422
+ return { output: `review_complete refused: ${errorText4(error)} \u2014 nothing was recorded`, isError: true };
148423
+ }
148424
+ if (factsDigest(facts) !== prepared.digest) {
148425
+ return { output: "The head, the evidence level or the waivers changed after the card was shown; nothing was recorded. Ask again.", isError: true };
148426
+ }
148427
+ const run = await runCompletionGates(ctx.projectRoot, prepared, confirmation.at, seams);
148428
+ if (!run.closed)
148429
+ return { output: `review_complete refused: ${errorText4(run.gateError)} \u2014 nothing was recorded`, isError: true };
148430
+ const recordError = await writeCompletionRecord(prepared.dir, {
148431
+ quote: confirmation.quote,
148432
+ confirmedVia: confirmation.confirmedVia,
148433
+ ...cardPressFields(confirmation),
148434
+ at: confirmation.at,
148435
+ head: prepared.head,
148436
+ evidence: facts.evidence,
148437
+ waivers: facts.waivers,
148438
+ dispositions: prepared.dispositions
148439
+ });
148440
+ return completionOutput(`review ${facts.reviewId} complete${prepared.head === undefined ? "" : ` at ${prepared.head.slice(0, 12)}`} (${confirmation.confirmedVia}, ${confirmation.at})`, run, recordError);
148441
+ }
148442
+ };
148443
+ }
148444
+ async function runCompletionGates(projectRoot, prepared, at, seams) {
148445
+ let done;
148446
+ let gateError;
148447
+ try {
148448
+ done = await completeReviewGated(projectRoot, prepared.dir, {
148449
+ dispositions: prepared.dispositions,
148450
+ prLookup: seams.prLookup ?? trackerPrLookup2,
148451
+ prFiles: seams.prFiles ?? githubPrFiles,
148452
+ now: new Date(at)
148453
+ });
148454
+ } catch (error) {
148455
+ gateError = error;
148456
+ }
148457
+ const closed = done !== undefined || (await completionFacts(prepared.dir).catch(() => {
148458
+ return;
148459
+ }))?.status === "closed";
148460
+ return { done, gateError, closed };
148461
+ }
148462
+ async function writeCompletionRecord(dir, record) {
148463
+ try {
148464
+ await writeFileAtomic(path337.join(dir, COMPLETION_FILE), `${JSON.stringify(record, null, 2)}
148465
+ `);
148466
+ return;
148467
+ } catch (error) {
148468
+ return error;
148469
+ }
148470
+ }
148471
+ function completionOutput(closedLine, run, recordError) {
148472
+ const problems = [];
148473
+ if (recordError !== undefined)
148474
+ problems.push(`the round is closed but the confirmation record could not be written: ${errorText4(recordError)}`);
148475
+ if (run.gateError !== undefined)
148476
+ problems.push(`the round is closed but its bookkeeping stopped: ${errorText4(run.gateError)}`);
148477
+ return {
148478
+ output: [
148479
+ closedLine,
148480
+ ...problems,
148481
+ ...run.done?.readiness === undefined ? [] : [readinessLine(run.done.readiness)],
148482
+ ...(run.done?.warnings ?? []).map((warning) => `warning: ${warning}`)
148483
+ ].join(`
148484
+ `),
148485
+ ...problems.length === 0 ? {} : { isError: true }
148486
+ };
148487
+ }
148214
148488
  function buildReviewConfirmSpecs(seams = {}) {
148215
148489
  return [
148216
148490
  chunksAnswerSpec(),
@@ -148219,25 +148493,33 @@ function buildReviewConfirmSpecs(seams = {}) {
148219
148493
  waiveReadinessSpec(seams),
148220
148494
  decisionSpec("mutation-waiver"),
148221
148495
  decisionSpec("reviewer-override"),
148222
- replySpec(),
148496
+ replySpec(seams),
148223
148497
  confirmPlanSpec(),
148224
- decidePremiseSpec()
148498
+ decidePremiseSpec(),
148499
+ completeSpec(seams)
148225
148500
  ];
148226
148501
  }
148227
148502
  function reviewConfirmSpecs() {
148228
148503
  return buildReviewConfirmSpecs();
148229
148504
  }
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) => {
148505
+ var packageSchema, label3 = (dir, projectRoot) => path337.relative(projectRoot, dir) || ".", failed = (error) => ({ refusal: error instanceof Error ? error.message : String(error) }), reasonOf2 = (input) => {
148231
148506
  const value = input["reason"];
148232
- return typeof value === "string" && value.trim() !== "" ? value.trim() : undefined;
148233
- }, REPLY_PREVIEW = 6;
148507
+ const text = typeof value === "string" ? value.trim() : "";
148508
+ if (text === "")
148509
+ return { refusal: "reason is required" };
148510
+ return hiddenInCard(text) ? { refusal: `reason ${HIDDEN_IN_CARD}` } : { reason: text };
148511
+ }, 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
148512
  var init_operator_confirm_review = __esm(() => {
148513
+ init_types4();
148235
148514
  init_service11();
148236
148515
  init_review_path_flags();
148237
148516
  init_blast_radius();
148238
148517
  init_chunks();
148239
148518
  init_chunks_estimate();
148519
+ init_complete_gated();
148240
148520
  init_coverage_complete();
148521
+ init_evidence_level();
148522
+ init_fs();
148241
148523
  init_managed();
148242
148524
  init_publish();
148243
148525
  init_readiness();
@@ -148246,6 +148528,24 @@ var init_operator_confirm_review = __esm(() => {
148246
148528
  init_premise_question();
148247
148529
  init_plan_confirmation();
148248
148530
  packageSchema = { type: "string", description: "The review package: its directory or its review id." };
148531
+ dispositionsSchema = {
148532
+ type: "array",
148533
+ 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.",
148534
+ items: {
148535
+ type: "object",
148536
+ properties: {
148537
+ 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." },
148538
+ disposition: { type: "string", enum: [...FINDING_DISPOSITION_STATES] },
148539
+ evidence: {
148540
+ type: "string",
148541
+ maxLength: MAX_DISPOSITION_EVIDENCE_CHARS,
148542
+ 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.`
148543
+ }
148544
+ },
148545
+ required: ["finding", "disposition"],
148546
+ additionalProperties: false
148547
+ }
148548
+ };
148249
148549
  });
148250
148550
 
148251
148551
  // src/commands/operator-confirm-tools.ts
@@ -148297,7 +148597,7 @@ function operatorConfirmTool(spec, binding) {
148297
148597
  const card = { title: built.title, lines: [...built.lines], subject: built.subject };
148298
148598
  const shown = renderOperatorCard(card);
148299
148599
  const token = randomUUID32();
148300
- pending.set(token, { input: key(input), card, shown, prepared: built.prepared });
148600
+ pending.set(token, { input: key(input), card, shown, prepared: built.prepared, issuedAt: now().getTime() });
148301
148601
  return { card: shown, title: card.title, token };
148302
148602
  },
148303
148603
  confirmationDeclined: (token) => {
@@ -148306,16 +148606,35 @@ function operatorConfirmTool(spec, binding) {
148306
148606
  invoke: async (input, ctx) => {
148307
148607
  const token = ctx?.confirmationToken;
148308
148608
  const entry = token === undefined ? undefined : pending.get(token);
148309
- if (token !== undefined)
148310
- pending.delete(token);
148609
+ const refuse = (why) => ({ output: `${spec.name}: ${why} \u2014 nothing was changed`, isError: true });
148311
148610
  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 };
148611
+ if (token !== undefined)
148612
+ pending.delete(token);
148613
+ return refuse("no operator-confirmed card for this request");
148614
+ }
148615
+ pending.delete(token);
148616
+ const remote = ctx?.confirmationVia === "remote";
148617
+ let pressedBy;
148618
+ let pressedAt;
148619
+ if (remote) {
148620
+ pressedBy = ctx?.confirmationPressedBy;
148621
+ pressedAt = ctx?.confirmationPressedAt;
148622
+ const allowed = binding.allowedUserIds?.();
148623
+ if (pressedBy === undefined || pressedAt === undefined)
148624
+ return refuse("the remote answer did not say who pressed it");
148625
+ if (allowed === undefined || !allowed.includes(pressedBy)) {
148626
+ return refuse(`Telegram user ${pressedBy} is not on the allowed list`);
148627
+ }
148628
+ if (now().getTime() - entry.issuedAt > REMOTE_CARD_TTL_MS) {
148629
+ return refuse("the card is older than 1 hour; ask again");
148630
+ }
148313
148631
  }
148314
148632
  const confirmation = {
148315
148633
  quote: `${entry.card.title}: yes`,
148316
148634
  answer: "yes",
148317
148635
  at: now().toISOString(),
148318
- confirmedVia: ctx?.confirmationVia === "remote" ? "remote-card" : "tui-card",
148636
+ confirmedVia: remote ? "remote-card" : "tui-card",
148637
+ ...remote ? { pressedBy, pressedAt } : {},
148319
148638
  subject: entry.card.subject,
148320
148639
  card: entry.shown
148321
148640
  };
@@ -148334,10 +148653,11 @@ function operatorConfirmSpecs() {
148334
148653
  function operatorConfirmTools(binding, specs = operatorConfirmSpecs()) {
148335
148654
  return specs.map((spec) => operatorConfirmTool(spec, binding));
148336
148655
  }
148337
- var OPERATOR_CONFIRM_NOTE;
148656
+ var REMOTE_CARD_TTL_MS, OPERATOR_CONFIRM_NOTE;
148338
148657
  var init_operator_confirm_tools = __esm(() => {
148339
148658
  init_operator_confirm_other();
148340
148659
  init_operator_confirm_review();
148660
+ REMOTE_CARD_TTL_MS = 60 * 60 * 1000;
148341
148661
  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
148662
  });
148343
148663
 
@@ -149520,7 +149840,7 @@ var init_workspace_lifecycle_tool = __esm(() => {
149520
149840
  });
149521
149841
 
149522
149842
  // src/mcp-servers/catalog.ts
149523
- import { createHash as createHash85 } from "crypto";
149843
+ import { createHash as createHash86 } from "crypto";
149524
149844
  function canonicalJson4(value) {
149525
149845
  if (value === undefined)
149526
149846
  return "null";
@@ -149538,7 +149858,7 @@ function toolDefinitionFingerprint(entry) {
149538
149858
  description: entry.description ?? null,
149539
149859
  inputSchema: entry.inputSchema ?? null
149540
149860
  });
149541
- return createHash85("sha256").update(canonical).digest("hex");
149861
+ return createHash86("sha256").update(canonical).digest("hex");
149542
149862
  }
149543
149863
  function buildFqn(server, rawName) {
149544
149864
  return `${server}${FQN_DELIMITER}${rawName}`;
@@ -149786,11 +150106,11 @@ var init_tools2 = __esm(() => {
149786
150106
  });
149787
150107
 
149788
150108
  // src/commands/review-arbiter-config.ts
149789
- import { readFileSync as readFileSync39 } from "fs";
150109
+ import { readFileSync as readFileSync40 } from "fs";
149790
150110
  import path339 from "path";
149791
150111
  function reviewArbiterBlock(cwd) {
149792
150112
  try {
149793
- const parsed = JSON.parse(readFileSync39(path339.join(cwd, REVIEW_GATE_CONFIG_PATH), "utf8"));
150113
+ const parsed = JSON.parse(readFileSync40(path339.join(cwd, REVIEW_GATE_CONFIG_PATH), "utf8"));
149794
150114
  if (typeof parsed !== "object" || parsed === null)
149795
150115
  return;
149796
150116
  const review = parsed["review"];
@@ -150275,8 +150595,8 @@ var init_arbiter_rules = __esm(() => {
150275
150595
  });
150276
150596
 
150277
150597
  // src/review/arbiter-rules.ts
150278
- import { createHash as createHash86 } from "crypto";
150279
- import { existsSync as existsSync77, readFileSync as readFileSync40 } from "fs";
150598
+ import { createHash as createHash87 } from "crypto";
150599
+ import { existsSync as existsSync77, readFileSync as readFileSync41 } from "fs";
150280
150600
  import path340 from "path";
150281
150601
  function isArbiterKind(value) {
150282
150602
  return typeof value === "string" && ARBITER_KINDS.includes(value);
@@ -150347,7 +150667,7 @@ function parseArbiterRules(raw, source) {
150347
150667
  });
150348
150668
  if (errors.length > 0)
150349
150669
  return { ok: false, errors };
150350
- const hash = createHash86("sha256").update(JSON.stringify({ version, rules })).digest("hex").slice(0, 12);
150670
+ const hash = createHash87("sha256").update(JSON.stringify({ version, rules })).digest("hex").slice(0, 12);
150351
150671
  return { ok: true, rules: { version, rules, source, hash } };
150352
150672
  }
150353
150673
  function parseWhen2(raw, where, errors) {
@@ -150426,7 +150746,7 @@ function loadArbiterRules(cwd) {
150426
150746
  return { rules: bundledArbiterRules() };
150427
150747
  let raw;
150428
150748
  try {
150429
- raw = JSON.parse(readFileSync40(file, "utf8"));
150749
+ raw = JSON.parse(readFileSync41(file, "utf8"));
150430
150750
  } catch (error) {
150431
150751
  return { error: `${PROJECT_RULES_PATH} cannot be read as JSON: ${error instanceof Error ? error.message : String(error)}` };
150432
150752
  }
@@ -150471,7 +150791,7 @@ var init_arbiter_rules2 = __esm(() => {
150471
150791
  });
150472
150792
 
150473
150793
  // src/commands/review-arbiter.ts
150474
- import { createHash as createHash87 } from "crypto";
150794
+ import { createHash as createHash88 } from "crypto";
150475
150795
  function parseQuestion(request) {
150476
150796
  const policy = request.policy;
150477
150797
  if (policy === undefined)
@@ -150592,7 +150912,7 @@ function checkModelAnswer(answer, question, rules) {
150592
150912
  }
150593
150913
  function cacheKey(question) {
150594
150914
  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");
150915
+ return createHash88("sha256").update(JSON.stringify([question.kind, facts, question.optionCount, question.recommended])).digest("hex");
150596
150916
  }
150597
150917
  function costOf2(prices, modelId, inputTokens, outputTokens) {
150598
150918
  const price = prices[modelId];
@@ -151068,7 +151388,7 @@ var init_shell_events = __esm(() => {
151068
151388
  // src/lib/shell-permissions.ts
151069
151389
  import { existsSync as existsSync78 } from "fs";
151070
151390
  import path341 from "path";
151071
- import { createHash as createHash88 } from "crypto";
151391
+ import { createHash as createHash89 } from "crypto";
151072
151392
  function validateShellPattern(pattern) {
151073
151393
  const trimmed = pattern.trim();
151074
151394
  if (trimmed.length === 0) {
@@ -151303,7 +151623,7 @@ function shellPermissionsFingerprint(dir) {
151303
151623
  if (!read.ok) {
151304
151624
  return "";
151305
151625
  }
151306
- return createHash88("sha256").update(read.text, "utf8").digest("hex");
151626
+ return createHash89("sha256").update(read.text, "utf8").digest("hex");
151307
151627
  } catch {
151308
151628
  return "";
151309
151629
  }
@@ -153394,7 +153714,7 @@ var init_child_model_request = __esm(() => {
153394
153714
  });
153395
153715
 
153396
153716
  // src/harness/tool/builtin/spawn-subagent-tool.ts
153397
- import { createHash as createHash89, randomUUID as randomUUID37 } from "crypto";
153717
+ import { createHash as createHash90, randomUUID as randomUUID37 } from "crypto";
153398
153718
  import { execFile as execFile14 } from "child_process";
153399
153719
  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
153720
  import { tmpdir as tmpdir13 } from "os";
@@ -153700,7 +154020,7 @@ function readExternalRuntimeRequest(input) {
153700
154020
  return agent === block.agent ? block : { ...block, agent };
153701
154021
  }
153702
154022
  function sha25611(text) {
153703
- return createHash89("sha256").update(text).digest("hex");
154023
+ return createHash90("sha256").update(text).digest("hex");
153704
154024
  }
153705
154025
  async function resolveSubagentCwd(rawCwd, parentCwd) {
153706
154026
  if (rawCwd === undefined) {
@@ -155121,7 +155441,7 @@ var init_http_headers = __esm(() => {
155121
155441
  });
155122
155442
 
155123
155443
  // src/mcp-servers/trust.ts
155124
- import { createHash as createHash90 } from "crypto";
155444
+ import { createHash as createHash91 } from "crypto";
155125
155445
  import path348 from "path";
155126
155446
  function trustFile(configDir) {
155127
155447
  return path348.join(configDir ?? ensureKeryxConfigDir(), "mcp-servers-trust.json");
@@ -155144,7 +155464,7 @@ function serverFingerprint(server) {
155144
155464
  }
155145
155465
  }
155146
155466
  });
155147
- return createHash90("sha256").update(material).digest("hex").slice(0, 32);
155467
+ return createHash91("sha256").update(material).digest("hex").slice(0, 32);
155148
155468
  }
155149
155469
  function sortedKeys(values) {
155150
155470
  return Object.entries(values ?? {}).sort(([a], [b]) => a.localeCompare(b));
@@ -158382,7 +158702,7 @@ var init_references_check = __esm(() => {
158382
158702
  init_project_skills();
158383
158703
  init_references_config();
158384
158704
  init_references_section();
158385
- DOCUMENT_CITATION = /`([^`\s]+\.md)`/g;
158705
+ DOCUMENT_CITATION = /`([^`\s#]+\.md)(?:#[^`\s]*)?`/g;
158386
158706
  });
158387
158707
 
158388
158708
  // src/gdskills/project-context-status.ts
@@ -160475,7 +160795,7 @@ var init_help_groups = __esm(() => {
160475
160795
  kind: "slash",
160476
160796
  name: "/decisions",
160477
160797
  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)."
160798
+ 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
160799
  },
160480
160800
  {
160481
160801
  kind: "slash",
@@ -160493,7 +160813,7 @@ var init_help_groups = __esm(() => {
160493
160813
  kind: "slash",
160494
160814
  name: "/remote-control",
160495
160815
  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].'
160816
+ 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
160817
  },
160498
160818
  {
160499
160819
  kind: "slash",
@@ -164653,7 +164973,7 @@ var init_extract3 = __esm(() => {
164653
164973
  });
164654
164974
 
164655
164975
  // src/product/corpus.ts
164656
- import { createHash as createHash91 } from "crypto";
164976
+ import { createHash as createHash92 } from "crypto";
164657
164977
  import { readdir as readdir67, readFile as readFile191 } from "fs/promises";
164658
164978
  import path366 from "path";
164659
164979
  function flowsDir(cwd) {
@@ -164698,7 +165018,7 @@ function digestOf4(content) {
164698
165018
  return "absent";
164699
165019
  if (content === "unreadable")
164700
165020
  return "unreadable";
164701
- return createHash91("sha256").update(content).digest("hex");
165021
+ return createHash92("sha256").update(content).digest("hex");
164702
165022
  }
164703
165023
  async function readBytes(file) {
164704
165024
  try {
@@ -164732,7 +165052,7 @@ async function corpusFingerprint(cwd) {
164732
165052
  const specification = await readBytes(path366.join(dir, "specification.md"));
164733
165053
  lines.push(`file\x00docs/requirements/${name}/specification.md\x00${digestOf4(specification)}`);
164734
165054
  }
164735
- return createHash91("sha256").update([...lines].sort().join(`
165055
+ return createHash92("sha256").update([...lines].sort().join(`
164736
165056
  `)).digest("hex");
164737
165057
  }
164738
165058
  function idOrder(intent) {
@@ -166909,7 +167229,8 @@ function mapApprovalAnswer(got, offered) {
166909
167229
  decision: yes ? "allow" : "deny",
166910
167230
  always: o.decision === "always" && offered,
166911
167231
  ...approvalId === undefined ? {} : { approvalId },
166912
- ...fromId === undefined ? {} : { fromId }
167232
+ ...fromId === undefined ? {} : { fromId },
167233
+ ...!yes && o.undelivered === true ? { undelivered: true } : {}
166913
167234
  };
166914
167235
  }
166915
167236
  function composeApprovalPrompt(prompt) {
@@ -166966,6 +167287,13 @@ function operatorCardTooLongForTopic(tool, inputJson, meta) {
166966
167287
  return false;
166967
167288
  return redactSensitiveText(describeApprovalForTopic(tool, inputJson, meta)).trim().length > MAX_APPROVAL_PROMPT_CHARS;
166968
167289
  }
167290
+ function mirrorUndeliveredNote(bridge) {
167291
+ if (bridge === undefined || !bridge.active)
167292
+ return `${MIRROR_NOT_DELIVERED}: remote control is not on in this shell. Answer it here.`;
167293
+ if (!bridge.connected)
167294
+ return `${MIRROR_NOT_DELIVERED}: the remote channel is not connected. Answer it here.`;
167295
+ return;
167296
+ }
166969
167297
 
166970
167298
  class RemoteBridge {
166971
167299
  options;
@@ -167070,6 +167398,9 @@ class RemoteBridge {
167070
167398
  get active() {
167071
167399
  return this.client !== undefined;
167072
167400
  }
167401
+ get connected() {
167402
+ return this.client?.connected === true;
167403
+ }
167073
167404
  get telegramTurnActive() {
167074
167405
  return this.tgTurn;
167075
167406
  }
@@ -167484,7 +167815,7 @@ class RemoteBridge {
167484
167815
  const client = this.client;
167485
167816
  if (client === undefined || !client.connected) {
167486
167817
  this.push("approval", "denied: not connected");
167487
- return { decision: "deny", always: false };
167818
+ return { decision: "deny", always: false, undelivered: true };
167488
167819
  }
167489
167820
  this.push("approval", "asked in the topic");
167490
167821
  let answer;
@@ -167563,7 +167894,7 @@ class RemoteBridge {
167563
167894
  await this.client?.reportApprovalResult?.(approvalId, remembered);
167564
167895
  }
167565
167896
  }
167566
- var TG_SOURCE = "tg", REMOTE_EVENT_LIMIT = 20, REMOTE_REPLY_MAX_CHARS = 16000, EVENT_PREVIEW_CHARS = 80, HISTORY_PACE_MS = 2000;
167897
+ 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
167898
  var init_shell_bridge = __esm(() => {
167568
167899
  init_approval_cause();
167569
167900
  init_service2();
@@ -167574,45 +167905,6 @@ var init_shell_bridge = __esm(() => {
167574
167905
  init_history2();
167575
167906
  });
167576
167907
 
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
167908
  // src/remote/history-host.ts
167617
167909
  function sessionEntryOf(resumed) {
167618
167910
  return { resumed, kind: resumed ? "resumed" : "new" };
@@ -168974,6 +169266,212 @@ var init_channels_surface = __esm(() => {
168974
169266
  TOKEN_CHAR = /^[A-Za-z0-9_:-]$/;
168975
169267
  });
168976
169268
 
169269
+ // src/tui/approval-mirror.ts
169270
+ function isTopicPress(answer) {
169271
+ return answer.decision === "allow" || answer.fromId !== undefined;
169272
+ }
169273
+ async function raceApproval(options) {
169274
+ const dockAbort = new AbortController;
169275
+ const topicAbort = new AbortController;
169276
+ let topicAnswer;
169277
+ let settled = false;
169278
+ let started = false;
169279
+ const race = {
169280
+ signal: AbortSignal.any([options.turnSignal, dockAbort.signal]),
169281
+ opened: () => {
169282
+ if (started || settled)
169283
+ return;
169284
+ started = true;
169285
+ options.topic(topicAbort.signal).then((answer) => {
169286
+ if (settled)
169287
+ return;
169288
+ if (answer.undelivered === true)
169289
+ options.onUndelivered?.();
169290
+ else if (!isTopicPress(answer) && !topicAbort.signal.aborted)
169291
+ options.onTopicEnded?.();
169292
+ if (!isTopicPress(answer))
169293
+ return;
169294
+ topicAnswer = answer;
169295
+ dockAbort.abort("topic");
169296
+ }).catch(() => {
169297
+ return;
169298
+ });
169299
+ },
169300
+ topicWon: () => topicAnswer !== undefined
169301
+ };
169302
+ try {
169303
+ const result = await options.dock(race);
169304
+ if (topicAnswer !== undefined)
169305
+ return { from: "topic", answer: topicAnswer };
169306
+ return { from: "dock", result };
169307
+ } finally {
169308
+ settled = true;
169309
+ if (topicAnswer === undefined)
169310
+ topicAbort.abort(options.turnSignal.aborted ? "cancelled" : "shell");
169311
+ }
169312
+ }
169313
+
169314
+ // src/tui/operator-confirmations.ts
169315
+ class OperatorConfirmationLog {
169316
+ now;
169317
+ items = [];
169318
+ nextId = 1;
169319
+ listeners = new Set;
169320
+ constructor(now = () => Date.now()) {
169321
+ this.now = now;
169322
+ }
169323
+ ask(question, mirrored) {
169324
+ const id = this.nextId++;
169325
+ this.items.push({ id, question, askedAt: this.now(), mirrored });
169326
+ this.changed();
169327
+ return id;
169328
+ }
169329
+ answer(id, result) {
169330
+ const at = this.items.findIndex((item) => item.id === id);
169331
+ const entry = this.items[at];
169332
+ if (entry === undefined || entry.answered !== undefined)
169333
+ return;
169334
+ this.items[at] = {
169335
+ ...entry,
169336
+ answered: { place: result.place, yes: result.yes, at: this.now(), ...result.pressedBy === undefined ? {} : { pressedBy: result.pressedBy } }
169337
+ };
169338
+ const answered = this.items.filter((item) => item.answered !== undefined);
169339
+ for (const old of answered.slice(0, Math.max(0, answered.length - ANSWERED_CARDS_KEPT))) {
169340
+ this.items.splice(this.items.indexOf(old), 1);
169341
+ }
169342
+ this.changed();
169343
+ }
169344
+ settle(id, outcome, turnAborted) {
169345
+ if (outcome === undefined)
169346
+ this.answer(id, { place: "withdrawn", yes: false });
169347
+ else if (outcome.from === "dock")
169348
+ this.answer(id, { place: turnAborted ? "withdrawn" : "shell", yes: outcome.result !== false });
169349
+ else
169350
+ this.answer(id, { place: "telegram", yes: outcome.answer.decision === "allow", ...outcome.answer.fromId === undefined ? {} : { pressedBy: outcome.answer.fromId } });
169351
+ }
169352
+ markUndelivered(id, reason = "undelivered") {
169353
+ const at = this.items.findIndex((item) => item.id === id);
169354
+ const entry = this.items[at];
169355
+ if (entry === undefined || entry.answered !== undefined || !entry.mirrored)
169356
+ return;
169357
+ this.items[at] = { ...entry, mirrored: false, ...reason === "ended" ? { topicEnded: true } : {} };
169358
+ this.changed();
169359
+ }
169360
+ waiting() {
169361
+ const now = this.now();
169362
+ return this.items.filter((item) => item.answered === undefined && item.mirrored && now - item.askedAt <= REMOTE_CARD_TTL_MS);
169363
+ }
169364
+ entries() {
169365
+ const open = this.items.filter((item) => item.answered === undefined);
169366
+ const done = this.items.filter((item) => item.answered !== undefined).reverse();
169367
+ return [...open, ...done];
169368
+ }
169369
+ subscribe(listener) {
169370
+ this.listeners.add(listener);
169371
+ return () => {
169372
+ this.listeners.delete(listener);
169373
+ };
169374
+ }
169375
+ changed() {
169376
+ for (const listener of [...this.listeners])
169377
+ listener();
169378
+ }
169379
+ }
169380
+ function age(ms) {
169381
+ const seconds = Math.max(0, Math.round(ms / 1000));
169382
+ if (seconds < 60)
169383
+ return `${seconds}s`;
169384
+ const minutes = Math.floor(seconds / 60);
169385
+ if (minutes < 60)
169386
+ return `${minutes}m`;
169387
+ return `${Math.floor(minutes / 60)}h${String(minutes % 60).padStart(2, "0")}m`;
169388
+ }
169389
+ function clock2(at) {
169390
+ return new Date(at).toISOString().slice(11, 19);
169391
+ }
169392
+ function clip7(text, max) {
169393
+ const flat = text.replace(/\s+/g, " ").trim();
169394
+ return flat.length <= max ? flat : `${flat.slice(0, Math.max(0, max - 1))}\u2026`;
169395
+ }
169396
+ function pendingConfirmationLine(entry, now) {
169397
+ const elapsed = now - entry.askedAt;
169398
+ const left = REMOTE_CARD_TTL_MS - elapsed;
169399
+ const token = left > 0 ? `${age(left)} left of the 1-hour token` : "Telegram token expired, answer in the shell";
169400
+ 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";
169401
+ return `${clip7(entry.question, 80)} \xB7 asked ${age(elapsed)} ago \xB7 ${token}${where}`;
169402
+ }
169403
+ function answeredConfirmationLine(entry) {
169404
+ const done = entry.answered;
169405
+ if (done === undefined)
169406
+ return clip7(entry.question, 80);
169407
+ const verdict = done.yes ? "yes" : "no";
169408
+ if (done.place === "telegram") {
169409
+ const who = done.pressedBy === undefined ? "a Telegram user" : `Telegram user ${done.pressedBy}`;
169410
+ return `${clip7(entry.question, 80)} \xB7 ${verdict} \xB7 pressed by ${who} at ${clock2(done.at)}`;
169411
+ }
169412
+ if (done.place === "withdrawn")
169413
+ return `${clip7(entry.question, 80)} \xB7 withdrawn (the turn stopped) at ${clock2(done.at)}`;
169414
+ return `${clip7(entry.question, 80)} \xB7 ${verdict} \xB7 answered in the shell at ${clock2(done.at)}`;
169415
+ }
169416
+ function confirmationLines(entries, now, heading = "Operator confirmations (Telegram cards)") {
169417
+ if (entries.length === 0)
169418
+ return [];
169419
+ const waiting = entries.filter((entry) => entry.answered === undefined);
169420
+ const answered = entries.filter((entry) => entry.answered !== undefined);
169421
+ const lines = [heading];
169422
+ for (const entry of waiting)
169423
+ lines.push(` waiting ${pendingConfirmationLine(entry, now)}`);
169424
+ for (const entry of answered)
169425
+ lines.push(` answered ${answeredConfirmationLine(entry)}`);
169426
+ return lines;
169427
+ }
169428
+ function waitingCardsText(waiting) {
169429
+ if (waiting <= 0)
169430
+ return;
169431
+ return `${waiting} card${waiting === 1 ? "" : "s"} waiting`;
169432
+ }
169433
+ function remoteApproval(fingerprint, fromId, now = () => new Date) {
169434
+ return { approved: true, fingerprint, via: "remote", ...fromId !== undefined ? { pressedBy: fromId } : {}, pressedAt: now().toISOString() };
169435
+ }
169436
+ async function askUnmirroredCard(log, question, turnSignal, ask) {
169437
+ const id = log.ask(question, false);
169438
+ let verdict;
169439
+ let answered = false;
169440
+ try {
169441
+ verdict = await ask();
169442
+ answered = true;
169443
+ return verdict;
169444
+ } finally {
169445
+ log.answer(id, { place: turnSignal.aborted || !answered ? "withdrawn" : "shell", yes: answered && verdict !== false });
169446
+ }
169447
+ }
169448
+ async function raceMirroredCard(options) {
169449
+ const id = options.question === undefined ? undefined : options.log.ask(options.question, true);
169450
+ let outcome;
169451
+ try {
169452
+ outcome = await raceApproval({
169453
+ turnSignal: options.turnSignal,
169454
+ ...id === undefined ? {} : {
169455
+ onUndelivered: () => {
169456
+ options.log.markUndelivered(id);
169457
+ options.onUndelivered?.();
169458
+ },
169459
+ onTopicEnded: () => options.log.markUndelivered(id, "ended")
169460
+ },
169461
+ dock: options.dock,
169462
+ topic: options.topic
169463
+ });
169464
+ return outcome;
169465
+ } finally {
169466
+ if (id !== undefined)
169467
+ options.log.settle(id, outcome, options.turnSignal.aborted);
169468
+ }
169469
+ }
169470
+ var ANSWERED_CARDS_KEPT = 5;
169471
+ var init_operator_confirmations = __esm(() => {
169472
+ init_operator_confirm_tools();
169473
+ });
169474
+
168977
169475
  // src/tui/remote-control-surface.ts
168978
169476
  function commandEchoText(line, source) {
168979
169477
  return source === TG_SOURCE ? `${TG_ECHO_MARKER} ${line}` : `\u276F ${line}`;
@@ -169023,9 +169521,9 @@ function formatHistoryStatus(status) {
169023
169521
  const history = status.history;
169024
169522
  if (history === undefined)
169025
169523
  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.`;
169524
+ 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
169525
  }
169028
- function formatRemoteStatusLines(status, posture) {
169526
+ function formatRemoteStatusLines(status, posture, confirmations) {
169029
169527
  const lines = [`Remote control: ${STATE_WORDS[status.state]}`];
169030
169528
  if (status.state === "off") {
169031
169529
  lines.push("Topic: none");
@@ -169041,25 +169539,27 @@ function formatRemoteStatusLines(status, posture) {
169041
169539
  const n = status.unconfirmedApprovals;
169042
169540
  lines.push(`Approvals not confirmed: ${n}. The topic shows ${n === 1 ? "it" : "them"} as "not confirmed" until serve takes the shell's ack.`);
169043
169541
  }
169542
+ if (confirmations !== undefined && confirmations.length > 0)
169543
+ lines.push("", ...confirmations);
169044
169544
  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
169545
  if (posture !== undefined && posture.length > 0) {
169046
169546
  lines.push("", "Telegram permissions (change with /remote-policy):", ...posture.map((line) => ` ${line}`));
169047
169547
  }
169048
169548
  return lines;
169049
169549
  }
169050
- function clock2(at) {
169550
+ function clock3(at) {
169051
169551
  return new Date(at).toISOString().slice(11, 19);
169052
169552
  }
169053
169553
  function formatRemoteEventLines(status) {
169054
169554
  if (status.events.length === 0)
169055
169555
  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}`);
169556
+ return [...status.events].reverse().slice(0, REMOTE_EVENT_LIMIT).map((event) => `${clock3(event.at)} ${event.kind.padEnd(8)} ${event.text}`);
169057
169557
  }
169058
169558
  function formatRemoteCommandLines(status) {
169059
169559
  const commands = status.events.filter((event) => event.kind === "command");
169060
169560
  if (commands.length === 0)
169061
169561
  return ["No remote commands yet."];
169062
- return [...commands].reverse().slice(0, REMOTE_EVENT_LIMIT).map((event) => `${clock2(event.at)} ${event.text}`);
169562
+ return [...commands].reverse().slice(0, REMOTE_EVENT_LIMIT).map((event) => `${clock3(event.at)} ${event.text}`);
169063
169563
  }
169064
169564
  function renderRemoteControlText(status) {
169065
169565
  return `${[...formatRemoteStatusLines(status), "", "Recent events:", ...formatRemoteEventLines(status)].join(`
@@ -169105,7 +169605,7 @@ function presentRemoteControl(open, otui, chrome, options) {
169105
169605
  return formatRemoteEventLines(status);
169106
169606
  if (tab === "commands")
169107
169607
  return formatRemoteCommandLines(status);
169108
- const lines = formatRemoteStatusLines(status, options.getPostureLines?.());
169608
+ const lines = formatRemoteStatusLines(status, options.getPostureLines?.(), options.getConfirmationLines?.());
169109
169609
  if (historyNote !== undefined) {
169110
169610
  const at = lines.findIndex((line) => line.startsWith("History:"));
169111
169611
  lines.splice(at < 0 ? lines.length : at + 1, 0, historyNote);
@@ -169196,14 +169696,17 @@ function fit3(text, width) {
169196
169696
  return "";
169197
169697
  return text.length <= width ? text : `${text.slice(0, Math.max(0, width - 1))}\u2026`;
169198
169698
  }
169199
- function projectRemoteRow(status, width) {
169699
+ function projectRemoteRow(status, width, waitingCards = 0) {
169200
169700
  if (status === undefined || status.state === "off")
169201
169701
  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) };
169702
+ const detail = status.history === undefined ? {} : { detail: fit3(`history ${status.history.count} \xB7 ${clock3(status.history.at).slice(0, 5)}`, width) };
169203
169703
  if (status.state === "offline")
169204
169704
  return { text: fit3("offline", width), role: "attention", action: "open", ...detail };
169205
169705
  const topic = status.name ?? "connecting";
169206
169706
  const unconfirmed = status.unconfirmedApprovals ?? 0;
169707
+ const cards = waitingCardsText(waitingCards);
169708
+ if (cards !== undefined)
169709
+ return { text: fit3(`${topic} \xB7 ${cards}`, width), role: "attention", action: "open", ...detail };
169207
169710
  if (unconfirmed > 0)
169208
169711
  return { text: fit3(`${topic} \xB7 ${unconfirmed} unconfirmed`, width), role: "attention", action: "open", ...detail };
169209
169712
  return { text: fit3(topic, width), role: "ok", action: "open", ...detail };
@@ -169232,7 +169735,7 @@ function mountRemotePanel(otui, renderer, parent, options) {
169232
169735
  box.add(detail);
169233
169736
  const posture = new core.TextRenderable(renderer, { id: "sb-remote-p", content: "" });
169234
169737
  box.add(posture);
169235
- const currentRow = () => projectRemoteRow(options.getStatus(), options.width);
169738
+ const currentRow = () => projectRemoteRow(options.getStatus(), options.width, options.getWaitingCards?.() ?? 0);
169236
169739
  const draw = () => {
169237
169740
  if (disposed)
169238
169741
  return;
@@ -169277,6 +169780,7 @@ var init_remote_control_surface = __esm(() => {
169277
169780
  init_history2();
169278
169781
  init_history_host();
169279
169782
  init_shell_bridge();
169783
+ init_operator_confirmations();
169280
169784
  init_flow_inspector();
169281
169785
  init_modal_host();
169282
169786
  init_theme_repaint();
@@ -169513,7 +170017,7 @@ var init_agent_commands = __esm(() => {
169513
170017
  },
169514
170018
  {
169515
170019
  name: "/remote-control",
169516
- description: "Drive this session from a Telegram topic: /remote-control [name|off|status]. Off by default",
170020
+ 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
170021
  modes: AGENT_ONLY
169518
170022
  },
169519
170023
  {
@@ -169598,7 +170102,7 @@ var init_agent_commands = __esm(() => {
169598
170102
  },
169599
170103
  {
169600
170104
  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",
170105
+ 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
170106
  modes: AGENT_ONLY
169603
170107
  },
169604
170108
  {
@@ -172314,7 +172818,7 @@ function approvalsText(views, now, options) {
172314
172818
  var RECENT_RESOLVED_LIMIT = 10;
172315
172819
 
172316
172820
  // src/lib/serve-turn-store.ts
172317
- import { createHash as createHash92 } from "crypto";
172821
+ import { createHash as createHash93 } from "crypto";
172318
172822
  import { existsSync as existsSync84, readdirSync as readdirSync16, rmSync as rmSync6 } from "fs";
172319
172823
  import path376 from "path";
172320
172824
  function turnsRoot(dir) {
@@ -172325,11 +172829,11 @@ function turnDir(turnId, dir) {
172325
172829
  }
172326
172830
  function keyPath(project, idempotencyKey, dir) {
172327
172831
  const projectBytes = Buffer.byteLength(project, "utf8");
172328
- const digest = createHash92("sha256").update(`${projectBytes}:${project}\x00${idempotencyKey}`, "utf8").digest("hex");
172832
+ const digest = createHash93("sha256").update(`${projectBytes}:${project}\x00${idempotencyKey}`, "utf8").digest("hex");
172329
172833
  return path376.join(turnsRoot(dir), "keys", `${digest}.json`);
172330
172834
  }
172331
172835
  function legacyKeyPath(idempotencyKey, dir) {
172332
- const digest = createHash92("sha256").update(idempotencyKey, "utf8").digest("hex");
172836
+ const digest = createHash93("sha256").update(idempotencyKey, "utf8").digest("hex");
172333
172837
  return path376.join(turnsRoot(dir), "keys", `${digest}.json`);
172334
172838
  }
172335
172839
  function adoptLegacyClaim(project, idempotencyKey, dir) {
@@ -173583,7 +174087,7 @@ var init_types10 = __esm(() => {
173583
174087
  });
173584
174088
 
173585
174089
  // src/intake/store.ts
173586
- import { createHash as createHash93 } from "crypto";
174090
+ import { createHash as createHash94 } from "crypto";
173587
174091
  import { appendFile as appendFile17, mkdir as mkdir81, readFile as readFile199, writeFile as writeFile50 } from "fs/promises";
173588
174092
  import path380 from "path";
173589
174093
  async function ensureIntakeDataIgnored(root) {
@@ -173606,11 +174110,11 @@ async function locked2(root, lock, fn) {
173606
174110
  return withFileLock(lock, fn, { timeoutMs: 5000 });
173607
174111
  }
173608
174112
  function cardIdFor(eventKey, stamp) {
173609
- return `c${createHash93("sha256").update(`${eventKey}
174113
+ return `c${createHash94("sha256").update(`${eventKey}
173610
174114
  ${stamp}`).digest("hex").slice(0, 11)}`;
173611
174115
  }
173612
174116
  function overflowCardId(seed) {
173613
- return `o${createHash93("sha256").update(seed).digest("hex").slice(0, 11)}`;
174117
+ return `o${createHash94("sha256").update(seed).digest("hex").slice(0, 11)}`;
173614
174118
  }
173615
174119
  function emptyIntakeState() {
173616
174120
  return { version: 1, paused: false, seen: {}, baselined: [], delivery: {}, recent: [], pendingEdits: {}, pendingEditTries: {} };
@@ -174993,7 +175497,7 @@ function summary(c) {
174993
175497
  ...c.remindAt !== undefined ? { remindAt: c.remindAt } : {}
174994
175498
  };
174995
175499
  }
174996
- function clock3(iso) {
175500
+ function clock4(iso) {
174997
175501
  if (iso === null)
174998
175502
  return "\u0435\u0449\u0451 \u043D\u0435 \u043E\u043F\u0440\u0430\u0448\u0438\u0432\u0430\u043B\u0441\u044F";
174999
175503
  const d = new Date(iso);
@@ -175013,7 +175517,7 @@ async function buildIntakeStatus(root, deps = {}) {
175013
175517
  const nextPollAt = nextIntakePollAt(config, state);
175014
175518
  const ghAccount = ghEnvForProject(root, env)["GH_ACCOUNT"] === "work" ? "work" : "personal";
175015
175519
  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)}`;
175520
+ 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
175521
  return {
175018
175522
  configured: file.present,
175019
175523
  enabled: config.enabled,
@@ -175811,12 +176315,19 @@ function presentDecisions(open, otui, chrome, options) {
175811
176315
  return handle;
175812
176316
  }
175813
176317
  async function openDecisions(otui, chrome, options) {
175814
- const { cwd, ...rest } = options;
175815
- const text = await reportText(cwd).catch(() => UNREADABLE);
176318
+ const { cwd, confirmationLines, ...rest } = options;
176319
+ const report = await reportText(cwd).catch(() => UNREADABLE);
176320
+ const text = withConfirmationLines(report, confirmationLines?.() ?? []);
175816
176321
  return presentDecisions((hostOtui, hostChrome, input) => openModal(hostOtui, hostChrome, input), otui, chrome, { ...rest, text });
175817
176322
  }
176323
+ function withConfirmationLines(report, cards) {
176324
+ return cards.length === 0 ? report : `${report}
176325
+
176326
+ ${cards.join(`
176327
+ `)}`;
176328
+ }
175818
176329
  async function openDecisionsArms(otui, chrome, options) {
175819
- const { cwd, ...rest } = options;
176330
+ const { cwd, confirmationLines: _cards, ...rest } = options;
175820
176331
  const text = await armsText(cwd);
175821
176332
  return presentDecisions((hostOtui, hostChrome, input) => openModal(hostOtui, hostChrome, input), otui, chrome, { ...rest, text, tabLabel: "Arms" });
175822
176333
  }
@@ -175909,6 +176420,7 @@ function mountDecisionsSidebar(options) {
175909
176420
  modal?.close({ restoreFocus: false });
175910
176421
  modal = await open(core, chrome, {
175911
176422
  cwd: options.cwd,
176423
+ ...options.confirmationLines !== undefined ? { confirmationLines: options.confirmationLines } : {},
175912
176424
  ...options.onKeypress !== undefined ? { onKeypress: options.onKeypress } : {}
175913
176425
  });
175914
176426
  return modal;
@@ -176766,7 +177278,7 @@ function discardResultText(result, runId) {
176766
177278
  return `Discarded run ${shortId(runId)}. Its stored patch was deleted.`;
176767
177279
  return `Not discarded (${result.reason}): ${clean3(result.detail)}`;
176768
177280
  }
176769
- function errorText4(error) {
177281
+ function errorText5(error) {
176770
177282
  return clean3(error instanceof Error ? error.message : String(error));
176771
177283
  }
176772
177284
  function orderedFiles(record) {
@@ -176798,7 +177310,7 @@ function externalDiffSlashText(deps) {
176798
177310
  }
176799
177311
  }
176800
177312
  } catch (error) {
176801
- lines.push(`Could not read the write runs: ${errorText4(error)}`);
177313
+ lines.push(`Could not read the write runs: ${errorText5(error)}`);
176802
177314
  }
176803
177315
  lines.push("Use keryx agents external review <run-id> and keryx agents external apply <run-id> in a terminal.");
176804
177316
  return `${lines.join(`
@@ -176823,7 +177335,7 @@ function createExternalDiffController(options) {
176823
177335
  runs = deps.list();
176824
177336
  } catch (error) {
176825
177337
  runs = [];
176826
- notice = `Could not read the write runs: ${errorText4(error)}`;
177338
+ notice = `Could not read the write runs: ${errorText5(error)}`;
176827
177339
  }
176828
177340
  const ids = new Set(runs.map((run) => run.runId));
176829
177341
  for (const id of [...views.keys()]) {
@@ -176853,7 +177365,7 @@ function createExternalDiffController(options) {
176853
177365
  const view = deps.view(run.runId);
176854
177366
  entry = view === undefined ? {} : { view };
176855
177367
  } catch (error) {
176856
- entry = { error: errorText4(error) };
177368
+ entry = { error: errorText5(error) };
176857
177369
  }
176858
177370
  views.set(run.runId, entry);
176859
177371
  return entry;
@@ -176951,7 +177463,7 @@ function createExternalDiffController(options) {
176951
177463
  try {
176952
177464
  text = landResultText(await deps.land({ runId, confirmedPatchHash: hash, allowFlagged }));
176953
177465
  } catch (error) {
176954
- text = `Apply failed: ${errorText4(error)}. ${NOTHING_APPLIED}`;
177466
+ text = `Apply failed: ${errorText5(error)}. ${NOTHING_APPLIED}`;
176955
177467
  }
176956
177468
  allowFlaggedRun = undefined;
176957
177469
  views.clear();
@@ -176972,7 +177484,7 @@ function createExternalDiffController(options) {
176972
177484
  try {
176973
177485
  notice = discardResultText(deps.discard(run.runId), run.runId);
176974
177486
  } catch (error) {
176975
- notice = `Discard failed: ${errorText4(error)}`;
177487
+ notice = `Discard failed: ${errorText5(error)}`;
176976
177488
  }
176977
177489
  allowFlaggedRun = undefined;
176978
177490
  views.clear();
@@ -177257,7 +177769,7 @@ var init_external_diff_sidebar = __esm(() => {
177257
177769
 
177258
177770
  // src/tui/autoreview-surface.ts
177259
177771
  import { spawn as nodeSpawn4 } from "child_process";
177260
- import { existsSync as existsSync87, readdirSync as readdirSync18, readFileSync as readFileSync41 } from "fs";
177772
+ import { existsSync as existsSync87, readdirSync as readdirSync18, readFileSync as readFileSync42 } from "fs";
177261
177773
  import path386 from "path";
177262
177774
  function parseAutoReviewArgs(line) {
177263
177775
  const words = line.trim().split(/\s+/).slice(1);
@@ -177306,7 +177818,7 @@ function parseAutoReviewArgs(line) {
177306
177818
  }
177307
177819
  function readJson15(file) {
177308
177820
  try {
177309
- return JSON.parse(readFileSync41(file, "utf8"));
177821
+ return JSON.parse(readFileSync42(file, "utf8"));
177310
177822
  } catch {
177311
177823
  return;
177312
177824
  }
@@ -178707,7 +179219,7 @@ var init_stdin_guard = __esm(() => {
178707
179219
 
178708
179220
  // src/tui/renderer-debug.ts
178709
179221
  import { spawnSync as spawnSync7 } from "child_process";
178710
- import { readFileSync as readFileSync42, readlinkSync as readlinkSync2 } from "fs";
179222
+ import { readFileSync as readFileSync43, readlinkSync as readlinkSync2 } from "fs";
178711
179223
  function routeProcessWarnings(write = (text) => {
178712
179224
  process.stderr.write(text);
178713
179225
  }) {
@@ -178744,7 +179256,7 @@ function eofDiagnostics() {
178744
179256
  }
178745
179257
  let stat;
178746
179258
  try {
178747
- stat = readFileSync42("/proc/self/stat", "utf8").trim();
179259
+ stat = readFileSync43("/proc/self/stat", "utf8").trim();
178748
179260
  } catch {
178749
179261
  stat = "";
178750
179262
  }
@@ -188619,12 +189131,12 @@ function maxOverFiles(files, values) {
188619
189131
  }
188620
189132
 
188621
189133
  // src/wiki/deep-enrich.ts
188622
- import { randomUUID as randomUUID45, createHash as createHash94 } from "crypto";
189134
+ import { randomUUID as randomUUID45, createHash as createHash95 } from "crypto";
188623
189135
  function errorMessage5(cause) {
188624
189136
  return cause instanceof Error ? cause.message : String(cause);
188625
189137
  }
188626
189138
  function sha256Hex10(text) {
188627
- return createHash94("sha256").update(text).digest("hex");
189139
+ return createHash95("sha256").update(text).digest("hex");
188628
189140
  }
188629
189141
  function buildDeepEnrichTools(port) {
188630
189142
  const deepOps = METAPROJECT_OPERATIONS.filter((op) => DEEP_ENRICH_OPS.includes(op.name));
@@ -188863,7 +189375,7 @@ var init_deep_enrich = __esm(() => {
188863
189375
  });
188864
189376
 
188865
189377
  // src/wiki/enrich.ts
188866
- import { existsSync as existsSync88, mkdirSync as mkdirSync14, readFileSync as readFileSync43, writeFileSync as writeFileSync12 } from "fs";
189378
+ import { existsSync as existsSync88, mkdirSync as mkdirSync14, readFileSync as readFileSync44, writeFileSync as writeFileSync12 } from "fs";
188867
189379
  import { readFile as readFile207 } from "fs/promises";
188868
189380
  import path393 from "path";
188869
189381
  function resolveEnrichProviderModel(input) {
@@ -188881,7 +189393,7 @@ function loadResumeState(cwd) {
188881
189393
  if (!existsSync88(file)) {
188882
189394
  return { updatedAt: new Date().toISOString(), completed: [], failed: [] };
188883
189395
  }
188884
- const raw = JSON.parse(readFileSync43(file, "utf8"));
189396
+ const raw = JSON.parse(readFileSync44(file, "utf8"));
188885
189397
  if (raw === null || typeof raw !== "object") {
188886
189398
  return { updatedAt: new Date().toISOString(), completed: [], failed: [] };
188887
189399
  }
@@ -191485,7 +191997,7 @@ var init_turn_loop = __esm(() => {
191485
191997
  });
191486
191998
 
191487
191999
  // src/tui/worker-fleet.ts
191488
- function clip7(s, max) {
192000
+ function clip8(s, max) {
191489
192001
  if (s.length <= max) {
191490
192002
  return s;
191491
192003
  }
@@ -191576,7 +192088,7 @@ function formatFleetSidebar(workers, maxLines = 14) {
191576
192088
  if (main !== undefined) {
191577
192089
  lines.push(mainHeadline(main.status));
191578
192090
  const phase = humanFleetPhase(main.status, main.detail);
191579
- lines.push(` ${clip7(phase, 26)}`);
192091
+ lines.push(` ${clip8(phase, 26)}`);
191580
192092
  if (main.status === "blocked") {
191581
192093
  lines.push(" \u2191 pick menu above input");
191582
192094
  }
@@ -191611,8 +192123,8 @@ function formatFleetSidebar(workers, maxLines = 14) {
191611
192123
  for (const w of shown) {
191612
192124
  const g = STATUS_GLYPH2[w.status];
191613
192125
  const phase = humanFleetPhase(w.status, w.detail);
191614
- const label = clip7(w.label, 12);
191615
- const det = phase.length > 0 ? ` ${clip7(phase, 12)}` : "";
192126
+ const label = clip8(w.label, 12);
192127
+ const det = phase.length > 0 ? ` ${clip8(phase, 12)}` : "";
191616
192128
  lines.push(`${g} ${label}${det}`);
191617
192129
  }
191618
192130
  if (sorted.length > shown.length) {
@@ -191642,8 +192154,8 @@ function formatPeerLine(peer) {
191642
192154
  const statusGlyph = PEER_STATUS_GLYPH[peer.status] ?? "?";
191643
192155
  const name = displaySafe(peer.name);
191644
192156
  const activity = displaySafe(peer.activity).trim();
191645
- const detail = activity.length > 0 ? ` ${clip7(activity, 14)}` : "";
191646
- return `${marker}${statusGlyph} @${clip7(name, 10)}${detail}`;
192157
+ const detail = activity.length > 0 ? ` ${clip8(activity, 14)}` : "";
192158
+ return `${marker}${statusGlyph} @${clip8(name, 10)}${detail}`;
191647
192159
  }
191648
192160
  function formatFleetSidebarWithPeers(workers, peers = [], maxLines = 14) {
191649
192161
  const base = formatFleetSidebar(workers, maxLines);
@@ -191746,7 +192258,7 @@ var init_worker_fleet = __esm(() => {
191746
192258
  });
191747
192259
 
191748
192260
  // src/tui/subagent-session.ts
191749
- function clip8(s, max) {
192261
+ function clip9(s, max) {
191750
192262
  if (s.length <= max) {
191751
192263
  return s;
191752
192264
  }
@@ -191778,11 +192290,11 @@ function formatSubagentRow(session, width = SIDEBAR_TEXT_WIDTH) {
191778
192290
  const mark = session.runtime === "external" ? EXTERNAL_SUBAGENT_MARK : "";
191779
192291
  const budget = Math.max(4, width - glyph.length - 1);
191780
192292
  const labelBudget = Math.max(1, Math.min(12, budget) - mark.length);
191781
- const label = `${mark}${clip8(session.label, labelBudget)}`;
192293
+ const label = `${mark}${clip9(session.label, labelBudget)}`;
191782
192294
  const used = glyph.length + 1 + label.length;
191783
192295
  const detBudget = width - used - 1;
191784
- const det = detBudget >= 3 && phase.length > 0 ? ` ${clip8(phase, detBudget)}` : "";
191785
- return clip8(`${glyph} ${label}${det}`, width);
192296
+ const det = detBudget >= 3 && phase.length > 0 ? ` ${clip9(phase, detBudget)}` : "";
192297
+ return clip9(`${glyph} ${label}${det}`, width);
191786
192298
  }
191787
192299
  function formatSubagentListHeader(count) {
191788
192300
  return `Subagents ${count}`;
@@ -192877,7 +193389,7 @@ function renderExternalTranscript(events, options = {}) {
192877
193389
  if (options.summary !== false && events.length > 0) {
192878
193390
  lines.push("", formatTranscriptFooter(foldExternalTranscript(events)));
192879
193391
  }
192880
- return options.width === undefined ? lines : lines.map((line) => clip9(line, options.width ?? 0));
193392
+ return options.width === undefined ? lines : lines.map((line) => clip10(line, options.width ?? 0));
192881
193393
  }
192882
193394
  function formatExternalWork(events, options = {}) {
192883
193395
  return renderExternalTranscript(events, options).join(`
@@ -193042,7 +193554,7 @@ function describeSkips(skippedLines) {
193042
193554
  function plural5(count, singular, pluralForm) {
193043
193555
  return `${count} ${count === 1 ? singular : pluralForm ?? `${singular}s`}`;
193044
193556
  }
193045
- function clip9(value, width) {
193557
+ function clip10(value, width) {
193046
193558
  if (width <= 0 || value.length <= width)
193047
193559
  return value;
193048
193560
  return `${value.slice(0, Math.max(1, width - 1))}\u2026`;
@@ -193185,7 +193697,7 @@ function appendBounded(output, chunk) {
193185
193697
  const combined = output + chunk;
193186
193698
  return combined.length > MAX_BACKGROUND_JOB_OUTPUT_CHARS ? combined.slice(-MAX_BACKGROUND_JOB_OUTPUT_CHARS) : combined;
193187
193699
  }
193188
- function clip10(s, max) {
193700
+ function clip11(s, max) {
193189
193701
  if (max <= 0) {
193190
193702
  return "";
193191
193703
  }
@@ -193200,8 +193712,8 @@ function formatJobListHeader(count) {
193200
193712
  function formatJobRow(entry, width = SIDEBAR_TEXT_WIDTH) {
193201
193713
  const glyph = STATUS_GLYPH4[entry.status];
193202
193714
  const budget = Math.max(1, width - glyph.length - 1);
193203
- const cmd = clip10(entry.command, budget);
193204
- return clip10(`${glyph} ${cmd}`, width);
193715
+ const cmd = clip11(entry.command, budget);
193716
+ return clip11(`${glyph} ${cmd}`, width);
193205
193717
  }
193206
193718
  function formatJobMeta(entry, now = Date.now()) {
193207
193719
  const rows = [
@@ -194981,6 +195493,9 @@ function shouldAutoApproveReadOnlySpawn(mode, hookAsk, untrustedOrigin) {
194981
195493
  function isUserPromptApprovalCard(tool) {
194982
195494
  return tool === "user_prompt";
194983
195495
  }
195496
+ function withMirrorNote(card, note) {
195497
+ return card.length === 0 ? [note] : [...card.slice(0, -1), note, card[card.length - 1]];
195498
+ }
194984
195499
  function operatorCardDialog(cardTitle) {
194985
195500
  if (cardTitle === undefined) {
194986
195501
  return {
@@ -196431,6 +196946,8 @@ async function launchTuiAgentShell(opts) {
196431
196946
  let liveReviewsPanel;
196432
196947
  let remoteBridge;
196433
196948
  let liveRemotePanel;
196949
+ const operatorCards = new OperatorConfirmationLog;
196950
+ const operatorCardLines = () => confirmationLines(operatorCards.entries(), Date.now());
196434
196951
  let liveChannelsPanel;
196435
196952
  const channelsClient = new ChannelsClient;
196436
196953
  let liveApprovals;
@@ -196785,6 +197302,7 @@ async function launchTuiAgentShell(opts) {
196785
197302
  liveRemotePanel = mountRemotePanel(otui, r, sidebar, {
196786
197303
  width: SIDEBAR_TEXT_WIDTH,
196787
197304
  getStatus: remoteStatus,
197305
+ getWaitingCards: () => operatorCards.waiting().length,
196788
197306
  onOpen: () => showRemoteControl(),
196789
197307
  onOpenPermissions: () => showPermissions(),
196790
197308
  getPosture: () => {
@@ -196826,7 +197344,8 @@ async function launchTuiAgentShell(opts) {
196826
197344
  notice: (text) => io.onSystem?.(`\u25C7 ${text}
196827
197345
  `),
196828
197346
  lastDecisionId: lastAskUserDecisionId,
196829
- session: askUserSessionId()
197347
+ session: askUserSessionId(),
197348
+ confirmationLines: operatorCardLines
196830
197349
  });
196831
197350
  liveDecisions = decisionsPanel;
196832
197351
  liveResearchSync = mountResearchSyncPanel(otui, r, sidebar, {
@@ -197824,10 +198343,16 @@ async function launchTuiAgentShell(opts) {
197824
198343
  `);
197825
198344
  if (decision !== "allow")
197826
198345
  return false;
197827
- return meta?.fingerprint !== undefined ? { approved: true, fingerprint: meta.fingerprint, via: "remote" } : true;
198346
+ return meta?.fingerprint !== undefined ? remoteApproval(meta.fingerprint, answer.fromId) : true;
197828
198347
  };
197829
198348
  const mirrorApprovalToTopic = (inner) => async (tool, inputJson, meta) => {
197830
198349
  const bridge = remoteBridge;
198350
+ const isCard = meta?.alwaysAsk === true && meta.card !== undefined && meta.card.length > 0 && !isUserPromptApprovalCard(tool);
198351
+ const unmirrored = isCard ? mirrorUndeliveredNote(bridge) : undefined;
198352
+ if (unmirrored !== undefined && meta?.card !== undefined) {
198353
+ const card = withMirrorNote(meta.card, unmirrored);
198354
+ return askUnmirroredCard(operatorCards, meta.card[0] ?? tool, foregroundOperation.signal, () => inner(tool, inputJson, { ...meta, card }));
198355
+ }
197831
198356
  if (bridge === undefined || !bridge.active) {
197832
198357
  return inner(tool, inputJson, meta);
197833
198358
  }
@@ -197836,8 +198361,12 @@ async function launchTuiAgentShell(opts) {
197836
198361
  return inner(tool, inputJson, meta);
197837
198362
  }
197838
198363
  const brief = tool.length > 60 ? `${tool.slice(0, 59)}\u2026` : tool;
197839
- const outcome = await raceApproval({
198364
+ const outcome = await raceMirroredCard({
198365
+ log: operatorCards,
198366
+ question: isCard && meta?.card !== undefined ? meta.card[0] ?? tool : undefined,
197840
198367
  turnSignal: foregroundOperation.signal,
198368
+ onUndelivered: () => io.onSystem?.(`\u25C7 Telegram mirror not delivered: the topic did not take the card. Answer it here.
198369
+ `),
197841
198370
  dock: (race) => inner(tool, inputJson, meta, race),
197842
198371
  topic: (signal) => bridge.askApproval(describeApprovalForTopic(tool, inputJson, meta), { signal, timeoutMs: MAX_APPROVAL_TIMEOUT_MS })
197843
198372
  });
@@ -197851,7 +198380,7 @@ async function launchTuiAgentShell(opts) {
197851
198380
  `);
197852
198381
  if (!allowed)
197853
198382
  return false;
197854
- return meta?.fingerprint !== undefined ? { approved: true, fingerprint: meta.fingerprint, via: "remote" } : true;
198383
+ return meta?.fingerprint !== undefined ? remoteApproval(meta.fingerprint, outcome.answer.fromId) : true;
197855
198384
  };
197856
198385
  io.requestApproval = trackApproval(mirrorApprovalToTopic(requestApprovalLocally));
197857
198386
  const askUserInteractive = async (req) => {
@@ -198698,9 +199227,14 @@ Staying in the current session.
198698
199227
  });
198699
199228
  };
198700
199229
  const remoteModalListeners = new Set;
199230
+ operatorCards.subscribe(() => {
199231
+ liveRemotePanel?.refresh();
199232
+ remoteModalListeners.forEach((listener) => listener());
199233
+ });
198701
199234
  const showRemoteControl = () => {
198702
199235
  openRemoteControl(otui, chrome, {
198703
199236
  getStatus: remoteStatus,
199237
+ getConfirmationLines: operatorCardLines,
198704
199238
  subscribeChange: (listener) => {
198705
199239
  remoteModalListeners.add(listener);
198706
199240
  return () => {
@@ -201276,6 +201810,7 @@ var init_tui_shell = __esm(() => {
201276
201810
  init_approvals_sidebar();
201277
201811
  init_intake_surface();
201278
201812
  init_decisions_surface();
201813
+ init_operator_confirmations();
201279
201814
  init_research_sync_panel();
201280
201815
  init_external_diff_sidebar();
201281
201816
  init_autoreview_surface();
@@ -201836,7 +202371,7 @@ var init_chat_shell = __esm(() => {
201836
202371
 
201837
202372
  // src/tui/debug-watcher.ts
201838
202373
  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";
202374
+ 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
202375
  import path396 from "path";
201841
202376
  function parseEpollFdinfo(text) {
201842
202377
  const out = [];
@@ -201881,7 +202416,7 @@ function isAlive(pid) {
201881
202416
  }
201882
202417
  function readText7(file) {
201883
202418
  try {
201884
- return readFileSync44(file, "utf8");
202419
+ return readFileSync45(file, "utf8");
201885
202420
  } catch {
201886
202421
  return;
201887
202422
  }
@@ -204928,7 +205463,13 @@ Example: keryx shell --provider ollama --model llama3.1:latest`);
204928
205463
  ...flags.denyTools !== undefined ? { denyTools: flags.denyTools } : {},
204929
205464
  ...bus !== undefined ? { bus } : {},
204930
205465
  schedules: { projectRoot: cwd, defaults: () => ({ provider: sel.provider, model: sel.model }) },
204931
- operatorConfirm: { projectRoot: cwd }
205466
+ operatorConfirm: {
205467
+ projectRoot: cwd,
205468
+ allowedUserIds: () => {
205469
+ const loaded = loadRemoteConfig();
205470
+ return loaded.ok ? loaded.value.allowedUserIds : undefined;
205471
+ }
205472
+ }
204932
205473
  }),
204933
205474
  mcpRuntime: () => mcpRuntime,
204934
205475
  maxOutputTokens: resolveAgentMaxOutputTokens({
@@ -205379,6 +205920,7 @@ var init_shell = __esm(() => {
205379
205920
  init_reviews_inspector();
205380
205921
  init_remote_control_surface();
205381
205922
  init_channels_client();
205923
+ init_config11();
205382
205924
  init_channels_surface();
205383
205925
  init_intake_surface();
205384
205926
  init_session_info();
@@ -211940,7 +212482,7 @@ var init_judge_recordings = __esm(() => {
211940
212482
  });
211941
212483
 
211942
212484
  // src/gdskills/governance/authoring-lint.ts
211943
- import { existsSync as existsSync90, readdirSync as readdirSync20, readFileSync as readFileSync45 } from "fs";
212485
+ import { existsSync as existsSync90, readdirSync as readdirSync20, readFileSync as readFileSync46 } from "fs";
211944
212486
  import path414 from "path";
211945
212487
  function skillDirectoryName(skillMdPath) {
211946
212488
  return path414.basename(path414.dirname(skillMdPath));
@@ -211965,7 +212507,7 @@ function deepReferenceFindings(skillMdPath) {
211965
212507
  if (!entry.isFile() || !entry.name.endsWith(".md"))
211966
212508
  continue;
211967
212509
  const refPath = path414.join(referencesDir, entry.name);
211968
- const content = readFileSync45(refPath, "utf8");
212510
+ const content = readFileSync46(refPath, "utf8");
211969
212511
  const linksAnotherReference = /\]\([^)]*\.md\)/.test(content) || /\breferences\/[\w.-]+\.md\b/.test(content);
211970
212512
  if (linksAnotherReference) {
211971
212513
  findings.push({
@@ -212086,15 +212628,15 @@ var init_authoring_lint = __esm(() => {
212086
212628
  });
212087
212629
 
212088
212630
  // 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";
212631
+ import { createHash as createHash96 } from "crypto";
212632
+ import { existsSync as existsSync91, readdirSync as readdirSync21, readFileSync as readFileSync47, statSync as statSync25 } from "fs";
212091
212633
  import path415 from "path";
212092
212634
  function cacheKeyMatches(cached, inputs, quick) {
212093
212635
  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
212636
  }
212095
212637
  function catalogFingerprint(catalog) {
212096
212638
  const parts = [...catalog].map((entry) => `${entry.id}:${entry.sha256}`).sort();
212097
- return createHash95("sha256").update(parts.join(`
212639
+ return createHash96("sha256").update(parts.join(`
212098
212640
  `)).digest("hex");
212099
212641
  }
212100
212642
  function verificationReportHash(root, entry) {
@@ -212102,7 +212644,7 @@ function verificationReportHash(root, entry) {
212102
212644
  if (!existsSync91(reportPath))
212103
212645
  return null;
212104
212646
  try {
212105
- return createHash95("sha256").update(readFileSync46(reportPath, "utf8")).digest("hex");
212647
+ return createHash96("sha256").update(readFileSync47(reportPath, "utf8")).digest("hex");
212106
212648
  } catch {
212107
212649
  return null;
212108
212650
  }
@@ -212124,11 +212666,11 @@ function referencesHash(entry) {
212124
212666
  }
212125
212667
  };
212126
212668
  walk(referencesDir, "");
212127
- const hash = createHash95("sha256");
212669
+ const hash = createHash96("sha256");
212128
212670
  for (const rel of files) {
212129
212671
  hash.update(rel);
212130
212672
  hash.update("\x00");
212131
- hash.update(readFileSync46(path415.join(referencesDir, rel)));
212673
+ hash.update(readFileSync47(path415.join(referencesDir, rel)));
212132
212674
  hash.update("\x00");
212133
212675
  }
212134
212676
  return hash.digest("hex");
@@ -212137,7 +212679,7 @@ function loadCache2(cachePath) {
212137
212679
  if (!existsSync91(cachePath))
212138
212680
  return {};
212139
212681
  try {
212140
- return JSON.parse(readFileSync46(cachePath, "utf8"));
212682
+ return JSON.parse(readFileSync47(cachePath, "utf8"));
212141
212683
  } catch {
212142
212684
  return {};
212143
212685
  }
@@ -212154,7 +212696,7 @@ function checkFreshness(root, entry) {
212154
212696
  if (!existsSync91(reportPath))
212155
212697
  return;
212156
212698
  try {
212157
- const report = JSON.parse(readFileSync46(reportPath, "utf8"));
212699
+ const report = JSON.parse(readFileSync47(reportPath, "utf8"));
212158
212700
  if (report.status === "stale" || report.status === "needs-review" || report.status === "blocked") {
212159
212701
  return {
212160
212702
  skillId: entry.id,
@@ -213436,7 +213978,7 @@ var init_install_manifest_schema = __esm(() => {
213436
213978
  });
213437
213979
 
213438
213980
  // src/gdskills/manifest/manifest.ts
213439
- import { existsSync as existsSync92, readFileSync as readFileSync47 } from "fs";
213981
+ import { existsSync as existsSync92, readFileSync as readFileSync48 } from "fs";
213440
213982
  import path417 from "path";
213441
213983
  import { fileURLToPath as fileURLToPath9 } from "url";
213442
213984
  function thisModuleDir() {
@@ -213468,7 +214010,7 @@ function parseInstallManifest(fileLabel, raw) {
213468
214010
  }
213469
214011
  function loadBundledManifest() {
213470
214012
  const file = bundledManifestPath();
213471
- const parsed = JSON.parse(readFileSync47(file, "utf8"));
214013
+ const parsed = JSON.parse(readFileSync48(file, "utf8"));
213472
214014
  return parseInstallManifest(file, parsed);
213473
214015
  }
213474
214016
  var HARNESS_IDS, BUNDLED_TREE, InvalidInstallManifestError;
@@ -213572,7 +214114,7 @@ var init_glob = __esm(() => {
213572
214114
  });
213573
214115
 
213574
214116
  // src/gdskills/manifest/plan.ts
213575
- import { createHash as createHash96 } from "crypto";
214117
+ import { createHash as createHash97 } from "crypto";
213576
214118
  import path419 from "path";
213577
214119
  function destinationRootsForTarget(target) {
213578
214120
  if (target === "claude")
@@ -213584,7 +214126,7 @@ function destinationRootsForTarget(target) {
213584
214126
  }
213585
214127
  function stackInputsSha256(stack) {
213586
214128
  const { detectedAt: _detectedAt, ...rest } = stack;
213587
- return createHash96("sha256").update(stableStringify3(rest)).digest("hex");
214129
+ return createHash97("sha256").update(stableStringify3(rest)).digest("hex");
213588
214130
  }
213589
214131
  function stableStringify3(value) {
213590
214132
  if (Array.isArray(value)) {
@@ -226040,7 +226582,7 @@ var init_detect3 = __esm(() => {
226040
226582
  });
226041
226583
 
226042
226584
  // src/stack/service.ts
226043
- import { createHash as createHash97 } from "crypto";
226585
+ import { createHash as createHash98 } from "crypto";
226044
226586
  import { readFile as readFile233 } from "fs/promises";
226045
226587
  import path446 from "path";
226046
226588
  function stackJsonPath(root) {
@@ -226066,7 +226608,7 @@ function serializeStackDetection(doc) {
226066
226608
  }
226067
226609
  function fingerprintOf(core) {
226068
226610
  const canonical = JSON.stringify(canonicalize2(core));
226069
- return createHash97("sha256").update(canonical, "utf8").digest("hex");
226611
+ return createHash98("sha256").update(canonical, "utf8").digest("hex");
226070
226612
  }
226071
226613
  async function readStackDetection(root) {
226072
226614
  try {
@@ -226340,9 +226882,9 @@ function evaluateCompletion(input, deps) {
226340
226882
  var SCHEMA_VERSION3 = 1;
226341
226883
 
226342
226884
  // src/harness/evidence/redaction.ts
226343
- import { createHash as createHash98 } from "crypto";
226885
+ import { createHash as createHash99 } from "crypto";
226344
226886
  function hashContent(content) {
226345
- return createHash98("sha256").update(content, "utf8").digest("hex");
226887
+ return createHash99("sha256").update(content, "utf8").digest("hex");
226346
226888
  }
226347
226889
  function redactForPersistence(content, deps) {
226348
226890
  const scan = deps.scan(content);
@@ -226392,7 +226934,7 @@ async function metaprojectBlastRadius(port, target) {
226392
226934
  }
226393
226935
 
226394
226936
  // src/harness/session/session.ts
226395
- import { createHash as createHash99 } from "crypto";
226937
+ import { createHash as createHash100 } from "crypto";
226396
226938
  function canonicalStringify(value) {
226397
226939
  if (Array.isArray(value)) {
226398
226940
  return `[${value.map(canonicalStringify).join(",")}]`;
@@ -226405,7 +226947,7 @@ function canonicalStringify(value) {
226405
226947
  return JSON.stringify(value) ?? "null";
226406
226948
  }
226407
226949
  function sha25612(input) {
226408
- return createHash99("sha256").update(input, "utf8").digest("hex");
226950
+ return createHash100("sha256").update(input, "utf8").digest("hex");
226409
226951
  }
226410
226952
  function deepFreeze(value) {
226411
226953
  if (value !== null && typeof value === "object") {
@@ -226549,7 +227091,7 @@ var init_startup = __esm(() => {
226549
227091
  });
226550
227092
 
226551
227093
  // src/harness/run/run.ts
226552
- import { createHash as createHash100 } from "crypto";
227094
+ import { createHash as createHash101 } from "crypto";
226553
227095
  function aliasRunHookToolName(keryxToolName) {
226554
227096
  return RUN_HOOK_TOOL_NAME_ALIASES[keryxToolName] ?? keryxToolName;
226555
227097
  }
@@ -226565,7 +227107,7 @@ function canonicalize3(value) {
226565
227107
  return `{${keys.map((key) => `${JSON.stringify(key)}:${canonicalize3(record[key])}`).join(",")}}`;
226566
227108
  }
226567
227109
  function sha25613(input) {
226568
- return createHash100("sha256").update(input, "utf8").digest("hex");
227110
+ return createHash101("sha256").update(input, "utf8").digest("hex");
226569
227111
  }
226570
227112
  function uniqueInOrder2(values) {
226571
227113
  const seen = new Set;
@@ -227158,7 +227700,7 @@ var init_replay = __esm(() => {
227158
227700
  });
227159
227701
 
227160
227702
  // src/harness/tool/registry.ts
227161
- import { createHash as createHash101 } from "crypto";
227703
+ import { createHash as createHash102 } from "crypto";
227162
227704
  function canonicalize4(value) {
227163
227705
  if (value === null || typeof value !== "object") {
227164
227706
  return JSON.stringify(value) ?? "null";
@@ -227172,7 +227714,7 @@ function canonicalize4(value) {
227172
227714
  return `{${entries.join(",")}}`;
227173
227715
  }
227174
227716
  function sha25614(input) {
227175
- return createHash101("sha256").update(input, "utf8").digest("hex");
227717
+ return createHash102("sha256").update(input, "utf8").digest("hex");
227176
227718
  }
227177
227719
  function definitionHash(definition) {
227178
227720
  return sha25614(canonicalize4(definition));
@@ -227325,9 +227867,9 @@ var init_executor = __esm(() => {
227325
227867
 
227326
227868
  // src/harness/process/real-process-adapter.ts
227327
227869
  import { spawnSync as spawnSync10 } from "child_process";
227328
- import { createHash as createHash102 } from "crypto";
227870
+ import { createHash as createHash103 } from "crypto";
227329
227871
  function sha256Hex11(input) {
227330
- return createHash102("sha256").update(input, "utf8").digest("hex");
227872
+ return createHash103("sha256").update(input, "utf8").digest("hex");
227331
227873
  }
227332
227874
  function byteLength3(chunk) {
227333
227875
  if (chunk === null || chunk === undefined)
@@ -227641,7 +228183,7 @@ function checkApproval(input) {
227641
228183
  }
227642
228184
 
227643
228185
  // src/commands/harness.ts
227644
- import { readFileSync as readFileSync48, writeFileSync as writeFileSync13 } from "fs";
228186
+ import { readFileSync as readFileSync49, writeFileSync as writeFileSync13 } from "fs";
227645
228187
  import path448 from "path";
227646
228188
  import { randomUUID as randomUUID49 } from "crypto";
227647
228189
  import { realpathSync as realpathSync21 } from "fs";
@@ -227884,7 +228426,7 @@ function parseReplayArgs(args) {
227884
228426
  function readJsonFile3(file) {
227885
228427
  let text;
227886
228428
  try {
227887
- text = readFileSync48(file, "utf8");
228429
+ text = readFileSync49(file, "utf8");
227888
228430
  } catch (error) {
227889
228431
  return { ok: false, reason: error instanceof Error ? error.message : String(error) };
227890
228432
  }
@@ -228248,7 +228790,7 @@ function readSpecArg(args) {
228248
228790
  if (specPath === undefined) {
228249
228791
  throw new Error("keryx harness: a --spec <path> argument is required when no spec is injected.");
228250
228792
  }
228251
- return JSON.parse(readFileSync48(specPath, "utf8"));
228793
+ return JSON.parse(readFileSync49(specPath, "utf8"));
228252
228794
  }
228253
228795
  function harnessExtension(args, deps) {
228254
228796
  const { clock, idSeq } = resolveRuntime(deps);
@@ -228431,7 +228973,7 @@ async function sessionsCommand(args) {
228431
228973
  console.log(pad2("ID", 10) + pad2("UPDATED", 22) + pad2("MSGS", 6) + pad2("LIVE", 7) + pad2("MODEL", 24) + "TITLE");
228432
228974
  for (const { summary: s, lease } of listed) {
228433
228975
  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));
228976
+ 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
228977
  if (s.remote !== undefined) {
228436
228978
  console.log(`${" ".repeat(10)}\u21C4 ${describeRemote(s.remote)}`);
228437
228979
  }
@@ -228537,7 +229079,7 @@ function leaseColumn(cwd, sessionId) {
228537
229079
  function pad2(s, n) {
228538
229080
  return s.length >= n ? `${s.slice(0, n - 1)} ` : s + " ".repeat(n - s.length);
228539
229081
  }
228540
- function clip11(s, n) {
229082
+ function clip12(s, n) {
228541
229083
  return s.length > n ? `${s.slice(0, n - 1)}\u2026` : s;
228542
229084
  }
228543
229085
  function printHelp20() {
@@ -229107,7 +229649,7 @@ var init_session3 = __esm(() => {
229107
229649
  });
229108
229650
 
229109
229651
  // src/acp/session-mcp.ts
229110
- import { createHash as createHash103 } from "crypto";
229652
+ import { createHash as createHash104 } from "crypto";
229111
229653
  function isRecord17(value) {
229112
229654
  return typeof value === "object" && value !== null && !Array.isArray(value);
229113
229655
  }
@@ -229234,7 +229776,7 @@ function acpMcpSetKey(parsed) {
229234
229776
  })),
229235
229777
  refused: parsed.refused
229236
229778
  };
229237
- return createHash103("sha256").update(JSON.stringify(canonical)).digest("hex");
229779
+ return createHash104("sha256").update(JSON.stringify(canonical)).digest("hex");
229238
229780
  }
229239
229781
  function startAcpSessionMcp(parsed, options = {}) {
229240
229782
  const refused = parsed.refused.map((problem) => ({ name: problem.name, reason: scrub(problem.reason, parsed.secrets) }));
@@ -230193,9 +230735,9 @@ var init_server2 = __esm(() => {
230193
230735
  });
230194
230736
 
230195
230737
  // src/acp/fixture-provider.ts
230196
- import { readFileSync as readFileSync49 } from "fs";
230738
+ import { readFileSync as readFileSync50 } from "fs";
230197
230739
  function loadAcpFixture(path) {
230198
- const raw = readFileSync49(path, "utf-8");
230740
+ const raw = readFileSync50(path, "utf-8");
230199
230741
  const parsed = JSON.parse(raw);
230200
230742
  const turns = parsed.turns;
230201
230743
  const models = parsed.models !== undefined && parsed.models.length > 0 ? [...parsed.models] : ["fixture-model"];
@@ -232278,7 +232820,7 @@ var init_serve_runner = __esm(() => {
232278
232820
  });
232279
232821
 
232280
232822
  // src/remote/naming.ts
232281
- import { createHash as createHash104 } from "crypto";
232823
+ import { createHash as createHash105 } from "crypto";
232282
232824
  import { hostname as hostname5 } from "os";
232283
232825
  import path451 from "path";
232284
232826
  function nameKey(name) {
@@ -232308,7 +232850,7 @@ function idChars(sessionId) {
232308
232850
  }
232309
232851
  function machineSlug(machine) {
232310
232852
  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)}`;
232853
+ return slug.length > 0 ? slug : `host-${createHash105("sha256").update(machine.normalize("NFC")).digest("hex").slice(0, 6)}`;
232312
232854
  }
232313
232855
  function localMachineName(read = hostname5) {
232314
232856
  let name = "";
@@ -233710,7 +234252,7 @@ var init_durable_log = __esm(() => {
233710
234252
  });
233711
234253
 
233712
234254
  // src/remote/inbound.ts
233713
- import { createHash as createHash105 } from "crypto";
234255
+ import { createHash as createHash106 } from "crypto";
233714
234256
  import { unlinkSync as unlinkSync11 } from "fs";
233715
234257
  import path452 from "path";
233716
234258
  function parseEntry6(value) {
@@ -233743,7 +234285,7 @@ class InboundQueues {
233743
234285
  this.directory = ensureRemoteDir(this.dir, INBOUND_DIRNAME);
233744
234286
  }
233745
234287
  fileFor(bindingKey) {
233746
- const name = createHash105("sha256").update(bindingKey).digest("hex").slice(0, 32);
234288
+ const name = createHash106("sha256").update(bindingKey).digest("hex").slice(0, 32);
233747
234289
  return path452.join(this.directory, `${name}.jsonl`);
233748
234290
  }
233749
234291
  log(bindingKey) {
@@ -238214,7 +238756,7 @@ var init_serve_research_sync = __esm(() => {
238214
238756
 
238215
238757
  // src/intake/assess.ts
238216
238758
  import { randomUUID as randomUUID58 } from "crypto";
238217
- function clip12(text, max) {
238759
+ function clip13(text, max) {
238218
238760
  const flat = redactSensitiveText(text).replace(/[\u0000-\u0008\u000b\u000c\u000e-\u001f\u007f]/g, " ").trim();
238219
238761
  return flat.length > max ? `${flat.slice(0, max)}\u2026` : flat;
238220
238762
  }
@@ -238222,10 +238764,10 @@ function promptFor(input) {
238222
238764
  const { event } = input;
238223
238765
  return [
238224
238766
  `Kind: ${event.kind}`,
238225
- `Title: ${clip12(event.title, TITLE_MAX).replace(/\s+/g, " ")}`,
238767
+ `Title: ${clip13(event.title, TITLE_MAX).replace(/\s+/g, " ")}`,
238226
238768
  `Allowed actions: ${input.allowed.join(", ")}`,
238227
238769
  "Text:",
238228
- clip12(event.body ?? "", BODY_MAX2)
238770
+ clip13(event.body ?? "", BODY_MAX2)
238229
238771
  ].join(`
238230
238772
  `);
238231
238773
  }
@@ -241288,7 +241830,7 @@ var init_agents_external = __esm(() => {
241288
241830
  });
241289
241831
 
241290
241832
  // src/commands/agents-catalog.ts
241291
- import { existsSync as existsSync94, lstatSync as lstatSync10, readFileSync as readFileSync50, writeFileSync as writeFileSync14 } from "fs";
241833
+ import { existsSync as existsSync94, lstatSync as lstatSync10, readFileSync as readFileSync51, writeFileSync as writeFileSync14 } from "fs";
241292
241834
  import path464 from "path";
241293
241835
  function resolveDeps2(deps) {
241294
241836
  return {
@@ -241569,7 +242111,7 @@ function printVerifyHelp2() {
241569
242111
  function readStackPackForGeneration(packJsonPath, stackId) {
241570
242112
  let raw;
241571
242113
  try {
241572
- raw = JSON.parse(readFileSync50(packJsonPath, "utf8"));
242114
+ raw = JSON.parse(readFileSync51(packJsonPath, "utf8"));
241573
242115
  } catch {
241574
242116
  return;
241575
242117
  }
@@ -241694,7 +242236,7 @@ function generateCommand(args, depsIn) {
241694
242236
  }
241695
242237
  const outcomes = [];
241696
242238
  for (const { file, filePath, existed } of prepared) {
241697
- const previous = existed ? readFileSync50(filePath, "utf8") : undefined;
242239
+ const previous = existed ? readFileSync51(filePath, "utf8") : undefined;
241698
242240
  const changed = previous !== file.content;
241699
242241
  if (!check && changed) {
241700
242242
  writeFileSync14(filePath, file.content, "utf8");
@@ -241818,7 +242360,7 @@ var init_reduce = __esm(() => {
241818
242360
  });
241819
242361
 
241820
242362
  // src/commands/agents.ts
241821
- import { readFileSync as readFileSync51 } from "fs";
242363
+ import { readFileSync as readFileSync52 } from "fs";
241822
242364
  async function agentsCommand(args = []) {
241823
242365
  const subcommand = args[0];
241824
242366
  if (!subcommand || subcommand === "--help" || subcommand === "-h") {
@@ -241870,7 +242412,7 @@ function monitorCommand(args) {
241870
242412
  }
241871
242413
  let events;
241872
242414
  try {
241873
- events = parseAgentEvents(readFileSync51(source, "utf8"));
242415
+ events = parseAgentEvents(readFileSync52(source, "utf8"));
241874
242416
  } catch (error) {
241875
242417
  console.error(`Failed to read/parse agent-event source "${source}": ${error.message}`);
241876
242418
  process.exitCode = 1;
@@ -246190,7 +246732,7 @@ var init_product = __esm(() => {
246190
246732
  });
246191
246733
 
246192
246734
  // src/commands/hooks.ts
246193
- import { existsSync as existsSync95, readFileSync as readFileSync52 } from "fs";
246735
+ import { existsSync as existsSync95, readFileSync as readFileSync53 } from "fs";
246194
246736
  import path469 from "path";
246195
246737
  function resolveHomeDir(deps) {
246196
246738
  return resolveHooksHomeDir(process.env, deps.homeDir);
@@ -246615,7 +247157,7 @@ async function runTest(args, deps) {
246615
247157
  if (payloadFileArg !== undefined) {
246616
247158
  let raw;
246617
247159
  try {
246618
- raw = readFileSync52(payloadFileArg, "utf8");
247160
+ raw = readFileSync53(payloadFileArg, "utf8");
246619
247161
  } catch (err) {
246620
247162
  fail8(`Could not read --payload-file "${payloadFileArg}": ${err instanceof Error ? err.message : String(err)}`);
246621
247163
  }
@@ -246755,7 +247297,7 @@ function emptyDoc() {
246755
247297
  function readDoc(filePath) {
246756
247298
  let raw;
246757
247299
  try {
246758
- raw = readFileSync52(filePath, "utf8");
247300
+ raw = readFileSync53(filePath, "utf8");
246759
247301
  } catch (err) {
246760
247302
  if (err instanceof Error && "code" in err && err.code === "ENOENT") {
246761
247303
  return emptyDoc();