@coreplane/switchboard 1.260.2 → 1.260.4

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.
Files changed (129) hide show
  1. package/dist/assets/deploy/cloudflare-memory/backgroundTasks.ts +27 -3
  2. package/dist/assets/deploy/cloudflare-memory/testDiagnostics.ts +227 -0
  3. package/dist/assets/deploy/cloudflare-memory/testDiagnosticsProtocol.ts +22 -0
  4. package/dist/assets/deploy/cloudflare-memory/testDiagnosticsReporter.ts +88 -0
  5. package/dist/assets/deploy/cloudflare-memory/tsconfig.json +14 -2
  6. package/dist/assets/deploy/cloudflare-resident/Dockerfile +11 -2
  7. package/dist/assets/package-lock.json +3 -3
  8. package/dist/assets/package.json +4 -3
  9. package/dist/assets/project.json +4 -0
  10. package/dist/assets/source.json +3 -3
  11. package/dist/assets/src/agents/registry.ts +6 -5
  12. package/dist/assets/src/core/coordinator/contract.ts +48 -2
  13. package/dist/assets/src/core/coordinator/driver.ts +40 -5
  14. package/dist/assets/src/core/reviewVerdict.ts +15 -5
  15. package/dist/assets/src/core/runFriction.ts +1 -1
  16. package/dist/assets/src/core/ship/contract.ts +2 -2
  17. package/dist/assets/src/core/ship/coordinator.ts +236 -108
  18. package/dist/assets/src/core/ship/renewal.ts +3 -1
  19. package/dist/assets/src/execution/bashTimeout.ts +1 -1
  20. package/dist/assets/src/execution/sandboxErrors.ts +6 -6
  21. package/dist/assets/web/dist/.vite/manifest.json +488 -488
  22. package/dist/assets/web/dist/assets/{AppShell-CSXDCV4x.js → AppShell-DPJVhUGP.js} +1 -1
  23. package/dist/assets/web/dist/assets/{CostChart-DsuPS2g7.js → CostChart-4TeDdbvJ.js} +1 -1
  24. package/dist/assets/web/dist/assets/CostsPage-8HDgvuWB.js +1 -0
  25. package/dist/assets/web/dist/assets/{DeliveryPage-62qCSLvk.js → DeliveryPage-BaemWKW8.js} +1 -1
  26. package/dist/assets/web/dist/assets/{HomePage-Dk3HRBc3.js → HomePage-fgKQHLaO.js} +1 -1
  27. package/dist/assets/web/dist/assets/{InputMenu-BdZXnibG.js → InputMenu-BRV35UWP.js} +1 -1
  28. package/dist/assets/web/dist/assets/{MetricsPage-o5fSaCTe.js → MetricsPage-BQBuIuYU.js} +1 -1
  29. package/dist/assets/web/dist/assets/{NotFoundPage-DHuWyjlq.js → NotFoundPage-CDjMfCcj.js} +1 -1
  30. package/dist/assets/web/dist/assets/{PendingTurnRow-CeTGOXUy.js → PendingTurnRow-vuWiDzvz.js} +1 -1
  31. package/dist/assets/web/dist/assets/{PlanePage-RD0M6O6W.js → PlanePage-DXMJf8FB.js} +1 -1
  32. package/dist/assets/web/dist/assets/{ResidentDetailPage-Dl2KId95.js → ResidentDetailPage-DdC8yU_S.js} +1 -1
  33. package/dist/assets/web/dist/assets/{ResidentsIndexPage-DBy43I1R.js → ResidentsIndexPage-CSM-KODv.js} +1 -1
  34. package/dist/assets/web/dist/assets/{RunFoldRow-CwHDL-Mo.js → RunFoldRow-Csik3wD6.js} +1 -1
  35. package/dist/assets/web/dist/assets/{RunRoutePage-Du1HqLZs.js → RunRoutePage-jo6NZxsP.js} +3 -3
  36. package/dist/assets/web/dist/assets/{RunsIndexPage-BZphy3Hr.js → RunsIndexPage-Dx1Y4sSq.js} +1 -1
  37. package/dist/assets/web/dist/assets/{RunsTabs-kHbbxVHx.js → RunsTabs-C7Ua3XX5.js} +1 -1
  38. package/dist/assets/web/dist/assets/{ScheduledPage-B7jh898t.js → ScheduledPage-BuB_NZyR.js} +1 -1
  39. package/dist/assets/web/dist/assets/{SettingSelect-BfID5nF4.js → SettingSelect-CEeA4iuC.js} +1 -1
  40. package/dist/assets/web/dist/assets/{SettingsPage-3uLEsiiE.js → SettingsPage-Cn96jB6Q.js} +1 -1
  41. package/dist/assets/web/dist/assets/{SilentTurn-CKxSxnQC.js → SilentTurn-D2cJfpEq.js} +1 -1
  42. package/dist/assets/web/dist/assets/{SlackMark-CtiHrNjr.js → SlackMark-DB0MGiF5.js} +1 -1
  43. package/dist/assets/web/dist/assets/{StatusDot-x7vcK0iE.js → StatusDot-DDHUCPVJ.js} +1 -1
  44. package/dist/assets/web/dist/assets/{Tooltip-wVXCWhMp.js → Tooltip-B4VIHkAI.js} +1 -1
  45. package/dist/assets/web/dist/assets/{UnitRoutePage-V5BXK7FH.js → UnitRoutePage-C-2DfjLI.js} +1 -1
  46. package/dist/assets/web/dist/assets/{angular-html-C8b5YTHf.js → angular-html-BRRhsdQE.js} +1 -1
  47. package/dist/assets/web/dist/assets/{angular-ts-C3f4WYNe.js → angular-ts-BFj-mqCm.js} +1 -1
  48. package/dist/assets/web/dist/assets/{apl-DSHxUJc3.js → apl-CsBuy1IL.js} +1 -1
  49. package/dist/assets/web/dist/assets/{astro-BSGCCBP6.js → astro-cVaqxBZq.js} +1 -1
  50. package/dist/assets/web/dist/assets/{blade-DFeYrMKk.js → blade-MEP5mWha.js} +1 -1
  51. package/dist/assets/web/dist/assets/{browser-B6T5hjfR.js → browser-CCImen5O.js} +1 -1
  52. package/dist/assets/web/dist/assets/{c-L_QOnFVv.js → c-D4NGQ_Pd.js} +1 -1
  53. package/dist/assets/web/dist/assets/{chapel-DLQWUwhu.js → chapel-BkWImsIy.js} +1 -1
  54. package/dist/assets/web/dist/assets/{cobol-1DlMmrEO.js → cobol-RPDSPSET.js} +1 -1
  55. package/dist/assets/web/dist/assets/{coffee-BTB6os6N.js → coffee-DZ5z7oVX.js} +1 -1
  56. package/dist/assets/web/dist/assets/{cpp-BvJI2z3u.js → cpp-CrBtIaRZ.js} +1 -1
  57. package/dist/assets/web/dist/assets/{crystal-B11wGW_h.js → crystal-DJesYi6f.js} +1 -1
  58. package/dist/assets/web/dist/assets/{css-BiIWx5Pw.js → css-DO9mjau-.js} +1 -1
  59. package/dist/assets/web/dist/assets/{dist-C-XGnvM4.js → dist-Cf1CmCSK.js} +2 -2
  60. package/dist/assets/web/dist/assets/{edge-nSglEXaD.js → edge-C3VzbSW0.js} +1 -1
  61. package/dist/assets/web/dist/assets/{elixir-x7o8qMl8.js → elixir-BkD_Vhfe.js} +1 -1
  62. package/dist/assets/web/dist/assets/{elm-DGrRyqTQ.js → elm-KIM0mFPk.js} +1 -1
  63. package/dist/assets/web/dist/assets/{erb--_dh58Nw.js → erb-D2ruCqb2.js} +1 -1
  64. package/dist/assets/web/dist/assets/{eventSource-Bj_dZf6M.js → eventSource-Cv0AFlMi.js} +1 -1
  65. package/dist/assets/web/dist/assets/{git-rebase-DbpUXwyg.js → git-rebase-CnfbQTxA.js} +1 -1
  66. package/dist/assets/web/dist/assets/{glimmer-js-BG0CuZG5.js → glimmer-js-CbiwFXWH.js} +1 -1
  67. package/dist/assets/web/dist/assets/{glimmer-ts-CQIl5qyR.js → glimmer-ts-DV8ii0Pv.js} +1 -1
  68. package/dist/assets/web/dist/assets/{glsl-9PJNQTcw.js → glsl-CgmznWkA.js} +1 -1
  69. package/dist/assets/web/dist/assets/{graphql-CzVTzjEI.js → graphql-BK-bBYfH.js} +1 -1
  70. package/dist/assets/web/dist/assets/{hack-BhCmAz3Q.js → hack-DnwTTP5n.js} +1 -1
  71. package/dist/assets/web/dist/assets/{haml-BmI12COR.js → haml-DAVJsWwT.js} +1 -1
  72. package/dist/assets/web/dist/assets/{handlebars-Cib-ISTS.js → handlebars-voOVfxFL.js} +1 -1
  73. package/dist/assets/web/dist/assets/{html-D3lDapvI.js → html-3FTqwaPk.js} +1 -1
  74. package/dist/assets/web/dist/assets/{html-derivative-DRPY8vOK.js → html-derivative-CSlhLDQn.js} +1 -1
  75. package/dist/assets/web/dist/assets/{http-B9VttK6m.js → http-Vx4ilPfS.js} +1 -1
  76. package/dist/assets/web/dist/assets/{hurl-C0saNJYj.js → hurl-DxyZDE3R.js} +1 -1
  77. package/dist/assets/web/dist/assets/{java-BCFbtYAj.js → java-nWqdjNJG.js} +1 -1
  78. package/dist/assets/web/dist/assets/{javascript-nwBjnrhh.js → javascript-Dr82HOwp.js} +1 -1
  79. package/dist/assets/web/dist/assets/{jinja-CMYO9byN.js → jinja-BhjLrPOt.js} +1 -1
  80. package/dist/assets/web/dist/assets/{jison-Cko8HnE_.js → jison-DilGIUa8.js} +1 -1
  81. package/dist/assets/web/dist/assets/{json-Dzv__YsX.js → json-CrOcvmhJ.js} +1 -1
  82. package/dist/assets/web/dist/assets/{jsx-CTFxagih.js → jsx-BW_RX-fo.js} +1 -1
  83. package/dist/assets/web/dist/assets/{julia-_Xvten1-.js → julia-CWH071DB.js} +1 -1
  84. package/dist/assets/web/dist/assets/{just-bLps4qOu.js → just-DkhhoWBy.js} +1 -1
  85. package/dist/assets/web/dist/assets/{latex-D6UaYsUP.js → latex-cuHJ4TUT.js} +1 -1
  86. package/dist/assets/web/dist/assets/{liquid-CoDIeULa.js → liquid-DG6afYQN.js} +1 -1
  87. package/dist/assets/web/dist/assets/{lua-D1KFRnVf.js → lua-C5CMnCPS.js} +1 -1
  88. package/dist/assets/web/dist/assets/{main-DAL--hyj.js → main-DxPqaFu9.js} +3 -3
  89. package/dist/assets/web/dist/assets/{marko-B5N5XcOw.js → marko-Dy2_DuC4.js} +1 -1
  90. package/dist/assets/web/dist/assets/{mdc-uxzjQO3q.js → mdc-BMKb_EeA.js} +1 -1
  91. package/dist/assets/web/dist/assets/{nginx-BMJxs2TK.js → nginx-og0RgMKf.js} +1 -1
  92. package/dist/assets/web/dist/assets/{nim-DqQ3-8p1.js → nim-CQxTQQP0.js} +1 -1
  93. package/dist/assets/web/dist/assets/{org-DsY6LK59.js → org-b5lkq_Pl.js} +1 -1
  94. package/dist/assets/web/dist/assets/{perl--pq9iJFp.js → perl-D9X5_i_J.js} +1 -1
  95. package/dist/assets/web/dist/assets/{php-RHCMbsM2.js → php-CwUxvLX0.js} +1 -1
  96. package/dist/assets/web/dist/assets/{pug-CFNq_FCp.js → pug-uOIOExr0.js} +1 -1
  97. package/dist/assets/web/dist/assets/{qml-C9Acx9E8.js → qml-g5RoADf-.js} +1 -1
  98. package/dist/assets/web/dist/assets/{r-DxlU8FBd.js → r-5LdAopD3.js} +1 -1
  99. package/dist/assets/web/dist/assets/{razor-CLJ2kKih.js → razor-DwrvFLYQ.js} +1 -1
  100. package/dist/assets/web/dist/assets/{regexp-CzmdGoEp.js → regexp-dGtAaxC-.js} +1 -1
  101. package/dist/assets/web/dist/assets/{rst-D9yuxBET.js → rst-wFdW0brr.js} +1 -1
  102. package/dist/assets/web/dist/assets/{ruby-CIRcEvnD.js → ruby-DPglN6I9.js} +1 -1
  103. package/dist/assets/web/dist/assets/{sas-LOl-uUPh.js → sas-CUbCs40d.js} +1 -1
  104. package/dist/assets/web/dist/assets/{scss-BX2_sZHY.js → scss-B-DUiI1c.js} +1 -1
  105. package/dist/assets/web/dist/assets/{shellscript-4d-QvKkY.js → shellscript-lGOV7Ram.js} +1 -1
  106. package/dist/assets/web/dist/assets/{shellsession-DhB16UGy.js → shellsession-BDWtAAjr.js} +1 -1
  107. package/dist/assets/web/dist/assets/{soy-Cm1xTUBr.js → soy-zod_dcOy.js} +1 -1
  108. package/dist/assets/web/dist/assets/{sql-BsfcBJdu.js → sql-BG0HSSoM.js} +1 -1
  109. package/dist/assets/web/dist/assets/{sseReplay-BqgOggyQ.js → sseReplay-BzoeVxZB.js} +1 -1
  110. package/dist/assets/web/dist/assets/{stata-Dwat3BSw.js → stata-BVUMLp1W.js} +1 -1
  111. package/dist/assets/web/dist/assets/{surrealql-P4rtxzAW.js → surrealql-DiSUbHKF.js} +1 -1
  112. package/dist/assets/web/dist/assets/{svelte-DTcsi8S8.js → svelte-DZenT45Y.js} +1 -1
  113. package/dist/assets/web/dist/assets/{templ-D-kP0Tg5.js → templ-BWKRNM0-.js} +1 -1
  114. package/dist/assets/web/dist/assets/{tex-D-4vuedK.js → tex-CbMzBlkK.js} +1 -1
  115. package/dist/assets/web/dist/assets/{ts-tags-jEEWYxsu.js → ts-tags-CZsVorDN.js} +1 -1
  116. package/dist/assets/web/dist/assets/{tsx-CFnlrVzT.js → tsx-R1r3mH4W.js} +1 -1
  117. package/dist/assets/web/dist/assets/{twig-D5rGrPjs.js → twig-BjPMyjKH.js} +1 -1
  118. package/dist/assets/web/dist/assets/{typescript-nYIQ35or.js → typescript-Bx-66VN_.js} +1 -1
  119. package/dist/assets/web/dist/assets/{typst-3-UX8dna.js → typst-fW037Eo6.js} +1 -1
  120. package/dist/assets/web/dist/assets/{vue-CLIPIX03.js → vue-D1V_GNxW.js} +1 -1
  121. package/dist/assets/web/dist/assets/{vue-html-Ci9CpkHU.js → vue-html-C-4pTZ-s.js} +1 -1
  122. package/dist/assets/web/dist/assets/{vue-vine-DL5pEYYk.js → vue-vine-C-QsX3hR.js} +1 -1
  123. package/dist/assets/web/dist/assets/{wallClock-DmaC1sKA.js → wallClock-x0hjr0J_.js} +1 -1
  124. package/dist/assets/web/dist/assets/{xml-bO80qikC.js → xml-BJTJXPgC.js} +1 -1
  125. package/dist/assets/web/dist/assets/{xsl-BPze60Ej.js → xsl-Xc9lukqe.js} +1 -1
  126. package/dist/assets/web/dist/assets/{yaml-BQUSon_o.js → yaml-Dw-FMsQa.js} +1 -1
  127. package/dist/cli.js +505 -253
  128. package/package.json +1 -1
  129. package/dist/assets/web/dist/assets/CostsPage-BaeWnm-o.js +0 -1
