@runfusion/fusion 0.77.0-beta.12 → 0.77.0-beta.13

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 (147) hide show
  1. package/dist/bin.js +1636 -903
  2. package/dist/child-process-worker.js +1064 -466
  3. package/dist/client/.vite/manifest.json +260 -260
  4. package/dist/client/assets/AgentDetailView-DPoh46Ym.css +1 -0
  5. package/dist/client/assets/{AgentDetailView-BZfDgV8f.js → AgentDetailView-DwCr9HOY.js} +1 -1
  6. package/dist/client/assets/{AgentPermissionPolicyEditor-8yBqnwbx.js → AgentPermissionPolicyEditor-CXRP1R7V.js} +1 -1
  7. package/dist/client/assets/{AgentsView-BjMOtIMq.js → AgentsView-DEVhP_Ad.js} +3 -3
  8. package/dist/client/assets/CommandCenter-BshTzOtB.css +1 -0
  9. package/dist/client/assets/{CommandCenter-CDuO3HPd.js → CommandCenter-sNTR0N1L.js} +22 -22
  10. package/dist/client/assets/{DevServerView-C96RV0QJ.js → DevServerView-X1Ncu9x-.js} +1 -1
  11. package/dist/client/assets/{DirectoryPicker-iRielXf7.js → DirectoryPicker-UrDTGJYj.js} +1 -1
  12. package/dist/client/assets/{DocumentsView-DhAUnUD_.js → DocumentsView-DyyfLF1R.js} +1 -1
  13. package/dist/client/assets/{EvalsView--n1p2Bt0.js → EvalsView-DgMdhmj1.js} +1 -1
  14. package/dist/client/assets/{ExperimentalAgentOnboardingModal-DraglCCV.js → ExperimentalAgentOnboardingModal-DqETOMkr.js} +1 -1
  15. package/dist/client/assets/{GoalsView-BOWOua5t.js → GoalsView-Cai1_q1j.js} +1 -1
  16. package/dist/client/assets/{InsightsView-C5YcJZSc.js → InsightsView-DN9i_pr2.js} +1 -1
  17. package/dist/client/assets/{MemoryView-DsCuGVGx.js → MemoryView-DpVhI_BB.js} +1 -1
  18. package/dist/client/assets/{PatchnodeView-CzgChkJV.js → PatchnodeView-C9glaWoU.js} +1 -1
  19. package/dist/client/assets/{PiExtensionsManager-qYbmaigE.js → PiExtensionsManager-DkJ3I9ur.js} +1 -1
  20. package/dist/client/assets/{PluginManager-Cklj4Smo.js → PluginManager-C3BKqDE-.js} +1 -1
  21. package/dist/client/assets/{PullRequestView-C6vozTTI.js → PullRequestView-BCPolzJY.js} +1 -1
  22. package/dist/client/assets/{ReportModal-B0LU4E4L.js → ReportModal-CBc5lSf3.js} +1 -1
  23. package/dist/client/assets/{ResearchView-CpESx46e.js → ResearchView-BInIb1tK.js} +1 -1
  24. package/dist/client/assets/{SecretsView-8ZdH9Yzs.js → SecretsView-Db2mKOcp.js} +1 -1
  25. package/dist/client/assets/SettingsModal-BEULP2a7.css +1 -0
  26. package/dist/client/assets/SettingsModal-Dn9vcqW7.js +21 -0
  27. package/dist/client/assets/{SettingsModal-BJXu-dhz.js → SettingsModal-tnr4Y2cG.js} +1 -1
  28. package/dist/client/assets/{SettingsTextareaRow-th-q0_2k.js → SettingsTextareaRow-DDIEo7kX.js} +1 -1
  29. package/dist/client/assets/{SetupWizardModal-CBElxrRA.js → SetupWizardModal-BgpCpOJi.js} +2 -2
  30. package/dist/client/assets/{SkillsView-BuoicLbJ.js → SkillsView-C4_dPxaH.js} +1 -1
  31. package/dist/client/assets/{WorkflowNodeEditor-BDw0LpWM.js → WorkflowNodeEditor-BRIoMtdz.js} +1 -1
  32. package/dist/client/assets/{agent-import-generation-CNridJHO.js → agent-import-generation-DZjrAuLD.js} +1 -1
  33. package/dist/client/assets/{agentsOrgChartLayout-BopFGp0r.js → agentsOrgChartLayout-T3vfKGm-.js} +1 -1
  34. package/dist/client/assets/{app-DKzqBaeH.js → app-BUPxz3_I.js} +1 -1
  35. package/dist/client/assets/{app-y8x9KYqa.js → app-Bsb-vSJ2.js} +3 -3
  36. package/dist/client/assets/{app-DkJZhePX.js → app-CDMzhkVk.js} +1 -1
  37. package/dist/client/assets/{app-BtBMuy2Z.js → app-Ca3_7roP.js} +3 -3
  38. package/dist/client/assets/{app-JnrgtY9I.js → app-CaiQxtA9.js} +3 -3
  39. package/dist/client/assets/{app-LrO9EOLE.js → app-D7N3gRgK.js} +3 -3
  40. package/dist/client/assets/{app-Cv-4fcPl.js → app-Du0s0Xs8.js} +1 -1
  41. package/dist/client/assets/{architectureDiagram-3BPJPVTR-2_yoUOD5.js → architectureDiagram-3BPJPVTR-CG7lqp3T.js} +1 -1
  42. package/dist/client/assets/{blockDiagram-GPEHLZMM-ueBSwDJh.js → blockDiagram-GPEHLZMM-CCQyln9-.js} +1 -1
  43. package/dist/client/assets/{c4Diagram-AAUBKEIU-D0cUU3pE.js → c4Diagram-AAUBKEIU-DgTWfMuv.js} +1 -1
  44. package/dist/client/assets/channel-DQn8tAbt.js +1 -0
  45. package/dist/client/assets/{chunk-2J33WTMH-DKJnGdD6.js → chunk-2J33WTMH-DbvgPKWu.js} +1 -1
  46. package/dist/client/assets/{chunk-4BX2VUAB-BUrgUtvt.js → chunk-4BX2VUAB-Jeug78io.js} +1 -1
  47. package/dist/client/assets/{chunk-55IACEB6-DHy2hCYz.js → chunk-55IACEB6-wSkfWEqo.js} +1 -1
  48. package/dist/client/assets/{chunk-727SXJPM-Dgbhllem.js → chunk-727SXJPM-o-cSBR4a.js} +1 -1
  49. package/dist/client/assets/{chunk-AQP2D5EJ-tPbicvl-.js → chunk-AQP2D5EJ-9iL_Co48.js} +1 -1
  50. package/dist/client/assets/{chunk-FMBD7UC4-BpHFGFmo.js → chunk-FMBD7UC4-HKJKjpLt.js} +1 -1
  51. package/dist/client/assets/{chunk-ND2GUHAM-DUTQ63ld.js → chunk-ND2GUHAM-DZUdY7Yv.js} +1 -1
  52. package/dist/client/assets/{chunk-QZHKN3VN-ChCf_usL.js → chunk-QZHKN3VN-AFzlVJep.js} +1 -1
  53. package/dist/client/assets/classDiagram-4FO5ZUOK-BVZs0DmH.js +1 -0
  54. package/dist/client/assets/classDiagram-v2-Q7XG4LA2-BVZs0DmH.js +1 -0
  55. package/dist/client/assets/{cose-bilkent-S5V4N54A-DgWzHtF9.js → cose-bilkent-S5V4N54A-vhimfYza.js} +1 -1
  56. package/dist/client/assets/{dagre-BM42HDAG-gQ1C1xHJ.js → dagre-BM42HDAG-wPz2jgXD.js} +1 -1
  57. package/dist/client/assets/{dashboard-view-BtFepPos.js → dashboard-view-BJVPfjWI.js} +1 -1
  58. package/dist/client/assets/{dashboard-view-BXtoQ9a4.js → dashboard-view-BN6as5aN.js} +1 -1
  59. package/dist/client/assets/{dashboard-view-AQmzkBNb.js → dashboard-view-ByLTU9KO.js} +1 -1
  60. package/dist/client/assets/{dashboard-view-BLenRoXu.js → dashboard-view-HiX6AOAw.js} +1 -1
  61. package/dist/client/assets/{diagram-2AECGRRQ-Dv96I1MI.js → diagram-2AECGRRQ-DvPGnzo9.js} +1 -1
  62. package/dist/client/assets/{diagram-5GNKFQAL-BRyhjYes.js → diagram-5GNKFQAL-CUD0xVXq.js} +1 -1
  63. package/dist/client/assets/{diagram-KO2AKTUF-Bmj3ihjc.js → diagram-KO2AKTUF-p88av64o.js} +1 -1
  64. package/dist/client/assets/{diagram-LMA3HP47-BYHVCFK2.js → diagram-LMA3HP47-B2V_VFOf.js} +1 -1
  65. package/dist/client/assets/{diagram-OG6HWLK6-0WZBLayt.js → diagram-OG6HWLK6-CO02xKJJ.js} +1 -1
  66. package/dist/client/assets/{erDiagram-TEJ5UH35-Cu-yj8hA.js → erDiagram-TEJ5UH35-LiIZMOks.js} +1 -1
  67. package/dist/client/assets/{flowDiagram-I6XJVG4X-BSetu6oh.js → flowDiagram-I6XJVG4X-BMgU0xdB.js} +1 -1
  68. package/dist/client/assets/{folder-open-CkswT0lE.js → folder-open-Bfz0pfA0.js} +1 -1
  69. package/dist/client/assets/{ganttDiagram-6RSMTGT7-B5qn0KFQ.js → ganttDiagram-6RSMTGT7-C-DNnJ7F.js} +1 -1
  70. package/dist/client/assets/{gitGraphDiagram-PVQCEYII-BVB3wm46.js → gitGraphDiagram-PVQCEYII-BKnH_s4c.js} +1 -1
  71. package/dist/client/assets/index-D6zw4rNc.js +2937 -0
  72. package/dist/client/assets/index-lxhrCjd_.css +1 -0
  73. package/dist/client/assets/{infoDiagram-5YYISTIA-ZBNKOBSK.js → infoDiagram-5YYISTIA-DrFgtiS_.js} +1 -1
  74. package/dist/client/assets/{ishikawaDiagram-YF4QCWOH-CbNKgenI.js → ishikawaDiagram-YF4QCWOH-cXcVkWVR.js} +1 -1
  75. package/dist/client/assets/{journeyDiagram-JHISSGLW-BLc2FHIF.js → journeyDiagram-JHISSGLW-2rAR6JrI.js} +1 -1
  76. package/dist/client/assets/{kanban-definition-UN3LZRKU-B08tij8f.js → kanban-definition-UN3LZRKU-uaIVKg6k.js} +1 -1
  77. package/dist/client/assets/{mermaid.core-QX5z7w8Z.js → mermaid.core-Bl1eKb-3.js} +4 -4
  78. package/dist/client/assets/{mindmap-definition-RKZ34NQL-C1md8M3c.js → mindmap-definition-RKZ34NQL-C_ZfL855.js} +1 -1
  79. package/dist/client/assets/{pieDiagram-4H26LBE5-BoQbn3Xp.js → pieDiagram-4H26LBE5-B-GWMxe6.js} +1 -1
  80. package/dist/client/assets/{puzzle-DCqPnWPE.js → puzzle--EYtEUHj.js} +1 -1
  81. package/dist/client/assets/{quadrantDiagram-W4KKPZXB-DPvNcVK-.js → quadrantDiagram-W4KKPZXB-C4yu-vxx.js} +1 -1
  82. package/dist/client/assets/{requirementDiagram-4Y6WPE33-KfdYf15t.js → requirementDiagram-4Y6WPE33-CmsMO-mZ.js} +1 -1
  83. package/dist/client/assets/{sankeyDiagram-5OEKKPKP-D1Eyoanf.js → sankeyDiagram-5OEKKPKP-DKXXMJDr.js} +1 -1
  84. package/dist/client/assets/{sequenceDiagram-3UESZ5HK-CmODsbCf.js → sequenceDiagram-3UESZ5HK-DbpptsX5.js} +1 -1
  85. package/dist/client/assets/{standing-instructions-template-D_mPaGcG.js → standing-instructions-template-bCcrFYtU.js} +1 -1
  86. package/dist/client/assets/{stateDiagram-AJRCARHV-Blbw_21G.js → stateDiagram-AJRCARHV-DDwdEj_k.js} +1 -1
  87. package/dist/client/assets/{stateDiagram-v2-BHNVJYJU-DwMgvYO8.js → stateDiagram-v2-BHNVJYJU-CjOnclTc.js} +1 -1
  88. package/dist/client/assets/{timeline-definition-PNZ67QCA-DDtndgLc.js → timeline-definition-PNZ67QCA-Q8Appf3Z.js} +1 -1
  89. package/dist/client/assets/{upload-DGCjJkMH.js → upload-Ka3GrAEL.js} +1 -1
  90. package/dist/client/assets/{users-CbWZvFZP.js → users-B7a_oi7v.js} +1 -1
  91. package/dist/client/assets/{vennDiagram-CIIHVFJN-B6Sx0OqW.js → vennDiagram-CIIHVFJN-ZsuVN3Hk.js} +1 -1
  92. package/dist/client/assets/{wardley-L42UT6IY-_SQFYU4g.js → wardley-L42UT6IY-Eh9WyxS2.js} +1 -1
  93. package/dist/client/assets/{wardleyDiagram-YWT4CUSO-Ce5igv6s.js → wardleyDiagram-YWT4CUSO-CG1fl8Sj.js} +1 -1
  94. package/dist/client/assets/{xychartDiagram-2RQKCTM6-zI0zyvDP.js → xychartDiagram-2RQKCTM6-BnZAlrGh.js} +1 -1
  95. package/dist/client/index.html +2 -2
  96. package/dist/client/version.json +1 -1
  97. package/dist/droid-cli/package.json +1 -1
  98. package/dist/extension.js +1479 -798
  99. package/dist/pi-claude-cli/package.json +1 -1
  100. package/dist/plugin-sdk/index.d.ts +1 -1
  101. package/dist/plugin-sdk/index.js +25 -14
  102. package/dist/plugins/fusion-plugin-acp-runtime/bundled.js +25 -14
  103. package/dist/plugins/fusion-plugin-acp-runtime/package.json +1 -1
  104. package/dist/plugins/fusion-plugin-claude-runtime/bundled.js +25 -14
  105. package/dist/plugins/fusion-plugin-claude-runtime/package.json +1 -1
  106. package/dist/plugins/fusion-plugin-cli-printing-press/bundled.js +25 -14
  107. package/dist/plugins/fusion-plugin-cli-printing-press/package.json +1 -1
  108. package/dist/plugins/fusion-plugin-compound-engineering/bundled.js +25 -14
  109. package/dist/plugins/fusion-plugin-compound-engineering/package.json +1 -1
  110. package/dist/plugins/fusion-plugin-cursor-runtime/bundled.js +25 -14
  111. package/dist/plugins/fusion-plugin-cursor-runtime/package.json +1 -1
  112. package/dist/plugins/fusion-plugin-dependency-graph/bundled.js +25 -14
  113. package/dist/plugins/fusion-plugin-dependency-graph/package.json +1 -1
  114. package/dist/plugins/fusion-plugin-droid-runtime/bundled.js +25 -14
  115. package/dist/plugins/fusion-plugin-droid-runtime/package.json +1 -1
  116. package/dist/plugins/fusion-plugin-grok-runtime/bundled.js +25 -14
  117. package/dist/plugins/fusion-plugin-grok-runtime/package.json +1 -1
  118. package/dist/plugins/fusion-plugin-hermes-runtime/bundled.js +25 -14
  119. package/dist/plugins/fusion-plugin-hermes-runtime/package.json +1 -1
  120. package/dist/plugins/fusion-plugin-linear-import/bundled.js +25 -14
  121. package/dist/plugins/fusion-plugin-linear-import/package.json +1 -1
  122. package/dist/plugins/fusion-plugin-omp-runtime/bundled.js +25 -14
  123. package/dist/plugins/fusion-plugin-omp-runtime/package.json +1 -1
  124. package/dist/plugins/fusion-plugin-openclaw-runtime/bundled.js +25 -14
  125. package/dist/plugins/fusion-plugin-openclaw-runtime/package.json +1 -1
  126. package/dist/plugins/fusion-plugin-paperclip-runtime/bundled.js +25 -14
  127. package/dist/plugins/fusion-plugin-paperclip-runtime/package.json +1 -1
  128. package/dist/plugins/fusion-plugin-quality/bundled.js +25 -14
  129. package/dist/plugins/fusion-plugin-quality/package.json +1 -1
  130. package/dist/plugins/fusion-plugin-reports/bundled.js +25 -14
  131. package/dist/plugins/fusion-plugin-reports/package.json +1 -1
  132. package/dist/plugins/fusion-plugin-roadmap/bundled.js +25 -14
  133. package/dist/plugins/fusion-plugin-roadmap/package.json +1 -1
  134. package/dist/plugins/fusion-plugin-todos/bundled.js +25 -14
  135. package/dist/plugins/fusion-plugin-todos/package.json +1 -1
  136. package/dist/plugins/fusion-plugin-whatsapp-chat/bundled.js +25 -14
  137. package/dist/plugins/fusion-plugin-whatsapp-chat/package.json +1 -1
  138. package/package.json +1 -1
  139. package/dist/client/assets/AgentDetailView-jzgy1BAS.css +0 -1
  140. package/dist/client/assets/CommandCenter-ilm-DbtI.css +0 -1
  141. package/dist/client/assets/SettingsModal-B1UCDc_h.js +0 -21
  142. package/dist/client/assets/SettingsModal-C5ic1ls7.css +0 -1
  143. package/dist/client/assets/channel-DfvbdnTK.js +0 -1
  144. package/dist/client/assets/classDiagram-4FO5ZUOK-CkMyClXV.js +0 -1
  145. package/dist/client/assets/classDiagram-v2-Q7XG4LA2-CkMyClXV.js +0 -1
  146. package/dist/client/assets/index-BgE915SE.js +0 -2937
  147. package/dist/client/assets/index-e2yEk7y4.css +0 -1
@@ -1592,6 +1592,199 @@ Be specific: cite the plan section or file path for every finding and explain th
1592
1592
  }
1593
1593
  });
1594
1594
 
1595
+ // ../core/src/workflows/workflow-merge-region.ts
1596
+ function isMergeRegionNode(node) {
1597
+ return MERGE_REGION_ENTRY_NODE_KINDS.has(node.kind) || node.config?.seam === "merge";
1598
+ }
1599
+ function classifyWorkflowNodeMergeRegion(ir2, nodeId) {
1600
+ if (ir2.version !== "v2" || !Array.isArray(ir2.nodes)) return "unknown";
1601
+ const node = ir2.nodes.find((candidate) => candidate.id === nodeId);
1602
+ if (!node) return "unknown";
1603
+ return isMergeRegionNode(node) ? "merge-region" : "outside-merge-region";
1604
+ }
1605
+ var MERGE_REGION_ENTRY_NODE_KINDS;
1606
+ var init_workflow_merge_region = __esm({
1607
+ "../core/src/workflows/workflow-merge-region.ts"() {
1608
+ "use strict";
1609
+ MERGE_REGION_ENTRY_NODE_KINDS = /* @__PURE__ */ new Set([
1610
+ "merge-gate",
1611
+ "merge-attempt",
1612
+ "manual-merge-hold",
1613
+ "retry-backoff",
1614
+ "recovery-router",
1615
+ "branch-group-member-integration",
1616
+ "branch-group-promotion",
1617
+ "pr-create",
1618
+ "pr-respond",
1619
+ "pr-merge"
1620
+ ]);
1621
+ }
1622
+ });
1623
+
1624
+ // ../core/src/workflows/workflow-optional-steps.ts
1625
+ function isWorkflowOptionalGroupEnabled(enabledWorkflowSteps, groupId, defaultOn = false) {
1626
+ return Array.isArray(enabledWorkflowSteps) ? enabledWorkflowSteps.includes(groupId) : defaultOn;
1627
+ }
1628
+ function isOptionalGroupNode(node) {
1629
+ return node.kind === "optional-group";
1630
+ }
1631
+ function computeNodeExecutionRanks(ir2) {
1632
+ const ranks = /* @__PURE__ */ new Map();
1633
+ if (ir2.version !== "v2" || !Array.isArray(ir2.nodes) || !Array.isArray(ir2.edges)) {
1634
+ return ranks;
1635
+ }
1636
+ const outgoing = /* @__PURE__ */ new Map();
1637
+ for (const edge of ir2.edges) {
1638
+ if (!outgoing.has(edge.from)) outgoing.set(edge.from, []);
1639
+ outgoing.get(edge.from)?.push(edge.to);
1640
+ }
1641
+ const queue = [];
1642
+ for (const node of ir2.nodes) {
1643
+ if (node.kind === "start" || node.id === "start") {
1644
+ queue.push({ id: node.id, rank: 0 });
1645
+ }
1646
+ }
1647
+ while (queue.length > 0) {
1648
+ const next = queue.shift();
1649
+ if (!next) continue;
1650
+ const current = ranks.get(next.id);
1651
+ if (current !== void 0 && current <= next.rank) continue;
1652
+ ranks.set(next.id, next.rank);
1653
+ for (const target of outgoing.get(next.id) ?? []) {
1654
+ queue.push({ id: target, rank: next.rank + 1 });
1655
+ }
1656
+ }
1657
+ return ranks;
1658
+ }
1659
+ function resolveWorkflowOptionalSteps(ir2, _pluginTemplates = []) {
1660
+ if (ir2.version !== "v2" || !Array.isArray(ir2.nodes)) return [];
1661
+ const ranks = computeNodeExecutionRanks(ir2);
1662
+ const resolved = [];
1663
+ for (const [nodeIndex, node] of ir2.nodes.entries()) {
1664
+ if (!isOptionalGroupNode(node)) continue;
1665
+ const config3 = node.config ?? {};
1666
+ resolved.push({
1667
+ // Keyed by the group node id (documented above); field name preserved.
1668
+ templateId: node.id,
1669
+ name: typeof config3.name === "string" && config3.name.trim() ? config3.name : node.id,
1670
+ description: "",
1671
+ /*
1672
+ FNXC:WorkflowOptionalSteps 2026-06-29-12:47:
1673
+ Post-merge verification is now a graph-native optional group. Task creation and detail surfaces need the resolver to preserve `config.phase` so post-merge toggles do not look like pre-merge gates.
1674
+ */
1675
+ phase: config3.phase === "post-merge" ? "post-merge" : "pre-merge",
1676
+ defaultOn: config3.defaultOn === true,
1677
+ reportingOnly: config3.reportingOnly === true,
1678
+ /*
1679
+ FNXC:WorkflowDefinitionSteps 2026-06-29-00:41:
1680
+ Definition/task creation surfaces must order optional groups by graph execution position, not raw node-array order. Derived built-ins can insert Plan Review between planning and parse while appending its node object, and operators still need the step list to show Plan Review before execution.
1681
+ */
1682
+ rank: ranks.get(node.id) ?? Number.MAX_SAFE_INTEGER,
1683
+ nodeIndex
1684
+ });
1685
+ }
1686
+ return resolved.sort((a, b3) => a.rank - b3.rank || a.nodeIndex - b3.nodeIndex).map(({ rank: _rank, nodeIndex: _nodeIndex, ...step }) => step);
1687
+ }
1688
+ function resolveDefaultOnOptionalGroupIds(ir2) {
1689
+ return resolveWorkflowOptionalSteps(ir2).filter((step) => step.defaultOn).map((step) => step.templateId);
1690
+ }
1691
+ function resolveAllOptionalGroupIds(ir2) {
1692
+ return resolveWorkflowOptionalSteps(ir2).map((step) => step.templateId);
1693
+ }
1694
+ function isReportingOnlyOptionalGroup(ir2, nodeId) {
1695
+ if (!nodeId) return false;
1696
+ return resolveWorkflowOptionalSteps(ir2).some((step) => step.templateId === nodeId && step.reportingOnly);
1697
+ }
1698
+ var init_workflow_optional_steps = __esm({
1699
+ "../core/src/workflows/workflow-optional-steps.ts"() {
1700
+ "use strict";
1701
+ }
1702
+ });
1703
+
1704
+ // ../core/src/workflows/workflow-fast-lane.ts
1705
+ function isFastExecutionMode(task) {
1706
+ return task.executionMode === "fast";
1707
+ }
1708
+ function configString(node, key) {
1709
+ const value = node.config?.[key];
1710
+ return typeof value === "string" ? value : void 0;
1711
+ }
1712
+ function isTaskStepsForeach(node) {
1713
+ return node.kind === "foreach" && node.config?.source === "task-steps";
1714
+ }
1715
+ function isFastLaneSkippableCustomNode(node, opts = {}) {
1716
+ if (node.config?.summaryTarget === "task" || node.id === "completion-summary") return false;
1717
+ if (opts.optionalGroupId) return false;
1718
+ if (node.config?.seam) return false;
1719
+ return node.kind === "prompt" || node.kind === "script" || node.kind === "gate";
1720
+ }
1721
+ function isFastLaneBypassedTemplateNode(node) {
1722
+ return node.kind === "step-review";
1723
+ }
1724
+ function inactiveRoute() {
1725
+ return {
1726
+ active: false,
1727
+ bypassedNodeIds: /* @__PURE__ */ new Set(),
1728
+ bypassedPreMergeGroups: [],
1729
+ parseStepsNodeIds: /* @__PURE__ */ new Set()
1730
+ };
1731
+ }
1732
+ function resolveFastLaneRoute(ir2, task) {
1733
+ if (!isFastExecutionMode(task)) return inactiveRoute();
1734
+ const foreachNode = ir2.nodes.find(isTaskStepsForeach);
1735
+ const executeNode = ir2.nodes.find((node) => configString(node, "seam") === "execute");
1736
+ const implementationNode = foreachNode ?? executeNode;
1737
+ if (!implementationNode) {
1738
+ return { ...inactiveRoute(), unsupportedReason: "no-implementation-node" };
1739
+ }
1740
+ const implementationKind = foreachNode ? "foreach-steps" : "execute-seam";
1741
+ const bypassedPreMergeGroups = resolveWorkflowOptionalSteps(ir2).filter((step) => step.phase === "pre-merge").map((step) => ({ nodeId: step.templateId, name: step.name }));
1742
+ const bypassedGroupIds = new Set(bypassedPreMergeGroups.map((group) => group.nodeId));
1743
+ const bypassedNodeIds = /* @__PURE__ */ new Set();
1744
+ for (const node of ir2.nodes) {
1745
+ if (isMergeRegionNode(node) || node.id === "completion-summary" || node.config?.summaryTarget === "task") continue;
1746
+ if (configString(node, "seam") === "planning") {
1747
+ bypassedNodeIds.add(node.id);
1748
+ continue;
1749
+ }
1750
+ if (bypassedGroupIds.has(node.id)) {
1751
+ bypassedNodeIds.add(node.id);
1752
+ continue;
1753
+ }
1754
+ const workflowAction = configString(node, "workflowAction");
1755
+ const groupId = configString(node, "forWorkflowStepId");
1756
+ if (workflowAction === "plan-replan" || workflowAction === "plan-review-no-op" || workflowAction === "pre-merge-remediation" || workflowAction === "review-remediation-steps" || groupId !== void 0 && bypassedGroupIds.has(groupId)) {
1757
+ bypassedNodeIds.add(node.id);
1758
+ }
1759
+ }
1760
+ const parseStepsNodeIds = new Set(ir2.nodes.filter((node) => node.kind === "parse-steps").map((node) => node.id));
1761
+ for (const parseNodeId of parseStepsNodeIds) {
1762
+ if (bypassedNodeIds.has(parseNodeId)) {
1763
+ throw new Error(`Fast lane cannot bypass parse-steps node '${parseNodeId}'`);
1764
+ }
1765
+ }
1766
+ return {
1767
+ active: true,
1768
+ bypassedNodeIds,
1769
+ bypassedPreMergeGroups,
1770
+ parseStepsNodeIds,
1771
+ implementationNodeId: implementationNode.id,
1772
+ implementationKind
1773
+ };
1774
+ }
1775
+ var FAST_LANE_STEP_NAME, FAST_LANE_SKIP_VALUE, FAST_MODE_BYPASS_ACTOR, FAST_LANE_STEP_REVIEW_ROUTE_VALUE;
1776
+ var init_workflow_fast_lane = __esm({
1777
+ "../core/src/workflows/workflow-fast-lane.ts"() {
1778
+ "use strict";
1779
+ init_workflow_merge_region();
1780
+ init_workflow_optional_steps();
1781
+ FAST_LANE_STEP_NAME = "Fast implementation";
1782
+ FAST_LANE_SKIP_VALUE = "fast-lane-skip";
1783
+ FAST_MODE_BYPASS_ACTOR = "fast-mode";
1784
+ FAST_LANE_STEP_REVIEW_ROUTE_VALUE = "approve";
1785
+ }
1786
+ });
1787
+
1595
1788
  // ../core/src/workflows/workflow-step-results.ts
