@runfusion/fusion 0.78.0-beta.2 → 0.78.0-beta.3
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.
- package/dist/bin.js +1150 -285
- package/dist/child-process-worker.js +760 -203
- package/dist/client/.vite/manifest.json +253 -253
- package/dist/client/assets/{AgentDetailView-4tEdPTG2.js → AgentDetailView-DAtUBPar.js} +1 -1
- package/dist/client/assets/{AgentPermissionPolicyEditor-DIGxjQFE.js → AgentPermissionPolicyEditor-Cth5Qyvz.js} +1 -1
- package/dist/client/assets/{AgentsView-BXFYQAPe.js → AgentsView-D6odi29-.js} +3 -3
- package/dist/client/assets/{CommandCenter-vknNrxBN.js → CommandCenter-DUlZFMvJ.js} +1 -1
- package/dist/client/assets/{DevServerView-COFb49kW.js → DevServerView-GD1-vwId.js} +1 -1
- package/dist/client/assets/{DirectoryPicker-C-wYRUc5.js → DirectoryPicker-De3Q7_zu.js} +1 -1
- package/dist/client/assets/{DocumentsView-B-uzhK3C.js → DocumentsView-BMYBV8DV.js} +1 -1
- package/dist/client/assets/{EvalsView-BcOgXvPe.js → EvalsView-CYfclysr.js} +1 -1
- package/dist/client/assets/{ExperimentalAgentOnboardingModal-B0zF29ER.js → ExperimentalAgentOnboardingModal-B1dQuReY.js} +1 -1
- package/dist/client/assets/{GoalsView--Cj2se_t.js → GoalsView-BHxjegdt.js} +1 -1
- package/dist/client/assets/{InsightsView-qMoM8oFe.js → InsightsView-DK0EP68R.js} +1 -1
- package/dist/client/assets/{MemoryView-Ca0Y2fYg.js → MemoryView-O8vfqipX.js} +1 -1
- package/dist/client/assets/{PatchnodeView--E259o36.js → PatchnodeView-WM5ZlnTS.js} +1 -1
- package/dist/client/assets/{PiExtensionsManager-DKKLn5YE.js → PiExtensionsManager-BrTit_vk.js} +1 -1
- package/dist/client/assets/{PluginManager-e0Rnv7RI.js → PluginManager-DBUm9Q30.js} +1 -1
- package/dist/client/assets/{PullRequestView-BFe77u-d.js → PullRequestView-DW9EqNT3.js} +1 -1
- package/dist/client/assets/{ReportModal-DvyDEj8D.js → ReportModal-Cv7Lxy1u.js} +1 -1
- package/dist/client/assets/{ResearchView-DROedGZR.js → ResearchView-DDGbBXlh.js} +1 -1
- package/dist/client/assets/{SecretsView-DTrrJEt9.js → SecretsView-C2K-n5_X.js} +1 -1
- package/dist/client/assets/{SettingsModal-CSbSmwxZ.js → SettingsModal-C7npF9nB.js} +3 -3
- package/dist/client/assets/{SettingsModal-DnTX4rMK.js → SettingsModal-Crpg0GmF.js} +1 -1
- package/dist/client/assets/{SettingsTextareaRow-y3gQ4u_G.js → SettingsTextareaRow-B7FIDTtF.js} +1 -1
- package/dist/client/assets/{SetupWizardModal-ClAbtrU0.js → SetupWizardModal-CBWFrde9.js} +2 -2
- package/dist/client/assets/{SkillsView-BpEJE16a.js → SkillsView-B1l4qkYj.js} +1 -1
- package/dist/client/assets/{WorkflowNodeEditor-CSbhpY5K.js → WorkflowNodeEditor-e122mQ0Z.js} +1 -1
- package/dist/client/assets/{agent-import-generation-BT0ZBGQA.js → agent-import-generation-CX_NVRPC.js} +1 -1
- package/dist/client/assets/{agentsOrgChartLayout-C_tkb_Pq.js → agentsOrgChartLayout-BAhAecHd.js} +1 -1
- package/dist/client/assets/{app-C8kudFlj.js → app-BO1iuWOt.js} +1 -1
- package/dist/client/assets/{app-B0hINWeH.js → app-BRFfR55q.js} +1 -1
- package/dist/client/assets/{app-BSxI9vvS.js → app-BW03hpJV.js} +3 -3
- package/dist/client/assets/{app-C8jogXBu.js → app-B_skBURN.js} +1 -1
- package/dist/client/assets/{app-hEhR2PdL.js → app-C0JL4IcI.js} +3 -3
- package/dist/client/assets/{app-BeDYumHl.js → app-C5uZ_9kI.js} +3 -3
- package/dist/client/assets/app-DxFiSzSJ.js +5 -0
- package/dist/client/assets/{architectureDiagram-3BPJPVTR-BfPYqDRS.js → architectureDiagram-3BPJPVTR-BOmFDW7M.js} +1 -1
- package/dist/client/assets/{blockDiagram-GPEHLZMM-BEcaGTmc.js → blockDiagram-GPEHLZMM-D7CnZ9-e.js} +1 -1
- package/dist/client/assets/{c4Diagram-AAUBKEIU-BX1xrinF.js → c4Diagram-AAUBKEIU-CQZw8PeP.js} +1 -1
- package/dist/client/assets/channel-Drj7VQBu.js +1 -0
- package/dist/client/assets/{chunk-2J33WTMH-OksyoE_c.js → chunk-2J33WTMH-DBS2o2AZ.js} +1 -1
- package/dist/client/assets/{chunk-4BX2VUAB-C5ivKrB5.js → chunk-4BX2VUAB-CFKEnTmJ.js} +1 -1
- package/dist/client/assets/{chunk-55IACEB6-C7vkVq-W.js → chunk-55IACEB6-DpjYRScs.js} +1 -1
- package/dist/client/assets/{chunk-727SXJPM-c-khhz21.js → chunk-727SXJPM-Db7lQvPG.js} +1 -1
- package/dist/client/assets/{chunk-AQP2D5EJ-DwxYJXXQ.js → chunk-AQP2D5EJ-rSGEvDdj.js} +1 -1
- package/dist/client/assets/{chunk-FMBD7UC4-BKhdu4up.js → chunk-FMBD7UC4-Cdi9zNME.js} +1 -1
- package/dist/client/assets/{chunk-ND2GUHAM-okVZM2p2.js → chunk-ND2GUHAM-D_3BQZ27.js} +1 -1
- package/dist/client/assets/{chunk-QZHKN3VN-DDso7rM3.js → chunk-QZHKN3VN-DnVoTnz9.js} +1 -1
- package/dist/client/assets/classDiagram-4FO5ZUOK-DGxwjUYU.js +1 -0
- package/dist/client/assets/classDiagram-v2-Q7XG4LA2-DGxwjUYU.js +1 -0
- package/dist/client/assets/{cose-bilkent-S5V4N54A-CNT9ltBk.js → cose-bilkent-S5V4N54A-HQFw9frh.js} +1 -1
- package/dist/client/assets/{dagre-BM42HDAG-C5a30-g5.js → dagre-BM42HDAG-BdHp0sc2.js} +1 -1
- package/dist/client/assets/{dashboard-view-Brqe-SVE.js → dashboard-view-C3vvo4zP.js} +1 -1
- package/dist/client/assets/{dashboard-view-DxpkE7dL.js → dashboard-view-CoxDPI4m.js} +1 -1
- package/dist/client/assets/{dashboard-view-WFOf0Kpl.js → dashboard-view-DaqL1Ufr.js} +1 -1
- package/dist/client/assets/{dashboard-view-DyAQ_agZ.js → dashboard-view-mWhkEiwE.js} +1 -1
- package/dist/client/assets/{diagram-2AECGRRQ-DG7EyUNe.js → diagram-2AECGRRQ-BDu9AETT.js} +1 -1
- package/dist/client/assets/{diagram-5GNKFQAL-DE7lhEna.js → diagram-5GNKFQAL-2qzqfWTz.js} +1 -1
- package/dist/client/assets/{diagram-KO2AKTUF-DFtJNqNF.js → diagram-KO2AKTUF-DYa-tplS.js} +1 -1
- package/dist/client/assets/{diagram-LMA3HP47-DUAuxZQD.js → diagram-LMA3HP47-ChwcQccK.js} +1 -1
- package/dist/client/assets/{diagram-OG6HWLK6-DCKETcWy.js → diagram-OG6HWLK6-DoGKft5E.js} +1 -1
- package/dist/client/assets/{erDiagram-TEJ5UH35-pa0blyAD.js → erDiagram-TEJ5UH35-5dsSgUXk.js} +1 -1
- package/dist/client/assets/{flowDiagram-I6XJVG4X-D7Veqs1W.js → flowDiagram-I6XJVG4X-BOHB4RzL.js} +1 -1
- package/dist/client/assets/{folder-open-CojKP0zy.js → folder-open-BQcY8jvq.js} +1 -1
- package/dist/client/assets/{ganttDiagram-6RSMTGT7-CsqBMy2D.js → ganttDiagram-6RSMTGT7-BD3aivUG.js} +1 -1
- package/dist/client/assets/{gitGraphDiagram-PVQCEYII-BRrVKZJo.js → gitGraphDiagram-PVQCEYII-HBZEAECF.js} +1 -1
- package/dist/client/assets/{index-Dliz9s5K.css → index-26HUNwwJ.css} +1 -1
- package/dist/client/assets/{index-Cv_ouAgx.js → index-QU8sENvc.js} +298 -298
- package/dist/client/assets/{infoDiagram-5YYISTIA-o2GEmmb2.js → infoDiagram-5YYISTIA-d7hsqM0V.js} +1 -1
- package/dist/client/assets/{ishikawaDiagram-YF4QCWOH-k1p6nWtj.js → ishikawaDiagram-YF4QCWOH-k4Dnhc_y.js} +1 -1
- package/dist/client/assets/{journeyDiagram-JHISSGLW-B43V37S0.js → journeyDiagram-JHISSGLW-Dh3yqZPQ.js} +1 -1
- package/dist/client/assets/{kanban-definition-UN3LZRKU-HRMYyAQB.js → kanban-definition-UN3LZRKU-BYqpe7Ju.js} +1 -1
- package/dist/client/assets/{mermaid.core-6vt3zKxL.js → mermaid.core-D5Ccw5tQ.js} +4 -4
- package/dist/client/assets/{mindmap-definition-RKZ34NQL-D3VJuvfj.js → mindmap-definition-RKZ34NQL-D64P3Mc4.js} +1 -1
- package/dist/client/assets/{pieDiagram-4H26LBE5-D0RjFhk9.js → pieDiagram-4H26LBE5-CtMfKnlZ.js} +1 -1
- package/dist/client/assets/{puzzle-D3dJEsTf.js → puzzle-D4y4g-nb.js} +1 -1
- package/dist/client/assets/{quadrantDiagram-W4KKPZXB-DHaEqU3Q.js → quadrantDiagram-W4KKPZXB-B5gSMr9M.js} +1 -1
- package/dist/client/assets/{requirementDiagram-4Y6WPE33-DnfFCRP0.js → requirementDiagram-4Y6WPE33-DKtBfWDi.js} +1 -1
- package/dist/client/assets/{sankeyDiagram-5OEKKPKP-D6GCuiqV.js → sankeyDiagram-5OEKKPKP-BcWeBcNk.js} +1 -1
- package/dist/client/assets/{sequenceDiagram-3UESZ5HK-DaGYYzxS.js → sequenceDiagram-3UESZ5HK-Crp7Mc0M.js} +1 -1
- package/dist/client/assets/{standing-instructions-template-CN8Kp4Hq.js → standing-instructions-template-lD5Vcanq.js} +1 -1
- package/dist/client/assets/{stateDiagram-AJRCARHV-ek4Wb8ai.js → stateDiagram-AJRCARHV-CA5FEPYe.js} +1 -1
- package/dist/client/assets/{stateDiagram-v2-BHNVJYJU-EPn8Vq7v.js → stateDiagram-v2-BHNVJYJU-Bo75sO6t.js} +1 -1
- package/dist/client/assets/{timeline-definition-PNZ67QCA-CyAwCbnE.js → timeline-definition-PNZ67QCA-Dmr4NeTD.js} +1 -1
- package/dist/client/assets/{upload-BhpNL3xI.js → upload-CzJ1o8Px.js} +1 -1
- package/dist/client/assets/{users-Bka1cIkR.js → users-BMnytPKO.js} +1 -1
- package/dist/client/assets/{vennDiagram-CIIHVFJN-Bd8-8r3a.js → vennDiagram-CIIHVFJN-DfbBSNgT.js} +1 -1
- package/dist/client/assets/{wardley-L42UT6IY-nNvWflNF.js → wardley-L42UT6IY-Wxq1L5VU.js} +1 -1
- package/dist/client/assets/{wardleyDiagram-YWT4CUSO-Dy7XrvPx.js → wardleyDiagram-YWT4CUSO-D6HnIS8j.js} +1 -1
- package/dist/client/assets/{xychartDiagram-2RQKCTM6-38VTptR4.js → xychartDiagram-2RQKCTM6-5MfFlu30.js} +1 -1
- package/dist/client/index.html +2 -2
- package/dist/client/version.json +1 -1
- package/dist/droid-cli/package.json +1 -1
- package/dist/extension.js +844 -264
- package/dist/pi-claude-cli/package.json +1 -1
- package/dist/plugin-sdk/index.d.ts +39 -11
- package/dist/plugin-sdk/index.js +2 -2
- package/dist/plugins/fusion-plugin-acp-runtime/bundled.js +2 -2
- package/dist/plugins/fusion-plugin-acp-runtime/package.json +1 -1
- package/dist/plugins/fusion-plugin-claude-runtime/bundled.js +2 -2
- package/dist/plugins/fusion-plugin-claude-runtime/package.json +1 -1
- package/dist/plugins/fusion-plugin-cli-printing-press/bundled.js +2 -2
- package/dist/plugins/fusion-plugin-cli-printing-press/package.json +1 -1
- package/dist/plugins/fusion-plugin-compound-engineering/bundled.js +2 -2
- package/dist/plugins/fusion-plugin-compound-engineering/package.json +1 -1
- package/dist/plugins/fusion-plugin-cursor-runtime/bundled.js +2 -2
- package/dist/plugins/fusion-plugin-cursor-runtime/package.json +1 -1
- package/dist/plugins/fusion-plugin-dependency-graph/bundled.js +2 -2
- package/dist/plugins/fusion-plugin-dependency-graph/package.json +1 -1
- package/dist/plugins/fusion-plugin-droid-runtime/bundled.js +2 -2
- package/dist/plugins/fusion-plugin-droid-runtime/package.json +1 -1
- package/dist/plugins/fusion-plugin-grok-runtime/bundled.js +2 -2
- package/dist/plugins/fusion-plugin-grok-runtime/package.json +1 -1
- package/dist/plugins/fusion-plugin-hermes-runtime/bundled.js +2 -2
- package/dist/plugins/fusion-plugin-hermes-runtime/package.json +1 -1
- package/dist/plugins/fusion-plugin-linear-import/bundled.js +2 -2
- package/dist/plugins/fusion-plugin-linear-import/package.json +1 -1
- package/dist/plugins/fusion-plugin-omp-runtime/bundled.js +2 -2
- package/dist/plugins/fusion-plugin-omp-runtime/package.json +1 -1
- package/dist/plugins/fusion-plugin-openclaw-runtime/bundled.js +2 -2
- package/dist/plugins/fusion-plugin-openclaw-runtime/package.json +1 -1
- package/dist/plugins/fusion-plugin-paperclip-runtime/bundled.js +2 -2
- package/dist/plugins/fusion-plugin-paperclip-runtime/package.json +1 -1
- package/dist/plugins/fusion-plugin-quality/bundled.js +2 -2
- package/dist/plugins/fusion-plugin-quality/package.json +1 -1
- package/dist/plugins/fusion-plugin-reports/bundled.js +2 -2
- package/dist/plugins/fusion-plugin-reports/package.json +1 -1
- package/dist/plugins/fusion-plugin-roadmap/bundled.js +2 -2
- package/dist/plugins/fusion-plugin-roadmap/package.json +1 -1
- package/dist/plugins/fusion-plugin-todos/bundled.js +2 -2
- package/dist/plugins/fusion-plugin-todos/package.json +1 -1
- package/dist/plugins/fusion-plugin-whatsapp-chat/bundled.js +2 -2
- package/dist/plugins/fusion-plugin-whatsapp-chat/package.json +1 -1
- package/package.json +1 -1
- package/dist/client/assets/app-DZ5j1n3j.js +0 -5
- package/dist/client/assets/channel-BjDq20V4.js +0 -1
- package/dist/client/assets/classDiagram-4FO5ZUOK-B114c_Fi.js +0 -1
- package/dist/client/assets/classDiagram-v2-Q7XG4LA2-B114c_Fi.js +0 -1
|
@@ -1919,10 +1919,11 @@ function toSnapshot(result) {
|
|
|
1919
1919
|
const { priorAttempts: _drop, ...rest } = result;
|
|
1920
1920
|
return rest;
|
|
1921
1921
|
}
|
|
1922
|
-
function archiveTerminalWorkflowStepFailures(results, archivedAt = (/* @__PURE__ */ new Date()).toISOString()) {
|
|
1923
|
-
|
|
1922
|
+
function archiveTerminalWorkflowStepFailures(results, archivedAt = (/* @__PURE__ */ new Date()).toISOString(), options = {}) {
|
|
1923
|
+
const inScope = (result) => isTerminalFailure(result) && (!options.workflowStepIds || options.workflowStepIds.has(result.workflowStepId));
|
|
1924
|
+
if (!results?.some(inScope)) return results;
|
|
1924
1925
|
return results.map((result) => {
|
|
1925
|
-
if (!
|
|
1926
|
+
if (!inScope(result)) return result;
|
|
1926
1927
|
const snapshot = toSnapshot(result);
|
|
1927
1928
|
const priorAttempts = [snapshot, ...result.priorAttempts ?? []].slice(0, MAX_WORKFLOW_STEP_PRIOR_ATTEMPTS);
|
|
1928
1929
|
const {
|
|
@@ -2098,6 +2099,26 @@ function isAuditedOperatorBypass(result) {
|
|
|
2098
2099
|
actor && !AUTOMATED_BYPASS_ACTORS.has(actor) && result.bypassedAt?.trim() && result.bypassReason?.trim()
|
|
2099
2100
|
);
|
|
2100
2101
|
}
|
|
2102
|
+
function resolveCollateralArchivedReviewGate(result, options) {
|
|
2103
|
+
const archivedFrom = result.remediationArchivedFromStatus;
|
|
2104
|
+
if ((result.phase ?? "pre-merge") !== "pre-merge" || result.status !== "skipped" || result.remediationArchivedAt == null || result.bypassedBy !== void 0 || archivedFrom !== "failed" && archivedFrom !== "advisory_failure" || options.remediationGateIds.has(result.workflowStepId)) {
|
|
2105
|
+
return void 0;
|
|
2106
|
+
}
|
|
2107
|
+
const {
|
|
2108
|
+
remediationArchivedAt: _archivedAt,
|
|
2109
|
+
remediationArchivedFromStatus: _archivedFrom,
|
|
2110
|
+
...rest
|
|
2111
|
+
} = result;
|
|
2112
|
+
return {
|
|
2113
|
+
restored: {
|
|
2114
|
+
...rest,
|
|
2115
|
+
status: archivedFrom,
|
|
2116
|
+
output: COLLATERAL_ARCHIVED_REVIEW_GATE_DIAGNOSTIC,
|
|
2117
|
+
notes: COLLATERAL_ARCHIVED_REVIEW_GATE_DIAGNOSTIC
|
|
2118
|
+
},
|
|
2119
|
+
reason: COLLATERAL_ARCHIVED_REVIEW_GATE_DIAGNOSTIC
|
|
2120
|
+
};
|
|
2121
|
+
}
|
|
2101
2122
|
function resolveUnprovenReviewApproval(result, options) {
|
|
2102
2123
|
if ((result.phase ?? "pre-merge") !== "pre-merge" || !requiresContentReviewProof(result.workflowStepId, result) || options.workspace || result.status !== "passed" || result.verdict !== "APPROVE" && result.verdict !== "APPROVE_WITH_NOTES" || result.reviewInputFingerprint !== void 0 || result.bypassedBy !== void 0 || result.remediationArchivedAt != null) {
|
|
2103
2124
|
return void 0;
|
|
@@ -2129,9 +2150,10 @@ function evaluateStep(workflowStepId, results, task, descriptor) {
|
|
|
2129
2150
|
const isPlanDomain = workflowStepId === PLAN_REVIEW_GROUP_ID || result.reviewKind === "plan";
|
|
2130
2151
|
const notRunApproves = isWorkflowStepNotRun(result) && !requiresAuthoredVerdict && !isPlanDomain;
|
|
2131
2152
|
const approved = result.status === "passed" && (requiresAuthoredVerdict ? approvedVerdict : result.verdict === void 0 || approvedVerdict) || result.status === "skipped" && !!result.bypassedBy || notRunApproves;
|
|
2132
|
-
|
|
2153
|
+
const auditedOperatorWaiver = isAuditedOperatorBypass(result) && descriptor?.kind !== "workspace";
|
|
2154
|
+
if (!approved || result.remediationArchivedAt != null && !auditedOperatorWaiver) return { workflowStepId, state: "not-approved" };
|
|
2133
2155
|
if (result.reviewKind === "plan") return { workflowStepId, state: "approved" };
|
|
2134
|
-
if (
|
|
2156
|
+
if (auditedOperatorWaiver) {
|
|
2135
2157
|
return { workflowStepId, state: "approved" };
|
|
2136
2158
|
}
|
|
2137
2159
|
const bindsContent = requiresExplicitVerdict || result.reviewInputFingerprint !== void 0;
|
|
@@ -2164,7 +2186,7 @@ function evaluateStep(workflowStepId, results, task, descriptor) {
|
|
|
2164
2186
|
if (stale.length) return { workflowStepId, state: "stale-content", repositories: stale };
|
|
2165
2187
|
return { workflowStepId, state: "approved" };
|
|
2166
2188
|
}
|
|
2167
|
-
var AUTOMATED_BYPASS_ACTORS, UNPROVEN_REVIEW_APPROVAL_DIAGNOSTIC;
|
|
2189
|
+
var AUTOMATED_BYPASS_ACTORS, UNPROVEN_REVIEW_APPROVAL_DIAGNOSTIC, COLLATERAL_ARCHIVED_REVIEW_GATE_DIAGNOSTIC;
|
|
2168
2190
|
var init_pre_merge_approval = __esm({
|
|
2169
2191
|
"../core/src/merge/pre-merge-approval.ts"() {
|
|
2170
2192
|
"use strict";
|
|
@@ -2173,6 +2195,7 @@ var init_pre_merge_approval = __esm({
|
|
|
2173
2195
|
init_workflow_step_results();
|
|
2174
2196
|
AUTOMATED_BYPASS_ACTORS = /* @__PURE__ */ new Set([FAST_MODE_BYPASS_ACTOR]);
|
|
2175
2197
|
UNPROVEN_REVIEW_APPROVAL_DIAGNOSTIC = "Content-binding review approval recorded without reviewInputFingerprint; approval invalidated so the gate can run again.";
|
|
2198
|
+
COLLATERAL_ARCHIVED_REVIEW_GATE_DIAGNOSTIC = "Gate archived as collateral of another gate's remediation and restored to its recoverable failed state by self-healing. No reviewer verdict was fabricated; re-run or bypass this gate to clear the merge door.";
|
|
2176
2199
|
}
|
|
2177
2200
|
});
|
|
2178
2201
|
|
|
@@ -2281,6 +2304,9 @@ function hasNonTerminalSteps(task) {
|
|
|
2281
2304
|
function isPreMergeStepsNotRunBlocker(blocker) {
|
|
2282
2305
|
return blocker === PRE_MERGE_STEPS_NOT_RUN_BLOCKER;
|
|
2283
2306
|
}
|
|
2307
|
+
function isStaleContentApprovalBlocker(blocker) {
|
|
2308
|
+
return typeof blocker === "string" && blocker.trim().endsWith(STALE_CONTENT_APPROVAL_BLOCKER);
|
|
2309
|
+
}
|
|
2284
2310
|
function getTaskMergeBlocker(task, options = {}) {
|
|
2285
2311
|
if (!options.skipColumnIdentityCheck) {
|
|
2286
2312
|
const reviewColumns = options.reviewColumns?.size ? options.reviewColumns : void 0;
|
|
@@ -2302,8 +2328,10 @@ function getTaskMergeBlocker(task, options = {}) {
|
|
|
2302
2328
|
}
|
|
2303
2329
|
const approval = evaluatePreMergeApprovals(task, options).find((candidate) => candidate.state !== "approved");
|
|
2304
2330
|
if (approval?.state === "missing") return PRE_MERGE_STEPS_NOT_RUN_BLOCKER;
|
|
2305
|
-
if (approval?.state === "not-approved")
|
|
2306
|
-
|
|
2331
|
+
if (approval?.state === "not-approved") {
|
|
2332
|
+
return `task has enabled pre-merge workflow steps without a current approval (gate '${approval.workflowStepId}')`;
|
|
2333
|
+
}
|
|
2334
|
+
if (approval?.state === "stale-content") return STALE_CONTENT_APPROVAL_BLOCKER;
|
|
2307
2335
|
if (approval?.state === "unprovable-content") return "task has no provable approval for the content being merged";
|
|
2308
2336
|
if (task.workflowStepResults?.some((result) => {
|
|
2309
2337
|
const phase = result.phase || "pre-merge";
|
|
@@ -2320,11 +2348,14 @@ function getTaskMergeBlocker(task, options = {}) {
|
|
|
2320
2348
|
return void 0;
|
|
2321
2349
|
}
|
|
2322
2350
|
function getLatestFailedPreMergeReviewStep(task) {
|
|
2323
|
-
|
|
2351
|
+
const results = task.workflowStepResults ?? [];
|
|
2352
|
+
const recentFirst = (a, b3) => {
|
|
2324
2353
|
const aTs = Date.parse(a.completedAt || a.startedAt || "");
|
|
2325
2354
|
const bTs = Date.parse(b3.completedAt || b3.startedAt || "");
|
|
2326
2355
|
return (Number.isFinite(bTs) ? bTs : 0) - (Number.isFinite(aTs) ? aTs : 0);
|
|
2327
|
-
}
|
|
2356
|
+
};
|
|
2357
|
+
const isPreMerge = (result) => (result.phase || "pre-merge") === "pre-merge";
|
|
2358
|
+
return results.filter((result) => isPreMerge(result) && result.status === "failed").sort(recentFirst)[0] ?? results.filter((result) => isPreMerge(result) && isArchivedRemediationCarrier(result) && (result.remediationArchivedFromStatus === "failed" || result.remediationArchivedFromStatus === "advisory_failure") && !result.bypassedBy && !result.supersededAt).sort(recentFirst)[0];
|
|
2328
2359
|
}
|
|
2329
2360
|
function clearMergeConfirmedTransientStatus(status) {
|
|
2330
2361
|
return status !== void 0 && MERGE_CONFIRMED_TRANSIENT_STATUSES.has(status) ? void 0 : status;
|
|
@@ -2408,13 +2439,14 @@ function findPendingPreMergeStep(task) {
|
|
|
2408
2439
|
(a, b3) => new Date(b3.startedAt ?? 0).getTime() - new Date(a.startedAt ?? 0).getTime()
|
|
2409
2440
|
)[0];
|
|
2410
2441
|
}
|
|
2411
|
-
var FUSION_SIBLING_BRANCH_RE, AWAITING_APPROVAL_PAUSE_REASON, HARD_BLOCKING_TASK_STATUSES, SCHEDULER_TRANSIENT_STATUSES, BLOCKING_TASK_STATUSES, NON_TERMINAL_STEP_STATUSES, NON_TERMINAL_WORKFLOW_STATUSES, PRE_MERGE_STEPS_NOT_RUN_BLOCKER, PreMergeStepsNotRunError, REVIEW_ELIGIBLE_SENTINEL_COLUMN, MERGE_CONFIRMED_TRANSIENT_STATUSES;
|
|
2442
|
+
var FUSION_SIBLING_BRANCH_RE, AWAITING_APPROVAL_PAUSE_REASON, HARD_BLOCKING_TASK_STATUSES, SCHEDULER_TRANSIENT_STATUSES, BLOCKING_TASK_STATUSES, NON_TERMINAL_STEP_STATUSES, NON_TERMINAL_WORKFLOW_STATUSES, PRE_MERGE_STEPS_NOT_RUN_BLOCKER, STALE_CONTENT_APPROVAL_BLOCKER, PreMergeStepsNotRunError, REVIEW_ELIGIBLE_SENTINEL_COLUMN, MERGE_CONFIRMED_TRANSIENT_STATUSES;
|
|
2412
2443
|
var init_task_merge = __esm({
|
|
2413
2444
|
"../core/src/merge/task-merge.ts"() {
|
|
2414
2445
|
"use strict";
|
|
2415
2446
|
init_branch_group_completion();
|
|
2416
2447
|
init_task_helpers();
|
|
2417
2448
|
init_pre_merge_approval();
|
|
2449
|
+
init_workflow_step_results();
|
|
2418
2450
|
FUSION_SIBLING_BRANCH_RE = /^fusion\/fn-/i;
|
|
2419
2451
|
AWAITING_APPROVAL_PAUSE_REASON = "awaiting-approval";
|
|
2420
2452
|
HARD_BLOCKING_TASK_STATUSES = /* @__PURE__ */ new Set([
|
|
@@ -2458,6 +2490,7 @@ var init_task_merge = __esm({
|
|
|
2458
2490
|
"pending"
|
|
2459
2491
|
]);
|
|
2460
2492
|
PRE_MERGE_STEPS_NOT_RUN_BLOCKER = "task has enabled pre-merge workflow steps that never ran";
|
|
2493
|
+
STALE_CONTENT_APPROVAL_BLOCKER = "task has a pre-merge approval recorded against different content";
|
|
2461
2494
|
PreMergeStepsNotRunError = class extends Error {
|
|
2462
2495
|
code = "pre-merge-steps-not-run";
|
|
2463
2496
|
taskId;
|
|
@@ -6437,8 +6470,9 @@ function renderValidationCause(diagnostics) {
|
|
|
6437
6470
|
}
|
|
6438
6471
|
function featureValidationRepairEligibility(feature) {
|
|
6439
6472
|
const repairableLoop = feature.loopState === "blocked" || feature.loopState === "needs_fix";
|
|
6473
|
+
const unvalidatedPassedMarker = feature.lastValidatorStatus === "passed" && !feature.lastValidatorRunId;
|
|
6440
6474
|
return {
|
|
6441
|
-
clear: repairableLoop || feature.status === "blocked",
|
|
6475
|
+
clear: repairableLoop || feature.status === "blocked" || unvalidatedPassedMarker,
|
|
6442
6476
|
reRun: repairableLoop || feature.status === "blocked" && (feature.loopState === void 0 || feature.loopState === "idle")
|
|
6443
6477
|
};
|
|
6444
6478
|
}
|
|
@@ -6502,7 +6536,8 @@ var init_mission_types = __esm({
|
|
|
6502
6536
|
implementing: [],
|
|
6503
6537
|
validating: [],
|
|
6504
6538
|
needs_fix: ["idle", "implementing"],
|
|
6505
|
-
|
|
6539
|
+
/* FNXC:Missions 2026-09-05-22:07: Explicit repair may return a fabricated unvalidated passed marker to idle (issue #3574). */
|
|
6540
|
+
passed: ["idle"],
|
|
6506
6541
|
blocked: ["idle", "implementing"]
|
|
6507
6542
|
};
|
|
6508
6543
|
VALIDATOR_RUN_STATUSES = ["running", "passed", "failed", "blocked", "error"];
|
|
@@ -13609,7 +13644,7 @@ function isBlockingFinding(finding, threshold) {
|
|
|
13609
13644
|
if (!finding.severity) return true;
|
|
13610
13645
|
return SEVERITY_RANK[finding.severity] >= SEVERITY_RANK[threshold];
|
|
13611
13646
|
}
|
|
13612
|
-
function applyReviewSeverityGate({ verdict, findings, threshold }) {
|
|
13647
|
+
function applyReviewSeverityGate({ verdict, findings, threshold, findingsUnreadable }) {
|
|
13613
13648
|
const all = findings ?? [];
|
|
13614
13649
|
const open3 = all.filter(isActionableReviewFinding);
|
|
13615
13650
|
const resolved = all.filter((finding) => !isActionableReviewFinding(finding));
|
|
@@ -13617,6 +13652,7 @@ function applyReviewSeverityGate({ verdict, findings, threshold }) {
|
|
|
13617
13652
|
const advisory = open3.filter((finding) => !isBlockingFinding(finding, threshold));
|
|
13618
13653
|
if (verdict !== "REVISE") return { verdict, downgraded: false, blocking, advisory, resolved };
|
|
13619
13654
|
if (blocking.length > 0) return { verdict, downgraded: false, blocking, advisory, resolved };
|
|
13655
|
+
if (findingsUnreadable) return { verdict, downgraded: false, blocking, advisory, resolved };
|
|
13620
13656
|
return { verdict: "APPROVE_WITH_NOTES", downgraded: true, blocking, advisory, resolved };
|
|
13621
13657
|
}
|
|
13622
13658
|
function formatFindingsByPriority(findings) {
|
|
@@ -18358,6 +18394,18 @@ function detectWorkflowDrift(ir2, pin) {
|
|
|
18358
18394
|
function defaultCodingWorkflowIr() {
|
|
18359
18395
|
return resolveDefaultWorkflowIr();
|
|
18360
18396
|
}
|
|
18397
|
+
async function prefetchWorkflowSelections(store, taskIds, cache) {
|
|
18398
|
+
const missingIds = [...new Set(taskIds)].filter((id) => !cache.has(id));
|
|
18399
|
+
if (missingIds.length === 0) return { batched: 0, singles: 0 };
|
|
18400
|
+
if (!store.getTaskWorkflowSelectionsAsync) return { batched: 0, singles: missingIds.length };
|
|
18401
|
+
try {
|
|
18402
|
+
const selections = await store.getTaskWorkflowSelectionsAsync(missingIds);
|
|
18403
|
+
for (const id of missingIds) cache.set(id, selections.get(id));
|
|
18404
|
+
return { batched: 1, singles: 0 };
|
|
18405
|
+
} catch {
|
|
18406
|
+
return { batched: 0, singles: missingIds.length };
|
|
18407
|
+
}
|
|
18408
|
+
}
|
|
18361
18409
|
function resolveSeamPromptFromIr(ir2, seam) {
|
|
18362
18410
|
for (const node of ir2.nodes) {
|
|
18363
18411
|
if (node.kind !== "prompt") continue;
|
|
@@ -18576,9 +18624,9 @@ function resolveLifecycleColumns(ir2) {
|
|
|
18576
18624
|
archived: first(LIFECYCLE_ROLE_FLAGS.archived)
|
|
18577
18625
|
};
|
|
18578
18626
|
}
|
|
18579
|
-
async function resolveTaskLifecycleColumns(store, taskId, cache) {
|
|
18627
|
+
async function resolveTaskLifecycleColumns(store, taskId, cache, selectionCache) {
|
|
18580
18628
|
try {
|
|
18581
|
-
const ir2 = await resolveWorkflowIrForTask(store, taskId, cache);
|
|
18629
|
+
const ir2 = await resolveWorkflowIrForTask(store, taskId, cache, selectionCache);
|
|
18582
18630
|
return resolveLifecycleColumns(ir2);
|
|
18583
18631
|
} catch {
|
|
18584
18632
|
return void 0;
|
|
@@ -30574,18 +30622,33 @@ function isValidPluginParserId(id) {
|
|
|
30574
30622
|
const seg = /^[a-z0-9-]+$/;
|
|
30575
30623
|
return seg.test(parts[1]) && seg.test(parts[2]);
|
|
30576
30624
|
}
|
|
30577
|
-
function
|
|
30578
|
-
const steps = [];
|
|
30625
|
+
function matchStepHeadings(content) {
|
|
30579
30626
|
const stepRegex = /^###\s+Step\s+\d+[^:]*:\s*(.+)$/gm;
|
|
30580
|
-
const annotatedRegex = /^###\s+Step\s+\d+\s*\(depends:\s*([^)]*)\)\s*:\s*([^\n]+)$/;
|
|
30581
30627
|
const matches = [];
|
|
30582
30628
|
let match;
|
|
30583
30629
|
while ((match = stepRegex.exec(content)) !== null) {
|
|
30584
30630
|
const heading = /^###\s+Step\s+(\d+)/.exec(match[0]);
|
|
30585
|
-
if (heading)
|
|
30586
|
-
|
|
30587
|
-
|
|
30631
|
+
if (!heading) continue;
|
|
30632
|
+
const index2 = match.index;
|
|
30633
|
+
const newline = content.indexOf("\n", index2);
|
|
30634
|
+
matches.push({
|
|
30635
|
+
headingNumber: Number(heading[1]),
|
|
30636
|
+
index: index2,
|
|
30637
|
+
headingLineEnd: newline === -1 ? content.length : newline,
|
|
30638
|
+
match: match[0]
|
|
30639
|
+
});
|
|
30588
30640
|
}
|
|
30641
|
+
return matches;
|
|
30642
|
+
}
|
|
30643
|
+
function parseStepHeadings(content) {
|
|
30644
|
+
const steps = [];
|
|
30645
|
+
const annotatedRegex = /^###\s+Step\s+\d+\s*\(depends:\s*([^)]*)\)\s*:\s*([^\n]+)$/;
|
|
30646
|
+
const matches = matchStepHeadings(content).map((entry) => ({
|
|
30647
|
+
full: entry.match,
|
|
30648
|
+
name: /^###\s+Step\s+\d+[^:]*:\s*(.+)$/m.exec(entry.match)?.[1] ?? "",
|
|
30649
|
+
headingNumber: entry.headingNumber
|
|
30650
|
+
}));
|
|
30651
|
+
let match;
|
|
30589
30652
|
const offset = resolveAuthoredStepHeadingOffset(matches.map((entry) => entry.headingNumber));
|
|
30590
30653
|
for (const { full, name: legacyName } of matches) {
|
|
30591
30654
|
if (!full.includes("(depends:")) {
|
|
@@ -75182,8 +75245,8 @@ function appendAgentLogEntriesSync(taskDir, entries) {
|
|
|
75182
75245
|
if (entries.length === 0) return [];
|
|
75183
75246
|
const filePath = getAgentLogFilePath(taskDir);
|
|
75184
75247
|
mkdirSync5(dirname14(filePath), { recursive: true });
|
|
75185
|
-
const startingLineNo =
|
|
75186
|
-
const payload = entries.map((entry) => serializeEntry(entry)).join("")
|
|
75248
|
+
const { lineCount: startingLineNo, endsWithNewline } = inspectAgentLogTail(filePath);
|
|
75249
|
+
const payload = `${startingLineNo > 0 && !endsWithNewline ? "\n" : ""}${entries.map((entry) => serializeEntry(entry)).join("")}`;
|
|
75187
75250
|
appendFileSync(filePath, payload, "utf8");
|
|
75188
75251
|
return entries.map((entry, index2) => materializeEntry(entry, startingLineNo + index2 + 1));
|
|
75189
75252
|
}
|
|
@@ -75219,6 +75282,7 @@ function readAllAgentLogEntries(taskDir, options = {}) {
|
|
|
75219
75282
|
}
|
|
75220
75283
|
const lines = content.split("\n");
|
|
75221
75284
|
const entries = [];
|
|
75285
|
+
const skippedLines = [];
|
|
75222
75286
|
for (let index2 = 0; index2 < lines.length; index2 += 1) {
|
|
75223
75287
|
const rawLine = lines[index2];
|
|
75224
75288
|
if (!rawLine) continue;
|
|
@@ -75240,9 +75304,18 @@ function readAllAgentLogEntries(taskDir, options = {}) {
|
|
|
75240
75304
|
}
|
|
75241
75305
|
entries.push(entry);
|
|
75242
75306
|
} catch (error2) {
|
|
75243
|
-
|
|
75307
|
+
skippedLines.push({
|
|
75308
|
+
lineNo,
|
|
75309
|
+
message: error2 instanceof Error ? error2.message : String(error2)
|
|
75310
|
+
});
|
|
75244
75311
|
}
|
|
75245
75312
|
}
|
|
75313
|
+
const firstSkipped = skippedLines[0];
|
|
75314
|
+
if (firstSkipped) {
|
|
75315
|
+
log7.warn(
|
|
75316
|
+
`Skipped ${skippedLines.length} malformed JSONL line(s) in ${filePath}; first line ${firstSkipped.lineNo}: ${firstSkipped.message}`
|
|
75317
|
+
);
|
|
75318
|
+
}
|
|
75246
75319
|
return entries;
|
|
75247
75320
|
}
|
|
75248
75321
|
function normalizeTimingMs(value) {
|
|
@@ -75285,16 +75358,19 @@ function materializeEntry(entry, lineNo) {
|
|
|
75285
75358
|
sourceRef: buildAgentLogSourceRef(entry.taskId, lineNo)
|
|
75286
75359
|
};
|
|
75287
75360
|
}
|
|
75288
|
-
function
|
|
75361
|
+
function inspectAgentLogTail(filePath) {
|
|
75289
75362
|
if (!existsSync16(filePath)) {
|
|
75290
|
-
return 0;
|
|
75363
|
+
return { lineCount: 0, endsWithNewline: true };
|
|
75291
75364
|
}
|
|
75292
75365
|
const content = readFileSync7(filePath, "utf8");
|
|
75293
75366
|
if (content.length === 0) {
|
|
75294
|
-
return 0;
|
|
75367
|
+
return { lineCount: 0, endsWithNewline: true };
|
|
75295
75368
|
}
|
|
75296
75369
|
const lines = content.split("\n");
|
|
75297
|
-
return
|
|
75370
|
+
return {
|
|
75371
|
+
lineCount: lines.at(-1) === "" ? lines.length - 1 : lines.length,
|
|
75372
|
+
endsWithNewline: content.endsWith("\n")
|
|
75373
|
+
};
|
|
75298
75374
|
}
|
|
75299
75375
|
function pruneAgentLogFiles(tasksDir, retentionDays, scanTaskIds) {
|
|
75300
75376
|
if (!Number.isFinite(retentionDays) || retentionDays <= 0 || !existsSync16(tasksDir)) {
|
|
@@ -79084,6 +79160,10 @@ ${reasonText}` : void 0)].filter(Boolean).join("\n\n") || void 0;
|
|
|
79084
79160
|
* FNXC:Missions 2026-07-11-12:35:
|
|
79085
79161
|
* A generated fix can also supersede itself once its own validator/loop evidence has passed.
|
|
79086
79162
|
* Reconciliation treats that as terminal evidence so a completed fix does not stay active only because its ancestor previously failed.
|
|
79163
|
+
*
|
|
79164
|
+
* FNXC:Missions 2026-09-05-22:07:
|
|
79165
|
+
* Superseding a generated fix means it is no longer needed, not that it passed validation.
|
|
79166
|
+
* Never stamp unearned validator evidence because that re-arms the reconciler's own trigger (issue #3574).
|
|
79087
79167
|
*/
|
|
79088
79168
|
reconcileSupersededGeneratedFixFeatures(sliceId) {
|
|
79089
79169
|
const features = this.listFeatures(sliceId);
|
|
@@ -79108,7 +79188,42 @@ ${reasonText}` : void 0)].filter(Boolean).join("\n\n") || void 0;
|
|
|
79108
79188
|
ancestorPassedMemo.set(feature.id, passed);
|
|
79109
79189
|
return passed;
|
|
79110
79190
|
};
|
|
79111
|
-
const
|
|
79191
|
+
const isFabricatedMarker = (feature) => Boolean(
|
|
79192
|
+
(feature.generatedFromFeatureId || feature.generatedFromRunId) && feature.lastValidatorStatus === "passed" && !feature.lastValidatorRunId && !feature.taskId && feature.status === "done" && !hasPassedAncestor(feature)
|
|
79193
|
+
);
|
|
79194
|
+
const repairedFeatureIds = features.filter(isFabricatedMarker).map((feature) => feature.id);
|
|
79195
|
+
const repairedIds = new Set(repairedFeatureIds);
|
|
79196
|
+
const supersededFeatureIds = features.filter((feature) => !repairedIds.has(feature.id)).filter((feature) => feature.generatedFromFeatureId && (featureHasPassed(feature) || hasPassedAncestor(feature))).filter((feature) => feature.status !== "done" || feature.loopState !== "passed" || feature.taskId).map((feature) => feature.id);
|
|
79197
|
+
if (repairedFeatureIds.length > 0) {
|
|
79198
|
+
this.db.transaction(() => {
|
|
79199
|
+
const hasCurrentPassedAncestor = (feature, seen = /* @__PURE__ */ new Set()) => {
|
|
79200
|
+
const sourceFeatureId = feature.generatedFromFeatureId;
|
|
79201
|
+
if (!sourceFeatureId || seen.has(sourceFeatureId)) return false;
|
|
79202
|
+
seen.add(sourceFeatureId);
|
|
79203
|
+
const sourceFeature = this.getFeature(sourceFeatureId);
|
|
79204
|
+
return featureHasPassed(sourceFeature) || (sourceFeature ? hasCurrentPassedAncestor(sourceFeature, seen) : false);
|
|
79205
|
+
};
|
|
79206
|
+
const isCurrentFabricatedMarker = (feature) => Boolean(
|
|
79207
|
+
(feature.generatedFromFeatureId || feature.generatedFromRunId) && feature.lastValidatorStatus === "passed" && !feature.lastValidatorRunId && !feature.taskId && feature.status === "done" && !hasCurrentPassedAncestor(feature)
|
|
79208
|
+
);
|
|
79209
|
+
for (const featureId of repairedFeatureIds) {
|
|
79210
|
+
const feature = this.getFeature(featureId);
|
|
79211
|
+
if (!feature || !isCurrentFabricatedMarker(feature)) continue;
|
|
79212
|
+
this.updateFeature(featureId, { status: "defined", taskId: void 0, loopState: "idle", lastValidatorStatus: void 0 });
|
|
79213
|
+
const slice = this.getSlice(feature.sliceId);
|
|
79214
|
+
const milestone = slice ? this.getMilestone(slice.milestoneId) : void 0;
|
|
79215
|
+
if (milestone) {
|
|
79216
|
+
this.logMissionEvent(milestone.missionId, "feature_status_changed", `Feature status changed from ${feature.status} to defined`, {
|
|
79217
|
+
featureId,
|
|
79218
|
+
field: "status",
|
|
79219
|
+
from: feature.status,
|
|
79220
|
+
to: "defined",
|
|
79221
|
+
source: "superseded-fix-marker-repair"
|
|
79222
|
+
});
|
|
79223
|
+
}
|
|
79224
|
+
}
|
|
79225
|
+
});
|
|
79226
|
+
}
|
|
79112
79227
|
if (supersededFeatureIds.length > 0) {
|
|
79113
79228
|
this.db.transaction(() => {
|
|
79114
79229
|
for (const featureId of supersededFeatureIds) {
|
|
@@ -79117,8 +79232,7 @@ ${reasonText}` : void 0)].filter(Boolean).join("\n\n") || void 0;
|
|
|
79117
79232
|
this.updateFeature(featureId, {
|
|
79118
79233
|
status: "done",
|
|
79119
79234
|
taskId: void 0,
|
|
79120
|
-
loopState: "passed"
|
|
79121
|
-
lastValidatorStatus: "passed"
|
|
79235
|
+
loopState: "passed"
|
|
79122
79236
|
});
|
|
79123
79237
|
if (feature.taskId) {
|
|
79124
79238
|
this.db.prepare('UPDATE tasks SET missionId = NULL, sliceId = NULL WHERE id = ? AND "deletedAt" IS NULL').run(feature.taskId);
|
|
@@ -79128,7 +79242,9 @@ ${reasonText}` : void 0)].filter(Boolean).join("\n\n") || void 0;
|
|
|
79128
79242
|
}
|
|
79129
79243
|
return {
|
|
79130
79244
|
supersededCount: supersededFeatureIds.length,
|
|
79131
|
-
featureIds: supersededFeatureIds
|
|
79245
|
+
featureIds: supersededFeatureIds,
|
|
79246
|
+
repairedCount: repairedFeatureIds.length,
|
|
79247
|
+
repairedFeatureIds
|
|
79132
79248
|
};
|
|
79133
79249
|
}
|
|
79134
79250
|
/**
|
|
@@ -82472,10 +82588,12 @@ var init_async_mission_store = __esm({
|
|
|
82472
82588
|
throw new RepairNotEligibleError(featureId, options.action);
|
|
82473
82589
|
}
|
|
82474
82590
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
82591
|
+
const hasUnvalidatedMarker = options.action === "clear" && feature.lastValidatorStatus === "passed" && !feature.lastValidatorRunId;
|
|
82592
|
+
const appliesUnvalidatedMarker = hasUnvalidatedMarker && Boolean(feature.generatedFromFeatureId || feature.generatedFromRunId) && !feature.taskId && feature.status === "done";
|
|
82475
82593
|
const priorLoopState = feature.loopState;
|
|
82476
82594
|
const priorStatus = feature.status;
|
|
82477
82595
|
let groundTruthMetadata = {};
|
|
82478
|
-
if (options.action === "clear" && feature.status === "blocked") {
|
|
82596
|
+
if (options.action === "clear" && (feature.status === "blocked" || appliesUnvalidatedMarker)) {
|
|
82479
82597
|
const fence = options.groundTruth;
|
|
82480
82598
|
if (!fence || fence.featureId !== featureId || fence.taskId !== (feature.taskId ?? null)) {
|
|
82481
82599
|
throw new RepairGroundTruthStaleError(featureId);
|
|
@@ -82505,14 +82623,14 @@ var init_async_mission_store = __esm({
|
|
|
82505
82623
|
let updated;
|
|
82506
82624
|
let run;
|
|
82507
82625
|
if (options.action === "clear") {
|
|
82508
|
-
const currentLoop = feature.loopState;
|
|
82509
|
-
const appliesLoop = currentLoop === "blocked" || currentLoop === "needs_fix";
|
|
82626
|
+
const currentLoop = feature.loopState ?? "idle";
|
|
82627
|
+
const appliesLoop = currentLoop === "blocked" || currentLoop === "needs_fix" || appliesUnvalidatedMarker;
|
|
82510
82628
|
const nextLoop = appliesLoop ? options.resolvedLoopState ?? "idle" : currentLoop;
|
|
82511
82629
|
if (appliesLoop && !FEATURE_LOOP_REPAIR_TRANSITIONS[currentLoop].includes(nextLoop)) {
|
|
82512
82630
|
throw new Error(`Invalid validation repair transition from '${currentLoop}' to '${nextLoop}'`);
|
|
82513
82631
|
}
|
|
82514
|
-
const appliesStatus = feature.status === "blocked";
|
|
82515
|
-
const nextStatus = appliesStatus ? options.resolvedStatus : feature.status;
|
|
82632
|
+
const appliesStatus = feature.status === "blocked" || appliesUnvalidatedMarker;
|
|
82633
|
+
const nextStatus = appliesUnvalidatedMarker ? "defined" : appliesStatus ? options.resolvedStatus : feature.status;
|
|
82516
82634
|
if (appliesStatus && (nextStatus !== "in-progress" && nextStatus !== "triaged" && nextStatus !== "defined")) {
|
|
82517
82635
|
throw new Error("Validation repair requires resolvedStatus of in-progress, triaged, or defined");
|
|
82518
82636
|
}
|
|
@@ -82524,13 +82642,13 @@ var init_async_mission_store = __esm({
|
|
|
82524
82642
|
const matchesLane = nextStatus === "in-progress" && fence.taskLiveness === "live" && fence.laneRole === "wip" || nextStatus === "triaged" && fence.taskLiveness === "live" && fence.laneRole === "planner" || nextStatus === "defined" && fence.taskLiveness === "absent" && fence.laneRole === "none";
|
|
82525
82643
|
if (!matchesLane) throw new RepairGroundTruthStaleError(featureId);
|
|
82526
82644
|
}
|
|
82527
|
-
if (!appliesLoop && !appliesStatus) throw new RepairNotEligibleError(featureId, options.action);
|
|
82645
|
+
if (!appliesLoop && !appliesStatus && !hasUnvalidatedMarker) throw new RepairNotEligibleError(featureId, options.action);
|
|
82528
82646
|
updated = {
|
|
82529
82647
|
...feature,
|
|
82530
82648
|
loopState: nextLoop,
|
|
82531
82649
|
status: nextStatus,
|
|
82532
82650
|
implementationAttemptCount: 0,
|
|
82533
|
-
...feature.lastValidatorStatus === "blocked" || feature.lastValidatorStatus === "failed" ? { lastValidatorStatus: void 0 } : {},
|
|
82651
|
+
...feature.lastValidatorStatus === "blocked" || feature.lastValidatorStatus === "failed" || hasUnvalidatedMarker ? { lastValidatorStatus: void 0 } : {},
|
|
82534
82652
|
updatedAt: now
|
|
82535
82653
|
};
|
|
82536
82654
|
await updateFeature(tx, updated);
|
|
@@ -82998,10 +83116,60 @@ ${reasonText}` : void 0)].filter(Boolean).join("\n\n") || void 0,
|
|
|
82998
83116
|
const source = byId.get(sourceId);
|
|
82999
83117
|
return passed(source) || (source ? hasPassedAncestor(source, seen) : false);
|
|
83000
83118
|
};
|
|
83119
|
+
const isFabricatedMarker = (feature) => Boolean(
|
|
83120
|
+
(feature.generatedFromFeatureId || feature.generatedFromRunId) && feature.lastValidatorStatus === "passed" && !feature.lastValidatorRunId && !feature.taskId && feature.status === "done" && !hasPassedAncestor(feature)
|
|
83121
|
+
);
|
|
83122
|
+
const repairIds = new Set(features.filter(isFabricatedMarker).map((feature) => feature.id));
|
|
83001
83123
|
const ids = [];
|
|
83002
83124
|
for (const feature of features) {
|
|
83003
|
-
if (!feature.generatedFromFeatureId || !(passed(feature) || hasPassedAncestor(feature))) continue;
|
|
83004
|
-
if (feature.status !== "done" || feature.loopState !== "passed" || feature.
|
|
83125
|
+
if (repairIds.has(feature.id) || !feature.generatedFromFeatureId || !(passed(feature) || hasPassedAncestor(feature))) continue;
|
|
83126
|
+
if (feature.status !== "done" || feature.loopState !== "passed" || feature.taskId) ids.push(feature.id);
|
|
83127
|
+
}
|
|
83128
|
+
const repairedFeatureIds = [];
|
|
83129
|
+
if (repairIds.size > 0) {
|
|
83130
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
83131
|
+
const repaired = await this.layer.transactionImmediate(async (tx) => {
|
|
83132
|
+
const locked = await tx.select({ id: project_exports.missionFeatures.id }).from(project_exports.missionFeatures).where(inArray(project_exports.missionFeatures.id, [...repairIds])).for("update");
|
|
83133
|
+
const preImages = locked.length > 0 ? await listFeaturesByIds(tx, locked.map((row) => row.id)) : [];
|
|
83134
|
+
const currentById = new Map(preImages.map((feature) => [feature.id, feature]));
|
|
83135
|
+
let missingAncestorIds = [...new Set(preImages.map((feature) => feature.generatedFromFeatureId).filter((id) => Boolean(id) && !currentById.has(id)))];
|
|
83136
|
+
while (missingAncestorIds.length > 0) {
|
|
83137
|
+
const lockedAncestors = await tx.select({ id: project_exports.missionFeatures.id }).from(project_exports.missionFeatures).where(inArray(project_exports.missionFeatures.id, missingAncestorIds)).for("update");
|
|
83138
|
+
const ancestors = lockedAncestors.length > 0 ? await listFeaturesByIds(tx, lockedAncestors.map((row) => row.id)) : [];
|
|
83139
|
+
for (const ancestor of ancestors) currentById.set(ancestor.id, ancestor);
|
|
83140
|
+
missingAncestorIds = [...new Set(ancestors.map((ancestor) => ancestor.generatedFromFeatureId).filter((id) => Boolean(id) && !currentById.has(id)))];
|
|
83141
|
+
}
|
|
83142
|
+
const hasCurrentPassedAncestor = (feature, seen = /* @__PURE__ */ new Set()) => {
|
|
83143
|
+
const sourceId = feature.generatedFromFeatureId;
|
|
83144
|
+
if (!sourceId || seen.has(sourceId)) return false;
|
|
83145
|
+
seen.add(sourceId);
|
|
83146
|
+
const source = currentById.get(sourceId);
|
|
83147
|
+
return passed(source) || (source ? hasCurrentPassedAncestor(source, seen) : false);
|
|
83148
|
+
};
|
|
83149
|
+
const changed = preImages.filter((feature) => Boolean(
|
|
83150
|
+
(feature.generatedFromFeatureId || feature.generatedFromRunId) && feature.lastValidatorStatus === "passed" && !feature.lastValidatorRunId && !feature.taskId && feature.status === "done" && !hasCurrentPassedAncestor(feature)
|
|
83151
|
+
));
|
|
83152
|
+
if (changed.length === 0) return { events: [], features: [] };
|
|
83153
|
+
await tx.update(project_exports.missionFeatures).set({
|
|
83154
|
+
status: "defined",
|
|
83155
|
+
taskId: null,
|
|
83156
|
+
loopState: "idle",
|
|
83157
|
+
lastValidatorStatus: null,
|
|
83158
|
+
updatedAt: now
|
|
83159
|
+
}).where(inArray(project_exports.missionFeatures.id, changed.map((feature) => feature.id)));
|
|
83160
|
+
let seq = await getMaxEventSeq(tx);
|
|
83161
|
+
const events = [];
|
|
83162
|
+
for (const feature of changed) {
|
|
83163
|
+
const event = await this.recordFeatureStatusChange(tx, feature, "defined", { type: "system", id: "mission-store", source: "superseded-fix-marker-repair" }, void 0, ++seq);
|
|
83164
|
+
if (event) events.push(event);
|
|
83165
|
+
}
|
|
83166
|
+
return { events, features: changed };
|
|
83167
|
+
});
|
|
83168
|
+
for (const event of repaired.events) this.emit("mission:event", event);
|
|
83169
|
+
for (const feature of repaired.features) {
|
|
83170
|
+
this.emit("feature:updated", { ...feature, status: "defined", taskId: void 0, loopState: "idle", lastValidatorStatus: void 0, updatedAt: now });
|
|
83171
|
+
repairedFeatureIds.push(feature.id);
|
|
83172
|
+
}
|
|
83005
83173
|
}
|
|
83006
83174
|
if (ids.length > 0) {
|
|
83007
83175
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
@@ -83009,13 +83177,12 @@ ${reasonText}` : void 0)].filter(Boolean).join("\n\n") || void 0,
|
|
|
83009
83177
|
const locked = await tx.select({ id: project_exports.missionFeatures.id }).from(project_exports.missionFeatures).where(inArray(project_exports.missionFeatures.id, ids)).for("update");
|
|
83010
83178
|
const lockedIds = locked.map((row) => row.id);
|
|
83011
83179
|
const preImages = lockedIds.length > 0 ? await listFeaturesByIds(tx, lockedIds) : [];
|
|
83012
|
-
const changed = preImages.filter((feature) => feature.status !== "done" || feature.loopState !== "passed" || feature.
|
|
83180
|
+
const changed = preImages.filter((feature) => feature.status !== "done" || feature.loopState !== "passed" || feature.taskId);
|
|
83013
83181
|
if (changed.length === 0) return { events: [], updatedFeatures: [] };
|
|
83014
83182
|
await tx.update(project_exports.missionFeatures).set({
|
|
83015
83183
|
status: "done",
|
|
83016
83184
|
taskId: null,
|
|
83017
83185
|
loopState: "passed",
|
|
83018
|
-
lastValidatorStatus: "passed",
|
|
83019
83186
|
updatedAt: now
|
|
83020
83187
|
}).where(inArray(project_exports.missionFeatures.id, changed.map((feature) => feature.id)));
|
|
83021
83188
|
let seq = await getMaxEventSeq(tx);
|
|
@@ -83030,13 +83197,14 @@ ${reasonText}` : void 0)].filter(Boolean).join("\n\n") || void 0,
|
|
|
83030
83197
|
});
|
|
83031
83198
|
for (const event of events) this.emit("mission:event", event);
|
|
83032
83199
|
for (const feature of updatedFeatures) {
|
|
83033
|
-
const updated = { ...feature, status: "done", taskId: void 0, loopState: "passed",
|
|
83200
|
+
const updated = { ...feature, status: "done", taskId: void 0, loopState: "passed", updatedAt: now };
|
|
83034
83201
|
this.emit("feature:updated", updated);
|
|
83035
83202
|
if (feature.taskId) await clearTaskMissionLinkage(this.db, feature.taskId);
|
|
83036
83203
|
}
|
|
83037
83204
|
if (updatedFeatures.length > 0) await this.recomputeSliceStatus(sliceId);
|
|
83038
83205
|
}
|
|
83039
|
-
|
|
83206
|
+
if (repairedFeatureIds.length > 0) await this.recomputeSliceStatus(sliceId);
|
|
83207
|
+
return { supersededCount: ids.length, featureIds: ids, repairedCount: repairedFeatureIds.length, repairedFeatureIds };
|
|
83040
83208
|
}
|
|
83041
83209
|
async transitionLoopState(featureId, newState) {
|
|
83042
83210
|
const feature = await getFeature(this.db, featureId);
|
|
@@ -88668,19 +88836,81 @@ async function archiveAllDoneImpl(store, options) {
|
|
|
88668
88836
|
for (const column of completeColumns) {
|
|
88669
88837
|
for (const task of await store.listTasks({ slim: true, column })) doneById.set(task.id, task);
|
|
88670
88838
|
}
|
|
88671
|
-
|
|
88672
|
-
|
|
88673
|
-
|
|
88839
|
+
if (doneById.size === 0) return { archived: [], skipped: [] };
|
|
88840
|
+
const archivedColumns = await resolveArchivedLanes(store).catch(() => void 0) ?? new Set(LEGACY_ARCHIVE_LANES);
|
|
88841
|
+
const liveTasks = (await store.listTasks({ slim: true, includeArchived: false })).filter((task) => !task.deletedAt && !archivedColumns.has(task.column));
|
|
88842
|
+
const childrenByParentId = /* @__PURE__ */ new Map();
|
|
88843
|
+
for (const task of liveTasks) {
|
|
88844
|
+
if (!task.sourceParentTaskId) continue;
|
|
88845
|
+
const children = childrenByParentId.get(task.sourceParentTaskId) ?? [];
|
|
88846
|
+
children.push(task.id);
|
|
88847
|
+
childrenByParentId.set(task.sourceParentTaskId, children);
|
|
88848
|
+
}
|
|
88849
|
+
for (const children of childrenByParentId.values()) children.sort();
|
|
88850
|
+
const bypassLineageGate = options?.removeLineageReferences === true;
|
|
88851
|
+
const archivableIds = new Set(doneById.keys());
|
|
88852
|
+
const skipped = [];
|
|
88853
|
+
if (!bypassLineageGate) {
|
|
88854
|
+
let firstRemovalWave = true;
|
|
88855
|
+
while (true) {
|
|
88856
|
+
const removals = [...archivableIds].filter(
|
|
88857
|
+
(id) => (childrenByParentId.get(id) ?? []).some((childId) => !archivableIds.has(childId))
|
|
88858
|
+
).sort();
|
|
88859
|
+
if (removals.length === 0) break;
|
|
88860
|
+
for (const id of removals) {
|
|
88861
|
+
const blockers = (childrenByParentId.get(id) ?? []).filter((childId) => !archivableIds.has(childId));
|
|
88862
|
+
skipped.push({
|
|
88863
|
+
id,
|
|
88864
|
+
reason: firstRemovalWave ? "open-lineage-children" : "blocked-by-unarchived-batch-member",
|
|
88865
|
+
blockers,
|
|
88866
|
+
message: firstRemovalWave ? `Live lineage children prevent archiving ${id}.` : `Unarchived batch members prevent archiving ${id}.`
|
|
88867
|
+
});
|
|
88868
|
+
archivableIds.delete(id);
|
|
88869
|
+
}
|
|
88870
|
+
firstRemovalWave = false;
|
|
88871
|
+
}
|
|
88872
|
+
}
|
|
88873
|
+
const archived = [];
|
|
88874
|
+
const remaining = new Set(archivableIds);
|
|
88875
|
+
const archivedIds = /* @__PURE__ */ new Set();
|
|
88876
|
+
const archiveWave = async (ids) => {
|
|
88877
|
+
const settled = await Promise.allSettled(ids.map((id) => store.archiveTask(id, {
|
|
88878
|
+
cleanup: true,
|
|
88879
|
+
removeLineageReferences: options?.removeLineageReferences
|
|
88880
|
+
})));
|
|
88881
|
+
for (const [index2, result] of settled.entries()) {
|
|
88882
|
+
const id = ids[index2];
|
|
88883
|
+
if (result.status === "fulfilled") {
|
|
88884
|
+
archived.push(result.value);
|
|
88885
|
+
archivedIds.add(id);
|
|
88886
|
+
} else {
|
|
88887
|
+
const error2 = result.reason;
|
|
88888
|
+
const blockers = error2 instanceof TaskHasLineageChildrenError ? error2.childIds : error2 instanceof TaskHasDependentsError ? error2.dependentIds : [];
|
|
88889
|
+
skipped.push({ id, reason: "archive-failed", blockers, message: error2 instanceof Error ? error2.message : String(error2) });
|
|
88890
|
+
}
|
|
88891
|
+
remaining.delete(id);
|
|
88892
|
+
}
|
|
88893
|
+
};
|
|
88894
|
+
while (remaining.size > 0) {
|
|
88895
|
+
const wave = [...remaining].filter(
|
|
88896
|
+
(id) => bypassLineageGate ? (childrenByParentId.get(id) ?? []).filter((childId) => remaining.has(childId)).length === 0 : (childrenByParentId.get(id) ?? []).filter((childId) => archivableIds.has(childId)).every((childId) => archivedIds.has(childId))
|
|
88897
|
+
).sort();
|
|
88898
|
+
if (wave.length > 0) {
|
|
88899
|
+
await archiveWave(wave);
|
|
88900
|
+
continue;
|
|
88901
|
+
}
|
|
88902
|
+
if (bypassLineageGate) {
|
|
88903
|
+
await archiveWave([...remaining].sort());
|
|
88904
|
+
} else {
|
|
88905
|
+
for (const id of [...remaining].sort()) {
|
|
88906
|
+
const blockers = (childrenByParentId.get(id) ?? []).filter((childId) => remaining.has(childId) || !archivedIds.has(childId));
|
|
88907
|
+
skipped.push({ id, reason: "blocked-by-unarchived-batch-member", blockers, message: `Unarchived batch members prevent archiving ${id}.` });
|
|
88908
|
+
remaining.delete(id);
|
|
88909
|
+
}
|
|
88910
|
+
}
|
|
88674
88911
|
}
|
|
88675
|
-
|
|
88676
|
-
|
|
88677
|
-
(task) => store.archiveTask(task.id, {
|
|
88678
|
-
cleanup: true,
|
|
88679
|
-
removeLineageReferences: options?.removeLineageReferences
|
|
88680
|
-
})
|
|
88681
|
-
)
|
|
88682
|
-
);
|
|
88683
|
-
return archivedTasks3;
|
|
88912
|
+
skipped.sort((left, right) => left.id.localeCompare(right.id));
|
|
88913
|
+
return { archived, skipped };
|
|
88684
88914
|
}
|
|
88685
88915
|
async function resolveUnarchiveTargetColumnImpl(store, preArchiveColumn, taskId) {
|
|
88686
88916
|
const lifecycle = taskId ? await resolveTaskLifecycleColumns(store, taskId) : void 0;
|
|
@@ -89144,6 +89374,7 @@ var init_task_artifacts_ops = __esm({
|
|
|
89144
89374
|
init_types();
|
|
89145
89375
|
init_store();
|
|
89146
89376
|
init_project_lane_vocabulary();
|
|
89377
|
+
init_errors3();
|
|
89147
89378
|
LEGACY_ARCHIVE_LANES = ["archived"];
|
|
89148
89379
|
}
|
|
89149
89380
|
});
|
|
@@ -107015,18 +107246,18 @@ function hasFreshAgentLogActivitySinceTaskUpdate(store, task, now, reviewColumns
|
|
|
107015
107246
|
}
|
|
107016
107247
|
return Math.max(0, now - latestAgentLogMs) < DEFAULT_STALE_MERGING_MIN_AGE_MS;
|
|
107017
107248
|
}
|
|
107018
|
-
async function resolveHoldColumnForTask(store, taskId, cache) {
|
|
107249
|
+
async function resolveHoldColumnForTask(store, taskId, cache, selectionCache) {
|
|
107019
107250
|
try {
|
|
107020
|
-
const lifecycle = resolveLifecycleColumns(await resolveWorkflowIrForTask(store, taskId, cache));
|
|
107251
|
+
const lifecycle = resolveLifecycleColumns(await resolveWorkflowIrForTask(store, taskId, cache, selectionCache));
|
|
107021
107252
|
return lifecycle?.hold ?? "todo";
|
|
107022
107253
|
} catch {
|
|
107023
107254
|
return "todo";
|
|
107024
107255
|
}
|
|
107025
107256
|
}
|
|
107026
|
-
async function resolveReviewColumnsForTask(store, taskId, cache) {
|
|
107257
|
+
async function resolveReviewColumnsForTask(store, taskId, cache, selectionCache) {
|
|
107027
107258
|
const columns = /* @__PURE__ */ new Set(["in-review"]);
|
|
107028
107259
|
try {
|
|
107029
|
-
const ir2 = await resolveWorkflowIrForTask(store, taskId, cache);
|
|
107260
|
+
const ir2 = await resolveWorkflowIrForTask(store, taskId, cache, selectionCache);
|
|
107030
107261
|
if (ir2) for (const id of resolveReviewColumns(ir2)) columns.add(id);
|
|
107031
107262
|
} catch {
|
|
107032
107263
|
}
|
|
@@ -107147,6 +107378,12 @@ async function listTasksImpl(store, options) {
|
|
|
107147
107378
|
const settings = await store.getSettingsFast();
|
|
107148
107379
|
const mergeQueuedTaskIds = await store.getMergeQueuedTaskIdsAsync();
|
|
107149
107380
|
const listPassIrCache = /* @__PURE__ */ new Map();
|
|
107381
|
+
const listPassSelectionCache = options?.selectionCache ?? /* @__PURE__ */ new Map();
|
|
107382
|
+
const listSelectionReads = await prefetchWorkflowSelections(store, filteredRows.map((row) => String(row.id)), listPassSelectionCache);
|
|
107383
|
+
if (options?.selectionReadTally) {
|
|
107384
|
+
options.selectionReadTally.batched += listSelectionReads.batched;
|
|
107385
|
+
options.selectionReadTally.singles += listSelectionReads.singles;
|
|
107386
|
+
}
|
|
107150
107387
|
const staleThresholds = {
|
|
107151
107388
|
inProgressWarningMs: settings.staleInProgressWarningMs,
|
|
107152
107389
|
inProgressCriticalMs: settings.staleInProgressCriticalMs,
|
|
@@ -107157,7 +107394,7 @@ async function listTasksImpl(store, options) {
|
|
|
107157
107394
|
const row = store.pgRowToTaskRow(pgRow);
|
|
107158
107395
|
const task = store.rowToTask(row);
|
|
107159
107396
|
const isMergeQueued = mergeQueuedTaskIds.has(task.id);
|
|
107160
|
-
const reviewColumnsForRow = await resolveReviewColumnsForTask(store, task.id, listPassIrCache);
|
|
107397
|
+
const reviewColumnsForRow = await resolveReviewColumnsForTask(store, task.id, listPassIrCache, listPassSelectionCache);
|
|
107161
107398
|
const hasFreshAgentLogActivity = hasFreshAgentLogActivitySinceTaskUpdate(store, task, now, reviewColumnsForRow);
|
|
107162
107399
|
const executingTaskIds = hasFreshAgentLogActivity ? /* @__PURE__ */ new Set([task.id]) : void 0;
|
|
107163
107400
|
task.inReviewStall = isMergeQueued ? void 0 : getInReviewStallReason(task, {
|
|
@@ -107189,7 +107426,7 @@ async function listTasksImpl(store, options) {
|
|
|
107189
107426
|
thresholdMs: settings.stalePausedTodoThresholdMs,
|
|
107190
107427
|
// Paused-only (the signal is a no-op otherwise), sharing the list-pass
|
|
107191
107428
|
// IR cache so one workflow is read once per pass, not once per card.
|
|
107192
|
-
holdColumn: task.paused === true ? await resolveHoldColumnForTask(store, task.id, listPassIrCache) : void 0,
|
|
107429
|
+
holdColumn: task.paused === true ? await resolveHoldColumnForTask(store, task.id, listPassIrCache, listPassSelectionCache) : void 0,
|
|
107193
107430
|
engineActiveSinceMs: settings.engineActiveSinceMs,
|
|
107194
107431
|
engineActivationGraceMs: settings.engineActivationGraceMs
|
|
107195
107432
|
});
|
|
@@ -107199,7 +107436,7 @@ async function listTasksImpl(store, options) {
|
|
|
107199
107436
|
thresholds: staleThresholds,
|
|
107200
107437
|
engineActiveSinceMs: settings.engineActiveSinceMs,
|
|
107201
107438
|
engineActivationGraceMs: settings.engineActivationGraceMs,
|
|
107202
|
-
lifecycle: await resolveTaskLifecycleColumns(store, task.id, listPassIrCache)
|
|
107439
|
+
lifecycle: await resolveTaskLifecycleColumns(store, task.id, listPassIrCache, listPassSelectionCache)
|
|
107203
107440
|
});
|
|
107204
107441
|
} catch (err) {
|
|
107205
107442
|
if (!(err instanceof RangeError)) throw err;
|
|
@@ -107278,12 +107515,14 @@ async function listTasksModifiedSinceImpl(store, since, limit, opts) {
|
|
|
107278
107515
|
const lifecycleByTaskId = /* @__PURE__ */ new Map();
|
|
107279
107516
|
{
|
|
107280
107517
|
const irCache = /* @__PURE__ */ new Map();
|
|
107518
|
+
const selectionCache = /* @__PURE__ */ new Map();
|
|
107519
|
+
await prefetchWorkflowSelections(store, pageRows.map((row) => row.id), selectionCache);
|
|
107281
107520
|
for (const pgRow of pageRows) {
|
|
107282
107521
|
const row = store.pgRowToTaskRow(pgRow);
|
|
107283
|
-
reviewColumnsByTaskId.set(row.id, await resolveReviewColumnsForTask(store, row.id, irCache));
|
|
107284
|
-
lifecycleByTaskId.set(row.id, await resolveTaskLifecycleColumns(store, row.id, irCache));
|
|
107522
|
+
reviewColumnsByTaskId.set(row.id, await resolveReviewColumnsForTask(store, row.id, irCache, selectionCache));
|
|
107523
|
+
lifecycleByTaskId.set(row.id, await resolveTaskLifecycleColumns(store, row.id, irCache, selectionCache));
|
|
107285
107524
|
if (store.rowToTask(row).paused !== true) continue;
|
|
107286
|
-
holdColumnByTaskId.set(row.id, await resolveHoldColumnForTask(store, row.id, irCache));
|
|
107525
|
+
holdColumnByTaskId.set(row.id, await resolveHoldColumnForTask(store, row.id, irCache, selectionCache));
|
|
107287
107526
|
}
|
|
107288
107527
|
}
|
|
107289
107528
|
const tasks2 = pageRows.map((pgRow) => {
|
|
@@ -107401,10 +107640,12 @@ async function searchTasksImpl(store, query, options) {
|
|
|
107401
107640
|
const settings = await store.getSettingsFast();
|
|
107402
107641
|
const mergeQueuedTaskIds = await store.getMergeQueuedTaskIdsAsync();
|
|
107403
107642
|
const searchPassIrCache = /* @__PURE__ */ new Map();
|
|
107643
|
+
const searchPassSelectionCache = /* @__PURE__ */ new Map();
|
|
107644
|
+
await prefetchWorkflowSelections(store, pgRows.map((row) => String(row.id)), searchPassSelectionCache);
|
|
107404
107645
|
const tasks2 = await Promise.all(pgRows.map(async (pgRow) => {
|
|
107405
107646
|
const task = store.rowToTask(store.pgRowToTaskRow(pgRow));
|
|
107406
107647
|
const isMergeQueued = mergeQueuedTaskIds.has(task.id);
|
|
107407
|
-
const reviewColumnsForRow = await resolveReviewColumnsForTask(store, task.id, searchPassIrCache);
|
|
107648
|
+
const reviewColumnsForRow = await resolveReviewColumnsForTask(store, task.id, searchPassIrCache, searchPassSelectionCache);
|
|
107408
107649
|
const hasFreshAgentLogActivity = hasFreshAgentLogActivitySinceTaskUpdate(store, task, now, reviewColumnsForRow);
|
|
107409
107650
|
const executingTaskIds = hasFreshAgentLogActivity ? /* @__PURE__ */ new Set([task.id]) : void 0;
|
|
107410
107651
|
task.inReviewStall = isMergeQueued ? void 0 : getInReviewStallReason(task, {
|
|
@@ -108644,6 +108885,7 @@ var init_store = __esm({
|
|
|
108644
108885
|
init_async_self_healing();
|
|
108645
108886
|
init_task_merge();
|
|
108646
108887
|
init_required_pre_merge_steps();
|
|
108888
|
+
init_pre_merge_approval();
|
|
108647
108889
|
init_logger2();
|
|
108648
108890
|
init_project_root_guard();
|
|
108649
108891
|
init_agent_store();
|
|
@@ -110409,29 +110651,35 @@ var init_store = __esm({
|
|
|
110409
110651
|
const results = task.workflowStepResults ?? [];
|
|
110410
110652
|
const failedTarget = getLatestFailedPreMergeReviewStep(task);
|
|
110411
110653
|
const reviewIrForBypass = failedTarget ? void 0 : await resolveWorkflowIrForTask(this, task.id);
|
|
110412
|
-
const
|
|
110413
|
-
|
|
110654
|
+
const requiredForBypass = reviewIrForBypass ? [...resolveRequiredPreMergeStepIds(reviewIrForBypass, task.enabledWorkflowSteps, task)] : [];
|
|
110655
|
+
const absentStepId = requiredForBypass.find((workflowStepId) => !results.some((result) => result.workflowStepId === workflowStepId));
|
|
110656
|
+
const unapprovedTarget = !failedTarget && !absentStepId && requiredForBypass.length > 0 ? (() => {
|
|
110657
|
+
const blocked = evaluatePreMergeApprovals(task, { requiredPreMergeStepIds: new Set(requiredForBypass) }).find((approval) => approval.state !== "approved");
|
|
110658
|
+
return blocked ? results.find((result) => result.workflowStepId === blocked.workflowStepId) : void 0;
|
|
110659
|
+
})() : void 0;
|
|
110660
|
+
if (!failedTarget && !absentStepId && !unapprovedTarget) {
|
|
110414
110661
|
throw new Error(`Cannot bypass review lane for ${id}: no failed pre-merge review step found`);
|
|
110415
110662
|
}
|
|
110416
|
-
const target = failedTarget ?? {
|
|
110663
|
+
const target = failedTarget ?? unapprovedTarget ?? {
|
|
110417
110664
|
workflowStepId: absentStepId,
|
|
110418
110665
|
workflowStepName: absentStepId,
|
|
110419
110666
|
phase: "pre-merge",
|
|
110420
110667
|
status: "absent"
|
|
110421
110668
|
};
|
|
110422
|
-
const
|
|
110423
|
-
|
|
110669
|
+
const rowTarget = failedTarget ?? unapprovedTarget;
|
|
110670
|
+
const targetIndex = rowTarget ? results.indexOf(rowTarget) : -1;
|
|
110671
|
+
if (rowTarget && targetIndex === -1) {
|
|
110424
110672
|
throw new Error(`Cannot bypass review lane for ${id}: failed step result not found`);
|
|
110425
110673
|
}
|
|
110426
110674
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
110427
|
-
const bypassed =
|
|
110428
|
-
...
|
|
110675
|
+
const bypassed = rowTarget ? {
|
|
110676
|
+
...rowTarget,
|
|
110429
110677
|
status: "skipped",
|
|
110430
110678
|
bypassedBy: actor,
|
|
110431
110679
|
bypassedAt: now,
|
|
110432
110680
|
bypassReason: reason,
|
|
110433
|
-
bypassedFromStatus:
|
|
110434
|
-
bypassedFromVerdict:
|
|
110681
|
+
bypassedFromStatus: rowTarget.remediationArchivedFromStatus ?? rowTarget.status,
|
|
110682
|
+
bypassedFromVerdict: rowTarget.verdict ?? rowTarget.priorAttempts?.[0]?.verdict
|
|
110435
110683
|
} : {
|
|
110436
110684
|
workflowStepId: absentStepId,
|
|
110437
110685
|
workflowStepName: absentStepId,
|
|
@@ -110465,8 +110713,8 @@ var init_store = __esm({
|
|
|
110465
110713
|
metadata: {
|
|
110466
110714
|
workflowStepId: target.workflowStepId,
|
|
110467
110715
|
workflowStepName: target.workflowStepName,
|
|
110468
|
-
bypassedFromStatus:
|
|
110469
|
-
bypassedFromVerdict:
|
|
110716
|
+
bypassedFromStatus: bypassed.bypassedFromStatus,
|
|
110717
|
+
bypassedFromVerdict: bypassed.bypassedFromVerdict ?? null,
|
|
110470
110718
|
reason
|
|
110471
110719
|
}
|
|
110472
110720
|
});
|
|
@@ -110528,9 +110776,8 @@ var init_store = __esm({
|
|
|
110528
110776
|
);
|
|
110529
110777
|
}
|
|
110530
110778
|
if (target.status !== "pending") {
|
|
110531
|
-
|
|
110532
|
-
|
|
110533
|
-
);
|
|
110779
|
+
const archivedFailure = target.remediationArchivedAt != null && (target.remediationArchivedFromStatus === "failed" || target.remediationArchivedFromStatus === "advisory_failure") && !target.bypassedBy && !target.supersededAt;
|
|
110780
|
+
throw new Error(`Cannot resume workflow step for ${id}: only pending steps can be resumed${archivedFailure ? ` (step '${stepId}' is an archived remediation carrier \u2014 use fn_task_bypass_review / POST /tasks/:id/bypass-review to clear it)` : ""}`);
|
|
110534
110781
|
}
|
|
110535
110782
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
110536
110783
|
const resumed = {
|
|
@@ -141038,6 +141285,7 @@ __export(src_exports, {
|
|
|
141038
141285
|
CLOUD_TICKET_QUERY: () => CLOUD_TICKET_QUERY,
|
|
141039
141286
|
CODE_REVIEW_BLOCKING_SEVERITY_SETTING_ID: () => CODE_REVIEW_BLOCKING_SEVERITY_SETTING_ID,
|
|
141040
141287
|
CODE_REVIEW_MAX_REVISIONS_SETTING_ID: () => CODE_REVIEW_MAX_REVISIONS_SETTING_ID,
|
|
141288
|
+
COLLATERAL_ARCHIVED_REVIEW_GATE_DIAGNOSTIC: () => COLLATERAL_ARCHIVED_REVIEW_GATE_DIAGNOSTIC,
|
|
141041
141289
|
COLOR_THEMES: () => COLOR_THEMES,
|
|
141042
141290
|
COLUMNS: () => COLUMNS,
|
|
141043
141291
|
COLUMN_DESCRIPTIONS: () => COLUMN_DESCRIPTIONS,
|
|
@@ -141440,6 +141688,7 @@ __export(src_exports, {
|
|
|
141440
141688
|
SLICE_PLAN_STATES: () => SLICE_PLAN_STATES,
|
|
141441
141689
|
SLICE_STATUSES: () => SLICE_STATUSES,
|
|
141442
141690
|
SPEC_LOCK_PARSER_VERSION: () => SPEC_LOCK_PARSER_VERSION,
|
|
141691
|
+
STALE_CONTENT_APPROVAL_BLOCKER: () => STALE_CONTENT_APPROVAL_BLOCKER,
|
|
141443
141692
|
STALE_HIGH_FANOUT_BLOCKER_AGE_THRESHOLD_MS: () => STALE_HIGH_FANOUT_BLOCKER_AGE_THRESHOLD_MS,
|
|
141444
141693
|
STALLED_REVIEW_INVALID_TRANSITION_PATTERN: () => STALLED_REVIEW_INVALID_TRANSITION_PATTERN,
|
|
141445
141694
|
STALLED_REVIEW_INVALID_TRANSITION_THRESHOLD: () => STALLED_REVIEW_INVALID_TRANSITION_THRESHOLD,
|
|
@@ -142169,6 +142418,7 @@ __export(src_exports, {
|
|
|
142169
142418
|
isSqliteLockError: () => isSqliteLockError,
|
|
142170
142419
|
isSqliteMigrationComplete: () => isSqliteMigrationComplete,
|
|
142171
142420
|
isStaleBlockedByBlocker: () => isStaleBlockedByBlocker,
|
|
142421
|
+
isStaleContentApprovalBlocker: () => isStaleContentApprovalBlocker,
|
|
142172
142422
|
isStoredAuthCredential: () => isStoredAuthCredential,
|
|
142173
142423
|
isStrictDescendantPath: () => isStrictDescendantPath,
|
|
142174
142424
|
isTailscaleCgnatIpv4: () => isTailscaleCgnatIpv4,
|
|
@@ -142248,6 +142498,7 @@ __export(src_exports, {
|
|
|
142248
142498
|
mapRoleToCapability: () => mapRoleToCapability,
|
|
142249
142499
|
markDraftSummarized: () => markDraftSummarized,
|
|
142250
142500
|
markdownHtmlCommentUnits: () => markdownHtmlCommentUnits,
|
|
142501
|
+
matchStepHeadings: () => matchStepHeadings,
|
|
142251
142502
|
matchesFrontendUxPath: () => matchesFrontendUxPath,
|
|
142252
142503
|
matchesPatchnodeQuery: () => matchesPatchnodeQuery,
|
|
142253
142504
|
materializeMcpServerSecrets: () => materializeMcpServerSecrets,
|
|
@@ -142361,6 +142612,7 @@ __export(src_exports, {
|
|
|
142361
142612
|
postMergeOptionalGroupNode: () => postMergeOptionalGroupNode,
|
|
142362
142613
|
postMergeVerificationOptionalGroupNode: () => postMergeVerificationOptionalGroupNode,
|
|
142363
142614
|
postgresSchema: () => schema_exports,
|
|
142615
|
+
prefetchWorkflowSelections: () => prefetchWorkflowSelections,
|
|
142364
142616
|
prepareAgentCompaniesImport: () => prepareAgentCompaniesImport,
|
|
142365
142617
|
pricingAsOf: () => pricingAsOf,
|
|
142366
142618
|
probeGitCliStatus: () => probeGitCliStatus,
|
|
@@ -142472,6 +142724,7 @@ __export(src_exports, {
|
|
|
142472
142724
|
resolveBackupSettingsMigrationConflict: () => resolveBackupSettingsMigrationConflict,
|
|
142473
142725
|
resolveCapacityPoolId: () => resolveCapacityPoolId,
|
|
142474
142726
|
resolveClaudeCodeClientVersion: () => resolveClaudeCodeClientVersion,
|
|
142727
|
+
resolveCollateralArchivedReviewGate: () => resolveCollateralArchivedReviewGate,
|
|
142475
142728
|
resolveColumnAdjacency: () => resolveColumnAdjacency,
|
|
142476
142729
|
resolveColumnAgentBinding: () => resolveColumnAgentBinding,
|
|
142477
142730
|
resolveColumnCapacity: () => resolveColumnCapacity,
|
|
@@ -149531,7 +149784,10 @@ async function runHoldReleaseSweep(store, deps) {
|
|
|
149531
149784
|
const settings = await store.getSettings();
|
|
149532
149785
|
if (expired()) return logPreambleTruncation(0);
|
|
149533
149786
|
counters.tasks += 1;
|
|
149534
|
-
const
|
|
149787
|
+
const selectionReadTally = { batched: 0, singles: 0 };
|
|
149788
|
+
const allTasks = await store.listTasks({ includeArchived: false, selectionCache, selectionReadTally });
|
|
149789
|
+
counters.batchSelections += selectionReadTally.batched;
|
|
149790
|
+
counters.selections += selectionReadTally.singles;
|
|
149535
149791
|
if (expired()) return logPreambleTruncation(allTasks.length);
|
|
149536
149792
|
const effectiveWorkflowIdByTask = /* @__PURE__ */ new Map();
|
|
149537
149793
|
const missingIds = [...new Set(allTasks.map((task) => task.id))].filter((id) => !selectionCache.has(id));
|
|
@@ -152369,7 +152625,7 @@ var init_scheduler = __esm({
|
|
|
152369
152625
|
for (const slice of hierarchy.milestones.flatMap((milestone) => milestone.slices).filter((slice2) => slice2.status === "active")) {
|
|
152370
152626
|
try {
|
|
152371
152627
|
const superseded = await missionStore.reconcileSupersededGeneratedFixFeatures(slice.id);
|
|
152372
|
-
fixed += superseded.supersededCount;
|
|
152628
|
+
fixed += superseded.supersededCount + (superseded.repairedCount ?? 0);
|
|
152373
152629
|
if (superseded.supersededCount > 0) {
|
|
152374
152630
|
const refreshed = await missionStore.getSlice(slice.id);
|
|
152375
152631
|
if (refreshed?.status === "complete") {
|
|
@@ -152382,8 +152638,9 @@ var init_scheduler = __esm({
|
|
|
152382
152638
|
const supersededFeatureIds = new Set(superseded.featureIds ?? []);
|
|
152383
152639
|
const autoTriage = mission.autopilotEnabled === true || mission.autoAdvance === true;
|
|
152384
152640
|
for (const feature of features) {
|
|
152385
|
-
if (supersededFeatureIds.has(feature.id) || feature.taskId || !autoTriage || feature.status === "blocked") continue;
|
|
152641
|
+
if (supersededFeatureIds.has(feature.id) || feature.taskId || !autoTriage || feature.status === "blocked" || feature.status === "done") continue;
|
|
152386
152642
|
if (feature.status !== "defined" && this.isGeneratedFixFeature(feature)) {
|
|
152643
|
+
schedulerLog.warn(`Parking stranded generated Fix feature ${feature.id} from ${feature.status}: not triageable without a defined state`);
|
|
152387
152644
|
await missionStore.updateFeature(feature.id, { status: "blocked", loopState: "blocked", taskId: void 0 });
|
|
152388
152645
|
fixed++;
|
|
152389
152646
|
continue;
|
|
@@ -179652,7 +179909,7 @@ function extractMarkdownSection(document2, headingName) {
|
|
|
179652
179909
|
return document2.slice(start, end).trim();
|
|
179653
179910
|
}
|
|
179654
179911
|
function compactTaskPromptStepsSection(section) {
|
|
179655
|
-
const stepTitles =
|
|
179912
|
+
const stepTitles = matchStepHeadings(section).map(({ index: index2, headingLineEnd }) => section.slice(index2, headingLineEnd).trim());
|
|
179656
179913
|
if (stepTitles.length === 0) {
|
|
179657
179914
|
return section.trim();
|
|
179658
179915
|
}
|
|
@@ -190744,7 +191001,7 @@ ${lines.join("\n")}`
|
|
|
190744
191001
|
}
|
|
190745
191002
|
};
|
|
190746
191003
|
}
|
|
190747
|
-
var TASK_CREATE_PRIORITY_VALUES, missionLineageParams, taskCreateParams, taskLogParams, agentLogTypeParams, agentLogDetailModeParams, taskLogsReadParams, chatTaskLogsReadParams, taskListParams, taskShowParams, taskSearchParams,
|
|
191004
|
+
var TASK_CREATE_PRIORITY_VALUES, missionLineageParams, taskCreateParams, taskLogParams, agentLogTypeParams, agentLogDetailModeParams, taskLogsReadParams, chatTaskLogsReadParams, taskListParams, taskShowParams, taskSearchParams, historyReadParams, installWorktreeDependenciesParams, taskDocumentWriteParams, taskDocumentReadParams, taskPromptWriteParams, taskFileScopeAddParams, chatTaskDocumentWriteParams, chatTaskDocumentReadParams, ARTIFACT_TYPE_VALUES, artifactTypeSchema, artifactRegisterParams, artifactListParams, artifactViewParams, chatArtifactRegisterParams, chatArtifactListParams, workflowListParams, workflowGetParams, workflowSelectParams, taskPromoteParams, taskArchiveParams, taskDeleteParams, taskUnarchiveParams, taskRetryParams, taskPauseParams, taskUnpauseParams, taskDuplicateParams, taskMergeParams, taskAddDepParams, STEP_STATUSES, taskUpdateParams, workflowCreateParams, workflowValidateParams, workflowUpdateParams, workflowDeleteParams, workflowSettingsParams, traitListParams, reflectOnPerformanceParams, readEvaluationsParams, updateIdentityParams, goalListParams, goalShowParams, listAgentsParams, delegateTaskParams, taskAssignParams, getAgentConfigParams, updateAgentConfigParams, createAgentParams, deleteAgentParams, sendMessageParams, readMessagesParams, postRoomMessageParams, askQuestionParams, memorySearchParams, memoryGetParams, webFetchParams, researchRunParams, researchListParams, researchGetParams, researchCancelParams, researchRetryParams, memoryAppendParams, log25, MAX_INSTRUCTIONS_TEXT_LENGTH, MAX_MEMORY_LENGTH, MAX_SOUL_LENGTH, AGENT_MEMORY_ROOT3, AGENT_MEMORY_FILENAME2, AGENT_DREAMS_FILENAME2, agentQmdRefreshState, AGENT_QMD_REFRESH_INTERVAL_MS, DAILY_AGENT_MEMORY_RE2, SEMANTIC_TOOL_READ_MAX_CHARS, AGENT_LOG_READ_DETAIL_PREVIEW_MAX, ARTIFACT_FILE_MAX_BYTES, ARTIFACT_EXTENSION_MIME_TYPES, GOAL_LIST_HARD_LIMIT, GOAL_LIST_SOFT_WARNING_THRESHOLD, GOAL_SNIPPET_MAX_CHARS, missionListParams, missionShowParams, missionCreateParams, missionUpdateParams, missionDeleteParams, missionSetStatusParams, missionReconcileParams, milestoneAddParams, milestoneUpdateParams, milestoneDeleteParams, sliceAddParams, sliceActivateParams, sliceDeleteParams, featureAddParams, featureUpdateParams, featureDeleteParams, featureSetStatusParams, featureLinkTaskParams, featureRepointTaskParams, featureUnlinkTaskParams, featureRepairValidationParams, researchFindingPromoteParams, missionToolResult, optionalText, updateFields, ideationStartParams, ideationDivergeParams, ideationShowParams, ideationConvergeParams, ideationToolResult, formatIdeationCandidate;
|
|
190748
191005
|
var init_agent_tools = __esm({
|
|
190749
191006
|
"../engine/src/agent-tools.ts"() {
|
|
190750
191007
|
"use strict";
|
|
@@ -190835,7 +191092,7 @@ var init_agent_tools = __esm({
|
|
|
190835
191092
|
includeArchived: Type2.Optional(Type2.Boolean({ description: "Include archived tasks (default true)" })),
|
|
190836
191093
|
limit: Type2.Optional(Type2.Number({ minimum: 1, maximum: 50, description: "Max results (default 20, max 50)" }))
|
|
190837
191094
|
});
|
|
190838
|
-
|
|
191095
|
+
historyReadParams = Type2.Object({
|
|
190839
191096
|
query: Type2.Optional(Type2.String({ description: "Search task IDs, titles, and completion summaries" })),
|
|
190840
191097
|
from: Type2.Optional(Type2.String({ description: "First UTC day, YYYY-MM-DD (inclusive)" })),
|
|
190841
191098
|
to: Type2.Optional(Type2.String({ description: "Last UTC day, YYYY-MM-DD (inclusive)" })),
|
|
@@ -211838,7 +212095,7 @@ function extractPromptSection(promptContent, sectionName) {
|
|
|
211838
212095
|
return promptContent.slice(start, end).trim();
|
|
211839
212096
|
}
|
|
211840
212097
|
function summarizePromptSteps(promptContent) {
|
|
211841
|
-
const stepTitles =
|
|
212098
|
+
const stepTitles = matchStepHeadings(promptContent).map(({ index: index2, headingLineEnd }) => promptContent.slice(index2, headingLineEnd).trim());
|
|
211842
212099
|
if (stepTitles.length === 0) {
|
|
211843
212100
|
return "";
|
|
211844
212101
|
}
|
|
@@ -214862,7 +215119,6 @@ init_transient_error_detector();
|
|
|
214862
215119
|
// ../engine/src/self-healing.ts
|
|
214863
215120
|
init_src2();
|
|
214864
215121
|
init_src2();
|
|
214865
|
-
init_logger();
|
|
214866
215122
|
import { execSync as execSync7 } from "node:child_process";
|
|
214867
215123
|
import { setImmediate as setImmediateCb2 } from "node:timers";
|
|
214868
215124
|
import { existsSync as existsSync64, mkdirSync as mkdirSync10, readdirSync as readdirSync10, readFileSync as readFileSync25, realpathSync as realpathSync9, rmdirSync as rmdirSync3, rmSync as rmSync6, statSync as statSync12, writeFileSync as writeFileSync11 } from "node:fs";
|
|
@@ -214870,6 +215126,21 @@ import { readFile as readFile46 } from "node:fs/promises";
|
|
|
214870
215126
|
import { tmpdir as tmpdir8 } from "node:os";
|
|
214871
215127
|
import { basename as basename22, dirname as dirname42, isAbsolute as isAbsolute19, join as join82, relative as relative19, resolve as resolve49 } from "node:path";
|
|
214872
215128
|
|
|
215129
|
+
// ../engine/src/merge/stale-content-park.ts
|
|
215130
|
+
init_src2();
|
|
215131
|
+
var AUTO_MERGE_RETRY_REJECTED_PREFIX = "AUTO_MERGE_RETRY_REJECTED:";
|
|
215132
|
+
function classifyStaleContentPark(task) {
|
|
215133
|
+
if (task.paused || task.userPaused || task.deletedAt) return void 0;
|
|
215134
|
+
if (typeof task.error !== "string" || !isStaleContentApprovalBlocker(task.error)) return void 0;
|
|
215135
|
+
if (task.status === "failed") {
|
|
215136
|
+
return task.error.startsWith(AUTO_MERGE_RETRY_REJECTED_PREFIX) ? "retry-rejected" : "raw-blocker";
|
|
215137
|
+
}
|
|
215138
|
+
return task.status == null ? "retry-exhausted" : void 0;
|
|
215139
|
+
}
|
|
215140
|
+
|
|
215141
|
+
// ../engine/src/self-healing.ts
|
|
215142
|
+
init_logger();
|
|
215143
|
+
|
|
214873
215144
|
// ../engine/src/execution/lifecycle-move-log.ts
|
|
214874
215145
|
init_src2();
|
|
214875
215146
|
function laneRole(column, lanes) {
|
|
@@ -221295,6 +221566,30 @@ var AutoRecoveryDispatcher = class {
|
|
|
221295
221566
|
// ../engine/src/self-healing.ts
|
|
221296
221567
|
init_active_session_registry();
|
|
221297
221568
|
|
|
221569
|
+
// ../engine/src/agents/planning-liveness.ts
|
|
221570
|
+
var probes = /* @__PURE__ */ new Set();
|
|
221571
|
+
var planningLivenessRegistry = {
|
|
221572
|
+
registerPlanningLivenessProbe(probe) {
|
|
221573
|
+
probes.add(probe);
|
|
221574
|
+
return () => probes.delete(probe);
|
|
221575
|
+
},
|
|
221576
|
+
isPlanningLive(taskId) {
|
|
221577
|
+
for (const probe of probes) {
|
|
221578
|
+
try {
|
|
221579
|
+
if (probe(taskId)) return true;
|
|
221580
|
+
} catch {
|
|
221581
|
+
return true;
|
|
221582
|
+
}
|
|
221583
|
+
}
|
|
221584
|
+
return false;
|
|
221585
|
+
},
|
|
221586
|
+
hasRegisteredProbe() {
|
|
221587
|
+
return probes.size > 0;
|
|
221588
|
+
}
|
|
221589
|
+
};
|
|
221590
|
+
var registerPlanningLivenessProbe = planningLivenessRegistry.registerPlanningLivenessProbe.bind(planningLivenessRegistry);
|
|
221591
|
+
var isPlanningLive = planningLivenessRegistry.isPlanningLive.bind(planningLivenessRegistry);
|
|
221592
|
+
|
|
221298
221593
|
// ../engine/src/execution/replan-target.ts
|
|
221299
221594
|
init_src2();
|
|
221300
221595
|
init_logger();
|
|
@@ -222634,6 +222929,17 @@ var STRANDED_COMPLETED_TODO_ACTIVE_STATUSES = /* @__PURE__ */ new Set([
|
|
|
222634
222929
|
]);
|
|
222635
222930
|
var ORPHANED_WITH_WORKTREE_GRACE_MS = 3e5;
|
|
222636
222931
|
var MAX_TASK_DONE_RETRIES = 3;
|
|
222932
|
+
function formatMergeableReviewRecoverySummary({
|
|
222933
|
+
enqueued,
|
|
222934
|
+
merged,
|
|
222935
|
+
parked
|
|
222936
|
+
}) {
|
|
222937
|
+
const outcomes = [];
|
|
222938
|
+
if (enqueued > 0) outcomes.push(`${enqueued} re-enqueued for merge`);
|
|
222939
|
+
if (merged > 0) outcomes.push(`${merged} merged \u2192 done`);
|
|
222940
|
+
if (parked > 0) outcomes.push(`${parked} parked after enqueue starvation`);
|
|
222941
|
+
return `Mergeable review recovery: ${outcomes.join(", ") || "no tasks recovered"}`;
|
|
222942
|
+
}
|
|
222637
222943
|
var RECONCILE_SCOPE_OVERRIDE_MERGE_ACTIVE_STATUS_SET = new Set(MERGE_ACTIVE_MISSING_WORKTREE_STATUSES);
|
|
222638
222944
|
async function resolveNoOpFinalizeGateIds2(store, task) {
|
|
222639
222945
|
const selection = store.getTaskWorkflowSelectionAsync ? await store.getTaskWorkflowSelectionAsync(task.id) : store.getTaskWorkflowSelection?.(task.id);
|
|
@@ -222766,6 +223072,9 @@ var SelfHealingManager = class _SelfHealingManager extends SelfHealingGitEvidenc
|
|
|
222766
223072
|
maintenanceTickCounter = 0;
|
|
222767
223073
|
taskLifecycleRetentionLastPrunedAt = /* @__PURE__ */ new Map();
|
|
222768
223074
|
staleContentRerouteAuditKeys = /* @__PURE__ */ new Set();
|
|
223075
|
+
mergeRecoveryBlockerWarnKeys = /* @__PURE__ */ new Set();
|
|
223076
|
+
staleContentParkRecoveryAttempts = /* @__PURE__ */ new Map();
|
|
223077
|
+
staleContentParkRecoveryBudgetLogged = /* @__PURE__ */ new Set();
|
|
222769
223078
|
unrunPreMergeGateRerouteAuditKeys = /* @__PURE__ */ new Set();
|
|
222770
223079
|
githubCheckStateRetentionLastPrunedAt = /* @__PURE__ */ new Map();
|
|
222771
223080
|
processBootStartedAt = Date.now();
|
|
@@ -223484,6 +223793,13 @@ var SelfHealingManager = class _SelfHealingManager extends SelfHealingGitEvidenc
|
|
|
223484
223793
|
{ name: "reconcile-orphaned-pending-step-results", fn: () => this.reconcileOrphanedPendingStepResults().then(() => void 0) },
|
|
223485
223794
|
{ name: "reconcile-unproven-review-approvals", fn: () => this.reconcileUnprovenReviewApprovals().then(() => void 0) },
|
|
223486
223795
|
/*
|
|
223796
|
+
FNXC:PreMergeApproval 2026-09-05-22:11:
|
|
223797
|
+
Runs AFTER the orphaned-step sweep, because that sweep is what produces the `failed` row this
|
|
223798
|
+
defect archives, and BEFORE failed-step recovery, so the restored gate is visible to it in the
|
|
223799
|
+
same startup pass instead of one cycle later.
|
|
223800
|
+
*/
|
|
223801
|
+
{ name: "reconcile-collateral-archived-review-gates", fn: () => this.reconcileCollateralArchivedReviewGates().then(() => void 0) },
|
|
223802
|
+
/*
|
|
223487
223803
|
FNXC:WorkspaceContention 2026-08-23-08:00:
|
|
223488
223804
|
`contention-hold` is owned by an in-memory retry timer. After a process restart that
|
|
223489
223805
|
timer is gone, so clear only an unowned wait before ordinary dispatch classification;
|
|
@@ -224364,6 +224680,7 @@ var SelfHealingManager = class _SelfHealingManager extends SelfHealingGitEvidenc
|
|
|
224364
224680
|
*/
|
|
224365
224681
|
{ name: "reconcile-orphaned-pending-step-results", fn: () => this.reconcileOrphanedPendingStepResults() },
|
|
224366
224682
|
{ name: "reconcile-unproven-review-approvals", fn: () => this.reconcileUnprovenReviewApprovals() },
|
|
224683
|
+
{ name: "reconcile-collateral-archived-review-gates", fn: () => this.reconcileCollateralArchivedReviewGates() },
|
|
224367
224684
|
{ name: "recover-failed-pre-merge-steps", fn: () => this.recoverReviewTasksWithFailedPreMergeSteps() },
|
|
224368
224685
|
{ name: "recover-missing-worktree-review-failures", fn: () => this.recoverMissingWorktreeReviewFailures() },
|
|
224369
224686
|
{ name: "recover-interrupted-merging", fn: () => this.recoverInterruptedMergingTasks() },
|
|
@@ -227850,29 +228167,28 @@ var SelfHealingManager = class _SelfHealingManager extends SelfHealingGitEvidenc
|
|
|
227850
228167
|
/** Repair legacy dependency residue before it can re-enter executor dispatch. */
|
|
227851
228168
|
async reconcileMissingDependencies() {
|
|
227852
228169
|
let repaired = 0;
|
|
227853
|
-
const
|
|
228170
|
+
const archivedColumns = new Set(await Promise.resolve().then(() => resolveProjectColumnsForRoles(this.store, ["archived"])).catch(() => /* @__PURE__ */ new Set()));
|
|
228171
|
+
archivedColumns.add("archived");
|
|
228172
|
+
const tasks2 = await this.store.listTasks({ slim: true, includeArchived: false });
|
|
227854
228173
|
for (const snapshot of tasks2) {
|
|
228174
|
+
if (snapshot.deletedAt || archivedColumns.has(snapshot.column)) continue;
|
|
227855
228175
|
if (!snapshot.dependencies.length || snapshot.userPaused || snapshot.paused || snapshot.autoMerge === false) continue;
|
|
227856
228176
|
const livePaths = activeSessionRegistry.pathsForTask(snapshot.id);
|
|
227857
228177
|
const liveExecution = livePaths.some((path6) => activeSessionRegistry.isPathActive(path6)) || executingTaskLock.has(snapshot.id) || this.options.getExecutingTaskIds?.().has(snapshot.id) === true || this.options.isTaskActive?.(snapshot.id) === true || Boolean(snapshot.checkedOutBy);
|
|
227858
228178
|
if (liveExecution) continue;
|
|
227859
228179
|
let removed = 0;
|
|
227860
228180
|
for (const dependencyId of snapshot.dependencies) {
|
|
227861
|
-
|
|
227862
|
-
|
|
227863
|
-
if (!dependency.deletedAt) continue;
|
|
227864
|
-
} catch (error2) {
|
|
227865
|
-
if (!isTaskNotFoundError2(error2)) throw error2;
|
|
227866
|
-
}
|
|
227867
|
-
const current = await this.store.getTask(snapshot.id).catch((error2) => {
|
|
227868
|
-
if (isTaskNotFoundError2(error2)) return void 0;
|
|
227869
|
-
throw error2;
|
|
227870
|
-
});
|
|
228181
|
+
if (await readLinkedTaskOrUndefined(this.store, dependencyId)) continue;
|
|
228182
|
+
const current = await readLinkedTaskOrUndefined(this.store, snapshot.id);
|
|
227871
228183
|
if (!current || current.userPaused || current.paused || current.autoMerge === false || current.checkedOutBy || activeSessionRegistry.pathsForTask(current.id).some((path6) => activeSessionRegistry.isPathActive(path6)) || executingTaskLock.has(current.id) || this.options.getExecutingTaskIds?.().has(current.id) === true || this.options.isTaskActive?.(current.id) === true || !current.dependencies.includes(dependencyId)) continue;
|
|
227872
228184
|
try {
|
|
227873
228185
|
await this.store.updateTaskDependencies(current.id, { operation: "remove", dependency: dependencyId });
|
|
227874
228186
|
removed += 1;
|
|
227875
228187
|
} catch (error2) {
|
|
228188
|
+
if (isMissingTaskLookupError(error2)) {
|
|
228189
|
+
log34.debug(`reconcileMissingDependencies: skipped unreadable ${current.id} dependency ${dependencyId}`);
|
|
228190
|
+
continue;
|
|
228191
|
+
}
|
|
227876
228192
|
if (!/does not depend on/i.test(error2 instanceof Error ? error2.message : String(error2))) throw error2;
|
|
227877
228193
|
}
|
|
227878
228194
|
}
|
|
@@ -228638,7 +228954,7 @@ var SelfHealingManager = class _SelfHealingManager extends SelfHealingGitEvidenc
|
|
|
228638
228954
|
const graceMs = 10 * 6e4;
|
|
228639
228955
|
const now = Date.now();
|
|
228640
228956
|
const nowIso3 = new Date(now).toISOString();
|
|
228641
|
-
const live = (taskId) => activeSessionRegistry.pathsForTask(taskId).some((path6) => activeSessionRegistry.isPathActive(path6)) || executingTaskLock.has(taskId) || this.options.isTaskActive?.(taskId) === true;
|
|
228957
|
+
const live = (taskId) => activeSessionRegistry.pathsForTask(taskId).some((path6) => activeSessionRegistry.isPathActive(path6)) || executingTaskLock.has(taskId) || isPlanningLive(taskId) || this.options.isTaskActive?.(taskId) === true;
|
|
228642
228958
|
const due = await this.store.listDueWorkflowWorkItems({
|
|
228643
228959
|
now: nowIso3,
|
|
228644
228960
|
states: [...ACTIVE_WORKFLOW_WORK_ITEM_STATES]
|
|
@@ -229015,6 +229331,109 @@ var SelfHealingManager = class _SelfHealingManager extends SelfHealingGitEvidenc
|
|
|
229015
229331
|
return 0;
|
|
229016
229332
|
}
|
|
229017
229333
|
}
|
|
229334
|
+
/**
|
|
229335
|
+
* FNXC:PreMergeApproval 2026-09-05-22:11:
|
|
229336
|
+
* FN-295: restore a required pre-merge gate that was archived as COLLATERAL of another gate's
|
|
229337
|
+
* remediation, so the card stops being permanently unmergeable with no owner.
|
|
229338
|
+
*
|
|
229339
|
+
* The wedge needs four ingredients, none of which is individually wrong: a review row left
|
|
229340
|
+
* `pending` by a dead session, the orphaned-step sweep rewriting it to `failed`, the remediation
|
|
229341
|
+
* archiver stamping EVERY terminal failure (not just its own gate), and the merge door treating
|
|
229342
|
+
* `remediationArchivedAt` as an unconditional veto. The result is a row the reseed cannot re-run
|
|
229343
|
+
* (it handles `missing`), the stale-content reroute cannot re-run (it handles `stale-content`),
|
|
229344
|
+
* and the operator bypass cannot select (it selects `status:"failed"`). Measured on FN-295: three
|
|
229345
|
+
* review restarts re-ran Code Review and Documentation to APPROVE and merged nothing.
|
|
229346
|
+
*
|
|
229347
|
+
* This sweep does NOT approve or merge anything: it undoes the collateral archive so the gate is
|
|
229348
|
+
* once again a visible failed step that the ordinary FN-7720 audited bypass — or a genuine re-run
|
|
229349
|
+
* — can clear. It never touches an operator waiver, the gate that owns the remediation wave, a
|
|
229350
|
+
* workspace card, a user-paused card, or a card with a live session, and it re-validates every
|
|
229351
|
+
* condition inside the atomic mutation.
|
|
229352
|
+
*/
|
|
229353
|
+
async reconcileCollateralArchivedReviewGates() {
|
|
229354
|
+
try {
|
|
229355
|
+
const reviewColumns = await resolveProjectColumnsForRoles(this.store, REVIEW_ROLES);
|
|
229356
|
+
const candidates = /* @__PURE__ */ new Map();
|
|
229357
|
+
for (const column of reviewColumns) {
|
|
229358
|
+
for (const task of await this.store.listTasks({ column, slim: true })) candidates.set(task.id, task);
|
|
229359
|
+
}
|
|
229360
|
+
let repairedTasks = 0;
|
|
229361
|
+
const isSessionLive = (taskId) => {
|
|
229362
|
+
const livePaths = activeSessionRegistry.pathsForTask(taskId);
|
|
229363
|
+
return livePaths.some((path6) => activeSessionRegistry.isPathActive(path6)) || executingTaskLock.has(taskId) || this.options.isTaskActive?.(taskId) === true;
|
|
229364
|
+
};
|
|
229365
|
+
const remediationGateIdsFor = (task) => new Set(
|
|
229366
|
+
(task.steps ?? []).map((step) => step.remediation?.gateStepId).filter((gateStepId) => typeof gateStepId === "string" && gateStepId.length > 0)
|
|
229367
|
+
);
|
|
229368
|
+
for (const candidate of candidates.values()) {
|
|
229369
|
+
if (candidate.userPaused === true || candidate.workspaceWorktrees !== void 0 || isSessionLive(candidate.id)) continue;
|
|
229370
|
+
const fresh = await this.store.getTask(candidate.id);
|
|
229371
|
+
if (!fresh || !reviewColumns.has(fresh.column) || fresh.userPaused === true || fresh.workspaceWorktrees !== void 0 || isSessionLive(fresh.id)) continue;
|
|
229372
|
+
if (!(fresh.workflowStepResults ?? []).some((result) => resolveCollateralArchivedReviewGate(result, { remediationGateIds: remediationGateIdsFor(fresh) }))) continue;
|
|
229373
|
+
let requiredPreMergeStepIds;
|
|
229374
|
+
try {
|
|
229375
|
+
requiredPreMergeStepIds = (await resolvePreMergeGateForTask(
|
|
229376
|
+
this.store,
|
|
229377
|
+
fresh.id,
|
|
229378
|
+
fresh.enabledWorkflowSteps,
|
|
229379
|
+
fresh
|
|
229380
|
+
)).requiredPreMergeStepIds;
|
|
229381
|
+
} catch {
|
|
229382
|
+
continue;
|
|
229383
|
+
}
|
|
229384
|
+
if (requiredPreMergeStepIds.size === 0) continue;
|
|
229385
|
+
let repair;
|
|
229386
|
+
try {
|
|
229387
|
+
await this.store.updateTaskAtomic(fresh.id, (live) => {
|
|
229388
|
+
repair = void 0;
|
|
229389
|
+
if (!reviewColumns.has(live.column) || live.userPaused === true || live.workspaceWorktrees !== void 0 || isSessionLive(live.id)) return null;
|
|
229390
|
+
const remediationGateIds = remediationGateIdsFor(live);
|
|
229391
|
+
const restoredStepIds = [];
|
|
229392
|
+
const results = (live.workflowStepResults ?? []).map((result) => {
|
|
229393
|
+
if (!requiredPreMergeStepIds.has(result.workflowStepId)) return result;
|
|
229394
|
+
const resolution = resolveCollateralArchivedReviewGate(result, { remediationGateIds });
|
|
229395
|
+
if (!resolution) return result;
|
|
229396
|
+
restoredStepIds.push(result.workflowStepId);
|
|
229397
|
+
return resolution.restored;
|
|
229398
|
+
});
|
|
229399
|
+
if (restoredStepIds.length === 0) return null;
|
|
229400
|
+
repair = { stepIds: restoredStepIds, column: live.column, resultCount: results.length };
|
|
229401
|
+
return { workflowStepResults: results };
|
|
229402
|
+
});
|
|
229403
|
+
if (!repair) continue;
|
|
229404
|
+
repairedTasks += 1;
|
|
229405
|
+
} catch (error2) {
|
|
229406
|
+
log34.warn(`reconcileCollateralArchivedReviewGates: failed for ${fresh.id}: ${error2 instanceof Error ? error2.message : String(error2)}`);
|
|
229407
|
+
continue;
|
|
229408
|
+
}
|
|
229409
|
+
await this.store.logEntry(
|
|
229410
|
+
fresh.id,
|
|
229411
|
+
`[pre-merge] Restored collaterally archived review gate(s): ${repair.stepIds.join(", ")}`,
|
|
229412
|
+
COLLATERAL_ARCHIVED_REVIEW_GATE_DIAGNOSTIC
|
|
229413
|
+
).catch(() => void 0);
|
|
229414
|
+
await emitBoundedRunAudit2(this.store, {
|
|
229415
|
+
taskId: fresh.id,
|
|
229416
|
+
agentId: "self-healing",
|
|
229417
|
+
runId: generateSyntheticRunId("reconcile-collateral-archived-review-gate", fresh.id),
|
|
229418
|
+
domain: "database",
|
|
229419
|
+
mutationType: "task:reconcile-collateral-archived-review-gate",
|
|
229420
|
+
target: fresh.id,
|
|
229421
|
+
metadata: {
|
|
229422
|
+
taskId: fresh.id,
|
|
229423
|
+
column: repair.column,
|
|
229424
|
+
workflowStepId: repair.stepIds[0],
|
|
229425
|
+
restoredCount: repair.stepIds.length,
|
|
229426
|
+
resultCount: repair.resultCount
|
|
229427
|
+
}
|
|
229428
|
+
}, { log: log34 });
|
|
229429
|
+
}
|
|
229430
|
+
if (repairedTasks > 0) log34.log(`Restored collaterally archived review gates on ${repairedTasks} task(s)`);
|
|
229431
|
+
return repairedTasks;
|
|
229432
|
+
} catch (error2) {
|
|
229433
|
+
log34.error(`reconcileCollateralArchivedReviewGates failed: ${error2 instanceof Error ? error2.message : String(error2)}`);
|
|
229434
|
+
return 0;
|
|
229435
|
+
}
|
|
229436
|
+
}
|
|
229018
229437
|
/**
|
|
229019
229438
|
* Backward lifecycle move gated on triple proof (FN-5335).
|
|
229020
229439
|
* When the unproven fallback predicate fails, emits `task:finalize-no-op-review-no-action` and skips lifecycle mutation.
|
|
@@ -229374,25 +229793,14 @@ var SelfHealingManager = class _SelfHealingManager extends SelfHealingGitEvidenc
|
|
|
229374
229793
|
blocker check lacks a fresh Git descriptor. Capture the same merge content the merge queue uses and
|
|
229375
229794
|
seed the graph back at Code Review, so cards like FN-9234 stop looping through doomed merge retries.
|
|
229376
229795
|
*/
|
|
229377
|
-
async routeStaleSingularApprovalBackToReview(task, reviewColumns,
|
|
229378
|
-
let mergeGate;
|
|
229379
|
-
try {
|
|
229380
|
-
mergeGate = await resolvePreMergeGateForTask(this.store, task.id, task.enabledWorkflowSteps, task);
|
|
229381
|
-
} catch {
|
|
229382
|
-
return false;
|
|
229383
|
-
}
|
|
229384
|
-
if (mergeGate.provenance === "default" && !mergeGate.selectionAbsent) return false;
|
|
229385
|
-
const mergeContent = await captureMergeContentDescriptor(task, {
|
|
229386
|
-
workspaceRootDir: this.options.rootDir,
|
|
229387
|
-
settings
|
|
229388
|
-
});
|
|
229796
|
+
async routeStaleSingularApprovalBackToReview(task, reviewColumns, mergeGate, mergeContent, parkShape) {
|
|
229389
229797
|
const blocker = getTaskMergeBlocker(task, {
|
|
229390
229798
|
reviewColumns,
|
|
229391
229799
|
requiredPreMergeStepIds: mergeGate.requiredPreMergeStepIds,
|
|
229392
229800
|
mergeContent
|
|
229393
229801
|
});
|
|
229394
|
-
if (blocker
|
|
229395
|
-
return false;
|
|
229802
|
+
if (!isStaleContentApprovalBlocker(blocker) || mergeContent.kind !== "singular") {
|
|
229803
|
+
return { handled: false, seeded: false, parkCleared: false, mergeRetriesReset: false };
|
|
229396
229804
|
}
|
|
229397
229805
|
const reroute = await rerouteSingularStaleContentToReview(this.store, task, {
|
|
229398
229806
|
requiredPreMergeStepIds: mergeGate.requiredPreMergeStepIds,
|
|
@@ -229403,11 +229811,25 @@ var SelfHealingManager = class _SelfHealingManager extends SelfHealingGitEvidenc
|
|
|
229403
229811
|
nodeId: void 0,
|
|
229404
229812
|
workflowStepId: void 0
|
|
229405
229813
|
}));
|
|
229814
|
+
let parkCleared = false;
|
|
229815
|
+
if (reroute.rerouted && parkShape) {
|
|
229816
|
+
await this.store.updateTaskAtomic(task.id, (live) => {
|
|
229817
|
+
if (classifyStaleContentPark(live) !== parkShape || live.paused || live.userPaused || live.deletedAt) {
|
|
229818
|
+
return null;
|
|
229819
|
+
}
|
|
229820
|
+
parkCleared = true;
|
|
229821
|
+
return { status: null, error: null, mergeRetries: 0 };
|
|
229822
|
+
});
|
|
229823
|
+
}
|
|
229824
|
+
const mergeRetriesReset = parkCleared;
|
|
229406
229825
|
if (reroute.rerouted) {
|
|
229407
|
-
await this.store.logEntry(task.id,
|
|
229826
|
+
await this.store.logEntry(task.id, `[pre-merge] Review lane '${reroute.nodeId ?? "code-review"}' approved older content; self-healing re-seeded it against current content.`);
|
|
229827
|
+
if (parkCleared) {
|
|
229828
|
+
await this.store.logEntry(task.id, `[pre-merge] Cleared the stale-content merge park after re-seeding review lane '${reroute.nodeId ?? "code-review"}' against current content.`);
|
|
229829
|
+
}
|
|
229408
229830
|
log34.warn(`Stale content approval for ${task.id} re-seeded at ${reroute.nodeId ?? "code-review"}`);
|
|
229409
229831
|
}
|
|
229410
|
-
const auditKey = `${task.id}:${reroute.reason}:${reroute.nodeId ?? ""}`;
|
|
229832
|
+
const auditKey = `${task.id}:${reroute.reason}:${reroute.nodeId ?? ""}:${parkShape ?? ""}`;
|
|
229411
229833
|
if (!this.staleContentRerouteAuditKeys.has(auditKey)) {
|
|
229412
229834
|
this.staleContentRerouteAuditKeys.add(auditKey);
|
|
229413
229835
|
await emitBoundedRunAudit2(this.store, {
|
|
@@ -229422,21 +229844,23 @@ var SelfHealingManager = class _SelfHealingManager extends SelfHealingGitEvidenc
|
|
|
229422
229844
|
nodeId: reroute.nodeId,
|
|
229423
229845
|
workflowStepId: reroute.workflowStepId,
|
|
229424
229846
|
reason: reroute.reason,
|
|
229425
|
-
source: "self-healing"
|
|
229847
|
+
source: "self-healing",
|
|
229848
|
+
parkShape,
|
|
229849
|
+
parkCleared,
|
|
229850
|
+
mergeRetriesReset
|
|
229426
229851
|
}
|
|
229427
229852
|
});
|
|
229428
229853
|
}
|
|
229429
|
-
return
|
|
229854
|
+
return {
|
|
229855
|
+
handled: true,
|
|
229856
|
+
seeded: reroute.rerouted,
|
|
229857
|
+
nodeId: reroute.nodeId,
|
|
229858
|
+
parkShape,
|
|
229859
|
+
parkCleared,
|
|
229860
|
+
mergeRetriesReset
|
|
229861
|
+
};
|
|
229430
229862
|
}
|
|
229431
|
-
async routeUnrunPreMergeGateBackToReview(task, reviewColumns,
|
|
229432
|
-
let mergeGate;
|
|
229433
|
-
try {
|
|
229434
|
-
mergeGate = await resolvePreMergeGateForTask(this.store, task.id, task.enabledWorkflowSteps, task);
|
|
229435
|
-
} catch {
|
|
229436
|
-
return false;
|
|
229437
|
-
}
|
|
229438
|
-
if (mergeGate.provenance === "default" && !mergeGate.selectionAbsent) return false;
|
|
229439
|
-
const mergeContent = await captureMergeContentDescriptor(task, { workspaceRootDir: this.options.rootDir, settings });
|
|
229863
|
+
async routeUnrunPreMergeGateBackToReview(task, reviewColumns, mergeGate, mergeContent) {
|
|
229440
229864
|
if (getTaskMergeBlocker(task, { reviewColumns, requiredPreMergeStepIds: mergeGate.requiredPreMergeStepIds, mergeContent }) !== "task has enabled pre-merge workflow steps that never ran") return false;
|
|
229441
229865
|
const reroute = await rerouteUnrunPreMergeGateToReview(this.store, task, { requiredPreMergeStepIds: mergeGate.requiredPreMergeStepIds, mergeContent }).catch(() => ({ rerouted: false, reason: "no-unrun-gate", nodeId: void 0, workflowStepId: void 0 }));
|
|
229442
229866
|
if (reroute.rerouted) {
|
|
@@ -229511,11 +229935,62 @@ var SelfHealingManager = class _SelfHealingManager extends SelfHealingGitEvidenc
|
|
|
229511
229935
|
for (const task of mergeable) {
|
|
229512
229936
|
const reviewColumns = await ownReviewLanesFor(task);
|
|
229513
229937
|
if (!reviewColumns.has(task.column)) continue;
|
|
229514
|
-
|
|
229515
|
-
|
|
229516
|
-
|
|
229938
|
+
let mergeGate;
|
|
229939
|
+
try {
|
|
229940
|
+
mergeGate = await resolvePreMergeGateForTask(this.store, task.id, task.enabledWorkflowSteps, task);
|
|
229941
|
+
} catch {
|
|
229942
|
+
continue;
|
|
229943
|
+
}
|
|
229944
|
+
if (mergeGate.provenance === "default" && !mergeGate.selectionAbsent) continue;
|
|
229945
|
+
const mergeContent = await captureMergeContentDescriptor(task, { workspaceRootDir: this.options.rootDir, settings });
|
|
229946
|
+
if ((await this.routeStaleSingularApprovalBackToReview(task, reviewColumns, mergeGate, mergeContent)).handled) continue;
|
|
229947
|
+
if (await this.routeUnrunPreMergeGateBackToReview(task, reviewColumns, mergeGate, mergeContent)) continue;
|
|
229948
|
+
const blocker = getTaskMergeBlocker(task, {
|
|
229949
|
+
reviewColumns,
|
|
229950
|
+
requiredPreMergeStepIds: mergeGate.requiredPreMergeStepIds,
|
|
229951
|
+
mergeContent
|
|
229952
|
+
});
|
|
229953
|
+
if (blocker !== void 0) {
|
|
229954
|
+
const warningKey = `${task.id}:${blocker}`;
|
|
229955
|
+
if (!this.mergeRecoveryBlockerWarnKeys.has(warningKey)) {
|
|
229956
|
+
this.mergeRecoveryBlockerWarnKeys.add(warningKey);
|
|
229957
|
+
log34.warn(`mergeable-review recovery declined ${task.id}: ${blocker}`);
|
|
229958
|
+
}
|
|
229959
|
+
continue;
|
|
229960
|
+
}
|
|
229517
229961
|
laneQualifiedMergeable.push(task);
|
|
229518
229962
|
}
|
|
229963
|
+
const hiddenStaleContentCandidateIds = /* @__PURE__ */ new Set();
|
|
229964
|
+
for (const task of tasks2) {
|
|
229965
|
+
if (mergeable.includes(task)) continue;
|
|
229966
|
+
const parkShape = classifyStaleContentPark(task);
|
|
229967
|
+
if (!parkShape || mergeAdmissionByTaskId.get(task.id) !== true || executingIds.has(task.id) || task.status === "merging" || task.status === "merging-pr") continue;
|
|
229968
|
+
const reviewColumns = await ownReviewLanesFor(task);
|
|
229969
|
+
if (!reviewColumns.has(task.column)) continue;
|
|
229970
|
+
hiddenStaleContentCandidateIds.add(task.id);
|
|
229971
|
+
const attempts = (this.staleContentParkRecoveryAttempts.get(task.id) ?? 0) + 1;
|
|
229972
|
+
this.staleContentParkRecoveryAttempts.set(task.id, attempts);
|
|
229973
|
+
if (attempts === MAX_STARVATION_DROPS && !this.staleContentParkRecoveryBudgetLogged.has(task.id)) {
|
|
229974
|
+
this.staleContentParkRecoveryBudgetLogged.add(task.id);
|
|
229975
|
+
await this.store.logEntry(task.id, `[pre-merge] Stopped stale-content park recovery after ${MAX_STARVATION_DROPS} attempts; operator attention is required.`);
|
|
229976
|
+
}
|
|
229977
|
+
if (attempts > MAX_STARVATION_DROPS) continue;
|
|
229978
|
+
let mergeGate;
|
|
229979
|
+
try {
|
|
229980
|
+
mergeGate = await resolvePreMergeGateForTask(this.store, task.id, task.enabledWorkflowSteps, task);
|
|
229981
|
+
} catch {
|
|
229982
|
+
continue;
|
|
229983
|
+
}
|
|
229984
|
+
if (mergeGate.provenance === "default" && !mergeGate.selectionAbsent) continue;
|
|
229985
|
+
const mergeContent = await captureMergeContentDescriptor(task, { workspaceRootDir: this.options.rootDir, settings });
|
|
229986
|
+
await this.routeStaleSingularApprovalBackToReview(task, reviewColumns, mergeGate, mergeContent, parkShape);
|
|
229987
|
+
}
|
|
229988
|
+
for (const taskId of this.staleContentParkRecoveryAttempts.keys()) {
|
|
229989
|
+
if (!hiddenStaleContentCandidateIds.has(taskId)) {
|
|
229990
|
+
this.staleContentParkRecoveryAttempts.delete(taskId);
|
|
229991
|
+
this.staleContentParkRecoveryBudgetLogged.delete(taskId);
|
|
229992
|
+
}
|
|
229993
|
+
}
|
|
229519
229994
|
if (unresolvedMergeableCards.length > 0) {
|
|
229520
229995
|
log34.warn(
|
|
229521
229996
|
`mergeable-review recovery: ${unresolvedMergeableCards.length} card(s) measured against the built-in review lane because their own workflow could not be resolved (${unresolvedMergeableCards.slice(0, 5).join(", ")}); a renamed review lane there stays stuck.`
|
|
@@ -229538,6 +230013,9 @@ var SelfHealingManager = class _SelfHealingManager extends SelfHealingGitEvidenc
|
|
|
229538
230013
|
);
|
|
229539
230014
|
const enqueueMerge = this.options.enqueueMerge;
|
|
229540
230015
|
let recovered = 0;
|
|
230016
|
+
let enqueued = 0;
|
|
230017
|
+
let merged = 0;
|
|
230018
|
+
let parked = 0;
|
|
229541
230019
|
for (const task of unownedMergeable) {
|
|
229542
230020
|
try {
|
|
229543
230021
|
if (enqueueMerge) {
|
|
@@ -229553,19 +230031,22 @@ var SelfHealingManager = class _SelfHealingManager extends SelfHealingGitEvidenc
|
|
|
229553
230031
|
await this.store.updateTask(task.id, { status: "failed", error: error2 });
|
|
229554
230032
|
await this.store.logEntry(task.id, error2);
|
|
229555
230033
|
this.mergeStarvationDrops.delete(task.id);
|
|
230034
|
+
parked++;
|
|
229556
230035
|
recovered++;
|
|
229557
230036
|
}
|
|
229558
230037
|
continue;
|
|
229559
230038
|
}
|
|
229560
230039
|
this.mergeStarvationDrops.delete(task.id);
|
|
230040
|
+
enqueued++;
|
|
229561
230041
|
} else {
|
|
229562
230042
|
await this.store.mergeTask(task.id);
|
|
230043
|
+
merged++;
|
|
229563
230044
|
}
|
|
229564
230045
|
await this.store.logEntry(
|
|
229565
230046
|
task.id,
|
|
229566
230047
|
enqueueMerge ? "Auto-recovered: eligible in-review task re-enqueued for merge" : "Auto-recovered: eligible in-review task was merged and moved to done"
|
|
229567
230048
|
);
|
|
229568
|
-
log34.log(`
|
|
230049
|
+
log34.log(enqueueMerge ? `Re-enqueued mergeable review task ${task.id} for merge` : `Merged review task ${task.id} and moved it to done`);
|
|
229569
230050
|
recovered++;
|
|
229570
230051
|
} catch (err) {
|
|
229571
230052
|
const errorMessage2 = err instanceof Error ? err.message : String(err);
|
|
@@ -229573,7 +230054,7 @@ var SelfHealingManager = class _SelfHealingManager extends SelfHealingGitEvidenc
|
|
|
229573
230054
|
}
|
|
229574
230055
|
}
|
|
229575
230056
|
if (recovered > 0) {
|
|
229576
|
-
log34.log(
|
|
230057
|
+
log34.log(formatMergeableReviewRecoverySummary({ enqueued, merged, parked }));
|
|
229577
230058
|
}
|
|
229578
230059
|
return recovered;
|
|
229579
230060
|
} catch (err) {
|
|
@@ -236389,23 +236870,63 @@ init_context_limit_detector();
|
|
|
236389
236870
|
init_usage_limit_detector();
|
|
236390
236871
|
init_agent_tools();
|
|
236391
236872
|
init_worktree_backend();
|
|
236392
|
-
init_worktree_prune();
|
|
236393
|
-
init_active_session_registry();
|
|
236394
236873
|
import { exec as exec40 } from "node:child_process";
|
|
236395
236874
|
import { promisify as promisify57 } from "node:util";
|
|
236396
236875
|
import { existsSync as existsSync66 } from "node:fs";
|
|
236397
236876
|
import { rm as rm10 } from "node:fs/promises";
|
|
236877
|
+
|
|
236878
|
+
// ../engine/src/execution/resolve-activity-run-agent-id.ts
|
|
236879
|
+
init_src2();
|
|
236880
|
+
var WORKFLOW_STEP_RUN_ATTRIBUTION_TIMEOUT_MS = 2e3;
|
|
236881
|
+
async function resolveWorkflowStepRunAgentId(store, candidate, options = {}) {
|
|
236882
|
+
if (!candidate?.trim()) return null;
|
|
236883
|
+
const resolve53 = async () => {
|
|
236884
|
+
if (typeof store?.getAgent === "function") {
|
|
236885
|
+
const agent = await store.getAgent(candidate);
|
|
236886
|
+
if (agent) return agent.id;
|
|
236887
|
+
}
|
|
236888
|
+
if (typeof store?.listAgents !== "function") return null;
|
|
236889
|
+
const matches = (await store.listAgents({ includeEphemeral: true })).filter(
|
|
236890
|
+
(agent) => agent.role === candidate || agent.roles?.includes(candidate)
|
|
236891
|
+
);
|
|
236892
|
+
const builtInOwners = matches.filter(
|
|
236893
|
+
(agent) => isBuiltinWorkflowRoleAgent(agent) && agent.metadata?.workflowRole === candidate
|
|
236894
|
+
);
|
|
236895
|
+
if (builtInOwners.length === 1) return builtInOwners[0].id;
|
|
236896
|
+
return matches.length === 1 ? matches[0].id : null;
|
|
236897
|
+
};
|
|
236898
|
+
let lookup2;
|
|
236899
|
+
try {
|
|
236900
|
+
lookup2 = Promise.resolve(resolve53());
|
|
236901
|
+
} catch {
|
|
236902
|
+
return null;
|
|
236903
|
+
}
|
|
236904
|
+
void lookup2.catch(() => void 0);
|
|
236905
|
+
return await new Promise((complete) => {
|
|
236906
|
+
const timer2 = setTimeout(() => complete(null), options.timeoutMs ?? WORKFLOW_STEP_RUN_ATTRIBUTION_TIMEOUT_MS);
|
|
236907
|
+
timer2.unref?.();
|
|
236908
|
+
void lookup2.then(
|
|
236909
|
+
(agentId) => {
|
|
236910
|
+
clearTimeout(timer2);
|
|
236911
|
+
complete(agentId);
|
|
236912
|
+
},
|
|
236913
|
+
() => {
|
|
236914
|
+
clearTimeout(timer2);
|
|
236915
|
+
complete(null);
|
|
236916
|
+
}
|
|
236917
|
+
);
|
|
236918
|
+
});
|
|
236919
|
+
}
|
|
236920
|
+
|
|
236921
|
+
// ../engine/src/execution/step-session-executor.ts
|
|
236922
|
+
init_worktree_prune();
|
|
236923
|
+
init_active_session_registry();
|
|
236398
236924
|
var execAsync37 = promisify57(exec40);
|
|
236399
236925
|
var stepExecLog = createLogger("step-session-executor");
|
|
236400
236926
|
function parseStepFileScopes(prompt) {
|
|
236401
236927
|
const result = /* @__PURE__ */ new Map();
|
|
236402
236928
|
if (!prompt) return result;
|
|
236403
|
-
const
|
|
236404
|
-
const splits = [];
|
|
236405
|
-
let match;
|
|
236406
|
-
while ((match = stepRegex.exec(prompt)) !== null) {
|
|
236407
|
-
splits.push({ index: match.index, stepNum: parseInt(match[1], 10) });
|
|
236408
|
-
}
|
|
236929
|
+
const splits = matchStepHeadings(prompt).map(({ index: index2, headingNumber }) => ({ index: index2, stepNum: headingNumber }));
|
|
236409
236930
|
if (splits.length === 0) return result;
|
|
236410
236931
|
for (let i = 0; i < splits.length; i++) {
|
|
236411
236932
|
const start = splits[i].index;
|
|
@@ -236681,12 +237202,7 @@ function scopePromptToWorktree(prompt, rootDir, worktreePath) {
|
|
|
236681
237202
|
return prompt.replaceAll(`${rootDir}/`, `${worktreePath}/`).replaceAll(`${worktreePath}/.fusion/`, `${rootDir}/.fusion/`);
|
|
236682
237203
|
}
|
|
236683
237204
|
function extractStepSection(prompt, stepIndex) {
|
|
236684
|
-
const
|
|
236685
|
-
const splits = [];
|
|
236686
|
-
let match;
|
|
236687
|
-
while ((match = stepRegex.exec(prompt)) !== null) {
|
|
236688
|
-
splits.push({ index: match.index, stepNum: parseInt(match[1], 10) });
|
|
236689
|
-
}
|
|
237205
|
+
const splits = matchStepHeadings(prompt).map(({ index: index2, headingNumber }) => ({ index: index2, stepNum: headingNumber }));
|
|
236690
237206
|
const offset = resolveAuthoredStepHeadingOffset(splits.map((split) => split.stepNum));
|
|
236691
237207
|
const targetSplit = splits.find((s3) => s3.stepNum === stepIndex + offset);
|
|
236692
237208
|
if (!targetSplit) return "";
|
|
@@ -236696,9 +237212,7 @@ function extractStepSection(prompt, stepIndex) {
|
|
|
236696
237212
|
return prompt.slice(start, end).trim();
|
|
236697
237213
|
}
|
|
236698
237214
|
function countSteps(prompt) {
|
|
236699
|
-
|
|
236700
|
-
const matches = prompt.match(stepRegex);
|
|
236701
|
-
return matches ? matches.length : 0;
|
|
237215
|
+
return matchStepHeadings(prompt).length;
|
|
236702
237216
|
}
|
|
236703
237217
|
function resolveStepSection(taskDetail, prompt, stepIndex, authoredStepCount) {
|
|
236704
237218
|
const authoredSection = extractStepSection(prompt, stepIndex);
|
|
@@ -236800,6 +237314,8 @@ var StepSessionExecutor = class {
|
|
|
236800
237314
|
credentialInstanceId;
|
|
236801
237315
|
reusablePrimaryRetargetPending = false;
|
|
236802
237316
|
reusablePrimaryAttemptActive = false;
|
|
237317
|
+
workflowStepRunAgentId;
|
|
237318
|
+
warnedUnattributableWorkflowStepRun = false;
|
|
236803
237319
|
registerActiveStepSession(stepIndex, handle2, worktreePath) {
|
|
236804
237320
|
this.activeSessions.set(stepIndex, handle2);
|
|
236805
237321
|
activeSessionRegistry.registerPath(worktreePath, {
|
|
@@ -237144,10 +237660,20 @@ var StepSessionExecutor = class {
|
|
|
237144
237660
|
modelId: current.modelId
|
|
237145
237661
|
};
|
|
237146
237662
|
}
|
|
237147
|
-
createWorkflowStepActivityRun(stepIndex, startedAt) {
|
|
237663
|
+
async createWorkflowStepActivityRun(stepIndex, startedAt) {
|
|
237148
237664
|
const { taskDetail } = this.options;
|
|
237149
237665
|
const step = taskDetail.steps?.[stepIndex];
|
|
237150
|
-
|
|
237666
|
+
if (typeof this.options.agentStore?.saveRun !== "function") return null;
|
|
237667
|
+
const agentIdCandidate = this.options.effectiveAgentId ?? taskDetail.assignedAgentId ?? "executor";
|
|
237668
|
+
this.workflowStepRunAgentId ??= resolveWorkflowStepRunAgentId(this.options.agentStore, agentIdCandidate);
|
|
237669
|
+
const agentId = await this.workflowStepRunAgentId;
|
|
237670
|
+
if (!agentId) {
|
|
237671
|
+
if (!this.warnedUnattributableWorkflowStepRun) {
|
|
237672
|
+
this.warnedUnattributableWorkflowStepRun = true;
|
|
237673
|
+
stepExecLog.warn(`Skipping unattributable workflow-step activity runs for task ${taskDetail.id} (candidate: ${agentIdCandidate})`);
|
|
237674
|
+
}
|
|
237675
|
+
return null;
|
|
237676
|
+
}
|
|
237151
237677
|
return {
|
|
237152
237678
|
id: `${generateSyntheticRunId("workflow-step", taskDetail.id)}-step-${stepIndex}`,
|
|
237153
237679
|
agentId,
|
|
@@ -237167,6 +237693,7 @@ var StepSessionExecutor = class {
|
|
|
237167
237693
|
taskTitle: taskDetail.title,
|
|
237168
237694
|
assignedAgentId: taskDetail.assignedAgentId,
|
|
237169
237695
|
effectiveAgentId: this.options.effectiveAgentId,
|
|
237696
|
+
agentIdCandidate,
|
|
237170
237697
|
agentId,
|
|
237171
237698
|
stepIndex,
|
|
237172
237699
|
stepName: step?.name ?? `Step ${stepIndex}`
|
|
@@ -237181,6 +237708,7 @@ var StepSessionExecutor = class {
|
|
|
237181
237708
|
};
|
|
237182
237709
|
}
|
|
237183
237710
|
async saveWorkflowStepActivityRun(run) {
|
|
237711
|
+
if (!run) return;
|
|
237184
237712
|
const saveRun2 = this.options.agentStore?.saveRun?.bind(this.options.agentStore);
|
|
237185
237713
|
if (!saveRun2) return;
|
|
237186
237714
|
try {
|
|
@@ -237193,6 +237721,7 @@ var StepSessionExecutor = class {
|
|
|
237193
237721
|
}
|
|
237194
237722
|
}
|
|
237195
237723
|
async completeWorkflowStepActivityRun(run, status, result) {
|
|
237724
|
+
if (!run) return;
|
|
237196
237725
|
const terminalRun = {
|
|
237197
237726
|
...run,
|
|
237198
237727
|
endedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
@@ -237252,7 +237781,7 @@ var StepSessionExecutor = class {
|
|
|
237252
237781
|
if (semaphore) {
|
|
237253
237782
|
await semaphore.acquire();
|
|
237254
237783
|
}
|
|
237255
|
-
const activityRun = this.createWorkflowStepActivityRun(stepIndex, (/* @__PURE__ */ new Date()).toISOString());
|
|
237784
|
+
const activityRun = await this.createWorkflowStepActivityRun(stepIndex, (/* @__PURE__ */ new Date()).toISOString());
|
|
237256
237785
|
await this.saveWorkflowStepActivityRun(activityRun);
|
|
237257
237786
|
const trackingKey = this.makeTrackingKey(stepIndex);
|
|
237258
237787
|
let retries = 0;
|
|
@@ -238181,10 +238710,23 @@ function normalizeOptionalStepRevisionKey2(value) {
|
|
|
238181
238710
|
function optionalStepRevisionKey2(nodeId, stepName) {
|
|
238182
238711
|
return normalizeOptionalStepRevisionKey2(nodeId) || normalizeOptionalStepRevisionKey2(stepName) || "pre-merge-optional-step";
|
|
238183
238712
|
}
|
|
238713
|
+
var OPTIONAL_STEP_REVISION_RESET_MARKER = "Workflow revision ledger reset:";
|
|
238714
|
+
function resetIndexFor(log42, normalizedKey) {
|
|
238715
|
+
for (let index2 = log42.length - 1; index2 >= 0; index2 -= 1) {
|
|
238716
|
+
const outcome = log42[index2]?.outcome ?? "";
|
|
238717
|
+
const markerIndex = outcome.indexOf(OPTIONAL_STEP_REVISION_RESET_MARKER);
|
|
238718
|
+
if (markerIndex < 0) continue;
|
|
238719
|
+
const markerValue = outcome.slice(markerIndex + OPTIONAL_STEP_REVISION_RESET_MARKER.length).split(/\r?\n/, 1)[0]?.trim();
|
|
238720
|
+
if (normalizeOptionalStepRevisionKey2(markerValue) === normalizedKey) return index2;
|
|
238721
|
+
}
|
|
238722
|
+
return -1;
|
|
238723
|
+
}
|
|
238184
238724
|
function countOptionalStepRevisionAttempts2(task, key, stepName) {
|
|
238185
238725
|
const normalizedKey = normalizeOptionalStepRevisionKey2(key);
|
|
238186
238726
|
const normalizedStepName = normalizeOptionalStepRevisionKey2(stepName);
|
|
238187
|
-
|
|
238727
|
+
const log42 = task.log ?? [];
|
|
238728
|
+
const since = resetIndexFor(log42, normalizedKey);
|
|
238729
|
+
return (since >= 0 ? log42.slice(since + 1) : log42).filter((entry) => {
|
|
238188
238730
|
const action = entry.action ?? "";
|
|
238189
238731
|
const outcome = entry.outcome ?? "";
|
|
238190
238732
|
if (!/attempt \d+\//.test(action)) return false;
|
|
@@ -239011,10 +239553,12 @@ ${JSON.stringify({ verdict: repairedVerdict })}`,
|
|
|
239011
239553
|
session.dispose();
|
|
239012
239554
|
await agentLogger.flush();
|
|
239013
239555
|
if (parsed.verdict) {
|
|
239556
|
+
const findingsUnreadable = verdictRepairResult === "repaired" && (parsed.findings?.length ?? 0) === 0;
|
|
239014
239557
|
const gated = reviewBlockingSeverity ? applyReviewSeverityGate({
|
|
239015
239558
|
verdict: parsed.verdict,
|
|
239016
239559
|
findings: parsed.findings,
|
|
239017
|
-
threshold: reviewBlockingSeverity
|
|
239560
|
+
threshold: reviewBlockingSeverity,
|
|
239561
|
+
findingsUnreadable
|
|
239018
239562
|
}) : void 0;
|
|
239019
239563
|
const effectiveVerdict = gated?.verdict ?? parsed.verdict;
|
|
239020
239564
|
if (gated?.downgraded) {
|
|
@@ -252725,7 +253269,7 @@ async function routeGraphMergeFailureToRetry(deps, live, result, abortProvenance
|
|
|
252725
253269
|
const parked = await parkTaskFailed(
|
|
252726
253270
|
deps.store,
|
|
252727
253271
|
live.id,
|
|
252728
|
-
|
|
253272
|
+
`${AUTO_MERGE_RETRY_REJECTED_PREFIX} ${reason}`,
|
|
252729
253273
|
deps.getRunContextFor(live.id),
|
|
252730
253274
|
live.columnMovedAt
|
|
252731
253275
|
);
|
|
@@ -257222,9 +257766,13 @@ async function runImplementation(deps, task, graphCompletion, reportImplementati
|
|
|
257222
257766
|
*/
|
|
257223
257767
|
credentialInstanceId: detail.credentialInstanceId,
|
|
257224
257768
|
resolveCredentialInstanceRetarget: nextStepInstance,
|
|
257225
|
-
|
|
257226
|
-
|
|
257227
|
-
|
|
257769
|
+
/*
|
|
257770
|
+
FNXC:ColumnAgent 2026-09-04-14:11:
|
|
257771
|
+
The effective step identity is the governing column agent or the authoritative assignee.
|
|
257772
|
+
Both are existence-proved reads; with empty project.boards the column seam is absent, so
|
|
257773
|
+
retaining only it lost attribution for every assigned task.
|
|
257774
|
+
*/
|
|
257775
|
+
effectiveAgentId: stepIdentityAgent?.id,
|
|
257228
257776
|
actionGateContext: deps.buildActionGateContext(task.id, stepIdentityAgent, settings.defaultAgentPermissionPolicy),
|
|
257229
257777
|
permanentAgentGating: deps.buildPermanentAgentGatingContext(task.id, stepIdentityAgent, settings.defaultAgentPermissionPolicy),
|
|
257230
257778
|
// FNXC:McpConfig 2026-06-25-23:03: Per-step workflow sessions are an executor lane, so they inherit the task's resolved MCP set from the effective step identity agent and never re-read or log plaintext secret values.
|
|
@@ -261806,10 +262354,22 @@ async function terminateAllChildren(deps, parentTaskId) {
|
|
|
261806
262354
|
|
|
261807
262355
|
// ../engine/src/executor/clear-terminal-step-failures-for-retry.ts
|
|
261808
262356
|
init_src2();
|
|
262357
|
+
function latestTerminalPreMergeFailureId(results) {
|
|
262358
|
+
return (results ?? []).filter((result) => (result.phase || "pre-merge") === "pre-merge" && (result.status === "failed" || result.status === "advisory_failure")).sort((left, right) => {
|
|
262359
|
+
const leftTime = Date.parse(left.completedAt || left.startedAt || "");
|
|
262360
|
+
const rightTime = Date.parse(right.completedAt || right.startedAt || "");
|
|
262361
|
+
return (Number.isFinite(rightTime) ? rightTime : 0) - (Number.isFinite(leftTime) ? leftTime : 0);
|
|
262362
|
+
})[0]?.workflowStepId;
|
|
262363
|
+
}
|
|
261809
262364
|
async function clearTerminalStepFailuresForRetry(deps, taskId, mode) {
|
|
261810
262365
|
const live = await deps.store.getTask(taskId).catch(() => null);
|
|
261811
262366
|
if (!live) return;
|
|
261812
|
-
const
|
|
262367
|
+
const remediatedStepId = mode === "archive" ? latestTerminalPreMergeFailureId(live.workflowStepResults) : void 0;
|
|
262368
|
+
const cleared = mode === "archive" ? archiveTerminalWorkflowStepFailures(
|
|
262369
|
+
live.workflowStepResults,
|
|
262370
|
+
void 0,
|
|
262371
|
+
remediatedStepId ? { workflowStepIds: /* @__PURE__ */ new Set([remediatedStepId]) } : {}
|
|
262372
|
+
) : clearTerminalWorkflowStepFailures(live.workflowStepResults);
|
|
261813
262373
|
if (cleared !== live.workflowStepResults) {
|
|
261814
262374
|
await deps.store.updateTask(taskId, { workflowStepResults: cleared }, deps.getRunContextFor(taskId));
|
|
261815
262375
|
}
|
|
@@ -270446,10 +271006,14 @@ var MissionExecutionLoop = class extends EventEmitter26 {
|
|
|
270446
271006
|
for (const slice of milestone.slices) {
|
|
270447
271007
|
if (slice.status !== "active") continue;
|
|
270448
271008
|
const supersededFixes = await this.missionStore.reconcileSupersededGeneratedFixFeatures(slice.id);
|
|
270449
|
-
const supersededFeatureIds = new Set(supersededFixes.featureIds);
|
|
271009
|
+
const supersededFeatureIds = new Set(supersededFixes.featureIds ?? []);
|
|
271010
|
+
if ((supersededFixes.repairedCount ?? 0) > 0) {
|
|
271011
|
+
loopLog.warn(`Recovery: restored ${supersededFixes.repairedCount ?? 0} generated Fix Features in slice ${slice.id}: ${(supersededFixes.repairedFeatureIds ?? []).join(", ")}`);
|
|
271012
|
+
recoveredCount += supersededFixes.repairedCount ?? 0;
|
|
271013
|
+
}
|
|
270450
271014
|
if (supersededFixes.supersededCount > 0) {
|
|
270451
271015
|
loopLog.warn(
|
|
270452
|
-
`Recovery: superseded ${supersededFixes.supersededCount} generated Fix Features in slice ${slice.id} because an ancestor feature already passed
|
|
271016
|
+
`Recovery: superseded ${supersededFixes.supersededCount} generated Fix Features in slice ${slice.id} because an ancestor or the Fix feature's own validation already passed`
|
|
270453
271017
|
);
|
|
270454
271018
|
recoveredCount += supersededFixes.supersededCount;
|
|
270455
271019
|
}
|
|
@@ -271828,6 +272392,7 @@ init_agent_session_helpers();
|
|
|
271828
272392
|
init_effective_settings();
|
|
271829
272393
|
|
|
271830
272394
|
// ../engine/src/spec-validation/task-document-references.ts
|
|
272395
|
+
init_src2();
|
|
271831
272396
|
import { access as access11 } from "node:fs/promises";
|
|
271832
272397
|
import { join as join95 } from "node:path";
|
|
271833
272398
|
var TASK_PATH_REGEX = /\.fusion\/tasks\/[A-Z0-9]+(?:-[A-Z0-9]+)*\/[^\s`'"\]]+/g;
|
|
@@ -271840,12 +272405,11 @@ function collectTaskPaths(text2) {
|
|
|
271840
272405
|
}
|
|
271841
272406
|
function collectStepSections(stepsSection) {
|
|
271842
272407
|
const sections2 = [];
|
|
271843
|
-
const headingMatches =
|
|
272408
|
+
const headingMatches = matchStepHeadings(stepsSection);
|
|
271844
272409
|
for (let i = 0; i < headingMatches.length; i += 1) {
|
|
271845
272410
|
const heading = headingMatches[i];
|
|
271846
|
-
const
|
|
271847
|
-
|
|
271848
|
-
sections2.push({ name: `Step ${heading[1]}`, body: stepsSection.slice(start, end).trim() });
|
|
272411
|
+
const end = i + 1 < headingMatches.length ? headingMatches[i + 1].index : stepsSection.length;
|
|
272412
|
+
sections2.push({ name: `Step ${heading.headingNumber}`, body: stepsSection.slice(heading.headingLineEnd, end).trim() });
|
|
271849
272413
|
}
|
|
271850
272414
|
return sections2;
|
|
271851
272415
|
}
|
|
@@ -271979,30 +272543,6 @@ var PlanningResetFence = class {
|
|
|
271979
272543
|
}
|
|
271980
272544
|
};
|
|
271981
272545
|
|
|
271982
|
-
// ../engine/src/agents/planning-liveness.ts
|
|
271983
|
-
var probes = /* @__PURE__ */ new Set();
|
|
271984
|
-
var planningLivenessRegistry = {
|
|
271985
|
-
registerPlanningLivenessProbe(probe) {
|
|
271986
|
-
probes.add(probe);
|
|
271987
|
-
return () => probes.delete(probe);
|
|
271988
|
-
},
|
|
271989
|
-
isPlanningLive(taskId) {
|
|
271990
|
-
for (const probe of probes) {
|
|
271991
|
-
try {
|
|
271992
|
-
if (probe(taskId)) return true;
|
|
271993
|
-
} catch {
|
|
271994
|
-
return true;
|
|
271995
|
-
}
|
|
271996
|
-
}
|
|
271997
|
-
return false;
|
|
271998
|
-
},
|
|
271999
|
-
hasRegisteredProbe() {
|
|
272000
|
-
return probes.size > 0;
|
|
272001
|
-
}
|
|
272002
|
-
};
|
|
272003
|
-
var registerPlanningLivenessProbe = planningLivenessRegistry.registerPlanningLivenessProbe.bind(planningLivenessRegistry);
|
|
272004
|
-
var isPlanningLive = planningLivenessRegistry.isPlanningLive.bind(planningLivenessRegistry);
|
|
272005
|
-
|
|
272006
272546
|
// ../engine/src/triage.ts
|
|
272007
272547
|
init_agent_instructions();
|
|
272008
272548
|
init_fallback_model_observer();
|
|
@@ -274239,7 +274779,7 @@ You are ${assignedAgent.name}${assignedAgent.title?.trim() ? `, ${assignedAgent.
|
|
|
274239
274779
|
planLog.warn(`${task.id} ${retryMessage}`);
|
|
274240
274780
|
await this.store.logEntry(task.id, retryMessage);
|
|
274241
274781
|
await this.updatePlanningStateIfStillCurrent(task, {
|
|
274242
|
-
status: this.
|
|
274782
|
+
status: await this.resolvePlanningRetryHoldStatus(task, written),
|
|
274243
274783
|
error: null,
|
|
274244
274784
|
recoveryRetryCount: decision.nextState.recoveryRetryCount,
|
|
274245
274785
|
nextRecoveryAt: decision.nextState.nextRecoveryAt
|
|
@@ -274276,9 +274816,13 @@ You are ${assignedAgent.name}${assignedAgent.title?.trim() ? `, ${assignedAgent.
|
|
|
274276
274816
|
const retryMessage = `Generated plan failed deterministic validation (${deterministicSpecFailure}) \u2014 retry ${attempt}/${MAX_RECOVERY_RETRIES} in ${delay3}.`;
|
|
274277
274817
|
planLog.warn(`${task.id} ${retryMessage}`);
|
|
274278
274818
|
await this.store.logEntry(task.id, retryMessage);
|
|
274279
|
-
|
|
274819
|
+
await this.store.logEntry(
|
|
274820
|
+
task.id,
|
|
274821
|
+
"AI spec revision requested",
|
|
274822
|
+
`Deterministic plan validation rejected the generated plan: ${deterministicSpecFailure}`
|
|
274823
|
+
).catch(() => void 0);
|
|
274280
274824
|
await this.updatePlanningStateIfStillCurrent(task, {
|
|
274281
|
-
status:
|
|
274825
|
+
status: await this.resolvePlanningRetryHoldStatus(task, written),
|
|
274282
274826
|
error: null,
|
|
274283
274827
|
recoveryRetryCount: decision.nextState.recoveryRetryCount,
|
|
274284
274828
|
nextRecoveryAt: decision.nextState.nextRecoveryAt
|
|
@@ -274448,9 +274992,10 @@ You are ${assignedAgent.name}${assignedAgent.title?.trim() ? `, ${assignedAgent.
|
|
|
274448
274992
|
const retryMessage = `${failureMessage} \u2014 retry ${decision.nextState.recoveryRetryCount}/${MAX_RECOVERY_RETRIES} in ${formatDelay(decision.delayMs)}.`;
|
|
274449
274993
|
planLog.warn(`${task.id} ${retryMessage}`);
|
|
274450
274994
|
await this.store.logEntry(task.id, retryMessage).catch(() => void 0);
|
|
274995
|
+
const retryHoldStatus = await this.resolvePlanningRetryHoldStatus(task);
|
|
274451
274996
|
await this.updatePlanningStateIfStillCurrent(task, (live) => ({
|
|
274452
274997
|
...persistMarker(live),
|
|
274453
|
-
status:
|
|
274998
|
+
status: retryHoldStatus,
|
|
274454
274999
|
error: null,
|
|
274455
275000
|
recoveryRetryCount: decision.nextState.recoveryRetryCount,
|
|
274456
275001
|
nextRecoveryAt: decision.nextState.nextRecoveryAt
|
|
@@ -274480,7 +275025,7 @@ You are ${assignedAgent.name}${assignedAgent.title?.trim() ? `, ${assignedAgent.
|
|
|
274480
275025
|
planLog.warn(`${task.id}: failed to log transient-error retry entry: ${msg}`);
|
|
274481
275026
|
});
|
|
274482
275027
|
}
|
|
274483
|
-
const restoreStatus = this.
|
|
275028
|
+
const restoreStatus = await this.resolvePlanningRetryHoldStatus(task);
|
|
274484
275029
|
await this.updatePlanningStateIfStillCurrent(task, {
|
|
274485
275030
|
status: restoreStatus,
|
|
274486
275031
|
recoveryRetryCount: decision.nextState.recoveryRetryCount,
|
|
@@ -274532,7 +275077,7 @@ You are ${assignedAgent.name}${assignedAgent.title?.trim() ? `, ${assignedAgent.
|
|
|
274532
275077
|
const msg = logErr instanceof Error ? logErr.message : String(logErr);
|
|
274533
275078
|
planLog.warn(`${task.id}: failed to log planning-failure retry entry: ${msg}`);
|
|
274534
275079
|
});
|
|
274535
|
-
const restoreStatus = this.
|
|
275080
|
+
const restoreStatus = await this.resolvePlanningRetryHoldStatus(task);
|
|
274536
275081
|
await this.updatePlanningStateIfStillCurrent(task, {
|
|
274537
275082
|
status: restoreStatus,
|
|
274538
275083
|
recoveryRetryCount: genericDecision.nextState.recoveryRetryCount,
|
|
@@ -274816,11 +275361,22 @@ ${lines.join("\n")}` }],
|
|
|
274816
275361
|
}
|
|
274817
275362
|
return null;
|
|
274818
275363
|
}
|
|
275364
|
+
/*
|
|
275365
|
+
FNXC:TriagePlanningRetry 2026-09-05-22:06:
|
|
275366
|
+
A retry over a finished-looking PROMPT.md must hold `needs-replan`, matching scheduler validation
|
|
275367
|
+
retries. Clearing status makes the card dispatchable and bypasses finalizeApprovedTask approval.
|
|
275368
|
+
*/
|
|
275369
|
+
async resolvePlanningRetryHoldStatus(task, knownPromptContent) {
|
|
275370
|
+
const restored = this.restoreStatusAfterInterruptedTriageWork(task);
|
|
275371
|
+
if (restored) return restored;
|
|
275372
|
+
const content = knownPromptContent ?? await this.readNonEmptyPromptDraft(task.id, "planning retry hold");
|
|
275373
|
+
return content && !isTaskAwaitingPlanning(task, content) ? "needs-replan" : null;
|
|
275374
|
+
}
|
|
274819
275375
|
async validateGeneratedPrompt(taskId, promptContent) {
|
|
274820
275376
|
if (!promptContent.trim()) {
|
|
274821
275377
|
return "PROMPT.md file not found or empty";
|
|
274822
275378
|
}
|
|
274823
|
-
const headingNumbers =
|
|
275379
|
+
const headingNumbers = matchStepHeadings(promptContent).map((match) => match.headingNumber);
|
|
274824
275380
|
if (headingNumbers.length > 0 && !headingNumbers.every((heading, index2) => heading === index2)) {
|
|
274825
275381
|
const diagnostic = `Step headings must be contiguous 0-based execution indices (observed: ${headingNumbers.join(", ")}). Renumber from Step 0 and update prose cross-references.`;
|
|
274826
275382
|
planLog.warn(`${taskId}: ${diagnostic}`);
|
|
@@ -287735,7 +288291,7 @@ ${detail}`);
|
|
|
287735
288291
|
metadata: { taskId: task.id, nodeId: reroute.nodeId, workflowStepId: reroute.workflowStepId, reason: reroute.reason, source: "merge-gate", missingGateCount: mergeGate.requiredPreMergeStepIds.size }
|
|
287736
288292
|
});
|
|
287737
288293
|
}
|
|
287738
|
-
if (blocker
|
|
288294
|
+
if (isStaleContentApprovalBlocker(blocker) && mergeContent.kind === "singular") {
|
|
287739
288295
|
const reroute = await rerouteSingularStaleContentToReview(store, task, {
|
|
287740
288296
|
requiredPreMergeStepIds: mergeGate.requiredPreMergeStepIds,
|
|
287741
288297
|
mergeContent
|
|
@@ -287745,9 +288301,10 @@ ${detail}`);
|
|
|
287745
288301
|
nodeId: void 0,
|
|
287746
288302
|
workflowStepId: void 0
|
|
287747
288303
|
}));
|
|
287748
|
-
|
|
287749
|
-
|
|
287750
|
-
|
|
288304
|
+
await store.logEntry(
|
|
288305
|
+
task.id,
|
|
288306
|
+
`[pre-merge] Review lane '${reroute.nodeId ?? "unknown"}' approved older content; re-review is owned by the workflow graph (${reroute.reason}).`
|
|
288307
|
+
);
|
|
287751
288308
|
const auditKey = `${task.id}:${reroute.reason}`;
|
|
287752
288309
|
if (!this.staleContentRerouteAuditKeys.has(auditKey)) {
|
|
287753
288310
|
this.staleContentRerouteAuditKeys.add(auditKey);
|