@@ -40,6 +40,7 @@
40
40
  // the shim Worker imports this by relative path.
41
41
 
42
42
  import { DAY_MS, DEFAULT_GRANT, GRANT_RENEWALS_MAX, IDLE_DAYS_MAX, type Grant, type GrantSource } from "../budgets.js";
43
+ import { isFindingShape, type Finding } from "../reviewVerdict.js";
43
44
  import { DEFAULT_VERBOSITY, isVerbosity, type Verbosity } from "../verbosity.js";
44
45
  import {
45
46
  applyReturn,
@@ -343,6 +344,8 @@ function readRecordReturn(step: string, a: BotAnswer): StepReturn {
343
344
  // they were written (the run record's validator), read here as they are.
344
345
  const facts = run as unknown as Omit<Extract<ChildFacts, { finished: true }>, "finished" | "status">;
345
346
  const {
347
+ finishedAt,
348
+ reviewAskedAt,
346
349
  finalReply,
347
350
  pr,
348
351
  headSha,
@@ -367,6 +370,8 @@ function readRecordReturn(step: string, a: BotAnswer): StepReturn {
367
370
  run: {
368
371
  finished: true,
369
372
  status: run.status as Extract<ChildFacts, { finished: true }>["status"],
373
+ ...(typeof finishedAt === "number" ? { finishedAt } : {}),
374
+ ...(typeof reviewAskedAt === "number" ? { reviewAskedAt } : {}),
370
375
  ...(finalReply !== undefined ? { finalReply } : {}),
371
376
  ...(pr !== undefined ? { pr } : {}),
372
377
  ...(headSha !== undefined ? { headSha } : {}),
@@ -403,6 +408,33 @@ function isCommitChecks(v: unknown): v is { total: number; pending: string[]; fa
403
408
  return typeof c.total === "number" && names(c.pending) && names(c.failed);
404
409
  }
405
410
 
411
+ function entryHumanGate(v: unknown):
412
+ | {
413
+ round: number;
414
+ findings: Finding[];
415
+ verdict: "approve" | "request_changes";
416
+ answer: string;
417
+ author: string;
418
+ commentId: string;
419
+ }
420
+ | undefined {
421
+ if (typeof v !== "object" || v === null) return undefined;
422
+ const row = v as Record<string, unknown>;
423
+ if (typeof row.round !== "number" || !Number.isInteger(row.round) || row.round < 1) return undefined;
424
+ if (!Array.isArray(row.findings) || !row.findings.every(isFindingShape)) return undefined;
425
+ if (row.verdict !== "approve" && row.verdict !== "request_changes") return undefined;
426
+ if (typeof row.answer !== "string" || typeof row.author !== "string" || typeof row.commentId !== "string")
427
+ return undefined;
428
+ return {
429
+ round: row.round,
430
+ findings: row.findings,
431
+ verdict: row.verdict,
432
+ answer: row.answer,
433
+ author: row.author,
434
+ commentId: row.commentId,
435
+ };
436
+ }
437
+
406
438
  function prCheckReturn(step: string, a: BotAnswer): StepReturn {
407
439
  const { ok, state, prNumber, url, headSha, sha, mergedAt, at } = a.body;
408
440
  if (ok === true && state === "none") {
@@ -448,6 +480,7 @@ function prCheckReturn(step: string, a: BotAnswer): StepReturn {
448
480
  // The base's merge-queue rule beside the checks (issue 2011): the
449
481
  // merge:person report says the person's merge is queued.
450
482
  ...(typeof a.body.baseHasMergeQueue === "boolean" ? { baseHasMergeQueue: a.body.baseHasMergeQueue } : {}),
483
+ ...(entryHumanGate(a.body.humanGate) !== undefined ? { humanGate: entryHumanGate(a.body.humanGate)! } : {}),
451
484
  },
452
485
  at,
453
486
  };
@@ -773,7 +806,7 @@ async function tellStepThrew(
773
806
  : `in round ${at.round.index} (\`${at.step}\`)`;
774
807
  const report =
775
808
  `⚠️ The runner failed ${where}: ${line}\n\n` +
776
- "Re-issue `agent:ship` in this thread to continue — a pull request already approved with green checks resumes at the checks step, never at a fresh coding round.";
809
+ "The unit remains bound to this thread; the next reply continues it, and a pull request already approved with green checks resumes at the checks step, never at a fresh coding round.";
777
810
  const body = {
778
811
  ...tag,
779
812
  ending: {
@@ -961,6 +994,7 @@ async function runUnit(
961
994
  ...(note.ending.from !== undefined ? { from: note.ending.from } : {}),
962
995
  spendUsd: note.ending.spendUsd,
963
996
  ...(note.ending.handoff !== undefined ? { handoff: note.ending.handoff } : {}),
997
+ ...(note.ending.humanGate !== undefined ? { humanGate: note.ending.humanGate } : {}),
964
998
  }
965
999
  : {}),
966
1000
  },
@@ -1007,14 +1041,14 @@ async function runUnit(
1007
1041
 
1008
1042
  /** The report of a unit the hard stop ended before it ran (record 0060; issue 1924). */
1009
1043
  function stoppedReport(unit: string): string {
1010
- return `⏹ Stopped: the pipeline's hosted parent was hard-stopped, so ${unit} was ended without running. Re-issue the plan naming the remaining units to run them.`;
1044
+ return `⏹ Stopped: the pipeline's hosted parent was hard-stopped, so ${unit} was ended without running. The original plan remains the durable task for any later pipeline.`;
1011
1045
  }
1012
1046
 
1013
1047
  function blockedReport(unit: string, dep: string, depEnding: string): string {
1014
1048
  if (depEnding === "blocked")
1015
- return `⛔ Blocked: ${unit} waits on ${dep}, which is blocked itself. Re-issue the plan naming the remaining units once it is resolved.`;
1049
+ return `⛔ Blocked: ${unit} waits on ${dep}, which is blocked itself. A later pipeline recognizes both units once the dependency is resolved.`;
1016
1050
  const person = depEnding === "merge_ready";
1017
- return `⛔ Blocked: ${unit} waits on ${dep}, which ended ${depEnding}${person ? " — a person's merge" : ""}. Re-issue the plan naming the remaining units once it is ${person ? "merged" : "resolved"}.`;
1051
+ return `⛔ Blocked: ${unit} waits on ${dep}, which ended ${depEnding}${person ? " — a person's merge" : ""}. A later pipeline recognizes the dependency once it is ${person ? "merged" : "resolved"}.`;
1018
1052
  }
1019
1053
 
1020
1054
  /** End a unit that never entered `runUnit` under the same failure net. */
@@ -1171,6 +1205,7 @@ async function waitOnIdle(
1171
1205
  ...(answer.runId !== undefined ? { previousRunId: answer.runId } : {}),
1172
1206
  ...(answer.handoff !== undefined ? { previousHandoff: answer.handoff } : {}),
1173
1207
  ...(answer.texts.length > 0 ? { texts: answer.texts } : {}),
1208
+ ...(answer.humanGate !== undefined ? { humanGate: answer.humanGate } : {}),
1174
1209
  ...(answer.leaseMs !== undefined
1175
1210
  ? { resume: { leaseMs: answer.leaseMs, attempt: (currentSession?.resume?.attempt ?? 0) + 1 } }
1176
1211
  : {}),
@@ -1240,7 +1275,7 @@ async function walk(step: StepRunner, bot: CoordinatorBot, instanceId: string):
1240
1275
  next,
1241
1276
  stepPrefixOf(next, session),
1242
1277
  ending,
1243
- plan.idleDays,
1278
+ ending.parkDays ?? plan.idleDays,
1244
1279
  session,
1245
1280
  );
1246
1281
  if (parked.kind === "ending") {
@@ -328,8 +328,14 @@ function findingCounts(findings: readonly Finding[] | undefined): string {
328
328
  return `${findings.length} finding${findings.length === 1 ? "" : "s"}: ${by}`;
329
329
  }
330
330
 
331
- /** A table cell: pipes escaped so the row holds, already one line. */
332
- const cell = (text: string): string => oneLine(text).replace(/\|/g, "\\|");
331
+ /** A one-line Markdown table cell. Backslashes are escaped before pipes so
332
+ * input cannot consume the pipe escape and split the row. */
333
+ export const escapeMarkdownTableCell = (text: string): string =>
334
+ oneLine(text).replace(/\\/g, "\\\\").replace(/\|/g, "\\|");
335
+
336
+ /** Decode the two escapes emitted by `escapeMarkdownTableCell`. This also
337
+ * accepts the legacy form that escaped pipes without first escaping slashes. */
338
+ export const unescapeMarkdownTableCell = (text: string): string => text.replace(/\\([\\|])/g, "$1");
333
339
 
334
340
  /** Whether a finding's `file` is a path a blob URL can point at: no
335
341
  * whitespace, no backtick, not a URL. */
@@ -339,7 +345,7 @@ const isRepoPath = (file: string): boolean => /^[^\s`]+$/.test(file) && !/^[a-z]
339
345
  * file is a path and a target is known. */
340
346
  function whereCell(f: Finding, target: ReviewBodyTarget | undefined): string {
341
347
  const location = f.line !== undefined ? `${f.file}:${f.line}` : f.file;
342
- const label = `\`${cell(location)}\``;
348
+ const label = `\`${escapeMarkdownTableCell(location)}\``;
343
349
  if (!target || !isRepoPath(f.file)) return label;
344
350
  const path = encodeURI(f.file).replace(/#/g, "%23").replace(/\?/g, "%3F");
345
351
  const url = `https://github.com/${target.repo}/blob/${target.head}/${path}${f.line !== undefined ? `#L${f.line}` : ""}`;
@@ -360,6 +366,7 @@ function verdictMarker(verdict: ReviewVerdict | undefined, target: ReviewBodyTar
360
366
  id: f.id,
361
367
  severity: f.severity,
362
368
  file: f.file,
369
+ title: f.title,
363
370
  ...(f.line !== undefined ? { line: f.line } : {}),
364
371
  ...(f.humanGated ? { humanGated: true } : {}),
365
372
  })),
@@ -395,7 +402,10 @@ export function buildReviewPostBody(
395
402
  [
396
403
  "| Severity | Finding | Where |",
397
404
  "| --- | --- | --- |",
398
- ...findings.map((f) => `| ${f.severity} | **${cell(f.id)}** ${cell(f.title)} | ${whereCell(f, target)} |`),
405
+ ...findings.map(
406
+ (f) =>
407
+ `| ${f.severity} | **${escapeMarkdownTableCell(f.id)}** ${escapeMarkdownTableCell(f.title)} | ${whereCell(f, target)} |`,
408
+ ),
399
409
  ].join("\n"),
400
410
  );
401
411
  }
@@ -517,7 +527,7 @@ const DISPOSITION_KINDS: readonly string[] = ["fixed", "declined"];
517
527
 
518
528
  const isRecordLike = (v: unknown): v is Record<string, unknown> => typeof v === "object" && v !== null;
519
529
 
520
- function isFindingShape(v: unknown): v is Finding {
530
+ export function isFindingShape(v: unknown): v is Finding {
521
531
  if (!isRecordLike(v)) return false;
522
532
  return (
523
533
  typeof v.id === "string" &&
@@ -621,7 +621,7 @@ export function analyzeRunFriction(events: readonly RunEvent[], opts: FrictionOp
621
621
  findings.push({
622
622
  category: "infra_failure",
623
623
  severity: "high",
624
- summary: `no result for tool call (run ended mid-tool): ${event.summary}`,
624
+ summary: `no result for tool call (the run ended mid-tool): ${event.summary}`,
625
625
  tool: event.tool,
626
626
  eventIndex: index,
627
627
  ...(durationMs !== undefined ? { durationMs } : {}),
@@ -480,9 +480,9 @@ function renderFirstInstruction(rebase: ChildContract["rebase"]): string {
480
480
  const gates = `${FAST_GATES_POINTER} ${GATE_RECEIPTS}`;
481
481
  return (
482
482
  `Rebase ${branch} onto ${onto} before any other work — the parent unit has merged and the base has moved; ` +
483
- `the only writes are your own on that branch. A conflict ends the unit: report it as the handoff and stop. ` +
483
+ `the only writes are your own on that branch. ` +
484
484
  `Push the branch as soon as the change exists and the fast gates pass — the project's full verification ` +
485
- `is CI's gate, run there after the push with any fix as a further commit; an unpushed tree does not ` +
485
+ `is CI's gate and runs there after the push, with any fix as a further commit; an unpushed tree does not ` +
486
486
  `survive the run's end. ${gates} ${REBASE_POINTER} At the wind-down note, ` +
487
487
  `commit and push what compiles, say what does not, then answer. ${TIMEOUT_ON_LONG_COMMANDS}`
488
488
  );