@miller-tech/uap 1.150.0 → 1.150.2

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.
@@ -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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"}
@@ -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
- const specChangeEvidence = { writes: 0 };
1277
- const acceptanceChurnBreaker = createAcceptanceChurnBreaker(Number(process.env.UAP_DELIVER_ACCEPTANCE_FLIP_LIMIT ?? 2), () => specChangeEvidence.writes > 0);
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: acceptanceSpec,
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 = acceptanceChurnBreaker.check(acceptanceSpec, verdict);
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
- specChangeEvidence.writes += record.filesApplied.length;
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
- const orchResult = await orchestrate({
1596
- mission: instruction,
1597
- tasks,
1598
- contextBudgetChars: Number(process.env.UAP_DELIVER_CONTEXT_BUDGET ?? 6000),
1599
- maxTasks: Number(process.env.UAP_DELIVER_MAX_TASKS ?? 40),
1600
- // P3 per-task memory retrieval: pull the few most relevant established
1601
- // decisions/patterns/gotchas for THIS task's goal (a small semantic
1602
- // query, not the full spec), so a fresh-context task reconstructs "what
1603
- // am I building and why" from memory. Fail-soft no design lines.
1604
- retrieveDesign: async (task) => {
1605
- try {
1606
- const { retrieveDynamicMemoryContext } = await import('../memory/dynamic-retrieval.js');
1607
- const mem = await retrieveDynamicMemoryContext(task.goal, projectRoot, { maxTokens: 400 });
1608
- const lines = [];
1609
- for (const m of mem.relevantMemories.slice(0, 4))
1610
- lines.push(m.content.slice(0, 160));
1611
- for (const g of mem.gotchas.slice(0, 2))
1612
- lines.push(`gotcha: ${g.slice(0, 140)}`);
1613
- for (const pat of mem.patterns.slice(0, 2))
1614
- lines.push(`pattern: ${pat.slice(0, 140)}`);
1615
- return lines;
1616
- }
1617
- catch {
1618
- return [];
1619
- }
1620
- },
1621
- // P3/P4 durable publish: a completed task's verified contract lands in
1622
- // short-term memory so later tasks (and future fresh sessions) retrieve
1623
- // the interface from memory instead of re-reading source.
1624
- publish: async (outcome, task) => {
1625
- if (!outcome.success)
1626
- return;
1627
- await recordOrchestratorTaskOutcome(task.id, task.title, outcome.contract ?? outcome.summary, projectRoot);
1628
- },
1629
- runTask: async (ctx, task) => {
1630
- console.log(chalk.bold(`\u25b6 task ${task.id}: ${task.title}`) +
1631
- chalk.dim(` (ctx ${ctx.prompt.length} chars, deps: ${ctx.includedDeps.join(',') || 'none'})`));
1632
- const taskRecord = await openDeliveryTask(`${task.title} — ${task.goal.slice(0, 120)}`, projectRoot, missionTask?.id);
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
- specChangeEvidence.writes = 0;
1635
- const loop = new ConvergenceLoop({ ...loopConfig, baselineCheck: false, resumeFrom: undefined, onIteration: makeIterationHook() }, executor, seams);
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 extract the VERIFIED public contract of what this task built so
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) => /\.(js|mjs|cjs|ts|tsx|jsx|py)$/.test(f))
1703
+ .filter((f) => SOURCE_FILE_RE.test(f))
1657
1704
  .map((f) => {
1658
1705
  try {
1659
- return { path: f, content: readFileSync(join(projectRoot, f), 'utf-8') };
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 adaptive re-planning feed: a task may surface work the initial
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 never free-form model text as durable memory.
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 = /NEW_TASKS:\s*(\[[\s\S]*?\])/.exec(r.finalOutput || r.finalFeedback || '');
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}`;