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

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 +1648 -909
  2. package/dist/child-process-worker.js +1072 -471
  3. package/dist/client/.vite/manifest.json +260 -260
  4. package/dist/client/assets/{AgentDetailView-BZfDgV8f.js → AgentDetailView-C-fxrRM5.js} +1 -1
  5. package/dist/client/assets/AgentDetailView-DPoh46Ym.css +1 -0
  6. package/dist/client/assets/{AgentPermissionPolicyEditor-8yBqnwbx.js → AgentPermissionPolicyEditor-BvEdHYdU.js} +1 -1
  7. package/dist/client/assets/{AgentsView-BjMOtIMq.js → AgentsView-nLidYV1p.js} +3 -3
  8. package/dist/client/assets/CommandCenter-BshTzOtB.css +1 -0
  9. package/dist/client/assets/{CommandCenter-CDuO3HPd.js → CommandCenter-BxFBD-rU.js} +22 -22
  10. package/dist/client/assets/{DevServerView-C96RV0QJ.js → DevServerView-KLdA68W-.js} +1 -1
  11. package/dist/client/assets/{DirectoryPicker-iRielXf7.js → DirectoryPicker-BiclyrJo.js} +1 -1
  12. package/dist/client/assets/{DocumentsView-DhAUnUD_.js → DocumentsView-BU24Dd5I.js} +1 -1
  13. package/dist/client/assets/{EvalsView--n1p2Bt0.js → EvalsView-BMSTg0vH.js} +1 -1
  14. package/dist/client/assets/{ExperimentalAgentOnboardingModal-DraglCCV.js → ExperimentalAgentOnboardingModal-BtYOAtE2.js} +1 -1
  15. package/dist/client/assets/{GoalsView-BOWOua5t.js → GoalsView-Dlb12ETy.js} +1 -1
  16. package/dist/client/assets/{InsightsView-C5YcJZSc.js → InsightsView-NJuA0vFY.js} +1 -1
  17. package/dist/client/assets/{MemoryView-DsCuGVGx.js → MemoryView-te2vPbGq.js} +1 -1
  18. package/dist/client/assets/{PatchnodeView-CzgChkJV.js → PatchnodeView-F-TmysVw.js} +1 -1
  19. package/dist/client/assets/{PiExtensionsManager-qYbmaigE.js → PiExtensionsManager-CClfNfAm.js} +1 -1
  20. package/dist/client/assets/{PluginManager-Cklj4Smo.js → PluginManager-BdNdqcjr.js} +1 -1
  21. package/dist/client/assets/{PullRequestView-C6vozTTI.js → PullRequestView-Du4FqTkG.js} +1 -1
  22. package/dist/client/assets/{ReportModal-B0LU4E4L.js → ReportModal-BCUOLOBM.js} +1 -1
  23. package/dist/client/assets/{ResearchView-CpESx46e.js → ResearchView-DukV-C42.js} +1 -1
  24. package/dist/client/assets/{SecretsView-8ZdH9Yzs.js → SecretsView-B3ApHMyx.js} +1 -1
  25. package/dist/client/assets/SettingsModal-BEULP2a7.css +1 -0
  26. package/dist/client/assets/{SettingsModal-BJXu-dhz.js → SettingsModal-D-_HKpi5.js} +1 -1
  27. package/dist/client/assets/SettingsModal-JPt8VHZy.js +21 -0
  28. package/dist/client/assets/{SettingsTextareaRow-th-q0_2k.js → SettingsTextareaRow-CpTYQz1W.js} +1 -1
  29. package/dist/client/assets/{SetupWizardModal-CBElxrRA.js → SetupWizardModal-DcD2ERVR.js} +2 -2
  30. package/dist/client/assets/{SkillsView-BuoicLbJ.js → SkillsView-DCqRw00z.js} +1 -1
  31. package/dist/client/assets/{WorkflowNodeEditor-BDw0LpWM.js → WorkflowNodeEditor-CxpjK-k_.js} +1 -1
  32. package/dist/client/assets/{agent-import-generation-CNridJHO.js → agent-import-generation-COouuGNI.js} +1 -1
  33. package/dist/client/assets/{agentsOrgChartLayout-BopFGp0r.js → agentsOrgChartLayout-BkXeACZM.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-CGeVlxee.js} +1 -1
  42. package/dist/client/assets/{blockDiagram-GPEHLZMM-ueBSwDJh.js → blockDiagram-GPEHLZMM-D3J6Tg96.js} +1 -1
  43. package/dist/client/assets/{c4Diagram-AAUBKEIU-D0cUU3pE.js → c4Diagram-AAUBKEIU-Ckh64Ujd.js} +1 -1
  44. package/dist/client/assets/channel-HtwB1uv6.js +1 -0
  45. package/dist/client/assets/{chunk-2J33WTMH-DKJnGdD6.js → chunk-2J33WTMH-D2nEfwjq.js} +1 -1
  46. package/dist/client/assets/{chunk-4BX2VUAB-BUrgUtvt.js → chunk-4BX2VUAB-C_LbWHxf.js} +1 -1
  47. package/dist/client/assets/{chunk-55IACEB6-DHy2hCYz.js → chunk-55IACEB6-DCe4vUlx.js} +1 -1
  48. package/dist/client/assets/{chunk-727SXJPM-Dgbhllem.js → chunk-727SXJPM-iwy0O4kD.js} +1 -1
  49. package/dist/client/assets/{chunk-AQP2D5EJ-tPbicvl-.js → chunk-AQP2D5EJ-3FRhkbWy.js} +1 -1
  50. package/dist/client/assets/{chunk-FMBD7UC4-BpHFGFmo.js → chunk-FMBD7UC4-DKwvI3J1.js} +1 -1
  51. package/dist/client/assets/{chunk-ND2GUHAM-DUTQ63ld.js → chunk-ND2GUHAM-BLCbQ396.js} +1 -1
  52. package/dist/client/assets/{chunk-QZHKN3VN-ChCf_usL.js → chunk-QZHKN3VN-B8_SVfXK.js} +1 -1
  53. package/dist/client/assets/classDiagram-4FO5ZUOK-DY01_o5q.js +1 -0
  54. package/dist/client/assets/classDiagram-v2-Q7XG4LA2-DY01_o5q.js +1 -0
  55. package/dist/client/assets/{cose-bilkent-S5V4N54A-DgWzHtF9.js → cose-bilkent-S5V4N54A-BI3P2QH1.js} +1 -1
  56. package/dist/client/assets/{dagre-BM42HDAG-gQ1C1xHJ.js → dagre-BM42HDAG-Cw7T4fR4.js} +1 -1
  57. package/dist/client/assets/{dashboard-view-BXtoQ9a4.js → dashboard-view-Bahw8YdV.js} +1 -1
  58. package/dist/client/assets/{dashboard-view-BLenRoXu.js → dashboard-view-GzD2Iawz.js} +1 -1
  59. package/dist/client/assets/{dashboard-view-AQmzkBNb.js → dashboard-view-_8Rh_v2d.js} +1 -1
  60. package/dist/client/assets/{dashboard-view-BtFepPos.js → dashboard-view-nmh7Oq3W.js} +1 -1
  61. package/dist/client/assets/{diagram-2AECGRRQ-Dv96I1MI.js → diagram-2AECGRRQ-D-HeBfdu.js} +1 -1
  62. package/dist/client/assets/{diagram-5GNKFQAL-BRyhjYes.js → diagram-5GNKFQAL-DdqdGn6R.js} +1 -1
  63. package/dist/client/assets/{diagram-KO2AKTUF-Bmj3ihjc.js → diagram-KO2AKTUF-BiqjUeas.js} +1 -1
  64. package/dist/client/assets/{diagram-LMA3HP47-BYHVCFK2.js → diagram-LMA3HP47-XAqAjX_Q.js} +1 -1
  65. package/dist/client/assets/{diagram-OG6HWLK6-0WZBLayt.js → diagram-OG6HWLK6-DFA7xW7A.js} +1 -1
  66. package/dist/client/assets/{erDiagram-TEJ5UH35-Cu-yj8hA.js → erDiagram-TEJ5UH35-ChXB-xPr.js} +1 -1
  67. package/dist/client/assets/{flowDiagram-I6XJVG4X-BSetu6oh.js → flowDiagram-I6XJVG4X-7EGMaA4S.js} +1 -1
  68. package/dist/client/assets/{folder-open-CkswT0lE.js → folder-open-CRG4IIvN.js} +1 -1
  69. package/dist/client/assets/{ganttDiagram-6RSMTGT7-B5qn0KFQ.js → ganttDiagram-6RSMTGT7-CFu_eMTW.js} +1 -1
  70. package/dist/client/assets/{gitGraphDiagram-PVQCEYII-BVB3wm46.js → gitGraphDiagram-PVQCEYII-B6D4IPay.js} +1 -1
  71. package/dist/client/assets/index-bjswUBcF.js +2937 -0
  72. package/dist/client/assets/index-lxhrCjd_.css +1 -0
  73. package/dist/client/assets/{infoDiagram-5YYISTIA-ZBNKOBSK.js → infoDiagram-5YYISTIA-WjP49ki8.js} +1 -1
  74. package/dist/client/assets/{ishikawaDiagram-YF4QCWOH-CbNKgenI.js → ishikawaDiagram-YF4QCWOH-D9dfXlEr.js} +1 -1
  75. package/dist/client/assets/{journeyDiagram-JHISSGLW-BLc2FHIF.js → journeyDiagram-JHISSGLW-B1qmhVqE.js} +1 -1
  76. package/dist/client/assets/{kanban-definition-UN3LZRKU-B08tij8f.js → kanban-definition-UN3LZRKU-DKJ9zHQh.js} +1 -1
  77. package/dist/client/assets/{mermaid.core-QX5z7w8Z.js → mermaid.core-L96x9i-s.js} +4 -4
  78. package/dist/client/assets/{mindmap-definition-RKZ34NQL-C1md8M3c.js → mindmap-definition-RKZ34NQL-Cd6f8xCu.js} +1 -1
  79. package/dist/client/assets/{pieDiagram-4H26LBE5-BoQbn3Xp.js → pieDiagram-4H26LBE5-DxV4qYtG.js} +1 -1
  80. package/dist/client/assets/{puzzle-DCqPnWPE.js → puzzle-B-xyPV3x.js} +1 -1
  81. package/dist/client/assets/{quadrantDiagram-W4KKPZXB-DPvNcVK-.js → quadrantDiagram-W4KKPZXB-CSfHWa-s.js} +1 -1
  82. package/dist/client/assets/{requirementDiagram-4Y6WPE33-KfdYf15t.js → requirementDiagram-4Y6WPE33-BVyQ-8E1.js} +1 -1
  83. package/dist/client/assets/{sankeyDiagram-5OEKKPKP-D1Eyoanf.js → sankeyDiagram-5OEKKPKP-bz2Lc_rV.js} +1 -1
  84. package/dist/client/assets/{sequenceDiagram-3UESZ5HK-CmODsbCf.js → sequenceDiagram-3UESZ5HK-ZvToVBro.js} +1 -1
  85. package/dist/client/assets/{standing-instructions-template-D_mPaGcG.js → standing-instructions-template-DVKRWk6d.js} +1 -1
  86. package/dist/client/assets/{stateDiagram-AJRCARHV-Blbw_21G.js → stateDiagram-AJRCARHV-CSCy0VcB.js} +1 -1
  87. package/dist/client/assets/{stateDiagram-v2-BHNVJYJU-DwMgvYO8.js → stateDiagram-v2-BHNVJYJU-ChRwUFh1.js} +1 -1
  88. package/dist/client/assets/{timeline-definition-PNZ67QCA-DDtndgLc.js → timeline-definition-PNZ67QCA-9MuQjnnW.js} +1 -1
  89. package/dist/client/assets/{upload-DGCjJkMH.js → upload-B1HSIk-m.js} +1 -1
  90. package/dist/client/assets/{users-CbWZvFZP.js → users-CHrRYBSi.js} +1 -1
  91. package/dist/client/assets/{vennDiagram-CIIHVFJN-B6Sx0OqW.js → vennDiagram-CIIHVFJN-D3XO2rfJ.js} +1 -1
  92. package/dist/client/assets/{wardley-L42UT6IY-_SQFYU4g.js → wardley-L42UT6IY-Dug4bLhv.js} +1 -1
  93. package/dist/client/assets/{wardleyDiagram-YWT4CUSO-Ce5igv6s.js → wardleyDiagram-YWT4CUSO-CtGA4P2g.js} +1 -1
  94. package/dist/client/assets/{xychartDiagram-2RQKCTM6-zI0zyvDP.js → xychartDiagram-2RQKCTM6-D0EMf4Z7.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 +1487 -803
  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
 
@@ -6505,7 +6730,7 @@ function scoreLatinLocale(text2) {
6505
6730
  return { locale: "en", confidence: "low" };
6506
6731
  }
6507
6732
  const ratio = secondScore === 0 ? Infinity : bestScore / secondScore;
6508
- const confidence = bestScore >= 4 && ratio >= 1.6 ? "high" : bestScore >= 2 && ratio >= 1.25 ? "medium" : "low";
6733
+ const confidence = bestScore >= 4 && ratio >= 1.6 ? "high" : bestScore >= 3 && ratio >= 1.5 ? "medium" : "low";
6509
6734
  return { locale: best[0] === "cs" ? "unknown" : best[0], confidence };
6510
6735
  }
