@miller-tech/uap 1.150.2 → 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.d.ts.map +1 -1
- package/dist/cli/deliver.js +182 -356
- 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 +3 -0
- package/dist/delivery/index.d.ts.map +1 -1
- package/dist/delivery/index.js +3 -0
- package/dist/delivery/index.js.map +1 -1
- package/dist/delivery/orchestrated-mission.d.ts +96 -0
- package/dist/delivery/orchestrated-mission.d.ts.map +1 -0
- package/dist/delivery/orchestrated-mission.js +249 -0
- package/dist/delivery/orchestrated-mission.js.map +1 -0
- package/dist/delivery/task-orchestrator.d.ts +5 -4
- package/dist/delivery/task-orchestrator.d.ts.map +1 -1
- package/dist/delivery/task-orchestrator.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
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"deliver.d.ts","sourceRoot":"","sources":["../../src/cli/deliver.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAqBH,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,oBAAoB,CAAC;AAIzD,OAAO,EAA2E,KAAK,gBAAgB,EAAE,MAAM,iCAAiC,CAAC;AAKjJ;;;;;;;;;;;GAWG;AACH;;;;GAIG;AACH,wBAAgB,kBAAkB,CAAC,IAAI,EAAE;IACvC,aAAa,EAAE,OAAO,CAAC;IACvB,WAAW,EAAE,OAAO,CAAC;IACrB,aAAa,EAAE,OAAO,CAAC;IACvB,eAAe,EAAE,OAAO,CAAC;IACzB;;;;;OAKG;IACH,gBAAgB,CAAC,EAAE,OAAO,CAAC;CAC5B,GAAG;IAAE,iBAAiB,EAAE,OAAO,CAAC;IAAC,aAAa,EAAE,OAAO,CAAC;IAAC,YAAY,EAAE,OAAO,CAAA;CAAE,CAQhF;AAED;;;;;GAKG;AACH,wBAAgB,yBAAyB,CAAC,IAAI,EAAE;IAC9C,iBAAiB,EAAE,OAAO,CAAC;IAC3B,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,iBAAiB,EAAE,OAAO,CAAC;CAC5B,GAAG,OAAO,CAMV;AAED;;;;;;GAMG;AACH,wBAAgB,sBAAsB,CAAC,IAAI,EAAE;IAC3C,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,eAAe,EAAE,MAAM,CAAC;IACxB,YAAY,CAAC,EAAE,MAAM,CAAC;CACvB,GAAG,MAAM,GAAG,IAAI,CAIhB;AAED,wBAAgB,wBAAwB,CACtC,CAAC,EAAE,gBAAgB,EACnB,iBAAiB,EAAE,OAAO,GACzB;IAAE,MAAM,EAAE,OAAO,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAC;IAAC,KAAK,CAAC,EAAE,MAAM,CAAA;CAAE,CAiBvD;
|
|
1
|
+
{"version":3,"file":"deliver.d.ts","sourceRoot":"","sources":["../../src/cli/deliver.ts"],"names":[],"mappings":"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"}
|
package/dist/cli/deliver.js
CHANGED
|
@@ -94,8 +94,7 @@ export function resolveAcceptanceVerdict(r, acceptancePrimary) {
|
|
|
94
94
|
import { createAgenticExecutor, noopApplier, selectExecutorMode, lockContractFiles } from '../delivery/agentic-executor.js';
|
|
95
95
|
import { createRepairEscalation } from '../delivery/repair-escalation.js';
|
|
96
96
|
import { clearStop, isStopRequested, loadRunState, newRunId, saveRunState } from '../delivery/run-state.js';
|
|
97
|
-
import {
|
|
98
|
-
import { runEpics } from '../delivery/epic-controller.js';
|
|
97
|
+
import { phaseInstruction, planDeliveryPhases, shouldDecompose } from '../delivery/decompose.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,17 +110,17 @@ function cfgRawEarlyForUvFactory(projectRoot) {
|
|
|
111
110
|
}
|
|
112
111
|
};
|
|
113
112
|
}
|
|
114
|
-
import { resolveSessionTokenBudget, sessionWorkingBudget, discoverModelContextWindow
|
|
115
|
-
import { orchestrate } from '../delivery/task-orchestrator.js';
|
|
113
|
+
import { resolveSessionTokenBudget, sessionWorkingBudget, discoverModelContextWindow } from '../delivery/context-budget.js';
|
|
116
114
|
import { preflightProject, formatPreflightFailure } from '../delivery/project-preflight.js';
|
|
117
115
|
import { resolveFidelity } from '../delivery/fidelity.js';
|
|
118
116
|
import { installRunExitRecorder } from '../delivery/run-exit.js';
|
|
119
117
|
import { shouldDetach, relaunchDetached, isDetachedChild, canHostDetachLog, NO_DETACH_ENV, } from './deliver-detach.js';
|
|
120
|
-
import { extractContract } from '../delivery/contract-extractor.js';
|
|
121
118
|
import { completeDeliveryTask, openDeliveryTask, recordDeliveryOutcome, recordOrchestratorTaskOutcome, reopenDeliveryTask } from '../delivery/task-sync.js';
|
|
122
119
|
import { autoMineHaloTraces, summarizeWeaknesses, weaknessGuidance, loadPersistedWeaknesses } from '../delivery/auto-mine.js';
|
|
123
120
|
import { createGitWorktreeProvider } from '../delivery/candidate-workspace.js';
|
|
124
121
|
import { createTaskWorkspaceManager, resolveParallelTasks } from '../delivery/task-workspace.js';
|
|
122
|
+
import { runOrchestratedMission as runOrchestratedMissionCore } from '../delivery/orchestrated-mission.js';
|
|
123
|
+
import { runEpicMission as runEpicMissionCore } from '../delivery/epic-mission.js';
|
|
125
124
|
import { createHaloDeliveryTracer } from '../delivery/halo-trace.js';
|
|
126
125
|
import { haloTracePath, isHaloTracingEnabled } from '../observability/halo-exporter.js';
|
|
127
126
|
import { createRunCoordinator } from '../delivery/run-coordinator.js';
|
|
@@ -1617,154 +1616,61 @@ async function runDeliver(instruction, options) {
|
|
|
1617
1616
|
* the per-task executor; a completed task publishes a compact summary its
|
|
1618
1617
|
* dependents read instead of source.
|
|
1619
1618
|
*/
|
|
1620
|
-
|
|
1621
|
-
|
|
1622
|
-
|
|
1623
|
-
|
|
1624
|
-
|
|
1625
|
-
|
|
1626
|
-
|
|
1627
|
-
|
|
1628
|
-
|
|
1629
|
-
|
|
1630
|
-
|
|
1631
|
-
|
|
1632
|
-
|
|
1633
|
-
|
|
1634
|
-
|
|
1635
|
-
|
|
1636
|
-
|
|
1637
|
-
|
|
1638
|
-
|
|
1639
|
-
|
|
1640
|
-
|
|
1641
|
-
|
|
1642
|
-
|
|
1643
|
-
|
|
1644
|
-
|
|
1645
|
-
|
|
1646
|
-
|
|
1647
|
-
|
|
1648
|
-
|
|
1649
|
-
const mergeProtected = new Set(options.protectTests !== false
|
|
1650
|
-
? [...snapshotProtection(projectRoot).protectedFiles, ...listGateConfigFiles(projectRoot)].map((f) => f.toLowerCase())
|
|
1651
|
-
: []);
|
|
1652
|
-
let mergeChain = Promise.resolve();
|
|
1653
|
-
const withMergeLock = (fn) => {
|
|
1654
|
-
const next = mergeChain.then(fn, fn);
|
|
1655
|
-
mergeChain = next.then(() => undefined, () => undefined);
|
|
1656
|
-
return next;
|
|
1657
|
-
};
|
|
1658
|
-
// Regexes built from strings so policy hooks never mistake a literal for a path.
|
|
1659
|
-
const SOURCE_FILE_RE = new RegExp('[.](js|mjs|cjs|ts|tsx|jsx|py)$');
|
|
1660
|
-
const NEW_TASKS_RE = new RegExp('NEW_TASKS:\\s*(\\[[\\s\\S]*?\\])');
|
|
1661
|
-
/** Run one task's convergence loop against `root` with executor `exec` -
|
|
1662
|
-
* the historic in-tree body, parameterized so isolated workspaces reuse it
|
|
1663
|
-
* verbatim. Turns/history flow into the mission aggregate as before. */
|
|
1664
|
-
const runTaskAt = async (ctx, task, root, exec) => {
|
|
1665
|
-
const taskRecord = await openDeliveryTask(`${task.title} — ${task.goal.slice(0, 120)}`, projectRoot, missionTask?.id);
|
|
1666
|
-
acceptanceSpecByRoot.set(root, ctx.prompt);
|
|
1667
|
-
if (root === projectRoot)
|
|
1668
|
-
acceptanceSpec = ctx.prompt;
|
|
1669
|
-
evidenceFor(root).writes = 0;
|
|
1670
|
-
try {
|
|
1671
|
-
const loop = new ConvergenceLoop({
|
|
1672
|
-
...loopConfig,
|
|
1673
|
-
projectRoot: root,
|
|
1674
|
-
baselineCheck: false,
|
|
1675
|
-
resumeFrom: undefined,
|
|
1676
|
-
// Checkpoints describe THE mission tree; isolated roots must not
|
|
1677
|
-
// clobber the durable run state.
|
|
1678
|
-
// The inherited explorer's workspaceProvider is bound to the MAIN
|
|
1679
|
-
// projectRoot; inside an isolated task it must not run at all.
|
|
1680
|
-
...(root !== projectRoot ? { onCheckpoint: undefined, explorer: undefined } : {}),
|
|
1681
|
-
onIteration: makeIterationHook(root),
|
|
1682
|
-
}, exec, seams);
|
|
1683
|
-
const r = await loop.deliver(ctx.prompt);
|
|
1684
|
-
all.turns += r.turns;
|
|
1685
|
-
all.history.push(...r.history);
|
|
1686
|
-
all.totalDurationMs += r.totalDurationMs;
|
|
1687
|
-
if (r.bestScore > all.bestScore) {
|
|
1688
|
-
all.bestScore = r.bestScore;
|
|
1689
|
-
all.bestTurn = r.bestTurn;
|
|
1690
|
-
}
|
|
1691
|
-
all.finalFeedback = r.finalFeedback;
|
|
1692
|
-
all.finalOutput = r.finalOutput;
|
|
1693
|
-
completeDeliveryTask(taskRecord, r);
|
|
1694
|
-
const files = [...new Set(r.history.flatMap((h) => h.filesApplied ?? []))];
|
|
1695
|
-
// P4 - extract the VERIFIED public contract of what this task built so
|
|
1696
|
-
// dependents load the interface (a few hundred chars), not the source.
|
|
1697
|
-
let contract;
|
|
1698
|
-
if (r.success && files.length > 0) {
|
|
1699
|
-
try {
|
|
1700
|
-
const { readFileSync } = await import('fs');
|
|
1701
|
-
const { join } = await import('path');
|
|
1702
|
-
const srcs = files
|
|
1703
|
-
.filter((f) => SOURCE_FILE_RE.test(f))
|
|
1704
|
-
.map((f) => {
|
|
1705
|
-
try {
|
|
1706
|
-
return { path: f, content: readFileSync(join(root, f), 'utf-8') };
|
|
1707
|
-
}
|
|
1708
|
-
catch {
|
|
1709
|
-
return null;
|
|
1710
|
-
}
|
|
1711
|
-
})
|
|
1712
|
-
.filter((x) => x !== null);
|
|
1713
|
-
contract = extractContract(srcs).contract || undefined;
|
|
1714
|
-
}
|
|
1715
|
-
catch {
|
|
1716
|
-
contract = undefined;
|
|
1717
|
-
}
|
|
1718
|
-
}
|
|
1719
|
-
// P5 - adaptive re-planning feed: a task may surface work the initial
|
|
1720
|
-
// plan missed by emitting a `NEW_TASKS: [ {id,title,goal,deps} ]` JSON
|
|
1721
|
-
// array in its output. Parsed through the same validator as the planner
|
|
1722
|
-
// (well-formed only), then folded into the DAG by orchestrate() (which
|
|
1723
|
-
// dedupes, topo-resorts, and caps at maxTasks). Only structural fields
|
|
1724
|
-
// are used - never free-form model text as durable memory.
|
|
1725
|
-
let newTasks;
|
|
1726
|
-
if (r.success) {
|
|
1727
|
-
try {
|
|
1728
|
-
const marker = NEW_TASKS_RE.exec(r.finalOutput || r.finalFeedback || '');
|
|
1729
|
-
if (marker) {
|
|
1730
|
-
const parsed = parsePhaseArray(marker[1]).filter((t) => t.id !== task.id);
|
|
1731
|
-
if (parsed.length > 0) {
|
|
1732
|
-
newTasks = parsed.map((t) => ({
|
|
1733
|
-
id: t.id, title: t.title, goal: t.goal,
|
|
1734
|
-
...(t.deps ? { deps: t.deps } : {}),
|
|
1735
|
-
}));
|
|
1736
|
-
console.log(chalk.dim(` ↳ re-planning: task ${task.id} discovered ${newTasks.length} follow-up task(s)`));
|
|
1737
|
-
}
|
|
1738
|
-
}
|
|
1739
|
-
}
|
|
1740
|
-
catch {
|
|
1741
|
-
newTasks = undefined;
|
|
1742
|
-
}
|
|
1743
|
-
}
|
|
1744
|
-
return {
|
|
1745
|
-
taskId: task.id,
|
|
1746
|
-
success: r.success,
|
|
1747
|
-
turns: r.turns,
|
|
1748
|
-
// ATG minimal repair: a FAILED task's summary must carry the failure
|
|
1749
|
-
// (the retry's only new information), never a goal restatement.
|
|
1750
|
-
summary: r.success
|
|
1751
|
-
? `${task.goal.slice(0, 160)}${files.length ? ` [files: ${files.join(', ')}]` : ''}`
|
|
1752
|
-
: `${task.goal.slice(0, 120)} — FAILED: ${(r.finalFeedback || 'gates did not pass').slice(0, 280)}`,
|
|
1753
|
-
...(contract ? { contract } : {}),
|
|
1754
|
-
...(newTasks && newTasks.length > 0 ? { newTasks } : {}),
|
|
1755
|
-
};
|
|
1756
|
-
}
|
|
1757
|
-
finally {
|
|
1758
|
-
acceptanceSpecByRoot.delete(root);
|
|
1759
|
-
}
|
|
1760
|
-
};
|
|
1761
|
-
const orchResult = await orchestrate({
|
|
1762
|
-
mission: instruction,
|
|
1763
|
-
tasks,
|
|
1619
|
+
/**
|
|
1620
|
+
* Wire deliver's ambient context (loop config, executors, judge state, task
|
|
1621
|
+
* records, gates) into the extracted orchestrated-mission runner
|
|
1622
|
+
* (src/delivery/orchestrated-mission.ts). Shared by the top-level
|
|
1623
|
+
* orchestrated path and the epic controller's inner missions — and, because
|
|
1624
|
+
* the runner is seam-injected, the orchestrate() wiring is finally
|
|
1625
|
+
* unit-testable (the PR #516 inert-concurrency critical could not recur
|
|
1626
|
+
* unobserved).
|
|
1627
|
+
*/
|
|
1628
|
+
const buildOrchestratedDeps = (missionText, taskPhases, parallelTasks, parentTaskId = missionTask?.id) => {
|
|
1629
|
+
const mergeProtected = new Set([
|
|
1630
|
+
...(options.protectTests !== false
|
|
1631
|
+
? [...snapshotProtection(projectRoot).protectedFiles, ...listGateConfigFiles(projectRoot)]
|
|
1632
|
+
: []),
|
|
1633
|
+
// Locked contract files are read-only for later epics; the merge
|
|
1634
|
+
// boundary re-checks them like the other protected surfaces instead
|
|
1635
|
+
// of trusting the in-worktree write_file guard transitively.
|
|
1636
|
+
...contractLock,
|
|
1637
|
+
].map((f) => f.toLowerCase()));
|
|
1638
|
+
return {
|
|
1639
|
+
instruction: missionText,
|
|
1640
|
+
projectRoot,
|
|
1641
|
+
tasks: taskPhases.map((ph) => ({
|
|
1642
|
+
id: ph.id,
|
|
1643
|
+
title: ph.title,
|
|
1644
|
+
goal: ph.goal,
|
|
1645
|
+
...(ph.deps ? { deps: ph.deps } : {}),
|
|
1646
|
+
})),
|
|
1647
|
+
parallelTasks,
|
|
1764
1648
|
contextBudgetChars: Number(process.env.UAP_DELIVER_CONTEXT_BUDGET ?? 6000),
|
|
1765
1649
|
maxTasks: Number(process.env.UAP_DELIVER_MAX_TASKS ?? 40),
|
|
1766
|
-
|
|
1767
|
-
|
|
1650
|
+
workspaceManager: parallelTasks > 1 ? createTaskWorkspaceManager(projectRoot) : null,
|
|
1651
|
+
note: (line) => console.log(line.startsWith('▶')
|
|
1652
|
+
? chalk.bold(line)
|
|
1653
|
+
: line.includes('unavailable') || line.includes('conflicted')
|
|
1654
|
+
? chalk.yellow(line)
|
|
1655
|
+
: line.includes('parallel tasks:') || line.includes('post-merge verification')
|
|
1656
|
+
? chalk.cyan(line)
|
|
1657
|
+
: chalk.dim(line)),
|
|
1658
|
+
beginTaskSpec: (root, spec) => {
|
|
1659
|
+
acceptanceSpecByRoot.set(root, spec);
|
|
1660
|
+
if (root === projectRoot)
|
|
1661
|
+
acceptanceSpec = spec;
|
|
1662
|
+
evidenceFor(root).writes = 0;
|
|
1663
|
+
},
|
|
1664
|
+
endTaskSpec: (root) => {
|
|
1665
|
+
acceptanceSpecByRoot.delete(root);
|
|
1666
|
+
// An in-tree task re-pointed the shared spec; restore the mission-
|
|
1667
|
+
// level spec so a later watch-ci re-converge never judges against a
|
|
1668
|
+
// stale task prompt.
|
|
1669
|
+
if (root === projectRoot)
|
|
1670
|
+
acceptanceSpec = missionText;
|
|
1671
|
+
},
|
|
1672
|
+
openTask: (title) => openDeliveryTask(title, projectRoot, parentTaskId),
|
|
1673
|
+
completeTask: (record, r) => completeDeliveryTask(record, r),
|
|
1768
1674
|
// P3 — per-task memory retrieval: pull the few most relevant established
|
|
1769
1675
|
// decisions/patterns/gotchas for THIS task's goal (a small semantic
|
|
1770
1676
|
// query, not the full spec), so a fresh-context task reconstructs "what
|
|
@@ -1794,85 +1700,62 @@ async function runDeliver(instruction, options) {
|
|
|
1794
1700
|
return;
|
|
1795
1701
|
await recordOrchestratorTaskOutcome(task.id, task.title, outcome.contract ?? outcome.summary, projectRoot);
|
|
1796
1702
|
},
|
|
1797
|
-
|
|
1798
|
-
|
|
1799
|
-
|
|
1800
|
-
//
|
|
1801
|
-
//
|
|
1802
|
-
|
|
1803
|
-
|
|
1804
|
-
if (wsManager) {
|
|
1805
|
-
console.log(chalk.yellow(` ⇉ task ${task.id}: workspace unavailable - running in-tree (serialized)`));
|
|
1806
|
-
// In-tree execution mutates the shared tree; hold the lock for the
|
|
1807
|
-
// WHOLE run so it never races an isolated task's merge-back.
|
|
1808
|
-
return withMergeLock(() => runTaskAt(ctx, task, projectRoot, executor));
|
|
1809
|
-
}
|
|
1810
|
-
return runTaskAt(ctx, task, projectRoot, executor);
|
|
1811
|
-
}
|
|
1703
|
+
isMergeBlocked: (f) => mergeProtected.has(f.toLowerCase()),
|
|
1704
|
+
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
|
|
1708
|
+
// the combined tree is verified against stale t0 gates only.
|
|
1709
|
+
let combinedRungs = rungs;
|
|
1812
1710
|
try {
|
|
1813
|
-
|
|
1814
|
-
|
|
1815
|
-
|
|
1816
|
-
|
|
1817
|
-
|
|
1818
|
-
|
|
1819
|
-
projectRoot: ws.root,
|
|
1820
|
-
endpoint: agenticEndpoint,
|
|
1821
|
-
temperature,
|
|
1822
|
-
contextTokenBudget: sessionBudget,
|
|
1823
|
-
contractFiles: contractLock,
|
|
1824
|
-
protectedFiles: options.protectTests !== false ? snapshotProtection(ws.root).protectedFiles : new Set(),
|
|
1825
|
-
protectGateConfigs: options.protectTests !== false,
|
|
1826
|
-
allowBash: options.allowBash === true || process.env.UAP_DELIVER_ALLOW_BASH === '1',
|
|
1827
|
-
onEvent: (e) => console.log(chalk.dim(` [${task.id} r${e.round} ${e.kind}${e.tool ? `:${e.tool}` : ''}] ${e.detail ?? ''}`)),
|
|
1828
|
-
})
|
|
1829
|
-
: executor;
|
|
1830
|
-
const outcome = await runTaskAt(ctx, task, ws.root, taskExecutor);
|
|
1831
|
-
if (!outcome.success)
|
|
1832
|
-
return outcome;
|
|
1833
|
-
const merged = await withMergeLock(() => ws.mergeBack((f) => mergeProtected.has(f.toLowerCase())));
|
|
1834
|
-
if (!merged.ok) {
|
|
1835
|
-
console.log(chalk.yellow(` ⇉ task ${task.id}: merge-back conflicted (${merged.reason ?? 'apply failed'}) - minimal repair retries on the updated tree`));
|
|
1836
|
-
return {
|
|
1837
|
-
taskId: task.id,
|
|
1838
|
-
success: false,
|
|
1839
|
-
turns: 0,
|
|
1840
|
-
summary: `${task.goal.slice(0, 120)} — FAILED: the tree changed underneath this task (parallel merge conflict) — rebuild it against the CURRENT state of the repository. Detail: ${(merged.reason ?? 'could not apply the task delta').slice(0, 200)}`,
|
|
1841
|
-
};
|
|
1842
|
-
}
|
|
1843
|
-
if (merged.files.length > 0) {
|
|
1844
|
-
console.log(chalk.dim(` ⇉ task ${task.id}: merged ${merged.files.length} file(s) into the main tree`));
|
|
1845
|
-
}
|
|
1846
|
-
return outcome;
|
|
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];
|
|
1847
1717
|
}
|
|
1848
|
-
|
|
1849
|
-
|
|
1718
|
+
catch {
|
|
1719
|
+
// detection unavailable - verify against the t0 rungs
|
|
1850
1720
|
}
|
|
1721
|
+
return Promise.resolve(tieredRunner(combinedRungs, projectRoot, {}));
|
|
1851
1722
|
},
|
|
1852
|
-
|
|
1853
|
-
|
|
1854
|
-
|
|
1855
|
-
|
|
1856
|
-
|
|
1857
|
-
|
|
1858
|
-
|
|
1859
|
-
|
|
1860
|
-
|
|
1861
|
-
|
|
1862
|
-
|
|
1863
|
-
|
|
1864
|
-
|
|
1865
|
-
|
|
1866
|
-
|
|
1867
|
-
|
|
1868
|
-
|
|
1869
|
-
|
|
1870
|
-
|
|
1871
|
-
|
|
1872
|
-
|
|
1873
|
-
|
|
1874
|
-
|
|
1875
|
-
|
|
1723
|
+
runLoop: async ({ root, prompt, taskId, isolated }) => {
|
|
1724
|
+
// The agentic executor binds tools to a root at construction — give
|
|
1725
|
+
// an isolated task its own, with the same protections re-derived for
|
|
1726
|
+
// its tree. The blind executor is root-free (the per-task loop's
|
|
1727
|
+
// projectRoot directs the applier and gates).
|
|
1728
|
+
const exec = isolated && agentic
|
|
1729
|
+
? createAgenticExecutor(model, {
|
|
1730
|
+
projectRoot: root,
|
|
1731
|
+
endpoint: agenticEndpoint,
|
|
1732
|
+
temperature,
|
|
1733
|
+
contextTokenBudget: sessionBudget,
|
|
1734
|
+
contractFiles: contractLock,
|
|
1735
|
+
protectedFiles: options.protectTests !== false ? snapshotProtection(root).protectedFiles : new Set(),
|
|
1736
|
+
protectGateConfigs: options.protectTests !== false,
|
|
1737
|
+
allowBash: options.allowBash === true || process.env.UAP_DELIVER_ALLOW_BASH === '1',
|
|
1738
|
+
onEvent: (e) => console.log(chalk.dim(` [${taskId} r${e.round} ${e.kind}${e.tool ? `:${e.tool}` : ''}] ${e.detail ?? ''}`)),
|
|
1739
|
+
})
|
|
1740
|
+
: executor;
|
|
1741
|
+
const loop = new ConvergenceLoop({
|
|
1742
|
+
...loopConfig,
|
|
1743
|
+
projectRoot: root,
|
|
1744
|
+
baselineCheck: false,
|
|
1745
|
+
resumeFrom: undefined,
|
|
1746
|
+
// Checkpoints describe THE mission tree, and the inherited
|
|
1747
|
+
// explorer's workspaceProvider is bound to the MAIN projectRoot —
|
|
1748
|
+
// neither may run inside an isolated task.
|
|
1749
|
+
...(isolated ? { onCheckpoint: undefined, explorer: undefined } : {}),
|
|
1750
|
+
onIteration: makeIterationHook(root),
|
|
1751
|
+
}, exec, seams);
|
|
1752
|
+
return loop.deliver(prompt);
|
|
1753
|
+
},
|
|
1754
|
+
};
|
|
1755
|
+
};
|
|
1756
|
+
const runOrchestratedMission = async () => {
|
|
1757
|
+
const parallelTasks = resolveParallelTasks(cfgRaw.deliver?.parallelTasks);
|
|
1758
|
+
return runOrchestratedMissionCore(buildOrchestratedDeps(instruction, phases, parallelTasks));
|
|
1876
1759
|
};
|
|
1877
1760
|
const runPhasedMission = async () => {
|
|
1878
1761
|
const all = {
|
|
@@ -1935,143 +1818,86 @@ async function runDeliver(instruction, options) {
|
|
|
1935
1818
|
* per-epic attempt budget is spent. The blackboard orchestrator still runs
|
|
1936
1819
|
* WITHIN each epic when that epic itself decomposes.
|
|
1937
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
|
+
*/
|
|
1938
1826
|
const runEpicMission = async () => {
|
|
1939
|
-
const
|
|
1940
|
-
success: true, alreadyDelivered: false, turns: 0, bestScore: 0, bestTurn: 0,
|
|
1941
|
-
history: [], finalFeedback: '', finalOutput: '', totalDurationMs: 0,
|
|
1942
|
-
};
|
|
1827
|
+
const epicParallelTasks = resolveParallelTasks(cfgRaw.deliver?.parallelTasks);
|
|
1943
1828
|
const contractsFirst = process.env.UAP_DELIVER_CONTRACTS !== '0';
|
|
1944
1829
|
const scaffoldFirst = process.env.UAP_DELIVER_SCAFFOLD !== '0';
|
|
1945
|
-
|
|
1946
|
-
|
|
1947
|
-
|
|
1948
|
-
|
|
1949
|
-
|
|
1950
|
-
|
|
1951
|
-
|
|
1952
|
-
|
|
1953
|
-
|
|
1954
|
-
|
|
1955
|
-
|
|
1956
|
-
|
|
1957
|
-
|
|
1958
|
-
|
|
1959
|
-
|
|
1960
|
-
|
|
1961
|
-
|
|
1962
|
-
|
|
1963
|
-
|
|
1964
|
-
|
|
1965
|
-
|
|
1966
|
-
|
|
1967
|
-
// split is split again, one level shallower, until each piece fits a rail
|
|
1968
|
-
// — instead of failing the whole mission at the first split. Bounded.
|
|
1969
|
-
splitDepth: Math.max(1, Number(process.env.UAP_DELIVER_EPIC_SPLIT_DEPTH ?? 2)),
|
|
1970
|
-
// (#5) Auto-escalation: re-plan a failed epic into smaller pieces and
|
|
1971
|
-
// retry them rather than declaring the mission incomplete. On by default
|
|
1972
|
-
// for the epic path (already gated to complex missions); disable with
|
|
1973
|
-
// UAP_DELIVER_SPLIT_ON_ANY_FAILURE=0 to restore budget-exhaustion-only.
|
|
1974
|
-
splitOnAnyFailure: process.env.UAP_DELIVER_SPLIT_ON_ANY_FAILURE !== '0',
|
|
1975
|
-
// Re-plan a failed epic into sub-epics. Fires on context-budget
|
|
1976
|
-
// exhaustion (rail auto-size) and, under splitOnAnyFailure, on any
|
|
1977
|
-
// exhausted-attempts failure (auto-escalation). Always provided so #5 has
|
|
1978
|
-
// a planner; declines (null) when it can't produce ≥2 pieces.
|
|
1979
|
-
splitEpic: async (epic, lastFailure) => {
|
|
1980
|
-
const budgetHit = (lastFailure ?? '').includes(CONTEXT_BUDGET_MARKER);
|
|
1981
|
-
const reason = budgetHit
|
|
1982
|
-
? `outgrew its ~${(sessionBudget ?? 0).toLocaleString()}-token session budget`
|
|
1983
|
-
: 'could not be delivered whole after all attempts';
|
|
1984
|
-
console.log(chalk.yellow(` ✂ epic ${epic.id} ${reason} — re-planning as smaller sub-epics`));
|
|
1985
|
-
const subGoal = `${epic.goal}\n\n(The previous attempt did not complete` +
|
|
1986
|
-
`${lastFailure ? `: ${lastFailure.slice(0, 300)}` : ''}. Split this into smaller, independently completable phases.)`;
|
|
1987
|
-
// Re-plan is already reactive (the failure is in subGoal) - the extra
|
|
1988
|
-
// thought-experiment judge call buys nothing here; skip it.
|
|
1989
|
-
const subs = await planDeliveryPhases(subGoal, verdictExecutor, undefined, { sessionTokenBudget: sessionBudget, thoughtExperiment: false });
|
|
1990
|
-
return subs.length >= 2 ? subs.map((s) => ({ id: s.id, title: s.title, goal: s.goal })) : null;
|
|
1991
|
-
},
|
|
1992
|
-
onEpic: (epic, outcome) => {
|
|
1993
|
-
try {
|
|
1994
|
-
markItem(projectRoot, epic.id, outcome.accepted ? 'done' : 'failed', outcome.accepted ? undefined : outcome.summary);
|
|
1995
|
-
}
|
|
1996
|
-
catch { /* best-effort */ }
|
|
1997
|
-
if (epic.contracts && outcome.accepted && lastContractEpicFiles.length > 0) {
|
|
1998
|
-
const locked = lockContractFiles(contractLock, projectRoot, lastContractEpicFiles);
|
|
1999
|
-
if (locked.length > 0) {
|
|
2000
|
-
console.log(chalk.cyan(` \u{1f512} contracts locked for later epics: ${locked.join(', ')}`));
|
|
2001
|
-
}
|
|
2002
|
-
}
|
|
2003
|
-
console.log((outcome.accepted ? chalk.green(' \u2713') : chalk.red(' \u2717')) +
|
|
2004
|
-
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);
|
|
2005
1852
|
},
|
|
2006
|
-
|
|
2007
|
-
|
|
2008
|
-
? `\n\nALREADY BUILT (prior epics \u2014 build on them, do not redo):\n${ctx.priorSummaries.map((sm, i) => `${i + 1}. ${sm}`).join('\n')}`
|
|
2009
|
-
: '';
|
|
2010
|
-
const retry = ctx.lastFailure ? `\n\nPREVIOUS ATTEMPT FEEDBACK (fix this):\n${ctx.lastFailure}` : '';
|
|
2011
|
-
// Contracts-first steering: the contracts epic is told its output IS
|
|
2012
|
-
// the frozen API; later epics are told which files are locked and to
|
|
2013
|
-
// build against them exactly. Locked paths also land in the judge's
|
|
2014
|
-
// spec, so spec-referenced evidence guarantees it SEES the contracts.
|
|
2015
|
-
const contractsNote = epic.contracts
|
|
2016
|
-
? '\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.'
|
|
2017
|
-
: contractLock.size > 0
|
|
2018
|
-
? `\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.`
|
|
2019
|
-
: '';
|
|
2020
|
-
const scaffoldIds = new Set(epics.filter((e) => e.scaffold).map((e) => e.id));
|
|
2021
|
-
const fillsScaffold = !epic.scaffold && (epic.deps ?? []).some((d) => scaffoldIds.has(d));
|
|
2022
|
-
const scaffoldNote = epic.scaffold
|
|
2023
|
-
? '\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.'
|
|
2024
|
-
: fillsScaffold
|
|
2025
|
-
? '\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.'
|
|
2026
|
-
: '';
|
|
2027
|
-
const scoped = `OVERALL MISSION (context): ${instruction.slice(0, 300)}\n\n` +
|
|
2028
|
-
`EPIC \u2014 ${epic.title}:\n${epic.goal}${priors}${retry}${contractsNote}${scaffoldNote}\n\n` +
|
|
2029
|
-
'Deliver ONLY this epic. All gates must pass at the end.';
|
|
2030
|
-
console.log(chalk.bold(`\u25b6 epic ${epic.id} (attempt ${ctx.attempt}): ${epic.title}`));
|
|
2031
|
-
// Grade the epic's DELIVERABLE, not the process prompt: the scoped
|
|
2032
|
-
// prompt carries process instructions ("read X first"), prior-epic
|
|
2033
|
-
// summaries, and retry feedback — none verifiable from code, so a
|
|
2034
|
-
// small judge rejects objectively-green turns against them forever.
|
|
2035
|
-
acceptanceSpec =
|
|
2036
|
-
`EPIC — ${epic.title}:\n${epic.goal}` +
|
|
2037
|
-
(epic.criteria?.length ? `\nAcceptance criteria:\n${epic.criteria.map((c) => `- ${c}`).join('\n')}` : '') +
|
|
2038
|
-
(!epic.contracts && contractLock.size > 0 ? `\n(Builds against locked contracts: ${[...contractLock].join(', ')})` : '') +
|
|
2039
|
-
(fillsScaffold ? '\n(FILL epic: no todo!()/NotImplementedError/TODO-throw stub markers may remain in the files it implements; signatures must be unchanged.)' : '') +
|
|
2040
|
-
(epic.scaffold ? '\n(SCAFFOLD epic: complete compiling signatures with stub bodies are the DELIVERABLE — unimplemented logic is expected and correct here.)' : '');
|
|
1853
|
+
setEpicSpec: (spec) => {
|
|
1854
|
+
acceptanceSpec = spec;
|
|
2041
1855
|
specChangeEvidence.writes = 0; // fresh epic — breaker needs fresh diff evidence
|
|
2042
|
-
const epicTask = await openDeliveryTask(`${epic.title} \u2014 ${epic.goal.slice(0, 120)}`, projectRoot, missionTask?.id);
|
|
2043
|
-
const loop = new ConvergenceLoop({ ...loopConfig, baselineCheck: false, resumeFrom: undefined, onIteration: makeIterationHook() }, executor, seams);
|
|
2044
|
-
const r = await loop.deliver(scoped);
|
|
2045
|
-
all.turns += r.turns;
|
|
2046
|
-
all.history.push(...r.history);
|
|
2047
|
-
all.totalDurationMs += r.totalDurationMs;
|
|
2048
|
-
if (r.bestScore > all.bestScore) {
|
|
2049
|
-
all.bestScore = r.bestScore;
|
|
2050
|
-
all.bestTurn = r.bestTurn;
|
|
2051
|
-
}
|
|
2052
|
-
all.finalFeedback = r.finalFeedback;
|
|
2053
|
-
all.finalOutput = r.finalOutput;
|
|
2054
|
-
completeDeliveryTask(epicTask, r);
|
|
2055
|
-
const files = [...new Set(r.history.flatMap((h) => h.filesApplied ?? []))];
|
|
2056
|
-
if (epic.contracts)
|
|
2057
|
-
lastContractEpicFiles = files;
|
|
2058
|
-
// Rail sizing: surface budget exhaustion to the epic controller — its
|
|
2059
|
-
// split path keys off CONTEXT_BUDGET_MARKER in the failure summary,
|
|
2060
|
-
// and the goal-based summary would otherwise swallow the signal.
|
|
2061
|
-
const budgetHit = !r.success && r.history.some((h) => h.budgetStopped);
|
|
2062
|
-
return {
|
|
2063
|
-
success: r.success,
|
|
2064
|
-
turns: r.turns,
|
|
2065
|
-
summary: `${epic.goal.slice(0, 140)}${files.length ? ` [files: ${files.join(', ')}]` : ''}` +
|
|
2066
|
-
(budgetHit ? ` ${CONTEXT_BUDGET_MARKER} session(s) exceeded the context budget — scope is too large for one session` : ''),
|
|
2067
|
-
};
|
|
2068
1856
|
},
|
|
1857
|
+
judgeEpic: options.acceptance && !skipJudgeForSimple
|
|
1858
|
+
? async (spec) => {
|
|
1859
|
+
try {
|
|
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);
|
|
1867
|
+
}
|
|
1868
|
+
catch {
|
|
1869
|
+
return null; // judge unavailable — the objective verdicts stand
|
|
1870
|
+
}
|
|
1871
|
+
}
|
|
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)),
|
|
2069
1900
|
});
|
|
2070
|
-
all.success = epicResult.success;
|
|
2071
|
-
if (!epicResult.success) {
|
|
2072
|
-
all.finalFeedback = `epic controller incomplete \u2014 failed epic(s): ${epicResult.failed.join(', ')}\n${all.finalFeedback}`;
|
|
2073
|
-
}
|
|
2074
|
-
return all;
|
|
2075
1901
|
};
|
|
2076
1902
|
// --keep-best (never regress): capture the starting required-gate score and a
|
|
2077
1903
|
// project snapshot so we can roll back if deliver ends up WORSE than it
|