@miller-tech/uap 1.151.0 → 1.152.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.
- package/dist/.tsbuildinfo +1 -1
- package/dist/cli/deliver.js +71 -205
- package/dist/cli/deliver.js.map +1 -1
- package/dist/delivery/epic-mission.d.ts +92 -0
- package/dist/delivery/epic-mission.d.ts.map +1 -0
- package/dist/delivery/epic-mission.js +229 -0
- package/dist/delivery/epic-mission.js.map +1 -0
- package/dist/delivery/index.d.ts +1 -0
- package/dist/delivery/index.d.ts.map +1 -1
- package/dist/delivery/index.js +1 -0
- package/dist/delivery/index.js.map +1 -1
- package/package.json +1 -1
- package/src/policies/enforcers/__pycache__/_common.cpython-312.pyc +0 -0
- package/templates/hooks/__pycache__/deliver_autoroute.cpython-312.pyc +0 -0
- package/tools/agents/scripts/__pycache__/toolcall_path_normalizer.cpython-312.pyc +0 -0
package/dist/cli/deliver.js
CHANGED
|
@@ -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
|
|
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,7 +119,8 @@ 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
|
|
122
|
+
import { runOrchestratedMission as runOrchestratedMissionCore } from '../delivery/orchestrated-mission.js';
|
|
123
|
+
import { runEpicMission as runEpicMissionCore } from '../delivery/epic-mission.js';
|
|
124
124
|
import { createHaloDeliveryTracer } from '../delivery/halo-trace.js';
|
|
125
125
|
import { haloTracePath, isHaloTracingEnabled } from '../observability/halo-exporter.js';
|
|
126
126
|
import { createRunCoordinator } from '../delivery/run-coordinator.js';
|
|
@@ -1818,220 +1818,86 @@ async function runDeliver(instruction, options) {
|
|
|
1818
1818
|
* per-epic attempt budget is spent. The blackboard orchestrator still runs
|
|
1819
1819
|
* WITHIN each epic when that epic itself decomposes.
|
|
1820
1820
|
*/
|
|
1821
|
+
/**
|
|
1822
|
+
* Wire deliver's ambient context into the extracted epic-mission runner
|
|
1823
|
+
* (src/delivery/epic-mission.ts) — the DEFAULT delivery path, now driven
|
|
1824
|
+
* through a unit-tested core instead of a 230-line untested closure.
|
|
1825
|
+
*/
|
|
1821
1826
|
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
1827
|
const epicParallelTasks = resolveParallelTasks(cfgRaw.deliver?.parallelTasks);
|
|
1827
1828
|
const contractsFirst = process.env.UAP_DELIVER_CONTRACTS !== '0';
|
|
1828
1829
|
const scaffoldFirst = process.env.UAP_DELIVER_SCAFFOLD !== '0';
|
|
1829
|
-
|
|
1830
|
-
|
|
1831
|
-
|
|
1832
|
-
|
|
1833
|
-
|
|
1834
|
-
|
|
1835
|
-
|
|
1836
|
-
|
|
1837
|
-
|
|
1838
|
-
|
|
1839
|
-
|
|
1840
|
-
|
|
1841
|
-
|
|
1842
|
-
|
|
1843
|
-
|
|
1844
|
-
|
|
1845
|
-
|
|
1846
|
-
|
|
1847
|
-
|
|
1848
|
-
|
|
1849
|
-
|
|
1850
|
-
|
|
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)`));
|
|
1830
|
+
return runEpicMissionCore({
|
|
1831
|
+
instruction,
|
|
1832
|
+
planEpics: () => planDeliveryPhases(instruction, verdictExecutor, undefined, {
|
|
1833
|
+
sessionTokenBudget: sessionBudget,
|
|
1834
|
+
contractsFirst,
|
|
1835
|
+
scaffoldFirst,
|
|
1836
|
+
}),
|
|
1837
|
+
// Re-decomposition is already reactive (the failure text is in the
|
|
1838
|
+
// goal) — the extra thought-experiment judge call buys nothing here.
|
|
1839
|
+
planSplit: (subGoal) => planDeliveryPhases(subGoal, verdictExecutor, undefined, {
|
|
1840
|
+
sessionTokenBudget: sessionBudget,
|
|
1841
|
+
thoughtExperiment: false,
|
|
1842
|
+
}),
|
|
1843
|
+
planEpicTasks: (goal) => planDeliveryPhases(goal, verdictExecutor, undefined, {
|
|
1844
|
+
sessionTokenBudget: sessionBudget,
|
|
1845
|
+
thoughtExperiment: false,
|
|
1846
|
+
}),
|
|
1847
|
+
epicParallelTasks,
|
|
1848
|
+
runOrchestrated: (missionText, plan, parentTaskId) => runOrchestratedMissionCore(buildOrchestratedDeps(missionText, plan, epicParallelTasks, parentTaskId)),
|
|
1849
|
+
runEpicLoop: async (scoped) => {
|
|
1850
|
+
const loop = new ConvergenceLoop({ ...loopConfig, baselineCheck: false, resumeFrom: undefined, onIteration: makeIterationHook() }, executor, seams);
|
|
1851
|
+
return loop.deliver(scoped);
|
|
1889
1852
|
},
|
|
1890
|
-
|
|
1891
|
-
|
|
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;
|
|
1853
|
+
setEpicSpec: (spec) => {
|
|
1854
|
+
acceptanceSpec = spec;
|
|
1925
1855
|
specChangeEvidence.writes = 0; // fresh epic — breaker needs fresh diff evidence
|
|
1926
|
-
|
|
1927
|
-
|
|
1928
|
-
|
|
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 = [];
|
|
1856
|
+
},
|
|
1857
|
+
judgeEpic: options.acceptance && !skipJudgeForSimple
|
|
1858
|
+
? async (spec) => {
|
|
1943
1859
|
try {
|
|
1944
|
-
|
|
1945
|
-
|
|
1946
|
-
|
|
1947
|
-
|
|
1860
|
+
const judged = await runAcceptanceGate({
|
|
1861
|
+
spec,
|
|
1862
|
+
projectRoot,
|
|
1863
|
+
executor: verdictExecutor,
|
|
1864
|
+
runtimeNote: 'Objective project gates ALL PASSED for every task and on the combined tree — treat build/test requirements as objectively verified.',
|
|
1865
|
+
});
|
|
1866
|
+
return resolveAcceptanceVerdict(judged, acceptancePrimary);
|
|
1948
1867
|
}
|
|
1949
1868
|
catch {
|
|
1950
|
-
|
|
1951
|
-
}
|
|
1952
|
-
if (epicPlan.length < 2) {
|
|
1953
|
-
console.log(chalk.dim(` ⇉ epic ${epic.id}: no usable task decomposition — classic single-loop epic`));
|
|
1869
|
+
return null; // judge unavailable — the objective verdicts stand
|
|
1954
1870
|
}
|
|
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
|
-
};
|
|
2001
|
-
}
|
|
2002
|
-
}
|
|
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
1871
|
}
|
|
2012
|
-
|
|
2013
|
-
|
|
2014
|
-
|
|
2015
|
-
|
|
2016
|
-
|
|
2017
|
-
|
|
2018
|
-
|
|
2019
|
-
|
|
2020
|
-
|
|
2021
|
-
|
|
2022
|
-
|
|
2023
|
-
|
|
2024
|
-
|
|
2025
|
-
|
|
2026
|
-
|
|
2027
|
-
|
|
2028
|
-
|
|
1872
|
+
: null,
|
|
1873
|
+
openTask: (title) => openDeliveryTask(title, projectRoot, missionTask?.id),
|
|
1874
|
+
completeTask: (record, r) => completeDeliveryTask(record, r),
|
|
1875
|
+
ledgerInit: (items) => initLedger(projectRoot, instruction, items.map((i) => ({ ...i, kind: 'epic' }))),
|
|
1876
|
+
ledgerMark: (id, status, noteText) => markItem(projectRoot, id, status, noteText),
|
|
1877
|
+
lockedContracts: () => [...contractLock],
|
|
1878
|
+
lockContracts: (files) => lockContractFiles(contractLock, projectRoot, files),
|
|
1879
|
+
maxAttemptsPerEpic: Number(process.env.UAP_DELIVER_EPIC_ATTEMPTS ?? 3), // (#4b) 2→3
|
|
1880
|
+
// (#4c) Recursive split depth: a huge epic that still can't land after
|
|
1881
|
+
// a split is split again, one level shallower — bounded.
|
|
1882
|
+
splitDepth: Math.max(1, Number(process.env.UAP_DELIVER_EPIC_SPLIT_DEPTH ?? 2)),
|
|
1883
|
+
// (#5) Auto-escalation on any exhausted-attempts failure; disable with
|
|
1884
|
+
// UAP_DELIVER_SPLIT_ON_ANY_FAILURE=0 for budget-exhaustion-only.
|
|
1885
|
+
splitOnAnyFailure: process.env.UAP_DELIVER_SPLIT_ON_ANY_FAILURE !== '0',
|
|
1886
|
+
sessionBudget,
|
|
1887
|
+
note: (line) => console.log(line.startsWith('▶')
|
|
1888
|
+
? chalk.bold(line)
|
|
1889
|
+
: line.startsWith(' ✗')
|
|
1890
|
+
? chalk.red(line)
|
|
1891
|
+
: line.startsWith(' ✓')
|
|
1892
|
+
? chalk.green(line)
|
|
1893
|
+
: line.startsWith(' ✂')
|
|
1894
|
+
? chalk.yellow(line)
|
|
1895
|
+
: line.includes('epic controller:') ||
|
|
1896
|
+
line.includes('contracts locked') ||
|
|
1897
|
+
line.includes('orchestrated parallel dispatch')
|
|
1898
|
+
? chalk.cyan(line)
|
|
1899
|
+
: chalk.dim(line)),
|
|
2029
1900
|
});
|
|
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
1901
|
};
|
|
2036
1902
|
// --keep-best (never regress): capture the starting required-gate score and a
|
|
2037
1903
|
// project snapshot so we can roll back if deliver ends up WORSE than it
|