@runfusion/fusion 0.77.0-beta.5 → 0.77.0-beta.6

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 (114) hide show
  1. package/dist/bin.js +433 -102
  2. package/dist/child-process-worker.js +352 -81
  3. package/dist/client/.vite/manifest.json +261 -261
  4. package/dist/client/assets/{AgentDetailView-CSK_Rnlk.js → AgentDetailView-oyY72uSo.js} +1 -1
  5. package/dist/client/assets/{AgentPermissionPolicyEditor-kG44Adcm.js → AgentPermissionPolicyEditor-C3Qo9p1M.js} +1 -1
  6. package/dist/client/assets/{AgentsView-ClKwKWnU.js → AgentsView-M2MhTIGW.js} +3 -3
  7. package/dist/client/assets/{ChatView-DoOqgkPX.js → ChatView-zKrUSqiQ.js} +1 -1
  8. package/dist/client/assets/{CommandCenter-CJxvPbgn.js → CommandCenter-CpcAt6jt.js} +27 -27
  9. package/dist/client/assets/{DevServerView-C4nyLONX.js → DevServerView-CjTneoG7.js} +1 -1
  10. package/dist/client/assets/{DirectoryPicker-BIvXWIB5.js → DirectoryPicker-BY6V5HLV.js} +1 -1
  11. package/dist/client/assets/{DocumentsView-DH_aZysM.js → DocumentsView-RDK56-qg.js} +1 -1
  12. package/dist/client/assets/{EvalsView-klRK-GJQ.js → EvalsView-DKK4ceUW.js} +1 -1
  13. package/dist/client/assets/{ExperimentalAgentOnboardingModal-CgBmukIm.js → ExperimentalAgentOnboardingModal-DiBPZVwC.js} +1 -1
  14. package/dist/client/assets/{GoalsView-ysh3X2x6.js → GoalsView-DPj59B3l.js} +1 -1
  15. package/dist/client/assets/{InsightsView-BbMjCu7J.js → InsightsView-QLJNkKJp.js} +1 -1
  16. package/dist/client/assets/{MemoryView-B3IgA8Bx.js → MemoryView-CDCS-1ig.js} +1 -1
  17. package/dist/client/assets/{PiExtensionsManager-CZlIw7C3.js → PiExtensionsManager-DktXXrmF.js} +1 -1
  18. package/dist/client/assets/{PluginManager-vy0YZ2Nx.js → PluginManager-CQ3iEFMq.js} +1 -1
  19. package/dist/client/assets/{PullRequestView-DhhsJV8I.js → PullRequestView-CkObT6e3.js} +1 -1
  20. package/dist/client/assets/{ReportModal-CQMn4i1y.js → ReportModal-B5LTdDgi.js} +1 -1
  21. package/dist/client/assets/{ResearchView-CVwfs4l3.js → ResearchView-CryFrV8l.js} +1 -1
  22. package/dist/client/assets/{SecretsView-CrJfHle9.js → SecretsView-9YcNW_fo.js} +1 -1
  23. package/dist/client/assets/{SessionTerminal-BRI8W1-G.js → SessionTerminal-BWsA9puD.js} +1 -1
  24. package/dist/client/assets/{SettingsModal-h1A_aFqD.js → SettingsModal-CL6aiXiI.js} +4 -4
  25. package/dist/client/assets/{SettingsModal-B6U5Umru.js → SettingsModal-llkoaJev.js} +1 -1
  26. package/dist/client/assets/{SettingsTextareaRow-O7-u9uMC.js → SettingsTextareaRow-B_Legu-A.js} +1 -1
  27. package/dist/client/assets/{SetupWizardModal-nXfJp8cA.js → SetupWizardModal-DLA4UAGp.js} +2 -2
  28. package/dist/client/assets/{SkillsView-CoWAjvWU.js → SkillsView-CSeauwIw.js} +1 -1
  29. package/dist/client/assets/{WorkflowNodeEditor-BFMM3RF_.js → WorkflowNodeEditor-CGgMiiWy.js} +1 -1
  30. package/dist/client/assets/{agent-import-generation-BqDmtTur.js → agent-import-generation-CttglSmZ.js} +1 -1
  31. package/dist/client/assets/{agentsOrgChartLayout-C4cLqlV6.js → agentsOrgChartLayout-f-9mwRbD.js} +1 -1
  32. package/dist/client/assets/{architectureDiagram-3BPJPVTR-CdxcwdRe.js → architectureDiagram-3BPJPVTR-DA_Hm_di.js} +1 -1
  33. package/dist/client/assets/{blockDiagram-GPEHLZMM-CfQrRfHR.js → blockDiagram-GPEHLZMM-JF7K8CcV.js} +1 -1
  34. package/dist/client/assets/{c4Diagram-AAUBKEIU-Cx7-2SOm.js → c4Diagram-AAUBKEIU-DN-wklCP.js} +1 -1
  35. package/dist/client/assets/channel-CwFfFSI9.js +1 -0
  36. package/dist/client/assets/{chunk-2J33WTMH-D8jvgP3M.js → chunk-2J33WTMH-ChtjU8Cu.js} +1 -1
  37. package/dist/client/assets/{chunk-4BX2VUAB-B5tBx_s_.js → chunk-4BX2VUAB-dJL0n3nn.js} +1 -1
  38. package/dist/client/assets/{chunk-55IACEB6-C-F-HQ5Z.js → chunk-55IACEB6-Bfg-QB9A.js} +1 -1
  39. package/dist/client/assets/{chunk-727SXJPM-BP8Ln-8C.js → chunk-727SXJPM-BHPPwYUS.js} +1 -1
  40. package/dist/client/assets/{chunk-AQP2D5EJ-DMnTmRmn.js → chunk-AQP2D5EJ-DIIpPUQN.js} +1 -1
  41. package/dist/client/assets/{chunk-FMBD7UC4-D_YdfnNc.js → chunk-FMBD7UC4-D230R32b.js} +1 -1
  42. package/dist/client/assets/{chunk-ND2GUHAM-CULXyuXq.js → chunk-ND2GUHAM-0Lhp5CRv.js} +1 -1
  43. package/dist/client/assets/{chunk-QZHKN3VN-CT1BWEJL.js → chunk-QZHKN3VN-BxbHBfKJ.js} +1 -1
  44. package/dist/client/assets/classDiagram-4FO5ZUOK-C8dZsxxX.js +1 -0
  45. package/dist/client/assets/classDiagram-v2-Q7XG4LA2-C8dZsxxX.js +1 -0
  46. package/dist/client/assets/{cose-bilkent-S5V4N54A-M-3DXa6e.js → cose-bilkent-S5V4N54A-3vEwhfYz.js} +1 -1
  47. package/dist/client/assets/{dagre-BM42HDAG-Bsiqvroz.js → dagre-BM42HDAG-Br44UsdY.js} +1 -1
  48. package/dist/client/assets/{dashboard-view-Bo_gFjdO.js → dashboard-view-B0OM3KYC.js} +1 -1
  49. package/dist/client/assets/{dashboard-view-CnFFG3cW.js → dashboard-view-BldxxYfz.js} +1 -1
  50. package/dist/client/assets/{dashboard-view-DAHn20ph.js → dashboard-view-CcPcELPx.js} +1 -1
  51. package/dist/client/assets/{dashboard-view-BEBh_Ei5.js → dashboard-view-_EglxMDl.js} +1 -1
  52. package/dist/client/assets/{diagram-2AECGRRQ-CZiHoXo5.js → diagram-2AECGRRQ-CLNxbHUP.js} +1 -1
  53. package/dist/client/assets/{diagram-5GNKFQAL-BoJVz4Or.js → diagram-5GNKFQAL-D3cNAeQH.js} +1 -1
  54. package/dist/client/assets/{diagram-KO2AKTUF-DivGQuAL.js → diagram-KO2AKTUF-DkX7h6to.js} +1 -1
  55. package/dist/client/assets/{diagram-LMA3HP47-DkMK7OJp.js → diagram-LMA3HP47-6iQIiYUr.js} +1 -1
  56. package/dist/client/assets/{diagram-OG6HWLK6-DFFvRcpW.js → diagram-OG6HWLK6-Caje3229.js} +1 -1
  57. package/dist/client/assets/{erDiagram-TEJ5UH35--217vrom.js → erDiagram-TEJ5UH35-BkU-1HB1.js} +1 -1
  58. package/dist/client/assets/{flowDiagram-I6XJVG4X-CQgWO3xf.js → flowDiagram-I6XJVG4X-BRx3V6RE.js} +1 -1
  59. package/dist/client/assets/{folder-open-DR6cRcNR.js → folder-open-BaSOBfk2.js} +1 -1
  60. package/dist/client/assets/{ganttDiagram-6RSMTGT7-BjDMUrS3.js → ganttDiagram-6RSMTGT7-D9f1kH3t.js} +1 -1
  61. package/dist/client/assets/{gitGraphDiagram-PVQCEYII-gKUcDnln.js → gitGraphDiagram-PVQCEYII-CQwmZcRd.js} +1 -1
  62. package/dist/client/assets/{index-oxnX59eO.js → index-B8PcG8AJ.js} +298 -298
  63. package/dist/client/assets/{infoDiagram-5YYISTIA-BA91T8S5.js → infoDiagram-5YYISTIA-DYWBkGjG.js} +1 -1
  64. package/dist/client/assets/{ishikawaDiagram-YF4QCWOH-0BY5xe-c.js → ishikawaDiagram-YF4QCWOH-Df0Nzvh4.js} +1 -1
  65. package/dist/client/assets/{journeyDiagram-JHISSGLW-B_OwenbQ.js → journeyDiagram-JHISSGLW-bSvrZSyZ.js} +1 -1
  66. package/dist/client/assets/{kanban-definition-UN3LZRKU-GLabUTKp.js → kanban-definition-UN3LZRKU-yHJ101M8.js} +1 -1
  67. package/dist/client/assets/{mermaid.core-BXo4rxYI.js → mermaid.core-Bc_0VLJw.js} +4 -4
  68. package/dist/client/assets/{mindmap-definition-RKZ34NQL-DZl6LPoE.js → mindmap-definition-RKZ34NQL-DWRnMguM.js} +1 -1
  69. package/dist/client/assets/{pieDiagram-4H26LBE5-DtHq-4py.js → pieDiagram-4H26LBE5-Ds-DEOqr.js} +1 -1
  70. package/dist/client/assets/{puzzle-OVm-I1L4.js → puzzle-CdYF81IT.js} +1 -1
  71. package/dist/client/assets/{quadrantDiagram-W4KKPZXB-BdjXhd5d.js → quadrantDiagram-W4KKPZXB-Bz-j2fkf.js} +1 -1
  72. package/dist/client/assets/{requirementDiagram-4Y6WPE33-BoFVjELN.js → requirementDiagram-4Y6WPE33-BaQ_PaZ-.js} +1 -1
  73. package/dist/client/assets/{sankeyDiagram-5OEKKPKP-B0qyJh-P.js → sankeyDiagram-5OEKKPKP-pUCz8V4-.js} +1 -1
  74. package/dist/client/assets/{sequenceDiagram-3UESZ5HK-ByY62Eqp.js → sequenceDiagram-3UESZ5HK-CFwUMspe.js} +1 -1
  75. package/dist/client/assets/{shield-alert-CDy69fl7.js → shield-alert-4qeU3_Rm.js} +1 -1
  76. package/dist/client/assets/{standing-instructions-template-BsnnRlwm.js → standing-instructions-template-Crocrob7.js} +1 -1
  77. package/dist/client/assets/{stateDiagram-AJRCARHV-CwJ-_Phi.js → stateDiagram-AJRCARHV-CHZwo-vd.js} +1 -1
  78. package/dist/client/assets/{stateDiagram-v2-BHNVJYJU-Cfvhq8mV.js → stateDiagram-v2-BHNVJYJU-BTvKaIZR.js} +1 -1
  79. package/dist/client/assets/{timeline-definition-PNZ67QCA-Co8VfGN9.js → timeline-definition-PNZ67QCA-Cn3XIoz2.js} +1 -1
  80. package/dist/client/assets/{upload-CkqDbEeE.js → upload--GqImz6h.js} +1 -1
  81. package/dist/client/assets/{useDiscoveredSkillsCache-BHUasRiZ.js → useDiscoveredSkillsCache-BEHgTFai.js} +1 -1
  82. package/dist/client/assets/{users-B8DkpbXD.js → users-B0h1q4x6.js} +1 -1
  83. package/dist/client/assets/{vennDiagram-CIIHVFJN-XSJteQrL.js → vennDiagram-CIIHVFJN-CS0FNVU_.js} +1 -1
  84. package/dist/client/assets/{wardley-L42UT6IY-Btc0FRfF.js → wardley-L42UT6IY-DdYkfF5d.js} +1 -1
  85. package/dist/client/assets/{wardleyDiagram-YWT4CUSO--IcDNmrf.js → wardleyDiagram-YWT4CUSO-B_rFmPCh.js} +1 -1
  86. package/dist/client/assets/{xychartDiagram-2RQKCTM6-CZW93G6X.js → xychartDiagram-2RQKCTM6-CCoaCgmc.js} +1 -1
  87. package/dist/client/index.html +1 -1
  88. package/dist/client/version.json +1 -1
  89. package/dist/droid-cli/package.json +1 -1
  90. package/dist/extension.js +381 -87
  91. package/dist/pi-claude-cli/package.json +1 -1
  92. package/dist/plugin-sdk/index.d.ts +9 -0
  93. package/dist/plugins/fusion-plugin-acp-runtime/package.json +1 -1
  94. package/dist/plugins/fusion-plugin-claude-runtime/package.json +1 -1
  95. package/dist/plugins/fusion-plugin-cli-printing-press/package.json +1 -1
  96. package/dist/plugins/fusion-plugin-compound-engineering/package.json +1 -1
  97. package/dist/plugins/fusion-plugin-cursor-runtime/package.json +1 -1
  98. package/dist/plugins/fusion-plugin-dependency-graph/package.json +1 -1
  99. package/dist/plugins/fusion-plugin-droid-runtime/package.json +1 -1
  100. package/dist/plugins/fusion-plugin-grok-runtime/package.json +1 -1
  101. package/dist/plugins/fusion-plugin-hermes-runtime/package.json +1 -1
  102. package/dist/plugins/fusion-plugin-linear-import/package.json +1 -1
  103. package/dist/plugins/fusion-plugin-omp-runtime/package.json +1 -1
  104. package/dist/plugins/fusion-plugin-openclaw-runtime/package.json +1 -1
  105. package/dist/plugins/fusion-plugin-paperclip-runtime/package.json +1 -1
  106. package/dist/plugins/fusion-plugin-quality/package.json +1 -1
  107. package/dist/plugins/fusion-plugin-reports/package.json +1 -1
  108. package/dist/plugins/fusion-plugin-roadmap/package.json +1 -1
  109. package/dist/plugins/fusion-plugin-todos/package.json +1 -1
  110. package/dist/plugins/fusion-plugin-whatsapp-chat/package.json +1 -1
  111. package/package.json +1 -1
  112. package/dist/client/assets/channel-B4WN1jC_.js +0 -1
  113. package/dist/client/assets/classDiagram-4FO5ZUOK-CKE8RSOv.js +0 -1
  114. package/dist/client/assets/classDiagram-v2-Q7XG4LA2-CKE8RSOv.js +0 -1
