@miller-tech/uap 1.151.0 → 1.153.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (46) hide show
  1. package/dist/.tsbuildinfo +1 -1
  2. package/dist/cli/deliver.d.ts.map +1 -1
  3. package/dist/cli/deliver.js +127 -265
  4. package/dist/cli/deliver.js.map +1 -1
  5. package/dist/delivery/ci-reconverge.d.ts +42 -0
  6. package/dist/delivery/ci-reconverge.d.ts.map +1 -0
  7. package/dist/delivery/ci-reconverge.js +65 -0
  8. package/dist/delivery/ci-reconverge.js.map +1 -0
  9. package/dist/delivery/convergence-loop.d.ts.map +1 -1
  10. package/dist/delivery/convergence-loop.js +3 -8
  11. package/dist/delivery/convergence-loop.js.map +1 -1
  12. package/dist/delivery/decompose.d.ts +8 -0
  13. package/dist/delivery/decompose.d.ts.map +1 -1
  14. package/dist/delivery/decompose.js +9 -1
  15. package/dist/delivery/decompose.js.map +1 -1
  16. package/dist/delivery/delivery-result.d.ts +11 -0
  17. package/dist/delivery/delivery-result.d.ts.map +1 -0
  18. package/dist/delivery/delivery-result.js +20 -0
  19. package/dist/delivery/delivery-result.js.map +1 -0
  20. package/dist/delivery/epic-controller.d.ts +9 -5
  21. package/dist/delivery/epic-controller.d.ts.map +1 -1
  22. package/dist/delivery/epic-controller.js +5 -1
  23. package/dist/delivery/epic-controller.js.map +1 -1
  24. package/dist/delivery/epic-mission.d.ts +92 -0
  25. package/dist/delivery/epic-mission.d.ts.map +1 -0
  26. package/dist/delivery/epic-mission.js +257 -0
  27. package/dist/delivery/epic-mission.js.map +1 -0
  28. package/dist/delivery/index.d.ts +2 -0
  29. package/dist/delivery/index.d.ts.map +1 -1
  30. package/dist/delivery/index.js +2 -0
  31. package/dist/delivery/index.js.map +1 -1
  32. package/dist/delivery/orchestrated-mission.d.ts +1 -4
  33. package/dist/delivery/orchestrated-mission.d.ts.map +1 -1
  34. package/dist/delivery/orchestrated-mission.js +4 -14
  35. package/dist/delivery/orchestrated-mission.js.map +1 -1
  36. package/dist/delivery/plan-check.d.ts.map +1 -1
  37. package/dist/delivery/plan-check.js +10 -1
  38. package/dist/delivery/plan-check.js.map +1 -1
  39. package/dist/delivery/verifier-ladder.d.ts +10 -0
  40. package/dist/delivery/verifier-ladder.d.ts.map +1 -1
  41. package/dist/delivery/verifier-ladder.js +16 -0
  42. package/dist/delivery/verifier-ladder.js.map +1 -1
  43. package/package.json +1 -1
  44. package/src/policies/enforcers/__pycache__/_common.cpython-312.pyc +0 -0
  45. package/templates/hooks/__pycache__/deliver_autoroute.cpython-312.pyc +0 -0
  46. package/tools/agents/scripts/__pycache__/toolcall_path_normalizer.cpython-312.pyc +0 -0