6511
6736
  function detectContentLanguage(text2) {
@@ -6647,11 +6872,16 @@ var init_detect_content_language = __esm({
6647
6872
  "there",
6648
6873
  "should"
6649
6874
  ],
6875
+ /*
6876
+ FNXC:TitleSummaryInputLanguage 2026-09-01-21:25:
6877
+ FN-9241 found that English and technical chat prose can contain `plus`, `par`, and `est`.
6878
+ Omit these collision-prone French tokens so a short English message cannot steer title generation
6879
+ toward French; stronger medium confidence keeps the import translation high-confidence behavior unchanged.
6880
+ */
6650
6881
  fr: [
6651
6882
  "les",
6652
6883
  "des",
6653
6884
  "une",
6654
- "est",
6655
6885
  "dans",
6656
6886
  "pour",
6657
6887
  "que",
@@ -6659,8 +6889,6 @@ var init_detect_content_language = __esm({
6659
6889
  "sur",
6660
6890
  "avec",
6661
6891
  "pas",
6662
- "plus",
6663
- "par",
6664
6892
  "sont",
6665
6893
  "cette",
6666
6894
  "aussi",
@@ -11453,35 +11681,6 @@ var init_workflow_ir_types = __esm({
11453
11681
  }
11454
11682
  });
11455
11683
 
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
11684
  // ../core/src/workflows/workflow-extension-types.ts
11486
11685
  function workflowExtensionRegistryId(pluginId, extensionId) {
11487
11686
  return `plugin:${pluginId}:${extensionId}`;
@@ -12477,6 +12676,16 @@ function validateReviewerAgentOverrides(nodes2) {
12477
12676
  if (Array.isArray(templateNodes)) validateReviewerAgentOverrides(templateNodes);
12478
12677
  }
12479
12678
  }
12679
+ function validateReadonlyMcpServersConfig(nodes2) {
12680
+ for (const node of nodes2) {
12681
+ const value = node.config?.readonlyMcpServers;
12682
+ if (value !== void 0 && (!Array.isArray(value) || value.some((name2) => typeof name2 !== "string" || !name2.trim()))) {
12683
+ throw new WorkflowIrError(`Workflow node '${node.id}' readonlyMcpServers must be an array of non-empty strings when present`);
12684
+ }
12685
+ const templateNodes = node.config?.template?.nodes;
12686
+ if (Array.isArray(templateNodes)) validateReadonlyMcpServersConfig(templateNodes);
12687
+ }
12688
+ }
12480
12689
  function validateThinkingLevelConfig(nodes2) {
12481
12690
  for (const node of nodes2) {
12482
12691
  const value = node.config?.thinkingLevel;
@@ -13091,6 +13300,7 @@ function validateV2(ir2) {
13091
13300
  const topLevelIds = new Set(ir2.nodes.map((n) => n.id));
13092
13301
  validateStepExecutePlacement(ir2.nodes);
13093
13302
  validateThinkingLevelConfig(ir2.nodes);
13303
+ validateReadonlyMcpServersConfig(ir2.nodes);
13094
13304
  validateCredentialInstanceIdConfig(ir2.nodes);
13095
13305
  validateReviewerAgentOverrides(ir2.nodes);
13096
13306
  for (const node of ir2.nodes) {
@@ -29161,169 +29371,6 @@ var init_explicit_duplicate_marker = __esm({
29161
29371
  }
29162
29372
  });
29163
29373
 
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
29374
  // ../core/src/mesh/mesh-task-replication.ts
29328
29375
  function buildBootstrapPrompt(taskId, title, description) {
29329
29376
  const heading = title ? `${taskId}: ${title}` : taskId;
@@ -66524,10 +66571,10 @@ var init_migration = __esm({
66524
66571
  return basename6(projectPath);
66525
66572
  }
66526
66573
  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(
66574
+ const { execFile: execFile43 } = await import("node:child_process");
66575
+ const { promisify: promisify91 } = await import("node:util");
66576
+ const execFileAsync37 = promisify91(execFile43);
66577
+ const { stdout } = await execFileAsync37(
66531
66578
  "git",
66532
66579
  ["remote", "get-url", "origin"],
66533
66580
  { cwd: projectPath, timeout: 1e3 }
@@ -68722,10 +68769,10 @@ var init_central_core = __esm({
68722
68769
  */
68723
68770
  async generateProjectName(projectPath) {
68724
68771
  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(
68772
+ const { execFile: execFile43 } = await import("node:child_process");
68773
+ const { promisify: promisify91 } = await import("node:util");
68774
+ const execFileAsync37 = promisify91(execFile43);
68775
+ const { stdout } = await execFileAsync37(
68729
68776
  "git",
68730
68777
  ["remote", "get-url", "origin"],
68731
68778
  { cwd: projectPath, timeout: 5e3 }
@@ -85527,14 +85574,12 @@ async function listWorkflowDefinitionsImpl(store, options) {
85527
85574
  return pluginFiltered;
85528
85575
  }
85529
85576
  async function readAllWorkflowDefinitionsImpl(store) {
85530
- if (store.workflowDefinitionsCache) return store.workflowDefinitionsCache;
85531
85577
  const layer = store.getAsyncLayer();
85532
85578
  if (!layer) {
85533
85579
  throw new Error("workflow definitions: AsyncDataLayer not initialized in backend mode");
85534
85580
  }
85535
85581
  const rows = await listWorkflowRows(layer);
85536
- store.workflowDefinitionsCache = [...BUILTIN_WORKFLOWS, ...rows.map((row) => store.toWorkflowDefinition(row))];
85537
- return store.workflowDefinitionsCache;
85582
+ return [...BUILTIN_WORKFLOWS, ...rows.map((row) => store.toWorkflowDefinition(row))];
85538
85583
  }
85539
85584
  async function getWorkflowDefinitionImpl(store, id) {
85540
85585
  const builtin = getBuiltinWorkflow(id);
@@ -85603,7 +85648,6 @@ function insertWorkflowDefinitionSyncImpl(store, input) {
85603
85648
  if (!isWorkflowDefinitionIdPrimaryKeyCollision(error2)) throw error2;
85604
85649
  continue;
85605
85650
  }
85606
- store.workflowDefinitionsCache = null;
85607
85651
  return definition;
85608
85652
  }
85609
85653
  throw new Error("Unable to allocate a free workflow definition id after repeated id collisions");
@@ -85993,11 +86037,6 @@ function getPluginStoreImpl(store) {
85993
86037
  ...pluginLayer ? { asyncLayer: pluginLayer } : {}
85994
86038
  }
85995
86039
  );
85996
- const clearWorkflowDefinitionCache = () => {
85997
- store.workflowDefinitionsCache = null;
85998
- };
85999
- store.pluginStore.on("plugin:registered", clearWorkflowDefinitionCache);
86000
- store.pluginStore.on("plugin:unregistered", clearWorkflowDefinitionCache);
86001
86040
  }
86002
86041
  return store.pluginStore;
86003
86042
  }
@@ -90369,11 +90408,11 @@ async function searchWithQmd(rootDir, options) {
90369
90408
  const command = "qmd";
90370
90409
  const limit = Math.max(1, Math.min(options.limit ?? 5, 20));
90371
90410
  try {
90372
- const execFileAsync36 = await getDefaultExecFileAsync();
90373
- await ensureQmdProjectMemoryCollection(rootDir, execFileAsync36);
90411
+ const execFileAsync37 = await getDefaultExecFileAsync();
90412
+ await ensureQmdProjectMemoryCollection(rootDir, execFileAsync37);
90374
90413
  scheduleQmdProjectMemoryRefresh(rootDir);
90375
90414
  const args = buildQmdSearchArgs(rootDir, options);
90376
- const { stdout } = await execFileAsync36(command, args, {
90415
+ const { stdout } = await execFileAsync37(command, args, {
90377
90416
  cwd: rootDir,
90378
90417
  timeout: 4e3,
90379
90418
  maxBuffer: 1024 * 1024
@@ -90398,12 +90437,12 @@ async function searchWithQmd(rootDir, options) {
90398
90437
  return [];
90399
90438
  }
90400
90439
  }
90401
- async function ensureQmdProjectMemoryCollection(rootDir, execFileAsync36) {
90440
+ async function ensureQmdProjectMemoryCollection(rootDir, execFileAsync37) {
90402
90441
  const collectionName = qmdMemoryCollectionName(rootDir);
90403
90442
  const memoryDir = memoryWorkspacePath(rootDir);
90404
90443
  await mkdir10(memoryDir, { recursive: true });
90405
90444
  try {
90406
- await execFileAsync36("qmd", buildQmdCollectionAddArgs(rootDir), {
90445
+ await execFileAsync37("qmd", buildQmdCollectionAddArgs(rootDir), {
90407
90446
  cwd: rootDir,
90408
90447
  timeout: 4e3,
90409
90448
  maxBuffer: 512 * 1024
@@ -90501,14 +90540,14 @@ async function refreshQmdProjectMemoryIndex(rootDir, options) {
90501
90540
  }
90502
90541
  }
90503
90542
  const promise = (async () => {
90504
- const execFileAsync36 = options?.execFileAsync ?? await getDefaultExecFileAsync();
90505
- await ensureQmdProjectMemoryCollection(rootDir, execFileAsync36);
90506
- await execFileAsync36("qmd", ["update"], {
90543
+ const execFileAsync37 = options?.execFileAsync ?? await getDefaultExecFileAsync();
90544
+ await ensureQmdProjectMemoryCollection(rootDir, execFileAsync37);
90545
+ await execFileAsync37("qmd", ["update"], {
90507
90546
  cwd: rootDir,
90508
90547
  timeout: 3e4,
90509
90548
  maxBuffer: 1024 * 1024
90510
90549
  });
90511
- await execFileAsync36("qmd", ["embed"], {
90550
+ await execFileAsync37("qmd", ["embed"], {
90512
90551
  cwd: rootDir,
90513
90552
  timeout: 12e4,
90514
90553
  maxBuffer: 1024 * 1024
@@ -90544,12 +90583,12 @@ async function refreshQmdAgentMemoryIndex(rootDir, agentId, options) {
90544
90583
  }
90545
90584
  }
90546
90585
  const promise = (async () => {
90547
- const execFileAsync36 = options?.execFileAsync ?? await getDefaultExecFileAsync();
90586
+ const execFileAsync37 = options?.execFileAsync ?? await getDefaultExecFileAsync();
90548
90587
  const { agentMemoryWorkspacePath: agentMemoryWorkspacePath2 } = await Promise.resolve().then(() => (init_memory_dreams(), memory_dreams_exports));
90549
90588
  const workspacePath = agentMemoryWorkspacePath2(rootDir, agentId);
90550
90589
  await mkdir10(workspacePath, { recursive: true });
90551
90590
  try {
90552
- await execFileAsync36("qmd", ["collection", "add", workspacePath, "--name", qmdAgentMemoryCollectionName(rootDir, agentId), "--mask", "**/*.md"], {
90591
+ await execFileAsync37("qmd", ["collection", "add", workspacePath, "--name", qmdAgentMemoryCollectionName(rootDir, agentId), "--mask", "**/*.md"], {
90553
90592
  cwd: rootDir,
90554
90593
  timeout: 4e3,
90555
90594
  maxBuffer: 512 * 1024
@@ -90562,8 +90601,8 @@ ${stderr}`)) {
90562
90601
  throw err;
90563
90602
  }
90564
90603
  }
90565
- await execFileAsync36("qmd", ["update"], { cwd: rootDir, timeout: 3e4, maxBuffer: 1024 * 1024 });
90566
- await execFileAsync36("qmd", ["embed"], { cwd: rootDir, timeout: 12e4, maxBuffer: 1024 * 1024 });
90604
+ await execFileAsync37("qmd", ["update"], { cwd: rootDir, timeout: 3e4, maxBuffer: 1024 * 1024 });
90605
+ await execFileAsync37("qmd", ["embed"], { cwd: rootDir, timeout: 12e4, maxBuffer: 1024 * 1024 });
90567
90606
  })();
90568
90607
  qmdAgentRefreshState.set(key, { lastStartedAt: now, inFlight: promise });
90569
90608
  try {
@@ -90584,8 +90623,8 @@ function scheduleQmdAgentMemoryRefresh(rootDir, agentId) {
90584
90623
  }
90585
90624
  async function isQmdAvailable() {
90586
90625
  try {
90587
- const execFileAsync36 = await getDefaultExecFileAsync();
90588
- await execFileAsync36("qmd", ["--help"], {
90626
+ const execFileAsync37 = await getDefaultExecFileAsync();
90627
+ await execFileAsync37("qmd", ["--help"], {
90589
90628
  timeout: 3e3,
90590
90629
  maxBuffer: 128 * 1024,
90591
90630
  keepAlive: true
@@ -90596,12 +90635,12 @@ async function isQmdAvailable() {
90596
90635
  }
90597
90636
  }
90598
90637
  async function installQmd(options) {
90599
- const execFileAsync36 = options?.execFileAsync ?? await getDefaultExecFileAsync();
90638
+ const execFileAsync37 = options?.execFileAsync ?? await getDefaultExecFileAsync();
90600
90639
  const [command, ...args] = QMD_INSTALL_COMMAND.split(" ");
90601
90640
  if (!command || args.length === 0) {
90602
90641
  throw new MemoryBackendError("BACKEND_UNAVAILABLE", "qmd install command is not configured", "qmd");
90603
90642
  }
90604
- await execFileAsync36(command, args, {
90643
+ await execFileAsync37(command, args, {
90605
90644
  timeout: 12e4,
90606
90645
  maxBuffer: 1024 * 1024
90607
90646
  });
@@ -94978,7 +95017,7 @@ Your ONLY job is to create a concise title (max 60 characters) that summarizes t
94978
95017
 
94979
95018
  ## Style
94980
95019
  - Clear, descriptive, actionable, professional
94981
- - Write the title in the SAME language as the task description content. For example, a French description requires a French title.
95020
+ - Write the title in the SAME language as the task description content.
94982
95021
  - Maximum 60 characters
94983
95022
  - Focus on the main goal or deliverable of the task`;
94984
95023
  MAX_DESCRIPTION_LENGTH = 2e3;
@@ -100116,7 +100155,6 @@ async function createWorkflowDefinitionImpl(store, input) {
100116
100155
  if (!isWorkflowDefinitionIdPrimaryKeyCollision(error2)) throw error2;
100117
100156
  continue;
100118
100157
  }
100119
- store.workflowDefinitionsCache = null;
100120
100158
  return definition;
100121
100159
  }
100122
100160
  throw new Error("Unable to allocate a free workflow definition id after repeated id collisions");
@@ -103700,7 +103738,6 @@ async function updateWorkflowDefinitionImpl(store, id, updates) {
103700
103738
  eq(project_exports.workflows.id, id),
103701
103739
  projectScopeFor(project_exports.workflows.projectId, layer.projectId)
103702
103740
  ));
103703
- store.workflowDefinitionsCache = null;
103704
103741
  return next;
103705
103742
  });
103706
103743
  if (pendingRehome) {
@@ -103735,7 +103772,6 @@ async function deleteWorkflowDefinitionImpl(store, id) {
103735
103772
  projectScopeFor(project_exports.workflows.projectId, layer.projectId)
103736
103773
  )).returning();
103737
103774
  if (deleted.length === 0) throw new Error(`Workflow '${id}' not found`);
103738
- store.workflowDefinitionsCache = null;
103739
103775
  await layer.db.delete(project_exports.workflowSettings).where(and(
103740
103776
  eq(project_exports.workflowSettings.workflowId, id),
103741
103777
  projectScopeFor(project_exports.workflowSettings.projectId, layer.projectId)
@@ -108329,7 +108365,6 @@ var init_store = __esm({
108329
108365
  taskIdIntegrityReport = { status: "ok", checkedAt: (/* @__PURE__ */ new Date()).toISOString(), anomalies: [] };
108330
108366
  lastTaskIdIntegrityLogSignature = null;
108331
108367
  workflowStepsCache = null;
108332
- workflowDefinitionsCache = null;
108333
108368
  _pluginWorkflowStepTemplates = [];
108334
108369
  globalSettingsStore;
108335
108370
  donePauseBackfillDone = false;
@@ -115959,7 +115994,7 @@ function getPostMergeFinalizeBlocker(task) {
115959
115994
  }
115960
115995
  function planConfirmedMergeChecklistReconciliation(task) {
115961
115996
  return {
115962
- skippedStepIndexes: task.steps.map((step, index2) => step.status === "pending" || step.status === "in-progress" ? index2 : -1).filter((index2) => index2 >= 0),
115997
+ skippedStepIndexes: (task.steps ?? []).map((step, index2) => step.status === "pending" || step.status === "in-progress" ? index2 : -1).filter((index2) => index2 >= 0),
115963
115998
  reconciledWorkflowStepIds: (task.workflowStepResults ?? []).filter((result) => result.status === "pending").map((result) => result.workflowStepId)
115964
115999
  };
115965
116000
  }
@@ -136009,11 +136044,11 @@ var require_extract_zip = __commonJS({
136009
136044
  var { createWriteStream, promises: fs10 } = __require("fs");
136010
136045
  var getStream = require_get_stream();
136011
136046
  var path6 = __require("path");
136012
- var { promisify: promisify90 } = __require("util");
136047
+ var { promisify: promisify91 } = __require("util");
136013
136048
  var stream = __require("stream");
136014
136049
  var yauzl = require_yauzl();
136015
- var openZip = promisify90(yauzl.open);
136016
- var pipeline = promisify90(stream.pipeline);
136050
+ var openZip = promisify91(yauzl.open);
136051
+ var pipeline = promisify91(stream.pipeline);
136017
136052
  var Extractor = class {
136018
136053
  constructor(zipPath, opts) {
136019
136054
  this.zipPath = zipPath;
@@ -136095,7 +136130,7 @@ var require_extract_zip = __commonJS({
136095
136130
  await fs10.mkdir(destDir, mkdirOptions);
136096
136131
  if (isDir) return;
136097
136132
  debug("opening read stream", dest);
136098
- const readStream = await promisify90(this.zipfile.openReadStream.bind(this.zipfile))(entry);
136133
+ const readStream = await promisify91(this.zipfile.openReadStream.bind(this.zipfile))(entry);
136099
136134
  if (symlink) {
136100
136135
  const link = await getStream(readStream);
136101
136136
  debug("creating symlink", link, dest);
@@ -137534,6 +137569,7 @@ async function updateChatSession(handle2, id, input) {
137534
137569
  if (input.status !== void 0) setValues.status = input.status;
137535
137570
  if (input.modelProvider !== void 0) setValues.modelProvider = input.modelProvider;
137536
137571
  if (input.modelId !== void 0) setValues.modelId = input.modelId;
137572
+ if (input.agentId !== void 0) setValues.agentId = input.agentId;
137537
137573
  if (input.thinkingLevel !== void 0) setValues.thinkingLevel = input.thinkingLevel;
137538
137574
  if (input.memoryFocus !== void 0) setValues.memoryFocus = input.memoryFocus;
137539
137575
  if (input.pinnedAt !== void 0) setValues.pinnedAt = input.pinnedAt;
@@ -138587,6 +138623,7 @@ var init_cli_session_store = __esm({
138587
138623
  });
138588
138624
 
138589
138625
  // ../core/src/secrets/oauth-credential-interop.ts
138626
+ import { createHash as createHash22 } from "node:crypto";
138590
138627
  import { existsSync as existsSync40, readFileSync as readFileSync11 } from "node:fs";
138591
138628
  import { homedir as homedir7 } from "node:os";
138592
138629
  import { join as join55 } from "node:path";
@@ -138646,6 +138683,22 @@ function getLastRefreshFallbackExpiryMs(lastRefresh) {
138646
138683
  }
138647
138684
  return parsed + CODEX_REFRESH_FALLBACK_WINDOW_MS;
138648
138685
  }
138686
+ function isSameStoredCredentialMaterial(left, right) {
138687
+ if (!left || !right || left.type !== right.type) {
138688
+ return false;
138689
+ }
138690
+ return left.key === right.key && left.access === right.access && left.refresh === right.refresh;
138691
+ }
138692
+ function computeStoredCredentialAccountFingerprint(credential) {
138693
+ if (!credential) {
138694
+ return void 0;
138695
+ }
138696
+ 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;
138697
+ if (!material) {
138698
+ return void 0;
138699
+ }
138700
+ return createHash22("sha256").update(`${credential.type ?? ""}\0${material}`).digest("hex").slice(0, 16);
138701
+ }
138649
138702
  function isStoredAuthCredential(value) {
138650
138703
  if (!value || typeof value !== "object" || Array.isArray(value)) {
138651
138704
  return false;
@@ -139616,7 +139669,7 @@ var init_task_timing = __esm({
139616
139669
  });
139617
139670
 
139618
139671
  // ../core/src/task-recommendation-notice.ts
139619
- import { createHash as createHash22 } from "node:crypto";
139672
+ import { createHash as createHash23 } from "node:crypto";
139620
139673
  function registerTaskRecommendationNoticeMailbox(store, mailbox) {
139621
139674
  mailboxes2.set(store, mailbox);
139622
139675
  return () => {
@@ -139641,7 +139694,7 @@ function buildTaskRecommendationNoticeContent(task, recommendations) {
139641
139694
  }
139642
139695
  function buildTaskRecommendationNoticeIdempotencyKey(taskId, recommendations) {
139643
139696
  const ids = recommendations.map(({ id }) => id).sort().join("\n");
139644
- const digest = createHash22("sha256").update(ids, "utf8").digest("hex").slice(0, 16);
139697
+ const digest = createHash23("sha256").update(ids, "utf8").digest("hex").slice(0, 16);
139645
139698
  return `task-recommendation-notice:${taskId}:${digest}`;
139646
139699
  }
139647
139700
  async function notifyOperatorOfTaskRecommendations(store, task, recommendations, settings) {
@@ -140087,6 +140140,7 @@ __export(src_exports, {
140087
140140
  APPROVAL_REQUEST_STATUSES: () => APPROVAL_REQUEST_STATUSES,
140088
140141
  ARCHIVED_TASK_DOCUMENT_ADDITION_BOUNDARY: () => ARCHIVED_TASK_DOCUMENT_ADDITION_BOUNDARY,
140089
140142
  ARCHIVED_TASK_DOCUMENT_PUBLICATION_REJECTED: () => ARCHIVED_TASK_DOCUMENT_PUBLICATION_REJECTED,
140143
+ AUTOMATED_BYPASS_ACTORS: () => AUTOMATED_BYPASS_ACTORS,
140090
140144
  AUTOMATION_PRESETS: () => AUTOMATION_PRESETS,
140091
140145
  AUTOMATION_SELECTABLE_TOOLS: () => AUTOMATION_SELECTABLE_TOOLS,
140092
140146
  AUTOPILOT_STATES: () => AUTOPILOT_STATES,
@@ -140282,6 +140336,7 @@ __export(src_exports, {
140282
140336
  FAST_LANE_SKIP_VALUE: () => FAST_LANE_SKIP_VALUE,
140283
140337
  FAST_LANE_STEP_NAME: () => FAST_LANE_STEP_NAME,
140284
140338
  FAST_LANE_STEP_REVIEW_ROUTE_VALUE: () => FAST_LANE_STEP_REVIEW_ROUTE_VALUE,
140339
+ FAST_MODE_BYPASS_ACTOR: () => FAST_MODE_BYPASS_ACTOR,
140285
140340
  FEATURE_LOOP_REPAIR_TRANSITIONS: () => FEATURE_LOOP_REPAIR_TRANSITIONS,
140286
140341
  FEATURE_LOOP_STATES: () => FEATURE_LOOP_STATES,
140287
140342
  FEATURE_STATUSES: () => FEATURE_STATUSES,
@@ -140846,6 +140901,7 @@ __export(src_exports, {
140846
140901
  computeRetrySummary: () => computeRetrySummary,
140847
140902
  computeSkillId: () => computeSkillId,
140848
140903
  computeSnapshotChecksum: () => computeSnapshotChecksum,
140904
+ computeStoredCredentialAccountFingerprint: () => computeStoredCredentialAccountFingerprint,
140849
140905
  computeWorkflowIrPin: () => computeWorkflowIrPin,
140850
140906
  configurationTargetKey: () => configurationTargetKey,
140851
140907
  configureApprovalRequestTtls: () => configureApprovalRequestTtls,
@@ -141168,6 +141224,7 @@ __export(src_exports, {
141168
141224
  isApprovalRequestExpired: () => isApprovalRequestExpired,
141169
141225
  isArchivedColumnRole: () => isArchivedColumnRole,
141170
141226
  isArchivedRemediationCarrier: () => isArchivedRemediationCarrier,
141227
+ isAuditedOperatorBypass: () => isAuditedOperatorBypass,
141171
141228
  isBlockingFinding: () => isBlockingFinding,
141172
141229
  isBranchGroupComplete: () => isBranchGroupComplete,
141173
141230
  isBranchGroupMemberLanded: () => isBranchGroupMemberLanded,
@@ -141255,6 +141312,7 @@ __export(src_exports, {
141255
141312
  isReviewColumnRole: () => isReviewColumnRole,
141256
141313
  isRunRecord: () => isRunRecord,
141257
141314
  isRunningAgentTask: () => isRunningAgentTask,
141315
+ isSameStoredCredentialMaterial: () => isSameStoredCredentialMaterial,
141258
141316
  isSanctionedEngineBackwardMove: () => isSanctionedEngineBackwardMove,
141259
141317
  isSandboxExperimentalEnabled: () => isSandboxExperimentalEnabled,
141260
141318
  isSecretAccessPolicy: () => isSecretAccessPolicy,
@@ -141533,6 +141591,7 @@ __export(src_exports, {
141533
141591
  renderTriagePolicyPlaceholders: () => renderTriagePolicyPlaceholders,
141534
141592
  renderValidationCause: () => renderValidationCause,
141535
141593
  renderValidationFailureDescription: () => renderValidationFailureDescription,
141594
+ requiresContentReviewProof: () => requiresContentReviewProof,
141536
141595
  requiresPlannerConfirmation: () => requiresPlannerConfirmation,
141537
141596
  requiresWorktrunkInstallVerification: () => requiresWorktrunkInstallVerification,
141538
141597
  resetGhAvailabilityCache: () => resetGhAvailabilityCache,
@@ -141696,6 +141755,7 @@ __export(src_exports, {
141696
141755
  resolveTitleSummarizerSettingsModel: () => resolveTitleSummarizerSettingsModel,
141697
141756
  resolveToolOutputBudget: () => resolveToolOutputBudget,
141698
141757
  resolveTrailingVerificationStepIndex: () => resolveTrailingVerificationStepIndex,
141758
+ resolveUnprovenReviewApproval: () => resolveUnprovenReviewApproval,
141699
141759
  resolveUpdateAutomationSettings: () => resolveUpdateAutomationSettings,
141700
141760
  resolveUpdateTargetVersion: () => resolveUpdateTargetVersion,
141701
141761
  resolveUpdatesExternallyManaged: () => resolveUpdatesExternallyManaged,
@@ -145177,18 +145237,25 @@ function parseWorktreesFromPorcelain(porcelain) {
145177
145237
  if (current.path) rows.push({ path: current.path, branch: current.branch });
145178
145238
  return rows;
145179
145239
  }
145240
+ function isRegenerableIgnoredPorcelainEntry(entry) {
145241
+ const path6 = entry.slice(3).replace(/\/$/, "");
145242
+ if (!path6 || path6.startsWith('"')) return false;
145243
+ const lastSegment = path6.split("/").at(-1);
145244
+ return lastSegment !== void 0 && REGENERABLE_IGNORED_DIR_NAMES.has(lastSegment);
145245
+ }
145180
145246
  function classifyWorktreeRemovalContent(porcelain) {
145181
145247
  const entries = porcelain.split(/\r?\n/).filter((line2) => line2.trim().length > 0);
145182
145248
  if (entries.length === 0) return "clean";
145183
- return entries.every((line2) => line2.startsWith("!!")) ? "ignored-only" : "deliverable";
145249
+ if (entries.some((line2) => !line2.startsWith("!! "))) return "deliverable";
145250
+ return entries.every(isRegenerableIgnoredPorcelainEntry) ? "regenerable-ignored" : "ignored-only";
145184
145251
  }
145185
145252
  async function assertCleanForDefensiveRemoval(worktreePath) {
145186
145253
  if (!existsSync42(worktreePath)) {
145187
- return "clean";
145254
+ return { classification: "clean", entryCount: 0 };
145188
145255
  }
145189
145256
  let stdout;
145190
145257
  try {
145191
- ({ stdout } = await execFileAsync7("git", ["status", "--porcelain=v1", "--ignored", "--untracked-files=normal"], {
145258
+ ({ stdout } = await execFileAsync7("git", ["status", "--porcelain=v1", "--ignored=matching", "--untracked-files=normal"], {
145192
145259
  cwd: worktreePath,
145193
145260
  encoding: "utf-8",
145194
145261
  timeout: 15e3,
@@ -145201,7 +145268,10 @@ async function assertCleanForDefensiveRemoval(worktreePath) {
145201
145268
  if (classification === "deliverable") {
145202
145269
  throw new Error(`preserving ${worktreePath}: uncommitted or ignored content present`);
145203
145270
  }
145204
- return classification;
145271
+ return {
145272
+ classification,
145273
+ entryCount: stdout.split(/\r?\n/).filter((line2) => line2.trim().length > 0).length
145274
+ };
145205
145275
  }
145206
145276
  async function removeWorktree(input) {
145207
145277
  const logger3 = {
@@ -145218,9 +145288,12 @@ async function removeWorktree(input) {
145218
145288
  }
145219
145289
  const requiresCleanWorktree = DEFENSIVE_REMOVAL_REASONS.has(input.reason);
145220
145290
  let contentClassification;
145291
+ let contentEntryCount = 0;
145221
145292
  if (requiresCleanWorktree) {
145222
145293
  try {
145223
- contentClassification = await assertCleanForDefensiveRemoval(input.worktreePath);
145294
+ const contentProbe = await assertCleanForDefensiveRemoval(input.worktreePath);
145295
+ contentClassification = contentProbe.classification;
145296
+ contentEntryCount = contentProbe.entryCount;
145224
145297
  } catch (error2) {
145225
145298
  const classification = error2 instanceof Error && error2.message.includes(": status probe failed (") ? "unverifiable" : "deliverable";
145226
145299
  await input.audit?.git({
@@ -145237,6 +145310,17 @@ async function removeWorktree(input) {
145237
145310
  throw error2;
145238
145311
  }
145239
145312
  if (contentClassification === "ignored-only" && !input.postLandingProof) {
145313
+ await input.audit?.git({
145314
+ type: "worktree:removal-preserved",
145315
+ target: input.worktreePath,
145316
+ metadata: {
145317
+ taskId: input.taskId,
145318
+ reason: input.reason,
145319
+ source: void 0,
145320
+ classification: "ignored-only",
145321
+ hasPostLandingProof: false
145322
+ }
145323
+ }).catch(() => void 0);
145240
145324
  throw new Error(`preserving ${input.worktreePath}: uncommitted or ignored content present`);
145241
145325
  }
145242
145326
  }
@@ -145256,6 +145340,17 @@ async function removeWorktree(input) {
145256
145340
  } catch {
145257
145341
  }
145258
145342
  };
145343
+ const recordRegenerableDiscard = async () => {
145344
+ if (contentClassification !== "regenerable-ignored") return;
145345
+ try {
145346
+ await input.audit?.git({
145347
+ type: "worktree:removal-discarded-regenerable-content",
145348
+ target: input.worktreePath,
145349
+ metadata: { taskId: input.taskId, reason: input.reason, entryCount: contentEntryCount }
145350
+ });
145351
+ } catch {
145352
+ }
145353
+ };
145259
145354
  if (input.expectedOwnerTaskId && input.liveOwnerProbe) {
145260
145355
  const reconciled = reconcileSelfOwnedActiveSessionForRemoval(
145261
145356
  activeSessionRegistry,
@@ -145309,6 +145404,7 @@ async function removeWorktree(input) {
145309
145404
  try {
145310
145405
  await backend.remove(removeInput);
145311
145406
  await recordIgnoredOnlyDiscard();
145407
+ await recordRegenerableDiscard();
145312
145408
  if (input.audit) {
145313
145409
  await input.audit.git({
145314
145410
  type: backend.kind === "worktrunk" ? "worktree:worktrunk-remove" : "worktree:remove",
@@ -145336,6 +145432,7 @@ async function removeWorktree(input) {
145336
145432
  try {
145337
145433
  await native.remove(removeInput);
145338
145434
  await recordIgnoredOnlyDiscard();
145435
+ await recordRegenerableDiscard();
145339
145436
  await input.audit?.git({ type: "worktree:remove", target: input.worktreePath });
145340
145437
  return { removed: true, classification: "removed" };
145341
145438
  } catch (nativeError) {
@@ -145357,7 +145454,7 @@ function resolveWorktreeBackend(settings, deps = {}) {
145357
145454
  }
145358
145455
  return new NativeWorktreeBackend({ logger: deps.logger, settings, audit: deps.audit });
145359
145456
  }
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;
145457
+ 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
145458
  var init_worktree_backend = __esm({
145362
145459
  "../engine/src/worktree/worktree-backend.ts"() {
145363
145460
  "use strict";
@@ -146097,6 +146194,27 @@ ${getErrorStderr(error2) ?? ""}`;
146097
146194
  this.name = "ActiveSessionWorktreeRemovalError";
146098
146195
  }
146099
146196
  };
146197
+ REGENERABLE_IGNORED_DIR_NAMES = /* @__PURE__ */ new Set([
146198
+ "node_modules",
146199
+ "dist",
146200
+ "build",
146201
+ "out",
146202
+ "target",
146203
+ "bin",
146204
+ "obj",
146205
+ "coverage",
146206
+ ".next",
146207
+ ".nuxt",
146208
+ ".turbo",
146209
+ ".svelte-kit",
146210
+ ".parcel-cache",
146211
+ ".pytest_cache",
146212
+ ".mypy_cache",
146213
+ "__pycache__",
146214
+ ".gradle",
146215
+ ".venv",
146216
+ "venv"
146217
+ ]);
146100
146218
  }
146101
146219
  });
146102
146220
 
@@ -146718,6 +146836,14 @@ function resolveAiMergeRootPath(rootDir, settings) {
146718
146836
  function resolveLegacyAiMergeRootPath(rootDir) {
146719
146837
  return join59(rootDir, ".fusion", "ai-merge");
146720
146838
  }
146839
+ function resolveAiMergeSearchRoots(rootDir, settings) {
146840
+ const roots = [
146841
+ resolveAiMergeRootPath(rootDir, settings),
146842
+ resolveLegacyAiMergeRootPath(rootDir),
146843
+ ...settings?.worktreesDir ? [] : [join59(resolveLegacyWorktreesDirLayout(rootDir), AI_MERGE_DIRNAME)]
146844
+ ];
146845
+ return [...new Set(roots)];
146846
+ }
146721
146847
  function resolveWorktreesDir(rootDir, settings, workspaceContext) {
146722
146848
  return resolveWorktreesDirLayout(rootDir, settings, workspaceContext);
146723
146849
  }
@@ -148194,7 +148320,7 @@ var init_plan_review_continuation = __esm({
148194
148320
  });
148195
148321
 
148196
148322
  // ../engine/src/execution/hold-release.ts
148197
- import { createHash as createHash24 } from "node:crypto";
148323
+ import { createHash as createHash25 } from "node:crypto";
148198
148324
  import { readFile as readFile36 } from "node:fs/promises";
148199
148325
  function findColumn2(ir2, columnId) {
148200
148326
  if (ir2.version !== "v2") return void 0;
@@ -148232,9 +148358,9 @@ async function checkAndRecordUnplannedExecutionBlock(store, task, ir2) {
148232
148358
  promptContent = "";
148233
148359
  }
148234
148360
  }
148235
- const promptMarker = promptContent.length > 0 ? createHash24("sha256").update(promptContent).digest("hex") : "missing";
148361
+ const promptMarker = promptContent.length > 0 ? createHash25("sha256").update(promptContent).digest("hex") : "missing";
148236
148362
  const dependencies = [...task.dependencies ?? []].sort();
148237
- const episode = createHash24("sha256").update(JSON.stringify({
148363
+ const episode = createHash25("sha256").update(JSON.stringify({
148238
148364
  planReviewNode,
148239
148365
  promptMarker,
148240
148366
  dependencies,
@@ -153237,6 +153363,15 @@ async function withOAuthRefreshLock(authPath, providerId, refresh) {
153237
153363
  await release3();
153238
153364
  }
153239
153365
  }
153366
+ async function withOAuthInstanceLoginLock(authPath, providerId, operation) {
153367
+ const safeProviderId = providerId.replace(/[^a-zA-Z0-9._-]/g, "_");
153368
+ const release3 = await import_proper_lockfile.default.lock(`${authPath}.${safeProviderId}.instance-login`, AUTH_LOCK_OPTIONS);
153369
+ try {
153370
+ return await operation();
153371
+ } finally {
153372
+ await release3();
153373
+ }
153374
+ }
153240
153375
  function readDefaultInstanceMap(data) {
153241
153376
  const candidate = data.__fusionDefaultInstances;
153242
153377
  if (!candidate || typeof candidate !== "object" || Array.isArray(candidate)) return {};
@@ -153526,7 +153661,7 @@ function createFusionAuthStorage() {
153526
153661
  primary.reload();
153527
153662
  const selectPersistedRefreshCredential = () => {
153528
153663
  const storedCredential = primary.get(storageProvider);
153529
- if (refreshLockProvider !== ANTHROPIC_PROVIDER_ID2) {
153664
+ if (storedCredential?.type === "oauth" || refreshLockProvider !== ANTHROPIC_PROVIDER_ID2) {
153530
153665
  return storedCredential;
153531
153666
  }
153532
153667
  const legacyCredential = primary.get(ANTHROPIC_PROVIDER_ID2);
@@ -153656,18 +153791,16 @@ function createFusionAuthStorage() {
153656
153791
  if (rawKey) return rawKey;
153657
153792
  }
153658
153793
  const subscriptionLoggedOut = loggedOutProviders.has(ANTHROPIC_SUBSCRIPTION_PROVIDER_ID);
153659
- const legacyAnthropicOAuthCredential = rawProviderLoggedOut ? void 0 : selectStoredCredentialByType(ANTHROPIC_PROVIDER_ID2, "oauth");
153794
+ const subscriptionCredential = subscriptionLoggedOut ? void 0 : selectStoredCredential(ANTHROPIC_SUBSCRIPTION_PROVIDER_ID);
153795
+ if (subscriptionCredential?.type === "oauth") {
153796
+ const subscriptionKey = await resolveRefreshableCredentialApiKey(ANTHROPIC_SUBSCRIPTION_PROVIDER_ID, subscriptionCredential);
153797
+ if (subscriptionKey) return subscriptionKey;
153798
+ }
153799
+ const legacyAnthropicOAuthCredential = rawProviderLoggedOut || subscriptionCredential ? void 0 : selectStoredCredentialByType(ANTHROPIC_PROVIDER_ID2, "oauth");
153660
153800
  if (!subscriptionLoggedOut && legacyAnthropicOAuthCredential) {
153661
153801
  const legacyKey = await resolveRefreshableCredentialApiKey(ANTHROPIC_PROVIDER_ID2, legacyAnthropicOAuthCredential);
153662
153802
  if (legacyKey) return legacyKey;
153663
153803
  }
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
153804
  if (preferSubscription) {
153672
153805
  const rawKey = resolveRawApiKey();
153673
153806
  if (rawKey) return rawKey;
@@ -153844,8 +153977,15 @@ function createFusionAuthStorage() {
153844
153977
  return void 0;
153845
153978
  }
153846
153979
  if (sourceProvider !== ANTHROPIC_SUBSCRIPTION_PROVIDER_ID) {
153847
- await primary.set(ANTHROPIC_SUBSCRIPTION_PROVIDER_ID, subscriptionCredential);
153848
- loggedOutProviders.delete(ANTHROPIC_SUBSCRIPTION_PROVIDER_ID);
153980
+ const legacyKey = await resolveRefreshableCredentialApiKey(ANTHROPIC_PROVIDER_ID2, subscriptionCredential);
153981
+ primary.reload();
153982
+ const storedSubscription = primary.get(ANTHROPIC_SUBSCRIPTION_PROVIDER_ID);
153983
+ const refreshedLegacyCredential = primary.get(ANTHROPIC_PROVIDER_ID2);
153984
+ if (!storedSubscription && refreshedLegacyCredential?.type === "oauth") {
153985
+ await primary.set(ANTHROPIC_SUBSCRIPTION_PROVIDER_ID, refreshedLegacyCredential);
153986
+ loggedOutProviders.delete(ANTHROPIC_SUBSCRIPTION_PROVIDER_ID);
153987
+ }
153988
+ return legacyKey;
153849
153989
  }
153850
153990
  return resolveRefreshableCredentialApiKey(ANTHROPIC_SUBSCRIPTION_PROVIDER_ID, subscriptionCredential);
153851
153991
  }
@@ -153890,6 +154030,9 @@ var init_auth_storage = __esm({
153890
154030
  }
153891
154031
  data = {};
153892
154032
  modelRuntime;
154033
+ async withProviderInstanceLoginLock(providerId, operation) {
154034
+ return withOAuthInstanceLoginLock(this.authPath, providerId, operation);
154035
+ }
153893
154036
  ensureFile() {
153894
154037
  const parent = dirname27(this.authPath);
153895
154038
  if (!existsSync47(parent)) mkdirSync7(parent, { recursive: true, mode: 448 });
@@ -154596,7 +154739,7 @@ var init_gating_classifications = __esm({
154596
154739
  });
154597
154740
 
154598
154741
  // ../engine/src/agents/agent-action-gate.ts
154599
- import { createHash as createHash25 } from "node:crypto";
154742
+ import { createHash as createHash26 } from "node:crypto";
154600
154743
  function getExemptTools() {
154601
154744
  if (!_exemptTools) {
154602
154745
  _exemptTools = new Set(DEFAULT_EXEMPT_TOOLS);
@@ -154630,7 +154773,7 @@ function evaluateAgentActionGate(params) {
154630
154773
  if (params.toolName === "bash") {
154631
154774
  const command = extractShellCommand(args);
154632
154775
  const git10 = classifyGitCommand(command);
154633
- resourceId = command ? `cmd:${createHash25("sha256").update(command).digest("hex").slice(0, 16)}` : void 0;
154776
+ resourceId = command ? `cmd:${createHash26("sha256").update(command).digest("hex").slice(0, 16)}` : void 0;
154634
154777
  if (git10?.write) {
154635
154778
  category = "git_write";
154636
154779
  operation = git10.operation;
@@ -176496,6 +176639,7 @@ async function connectMcpSessionTools(servers, opts = {}) {
176496
176639
  const clients = [];
176497
176640
  const closedClients = /* @__PURE__ */ new WeakSet();
176498
176641
  const usedToolNames = /* @__PURE__ */ new Set();
176642
+ const serverByToolName = /* @__PURE__ */ new Map();
176499
176643
  let disposed = false;
176500
176644
  const closeOnce = async (client) => {
176501
176645
  if (closedClients.has(client)) return;
@@ -176545,7 +176689,9 @@ async function connectMcpSessionTools(servers, opts = {}) {
176545
176689
  opts.logger?.log?.(connectMsg);
176546
176690
  }
176547
176691
  for (const tool of listedTools) {
176548
- tools.push(wrapMcpTool(server.name, tool, client, usedToolNames));
176692
+ const wrappedTool = wrapMcpTool(server.name, tool, client, usedToolNames);
176693
+ tools.push(wrappedTool);
176694
+ serverByToolName.set(wrappedTool.name, server.name);
176549
176695
  }
176550
176696
  break;
176551
176697
  } catch (error2) {
@@ -176572,7 +176718,7 @@ async function connectMcpSessionTools(servers, opts = {}) {
176572
176718
  await closeAll();
176573
176719
  }
176574
176720
  }
176575
- return { tools, connected, skipped, dispose: closeAll };
176721
+ return { tools, connected, skipped, serverByToolName, dispose: closeAll };
176576
176722
  }
176577
176723
  function normalizeMaxAttempts(value) {
176578
176724
  if (value === void 0 || !Number.isFinite(value)) return DEFAULT_MAX_ATTEMPTS;
@@ -179706,8 +179852,10 @@ async function createPiAgentSessionRaw(options) {
179706
179852
  const createSessionWithModel = async (modelOverride) => {
179707
179853
  let mcpToolset;
179708
179854
  const allowReadonlyMcpTools = options.allowMcpToolsInReadonly === true;
179709
- if (forwardedMcpServers.length > 0 && (!isReadonly || allowReadonlyMcpTools)) {
179710
- mcpToolset = await connectMcpSessionTools(forwardedMcpServers, {
179855
+ const readonlyMcpServerAllowlist = options.readonlyMcpServerAllowlist === void 0 ? void 0 : new Set(options.readonlyMcpServerAllowlist.map((name2) => name2.trim()).filter(Boolean));
179856
+ const mcpServersToConnect = isReadonly && allowReadonlyMcpTools && readonlyMcpServerAllowlist !== void 0 ? forwardedMcpServers.filter((server) => readonlyMcpServerAllowlist.has(server.name)) : forwardedMcpServers;
179857
+ if (mcpServersToConnect.length > 0 && (!isReadonly || allowReadonlyMcpTools)) {
179858
+ mcpToolset = await connectMcpSessionTools(mcpServersToConnect, {
179711
179859
  cwd: options.cwd,
179712
179860
  clientFactory: options.mcpClientFactory,
179713
179861
  logger: piLog,
@@ -179718,7 +179866,8 @@ async function createPiAgentSessionRaw(options) {
179718
179866
  piLog.warn(`MCP session continuing with unavailable servers: count=${bootstrapFailures.length}`);
179719
179867
  }
179720
179868
  } else if (forwardedMcpServers.length > 0 && isReadonly) {
179721
- piLog.debug(`readonly session \u2014 MCP servers (${forwardedMcpServers.length}) skipped`);
179869
+ const reason = !allowReadonlyMcpTools ? "no-readonly-opt-in" : "readonly-allowlist-no-match";
179870
+ piLog.log(`readonly session \u2014 MCP servers (${forwardedMcpServers.length}) skipped: reason=${reason}`);
179722
179871
  }
179723
179872
  const mcpReadonlyTools = new Set(mcpToolset?.tools ?? []);
179724
179873
  const candidateCustomTools = [
@@ -179727,7 +179876,9 @@ async function createPiAgentSessionRaw(options) {
179727
179876
  ];
179728
179877
  const readonlyFilteredCustomTools = isReadonly ? filterCustomToolsForReadonly(
179729
179878
  candidateCustomTools,
179730
- allowReadonlyMcpTools ? { allowTool: (tool) => mcpReadonlyTools.has(tool) } : {}
179879
+ allowReadonlyMcpTools ? {
179880
+ allowTool: (tool) => readonlyMcpServerAllowlist === void 0 ? mcpReadonlyTools.has(tool) : readonlyMcpServerAllowlist.has(mcpToolset?.serverByToolName?.get(tool.name) ?? "")
179881
+ } : {}
179731
179882
  ) : { allowed: candidateCustomTools, denied: [] };
179732
179883
  const allowlistFilteredCustomTools = {
179733
179884
  ...readonlyFilteredCustomTools,
@@ -180197,7 +180348,7 @@ var init_pi = __esm({
180197
180348
  });
180198
180349
 
180199
180350
  // ../engine/src/agents/agent-logger.ts
180200
- import { createHash as createHash26 } from "node:crypto";
180351
+ import { createHash as createHash27 } from "node:crypto";
180201
180352
  function truncateToolResultDetail(value) {
180202
180353
  if (value.length <= TOOL_RESULT_DETAIL_LIMIT) return value;
180203
180354
  return `${value.slice(0, TOOL_RESULT_DETAIL_LIMIT)}${TOOL_RESULT_DETAIL_TRUNCATION_NOTICE}`;
@@ -180305,7 +180456,7 @@ function summarizeToolArgs(name2, args) {
180305
180456
  return `${key}=${clipToolArgValue(compactToolArgValue(args[key]), maximum)}`;
180306
180457
  }).join(", ");
180307
180458
  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);
180459
+ const hash3 = createHash27("sha256").update(fullArgsJson).digest("hex").slice(0, STRUCTURED_ARG_HASH_HEX_LEN);
180309
180460
  const suffix = `\u2026#${hash3}`;
180310
180461
  const keep = Math.max(0, TOOL_ARG_SUMMARY_MAX - suffix.length);
180311
180462
  return `${rendered.slice(0, keep)}${suffix}`;
@@ -183013,7 +183164,7 @@ var init_retry_burned_logger = __esm({
183013
183164
 
183014
183165
  // ../engine/src/merge/merge-dependency-sync.ts
183015
183166
  import { exec as exec20 } from "node:child_process";
183016
- import { createHash as createHash27 } from "node:crypto";
183167
+ import { createHash as createHash28 } from "node:crypto";
183017
183168
  import { existsSync as existsSync50, readFileSync as readFileSync19, writeFileSync as writeFileSync6 } from "node:fs";
183018
183169
  import { join as join69 } from "node:path";
183019
183170
  import { promisify as promisify27 } from "node:util";
@@ -183046,7 +183197,7 @@ function computeLockfileHash(rootDir) {
183046
183197
  const p2 = join69(rootDir, name2);
183047
183198
  if (existsSync50(p2)) {
183048
183199
  try {
183049
- return createHash27("sha256").update(readFileSync19(p2)).digest("hex");
183200
+ return createHash28("sha256").update(readFileSync19(p2)).digest("hex");
183050
183201
  } catch {
183051
183202
  return null;
183052
183203
  }
@@ -183176,7 +183327,7 @@ var init_merge_dependency_sync = __esm({
183176
183327
  });
183177
183328
 
183178
183329
  // ../engine/src/worktree/worktree-dependency-install.ts
183179
- import { createHash as createHash28 } from "node:crypto";
183330
+ import { createHash as createHash29 } from "node:crypto";
183180
183331
  import { existsSync as existsSync51, readFileSync as readFileSync20, readdirSync as readdirSync8, statSync as statSync11, writeFileSync as writeFileSync7 } from "node:fs";
183181
183332
  import { delimiter as delimiter2, join as join70 } from "node:path";
183182
183333
  function rootEntries(rootDir) {
@@ -183274,7 +183425,7 @@ function isCommandAvailable(binary, env = process.env) {
183274
183425
  );
183275
183426
  }
183276
183427
  function hashParts(parts) {
183277
- const hash3 = createHash28("sha256");
183428
+ const hash3 = createHash29("sha256");
183278
183429
  for (const [name2, value] of [...parts].sort(([left], [right]) => left.localeCompare(right))) {
183279
183430
  hash3.update(name2).update("\0").update(value).update("\0");
183280
183431
  }
@@ -185558,7 +185709,7 @@ var init_code_node_runner = __esm({
185558
185709
  // ../engine/src/agent-tools.ts
185559
185710
  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
185711
  import { existsSync as existsSync53 } from "node:fs";
185561
- import { createHash as createHash29, randomUUID as randomUUID48 } from "node:crypto";
185712
+ import { createHash as createHash30, randomUUID as randomUUID48 } from "node:crypto";
185562
185713
  import { tmpdir as tmpdir6 } from "node:os";
185563
185714
  import { extname as extname4, isAbsolute as isAbsolute14, join as join72, relative as relative13, resolve as resolve40, sep as sep11 } from "node:path";
185564
185715
  import { Type as Type2 } from "@earendil-works/pi-ai";
@@ -185602,7 +185753,7 @@ async function readAgentMemoryWorkspaceLongTerm(rootDir, agentId) {
185602
185753
  }
185603
185754
  }
185604
185755
  function qmdAgentMemoryCollectionName2(rootDir, agentId) {
185605
- const hash3 = createHash29("sha1").update(`${rootDir}:${agentId}`).digest("hex").slice(0, 12);
185756
+ const hash3 = createHash30("sha1").update(`${rootDir}:${agentId}`).digest("hex").slice(0, 12);
185606
185757
  return `fusion-agent-memory-${sanitizeAgentMemoryId(agentId).toLowerCase()}-${hash3}`;
185607
185758
  }
185608
185759
  function buildQmdAgentMemoryCollectionAddArgs(rootDir, agentId) {
@@ -185731,11 +185882,11 @@ async function refreshAgentMemoryQmdIndex(rootDir, agentMemory) {
185731
185882
  return;
185732
185883
  }
185733
185884
  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);
185885
+ const { execFile: execFile43 } = await import("node:child_process");
185886
+ const { promisify: promisify91 } = await import("node:util");
185887
+ const execFileAsync37 = promisify91(execFile43);
185737
185888
  try {
185738
- await execFileAsync36("qmd", buildQmdAgentMemoryCollectionAddArgs(rootDir, agentMemory.agentId), {
185889
+ await execFileAsync37("qmd", buildQmdAgentMemoryCollectionAddArgs(rootDir, agentMemory.agentId), {
185739
185890
  cwd: rootDir,
185740
185891
  timeout: 4e3,
185741
185892
  maxBuffer: 512 * 1024
@@ -185748,8 +185899,8 @@ ${stderr}`)) {
185748
185899
  throw error2;
185749
185900
  }
185750
185901
  }
185751
- await execFileAsync36("qmd", ["update"], { cwd: rootDir, timeout: 3e4, maxBuffer: 1024 * 1024 });
185752
- await execFileAsync36("qmd", ["embed"], { cwd: rootDir, timeout: 12e4, maxBuffer: 1024 * 1024 });
185902
+ await execFileAsync37("qmd", ["update"], { cwd: rootDir, timeout: 3e4, maxBuffer: 1024 * 1024 });
185903
+ await execFileAsync37("qmd", ["embed"], { cwd: rootDir, timeout: 12e4, maxBuffer: 1024 * 1024 });
185753
185904
  })();
185754
185905
  agentQmdRefreshState.set(key, { lastStartedAt: now, inFlight: promise });
185755
185906
  try {
@@ -185804,10 +185955,10 @@ async function searchAgentMemoryWithQmd(rootDir, agentMemory, query, limit) {
185804
185955
  }
185805
185956
  try {
185806
185957
  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), {
185958
+ const { execFile: execFile43 } = await import("node:child_process");
185959
+ const { promisify: promisify91 } = await import("node:util");
185960
+ const execFileAsync37 = promisify91(execFile43);
185961
+ const { stdout } = await execFileAsync37("qmd", buildQmdAgentMemorySearchArgs(rootDir, agentMemory.agentId, query, limit), {
185811
185962
  cwd: rootDir,
185812
185963
  timeout: 4e3,
185813
185964
  maxBuffer: 1024 * 1024
@@ -190384,7 +190535,7 @@ var init_workspace_review_remediation = __esm({
190384
190535
  });
190385
190536
 
190386
190537
  // ../engine/src/worktree/secrets-env-writer.ts
190387
- import { createHash as createHash30 } from "node:crypto";
190538
+ import { createHash as createHash31 } from "node:crypto";
190388
190539
  import { execFile as execFile17 } from "node:child_process";
190389
190540
  import { promises as fs6 } from "node:fs";
190390
190541
  import path3 from "node:path";
@@ -190393,7 +190544,7 @@ function isValidFilename(filename) {
190393
190544
  return !!filename && !filename.includes("/") && !filename.includes("\\") && !filename.includes("..") && !filename.includes("\0") && filename !== FINGERPRINT_FILE;
190394
190545
  }
190395
190546
  function sha2562(content) {
190396
- return createHash30("sha256").update(Buffer.from(content, "utf8")).digest("hex");
190547
+ return createHash31("sha256").update(Buffer.from(content, "utf8")).digest("hex");
190397
190548
  }
190398
190549
  function quote(value) {
190399
190550
  return `"${value.replaceAll("\\", "\\\\").replaceAll('"', '\\"').replaceAll("\n", "\\n").replaceAll("\r", "\\r").replaceAll(" ", "\\t")}"`;
@@ -199189,7 +199340,7 @@ var init_mesh_lease_manager = __esm({
199189
199340
  });
199190
199341
 
199191
199342
  // ../engine/src/merge/merger-integration-worktree.ts
199192
- import { createHash as createHash32 } from "node:crypto";
199343
+ import { createHash as createHash33 } from "node:crypto";
199193
199344
  import { exec as exec33, execFile as execFile26 } from "node:child_process";
199194
199345
  import { promisify as promisify49 } from "node:util";
199195
199346
  function quoteAutostashArg(value) {
@@ -199312,7 +199463,7 @@ async function gitDirtyFingerprintLocal(rootDir) {
199312
199463
  )
199313
199464
  ]);
199314
199465
  if (!diffOut && !statusOut) return "";
199315
- return createHash32("sha256").update(diffOut).update("\0").update(statusOut).digest("hex");
199466
+ return createHash33("sha256").update(diffOut).update("\0").update(statusOut).digest("hex");
199316
199467
  } catch {
199317
199468
  return "";
199318
199469
  }
@@ -200295,7 +200446,7 @@ import { execSync as execSync6, exec as exec35 } from "node:child_process";
200295
200446
  import * as childProcess4 from "node:child_process";
200296
200447
  import { promisify as promisify52 } from "node:util";
200297
200448
  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";
200449
+ import { createHash as createHash34 } from "node:crypto";
200299
200450
  import { join as join80, resolve as resolve47 } from "node:path";
200300
200451
  import { hostname as hostname3 } from "node:os";
200301
200452
  import { Type as Type4 } from "typebox";
@@ -200550,7 +200701,7 @@ async function gitDirtyFingerprint(rootDir) {
200550
200701
  )
200551
200702
  ]);
200552
200703
  if (!diffOut && !statusOut) return "";
200553
- return createHash33("sha256").update(diffOut).update("\0").update(statusOut).digest("hex");
200704
+ return createHash34("sha256").update(diffOut).update("\0").update(statusOut).digest("hex");
200554
200705
  } catch {
200555
200706
  return "";
200556
200707
  }
@@ -209575,19 +209726,51 @@ async function captureBaseCommitSha(store, task, worktreePath, audit, options =
209575
209726
  }
209576
209727
 
209577
209728
  // ../engine/src/worktree/review-diff-fingerprint.ts
209578
- import { createHash as createHash23 } from "node:crypto";
209729
+ import { createHash as createHash24 } from "node:crypto";
209579
209730
  import { execFile as execFile8 } from "node:child_process";
209580
209731
  import { promisify as promisify8 } from "node:util";
209581
209732
  var execFileAsync5 = promisify8(execFile8);
209733
+ var REVIEW_DIFF_GIT_MAX_BUFFER_BYTES = 10 * 1024 * 1024;
209734
+ var REVIEW_DIFF_GIT_TIMEOUT_MS = 12e4;
209735
+ function gitCaptureOptions(cwd) {
209736
+ return {
209737
+ cwd,
209738
+ encoding: "utf8",
209739
+ maxBuffer: REVIEW_DIFF_GIT_MAX_BUFFER_BYTES,
209740
+ timeout: REVIEW_DIFF_GIT_TIMEOUT_MS
209741
+ };
209742
+ }
209743
+ function isGitDiffBufferOverflow(error2) {
209744
+ if (typeof error2 !== "object" || error2 === null) return false;
209745
+ const candidate = error2;
209746
+ return candidate.code === "ERR_CHILD_PROCESS_STDIO_MAXBUFFER" || typeof candidate.message === "string" && candidate.message.includes("maxBuffer");
209747
+ }
209582
209748
  var MAX_REVIEW_CHANGED_FILES = 100;
209583
209749
  var EMPTY_REVIEW_DIFF_FINGERPRINT = "empty-review-input:v1";
209750
+ async function resolveContentReviewInputProof(worktreePath, diffBaseCommitSha) {
209751
+ let baseRef;
209752
+ try {
209753
+ baseRef = await resolveDiffBaseRef(worktreePath, diffBaseCommitSha);
209754
+ } catch {
209755
+ return { kind: "unprovable", reason: "no-diff-base" };
209756
+ }
209757
+ if (!baseRef) return { kind: "unprovable", reason: "no-diff-base" };
209758
+ const probe = await probeReviewDiffFingerprint(worktreePath, baseRef);
209759
+ if (probe.state === "empty") {
209760
+ return { kind: "fingerprint", fingerprint: EMPTY_REVIEW_DIFF_FINGERPRINT };
209761
+ }
209762
+ return probe.state === "fingerprint" ? { kind: "fingerprint", fingerprint: probe.fingerprint } : { kind: "unprovable", reason: probe.reason };
209763
+ }
209584
209764
  async function probeReviewDiffFingerprint(worktreePath, baseRef, targetRef = "HEAD") {
209585
209765
  if (!worktreePath || !baseRef) return { state: "unavailable", reason: "missing-worktree-or-base" };
209586
209766
  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" };
209767
+ const { stdout } = await execFileAsync5("git", ["diff", "--binary", `${baseRef}..${targetRef}`], gitCaptureOptions(worktreePath));
209768
+ return stdout ? { state: "fingerprint", fingerprint: createHash24("sha256").update(stdout).digest("hex") } : { state: "empty" };
209769
+ } catch (error2) {
209770
+ return {
209771
+ state: "unavailable",
209772
+ reason: isGitDiffBufferOverflow(error2) ? "git-diff-too-large" : "git-diff-failed"
209773
+ };
209591
209774
  }
209592
209775
  }
209593
209776
  async function probeReviewChangesSinceCommit(worktreePath, reviewedCommitSha) {
@@ -209595,14 +209778,11 @@ async function probeReviewChangesSinceCommit(worktreePath, reviewedCommitSha) {
209595
209778
  return { state: "unavailable", reason: "missing-worktree-or-reviewed-commit" };
209596
209779
  }
209597
209780
  try {
209598
- await execFileAsync5("git", ["cat-file", "-e", `${reviewedCommitSha}^{commit}`], {
209599
- cwd: worktreePath,
209600
- encoding: "utf8"
209601
- });
209781
+ await execFileAsync5("git", ["cat-file", "-e", `${reviewedCommitSha}^{commit}`], gitCaptureOptions(worktreePath));
209602
209782
  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" })
209783
+ execFileAsync5("git", ["rev-list", "--count", `${reviewedCommitSha}..HEAD`], gitCaptureOptions(worktreePath)),
209784
+ execFileAsync5("git", ["diff", "--name-only", "-z", `${reviewedCommitSha}..HEAD`], gitCaptureOptions(worktreePath)),
209785
+ execFileAsync5("git", ["diff", "--shortstat", `${reviewedCommitSha}..HEAD`], gitCaptureOptions(worktreePath))
209606
209786
  ]);
209607
209787
  const commitCount = Number.parseInt(countOutput.trim(), 10);
209608
209788
  if (!Number.isSafeInteger(commitCount) || commitCount < 0) {
@@ -209639,7 +209819,12 @@ import { promisify as promisify11 } from "node:util";
209639
209819
  import { join as join57 } from "node:path";
209640
209820
  var execFileAsync6 = promisify11(execFile9);
209641
209821
  async function git(args, cwd) {
209642
- const { stdout } = await execFileAsync6("git", args, { cwd, encoding: "utf8" });
209822
+ const { stdout } = await execFileAsync6("git", args, {
209823
+ cwd,
209824
+ encoding: "utf8",
209825
+ maxBuffer: REVIEW_DIFF_GIT_MAX_BUFFER_BYTES,
209826
+ timeout: REVIEW_DIFF_GIT_TIMEOUT_MS
209827
+ });
209643
209828
  return stdout.trim();
209644
209829
  }
209645
209830
  async function captureWorkspaceReviewEvidence(options) {
@@ -209721,14 +209906,14 @@ async function captureMergeContentDescriptor(task, deps) {
209721
209906
 
209722
209907
  // ../engine/src/project-engine.ts
209723
209908
  init_integration_branch();
209724
- import { execFile as execFile41 } from "node:child_process";
209725
- import { promisify as promisify89 } from "node:util";
209909
+ import { execFile as execFile42 } from "node:child_process";
209910
+ import { promisify as promisify90 } from "node:util";
209726
209911
 
209727
209912
  // ../engine/src/runtimes/in-process-runtime.ts
209728
209913
  init_src2();
209729
209914
  init_scheduler();
209730
209915
  init_pi();
209731
- import { createHash as createHash37 } from "node:crypto";
209916
+ import { createHash as createHash38 } from "node:crypto";
209732
209917
  import { EventEmitter as EventEmitter27 } from "node:events";
209733
209918
 
209734
209919
  // ../engine/src/executor/public-reexports.ts
@@ -211291,7 +211476,7 @@ var WorkflowGraphExecutor = class {
211291
211476
  phase: "pre-merge",
211292
211477
  source: "optional-group",
211293
211478
  status: "skipped",
211294
- bypassedBy: "fast-mode",
211479
+ bypassedBy: FAST_MODE_BYPASS_ACTOR,
211295
211480
  bypassedAt,
211296
211481
  bypassReason: "Fast mode bypasses pre-merge workflow gates",
211297
211482
  bypassedFromStatus: "absent",
@@ -211495,7 +211680,7 @@ var WorkflowGraphExecutor = class {
211495
211680
  const stepFindings = this.workflowReviewKind(node) && Array.isArray(exitContextPatch?.findings) ? exitContextPatch.findings : void 0;
211496
211681
  const repositoryReviewOutcomes = this.workflowReviewKind(node) && Array.isArray(exitContextPatch?.repositoryReviewOutcomes) ? exitContextPatch.repositoryReviewOutcomes : void 0;
211497
211682
  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;
211683
+ const reviewInputFingerprint = requiresContentReviewProof(node.id, { reviewKind: this.workflowReviewKind(node) }) && typeof exitContextPatch?.reviewInputFingerprint === "string" ? exitContextPatch.reviewInputFingerprint : void 0;
211499
211684
  const reviewedCommitSha = this.workflowReviewKind(node) && typeof exitContextPatch?.reviewedCommitSha === "string" ? exitContextPatch.reviewedCommitSha : void 0;
211500
211685
  const supersededFindingSourceWorkflowStepId = this.workflowReviewKind(node) && typeof exitContextPatch?.supersededFindingSourceWorkflowStepId === "string" ? exitContextPatch.supersededFindingSourceWorkflowStepId : void 0;
211501
211686
  const supersededFindingIds = supersededFindingSourceWorkflowStepId && this.workflowReviewKind(node) && Array.isArray(exitContextPatch?.supersededFindingIds) ? exitContextPatch.supersededFindingIds.filter((id) => typeof id === "string") : void 0;
@@ -212234,7 +212419,7 @@ ${stepOutput}` : ""}`);
212234
212419
  const findings = this.workflowReviewKind(node) && Array.isArray(contextPatch.findings) ? contextPatch.findings : void 0;
212235
212420
  const repositoryReviewOutcomes = this.workflowReviewKind(node) && Array.isArray(contextPatch.repositoryReviewOutcomes) ? contextPatch.repositoryReviewOutcomes : void 0;
212236
212421
  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;
212422
+ const reviewInputFingerprint = requiresContentReviewProof(node.id, { reviewKind: this.workflowReviewKind(node) }) && typeof contextPatch.reviewInputFingerprint === "string" ? contextPatch.reviewInputFingerprint : void 0;
212238
212423
  const reviewedCommitSha = this.workflowReviewKind(node) && typeof contextPatch.reviewedCommitSha === "string" ? contextPatch.reviewedCommitSha : void 0;
212239
212424
  const supersededFindingSourceWorkflowStepId = this.workflowReviewKind(node) && typeof contextPatch.supersededFindingSourceWorkflowStepId === "string" ? contextPatch.supersededFindingSourceWorkflowStepId : void 0;
212240
212425
  const supersededFindingIds = supersededFindingSourceWorkflowStepId && this.workflowReviewKind(node) && Array.isArray(contextPatch.supersededFindingIds) ? contextPatch.supersededFindingIds.filter((id) => typeof id === "string") : void 0;
@@ -212756,7 +212941,7 @@ init_agent_tools();
212756
212941
  init_agent_logger();
212757
212942
  init_agent_usage_telemetry();
212758
212943
  import { Type as Type3 } from "@earendil-works/pi-ai";
212759
- import { createHash as createHash31 } from "node:crypto";
212944
+ import { createHash as createHash32 } from "node:crypto";
212760
212945
 
212761
212946
  // ../engine/src/agents/approval-mail.ts
212762
212947
  init_src2();
@@ -214385,7 +214570,7 @@ function truncatePrompt(text2, maxChars) {
214385
214570
  ... (truncated, ${text2.length} chars)`;
214386
214571
  }
214387
214572
  function shortContentHash(value) {
214388
- return createHash31("sha256").update(value).digest("hex").slice(0, 8);
214573
+ return createHash32("sha256").update(value).digest("hex").slice(0, 8);
214389
214574
  }
214390
214575
  function buildIdentitySnapshot(args) {
214391
214576
  const { agent, resolvedInstructions, workspaceMemory } = args;
@@ -218766,6 +218951,51 @@ async function finalizeProvenAutoMergeTask({
218766
218951
  }
218767
218952
  }
218768
218953
 
218954
+ // ../engine/src/merge/stale-content-review-reroute.ts
218955
+ init_src2();
218956
+ function isContentReviewNode(node) {
218957
+ if (node.kind !== "optional-group" && node.kind !== "step-review") return false;
218958
+ const name2 = typeof node.config?.name === "string" ? node.config.name : "";
218959
+ return node.id === "code-review" || node.config?.reviewKind === "code" || /code review/i.test(name2);
218960
+ }
218961
+ async function rerouteSingularStaleContentToReview(store, task, options) {
218962
+ const mergeContent = options.mergeContent;
218963
+ if (mergeContent.kind !== "singular" || task.workspaceWorktrees !== void 0) {
218964
+ return { rerouted: false, reason: "not-singular" };
218965
+ }
218966
+ if (task.paused || task.userPaused || task.deletedAt || task.autoMerge === false) {
218967
+ return { rerouted: false, reason: "operator-held" };
218968
+ }
218969
+ if (mergeContent.diff.state !== "fingerprint") return { rerouted: false, reason: "no-progress" };
218970
+ const currentFingerprint = mergeContent.diff.fingerprint;
218971
+ const ir2 = await resolveWorkflowIrForTask(store, task.id);
218972
+ const selection = store.getTaskWorkflowSelectionAsync ? await store.getTaskWorkflowSelectionAsync(task.id) : store.getTaskWorkflowSelection(task.id);
218973
+ const selected = selection?.stepIds ?? [];
218974
+ const nodes2 = ir2.nodes.filter((node2) => {
218975
+ if (!isContentReviewNode(node2) || !options.requiredPreMergeStepIds.has(node2.id)) return false;
218976
+ return node2.config?.defaultOn === true || selected.includes(node2.id);
218977
+ });
218978
+ const node = nodes2.find((candidate) => {
218979
+ const latest = task.workflowStepResults?.filter((result2) => result2.workflowStepId === candidate.id).at(-1);
218980
+ return latest?.reviewInputFingerprint !== currentFingerprint;
218981
+ });
218982
+ if (!node) return { rerouted: false, reason: nodes2.length ? "no-progress" : "no-review-route" };
218983
+ const items = await store.listWorkflowWorkItemsForTask(task.id);
218984
+ const result = await store.seedWorkspaceCodeReviewContinuationIfIdle({
218985
+ taskId: task.id,
218986
+ nodeId: node.id,
218987
+ kind: "task",
218988
+ state: "runnable",
218989
+ runId: `${task.id}:stale-content-review-reroute:${node.id}:${items.length}`,
218990
+ stableWorkflowRunId: `${task.id}:${ir2.name}`,
218991
+ continuationSequence: items.length,
218992
+ sourceColumn: task.column,
218993
+ targetColumn: node.column ?? task.column,
218994
+ irHash: computeWorkflowIrPin(ir2, node.id).irHash
218995
+ });
218996
+ return result.seeded ? { rerouted: true, reason: "seeded", nodeId: node.id, workflowStepId: node.id } : { rerouted: false, reason: "active-continuation", nodeId: node.id, workflowStepId: node.id };
218997
+ }
218998
+
218769
218999
  // ../engine/src/healing/auto-recovery.ts
218770
219000
  init_logger();
218771
219001
  var autoRecoveryLog = createLogger("auto-recovery");
@@ -220175,9 +220405,6 @@ var BOARD_STALL_NOTIFICATION_COOLDOWN_MS = 60 * 6e4;
220175
220405
  var DB_CORRUPTION_NOTIFICATION_COOLDOWN_MS = 60 * 60 * 1e3;
220176
220406
  var PHANTOM_EXECUTOR_BINDING_AGE_MULTIPLIER = 3;
220177
220407
  var MAX_NO_PROGRESS_RESUME_ATTEMPTS = 2;
220178
- function resolveRepoLocalAiMergeRoot(rootDir, settings) {
220179
- return resolveAiMergeRootPath(rootDir, settings);
220180
- }
220181
220408
  async function classifyOwnedLandedEvidenceForSelfHealing(rootDir, task, mergeTargetBranch) {
220182
220409
  const { classifyOwnedLandedEvidence: classifyOwnedLandedEvidence2 } = await Promise.resolve().then(() => (init_merger(), merger_exports));
220183
220410
  return classifyOwnedLandedEvidence2(rootDir, task, { mergeTargetBranch });
@@ -220354,6 +220581,7 @@ var SelfHealingManager = class _SelfHealingManager extends SelfHealingGitEvidenc
220354
220581
  symbolLockNoActionAudited = false;
220355
220582
  maintenanceTickCounter = 0;
220356
220583
  taskLifecycleRetentionLastPrunedAt = /* @__PURE__ */ new Map();
220584
+ staleContentRerouteAuditKeys = /* @__PURE__ */ new Set();
220357
220585
  githubCheckStateRetentionLastPrunedAt = /* @__PURE__ */ new Map();
220358
220586
  processBootStartedAt = Date.now();
220359
220587
  lastDbCorruptionNotifiedAt = null;
@@ -221069,6 +221297,7 @@ var SelfHealingManager = class _SelfHealingManager extends SelfHealingGitEvidenc
221069
221297
  // flight" to all of them (the merge gate included), which is the two-hour
221070
221298
  // stall-deadlock ride this sweep exists to prevent.
221071
221299
  { name: "reconcile-orphaned-pending-step-results", fn: () => this.reconcileOrphanedPendingStepResults().then(() => void 0) },
221300
+ { name: "reconcile-unproven-review-approvals", fn: () => this.reconcileUnprovenReviewApprovals().then(() => void 0) },
221072
221301
  /*
221073
221302
  FNXC:WorkspaceContention 2026-08-23-08:00:
221074
221303
  `contention-hold` is owned by an in-memory retry timer. After a process restart that
@@ -221949,6 +222178,7 @@ var SelfHealingManager = class _SelfHealingManager extends SelfHealingGitEvidenc
221949
222178
  Keep them adjacent and in this order.
221950
222179
  */
221951
222180
  { name: "reconcile-orphaned-pending-step-results", fn: () => this.reconcileOrphanedPendingStepResults() },
222181
+ { name: "reconcile-unproven-review-approvals", fn: () => this.reconcileUnprovenReviewApprovals() },
221952
222182
  { name: "recover-failed-pre-merge-steps", fn: () => this.recoverReviewTasksWithFailedPreMergeSteps() },
221953
222183
  { name: "recover-missing-worktree-review-failures", fn: () => this.recoverMissingWorktreeReviewFailures() },
221954
222184
  { name: "recover-interrupted-merging", fn: () => this.recoverInterruptedMergingTasks() },
@@ -226395,6 +226625,95 @@ var SelfHealingManager = class _SelfHealingManager extends SelfHealingGitEvidenc
226395
226625
  return 0;
226396
226626
  }
226397
226627
  }
226628
+ /*
226629
+ FNXC:ReviewInputProof 2026-09-01-11:28:
226630
+ Prevention cannot release cards that already persisted a proofless `passed` content review. Repair
226631
+ only the invalid result in place, including when auto-merge is off, so automatic recovery can re-run
226632
+ eligible gates and a human-held card gains the failed result its audited bypass needs. The
226633
+ `needsOperatorBypass` label is therefore truthful rather than aspirational; no lifecycle state moves.
226634
+
226635
+ FNXC:ReviewInputProof 2026-09-01-11:57:
226636
+ Reconciliation must recompute from the lock-held task inside `updateTaskAtomic`; a graph writer can
226637
+ publish a proof-bound approval or another gate after the preliminary read, and a stale array write
226638
+ would discard that valid result. Re-check lane, user pause, workspace ownership, and session liveness
226639
+ in the callback, then log and audit only a repair whose atomic mutation actually applied.
226640
+ */
226641
+ async reconcileUnprovenReviewApprovals() {
226642
+ try {
226643
+ const reviewColumns = await resolveProjectColumnsForRoles(this.store, REVIEW_ROLES);
226644
+ const candidates = /* @__PURE__ */ new Map();
226645
+ for (const column of reviewColumns) {
226646
+ for (const task of await this.store.listTasks({ column, slim: true })) candidates.set(task.id, task);
226647
+ }
226648
+ const settings = await this.store.getSettings().catch(() => void 0);
226649
+ let repairedTasks = 0;
226650
+ const isSessionLive = (taskId) => {
226651
+ const livePaths = activeSessionRegistry.pathsForTask(taskId);
226652
+ return livePaths.some((path6) => activeSessionRegistry.isPathActive(path6)) || executingTaskLock.has(taskId) || this.options.isTaskActive?.(taskId) === true;
226653
+ };
226654
+ for (const candidate of candidates.values()) {
226655
+ if (candidate.userPaused === true || candidate.workspaceWorktrees !== void 0 || isSessionLive(candidate.id)) continue;
226656
+ if (!candidate.workflowStepResults?.some((result) => resolveUnprovenReviewApproval(result, { workspace: false }))) continue;
226657
+ const fresh = await this.store.getTask(candidate.id);
226658
+ if (!fresh || !reviewColumns.has(fresh.column) || fresh.userPaused === true || fresh.workspaceWorktrees !== void 0 || isSessionLive(fresh.id)) continue;
226659
+ let repair;
226660
+ try {
226661
+ await this.store.updateTaskAtomic(fresh.id, (live) => {
226662
+ repair = void 0;
226663
+ if (!reviewColumns.has(live.column) || live.userPaused === true || live.workspaceWorktrees !== void 0 || isSessionLive(live.id)) return null;
226664
+ const repairedStepIds = [];
226665
+ let repairReason;
226666
+ const results = (live.workflowStepResults ?? []).map((result) => {
226667
+ const resolution = resolveUnprovenReviewApproval(result, { workspace: false });
226668
+ if (!resolution) return result;
226669
+ repairedStepIds.push(result.workflowStepId);
226670
+ repairReason ??= resolution.reason;
226671
+ return resolution.downgraded;
226672
+ });
226673
+ if (repairedStepIds.length === 0 || !repairReason) return null;
226674
+ repair = {
226675
+ stepIds: repairedStepIds,
226676
+ reason: repairReason,
226677
+ column: live.column,
226678
+ resultCount: results.length,
226679
+ needsOperatorBypass: settings ? !allowsAutoMergeProcessing(live, settings) : void 0
226680
+ };
226681
+ return { workflowStepResults: results };
226682
+ });
226683
+ if (!repair) continue;
226684
+ repairedTasks += 1;
226685
+ } catch (error2) {
226686
+ log34.warn(`reconcileUnprovenReviewApprovals: failed for ${fresh.id}: ${error2 instanceof Error ? error2.message : String(error2)}`);
226687
+ continue;
226688
+ }
226689
+ await this.store.logEntry(
226690
+ fresh.id,
226691
+ `[pre-merge] Invalid proofless approval repaired for ${repair.stepIds.join(", ")}: ${repair.reason}`
226692
+ ).catch(() => void 0);
226693
+ await emitBoundedRunAudit2(this.store, {
226694
+ taskId: fresh.id,
226695
+ agentId: "self-healing",
226696
+ runId: generateSyntheticRunId("reconcile-unproven-review-approval", fresh.id),
226697
+ domain: "database",
226698
+ mutationType: "task:reconcile-unproven-review-approval",
226699
+ target: fresh.id,
226700
+ metadata: {
226701
+ taskId: fresh.id,
226702
+ column: repair.column,
226703
+ workflowStepId: repair.stepIds[0],
226704
+ repairedCount: repair.stepIds.length,
226705
+ resultCount: repair.resultCount,
226706
+ needsOperatorBypass: repair.needsOperatorBypass
226707
+ }
226708
+ }, { log: log34 });
226709
+ }
226710
+ if (repairedTasks > 0) log34.log(`Repaired unproven review approvals on ${repairedTasks} task(s)`);
226711
+ return repairedTasks;
226712
+ } catch (error2) {
226713
+ log34.error(`reconcileUnprovenReviewApprovals failed: ${error2 instanceof Error ? error2.message : String(error2)}`);
226714
+ return 0;
226715
+ }
226716
+ }
226398
226717
  /**
226399
226718
  * Backward lifecycle move gated on triple proof (FN-5335).
226400
226719
  * When the unproven fallback predicate fails, emits `task:finalize-no-op-review-no-action` and skips lifecycle mutation.
@@ -226748,6 +227067,66 @@ var SelfHealingManager = class _SelfHealingManager extends SelfHealingGitEvidenc
226748
227067
  }
226749
227068
  return allowsAutoMergeProcessing(task, settings) && resolveEffectiveAutoMerge(task, settings) !== false;
226750
227069
  }
227070
+ /*
227071
+ FNXC:PreMergeApproval 2026-09-01-14:05:
227072
+ Self-healing must not treat a stale content-bound approval as mergeable just because the cheap
227073
+ blocker check lacks a fresh Git descriptor. Capture the same merge content the merge queue uses and
227074
+ seed the graph back at Code Review, so cards like FN-9234 stop looping through doomed merge retries.
227075
+ */
227076
+ async routeStaleSingularApprovalBackToReview(task, reviewColumns, settings) {
227077
+ let mergeGate;
227078
+ try {
227079
+ mergeGate = await resolvePreMergeGateForTask(this.store, task.id, task.enabledWorkflowSteps, task);
227080
+ } catch {
227081
+ return false;
227082
+ }
227083
+ if (mergeGate.provenance === "default" && !mergeGate.selectionAbsent) return false;
227084
+ const mergeContent = await captureMergeContentDescriptor(task, {
227085
+ workspaceRootDir: this.options.rootDir,
227086
+ settings
227087
+ });
227088
+ const blocker = getTaskMergeBlocker(task, {
227089
+ reviewColumns,
227090
+ requiredPreMergeStepIds: mergeGate.requiredPreMergeStepIds,
227091
+ mergeContent
227092
+ });
227093
+ if (blocker !== "task has a pre-merge approval recorded against different content" || mergeContent.kind !== "singular") {
227094
+ return false;
227095
+ }
227096
+ const reroute = await rerouteSingularStaleContentToReview(this.store, task, {
227097
+ requiredPreMergeStepIds: mergeGate.requiredPreMergeStepIds,
227098
+ mergeContent
227099
+ }).catch(() => ({
227100
+ rerouted: false,
227101
+ reason: "no-progress",
227102
+ nodeId: void 0,
227103
+ workflowStepId: void 0
227104
+ }));
227105
+ if (reroute.rerouted) {
227106
+ await this.store.logEntry(task.id, "[pre-merge] Self-healing re-seeded Code Review after stale content evidence blocked merge.");
227107
+ log34.warn(`Stale content approval for ${task.id} re-seeded at ${reroute.nodeId ?? "code-review"}`);
227108
+ }
227109
+ const auditKey = `${task.id}:${reroute.reason}:${reroute.nodeId ?? ""}`;
227110
+ if (!this.staleContentRerouteAuditKeys.has(auditKey)) {
227111
+ this.staleContentRerouteAuditKeys.add(auditKey);
227112
+ await emitBoundedRunAudit2(this.store, {
227113
+ taskId: task.id,
227114
+ agentId: "self-healing",
227115
+ runId: generateSyntheticRunId("self-healing", task.id),
227116
+ domain: "database",
227117
+ mutationType: "task:merge-stale-content-review-rerouted",
227118
+ target: task.id,
227119
+ metadata: {
227120
+ taskId: task.id,
227121
+ nodeId: reroute.nodeId,
227122
+ workflowStepId: reroute.workflowStepId,
227123
+ reason: reroute.reason,
227124
+ source: "self-healing"
227125
+ }
227126
+ });
227127
+ }
227128
+ return true;
227129
+ }
226751
227130
  /**
226752
227131
  * Recover `in-review` tasks that are fully mergeable but never had
226753
227132
  * `mergeTask()` invoked.
@@ -226801,6 +227180,7 @@ var SelfHealingManager = class _SelfHealingManager extends SelfHealingGitEvidenc
226801
227180
  for (const task of mergeable) {
226802
227181
  const reviewColumns = await ownReviewLanesFor(task);
226803
227182
  if (!reviewColumns.has(task.column)) continue;
227183
+ if (await this.routeStaleSingularApprovalBackToReview(task, reviewColumns, settings)) continue;
226804
227184
  if (getTaskMergeBlocker(task, { reviewColumns }) !== void 0) continue;
226805
227185
  laneQualifiedMergeable.push(task);
226806
227186
  }
@@ -229367,8 +229747,7 @@ This review is not retried until its findings change. Re-run the review after ad
229367
229747
  }
229368
229748
  async listAiMergeWorktreeCandidates(taskId, settings) {
229369
229749
  const roots = Array.from(/* @__PURE__ */ new Set([
229370
- resolveRepoLocalAiMergeRoot(this.options.rootDir, settings),
229371
- resolveLegacyAiMergeRootPath(this.options.rootDir),
229750
+ ...resolveAiMergeSearchRoots(this.options.rootDir, settings),
229372
229751
  tmpdir8()
229373
229752
  ]));
229374
229753
  const testWorkerRoot = process.env.FUSION_TEST_WORKER_ROOT;
@@ -231902,9 +232281,7 @@ This review is not retried until its findings change. Re-run the review after ad
231902
232281
  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
232282
  }
231904
232283
  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")],
232284
+ ...resolveAiMergeSearchRoots(this.options.rootDir, settings),
231908
232285
  tmpdir8()
231909
232286
  ]));
231910
232287
  const auditor = createRunAuditor(this.store, {
@@ -234559,7 +234936,7 @@ ${recommendation.description}`)) {
234559
234936
  // ../engine/src/executor/execute-workflow-step.ts
234560
234937
  init_src2();
234561
234938
  import { exec as exec41 } from "node:child_process";
234562
- import { promisify as promisify58 } from "node:util";
234939
+ import { promisify as promisify59 } from "node:util";
234563
234940
 
234564
234941
  // ../engine/src/executor/shared-worker-tools.ts
234565
234942
  init_agent_tools();
@@ -236418,6 +236795,47 @@ function isWorkflowStepSkillDiscoverable(skillName, additionalSkillPaths, ceSkil
236418
236795
  );
236419
236796
  }
236420
236797
 
236798
+ // ../engine/src/worktree/review-inline-fix-recapture.ts
236799
+ import { execFile as execFile29 } from "node:child_process";
236800
+ import { promisify as promisify58 } from "node:util";
236801
+ var execFileAsync23 = promisify58(execFile29);
236802
+ function classifyReviewInlineFixRecapture(input) {
236803
+ if (input.reviewKind === "plan") return { recapture: false, reason: "plan-domain" };
236804
+ if (input.verdict !== "APPROVE" && input.verdict !== "APPROVE_WITH_NOTES") {
236805
+ return { recapture: false, reason: "not-approval-verdict" };
236806
+ }
236807
+ if (!input.reviewedCommitSha || !input.currentHeadSha || !input.baseRef) {
236808
+ return { recapture: false, reason: "missing-anchor" };
236809
+ }
236810
+ if (input.reviewedCommitSha === input.currentHeadSha) return { recapture: false, reason: "head-unchanged" };
236811
+ if (input.fastForwardAdvance === void 0 || input.baseIsAncestor === void 0) {
236812
+ return { recapture: false, reason: "probe-unavailable" };
236813
+ }
236814
+ if (!input.fastForwardAdvance) return { recapture: false, reason: "history-rewritten" };
236815
+ if (!input.baseIsAncestor) return { recapture: false, reason: "base-not-ancestor" };
236816
+ if (!input.fingerprintProbeAvailable) return { recapture: false, reason: "probe-unavailable" };
236817
+ return { recapture: true, reason: "recaptured" };
236818
+ }
236819
+ async function readHeadSha(worktreePath) {
236820
+ if (!worktreePath) return void 0;
236821
+ try {
236822
+ const { stdout } = await execFileAsync23("git", ["rev-parse", "HEAD"], { cwd: worktreePath, encoding: "utf8" });
236823
+ return stdout.trim() || void 0;
236824
+ } catch {
236825
+ return void 0;
236826
+ }
236827
+ }
236828
+ async function isFastForwardAdvance(worktreePath, fromSha, toSha) {
236829
+ if (!worktreePath || !fromSha || !toSha) return void 0;
236830
+ try {
236831
+ await execFileAsync23("git", ["merge-base", "--is-ancestor", fromSha, toSha], { cwd: worktreePath, encoding: "utf8" });
236832
+ return true;
236833
+ } catch (error2) {
236834
+ const code = error2?.code;
236835
+ return code === 1 ? false : void 0;
236836
+ }
236837
+ }
236838
+
236421
236839
  // ../engine/src/executor/optional-step-revision.ts
236422
236840
  init_src2();
236423
236841
 
@@ -236575,7 +236993,7 @@ function buildGraphPlanReviewConvergenceContext(task, revisionKey) {
236575
236993
 
236576
236994
  // ../engine/src/executor/execute-workflow-step.ts
236577
236995
  init_agent_usage_telemetry();
236578
- var execAsync38 = promisify58(exec41);
236996
+ var execAsync38 = promisify59(exec41);
236579
236997
  var WORKFLOW_STEP_NOTES_REPAIR_TIMEOUT_MS = 12e4;
236580
236998
  function findReusableReviewResult(task, workflowStepId, reviewInputFingerprint, repositoryScopeRevision) {
236581
236999
  if (!reviewInputFingerprint) return void 0;
@@ -236614,6 +237032,8 @@ async function executeWorkflowStep(deps, task, workflowStep, worktreePath, setti
236614
237032
  const unattended = stepOptions?.unattended === true;
236615
237033
  const workflowStepMetadata = workflowStep;
236616
237034
  const optionalGroupId = workflowStepMetadata.optionalGroupId;
237035
+ const effectiveWorkflowStepId = optionalGroupId ?? workflowStep.id.replace(/^graph:/, "");
237036
+ const isContentBindingStep = requiresContentReviewProof(effectiveWorkflowStepId, workflowStepMetadata);
236617
237037
  const isPlanReviewStep = workflowStep.id === "graph:plan-review-step" || workflowStep.name === "Plan Review" || optionalGroupId === PLAN_REVIEW_GROUP_ID;
236618
237038
  const planReviewRevisionKey = optionalStepRevisionKey2(optionalGroupId, workflowStep.name);
236619
237039
  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 +237044,8 @@ async function executeWorkflowStep(deps, task, workflowStep, worktreePath, setti
236624
237044
  toolMode = "coding";
236625
237045
  }
236626
237046
  const requireExternalIntegrationEvidence = workflowStepMetadata.requireExternalIntegrationEvidence === true;
237047
+ const readonlyMcpServerAllowlist = toolMode === "readonly" ? [...new Set((workflowStepMetadata.readonlyMcpServers ?? []).map((name2) => name2.trim()).filter(Boolean))] : [];
237048
+ const allowReadonlyMcpTools = toolMode === "readonly" && readonlyMcpServerAllowlist.length > 0;
236627
237049
  let workflowReviewSpecArtifact;
236628
237050
  if (isReviewTypeWorkflowStep) {
236629
237051
  try {
@@ -236642,7 +237064,7 @@ async function executeWorkflowStep(deps, task, workflowStep, worktreePath, setti
236642
237064
  const workflowReviewSpecText = typeof workflowReviewSpecArtifact === "string" ? workflowReviewSpecArtifact : "";
236643
237065
  const planReviewSpecText = isPlanReviewStep ? workflowReviewSpecText : "";
236644
237066
  const latestTaskForUserComments = await deps.store.getTask(task.id).catch(() => task);
236645
- const sameGateStepId = workflowStep.id.replace(/^graph:/, "");
237067
+ const sameGateStepId = effectiveWorkflowStepId;
236646
237068
  const planReviewConvergenceContext = isPlanReviewStep ? buildGraphPlanReviewConvergenceContext(latestTaskForUserComments, planReviewRevisionKey) : "";
236647
237069
  const planReviewEnvironmentCapabilities = isPlanReviewStep ? await probeEnvironmentCapabilities({
236648
237070
  extraCommands: [
@@ -236690,21 +237112,44 @@ async function executeWorkflowStep(deps, task, workflowStep, worktreePath, setti
236690
237112
  let diffShortstat;
236691
237113
  let reviewInputFingerprint;
236692
237114
  let reviewedCommitSha;
237115
+ let baseRef;
236693
237116
  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;
237117
+ baseRef = await resolveDiffBaseRef(worktreePath, diffBaseCommitSha);
237118
+ } catch {
237119
+ }
237120
+ if (isContentBindingStep && task.workspaceWorktrees === void 0) {
237121
+ const suppliedFingerprint = stepOptions?.reviewInputFingerprint?.trim();
237122
+ const proof = suppliedFingerprint ? { kind: "fingerprint", fingerprint: suppliedFingerprint } : await resolveContentReviewInputProof(worktreePath, diffBaseCommitSha);
237123
+ if (proof.kind === "unprovable") {
237124
+ const diagnostic = `${workflowStep.name} review input is unprovable (${proof.reason}); reviewer dispatch refused.`;
237125
+ await deps.store.logEntry(
237126
+ task.id,
237127
+ `[pre-merge] ${diagnostic}`,
237128
+ void 0,
237129
+ deps.getRunContextFor(task.id)
237130
+ );
237131
+ return { success: false, error: diagnostic, failureValue: "review-input-unprovable" };
237132
+ }
237133
+ reviewInputFingerprint = proof.fingerprint;
237134
+ } else if (baseRef) {
237135
+ try {
236701
237136
  if (workflowStepMetadata.reviewKind === "code") {
236702
237137
  reviewInputFingerprint = await computeCodeReviewInputFingerprint(worktreePath, baseRef);
236703
237138
  } else if (isReviewTypeWorkflowStep) {
236704
237139
  reviewInputFingerprint = await computeReviewDiffFingerprint(worktreePath, baseRef);
236705
237140
  }
237141
+ } catch {
237142
+ }
237143
+ }
237144
+ if (baseRef) {
237145
+ try {
237146
+ const { stdout } = await execAsync38(`git diff --shortstat ${baseRef}..HEAD`, {
237147
+ cwd: worktreePath,
237148
+ encoding: "utf-8"
237149
+ });
237150
+ diffShortstat = stdout.trim() || void 0;
237151
+ } catch {
236706
237152
  }
236707
- } catch {
236708
237153
  }
236709
237154
  if (isReviewTypeWorkflowStep) {
236710
237155
  try {
@@ -237014,6 +237459,12 @@ ${workflowStepUserCommentSection}` : ""}${priorFindingsBlock}
237014
237459
  const workflowStepThinkingSource = workflowStep.thinkingLevel ?? (isReviewTypeWorkflowStep ? task.validatorThinkingLevel ?? task.thinkingLevel : task.thinkingLevel);
237015
237460
  const workflowStepThinkingLevel = attemptLabel === "fallback" ? isReviewTypeWorkflowStep ? resolveValidatorFallbackThinkingLevel(workflowStepThinkingSource, settings) : resolveExecutorFallbackThinkingLevel(workflowStepThinkingSource, settings) : isReviewTypeWorkflowStep ? resolveValidatorThinkingLevel(workflowStepThinkingSource, settings) : resolveExecutorThinkingLevel(workflowStepThinkingSource, settings);
237016
237461
  const workflowStepFallbackThinkingLevel = isReviewTypeWorkflowStep ? resolveValidatorFallbackThinkingLevel(workflowStepThinkingSource, settings) : resolveExecutorFallbackThinkingLevel(workflowStepThinkingSource, settings);
237462
+ if (allowReadonlyMcpTools) {
237463
+ await deps.store.logEntry(
237464
+ task.id,
237465
+ `Workflow step '${workflowStep.name}' enabled read-only MCP servers: ${readonlyMcpServerAllowlist.join(", ")}`
237466
+ );
237467
+ }
237017
237468
  const { session } = await createResolvedAgentSession({
237018
237469
  sessionPurpose: "executor",
237019
237470
  taskExecutionSession: true,
@@ -237034,6 +237485,10 @@ ${workflowStepUserCommentSection}` : ""}${priorFindingsBlock}
237034
237485
  settings,
237035
237486
  taskEnv: stepEnv,
237036
237487
  mcpServers: await deps.resolveMcpServers(void 0),
237488
+ ...allowReadonlyMcpTools ? {
237489
+ allowMcpToolsInReadonly: true,
237490
+ readonlyMcpServerAllowlist
237491
+ } : {},
237037
237492
  // FNXC:SessionRouting 2026-06-24-11:20:
237038
237493
  // #1675: propagate task id so workflow-step requests carry the same
237039
237494
  // X-Session-Id/X-Session-Affinity as the primary session.
@@ -237249,6 +237704,60 @@ ${detectedQuestion}
237249
237704
  });
237250
237705
  }
237251
237706
  const revisionRequested = effectiveVerdict === "REVISE";
237707
+ if (isReviewTypeWorkflowStep && !isPlanReviewStep && !revisionRequested) {
237708
+ const priorReviewedCommitSha = reviewedCommitSha;
237709
+ const currentHeadSha = await readHeadSha(worktreePath);
237710
+ const fastForwardAdvance = await isFastForwardAdvance(worktreePath, priorReviewedCommitSha, currentHeadSha);
237711
+ const baseIsAncestor = await isFastForwardAdvance(worktreePath, baseRef, currentHeadSha);
237712
+ const decision = classifyReviewInlineFixRecapture({
237713
+ verdict: effectiveVerdict,
237714
+ reviewKind: workflowStepMetadata.reviewKind,
237715
+ reviewedCommitSha: priorReviewedCommitSha,
237716
+ currentHeadSha,
237717
+ baseRef,
237718
+ fastForwardAdvance,
237719
+ baseIsAncestor,
237720
+ fingerprintProbeAvailable: true
237721
+ });
237722
+ if (decision.recapture) {
237723
+ try {
237724
+ const recapturedFingerprint = workflowStepMetadata.reviewKind === "code" ? await computeCodeReviewInputFingerprint(worktreePath, baseRef) : await computeReviewDiffFingerprint(worktreePath, baseRef);
237725
+ const finalDecision = classifyReviewInlineFixRecapture({
237726
+ verdict: effectiveVerdict,
237727
+ reviewKind: workflowStepMetadata.reviewKind,
237728
+ reviewedCommitSha: priorReviewedCommitSha,
237729
+ currentHeadSha,
237730
+ baseRef,
237731
+ fastForwardAdvance,
237732
+ baseIsAncestor,
237733
+ fingerprintProbeAvailable: recapturedFingerprint !== void 0
237734
+ });
237735
+ if (finalDecision.recapture && currentHeadSha) {
237736
+ reviewInputFingerprint = recapturedFingerprint;
237737
+ reviewedCommitSha = currentHeadSha;
237738
+ const resolvedInReviewFindingCount = parsed.findings?.filter((finding) => finding.resolution === "resolved-in-review").length ?? 0;
237739
+ 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})`);
237740
+ const runContext = deps.getRunContextFor(task.id);
237741
+ if (runContext) await emitBoundedRunAudit2(deps.store, {
237742
+ taskId: task.id,
237743
+ agentId: runContext.agentId,
237744
+ runId: runContext.runId,
237745
+ domain: "git",
237746
+ mutationType: "task:review-input-recaptured",
237747
+ target: task.id,
237748
+ metadata: {
237749
+ taskId: task.id,
237750
+ workflowStepId: workflowStep.id,
237751
+ verdict: effectiveVerdict,
237752
+ resolvedInReviewFindingCount,
237753
+ reason: finalDecision.reason
237754
+ }
237755
+ });
237756
+ }
237757
+ } catch {
237758
+ }
237759
+ }
237760
+ }
237252
237761
  if (workflowStep.requiresBrowser === true) {
237253
237762
  await logBrowserVerificationActivity(`[browser-verification] finished browser verification for task ${task.id}: verdict ${effectiveVerdict}`);
237254
237763
  }
@@ -237953,10 +238462,10 @@ init_branch_conflicts();
237953
238462
  init_integration_branch();
237954
238463
  init_logger();
237955
238464
  import { exec as exec42 } from "node:child_process";
237956
- import { promisify as promisify59 } from "node:util";
238465
+ import { promisify as promisify60 } from "node:util";
237957
238466
  import { existsSync as existsSync66 } from "node:fs";
237958
238467
  import { rm as rm11 } from "node:fs/promises";
237959
- var execAsync39 = promisify59(exec42);
238468
+ var execAsync39 = promisify60(exec42);
237960
238469
  async function tryCreateWorktree(deps, branch, path6, taskId, startPoint, attemptNumber = 0, recoveryDepth = 0, allowSiblingBranchRename = false, settings = {}) {
237961
238470
  await assertWorktreePathNotNested(deps.rootDir, deps.store, path6, taskId);
237962
238471
  const installGuardOrCleanup = async () => {
@@ -238244,10 +238753,10 @@ init_src2();
238244
238753
  init_worktree_pool();
238245
238754
  init_logger();
238246
238755
  import { exec as exec43 } from "node:child_process";
238247
- import { promisify as promisify60 } from "node:util";
238756
+ import { promisify as promisify61 } from "node:util";
238248
238757
  import { existsSync as existsSync67, lstatSync as lstatSync3, realpathSync as realpathSync11 } from "node:fs";
238249
238758
  import { rm as rm12 } from "node:fs/promises";
238250
- var execAsync40 = promisify60(exec43);
238759
+ var execAsync40 = promisify61(exec43);
238251
238760
  async function cleanupConflictingWorktree(deps, worktreePath, branch, taskId) {
238252
238761
  await deps.reconcileSelfOwnedBeforeRemove(worktreePath, taskId);
238253
238762
  const activeOwner = await deps.findActiveWorktreeOwner(worktreePath, taskId);
@@ -238364,10 +238873,10 @@ init_worktree_stale_lock();
238364
238873
  init_integration_branch();
238365
238874
  init_logger();
238366
238875
  import { exec as exec44 } from "node:child_process";
238367
- import { promisify as promisify61 } from "node:util";
238876
+ import { promisify as promisify62 } from "node:util";
238368
238877
  import { existsSync as existsSync68 } from "node:fs";
238369
238878
  import { isAbsolute as isAbsolute22 } from "node:path";
238370
- var execAsync41 = promisify61(exec44);
238879
+ var execAsync41 = promisify62(exec44);
238371
238880
  async function resolveWorktreeStartPoint(rootDir, store, startPoint, taskId) {
238372
238881
  const command = isAbsolute22(startPoint) && existsSync68(startPoint) ? `git -C "${startPoint}" rev-parse --verify HEAD^{commit}` : `git rev-parse --verify "${startPoint}^{commit}"`;
238373
238882
  try {
@@ -238569,8 +239078,8 @@ init_effective_settings();
238569
239078
  init_worktree_pinning();
238570
239079
  init_logger();
238571
239080
  import { exec as exec45 } from "node:child_process";
238572
- import { promisify as promisify62 } from "node:util";
238573
- var execAsync42 = promisify62(exec45);
239081
+ import { promisify as promisify63 } from "node:util";
239082
+ var execAsync42 = promisify63(exec45);
238574
239083
  async function reclaimExistingWorktree(deps, task, livePath, branch, tipSha, count2, settings) {
238575
239084
  const targetPath = preservedWorktreeTargetPathForTask(task.id, livePath, settings, deps.rootDir);
238576
239085
  const normalizedPath = await deps.normalizeReclaimableWorktreePath(livePath, targetPath, task.id, settings);
@@ -238803,7 +239312,7 @@ init_worktree_names();
238803
239312
  init_logger();
238804
239313
  init_run_audit();
238805
239314
  import { exec as exec47 } from "node:child_process";
238806
- import { promisify as promisify65 } from "node:util";
239315
+ import { promisify as promisify66 } from "node:util";
238807
239316
  import { existsSync as existsSync71 } from "node:fs";
238808
239317
 
238809
239318
  // ../engine/src/executor/session-worktree-paths.ts
@@ -238931,11 +239440,11 @@ function classifyWorkspaceZeroAcquire(task, options) {
238931
239440
  }
238932
239441
 
238933
239442
  // ../engine/src/execution/external-execution-checkout.ts
238934
- import { execFile as execFile29 } from "node:child_process";
239443
+ import { execFile as execFile30 } from "node:child_process";
238935
239444
  import { existsSync as existsSync69, realpathSync as realpathSync13, statSync as statSync14 } from "node:fs";
238936
239445
  import { isAbsolute as isAbsolute23 } from "node:path";
238937
- import { promisify as promisify63 } from "node:util";
238938
- var execFileAsync23 = promisify63(execFile29);
239446
+ import { promisify as promisify64 } from "node:util";
239447
+ var execFileAsync24 = promisify64(execFile30);
238939
239448
  function readSourceMetadata(task) {
238940
239449
  if (!task || typeof task !== "object") return void 0;
238941
239450
  const sourceMetadata = task.sourceMetadata;
@@ -238954,7 +239463,7 @@ async function inspectExternalGitCheckout(candidate, options = {}) {
238954
239463
  return { valid: false, reason: `checkoutPath is not a directory: ${checkoutPath}` };
238955
239464
  }
238956
239465
  const canonicalCheckout = realpathSync13(checkoutPath);
238957
- const { stdout: topLevelOutput } = await execFileAsync23("git", ["rev-parse", "--show-toplevel"], {
239466
+ const { stdout: topLevelOutput } = await execFileAsync24("git", ["rev-parse", "--show-toplevel"], {
238958
239467
  cwd: canonicalCheckout,
238959
239468
  encoding: "utf-8",
238960
239469
  timeout: 1e4
@@ -238967,7 +239476,7 @@ async function inspectExternalGitCheckout(candidate, options = {}) {
238967
239476
  reason: `checkoutPath must be the Git top-level (observed ${canonicalTopLevel})`
238968
239477
  };
238969
239478
  }
238970
- const { stdout: branchOutput } = await execFileAsync23("git", ["symbolic-ref", "--quiet", "--short", "HEAD"], {
239479
+ const { stdout: branchOutput } = await execFileAsync24("git", ["symbolic-ref", "--quiet", "--short", "HEAD"], {
238971
239480
  cwd: canonicalCheckout,
238972
239481
  encoding: "utf-8",
238973
239482
  timeout: 1e4
@@ -238977,7 +239486,7 @@ async function inspectExternalGitCheckout(candidate, options = {}) {
238977
239486
  return { valid: false, reason: "checkoutPath must have a checked-out branch" };
238978
239487
  }
238979
239488
  if (options.requireClean) {
238980
- const { stdout: statusOutput } = await execFileAsync23("git", ["status", "--porcelain=v1"], {
239489
+ const { stdout: statusOutput } = await execFileAsync24("git", ["status", "--porcelain=v1"], {
238981
239490
  cwd: canonicalCheckout,
238982
239491
  encoding: "utf-8",
238983
239492
  timeout: 1e4
@@ -239040,9 +239549,9 @@ async function resolveAuthoritativeExternalExecutionRoute(store, task) {
239040
239549
  import { exec as exec46 } from "node:child_process";
239041
239550
  import { existsSync as existsSync70, promises as fs7 } from "node:fs";
239042
239551
  import path4 from "node:path";
239043
- import { promisify as promisify64 } from "node:util";
239552
+ import { promisify as promisify65 } from "node:util";
239044
239553
  init_worktree_paths();
239045
- var execAsync43 = promisify64(exec46);
239554
+ var execAsync43 = promisify65(exec46);
239046
239555
  var probeOptions = { encoding: "utf-8", timeout: 1e4, maxBuffer: 1024 * 1024 };
239047
239556
  function workspaceExecutionAnchor(task) {
239048
239557
  const executionValues = [task.firstExecutionAt, task.executionStartedAt].map((value) => typeof value === "string" ? Date.parse(value) : Number.NaN).filter(Number.isFinite);
@@ -239162,7 +239671,7 @@ async function detectWorkspaceMainCheckoutWork(deps, task, repos, declaredScope)
239162
239671
  }
239163
239672
 
239164
239673
  // ../engine/src/executor/worktree-verify-invariants.ts
239165
- var execAsync44 = promisify65(exec47);
239674
+ var execAsync44 = promisify66(exec47);
239166
239675
  async function verifyWorktreeInvariants(deps, task, worktreePathOverride, allowReanchor = true, options) {
239167
239676
  const workspaceConfig = deps.ensureWorkspaceConfig ? await deps.ensureWorkspaceConfig() : deps.workspaceConfig;
239168
239677
  const settings = await deps.store.getSettings();
@@ -239567,14 +240076,14 @@ async function emitWorktreeReanchoredAudit(deps, taskId, fromPath, toPath, sourc
239567
240076
  }
239568
240077
 
239569
240078
  // ../engine/src/executor/worktree-task-done-scope-leak.ts
239570
- import { execFile as execFile30 } from "node:child_process";
240079
+ import { execFile as execFile31 } from "node:child_process";
239571
240080
  import { access as access9 } from "node:fs/promises";
239572
- import { promisify as promisify66 } from "node:util";
240081
+ import { promisify as promisify67 } from "node:util";
239573
240082
  init_logger();
239574
- var execFileAsync24 = promisify66(execFile30);
240083
+ var execFileAsync25 = promisify67(execFile31);
239575
240084
  async function verifyRepositoryCaptureEvidence(worktreePath) {
239576
240085
  await access9(worktreePath);
239577
- const { stdout } = await execFileAsync24("git", ["rev-parse", "--is-inside-work-tree"], {
240086
+ const { stdout } = await execFileAsync25("git", ["rev-parse", "--is-inside-work-tree"], {
239578
240087
  cwd: worktreePath,
239579
240088
  encoding: "utf8"
239580
240089
  });
@@ -239591,7 +240100,7 @@ async function captureRepositoryChangeEvidence(worktreePath, baseCommitSha) {
239591
240100
  } else {
239592
240101
  commands.push(["rev-parse", "--verify", "HEAD"]);
239593
240102
  }
239594
- const results = await Promise.all(commands.map((args) => execFileAsync24("git", args, {
240103
+ const results = await Promise.all(commands.map((args) => execFileAsync25("git", args, {
239595
240104
  cwd: worktreePath,
239596
240105
  encoding: "utf8"
239597
240106
  })));
@@ -239725,8 +240234,8 @@ async function evaluateTaskDoneScopeLeak(deps, task, worktreePath, promptContent
239725
240234
  init_branch_attribution();
239726
240235
  init_logger();
239727
240236
  import { exec as exec48 } from "node:child_process";
239728
- import { promisify as promisify67 } from "node:util";
239729
- var execAsync45 = promisify67(exec48);
240237
+ import { promisify as promisify68 } from "node:util";
240238
+ var execAsync45 = promisify68(exec48);
239730
240239
  async function captureModifiedFiles(worktreePath, baseCommitSha, taskId, audit, source = "unspecified") {
239731
240240
  try {
239732
240241
  const baseRef = await resolveDiffBaseRef(worktreePath, baseCommitSha);
@@ -240024,6 +240533,27 @@ ${result.review}`);
240024
240533
  summarySections.push(`[${repoRel}] ${result.verdict}: ${result.summary}`);
240025
240534
  }
240026
240535
  const aggregateVerdict = reviewedResults.some(({ result }) => result.verdict === "RETHINK") ? "RETHINK" : reviewedResults.some(({ result }) => result.verdict === "REVISE") ? "REVISE" : reviewedResults.some(({ result }) => !isApprovalFamilyVerdict(result.verdict)) ? "UNAVAILABLE" : "APPROVE";
240536
+ let publishedModifiedFiles = modifiedFiles;
240537
+ if (isApprovalFamilyVerdict(aggregateVerdict) && evidence && options.workspaceRootDir) {
240538
+ try {
240539
+ const refreshed = await captureWorkspaceReviewEvidence({ task, workspaceRootDir: options.workspaceRootDir, settings: options.settings ?? {} });
240540
+ if (refreshed.outOfScopeRepositories.size > 0) {
240541
+ 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 };
240542
+ }
240543
+ for (const updated of refreshed.repositories) {
240544
+ const original = evidence.repositories.find((candidate) => candidate.repository === updated.repository);
240545
+ const worktreePath = workspaceWorktrees[updated.repository]?.worktreePath;
240546
+ const outcome = repositoryReviewOutcomes.find((candidate) => candidate.repository === updated.repository && candidate.status === "REVIEWED");
240547
+ if (!original || !worktreePath || !updated.fingerprint || !outcome || !repositoryScope.has(updated.repository)) continue;
240548
+ if (await isFastForwardAdvance(worktreePath, original.branch, updated.branch)) {
240549
+ repositoryDiffFingerprints[updated.repository] = updated.fingerprint;
240550
+ outcome.fingerprint = updated.fingerprint;
240551
+ }
240552
+ }
240553
+ publishedModifiedFiles = refreshed.modifiedFiles;
240554
+ } catch {
240555
+ }
240556
+ }
240027
240557
  const blockingRepositories = reviewedResults.filter(({ result }) => !isApprovalFamilyVerdict(result.verdict)).map(({ repository }) => repository);
240028
240558
  const blockingSummary = blockingRepositories.length > 0 ? `blocking repositories: ${blockingRepositories.join(", ")}` : `all ${repoKeys.length} modified in-scope repositories approved`;
240029
240559
  const coverageNotice = noVerdictRepositories.length > 0 ? `
@@ -240035,7 +240565,7 @@ Not covered by a verdict: ${noVerdictRepositories.join(", ")}.` : "";
240035
240565
  ${reviewSections.join("\n\n")}`,
240036
240566
  summary: `${aggregateVerdict} \u2014 ${blockingSummary} \u2014 ${summarySections.join(" | ")}`,
240037
240567
  repositoryDiffFingerprints,
240038
- repositoryModifiedFiles: modifiedFiles,
240568
+ repositoryModifiedFiles: publishedModifiedFiles,
240039
240569
  repositoryReviewOutcomes,
240040
240570
  ...findings.length > 0 ? { findings } : {},
240041
240571
  repositoryScopeRevision
@@ -240357,9 +240887,9 @@ init_worktree_names();
240357
240887
  init_worktree_pool();
240358
240888
  init_logger();
240359
240889
  import { exec as exec49 } from "node:child_process";
240360
- import { promisify as promisify68 } from "node:util";
240890
+ import { promisify as promisify69 } from "node:util";
240361
240891
  init_lifecycle_columns();
240362
- var execAsync46 = promisify68(exec49);
240892
+ var execAsync46 = promisify69(exec49);
240363
240893
  async function handleDepAbortCleanup(deps, taskId, worktreePath) {
240364
240894
  executorLog.log(`${taskId} dependency added \u2014 work discarded, moved to triage for re-planning`);
240365
240895
  const task = await deps.store.getTask(taskId);
@@ -240935,8 +241465,8 @@ async function advanceNoMergeWorkflowToCompleteColumn(store, task) {
240935
241465
  init_logger();
240936
241466
  import { exec as exec50 } from "node:child_process";
240937
241467
  import { existsSync as existsSync73, statSync as statSync15 } from "node:fs";
240938
- import { promisify as promisify69 } from "node:util";
240939
- var execAsync47 = promisify69(exec50);
241468
+ import { promisify as promisify70 } from "node:util";
241469
+ var execAsync47 = promisify70(exec50);
240940
241470
  async function runTaskStep(deps, task, stepIndex, opts = {}) {
240941
241471
  const { store, worktreePath } = deps;
240942
241472
  const gitRevParse = deps.gitRevParse ?? defaultGitRevParse;
@@ -243022,8 +243552,8 @@ async function recoverFailedPreMergeWorkflowStep(deps, task) {
243022
243552
  init_src2();
243023
243553
  init_logger();
243024
243554
  import { exec as exec51 } from "node:child_process";
243025
- import { promisify as promisify70 } from "node:util";
243026
- var execAsync48 = promisify70(exec51);
243555
+ import { promisify as promisify71 } from "node:util";
243556
+ var execAsync48 = promisify71(exec51);
243027
243557
  async function reconcileStepsFromGitHistory(deps, taskId, detail, worktreePath) {
243028
243558
  const baseCommitSha = detail.baseCommitSha;
243029
243559
  if (!baseCommitSha) return;
@@ -245347,7 +245877,14 @@ async function persistWorkflowStepResultWithOutcome(deps, taskId, result, fence
245347
245877
  scopeSuperseded = true;
245348
245878
  return null;
245349
245879
  }
245350
- const built = buildWorkflowStepResultPatch(current, result, isPlanReviewResult);
245880
+ const unprovenApproval = resolveUnprovenReviewApproval(result, {
245881
+ workspace: current.workspaceWorktrees !== void 0
245882
+ });
245883
+ const built = buildWorkflowStepResultPatch(
245884
+ current,
245885
+ unprovenApproval?.downgraded ?? result,
245886
+ isPlanReviewResult
245887
+ );
245351
245888
  activityResult = built.resultToPersist;
245352
245889
  activityResults = built.results;
245353
245890
  return built.patch;
@@ -245384,21 +245921,30 @@ async function persistWorkflowStepResultWithOutcome(deps, taskId, result, fence
245384
245921
  if (scopeSuperseded) return { scopeCurrent: false, persisted: false };
245385
245922
  if (fenceRefused) return { scopeCurrent: true, persisted: false };
245386
245923
  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";
245924
+ const approvalDowngraded = result.status === "passed" && persistedResult.status === "failed" && result.reviewInputFingerprint === void 0 && persistedResult.verdict === void 0;
245925
+ if (approvalDowngraded) {
245926
+ await deps.store.logEntry(
245927
+ taskId,
245928
+ `[pre-merge] ${result.workflowStepName} approval invalidated: ${persistedResult.notes ?? persistedResult.output ?? "review input proof missing"}`,
245929
+ void 0,
245930
+ deps.getRunContextFor(taskId)
245931
+ ).catch(() => void 0);
245932
+ }
245933
+ if (isTerminalStepResult(persistedResult)) {
245934
+ const passed = persistedResult.status === "passed" || persistedResult.status === "skipped" || persistedResult.verdict === "APPROVE" || persistedResult.verdict === "APPROVE_WITH_NOTES" || persistedResult.verdict === "CLOSE_NO_OP";
245389
245935
  try {
245390
245936
  await deps.store.recordAgentActivity({
245391
245937
  type: passed ? "workflow:gate-passed" : "workflow:gate-failed",
245392
245938
  attributionClaim: resolveWorkflowGateActivityClaim(
245393
- deps.workflowGateActivityPrincipals?.get(`${taskId}\0${result.workflowStepId}`) ?? deps.activeWorkflowPrincipals?.get(taskId)?.agentId,
245939
+ deps.workflowGateActivityPrincipals?.get(`${taskId}\0${persistedResult.workflowStepId}`) ?? deps.activeWorkflowPrincipals?.get(taskId)?.agentId,
245394
245940
  live?.assignedAgentId
245395
245941
  ),
245396
245942
  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)
245943
+ occurredAt: persistedResult.completedAt ?? persistedResult.startedAt ?? (/* @__PURE__ */ new Date()).toISOString(),
245944
+ discriminator: `${persistedResult.workflowStepId}:${persistedResult.startedAt ?? persistedResult.completedAt ?? persistedResult.status}`,
245945
+ metadata: buildWorkflowGateActivityMetadata(persistedResult, persistedResult)
245400
245946
  });
245401
- deps.workflowGateActivityPrincipals?.delete(`${taskId}\0${result.workflowStepId}`);
245947
+ deps.workflowGateActivityPrincipals?.delete(`${taskId}\0${persistedResult.workflowStepId}`);
245402
245948
  } catch (error2) {
245403
245949
  executorLog.warn(`[agent-activity] ${taskId}: failed to record workflow gate activity: ${error2 instanceof Error ? error2.message : String(error2)}`);
245404
245950
  }
@@ -247323,8 +247869,8 @@ async function runSpawnedChild(deps, agentId, childSession, taskPrompt) {
247323
247869
 
247324
247870
  // ../engine/src/auto-recovery-handlers/file-scope.ts
247325
247871
  import { exec as exec52 } from "node:child_process";
247326
- import { promisify as promisify71 } from "node:util";
247327
- var execAsync49 = promisify71(exec52);
247872
+ import { promisify as promisify72 } from "node:util";
247873
+ var execAsync49 = promisify72(exec52);
247328
247874
  var FileScopeAutoRecoveryHandler = class {
247329
247875
  constructor(deps) {
247330
247876
  this.deps = deps;
@@ -247349,8 +247895,8 @@ init_integration_branch();
247349
247895
  init_logger();
247350
247896
  import { exec as exec53 } from "node:child_process";
247351
247897
  import { existsSync as existsSync76 } from "node:fs";
247352
- import { promisify as promisify72 } from "node:util";
247353
- var execAsync50 = promisify72(exec53);
247898
+ import { promisify as promisify73 } from "node:util";
247899
+ var execAsync50 = promisify73(exec53);
247354
247900
  var baseLog2 = createLogger("auto-recovery:branch-worktree");
247355
247901
  var GIT_TIMEOUT_MS8 = 3e4;
247356
247902
  var GIT_MAX_BUFFER6 = 10 * 1024 * 1024;
@@ -248340,8 +248886,8 @@ init_src2();
248340
248886
  init_worktree_names();
248341
248887
  init_logger();
248342
248888
  import { exec as exec54 } from "node:child_process";
248343
- import { promisify as promisify73 } from "node:util";
248344
- var execAsync51 = promisify73(exec54);
248889
+ import { promisify as promisify74 } from "node:util";
248890
+ var execAsync51 = promisify74(exec54);
248345
248891
  async function resetStepsIfWorkLost(deps, task) {
248346
248892
  const completedSteps = task.steps.filter(
248347
248893
  (s3) => s3.status === "done" || s3.status === "in-progress"
@@ -248416,8 +248962,8 @@ init_worktree_names();
248416
248962
  init_worktree_paths();
248417
248963
  init_logger();
248418
248964
  import { exec as exec55 } from "node:child_process";
248419
- import { promisify as promisify74 } from "node:util";
248420
- var execAsync52 = promisify74(exec55);
248965
+ import { promisify as promisify75 } from "node:util";
248966
+ var execAsync52 = promisify75(exec55);
248421
248967
  var WORKSPACE_ISOLATION_UNSUPPORTED_MESSAGE = "per-instance worktree isolation is not supported for workspace projects";
248422
248968
  function buildForeachWorktreeDeps(deps, task, runId) {
248423
248969
  const taskId = task.id;
@@ -248610,7 +249156,7 @@ init_src2();
248610
249156
  // ../engine/src/executor/derive-remediation-steps.ts
248611
249157
  init_src2();
248612
249158
  init_merger_file_scope();
248613
- import { createHash as createHash34 } from "node:crypto";
249159
+ import { createHash as createHash35 } from "node:crypto";
248614
249160
  var fileReference = /(?:^|[\s(])([\w@./-]+\.(?:[cm]?[jt]sx?|json|md|css|html|yml|yaml))(?::(\d+))?/gm;
248615
249161
  var ansiEscapeSequence = /\u001B\[[0-?]*[ -/]*[@-~]/g;
248616
249162
  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 +249169,7 @@ function normalizeVerificationEvidence(output) {
248623
249169
  }
248624
249170
  function verificationEvidenceDigest(output) {
248625
249171
  const evidence = normalizeVerificationEvidence(output);
248626
- return evidence ? createHash34("sha256").update(evidence).digest("hex").slice(0, 16) : void 0;
249172
+ return evidence ? createHash35("sha256").update(evidence).digest("hex").slice(0, 16) : void 0;
248627
249173
  }
248628
249174
  function verificationCandidates(input) {
248629
249175
  const detail = input.verificationCommandLabel ?? "verification command";
@@ -252011,6 +252557,8 @@ async function runGraphCustomNode(deps, node, nodeTask, settings, columnBinding,
252011
252557
  }
252012
252558
  const optionalGroupId = typeof graphContext?.[WORKFLOW_OPTIONAL_GROUP_CONTEXT_KEY] === "string" ? graphContext[WORKFLOW_OPTIONAL_GROUP_CONTEXT_KEY] : void 0;
252013
252559
  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;
252560
+ const effectiveStepId = optionalGroupId ?? node.id;
252561
+ const isContentBindingStep = requiresContentReviewProof(effectiveStepId, { reviewKind: declaredReviewKind });
252014
252562
  if (isFastExecutionMode(live) && graphContext?.["workflow:fast-lane-active"] === true && isFastLaneSkippableCustomNode(node, { optionalGroupId })) {
252015
252563
  executorLog.debug(`${live.id}: fast mode \u2014 skipping custom graph node '${node.id}'`);
252016
252564
  await deps.store.logEntry(
@@ -252205,6 +252753,7 @@ ${prompt}`;
252205
252753
  const now = (/* @__PURE__ */ new Date()).toISOString();
252206
252754
  const stepSkillName = executorKind === "skill" && typeof cfg.skillName === "string" && cfg.skillName.trim() ? cfg.skillName.trim() : void 0;
252207
252755
  const stepThinkingLevel = typeof cfg.thinkingLevel === "string" && WORKFLOW_THINKING_LEVEL_SET2.has(cfg.thinkingLevel) ? cfg.thinkingLevel : void 0;
252756
+ const readonlyMcpServers = Array.isArray(cfg.readonlyMcpServers) ? [...new Set(cfg.readonlyMcpServers.filter((name2) => typeof name2 === "string").map((name2) => name2.trim()).filter(Boolean))] : [];
252208
252757
  const step = {
252209
252758
  id: `graph:${node.id}`,
252210
252759
  name: typeof cfg.name === "string" && cfg.name.trim() ? cfg.name : node.id,
@@ -252221,7 +252770,8 @@ ${prompt}`;
252221
252770
  ...stepSkillName ? { skillName: stepSkillName } : {},
252222
252771
  ...cfg.requiresBrowser === true ? { requiresBrowser: true } : {},
252223
252772
  ...modelProvider && modelId ? { modelProvider, modelId } : {},
252224
- ...stepThinkingLevel ? { thinkingLevel: stepThinkingLevel } : {}
252773
+ ...stepThinkingLevel ? { thinkingLevel: stepThinkingLevel } : {},
252774
+ ...readonlyMcpServers.length > 0 ? { readonlyMcpServers } : {}
252225
252775
  };
252226
252776
  if (cfg.summaryTarget === "task") {
252227
252777
  step.summaryTarget = "task";
@@ -252335,12 +252885,36 @@ ${prompt}`;
252335
252885
  outcome = buildWorkspaceReviewOutcome(aggregate, { superseded: reviewSuperseded });
252336
252886
  }
252337
252887
  } 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
- });
252888
+ const dispatchSingularStep = async (reviewInputFingerprint) => {
252889
+ if (mode === "script") {
252890
+ const scriptOutcome = await deps.executeScriptWorkflowStep(live, step, worktreePath, settings, nodeEnv);
252891
+ return reviewInputFingerprint === void 0 ? scriptOutcome : { ...scriptOutcome, reviewInputFingerprint };
252892
+ }
252893
+ return deps.executeWorkflowStep(live, step, worktreePath, settings, nodeEnv, {
252894
+ unattended,
252895
+ principalAgentId,
252896
+ outputLanguage,
252897
+ ...nodeSessionBoundary ? { sessionBoundary: nodeSessionBoundary } : {},
252898
+ ...reviewInputFingerprint !== void 0 ? { reviewInputFingerprint } : {}
252899
+ });
252900
+ };
252901
+ if (isContentBindingStep && !workspaceConfig) {
252902
+ const proof = await resolveContentReviewInputProof(worktreePath, executionTarget.baseCommitSha);
252903
+ if (proof.kind === "unprovable") {
252904
+ const diagnostic = `${step.name} review input is unprovable (${proof.reason}); reviewer dispatch refused.`;
252905
+ await deps.store.logEntry(
252906
+ live.id,
252907
+ `[pre-merge] ${diagnostic}`,
252908
+ void 0,
252909
+ deps.getRunContextFor(live.id)
252910
+ );
252911
+ outcome = { success: false, error: diagnostic, failureValue: "review-input-unprovable" };
252912
+ } else {
252913
+ outcome = await dispatchSingularStep(proof.fingerprint);
252914
+ }
252915
+ } else {
252916
+ outcome = await dispatchSingularStep();
252917
+ }
252344
252918
  }
252345
252919
  if (declaredReviewKind && typeof outcome.output === "string") {
252346
252920
  const rawReviewOutput = outcome.output;
@@ -253328,7 +253902,7 @@ async function runImplementationPhase(deps, task, prepared) {
253328
253902
  // ../engine/src/executor/run-implementation.ts
253329
253903
  init_src2();
253330
253904
  import { exec as exec56 } from "node:child_process";
253331
- import { promisify as promisify76 } from "node:util";
253905
+ import { promisify as promisify77 } from "node:util";
253332
253906
  import { existsSync as existsSync80 } from "node:fs";
253333
253907
  import { join as join91 } from "node:path";
253334
253908
  import { SessionManager as SessionManager2 } from "@earendil-works/pi-coding-agent";
@@ -254203,11 +254777,11 @@ init_logger();
254203
254777
  // ../engine/src/merge/merger-orphan-rehome.ts
254204
254778
  init_src2();
254205
254779
  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);
254780
+ import { execFile as execFile32 } from "node:child_process";
254781
+ import { promisify as promisify76 } from "node:util";
254782
+ var execFileAsync26 = promisify76(execFile32);
254209
254783
  async function runGit5(args, cwd) {
254210
- const { stdout } = await execFileAsync25("git", args, {
254784
+ const { stdout } = await execFileAsync26("git", args, {
254211
254785
  cwd,
254212
254786
  encoding: "utf-8",
254213
254787
  timeout: 3e4
@@ -254216,7 +254790,7 @@ async function runGit5(args, cwd) {
254216
254790
  }
254217
254791
  async function isAncestor3(repoDir, ancestor, descendant) {
254218
254792
  try {
254219
- await execFileAsync25(
254793
+ await execFileAsync26(
254220
254794
  "git",
254221
254795
  ["merge-base", "--is-ancestor", ancestor, descendant],
254222
254796
  { cwd: repoDir, encoding: "utf-8", timeout: 3e4 }
@@ -254320,7 +254894,7 @@ init_run_audit();
254320
254894
  init_worktree_acquisition();
254321
254895
  init_worktree_paths();
254322
254896
  init_worktree_pool();
254323
- var execAsync53 = promisify76(exec56);
254897
+ var execAsync53 = promisify77(exec56);
254324
254898
  var MAX_TASK_DONE_SESSION_RETRIES = 3;
254325
254899
  async function parkExternalSessionObstacle(deps, taskId, errorMessage) {
254326
254900
  const obstacle = classifyExternalObstacle(errorMessage);
@@ -260093,8 +260667,8 @@ init_active_session_registry();
260093
260667
  init_agent_session_helpers();
260094
260668
  init_logger();
260095
260669
  init_effective_settings();
260096
- import { execFile as execFile32 } from "node:child_process";
260097
- import { promisify as promisify77 } from "node:util";
260670
+ import { execFile as execFile33 } from "node:child_process";
260671
+ import { promisify as promisify78 } from "node:util";
260098
260672
 
260099
260673
  // ../engine/src/agents/workflow-agent-capacity.ts
260100
260674
  var WorkflowAgentCapacity = class _WorkflowAgentCapacity {
@@ -260171,7 +260745,7 @@ var WorkflowAgentCapacity = class _WorkflowAgentCapacity {
260171
260745
  };
260172
260746
 
260173
260747
  // ../engine/src/executor/wire-executor-lifecycle.ts
260174
- var execFileAsync26 = promisify77(execFile32);
260748
+ var execFileAsync27 = promisify78(execFile33);
260175
260749
  async function releaseWorkspaceAcquireClaims(store, taskId) {
260176
260750
  for (const entry of activeSessionRegistry.entriesByKind("workspace-repo-acquire")) {
260177
260751
  if (entry.taskId === taskId) activeSessionRegistry.unregisterPath(entry.path);
@@ -260263,7 +260837,7 @@ function wireExecutorLifecycle(deps) {
260263
260837
  if (await canonicalizeWorktreePath(entry.worktreePath) === await canonicalizeWorktreePath(entry.repoRootDir)) throw new Error("Refusing to remove workspace repository root");
260264
260838
  activeSessionRegistry.unregisterPath(entry.worktreePath);
260265
260839
  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 });
260840
+ await execFileAsync27("git", ["branch", "-D", entry.branch], { cwd: entry.repoRootDir, timeout: 12e4, maxBuffer: 10 * 1024 * 1024 });
260267
260841
  if (task.workspaceWorktrees) for (const repoRel of [entry.repoRel, ...entry.aliasRepoRels]) delete task.workspaceWorktrees[repoRel];
260268
260842
  removed.push(entry.repoRel);
260269
260843
  } catch (error2) {
@@ -262939,9 +263513,9 @@ var BUNDLED_CLI_ADAPTERS = Object.freeze([
262939
263513
 
262940
263514
  // ../engine/src/cli-agent/resume-coordinator.ts
262941
263515
  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);
263516
+ import { execFile as execFile34 } from "node:child_process";
263517
+ import { promisify as promisify79 } from "node:util";
263518
+ var execFileAsync28 = promisify79(execFile34);
262945
263519
  var ORPHANED_LIVE_STATES = /* @__PURE__ */ new Set([
262946
263520
  "starting",
262947
263521
  "ready",
@@ -262952,7 +263526,7 @@ var DEFAULT_MAX_RESUME_ATTEMPTS = 2;
262952
263526
  var DEFAULT_RESUME_BACKOFF_BASE_MS = 1e3;
262953
263527
  async function defaultIsWorktreeDirty(worktreePath) {
262954
263528
  try {
262955
- const { stdout } = await execFileAsync27("git", ["status", "--porcelain"], {
263529
+ const { stdout } = await execFileAsync28("git", ["status", "--porcelain"], {
262956
263530
  cwd: worktreePath
262957
263531
  });
262958
263532
  return stdout.trim().length > 0;
@@ -265301,7 +265875,7 @@ init_notifier();
265301
265875
 
265302
265876
  // ../engine/src/healing/stuck-task-detector.ts
265303
265877
  init_logger();
265304
- import { createHash as createHash35 } from "node:crypto";
265878
+ import { createHash as createHash36 } from "node:crypto";
265305
265879
  var stuckLog = createLogger("stuck-detector");
265306
265880
  var LOOP_ACTIVITY_THRESHOLD = 60;
265307
265881
  var LOOP_IGNORED_STEP_MIN = 10;
@@ -265312,7 +265886,7 @@ var TOOL_DETAIL_HASH_HEX_LEN = 16;
265312
265886
  var NO_PROGRESS_CHURN_THRESHOLD = 25;
265313
265887
  var VERIFICATION_DEADLINE_GRACE_MS = 5e3;
265314
265888
  function hashToolDetail(detail) {
265315
- return createHash35("sha256").update(detail).digest("hex").slice(0, TOOL_DETAIL_HASH_HEX_LEN);
265889
+ return createHash36("sha256").update(detail).digest("hex").slice(0, TOOL_DETAIL_HASH_HEX_LEN);
265316
265890
  }
265317
265891
  function buildToolFingerprint(toolName, toolDetail) {
265318
265892
  const name2 = toolName.trim().toLowerCase() || "tool";
@@ -268021,7 +268595,7 @@ var MissionAutopilot = class {
268021
268595
  // ../engine/src/missions/mission-execution-loop.ts
268022
268596
  init_src2();
268023
268597
  import { EventEmitter as EventEmitter26 } from "node:events";
268024
- import { createHash as createHash36 } from "node:crypto";
268598
+ import { createHash as createHash37 } from "node:crypto";
268025
268599
 
268026
268600
  // ../engine/src/missions/mission-verification.ts
268027
268601
  init_sandbox();
@@ -268033,8 +268607,8 @@ import { promises as fs9 } from "node:fs";
268033
268607
  import os7 from "node:os";
268034
268608
  import path5 from "node:path";
268035
268609
  import { exec as exec58 } from "node:child_process";
268036
- import { promisify as promisify79 } from "node:util";
268037
- var execAsync54 = promisify79(exec58);
268610
+ import { promisify as promisify80 } from "node:util";
268611
+ var execAsync54 = promisify80(exec58);
268038
268612
  var verifyLog = createLogger("mission-verify");
268039
268613
  var GitCheckoutMaterializer = class {
268040
268614
  async materialize(rootDir, revision) {
@@ -268083,8 +268657,8 @@ init_fallback_model_observer();
268083
268657
  init_mcp_resolution();
268084
268658
  init_run_audit();
268085
268659
  import { exec as exec59 } from "node:child_process";
268086
- import { promisify as promisify80 } from "node:util";
268087
- var execAsync55 = promisify80(exec59);
268660
+ import { promisify as promisify81 } from "node:util";
268661
+ var execAsync55 = promisify81(exec59);
268088
268662
  function quoteShellArg8(value) {
268089
268663
  return `'${value.replace(/'/g, `'\\''`)}'`;
268090
268664
  }
@@ -268094,7 +268668,7 @@ var MAX_VALIDATION_RESPONSE_BYTES = 256 * 1024;
268094
268668
  var MAX_VALIDATION_JSON_CANDIDATES = 8;
268095
268669
  var VALIDATION_FAILURE_BUDGET_PER_FINGERPRINT = 3;
268096
268670
  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");
268671
+ return createHash37("sha256").update(Buffer.from(JSON.stringify(["mission-validation-input-v1", landedSha, provider, modelId, systemPrompt, userPrompt]), "utf8")).digest("hex");
268098
268672
  }
268099
268673
  var MissionExecutionLoop = class extends EventEmitter26 {
268100
268674
  running = false;
@@ -269802,7 +270376,7 @@ import { exec as exec60 } from "node:child_process";
269802
270376
  import { existsSync as existsSync84 } from "node:fs";
269803
270377
  import { readFile as readFile50, writeFile as writeFile29, rm as rm16 } from "node:fs/promises";
269804
270378
  import { join as join94 } from "node:path";
269805
- import { promisify as promisify81 } from "node:util";
270379
+ import { promisify as promisify82 } from "node:util";
269806
270380
 
269807
270381
  // ../engine/src/triage-domain/triage-preflight.ts
269808
270382
  var BUG_FIX_REGEX = /typecheck error|compile error|broken|regression|lint error/i;
@@ -273020,7 +273594,7 @@ ${lines.join("\n")}` }],
273020
273594
  written,
273021
273595
  {
273022
273596
  cwd: this.rootDir,
273023
- exec: promisify81(exec60)
273597
+ exec: promisify82(exec60)
273024
273598
  }
273025
273599
  ),
273026
273600
  new Promise((resolve52) => setTimeout(() => resolve52(null), 15e3))
@@ -273797,7 +274371,7 @@ function stableNotificationJson(value) {
273797
274371
  return `{${fields.join(",")}}`;
273798
274372
  }
273799
274373
  function buildCliAgentNotificationDedupeKey(input) {
273800
- const notificationFingerprint = createHash37("sha256").update(stableNotificationJson(input.info.notification ?? null)).digest("hex").slice(0, 16);
274374
+ const notificationFingerprint = createHash38("sha256").update(stableNotificationJson(input.info.notification ?? null)).digest("hex").slice(0, 16);
273801
274375
  const waitingEpoch = input.session?.updatedAt ?? "unknown-waiting-epoch";
273802
274376
  return [
273803
274377
  "cli-agent",
@@ -278487,8 +279061,8 @@ init_src2();
278487
279061
  init_agent_user_comments();
278488
279062
  init_worktree_names();
278489
279063
  init_integration_branch();
278490
- import { execFile as execFile38 } from "node:child_process";
278491
- import { promisify as promisify86 } from "node:util";
279064
+ import { execFile as execFile39 } from "node:child_process";
279065
+ import { promisify as promisify87 } from "node:util";
278492
279066
  import { realpathSync as realpathSync15, readdirSync as readdirSync13 } from "node:fs";
278493
279067
  import { mkdtemp as mkdtemp5 } from "node:fs/promises";
278494
279068
  import { tmpdir as tmpdir12 } from "node:os";
@@ -278497,10 +279071,10 @@ init_workspace_base_branch();
278497
279071
  init_merger_ref_update_advance();
278498
279072
 
278499
279073
  // ../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";
279074
+ import { execFile as execFile35 } from "node:child_process";
279075
+ import { promisify as promisify83 } from "node:util";
278502
279076
  init_merger_file_scope();
278503
- var execFileAsync28 = promisify82(execFile34);
279077
+ var execFileAsync29 = promisify83(execFile35);
278504
279078
  function resolveRepoDeclaredScopeTransform({ repoRel, repoKeys }) {
278505
279079
  return {
278506
279080
  transform(scope) {
@@ -278531,8 +279105,8 @@ async function enforceAiMergeSquashGates(params) {
278531
279105
  });
278532
279106
  } catch (error2) {
278533
279107
  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 });
279108
+ await execFileAsync29("git", ["reset", "--hard", params.tipSha], { cwd: params.mergeRoot });
279109
+ await execFileAsync29("git", ["clean", "-fd"], { cwd: params.mergeRoot });
278536
279110
  try {
278537
279111
  await params.store.updateTask(params.taskId, { aiMergeReviewReconciliation: null });
278538
279112
  } catch {
@@ -278721,10 +279295,10 @@ function resolveWorkspaceMergeReadiness(task, freshModifiedRepositories, netZero
278721
279295
  }
278722
279296
 
278723
279297
  // ../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);
279298
+ import { execFile as execFile36 } from "node:child_process";
279299
+ import { createHash as createHash39 } from "node:crypto";
279300
+ import { promisify as promisify84 } from "node:util";
279301
+ var execFileAsync30 = promisify84(execFile36);
278728
279302
  var WorkspaceFenceRefError = class extends Error {
278729
279303
  constructor(message, kind) {
278730
279304
  super(message);
@@ -278733,7 +279307,7 @@ var WorkspaceFenceRefError = class extends Error {
278733
279307
  }
278734
279308
  };
278735
279309
  async function git6(args, cwd) {
278736
- const { stdout } = await execFileAsync29("git", args, {
279310
+ const { stdout } = await execFileAsync30("git", args, {
278737
279311
  cwd,
278738
279312
  encoding: "utf8",
278739
279313
  maxBuffer: 1024 * 1024
@@ -278746,7 +279320,7 @@ async function remoteRefSha(remote, ref, cwd) {
278746
279320
  return /^[0-9a-f]{40,64}$/i.test(sha ?? "") ? sha : void 0;
278747
279321
  }
278748
279322
  function workspaceLandFenceRef(repoRelPath) {
278749
- const slug2 = createHash38("sha256").update(repoRelPath).digest("hex").slice(0, 24);
279323
+ const slug2 = createHash39("sha256").update(repoRelPath).digest("hex").slice(0, 24);
278750
279324
  return `refs/fusion/workspace-lease/${slug2}`;
278751
279325
  }
278752
279326
  function mergeDispatchFenceRef(taskId) {
@@ -278755,7 +279329,7 @@ function mergeDispatchFenceRef(taskId) {
278755
279329
  async function createFenceObject(cwd, handle2) {
278756
279330
  const tree = await git6(["hash-object", "-w", "-t", "tree", "/dev/null"], cwd);
278757
279331
  const message = `workspace lease ${handle2.leaseKey} ${handle2.owner.taskId} ${handle2.owner.nodeId} ${handle2.owner.incarnationId} ${handle2.fenceToken}`;
278758
- const { stdout } = await execFileAsync29("git", [
279332
+ const { stdout } = await execFileAsync30("git", [
278759
279333
  "-c",
278760
279334
  "user.name=Fusion",
278761
279335
  "-c",
@@ -278890,9 +279464,9 @@ async function publishWorkspaceIntegrationRef(input) {
278890
279464
  init_push_after_merge_policy();
278891
279465
 
278892
279466
  // ../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);
279467
+ import { execFile as execFile37 } from "node:child_process";
279468
+ import { promisify as promisify85 } from "node:util";
279469
+ var execFileAsync31 = promisify85(execFile37);
278896
279470
  var WorkspaceEnvironmentError = class extends Error {
278897
279471
  constructor(repository, resource, action, message) {
278898
279472
  super(message);
@@ -278912,7 +279486,7 @@ var WorkspaceIntegrationTargetError = class extends WorkspaceEnvironmentError {
278912
279486
  }
278913
279487
  };
278914
279488
  async function git7(args, cwd) {
278915
- const { stdout } = await execFileAsync30("git", args, { cwd, encoding: "utf8" });
279489
+ const { stdout } = await execFileAsync31("git", args, { cwd, encoding: "utf8" });
278916
279490
  return stdout.trim();
278917
279491
  }
278918
279492
  async function resolveWorkspaceIntegrationTarget(input) {
@@ -278959,17 +279533,17 @@ init_worktree_paths();
278959
279533
 
278960
279534
  // ../engine/src/merge/merger-ai-worktree.ts
278961
279535
  init_active_session_registry();
278962
- import { execFile as execFile37 } from "node:child_process";
279536
+ import { execFile as execFile38 } from "node:child_process";
278963
279537
  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
279538
  import { rm as rm17 } from "node:fs/promises";
278965
279539
  import { tmpdir as tmpdir11 } from "node:os";
278966
279540
  import { isAbsolute as isAbsolute27, join as join96, relative as relative23 } from "node:path";
278967
- import { promisify as promisify85 } from "node:util";
279541
+ import { promisify as promisify86 } from "node:util";
278968
279542
  init_worktree_paths();
278969
279543
  init_worktree_removal_retry();
278970
- var execFileAsync31 = promisify85(execFile37);
279544
+ var execFileAsync32 = promisify86(execFile38);
278971
279545
  async function git8(args, cwd, opts = {}) {
278972
- const { stdout } = await execFileAsync31("git", args, {
279546
+ const { stdout } = await execFileAsync32("git", args, {
278973
279547
  cwd,
278974
279548
  encoding: "utf-8",
278975
279549
  timeout: opts.timeout ?? 12e4,
@@ -278998,14 +279572,7 @@ function ensureAiMergeRootIgnored(projectRootDir, settings) {
278998
279572
  if (!existsSync87(excludePath)) return;
278999
279573
  try {
279000
279574
  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
- }
279575
+ const entries = resolveAiMergeSearchRoots(projectRootDir, settings).map((root) => relative23(projectRootDir, root)).filter((root) => root && !root.startsWith("..") && !isAbsolute27(root)).map((root) => `${root.replaceAll("\\", "/")}/`);
279009
279576
  const missing = entries.filter((entry) => !current.split(/\r?\n/).includes(entry));
279010
279577
  if (missing.length > 0) {
279011
279578
  appendFileSync2(excludePath, `${current.endsWith("\n") ? "" : "\n"}${missing.join("\n")}
@@ -279021,7 +279588,7 @@ function resolveAiMergeRoot(projectRootDir, settings) {
279021
279588
  return root;
279022
279589
  }
279023
279590
  function getAiMergeTempSearchRoots(projectRootDir, settings) {
279024
- const roots = [resolveAiMergeRoot(projectRootDir, settings), resolveLegacyAiMergeRootPath(projectRootDir), tmpdir11()];
279591
+ const roots = [resolveAiMergeRoot(projectRootDir, settings), ...resolveAiMergeSearchRoots(projectRootDir, settings), tmpdir11()];
279025
279592
  const testWorkerRoot = process.env.FUSION_TEST_WORKER_ROOT;
279026
279593
  if (testWorkerRoot) {
279027
279594
  try {
@@ -279074,7 +279641,7 @@ async function pruneExistingAiMergeWorktrees(taskId, projectRootDir, audit, log4
279074
279641
  let alreadyAbsent = false;
279075
279642
  try {
279076
279643
  cleanupAttempted = true;
279077
- await execFileAsync31("git", ["worktree", "remove", "--force", canonicalPath], {
279644
+ await execFileAsync32("git", ["worktree", "remove", "--force", canonicalPath], {
279078
279645
  cwd: projectRootDir,
279079
279646
  timeout: 3e4
279080
279647
  });
@@ -279105,7 +279672,7 @@ async function pruneExistingAiMergeWorktrees(taskId, projectRootDir, audit, log4
279105
279672
  }
279106
279673
  if (cleanupAttempted) {
279107
279674
  try {
279108
- await execFileAsync31("git", ["worktree", "prune"], { cwd: projectRootDir, timeout: 3e4 });
279675
+ await execFileAsync32("git", ["worktree", "prune"], { cwd: projectRootDir, timeout: 3e4 });
279109
279676
  } catch (err) {
279110
279677
  await log42(`AI merge pre-merge prune: git worktree prune failed: ${describeCleanupError(err)}`);
279111
279678
  }
@@ -279514,7 +280081,7 @@ function short(sha) {
279514
280081
  }
279515
280082
 
279516
280083
  // ../engine/src/merge/merger-ai.ts
279517
- var execFileAsync32 = promisify86(execFile38);
280084
+ var execFileAsync33 = promisify87(execFile39);
279518
280085
  var aiMergeLog = createLogger("merger-ai");
279519
280086
  async function resolveNoOpFinalizeGateIds3(store, task) {
279520
280087
  const selection = store.getTaskWorkflowSelectionAsync ? await store.getTaskWorkflowSelectionAsync(task.id) : store.getTaskWorkflowSelection?.(task.id);
@@ -279540,7 +280107,7 @@ function writeTransientMergeStatus(store, taskId, signal, status) {
279540
280107
  return fence.write("lifecycle", () => store.updateTask(taskId, { status }).catch(() => void 0));
279541
280108
  }
279542
280109
  async function git9(args, cwd, opts = {}) {
279543
- const { stdout } = await execFileAsync32("git", args, {
280110
+ const { stdout } = await execFileAsync33("git", args, {
279544
280111
  cwd,
279545
280112
  encoding: "utf-8",
279546
280113
  timeout: opts.timeout ?? 12e4,
@@ -279581,7 +280148,7 @@ function boundBlockingReviewReasons(reasons) {
279581
280148
  }
279582
280149
  function listAiMergeWorktreeCandidates(taskId, projectRootDir, settings) {
279583
280150
  const prefix = `fusion-ai-merge-${taskId.toLowerCase()}-`;
279584
- const roots = Array.from(/* @__PURE__ */ new Set([resolveAiMergeRoot(projectRootDir, settings), resolveLegacyAiMergeRootPath(projectRootDir), tmpdir12()]));
280151
+ const roots = Array.from(/* @__PURE__ */ new Set([resolveAiMergeRoot(projectRootDir, settings), ...resolveAiMergeSearchRoots(projectRootDir, settings), tmpdir12()]));
279585
280152
  const testWorkerRoot = process.env.FUSION_TEST_WORKER_ROOT;
279586
280153
  if (testWorkerRoot) {
279587
280154
  try {
@@ -282166,8 +282733,8 @@ init_transient_merge_error_classifier();
282166
282733
 
282167
282734
  // ../engine/src/remote-access/remote-tunnel-service.ts
282168
282735
  init_logger();
282169
- import { execFile as execFile40 } from "node:child_process";
282170
- import { promisify as promisify88 } from "node:util";
282736
+ import { execFile as execFile41 } from "node:child_process";
282737
+ import { promisify as promisify89 } from "node:util";
282171
282738
 
282172
282739
  // ../engine/src/local-dashboard-port.ts
282173
282740
  var DEFAULT_DASHBOARD_PORT = 4040;
@@ -282180,8 +282747,8 @@ function getLocalDashboardPort() {
282180
282747
  init_src2();
282181
282748
  init_logger();
282182
282749
  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";
282750
+ import { exec as exec61, execFile as execFile40, spawn as spawn8 } from "node:child_process";
282751
+ import { promisify as promisify88 } from "node:util";
282185
282752
 
282186
282753
  // ../engine/src/remote-access/provider-adapters.ts
282187
282754
  import { accessSync, constants as fsConstants5 } from "node:fs";
@@ -282349,8 +282916,8 @@ var DEFAULT_MAX_LOG_ENTRIES = 400;
282349
282916
  var DEFAULT_STOP_TIMEOUT_MS2 = 5e3;
282350
282917
  var DEFAULT_RESTART_BASE_DELAY_MS = 1e3;
282351
282918
  var DEFAULT_RESTART_MAX_DELAY_MS = 3e4;
282352
- var execFileAsync33 = promisify87(execFile39);
282353
- var execAsync56 = promisify87(exec61);
282919
+ var execFileAsync34 = promisify88(execFile40);
282920
+ var execAsync56 = promisify88(exec61);
282354
282921
  function serveConfigScopes(config3) {
282355
282922
  const foreground = Object.values(config3.Foreground ?? {}).filter(
282356
282923
  (scope) => Boolean(scope)
@@ -282500,7 +283067,7 @@ var TunnelProcessManager = class extends EventEmitter28 {
282500
283067
  return null;
282501
283068
  }
282502
283069
  try {
282503
- const { stdout } = await execFileAsync33("tailscale", ["status", "--json"], { timeout: 3e3 });
283070
+ const { stdout } = await execFileAsync34("tailscale", ["status", "--json"], { timeout: 3e3 });
282504
283071
  const data = JSON.parse(String(stdout));
282505
283072
  const dnsName = data.Self?.DNSName?.replace(/\.$/, "");
282506
283073
  if (!dnsName) {
@@ -282523,7 +283090,7 @@ var TunnelProcessManager = class extends EventEmitter28 {
282523
283090
  ];
282524
283091
  for (const resetCommand of resetCommands) {
282525
283092
  try {
282526
- await execFileAsync33(resetCommand.command, resetCommand.args, { timeout: 5e3 });
283093
+ await execFileAsync34(resetCommand.command, resetCommand.args, { timeout: 5e3 });
282527
283094
  return;
282528
283095
  } catch {
282529
283096
  }
@@ -282629,7 +283196,7 @@ var TunnelProcessManager = class extends EventEmitter28 {
282629
283196
  async detectActiveFunnel() {
282630
283197
  let stdout = "";
282631
283198
  try {
282632
- const result = await execFileAsync33("tailscale", ["serve", "status", "--json"], { timeout: 5e3 });
283199
+ const result = await execFileAsync34("tailscale", ["serve", "status", "--json"], { timeout: 5e3 });
282633
283200
  stdout = String(result.stdout ?? "");
282634
283201
  } catch (error2) {
282635
283202
  const failure = error2;
@@ -283045,7 +283612,7 @@ var TunnelProcessManager = class extends EventEmitter28 {
283045
283612
  };
283046
283613
 
283047
283614
  // ../engine/src/remote-access/remote-tunnel-service.ts
283048
- var execFileAsync34 = promisify88(execFile40);
283615
+ var execFileAsync35 = promisify89(execFile41);
283049
283616
  var isRemoteActive = (ra) => ra?.activeProvider != null && (ra.providers[ra.activeProvider]?.enabled ?? false);
283050
283617
  var RemoteTunnelService = class {
283051
283618
  manager = new TunnelProcessManager();
@@ -283394,7 +283961,7 @@ var RemoteTunnelService = class {
283394
283961
  async checkTailscaleDaemonReady() {
283395
283962
  let stdout = "";
283396
283963
  try {
283397
- const result = await execFileAsync34("tailscale", ["status", "--json"], {
283964
+ const result = await execFileAsync35("tailscale", ["status", "--json"], {
283398
283965
  timeout: 5e3,
283399
283966
  maxBuffer: 8 * 1024 * 1024
283400
283967
  });
@@ -283442,7 +284009,7 @@ var RemoteTunnelService = class {
283442
284009
  async checkExecutableAvailable(command) {
283443
284010
  const checker = process.platform === "win32" ? "where" : "which";
283444
284011
  try {
283445
- await execFileAsync34(checker, [command]);
284012
+ await execFileAsync35(checker, [command]);
283446
284013
  return { available: true };
283447
284014
  } catch {
283448
284015
  return {
@@ -283625,7 +284192,7 @@ function emitMergeRequestShadowDequeueParityAudit(store, legacyTaskId, shadowTas
283625
284192
  metadata: { legacyTaskId, shadowTaskId, agree }
283626
284193
  }, { log: runtimeLog });
283627
284194
  }
283628
- var execFileAsync35 = promisify89(execFile41);
284195
+ var execFileAsync36 = promisify90(execFile42);
283629
284196
  var MERGE_HANDOFF_GRACE_MS = 300;
283630
284197
  function isActiveMergeRequestState(state) {
283631
284198
  return state === "running" || state === "retrying";
@@ -283671,7 +284238,7 @@ async function verifyMergeConfirmedReachability(args) {
283671
284238
  return { reachable: true, skipped: "no-integration-branch" };
283672
284239
  }
283673
284240
  try {
283674
- await execFileAsync35("git", ["cat-file", "-e", `${commitSha}^{commit}`], {
284241
+ await execFileAsync36("git", ["cat-file", "-e", `${commitSha}^{commit}`], {
283675
284242
  cwd,
283676
284243
  timeout: 1e4
283677
284244
  });
@@ -283680,7 +284247,7 @@ async function verifyMergeConfirmedReachability(args) {
283680
284247
  return { reachable: false, reason: "commit-missing", diagnostic };
283681
284248
  }
283682
284249
  try {
283683
- await execFileAsync35(
284250
+ await execFileAsync36(
283684
284251
  "git",
283685
284252
  ["merge-base", "--is-ancestor", commitSha, `refs/heads/${integrationBranch}`],
283686
284253
  { cwd, timeout: 1e4 }
@@ -283756,6 +284323,7 @@ var ProjectEngine = class _ProjectEngine {
283756
284323
  }
283757
284324
  });
283758
284325
  }
284326
+ staleContentRerouteAuditKeys = /* @__PURE__ */ new Set();
283759
284327
  runtime;
283760
284328
  started = false;
283761
284329
  specDriftReconciler;
@@ -285552,11 +286120,44 @@ ${detail}`);
285552
286120
  workspaceRootDir: this.config.workingDirectory,
285553
286121
  settings
285554
286122
  });
285555
- return getTaskMergeBlocker(task, {
286123
+ const blocker = getTaskMergeBlocker(task, {
285556
286124
  reviewColumns: mergeGate.reviewColumns.size > 0 ? mergeGate.reviewColumns : /* @__PURE__ */ new Set(["in-review"]),
285557
286125
  requiredPreMergeStepIds: mergeGate.requiredPreMergeStepIds,
285558
286126
  mergeContent
285559
286127
  });
286128
+ if (blocker === "task has a pre-merge approval recorded against different content" && mergeContent.kind === "singular") {
286129
+ const reroute = await rerouteSingularStaleContentToReview(store, task, {
286130
+ requiredPreMergeStepIds: mergeGate.requiredPreMergeStepIds,
286131
+ mergeContent
286132
+ }).catch(() => ({
286133
+ rerouted: false,
286134
+ reason: "no-progress",
286135
+ nodeId: void 0,
286136
+ workflowStepId: void 0
286137
+ }));
286138
+ if (reroute.rerouted) {
286139
+ await store.logEntry(task.id, `[pre-merge] Code Review re-entry is owned by the workflow graph after stale content evidence was refused.`);
286140
+ }
286141
+ const auditKey = `${task.id}:${reroute.reason}`;
286142
+ if (!this.staleContentRerouteAuditKeys.has(auditKey)) {
286143
+ this.staleContentRerouteAuditKeys.add(auditKey);
286144
+ await emitBoundedRunAudit2(store, {
286145
+ taskId: task.id,
286146
+ agentId: "merge-gate",
286147
+ runId: `${task.id}:merge-gate`,
286148
+ domain: "database",
286149
+ mutationType: "task:merge-stale-content-review-rerouted",
286150
+ target: task.id,
286151
+ metadata: {
286152
+ taskId: task.id,
286153
+ nodeId: reroute.nodeId,
286154
+ workflowStepId: reroute.workflowStepId,
286155
+ reason: reroute.reason
286156
+ }
286157
+ });
286158
+ }
286159
+ }
286160
+ return blocker;
285560
286161
  }
285561
286162
  /**
285562
286163
  * Remove and return the highest-priority taskId from the merge queue.
@@ -286402,7 +287003,7 @@ ${detail}`);
286402
287003
  }
286403
287004
  const checklist = planConfirmedMergeChecklistReconciliation(task);
286404
287005
  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);
287006
+ const steps = (task.steps ?? []).map((step, index2) => checklist.skippedStepIndexes.includes(index2) ? { ...step, status: "skipped" } : step);
286406
287007
  const workflowStepResults = (task.workflowStepResults ?? []).map((result) => checklist.reconciledWorkflowStepIds.includes(result.workflowStepId) ? { ...result, status: "skipped" } : result);
286407
287008
  await store.updateTask(taskId, { steps, workflowStepResults });
286408
287009
  task.steps = steps;