@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
package/dist/extension.js CHANGED
@@ -152,10 +152,16 @@ function resolveEntryPointBranchAssignment(input) {
152
152
  };
153
153
  }
154
154
  }
155
- var isValidTaskBranchName;
155
+ var BranchWriteProvenanceError, isValidTaskBranchName;
156
156
  var init_branch_assignment = __esm({
157
157
  "../core/src/branch/branch-assignment.ts"() {
158
158
  "use strict";
159
+ BranchWriteProvenanceError = class extends Error {
160
+ constructor() {
161
+ super("branchWriteOrigin is required when branch is provided");
162
+ this.name = "BranchWriteProvenanceError";
163
+ }
164
+ };
159
165
  isValidTaskBranchName = isValidBranchGroupBranchName;
160
166
  }
161
167
  });
@@ -4533,8 +4539,8 @@ var init_research_settings = __esm({
4533
4539
  function resolveResearchFindingId(finding) {
4534
4540
  const explicitId = typeof finding.id === "string" ? finding.id.trim() : "";
4535
4541
  if (explicitId) return explicitId;
4536
- const normalize9 = (value) => value.trim().replace(/\s+/g, " ").toLowerCase();
4537
- const canonical = [normalize9(finding.heading ?? ""), normalize9(finding.content ?? ""), ...[...finding.sources ?? []].map(normalize9).filter(Boolean).sort()].join("\n");
4542
+ const normalize10 = (value) => value.trim().replace(/\s+/g, " ").toLowerCase();
4543
+ const canonical = [normalize10(finding.heading ?? ""), normalize10(finding.content ?? ""), ...[...finding.sources ?? []].map(normalize10).filter(Boolean).sort()].join("\n");
4538
4544
  let hash3 = 2166136261;
4539
4545
  for (let index2 = 0; index2 < canonical.length; index2 += 1) {
4540
4546
  hash3 ^= canonical.charCodeAt(index2);
@@ -35988,17 +35994,17 @@ var init_agent_store = __esm({
35988
35994
  * @returns Matching agent when unambiguous; otherwise null
35989
35995
  */
35990
35996
  async resolveAgent(shortname) {
35991
- const normalize9 = (value) => value.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "");
35997
+ const normalize10 = (value) => value.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "");
35992
35998
  const all = await this.listAgents();
35993
35999
  const exact = all.find((agent) => agent.id === shortname);
35994
36000
  if (exact) {
35995
36001
  return exact;
35996
36002
  }
35997
- const normalizedTarget = normalize9(shortname);
36003
+ const normalizedTarget = normalize10(shortname);
35998
36004
  if (!normalizedTarget) {
35999
36005
  return null;
36000
36006
  }
36001
- const matches = all.filter((agent) => normalize9(agent.name) === normalizedTarget);
36007
+ const matches = all.filter((agent) => normalize10(agent.name) === normalizedTarget);
36002
36008
  return matches.length === 1 ? matches[0] : null;
36003
36009
  }
36004
36010
  // ─────────────────────────────────────────────────────────────────────────
@@ -45240,11 +45246,11 @@ function withWindowsNativeBinPath(environment, nativeRoot, platform4 = process.p
45240
45246
  if (platform4 !== "win32") return environment;
45241
45247
  const pathKey = Object.keys(environment).find((key) => key.toLowerCase() === "path") ?? "PATH";
45242
45248
  const bin = nativeRoot.replace(/[\\/]+$/, "") + "\\bin";
45243
- const normalize9 = (value) => value.replace(/\//g, "\\").toLowerCase();
45249
+ const normalize10 = (value) => value.replace(/\//g, "\\").toLowerCase();
45244
45250
  const inherited = environment[pathKey] ?? "";
45245
45251
  const segments = inherited.split(";").filter(Boolean);
45246
- const normalizedBin = normalize9(bin);
45247
- const deduplicated = segments.filter((segment) => normalize9(segment) !== normalizedBin);
45252
+ const normalizedBin = normalize10(bin);
45253
+ const deduplicated = segments.filter((segment) => normalize10(segment) !== normalizedBin);
45248
45254
  return { ...environment, [pathKey]: [bin, ...deduplicated].join(";") };
45249
45255
  }
45250
45256
  function readPostgresPid(dataDir) {
@@ -67193,14 +67199,14 @@ function resolveGlobalDir(dir) {
67193
67199
  const isFusionDirInsideRepo = basename8(dir) === ".fusion" && existsSync14(join19(dirname11(dir), ".git"));
67194
67200
  if (isFusionDirInsideRepo) {
67195
67201
  const homeGlobalDir = resolveGlobalDirForHome(getHomeDir2());
67196
- const normalize9 = (p2) => {
67202
+ const normalize10 = (p2) => {
67197
67203
  try {
67198
67204
  return realpathSync3.native(p2);
67199
67205
  } catch {
67200
67206
  return resolve13(p2);
67201
67207
  }
67202
67208
  };
67203
- if (normalize9(dir) !== normalize9(homeGlobalDir)) {
67209
+ if (normalize10(dir) !== normalize10(homeGlobalDir)) {
67204
67210
  throw new Error(
67205
67211
  `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.`
67206
67212
  );
@@ -87927,15 +87933,15 @@ async function appendRecall(layer, input) {
87927
87933
  async function mergeRecallGraphNodeIds(layer, recordId, addGraphNodeIds) {
87928
87934
  const pid = projectId(layer);
87929
87935
  const table = project_exports.memoryRecallRecords;
87930
- const normalize9 = (ids) => [...new Set(ids.map((id) => id.trim()).filter(Boolean))].sort();
87936
+ const normalize10 = (ids) => [...new Set(ids.map((id) => id.trim()).filter(Boolean))].sort();
87931
87937
  return layer.transactionImmediate(async (tx) => {
87932
87938
  await tx.execute(sql`SELECT pg_advisory_xact_lock(hashtext(${recallGraphNodeIdsLockKey(pid, recordId)}))`);
87933
87939
  const rows = await tx.select().from(table).where(and(eq(table.projectId, pid), eq(table.id, recordId))).limit(1);
87934
87940
  await recallGraphNodeIdsTestHooks?.afterRowRead?.();
87935
87941
  const row = rows[0];
87936
87942
  if (!row) return { status: "missing" };
87937
- const stored = normalize9(row.graphNodeIds);
87938
- const next = normalize9([...stored, ...addGraphNodeIds]);
87943
+ const stored = normalize10(row.graphNodeIds);
87944
+ const next = normalize10([...stored, ...addGraphNodeIds]);
87939
87945
  if (stored.length === next.length && stored.every((value, index2) => value === next[index2])) return { status: "unchanged" };
87940
87946
  await tx.update(table).set({ graphNodeIds: next, updatedAt: (/* @__PURE__ */ new Date()).toISOString() }).where(and(eq(table.projectId, pid), eq(table.id, recordId)));
87941
87947
  return { status: "updated" };
@@ -92338,7 +92344,7 @@ import { randomUUID as randomUUID31 } from "node:crypto";
92338
92344
  function normalizeCreateBranchProvenance(input) {
92339
92345
  if (input.branch === void 0) return input;
92340
92346
  if (input.branchWriteOrigin !== "operator" && input.branchWriteOrigin !== "engine") {
92341
- throw new Error("branchWriteOrigin is required when branch is provided");
92347
+ throw new BranchWriteProvenanceError();
92342
92348
  }
92343
92349
  validateTaskBranchName(input.branch);
92344
92350
  if (input.branchWriteOrigin === "operator") {
@@ -94555,7 +94561,7 @@ async function updateTaskRepositoryScopeImpl(store, id, requestedScope) {
94555
94561
  ...replacement,
94556
94562
  repositories: nextRepositories,
94557
94563
  revision: Math.max((current.repositoryScope?.revision ?? 0) + 1, replacement.revision ?? 0),
94558
- ...scopeChanged ? { reviewEvidence: void 0 } : {}
94564
+ ...scopeChanged ? { reviewEvidence: void 0, reviewRemediation: void 0 } : {}
94559
94565
  };
94560
94566
  const [updatedRow] = await tx.update(project_exports.tasks).set({
94561
94567
  repositoryScope: normalized2 ?? null,
@@ -94571,6 +94577,35 @@ async function updateTaskRepositoryScopeImpl(store, id, requestedScope) {
94571
94577
  return outcome;
94572
94578
  }));
94573
94579
  }
94580
+ async function updateWorkspaceReviewStateImpl(store, id, expectedScopeRevision, reviewRemediation) {
94581
+ return store.withTaskLock(id, async () => {
94582
+ const layer = store.asyncLayer;
94583
+ const outcome = await layer.transactionImmediate(async (tx) => {
94584
+ await acquireTaskAdvisoryXactLock(tx, layer.projectId, id);
94585
+ const row = await readTaskRowInTransaction(tx, id, { includeDeleted: true }, layer.projectId);
94586
+ if (!row) throw new TaskNotFoundError(id);
94587
+ if (row.deletedAt) throw new TaskDeletedError(id, row.deletedAt);
94588
+ const current = store.rowToTask(store.pgRowToTaskRow(row));
94589
+ if (current.repositoryScope?.revision !== expectedScopeRevision) return { task: current, updated: false };
94590
+ const repositoryScope = {
94591
+ ...current.repositoryScope,
94592
+ ...reviewRemediation ? { reviewRemediation } : { reviewRemediation: void 0 }
94593
+ };
94594
+ const [updatedRow] = await tx.update(project_exports.tasks).set({
94595
+ repositoryScope,
94596
+ updatedAt: (/* @__PURE__ */ new Date()).toISOString()
94597
+ }).where(and(eq(project_exports.tasks.id, id), taskProjectScope(layer))).returning();
94598
+ if (!updatedRow) throw new TaskNotFoundError(id);
94599
+ return { task: store.rowToTask(store.pgRowToTaskRow(updatedRow)), updated: true };
94600
+ });
94601
+ if (outcome.updated) {
94602
+ await store.writeTaskJsonFile(store.taskDir(id), outcome.task);
94603
+ if (store.isWatching) store.taskCache.set(id, { ...outcome.task });
94604
+ store.emitTaskLifecycleEventSafely("task:updated", [outcome.task]);
94605
+ }
94606
+ return outcome;
94607
+ });
94608
+ }
94574
94609
  async function resolveTaskWedgeNotificationEpisodeImpl(store, id, episodeId) {
94575
94610
  const layer = store.asyncLayer;
94576
94611
  const row = await resolveActiveTaskWedgeEpisodeRow(layer, id, episodeId, (/* @__PURE__ */ new Date()).toISOString());
@@ -103374,7 +103409,7 @@ async function updateTaskUnlockedImpl(store, id, updates, runContext) {
103374
103409
  if (updates.recommendations !== void 0) assertValidRecommendations(updates.recommendations);
103375
103410
  if (updates.branch !== void 0) {
103376
103411
  if (updates.branchWriteOrigin !== "operator" && updates.branchWriteOrigin !== "engine") {
103377
- throw new Error("branchWriteOrigin is required when branch is provided");
103412
+ throw new BranchWriteProvenanceError();
103378
103413
  }
103379
103414
  if (updates.branch !== null) validateTaskBranchName(updates.branch);
103380
103415
  }
@@ -103463,7 +103498,7 @@ async function updateTaskUnlockedImpl(store, id, updates, runContext) {
103463
103498
  task.repositoryScope = void 0;
103464
103499
  } else if (updates.repositoryScope !== void 0) {
103465
103500
  const scopeRevisionChanged = task.repositoryScope?.revision !== updates.repositoryScope.revision;
103466
- task.repositoryScope = scopeRevisionChanged ? { ...updates.repositoryScope, reviewEvidence: void 0 } : updates.repositoryScope;
103501
+ task.repositoryScope = scopeRevisionChanged ? { ...updates.repositoryScope, reviewEvidence: void 0, reviewRemediation: void 0 } : updates.repositoryScope;
103467
103502
  if (scopeRevisionChanged) {
103468
103503
  task.workflowStepResults = invalidateSupersededRepositoryScopeReviews(
103469
103504
  task.workflowStepResults,
@@ -105963,6 +105998,9 @@ var init_store = __esm({
105963
105998
  async mutateTaskRepositoryScope(id, mutation) {
105964
105999
  return updateTaskRepositoryScopeImpl(this, id, mutation);
105965
106000
  }
106001
+ async updateWorkspaceReviewState(id, expectedScopeRevision, reviewRemediation) {
106002
+ return updateWorkspaceReviewStateImpl(this, id, expectedScopeRevision, reviewRemediation);
106003
+ }
105966
106004
  async resolveTaskWedgeNotificationEpisode(id, episodeId) {
105967
106005
  return resolveTaskWedgeNotificationEpisodeImpl(this, id, episodeId);
105968
106006
  }
@@ -135970,6 +136008,7 @@ __export(src_exports, {
135970
136008
  BUNDLED_PLUGIN_IDS: () => BUNDLED_PLUGIN_IDS,
135971
136009
  BackupManager: () => BackupManager,
135972
136010
  BackwardCompat: () => BackwardCompat,
136011
+ BranchWriteProvenanceError: () => BranchWriteProvenanceError,
135973
136012
  CENTRAL_BACKUP_SCHEMAS: () => CENTRAL_BACKUP_SCHEMAS,
135974
136013
  CLAUDE_SONNET_5_MODEL_ID: () => CLAUDE_SONNET_5_MODEL_ID,
135975
136014
  CLEAN_COMPLETION_MARKERS: () => CLEAN_COMPLETION_MARKERS,
@@ -157868,8 +157907,8 @@ var require_resolve = __commonJS({
157868
157907
  }
157869
157908
  return count2;
157870
157909
  }
157871
- function getFullPath(resolver, id = "", normalize9) {
157872
- if (normalize9 !== false)
157910
+ function getFullPath(resolver, id = "", normalize10) {
157911
+ if (normalize10 !== false)
157873
157912
  id = normalizeId2(id);
157874
157913
  const p2 = resolver.parse(id);
157875
157914
  return _getFullPath(resolver, p2);
@@ -159209,7 +159248,7 @@ var require_fast_uri = __commonJS({
159209
159248
  "use strict";
159210
159249
  var { normalizeIPv6, removeDotSegments, recomposeAuthority, normalizeComponentEncoding, isIPv4, nonSimpleDomain } = require_utils();
159211
159250
  var { SCHEMES, getSchemeHandler } = require_schemes();
159212
- function normalize9(uri, options) {
159251
+ function normalize10(uri, options) {
159213
159252
  if (typeof uri === "string") {
159214
159253
  uri = /** @type {T} */
159215
159254
  serialize(parse4(uri, options), options);
@@ -159445,7 +159484,7 @@ var require_fast_uri = __commonJS({
159445
159484
  }
159446
159485
  var fastUri = {
159447
159486
  SCHEMES,
159448
- normalize: normalize9,
159487
+ normalize: normalize10,
159449
159488
  resolve: resolve63,
159450
159489
  resolveComponent,
159451
159490
  equal,
@@ -174866,6 +174905,39 @@ var init_worktrunk_failure_handler = __esm({
174866
174905
  }
174867
174906
  });
174868
174907
 
174908
+ // ../engine/src/executor/workspace-review-remediation.ts
174909
+ function normalize8(value) {
174910
+ return (value ?? "").replace(/\s+/g, " ").trim().toLowerCase();
174911
+ }
174912
+ function resolveWorkspaceReviewRemediationRepository(task, declaredRepositories) {
174913
+ const scope = task.repositoryScope;
174914
+ const remediation = scope?.reviewRemediation;
174915
+ if (!remediation) return void 0;
174916
+ if (scope?.state !== "confirmed" || scope.revision !== remediation.scopeRevision) {
174917
+ throw new Error(`Workspace Code Review remediation target is stale for ${task.id}`);
174918
+ }
174919
+ if (!declaredRepositories.includes(remediation.repository)) {
174920
+ throw new Error(`Workspace Code Review remediation repository ${remediation.repository} is not declared for ${task.id}`);
174921
+ }
174922
+ return remediation.repository;
174923
+ }
174924
+ function deriveWorkspaceReviewRemediation(result2) {
174925
+ if (typeof result2.repositoryScopeRevision !== "number") return void 0;
174926
+ const blocking = (result2.repositoryReviewOutcomes ?? []).filter((outcome) => outcome.status === "REVIEWED" && (outcome.verdict === "REVISE" || outcome.verdict === "RETHINK")).sort((left, right) => left.repository.localeCompare(right.repository))[0];
174927
+ if (!blocking) return void 0;
174928
+ const findings = (blocking.findings ?? []).map((finding) => `${finding.id}:${normalize8(finding.title)}:${normalize8(finding.body)}`).sort().join("|");
174929
+ return {
174930
+ scopeRevision: result2.repositoryScopeRevision,
174931
+ repository: blocking.repository,
174932
+ inputSignature: `${result2.workflowStepId}\0${blocking.repository}\0${blocking.fingerprint ?? ""}\0${blocking.verdict}\0${findings}`
174933
+ };
174934
+ }
174935
+ var init_workspace_review_remediation = __esm({
174936
+ "../engine/src/executor/workspace-review-remediation.ts"() {
174937
+ "use strict";
174938
+ }
174939
+ });
174940
+
174869
174941
  // ../engine/src/worktree/worktree-copy-files.ts
174870
174942
  import { constants as fsConstants3 } from "node:fs";
174871
174943
  import { access as access7, copyFile as copyFile2, lstat as lstat2, mkdir as mkdir26 } from "node:fs/promises";
@@ -175252,14 +175324,15 @@ function invalidateWorkspaceConfigCache(owner) {
175252
175324
  workspaceConfigEpochs.set(owner, (workspaceConfigEpochs.get(owner) ?? 0) + 1);
175253
175325
  }
175254
175326
  async function normalizeWorkspaceTaskRouting(store, taskId) {
175255
- const normalize9 = store.normalizeWorkspaceTaskWorktreeMetadata;
175256
- if (typeof normalize9 === "function") return normalize9.call(store, taskId);
175327
+ const normalize10 = store.normalizeWorkspaceTaskWorktreeMetadata;
175328
+ if (typeof normalize10 === "function") return normalize10.call(store, taskId);
175257
175329
  let task = await store.getTask(taskId);
175258
175330
  if (task.worktree || task.branch || task.executionStartBranch || task.baseCommitSha) {
175259
175331
  if (typeof store.updateTask !== "function") return task;
175260
175332
  await store.updateTask(taskId, {
175261
175333
  worktree: null,
175262
175334
  branch: null,
175335
+ branchWriteOrigin: "engine",
175263
175336
  executionStartBranch: null,
175264
175337
  baseCommitSha: null,
175265
175338
  sessionFile: null
@@ -175461,11 +175534,15 @@ async function ensureWorkspaceGroupOwnership(workspaceContext, settings) {
175461
175534
  async function acquireTaskWorktree(opts) {
175462
175535
  const { task, rootDir, store, settings, pool, logger: logger3, audit, runContext, createWorktree: createWorktree2, runConfiguredCommand: runConfiguredCommand2, runInitCommand, taskEnv, secretsStore, workspaceContext } = opts;
175463
175536
  const persistWorktreeAssignment = async (patch) => {
175537
+ const provenancePatch = patch.branch === void 0 ? patch : {
175538
+ ...patch,
175539
+ branchWriteOrigin: patch.branchWriteOrigin ?? (classifyTaskBranchOrigin(task, patch.branch ?? void 0) === "operator-supplied" ? "operator" : "engine")
175540
+ };
175464
175541
  if (!opts.suppressSingularWorktreePersist) {
175465
- await store.updateTask(task.id, patch);
175542
+ await store.updateTask(task.id, provenancePatch);
175466
175543
  return;
175467
175544
  }
175468
- const { worktree: _worktree, branch: _branch, ...nonSingularPatch } = patch;
175545
+ const { worktree: _worktree, branch: _branch, branchWriteOrigin: _origin, ...nonSingularPatch } = provenancePatch;
175469
175546
  if (Object.keys(nonSingularPatch).length > 0) {
175470
175547
  await store.updateTask(task.id, nonSingularPatch);
175471
175548
  }
@@ -175702,7 +175779,22 @@ async function acquireTaskWorktree(opts) {
175702
175779
  }
175703
175780
  worktreePath = created2.path;
175704
175781
  branch = created2.branch;
175705
- await persistWorktreeAssignment({ worktree: created2.path, branch: created2.branch });
175782
+ try {
175783
+ await persistWorktreeAssignment({ worktree: created2.path, branch: created2.branch });
175784
+ } catch (error3) {
175785
+ try {
175786
+ await removeWorktree({
175787
+ worktreePath: created2.path,
175788
+ rootDir,
175789
+ settings,
175790
+ taskId: task.id,
175791
+ reason: RemovalReason.ExecutorDispose
175792
+ });
175793
+ } catch (cleanupError) {
175794
+ logger3?.warn(`${task.id}: failed to compensate unpersisted worktree ${created2.path}: ${formatError(cleanupError)}`);
175795
+ }
175796
+ throw error3;
175797
+ }
175706
175798
  await audit?.git({ type: "worktree:create", target: created2.path, metadata: { branch: created2.branch, source: logOrigin === "return-guard" ? "acquire-return-guard" : void 0 } });
175707
175799
  await audit?.git({ type: "branch:create", target: created2.branch });
175708
175800
  if (created2.branch !== branchName) {
@@ -175944,6 +176036,17 @@ async function acquireTaskWorktree(opts) {
175944
176036
  if (pinned) {
175945
176037
  return acquirePinnedWorktree();
175946
176038
  }
176039
+ if (!workspaceContext && !task.worktree && !(pool && settings.recycleWorktrees)) {
176040
+ const registeredMatches = (await getRegisteredWorktreeBranches(rootDir)).filter((entry) => entry.branch === branchName);
176041
+ if (registeredMatches.length === 1) {
176042
+ const registeredPath = registeredMatches[0].worktreePath;
176043
+ const classification = await classifyTaskWorktree(rootDir, registeredPath);
176044
+ if (classification.ok) {
176045
+ await persistWorktreeAssignment({ worktree: registeredPath, branch: branchName });
176046
+ return reuseWarmWorktree(registeredPath, branchName, "existing");
176047
+ }
176048
+ }
176049
+ }
175947
176050
  if (task.worktree && isResume) {
175948
176051
  if (logger3?.debug) logger3.debug(`Reusing existing worktree: ${worktreePath}`);
175949
176052
  const cleanup = await removeDesktopBuildArtifacts(worktreePath, logger3);
@@ -175979,6 +176082,7 @@ async function acquireTaskWorktree(opts) {
175979
176082
  }
175980
176083
  }
175981
176084
  if (pooled) {
176085
+ let poolAssignmentPersistenceStarted = false;
175982
176086
  try {
175983
176087
  const preparedRaw = await pool.prepareForTask(pooled, branchName, freshStartPoint, {
175984
176088
  allowSiblingBranchRename,
@@ -176036,7 +176140,9 @@ async function acquireTaskWorktree(opts) {
176036
176140
  });
176037
176141
  acquiredFromPool = true;
176038
176142
  logger3?.log(`Acquired worktree from pool: ${worktreePath}`);
176143
+ poolAssignmentPersistenceStarted = true;
176039
176144
  await persistWorktreeAssignment({ worktree: worktreePath, branch });
176145
+ poolAssignmentPersistenceStarted = false;
176040
176146
  await audit?.git({ type: "worktree:reuse", target: worktreePath, metadata: { branch, reclaimed: prepared.reclaimed } });
176041
176147
  if (prepared.reclaimed) {
176042
176148
  await store.logEntry(task.id, `Acquired reclaimed worktree from pool: ${worktreePath} (${prepared.strandedCommitCount ?? 0} commits preserved)`, void 0, runContext);
@@ -176090,6 +176196,10 @@ async function acquireTaskWorktree(opts) {
176090
176196
  });
176091
176197
  }
176092
176198
  } catch (poolErr) {
176199
+ if (poolAssignmentPersistenceStarted) {
176200
+ pool.release(pooled, task.id);
176201
+ throw poolErr;
176202
+ }
176093
176203
  if (poolErr instanceof WorktreeBaseRefreshError) {
176094
176204
  await persistWorktreeAssignment({ worktree: null, branch: null, branchWriteOrigin: "engine", sessionFile: null });
176095
176205
  pool.release(pooled, task.id);
@@ -176401,7 +176511,11 @@ async function acquireWorkspaceTaskWorktrees(opts) {
176401
176511
  current = await opts.store.getTask(opts.task.id);
176402
176512
  }
176403
176513
  const entries = current.workspaceWorktrees ?? {};
176404
- const coordinatorWorktreePath = repoRelPaths.map((repoRelPath) => entries[repoRelPath]?.worktreePath).find((path13) => typeof path13 === "string" && path13.length > 0);
176514
+ const remediationRepository = resolveWorkspaceReviewRemediationRepository(current, repoRelPaths);
176515
+ if (opts.remediationRepository !== remediationRepository) {
176516
+ throw new Error(`Workspace Code Review remediation target changed during acquisition for ${opts.task.id}`);
176517
+ }
176518
+ const coordinatorWorktreePath = remediationRepository ? entries[remediationRepository]?.worktreePath : repoRelPaths.map((repoRelPath) => entries[repoRelPath]?.worktreePath).find((path13) => typeof path13 === "string" && path13.length > 0);
176405
176519
  if (!coordinatorWorktreePath) {
176406
176520
  throw new Error(`Workspace task ${opts.task.id} did not acquire a declared repository worktree`);
176407
176521
  }
@@ -176422,6 +176536,7 @@ var init_worktree_acquisition = __esm({
176422
176536
  init_worktree_backend();
176423
176537
  init_worktrunk_installer();
176424
176538
  init_worktrunk_failure_handler();
176539
+ init_workspace_review_remediation();
176425
176540
  init_secrets_env_writer();
176426
176541
  init_worktree_desktop_artifacts();
176427
176542
  init_worktree_hooks();
@@ -191255,7 +191370,7 @@ var init_agent_memory_index = __esm({
191255
191370
  // ../engine/src/agents/agent-instructions.ts
191256
191371
  import { readFile as readFile42, writeFile as writeFile28, mkdir as mkdir29, access as access9 } from "node:fs/promises";
191257
191372
  import { constants as fsConstants4 } from "node:fs";
191258
- import { isAbsolute as isAbsolute16, resolve as resolve40, relative as relative15, normalize as normalize8, sep as sep12, dirname as dirname31 } from "node:path";
191373
+ import { isAbsolute as isAbsolute16, resolve as resolve40, relative as relative15, normalize as normalize9, sep as sep12, dirname as dirname31 } from "node:path";
191259
191374
  function trimAndClamp(value, maxLength, label, agentId) {
191260
191375
  const trimmed = value.trim();
191261
191376
  if (!trimmed) {
@@ -191289,7 +191404,7 @@ function resolveValidatedMarkdownPath(rawPath, rootDir, agentId, fieldLabel) {
191289
191404
  log26.warn(`${fieldLabel} must be project-relative for agent ${agentId}: ${trimmed}`);
191290
191405
  return null;
191291
191406
  }
191292
- const normalized2 = normalize8(trimmed);
191407
+ const normalized2 = normalize9(trimmed);
191293
191408
  if (isPathTraversal2(normalized2)) {
191294
191409
  log26.warn(`${fieldLabel} traversal is not allowed for agent ${agentId}: ${trimmed}`);
191295
191410
  return null;
@@ -200052,8 +200167,8 @@ async function autoRecoverWorktreeSessionStartFailure(store, task, opts) {
200052
200167
  }
200053
200168
  const workspaceTask = configuredWorkspace || isWorkspaceTask(task);
200054
200169
  if (workspaceTask) {
200055
- const normalize9 = store.normalizeWorkspaceTaskWorktreeMetadata;
200056
- if (typeof normalize9 === "function") task = await normalize9.call(store, task.id);
200170
+ const normalize10 = store.normalizeWorkspaceTaskWorktreeMetadata;
200171
+ if (typeof normalize10 === "function") task = await normalize10.call(store, task.id);
200057
200172
  }
200058
200173
  const resetRetryBudget = opts.resetRetryBudgetOnStaleMetadataClear === true;
200059
200174
  const staleMetadataClearRecoveryRetryCount = opts.staleMetadataClearRecoveryRetryCount;
@@ -200108,7 +200223,13 @@ async function autoRecoverWorktreeSessionStartFailure(store, task, opts) {
200108
200223
  worktreeSessionRetryCount: nextCount,
200109
200224
  ...nextStaleMetadataClearRecoveryCount === void 0 ? {} : { recoveryRetryCount: nextStaleMetadataClearRecoveryCount },
200110
200225
  worktree: clearWorktreeMetadata ? null : staleWorktree,
200111
- branch: nextBranch,
200226
+ /*
200227
+ * FNXC:BranchNaming 2026-08-21-09:36:
200228
+ * Session recovery often changes only worktree/session metadata. Do not reassert an unchanged
200229
+ * branch with engine provenance: that would erase a matching operator override and later make
200230
+ * its branch eligible for engine cleanup. A real canonical clear remains an explicit engine write.
200231
+ */
200232
+ ...(task.branch ?? null) === nextBranch ? {} : { branch: nextBranch, branchWriteOrigin: "engine" },
200112
200233
  sessionFile: null
200113
200234
  });
200114
200235
  await opts.auditor?.database({
@@ -209837,7 +209958,8 @@ async function tryEarlyEmptyOwnDiffFinalize(input) {
209837
209958
  try {
209838
209959
  await store.updateTask(taskId, {
209839
209960
  worktree: worktreeRemoved ? null : task.worktree,
209840
- branch: branchDeleted ? null : task.branch
209961
+ branch: branchDeleted ? null : task.branch,
209962
+ ...branchDeleted ? { branchWriteOrigin: "engine" } : {}
209841
209963
  });
209842
209964
  if (worktreeRemoved) task.worktree = void 0;
209843
209965
  if (branchDeleted) task.branch = void 0;
@@ -210197,7 +210319,7 @@ async function aiMergeTask(store, rootDir, taskId, options = {}) {
210197
210319
  rootDir,
210198
210320
  integrationBranch: mergeTarget.branch
210199
210321
  });
210200
- await store.updateTask(taskId, { worktree: reusableMatch.path, branch: reusableMatch.branch });
210322
+ await store.updateTask(taskId, { worktree: reusableMatch.path, branch: reusableMatch.branch, branchWriteOrigin: "engine" });
210201
210323
  await emitReuseHandoffAuditEvent(
210202
210324
  "merge:reuse-fallback-reused-existing-registration",
210203
210325
  {
@@ -217757,7 +217879,7 @@ var init_self_healing = __esm({
217757
217879
  } catch {
217758
217880
  }
217759
217881
  }
217760
- const patch = preservedWorktree ? { branch: selected.branch } : { branch: selected.branch, worktree: null };
217882
+ const patch = preservedWorktree ? { branch: selected.branch, branchWriteOrigin: "engine" } : { branch: selected.branch, branchWriteOrigin: "engine", worktree: null };
217761
217883
  if (!task.baseCommitSha) {
217762
217884
  const derivedBaseCommit = (await execAsync18(
217763
217885
  `git merge-base ${shellQuote2(integrationBase)} ${shellQuote2(selected.branch)}`,
@@ -221434,6 +221556,12 @@ var init_self_healing = __esm({
221434
221556
  await this.emitWorkspacePartialLandNoAction(task, "merge-pending", liveness.livePaths);
221435
221557
  continue;
221436
221558
  }
221559
+ const latestTask = await this.store.getTask(task.id).catch(() => null);
221560
+ const blockerTask = latestTask?.status === "failed" && latestTask.error?.startsWith("Workspace partial land:") ? { ...latestTask, status: null, error: void 0 } : latestTask;
221561
+ if (!blockerTask || getTaskMergeBlocker(blockerTask, { skipColumnIdentityCheck: true }) !== void 0) {
221562
+ await this.emitWorkspacePartialLandNoAction(task, "merge-blocked", []);
221563
+ continue;
221564
+ }
221437
221565
  const workspaceWorktrees = task.workspaceWorktrees ?? {};
221438
221566
  const explicitScope = task.repositoryScope?.state === "confirmed" ? task.repositoryScope.repositories : void 0;
221439
221567
  if (!explicitScope) {
@@ -221441,8 +221569,12 @@ var init_self_healing = __esm({
221441
221569
  continue;
221442
221570
  }
221443
221571
  const repoKeys = Object.keys(workspaceWorktrees).filter(
221444
- (repoRel) => explicitScope.includes(repoRel) && (task.modifiedFiles ?? []).some((file) => file.startsWith(`${repoRel}/`))
221572
+ (repoRel) => explicitScope.includes(repoRel) && ((task.modifiedFiles ?? []).some((file) => file.startsWith(`${repoRel}/`)) || Boolean(workspaceWorktrees[repoRel]?.landedSha))
221445
221573
  );
221574
+ if (repoKeys.length === 0) {
221575
+ await this.emitWorkspacePartialLandNoAction(task, "empty-obligations", []);
221576
+ continue;
221577
+ }
221446
221578
  const landedRepos = [];
221447
221579
  const unlandedRepos = [];
221448
221580
  const unrecoverableRepos = [];
@@ -221499,13 +221631,12 @@ var init_self_healing = __esm({
221499
221631
  phase: "reconcile-workspace-partial-land"
221500
221632
  });
221501
221633
  if (evidenceUnavailableRepos.length > 0) {
221502
- const defers = (this.workspacePartialLandEvidenceDefers.get(task.id) ?? 0) + 1;
221503
- this.workspacePartialLandEvidenceDefers.set(task.id, defers);
221634
+ const defers = (task.mergeTransientRetryCount ?? 0) + 1;
221635
+ await this.store.updateTask(task.id, { mergeTransientRetryCount: defers });
221504
221636
  if (defers >= MAX_STARVATION_DROPS) {
221505
221637
  const error3 = `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.`;
221506
221638
  await this.store.updateTask(task.id, { status: "failed", error: error3 });
221507
221639
  await this.store.logEntry(task.id, error3);
221508
- this.workspacePartialLandEvidenceDefers.delete(task.id);
221509
221640
  await auditor.database({
221510
221641
  type: "task:reconcile-workspace-partial-land",
221511
221642
  target: task.id,
@@ -221518,7 +221649,6 @@ var init_self_healing = __esm({
221518
221649
  }
221519
221650
  continue;
221520
221651
  }
221521
- this.workspacePartialLandEvidenceDefers.delete(task.id);
221522
221652
  if (unrecoverableRepos.length > 0) {
221523
221653
  const missingBranches = unrecoverableRepos.map((repoRel) => workspaceWorktrees[repoRel]?.branch ?? resolveTaskWorkingBranch(task)).join(", ");
221524
221654
  const error3 = `Workspace partial-land unrecoverable: sub-repo(s) ${unrecoverableRepos.join(", ")} have no branch (${missingBranches}) and no landedSha \u2014 manual intervention required.`;
@@ -221546,7 +221676,7 @@ var init_self_healing = __esm({
221546
221676
  landedRepos,
221547
221677
  unlandedRepos: [],
221548
221678
  reason: "all-landed-not-finalized",
221549
- successLog: "Auto-recovered (workspace): all sub-repos landed but task not finalized \u2014 re-enqueued finalize-once"
221679
+ successLog: "Workspace merge recovery scheduled: all sub-repositories have proven landing evidence; awaiting finalize-once result"
221550
221680
  });
221551
221681
  recovered++;
221552
221682
  continue;
@@ -221555,7 +221685,7 @@ var init_self_healing = __esm({
221555
221685
  landedRepos,
221556
221686
  unlandedRepos,
221557
221687
  reason: landedRepos.length > 0 ? "partial-land" : "zero-land",
221558
- successLog: `Auto-recovered (workspace): re-enqueued partial land (${landedRepos.length} landed, ${unlandedRepos.length} pending)`
221688
+ successLog: `Workspace merge recovery scheduled: ${landedRepos.length} landed, ${unlandedRepos.length} pending`
221559
221689
  });
221560
221690
  recovered++;
221561
221691
  } catch (err) {
@@ -221620,7 +221750,8 @@ var init_self_healing = __esm({
221620
221750
  }).catch(() => void 0);
221621
221751
  return false;
221622
221752
  }
221623
- const drops = (this.workspacePartialLandDrops.get(task.id) ?? 0) + 1;
221753
+ const drops = (task.mergeTransientRetryCount ?? 0) + 1;
221754
+ await this.store.updateTask(task.id, { mergeTransientRetryCount: drops });
221624
221755
  this.workspacePartialLandDrops.set(task.id, drops);
221625
221756
  log34.warn(`reconcileWorkspacePartialLands: enqueue dropped for ${task.id} (${drops}/${MAX_STARVATION_DROPS}); merge queue rejected re-enqueue`);
221626
221757
  if (drops >= MAX_STARVATION_DROPS) {
@@ -231216,7 +231347,7 @@ function recoverPassedPlanReviewFromLatestLog(task) {
231216
231347
  completedAt: latest.timestamp
231217
231348
  };
231218
231349
  }
231219
- var PLAN_REVIEW_PROVIDER_FAILURE_HOLD_VALUE, PLAN_REVIEW_LEASE_HELD_VALUE, SESSION_CONTENTION_HOLD_VALUE, WORKFLOW_INTERRUPTED_NODE_ID_CONTEXT_KEY, WORKFLOW_INTERRUPTED_NODE_ABORT_KIND_CONTEXT_KEY, WORKFLOW_OPTIONAL_GROUP_CONTEXT_KEY, WORKFLOW_REVIEW_KIND_CONTEXT_KEY, WORKFLOW_NODE_ENGINE_PAUSE_ABORT_KIND, WORKFLOW_DRIFT_PARK_CONTEXT_KEY, WorkflowGraphSuspended, MERGE_REGION_KINDS, WorkflowGraphExecutor;
231350
+ var PLAN_REVIEW_PROVIDER_FAILURE_HOLD_VALUE, BRANCH_WRITE_PROVENANCE_FAILURE_VALUE, PLAN_REVIEW_LEASE_HELD_VALUE, SESSION_CONTENTION_HOLD_VALUE, WORKFLOW_INTERRUPTED_NODE_ID_CONTEXT_KEY, WORKFLOW_INTERRUPTED_NODE_ABORT_KIND_CONTEXT_KEY, WORKFLOW_OPTIONAL_GROUP_CONTEXT_KEY, WORKFLOW_REVIEW_KIND_CONTEXT_KEY, WORKFLOW_NODE_ENGINE_PAUSE_ABORT_KIND, WORKFLOW_DRIFT_PARK_CONTEXT_KEY, WorkflowGraphSuspended, MERGE_REGION_KINDS, WorkflowGraphExecutor;
231220
231351
  var init_workflow_graph_executor = __esm({
231221
231352
  "../engine/src/workflows/workflow-graph-executor.ts"() {
231222
231353
  "use strict";
@@ -231229,8 +231360,10 @@ var init_workflow_graph_executor = __esm({
231229
231360
  init_workflow_graph_foreach();
231230
231361
  init_workflow_graph_loop();
231231
231362
  init_workflow_node_execution_needs();
231363
+ init_src2();
231232
231364
  init_worktree_acquisition();
231233
231365
  PLAN_REVIEW_PROVIDER_FAILURE_HOLD_VALUE = "plan-review-provider-failure-hold";
231366
+ BRANCH_WRITE_PROVENANCE_FAILURE_VALUE = "branch-write-provenance-failure";
231234
231367
  PLAN_REVIEW_LEASE_HELD_VALUE = "plan-review-lease-held";
231235
231368
  SESSION_CONTENTION_HOLD_VALUE = "session-contention-hold";
231236
231369
  WORKFLOW_INTERRUPTED_NODE_ID_CONTEXT_KEY = "workflow:interruptedNodeId";
@@ -232147,7 +232280,7 @@ var init_workflow_graph_executor = __esm({
232147
232280
  const failureResult = {
232148
232281
  outcome: "failure",
232149
232282
  // FNXC:SessionContention 2026-07-25-21:30: contention is a retryable hold, not an exception.
232150
- value: isSessionContentionError(lastErrorText) ? SESSION_CONTENTION_HOLD_VALUE : "exception",
232283
+ value: lastError instanceof BranchWriteProvenanceError ? BRANCH_WRITE_PROVENANCE_FAILURE_VALUE : isSessionContentionError(lastErrorText) ? SESSION_CONTENTION_HOLD_VALUE : "exception",
232151
232284
  contextPatch: {
232152
232285
  [`node:${node.id}:error`]: lastErrorText
232153
232286
  }
@@ -236235,6 +236368,7 @@ async function handleBranchConflict(deps, task, error3) {
236235
236368
  status: "failed",
236236
236369
  error: conflictMessage,
236237
236370
  branch: error3.branchName,
236371
+ branchWriteOrigin: "engine",
236238
236372
  worktree: error3.conflictingWorktreePath,
236239
236373
  paused: true,
236240
236374
  pausedReason: "branch-conflict-unrecoverable"
@@ -236290,8 +236424,8 @@ async function recoverMissingWorktreeSessionStartFailure(deps, task, worktreePat
236290
236424
  }
236291
236425
  let recoveryTask = task;
236292
236426
  if (workspaceTask) {
236293
- const normalize9 = deps.store.normalizeWorkspaceTaskWorktreeMetadata;
236294
- if (typeof normalize9 === "function") recoveryTask = await normalize9.call(deps.store, task.id);
236427
+ const normalize10 = deps.store.normalizeWorkspaceTaskWorktreeMetadata;
236428
+ if (typeof normalize10 === "function") recoveryTask = await normalize10.call(deps.store, task.id);
236295
236429
  }
236296
236430
  if (!workspaceTask && isInsideWorktreesDir(deps.rootDir, staleWorktreePath)) {
236297
236431
  try {
@@ -236709,8 +236843,21 @@ async function detectWorkspaceMainCheckoutWork(deps, task, repos, declaredScope)
236709
236843
  const { stdout } = await execAsync41("git log -n 200 --format=%H%x1f%ct%x1f%B%x1e HEAD", { ...probeOptions, cwd: checkout });
236710
236844
  const attributed = new RegExp(`(?:${task.id.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}|Fusion-Task-Id:\\s*${task.id.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")})`, "i");
236711
236845
  for (const commit of parseCommits(stdout)) {
236712
- const evidence = attributed.test(commit.body) ? "task-attributed-commit" : anchor !== null && commit.committedAt >= anchor ? "post-anchor-commit" : null;
236713
- if (evidence) violations.push({ repo, files: [], commits: [commit.sha], evidence });
236846
+ const entry = workspaceWorktrees[repo];
236847
+ const recordedLanding = entry?.landedSha === commit.sha || task.mergeDetails?.workspaceLandedShas?.[repo] === commit.sha || task.mergeDetails?.commitSha === commit.sha;
236848
+ let reachableFromBaseline = false;
236849
+ if (entry?.baseCommitSha) {
236850
+ try {
236851
+ await execAsync41(`git merge-base --is-ancestor ${commit.sha} ${entry.baseCommitSha}`, { ...probeOptions, cwd: checkout });
236852
+ reachableFromBaseline = true;
236853
+ } catch {
236854
+ }
236855
+ }
236856
+ if (attributed.test(commit.body) && !recordedLanding && !reachableFromBaseline && anchor !== null && commit.committedAt >= anchor) {
236857
+ violations.push({ repo, files: [], commits: [commit.sha], evidence: "task-attributed-commit" });
236858
+ } else if (!recordedLanding && !reachableFromBaseline && anchor !== null && commit.committedAt >= anchor) {
236859
+ warnings.push({ repo, files: [], commits: [commit.sha], reason: "pre-existing-dirt" });
236860
+ }
236714
236861
  }
236715
236862
  } catch {
236716
236863
  warnings.push({ repo, files: [], commits: [], reason: "commit-scan-unavailable" });
@@ -236790,7 +236937,7 @@ async function verifyWorktreeInvariants(deps, task, worktreePathOverride, allowR
236790
236937
  reason: "main_checkout_edit",
236791
236938
  repo: firstMainCheckoutViolation.repo,
236792
236939
  observed: `${firstMainCheckoutViolation.evidence}: ${observed}`,
236793
- 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`
236940
+ 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`
236794
236941
  };
236795
236942
  }
236796
236943
  const zeroAcquire = classifyWorkspaceZeroAcquire(task, {
@@ -238273,7 +238420,7 @@ var init_workflow_step_failure_injection = __esm({
238273
238420
  });
238274
238421
 
238275
238422
  // ../engine/src/executor/send-task-back-for-fix.ts
238276
- async function sendTaskBackForFix(deps, task, worktreePath, failureFeedback, stepName, reason, preserveResumeState = true, mergeVerificationFailure = false, retryPresentation, findings) {
238423
+ async function sendTaskBackForFix(deps, task, worktreePath, failureFeedback, stepName, reason, preserveResumeState = true, mergeVerificationFailure = false, retryPresentation, findings, persistWorktreePath) {
238277
238424
  const taskId = task.id;
238278
238425
  deps.clearCompletedTaskWatchdog(taskId);
238279
238426
  const { task: authoritativeRemediationTask, route: externalExecutionRoute } = await resolveAuthoritativeExternalExecutionRoute(deps.store, task);
@@ -238316,7 +238463,7 @@ Please fix the issues so the verification can pass on the next attempt.`,
238316
238463
  remediationWorktreePath,
238317
238464
  `${taskId}: sent back to in-progress for remediation`,
238318
238465
  preserveResumeState,
238319
- !externalExecutionRoute.configured
238466
+ persistWorktreePath ?? !externalExecutionRoute.configured
238320
238467
  );
238321
238468
  }
238322
238469
  var init_send_task_back_for_fix = __esm({
@@ -243412,6 +243559,7 @@ async function ensureGraphCustomNodeWorktree(deps, task, settings, nodeId, refre
243412
243559
  deps.getRunContextFor(task.id)
243413
243560
  );
243414
243561
  if (workspaceConfig) {
243562
+ const remediationRepository = resolveWorkspaceReviewRemediationRepository(task, workspaceConfig.repos);
243415
243563
  const workspace = await acquireWorkspaceTaskWorktrees({
243416
243564
  workspaceConfig,
243417
243565
  workspaceRootDir: deps.rootDir,
@@ -243436,7 +243584,8 @@ async function ensureGraphCustomNodeWorktree(deps, task, settings, nodeId, refre
243436
243584
  return result2;
243437
243585
  }),
243438
243586
  taskEnv: process.env,
243439
- addActiveWorktree: deps.addActiveWorktree
243587
+ addActiveWorktree: deps.addActiveWorktree,
243588
+ remediationRepository
243440
243589
  });
243441
243590
  deps.onStart?.(workspace.task, workspace.coordinatorWorktreePath);
243442
243591
  executorLog.debug(`${task.id}: workflow node '${nodeId}' using workspace coordinator ${workspace.coordinatorWorktreePath}`);
@@ -243500,6 +243649,7 @@ var init_ensure_graph_custom_node_worktree = __esm({
243500
243649
  init_worktree_git_refs();
243501
243650
  init_task_predicates();
243502
243651
  init_workspace_config_resolver();
243652
+ init_workspace_review_remediation();
243503
243653
  }
243504
243654
  });
243505
243655
 
@@ -244088,7 +244238,22 @@ ${info.feedback}`,
244088
244238
  return false;
244089
244239
  }
244090
244240
  const revisionKey = optionalStepRevisionKey2(info.nodeId, info.stepName);
244091
- if (hasRepeatedUnchangedCodeReview(liveTask, info)) {
244241
+ const reviewResult = (liveTask.workflowStepResults ?? []).find(
244242
+ (result2) => (result2.workflowStepId === info.nodeId || result2.workflowStepName === info.stepName) && result2.verdict === "REVISE"
244243
+ );
244244
+ const derivedRemediation = reviewResult ? deriveWorkspaceReviewRemediation(reviewResult) : void 0;
244245
+ const remediation = derivedRemediation && liveTask.workspaceWorktrees ? derivedRemediation : void 0;
244246
+ const priorRemediation = liveTask.repositoryScope?.reviewRemediation;
244247
+ const hasDurableRepeatedWorkspaceReview = remediation !== void 0 && priorRemediation?.scopeRevision === remediation.scopeRevision && priorRemediation.repository === remediation.repository && priorRemediation.inputSignature === remediation.inputSignature;
244248
+ const updateWorkspaceReviewState = deps.store.updateWorkspaceReviewState;
244249
+ if (remediation && !hasDurableRepeatedWorkspaceReview && updateWorkspaceReviewState) {
244250
+ const persisted = await updateWorkspaceReviewState.call(deps.store, taskId, remediation.scopeRevision, remediation);
244251
+ if (!persisted.updated) {
244252
+ executorLog.warn(`${taskId}: workspace Code Review remediation was superseded by a repository scope change.`);
244253
+ return false;
244254
+ }
244255
+ }
244256
+ if (hasDurableRepeatedWorkspaceReview || (!remediation || !updateWorkspaceReviewState) && hasRepeatedUnchangedCodeReview(liveTask, info)) {
244092
244257
  const runContext = deps.getRunContextFor(taskId);
244093
244258
  await deps.store.logEntry(
244094
244259
  taskId,
@@ -244136,9 +244301,18 @@ Feedback:
244136
244301
  ${info.feedback}`, revisionKey),
244137
244302
  deps.getRunContextFor(taskId)
244138
244303
  );
244139
- await deps.sendTaskBackForFix(
244304
+ const remediationWorktreePath = remediation ? liveTask.workspaceWorktrees?.[remediation.repository]?.worktreePath : liveTask.worktree;
244305
+ if (remediation && !remediationWorktreePath) {
244306
+ await deps.store.updateTask(taskId, {
244307
+ status: "awaiting-approval",
244308
+ awaitingApprovalReason: "code-review-non-convergence",
244309
+ error: `Workspace Code Review remediation target ${remediation.repository} has no acquired worktree.`
244310
+ });
244311
+ return false;
244312
+ }
244313
+ const sendArgs = [
244140
244314
  liveTask,
244141
- liveTask.worktree ?? "",
244315
+ remediationWorktreePath ?? "",
244142
244316
  info.feedback,
244143
244317
  info.stepName,
244144
244318
  `Pre-merge optional workflow step "${info.stepName}" requested revision`,
@@ -244146,7 +244320,12 @@ ${info.feedback}`, revisionKey),
244146
244320
  false,
244147
244321
  { attempt: nextCount, max: budget.unbounded ? void 0 : budget.max },
244148
244322
  info.findings
244149
- );
244323
+ ];
244324
+ if (remediation) {
244325
+ await deps.sendTaskBackForFix(...sendArgs, false);
244326
+ } else {
244327
+ await deps.sendTaskBackForFix(...sendArgs);
244328
+ }
244150
244329
  return true;
244151
244330
  }
244152
244331
  var init_request_pre_merge_optional_step_fix = __esm({
@@ -244161,6 +244340,7 @@ var init_request_pre_merge_optional_step_fix = __esm({
244161
244340
  init_plan_review_feedback_history();
244162
244341
  init_logger3();
244163
244342
  init_emit_bounded_run_audit3();
244343
+ init_workspace_review_remediation();
244164
244344
  }
244165
244345
  });
244166
244346
 
@@ -245049,6 +245229,14 @@ function createTaskDoneTool(deps, taskId, worktreePath, promptContent, codeRevie
245049
245229
  const refusalMessage = `fn_task_done refused: ${invariantCheck.reason} \u2014 observed=${invariantCheck.observed}, expected=${invariantCheck.expected}`;
245050
245230
  await store.logEntry(taskId, refusalMessage, void 0, deps.getRunContextFor(task.id));
245051
245231
  executorLog.error(`${taskId}: fn_task_done refused (${invariantCheck.reason}) \u2014 observed=${invariantCheck.observed}, expected=${invariantCheck.expected}`);
245232
+ if (invariantCheck.reason === "main_checkout_edit") {
245233
+ await store.updateTask(taskId, { status: "failed", error: refusalMessage });
245234
+ await store.logEntry(taskId, `${refusalMessage} \u2014 operator cleanup and Retry are required; executor retry budget preserved`, void 0, deps.getRunContextFor(task.id));
245235
+ return {
245236
+ content: [{ type: "text", text: refusalMessage }],
245237
+ details: { error: refusalMessage }
245238
+ };
245239
+ }
245052
245240
  const priorRequeues = task.taskDoneRetryCount ?? 0;
245053
245241
  const nextRequeueCount = priorRequeues + 1;
245054
245242
  if (priorRequeues < MAX_TASK_DONE_REQUEUE_RETRIES) {
@@ -246881,7 +247069,9 @@ Repository scope (task-level; review this plan once): ${detail.repositoryScope?.
246881
247069
  return {
246882
247070
  repositoryScope: {
246883
247071
  ...currentScope,
246884
- reviewEvidence: Object.fromEntries(Object.entries(review.repositoryDiffFingerprints).map(([repo, fingerprint]) => [repo, { fingerprint, approvedAt }]))
247072
+ reviewEvidence: Object.fromEntries(Object.entries(review.repositoryDiffFingerprints).map(([repo, fingerprint]) => [repo, { fingerprint, approvedAt }])),
247073
+ // FNXC:WorkspaceFinalization 2026-08-21-09:50: An APPROVE for this fenced scope ends its REVISE episode; retaining its target would misroute later acquisition.
247074
+ ...currentScope.reviewRemediation?.scopeRevision === review.repositoryScopeRevision ? { reviewRemediation: void 0 } : {}
246885
247075
  }
246886
247076
  };
246887
247077
  });
@@ -248286,7 +248476,8 @@ async function runGraphCustomNode(deps, node, nodeTask, settings, columnBinding,
248286
248476
  executionTarget = await deps.ensureGraphCustomNodeWorktree(acquisitionTask, settings, node.id);
248287
248477
  }
248288
248478
  }
248289
- const workspaceCoordinator = workspaceConfig ? Object.keys(executionTarget.workspaceWorktrees ?? {}).filter((repoRelPath) => workspaceConfig.repos.includes(repoRelPath)).sort().map((repoRelPath) => executionTarget.workspaceWorktrees?.[repoRelPath]?.worktreePath).find((path13) => typeof path13 === "string" && path13.length > 0) : void 0;
248479
+ const remediationRepository = workspaceConfig ? resolveWorkspaceReviewRemediationRepository(executionTarget, workspaceConfig.repos) : void 0;
248480
+ 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((path13) => typeof path13 === "string" && path13.length > 0) : void 0;
248290
248481
  if (!executionTarget.worktree && !workspaceCoordinator) {
248291
248482
  return { outcome: "failure", value: "no-worktree-for-write-node" };
248292
248483
  }
@@ -248446,7 +248637,9 @@ ${prompt}`;
248446
248637
  return {
248447
248638
  repositoryScope: {
248448
248639
  ...currentScope,
248449
- reviewEvidence: Object.fromEntries(Object.entries(aggregate.repositoryDiffFingerprints).map(([repo, fingerprint]) => [repo, { fingerprint, approvedAt }]))
248640
+ reviewEvidence: Object.fromEntries(Object.entries(aggregate.repositoryDiffFingerprints).map(([repo, fingerprint]) => [repo, { fingerprint, approvedAt }])),
248641
+ // FNXC:WorkspaceFinalization 2026-08-21-09:50: A current-scope APPROVE clears the durable REVISE coordinator before graph completion can schedule another run.
248642
+ ...currentScope.reviewRemediation?.scopeRevision === aggregate.repositoryScopeRevision ? { reviewRemediation: void 0 } : {}
248450
248643
  }
248451
248644
  };
248452
248645
  });
@@ -248544,6 +248737,7 @@ var init_run_graph_custom_node = __esm({
248544
248737
  "use strict";
248545
248738
  init_src2();
248546
248739
  init_logger3();
248740
+ init_workspace_review_remediation();
248547
248741
  init_workflow_graph_executor();
248548
248742
  init_workflow_node_execution_needs();
248549
248743
  init_workflow_step_verdict();
@@ -248599,6 +248793,13 @@ async function handleGraphFailure(deps, task, result2) {
248599
248793
  await deps.persistTokenUsage(task.id);
248600
248794
  return;
248601
248795
  }
248796
+ if (graphFailureValue(result2) === BRANCH_WRITE_PROVENANCE_FAILURE_VALUE) {
248797
+ const diagnostic = graphFailureErrorTexts(result2).find((message2) => message2.includes("branchWriteOrigin is required when branch is provided")) ?? "branchWriteOrigin is required when branch is provided";
248798
+ await deps.store.logEntry(task.id, diagnostic, void 0, deps.getRunContextFor(task.id));
248799
+ await deps.store.updateTask(task.id, { status: "failed", error: diagnostic }, deps.getRunContextFor(task.id));
248800
+ await deps.persistTokenUsage(task.id);
248801
+ return;
248802
+ }
248602
248803
  if (isSessionContentionGraphFailure(result2)) {
248603
248804
  await deps.holdForSessionContention(task, live, result2);
248604
248805
  await deps.persistTokenUsage(task.id);
@@ -251336,6 +251537,7 @@ async function runImplementation(deps, task, graphCompletion, reportImplementati
251336
251537
  deps.registerConfiguredCommandController(task.id, taskCommandAbortController);
251337
251538
  let workspaceCoordinatorWorktree;
251338
251539
  if (hasWorkspaceRepos && deps.workspaceConfig) {
251540
+ const remediationRepository = resolveWorkspaceReviewRemediationRepository(task, deps.workspaceConfig.repos);
251339
251541
  const workspace = await acquireWorkspaceTaskWorktrees({
251340
251542
  workspaceConfig: deps.workspaceConfig,
251341
251543
  workspaceRootDir: deps.rootDir,
@@ -251360,7 +251562,8 @@ async function runImplementation(deps, task, graphCompletion, reportImplementati
251360
251562
  return result2;
251361
251563
  }),
251362
251564
  taskEnv,
251363
- addActiveWorktree: deps.addActiveWorktree
251565
+ addActiveWorktree: deps.addActiveWorktree,
251566
+ remediationRepository
251364
251567
  });
251365
251568
  task = workspace.task;
251366
251569
  workspaceCoordinatorWorktree = workspace.coordinatorWorktreePath;
@@ -253920,6 +254123,7 @@ var init_run_implementation = __esm({
253920
254123
  init_run_audit();
253921
254124
  init_emit_bounded_run_audit3();
253922
254125
  init_worktree_acquisition();
254126
+ init_workspace_review_remediation();
253923
254127
  init_worktree_paths();
253924
254128
  init_worktree_pool();
253925
254129
  execAsync52 = promisify75(exec56);
@@ -262827,6 +263031,40 @@ var init_overseer_noop_finalize_veto = __esm({
262827
263031
  }
262828
263032
  });
262829
263033
 
263034
+ // ../engine/src/merge/workspace-merge-readiness.ts
263035
+ function resolveWorkspaceMergeReadiness(task, freshModifiedRepositories, netZeroBranchRepositories) {
263036
+ const scope = task.repositoryScope;
263037
+ if (scope?.state !== "confirmed") {
263038
+ return { kind: "blocked", reason: `Workspace repository scope is unresolved for ${task.id}; operator confirmation is required before landing` };
263039
+ }
263040
+ const entries = task.workspaceWorktrees ?? {};
263041
+ const declaredRepositories = scope.repositories.map((repository) => repository.trim());
263042
+ if (declaredRepositories.some((repository) => !repository) || new Set(declaredRepositories).size !== declaredRepositories.length) {
263043
+ return { kind: "blocked", reason: `Workspace repository scope has duplicate or invalid declarations for ${task.id}; operator repair is required` };
263044
+ }
263045
+ const worktreePaths = Object.values(entries).map((entry) => entry.worktreePath?.trim()).filter((path13) => Boolean(path13));
263046
+ if (new Set(worktreePaths).size !== worktreePaths.length) {
263047
+ return { kind: "blocked", reason: `Workspace merge has duplicate worktree paths for ${task.id}; operator repair is required` };
263048
+ }
263049
+ const repositories = declaredRepositories.filter((repo) => Boolean(entries[repo])).filter((repo) => freshModifiedRepositories.has(repo) || netZeroBranchRepositories.has(repo) || Boolean(entries[repo]?.landedSha)).sort();
263050
+ const preservedFiles = [...new Set(task.modifiedFiles ?? [])].sort();
263051
+ if (repositories.length > 0) return { kind: "ready", repositories, preservedFiles };
263052
+ if (task.noCommitsExpected === true && scope.repositories.length === 0) {
263053
+ return { kind: "no-op", repositories: [], preservedFiles: [] };
263054
+ }
263055
+ const declared = [...declaredRepositories].sort();
263056
+ const knownEntries = declared.filter((repo) => entries[repo] !== void 0);
263057
+ if (knownEntries.length === 0) {
263058
+ return { kind: "blocked", reason: `Workspace merge has no acquired repositories matching confirmed scope for ${task.id}; operator repair is required` };
263059
+ }
263060
+ return { kind: "blocked", reason: `Workspace merge has no evidenced landing obligations for ${task.id}; operator repair is required` };
263061
+ }
263062
+ var init_workspace_merge_readiness = __esm({
263063
+ "../engine/src/merge/workspace-merge-readiness.ts"() {
263064
+ "use strict";
263065
+ }
263066
+ });
263067
+
262830
263068
  // ../engine/src/merge/workspace-fence-ref.ts
262831
263069
  import { execFile as execFile32 } from "node:child_process";
262832
263070
  import { createHash as createHash33 } from "node:crypto";
@@ -264591,8 +264829,21 @@ async function landWorkspaceTask(store, task, workspaceRootDir, options = {}, de
264591
264829
  const taskTitle = task.title?.trim() ? task.title.split("\n")[0] : void 0;
264592
264830
  const liveMergeBoundaryTask = await store.getTask(taskId).catch(() => void 0);
264593
264831
  const mergeBoundaryTask = liveMergeBoundaryTask?.workspaceWorktrees ? liveMergeBoundaryTask : task;
264832
+ const reviewColumns = /* @__PURE__ */ new Set(["in-review"]);
264833
+ const workflowIr = await resolveWorkflowIrForTask(store, taskId).catch(() => void 0);
264834
+ if (workflowIr) for (const column of resolveReviewColumns(workflowIr)) reviewColumns.add(column);
264835
+ const completeColumns = /* @__PURE__ */ new Set(["done"]);
264836
+ if (workflowIr) for (const column of resolveTerminalColumns(workflowIr)) completeColumns.add(column);
264837
+ if (!completeColumns.has(mergeBoundaryTask.column)) {
264838
+ const mergeBlocker = getTaskMergeBlocker(mergeBoundaryTask, {
264839
+ manual: options.manual === true,
264840
+ reviewColumns
264841
+ });
264842
+ if (mergeBlocker) throw new WorkspaceFinalizeBlockedError(taskId, mergeBlocker);
264843
+ }
264594
264844
  const confirmedScope = mergeBoundaryTask.repositoryScope?.state === "confirmed" ? new Set(mergeBoundaryTask.repositoryScope.repositories) : void 0;
264595
264845
  const mergeBoundaryModifiedFiles = [];
264846
+ const mergeBoundaryModifiedRepositories = /* @__PURE__ */ new Set();
264596
264847
  const mergeBoundaryFingerprints = {};
264597
264848
  const modifiedOutOfScopeRepositories = /* @__PURE__ */ new Set();
264598
264849
  const netZeroBranchRepositories = /* @__PURE__ */ new Set();
@@ -264609,6 +264860,7 @@ async function landWorkspaceTask(store, task, workspaceRootDir, options = {}, de
264609
264860
  if (files.length > 0 && !confirmedScope?.has(repoRel)) {
264610
264861
  modifiedOutOfScopeRepositories.add(repoRel);
264611
264862
  } else if (confirmedScope?.has(repoRel)) {
264863
+ if (files.length > 0) mergeBoundaryModifiedRepositories.add(repoRel);
264612
264864
  mergeBoundaryModifiedFiles.push(...files.map((file) => `${repoRel}/${file}`));
264613
264865
  if (files.length === 0) {
264614
264866
  const { stdout: aheadCount } = await execFileAsync28("git", ["rev-list", "--count", `HEAD..${entry.branch}`], { cwd: entry.worktreePath, encoding: "utf8" });
@@ -264624,19 +264876,26 @@ async function landWorkspaceTask(store, task, workspaceRootDir, options = {}, de
264624
264876
  }
264625
264877
  const normalizedMergeBoundaryFiles = [...new Set(mergeBoundaryModifiedFiles)].sort();
264626
264878
  const persistedReviewFiles = [...new Set(mergeBoundaryTask.modifiedFiles ?? [])].sort();
264627
- const approvedReviewEvidence = mergeBoundaryTask.repositoryScope?.reviewEvidence ?? {};
264628
- const missingOrStaleReviewApproval = Object.entries(mergeBoundaryFingerprints).some(([repoRel, fingerprint]) => approvedReviewEvidence[repoRel]?.fingerprint !== fingerprint);
264629
- if (normalizedMergeBoundaryFiles.some((file) => !persistedReviewFiles.includes(file)) || missingOrStaleReviewApproval) {
264879
+ const approvedReviewEvidence = mergeBoundaryTask.repositoryScope?.reviewEvidence;
264880
+ const requiresRepositoryReviewEvidence = approvedReviewEvidence !== void 0 || (mergeBoundaryTask.enabledWorkflowSteps ?? []).some((step) => /review/i.test(step));
264881
+ const missingOrStaleReviewApproval = requiresRepositoryReviewEvidence && Object.entries(mergeBoundaryFingerprints).some(([repoRel, fingerprint]) => approvedReviewEvidence?.[repoRel]?.fingerprint !== fingerprint);
264882
+ if (requiresRepositoryReviewEvidence && normalizedMergeBoundaryFiles.some((file) => !persistedReviewFiles.includes(file)) || missingOrStaleReviewApproval) {
264630
264883
  throw new Error(`Workspace merge evidence changed after review for ${taskId}; return the task to Code Review before landing`);
264631
264884
  }
264632
- await store.updateTask(taskId, { modifiedFiles: normalizedMergeBoundaryFiles });
264633
- task = { ...mergeBoundaryTask, modifiedFiles: normalizedMergeBoundaryFiles };
264634
- const workspaceWorktrees = task.workspaceWorktrees ?? {};
264635
- const explicitScope = task.repositoryScope?.state === "confirmed" ? task.repositoryScope.repositories : void 0;
264636
- if (!explicitScope) {
264637
- throw new Error(`Workspace repository scope is unresolved for ${taskId}; operator confirmation is required before landing`);
264885
+ const readiness = resolveWorkspaceMergeReadiness(
264886
+ mergeBoundaryTask,
264887
+ mergeBoundaryModifiedRepositories,
264888
+ netZeroBranchRepositories
264889
+ );
264890
+ if (readiness.kind === "blocked") throw new Error(readiness.reason);
264891
+ if (readiness.kind === "no-op") {
264892
+ throw new Error(`Workspace task ${taskId} has explicit no-commits policy and cannot enter repository landing`);
264638
264893
  }
264639
- const repoKeys = Object.keys(workspaceWorktrees).filter((repoRel) => explicitScope.includes(repoRel) && ((task.modifiedFiles ?? []).some((file) => file.startsWith(`${repoRel}/`)) || netZeroBranchRepositories.has(repoRel))).sort();
264894
+ const retainedModifiedFiles = [.../* @__PURE__ */ new Set([...normalizedMergeBoundaryFiles, ...readiness.preservedFiles])].sort();
264895
+ await store.updateTask(taskId, { modifiedFiles: retainedModifiedFiles });
264896
+ task = { ...mergeBoundaryTask, modifiedFiles: retainedModifiedFiles };
264897
+ const workspaceWorktrees = task.workspaceWorktrees ?? {};
264898
+ const repoKeys = readiness.repositories;
264640
264899
  const repos = [];
264641
264900
  let allLanded = true;
264642
264901
  await setStatus("merging");
@@ -264665,7 +264924,7 @@ async function landWorkspaceTask(store, task, workspaceRootDir, options = {}, de
264665
264924
  };
264666
264925
  } catch (error3) {
264667
264926
  if (error3 instanceof WorkspaceFenceRefError) {
264668
- throw new WorkspaceRepoLandBusyError(repoKeys[0] ?? "workspace", "workspace-merge-dispatch-fence", taskId);
264927
+ throw new WorkspaceMergeTechnicalError("dispatch-fence-publication", `Workspace merge dispatch fence publication failed for ${taskId}: ${error3.message}`);
264669
264928
  }
264670
264929
  throw error3;
264671
264930
  }
@@ -264754,7 +265013,7 @@ async function landWorkspaceTask(store, task, workspaceRootDir, options = {}, de
264754
265013
  } catch (error3) {
264755
265014
  if (durableLandLease) await store.releaseWorkspaceLease(durableLandLease).catch(() => void 0);
264756
265015
  if (error3 instanceof WorkspaceRepoLandBusyError) throw error3;
264757
- throw new WorkspaceRepoLandBusyError(repoRel, "durable-workspace-lease", taskId);
265016
+ throw new WorkspaceMergeTechnicalError("durable-lease", `Workspace repository lease unavailable for ${repoRel}: ${getErrorMessage4(error3)}`);
264758
265017
  }
264759
265018
  try {
264760
265019
  activeSessionRegistry.registerPath(repoRootDir, {
@@ -264791,7 +265050,7 @@ async function landWorkspaceTask(store, task, workspaceRootDir, options = {}, de
264791
265050
  renewalTimer?.unref?.();
264792
265051
  const landSignal = options.signal ? AbortSignal.any([options.signal, leaseAbort.signal]) : leaseAbort.signal;
264793
265052
  const assertLeaseLive = () => {
264794
- if (leaseLost || leaseAbort.signal.aborted) throw new WorkspaceRepoLandBusyError(repoRel, "durable-workspace-lease", taskId);
265053
+ if (leaseLost || leaseAbort.signal.aborted) throw new WorkspaceMergeTechnicalError("durable-lease", `Workspace repository lease renewal was lost for ${repoRel}`);
264795
265054
  };
264796
265055
  try {
264797
265056
  const landResult = await landOneRepo(repoRootDir, entry.branch, integrationBranch, {
@@ -264876,11 +265135,11 @@ async function landWorkspaceTask(store, task, workspaceRootDir, options = {}, de
264876
265135
  repos.push({ repo: repoRel, repoRootDir, integrationBranch, branch: entry.branch, status: "empty" });
264877
265136
  }
264878
265137
  } catch (err) {
264879
- if (leaseLost) throw new WorkspaceRepoLandBusyError(repoRel, "durable-workspace-lease", taskId);
265138
+ if (leaseLost) throw new WorkspaceMergeTechnicalError("durable-lease", `Workspace repository lease renewal was lost for ${repoRel}`);
264880
265139
  if (isMergeAbortedError(err)) throw err;
264881
265140
  if (err instanceof WorkspacePartialLandError) throw err;
264882
265141
  if (err instanceof WorkspaceFenceRefError) {
264883
- throw new WorkspaceRepoLandBusyError(repoRel, "workspace-merge-dispatch-fence", taskId);
265142
+ throw new WorkspaceMergeTechnicalError("repository-fence-publication", `Workspace repository fence publication failed for ${repoRel}: ${err.message}`);
264884
265143
  }
264885
265144
  const message = getErrorMessage4(err);
264886
265145
  await log42(`AI merge (workspace): sub-repo ${repoRel} land failed: ${message}`);
@@ -265439,7 +265698,7 @@ async function finalizeTask(store, taskId, result2, audit, log42, rootDir, fence
265439
265698
  function throwIfAborted2(signal, taskId) {
265440
265699
  assertMergeGenerationOwned(signal, taskId);
265441
265700
  }
265442
- var execFileAsync28, aiMergeLog, MAX_CONCURRENT_ADVANCE_RETRIES, MAX_BLOCKING_REVIEW_REASONS, AiMergeBlockedError, AiMergeReviewReconciliationInvalidatedError, LEGACY_COMPLETE_COLUMN, LEGACY_ARCHIVED_COLUMN, LEGACY_TERMINAL_COLUMNS3, LEGACY_REBOUND_COLUMN2, STRONG_LANDED_STRATEGIES, WORKSPACE_REPO_LAND_OWNER_KEY, WorkspaceRepoLandBusyError, WorkspacePartialLandError, WorkspaceFinalizeBlockedError, WorkspaceMergeDispatchSupersededError;
265701
+ var execFileAsync28, aiMergeLog, MAX_CONCURRENT_ADVANCE_RETRIES, MAX_BLOCKING_REVIEW_REASONS, AiMergeBlockedError, AiMergeReviewReconciliationInvalidatedError, LEGACY_COMPLETE_COLUMN, LEGACY_ARCHIVED_COLUMN, LEGACY_TERMINAL_COLUMNS3, LEGACY_REBOUND_COLUMN2, STRONG_LANDED_STRATEGIES, WORKSPACE_REPO_LAND_OWNER_KEY, WorkspaceRepoLandBusyError, WorkspaceMergeTechnicalError, WorkspacePartialLandError, WorkspaceFinalizeBlockedError, WorkspaceMergeDispatchSupersededError;
265443
265702
  var init_merger_ai = __esm({
265444
265703
  "../engine/src/merge/merger-ai.ts"() {
265445
265704
  "use strict";
@@ -265471,6 +265730,7 @@ var init_merger_ai = __esm({
265471
265730
  init_active_session_registry();
265472
265731
  init_mcp_resolution();
265473
265732
  init_workspace_land_predicate();
265733
+ init_workspace_merge_readiness();
265474
265734
  init_workspace_land_failure();
265475
265735
  init_workspace_fence_ref();
265476
265736
  init_auto_merge_finalization();
@@ -265516,6 +265776,14 @@ var init_merger_ai = __esm({
265516
265776
  }
265517
265777
  retryable = true;
265518
265778
  };
265779
+ WorkspaceMergeTechnicalError = class extends Error {
265780
+ constructor(kind, message) {
265781
+ super(message);
265782
+ this.kind = kind;
265783
+ this.name = "WorkspaceMergeTechnicalError";
265784
+ }
265785
+ retryable = true;
265786
+ };
265519
265787
  WorkspacePartialLandError = class extends Error {
265520
265788
  constructor(landedCount, failedRepos, message) {
265521
265789
  super(message);
@@ -284709,6 +284977,15 @@ ${detail}`);
284709
284977
  }
284710
284978
  continue;
284711
284979
  }
284980
+ if (err instanceof WorkspaceMergeTechnicalError) {
284981
+ await store.logEntry(taskId, `Workspace merge technical failure (${err.kind}): ${err.message}`, "WorkspaceMergeTechnicalFailure").catch(() => void 0);
284982
+ if (hasManualResolver) {
284983
+ this.rejectMergeResolvers(taskId, err);
284984
+ } else if (!await this.maybeRetryTransientMerge(store, taskId, await store.getTask(taskId).catch(() => null), err.message, true)) {
284985
+ await store.updateTask(taskId, { status: "failed", error: err.message }).catch(() => void 0);
284986
+ }
284987
+ continue;
284988
+ }
284712
284989
  const isWorkspaceBusyError = err instanceof WorkspaceRepoLandBusyError || err instanceof WorkspaceMergeDispatchBusyError || err instanceof WorkspaceMergeDispatchSupersededError;
284713
284990
  if (isWorkspaceBusyError && hasManualResolver) {
284714
284991
  await store.logEntry(taskId, `Workspace sub-repo land busy (contention): ${errorMsg}`, "WorkspaceRepoLandBusy").catch(() => void 0);
@@ -284716,18 +284993,18 @@ ${detail}`);
284716
284993
  continue;
284717
284994
  }
284718
284995
  if (isWorkspaceBusyError && !hasManualResolver) {
284719
- const busyCount = this.workspaceBusyReenqueues.get(taskId) ?? 0;
284996
+ const liveTask = await store.getTask(taskId).catch(() => null);
284997
+ const busyCount = liveTask?.mergeTransientRetryCount ?? 0;
284720
284998
  await store.logEntry(taskId, `Workspace sub-repo land busy (contention): ${errorMsg}`, "WorkspaceRepoLandBusy").catch(() => void 0);
284721
284999
  if (busyCount < _ProjectEngine.WORKSPACE_BUSY_MAX_REENQUEUES) {
284722
- this.workspaceBusyReenqueues.set(taskId, busyCount + 1);
285000
+ const nextCount = busyCount + 1;
284723
285001
  const delayMs = Math.min(5e3 * Math.pow(2, busyCount), 6e4);
284724
- await store.updateTask(taskId, { status: null }).catch(() => void 0);
285002
+ await store.updateTask(taskId, { status: null, mergeTransientRetryCount: nextCount }).catch(() => void 0);
284725
285003
  runtimeLog.log(
284726
- `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)`
285004
+ `Workspace land busy re-enqueue ${nextCount}/${_ProjectEngine.WORKSPACE_BUSY_MAX_REENQUEUES} for ${taskId} in ${delayMs / 1e3}s (durable transient retry)`
284727
285005
  );
284728
285006
  this.scheduleWorkspaceBusyReenqueue(taskId, delayMs);
284729
285007
  } else {
284730
- this.workspaceBusyReenqueues.delete(taskId);
284731
285008
  await store.updateTask(taskId, { status: "failed", error: errorMsg }).catch(() => void 0);
284732
285009
  runtimeLog.error(
284733
285010
  `Auto-merge: ${taskId} workspace land busy ${_ProjectEngine.WORKSPACE_BUSY_MAX_REENQUEUES} times \u2014 parked as failed (sustained sub-repo lease contention)`
@@ -286056,7 +286333,7 @@ var init_engine_singleton_lock = __esm({
286056
286333
  import { realpathSync as realpathSync16 } from "node:fs";
286057
286334
  import { resolve as pathResolve } from "node:path";
286058
286335
  function sameProjectRoot(a, b3) {
286059
- const normalize9 = (p2) => {
286336
+ const normalize10 = (p2) => {
286060
286337
  const resolved = pathResolve(p2);
286061
286338
  try {
286062
286339
  return realpathSync16(resolved);
@@ -286064,7 +286341,7 @@ function sameProjectRoot(a, b3) {
286064
286341
  return resolved;
286065
286342
  }
286066
286343
  };
286067
- return normalize9(a) === normalize9(b3);
286344
+ return normalize10(a) === normalize10(b3);
286068
286345
  }
286069
286346
  var DEFAULT_RECONCILIATION_INTERVAL_MS, ProjectEngineManager;
286070
286347
  var init_project_engine_manager = __esm({
@@ -296872,7 +297149,7 @@ var init_gitlab = __esm({
296872
297149
  if (response.status === 204) return void 0;
296873
297150
  return await response.json();
296874
297151
  }
296875
- async listPaginated(path13, options, normalize9) {
297152
+ async listPaginated(path13, options, normalize10) {
296876
297153
  const limit = clampLimit(options.limit);
296877
297154
  const out = [];
296878
297155
  let page = 1;
@@ -296883,7 +297160,7 @@ var init_gitlab = __esm({
296883
297160
  appendListParams(params, options);
296884
297161
  const rows = await this.request(`${path13}?${params.toString()}`);
296885
297162
  if (!Array.isArray(rows) || rows.length === 0) break;
296886
- out.push(...rows.map(normalize9));
297163
+ out.push(...rows.map(normalize10));
296887
297164
  if (rows.length < Number(params.get("per_page"))) break;
296888
297165
  page += 1;
296889
297166
  }
@@ -315714,6 +315991,22 @@ var init_update_install_coordinator = __esm({
315714
315991
  pendingVersion;
315715
315992
  pendingResult;
315716
315993
  restartRequested = false;
315994
+ /**
315995
+ * FNXC:PendingUpdateInstall 2026-08-21-05:58:
315996
+ * A completed install belongs to the still-running old process, not a mounted
315997
+ * Settings dialog. Expose its target until process replacement so every route
315998
+ * can reject a second install and every dashboard remount can offer restart.
315999
+ */
316000
+ getPendingInstall() {
316001
+ if (!this.pendingVersion || !this.pendingResult) return void 0;
316002
+ return {
316003
+ ...this.pendingResult,
316004
+ latestVersion: this.pendingVersion,
316005
+ restartAttempted: this.restartRequested,
316006
+ restartScheduled: this.restartRequested,
316007
+ priorPid: this.restartRequested ? process.pid : void 0
316008
+ };
316009
+ }
315717
316010
  async install(targetVersion, operation) {
315718
316011
  if (this.pendingVersion && this.pendingResult) return this.pendingResult;
315719
316012
  if (!this.inFlight) {
@@ -316932,6 +317225,7 @@ var init_server2 = __esm({
316932
317225
  init_api_error();
316933
317226
  init_project_store_resolver();
316934
317227
  init_chat_project_services();
317228
+ init_file_service();
316935
317229
  init_terminal_viewport();
316936
317230
  init_terminal_service();
316937
317231
  init_terminal();