@@ -1 +1 @@
1
- {"version":3,"file":"deliver.d.ts","sourceRoot":"","sources":["../../src/cli/deliver.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"deliver.d.ts","sourceRoot":"","sources":["../../src/cli/deliver.ts"],"names":[],"mappings":"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"}
@@ -95,7 +95,6 @@ import { createAgenticExecutor, noopApplier, selectExecutorMode, lockContractFil
95
95
  import { createRepairEscalation } from '../delivery/repair-escalation.js';
96
96
  import { clearStop, isStopRequested, loadRunState, newRunId, saveRunState } from '../delivery/run-state.js';
97
97
  import { phaseInstruction, planDeliveryPhases, shouldDecompose } from '../delivery/decompose.js';
98
- import { runEpics } from '../delivery/epic-controller.js';
99
98
  import { initLedger, markItem } from '../delivery/completion-ledger.js';
100
99
  import { loadUapConfigRaw } from '../utils/config-loader.js';
101
100
  import { buildUserPathsNote, createUserValidationRunner, resolveUserValidationMode, synthesizeUserValidationRung, } from '../delivery/user-validation.js';
@@ -111,7 +110,7 @@ function cfgRawEarlyForUvFactory(projectRoot) {
111
110
  }
112
111
  };
113
112
  }
114
- import { resolveSessionTokenBudget, sessionWorkingBudget, discoverModelContextWindow, CONTEXT_BUDGET_MARKER } from '../delivery/context-budget.js';
113
+ import { resolveSessionTokenBudget, sessionWorkingBudget, discoverModelContextWindow } from '../delivery/context-budget.js';
115
114
  import { preflightProject, formatPreflightFailure } from '../delivery/project-preflight.js';
116
115
  import { resolveFidelity } from '../delivery/fidelity.js';
117
116
  import { installRunExitRecorder } from '../delivery/run-exit.js';
@@ -120,14 +119,16 @@ import { completeDeliveryTask, openDeliveryTask, recordDeliveryOutcome, recordOr
120
119
  import { autoMineHaloTraces, summarizeWeaknesses, weaknessGuidance, loadPersistedWeaknesses } from '../delivery/auto-mine.js';
121
120
  import { createGitWorktreeProvider } from '../delivery/candidate-workspace.js';
122
121
  import { createTaskWorkspaceManager, resolveParallelTasks } from '../delivery/task-workspace.js';
123
- import { runOrchestratedMission as runOrchestratedMissionCore, foldDeliveryResult } from '../delivery/orchestrated-mission.js';
122
+ import { runOrchestratedMission as runOrchestratedMissionCore } from '../delivery/orchestrated-mission.js';
123
+ import { runCiReconverge } from '../delivery/ci-reconverge.js';
124
+ import { runEpicMission as runEpicMissionCore } from '../delivery/epic-mission.js';
124
125
  import { createHaloDeliveryTracer } from '../delivery/halo-trace.js';
125
126
  import { haloTracePath, isHaloTracingEnabled } from '../observability/halo-exporter.js';
126
127
  import { createRunCoordinator } from '../delivery/run-coordinator.js';
127
128
  import { readCuratedIdeas } from './ideate.js';
128
129
  import { createEscalationController, defaultEscalationLadder } from '../delivery/escalation.js';
129
130
  import { FilePracticeStore, defaultPracticePath, extractKeywords, retrievePracticesSemantic, } from '../delivery/practice.js';
130
- import { detectRungs, runLadder, runTieredLadder, tierOf, TIER_ORDER, demoteBaselineFailures } from '../delivery/verifier-ladder.js';
131
+ import { detectRungs, mergeRedetectedRungs, runLadder, runTieredLadder, tierOf, TIER_ORDER, demoteBaselineFailures } from '../delivery/verifier-ladder.js';
131
132
  import { runDeployDevLadder } from '../delivery/deploy-dev-gate.js';
132
133
  import { commitPushAndWatch } from '../delivery/ci-watcher.js';
133
134
  import { snapshotTree, restoreTree, disposeSnapshot } from '../delivery/snapshot.js';
@@ -1507,7 +1508,11 @@ async function runDeliver(instruction, options) {
1507
1508
  envDecompose: process.env.UAP_DELIVER_DECOMPOSE,
1508
1509
  heuristic: () => shouldDecompose(instruction, autoPlan?.complexity),
1509
1510
  });
1510
- if (!phases && decomposeWanted && !resumeState) {
1511
+ // Skipped when the epic path owns the mission: runEpicMission re-plans via
1512
+ // planEpics, so this call would burn an evaluator pass (+ thought
1513
+ // experiment) on a phases array the dispatch never uses. Kept for resume
1514
+ // and the --no-epics paths.
1515
+ if (!phases && decomposeWanted && !resumeState && !epicsEnabled) {
1511
1516
  console.log(chalk.cyan('🧩 decompose: planning sequential delivery phases…'));
1512
1517
  // ExpertOrchestrator lifecycle chain → phase-shaping hints (fail-soft).
1513
1518
  let lifecycleHints;
@@ -1702,18 +1707,12 @@ async function runDeliver(instruction, options) {
1702
1707
  },
1703
1708
  isMergeBlocked: (f) => mergeProtected.has(f.toLowerCase()),
1704
1709
  verifyCombined: () => {
1705
- // Mirror the loop's redetect-merge: redetection REASSIGNS the loop's
1706
- // local rungs and never mutates this captured t0 array, so a
1707
- // from-scratch build's mid-mission gates must be re-merged here or
1710
+ // Redetection REASSIGNS the loop's local rungs and never mutates this
1711
+ // captured t0 array re-merge (same pure policy the loop uses) or
1708
1712
  // the combined tree is verified against stale t0 gates only.
1709
1713
  let combinedRungs = rungs;
1710
1714
  try {
1711
- const detected = detectRungs(projectRoot);
1712
- const have = new Set(rungs.map((r) => r.id));
1713
- const allow = loopConfig.redetectFilter ?? ((r) => tierOf(r) === 'fast' || tierOf(r) === 'runtime');
1714
- const added = detected.filter((r) => !have.has(r.id) && allow(r));
1715
- if (added.length > 0)
1716
- combinedRungs = [...rungs, ...added];
1715
+ combinedRungs = mergeRedetectedRungs(rungs, detectRungs(projectRoot), loopConfig.redetectFilter);
1717
1716
  }
1718
1717
  catch {
1719
1718
  // detection unavailable - verify against the t0 rungs
@@ -1818,220 +1817,86 @@ async function runDeliver(instruction, options) {
1818
1817
  * per-epic attempt budget is spent. The blackboard orchestrator still runs
1819
1818
  * WITHIN each epic when that epic itself decomposes.
1820
1819
  */
1820
+ /**
1821
+ * Wire deliver's ambient context into the extracted epic-mission runner
1822
+ * (src/delivery/epic-mission.ts) — the DEFAULT delivery path, now driven
1823
+ * through a unit-tested core instead of a 230-line untested closure.
1824
+ */
1821
1825
  const runEpicMission = async () => {
1822
- const all = {
1823
- success: true, alreadyDelivered: false, turns: 0, bestScore: 0, bestTurn: 0,
1824
- history: [], finalFeedback: '', finalOutput: '', totalDurationMs: 0,
1825
- };
1826
1826
  const epicParallelTasks = resolveParallelTasks(cfgRaw.deliver?.parallelTasks);
1827
1827
  const contractsFirst = process.env.UAP_DELIVER_CONTRACTS !== '0';
1828
1828
  const scaffoldFirst = process.env.UAP_DELIVER_SCAFFOLD !== '0';
1829
- const planned = await planDeliveryPhases(instruction, verdictExecutor, undefined, {
1830
- sessionTokenBudget: sessionBudget,
1831
- contractsFirst,
1832
- scaffoldFirst,
1833
- });
1834
- const epics = (planned.length >= 2
1835
- ? planned
1836
- : [{ id: 'mission', title: 'Mission', goal: instruction }]).map((ph) => ({ id: ph.id, title: ph.title, goal: ph.goal, ...(ph.deps ? { deps: ph.deps } : {}), ...(ph.contracts ? { contracts: true } : {}), ...(ph.scaffold ? { scaffold: true } : {}) }));
1837
- console.log(chalk.cyan(`\u{1f5c2} epic controller: ${epics.length} epic(s): ${epics.map((e) => e.title).join(' \u2192 ')}`));
1838
- let lastContractEpicFiles = [];
1839
- // Hands-free: auto-populate the completion ledger so the whole multi-epic
1840
- // build has an objective, cross-session definition of done (Option B). The
1841
- // Stop hook + reactor consult it to keep any model going until 100%.
1842
- try {
1843
- initLedger(projectRoot, instruction, epics.map((e) => ({ id: e.id, title: e.title, kind: 'epic', ...(e.deps ? { deps: e.deps } : {}) })));
1844
- }
1845
- catch { /* ledger is best-effort */ }
1846
- const epicResult = await runEpics({
1847
- mission: instruction,
1848
- epics,
1849
- maxAttemptsPerEpic: Number(process.env.UAP_DELIVER_EPIC_ATTEMPTS ?? 3), // (#4b) 2→3
1850
- // (#4c) Recursive split depth: a huge epic that still can't land after a
1851
- // split is split again, one level shallower, until each piece fits a rail
1852
- // — instead of failing the whole mission at the first split. Bounded.
1853
- splitDepth: Math.max(1, Number(process.env.UAP_DELIVER_EPIC_SPLIT_DEPTH ?? 2)),
1854
- // (#5) Auto-escalation: re-plan a failed epic into smaller pieces and
1855
- // retry them rather than declaring the mission incomplete. On by default
1856
- // for the epic path (already gated to complex missions); disable with
1857
- // UAP_DELIVER_SPLIT_ON_ANY_FAILURE=0 to restore budget-exhaustion-only.
1858
- splitOnAnyFailure: process.env.UAP_DELIVER_SPLIT_ON_ANY_FAILURE !== '0',
1859
- // Re-plan a failed epic into sub-epics. Fires on context-budget
1860
- // exhaustion (rail auto-size) and, under splitOnAnyFailure, on any
1861
- // exhausted-attempts failure (auto-escalation). Always provided so #5 has
1862
- // a planner; declines (null) when it can't produce ≥2 pieces.
1863
- splitEpic: async (epic, lastFailure) => {
1864
- const budgetHit = (lastFailure ?? '').includes(CONTEXT_BUDGET_MARKER);
1865
- const reason = budgetHit
1866
- ? `outgrew its ~${(sessionBudget ?? 0).toLocaleString()}-token session budget`
1867
- : 'could not be delivered whole after all attempts';
1868
- console.log(chalk.yellow(` ✂ epic ${epic.id} ${reason} — re-planning as smaller sub-epics`));
1869
- const subGoal = `${epic.goal}\n\n(The previous attempt did not complete` +
1870
- `${lastFailure ? `: ${lastFailure.slice(0, 300)}` : ''}. Split this into smaller, independently completable phases.)`;
1871
- // Re-plan is already reactive (the failure is in subGoal) - the extra
1872
- // thought-experiment judge call buys nothing here; skip it.
1873
- const subs = await planDeliveryPhases(subGoal, verdictExecutor, undefined, { sessionTokenBudget: sessionBudget, thoughtExperiment: false });
1874
- return subs.length >= 2 ? subs.map((s) => ({ id: s.id, title: s.title, goal: s.goal })) : null;
1875
- },
1876
- onEpic: (epic, outcome) => {
1877
- try {
1878
- markItem(projectRoot, epic.id, outcome.accepted ? 'done' : 'failed', outcome.accepted ? undefined : outcome.summary);
1879
- }
1880
- catch { /* best-effort */ }
1881
- if (epic.contracts && outcome.accepted && lastContractEpicFiles.length > 0) {
1882
- const locked = lockContractFiles(contractLock, projectRoot, lastContractEpicFiles);
1883
- if (locked.length > 0) {
1884
- console.log(chalk.cyan(` \u{1f512} contracts locked for later epics: ${locked.join(', ')}`));
1885
- }
1886
- }
1887
- console.log((outcome.accepted ? chalk.green(' \u2713') : chalk.red(' \u2717')) +
1888
- chalk.dim(` epic ${epic.id}: ${outcome.accepted ? 'accepted' : 'failed'} after ${outcome.attempts} attempt(s), ${outcome.turns} turn(s)`));
1829
+ return runEpicMissionCore({
1830
+ instruction,
1831
+ planEpics: () => planDeliveryPhases(instruction, verdictExecutor, undefined, {
1832
+ sessionTokenBudget: sessionBudget,
1833
+ contractsFirst,
1834
+ scaffoldFirst,
1835
+ }),
1836
+ // Re-decomposition is already reactive (the failure text is in the
1837
+ // goal) the extra thought-experiment judge call buys nothing here.
1838
+ planSplit: (subGoal) => planDeliveryPhases(subGoal, verdictExecutor, undefined, {
1839
+ sessionTokenBudget: sessionBudget,
1840
+ thoughtExperiment: false,
1841
+ }),
1842
+ planEpicTasks: (goal) => planDeliveryPhases(goal, verdictExecutor, undefined, {
1843
+ sessionTokenBudget: sessionBudget,
1844
+ thoughtExperiment: false,
1845
+ }),
1846
+ epicParallelTasks,
1847
+ runOrchestrated: (missionText, plan, parentTaskId) => runOrchestratedMissionCore(buildOrchestratedDeps(missionText, plan, epicParallelTasks, parentTaskId)),
1848
+ runEpicLoop: async (scoped) => {
1849
+ const loop = new ConvergenceLoop({ ...loopConfig, baselineCheck: false, resumeFrom: undefined, onIteration: makeIterationHook() }, executor, seams);
1850
+ return loop.deliver(scoped);
1889
1851
  },
1890
- runEpic: async (epic, ctx) => {
1891
- const priors = ctx.priorSummaries.length
1892
- ? `\n\nALREADY BUILT (prior epics \u2014 build on them, do not redo):\n${ctx.priorSummaries.map((sm, i) => `${i + 1}. ${sm}`).join('\n')}`
1893
- : '';
1894
- const retry = ctx.lastFailure ? `\n\nPREVIOUS ATTEMPT FEEDBACK (fix this):\n${ctx.lastFailure}` : '';
1895
- // Contracts-first steering: the contracts epic is told its output IS
1896
- // the frozen API; later epics are told which files are locked and to
1897
- // build against them exactly. Locked paths also land in the judge's
1898
- // spec, so spec-referenced evidence guarantees it SEES the contracts.
1899
- const contractsNote = epic.contracts
1900
- ? '\n\nThis is the CONTRACTS epic: define the COMPLETE shared types/interfaces/registry APIs the later epics will build against. They must compile, with minimal stub bodies. After this epic is accepted these files are FROZEN for the rest of the mission — make the signatures right.'
1901
- : contractLock.size > 0
1902
- ? `\n\nLOCKED CONTRACTS (read-only — write attempts will be refused): ${[...contractLock].join(', ')}. Build against these exact APIs; make YOUR code match their imports, type names and signatures.`
1903
- : '';
1904
- const scaffoldIds = new Set(epics.filter((e) => e.scaffold).map((e) => e.id));
1905
- const fillsScaffold = !epic.scaffold && (epic.deps ?? []).some((d) => scaffoldIds.has(d));
1906
- const scaffoldNote = epic.scaffold
1907
- ? '\n\nThis is a SCAFFOLD epic: create the compiling SKELETON only — complete public signatures, wired imports/exports, and todo!()-style stub bodies (todo!() / raise NotImplementedError / throw new Error("TODO")). Do NOT implement the logic; a later FILL epic does that. The build/check gates must pass.'
1908
- : fillsScaffold
1909
- ? '\n\nThis is a FILL epic: the skeleton already exists with correct signatures. IMPLEMENT the stub bodies (todo!()/NotImplementedError/TODO throws) — do NOT change any existing signature, rename anything, or restructure modules. When you finish, no stub markers should remain in the files this epic fills.'
1910
- : '';
1911
- const scoped = `OVERALL MISSION (context): ${instruction.slice(0, 300)}\n\n` +
1912
- `EPIC \u2014 ${epic.title}:\n${epic.goal}${priors}${retry}${contractsNote}${scaffoldNote}\n\n` +
1913
- 'Deliver ONLY this epic. All gates must pass at the end.';
1914
- console.log(chalk.bold(`\u25b6 epic ${epic.id} (attempt ${ctx.attempt}): ${epic.title}`));
1915
- // Grade the epic's DELIVERABLE, not the process prompt: the scoped
1916
- // prompt carries process instructions ("read X first"), prior-epic
1917
- // summaries, and retry feedback — none verifiable from code, so a
1918
- // small judge rejects objectively-green turns against them forever.
1919
- const epicSpec = `EPIC — ${epic.title}:\n${epic.goal}` +
1920
- (epic.criteria?.length ? `\nAcceptance criteria:\n${epic.criteria.map((c) => `- ${c}`).join('\n')}` : '') +
1921
- (!epic.contracts && contractLock.size > 0 ? `\n(Builds against locked contracts: ${[...contractLock].join(', ')})` : '') +
1922
- (fillsScaffold ? '\n(FILL epic: no todo!()/NotImplementedError/TODO-throw stub markers may remain in the files it implements; signatures must be unchanged.)' : '') +
1923
- (epic.scaffold ? '\n(SCAFFOLD epic: complete compiling signatures with stub bodies are the DELIVERABLE — unimplemented logic is expected and correct here.)' : '');
1924
- acceptanceSpec = epicSpec;
1852
+ setEpicSpec: (spec) => {
1853
+ acceptanceSpec = spec;
1925
1854
  specChangeEvidence.writes = 0; // fresh epic — breaker needs fresh diff evidence
1926
- const epicTask = await openDeliveryTask(`${epic.title} \u2014 ${epic.goal.slice(0, 120)}`, projectRoot, missionTask?.id);
1927
- // Epic-path parallel dispatch (PR #516 follow-up): with
1928
- // deliver.parallelTasks > 1, an epic whose goal itself decomposes
1929
- // runs as an orchestrated task DAG — worktree-isolated parallel
1930
- // dispatch inside the DEFAULT (epics-on) path. Only the PLANNER call
1931
- // is fail-soft (a miss falls through to the classic single loop); a
1932
- // runner exception must propagate — swallowing it would silently
1933
- // restart the epic on a partially-merged tree.
1934
- if (epicParallelTasks > 1) {
1935
- // Retry feedback must reach the decomposition (fresh tasks that fix
1936
- // the failure) — mirroring splitEpic — because the orchestrator's
1937
- // 300-char mission snippet cannot carry it into every task.
1938
- const epicPlanGoal = `${epic.title}: ${epic.goal}` +
1939
- (ctx.lastFailure
1940
- ? `\n\n(The previous attempt did not complete: ${ctx.lastFailure.slice(0, 300)}. The tasks you produce must FIX this.)`
1941
- : '');
1942
- let epicPlan = [];
1855
+ },
1856
+ judgeEpic: options.acceptance && !skipJudgeForSimple
1857
+ ? async (spec) => {
1943
1858
  try {
1944
- epicPlan = await planDeliveryPhases(epicPlanGoal, verdictExecutor, undefined,
1945
- // The mission-level plan already passed validation and each
1946
- // task converges against real gates — skip the judge call here.
1947
- { sessionTokenBudget: sessionBudget, thoughtExperiment: false });
1859
+ const judged = await runAcceptanceGate({
1860
+ spec,
1861
+ projectRoot,
1862
+ executor: verdictExecutor,
1863
+ runtimeNote: 'Objective project gates ALL PASSED for every task and on the combined tree — treat build/test requirements as objectively verified.',
1864
+ });
1865
+ return resolveAcceptanceVerdict(judged, acceptancePrimary);
1948
1866
  }
1949
1867
  catch {
1950
- epicPlan = [];
1951
- }
1952
- if (epicPlan.length < 2) {
1953
- console.log(chalk.dim(` ⇉ epic ${epic.id}: no usable task decomposition — classic single-loop epic`));
1954
- }
1955
- else {
1956
- console.log(chalk.cyan(` ⇉ epic ${epic.id}: decomposed into ${epicPlan.length} tasks — orchestrated parallel dispatch`));
1957
- // The orchestrator's mission snippet carries the HEAD of this
1958
- // text — lead with the epic essence, then retry + steering.
1959
- const epicMissionText = `EPIC — ${epic.title}: ${epic.goal}` +
1960
- (ctx.lastFailure ? `\nPREVIOUS ATTEMPT FEEDBACK (address this): ${ctx.lastFailure}` : '') +
1961
- `${contractsNote}${scaffoldNote}${priors}`;
1962
- const r = await runOrchestratedMissionCore(buildOrchestratedDeps(epicMissionText, epicPlan, epicParallelTasks, epicTask?.id));
1963
- foldDeliveryResult(all, r);
1964
- completeDeliveryTask(epicTask, r);
1965
- const files = [...new Set(r.history.flatMap((h) => h.filesApplied ?? []))];
1966
- if (epic.contracts)
1967
- lastContractEpicFiles = files;
1968
- // Epic-level acceptance parity: the classic path judges every
1969
- // green turn against the EPIC spec (criteria, FILL "no stub
1970
- // markers remain", locked contracts). Green tasks + a green
1971
- // combined tree still must satisfy it — one judge call.
1972
- // Fail-soft on judge availability; hard on a completed verdict.
1973
- if (r.success && options.acceptance && !skipJudgeForSimple) {
1974
- try {
1975
- const judged = await runAcceptanceGate({
1976
- spec: epicSpec,
1977
- projectRoot,
1978
- executor: verdictExecutor,
1979
- runtimeNote: 'Objective project gates ALL PASSED for every task and on the combined tree — treat build/test requirements as objectively verified.',
1980
- });
1981
- const verdict = resolveAcceptanceVerdict(judged, acceptancePrimary);
1982
- if (!verdict.passed) {
1983
- return {
1984
- success: false,
1985
- turns: r.turns,
1986
- summary: `${epic.goal.slice(0, 120)} — tasks green but EPIC acceptance failed: ${(verdict.feedback ?? '').slice(0, 240)}`,
1987
- };
1988
- }
1989
- }
1990
- catch {
1991
- // judge unavailable — the objective verdicts stand
1992
- }
1993
- }
1994
- const budgetHit = !r.success && r.history.some((h) => h.budgetStopped);
1995
- return {
1996
- success: r.success,
1997
- turns: r.turns,
1998
- summary: `${epic.goal.slice(0, 140)}${files.length ? ` [files: ${files.join(', ')}]` : ''}` +
1999
- (budgetHit ? ` ${CONTEXT_BUDGET_MARKER} session(s) exceeded the context budget — scope is too large for one session` : ''),
2000
- };
1868
+ return null; // judge unavailable — the objective verdicts stand
2001
1869
  }
2002
1870
  }
2003
- const loop = new ConvergenceLoop({ ...loopConfig, baselineCheck: false, resumeFrom: undefined, onIteration: makeIterationHook() }, executor, seams);
2004
- const r = await loop.deliver(scoped);
2005
- all.turns += r.turns;
2006
- all.history.push(...r.history);
2007
- all.totalDurationMs += r.totalDurationMs;
2008
- if (r.bestScore > all.bestScore) {
2009
- all.bestScore = r.bestScore;
2010
- all.bestTurn = r.bestTurn;
2011
- }
2012
- all.finalFeedback = r.finalFeedback;
2013
- all.finalOutput = r.finalOutput;
2014
- completeDeliveryTask(epicTask, r);
2015
- const files = [...new Set(r.history.flatMap((h) => h.filesApplied ?? []))];
2016
- if (epic.contracts)
2017
- lastContractEpicFiles = files;
2018
- // Rail sizing: surface budget exhaustion to the epic controller — its
2019
- // split path keys off CONTEXT_BUDGET_MARKER in the failure summary,
2020
- // and the goal-based summary would otherwise swallow the signal.
2021
- const budgetHit = !r.success && r.history.some((h) => h.budgetStopped);
2022
- return {
2023
- success: r.success,
2024
- turns: r.turns,
2025
- summary: `${epic.goal.slice(0, 140)}${files.length ? ` [files: ${files.join(', ')}]` : ''}` +
2026
- (budgetHit ? ` ${CONTEXT_BUDGET_MARKER} session(s) exceeded the context budget — scope is too large for one session` : ''),
2027
- };
2028
- },
1871
+ : null,
1872
+ openTask: (title) => openDeliveryTask(title, projectRoot, missionTask?.id),
1873
+ completeTask: (record, r) => completeDeliveryTask(record, r),
1874
+ ledgerInit: (items) => initLedger(projectRoot, instruction, items.map((i) => ({ ...i, kind: 'epic' }))),
1875
+ ledgerMark: (id, status, noteText) => markItem(projectRoot, id, status, noteText),
1876
+ lockedContracts: () => [...contractLock],
1877
+ lockContracts: (files) => lockContractFiles(contractLock, projectRoot, files),
1878
+ maxAttemptsPerEpic: Number(process.env.UAP_DELIVER_EPIC_ATTEMPTS ?? 3), // (#4b) 2→3
1879
+ // (#4c) Recursive split depth: a huge epic that still can't land after
1880
+ // a split is split again, one level shallower — bounded.
1881
+ splitDepth: Math.max(1, Number(process.env.UAP_DELIVER_EPIC_SPLIT_DEPTH ?? 2)),
1882
+ // (#5) Auto-escalation on any exhausted-attempts failure; disable with
1883
+ // UAP_DELIVER_SPLIT_ON_ANY_FAILURE=0 for budget-exhaustion-only.
1884
+ splitOnAnyFailure: process.env.UAP_DELIVER_SPLIT_ON_ANY_FAILURE !== '0',
1885
+ sessionBudget,
1886
+ note: (line) => console.log(line.startsWith('▶')
1887
+ ? chalk.bold(line)
1888
+ : line.startsWith(' ✗')
1889
+ ? chalk.red(line)
1890
+ : line.startsWith(' ✓')
1891
+ ? chalk.green(line)
1892
+ : line.startsWith(' ✂')
1893
+ ? chalk.yellow(line)
1894
+ : line.includes('epic controller:') ||
1895
+ line.includes('contracts locked') ||
1896
+ line.includes('orchestrated parallel dispatch')
1897
+ ? chalk.cyan(line)
1898
+ : chalk.dim(line)),
2029
1899
  });
2030
- all.success = epicResult.success;
2031
- if (!epicResult.success) {
2032
- all.finalFeedback = `epic controller incomplete \u2014 failed epic(s): ${epicResult.failed.join(', ')}\n${all.finalFeedback}`;
2033
- }
2034
- return all;
2035
1900
  };
2036
1901
  // --keep-best (never regress): capture the starting required-gate score and a
2037
1902
  // project snapshot so we can roll back if deliver ends up WORSE than it
@@ -2101,7 +1966,10 @@ async function runDeliver(instruction, options) {
2101
1966
  else if (epicsEnabled) {
2102
1967
  result = await runEpicMission();
2103
1968
  }
2104
- else if (phases && phases.length >= 2 && orchestrateEnabled) {
1969
+ else if (phases && phases.length >= 2 && orchestrateEnabled && !resumeState) {
1970
+ // A RESUMED phased run must not route here: the orchestrated runner
1971
+ // ignores phaseIndex/resumeCheckpoint and would restart the DAG from
1972
+ // scratch. runPhasedMission below is the only cursor-honoring path.
2105
1973
  result = await runOrchestratedMission();
2106
1974
  }
2107
1975
  else if (phases && phases.length >= 2) {
@@ -2114,56 +1982,50 @@ async function runDeliver(instruction, options) {
2114
1982
  result = await loop.deliver(instruction);
2115
1983
  }
2116
1984
  // CI watch boundary + re-converge: once local tiers are green, commit/push
2117
- // the worktree branch and watch the CI run. On CI/deploy failure, feed the
2118
- // sanitized failure back into a fresh convergence pass (baselineCheck off so
2119
- // turn 1 always runs the model — local gates already pass). Bounded by
2120
- // --ci-passes. Skipped when nothing changed (alreadyDelivered).
1985
+ // the worktree branch and watch the CI run extracted policy, see
1986
+ // src/delivery/ci-reconverge.ts. Skipped when nothing changed.
2121
1987
  if (watchCi && result.success && !result.alreadyDelivered) {
1988
+ // Re-point the judge at the MISSION: epic/phased runners leave
1989
+ // acceptanceSpec on the LAST epic/phase spec, and a re-converge pass
1990
+ // would otherwise be judged against that stale slice.
1991
+ acceptanceSpec = instruction;
1992
+ specChangeEvidence.writes = 0; // evidence resets wherever the spec re-points
2122
1993
  const branch = currentBranch(projectRoot) ?? undefined;
2123
- let pass = 0;
2124
- for (;;) {
2125
- // Prefer the loop's applied-file set; the agentic executor reports none
2126
- // (it mutates the repo directly), so fall back to an explicit
2127
- // git-status list rather than letting the watcher do `git add -A`.
2128
- let files = [...new Set(result.history.flatMap((h) => h.filesApplied ?? []))];
2129
- if (files.length === 0)
2130
- files = changedFiles(projectRoot);
2131
- console.log(chalk.cyan(`☁ watch-ci: committing & pushing ${branch ?? 'current branch'}, watching CI…`));
2132
- const watch = await commitPushAndWatch({
2133
- projectRoot,
2134
- branch,
2135
- commitMessage: `feat(delivery): ${instruction.slice(0, 72)}`,
2136
- files,
2137
- timeoutMs: ciTimeoutMs,
2138
- watchEnvironments,
2139
- onProgress: (m) => console.log(chalk.dim(` ${m}`)),
2140
- });
2141
- if (watch.runUrl)
2142
- console.log(chalk.dim(` run: ${watch.runUrl}`));
2143
- if (watch.status === 'green') {
2144
- console.log(chalk.green(` ✓ CI green${watchEnvironments ? ` (${watchEnvironments.join('/')} deploy verified)` : ''}`));
2145
- break;
2146
- }
2147
- if (watch.status === 'skipped' || watch.status === 'no-run') {
2148
- console.log(chalk.yellow(` ⚠ watch-ci ${watch.status}: ${watch.feedback ?? ''}`));
2149
- break;
2150
- }
2151
- // failed | timeout
2152
- pass++;
2153
- console.log(chalk.yellow(` ✗ CI ${watch.status} (re-converge pass ${pass}/${ciPasses})`));
2154
- if (pass >= ciPasses) {
2155
- console.log(chalk.red(` watch-ci: exhausted ${ciPasses} pass(es); CI still not green.`));
2156
- result = { ...result, success: false, finalFeedback: watch.feedback ?? result.finalFeedback };
2157
- break;
2158
- }
2159
- console.log(chalk.cyan(' ⟲ re-converging against CI feedback…'));
2160
- const reconvergeLoop = new ConvergenceLoop({ ...loopConfig, baselineCheck: false, resumeFrom: undefined, onCheckpoint: undefined }, executor, seams);
2161
- result = await reconvergeLoop.deliver(`${instruction}\n\n${watch.feedback ?? ''}`);
2162
- if (!result.success) {
2163
- console.log(chalk.red(' re-converge did not reach local-green; stopping watch-ci.'));
2164
- break;
2165
- }
2166
- }
1994
+ result = await runCiReconverge({
1995
+ instruction,
1996
+ initial: result,
1997
+ ciPasses,
1998
+ changedFiles: () => changedFiles(projectRoot),
1999
+ greenDetail: watchEnvironments ? ` (${watchEnvironments.join('/')} deploy verified)` : '',
2000
+ commitAndWatch: async (files) => {
2001
+ console.log(chalk.cyan(`☁ watch-ci: committing & pushing ${branch ?? 'current branch'}, watching CI…`));
2002
+ const watch = await commitPushAndWatch({
2003
+ projectRoot,
2004
+ branch,
2005
+ commitMessage: `feat(delivery): ${instruction.slice(0, 72)}`,
2006
+ files,
2007
+ timeoutMs: ciTimeoutMs,
2008
+ watchEnvironments,
2009
+ onProgress: (m) => console.log(chalk.dim(` ${m}`)),
2010
+ });
2011
+ if (watch.runUrl)
2012
+ console.log(chalk.dim(` run: ${watch.runUrl}`));
2013
+ return watch;
2014
+ },
2015
+ reconverge: async (prompt) => {
2016
+ const reconvergeLoop = new ConvergenceLoop({ ...loopConfig, baselineCheck: false, resumeFrom: undefined, onCheckpoint: undefined }, executor, seams);
2017
+ return reconvergeLoop.deliver(prompt);
2018
+ },
2019
+ note: (line) => console.log(line.startsWith(' ')
2020
+ ? chalk.green(line)
2021
+ : line.startsWith(' ⚠') || line.startsWith(' ✗')
2022
+ ? chalk.yellow(line)
2023
+ : line.startsWith(' ⟲')
2024
+ ? chalk.cyan(line)
2025
+ : line.includes('exhausted') || line.includes('did not reach')
2026
+ ? chalk.red(line)
2027
+ : chalk.dim(line)),
2028
+ });
2167
2029
  }
2168
2030
  }
2169
2031
  catch (err) {