@miller-tech/uap 1.150.0 → 1.150.1
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 +212 -52
- package/dist/cli/deliver.js.map +1 -1
- package/dist/delivery/task-workspace.d.ts +70 -0
- package/dist/delivery/task-workspace.d.ts.map +1 -0
- package/dist/delivery/task-workspace.js +305 -0
- package/dist/delivery/task-workspace.js.map +1 -0
- package/docs/reference/CONFIGURATION.md +9 -0
- 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
|
@@ -121,6 +121,7 @@ import { extractContract } from '../delivery/contract-extractor.js';
|
|
|
121
121
|
import { completeDeliveryTask, openDeliveryTask, recordDeliveryOutcome, recordOrchestratorTaskOutcome, reopenDeliveryTask } from '../delivery/task-sync.js';
|
|
122
122
|
import { autoMineHaloTraces, summarizeWeaknesses, weaknessGuidance, loadPersistedWeaknesses } from '../delivery/auto-mine.js';
|
|
123
123
|
import { createGitWorktreeProvider } from '../delivery/candidate-workspace.js';
|
|
124
|
+
import { createTaskWorkspaceManager, resolveParallelTasks } from '../delivery/task-workspace.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';
|
|
@@ -1267,14 +1268,45 @@ async function runDeliver(instruction, options) {
|
|
|
1267
1268
|
// Phased missions re-point the judge at the current phase's goal — judging
|
|
1268
1269
|
// phase 1 against the FULL mission would fail every phase by construction.
|
|
1269
1270
|
let acceptanceSpec = instruction;
|
|
1271
|
+
// Parallel orchestrated tasks run in ISOLATED worktree roots; each loop's
|
|
1272
|
+
// judge must grade against ITS task's spec, not a shared mutable. The
|
|
1273
|
+
// acceptance gate resolves by the root it is invoked with; the phased and
|
|
1274
|
+
// epic paths (single loop at a time) keep using the shared variable.
|
|
1275
|
+
const acceptanceSpecByRoot = new Map();
|
|
1270
1276
|
// Secondary-judge churn breaker: bounds consecutive judge rejections of
|
|
1271
1277
|
// objectively-green turns per spec (env UAP_DELIVER_ACCEPTANCE_FLIP_LIMIT,
|
|
1272
1278
|
// default 2), after which the objective gates win. Primary mode is exempt.
|
|
1273
1279
|
// Change-evidence for the breaker's zero-diff guard: files applied since the
|
|
1274
1280
|
// current acceptance spec (epic) began. Incremented by the iteration hook,
|
|
1275
1281
|
// reset wherever acceptanceSpec is re-pointed.
|
|
1276
|
-
|
|
1277
|
-
|
|
1282
|
+
// Per-ROOT under parallel dispatch: concurrent loops must not zero or
|
|
1283
|
+
// inflate each other's evidence (an inflated count could let the breaker
|
|
1284
|
+
// accept a task with zero writes of its own). projectRoot's entry is the
|
|
1285
|
+
// historic shared counter the phased/epic paths keep using.
|
|
1286
|
+
const writesByRoot = new Map();
|
|
1287
|
+
const evidenceFor = (root) => {
|
|
1288
|
+
let e = writesByRoot.get(root);
|
|
1289
|
+
if (!e) {
|
|
1290
|
+
e = { writes: 0 };
|
|
1291
|
+
writesByRoot.set(root, e);
|
|
1292
|
+
}
|
|
1293
|
+
return e;
|
|
1294
|
+
};
|
|
1295
|
+
const specChangeEvidence = evidenceFor(projectRoot);
|
|
1296
|
+
// Breaker state is per SPEC: one shared instance thrashes its current-spec
|
|
1297
|
+
// slot under parallel dispatch and can then never trip.
|
|
1298
|
+
const acceptanceFlipLimit = Number(process.env.UAP_DELIVER_ACCEPTANCE_FLIP_LIMIT ?? 2);
|
|
1299
|
+
const acceptanceBreakers = new Map();
|
|
1300
|
+
const breakerFor = (spec, root) => {
|
|
1301
|
+
let b = acceptanceBreakers.get(spec);
|
|
1302
|
+
if (!b) {
|
|
1303
|
+
if (acceptanceBreakers.size > 100)
|
|
1304
|
+
acceptanceBreakers.clear(); // runaway guard
|
|
1305
|
+
b = createAcceptanceChurnBreaker(acceptanceFlipLimit, () => evidenceFor(root).writes > 0);
|
|
1306
|
+
acceptanceBreakers.set(spec, b);
|
|
1307
|
+
}
|
|
1308
|
+
return b;
|
|
1309
|
+
};
|
|
1278
1310
|
const acceptanceGate = options.acceptance && !skipJudgeForSimple
|
|
1279
1311
|
? async (root) => {
|
|
1280
1312
|
// Primary mode: the only objective rung is the trivial bootstrap, and the
|
|
@@ -1306,13 +1338,14 @@ async function runDeliver(instruction, options) {
|
|
|
1306
1338
|
// gates all passed — hand the judge that fact as evidence, so
|
|
1307
1339
|
// requirements like "make the tests pass" are graded on the gate
|
|
1308
1340
|
// result instead of speculated about from static code.
|
|
1341
|
+
const resolvedSpec = acceptanceSpecByRoot.get(root) ?? acceptanceSpec;
|
|
1309
1342
|
const uvNote = buildUserPathsNote(root);
|
|
1310
1343
|
const baseNote = acceptancePrimary
|
|
1311
1344
|
? visualNote
|
|
1312
1345
|
: 'Objective project gates (build/test suite) ALL PASSED on this turn — treat test/build-related requirements as objectively verified.';
|
|
1313
1346
|
const runtimeNote = [baseNote, uvNote?.note].filter(Boolean).join(' ');
|
|
1314
1347
|
const r = await runAcceptanceGate({
|
|
1315
|
-
spec:
|
|
1348
|
+
spec: resolvedSpec,
|
|
1316
1349
|
projectRoot: root,
|
|
1317
1350
|
executor: verdictExecutor,
|
|
1318
1351
|
...(runtimeNote ? { runtimeNote } : {}),
|
|
@@ -1322,7 +1355,7 @@ async function runDeliver(instruction, options) {
|
|
|
1322
1355
|
// objective gates ALL passed, so a bounded number of consecutive judge
|
|
1323
1356
|
// rejections hands the verdict back to the gates instead of wedging.
|
|
1324
1357
|
if (!acceptancePrimary) {
|
|
1325
|
-
const checked =
|
|
1358
|
+
const checked = breakerFor(resolvedSpec, root).check(resolvedSpec, verdict);
|
|
1326
1359
|
if (checked.overridden) {
|
|
1327
1360
|
console.log(chalk.yellow(' ⚖ acceptance: judge rejected consecutive objectively-green turns — accepting on gates (raise UAP_DELIVER_ACCEPTANCE_FLIP_LIMIT to let the judge argue longer)'));
|
|
1328
1361
|
}
|
|
@@ -1339,7 +1372,7 @@ async function runDeliver(instruction, options) {
|
|
|
1339
1372
|
...(acceptanceGate ? { acceptanceGate } : {}),
|
|
1340
1373
|
};
|
|
1341
1374
|
/** Compose the per-turn hooks with a FRESH escalation controller. */
|
|
1342
|
-
const makeIterationHook = () => {
|
|
1375
|
+
const makeIterationHook = (evidenceRoot = projectRoot) => {
|
|
1343
1376
|
const escalation = makeEscalation();
|
|
1344
1377
|
const repair = repairAgent && process.env.UAP_DELIVER_REPAIR_ESCALATION !== '0'
|
|
1345
1378
|
? createRepairEscalation({
|
|
@@ -1356,7 +1389,7 @@ async function runDeliver(instruction, options) {
|
|
|
1356
1389
|
return composeIterationHooks((record) => printProgress(record), (record) => {
|
|
1357
1390
|
// Feed the acceptance breaker's zero-diff guard.
|
|
1358
1391
|
if (record.filesApplied.length > 0)
|
|
1359
|
-
|
|
1392
|
+
evidenceFor(evidenceRoot).writes += record.filesApplied.length;
|
|
1360
1393
|
return undefined;
|
|
1361
1394
|
}, (record) => haloTracer.onIteration(record), repair ? (record) => repair.onIteration(record) : undefined, coordinator ? (record) => coordinator.onIteration(record) : undefined, escalation ? (record) => escalation.onIteration(record) : undefined);
|
|
1362
1395
|
};
|
|
@@ -1592,47 +1625,61 @@ async function runDeliver(instruction, options) {
|
|
|
1592
1625
|
const tasks = phases.map((ph) => ({
|
|
1593
1626
|
id: ph.id, title: ph.title, goal: ph.goal, ...(ph.deps ? { deps: ph.deps } : {}),
|
|
1594
1627
|
}));
|
|
1595
|
-
|
|
1596
|
-
|
|
1597
|
-
|
|
1598
|
-
|
|
1599
|
-
|
|
1600
|
-
|
|
1601
|
-
|
|
1602
|
-
|
|
1603
|
-
|
|
1604
|
-
|
|
1605
|
-
|
|
1606
|
-
|
|
1607
|
-
|
|
1608
|
-
|
|
1609
|
-
|
|
1610
|
-
|
|
1611
|
-
|
|
1612
|
-
|
|
1613
|
-
|
|
1614
|
-
|
|
1615
|
-
|
|
1616
|
-
|
|
1617
|
-
|
|
1618
|
-
|
|
1619
|
-
|
|
1620
|
-
|
|
1621
|
-
|
|
1622
|
-
|
|
1623
|
-
|
|
1624
|
-
|
|
1625
|
-
|
|
1626
|
-
|
|
1627
|
-
|
|
1628
|
-
|
|
1629
|
-
|
|
1630
|
-
|
|
1631
|
-
|
|
1632
|
-
|
|
1628
|
+
// -- Worktree-isolated parallel dispatch --
|
|
1629
|
+
// `.uap.json` deliver.parallelTasks (config-only BY DESIGN - no env; see
|
|
1630
|
+
// the concurrency notes in task-orchestrator.ts) dispatches independent
|
|
1631
|
+
// READY tasks concurrently, EACH in its own detached git worktree seeded
|
|
1632
|
+
// with the main tree's current uncommitted state and judged against its
|
|
1633
|
+
// own spec. Merge-backs are SERIALIZED under a lock; a conflicting merge
|
|
1634
|
+
// fails the task, and the orchestrator's minimal repair retries it in a
|
|
1635
|
+
// fresh workspace seeded with the updated baseline - conflicts resolve
|
|
1636
|
+
// through the ATG repair path instead of corrupting the tree.
|
|
1637
|
+
const parallelTasks = resolveParallelTasks(cfgRaw.deliver?.parallelTasks);
|
|
1638
|
+
const wsManager = parallelTasks > 1 ? createTaskWorkspaceManager(projectRoot) : null;
|
|
1639
|
+
if (parallelTasks > 1 && !wsManager) {
|
|
1640
|
+
console.log(chalk.yellow(' ⇉ parallel tasks: worktree isolation unavailable (not a git repo?) - running sequentially'));
|
|
1641
|
+
}
|
|
1642
|
+
else if (wsManager) {
|
|
1643
|
+
console.log(chalk.cyan(` ⇉ parallel tasks: up to ${parallelTasks} independent tasks in isolated worktrees`));
|
|
1644
|
+
}
|
|
1645
|
+
// Merge-boundary defense-in-depth: refuse a delta touching protected
|
|
1646
|
+
// tests/gate configs at the boundary, instead of trusting the in-worktree
|
|
1647
|
+
// layers transitively (mode-only changes and capture-skipped files would
|
|
1648
|
+
// otherwise ride the patch back).
|
|
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)
|
|
1633
1668
|
acceptanceSpec = ctx.prompt;
|
|
1634
|
-
|
|
1635
|
-
|
|
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);
|
|
1636
1683
|
const r = await loop.deliver(ctx.prompt);
|
|
1637
1684
|
all.turns += r.turns;
|
|
1638
1685
|
all.history.push(...r.history);
|
|
@@ -1645,7 +1692,7 @@ async function runDeliver(instruction, options) {
|
|
|
1645
1692
|
all.finalOutput = r.finalOutput;
|
|
1646
1693
|
completeDeliveryTask(taskRecord, r);
|
|
1647
1694
|
const files = [...new Set(r.history.flatMap((h) => h.filesApplied ?? []))];
|
|
1648
|
-
// P4
|
|
1695
|
+
// P4 - extract the VERIFIED public contract of what this task built so
|
|
1649
1696
|
// dependents load the interface (a few hundred chars), not the source.
|
|
1650
1697
|
let contract;
|
|
1651
1698
|
if (r.success && files.length > 0) {
|
|
@@ -1653,10 +1700,10 @@ async function runDeliver(instruction, options) {
|
|
|
1653
1700
|
const { readFileSync } = await import('fs');
|
|
1654
1701
|
const { join } = await import('path');
|
|
1655
1702
|
const srcs = files
|
|
1656
|
-
.filter((f) =>
|
|
1703
|
+
.filter((f) => SOURCE_FILE_RE.test(f))
|
|
1657
1704
|
.map((f) => {
|
|
1658
1705
|
try {
|
|
1659
|
-
return { path: f, content: readFileSync(join(
|
|
1706
|
+
return { path: f, content: readFileSync(join(root, f), 'utf-8') };
|
|
1660
1707
|
}
|
|
1661
1708
|
catch {
|
|
1662
1709
|
return null;
|
|
@@ -1669,16 +1716,16 @@ async function runDeliver(instruction, options) {
|
|
|
1669
1716
|
contract = undefined;
|
|
1670
1717
|
}
|
|
1671
1718
|
}
|
|
1672
|
-
// P5
|
|
1719
|
+
// P5 - adaptive re-planning feed: a task may surface work the initial
|
|
1673
1720
|
// plan missed by emitting a `NEW_TASKS: [ {id,title,goal,deps} ]` JSON
|
|
1674
1721
|
// array in its output. Parsed through the same validator as the planner
|
|
1675
1722
|
// (well-formed only), then folded into the DAG by orchestrate() (which
|
|
1676
1723
|
// dedupes, topo-resorts, and caps at maxTasks). Only structural fields
|
|
1677
|
-
// are used
|
|
1724
|
+
// are used - never free-form model text as durable memory.
|
|
1678
1725
|
let newTasks;
|
|
1679
1726
|
if (r.success) {
|
|
1680
1727
|
try {
|
|
1681
|
-
const marker =
|
|
1728
|
+
const marker = NEW_TASKS_RE.exec(r.finalOutput || r.finalFeedback || '');
|
|
1682
1729
|
if (marker) {
|
|
1683
1730
|
const parsed = parsePhaseArray(marker[1]).filter((t) => t.id !== task.id);
|
|
1684
1731
|
if (parsed.length > 0) {
|
|
@@ -1706,8 +1753,121 @@ async function runDeliver(instruction, options) {
|
|
|
1706
1753
|
...(contract ? { contract } : {}),
|
|
1707
1754
|
...(newTasks && newTasks.length > 0 ? { newTasks } : {}),
|
|
1708
1755
|
};
|
|
1756
|
+
}
|
|
1757
|
+
finally {
|
|
1758
|
+
acceptanceSpecByRoot.delete(root);
|
|
1759
|
+
}
|
|
1760
|
+
};
|
|
1761
|
+
const orchResult = await orchestrate({
|
|
1762
|
+
mission: instruction,
|
|
1763
|
+
tasks,
|
|
1764
|
+
contextBudgetChars: Number(process.env.UAP_DELIVER_CONTEXT_BUDGET ?? 6000),
|
|
1765
|
+
maxTasks: Number(process.env.UAP_DELIVER_MAX_TASKS ?? 40),
|
|
1766
|
+
// Dependency-aware parallel dispatch — only with worktree isolation.
|
|
1767
|
+
concurrency: wsManager ? parallelTasks : 1,
|
|
1768
|
+
// P3 — per-task memory retrieval: pull the few most relevant established
|
|
1769
|
+
// decisions/patterns/gotchas for THIS task's goal (a small semantic
|
|
1770
|
+
// query, not the full spec), so a fresh-context task reconstructs "what
|
|
1771
|
+
// am I building and why" from memory. Fail-soft → no design lines.
|
|
1772
|
+
retrieveDesign: async (task) => {
|
|
1773
|
+
try {
|
|
1774
|
+
const { retrieveDynamicMemoryContext } = await import('../memory/dynamic-retrieval.js');
|
|
1775
|
+
const mem = await retrieveDynamicMemoryContext(task.goal, projectRoot, { maxTokens: 400 });
|
|
1776
|
+
const lines = [];
|
|
1777
|
+
for (const m of mem.relevantMemories.slice(0, 4))
|
|
1778
|
+
lines.push(m.content.slice(0, 160));
|
|
1779
|
+
for (const g of mem.gotchas.slice(0, 2))
|
|
1780
|
+
lines.push(`gotcha: ${g.slice(0, 140)}`);
|
|
1781
|
+
for (const pat of mem.patterns.slice(0, 2))
|
|
1782
|
+
lines.push(`pattern: ${pat.slice(0, 140)}`);
|
|
1783
|
+
return lines;
|
|
1784
|
+
}
|
|
1785
|
+
catch {
|
|
1786
|
+
return [];
|
|
1787
|
+
}
|
|
1788
|
+
},
|
|
1789
|
+
// P3/P4 — durable publish: a completed task's verified contract lands in
|
|
1790
|
+
// short-term memory so later tasks (and future fresh sessions) retrieve
|
|
1791
|
+
// the interface from memory instead of re-reading source.
|
|
1792
|
+
publish: async (outcome, task) => {
|
|
1793
|
+
if (!outcome.success)
|
|
1794
|
+
return;
|
|
1795
|
+
await recordOrchestratorTaskOutcome(task.id, task.title, outcome.contract ?? outcome.summary, projectRoot);
|
|
1796
|
+
},
|
|
1797
|
+
runTask: async (ctx, task) => {
|
|
1798
|
+
console.log(chalk.bold(`▶ task ${task.id}: ${task.title}`) +
|
|
1799
|
+
chalk.dim(` (ctx ${ctx.prompt.length} chars, deps: ${ctx.includedDeps.join(',') || 'none'})`));
|
|
1800
|
+
// Workspace acquisition mutates git state (worktree add) - serialize
|
|
1801
|
+
// it behind the same lock the merges use.
|
|
1802
|
+
const ws = wsManager ? await withMergeLock(() => wsManager.acquire(task.id)) : null;
|
|
1803
|
+
if (!ws) {
|
|
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
|
+
}
|
|
1812
|
+
try {
|
|
1813
|
+
// The agentic executor binds tools to a root at construction - give
|
|
1814
|
+
// the isolated task its own, with the same protections re-derived
|
|
1815
|
+
// for its tree. The blind executor is root-free (the per-task loop's
|
|
1816
|
+
// projectRoot directs the applier and gates).
|
|
1817
|
+
const taskExecutor = agentic
|
|
1818
|
+
? createAgenticExecutor(model, {
|
|
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;
|
|
1847
|
+
}
|
|
1848
|
+
finally {
|
|
1849
|
+
ws.cleanup();
|
|
1850
|
+
}
|
|
1709
1851
|
},
|
|
1710
1852
|
});
|
|
1853
|
+
// Post-merge verification: every gate passed in SOME tree, but 3-way
|
|
1854
|
+
// resolution can produce a combined main tree no gate ever saw. One
|
|
1855
|
+
// ladder run on the merged result closes that. Fail-soft on runner
|
|
1856
|
+
// errors; hard on gate failures.
|
|
1857
|
+
if (wsManager && orchResult.success) {
|
|
1858
|
+
try {
|
|
1859
|
+
console.log(chalk.cyan(' ⇉ post-merge verification: gate ladder on the merged main tree…'));
|
|
1860
|
+
const combined = await tieredRunner(rungs, projectRoot, {});
|
|
1861
|
+
if (!combined.passed) {
|
|
1862
|
+
orchResult.success = false;
|
|
1863
|
+
orchResult.failed.push('post-merge-verification');
|
|
1864
|
+
all.finalFeedback = `post-merge verification FAILED on the combined tree:\n${combined.feedback}`.slice(0, 4000);
|
|
1865
|
+
}
|
|
1866
|
+
}
|
|
1867
|
+
catch {
|
|
1868
|
+
// verification unavailable — keep the per-task verdicts
|
|
1869
|
+
}
|
|
1870
|
+
}
|
|
1711
1871
|
all.success = orchResult.success;
|
|
1712
1872
|
if (!orchResult.success) {
|
|
1713
1873
|
all.finalFeedback = `orchestration incomplete — failed tasks: ${orchResult.failed.join(', ')}\n${all.finalFeedback}`;
|