1596
1789
  function isWorkflowStepNotRun(result) {
1597
1790
  return result.status === "skipped" && typeof result.notRunReason === "string" && WORKFLOW_STEP_NOT_RUN_REASONS.includes(result.notRunReason);
@@ -1892,6 +2085,31 @@ var init_workflow_step_results = __esm({
1892
2085
  });
1893
2086
 
1894
2087
  // ../core/src/merge/pre-merge-approval.ts
2088
+ function requiresContentReviewProof(workflowStepId, result) {
2089
+ return workflowStepId === "code-review" || result.reviewKind === "code";
2090
+ }
2091
+ function isAuditedOperatorBypass(result) {
2092
+ if (result.status !== "skipped") return false;
2093
+ const actor = result.bypassedBy?.trim();
2094
+ return Boolean(
2095
+ actor && !AUTOMATED_BYPASS_ACTORS.has(actor) && result.bypassedAt?.trim() && result.bypassReason?.trim()
2096
+ );
2097
+ }
2098
+ function resolveUnprovenReviewApproval(result, options) {
2099
+ if ((result.phase ?? "pre-merge") !== "pre-merge" || !requiresContentReviewProof(result.workflowStepId, result) || options.workspace || result.status !== "passed" || result.verdict !== "APPROVE" && result.verdict !== "APPROVE_WITH_NOTES" || result.reviewInputFingerprint !== void 0 || result.bypassedBy !== void 0 || result.remediationArchivedAt != null) {
2100
+ return void 0;
2101
+ }
2102
+ const { verdict: _verdict, ...withoutVerdict } = result;
2103
+ return {
2104
+ downgraded: {
2105
+ ...withoutVerdict,
2106
+ status: "failed",
2107
+ output: UNPROVEN_REVIEW_APPROVAL_DIAGNOSTIC,
2108
+ notes: UNPROVEN_REVIEW_APPROVAL_DIAGNOSTIC
2109
+ },
2110
+ reason: UNPROVEN_REVIEW_APPROVAL_DIAGNOSTIC
2111
+ };
2112
+ }
1895
2113
  function evaluatePreMergeApprovals(task, options = {}) {
1896
2114
  const required2 = options.requiredPreMergeStepIds;
1897
2115
  if (!required2?.size) return [];
@@ -1902,13 +2120,16 @@ function evaluateStep(workflowStepId, results, task, descriptor) {
1902
2120
  const result = results.filter((candidate) => candidate.workflowStepId === workflowStepId).at(-1);
1903
2121
  if (!result && descriptor?.kind !== "workspace") return { workflowStepId, state: "missing" };
1904
2122
  if (result) {
1905
- const requiresExplicitVerdict = workflowStepId === "code-review" || result.reviewKind === "code";
2123
+ const requiresExplicitVerdict = requiresContentReviewProof(workflowStepId, result);
1906
2124
  const approvedVerdict = result.verdict === "APPROVE" || result.verdict === "APPROVE_WITH_NOTES";
1907
2125
  const isPlanDomain = workflowStepId === PLAN_REVIEW_GROUP_ID || result.reviewKind === "plan";
1908
2126
  const notRunApproves = isWorkflowStepNotRun(result) && !requiresExplicitVerdict && !isPlanDomain;
1909
2127
  const approved = result.status === "passed" && (requiresExplicitVerdict ? approvedVerdict : result.verdict === void 0 || approvedVerdict) || result.status === "skipped" && !!result.bypassedBy || notRunApproves;
1910
2128
  if (!approved || !!result.remediationArchivedAt) return { workflowStepId, state: "not-approved" };
1911
2129
  if (result.reviewKind === "plan") return { workflowStepId, state: "approved" };
2130
+ if (isAuditedOperatorBypass(result) && descriptor?.kind !== "workspace") {
2131
+ return { workflowStepId, state: "approved" };
2132
+ }
1912
2133
  const bindsContent = requiresExplicitVerdict || result.reviewInputFingerprint !== void 0;
1913
2134
  if (!bindsContent) return { workflowStepId, state: "approved" };
1914
2135
  }
@@ -1939,11 +2160,15 @@ function evaluateStep(workflowStepId, results, task, descriptor) {
1939
2160
  if (stale.length) return { workflowStepId, state: "stale-content", repositories: stale };
1940
2161
  return { workflowStepId, state: "approved" };
1941
2162
  }
2163
+ var AUTOMATED_BYPASS_ACTORS, UNPROVEN_REVIEW_APPROVAL_DIAGNOSTIC;
1942
2164
  var init_pre_merge_approval = __esm({
1943
2165
  "../core/src/merge/pre-merge-approval.ts"() {
1944
2166
  "use strict";
1945
2167
  init_builtin_plan_review_group();
2168
+ init_workflow_fast_lane();
1946
2169
  init_workflow_step_results();
2170
+ AUTOMATED_BYPASS_ACTORS = /* @__PURE__ */ new Set([FAST_MODE_BYPASS_ACTOR]);
2171
+ UNPROVEN_REVIEW_APPROVAL_DIAGNOSTIC = "Content-binding review approval recorded without reviewInputFingerprint; approval invalidated so the gate can run again.";
1947
2172
  }
1948
2173
  });
1949
2174
 
@@ -11453,35 +11678,6 @@ var init_workflow_ir_types = __esm({
11453
11678
  }
11454
11679
  });
11455
11680
 
11456
- // ../core/src/workflows/workflow-merge-region.ts
11457
- function isMergeRegionNode(node) {
11458
- return MERGE_REGION_ENTRY_NODE_KINDS.has(node.kind) || node.config?.seam === "merge";
11459
- }
11460
- function classifyWorkflowNodeMergeRegion(ir2, nodeId) {
11461
- if (ir2.version !== "v2" || !Array.isArray(ir2.nodes)) return "unknown";
11462
- const node = ir2.nodes.find((candidate) => candidate.id === nodeId);
11463
- if (!node) return "unknown";
11464
- return isMergeRegionNode(node) ? "merge-region" : "outside-merge-region";
11465
- }
11466
- var MERGE_REGION_ENTRY_NODE_KINDS;
11467
- var init_workflow_merge_region = __esm({
11468
- "../core/src/workflows/workflow-merge-region.ts"() {
11469
- "use strict";
11470
- MERGE_REGION_ENTRY_NODE_KINDS = /* @__PURE__ */ new Set([
11471
- "merge-gate",
11472
- "merge-attempt",
11473
- "manual-merge-hold",
11474
- "retry-backoff",
11475
- "recovery-router",
11476
- "branch-group-member-integration",
11477
- "branch-group-promotion",
11478
- "pr-create",
11479
- "pr-respond",
11480
- "pr-merge"
11481
- ]);
11482
- }
11483
- });
11484
-
11485
11681
  // ../core/src/workflows/workflow-extension-types.ts
11486
11682
  function workflowExtensionRegistryId(pluginId, extensionId) {
11487
11683
  return `plugin:${pluginId}:${extensionId}`;
@@ -12477,6 +12673,16 @@ function validateReviewerAgentOverrides(nodes2) {
12477
12673
  if (Array.isArray(templateNodes)) validateReviewerAgentOverrides(templateNodes);
12478
12674
  }
12479
12675
  }
12676
+ function validateReadonlyMcpServersConfig(nodes2) {
12677
+ for (const node of nodes2) {
12678
+ const value = node.config?.readonlyMcpServers;
12679
+ if (value !== void 0 && (!Array.isArray(value) || value.some((name2) => typeof name2 !== "string" || !name2.trim()))) {
12680
+ throw new WorkflowIrError(`Workflow node '${node.id}' readonlyMcpServers must be an array of non-empty strings when present`);
12681
+ }
12682
+ const templateNodes = node.config?.template?.nodes;
12683
+ if (Array.isArray(templateNodes)) validateReadonlyMcpServersConfig(templateNodes);
12684
+ }
12685
+ }
12480
12686
  function validateThinkingLevelConfig(nodes2) {
12481
12687
  for (const node of nodes2) {
12482
12688
  const value = node.config?.thinkingLevel;
@@ -13091,6 +13297,7 @@ function validateV2(ir2) {
13091
13297
  const topLevelIds = new Set(ir2.nodes.map((n) => n.id));
13092
13298
  validateStepExecutePlacement(ir2.nodes);
13093
13299
  validateThinkingLevelConfig(ir2.nodes);
13300
+ validateReadonlyMcpServersConfig(ir2.nodes);
13094
13301
  validateCredentialInstanceIdConfig(ir2.nodes);
13095
13302
  validateReviewerAgentOverrides(ir2.nodes);
13096
13303
  for (const node of ir2.nodes) {
@@ -29161,169 +29368,6 @@ var init_explicit_duplicate_marker = __esm({
29161
29368
  }
29162
29369
  });
29163
29370
 
29164
- // ../core/src/workflows/workflow-optional-steps.ts
29165
- function isWorkflowOptionalGroupEnabled(enabledWorkflowSteps, groupId, defaultOn = false) {
29166
- return Array.isArray(enabledWorkflowSteps) ? enabledWorkflowSteps.includes(groupId) : defaultOn;
29167
- }
29168
- function isOptionalGroupNode(node) {
29169
- return node.kind === "optional-group";
29170
- }
29171
- function computeNodeExecutionRanks(ir2) {
29172
- const ranks = /* @__PURE__ */ new Map();
29173
- if (ir2.version !== "v2" || !Array.isArray(ir2.nodes) || !Array.isArray(ir2.edges)) {
29174
- return ranks;
29175
- }
29176
- const outgoing = /* @__PURE__ */ new Map();
29177
- for (const edge of ir2.edges) {
29178
- if (!outgoing.has(edge.from)) outgoing.set(edge.from, []);
29179
- outgoing.get(edge.from)?.push(edge.to);
29180
- }
29181
- const queue = [];
29182
- for (const node of ir2.nodes) {
29183
- if (node.kind === "start" || node.id === "start") {
29184
- queue.push({ id: node.id, rank: 0 });
29185
- }
29186
- }
29187
- while (queue.length > 0) {
29188
- const next = queue.shift();
29189
- if (!next) continue;
29190
- const current = ranks.get(next.id);
29191
- if (current !== void 0 && current <= next.rank) continue;
29192
- ranks.set(next.id, next.rank);
29193
- for (const target of outgoing.get(next.id) ?? []) {
29194
- queue.push({ id: target, rank: next.rank + 1 });
29195
- }
29196
- }
29197
- return ranks;
29198
- }
29199
- function resolveWorkflowOptionalSteps(ir2, _pluginTemplates = []) {
29200
- if (ir2.version !== "v2" || !Array.isArray(ir2.nodes)) return [];
29201
- const ranks = computeNodeExecutionRanks(ir2);
29202
- const resolved = [];
29203
- for (const [nodeIndex, node] of ir2.nodes.entries()) {
29204
- if (!isOptionalGroupNode(node)) continue;
29205
- const config3 = node.config ?? {};
29206
- resolved.push({
29207
- // Keyed by the group node id (documented above); field name preserved.
29208
- templateId: node.id,
29209
- name: typeof config3.name === "string" && config3.name.trim() ? config3.name : node.id,
29210
- description: "",
29211
- /*
29212
- FNXC:WorkflowOptionalSteps 2026-06-29-12:47:
29213
- Post-merge verification is now a graph-native optional group. Task creation and detail surfaces need the resolver to preserve `config.phase` so post-merge toggles do not look like pre-merge gates.
29214
- */
29215
- phase: config3.phase === "post-merge" ? "post-merge" : "pre-merge",
29216
- defaultOn: config3.defaultOn === true,
29217
- reportingOnly: config3.reportingOnly === true,
29218
- /*
29219
- FNXC:WorkflowDefinitionSteps 2026-06-29-00:41:
29220
- Definition/task creation surfaces must order optional groups by graph execution position, not raw node-array order. Derived built-ins can insert Plan Review between planning and parse while appending its node object, and operators still need the step list to show Plan Review before execution.
29221
- */
29222
- rank: ranks.get(node.id) ?? Number.MAX_SAFE_INTEGER,
29223
- nodeIndex
29224
- });
29225
- }
29226
- return resolved.sort((a, b3) => a.rank - b3.rank || a.nodeIndex - b3.nodeIndex).map(({ rank: _rank, nodeIndex: _nodeIndex, ...step }) => step);
29227
- }
29228
- function resolveDefaultOnOptionalGroupIds(ir2) {
29229
- return resolveWorkflowOptionalSteps(ir2).filter((step) => step.defaultOn).map((step) => step.templateId);
29230
- }
29231
- function resolveAllOptionalGroupIds(ir2) {
29232
- return resolveWorkflowOptionalSteps(ir2).map((step) => step.templateId);
29233
- }
29234
- function isReportingOnlyOptionalGroup(ir2, nodeId) {
29235
- if (!nodeId) return false;
29236
- return resolveWorkflowOptionalSteps(ir2).some((step) => step.templateId === nodeId && step.reportingOnly);
29237
- }
29238
- var init_workflow_optional_steps = __esm({
29239
- "../core/src/workflows/workflow-optional-steps.ts"() {
29240
- "use strict";
29241
- }
29242
- });
29243
-
29244
- // ../core/src/workflows/workflow-fast-lane.ts
29245
- function isFastExecutionMode(task) {
29246
- return task.executionMode === "fast";
29247
- }
29248
- function configString(node, key) {
29249
- const value = node.config?.[key];
29250
- return typeof value === "string" ? value : void 0;
29251
- }
29252
- function isTaskStepsForeach(node) {
29253
- return node.kind === "foreach" && node.config?.source === "task-steps";
29254
- }
29255
- function isFastLaneSkippableCustomNode(node, opts = {}) {
29256
- if (node.config?.summaryTarget === "task" || node.id === "completion-summary") return false;
29257
- if (opts.optionalGroupId) return false;
29258
- if (node.config?.seam) return false;
29259
- return node.kind === "prompt" || node.kind === "script" || node.kind === "gate";
29260
- }
29261
- function isFastLaneBypassedTemplateNode(node) {
29262
- return node.kind === "step-review";
29263
- }
29264
- function inactiveRoute() {
29265
- return {
29266
- active: false,
29267
- bypassedNodeIds: /* @__PURE__ */ new Set(),
29268
- bypassedPreMergeGroups: [],
29269
- parseStepsNodeIds: /* @__PURE__ */ new Set()
29270
- };
29271
- }
29272
- function resolveFastLaneRoute(ir2, task) {
29273
- if (!isFastExecutionMode(task)) return inactiveRoute();
29274
- const foreachNode = ir2.nodes.find(isTaskStepsForeach);
29275
- const executeNode = ir2.nodes.find((node) => configString(node, "seam") === "execute");
29276
- const implementationNode = foreachNode ?? executeNode;
29277
- if (!implementationNode) {
29278
- return { ...inactiveRoute(), unsupportedReason: "no-implementation-node" };
29279
- }
29280
- const implementationKind = foreachNode ? "foreach-steps" : "execute-seam";
29281
- const bypassedPreMergeGroups = resolveWorkflowOptionalSteps(ir2).filter((step) => step.phase === "pre-merge").map((step) => ({ nodeId: step.templateId, name: step.name }));
29282
- const bypassedGroupIds = new Set(bypassedPreMergeGroups.map((group) => group.nodeId));
29283
- const bypassedNodeIds = /* @__PURE__ */ new Set();
29284
- for (const node of ir2.nodes) {
29285
- if (isMergeRegionNode(node) || node.id === "completion-summary" || node.config?.summaryTarget === "task") continue;
29286
- if (configString(node, "seam") === "planning") {
29287
- bypassedNodeIds.add(node.id);
29288
- continue;
29289
- }
29290
- if (bypassedGroupIds.has(node.id)) {
29291
- bypassedNodeIds.add(node.id);
29292
- continue;
29293
- }
29294
- const workflowAction = configString(node, "workflowAction");
29295
- const groupId = configString(node, "forWorkflowStepId");
29296
- if (workflowAction === "plan-replan" || workflowAction === "plan-review-no-op" || workflowAction === "pre-merge-remediation" || workflowAction === "review-remediation-steps" || groupId !== void 0 && bypassedGroupIds.has(groupId)) {
29297
- bypassedNodeIds.add(node.id);
29298
- }
29299
- }
29300
- const parseStepsNodeIds = new Set(ir2.nodes.filter((node) => node.kind === "parse-steps").map((node) => node.id));
29301
- for (const parseNodeId of parseStepsNodeIds) {
29302
- if (bypassedNodeIds.has(parseNodeId)) {
29303
- throw new Error(`Fast lane cannot bypass parse-steps node '${parseNodeId}'`);
29304
- }
29305
- }
29306
- return {
29307
- active: true,
29308
- bypassedNodeIds,
29309
- bypassedPreMergeGroups,
29310
- parseStepsNodeIds,
29311
- implementationNodeId: implementationNode.id,
29312
- implementationKind
29313
- };
29314
- }
29315
- var FAST_LANE_STEP_NAME, FAST_LANE_SKIP_VALUE, FAST_LANE_STEP_REVIEW_ROUTE_VALUE;
29316
- var init_workflow_fast_lane = __esm({
29317
- "../core/src/workflows/workflow-fast-lane.ts"() {
29318
- "use strict";
29319
- init_workflow_merge_region();
29320
- init_workflow_optional_steps();
29321
- FAST_LANE_STEP_NAME = "Fast implementation";
29322
- FAST_LANE_SKIP_VALUE = "fast-lane-skip";
29323
- FAST_LANE_STEP_REVIEW_ROUTE_VALUE = "approve";
29324
- }
29325
- });
29326
-
29327
29371
  // ../core/src/mesh/mesh-task-replication.ts
29328
29372
  function buildBootstrapPrompt(taskId, title, description) {
29329
29373
  const heading = title ? `${taskId}: ${title}` : taskId;
@@ -66524,10 +66568,10 @@ var init_migration = __esm({
66524
66568
  return basename6(projectPath);
66525
66569
  }
66526
66570
  try {
66527
- const { execFile: execFile42 } = await import("node:child_process");
66528
- const { promisify: promisify90 } = await import("node:util");
66529
- const execFileAsync36 = promisify90(execFile42);
66530
- const { stdout } = await execFileAsync36(
66571
+ const { execFile: execFile43 } = await import("node:child_process");
66572
+ const { promisify: promisify91 } = await import("node:util");
66573
+ const execFileAsync37 = promisify91(execFile43);
66574
+ const { stdout } = await execFileAsync37(
66531
66575
  "git",
66532
66576
  ["remote", "get-url", "origin"],
66533
66577
  { cwd: projectPath, timeout: 1e3 }
@@ -68722,10 +68766,10 @@ var init_central_core = __esm({
68722
68766
  */
68723
68767
  async generateProjectName(projectPath) {
68724
68768
  try {
68725
- const { execFile: execFile42 } = await import("node:child_process");
68726
- const { promisify: promisify90 } = await import("node:util");
68727
- const execFileAsync36 = promisify90(execFile42);
68728
- const { stdout } = await execFileAsync36(
68769
+ const { execFile: execFile43 } = await import("node:child_process");
68770
+ const { promisify: promisify91 } = await import("node:util");
68771
+ const execFileAsync37 = promisify91(execFile43);
68772
+ const { stdout } = await execFileAsync37(
68729
68773
  "git",
68730
68774
  ["remote", "get-url", "origin"],
68731
68775
  { cwd: projectPath, timeout: 5e3 }
@@ -85527,14 +85571,12 @@ async function listWorkflowDefinitionsImpl(store, options) {
85527
85571
  return pluginFiltered;
85528
85572
  }
85529
85573
  async function readAllWorkflowDefinitionsImpl(store) {
85530
- if (store.workflowDefinitionsCache) return store.workflowDefinitionsCache;
85531
85574
  const layer = store.getAsyncLayer();
85532
85575
  if (!layer) {
85533
85576
  throw new Error("workflow definitions: AsyncDataLayer not initialized in backend mode");
85534
85577
  }
85535
85578
  const rows = await listWorkflowRows(layer);
85536
- store.workflowDefinitionsCache = [...BUILTIN_WORKFLOWS, ...rows.map((row) => store.toWorkflowDefinition(row))];
85537
- return store.workflowDefinitionsCache;
85579
+ return [...BUILTIN_WORKFLOWS, ...rows.map((row) => store.toWorkflowDefinition(row))];
85538
85580
  }
85539
85581
  async function getWorkflowDefinitionImpl(store, id) {
85540
85582
  const builtin = getBuiltinWorkflow(id);
@@ -85603,7 +85645,6 @@ function insertWorkflowDefinitionSyncImpl(store, input) {
85603
85645
  if (!isWorkflowDefinitionIdPrimaryKeyCollision(error2)) throw error2;
85604
85646
  continue;
85605
85647
  }
85606
- store.workflowDefinitionsCache = null;
85607
85648
  return definition;
85608
85649
  }
85609
85650
  throw new Error("Unable to allocate a free workflow definition id after repeated id collisions");
@@ -85993,11 +86034,6 @@ function getPluginStoreImpl(store) {
85993
86034
  ...pluginLayer ? { asyncLayer: pluginLayer } : {}
85994
86035
  }
85995
86036
  );
85996
- const clearWorkflowDefinitionCache = () => {
85997
- store.workflowDefinitionsCache = null;
85998
- };
85999
- store.pluginStore.on("plugin:registered", clearWorkflowDefinitionCache);
86000
- store.pluginStore.on("plugin:unregistered", clearWorkflowDefinitionCache);
86001
86037
  }
86002
86038
  return store.pluginStore;
86003
86039
  }
@@ -90369,11 +90405,11 @@ async function searchWithQmd(rootDir, options) {
90369
90405
  const command = "qmd";
90370
90406
  const limit = Math.max(1, Math.min(options.limit ?? 5, 20));
90371
90407
  try {
90372
- const execFileAsync36 = await getDefaultExecFileAsync();
90373
- await ensureQmdProjectMemoryCollection(rootDir, execFileAsync36);
90408
+ const execFileAsync37 = await getDefaultExecFileAsync();
90409
+ await ensureQmdProjectMemoryCollection(rootDir, execFileAsync37);
90374
90410
  scheduleQmdProjectMemoryRefresh(rootDir);
90375
90411
  const args = buildQmdSearchArgs(rootDir, options);
90376
- const { stdout } = await execFileAsync36(command, args, {
90412
+ const { stdout } = await execFileAsync37(command, args, {
90377
90413
  cwd: rootDir,
90378
90414
  timeout: 4e3,
90379
90415
  maxBuffer: 1024 * 1024
@@ -90398,12 +90434,12 @@ async function searchWithQmd(rootDir, options) {
90398
90434
  return [];
90399
90435
  }
90400
90436
  }
90401
- async function ensureQmdProjectMemoryCollection(rootDir, execFileAsync36) {
90437
+ async function ensureQmdProjectMemoryCollection(rootDir, execFileAsync37) {
90402
90438
  const collectionName = qmdMemoryCollectionName(rootDir);
90403
90439
  const memoryDir = memoryWorkspacePath(rootDir);
90404
90440
  await mkdir10(memoryDir, { recursive: true });
90405
90441
  try {
90406
- await execFileAsync36("qmd", buildQmdCollectionAddArgs(rootDir), {
90442
+ await execFileAsync37("qmd", buildQmdCollectionAddArgs(rootDir), {
90407
90443
  cwd: rootDir,
90408
90444
  timeout: 4e3,
90409
90445
  maxBuffer: 512 * 1024
@@ -90501,14 +90537,14 @@ async function refreshQmdProjectMemoryIndex(rootDir, options) {
90501
90537
  }
90502
90538
  }
90503
90539
  const promise = (async () => {
90504
- const execFileAsync36 = options?.execFileAsync ?? await getDefaultExecFileAsync();
90505
- await ensureQmdProjectMemoryCollection(rootDir, execFileAsync36);
90506
- await execFileAsync36("qmd", ["update"], {
90540
+ const execFileAsync37 = options?.execFileAsync ?? await getDefaultExecFileAsync();
90541
+ await ensureQmdProjectMemoryCollection(rootDir, execFileAsync37);
90542
+ await execFileAsync37("qmd", ["update"], {
90507
90543
  cwd: rootDir,
90508
90544
  timeout: 3e4,
90509
90545
  maxBuffer: 1024 * 1024
90510
90546
  });
90511
- await execFileAsync36("qmd", ["embed"], {
90547
+ await execFileAsync37("qmd", ["embed"], {
90512
90548
  cwd: rootDir,
90513
90549
  timeout: 12e4,
90514
90550
  maxBuffer: 1024 * 1024
@@ -90544,12 +90580,12 @@ async function refreshQmdAgentMemoryIndex(rootDir, agentId, options) {
90544
90580
  }
90545
90581
  }
90546
90582
  const promise = (async () => {
90547
- const execFileAsync36 = options?.execFileAsync ?? await getDefaultExecFileAsync();
90583
+ const execFileAsync37 = options?.execFileAsync ?? await getDefaultExecFileAsync();
90548
90584
  const { agentMemoryWorkspacePath: agentMemoryWorkspacePath2 } = await Promise.resolve().then(() => (init_memory_dreams(), memory_dreams_exports));
90549
90585
  const workspacePath = agentMemoryWorkspacePath2(rootDir, agentId);
90550
90586
  await mkdir10(workspacePath, { recursive: true });
90551
90587
  try {
90552
- await execFileAsync36("qmd", ["collection", "add", workspacePath, "--name", qmdAgentMemoryCollectionName(rootDir, agentId), "--mask", "**/*.md"], {
90588
+ await execFileAsync37("qmd", ["collection", "add", workspacePath, "--name", qmdAgentMemoryCollectionName(rootDir, agentId), "--mask", "**/*.md"], {
90553
90589
  cwd: rootDir,
90554
90590
  timeout: 4e3,
90555
90591
  maxBuffer: 512 * 1024
@@ -90562,8 +90598,8 @@ ${stderr}`)) {
90562
90598
  throw err;
90563
90599
  }
90564
90600
  }
90565
- await execFileAsync36("qmd", ["update"], { cwd: rootDir, timeout: 3e4, maxBuffer: 1024 * 1024 });
90566
- await execFileAsync36("qmd", ["embed"], { cwd: rootDir, timeout: 12e4, maxBuffer: 1024 * 1024 });
90601
+ await execFileAsync37("qmd", ["update"], { cwd: rootDir, timeout: 3e4, maxBuffer: 1024 * 1024 });
90602
+ await execFileAsync37("qmd", ["embed"], { cwd: rootDir, timeout: 12e4, maxBuffer: 1024 * 1024 });
90567
90603
  })();
90568
90604
  qmdAgentRefreshState.set(key, { lastStartedAt: now, inFlight: promise });
90569
90605
  try {
@@ -90584,8 +90620,8 @@ function scheduleQmdAgentMemoryRefresh(rootDir, agentId) {
90584
90620
  }
90585
90621
  async function isQmdAvailable() {
90586
90622
  try {
90587
- const execFileAsync36 = await getDefaultExecFileAsync();
90588
- await execFileAsync36("qmd", ["--help"], {
90623
+ const execFileAsync37 = await getDefaultExecFileAsync();
90624
+ await execFileAsync37("qmd", ["--help"], {
90589
90625
  timeout: 3e3,
90590
90626
  maxBuffer: 128 * 1024,
90591
90627
  keepAlive: true
@@ -90596,12 +90632,12 @@ async function isQmdAvailable() {
90596
90632
  }
90597
90633
  }
90598
90634
  async function installQmd(options) {
90599
- const execFileAsync36 = options?.execFileAsync ?? await getDefaultExecFileAsync();
90635
+ const execFileAsync37 = options?.execFileAsync ?? await getDefaultExecFileAsync();
90600
90636
  const [command, ...args] = QMD_INSTALL_COMMAND.split(" ");
90601
90637
  if (!command || args.length === 0) {
90602
90638
  throw new MemoryBackendError("BACKEND_UNAVAILABLE", "qmd install command is not configured", "qmd");
90603
90639
  }
90604
- await execFileAsync36(command, args, {
90640
+ await execFileAsync37(command, args, {
90605
90641
  timeout: 12e4,
90606
90642
  maxBuffer: 1024 * 1024
90607
90643
  });
@@ -100116,7 +100152,6 @@ async function createWorkflowDefinitionImpl(store, input) {
100116
100152
  if (!isWorkflowDefinitionIdPrimaryKeyCollision(error2)) throw error2;
100117
100153
  continue;
100118
100154
  }
100119
- store.workflowDefinitionsCache = null;
100120
100155
  return definition;
100121
100156
  }
100122
100157
  throw new Error("Unable to allocate a free workflow definition id after repeated id collisions");
@@ -103700,7 +103735,6 @@ async function updateWorkflowDefinitionImpl(store, id, updates) {
103700
103735
  eq(project_exports.workflows.id, id),
103701
103736
  projectScopeFor(project_exports.workflows.projectId, layer.projectId)
103702
103737
  ));
103703
- store.workflowDefinitionsCache = null;
103704
103738
  return next;
103705
103739
  });
103706
103740
  if (pendingRehome) {
@@ -103735,7 +103769,6 @@ async function deleteWorkflowDefinitionImpl(store, id) {
103735
103769
  projectScopeFor(project_exports.workflows.projectId, layer.projectId)
103736
103770
  )).returning();
103737
103771
  if (deleted.length === 0) throw new Error(`Workflow '${id}' not found`);
103738
- store.workflowDefinitionsCache = null;
103739
103772
  await layer.db.delete(project_exports.workflowSettings).where(and(
103740
103773
  eq(project_exports.workflowSettings.workflowId, id),
103741
103774
  projectScopeFor(project_exports.workflowSettings.projectId, layer.projectId)
@@ -108329,7 +108362,6 @@ var init_store = __esm({
108329
108362
  taskIdIntegrityReport = { status: "ok", checkedAt: (/* @__PURE__ */ new Date()).toISOString(), anomalies: [] };
108330
108363
  lastTaskIdIntegrityLogSignature = null;
108331
108364
  workflowStepsCache = null;
108332
- workflowDefinitionsCache = null;
108333
108365
  _pluginWorkflowStepTemplates = [];
108334
108366
  globalSettingsStore;
108335
108367
  donePauseBackfillDone = false;
@@ -115959,7 +115991,7 @@ function getPostMergeFinalizeBlocker(task) {
115959
115991
  }
115960
115992
  function planConfirmedMergeChecklistReconciliation(task) {
115961
115993
  return {
115962
- skippedStepIndexes: task.steps.map((step, index2) => step.status === "pending" || step.status === "in-progress" ? index2 : -1).filter((index2) => index2 >= 0),
115994
+ skippedStepIndexes: (task.steps ?? []).map((step, index2) => step.status === "pending" || step.status === "in-progress" ? index2 : -1).filter((index2) => index2 >= 0),
115963
115995
  reconciledWorkflowStepIds: (task.workflowStepResults ?? []).filter((result) => result.status === "pending").map((result) => result.workflowStepId)
115964
115996
  };
115965
115997
  }
@@ -136009,11 +136041,11 @@ var require_extract_zip = __commonJS({
136009
136041
  var { createWriteStream, promises: fs10 } = __require("fs");
136010
136042
  var getStream = require_get_stream();
136011
136043
  var path6 = __require("path");
136012
- var { promisify: promisify90 } = __require("util");
136044
+ var { promisify: promisify91 } = __require("util");
136013
136045
  var stream = __require("stream");
136014
136046
  var yauzl = require_yauzl();
136015
- var openZip = promisify90(yauzl.open);
136016
- var pipeline = promisify90(stream.pipeline);
136047
+ var openZip = promisify91(yauzl.open);
136048
+ var pipeline = promisify91(stream.pipeline);
136017
136049
  var Extractor = class {
136018
136050
  constructor(zipPath, opts) {
136019
136051
  this.zipPath = zipPath;
@@ -136095,7 +136127,7 @@ var require_extract_zip = __commonJS({
136095
136127
  await fs10.mkdir(destDir, mkdirOptions);
136096
136128
  if (isDir) return;
136097
136129
  debug("opening read stream", dest);
136098
- const readStream = await promisify90(this.zipfile.openReadStream.bind(this.zipfile))(entry);
136130
+ const readStream = await promisify91(this.zipfile.openReadStream.bind(this.zipfile))(entry);
136099
136131
  if (symlink) {
136100
136132
  const link = await getStream(readStream);
136101
136133
  debug("creating symlink", link, dest);
@@ -137534,6 +137566,7 @@ async function updateChatSession(handle2, id, input) {
137534
137566
  if (input.status !== void 0) setValues.status = input.status;
137535
137567
  if (input.modelProvider !== void 0) setValues.modelProvider = input.modelProvider;
137536
137568
  if (input.modelId !== void 0) setValues.modelId = input.modelId;
137569
+ if (input.agentId !== void 0) setValues.agentId = input.agentId;
137537
137570
  if (input.thinkingLevel !== void 0) setValues.thinkingLevel = input.thinkingLevel;
137538
137571
  if (input.memoryFocus !== void 0) setValues.memoryFocus = input.memoryFocus;
137539
137572
  if (input.pinnedAt !== void 0) setValues.pinnedAt = input.pinnedAt;
@@ -138587,6 +138620,7 @@ var init_cli_session_store = __esm({
138587
138620
  });
138588
138621
 
138589
138622
  // ../core/src/secrets/oauth-credential-interop.ts
138623
+ import { createHash as createHash22 } from "node:crypto";
138590
138624
  import { existsSync as existsSync40, readFileSync as readFileSync11 } from "node:fs";
138591
138625
  import { homedir as homedir7 } from "node:os";
138592
138626
  import { join as join55 } from "node:path";
@@ -138646,6 +138680,22 @@ function getLastRefreshFallbackExpiryMs(lastRefresh) {
138646
138680
  }
138647
138681
  return parsed + CODEX_REFRESH_FALLBACK_WINDOW_MS;
138648
138682
  }
138683
+ function isSameStoredCredentialMaterial(left, right) {
138684
+ if (!left || !right || left.type !== right.type) {
138685
+ return false;
138686
+ }
138687
+ return left.key === right.key && left.access === right.access && left.refresh === right.refresh;
138688
+ }
138689
+ function computeStoredCredentialAccountFingerprint(credential) {
138690
+ if (!credential) {
138691
+ return void 0;
138692
+ }
138693
+ const material = typeof credential.refresh === "string" && credential.refresh.length > 0 ? credential.refresh : typeof credential.access === "string" && credential.access.length > 0 ? credential.access : typeof credential.key === "string" && credential.key.length > 0 ? credential.key : void 0;
138694
+ if (!material) {
138695
+ return void 0;
138696
+ }
138697
+ return createHash22("sha256").update(`${credential.type ?? ""}\0${material}`).digest("hex").slice(0, 16);
138698
+ }
138649
138699
  function isStoredAuthCredential(value) {
138650
138700
  if (!value || typeof value !== "object" || Array.isArray(value)) {
138651
138701
  return false;
@@ -139616,7 +139666,7 @@ var init_task_timing = __esm({
139616
139666
  });
139617
139667
 
139618
139668
  // ../core/src/task-recommendation-notice.ts
139619
- import { createHash as createHash22 } from "node:crypto";
139669
+ import { createHash as createHash23 } from "node:crypto";
139620
139670
  function registerTaskRecommendationNoticeMailbox(store, mailbox) {
139621
139671
  mailboxes2.set(store, mailbox);
139622
139672
  return () => {
@@ -139641,7 +139691,7 @@ function buildTaskRecommendationNoticeContent(task, recommendations) {
139641
139691
  }
139642
139692
  function buildTaskRecommendationNoticeIdempotencyKey(taskId, recommendations) {
139643
139693
  const ids = recommendations.map(({ id }) => id).sort().join("\n");
139644
- const digest = createHash22("sha256").update(ids, "utf8").digest("hex").slice(0, 16);
139694
+ const digest = createHash23("sha256").update(ids, "utf8").digest("hex").slice(0, 16);
139645
139695
  return `task-recommendation-notice:${taskId}:${digest}`;
139646
139696
  }
139647
139697
  async function notifyOperatorOfTaskRecommendations(store, task, recommendations, settings) {
@@ -140087,6 +140137,7 @@ __export(src_exports, {
140087
140137
  APPROVAL_REQUEST_STATUSES: () => APPROVAL_REQUEST_STATUSES,
140088
140138
  ARCHIVED_TASK_DOCUMENT_ADDITION_BOUNDARY: () => ARCHIVED_TASK_DOCUMENT_ADDITION_BOUNDARY,
140089
140139
  ARCHIVED_TASK_DOCUMENT_PUBLICATION_REJECTED: () => ARCHIVED_TASK_DOCUMENT_PUBLICATION_REJECTED,
140140
+ AUTOMATED_BYPASS_ACTORS: () => AUTOMATED_BYPASS_ACTORS,
140090
140141
  AUTOMATION_PRESETS: () => AUTOMATION_PRESETS,
140091
140142
  AUTOMATION_SELECTABLE_TOOLS: () => AUTOMATION_SELECTABLE_TOOLS,
140092
140143
  AUTOPILOT_STATES: () => AUTOPILOT_STATES,
@@ -140282,6 +140333,7 @@ __export(src_exports, {
140282
140333
  FAST_LANE_SKIP_VALUE: () => FAST_LANE_SKIP_VALUE,
140283
140334
  FAST_LANE_STEP_NAME: () => FAST_LANE_STEP_NAME,
140284
140335
  FAST_LANE_STEP_REVIEW_ROUTE_VALUE: () => FAST_LANE_STEP_REVIEW_ROUTE_VALUE,
140336
+ FAST_MODE_BYPASS_ACTOR: () => FAST_MODE_BYPASS_ACTOR,
140285
140337
  FEATURE_LOOP_REPAIR_TRANSITIONS: () => FEATURE_LOOP_REPAIR_TRANSITIONS,
140286
140338
  FEATURE_LOOP_STATES: () => FEATURE_LOOP_STATES,
140287
140339
  FEATURE_STATUSES: () => FEATURE_STATUSES,
@@ -140846,6 +140898,7 @@ __export(src_exports, {
140846
140898
  computeRetrySummary: () => computeRetrySummary,
140847
140899
  computeSkillId: () => computeSkillId,
140848
140900
  computeSnapshotChecksum: () => computeSnapshotChecksum,
140901
+ computeStoredCredentialAccountFingerprint: () => computeStoredCredentialAccountFingerprint,
140849
140902
  computeWorkflowIrPin: () => computeWorkflowIrPin,
140850
140903
  configurationTargetKey: () => configurationTargetKey,
140851
140904
  configureApprovalRequestTtls: () => configureApprovalRequestTtls,
@@ -141168,6 +141221,7 @@ __export(src_exports, {
141168
141221
  isApprovalRequestExpired: () => isApprovalRequestExpired,
141169
141222
  isArchivedColumnRole: () => isArchivedColumnRole,
141170
141223
  isArchivedRemediationCarrier: () => isArchivedRemediationCarrier,
141224
+ isAuditedOperatorBypass: () => isAuditedOperatorBypass,
141171
141225
  isBlockingFinding: () => isBlockingFinding,
141172
141226
  isBranchGroupComplete: () => isBranchGroupComplete,
141173
141227
  isBranchGroupMemberLanded: () => isBranchGroupMemberLanded,
@@ -141255,6 +141309,7 @@ __export(src_exports, {
141255
141309
  isReviewColumnRole: () => isReviewColumnRole,
141256
141310
  isRunRecord: () => isRunRecord,
141257
141311
  isRunningAgentTask: () => isRunningAgentTask,
141312
+ isSameStoredCredentialMaterial: () => isSameStoredCredentialMaterial,
141258
141313
  isSanctionedEngineBackwardMove: () => isSanctionedEngineBackwardMove,
141259
141314
  isSandboxExperimentalEnabled: () => isSandboxExperimentalEnabled,
141260
141315
  isSecretAccessPolicy: () => isSecretAccessPolicy,
@@ -141533,6 +141588,7 @@ __export(src_exports, {
141533
141588
  renderTriagePolicyPlaceholders: () => renderTriagePolicyPlaceholders,
141534
141589
  renderValidationCause: () => renderValidationCause,
141535
141590
  renderValidationFailureDescription: () => renderValidationFailureDescription,
141591
+ requiresContentReviewProof: () => requiresContentReviewProof,
141536
141592
  requiresPlannerConfirmation: () => requiresPlannerConfirmation,
141537
141593
  requiresWorktrunkInstallVerification: () => requiresWorktrunkInstallVerification,
141538
141594
  resetGhAvailabilityCache: () => resetGhAvailabilityCache,
@@ -141696,6 +141752,7 @@ __export(src_exports, {
141696
141752
  resolveTitleSummarizerSettingsModel: () => resolveTitleSummarizerSettingsModel,
141697
141753
  resolveToolOutputBudget: () => resolveToolOutputBudget,
141698
141754
  resolveTrailingVerificationStepIndex: () => resolveTrailingVerificationStepIndex,
141755
+ resolveUnprovenReviewApproval: () => resolveUnprovenReviewApproval,
141699
141756
  resolveUpdateAutomationSettings: () => resolveUpdateAutomationSettings,
141700
141757
  resolveUpdateTargetVersion: () => resolveUpdateTargetVersion,
141701
141758
  resolveUpdatesExternallyManaged: () => resolveUpdatesExternallyManaged,
@@ -145177,18 +145234,25 @@ function parseWorktreesFromPorcelain(porcelain) {
145177
145234
  if (current.path) rows.push({ path: current.path, branch: current.branch });
145178
145235
  return rows;
145179
145236
  }
145237
+ function isRegenerableIgnoredPorcelainEntry(entry) {
145238
+ const path6 = entry.slice(3).replace(/\/$/, "");
145239
+ if (!path6 || path6.startsWith('"')) return false;
145240
+ const lastSegment = path6.split("/").at(-1);
145241
+ return lastSegment !== void 0 && REGENERABLE_IGNORED_DIR_NAMES.has(lastSegment);
145242
+ }
145180
145243
  function classifyWorktreeRemovalContent(porcelain) {
145181
145244
  const entries = porcelain.split(/\r?\n/).filter((line2) => line2.trim().length > 0);
145182
145245
  if (entries.length === 0) return "clean";
145183
- return entries.every((line2) => line2.startsWith("!!")) ? "ignored-only" : "deliverable";
145246
+ if (entries.some((line2) => !line2.startsWith("!! "))) return "deliverable";
145247
+ return entries.every(isRegenerableIgnoredPorcelainEntry) ? "regenerable-ignored" : "ignored-only";
145184
145248
  }
145185
145249
  async function assertCleanForDefensiveRemoval(worktreePath) {
145186
145250
  if (!existsSync42(worktreePath)) {
145187
- return "clean";
145251
+ return { classification: "clean", entryCount: 0 };
145188
145252
  }
145189
145253
  let stdout;
145190
145254
  try {
145191
- ({ stdout } = await execFileAsync7("git", ["status", "--porcelain=v1", "--ignored", "--untracked-files=normal"], {
145255
+ ({ stdout } = await execFileAsync7("git", ["status", "--porcelain=v1", "--ignored=matching", "--untracked-files=normal"], {
145192
145256
  cwd: worktreePath,
145193
145257
  encoding: "utf-8",
145194
145258
  timeout: 15e3,
@@ -145201,7 +145265,10 @@ async function assertCleanForDefensiveRemoval(worktreePath) {
145201
145265
  if (classification === "deliverable") {
145202
145266
  throw new Error(`preserving ${worktreePath}: uncommitted or ignored content present`);
145203
145267
  }
145204
- return classification;
145268
+ return {
145269
+ classification,
145270
+ entryCount: stdout.split(/\r?\n/).filter((line2) => line2.trim().length > 0).length
145271
+ };
145205
145272
  }
145206
145273
  async function removeWorktree(input) {
145207
145274
  const logger3 = {
@@ -145218,9 +145285,12 @@ async function removeWorktree(input) {
145218
145285
  }
145219
145286
  const requiresCleanWorktree = DEFENSIVE_REMOVAL_REASONS.has(input.reason);
145220
145287
  let contentClassification;
145288
+ let contentEntryCount = 0;
145221
145289
  if (requiresCleanWorktree) {
145222
145290
  try {
145223
- contentClassification = await assertCleanForDefensiveRemoval(input.worktreePath);
145291
+ const contentProbe = await assertCleanForDefensiveRemoval(input.worktreePath);
145292
+ contentClassification = contentProbe.classification;
145293
+ contentEntryCount = contentProbe.entryCount;
145224
145294
  } catch (error2) {
145225
145295
  const classification = error2 instanceof Error && error2.message.includes(": status probe failed (") ? "unverifiable" : "deliverable";
145226
145296
  await input.audit?.git({
@@ -145237,6 +145307,17 @@ async function removeWorktree(input) {
145237
145307
  throw error2;
145238
145308
  }
145239
145309
  if (contentClassification === "ignored-only" && !input.postLandingProof) {
145310
+ await input.audit?.git({
145311
+ type: "worktree:removal-preserved",
145312
+ target: input.worktreePath,
145313
+ metadata: {
145314
+ taskId: input.taskId,
145315
+ reason: input.reason,
145316
+ source: void 0,
145317
+ classification: "ignored-only",
145318
+ hasPostLandingProof: false
145319
+ }
145320
+ }).catch(() => void 0);
145240
145321
  throw new Error(`preserving ${input.worktreePath}: uncommitted or ignored content present`);
145241
145322
  }
145242
145323
  }
@@ -145256,6 +145337,17 @@ async function removeWorktree(input) {
145256
145337
  } catch {
145257
145338
  }
145258
145339
  };
145340
+ const recordRegenerableDiscard = async () => {
145341
+ if (contentClassification !== "regenerable-ignored") return;
145342
+ try {
145343
+ await input.audit?.git({
145344
+ type: "worktree:removal-discarded-regenerable-content",
145345
+ target: input.worktreePath,
145346
+ metadata: { taskId: input.taskId, reason: input.reason, entryCount: contentEntryCount }
145347
+ });
145348
+ } catch {
145349
+ }
145350
+ };
145259
145351
  if (input.expectedOwnerTaskId && input.liveOwnerProbe) {
145260
145352
  const reconciled = reconcileSelfOwnedActiveSessionForRemoval(
145261
145353
  activeSessionRegistry,
@@ -145309,6 +145401,7 @@ async function removeWorktree(input) {
145309
145401
  try {
145310
145402
  await backend.remove(removeInput);
145311
145403
  await recordIgnoredOnlyDiscard();
145404
+ await recordRegenerableDiscard();
145312
145405
  if (input.audit) {
145313
145406
  await input.audit.git({
145314
145407
  type: backend.kind === "worktrunk" ? "worktree:worktrunk-remove" : "worktree:remove",
@@ -145336,6 +145429,7 @@ async function removeWorktree(input) {
145336
145429
  try {
145337
145430
  await native.remove(removeInput);
145338
145431
  await recordIgnoredOnlyDiscard();
145432
+ await recordRegenerableDiscard();
145339
145433
  await input.audit?.git({ type: "worktree:remove", target: input.worktreePath });
145340
145434
  return { removed: true, classification: "removed" };
145341
145435
  } catch (nativeError) {
@@ -145357,7 +145451,7 @@ function resolveWorktreeBackend(settings, deps = {}) {
145357
145451
  }
145358
145452
  return new NativeWorktreeBackend({ logger: deps.logger, settings, audit: deps.audit });
145359
145453
  }
145360
- var execAsync10, execFileAsync7, NATIVE_TIMEOUT_MS, REMOVE_TIMEOUT_MS, MAX_BUFFER3, HARMLESS_MERGE_REMOVE_ERROR_PATTERNS, WORKTRUNK_TIMEOUTS_MS, WORKTREE_BACKEND_MARKER, WorktrunkOperationError, NativeWorktreeBackend, WorktrunkWorktreeBackend, RemovalReason, ALLOWED_FORCE_REASONS, DEFENSIVE_REMOVAL_REASONS, POST_LANDING_PROOF_REASONS, InvalidForceUsageError, InvalidPostLandingProofUsageError, ActiveSessionWorktreeRemovalError;
145454
+ var execAsync10, execFileAsync7, NATIVE_TIMEOUT_MS, REMOVE_TIMEOUT_MS, MAX_BUFFER3, HARMLESS_MERGE_REMOVE_ERROR_PATTERNS, WORKTRUNK_TIMEOUTS_MS, WORKTREE_BACKEND_MARKER, WorktrunkOperationError, NativeWorktreeBackend, WorktrunkWorktreeBackend, RemovalReason, ALLOWED_FORCE_REASONS, DEFENSIVE_REMOVAL_REASONS, POST_LANDING_PROOF_REASONS, InvalidForceUsageError, InvalidPostLandingProofUsageError, ActiveSessionWorktreeRemovalError, REGENERABLE_IGNORED_DIR_NAMES;
145361
145455
  var init_worktree_backend = __esm({
145362
145456
  "../engine/src/worktree/worktree-backend.ts"() {
145363
145457
  "use strict";
@@ -146097,6 +146191,27 @@ ${getErrorStderr(error2) ?? ""}`;
146097
146191
  this.name = "ActiveSessionWorktreeRemovalError";
146098
146192
  }
146099
146193
  };
146194
+ REGENERABLE_IGNORED_DIR_NAMES = /* @__PURE__ */ new Set([
146195
+ "node_modules",
146196
+ "dist",
146197
+ "build",
146198
+ "out",
146199
+ "target",
146200
+ "bin",
146201
+ "obj",
146202
+ "coverage",
146203
+ ".next",
146204
+ ".nuxt",
146205
+ ".turbo",
146206
+ ".svelte-kit",
146207
+ ".parcel-cache",
146208
+ ".pytest_cache",
146209
+ ".mypy_cache",
146210
+ "__pycache__",
146211
+ ".gradle",
146212
+ ".venv",
146213
+ "venv"
146214
+ ]);
146100
146215
  }
146101
146216
  });
146102
146217
 
@@ -146718,6 +146833,14 @@ function resolveAiMergeRootPath(rootDir, settings) {
146718
146833
  function resolveLegacyAiMergeRootPath(rootDir) {
146719
146834
  return join59(rootDir, ".fusion", "ai-merge");
146720
146835
  }
146836
+ function resolveAiMergeSearchRoots(rootDir, settings) {
146837
+ const roots = [
146838
+ resolveAiMergeRootPath(rootDir, settings),
146839
+ resolveLegacyAiMergeRootPath(rootDir),
146840
+ ...settings?.worktreesDir ? [] : [join59(resolveLegacyWorktreesDirLayout(rootDir), AI_MERGE_DIRNAME)]
146841
+ ];
146842
+ return [...new Set(roots)];
146843
+ }
146721
146844
  function resolveWorktreesDir(rootDir, settings, workspaceContext) {
146722
146845
  return resolveWorktreesDirLayout(rootDir, settings, workspaceContext);
146723
146846
  }
@@ -148194,7 +148317,7 @@ var init_plan_review_continuation = __esm({
148194
148317
  });
148195
148318
 
148196
148319
  // ../engine/src/execution/hold-release.ts
148197
- import { createHash as createHash24 } from "node:crypto";
148320
+ import { createHash as createHash25 } from "node:crypto";
148198
148321
  import { readFile as readFile36 } from "node:fs/promises";
148199
148322
  function findColumn2(ir2, columnId) {
148200
148323
  if (ir2.version !== "v2") return void 0;
@@ -148232,9 +148355,9 @@ async function checkAndRecordUnplannedExecutionBlock(store, task, ir2) {
148232
148355
  promptContent = "";
148233
148356
  }
148234
148357
  }
148235
- const promptMarker = promptContent.length > 0 ? createHash24("sha256").update(promptContent).digest("hex") : "missing";
148358
+ const promptMarker = promptContent.length > 0 ? createHash25("sha256").update(promptContent).digest("hex") : "missing";
148236
148359
  const dependencies = [...task.dependencies ?? []].sort();
148237
- const episode = createHash24("sha256").update(JSON.stringify({
148360
+ const episode = createHash25("sha256").update(JSON.stringify({
148238
148361
  planReviewNode,
148239
148362
  promptMarker,
148240
148363
  dependencies,
@@ -153237,6 +153360,15 @@ async function withOAuthRefreshLock(authPath, providerId, refresh) {
153237
153360
  await release3();
153238
153361
  }
153239
153362
  }
153363
+ async function withOAuthInstanceLoginLock(authPath, providerId, operation) {
153364
+ const safeProviderId = providerId.replace(/[^a-zA-Z0-9._-]/g, "_");
153365
+ const release3 = await import_proper_lockfile.default.lock(`${authPath}.${safeProviderId}.instance-login`, AUTH_LOCK_OPTIONS);
153366
+ try {
153367
+ return await operation();
153368
+ } finally {
153369
+ await release3();
153370
+ }
153371
+ }
153240
153372
  function readDefaultInstanceMap(data) {
153241
153373
  const candidate = data.__fusionDefaultInstances;
153242
153374
  if (!candidate || typeof candidate !== "object" || Array.isArray(candidate)) return {};
@@ -153526,7 +153658,7 @@ function createFusionAuthStorage() {
153526
153658
  primary.reload();
153527
153659
  const selectPersistedRefreshCredential = () => {
153528
153660
  const storedCredential = primary.get(storageProvider);
153529
- if (refreshLockProvider !== ANTHROPIC_PROVIDER_ID2) {
153661
+ if (storedCredential?.type === "oauth" || refreshLockProvider !== ANTHROPIC_PROVIDER_ID2) {
153530
153662
  return storedCredential;
153531
153663
  }
153532
153664
  const legacyCredential = primary.get(ANTHROPIC_PROVIDER_ID2);
@@ -153656,18 +153788,16 @@ function createFusionAuthStorage() {
153656
153788
  if (rawKey) return rawKey;
153657
153789
  }
153658
153790
  const subscriptionLoggedOut = loggedOutProviders.has(ANTHROPIC_SUBSCRIPTION_PROVIDER_ID);
153659
- const legacyAnthropicOAuthCredential = rawProviderLoggedOut ? void 0 : selectStoredCredentialByType(ANTHROPIC_PROVIDER_ID2, "oauth");
153791
+ const subscriptionCredential = subscriptionLoggedOut ? void 0 : selectStoredCredential(ANTHROPIC_SUBSCRIPTION_PROVIDER_ID);
153792
+ if (subscriptionCredential?.type === "oauth") {
153793
+ const subscriptionKey = await resolveRefreshableCredentialApiKey(ANTHROPIC_SUBSCRIPTION_PROVIDER_ID, subscriptionCredential);
153794
+ if (subscriptionKey) return subscriptionKey;
153795
+ }
153796
+ const legacyAnthropicOAuthCredential = rawProviderLoggedOut || subscriptionCredential ? void 0 : selectStoredCredentialByType(ANTHROPIC_PROVIDER_ID2, "oauth");
153660
153797
  if (!subscriptionLoggedOut && legacyAnthropicOAuthCredential) {
153661
153798
  const legacyKey = await resolveRefreshableCredentialApiKey(ANTHROPIC_PROVIDER_ID2, legacyAnthropicOAuthCredential);
153662
153799
  if (legacyKey) return legacyKey;
153663
153800
  }
153664
- if (!subscriptionLoggedOut) {
153665
- const subscriptionCredential = selectStoredCredential(ANTHROPIC_SUBSCRIPTION_PROVIDER_ID);
153666
- if (subscriptionCredential?.type === "oauth") {
153667
- const subscriptionKey = await resolveRefreshableCredentialApiKey(ANTHROPIC_SUBSCRIPTION_PROVIDER_ID, subscriptionCredential);
153668
- if (subscriptionKey) return subscriptionKey;
153669
- }
153670
- }
153671
153801
  if (preferSubscription) {
153672
153802
  const rawKey = resolveRawApiKey();
153673
153803
  if (rawKey) return rawKey;
@@ -153844,8 +153974,15 @@ function createFusionAuthStorage() {
153844
153974
  return void 0;
153845
153975
  }
153846
153976
  if (sourceProvider !== ANTHROPIC_SUBSCRIPTION_PROVIDER_ID) {
153847
- await primary.set(ANTHROPIC_SUBSCRIPTION_PROVIDER_ID, subscriptionCredential);
153848
- loggedOutProviders.delete(ANTHROPIC_SUBSCRIPTION_PROVIDER_ID);
153977
+ const legacyKey = await resolveRefreshableCredentialApiKey(ANTHROPIC_PROVIDER_ID2, subscriptionCredential);
153978
+ primary.reload();
153979
+ const storedSubscription = primary.get(ANTHROPIC_SUBSCRIPTION_PROVIDER_ID);
153980
+ const refreshedLegacyCredential = primary.get(ANTHROPIC_PROVIDER_ID2);
153981
+ if (!storedSubscription && refreshedLegacyCredential?.type === "oauth") {
153982
+ await primary.set(ANTHROPIC_SUBSCRIPTION_PROVIDER_ID, refreshedLegacyCredential);
153983
+ loggedOutProviders.delete(ANTHROPIC_SUBSCRIPTION_PROVIDER_ID);
153984
+ }
153985
+ return legacyKey;
153849
153986
  }
153850
153987
  return resolveRefreshableCredentialApiKey(ANTHROPIC_SUBSCRIPTION_PROVIDER_ID, subscriptionCredential);
153851
153988
  }
@@ -153890,6 +154027,9 @@ var init_auth_storage = __esm({
153890
154027
  }
153891
154028
  data = {};
153892
154029
  modelRuntime;
154030
+ async withProviderInstanceLoginLock(providerId, operation) {
154031
+ return withOAuthInstanceLoginLock(this.authPath, providerId, operation);
154032
+ }
153893
154033
  ensureFile() {
153894
154034
  const parent = dirname27(this.authPath);
153895
154035
  if (!existsSync47(parent)) mkdirSync7(parent, { recursive: true, mode: 448 });
@@ -154596,7 +154736,7 @@ var init_gating_classifications = __esm({
154596
154736
  });
154597
154737
 
154598
154738
  // ../engine/src/agents/agent-action-gate.ts
154599
- import { createHash as createHash25 } from "node:crypto";
154739
+ import { createHash as createHash26 } from "node:crypto";
154600
154740
  function getExemptTools() {
154601
154741
  if (!_exemptTools) {
154602
154742
  _exemptTools = new Set(DEFAULT_EXEMPT_TOOLS);
@@ -154630,7 +154770,7 @@ function evaluateAgentActionGate(params) {
154630
154770
  if (params.toolName === "bash") {
154631
154771
  const command = extractShellCommand(args);
154632
154772
  const git10 = classifyGitCommand(command);
154633
- resourceId = command ? `cmd:${createHash25("sha256").update(command).digest("hex").slice(0, 16)}` : void 0;
154773
+ resourceId = command ? `cmd:${createHash26("sha256").update(command).digest("hex").slice(0, 16)}` : void 0;
154634
154774
  if (git10?.write) {
154635
154775
  category = "git_write";
154636
154776
  operation = git10.operation;
@@ -176496,6 +176636,7 @@ async function connectMcpSessionTools(servers, opts = {}) {
176496
176636
  const clients = [];
176497
176637
  const closedClients = /* @__PURE__ */ new WeakSet();
176498
176638
  const usedToolNames = /* @__PURE__ */ new Set();
176639
+ const serverByToolName = /* @__PURE__ */ new Map();
176499
176640
  let disposed = false;
176500
176641
  const closeOnce = async (client) => {
176501
176642
  if (closedClients.has(client)) return;
@@ -176545,7 +176686,9 @@ async function connectMcpSessionTools(servers, opts = {}) {
176545
176686
  opts.logger?.log?.(connectMsg);
176546
176687
  }
176547
176688
  for (const tool of listedTools) {
176548
- tools.push(wrapMcpTool(server.name, tool, client, usedToolNames));
176689
+ const wrappedTool = wrapMcpTool(server.name, tool, client, usedToolNames);
176690
+ tools.push(wrappedTool);
176691
+ serverByToolName.set(wrappedTool.name, server.name);
176549
176692
  }
176550
176693
  break;
176551
176694
  } catch (error2) {
@@ -176572,7 +176715,7 @@ async function connectMcpSessionTools(servers, opts = {}) {
176572
176715
  await closeAll();
176573
176716
  }
176574
176717
  }
176575
- return { tools, connected, skipped, dispose: closeAll };
176718
+ return { tools, connected, skipped, serverByToolName, dispose: closeAll };
176576
176719
  }
176577
176720
  function normalizeMaxAttempts(value) {
176578
176721
  if (value === void 0 || !Number.isFinite(value)) return DEFAULT_MAX_ATTEMPTS;
@@ -179706,8 +179849,10 @@ async function createPiAgentSessionRaw(options) {
179706
179849
  const createSessionWithModel = async (modelOverride) => {
179707
179850
  let mcpToolset;
179708
179851
  const allowReadonlyMcpTools = options.allowMcpToolsInReadonly === true;
179709
- if (forwardedMcpServers.length > 0 && (!isReadonly || allowReadonlyMcpTools)) {
179710
- mcpToolset = await connectMcpSessionTools(forwardedMcpServers, {
179852
+ const readonlyMcpServerAllowlist = options.readonlyMcpServerAllowlist === void 0 ? void 0 : new Set(options.readonlyMcpServerAllowlist.map((name2) => name2.trim()).filter(Boolean));
179853
+ const mcpServersToConnect = isReadonly && allowReadonlyMcpTools && readonlyMcpServerAllowlist !== void 0 ? forwardedMcpServers.filter((server) => readonlyMcpServerAllowlist.has(server.name)) : forwardedMcpServers;
179854
+ if (mcpServersToConnect.length > 0 && (!isReadonly || allowReadonlyMcpTools)) {
179855
+ mcpToolset = await connectMcpSessionTools(mcpServersToConnect, {
179711
179856
  cwd: options.cwd,
179712
179857
  clientFactory: options.mcpClientFactory,
179713
179858
  logger: piLog,
@@ -179718,7 +179863,8 @@ async function createPiAgentSessionRaw(options) {
179718
179863
  piLog.warn(`MCP session continuing with unavailable servers: count=${bootstrapFailures.length}`);
179719
179864
  }
179720
179865
  } else if (forwardedMcpServers.length > 0 && isReadonly) {
179721
- piLog.debug(`readonly session \u2014 MCP servers (${forwardedMcpServers.length}) skipped`);
179866
+ const reason = !allowReadonlyMcpTools ? "no-readonly-opt-in" : "readonly-allowlist-no-match";
179867
+ piLog.log(`readonly session \u2014 MCP servers (${forwardedMcpServers.length}) skipped: reason=${reason}`);
179722
179868
  }
179723
179869
  const mcpReadonlyTools = new Set(mcpToolset?.tools ?? []);
179724
179870
  const candidateCustomTools = [
@@ -179727,7 +179873,9 @@ async function createPiAgentSessionRaw(options) {
179727
179873
  ];
179728
179874
  const readonlyFilteredCustomTools = isReadonly ? filterCustomToolsForReadonly(
179729
179875
  candidateCustomTools,
179730
- allowReadonlyMcpTools ? { allowTool: (tool) => mcpReadonlyTools.has(tool) } : {}
179876
+ allowReadonlyMcpTools ? {
179877
+ allowTool: (tool) => readonlyMcpServerAllowlist === void 0 ? mcpReadonlyTools.has(tool) : readonlyMcpServerAllowlist.has(mcpToolset?.serverByToolName?.get(tool.name) ?? "")
179878
+ } : {}
179731
179879
  ) : { allowed: candidateCustomTools, denied: [] };
179732
179880
  const allowlistFilteredCustomTools = {
179733
179881
  ...readonlyFilteredCustomTools,
@@ -180197,7 +180345,7 @@ var init_pi = __esm({
180197
180345
  });
180198
180346
 
180199
180347
  // ../engine/src/agents/agent-logger.ts
180200
- import { createHash as createHash26 } from "node:crypto";
180348
+ import { createHash as createHash27 } from "node:crypto";
180201
180349
  function truncateToolResultDetail(value) {
180202
180350
  if (value.length <= TOOL_RESULT_DETAIL_LIMIT) return value;
180203
180351
  return `${value.slice(0, TOOL_RESULT_DETAIL_LIMIT)}${TOOL_RESULT_DETAIL_TRUNCATION_NOTICE}`;
@@ -180305,7 +180453,7 @@ function summarizeToolArgs(name2, args) {
180305
180453
  return `${key}=${clipToolArgValue(compactToolArgValue(args[key]), maximum)}`;
180306
180454
  }).join(", ");
180307
180455
  if (rendered.length <= TOOL_ARG_SUMMARY_MAX) return rendered;
180308
- const hash3 = createHash26("sha256").update(fullArgsJson).digest("hex").slice(0, STRUCTURED_ARG_HASH_HEX_LEN);
180456
+ const hash3 = createHash27("sha256").update(fullArgsJson).digest("hex").slice(0, STRUCTURED_ARG_HASH_HEX_LEN);
180309
180457
  const suffix = `\u2026#${hash3}`;
180310
180458
  const keep = Math.max(0, TOOL_ARG_SUMMARY_MAX - suffix.length);
180311
180459
  return `${rendered.slice(0, keep)}${suffix}`;
@@ -183013,7 +183161,7 @@ var init_retry_burned_logger = __esm({
183013
183161
 
183014
183162
  // ../engine/src/merge/merge-dependency-sync.ts
183015
183163
  import { exec as exec20 } from "node:child_process";
183016
- import { createHash as createHash27 } from "node:crypto";
183164
+ import { createHash as createHash28 } from "node:crypto";
183017
183165
  import { existsSync as existsSync50, readFileSync as readFileSync19, writeFileSync as writeFileSync6 } from "node:fs";
183018
183166
  import { join as join69 } from "node:path";
183019
183167
  import { promisify as promisify27 } from "node:util";
@@ -183046,7 +183194,7 @@ function computeLockfileHash(rootDir) {
183046
183194
  const p2 = join69(rootDir, name2);
183047
183195
  if (existsSync50(p2)) {
183048
183196
  try {
183049
- return createHash27("sha256").update(readFileSync19(p2)).digest("hex");
183197
+ return createHash28("sha256").update(readFileSync19(p2)).digest("hex");
183050
183198
  } catch {
183051
183199
  return null;
183052
183200
  }
@@ -183176,7 +183324,7 @@ var init_merge_dependency_sync = __esm({
183176
183324
  });
183177
183325
 
183178
183326
  // ../engine/src/worktree/worktree-dependency-install.ts
183179
- import { createHash as createHash28 } from "node:crypto";
183327
+ import { createHash as createHash29 } from "node:crypto";
183180
183328
  import { existsSync as existsSync51, readFileSync as readFileSync20, readdirSync as readdirSync8, statSync as statSync11, writeFileSync as writeFileSync7 } from "node:fs";
183181
183329
  import { delimiter as delimiter2, join as join70 } from "node:path";
183182
183330
  function rootEntries(rootDir) {
@@ -183274,7 +183422,7 @@ function isCommandAvailable(binary, env = process.env) {
183274
183422
  );
183275
183423
  }
183276
183424
  function hashParts(parts) {
183277
- const hash3 = createHash28("sha256");
183425
+ const hash3 = createHash29("sha256");
183278
183426
  for (const [name2, value] of [...parts].sort(([left], [right]) => left.localeCompare(right))) {
183279
183427
  hash3.update(name2).update("\0").update(value).update("\0");
183280
183428
  }
@@ -185558,7 +185706,7 @@ var init_code_node_runner = __esm({
185558
185706
  // ../engine/src/agent-tools.ts
185559
185707
  import { appendFile as appendFile3, mkdir as mkdir27, readFile as readFile40, readdir as readdir11, realpath, stat as stat11, writeFile as writeFile23 } from "node:fs/promises";
185560
185708
  import { existsSync as existsSync53 } from "node:fs";
185561
- import { createHash as createHash29, randomUUID as randomUUID48 } from "node:crypto";
185709
+ import { createHash as createHash30, randomUUID as randomUUID48 } from "node:crypto";
185562
185710
  import { tmpdir as tmpdir6 } from "node:os";
185563
185711
  import { extname as extname4, isAbsolute as isAbsolute14, join as join72, relative as relative13, resolve as resolve40, sep as sep11 } from "node:path";
185564
185712
  import { Type as Type2 } from "@earendil-works/pi-ai";
@@ -185602,7 +185750,7 @@ async function readAgentMemoryWorkspaceLongTerm(rootDir, agentId) {
185602
185750
  }
185603
185751
  }
185604
185752
  function qmdAgentMemoryCollectionName2(rootDir, agentId) {
185605
- const hash3 = createHash29("sha1").update(`${rootDir}:${agentId}`).digest("hex").slice(0, 12);
185753
+ const hash3 = createHash30("sha1").update(`${rootDir}:${agentId}`).digest("hex").slice(0, 12);
185606
185754
  return `fusion-agent-memory-${sanitizeAgentMemoryId(agentId).toLowerCase()}-${hash3}`;
185607
185755
  }
185608
185756
  function buildQmdAgentMemoryCollectionAddArgs(rootDir, agentId) {
@@ -185731,11 +185879,11 @@ async function refreshAgentMemoryQmdIndex(rootDir, agentMemory) {
185731
185879
  return;
185732
185880
  }
185733
185881
  const promise = (async () => {
185734
- const { execFile: execFile42 } = await import("node:child_process");
185735
- const { promisify: promisify90 } = await import("node:util");
185736
- const execFileAsync36 = promisify90(execFile42);
185882
+ const { execFile: execFile43 } = await import("node:child_process");
185883
+ const { promisify: promisify91 } = await import("node:util");
185884
+ const execFileAsync37 = promisify91(execFile43);
185737
185885
  try {
185738
- await execFileAsync36("qmd", buildQmdAgentMemoryCollectionAddArgs(rootDir, agentMemory.agentId), {
185886
+ await execFileAsync37("qmd", buildQmdAgentMemoryCollectionAddArgs(rootDir, agentMemory.agentId), {
185739
185887
  cwd: rootDir,
185740
185888
  timeout: 4e3,
185741
185889
  maxBuffer: 512 * 1024
@@ -185748,8 +185896,8 @@ ${stderr}`)) {
185748
185896
  throw error2;
185749
185897
  }
185750
185898
  }
185751
- await execFileAsync36("qmd", ["update"], { cwd: rootDir, timeout: 3e4, maxBuffer: 1024 * 1024 });
185752
- await execFileAsync36("qmd", ["embed"], { cwd: rootDir, timeout: 12e4, maxBuffer: 1024 * 1024 });
185899
+ await execFileAsync37("qmd", ["update"], { cwd: rootDir, timeout: 3e4, maxBuffer: 1024 * 1024 });
185900
+ await execFileAsync37("qmd", ["embed"], { cwd: rootDir, timeout: 12e4, maxBuffer: 1024 * 1024 });
185753
185901
  })();
185754
185902
  agentQmdRefreshState.set(key, { lastStartedAt: now, inFlight: promise });
185755
185903
  try {
@@ -185804,10 +185952,10 @@ async function searchAgentMemoryWithQmd(rootDir, agentMemory, query, limit) {
185804
185952
  }
185805
185953
  try {
185806
185954
  await refreshAgentMemoryQmdIndex(rootDir, agentMemory);
185807
- const { execFile: execFile42 } = await import("node:child_process");
185808
- const { promisify: promisify90 } = await import("node:util");
185809
- const execFileAsync36 = promisify90(execFile42);
185810
- const { stdout } = await execFileAsync36("qmd", buildQmdAgentMemorySearchArgs(rootDir, agentMemory.agentId, query, limit), {
185955
+ const { execFile: execFile43 } = await import("node:child_process");
185956
+ const { promisify: promisify91 } = await import("node:util");
185957
+ const execFileAsync37 = promisify91(execFile43);
185958
+ const { stdout } = await execFileAsync37("qmd", buildQmdAgentMemorySearchArgs(rootDir, agentMemory.agentId, query, limit), {
185811
185959
  cwd: rootDir,
185812
185960
  timeout: 4e3,
185813
185961
  maxBuffer: 1024 * 1024
@@ -190384,7 +190532,7 @@ var init_workspace_review_remediation = __esm({
190384
190532
  });
190385
190533
 
190386
190534
  // ../engine/src/worktree/secrets-env-writer.ts
190387
- import { createHash as createHash30 } from "node:crypto";
190535
+ import { createHash as createHash31 } from "node:crypto";
190388
190536
  import { execFile as execFile17 } from "node:child_process";
190389
190537
  import { promises as fs6 } from "node:fs";
190390
190538
  import path3 from "node:path";
@@ -190393,7 +190541,7 @@ function isValidFilename(filename) {
190393
190541
  return !!filename && !filename.includes("/") && !filename.includes("\\") && !filename.includes("..") && !filename.includes("\0") && filename !== FINGERPRINT_FILE;
190394
190542
  }
190395
190543
  function sha2562(content) {
190396
- return createHash30("sha256").update(Buffer.from(content, "utf8")).digest("hex");
190544
+ return createHash31("sha256").update(Buffer.from(content, "utf8")).digest("hex");
190397
190545
  }
190398
190546
  function quote(value) {
190399
190547
  return `"${value.replaceAll("\\", "\\\\").replaceAll('"', '\\"').replaceAll("\n", "\\n").replaceAll("\r", "\\r").replaceAll(" ", "\\t")}"`;
@@ -199189,7 +199337,7 @@ var init_mesh_lease_manager = __esm({
199189
199337
  });
199190
199338
 
199191
199339
  // ../engine/src/merge/merger-integration-worktree.ts
199192
- import { createHash as createHash32 } from "node:crypto";
199340
+ import { createHash as createHash33 } from "node:crypto";
199193
199341
  import { exec as exec33, execFile as execFile26 } from "node:child_process";
199194
199342
  import { promisify as promisify49 } from "node:util";
199195
199343
  function quoteAutostashArg(value) {
@@ -199312,7 +199460,7 @@ async function gitDirtyFingerprintLocal(rootDir) {
199312
199460
  )
199313
199461
  ]);
199314
199462
  if (!diffOut && !statusOut) return "";
199315
- return createHash32("sha256").update(diffOut).update("\0").update(statusOut).digest("hex");
199463
+ return createHash33("sha256").update(diffOut).update("\0").update(statusOut).digest("hex");
199316
199464
  } catch {
199317
199465
  return "";
199318
199466
  }
@@ -200295,7 +200443,7 @@ import { execSync as execSync6, exec as exec35 } from "node:child_process";
200295
200443
  import * as childProcess4 from "node:child_process";
200296
200444
  import { promisify as promisify52 } from "node:util";
200297
200445
  import { existsSync as existsSync61, readFileSync as readFileSync23, writeFileSync as writeFileSync9, unlinkSync as unlinkSync3, renameSync as renameSync4 } from "node:fs";
200298
- import { createHash as createHash33 } from "node:crypto";
200446
+ import { createHash as createHash34 } from "node:crypto";
200299
200447
  import { join as join80, resolve as resolve47 } from "node:path";
200300
200448
  import { hostname as hostname3 } from "node:os";
200301
200449
  import { Type as Type4 } from "typebox";
@@ -200550,7 +200698,7 @@ async function gitDirtyFingerprint(rootDir) {
200550
200698
  )
200551
200699
  ]);
200552
200700
  if (!diffOut && !statusOut) return "";
200553
- return createHash33("sha256").update(diffOut).update("\0").update(statusOut).digest("hex");
200701
+ return createHash34("sha256").update(diffOut).update("\0").update(statusOut).digest("hex");
200554
200702
  } catch {
200555
200703
  return "";
200556
200704
  }
@@ -209575,19 +209723,51 @@ async function captureBaseCommitSha(store, task, worktreePath, audit, options =
209575
209723
  }
209576
209724
 
209577
209725
  // ../engine/src/worktree/review-diff-fingerprint.ts
209578
- import { createHash as createHash23 } from "node:crypto";
209726
+ import { createHash as createHash24 } from "node:crypto";
209579
209727
  import { execFile as execFile8 } from "node:child_process";
209580
209728
  import { promisify as promisify8 } from "node:util";
209581
209729
  var execFileAsync5 = promisify8(execFile8);
209730
+ var REVIEW_DIFF_GIT_MAX_BUFFER_BYTES = 10 * 1024 * 1024;
209731
+ var REVIEW_DIFF_GIT_TIMEOUT_MS = 12e4;
209732
+ function gitCaptureOptions(cwd) {
209733
+ return {
209734
+ cwd,
209735
+ encoding: "utf8",
209736
+ maxBuffer: REVIEW_DIFF_GIT_MAX_BUFFER_BYTES,
209737
+ timeout: REVIEW_DIFF_GIT_TIMEOUT_MS
209738
+ };
209739
+ }
209740
+ function isGitDiffBufferOverflow(error2) {
209741
+ if (typeof error2 !== "object" || error2 === null) return false;
209742
+ const candidate = error2;
209743
+ return candidate.code === "ERR_CHILD_PROCESS_STDIO_MAXBUFFER" || typeof candidate.message === "string" && candidate.message.includes("maxBuffer");
209744
+ }
209582
209745
  var MAX_REVIEW_CHANGED_FILES = 100;
209583
209746
  var EMPTY_REVIEW_DIFF_FINGERPRINT = "empty-review-input:v1";
209747
+ async function resolveContentReviewInputProof(worktreePath, diffBaseCommitSha) {
209748
+ let baseRef;
209749
+ try {
209750
+ baseRef = await resolveDiffBaseRef(worktreePath, diffBaseCommitSha);
209751
+ } catch {
209752
+ return { kind: "unprovable", reason: "no-diff-base" };
209753
+ }
209754
+ if (!baseRef) return { kind: "unprovable", reason: "no-diff-base" };
209755
+ const probe = await probeReviewDiffFingerprint(worktreePath, baseRef);
209756
+ if (probe.state === "empty") {
209757
+ return { kind: "fingerprint", fingerprint: EMPTY_REVIEW_DIFF_FINGERPRINT };
209758
+ }
209759
+ return probe.state === "fingerprint" ? { kind: "fingerprint", fingerprint: probe.fingerprint } : { kind: "unprovable", reason: probe.reason };
209760
+ }
209584
209761
  async function probeReviewDiffFingerprint(worktreePath, baseRef, targetRef = "HEAD") {
209585
209762
  if (!worktreePath || !baseRef) return { state: "unavailable", reason: "missing-worktree-or-base" };
209586
209763
  try {
209587
- const { stdout } = await execFileAsync5("git", ["diff", "--binary", `${baseRef}..${targetRef}`], { cwd: worktreePath, encoding: "utf8" });
209588
- return stdout ? { state: "fingerprint", fingerprint: createHash23("sha256").update(stdout).digest("hex") } : { state: "empty" };
209589
- } catch {
209590
- return { state: "unavailable", reason: "git-diff-failed" };
209764
+ const { stdout } = await execFileAsync5("git", ["diff", "--binary", `${baseRef}..${targetRef}`], gitCaptureOptions(worktreePath));
209765
+ return stdout ? { state: "fingerprint", fingerprint: createHash24("sha256").update(stdout).digest("hex") } : { state: "empty" };
209766
+ } catch (error2) {
209767
+ return {
209768
+ state: "unavailable",
209769
+ reason: isGitDiffBufferOverflow(error2) ? "git-diff-too-large" : "git-diff-failed"
209770
+ };
209591
209771
  }
209592
209772
  }
209593
209773
  async function probeReviewChangesSinceCommit(worktreePath, reviewedCommitSha) {
@@ -209595,14 +209775,11 @@ async function probeReviewChangesSinceCommit(worktreePath, reviewedCommitSha) {
209595
209775
  return { state: "unavailable", reason: "missing-worktree-or-reviewed-commit" };
209596
209776
  }
209597
209777
  try {
209598
- await execFileAsync5("git", ["cat-file", "-e", `${reviewedCommitSha}^{commit}`], {
209599
- cwd: worktreePath,
209600
- encoding: "utf8"
209601
- });
209778
+ await execFileAsync5("git", ["cat-file", "-e", `${reviewedCommitSha}^{commit}`], gitCaptureOptions(worktreePath));
209602
209779
  const [{ stdout: countOutput }, { stdout: filesOutput }, { stdout: shortstatOutput }] = await Promise.all([
209603
- execFileAsync5("git", ["rev-list", "--count", `${reviewedCommitSha}..HEAD`], { cwd: worktreePath, encoding: "utf8" }),
209604
- execFileAsync5("git", ["diff", "--name-only", "-z", `${reviewedCommitSha}..HEAD`], { cwd: worktreePath, encoding: "utf8" }),
209605
- execFileAsync5("git", ["diff", "--shortstat", `${reviewedCommitSha}..HEAD`], { cwd: worktreePath, encoding: "utf8" })
209780
+ execFileAsync5("git", ["rev-list", "--count", `${reviewedCommitSha}..HEAD`], gitCaptureOptions(worktreePath)),
209781
+ execFileAsync5("git", ["diff", "--name-only", "-z", `${reviewedCommitSha}..HEAD`], gitCaptureOptions(worktreePath)),
209782
+ execFileAsync5("git", ["diff", "--shortstat", `${reviewedCommitSha}..HEAD`], gitCaptureOptions(worktreePath))
209606
209783
  ]);
209607
209784
  const commitCount = Number.parseInt(countOutput.trim(), 10);
209608
209785
  if (!Number.isSafeInteger(commitCount) || commitCount < 0) {
@@ -209639,7 +209816,12 @@ import { promisify as promisify11 } from "node:util";
209639
209816
  import { join as join57 } from "node:path";
209640
209817
  var execFileAsync6 = promisify11(execFile9);
209641
209818
  async function git(args, cwd) {
209642
- const { stdout } = await execFileAsync6("git", args, { cwd, encoding: "utf8" });
209819
+ const { stdout } = await execFileAsync6("git", args, {
209820
+ cwd,
209821
+ encoding: "utf8",
209822
+ maxBuffer: REVIEW_DIFF_GIT_MAX_BUFFER_BYTES,
209823
+ timeout: REVIEW_DIFF_GIT_TIMEOUT_MS
209824
+ });
209643
209825
  return stdout.trim();
209644
209826
  }
209645
209827
  async function captureWorkspaceReviewEvidence(options) {
@@ -209721,14 +209903,14 @@ async function captureMergeContentDescriptor(task, deps) {
209721
209903
 
209722
209904
  // ../engine/src/project-engine.ts
209723
209905
  init_integration_branch();
209724
- import { execFile as execFile41 } from "node:child_process";
209725
- import { promisify as promisify89 } from "node:util";
209906
+ import { execFile as execFile42 } from "node:child_process";
209907
+ import { promisify as promisify90 } from "node:util";
209726
209908
 
209727
209909
  // ../engine/src/runtimes/in-process-runtime.ts
209728
209910
  init_src2();
209729
209911
  init_scheduler();
209730
209912
  init_pi();
209731
- import { createHash as createHash37 } from "node:crypto";
209913
+ import { createHash as createHash38 } from "node:crypto";
209732
209914
  import { EventEmitter as EventEmitter27 } from "node:events";
209733
209915
 
209734
209916
  // ../engine/src/executor/public-reexports.ts
@@ -211291,7 +211473,7 @@ var WorkflowGraphExecutor = class {
211291
211473
  phase: "pre-merge",
211292
211474
  source: "optional-group",
211293
211475
  status: "skipped",
211294
- bypassedBy: "fast-mode",
211476
+ bypassedBy: FAST_MODE_BYPASS_ACTOR,
211295
211477
  bypassedAt,
211296
211478
  bypassReason: "Fast mode bypasses pre-merge workflow gates",
211297
211479
  bypassedFromStatus: "absent",
@@ -211495,7 +211677,7 @@ var WorkflowGraphExecutor = class {
211495
211677
  const stepFindings = this.workflowReviewKind(node) && Array.isArray(exitContextPatch?.findings) ? exitContextPatch.findings : void 0;
211496
211678
  const repositoryReviewOutcomes = this.workflowReviewKind(node) && Array.isArray(exitContextPatch?.repositoryReviewOutcomes) ? exitContextPatch.repositoryReviewOutcomes : void 0;
211497
211679
  const repositoryScopeRevision = this.workflowReviewKind(node) && typeof exitContextPatch?.repositoryScopeRevision === "number" ? exitContextPatch.repositoryScopeRevision : void 0;
211498
- const reviewInputFingerprint = this.workflowReviewKind(node) && typeof exitContextPatch?.reviewInputFingerprint === "string" ? exitContextPatch.reviewInputFingerprint : void 0;
211680
+ const reviewInputFingerprint = requiresContentReviewProof(node.id, { reviewKind: this.workflowReviewKind(node) }) && typeof exitContextPatch?.reviewInputFingerprint === "string" ? exitContextPatch.reviewInputFingerprint : void 0;
211499
211681
  const reviewedCommitSha = this.workflowReviewKind(node) && typeof exitContextPatch?.reviewedCommitSha === "string" ? exitContextPatch.reviewedCommitSha : void 0;
211500
211682
  const supersededFindingSourceWorkflowStepId = this.workflowReviewKind(node) && typeof exitContextPatch?.supersededFindingSourceWorkflowStepId === "string" ? exitContextPatch.supersededFindingSourceWorkflowStepId : void 0;
211501
211683
  const supersededFindingIds = supersededFindingSourceWorkflowStepId && this.workflowReviewKind(node) && Array.isArray(exitContextPatch?.supersededFindingIds) ? exitContextPatch.supersededFindingIds.filter((id) => typeof id === "string") : void 0;
@@ -212234,7 +212416,7 @@ ${stepOutput}` : ""}`);
212234
212416
  const findings = this.workflowReviewKind(node) && Array.isArray(contextPatch.findings) ? contextPatch.findings : void 0;
212235
212417
  const repositoryReviewOutcomes = this.workflowReviewKind(node) && Array.isArray(contextPatch.repositoryReviewOutcomes) ? contextPatch.repositoryReviewOutcomes : void 0;
212236
212418
  const repositoryScopeRevision = this.workflowReviewKind(node) && typeof contextPatch.repositoryScopeRevision === "number" ? contextPatch.repositoryScopeRevision : void 0;
212237
- const reviewInputFingerprint = this.workflowReviewKind(node) && typeof contextPatch.reviewInputFingerprint === "string" ? contextPatch.reviewInputFingerprint : void 0;
212419
+ const reviewInputFingerprint = requiresContentReviewProof(node.id, { reviewKind: this.workflowReviewKind(node) }) && typeof contextPatch.reviewInputFingerprint === "string" ? contextPatch.reviewInputFingerprint : void 0;
212238
212420
  const reviewedCommitSha = this.workflowReviewKind(node) && typeof contextPatch.reviewedCommitSha === "string" ? contextPatch.reviewedCommitSha : void 0;
212239
212421
  const supersededFindingSourceWorkflowStepId = this.workflowReviewKind(node) && typeof contextPatch.supersededFindingSourceWorkflowStepId === "string" ? contextPatch.supersededFindingSourceWorkflowStepId : void 0;
212240
212422
  const supersededFindingIds = supersededFindingSourceWorkflowStepId && this.workflowReviewKind(node) && Array.isArray(contextPatch.supersededFindingIds) ? contextPatch.supersededFindingIds.filter((id) => typeof id === "string") : void 0;
@@ -212756,7 +212938,7 @@ init_agent_tools();
212756
212938
  init_agent_logger();
212757
212939
  init_agent_usage_telemetry();
212758
212940
  import { Type as Type3 } from "@earendil-works/pi-ai";
212759
- import { createHash as createHash31 } from "node:crypto";
212941
+ import { createHash as createHash32 } from "node:crypto";
212760
212942
 
212761
212943
  // ../engine/src/agents/approval-mail.ts
212762
212944
  init_src2();
@@ -214385,7 +214567,7 @@ function truncatePrompt(text2, maxChars) {
214385
214567
  ... (truncated, ${text2.length} chars)`;
214386
214568
  }
214387
214569
  function shortContentHash(value) {
214388
- return createHash31("sha256").update(value).digest("hex").slice(0, 8);
214570
+ return createHash32("sha256").update(value).digest("hex").slice(0, 8);
214389
214571
  }
214390
214572
  function buildIdentitySnapshot(args) {
214391
214573
  const { agent, resolvedInstructions, workspaceMemory } = args;
@@ -218766,6 +218948,51 @@ async function finalizeProvenAutoMergeTask({
218766
218948
  }
218767
218949
  }
218768
218950
 
218951
+ // ../engine/src/merge/stale-content-review-reroute.ts
218952
+ init_src2();
218953
+ function isContentReviewNode(node) {
218954
+ if (node.kind !== "optional-group" && node.kind !== "step-review") return false;
218955
+ const name2 = typeof node.config?.name === "string" ? node.config.name : "";
218956
+ return node.id === "code-review" || node.config?.reviewKind === "code" || /code review/i.test(name2);
218957
+ }
218958
+ async function rerouteSingularStaleContentToReview(store, task, options) {
218959
+ const mergeContent = options.mergeContent;
218960
+ if (mergeContent.kind !== "singular" || task.workspaceWorktrees !== void 0) {
218961
+ return { rerouted: false, reason: "not-singular" };
218962
+ }
218963
+ if (task.paused || task.userPaused || task.deletedAt || task.autoMerge === false) {
218964
+ return { rerouted: false, reason: "operator-held" };
218965
+ }
218966
+ if (mergeContent.diff.state !== "fingerprint") return { rerouted: false, reason: "no-progress" };
218967
+ const currentFingerprint = mergeContent.diff.fingerprint;
218968
+ const ir2 = await resolveWorkflowIrForTask(store, task.id);
218969
+ const selection = store.getTaskWorkflowSelectionAsync ? await store.getTaskWorkflowSelectionAsync(task.id) : store.getTaskWorkflowSelection(task.id);
218970
+ const selected = selection?.stepIds ?? [];
218971
+ const nodes2 = ir2.nodes.filter((node2) => {
218972
+ if (!isContentReviewNode(node2) || !options.requiredPreMergeStepIds.has(node2.id)) return false;
218973
+ return node2.config?.defaultOn === true || selected.includes(node2.id);
218974
+ });
218975
+ const node = nodes2.find((candidate) => {
218976
+ const latest = task.workflowStepResults?.filter((result2) => result2.workflowStepId === candidate.id).at(-1);
218977
+ return latest?.reviewInputFingerprint !== currentFingerprint;
218978
+ });
218979
+ if (!node) return { rerouted: false, reason: nodes2.length ? "no-progress" : "no-review-route" };
218980
+ const items = await store.listWorkflowWorkItemsForTask(task.id);
218981
+ const result = await store.seedWorkspaceCodeReviewContinuationIfIdle({
218982
+ taskId: task.id,
218983
+ nodeId: node.id,
218984
+ kind: "task",
218985
+ state: "runnable",
218986
+ runId: `${task.id}:stale-content-review-reroute:${node.id}:${items.length}`,
218987
+ stableWorkflowRunId: `${task.id}:${ir2.name}`,
218988
+ continuationSequence: items.length,
218989
+ sourceColumn: task.column,
218990
+ targetColumn: node.column ?? task.column,
218991
+ irHash: computeWorkflowIrPin(ir2, node.id).irHash
218992
+ });
218993
+ return result.seeded ? { rerouted: true, reason: "seeded", nodeId: node.id, workflowStepId: node.id } : { rerouted: false, reason: "active-continuation", nodeId: node.id, workflowStepId: node.id };
218994
+ }
218995
+
218769
218996
  // ../engine/src/healing/auto-recovery.ts
218770
218997
  init_logger();
218771
218998
  var autoRecoveryLog = createLogger("auto-recovery");
@@ -220175,9 +220402,6 @@ var BOARD_STALL_NOTIFICATION_COOLDOWN_MS = 60 * 6e4;
220175
220402
  var DB_CORRUPTION_NOTIFICATION_COOLDOWN_MS = 60 * 60 * 1e3;
220176
220403
  var PHANTOM_EXECUTOR_BINDING_AGE_MULTIPLIER = 3;
220177
220404
  var MAX_NO_PROGRESS_RESUME_ATTEMPTS = 2;
220178
- function resolveRepoLocalAiMergeRoot(rootDir, settings) {
220179
- return resolveAiMergeRootPath(rootDir, settings);
220180
- }
220181
220405
  async function classifyOwnedLandedEvidenceForSelfHealing(rootDir, task, mergeTargetBranch) {
220182
220406
  const { classifyOwnedLandedEvidence: classifyOwnedLandedEvidence2 } = await Promise.resolve().then(() => (init_merger(), merger_exports));
220183
220407
  return classifyOwnedLandedEvidence2(rootDir, task, { mergeTargetBranch });
@@ -220354,6 +220578,7 @@ var SelfHealingManager = class _SelfHealingManager extends SelfHealingGitEvidenc
220354
220578
  symbolLockNoActionAudited = false;
220355
220579
  maintenanceTickCounter = 0;
220356
220580
  taskLifecycleRetentionLastPrunedAt = /* @__PURE__ */ new Map();
220581
+ staleContentRerouteAuditKeys = /* @__PURE__ */ new Set();
220357
220582
  githubCheckStateRetentionLastPrunedAt = /* @__PURE__ */ new Map();
220358
220583
  processBootStartedAt = Date.now();
220359
220584
  lastDbCorruptionNotifiedAt = null;
@@ -221069,6 +221294,7 @@ var SelfHealingManager = class _SelfHealingManager extends SelfHealingGitEvidenc
221069
221294
  // flight" to all of them (the merge gate included), which is the two-hour
221070
221295
  // stall-deadlock ride this sweep exists to prevent.
221071
221296
  { name: "reconcile-orphaned-pending-step-results", fn: () => this.reconcileOrphanedPendingStepResults().then(() => void 0) },
221297
+ { name: "reconcile-unproven-review-approvals", fn: () => this.reconcileUnprovenReviewApprovals().then(() => void 0) },
221072
221298
  /*
221073
221299
  FNXC:WorkspaceContention 2026-08-23-08:00:
221074
221300
  `contention-hold` is owned by an in-memory retry timer. After a process restart that
@@ -221949,6 +222175,7 @@ var SelfHealingManager = class _SelfHealingManager extends SelfHealingGitEvidenc
221949
222175
  Keep them adjacent and in this order.
221950
222176
  */
221951
222177
  { name: "reconcile-orphaned-pending-step-results", fn: () => this.reconcileOrphanedPendingStepResults() },
222178
+ { name: "reconcile-unproven-review-approvals", fn: () => this.reconcileUnprovenReviewApprovals() },
221952
222179
  { name: "recover-failed-pre-merge-steps", fn: () => this.recoverReviewTasksWithFailedPreMergeSteps() },
221953
222180
  { name: "recover-missing-worktree-review-failures", fn: () => this.recoverMissingWorktreeReviewFailures() },
221954
222181
  { name: "recover-interrupted-merging", fn: () => this.recoverInterruptedMergingTasks() },
@@ -226395,6 +226622,95 @@ var SelfHealingManager = class _SelfHealingManager extends SelfHealingGitEvidenc
226395
226622
  return 0;
226396
226623
  }
226397
226624
  }
226625
+ /*
226626
+ FNXC:ReviewInputProof 2026-09-01-11:28:
226627
+ Prevention cannot release cards that already persisted a proofless `passed` content review. Repair
226628
+ only the invalid result in place, including when auto-merge is off, so automatic recovery can re-run
226629
+ eligible gates and a human-held card gains the failed result its audited bypass needs. The
226630
+ `needsOperatorBypass` label is therefore truthful rather than aspirational; no lifecycle state moves.
226631
+
226632
+ FNXC:ReviewInputProof 2026-09-01-11:57:
226633
+ Reconciliation must recompute from the lock-held task inside `updateTaskAtomic`; a graph writer can
226634
+ publish a proof-bound approval or another gate after the preliminary read, and a stale array write
226635
+ would discard that valid result. Re-check lane, user pause, workspace ownership, and session liveness
226636
+ in the callback, then log and audit only a repair whose atomic mutation actually applied.
226637
+ */
226638
+ async reconcileUnprovenReviewApprovals() {
226639
+ try {
226640
+ const reviewColumns = await resolveProjectColumnsForRoles(this.store, REVIEW_ROLES);
226641
+ const candidates = /* @__PURE__ */ new Map();
226642
+ for (const column of reviewColumns) {
226643
+ for (const task of await this.store.listTasks({ column, slim: true })) candidates.set(task.id, task);
226644
+ }
226645
+ const settings = await this.store.getSettings().catch(() => void 0);
226646
+ let repairedTasks = 0;
226647
+ const isSessionLive = (taskId) => {
226648
+ const livePaths = activeSessionRegistry.pathsForTask(taskId);
226649
+ return livePaths.some((path6) => activeSessionRegistry.isPathActive(path6)) || executingTaskLock.has(taskId) || this.options.isTaskActive?.(taskId) === true;
226650
+ };
226651
+ for (const candidate of candidates.values()) {
226652
+ if (candidate.userPaused === true || candidate.workspaceWorktrees !== void 0 || isSessionLive(candidate.id)) continue;
226653
+ if (!candidate.workflowStepResults?.some((result) => resolveUnprovenReviewApproval(result, { workspace: false }))) continue;
226654
+ const fresh = await this.store.getTask(candidate.id);
226655
+ if (!fresh || !reviewColumns.has(fresh.column) || fresh.userPaused === true || fresh.workspaceWorktrees !== void 0 || isSessionLive(fresh.id)) continue;
226656
+ let repair;
226657
+ try {
226658
+ await this.store.updateTaskAtomic(fresh.id, (live) => {
226659
+ repair = void 0;
226660
+ if (!reviewColumns.has(live.column) || live.userPaused === true || live.workspaceWorktrees !== void 0 || isSessionLive(live.id)) return null;
226661
+ const repairedStepIds = [];
226662
+ let repairReason;
226663
+ const results = (live.workflowStepResults ?? []).map((result) => {
226664
+ const resolution = resolveUnprovenReviewApproval(result, { workspace: false });
226665
+ if (!resolution) return result;
226666
+ repairedStepIds.push(result.workflowStepId);
226667
+ repairReason ??= resolution.reason;
226668
+ return resolution.downgraded;
226669
+ });
226670
+ if (repairedStepIds.length === 0 || !repairReason) return null;
226671
+ repair = {
226672
+ stepIds: repairedStepIds,
226673
+ reason: repairReason,
226674
+ column: live.column,
226675
+ resultCount: results.length,
226676
+ needsOperatorBypass: settings ? !allowsAutoMergeProcessing(live, settings) : void 0
226677
+ };
226678
+ return { workflowStepResults: results };
226679
+ });
226680
+ if (!repair) continue;
226681
+ repairedTasks += 1;
226682
+ } catch (error2) {
226683
+ log34.warn(`reconcileUnprovenReviewApprovals: failed for ${fresh.id}: ${error2 instanceof Error ? error2.message : String(error2)}`);
226684
+ continue;
226685
+ }
226686
+ await this.store.logEntry(
226687
+ fresh.id,
226688
+ `[pre-merge] Invalid proofless approval repaired for ${repair.stepIds.join(", ")}: ${repair.reason}`
226689
+ ).catch(() => void 0);
226690
+ await emitBoundedRunAudit2(this.store, {
226691
+ taskId: fresh.id,
226692
+ agentId: "self-healing",
226693
+ runId: generateSyntheticRunId("reconcile-unproven-review-approval", fresh.id),
226694
+ domain: "database",
226695
+ mutationType: "task:reconcile-unproven-review-approval",
226696
+ target: fresh.id,
226697
+ metadata: {
226698
+ taskId: fresh.id,
226699
+ column: repair.column,
226700
+ workflowStepId: repair.stepIds[0],
226701
+ repairedCount: repair.stepIds.length,
226702
+ resultCount: repair.resultCount,
226703
+ needsOperatorBypass: repair.needsOperatorBypass
226704
+ }
226705
+ }, { log: log34 });
226706
+ }
226707
+ if (repairedTasks > 0) log34.log(`Repaired unproven review approvals on ${repairedTasks} task(s)`);
226708
+ return repairedTasks;
226709
+ } catch (error2) {
226710
+ log34.error(`reconcileUnprovenReviewApprovals failed: ${error2 instanceof Error ? error2.message : String(error2)}`);
226711
+ return 0;
226712
+ }
226713
+ }
226398
226714
  /**
226399
226715
  * Backward lifecycle move gated on triple proof (FN-5335).
226400
226716
  * When the unproven fallback predicate fails, emits `task:finalize-no-op-review-no-action` and skips lifecycle mutation.
@@ -226748,6 +227064,66 @@ var SelfHealingManager = class _SelfHealingManager extends SelfHealingGitEvidenc
226748
227064
  }
226749
227065
  return allowsAutoMergeProcessing(task, settings) && resolveEffectiveAutoMerge(task, settings) !== false;
226750
227066
  }
227067
+ /*
227068
+ FNXC:PreMergeApproval 2026-09-01-14:05:
227069
+ Self-healing must not treat a stale content-bound approval as mergeable just because the cheap
227070
+ blocker check lacks a fresh Git descriptor. Capture the same merge content the merge queue uses and
227071
+ seed the graph back at Code Review, so cards like FN-9234 stop looping through doomed merge retries.
227072
+ */
227073
+ async routeStaleSingularApprovalBackToReview(task, reviewColumns, settings) {
227074
+ let mergeGate;
227075
+ try {
227076
+ mergeGate = await resolvePreMergeGateForTask(this.store, task.id, task.enabledWorkflowSteps, task);
227077
+ } catch {
227078
+ return false;
227079
+ }
227080
+ if (mergeGate.provenance === "default" && !mergeGate.selectionAbsent) return false;
227081
+ const mergeContent = await captureMergeContentDescriptor(task, {
227082
+ workspaceRootDir: this.options.rootDir,
227083
+ settings
227084
+ });
227085
+ const blocker = getTaskMergeBlocker(task, {
227086
+ reviewColumns,
227087
+ requiredPreMergeStepIds: mergeGate.requiredPreMergeStepIds,
227088
+ mergeContent
227089
+ });
227090
+ if (blocker !== "task has a pre-merge approval recorded against different content" || mergeContent.kind !== "singular") {
227091
+ return false;
227092
+ }
227093
+ const reroute = await rerouteSingularStaleContentToReview(this.store, task, {
227094
+ requiredPreMergeStepIds: mergeGate.requiredPreMergeStepIds,
227095
+ mergeContent
227096
+ }).catch(() => ({
227097
+ rerouted: false,
227098
+ reason: "no-progress",
227099
+ nodeId: void 0,
227100
+ workflowStepId: void 0
227101
+ }));
227102
+ if (reroute.rerouted) {
227103
+ await this.store.logEntry(task.id, "[pre-merge] Self-healing re-seeded Code Review after stale content evidence blocked merge.");
227104
+ log34.warn(`Stale content approval for ${task.id} re-seeded at ${reroute.nodeId ?? "code-review"}`);
227105
+ }
227106
+ const auditKey = `${task.id}:${reroute.reason}:${reroute.nodeId ?? ""}`;
227107
+ if (!this.staleContentRerouteAuditKeys.has(auditKey)) {
227108
+ this.staleContentRerouteAuditKeys.add(auditKey);
227109
+ await emitBoundedRunAudit2(this.store, {
227110
+ taskId: task.id,
227111
+ agentId: "self-healing",
227112
+ runId: generateSyntheticRunId("self-healing", task.id),
227113
+ domain: "database",
227114
+ mutationType: "task:merge-stale-content-review-rerouted",
227115
+ target: task.id,
227116
+ metadata: {
227117
+ taskId: task.id,
227118
+ nodeId: reroute.nodeId,
227119
+ workflowStepId: reroute.workflowStepId,
227120
+ reason: reroute.reason,
227121
+ source: "self-healing"
227122
+ }
227123
+ });
227124
+ }
227125
+ return true;
227126
+ }
226751
227127
  /**
226752
227128
  * Recover `in-review` tasks that are fully mergeable but never had
226753
227129
  * `mergeTask()` invoked.
@@ -226801,6 +227177,7 @@ var SelfHealingManager = class _SelfHealingManager extends SelfHealingGitEvidenc
226801
227177
  for (const task of mergeable) {
226802
227178
  const reviewColumns = await ownReviewLanesFor(task);
226803
227179
  if (!reviewColumns.has(task.column)) continue;
227180
+ if (await this.routeStaleSingularApprovalBackToReview(task, reviewColumns, settings)) continue;
226804
227181
  if (getTaskMergeBlocker(task, { reviewColumns }) !== void 0) continue;
226805
227182
  laneQualifiedMergeable.push(task);
226806
227183
  }
@@ -229367,8 +229744,7 @@ This review is not retried until its findings change. Re-run the review after ad
229367
229744
  }
229368
229745
  async listAiMergeWorktreeCandidates(taskId, settings) {
229369
229746
  const roots = Array.from(/* @__PURE__ */ new Set([
229370
- resolveRepoLocalAiMergeRoot(this.options.rootDir, settings),
229371
- resolveLegacyAiMergeRootPath(this.options.rootDir),
229747
+ ...resolveAiMergeSearchRoots(this.options.rootDir, settings),
229372
229748
  tmpdir8()
229373
229749
  ]));
229374
229750
  const testWorkerRoot = process.env.FUSION_TEST_WORKER_ROOT;
@@ -231902,9 +232278,7 @@ This review is not retried until its findings change. Re-run the review after ad
231902
232278
  log34.debug("[self-healing] temp-dir sweep: worktrunk enabled \u2014 AI merge clean-room worktrees use Fusion's dedicated clean-room root, proceeding with native sweep");
231903
232279
  }
231904
232280
  const roots = Array.from(/* @__PURE__ */ new Set([
231905
- resolveRepoLocalAiMergeRoot(this.options.rootDir, settings),
231906
- resolveLegacyAiMergeRootPath(this.options.rootDir),
231907
- ...settings.worktreesDir ? [] : [join81(resolveLegacyWorktreesDirLayout(this.options.rootDir), ".ai-merge")],
232281
+ ...resolveAiMergeSearchRoots(this.options.rootDir, settings),
231908
232282
  tmpdir8()
231909
232283
  ]));
231910
232284
  const auditor = createRunAuditor(this.store, {
@@ -234559,7 +234933,7 @@ ${recommendation.description}`)) {
234559
234933
  // ../engine/src/executor/execute-workflow-step.ts
234560
234934
  init_src2();
234561
234935
  import { exec as exec41 } from "node:child_process";
234562
- import { promisify as promisify58 } from "node:util";
234936
+ import { promisify as promisify59 } from "node:util";
234563
234937
 
234564
234938
  // ../engine/src/executor/shared-worker-tools.ts
234565
234939
  init_agent_tools();
@@ -236418,6 +236792,47 @@ function isWorkflowStepSkillDiscoverable(skillName, additionalSkillPaths, ceSkil
236418
236792
  );
236419
236793
  }
236420
236794
 
236795
+ // ../engine/src/worktree/review-inline-fix-recapture.ts
236796
+ import { execFile as execFile29 } from "node:child_process";
236797
+ import { promisify as promisify58 } from "node:util";
236798
+ var execFileAsync23 = promisify58(execFile29);
236799
+ function classifyReviewInlineFixRecapture(input) {
236800
+ if (input.reviewKind === "plan") return { recapture: false, reason: "plan-domain" };
236801
+ if (input.verdict !== "APPROVE" && input.verdict !== "APPROVE_WITH_NOTES") {
236802
+ return { recapture: false, reason: "not-approval-verdict" };
236803
+ }
236804
+ if (!input.reviewedCommitSha || !input.currentHeadSha || !input.baseRef) {
236805
+ return { recapture: false, reason: "missing-anchor" };
236806
+ }
236807
+ if (input.reviewedCommitSha === input.currentHeadSha) return { recapture: false, reason: "head-unchanged" };
236808
+ if (input.fastForwardAdvance === void 0 || input.baseIsAncestor === void 0) {
236809
+ return { recapture: false, reason: "probe-unavailable" };
236810
+ }
236811
+ if (!input.fastForwardAdvance) return { recapture: false, reason: "history-rewritten" };
236812
+ if (!input.baseIsAncestor) return { recapture: false, reason: "base-not-ancestor" };
236813
+ if (!input.fingerprintProbeAvailable) return { recapture: false, reason: "probe-unavailable" };
236814
+ return { recapture: true, reason: "recaptured" };
236815
+ }
236816
+ async function readHeadSha(worktreePath) {
236817
+ if (!worktreePath) return void 0;
236818
+ try {
236819
+ const { stdout } = await execFileAsync23("git", ["rev-parse", "HEAD"], { cwd: worktreePath, encoding: "utf8" });
236820
+ return stdout.trim() || void 0;
236821
+ } catch {
236822
+ return void 0;
236823
+ }
236824
+ }
236825
+ async function isFastForwardAdvance(worktreePath, fromSha, toSha) {
236826
+ if (!worktreePath || !fromSha || !toSha) return void 0;
236827
+ try {
236828
+ await execFileAsync23("git", ["merge-base", "--is-ancestor", fromSha, toSha], { cwd: worktreePath, encoding: "utf8" });
236829
+ return true;
236830
+ } catch (error2) {
236831
+ const code = error2?.code;
236832
+ return code === 1 ? false : void 0;
236833
+ }
236834
+ }
236835
+
236421
236836
  // ../engine/src/executor/optional-step-revision.ts
236422
236837
  init_src2();
236423
236838
 
@@ -236575,7 +236990,7 @@ function buildGraphPlanReviewConvergenceContext(task, revisionKey) {
236575
236990
 
236576
236991
  // ../engine/src/executor/execute-workflow-step.ts
236577
236992
  init_agent_usage_telemetry();
236578
- var execAsync38 = promisify58(exec41);
236993
+ var execAsync38 = promisify59(exec41);
236579
236994
  var WORKFLOW_STEP_NOTES_REPAIR_TIMEOUT_MS = 12e4;
236580
236995
  function findReusableReviewResult(task, workflowStepId, reviewInputFingerprint, repositoryScopeRevision) {
236581
236996
  if (!reviewInputFingerprint) return void 0;
@@ -236614,6 +237029,8 @@ async function executeWorkflowStep(deps, task, workflowStep, worktreePath, setti
236614
237029
  const unattended = stepOptions?.unattended === true;
236615
237030
  const workflowStepMetadata = workflowStep;
236616
237031
  const optionalGroupId = workflowStepMetadata.optionalGroupId;
237032
+ const effectiveWorkflowStepId = optionalGroupId ?? workflowStep.id.replace(/^graph:/, "");
237033
+ const isContentBindingStep = requiresContentReviewProof(effectiveWorkflowStepId, workflowStepMetadata);
236617
237034
  const isPlanReviewStep = workflowStep.id === "graph:plan-review-step" || workflowStep.name === "Plan Review" || optionalGroupId === PLAN_REVIEW_GROUP_ID;
236618
237035
  const planReviewRevisionKey = optionalStepRevisionKey2(optionalGroupId, workflowStep.name);
236619
237036
  const isReviewTypeWorkflowStep = isPlanReviewStep || workflowStepMetadata.reviewCanFixInline === true || /(?:^|\b)(?:review|verification)(?:\b|$)/i.test(workflowStep.name) || optionalGroupId === "plan-review" || optionalGroupId === "code-review" || optionalGroupId === "browser-verification";
@@ -236624,6 +237041,8 @@ async function executeWorkflowStep(deps, task, workflowStep, worktreePath, setti
236624
237041
  toolMode = "coding";
236625
237042
  }
236626
237043
  const requireExternalIntegrationEvidence = workflowStepMetadata.requireExternalIntegrationEvidence === true;
237044
+ const readonlyMcpServerAllowlist = toolMode === "readonly" ? [...new Set((workflowStepMetadata.readonlyMcpServers ?? []).map((name2) => name2.trim()).filter(Boolean))] : [];
237045
+ const allowReadonlyMcpTools = toolMode === "readonly" && readonlyMcpServerAllowlist.length > 0;
236627
237046
  let workflowReviewSpecArtifact;
236628
237047
  if (isReviewTypeWorkflowStep) {
236629
237048
  try {
@@ -236642,7 +237061,7 @@ async function executeWorkflowStep(deps, task, workflowStep, worktreePath, setti
236642
237061
  const workflowReviewSpecText = typeof workflowReviewSpecArtifact === "string" ? workflowReviewSpecArtifact : "";
236643
237062
  const planReviewSpecText = isPlanReviewStep ? workflowReviewSpecText : "";
236644
237063
  const latestTaskForUserComments = await deps.store.getTask(task.id).catch(() => task);
236645
- const sameGateStepId = workflowStep.id.replace(/^graph:/, "");
237064
+ const sameGateStepId = effectiveWorkflowStepId;
236646
237065
  const planReviewConvergenceContext = isPlanReviewStep ? buildGraphPlanReviewConvergenceContext(latestTaskForUserComments, planReviewRevisionKey) : "";
236647
237066
  const planReviewEnvironmentCapabilities = isPlanReviewStep ? await probeEnvironmentCapabilities({
236648
237067
  extraCommands: [
@@ -236690,21 +237109,44 @@ async function executeWorkflowStep(deps, task, workflowStep, worktreePath, setti
236690
237109
  let diffShortstat;
236691
237110
  let reviewInputFingerprint;
236692
237111
  let reviewedCommitSha;
237112
+ let baseRef;
236693
237113
  try {
236694
- const baseRef = await resolveDiffBaseRef(worktreePath, diffBaseCommitSha);
236695
- if (baseRef) {
236696
- const { stdout } = await execAsync38(`git diff --shortstat ${baseRef}..HEAD`, {
236697
- cwd: worktreePath,
236698
- encoding: "utf-8"
236699
- });
236700
- diffShortstat = stdout.trim() || void 0;
237114
+ baseRef = await resolveDiffBaseRef(worktreePath, diffBaseCommitSha);
237115
+ } catch {
237116
+ }
237117
+ if (isContentBindingStep && task.workspaceWorktrees === void 0) {
237118
+ const suppliedFingerprint = stepOptions?.reviewInputFingerprint?.trim();
237119
+ const proof = suppliedFingerprint ? { kind: "fingerprint", fingerprint: suppliedFingerprint } : await resolveContentReviewInputProof(worktreePath, diffBaseCommitSha);
237120
+ if (proof.kind === "unprovable") {
237121
+ const diagnostic = `${workflowStep.name} review input is unprovable (${proof.reason}); reviewer dispatch refused.`;
237122
+ await deps.store.logEntry(
237123
+ task.id,
237124
+ `[pre-merge] ${diagnostic}`,
237125
+ void 0,
237126
+ deps.getRunContextFor(task.id)
237127
+ );
237128
+ return { success: false, error: diagnostic, failureValue: "review-input-unprovable" };
237129
+ }
237130
+ reviewInputFingerprint = proof.fingerprint;
237131
+ } else if (baseRef) {
237132
+ try {
236701
237133
  if (workflowStepMetadata.reviewKind === "code") {
236702
237134
  reviewInputFingerprint = await computeCodeReviewInputFingerprint(worktreePath, baseRef);
236703
237135
  } else if (isReviewTypeWorkflowStep) {
236704
237136
  reviewInputFingerprint = await computeReviewDiffFingerprint(worktreePath, baseRef);
236705
237137
  }
237138
+ } catch {
237139
+ }
237140
+ }
237141
+ if (baseRef) {
237142
+ try {
237143
+ const { stdout } = await execAsync38(`git diff --shortstat ${baseRef}..HEAD`, {
237144
+ cwd: worktreePath,
237145
+ encoding: "utf-8"
237146
+ });
237147
+ diffShortstat = stdout.trim() || void 0;
237148
+ } catch {
236706
237149
  }
236707
- } catch {
236708
237150
  }
236709
237151
  if (isReviewTypeWorkflowStep) {
236710
237152
  try {
@@ -237014,6 +237456,12 @@ ${workflowStepUserCommentSection}` : ""}${priorFindingsBlock}
237014
237456
  const workflowStepThinkingSource = workflowStep.thinkingLevel ?? (isReviewTypeWorkflowStep ? task.validatorThinkingLevel ?? task.thinkingLevel : task.thinkingLevel);
237015
237457
  const workflowStepThinkingLevel = attemptLabel === "fallback" ? isReviewTypeWorkflowStep ? resolveValidatorFallbackThinkingLevel(workflowStepThinkingSource, settings) : resolveExecutorFallbackThinkingLevel(workflowStepThinkingSource, settings) : isReviewTypeWorkflowStep ? resolveValidatorThinkingLevel(workflowStepThinkingSource, settings) : resolveExecutorThinkingLevel(workflowStepThinkingSource, settings);
237016
237458
  const workflowStepFallbackThinkingLevel = isReviewTypeWorkflowStep ? resolveValidatorFallbackThinkingLevel(workflowStepThinkingSource, settings) : resolveExecutorFallbackThinkingLevel(workflowStepThinkingSource, settings);
237459
+ if (allowReadonlyMcpTools) {
237460
+ await deps.store.logEntry(
237461
+ task.id,
237462
+ `Workflow step '${workflowStep.name}' enabled read-only MCP servers: ${readonlyMcpServerAllowlist.join(", ")}`
237463
+ );
237464
+ }
237017
237465
  const { session } = await createResolvedAgentSession({
237018
237466
  sessionPurpose: "executor",
237019
237467
  taskExecutionSession: true,
@@ -237034,6 +237482,10 @@ ${workflowStepUserCommentSection}` : ""}${priorFindingsBlock}
237034
237482
  settings,
237035
237483
  taskEnv: stepEnv,
237036
237484
  mcpServers: await deps.resolveMcpServers(void 0),
237485
+ ...allowReadonlyMcpTools ? {
237486
+ allowMcpToolsInReadonly: true,
237487
+ readonlyMcpServerAllowlist
237488
+ } : {},
237037
237489
  // FNXC:SessionRouting 2026-06-24-11:20:
237038
237490
  // #1675: propagate task id so workflow-step requests carry the same
237039
237491
  // X-Session-Id/X-Session-Affinity as the primary session.
@@ -237249,6 +237701,60 @@ ${detectedQuestion}
237249
237701
  });
237250
237702
  }
237251
237703
  const revisionRequested = effectiveVerdict === "REVISE";
237704
+ if (isReviewTypeWorkflowStep && !isPlanReviewStep && !revisionRequested) {
237705
+ const priorReviewedCommitSha = reviewedCommitSha;
237706
+ const currentHeadSha = await readHeadSha(worktreePath);
237707
+ const fastForwardAdvance = await isFastForwardAdvance(worktreePath, priorReviewedCommitSha, currentHeadSha);
237708
+ const baseIsAncestor = await isFastForwardAdvance(worktreePath, baseRef, currentHeadSha);
237709
+ const decision = classifyReviewInlineFixRecapture({
237710
+ verdict: effectiveVerdict,
237711
+ reviewKind: workflowStepMetadata.reviewKind,
237712
+ reviewedCommitSha: priorReviewedCommitSha,
237713
+ currentHeadSha,
237714
+ baseRef,
237715
+ fastForwardAdvance,
237716
+ baseIsAncestor,
237717
+ fingerprintProbeAvailable: true
237718
+ });
237719
+ if (decision.recapture) {
237720
+ try {
237721
+ const recapturedFingerprint = workflowStepMetadata.reviewKind === "code" ? await computeCodeReviewInputFingerprint(worktreePath, baseRef) : await computeReviewDiffFingerprint(worktreePath, baseRef);
237722
+ const finalDecision = classifyReviewInlineFixRecapture({
237723
+ verdict: effectiveVerdict,
237724
+ reviewKind: workflowStepMetadata.reviewKind,
237725
+ reviewedCommitSha: priorReviewedCommitSha,
237726
+ currentHeadSha,
237727
+ baseRef,
237728
+ fastForwardAdvance,
237729
+ baseIsAncestor,
237730
+ fingerprintProbeAvailable: recapturedFingerprint !== void 0
237731
+ });
237732
+ if (finalDecision.recapture && currentHeadSha) {
237733
+ reviewInputFingerprint = recapturedFingerprint;
237734
+ reviewedCommitSha = currentHeadSha;
237735
+ const resolvedInReviewFindingCount = parsed.findings?.filter((finding) => finding.resolution === "resolved-in-review").length ?? 0;
237736
+ await deps.store.logEntry(task.id, `[pre-merge] ${workflowStep.name} re-captured its own review identity after fast-forward ${priorReviewedCommitSha?.slice(0, 7)} \u2192 ${currentHeadSha.slice(0, 7)} (${finalDecision.reason})`);
237737
+ const runContext = deps.getRunContextFor(task.id);
237738
+ if (runContext) await emitBoundedRunAudit2(deps.store, {
237739
+ taskId: task.id,
237740
+ agentId: runContext.agentId,
237741
+ runId: runContext.runId,
237742
+ domain: "git",
237743
+ mutationType: "task:review-input-recaptured",
237744
+ target: task.id,
237745
+ metadata: {
237746
+ taskId: task.id,
237747
+ workflowStepId: workflowStep.id,
237748
+ verdict: effectiveVerdict,
237749
+ resolvedInReviewFindingCount,
237750
+ reason: finalDecision.reason
237751
+ }
237752
+ });
237753
+ }
237754
+ } catch {
237755
+ }
237756
+ }
237757
+ }
237252
237758
  if (workflowStep.requiresBrowser === true) {
237253
237759
  await logBrowserVerificationActivity(`[browser-verification] finished browser verification for task ${task.id}: verdict ${effectiveVerdict}`);
237254
237760
  }
@@ -237953,10 +238459,10 @@ init_branch_conflicts();
237953
238459
  init_integration_branch();
237954
238460
  init_logger();
237955
238461
  import { exec as exec42 } from "node:child_process";
237956
- import { promisify as promisify59 } from "node:util";
238462
+ import { promisify as promisify60 } from "node:util";
237957
238463
  import { existsSync as existsSync66 } from "node:fs";
237958
238464
  import { rm as rm11 } from "node:fs/promises";
237959
- var execAsync39 = promisify59(exec42);
238465
+ var execAsync39 = promisify60(exec42);
237960
238466
  async function tryCreateWorktree(deps, branch, path6, taskId, startPoint, attemptNumber = 0, recoveryDepth = 0, allowSiblingBranchRename = false, settings = {}) {
237961
238467
  await assertWorktreePathNotNested(deps.rootDir, deps.store, path6, taskId);
237962
238468
  const installGuardOrCleanup = async () => {
@@ -238244,10 +238750,10 @@ init_src2();
238244
238750
  init_worktree_pool();
238245
238751
  init_logger();
238246
238752
  import { exec as exec43 } from "node:child_process";
238247
- import { promisify as promisify60 } from "node:util";
238753
+ import { promisify as promisify61 } from "node:util";
238248
238754
  import { existsSync as existsSync67, lstatSync as lstatSync3, realpathSync as realpathSync11 } from "node:fs";
238249
238755
  import { rm as rm12 } from "node:fs/promises";
238250
- var execAsync40 = promisify60(exec43);
238756
+ var execAsync40 = promisify61(exec43);
238251
238757
  async function cleanupConflictingWorktree(deps, worktreePath, branch, taskId) {
238252
238758
  await deps.reconcileSelfOwnedBeforeRemove(worktreePath, taskId);
238253
238759
  const activeOwner = await deps.findActiveWorktreeOwner(worktreePath, taskId);
@@ -238364,10 +238870,10 @@ init_worktree_stale_lock();
238364
238870
  init_integration_branch();
238365
238871
  init_logger();
238366
238872
  import { exec as exec44 } from "node:child_process";
238367
- import { promisify as promisify61 } from "node:util";
238873
+ import { promisify as promisify62 } from "node:util";
238368
238874
  import { existsSync as existsSync68 } from "node:fs";
238369
238875
  import { isAbsolute as isAbsolute22 } from "node:path";
238370
- var execAsync41 = promisify61(exec44);
238876
+ var execAsync41 = promisify62(exec44);
238371
238877
  async function resolveWorktreeStartPoint(rootDir, store, startPoint, taskId) {
238372
238878
  const command = isAbsolute22(startPoint) && existsSync68(startPoint) ? `git -C "${startPoint}" rev-parse --verify HEAD^{commit}` : `git rev-parse --verify "${startPoint}^{commit}"`;
238373
238879
  try {
@@ -238569,8 +239075,8 @@ init_effective_settings();
238569
239075
  init_worktree_pinning();
238570
239076
  init_logger();
238571
239077
  import { exec as exec45 } from "node:child_process";
238572
- import { promisify as promisify62 } from "node:util";
238573
- var execAsync42 = promisify62(exec45);
239078
+ import { promisify as promisify63 } from "node:util";
239079
+ var execAsync42 = promisify63(exec45);
238574
239080
  async function reclaimExistingWorktree(deps, task, livePath, branch, tipSha, count2, settings) {
238575
239081
  const targetPath = preservedWorktreeTargetPathForTask(task.id, livePath, settings, deps.rootDir);
238576
239082
  const normalizedPath = await deps.normalizeReclaimableWorktreePath(livePath, targetPath, task.id, settings);
@@ -238803,7 +239309,7 @@ init_worktree_names();
238803
239309
  init_logger();
238804
239310
  init_run_audit();
238805
239311
  import { exec as exec47 } from "node:child_process";
238806
- import { promisify as promisify65 } from "node:util";
239312
+ import { promisify as promisify66 } from "node:util";
238807
239313
  import { existsSync as existsSync71 } from "node:fs";
238808
239314
 
238809
239315
  // ../engine/src/executor/session-worktree-paths.ts
@@ -238931,11 +239437,11 @@ function classifyWorkspaceZeroAcquire(task, options) {
238931
239437
  }
238932
239438
 
238933
239439
  // ../engine/src/execution/external-execution-checkout.ts
238934
- import { execFile as execFile29 } from "node:child_process";
239440
+ import { execFile as execFile30 } from "node:child_process";
238935
239441
  import { existsSync as existsSync69, realpathSync as realpathSync13, statSync as statSync14 } from "node:fs";
238936
239442
  import { isAbsolute as isAbsolute23 } from "node:path";
238937
- import { promisify as promisify63 } from "node:util";
238938
- var execFileAsync23 = promisify63(execFile29);
239443
+ import { promisify as promisify64 } from "node:util";
239444
+ var execFileAsync24 = promisify64(execFile30);
238939
239445
  function readSourceMetadata(task) {
238940
239446
  if (!task || typeof task !== "object") return void 0;
238941
239447
  const sourceMetadata = task.sourceMetadata;
@@ -238954,7 +239460,7 @@ async function inspectExternalGitCheckout(candidate, options = {}) {
238954
239460
  return { valid: false, reason: `checkoutPath is not a directory: ${checkoutPath}` };
238955
239461
  }
238956
239462
  const canonicalCheckout = realpathSync13(checkoutPath);
238957
- const { stdout: topLevelOutput } = await execFileAsync23("git", ["rev-parse", "--show-toplevel"], {
239463
+ const { stdout: topLevelOutput } = await execFileAsync24("git", ["rev-parse", "--show-toplevel"], {
238958
239464
  cwd: canonicalCheckout,
238959
239465
  encoding: "utf-8",
238960
239466
  timeout: 1e4
@@ -238967,7 +239473,7 @@ async function inspectExternalGitCheckout(candidate, options = {}) {
238967
239473
  reason: `checkoutPath must be the Git top-level (observed ${canonicalTopLevel})`
238968
239474
  };
238969
239475
  }
238970
- const { stdout: branchOutput } = await execFileAsync23("git", ["symbolic-ref", "--quiet", "--short", "HEAD"], {
239476
+ const { stdout: branchOutput } = await execFileAsync24("git", ["symbolic-ref", "--quiet", "--short", "HEAD"], {
238971
239477
  cwd: canonicalCheckout,
238972
239478
  encoding: "utf-8",
238973
239479
  timeout: 1e4
@@ -238977,7 +239483,7 @@ async function inspectExternalGitCheckout(candidate, options = {}) {
238977
239483
  return { valid: false, reason: "checkoutPath must have a checked-out branch" };
238978
239484
  }
238979
239485
  if (options.requireClean) {
238980
- const { stdout: statusOutput } = await execFileAsync23("git", ["status", "--porcelain=v1"], {
239486
+ const { stdout: statusOutput } = await execFileAsync24("git", ["status", "--porcelain=v1"], {
238981
239487
  cwd: canonicalCheckout,
238982
239488
  encoding: "utf-8",
238983
239489
  timeout: 1e4
@@ -239040,9 +239546,9 @@ async function resolveAuthoritativeExternalExecutionRoute(store, task) {
239040
239546
  import { exec as exec46 } from "node:child_process";
239041
239547
  import { existsSync as existsSync70, promises as fs7 } from "node:fs";
239042
239548
  import path4 from "node:path";
239043
- import { promisify as promisify64 } from "node:util";
239549
+ import { promisify as promisify65 } from "node:util";
239044
239550
  init_worktree_paths();
239045
- var execAsync43 = promisify64(exec46);
239551
+ var execAsync43 = promisify65(exec46);
239046
239552
  var probeOptions = { encoding: "utf-8", timeout: 1e4, maxBuffer: 1024 * 1024 };
239047
239553
  function workspaceExecutionAnchor(task) {
239048
239554
  const executionValues = [task.firstExecutionAt, task.executionStartedAt].map((value) => typeof value === "string" ? Date.parse(value) : Number.NaN).filter(Number.isFinite);
@@ -239162,7 +239668,7 @@ async function detectWorkspaceMainCheckoutWork(deps, task, repos, declaredScope)
239162
239668
  }
239163
239669
 
239164
239670
  // ../engine/src/executor/worktree-verify-invariants.ts
239165
- var execAsync44 = promisify65(exec47);
239671
+ var execAsync44 = promisify66(exec47);
239166
239672
  async function verifyWorktreeInvariants(deps, task, worktreePathOverride, allowReanchor = true, options) {
239167
239673
  const workspaceConfig = deps.ensureWorkspaceConfig ? await deps.ensureWorkspaceConfig() : deps.workspaceConfig;
239168
239674
  const settings = await deps.store.getSettings();
@@ -239567,14 +240073,14 @@ async function emitWorktreeReanchoredAudit(deps, taskId, fromPath, toPath, sourc
239567
240073
  }
239568
240074
 
239569
240075
  // ../engine/src/executor/worktree-task-done-scope-leak.ts
239570
- import { execFile as execFile30 } from "node:child_process";
240076
+ import { execFile as execFile31 } from "node:child_process";
239571
240077
  import { access as access9 } from "node:fs/promises";
239572
- import { promisify as promisify66 } from "node:util";
240078
+ import { promisify as promisify67 } from "node:util";
239573
240079
  init_logger();
239574
- var execFileAsync24 = promisify66(execFile30);
240080
+ var execFileAsync25 = promisify67(execFile31);
239575
240081
  async function verifyRepositoryCaptureEvidence(worktreePath) {
239576
240082
  await access9(worktreePath);
239577
- const { stdout } = await execFileAsync24("git", ["rev-parse", "--is-inside-work-tree"], {
240083
+ const { stdout } = await execFileAsync25("git", ["rev-parse", "--is-inside-work-tree"], {
239578
240084
  cwd: worktreePath,
239579
240085
  encoding: "utf8"
239580
240086
  });
@@ -239591,7 +240097,7 @@ async function captureRepositoryChangeEvidence(worktreePath, baseCommitSha) {
239591
240097
  } else {
239592
240098
  commands.push(["rev-parse", "--verify", "HEAD"]);
239593
240099
  }
239594
- const results = await Promise.all(commands.map((args) => execFileAsync24("git", args, {
240100
+ const results = await Promise.all(commands.map((args) => execFileAsync25("git", args, {
239595
240101
  cwd: worktreePath,
239596
240102
  encoding: "utf8"
239597
240103
  })));
@@ -239725,8 +240231,8 @@ async function evaluateTaskDoneScopeLeak(deps, task, worktreePath, promptContent
239725
240231
  init_branch_attribution();
239726
240232
  init_logger();
239727
240233
  import { exec as exec48 } from "node:child_process";
239728
- import { promisify as promisify67 } from "node:util";
239729
- var execAsync45 = promisify67(exec48);
240234
+ import { promisify as promisify68 } from "node:util";
240235
+ var execAsync45 = promisify68(exec48);
239730
240236
  async function captureModifiedFiles(worktreePath, baseCommitSha, taskId, audit, source = "unspecified") {
239731
240237
  try {
239732
240238
  const baseRef = await resolveDiffBaseRef(worktreePath, baseCommitSha);
@@ -240024,6 +240530,27 @@ ${result.review}`);
240024
240530
  summarySections.push(`[${repoRel}] ${result.verdict}: ${result.summary}`);
240025
240531
  }
240026
240532
  const aggregateVerdict = reviewedResults.some(({ result }) => result.verdict === "RETHINK") ? "RETHINK" : reviewedResults.some(({ result }) => result.verdict === "REVISE") ? "REVISE" : reviewedResults.some(({ result }) => !isApprovalFamilyVerdict(result.verdict)) ? "UNAVAILABLE" : "APPROVE";
240533
+ let publishedModifiedFiles = modifiedFiles;
240534
+ if (isApprovalFamilyVerdict(aggregateVerdict) && evidence && options.workspaceRootDir) {
240535
+ try {
240536
+ const refreshed = await captureWorkspaceReviewEvidence({ task, workspaceRootDir: options.workspaceRootDir, settings: options.settings ?? {} });
240537
+ if (refreshed.outOfScopeRepositories.size > 0) {
240538
+ return { verdict: "UNAVAILABLE", retryable: false, review: "Workspace Code Review cannot approve changes outside confirmed scope after review.", summary: "Unavailable: changes outside confirmed scope after review", repositoryScopeRevision };
240539
+ }
240540
+ for (const updated of refreshed.repositories) {
240541
+ const original = evidence.repositories.find((candidate) => candidate.repository === updated.repository);
240542
+ const worktreePath = workspaceWorktrees[updated.repository]?.worktreePath;
240543
+ const outcome = repositoryReviewOutcomes.find((candidate) => candidate.repository === updated.repository && candidate.status === "REVIEWED");
240544
+ if (!original || !worktreePath || !updated.fingerprint || !outcome || !repositoryScope.has(updated.repository)) continue;
240545
+ if (await isFastForwardAdvance(worktreePath, original.branch, updated.branch)) {
240546
+ repositoryDiffFingerprints[updated.repository] = updated.fingerprint;
240547
+ outcome.fingerprint = updated.fingerprint;
240548
+ }
240549
+ }
240550
+ publishedModifiedFiles = refreshed.modifiedFiles;
240551
+ } catch {
240552
+ }
240553
+ }
240027
240554
  const blockingRepositories = reviewedResults.filter(({ result }) => !isApprovalFamilyVerdict(result.verdict)).map(({ repository }) => repository);
240028
240555
  const blockingSummary = blockingRepositories.length > 0 ? `blocking repositories: ${blockingRepositories.join(", ")}` : `all ${repoKeys.length} modified in-scope repositories approved`;
240029
240556
  const coverageNotice = noVerdictRepositories.length > 0 ? `
@@ -240035,7 +240562,7 @@ Not covered by a verdict: ${noVerdictRepositories.join(", ")}.` : "";
240035
240562
  ${reviewSections.join("\n\n")}`,
240036
240563
  summary: `${aggregateVerdict} \u2014 ${blockingSummary} \u2014 ${summarySections.join(" | ")}`,
240037
240564
  repositoryDiffFingerprints,
240038
- repositoryModifiedFiles: modifiedFiles,
240565
+ repositoryModifiedFiles: publishedModifiedFiles,
240039
240566
  repositoryReviewOutcomes,
240040
240567
  ...findings.length > 0 ? { findings } : {},
240041
240568
  repositoryScopeRevision
@@ -240357,9 +240884,9 @@ init_worktree_names();
240357
240884
  init_worktree_pool();
240358
240885
  init_logger();
240359
240886
  import { exec as exec49 } from "node:child_process";
240360
- import { promisify as promisify68 } from "node:util";
240887
+ import { promisify as promisify69 } from "node:util";
240361
240888
  init_lifecycle_columns();
240362
- var execAsync46 = promisify68(exec49);
240889
+ var execAsync46 = promisify69(exec49);
240363
240890
  async function handleDepAbortCleanup(deps, taskId, worktreePath) {
240364
240891
  executorLog.log(`${taskId} dependency added \u2014 work discarded, moved to triage for re-planning`);
240365
240892
  const task = await deps.store.getTask(taskId);
@@ -240935,8 +241462,8 @@ async function advanceNoMergeWorkflowToCompleteColumn(store, task) {
240935
241462
  init_logger();
240936
241463
  import { exec as exec50 } from "node:child_process";
240937
241464
  import { existsSync as existsSync73, statSync as statSync15 } from "node:fs";
240938
- import { promisify as promisify69 } from "node:util";
240939
- var execAsync47 = promisify69(exec50);
241465
+ import { promisify as promisify70 } from "node:util";
241466
+ var execAsync47 = promisify70(exec50);
240940
241467
  async function runTaskStep(deps, task, stepIndex, opts = {}) {
240941
241468
  const { store, worktreePath } = deps;
240942
241469
  const gitRevParse = deps.gitRevParse ?? defaultGitRevParse;
@@ -243022,8 +243549,8 @@ async function recoverFailedPreMergeWorkflowStep(deps, task) {
243022
243549
  init_src2();
243023
243550
  init_logger();
243024
243551
  import { exec as exec51 } from "node:child_process";
243025
- import { promisify as promisify70 } from "node:util";
243026
- var execAsync48 = promisify70(exec51);
243552
+ import { promisify as promisify71 } from "node:util";
243553
+ var execAsync48 = promisify71(exec51);
243027
243554
  async function reconcileStepsFromGitHistory(deps, taskId, detail, worktreePath) {
243028
243555
  const baseCommitSha = detail.baseCommitSha;
243029
243556
  if (!baseCommitSha) return;
@@ -245347,7 +245874,14 @@ async function persistWorkflowStepResultWithOutcome(deps, taskId, result, fence
245347
245874
  scopeSuperseded = true;
245348
245875
  return null;
245349
245876
  }
245350
- const built = buildWorkflowStepResultPatch(current, result, isPlanReviewResult);
245877
+ const unprovenApproval = resolveUnprovenReviewApproval(result, {
245878
+ workspace: current.workspaceWorktrees !== void 0
245879
+ });
245880
+ const built = buildWorkflowStepResultPatch(
245881
+ current,
245882
+ unprovenApproval?.downgraded ?? result,
245883
+ isPlanReviewResult
245884
+ );
245351
245885
  activityResult = built.resultToPersist;
245352
245886
  activityResults = built.results;
245353
245887
  return built.patch;
@@ -245384,21 +245918,30 @@ async function persistWorkflowStepResultWithOutcome(deps, taskId, result, fence
245384
245918
  if (scopeSuperseded) return { scopeCurrent: false, persisted: false };
245385
245919
  if (fenceRefused) return { scopeCurrent: true, persisted: false };
245386
245920
  const persistedResult = activityResults?.find((entry) => entry.workflowStepId === result.workflowStepId) ?? activityResult;
245387
- if (isTerminalStepResult(result)) {
245388
- const passed = result.status === "passed" || result.status === "skipped" || result.verdict === "APPROVE" || result.verdict === "APPROVE_WITH_NOTES" || result.verdict === "CLOSE_NO_OP";
245921
+ const approvalDowngraded = result.status === "passed" && persistedResult.status === "failed" && result.reviewInputFingerprint === void 0 && persistedResult.verdict === void 0;
245922
+ if (approvalDowngraded) {
245923
+ await deps.store.logEntry(
245924
+ taskId,
245925
+ `[pre-merge] ${result.workflowStepName} approval invalidated: ${persistedResult.notes ?? persistedResult.output ?? "review input proof missing"}`,
245926
+ void 0,
245927
+ deps.getRunContextFor(taskId)
245928
+ ).catch(() => void 0);
245929
+ }
245930
+ if (isTerminalStepResult(persistedResult)) {
245931
+ const passed = persistedResult.status === "passed" || persistedResult.status === "skipped" || persistedResult.verdict === "APPROVE" || persistedResult.verdict === "APPROVE_WITH_NOTES" || persistedResult.verdict === "CLOSE_NO_OP";
245389
245932
  try {
245390
245933
  await deps.store.recordAgentActivity({
245391
245934
  type: passed ? "workflow:gate-passed" : "workflow:gate-failed",
245392
245935
  attributionClaim: resolveWorkflowGateActivityClaim(
245393
- deps.workflowGateActivityPrincipals?.get(`${taskId}\0${result.workflowStepId}`) ?? deps.activeWorkflowPrincipals?.get(taskId)?.agentId,
245936
+ deps.workflowGateActivityPrincipals?.get(`${taskId}\0${persistedResult.workflowStepId}`) ?? deps.activeWorkflowPrincipals?.get(taskId)?.agentId,
245394
245937
  live?.assignedAgentId
245395
245938
  ),
245396
245939
  taskId,
245397
- occurredAt: result.completedAt ?? result.startedAt ?? (/* @__PURE__ */ new Date()).toISOString(),
245398
- discriminator: `${result.workflowStepId}:${result.startedAt ?? result.completedAt ?? result.status}`,
245399
- metadata: buildWorkflowGateActivityMetadata(result, persistedResult)
245940
+ occurredAt: persistedResult.completedAt ?? persistedResult.startedAt ?? (/* @__PURE__ */ new Date()).toISOString(),
245941
+ discriminator: `${persistedResult.workflowStepId}:${persistedResult.startedAt ?? persistedResult.completedAt ?? persistedResult.status}`,
245942
+ metadata: buildWorkflowGateActivityMetadata(persistedResult, persistedResult)
245400
245943
  });
245401
- deps.workflowGateActivityPrincipals?.delete(`${taskId}\0${result.workflowStepId}`);
245944
+ deps.workflowGateActivityPrincipals?.delete(`${taskId}\0${persistedResult.workflowStepId}`);
245402
245945
  } catch (error2) {
245403
245946
  executorLog.warn(`[agent-activity] ${taskId}: failed to record workflow gate activity: ${error2 instanceof Error ? error2.message : String(error2)}`);
245404
245947
  }
@@ -247323,8 +247866,8 @@ async function runSpawnedChild(deps, agentId, childSession, taskPrompt) {
247323
247866
 
247324
247867
  // ../engine/src/auto-recovery-handlers/file-scope.ts
247325
247868
  import { exec as exec52 } from "node:child_process";
247326
- import { promisify as promisify71 } from "node:util";
247327
- var execAsync49 = promisify71(exec52);
247869
+ import { promisify as promisify72 } from "node:util";
247870
+ var execAsync49 = promisify72(exec52);
247328
247871
  var FileScopeAutoRecoveryHandler = class {
247329
247872
  constructor(deps) {
247330
247873
  this.deps = deps;
@@ -247349,8 +247892,8 @@ init_integration_branch();
247349
247892
  init_logger();
247350
247893
  import { exec as exec53 } from "node:child_process";
247351
247894
  import { existsSync as existsSync76 } from "node:fs";
247352
- import { promisify as promisify72 } from "node:util";
247353
- var execAsync50 = promisify72(exec53);
247895
+ import { promisify as promisify73 } from "node:util";
247896
+ var execAsync50 = promisify73(exec53);
247354
247897
  var baseLog2 = createLogger("auto-recovery:branch-worktree");
247355
247898
  var GIT_TIMEOUT_MS8 = 3e4;
247356
247899
  var GIT_MAX_BUFFER6 = 10 * 1024 * 1024;
@@ -248340,8 +248883,8 @@ init_src2();
248340
248883
  init_worktree_names();
248341
248884
  init_logger();
248342
248885
  import { exec as exec54 } from "node:child_process";
248343
- import { promisify as promisify73 } from "node:util";
248344
- var execAsync51 = promisify73(exec54);
248886
+ import { promisify as promisify74 } from "node:util";
248887
+ var execAsync51 = promisify74(exec54);
248345
248888
  async function resetStepsIfWorkLost(deps, task) {
248346
248889
  const completedSteps = task.steps.filter(
248347
248890
  (s3) => s3.status === "done" || s3.status === "in-progress"
@@ -248416,8 +248959,8 @@ init_worktree_names();
248416
248959
  init_worktree_paths();
248417
248960
  init_logger();
248418
248961
  import { exec as exec55 } from "node:child_process";
248419
- import { promisify as promisify74 } from "node:util";
248420
- var execAsync52 = promisify74(exec55);
248962
+ import { promisify as promisify75 } from "node:util";
248963
+ var execAsync52 = promisify75(exec55);
248421
248964
  var WORKSPACE_ISOLATION_UNSUPPORTED_MESSAGE = "per-instance worktree isolation is not supported for workspace projects";
248422
248965
  function buildForeachWorktreeDeps(deps, task, runId) {
248423
248966
  const taskId = task.id;
@@ -248610,7 +249153,7 @@ init_src2();
248610
249153
  // ../engine/src/executor/derive-remediation-steps.ts
248611
249154
  init_src2();
248612
249155
  init_merger_file_scope();
248613
- import { createHash as createHash34 } from "node:crypto";
249156
+ import { createHash as createHash35 } from "node:crypto";
248614
249157
  var fileReference = /(?:^|[\s(])([\w@./-]+\.(?:[cm]?[jt]sx?|json|md|css|html|yml|yaml))(?::(\d+))?/gm;
248615
249158
  var ansiEscapeSequence = /\u001B\[[0-?]*[ -/]*[@-~]/g;
248616
249159
  var isoTimestamp = /\b\d{4}-\d{2}-\d{2}[T ]\d{2}:\d{2}:\d{2}(?:\.\d+)?(?:Z|[+-]\d{2}:?\d{2})?\b/g;
@@ -248623,7 +249166,7 @@ function normalizeVerificationEvidence(output) {
248623
249166
  }
248624
249167
  function verificationEvidenceDigest(output) {
248625
249168
  const evidence = normalizeVerificationEvidence(output);
248626
- return evidence ? createHash34("sha256").update(evidence).digest("hex").slice(0, 16) : void 0;
249169
+ return evidence ? createHash35("sha256").update(evidence).digest("hex").slice(0, 16) : void 0;
248627
249170
  }
248628
249171
  function verificationCandidates(input) {
248629
249172
  const detail = input.verificationCommandLabel ?? "verification command";
@@ -252011,6 +252554,8 @@ async function runGraphCustomNode(deps, node, nodeTask, settings, columnBinding,
252011
252554
  }
252012
252555
  const optionalGroupId = typeof graphContext?.[WORKFLOW_OPTIONAL_GROUP_CONTEXT_KEY] === "string" ? graphContext[WORKFLOW_OPTIONAL_GROUP_CONTEXT_KEY] : void 0;
252013
252556
  const declaredReviewKind = cfg.reviewKind === "plan" || cfg.reviewKind === "code" ? cfg.reviewKind : graphContext?.[WORKFLOW_REVIEW_KIND_CONTEXT_KEY] === "plan" || graphContext?.[WORKFLOW_REVIEW_KIND_CONTEXT_KEY] === "code" ? graphContext[WORKFLOW_REVIEW_KIND_CONTEXT_KEY] : void 0;
252557
+ const effectiveStepId = optionalGroupId ?? node.id;
252558
+ const isContentBindingStep = requiresContentReviewProof(effectiveStepId, { reviewKind: declaredReviewKind });
252014
252559
  if (isFastExecutionMode(live) && graphContext?.["workflow:fast-lane-active"] === true && isFastLaneSkippableCustomNode(node, { optionalGroupId })) {
252015
252560
  executorLog.debug(`${live.id}: fast mode \u2014 skipping custom graph node '${node.id}'`);
252016
252561
  await deps.store.logEntry(
@@ -252205,6 +252750,7 @@ ${prompt}`;
252205
252750
  const now = (/* @__PURE__ */ new Date()).toISOString();
252206
252751
  const stepSkillName = executorKind === "skill" && typeof cfg.skillName === "string" && cfg.skillName.trim() ? cfg.skillName.trim() : void 0;
252207
252752
  const stepThinkingLevel = typeof cfg.thinkingLevel === "string" && WORKFLOW_THINKING_LEVEL_SET2.has(cfg.thinkingLevel) ? cfg.thinkingLevel : void 0;
252753
+ const readonlyMcpServers = Array.isArray(cfg.readonlyMcpServers) ? [...new Set(cfg.readonlyMcpServers.filter((name2) => typeof name2 === "string").map((name2) => name2.trim()).filter(Boolean))] : [];
252208
252754
  const step = {
252209
252755
  id: `graph:${node.id}`,
252210
252756
  name: typeof cfg.name === "string" && cfg.name.trim() ? cfg.name : node.id,
@@ -252221,7 +252767,8 @@ ${prompt}`;
252221
252767
  ...stepSkillName ? { skillName: stepSkillName } : {},
252222
252768
  ...cfg.requiresBrowser === true ? { requiresBrowser: true } : {},
252223
252769
  ...modelProvider && modelId ? { modelProvider, modelId } : {},
252224
- ...stepThinkingLevel ? { thinkingLevel: stepThinkingLevel } : {}
252770
+ ...stepThinkingLevel ? { thinkingLevel: stepThinkingLevel } : {},
252771
+ ...readonlyMcpServers.length > 0 ? { readonlyMcpServers } : {}
252225
252772
  };
252226
252773
  if (cfg.summaryTarget === "task") {
252227
252774
  step.summaryTarget = "task";
@@ -252335,12 +252882,36 @@ ${prompt}`;
252335
252882
  outcome = buildWorkspaceReviewOutcome(aggregate, { superseded: reviewSuperseded });
252336
252883
  }
252337
252884
  } else {
252338
- outcome = mode === "script" ? await deps.executeScriptWorkflowStep(live, step, worktreePath, settings, nodeEnv) : await deps.executeWorkflowStep(live, step, worktreePath, settings, nodeEnv, {
252339
- unattended,
252340
- principalAgentId,
252341
- outputLanguage,
252342
- ...nodeSessionBoundary ? { sessionBoundary: nodeSessionBoundary } : {}
252343
- });
252885
+ const dispatchSingularStep = async (reviewInputFingerprint) => {
252886
+ if (mode === "script") {
252887
+ const scriptOutcome = await deps.executeScriptWorkflowStep(live, step, worktreePath, settings, nodeEnv);
252888
+ return reviewInputFingerprint === void 0 ? scriptOutcome : { ...scriptOutcome, reviewInputFingerprint };
252889
+ }
252890
+ return deps.executeWorkflowStep(live, step, worktreePath, settings, nodeEnv, {
252891
+ unattended,
252892
+ principalAgentId,
252893
+ outputLanguage,
252894
+ ...nodeSessionBoundary ? { sessionBoundary: nodeSessionBoundary } : {},
252895
+ ...reviewInputFingerprint !== void 0 ? { reviewInputFingerprint } : {}
252896
+ });
252897
+ };
252898
+ if (isContentBindingStep && !workspaceConfig) {
252899
+ const proof = await resolveContentReviewInputProof(worktreePath, executionTarget.baseCommitSha);
252900
+ if (proof.kind === "unprovable") {
252901
+ const diagnostic = `${step.name} review input is unprovable (${proof.reason}); reviewer dispatch refused.`;
252902
+ await deps.store.logEntry(
252903
+ live.id,
252904
+ `[pre-merge] ${diagnostic}`,
252905
+ void 0,
252906
+ deps.getRunContextFor(live.id)
252907
+ );
252908
+ outcome = { success: false, error: diagnostic, failureValue: "review-input-unprovable" };
252909
+ } else {
252910
+ outcome = await dispatchSingularStep(proof.fingerprint);
252911
+ }
252912
+ } else {
252913
+ outcome = await dispatchSingularStep();
252914
+ }
252344
252915
  }
252345
252916
  if (declaredReviewKind && typeof outcome.output === "string") {
252346
252917
  const rawReviewOutput = outcome.output;
@@ -253328,7 +253899,7 @@ async function runImplementationPhase(deps, task, prepared) {
253328
253899
  // ../engine/src/executor/run-implementation.ts
253329
253900
  init_src2();
253330
253901
  import { exec as exec56 } from "node:child_process";
253331
- import { promisify as promisify76 } from "node:util";
253902
+ import { promisify as promisify77 } from "node:util";
253332
253903
  import { existsSync as existsSync80 } from "node:fs";
253333
253904
  import { join as join91 } from "node:path";
253334
253905
  import { SessionManager as SessionManager2 } from "@earendil-works/pi-coding-agent";
@@ -254203,11 +254774,11 @@ init_logger();
254203
254774
  // ../engine/src/merge/merger-orphan-rehome.ts
254204
254775
  init_src2();
254205
254776
  init_merger_ref_update_advance();
254206
- import { execFile as execFile31 } from "node:child_process";
254207
- import { promisify as promisify75 } from "node:util";
254208
- var execFileAsync25 = promisify75(execFile31);
254777
+ import { execFile as execFile32 } from "node:child_process";
254778
+ import { promisify as promisify76 } from "node:util";
254779
+ var execFileAsync26 = promisify76(execFile32);
254209
254780
  async function runGit5(args, cwd) {
254210
- const { stdout } = await execFileAsync25("git", args, {
254781
+ const { stdout } = await execFileAsync26("git", args, {
254211
254782
  cwd,
254212
254783
  encoding: "utf-8",
254213
254784
  timeout: 3e4
@@ -254216,7 +254787,7 @@ async function runGit5(args, cwd) {
254216
254787
  }
254217
254788
  async function isAncestor3(repoDir, ancestor, descendant) {
254218
254789
  try {
254219
- await execFileAsync25(
254790
+ await execFileAsync26(
254220
254791
  "git",
254221
254792
  ["merge-base", "--is-ancestor", ancestor, descendant],
254222
254793
  { cwd: repoDir, encoding: "utf-8", timeout: 3e4 }
@@ -254320,7 +254891,7 @@ init_run_audit();
254320
254891
  init_worktree_acquisition();
254321
254892
  init_worktree_paths();
254322
254893
  init_worktree_pool();
254323
- var execAsync53 = promisify76(exec56);
254894
+ var execAsync53 = promisify77(exec56);
254324
254895
  var MAX_TASK_DONE_SESSION_RETRIES = 3;
254325
254896
  async function parkExternalSessionObstacle(deps, taskId, errorMessage) {
254326
254897
  const obstacle = classifyExternalObstacle(errorMessage);
@@ -260093,8 +260664,8 @@ init_active_session_registry();
260093
260664
  init_agent_session_helpers();
260094
260665
  init_logger();
260095
260666
  init_effective_settings();
260096
- import { execFile as execFile32 } from "node:child_process";
260097
- import { promisify as promisify77 } from "node:util";
260667
+ import { execFile as execFile33 } from "node:child_process";
260668
+ import { promisify as promisify78 } from "node:util";
260098
260669
 
260099
260670
  // ../engine/src/agents/workflow-agent-capacity.ts
260100
260671
  var WorkflowAgentCapacity = class _WorkflowAgentCapacity {
@@ -260171,7 +260742,7 @@ var WorkflowAgentCapacity = class _WorkflowAgentCapacity {
260171
260742
  };
260172
260743
 
260173
260744
  // ../engine/src/executor/wire-executor-lifecycle.ts
260174
- var execFileAsync26 = promisify77(execFile32);
260745
+ var execFileAsync27 = promisify78(execFile33);
260175
260746
  async function releaseWorkspaceAcquireClaims(store, taskId) {
260176
260747
  for (const entry of activeSessionRegistry.entriesByKind("workspace-repo-acquire")) {
260177
260748
  if (entry.taskId === taskId) activeSessionRegistry.unregisterPath(entry.path);
@@ -260263,7 +260834,7 @@ function wireExecutorLifecycle(deps) {
260263
260834
  if (await canonicalizeWorktreePath(entry.worktreePath) === await canonicalizeWorktreePath(entry.repoRootDir)) throw new Error("Refusing to remove workspace repository root");
260264
260835
  activeSessionRegistry.unregisterPath(entry.worktreePath);
260265
260836
  await removeWorktree({ worktreePath: entry.worktreePath, rootDir: entry.repoRootDir, settings: await deps.store.getSettings(), taskId: task.id, reason: RemovalReason.ExecutorDispose, force: true });
260266
- await execFileAsync26("git", ["branch", "-D", entry.branch], { cwd: entry.repoRootDir, timeout: 12e4, maxBuffer: 10 * 1024 * 1024 });
260837
+ await execFileAsync27("git", ["branch", "-D", entry.branch], { cwd: entry.repoRootDir, timeout: 12e4, maxBuffer: 10 * 1024 * 1024 });
260267
260838
  if (task.workspaceWorktrees) for (const repoRel of [entry.repoRel, ...entry.aliasRepoRels]) delete task.workspaceWorktrees[repoRel];
260268
260839
  removed.push(entry.repoRel);
260269
260840
  } catch (error2) {
@@ -262939,9 +263510,9 @@ var BUNDLED_CLI_ADAPTERS = Object.freeze([
262939
263510
 
262940
263511
  // ../engine/src/cli-agent/resume-coordinator.ts
262941
263512
  import { existsSync as existsSync83 } from "node:fs";
262942
- import { execFile as execFile33 } from "node:child_process";
262943
- import { promisify as promisify78 } from "node:util";
262944
- var execFileAsync27 = promisify78(execFile33);
263513
+ import { execFile as execFile34 } from "node:child_process";
263514
+ import { promisify as promisify79 } from "node:util";
263515
+ var execFileAsync28 = promisify79(execFile34);
262945
263516
  var ORPHANED_LIVE_STATES = /* @__PURE__ */ new Set([
262946
263517
  "starting",
262947
263518
  "ready",
@@ -262952,7 +263523,7 @@ var DEFAULT_MAX_RESUME_ATTEMPTS = 2;
262952
263523
  var DEFAULT_RESUME_BACKOFF_BASE_MS = 1e3;
262953
263524
  async function defaultIsWorktreeDirty(worktreePath) {
262954
263525
  try {
262955
- const { stdout } = await execFileAsync27("git", ["status", "--porcelain"], {
263526
+ const { stdout } = await execFileAsync28("git", ["status", "--porcelain"], {
262956
263527
  cwd: worktreePath
262957
263528
  });
262958
263529
  return stdout.trim().length > 0;
@@ -265301,7 +265872,7 @@ init_notifier();
265301
265872
 
265302
265873
  // ../engine/src/healing/stuck-task-detector.ts
265303
265874
  init_logger();
265304
- import { createHash as createHash35 } from "node:crypto";
265875
+ import { createHash as createHash36 } from "node:crypto";
265305
265876
  var stuckLog = createLogger("stuck-detector");
265306
265877
  var LOOP_ACTIVITY_THRESHOLD = 60;
265307
265878
  var LOOP_IGNORED_STEP_MIN = 10;
@@ -265312,7 +265883,7 @@ var TOOL_DETAIL_HASH_HEX_LEN = 16;
265312
265883
  var NO_PROGRESS_CHURN_THRESHOLD = 25;
265313
265884
  var VERIFICATION_DEADLINE_GRACE_MS = 5e3;
265314
265885
  function hashToolDetail(detail) {
265315
- return createHash35("sha256").update(detail).digest("hex").slice(0, TOOL_DETAIL_HASH_HEX_LEN);
265886
+ return createHash36("sha256").update(detail).digest("hex").slice(0, TOOL_DETAIL_HASH_HEX_LEN);
265316
265887
  }
265317
265888
  function buildToolFingerprint(toolName, toolDetail) {
265318
265889
  const name2 = toolName.trim().toLowerCase() || "tool";
@@ -268021,7 +268592,7 @@ var MissionAutopilot = class {
268021
268592
  // ../engine/src/missions/mission-execution-loop.ts
268022
268593
  init_src2();
268023
268594
  import { EventEmitter as EventEmitter26 } from "node:events";
268024
- import { createHash as createHash36 } from "node:crypto";
268595
+ import { createHash as createHash37 } from "node:crypto";
268025
268596
 
268026
268597
  // ../engine/src/missions/mission-verification.ts
268027
268598
  init_sandbox();
@@ -268033,8 +268604,8 @@ import { promises as fs9 } from "node:fs";
268033
268604
  import os7 from "node:os";
268034
268605
  import path5 from "node:path";
268035
268606
  import { exec as exec58 } from "node:child_process";
268036
- import { promisify as promisify79 } from "node:util";
268037
- var execAsync54 = promisify79(exec58);
268607
+ import { promisify as promisify80 } from "node:util";
268608
+ var execAsync54 = promisify80(exec58);
268038
268609
  var verifyLog = createLogger("mission-verify");
268039
268610
  var GitCheckoutMaterializer = class {
268040
268611
  async materialize(rootDir, revision) {
@@ -268083,8 +268654,8 @@ init_fallback_model_observer();
268083
268654
  init_mcp_resolution();
268084
268655
  init_run_audit();
268085
268656
  import { exec as exec59 } from "node:child_process";
268086
- import { promisify as promisify80 } from "node:util";
268087
- var execAsync55 = promisify80(exec59);
268657
+ import { promisify as promisify81 } from "node:util";
268658
+ var execAsync55 = promisify81(exec59);
268088
268659
  function quoteShellArg8(value) {
268089
268660
  return `'${value.replace(/'/g, `'\\''`)}'`;
268090
268661
  }
@@ -268094,7 +268665,7 @@ var MAX_VALIDATION_RESPONSE_BYTES = 256 * 1024;
268094
268665
  var MAX_VALIDATION_JSON_CANDIDATES = 8;
268095
268666
  var VALIDATION_FAILURE_BUDGET_PER_FINGERPRINT = 3;
268096
268667
  function fingerprintMissionValidationInput(landedSha, provider, modelId, systemPrompt, userPrompt) {
268097
- return createHash36("sha256").update(Buffer.from(JSON.stringify(["mission-validation-input-v1", landedSha, provider, modelId, systemPrompt, userPrompt]), "utf8")).digest("hex");
268668
+ return createHash37("sha256").update(Buffer.from(JSON.stringify(["mission-validation-input-v1", landedSha, provider, modelId, systemPrompt, userPrompt]), "utf8")).digest("hex");
268098
268669
  }
268099
268670
  var MissionExecutionLoop = class extends EventEmitter26 {
268100
268671
  running = false;
@@ -269802,7 +270373,7 @@ import { exec as exec60 } from "node:child_process";
269802
270373
  import { existsSync as existsSync84 } from "node:fs";
269803
270374
  import { readFile as readFile50, writeFile as writeFile29, rm as rm16 } from "node:fs/promises";
269804
270375
  import { join as join94 } from "node:path";
269805
- import { promisify as promisify81 } from "node:util";
270376
+ import { promisify as promisify82 } from "node:util";
269806
270377
 
269807
270378
  // ../engine/src/triage-domain/triage-preflight.ts
269808
270379
  var BUG_FIX_REGEX = /typecheck error|compile error|broken|regression|lint error/i;
@@ -273020,7 +273591,7 @@ ${lines.join("\n")}` }],
273020
273591
  written,
273021
273592
  {
273022
273593
  cwd: this.rootDir,
273023
- exec: promisify81(exec60)
273594
+ exec: promisify82(exec60)
273024
273595
  }
273025
273596
  ),
273026
273597
  new Promise((resolve52) => setTimeout(() => resolve52(null), 15e3))
@@ -273797,7 +274368,7 @@ function stableNotificationJson(value) {
273797
274368
  return `{${fields.join(",")}}`;
273798
274369
  }
273799
274370
  function buildCliAgentNotificationDedupeKey(input) {
273800
- const notificationFingerprint = createHash37("sha256").update(stableNotificationJson(input.info.notification ?? null)).digest("hex").slice(0, 16);
274371
+ const notificationFingerprint = createHash38("sha256").update(stableNotificationJson(input.info.notification ?? null)).digest("hex").slice(0, 16);
273801
274372
  const waitingEpoch = input.session?.updatedAt ?? "unknown-waiting-epoch";
273802
274373
  return [
273803
274374
  "cli-agent",
@@ -278487,8 +279058,8 @@ init_src2();
278487
279058
  init_agent_user_comments();
278488
279059
  init_worktree_names();
278489
279060
  init_integration_branch();
278490
- import { execFile as execFile38 } from "node:child_process";
278491
- import { promisify as promisify86 } from "node:util";
279061
+ import { execFile as execFile39 } from "node:child_process";
279062
+ import { promisify as promisify87 } from "node:util";
278492
279063
  import { realpathSync as realpathSync15, readdirSync as readdirSync13 } from "node:fs";
278493
279064
  import { mkdtemp as mkdtemp5 } from "node:fs/promises";
278494
279065
  import { tmpdir as tmpdir12 } from "node:os";
@@ -278497,10 +279068,10 @@ init_workspace_base_branch();
278497
279068
  init_merger_ref_update_advance();
278498
279069
 
278499
279070
  // ../engine/src/merge/merger-ai-squash-gates.ts
278500
- import { execFile as execFile34 } from "node:child_process";
278501
- import { promisify as promisify82 } from "node:util";
279071
+ import { execFile as execFile35 } from "node:child_process";
279072
+ import { promisify as promisify83 } from "node:util";
278502
279073
  init_merger_file_scope();
278503
- var execFileAsync28 = promisify82(execFile34);
279074
+ var execFileAsync29 = promisify83(execFile35);
278504
279075
  function resolveRepoDeclaredScopeTransform({ repoRel, repoKeys }) {
278505
279076
  return {
278506
279077
  transform(scope) {
@@ -278531,8 +279102,8 @@ async function enforceAiMergeSquashGates(params) {
278531
279102
  });
278532
279103
  } catch (error2) {
278533
279104
  if (!(error2 instanceof FileScopeViolationError)) throw error2;
278534
- await execFileAsync28("git", ["reset", "--hard", params.tipSha], { cwd: params.mergeRoot });
278535
- await execFileAsync28("git", ["clean", "-fd"], { cwd: params.mergeRoot });
279105
+ await execFileAsync29("git", ["reset", "--hard", params.tipSha], { cwd: params.mergeRoot });
279106
+ await execFileAsync29("git", ["clean", "-fd"], { cwd: params.mergeRoot });
278536
279107
  try {
278537
279108
  await params.store.updateTask(params.taskId, { aiMergeReviewReconciliation: null });
278538
279109
  } catch {
@@ -278721,10 +279292,10 @@ function resolveWorkspaceMergeReadiness(task, freshModifiedRepositories, netZero
278721
279292
  }
278722
279293
 
278723
279294
  // ../engine/src/merge/workspace-fence-ref.ts
278724
- import { execFile as execFile35 } from "node:child_process";
278725
- import { createHash as createHash38 } from "node:crypto";
278726
- import { promisify as promisify83 } from "node:util";
278727
- var execFileAsync29 = promisify83(execFile35);
279295
+ import { execFile as execFile36 } from "node:child_process";
279296
+ import { createHash as createHash39 } from "node:crypto";
279297
+ import { promisify as promisify84 } from "node:util";
279298
+ var execFileAsync30 = promisify84(execFile36);
278728
279299
  var WorkspaceFenceRefError = class extends Error {
278729
279300
  constructor(message, kind) {
278730
279301
  super(message);
@@ -278733,7 +279304,7 @@ var WorkspaceFenceRefError = class extends Error {
278733
279304
  }
278734
279305
  };
278735
279306
  async function git6(args, cwd) {
278736
- const { stdout } = await execFileAsync29("git", args, {
279307
+ const { stdout } = await execFileAsync30("git", args, {
278737
279308
  cwd,
278738
279309
  encoding: "utf8",
278739
279310
  maxBuffer: 1024 * 1024
@@ -278746,7 +279317,7 @@ async function remoteRefSha(remote, ref, cwd) {
278746
279317
  return /^[0-9a-f]{40,64}$/i.test(sha ?? "") ? sha : void 0;
278747
279318
  }
278748
279319
  function workspaceLandFenceRef(repoRelPath) {
278749
- const slug2 = createHash38("sha256").update(repoRelPath).digest("hex").slice(0, 24);
279320
+ const slug2 = createHash39("sha256").update(repoRelPath).digest("hex").slice(0, 24);
278750
279321
  return `refs/fusion/workspace-lease/${slug2}`;
278751
279322
  }
278752
279323
  function mergeDispatchFenceRef(taskId) {
@@ -278755,7 +279326,7 @@ function mergeDispatchFenceRef(taskId) {
278755
279326
  async function createFenceObject(cwd, handle2) {
278756
279327
  const tree = await git6(["hash-object", "-w", "-t", "tree", "/dev/null"], cwd);
278757
279328
  const message = `workspace lease ${handle2.leaseKey} ${handle2.owner.taskId} ${handle2.owner.nodeId} ${handle2.owner.incarnationId} ${handle2.fenceToken}`;
278758
- const { stdout } = await execFileAsync29("git", [
279329
+ const { stdout } = await execFileAsync30("git", [
278759
279330
  "-c",
278760
279331
  "user.name=Fusion",
278761
279332
  "-c",
@@ -278890,9 +279461,9 @@ async function publishWorkspaceIntegrationRef(input) {
278890
279461
  init_push_after_merge_policy();
278891
279462
 
278892
279463
  // ../engine/src/merge/workspace-integration-target.ts
278893
- import { execFile as execFile36 } from "node:child_process";
278894
- import { promisify as promisify84 } from "node:util";
278895
- var execFileAsync30 = promisify84(execFile36);
279464
+ import { execFile as execFile37 } from "node:child_process";
279465
+ import { promisify as promisify85 } from "node:util";
279466
+ var execFileAsync31 = promisify85(execFile37);
278896
279467
  var WorkspaceEnvironmentError = class extends Error {
278897
279468
  constructor(repository, resource, action, message) {
278898
279469
  super(message);
@@ -278912,7 +279483,7 @@ var WorkspaceIntegrationTargetError = class extends WorkspaceEnvironmentError {
278912
279483
  }
278913
279484
  };
278914
279485
  async function git7(args, cwd) {
278915
- const { stdout } = await execFileAsync30("git", args, { cwd, encoding: "utf8" });
279486
+ const { stdout } = await execFileAsync31("git", args, { cwd, encoding: "utf8" });
278916
279487
  return stdout.trim();
278917
279488
  }
278918
279489
  async function resolveWorkspaceIntegrationTarget(input) {
@@ -278959,17 +279530,17 @@ init_worktree_paths();
278959
279530
 
278960
279531
  // ../engine/src/merge/merger-ai-worktree.ts
278961
279532
  init_active_session_registry();
278962
- import { execFile as execFile37 } from "node:child_process";
279533
+ import { execFile as execFile38 } from "node:child_process";
278963
279534
  import { appendFileSync as appendFileSync2, existsSync as existsSync87, mkdirSync as mkdirSync11, readdirSync as readdirSync12, readFileSync as readFileSync28, realpathSync as realpathSync14, rmSync as rmSync8, statSync as statSync16 } from "node:fs";
278964
279535
  import { rm as rm17 } from "node:fs/promises";
278965
279536
  import { tmpdir as tmpdir11 } from "node:os";
278966
279537
  import { isAbsolute as isAbsolute27, join as join96, relative as relative23 } from "node:path";
278967
- import { promisify as promisify85 } from "node:util";
279538
+ import { promisify as promisify86 } from "node:util";
278968
279539
  init_worktree_paths();
278969
279540
  init_worktree_removal_retry();
278970
- var execFileAsync31 = promisify85(execFile37);
279541
+ var execFileAsync32 = promisify86(execFile38);
278971
279542
  async function git8(args, cwd, opts = {}) {
278972
- const { stdout } = await execFileAsync31("git", args, {
279543
+ const { stdout } = await execFileAsync32("git", args, {
278973
279544
  cwd,
278974
279545
  encoding: "utf-8",
278975
279546
  timeout: opts.timeout ?? 12e4,
@@ -278998,14 +279569,7 @@ function ensureAiMergeRootIgnored(projectRootDir, settings) {
278998
279569
  if (!existsSync87(excludePath)) return;
278999
279570
  try {
279000
279571
  const current = readFileSync28(excludePath, "utf-8");
279001
- const legacyAiMergeRoot = resolveLegacyAiMergeRootPath(projectRootDir);
279002
- const legacyRelativeAiMergeRoot = relative23(projectRootDir, legacyAiMergeRoot);
279003
- const entries = [`${legacyRelativeAiMergeRoot.replaceAll("\\", "/")}/`];
279004
- const aiMergeRoot = resolveAiMergeRootPath(projectRootDir, settings);
279005
- const relativeAiMergeRoot = relative23(projectRootDir, aiMergeRoot);
279006
- if (relativeAiMergeRoot && !relativeAiMergeRoot.startsWith("..") && !isAbsolute27(relativeAiMergeRoot)) {
279007
- entries.push(`${relativeAiMergeRoot.replaceAll("\\", "/")}/`);
279008
- }
279572
+ const entries = resolveAiMergeSearchRoots(projectRootDir, settings).map((root) => relative23(projectRootDir, root)).filter((root) => root && !root.startsWith("..") && !isAbsolute27(root)).map((root) => `${root.replaceAll("\\", "/")}/`);
279009
279573
  const missing = entries.filter((entry) => !current.split(/\r?\n/).includes(entry));
279010
279574
  if (missing.length > 0) {
279011
279575
  appendFileSync2(excludePath, `${current.endsWith("\n") ? "" : "\n"}${missing.join("\n")}
@@ -279021,7 +279585,7 @@ function resolveAiMergeRoot(projectRootDir, settings) {
279021
279585
  return root;
279022
279586
  }
279023
279587
  function getAiMergeTempSearchRoots(projectRootDir, settings) {
279024
- const roots = [resolveAiMergeRoot(projectRootDir, settings), resolveLegacyAiMergeRootPath(projectRootDir), tmpdir11()];
279588
+ const roots = [resolveAiMergeRoot(projectRootDir, settings), ...resolveAiMergeSearchRoots(projectRootDir, settings), tmpdir11()];
279025
279589
  const testWorkerRoot = process.env.FUSION_TEST_WORKER_ROOT;
279026
279590
  if (testWorkerRoot) {
279027
279591
  try {
@@ -279074,7 +279638,7 @@ async function pruneExistingAiMergeWorktrees(taskId, projectRootDir, audit, log4
279074
279638
  let alreadyAbsent = false;
279075
279639
  try {
279076
279640
  cleanupAttempted = true;
279077
- await execFileAsync31("git", ["worktree", "remove", "--force", canonicalPath], {
279641
+ await execFileAsync32("git", ["worktree", "remove", "--force", canonicalPath], {
279078
279642
  cwd: projectRootDir,
279079
279643
  timeout: 3e4
279080
279644
  });
@@ -279105,7 +279669,7 @@ async function pruneExistingAiMergeWorktrees(taskId, projectRootDir, audit, log4
279105
279669
  }
279106
279670
  if (cleanupAttempted) {
279107
279671
  try {
279108
- await execFileAsync31("git", ["worktree", "prune"], { cwd: projectRootDir, timeout: 3e4 });
279672
+ await execFileAsync32("git", ["worktree", "prune"], { cwd: projectRootDir, timeout: 3e4 });
279109
279673
  } catch (err) {
279110
279674
  await log42(`AI merge pre-merge prune: git worktree prune failed: ${describeCleanupError(err)}`);
279111
279675
  }
@@ -279514,7 +280078,7 @@ function short(sha) {
279514
280078
  }
279515
280079
 
279516
280080
  // ../engine/src/merge/merger-ai.ts
279517
- var execFileAsync32 = promisify86(execFile38);
280081
+ var execFileAsync33 = promisify87(execFile39);
279518
280082
  var aiMergeLog = createLogger("merger-ai");
279519
280083
  async function resolveNoOpFinalizeGateIds3(store, task) {
279520
280084
  const selection = store.getTaskWorkflowSelectionAsync ? await store.getTaskWorkflowSelectionAsync(task.id) : store.getTaskWorkflowSelection?.(task.id);
@@ -279540,7 +280104,7 @@ function writeTransientMergeStatus(store, taskId, signal, status) {
279540
280104
  return fence.write("lifecycle", () => store.updateTask(taskId, { status }).catch(() => void 0));
279541
280105
  }
279542
280106
  async function git9(args, cwd, opts = {}) {
279543
- const { stdout } = await execFileAsync32("git", args, {
280107
+ const { stdout } = await execFileAsync33("git", args, {
279544
280108
  cwd,
279545
280109
  encoding: "utf-8",
279546
280110
  timeout: opts.timeout ?? 12e4,
@@ -279581,7 +280145,7 @@ function boundBlockingReviewReasons(reasons) {
279581
280145
  }
279582
280146
  function listAiMergeWorktreeCandidates(taskId, projectRootDir, settings) {
279583
280147
  const prefix = `fusion-ai-merge-${taskId.toLowerCase()}-`;
279584
- const roots = Array.from(/* @__PURE__ */ new Set([resolveAiMergeRoot(projectRootDir, settings), resolveLegacyAiMergeRootPath(projectRootDir), tmpdir12()]));
280148
+ const roots = Array.from(/* @__PURE__ */ new Set([resolveAiMergeRoot(projectRootDir, settings), ...resolveAiMergeSearchRoots(projectRootDir, settings), tmpdir12()]));
279585
280149
  const testWorkerRoot = process.env.FUSION_TEST_WORKER_ROOT;
279586
280150
  if (testWorkerRoot) {
279587
280151
  try {
@@ -282166,8 +282730,8 @@ init_transient_merge_error_classifier();
282166
282730
 
282167
282731
  // ../engine/src/remote-access/remote-tunnel-service.ts
282168
282732
  init_logger();
282169
- import { execFile as execFile40 } from "node:child_process";
282170
- import { promisify as promisify88 } from "node:util";
282733
+ import { execFile as execFile41 } from "node:child_process";
282734
+ import { promisify as promisify89 } from "node:util";
282171
282735
 
282172
282736
  // ../engine/src/local-dashboard-port.ts
282173
282737
  var DEFAULT_DASHBOARD_PORT = 4040;
@@ -282180,8 +282744,8 @@ function getLocalDashboardPort() {
282180
282744
  init_src2();
282181
282745
  init_logger();
282182
282746
  import { EventEmitter as EventEmitter28 } from "node:events";
282183
- import { exec as exec61, execFile as execFile39, spawn as spawn8 } from "node:child_process";
282184
- import { promisify as promisify87 } from "node:util";
282747
+ import { exec as exec61, execFile as execFile40, spawn as spawn8 } from "node:child_process";
282748
+ import { promisify as promisify88 } from "node:util";
282185
282749
 
282186
282750
  // ../engine/src/remote-access/provider-adapters.ts
282187
282751
  import { accessSync, constants as fsConstants5 } from "node:fs";
@@ -282349,8 +282913,8 @@ var DEFAULT_MAX_LOG_ENTRIES = 400;
282349
282913
  var DEFAULT_STOP_TIMEOUT_MS2 = 5e3;
282350
282914
  var DEFAULT_RESTART_BASE_DELAY_MS = 1e3;
282351
282915
  var DEFAULT_RESTART_MAX_DELAY_MS = 3e4;
282352
- var execFileAsync33 = promisify87(execFile39);
282353
- var execAsync56 = promisify87(exec61);
282916
+ var execFileAsync34 = promisify88(execFile40);
282917
+ var execAsync56 = promisify88(exec61);
282354
282918
  function serveConfigScopes(config3) {
282355
282919
  const foreground = Object.values(config3.Foreground ?? {}).filter(
282356
282920
  (scope) => Boolean(scope)
@@ -282500,7 +283064,7 @@ var TunnelProcessManager = class extends EventEmitter28 {
282500
283064
  return null;
282501
283065
  }
282502
283066
  try {
282503
- const { stdout } = await execFileAsync33("tailscale", ["status", "--json"], { timeout: 3e3 });
283067
+ const { stdout } = await execFileAsync34("tailscale", ["status", "--json"], { timeout: 3e3 });
282504
283068
  const data = JSON.parse(String(stdout));
282505
283069
  const dnsName = data.Self?.DNSName?.replace(/\.$/, "");
282506
283070
  if (!dnsName) {
@@ -282523,7 +283087,7 @@ var TunnelProcessManager = class extends EventEmitter28 {
282523
283087
  ];
282524
283088
  for (const resetCommand of resetCommands) {
282525
283089
  try {
282526
- await execFileAsync33(resetCommand.command, resetCommand.args, { timeout: 5e3 });
283090
+ await execFileAsync34(resetCommand.command, resetCommand.args, { timeout: 5e3 });
282527
283091
  return;
282528
283092
  } catch {
282529
283093
  }
@@ -282629,7 +283193,7 @@ var TunnelProcessManager = class extends EventEmitter28 {
282629
283193
  async detectActiveFunnel() {
282630
283194
  let stdout = "";
282631
283195
  try {
282632
- const result = await execFileAsync33("tailscale", ["serve", "status", "--json"], { timeout: 5e3 });
283196
+ const result = await execFileAsync34("tailscale", ["serve", "status", "--json"], { timeout: 5e3 });
282633
283197
  stdout = String(result.stdout ?? "");
282634
283198
  } catch (error2) {
282635
283199
  const failure = error2;
@@ -283045,7 +283609,7 @@ var TunnelProcessManager = class extends EventEmitter28 {
283045
283609
  };
283046
283610
 
283047
283611
  // ../engine/src/remote-access/remote-tunnel-service.ts
283048
- var execFileAsync34 = promisify88(execFile40);
283612
+ var execFileAsync35 = promisify89(execFile41);
283049
283613
  var isRemoteActive = (ra) => ra?.activeProvider != null && (ra.providers[ra.activeProvider]?.enabled ?? false);
283050
283614
  var RemoteTunnelService = class {
283051
283615
  manager = new TunnelProcessManager();
@@ -283394,7 +283958,7 @@ var RemoteTunnelService = class {
283394
283958
  async checkTailscaleDaemonReady() {
283395
283959
  let stdout = "";
283396
283960
  try {
283397
- const result = await execFileAsync34("tailscale", ["status", "--json"], {
283961
+ const result = await execFileAsync35("tailscale", ["status", "--json"], {
283398
283962
  timeout: 5e3,
283399
283963
  maxBuffer: 8 * 1024 * 1024
283400
283964
  });
@@ -283442,7 +284006,7 @@ var RemoteTunnelService = class {
283442
284006
  async checkExecutableAvailable(command) {
283443
284007
  const checker = process.platform === "win32" ? "where" : "which";
283444
284008
  try {
283445
- await execFileAsync34(checker, [command]);
284009
+ await execFileAsync35(checker, [command]);
283446
284010
  return { available: true };
283447
284011
  } catch {
283448
284012
  return {
@@ -283625,7 +284189,7 @@ function emitMergeRequestShadowDequeueParityAudit(store, legacyTaskId, shadowTas
283625
284189
  metadata: { legacyTaskId, shadowTaskId, agree }
283626
284190
  }, { log: runtimeLog });
283627
284191
  }
283628
- var execFileAsync35 = promisify89(execFile41);
284192
+ var execFileAsync36 = promisify90(execFile42);
283629
284193
  var MERGE_HANDOFF_GRACE_MS = 300;
283630
284194
  function isActiveMergeRequestState(state) {
283631
284195
  return state === "running" || state === "retrying";
@@ -283671,7 +284235,7 @@ async function verifyMergeConfirmedReachability(args) {
283671
284235
  return { reachable: true, skipped: "no-integration-branch" };
283672
284236
  }
283673
284237
  try {
283674
- await execFileAsync35("git", ["cat-file", "-e", `${commitSha}^{commit}`], {
284238
+ await execFileAsync36("git", ["cat-file", "-e", `${commitSha}^{commit}`], {
283675
284239
  cwd,
283676
284240
  timeout: 1e4
283677
284241
  });
@@ -283680,7 +284244,7 @@ async function verifyMergeConfirmedReachability(args) {
283680
284244
  return { reachable: false, reason: "commit-missing", diagnostic };
283681
284245
  }
283682
284246
  try {
283683
- await execFileAsync35(
284247
+ await execFileAsync36(
283684
284248
  "git",
283685
284249
  ["merge-base", "--is-ancestor", commitSha, `refs/heads/${integrationBranch}`],
283686
284250
  { cwd, timeout: 1e4 }
@@ -283756,6 +284320,7 @@ var ProjectEngine = class _ProjectEngine {
283756
284320
  }
283757
284321
  });
283758
284322
  }
284323
+ staleContentRerouteAuditKeys = /* @__PURE__ */ new Set();
283759
284324
  runtime;
283760
284325
  started = false;
283761
284326
  specDriftReconciler;
@@ -285552,11 +286117,44 @@ ${detail}`);
285552
286117
  workspaceRootDir: this.config.workingDirectory,
285553
286118
  settings
285554
286119
  });
285555
- return getTaskMergeBlocker(task, {
286120
+ const blocker = getTaskMergeBlocker(task, {
285556
286121
  reviewColumns: mergeGate.reviewColumns.size > 0 ? mergeGate.reviewColumns : /* @__PURE__ */ new Set(["in-review"]),
285557
286122
  requiredPreMergeStepIds: mergeGate.requiredPreMergeStepIds,
285558
286123
  mergeContent
285559
286124
  });
286125
+ if (blocker === "task has a pre-merge approval recorded against different content" && mergeContent.kind === "singular") {
286126
+ const reroute = await rerouteSingularStaleContentToReview(store, task, {
286127
+ requiredPreMergeStepIds: mergeGate.requiredPreMergeStepIds,
286128
+ mergeContent
286129
+ }).catch(() => ({
286130
+ rerouted: false,
286131
+ reason: "no-progress",
286132
+ nodeId: void 0,
286133
+ workflowStepId: void 0
286134
+ }));
286135
+ if (reroute.rerouted) {
286136
+ await store.logEntry(task.id, `[pre-merge] Code Review re-entry is owned by the workflow graph after stale content evidence was refused.`);
286137
+ }
286138
+ const auditKey = `${task.id}:${reroute.reason}`;
286139
+ if (!this.staleContentRerouteAuditKeys.has(auditKey)) {
286140
+ this.staleContentRerouteAuditKeys.add(auditKey);
286141
+ await emitBoundedRunAudit2(store, {
286142
+ taskId: task.id,
286143
+ agentId: "merge-gate",
286144
+ runId: `${task.id}:merge-gate`,
286145
+ domain: "database",
286146
+ mutationType: "task:merge-stale-content-review-rerouted",
286147
+ target: task.id,
286148
+ metadata: {
286149
+ taskId: task.id,
286150
+ nodeId: reroute.nodeId,
286151
+ workflowStepId: reroute.workflowStepId,
286152
+ reason: reroute.reason
286153
+ }
286154
+ });
286155
+ }
286156
+ }
286157
+ return blocker;
285560
286158
  }
285561
286159
  /**
285562
286160
  * Remove and return the highest-priority taskId from the merge queue.
@@ -286402,7 +287000,7 @@ ${detail}`);
286402
287000
  }
286403
287001
  const checklist = planConfirmedMergeChecklistReconciliation(task);
286404
287002
  if (checklist.skippedStepIndexes.length > 0 || checklist.reconciledWorkflowStepIds.length > 0) {
286405
- const steps = task.steps.map((step, index2) => checklist.skippedStepIndexes.includes(index2) ? { ...step, status: "skipped" } : step);
287003
+ const steps = (task.steps ?? []).map((step, index2) => checklist.skippedStepIndexes.includes(index2) ? { ...step, status: "skipped" } : step);
286406
287004
  const workflowStepResults = (task.workflowStepResults ?? []).map((result) => checklist.reconciledWorkflowStepIds.includes(result.workflowStepId) ? { ...result, status: "skipped" } : result);
286407
287005
  await store.updateTask(taskId, { steps, workflowStepResults });
286408
287006
  task.steps = steps;