@@ -231,10 +231,16 @@ function resolveEntryPointBranchAssignment(input) {
231
231
  };
232
232
  }
233
233
  }
234
- var isValidTaskBranchName;
234
+ var BranchWriteProvenanceError, isValidTaskBranchName;
235
235
  var init_branch_assignment = __esm({
236
236
  "../core/src/branch/branch-assignment.ts"() {
237
237
  "use strict";
238
+ BranchWriteProvenanceError = class extends Error {
239
+ constructor() {
240
+ super("branchWriteOrigin is required when branch is provided");
241
+ this.name = "BranchWriteProvenanceError";
242
+ }
243
+ };
238
244
  isValidTaskBranchName = isValidBranchGroupBranchName;
239
245
  }
240
246
  });
@@ -4612,8 +4618,8 @@ var init_research_settings = __esm({
4612
4618
  function resolveResearchFindingId(finding) {
4613
4619
  const explicitId = typeof finding.id === "string" ? finding.id.trim() : "";
4614
4620
  if (explicitId) return explicitId;
4615
- const normalize9 = (value) => value.trim().replace(/\s+/g, " ").toLowerCase();
4616
- const canonical = [normalize9(finding.heading ?? ""), normalize9(finding.content ?? ""), ...[...finding.sources ?? []].map(normalize9).filter(Boolean).sort()].join("\n");
4621
+ const normalize10 = (value) => value.trim().replace(/\s+/g, " ").toLowerCase();
4622
+ const canonical = [normalize10(finding.heading ?? ""), normalize10(finding.content ?? ""), ...[...finding.sources ?? []].map(normalize10).filter(Boolean).sort()].join("\n");
4617
4623
  let hash3 = 2166136261;
4618
4624
  for (let index2 = 0; index2 < canonical.length; index2 += 1) {
4619
4625
  hash3 ^= canonical.charCodeAt(index2);
@@ -36067,17 +36073,17 @@ var init_agent_store = __esm({
36067
36073
  * @returns Matching agent when unambiguous; otherwise null
36068
36074
  */
36069
36075
  async resolveAgent(shortname) {
36070
- const normalize9 = (value) => value.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "");
36076
+ const normalize10 = (value) => value.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "");
36071
36077
  const all = await this.listAgents();
36072
36078
  const exact = all.find((agent) => agent.id === shortname);
36073
36079
  if (exact) {
36074
36080
  return exact;
36075
36081
  }
36076
- const normalizedTarget = normalize9(shortname);
36082
+ const normalizedTarget = normalize10(shortname);
36077
36083
  if (!normalizedTarget) {
36078
36084
  return null;
36079
36085
  }
36080
- const matches = all.filter((agent) => normalize9(agent.name) === normalizedTarget);
36086
+ const matches = all.filter((agent) => normalize10(agent.name) === normalizedTarget);
36081
36087
  return matches.length === 1 ? matches[0] : null;
36082
36088
  }
36083
36089
  // ─────────────────────────────────────────────────────────────────────────
@@ -45319,11 +45325,11 @@ function withWindowsNativeBinPath(environment, nativeRoot, platform3 = process.p
45319
45325
  if (platform3 !== "win32") return environment;
45320
45326
  const pathKey = Object.keys(environment).find((key) => key.toLowerCase() === "path") ?? "PATH";
45321
45327
  const bin = nativeRoot.replace(/[\\/]+$/, "") + "\\bin";
45322
- const normalize9 = (value) => value.replace(/\//g, "\\").toLowerCase();
45328
+ const normalize10 = (value) => value.replace(/\//g, "\\").toLowerCase();
45323
45329
  const inherited = environment[pathKey] ?? "";
45324
45330
  const segments = inherited.split(";").filter(Boolean);
45325
- const normalizedBin = normalize9(bin);
45326
- const deduplicated = segments.filter((segment) => normalize9(segment) !== normalizedBin);
45331
+ const normalizedBin = normalize10(bin);
45332
+ const deduplicated = segments.filter((segment) => normalize10(segment) !== normalizedBin);
45327
45333
  return { ...environment, [pathKey]: [bin, ...deduplicated].join(";") };
45328
45334
  }
45329
45335
  function readPostgresPid(dataDir) {
@@ -67272,14 +67278,14 @@ function resolveGlobalDir(dir) {
67272
67278
  const isFusionDirInsideRepo = basename8(dir) === ".fusion" && existsSync14(join19(dirname11(dir), ".git"));
67273
67279
  if (isFusionDirInsideRepo) {
67274
67280
  const homeGlobalDir = resolveGlobalDirForHome(getHomeDir2());
67275
- const normalize9 = (p2) => {
67281
+ const normalize10 = (p2) => {
67276
67282
  try {
67277
67283
  return realpathSync3.native(p2);
67278
67284
  } catch {
67279
67285
  return resolve13(p2);
67280
67286
  }
67281
67287
  };
67282
- if (normalize9(dir) !== normalize9(homeGlobalDir)) {
67288
+ if (normalize10(dir) !== normalize10(homeGlobalDir)) {
67283
67289
  throw new Error(
67284
67290
  `resolveGlobalDir(): refusing project-local '.fusion' directory '${dir}' for the central/global store. This would create a stray per-project central database seeded with default global settings and silently reset them. Pass the resolved global dir (or omit the argument so it defaults to ~/.fusion); see TaskStore.getGlobalSettingsDir(). If this really is your intended global dir (e.g. a version-controlled dotfiles repo), set FUSION_ALLOW_PROJECT_LOCAL_GLOBAL_DIR=true to override.`
67285
67291
  );
@@ -88006,15 +88012,15 @@ async function appendRecall(layer, input) {
88006
88012
  async function mergeRecallGraphNodeIds(layer, recordId, addGraphNodeIds) {
88007
88013
  const pid = projectId(layer);
88008
88014
  const table = project_exports.memoryRecallRecords;
88009
- const normalize9 = (ids) => [...new Set(ids.map((id) => id.trim()).filter(Boolean))].sort();
88015
+ const normalize10 = (ids) => [...new Set(ids.map((id) => id.trim()).filter(Boolean))].sort();
88010
88016
  return layer.transactionImmediate(async (tx) => {
88011
88017
  await tx.execute(sql`SELECT pg_advisory_xact_lock(hashtext(${recallGraphNodeIdsLockKey(pid, recordId)}))`);
88012
88018
  const rows = await tx.select().from(table).where(and(eq(table.projectId, pid), eq(table.id, recordId))).limit(1);
88013
88019
  await recallGraphNodeIdsTestHooks?.afterRowRead?.();
88014
88020
  const row = rows[0];
88015
88021
  if (!row) return { status: "missing" };
88016
- const stored = normalize9(row.graphNodeIds);
88017
- const next = normalize9([...stored, ...addGraphNodeIds]);
88022
+ const stored = normalize10(row.graphNodeIds);
88023
+ const next = normalize10([...stored, ...addGraphNodeIds]);
88018
88024
  if (stored.length === next.length && stored.every((value, index2) => value === next[index2])) return { status: "unchanged" };
88019
88025
  await tx.update(table).set({ graphNodeIds: next, updatedAt: (/* @__PURE__ */ new Date()).toISOString() }).where(and(eq(table.projectId, pid), eq(table.id, recordId)));
88020
88026
  return { status: "updated" };
@@ -92417,7 +92423,7 @@ import { randomUUID as randomUUID31 } from "node:crypto";
92417
92423
  function normalizeCreateBranchProvenance(input) {
92418
92424
  if (input.branch === void 0) return input;
92419
92425
  if (input.branchWriteOrigin !== "operator" && input.branchWriteOrigin !== "engine") {
92420
- throw new Error("branchWriteOrigin is required when branch is provided");
92426
+ throw new BranchWriteProvenanceError();
92421
92427
  }
92422
92428
  validateTaskBranchName(input.branch);
92423
92429
  if (input.branchWriteOrigin === "operator") {
@@ -94634,7 +94640,7 @@ async function updateTaskRepositoryScopeImpl(store, id, requestedScope) {
94634
94640
  ...replacement,
94635
94641
  repositories: nextRepositories,
94636
94642
  revision: Math.max((current.repositoryScope?.revision ?? 0) + 1, replacement.revision ?? 0),
94637
- ...scopeChanged ? { reviewEvidence: void 0 } : {}
94643
+ ...scopeChanged ? { reviewEvidence: void 0, reviewRemediation: void 0 } : {}
94638
94644
  };
94639
94645
  const [updatedRow] = await tx.update(project_exports.tasks).set({
94640
94646
  repositoryScope: normalized2 ?? null,
@@ -94650,6 +94656,35 @@ async function updateTaskRepositoryScopeImpl(store, id, requestedScope) {
94650
94656
  return outcome;
94651
94657
  }));
94652
94658
  }
94659
+ async function updateWorkspaceReviewStateImpl(store, id, expectedScopeRevision, reviewRemediation) {
94660
+ return store.withTaskLock(id, async () => {
94661
+ const layer = store.asyncLayer;
94662
+ const outcome = await layer.transactionImmediate(async (tx) => {
94663
+ await acquireTaskAdvisoryXactLock(tx, layer.projectId, id);
94664
+ const row = await readTaskRowInTransaction(tx, id, { includeDeleted: true }, layer.projectId);
94665
+ if (!row) throw new TaskNotFoundError(id);
94666
+ if (row.deletedAt) throw new TaskDeletedError(id, row.deletedAt);
94667
+ const current = store.rowToTask(store.pgRowToTaskRow(row));
94668
+ if (current.repositoryScope?.revision !== expectedScopeRevision) return { task: current, updated: false };
94669
+ const repositoryScope = {
94670
+ ...current.repositoryScope,
94671
+ ...reviewRemediation ? { reviewRemediation } : { reviewRemediation: void 0 }
94672
+ };
94673
+ const [updatedRow] = await tx.update(project_exports.tasks).set({
94674
+ repositoryScope,
94675
+ updatedAt: (/* @__PURE__ */ new Date()).toISOString()
94676
+ }).where(and(eq(project_exports.tasks.id, id), taskProjectScope(layer))).returning();
94677
+ if (!updatedRow) throw new TaskNotFoundError(id);
94678
+ return { task: store.rowToTask(store.pgRowToTaskRow(updatedRow)), updated: true };
94679
+ });
94680
+ if (outcome.updated) {
94681
+ await store.writeTaskJsonFile(store.taskDir(id), outcome.task);
94682
+ if (store.isWatching) store.taskCache.set(id, { ...outcome.task });
94683
+ store.emitTaskLifecycleEventSafely("task:updated", [outcome.task]);
94684
+ }
94685
+ return outcome;
94686
+ });
94687
+ }
94653
94688
  async function resolveTaskWedgeNotificationEpisodeImpl(store, id, episodeId) {
94654
94689
  const layer = store.asyncLayer;
94655
94690
  const row = await resolveActiveTaskWedgeEpisodeRow(layer, id, episodeId, (/* @__PURE__ */ new Date()).toISOString());
@@ -103453,7 +103488,7 @@ async function updateTaskUnlockedImpl(store, id, updates, runContext) {
103453
103488
  if (updates.recommendations !== void 0) assertValidRecommendations(updates.recommendations);
103454
103489
  if (updates.branch !== void 0) {
103455
103490
  if (updates.branchWriteOrigin !== "operator" && updates.branchWriteOrigin !== "engine") {
103456
- throw new Error("branchWriteOrigin is required when branch is provided");
103491
+ throw new BranchWriteProvenanceError();
103457
103492
  }
103458
103493
  if (updates.branch !== null) validateTaskBranchName(updates.branch);
103459
103494
  }
@@ -103542,7 +103577,7 @@ async function updateTaskUnlockedImpl(store, id, updates, runContext) {
103542
103577
  task.repositoryScope = void 0;
103543
103578
  } else if (updates.repositoryScope !== void 0) {
103544
103579
  const scopeRevisionChanged = task.repositoryScope?.revision !== updates.repositoryScope.revision;
103545
- task.repositoryScope = scopeRevisionChanged ? { ...updates.repositoryScope, reviewEvidence: void 0 } : updates.repositoryScope;
103580
+ task.repositoryScope = scopeRevisionChanged ? { ...updates.repositoryScope, reviewEvidence: void 0, reviewRemediation: void 0 } : updates.repositoryScope;
103546
103581
  if (scopeRevisionChanged) {
103547
103582
  task.workflowStepResults = invalidateSupersededRepositoryScopeReviews(
103548
103583
  task.workflowStepResults,
@@ -106042,6 +106077,9 @@ var init_store = __esm({
106042
106077
  async mutateTaskRepositoryScope(id, mutation) {
106043
106078
  return updateTaskRepositoryScopeImpl(this, id, mutation);
106044
106079
  }
106080
+ async updateWorkspaceReviewState(id, expectedScopeRevision, reviewRemediation) {
106081
+ return updateWorkspaceReviewStateImpl(this, id, expectedScopeRevision, reviewRemediation);
106082
+ }
106045
106083
  async resolveTaskWedgeNotificationEpisode(id, episodeId) {
106046
106084
  return resolveTaskWedgeNotificationEpisodeImpl(this, id, episodeId);
106047
106085
  }
@@ -136049,6 +136087,7 @@ __export(src_exports, {
136049
136087
  BUNDLED_PLUGIN_IDS: () => BUNDLED_PLUGIN_IDS,
136050
136088
  BackupManager: () => BackupManager,
136051
136089
  BackwardCompat: () => BackwardCompat,
136090
+ BranchWriteProvenanceError: () => BranchWriteProvenanceError,
136052
136091
  CENTRAL_BACKUP_SCHEMAS: () => CENTRAL_BACKUP_SCHEMAS,
136053
136092
  CLAUDE_SONNET_5_MODEL_ID: () => CLAUDE_SONNET_5_MODEL_ID,
136054
136093
  CLEAN_COMPLETION_MARKERS: () => CLEAN_COMPLETION_MARKERS,
@@ -163898,8 +163937,8 @@ var require_resolve = __commonJS({
163898
163937
  }
163899
163938
  return count2;
163900
163939
  }
163901
- function getFullPath(resolver, id = "", normalize9) {
163902
- if (normalize9 !== false)
163940
+ function getFullPath(resolver, id = "", normalize10) {
163941
+ if (normalize10 !== false)
163903
163942
  id = normalizeId2(id);
163904
163943
  const p2 = resolver.parse(id);
163905
163944
  return _getFullPath(resolver, p2);
@@ -165239,7 +165278,7 @@ var require_fast_uri = __commonJS({
165239
165278
  "use strict";
165240
165279
  var { normalizeIPv6, removeDotSegments, recomposeAuthority, normalizeComponentEncoding, isIPv4, nonSimpleDomain } = require_utils();
165241
165280
  var { SCHEMES, getSchemeHandler } = require_schemes();
165242
- function normalize9(uri, options) {
165281
+ function normalize10(uri, options) {
165243
165282
  if (typeof uri === "string") {
165244
165283
  uri = /** @type {T} */
165245
165284
  serialize(parse4(uri, options), options);
@@ -165475,7 +165514,7 @@ var require_fast_uri = __commonJS({
165475
165514
  }
165476
165515
  var fastUri = {
165477
165516
  SCHEMES,
165478
- normalize: normalize9,
165517
+ normalize: normalize10,
165479
165518
  resolve: resolve50,
165480
165519
  resolveComponent,
165481
165520
  equal,
@@ -177689,6 +177728,39 @@ var init_worktrunk_failure_handler = __esm({
177689
177728
  }
177690
177729
  });
177691
177730
 
177731
+ // ../engine/src/executor/workspace-review-remediation.ts
177732
+ function normalize8(value) {
177733
+ return (value ?? "").replace(/\s+/g, " ").trim().toLowerCase();
177734
+ }
177735
+ function resolveWorkspaceReviewRemediationRepository(task, declaredRepositories) {
177736
+ const scope = task.repositoryScope;
177737
+ const remediation = scope?.reviewRemediation;
177738
+ if (!remediation) return void 0;
177739
+ if (scope?.state !== "confirmed" || scope.revision !== remediation.scopeRevision) {
177740
+ throw new Error(`Workspace Code Review remediation target is stale for ${task.id}`);
177741
+ }
177742
+ if (!declaredRepositories.includes(remediation.repository)) {
177743
+ throw new Error(`Workspace Code Review remediation repository ${remediation.repository} is not declared for ${task.id}`);
177744
+ }
177745
+ return remediation.repository;
177746
+ }
177747
+ function deriveWorkspaceReviewRemediation(result) {
177748
+ if (typeof result.repositoryScopeRevision !== "number") return void 0;
177749
+ const blocking = (result.repositoryReviewOutcomes ?? []).filter((outcome) => outcome.status === "REVIEWED" && (outcome.verdict === "REVISE" || outcome.verdict === "RETHINK")).sort((left, right) => left.repository.localeCompare(right.repository))[0];
177750
+ if (!blocking) return void 0;
177751
+ const findings = (blocking.findings ?? []).map((finding) => `${finding.id}:${normalize8(finding.title)}:${normalize8(finding.body)}`).sort().join("|");
177752
+ return {
177753
+ scopeRevision: result.repositoryScopeRevision,
177754
+ repository: blocking.repository,
177755
+ inputSignature: `${result.workflowStepId}\0${blocking.repository}\0${blocking.fingerprint ?? ""}\0${blocking.verdict}\0${findings}`
177756
+ };
177757
+ }
177758
+ var init_workspace_review_remediation = __esm({
177759
+ "../engine/src/executor/workspace-review-remediation.ts"() {
177760
+ "use strict";
177761
+ }
177762
+ });
177763
+
177692
177764
  // ../engine/src/worktree/worktree-copy-files.ts
177693
177765
  import { constants as fsConstants3 } from "node:fs";
177694
177766
  import { access as access7, copyFile as copyFile2, lstat as lstat2, mkdir as mkdir26 } from "node:fs/promises";
@@ -178075,14 +178147,15 @@ function invalidateWorkspaceConfigCache(owner) {
178075
178147
  workspaceConfigEpochs.set(owner, (workspaceConfigEpochs.get(owner) ?? 0) + 1);
178076
178148
  }
178077
178149
  async function normalizeWorkspaceTaskRouting(store, taskId) {
178078
- const normalize9 = store.normalizeWorkspaceTaskWorktreeMetadata;
178079
- if (typeof normalize9 === "function") return normalize9.call(store, taskId);
178150
+ const normalize10 = store.normalizeWorkspaceTaskWorktreeMetadata;
178151
+ if (typeof normalize10 === "function") return normalize10.call(store, taskId);
178080
178152
  let task = await store.getTask(taskId);
178081
178153
  if (task.worktree || task.branch || task.executionStartBranch || task.baseCommitSha) {
178082
178154
  if (typeof store.updateTask !== "function") return task;
178083
178155
  await store.updateTask(taskId, {
178084
178156
  worktree: null,
178085
178157
  branch: null,
178158
+ branchWriteOrigin: "engine",
178086
178159
  executionStartBranch: null,
178087
178160
  baseCommitSha: null,
178088
178161
  sessionFile: null
@@ -178284,11 +178357,15 @@ async function ensureWorkspaceGroupOwnership(workspaceContext, settings) {
178284
178357
  async function acquireTaskWorktree(opts) {
178285
178358
  const { task, rootDir, store, settings, pool, logger: logger3, audit, runContext, createWorktree: createWorktree2, runConfiguredCommand: runConfiguredCommand2, runInitCommand, taskEnv, secretsStore, workspaceContext } = opts;
178286
178359
  const persistWorktreeAssignment = async (patch) => {
178360
+ const provenancePatch = patch.branch === void 0 ? patch : {
178361
+ ...patch,
178362
+ branchWriteOrigin: patch.branchWriteOrigin ?? (classifyTaskBranchOrigin(task, patch.branch ?? void 0) === "operator-supplied" ? "operator" : "engine")
178363
+ };
178287
178364
  if (!opts.suppressSingularWorktreePersist) {
178288
- await store.updateTask(task.id, patch);
178365
+ await store.updateTask(task.id, provenancePatch);
178289
178366
  return;
178290
178367
  }
178291
- const { worktree: _worktree, branch: _branch, ...nonSingularPatch } = patch;
178368
+ const { worktree: _worktree, branch: _branch, branchWriteOrigin: _origin, ...nonSingularPatch } = provenancePatch;
178292
178369
  if (Object.keys(nonSingularPatch).length > 0) {
178293
178370
  await store.updateTask(task.id, nonSingularPatch);
178294
178371
  }
@@ -178525,7 +178602,22 @@ async function acquireTaskWorktree(opts) {
178525
178602
  }
178526
178603
  worktreePath = created2.path;
178527
178604
  branch = created2.branch;
178528
- await persistWorktreeAssignment({ worktree: created2.path, branch: created2.branch });
178605
+ try {
178606
+ await persistWorktreeAssignment({ worktree: created2.path, branch: created2.branch });
178607
+ } catch (error2) {
178608
+ try {
178609
+ await removeWorktree({
178610
+ worktreePath: created2.path,
178611
+ rootDir,
178612
+ settings,
178613
+ taskId: task.id,
178614
+ reason: RemovalReason.ExecutorDispose
178615
+ });
178616
+ } catch (cleanupError) {
178617
+ logger3?.warn(`${task.id}: failed to compensate unpersisted worktree ${created2.path}: ${formatError(cleanupError)}`);
178618
+ }
178619
+ throw error2;
178620
+ }
178529
178621
  await audit?.git({ type: "worktree:create", target: created2.path, metadata: { branch: created2.branch, source: logOrigin === "return-guard" ? "acquire-return-guard" : void 0 } });
178530
178622
  await audit?.git({ type: "branch:create", target: created2.branch });
178531
178623
  if (created2.branch !== branchName) {
@@ -178767,6 +178859,17 @@ async function acquireTaskWorktree(opts) {
178767
178859
  if (pinned) {
178768
178860
  return acquirePinnedWorktree();
178769
178861
  }
178862
+ if (!workspaceContext && !task.worktree && !(pool && settings.recycleWorktrees)) {
178863
+ const registeredMatches = (await getRegisteredWorktreeBranches(rootDir)).filter((entry) => entry.branch === branchName);
178864
+ if (registeredMatches.length === 1) {
178865
+ const registeredPath = registeredMatches[0].worktreePath;
178866
+ const classification = await classifyTaskWorktree(rootDir, registeredPath);
178867
+ if (classification.ok) {
178868
+ await persistWorktreeAssignment({ worktree: registeredPath, branch: branchName });
178869
+ return reuseWarmWorktree(registeredPath, branchName, "existing");
178870
+ }
178871
+ }
178872
+ }
178770
178873
  if (task.worktree && isResume) {
178771
178874
  if (logger3?.debug) logger3.debug(`Reusing existing worktree: ${worktreePath}`);
178772
178875
  const cleanup = await removeDesktopBuildArtifacts(worktreePath, logger3);
@@ -178802,6 +178905,7 @@ async function acquireTaskWorktree(opts) {
178802
178905
  }
178803
178906
  }
178804
178907
  if (pooled) {
178908
+ let poolAssignmentPersistenceStarted = false;
178805
178909
  try {
178806
178910
  const preparedRaw = await pool.prepareForTask(pooled, branchName, freshStartPoint, {
178807
178911
  allowSiblingBranchRename,
@@ -178859,7 +178963,9 @@ async function acquireTaskWorktree(opts) {
178859
178963
  });
178860
178964
  acquiredFromPool = true;
178861
178965
  logger3?.log(`Acquired worktree from pool: ${worktreePath}`);
178966
+ poolAssignmentPersistenceStarted = true;
178862
178967
  await persistWorktreeAssignment({ worktree: worktreePath, branch });
178968
+ poolAssignmentPersistenceStarted = false;
178863
178969
  await audit?.git({ type: "worktree:reuse", target: worktreePath, metadata: { branch, reclaimed: prepared.reclaimed } });
178864
178970
  if (prepared.reclaimed) {
178865
178971
  await store.logEntry(task.id, `Acquired reclaimed worktree from pool: ${worktreePath} (${prepared.strandedCommitCount ?? 0} commits preserved)`, void 0, runContext);
@@ -178913,6 +179019,10 @@ async function acquireTaskWorktree(opts) {
178913
179019
  });
178914
179020
  }
178915
179021
  } catch (poolErr) {
179022
+ if (poolAssignmentPersistenceStarted) {
179023
+ pool.release(pooled, task.id);
179024
+ throw poolErr;
179025
+ }
178916
179026
  if (poolErr instanceof WorktreeBaseRefreshError) {
178917
179027
  await persistWorktreeAssignment({ worktree: null, branch: null, branchWriteOrigin: "engine", sessionFile: null });
178918
179028
  pool.release(pooled, task.id);
@@ -179224,7 +179334,11 @@ async function acquireWorkspaceTaskWorktrees(opts) {
179224
179334
  current = await opts.store.getTask(opts.task.id);
179225
179335
  }
179226
179336
  const entries = current.workspaceWorktrees ?? {};
179227
- const coordinatorWorktreePath = repoRelPaths.map((repoRelPath) => entries[repoRelPath]?.worktreePath).find((path6) => typeof path6 === "string" && path6.length > 0);
179337
+ const remediationRepository = resolveWorkspaceReviewRemediationRepository(current, repoRelPaths);
179338
+ if (opts.remediationRepository !== remediationRepository) {
179339
+ throw new Error(`Workspace Code Review remediation target changed during acquisition for ${opts.task.id}`);
179340
+ }
179341
+ const coordinatorWorktreePath = remediationRepository ? entries[remediationRepository]?.worktreePath : repoRelPaths.map((repoRelPath) => entries[repoRelPath]?.worktreePath).find((path6) => typeof path6 === "string" && path6.length > 0);
179228
179342
  if (!coordinatorWorktreePath) {
179229
179343
  throw new Error(`Workspace task ${opts.task.id} did not acquire a declared repository worktree`);
179230
179344
  }
@@ -179245,6 +179359,7 @@ var init_worktree_acquisition = __esm({
179245
179359
  init_worktree_backend();
179246
179360
  init_worktrunk_installer();
179247
179361
  init_worktrunk_failure_handler();
179362
+ init_workspace_review_remediation();
179248
179363
  init_secrets_env_writer();
179249
179364
  init_worktree_desktop_artifacts();
179250
179365
  init_worktree_hooks();
@@ -185943,7 +186058,7 @@ var init_agent_memory_index = __esm({
185943
186058
  // ../engine/src/agents/agent-instructions.ts
185944
186059
  import { readFile as readFile42, writeFile as writeFile28, mkdir as mkdir29, access as access8 } from "node:fs/promises";
185945
186060
  import { constants as fsConstants4 } from "node:fs";
185946
- import { isAbsolute as isAbsolute17, resolve as resolve40, relative as relative16, normalize as normalize8, sep as sep12, dirname as dirname30 } from "node:path";
186061
+ import { isAbsolute as isAbsolute17, resolve as resolve40, relative as relative16, normalize as normalize9, sep as sep12, dirname as dirname30 } from "node:path";
185947
186062
  function trimAndClamp(value, maxLength, label, agentId) {
185948
186063
  const trimmed = value.trim();
185949
186064
  if (!trimmed) {
@@ -185977,7 +186092,7 @@ function resolveValidatedMarkdownPath(rawPath, rootDir, agentId, fieldLabel) {
185977
186092
  log27.warn(`${fieldLabel} must be project-relative for agent ${agentId}: ${trimmed}`);
185978
186093
  return null;
185979
186094
  }
185980
- const normalized2 = normalize8(trimmed);
186095
+ const normalized2 = normalize9(trimmed);
185981
186096
  if (isPathTraversal2(normalized2)) {
185982
186097
  log27.warn(`${fieldLabel} traversal is not allowed for agent ${agentId}: ${trimmed}`);
185983
186098
  return null;
@@ -199373,7 +199488,8 @@ async function tryEarlyEmptyOwnDiffFinalize(input) {
199373
199488
  try {
199374
199489
  await store.updateTask(taskId, {
199375
199490
  worktree: worktreeRemoved ? null : task.worktree,
199376
- branch: branchDeleted ? null : task.branch
199491
+ branch: branchDeleted ? null : task.branch,
199492
+ ...branchDeleted ? { branchWriteOrigin: "engine" } : {}
199377
199493
  });
199378
199494
  if (worktreeRemoved) task.worktree = void 0;
199379
199495
  if (branchDeleted) task.branch = void 0;
@@ -199733,7 +199849,7 @@ async function aiMergeTask(store, rootDir, taskId, options = {}) {
199733
199849
  rootDir,
199734
199850
  integrationBranch: mergeTarget.branch
199735
199851
  });
199736
- await store.updateTask(taskId, { worktree: reusableMatch.path, branch: reusableMatch.branch });
199852
+ await store.updateTask(taskId, { worktree: reusableMatch.path, branch: reusableMatch.branch, branchWriteOrigin: "engine" });
199737
199853
  await emitReuseHandoffAuditEvent(
199738
199854
  "merge:reuse-fallback-reused-existing-registration",
199739
199855
  {
@@ -205486,8 +205602,10 @@ function workflowNodeRequiresWorktree(node, { optionalGroupId, reviewerInlineFix
205486
205602
  }
205487
205603
 
205488
205604
  // ../engine/src/workflows/workflow-graph-executor.ts
205605
+ init_src2();
205489
205606
  init_worktree_acquisition();
205490
205607
  var PLAN_REVIEW_PROVIDER_FAILURE_HOLD_VALUE = "plan-review-provider-failure-hold";
205608
+ var BRANCH_WRITE_PROVENANCE_FAILURE_VALUE = "branch-write-provenance-failure";
205491
205609
  var PLAN_REVIEW_LEASE_HELD_VALUE = "plan-review-lease-held";
205492
205610
  function enclosingContainerInstanceId(parent, nodeId) {
205493
205611
  if (!parent || parent === nodeId) return void 0;
@@ -206514,7 +206632,7 @@ var WorkflowGraphExecutor = class {
206514
206632
  const failureResult = {
206515
206633
  outcome: "failure",
206516
206634
  // FNXC:SessionContention 2026-07-25-21:30: contention is a retryable hold, not an exception.
206517
- value: isSessionContentionError(lastErrorText) ? SESSION_CONTENTION_HOLD_VALUE : "exception",
206635
+ value: lastError instanceof BranchWriteProvenanceError ? BRANCH_WRITE_PROVENANCE_FAILURE_VALUE : isSessionContentionError(lastErrorText) ? SESSION_CONTENTION_HOLD_VALUE : "exception",
206518
206636
  contextPatch: {
206519
206637
  [`node:${node.id}:error`]: lastErrorText
206520
206638
  }
@@ -213855,8 +213973,8 @@ async function autoRecoverWorktreeSessionStartFailure(store, task, opts) {
213855
213973
  }
213856
213974
  const workspaceTask = configuredWorkspace || isWorkspaceTask(task);
213857
213975
  if (workspaceTask) {
213858
- const normalize9 = store.normalizeWorkspaceTaskWorktreeMetadata;
213859
- if (typeof normalize9 === "function") task = await normalize9.call(store, task.id);
213976
+ const normalize10 = store.normalizeWorkspaceTaskWorktreeMetadata;
213977
+ if (typeof normalize10 === "function") task = await normalize10.call(store, task.id);
213860
213978
  }
213861
213979
  const resetRetryBudget = opts.resetRetryBudgetOnStaleMetadataClear === true;
213862
213980
  const staleMetadataClearRecoveryRetryCount = opts.staleMetadataClearRecoveryRetryCount;
@@ -213911,7 +214029,13 @@ async function autoRecoverWorktreeSessionStartFailure(store, task, opts) {
213911
214029
  worktreeSessionRetryCount: nextCount,
213912
214030
  ...nextStaleMetadataClearRecoveryCount === void 0 ? {} : { recoveryRetryCount: nextStaleMetadataClearRecoveryCount },
213913
214031
  worktree: clearWorktreeMetadata ? null : staleWorktree,
213914
- branch: nextBranch,
214032
+ /*
214033
+ * FNXC:BranchNaming 2026-08-21-09:36:
214034
+ * Session recovery often changes only worktree/session metadata. Do not reassert an unchanged
214035
+ * branch with engine provenance: that would erase a matching operator override and later make
214036
+ * its branch eligible for engine cleanup. A real canonical clear remains an explicit engine write.
214037
+ */
214038
+ ...(task.branch ?? null) === nextBranch ? {} : { branch: nextBranch, branchWriteOrigin: "engine" },
213915
214039
  sessionFile: null
213916
214040
  });
213917
214041
  await opts.auditor?.database({
@@ -218009,7 +218133,7 @@ var SelfHealingManager = class _SelfHealingManager extends SelfHealingGitEvidenc
218009
218133
  } catch {
218010
218134
  }
218011
218135
  }
218012
- const patch = preservedWorktree ? { branch: selected.branch } : { branch: selected.branch, worktree: null };
218136
+ const patch = preservedWorktree ? { branch: selected.branch, branchWriteOrigin: "engine" } : { branch: selected.branch, branchWriteOrigin: "engine", worktree: null };
218013
218137
  if (!task.baseCommitSha) {
218014
218138
  const derivedBaseCommit = (await execAsync18(
218015
218139
  `git merge-base ${shellQuote2(integrationBase)} ${shellQuote2(selected.branch)}`,
@@ -221686,6 +221810,12 @@ var SelfHealingManager = class _SelfHealingManager extends SelfHealingGitEvidenc
221686
221810
  await this.emitWorkspacePartialLandNoAction(task, "merge-pending", liveness.livePaths);
221687
221811
  continue;
221688
221812
  }
221813
+ const latestTask = await this.store.getTask(task.id).catch(() => null);
221814
+ const blockerTask = latestTask?.status === "failed" && latestTask.error?.startsWith("Workspace partial land:") ? { ...latestTask, status: null, error: void 0 } : latestTask;
221815
+ if (!blockerTask || getTaskMergeBlocker(blockerTask, { skipColumnIdentityCheck: true }) !== void 0) {
221816
+ await this.emitWorkspacePartialLandNoAction(task, "merge-blocked", []);
221817
+ continue;
221818
+ }
221689
221819
  const workspaceWorktrees = task.workspaceWorktrees ?? {};
221690
221820
  const explicitScope = task.repositoryScope?.state === "confirmed" ? task.repositoryScope.repositories : void 0;
221691
221821
  if (!explicitScope) {
@@ -221693,8 +221823,12 @@ var SelfHealingManager = class _SelfHealingManager extends SelfHealingGitEvidenc
221693
221823
  continue;
221694
221824
  }
221695
221825
  const repoKeys = Object.keys(workspaceWorktrees).filter(
221696
- (repoRel) => explicitScope.includes(repoRel) && (task.modifiedFiles ?? []).some((file) => file.startsWith(`${repoRel}/`))
221826
+ (repoRel) => explicitScope.includes(repoRel) && ((task.modifiedFiles ?? []).some((file) => file.startsWith(`${repoRel}/`)) || Boolean(workspaceWorktrees[repoRel]?.landedSha))
221697
221827
  );
221828
+ if (repoKeys.length === 0) {
221829
+ await this.emitWorkspacePartialLandNoAction(task, "empty-obligations", []);
221830
+ continue;
221831
+ }
221698
221832
  const landedRepos = [];
221699
221833
  const unlandedRepos = [];
221700
221834
  const unrecoverableRepos = [];
@@ -221751,13 +221885,12 @@ var SelfHealingManager = class _SelfHealingManager extends SelfHealingGitEvidenc
221751
221885
  phase: "reconcile-workspace-partial-land"
221752
221886
  });
221753
221887
  if (evidenceUnavailableRepos.length > 0) {
221754
- const defers = (this.workspacePartialLandEvidenceDefers.get(task.id) ?? 0) + 1;
221755
- this.workspacePartialLandEvidenceDefers.set(task.id, defers);
221888
+ const defers = (task.mergeTransientRetryCount ?? 0) + 1;
221889
+ await this.store.updateTask(task.id, { mergeTransientRetryCount: defers });
221756
221890
  if (defers >= MAX_STARVATION_DROPS) {
221757
221891
  const error2 = `Workspace partial-land evidence unavailable: branch state could not be read after ${MAX_STARVATION_DROPS} sweeps for sub-repo(s) ${evidenceUnavailableRepos.join(", ")} \u2014 manual intervention required.`;
221758
221892
  await this.store.updateTask(task.id, { status: "failed", error: error2 });
221759
221893
  await this.store.logEntry(task.id, error2);
221760
- this.workspacePartialLandEvidenceDefers.delete(task.id);
221761
221894
  await auditor.database({
221762
221895
  type: "task:reconcile-workspace-partial-land",
221763
221896
  target: task.id,
@@ -221770,7 +221903,6 @@ var SelfHealingManager = class _SelfHealingManager extends SelfHealingGitEvidenc
221770
221903
  }
221771
221904
  continue;
221772
221905
  }
221773
- this.workspacePartialLandEvidenceDefers.delete(task.id);
221774
221906
  if (unrecoverableRepos.length > 0) {
221775
221907
  const missingBranches = unrecoverableRepos.map((repoRel) => workspaceWorktrees[repoRel]?.branch ?? resolveTaskWorkingBranch(task)).join(", ");
221776
221908
  const error2 = `Workspace partial-land unrecoverable: sub-repo(s) ${unrecoverableRepos.join(", ")} have no branch (${missingBranches}) and no landedSha \u2014 manual intervention required.`;
@@ -221798,7 +221930,7 @@ var SelfHealingManager = class _SelfHealingManager extends SelfHealingGitEvidenc
221798
221930
  landedRepos,
221799
221931
  unlandedRepos: [],
221800
221932
  reason: "all-landed-not-finalized",
221801
- successLog: "Auto-recovered (workspace): all sub-repos landed but task not finalized \u2014 re-enqueued finalize-once"
221933
+ successLog: "Workspace merge recovery scheduled: all sub-repositories have proven landing evidence; awaiting finalize-once result"
221802
221934
  });
221803
221935
  recovered++;
221804
221936
  continue;
@@ -221807,7 +221939,7 @@ var SelfHealingManager = class _SelfHealingManager extends SelfHealingGitEvidenc
221807
221939
  landedRepos,
221808
221940
  unlandedRepos,
221809
221941
  reason: landedRepos.length > 0 ? "partial-land" : "zero-land",
221810
- successLog: `Auto-recovered (workspace): re-enqueued partial land (${landedRepos.length} landed, ${unlandedRepos.length} pending)`
221942
+ successLog: `Workspace merge recovery scheduled: ${landedRepos.length} landed, ${unlandedRepos.length} pending`
221811
221943
  });
221812
221944
  recovered++;
221813
221945
  } catch (err) {
@@ -221872,7 +222004,8 @@ var SelfHealingManager = class _SelfHealingManager extends SelfHealingGitEvidenc
221872
222004
  }).catch(() => void 0);
221873
222005
  return false;
221874
222006
  }
221875
- const drops = (this.workspacePartialLandDrops.get(task.id) ?? 0) + 1;
222007
+ const drops = (task.mergeTransientRetryCount ?? 0) + 1;
222008
+ await this.store.updateTask(task.id, { mergeTransientRetryCount: drops });
221876
222009
  this.workspacePartialLandDrops.set(task.id, drops);
221877
222010
  log34.warn(`reconcileWorkspacePartialLands: enqueue dropped for ${task.id} (${drops}/${MAX_STARVATION_DROPS}); merge queue rejected re-enqueue`);
221878
222011
  if (drops >= MAX_STARVATION_DROPS) {
@@ -230129,6 +230262,7 @@ async function handleBranchConflict(deps, task, error2) {
230129
230262
  status: "failed",
230130
230263
  error: conflictMessage,
230131
230264
  branch: error2.branchName,
230265
+ branchWriteOrigin: "engine",
230132
230266
  worktree: error2.conflictingWorktreePath,
230133
230267
  paused: true,
230134
230268
  pausedReason: "branch-conflict-unrecoverable"
@@ -230171,8 +230305,8 @@ async function recoverMissingWorktreeSessionStartFailure(deps, task, worktreePat
230171
230305
  }
230172
230306
  let recoveryTask = task;
230173
230307
  if (workspaceTask) {
230174
- const normalize9 = deps.store.normalizeWorkspaceTaskWorktreeMetadata;
230175
- if (typeof normalize9 === "function") recoveryTask = await normalize9.call(deps.store, task.id);
230308
+ const normalize10 = deps.store.normalizeWorkspaceTaskWorktreeMetadata;
230309
+ if (typeof normalize10 === "function") recoveryTask = await normalize10.call(deps.store, task.id);
230176
230310
  }
230177
230311
  if (!workspaceTask && isInsideWorktreesDir(deps.rootDir, staleWorktreePath)) {
230178
230312
  try {
@@ -230564,8 +230698,21 @@ async function detectWorkspaceMainCheckoutWork(deps, task, repos, declaredScope)
230564
230698
  const { stdout } = await execAsync40("git log -n 200 --format=%H%x1f%ct%x1f%B%x1e HEAD", { ...probeOptions, cwd: checkout });
230565
230699
  const attributed = new RegExp(`(?:${task.id.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}|Fusion-Task-Id:\\s*${task.id.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")})`, "i");
230566
230700
  for (const commit of parseCommits(stdout)) {
230567
- const evidence = attributed.test(commit.body) ? "task-attributed-commit" : anchor !== null && commit.committedAt >= anchor ? "post-anchor-commit" : null;
230568
- if (evidence) violations.push({ repo, files: [], commits: [commit.sha], evidence });
230701
+ const entry = workspaceWorktrees[repo];
230702
+ const recordedLanding = entry?.landedSha === commit.sha || task.mergeDetails?.workspaceLandedShas?.[repo] === commit.sha || task.mergeDetails?.commitSha === commit.sha;
230703
+ let reachableFromBaseline = false;
230704
+ if (entry?.baseCommitSha) {
230705
+ try {
230706
+ await execAsync40(`git merge-base --is-ancestor ${commit.sha} ${entry.baseCommitSha}`, { ...probeOptions, cwd: checkout });
230707
+ reachableFromBaseline = true;
230708
+ } catch {
230709
+ }
230710
+ }
230711
+ if (attributed.test(commit.body) && !recordedLanding && !reachableFromBaseline && anchor !== null && commit.committedAt >= anchor) {
230712
+ violations.push({ repo, files: [], commits: [commit.sha], evidence: "task-attributed-commit" });
230713
+ } else if (!recordedLanding && !reachableFromBaseline && anchor !== null && commit.committedAt >= anchor) {
230714
+ warnings.push({ repo, files: [], commits: [commit.sha], reason: "pre-existing-dirt" });
230715
+ }
230569
230716
  }
230570
230717
  } catch {
230571
230718
  warnings.push({ repo, files: [], commits: [], reason: "commit-scan-unavailable" });
@@ -230632,7 +230779,7 @@ async function verifyWorktreeInvariants(deps, task, worktreePathOverride, allowR
230632
230779
  reason: "main_checkout_edit",
230633
230780
  repo: firstMainCheckoutViolation.repo,
230634
230781
  observed: `${firstMainCheckoutViolation.evidence}: ${observed}`,
230635
- expected: `move task work into the acquired fusion/${task.id} worktree for ${firstMainCheckoutViolation.repo} (acquire it first if needed), restore its main checkout, then retry fn_task_done; only 3 requeue attempts are available`
230782
+ expected: `move task work into the acquired fusion/${task.id} worktree for ${firstMainCheckoutViolation.repo} (acquire it first if needed), restore its main checkout, then ask an operator to retry fn_task_done`
230636
230783
  };
230637
230784
  }
230638
230785
  const zeroAcquire = classifyWorkspaceZeroAcquire(task, {
@@ -232005,7 +232152,7 @@ ${scopeGuard}
232005
232152
  }
232006
232153
 
232007
232154
  // ../engine/src/executor/send-task-back-for-fix.ts
232008
- async function sendTaskBackForFix(deps, task, worktreePath, failureFeedback, stepName, reason, preserveResumeState = true, mergeVerificationFailure = false, retryPresentation, findings) {
232155
+ async function sendTaskBackForFix(deps, task, worktreePath, failureFeedback, stepName, reason, preserveResumeState = true, mergeVerificationFailure = false, retryPresentation, findings, persistWorktreePath) {
232009
232156
  const taskId = task.id;
232010
232157
  deps.clearCompletedTaskWatchdog(taskId);
232011
232158
  const { task: authoritativeRemediationTask, route: externalExecutionRoute } = await resolveAuthoritativeExternalExecutionRoute(deps.store, task);
@@ -232048,7 +232195,7 @@ Please fix the issues so the verification can pass on the next attempt.`,
232048
232195
  remediationWorktreePath,
232049
232196
  `${taskId}: sent back to in-progress for remediation`,
232050
232197
  preserveResumeState,
232051
- !externalExecutionRoute.configured
232198
+ persistWorktreePath ?? !externalExecutionRoute.configured
232052
232199
  );
232053
232200
  }
232054
232201
 
@@ -236837,6 +236984,7 @@ init_logger();
236837
236984
  init_run_audit();
236838
236985
  init_worktree_acquisition();
236839
236986
  init_workspace_config_resolver();
236987
+ init_workspace_review_remediation();
236840
236988
  async function ensureGraphCustomNodeWorktree(deps, task, settings, nodeId, refreshStaleBase = false) {
236841
236989
  const workspaceConfig = await resolveWorkspaceConfigOnce(deps);
236842
236990
  const syntheticRunId = generateSyntheticRunId("workflow-node-worktree", task.id);
@@ -236856,6 +237004,7 @@ async function ensureGraphCustomNodeWorktree(deps, task, settings, nodeId, refre
236856
237004
  deps.getRunContextFor(task.id)
236857
237005
  );
236858
237006
  if (workspaceConfig) {
237007
+ const remediationRepository = resolveWorkspaceReviewRemediationRepository(task, workspaceConfig.repos);
236859
237008
  const workspace = await acquireWorkspaceTaskWorktrees({
236860
237009
  workspaceConfig,
236861
237010
  workspaceRootDir: deps.rootDir,
@@ -236880,7 +237029,8 @@ async function ensureGraphCustomNodeWorktree(deps, task, settings, nodeId, refre
236880
237029
  return result;
236881
237030
  }),
236882
237031
  taskEnv: process.env,
236883
- addActiveWorktree: deps.addActiveWorktree
237032
+ addActiveWorktree: deps.addActiveWorktree,
237033
+ remediationRepository
236884
237034
  });
236885
237035
  deps.onStart?.(workspace.task, workspace.coordinatorWorktreePath);
236886
237036
  executorLog.debug(`${task.id}: workflow node '${nodeId}' using workspace coordinator ${workspace.coordinatorWorktreePath}`);
@@ -237410,6 +237560,7 @@ init_effective_settings();
237410
237560
  init_replan_target();
237411
237561
  init_transient_error_detector();
237412
237562
  init_logger();
237563
+ init_workspace_review_remediation();
237413
237564
  function normalizeConvergenceText(value) {
237414
237565
  return (value ?? "").replace(/\s+/g, " ").trim().toLowerCase();
237415
237566
  }
@@ -237570,7 +237721,22 @@ ${info.feedback}`,
237570
237721
  return false;
237571
237722
  }
237572
237723
  const revisionKey = optionalStepRevisionKey2(info.nodeId, info.stepName);
237573
- if (hasRepeatedUnchangedCodeReview(liveTask, info)) {
237724
+ const reviewResult = (liveTask.workflowStepResults ?? []).find(
237725
+ (result) => (result.workflowStepId === info.nodeId || result.workflowStepName === info.stepName) && result.verdict === "REVISE"
237726
+ );
237727
+ const derivedRemediation = reviewResult ? deriveWorkspaceReviewRemediation(reviewResult) : void 0;
237728
+ const remediation = derivedRemediation && liveTask.workspaceWorktrees ? derivedRemediation : void 0;
237729
+ const priorRemediation = liveTask.repositoryScope?.reviewRemediation;
237730
+ const hasDurableRepeatedWorkspaceReview = remediation !== void 0 && priorRemediation?.scopeRevision === remediation.scopeRevision && priorRemediation.repository === remediation.repository && priorRemediation.inputSignature === remediation.inputSignature;
237731
+ const updateWorkspaceReviewState = deps.store.updateWorkspaceReviewState;
237732
+ if (remediation && !hasDurableRepeatedWorkspaceReview && updateWorkspaceReviewState) {
237733
+ const persisted = await updateWorkspaceReviewState.call(deps.store, taskId, remediation.scopeRevision, remediation);
237734
+ if (!persisted.updated) {
237735
+ executorLog.warn(`${taskId}: workspace Code Review remediation was superseded by a repository scope change.`);
237736
+ return false;
237737
+ }
237738
+ }
237739
+ if (hasDurableRepeatedWorkspaceReview || (!remediation || !updateWorkspaceReviewState) && hasRepeatedUnchangedCodeReview(liveTask, info)) {
237574
237740
  const runContext = deps.getRunContextFor(taskId);
237575
237741
  await deps.store.logEntry(
237576
237742
  taskId,
@@ -237618,9 +237784,18 @@ Feedback:
237618
237784
  ${info.feedback}`, revisionKey),
237619
237785
  deps.getRunContextFor(taskId)
237620
237786
  );
237621
- await deps.sendTaskBackForFix(
237787
+ const remediationWorktreePath = remediation ? liveTask.workspaceWorktrees?.[remediation.repository]?.worktreePath : liveTask.worktree;
237788
+ if (remediation && !remediationWorktreePath) {
237789
+ await deps.store.updateTask(taskId, {
237790
+ status: "awaiting-approval",
237791
+ awaitingApprovalReason: "code-review-non-convergence",
237792
+ error: `Workspace Code Review remediation target ${remediation.repository} has no acquired worktree.`
237793
+ });
237794
+ return false;
237795
+ }
237796
+ const sendArgs = [
237622
237797
  liveTask,
237623
- liveTask.worktree ?? "",
237798
+ remediationWorktreePath ?? "",
237624
237799
  info.feedback,
237625
237800
  info.stepName,
237626
237801
  `Pre-merge optional workflow step "${info.stepName}" requested revision`,
@@ -237628,7 +237803,12 @@ ${info.feedback}`, revisionKey),
237628
237803
  false,
237629
237804
  { attempt: nextCount, max: budget.unbounded ? void 0 : budget.max },
237630
237805
  info.findings
237631
- );
237806
+ ];
237807
+ if (remediation) {
237808
+ await deps.sendTaskBackForFix(...sendArgs, false);
237809
+ } else {
237810
+ await deps.sendTaskBackForFix(...sendArgs);
237811
+ }
237632
237812
  return true;
237633
237813
  }
237634
237814
 
@@ -238498,6 +238678,14 @@ function createTaskDoneTool(deps, taskId, worktreePath, promptContent, codeRevie
238498
238678
  const refusalMessage = `fn_task_done refused: ${invariantCheck.reason} \u2014 observed=${invariantCheck.observed}, expected=${invariantCheck.expected}`;
238499
238679
  await store.logEntry(taskId, refusalMessage, void 0, deps.getRunContextFor(task.id));
238500
238680
  executorLog.error(`${taskId}: fn_task_done refused (${invariantCheck.reason}) \u2014 observed=${invariantCheck.observed}, expected=${invariantCheck.expected}`);
238681
+ if (invariantCheck.reason === "main_checkout_edit") {
238682
+ await store.updateTask(taskId, { status: "failed", error: refusalMessage });
238683
+ await store.logEntry(taskId, `${refusalMessage} \u2014 operator cleanup and Retry are required; executor retry budget preserved`, void 0, deps.getRunContextFor(task.id));
238684
+ return {
238685
+ content: [{ type: "text", text: refusalMessage }],
238686
+ details: { error: refusalMessage }
238687
+ };
238688
+ }
238501
238689
  const priorRequeues = task.taskDoneRetryCount ?? 0;
238502
238690
  const nextRequeueCount = priorRequeues + 1;
238503
238691
  if (priorRequeues < MAX_TASK_DONE_REQUEUE_RETRIES) {
@@ -240161,7 +240349,9 @@ Repository scope (task-level; review this plan once): ${detail.repositoryScope?.
240161
240349
  return {
240162
240350
  repositoryScope: {
240163
240351
  ...currentScope,
240164
- reviewEvidence: Object.fromEntries(Object.entries(review.repositoryDiffFingerprints).map(([repo, fingerprint]) => [repo, { fingerprint, approvedAt }]))
240352
+ reviewEvidence: Object.fromEntries(Object.entries(review.repositoryDiffFingerprints).map(([repo, fingerprint]) => [repo, { fingerprint, approvedAt }])),
240353
+ // FNXC:WorkspaceFinalization 2026-08-21-09:50: An APPROVE for this fenced scope ends its REVISE episode; retaining its target would misroute later acquisition.
240354
+ ...currentScope.reviewRemediation?.scopeRevision === review.repositoryScopeRevision ? { reviewRemediation: void 0 } : {}
240165
240355
  }
240166
240356
  };
240167
240357
  });
@@ -241438,6 +241628,7 @@ async function executeWorkflowGraph(deps, task, opts) {
241438
241628
  // ../engine/src/executor/run-graph-custom-node.ts
241439
241629
  init_src2();
241440
241630
  init_logger();
241631
+ init_workspace_review_remediation();
241441
241632
  import { existsSync as existsSync74 } from "node:fs";
241442
241633
  var WORKFLOW_THINKING_LEVEL_SET2 = new Set(THINKING_LEVELS);
241443
241634
  async function runGraphCustomNode(deps, node, nodeTask, settings, columnBinding, graphContext, outputLanguage) {
@@ -241517,7 +241708,8 @@ async function runGraphCustomNode(deps, node, nodeTask, settings, columnBinding,
241517
241708
  executionTarget = await deps.ensureGraphCustomNodeWorktree(acquisitionTask, settings, node.id);
241518
241709
  }
241519
241710
  }
241520
- const workspaceCoordinator = workspaceConfig ? Object.keys(executionTarget.workspaceWorktrees ?? {}).filter((repoRelPath) => workspaceConfig.repos.includes(repoRelPath)).sort().map((repoRelPath) => executionTarget.workspaceWorktrees?.[repoRelPath]?.worktreePath).find((path6) => typeof path6 === "string" && path6.length > 0) : void 0;
241711
+ const remediationRepository = workspaceConfig ? resolveWorkspaceReviewRemediationRepository(executionTarget, workspaceConfig.repos) : void 0;
241712
+ const workspaceCoordinator = workspaceConfig ? remediationRepository ? executionTarget.workspaceWorktrees?.[remediationRepository]?.worktreePath : Object.keys(executionTarget.workspaceWorktrees ?? {}).filter((repoRelPath) => workspaceConfig.repos.includes(repoRelPath)).sort().map((repoRelPath) => executionTarget.workspaceWorktrees?.[repoRelPath]?.worktreePath).find((path6) => typeof path6 === "string" && path6.length > 0) : void 0;
241521
241713
  if (!executionTarget.worktree && !workspaceCoordinator) {
241522
241714
  return { outcome: "failure", value: "no-worktree-for-write-node" };
241523
241715
  }
@@ -241677,7 +241869,9 @@ ${prompt}`;
241677
241869
  return {
241678
241870
  repositoryScope: {
241679
241871
  ...currentScope,
241680
- reviewEvidence: Object.fromEntries(Object.entries(aggregate.repositoryDiffFingerprints).map(([repo, fingerprint]) => [repo, { fingerprint, approvedAt }]))
241872
+ reviewEvidence: Object.fromEntries(Object.entries(aggregate.repositoryDiffFingerprints).map(([repo, fingerprint]) => [repo, { fingerprint, approvedAt }])),
241873
+ // FNXC:WorkspaceFinalization 2026-08-21-09:50: A current-scope APPROVE clears the durable REVISE coordinator before graph completion can schedule another run.
241874
+ ...currentScope.reviewRemediation?.scopeRevision === aggregate.repositoryScopeRevision ? { reviewRemediation: void 0 } : {}
241681
241875
  }
241682
241876
  };
241683
241877
  });
@@ -241824,6 +242018,13 @@ async function handleGraphFailure(deps, task, result) {
241824
242018
  await deps.persistTokenUsage(task.id);
241825
242019
  return;
241826
242020
  }
242021
+ if (graphFailureValue(result) === BRANCH_WRITE_PROVENANCE_FAILURE_VALUE) {
242022
+ const diagnostic = graphFailureErrorTexts(result).find((message2) => message2.includes("branchWriteOrigin is required when branch is provided")) ?? "branchWriteOrigin is required when branch is provided";
242023
+ await deps.store.logEntry(task.id, diagnostic, void 0, deps.getRunContextFor(task.id));
242024
+ await deps.store.updateTask(task.id, { status: "failed", error: diagnostic }, deps.getRunContextFor(task.id));
242025
+ await deps.persistTokenUsage(task.id);
242026
+ return;
242027
+ }
241827
242028
  if (isSessionContentionGraphFailure(result)) {
241828
242029
  await deps.holdForSessionContention(task, live, result);
241829
242030
  await deps.persistTokenUsage(task.id);
@@ -245639,6 +245840,7 @@ init_planner_lane_resolution();
245639
245840
  init_effective_settings();
245640
245841
  init_run_audit();
245641
245842
  init_worktree_acquisition();
245843
+ init_workspace_review_remediation();
245642
245844
  init_worktree_paths();
245643
245845
  init_worktree_pool();
245644
245846
  var execAsync52 = promisify74(exec55);
@@ -245817,6 +246019,7 @@ async function runImplementation(deps, task, graphCompletion, reportImplementati
245817
246019
  deps.registerConfiguredCommandController(task.id, taskCommandAbortController);
245818
246020
  let workspaceCoordinatorWorktree;
245819
246021
  if (hasWorkspaceRepos && deps.workspaceConfig) {
246022
+ const remediationRepository = resolveWorkspaceReviewRemediationRepository(task, deps.workspaceConfig.repos);
245820
246023
  const workspace = await acquireWorkspaceTaskWorktrees({
245821
246024
  workspaceConfig: deps.workspaceConfig,
245822
246025
  workspaceRootDir: deps.rootDir,
@@ -245841,7 +246044,8 @@ async function runImplementation(deps, task, graphCompletion, reportImplementati
245841
246044
  return result;
245842
246045
  }),
245843
246046
  taskEnv,
245844
- addActiveWorktree: deps.addActiveWorktree
246047
+ addActiveWorktree: deps.addActiveWorktree,
246048
+ remediationRepository
245845
246049
  });
245846
246050
  task = workspace.task;
245847
246051
  workspaceCoordinatorWorktree = workspace.coordinatorWorktreePath;
@@ -269259,6 +269463,35 @@ init_merge_dependency_sync();
269259
269463
  init_active_session_registry();
269260
269464
  init_mcp_resolution();
269261
269465
 
269466
+ // ../engine/src/merge/workspace-merge-readiness.ts
269467
+ function resolveWorkspaceMergeReadiness(task, freshModifiedRepositories, netZeroBranchRepositories) {
269468
+ const scope = task.repositoryScope;
269469
+ if (scope?.state !== "confirmed") {
269470
+ return { kind: "blocked", reason: `Workspace repository scope is unresolved for ${task.id}; operator confirmation is required before landing` };
269471
+ }
269472
+ const entries = task.workspaceWorktrees ?? {};
269473
+ const declaredRepositories = scope.repositories.map((repository) => repository.trim());
269474
+ if (declaredRepositories.some((repository) => !repository) || new Set(declaredRepositories).size !== declaredRepositories.length) {
269475
+ return { kind: "blocked", reason: `Workspace repository scope has duplicate or invalid declarations for ${task.id}; operator repair is required` };
269476
+ }
269477
+ const worktreePaths = Object.values(entries).map((entry) => entry.worktreePath?.trim()).filter((path6) => Boolean(path6));
269478
+ if (new Set(worktreePaths).size !== worktreePaths.length) {
269479
+ return { kind: "blocked", reason: `Workspace merge has duplicate worktree paths for ${task.id}; operator repair is required` };
269480
+ }
269481
+ const repositories = declaredRepositories.filter((repo) => Boolean(entries[repo])).filter((repo) => freshModifiedRepositories.has(repo) || netZeroBranchRepositories.has(repo) || Boolean(entries[repo]?.landedSha)).sort();
269482
+ const preservedFiles = [...new Set(task.modifiedFiles ?? [])].sort();
269483
+ if (repositories.length > 0) return { kind: "ready", repositories, preservedFiles };
269484
+ if (task.noCommitsExpected === true && scope.repositories.length === 0) {
269485
+ return { kind: "no-op", repositories: [], preservedFiles: [] };
269486
+ }
269487
+ const declared = [...declaredRepositories].sort();
269488
+ const knownEntries = declared.filter((repo) => entries[repo] !== void 0);
269489
+ if (knownEntries.length === 0) {
269490
+ return { kind: "blocked", reason: `Workspace merge has no acquired repositories matching confirmed scope for ${task.id}; operator repair is required` };
269491
+ }
269492
+ return { kind: "blocked", reason: `Workspace merge has no evidenced landing obligations for ${task.id}; operator repair is required` };
269493
+ }
269494
+
269262
269495
  // ../engine/src/merge/workspace-fence-ref.ts
269263
269496
  import { execFile as execFile33 } from "node:child_process";
269264
269497
  import { createHash as createHash35 } from "node:crypto";
@@ -271000,6 +271233,14 @@ var WorkspaceRepoLandBusyError = class extends Error {
271000
271233
  }
271001
271234
  retryable = true;
271002
271235
  };
271236
+ var WorkspaceMergeTechnicalError = class extends Error {
271237
+ constructor(kind, message) {
271238
+ super(message);
271239
+ this.kind = kind;
271240
+ this.name = "WorkspaceMergeTechnicalError";
271241
+ }
271242
+ retryable = true;
271243
+ };
271003
271244
  var WorkspacePartialLandError = class extends Error {
271004
271245
  constructor(landedCount, failedRepos, message) {
271005
271246
  super(message);
@@ -271061,8 +271302,21 @@ async function landWorkspaceTask(store, task, workspaceRootDir, options = {}, de
271061
271302
  const taskTitle = task.title?.trim() ? task.title.split("\n")[0] : void 0;
271062
271303
  const liveMergeBoundaryTask = await store.getTask(taskId).catch(() => void 0);
271063
271304
  const mergeBoundaryTask = liveMergeBoundaryTask?.workspaceWorktrees ? liveMergeBoundaryTask : task;
271305
+ const reviewColumns = /* @__PURE__ */ new Set(["in-review"]);
271306
+ const workflowIr = await resolveWorkflowIrForTask(store, taskId).catch(() => void 0);
271307
+ if (workflowIr) for (const column of resolveReviewColumns(workflowIr)) reviewColumns.add(column);
271308
+ const completeColumns = /* @__PURE__ */ new Set(["done"]);
271309
+ if (workflowIr) for (const column of resolveTerminalColumns(workflowIr)) completeColumns.add(column);
271310
+ if (!completeColumns.has(mergeBoundaryTask.column)) {
271311
+ const mergeBlocker = getTaskMergeBlocker(mergeBoundaryTask, {
271312
+ manual: options.manual === true,
271313
+ reviewColumns
271314
+ });
271315
+ if (mergeBlocker) throw new WorkspaceFinalizeBlockedError(taskId, mergeBlocker);
271316
+ }
271064
271317
  const confirmedScope = mergeBoundaryTask.repositoryScope?.state === "confirmed" ? new Set(mergeBoundaryTask.repositoryScope.repositories) : void 0;
271065
271318
  const mergeBoundaryModifiedFiles = [];
271319
+ const mergeBoundaryModifiedRepositories = /* @__PURE__ */ new Set();
271066
271320
  const mergeBoundaryFingerprints = {};
271067
271321
  const modifiedOutOfScopeRepositories = /* @__PURE__ */ new Set();
271068
271322
  const netZeroBranchRepositories = /* @__PURE__ */ new Set();
@@ -271079,6 +271333,7 @@ async function landWorkspaceTask(store, task, workspaceRootDir, options = {}, de
271079
271333
  if (files.length > 0 && !confirmedScope?.has(repoRel)) {
271080
271334
  modifiedOutOfScopeRepositories.add(repoRel);
271081
271335
  } else if (confirmedScope?.has(repoRel)) {
271336
+ if (files.length > 0) mergeBoundaryModifiedRepositories.add(repoRel);
271082
271337
  mergeBoundaryModifiedFiles.push(...files.map((file) => `${repoRel}/${file}`));
271083
271338
  if (files.length === 0) {
271084
271339
  const { stdout: aheadCount } = await execFileAsync29("git", ["rev-list", "--count", `HEAD..${entry.branch}`], { cwd: entry.worktreePath, encoding: "utf8" });
@@ -271094,19 +271349,26 @@ async function landWorkspaceTask(store, task, workspaceRootDir, options = {}, de
271094
271349
  }
271095
271350
  const normalizedMergeBoundaryFiles = [...new Set(mergeBoundaryModifiedFiles)].sort();
271096
271351
  const persistedReviewFiles = [...new Set(mergeBoundaryTask.modifiedFiles ?? [])].sort();
271097
- const approvedReviewEvidence = mergeBoundaryTask.repositoryScope?.reviewEvidence ?? {};
271098
- const missingOrStaleReviewApproval = Object.entries(mergeBoundaryFingerprints).some(([repoRel, fingerprint]) => approvedReviewEvidence[repoRel]?.fingerprint !== fingerprint);
271099
- if (normalizedMergeBoundaryFiles.some((file) => !persistedReviewFiles.includes(file)) || missingOrStaleReviewApproval) {
271352
+ const approvedReviewEvidence = mergeBoundaryTask.repositoryScope?.reviewEvidence;
271353
+ const requiresRepositoryReviewEvidence = approvedReviewEvidence !== void 0 || (mergeBoundaryTask.enabledWorkflowSteps ?? []).some((step) => /review/i.test(step));
271354
+ const missingOrStaleReviewApproval = requiresRepositoryReviewEvidence && Object.entries(mergeBoundaryFingerprints).some(([repoRel, fingerprint]) => approvedReviewEvidence?.[repoRel]?.fingerprint !== fingerprint);
271355
+ if (requiresRepositoryReviewEvidence && normalizedMergeBoundaryFiles.some((file) => !persistedReviewFiles.includes(file)) || missingOrStaleReviewApproval) {
271100
271356
  throw new Error(`Workspace merge evidence changed after review for ${taskId}; return the task to Code Review before landing`);
271101
271357
  }
271102
- await store.updateTask(taskId, { modifiedFiles: normalizedMergeBoundaryFiles });
271103
- task = { ...mergeBoundaryTask, modifiedFiles: normalizedMergeBoundaryFiles };
271104
- const workspaceWorktrees = task.workspaceWorktrees ?? {};
271105
- const explicitScope = task.repositoryScope?.state === "confirmed" ? task.repositoryScope.repositories : void 0;
271106
- if (!explicitScope) {
271107
- throw new Error(`Workspace repository scope is unresolved for ${taskId}; operator confirmation is required before landing`);
271358
+ const readiness = resolveWorkspaceMergeReadiness(
271359
+ mergeBoundaryTask,
271360
+ mergeBoundaryModifiedRepositories,
271361
+ netZeroBranchRepositories
271362
+ );
271363
+ if (readiness.kind === "blocked") throw new Error(readiness.reason);
271364
+ if (readiness.kind === "no-op") {
271365
+ throw new Error(`Workspace task ${taskId} has explicit no-commits policy and cannot enter repository landing`);
271108
271366
  }
271109
- const repoKeys = Object.keys(workspaceWorktrees).filter((repoRel) => explicitScope.includes(repoRel) && ((task.modifiedFiles ?? []).some((file) => file.startsWith(`${repoRel}/`)) || netZeroBranchRepositories.has(repoRel))).sort();
271367
+ const retainedModifiedFiles = [.../* @__PURE__ */ new Set([...normalizedMergeBoundaryFiles, ...readiness.preservedFiles])].sort();
271368
+ await store.updateTask(taskId, { modifiedFiles: retainedModifiedFiles });
271369
+ task = { ...mergeBoundaryTask, modifiedFiles: retainedModifiedFiles };
271370
+ const workspaceWorktrees = task.workspaceWorktrees ?? {};
271371
+ const repoKeys = readiness.repositories;
271110
271372
  const repos = [];
271111
271373
  let allLanded = true;
271112
271374
  await setStatus("merging");
@@ -271135,7 +271397,7 @@ async function landWorkspaceTask(store, task, workspaceRootDir, options = {}, de
271135
271397
  };
271136
271398
  } catch (error2) {
271137
271399
  if (error2 instanceof WorkspaceFenceRefError) {
271138
- throw new WorkspaceRepoLandBusyError(repoKeys[0] ?? "workspace", "workspace-merge-dispatch-fence", taskId);
271400
+ throw new WorkspaceMergeTechnicalError("dispatch-fence-publication", `Workspace merge dispatch fence publication failed for ${taskId}: ${error2.message}`);
271139
271401
  }
271140
271402
  throw error2;
271141
271403
  }
@@ -271224,7 +271486,7 @@ async function landWorkspaceTask(store, task, workspaceRootDir, options = {}, de
271224
271486
  } catch (error2) {
271225
271487
  if (durableLandLease) await store.releaseWorkspaceLease(durableLandLease).catch(() => void 0);
271226
271488
  if (error2 instanceof WorkspaceRepoLandBusyError) throw error2;
271227
- throw new WorkspaceRepoLandBusyError(repoRel, "durable-workspace-lease", taskId);
271489
+ throw new WorkspaceMergeTechnicalError("durable-lease", `Workspace repository lease unavailable for ${repoRel}: ${getErrorMessage4(error2)}`);
271228
271490
  }
271229
271491
  try {
271230
271492
  activeSessionRegistry.registerPath(repoRootDir, {
@@ -271261,7 +271523,7 @@ async function landWorkspaceTask(store, task, workspaceRootDir, options = {}, de
271261
271523
  renewalTimer?.unref?.();
271262
271524
  const landSignal = options.signal ? AbortSignal.any([options.signal, leaseAbort.signal]) : leaseAbort.signal;
271263
271525
  const assertLeaseLive = () => {
271264
- if (leaseLost || leaseAbort.signal.aborted) throw new WorkspaceRepoLandBusyError(repoRel, "durable-workspace-lease", taskId);
271526
+ if (leaseLost || leaseAbort.signal.aborted) throw new WorkspaceMergeTechnicalError("durable-lease", `Workspace repository lease renewal was lost for ${repoRel}`);
271265
271527
  };
271266
271528
  try {
271267
271529
  const landResult = await landOneRepo(repoRootDir, entry.branch, integrationBranch, {
@@ -271346,11 +271608,11 @@ async function landWorkspaceTask(store, task, workspaceRootDir, options = {}, de
271346
271608
  repos.push({ repo: repoRel, repoRootDir, integrationBranch, branch: entry.branch, status: "empty" });
271347
271609
  }
271348
271610
  } catch (err) {
271349
- if (leaseLost) throw new WorkspaceRepoLandBusyError(repoRel, "durable-workspace-lease", taskId);
271611
+ if (leaseLost) throw new WorkspaceMergeTechnicalError("durable-lease", `Workspace repository lease renewal was lost for ${repoRel}`);
271350
271612
  if (isMergeAbortedError(err)) throw err;
271351
271613
  if (err instanceof WorkspacePartialLandError) throw err;
271352
271614
  if (err instanceof WorkspaceFenceRefError) {
271353
- throw new WorkspaceRepoLandBusyError(repoRel, "workspace-merge-dispatch-fence", taskId);
271615
+ throw new WorkspaceMergeTechnicalError("repository-fence-publication", `Workspace repository fence publication failed for ${repoRel}: ${err.message}`);
271354
271616
  }
271355
271617
  const message = getErrorMessage4(err);
271356
271618
  await log42(`AI merge (workspace): sub-repo ${repoRel} land failed: ${message}`);
@@ -276162,6 +276424,15 @@ ${detail}`);
276162
276424
  }
276163
276425
  continue;
276164
276426
  }
276427
+ if (err instanceof WorkspaceMergeTechnicalError) {
276428
+ await store.logEntry(taskId, `Workspace merge technical failure (${err.kind}): ${err.message}`, "WorkspaceMergeTechnicalFailure").catch(() => void 0);
276429
+ if (hasManualResolver) {
276430
+ this.rejectMergeResolvers(taskId, err);
276431
+ } else if (!await this.maybeRetryTransientMerge(store, taskId, await store.getTask(taskId).catch(() => null), err.message, true)) {
276432
+ await store.updateTask(taskId, { status: "failed", error: err.message }).catch(() => void 0);
276433
+ }
276434
+ continue;
276435
+ }
276165
276436
  const isWorkspaceBusyError = err instanceof WorkspaceRepoLandBusyError || err instanceof WorkspaceMergeDispatchBusyError || err instanceof WorkspaceMergeDispatchSupersededError;
276166
276437
  if (isWorkspaceBusyError && hasManualResolver) {
276167
276438
  await store.logEntry(taskId, `Workspace sub-repo land busy (contention): ${errorMsg}`, "WorkspaceRepoLandBusy").catch(() => void 0);
@@ -276169,18 +276440,18 @@ ${detail}`);
276169
276440
  continue;
276170
276441
  }
276171
276442
  if (isWorkspaceBusyError && !hasManualResolver) {
276172
- const busyCount = this.workspaceBusyReenqueues.get(taskId) ?? 0;
276443
+ const liveTask = await store.getTask(taskId).catch(() => null);
276444
+ const busyCount = liveTask?.mergeTransientRetryCount ?? 0;
276173
276445
  await store.logEntry(taskId, `Workspace sub-repo land busy (contention): ${errorMsg}`, "WorkspaceRepoLandBusy").catch(() => void 0);
276174
276446
  if (busyCount < _ProjectEngine.WORKSPACE_BUSY_MAX_REENQUEUES) {
276175
- this.workspaceBusyReenqueues.set(taskId, busyCount + 1);
276447
+ const nextCount = busyCount + 1;
276176
276448
  const delayMs = Math.min(5e3 * Math.pow(2, busyCount), 6e4);
276177
- await store.updateTask(taskId, { status: null }).catch(() => void 0);
276449
+ await store.updateTask(taskId, { status: null, mergeTransientRetryCount: nextCount }).catch(() => void 0);
276178
276450
  runtimeLog.log(
276179
- `Workspace land busy re-enqueue ${busyCount + 1}/${_ProjectEngine.WORKSPACE_BUSY_MAX_REENQUEUES} for ${taskId} in ${delayMs / 1e3}s (no mergeRetry consumed \u2014 pure lease contention)`
276451
+ `Workspace land busy re-enqueue ${nextCount}/${_ProjectEngine.WORKSPACE_BUSY_MAX_REENQUEUES} for ${taskId} in ${delayMs / 1e3}s (durable transient retry)`
276180
276452
  );
276181
276453
  this.scheduleWorkspaceBusyReenqueue(taskId, delayMs);
276182
276454
  } else {
276183
- this.workspaceBusyReenqueues.delete(taskId);
276184
276455
  await store.updateTask(taskId, { status: "failed", error: errorMsg }).catch(() => void 0);
276185
276456
  runtimeLog.error(
276186
276457
  `Auto-merge: ${taskId} workspace land busy ${_ProjectEngine.WORKSPACE_BUSY_MAX_REENQUEUES} times \u2014 parked as failed (sustained sub-repo lease contention)`