taskplane 0.14.1 → 0.15.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/extensions/task-runner.ts +13 -4
- package/extensions/taskplane/extension.ts +292 -275
- package/extensions/taskplane/supervisor.ts +4 -1
- package/package.json +1 -1
- package/skills/create-taskplane-task/SKILL.md +4 -1
- package/skills/create-taskplane-task/references/prompt-template.md +5 -3
- package/templates/agents/local/task-worker.md +1 -0
- package/templates/agents/supervisor-routing.md +1 -0
- package/templates/agents/supervisor.md +2 -0
- package/templates/agents/task-worker.md +47 -0
|
@@ -2603,16 +2603,25 @@ export default function (pi: ExtensionAPI) {
|
|
|
2603
2603
|
// If context limit is hit mid-task, the next iteration picks up from
|
|
2604
2604
|
// the first incomplete step via STATUS.md — same recovery mechanism.
|
|
2605
2605
|
|
|
2606
|
-
// Mark
|
|
2606
|
+
// Mark only the first incomplete step as in-progress
|
|
2607
2607
|
{
|
|
2608
2608
|
const currentStatus = parseStatusMd(readFileSync(statusPath, "utf-8"));
|
|
2609
|
+
let foundFirstIncomplete = false;
|
|
2609
2610
|
for (const step of task.steps) {
|
|
2610
2611
|
const ss = currentStatus.steps.find(s => s.number === step.number);
|
|
2611
2612
|
if (ss?.status === "complete") continue;
|
|
2612
2613
|
|
|
2613
|
-
|
|
2614
|
-
|
|
2615
|
-
|
|
2614
|
+
if (!foundFirstIncomplete) {
|
|
2615
|
+
// Mark the first incomplete step as in-progress
|
|
2616
|
+
updateStepStatus(statusPath, step.number, "in-progress");
|
|
2617
|
+
logExecution(statusPath, `Step ${step.number} started`, step.name);
|
|
2618
|
+
foundFirstIncomplete = true;
|
|
2619
|
+
} else {
|
|
2620
|
+
// Ensure future steps show as not-started, not in-progress
|
|
2621
|
+
if (ss?.status === "in-progress") {
|
|
2622
|
+
updateStepStatus(statusPath, step.number, "not-started");
|
|
2623
|
+
}
|
|
2624
|
+
}
|
|
2616
2625
|
}
|
|
2617
2626
|
}
|
|
2618
2627
|
|
|
@@ -1344,282 +1344,11 @@ export default function (pi: ExtensionAPI) {
|
|
|
1344
1344
|
|
|
1345
1345
|
if (!requireExecCtx(ctx)) return;
|
|
1346
1346
|
|
|
1347
|
-
// ── TP-
|
|
1348
|
-
|
|
1349
|
-
|
|
1350
|
-
|
|
1351
|
-
// without the operator needing to know about internal mode distinctions.
|
|
1352
|
-
if (supervisorState.active && supervisorState.routingContext) {
|
|
1353
|
-
await deactivateSupervisor(pi, supervisorState);
|
|
1347
|
+
// ── TP-061: Delegate to shared helper ────────────────────
|
|
1348
|
+
const result = await doOrchStart(args, ctx);
|
|
1349
|
+
if (result.error) {
|
|
1350
|
+
ctx.ui.notify(result.message, "warning");
|
|
1354
1351
|
}
|
|
1355
|
-
|
|
1356
|
-
// Prevent concurrent batch execution (merging is an active state)
|
|
1357
|
-
if (orchBatchState.phase !== "idle" && orchBatchState.phase !== "completed" && orchBatchState.phase !== "failed" && orchBatchState.phase !== "stopped") {
|
|
1358
|
-
ctx.ui.notify(
|
|
1359
|
-
`⚠️ A batch is already ${orchBatchState.phase} (${orchBatchState.batchId}). ` +
|
|
1360
|
-
`Use /orch-pause to pause or wait for completion.`,
|
|
1361
|
-
"warning",
|
|
1362
|
-
);
|
|
1363
|
-
return;
|
|
1364
|
-
}
|
|
1365
|
-
|
|
1366
|
-
// Root references from execution context.
|
|
1367
|
-
// Currently all .pi state, orphan detection, batch state, abort signal,
|
|
1368
|
-
// and discovery operations use repoRoot for consistency with engine.ts,
|
|
1369
|
-
// resume.ts, and execution.ts which all alias cwd → repoRoot.
|
|
1370
|
-
// In repo mode workspaceRoot === repoRoot, so this is safe.
|
|
1371
|
-
// TODO(workspace-mode): when workspace mode is fully threaded through
|
|
1372
|
-
// engine/resume/execution, split state root from git root.
|
|
1373
|
-
const { repoRoot } = execCtx!;
|
|
1374
|
-
|
|
1375
|
-
// ── Orphan detection (TS-009 Step 3) ─────────────────────
|
|
1376
|
-
const orphanResult = detectOrphanSessions(
|
|
1377
|
-
orchConfig.orchestrator.tmux_prefix,
|
|
1378
|
-
repoRoot,
|
|
1379
|
-
);
|
|
1380
|
-
|
|
1381
|
-
switch (orphanResult.recommendedAction) {
|
|
1382
|
-
case "resume": {
|
|
1383
|
-
// Safety net: if the persisted phase is not actually resumable (e.g. "failed",
|
|
1384
|
-
// "stopped") — which can happen when the batch crashed after writing a terminal
|
|
1385
|
-
// phase but before /orch-abort cleaned up — auto-delete the state file and
|
|
1386
|
-
// fall through to start fresh rather than blocking the user with a catch-22.
|
|
1387
|
-
const resumablePhases = ["paused", "executing", "merging"];
|
|
1388
|
-
const phase = orphanResult.loadedState?.phase ?? "";
|
|
1389
|
-
const hasOrphans = orphanResult.orphanSessions.length > 0;
|
|
1390
|
-
if (!hasOrphans && !resumablePhases.includes(phase)) {
|
|
1391
|
-
try { deleteBatchState(repoRoot); } catch { /* best effort */ }
|
|
1392
|
-
ctx.ui.notify(
|
|
1393
|
-
`🧹 Cleared non-resumable stale batch (${orphanResult.loadedState?.batchId}, phase=${phase}). Starting fresh.`,
|
|
1394
|
-
"info",
|
|
1395
|
-
);
|
|
1396
|
-
break; // fall through to start a new batch
|
|
1397
|
-
}
|
|
1398
|
-
// Genuinely resumable or has live orphan sessions — prompt user
|
|
1399
|
-
ctx.ui.notify(orphanResult.userMessage, "warning");
|
|
1400
|
-
return;
|
|
1401
|
-
}
|
|
1402
|
-
|
|
1403
|
-
case "abort-orphans":
|
|
1404
|
-
// Orphan sessions without usable state
|
|
1405
|
-
ctx.ui.notify(orphanResult.userMessage, "warning");
|
|
1406
|
-
return;
|
|
1407
|
-
|
|
1408
|
-
case "cleanup-stale":
|
|
1409
|
-
// No orphans + stale/completed state file — auto-delete and continue
|
|
1410
|
-
try {
|
|
1411
|
-
deleteBatchState(repoRoot);
|
|
1412
|
-
} catch {
|
|
1413
|
-
// Best-effort cleanup — proceed even if delete fails
|
|
1414
|
-
}
|
|
1415
|
-
if (orphanResult.userMessage) {
|
|
1416
|
-
ctx.ui.notify(orphanResult.userMessage, "info");
|
|
1417
|
-
}
|
|
1418
|
-
break;
|
|
1419
|
-
|
|
1420
|
-
case "paused-corrupt":
|
|
1421
|
-
// Corrupt/unreadable state file — do NOT auto-delete.
|
|
1422
|
-
// Enter paused phase so operator-visible state reflects the issue,
|
|
1423
|
-
// notify user, refresh widget, then stop.
|
|
1424
|
-
orchBatchState.phase = "paused";
|
|
1425
|
-
orchBatchState.errors.push(orphanResult.userMessage);
|
|
1426
|
-
updateOrchWidget();
|
|
1427
|
-
ctx.ui.notify(orphanResult.userMessage, "warning");
|
|
1428
|
-
return;
|
|
1429
|
-
|
|
1430
|
-
case "start-fresh":
|
|
1431
|
-
// No orphans, no state file — proceed normally
|
|
1432
|
-
break;
|
|
1433
|
-
}
|
|
1434
|
-
|
|
1435
|
-
// ── Model availability pre-flight ────────────────────────
|
|
1436
|
-
// Validate that all configured agent models are resolvable in
|
|
1437
|
-
// the model registry before starting. Catches misconfigured
|
|
1438
|
-
// model names early instead of failing hours into a batch.
|
|
1439
|
-
// Note: runnerConfig (TaskRunnerConfig) is a stripped type without
|
|
1440
|
-
// worker/reviewer model fields. Load the full unified config to
|
|
1441
|
-
// get the actual agent model strings (including user preferences).
|
|
1442
|
-
let agentModels: { workerModel?: string; reviewerModel?: string } | undefined;
|
|
1443
|
-
try {
|
|
1444
|
-
const fullConfig = loadProjectConfig(execCtx!.repoRoot);
|
|
1445
|
-
agentModels = {
|
|
1446
|
-
workerModel: fullConfig.taskRunner.worker.model || "",
|
|
1447
|
-
reviewerModel: fullConfig.taskRunner.reviewer.model || "",
|
|
1448
|
-
};
|
|
1449
|
-
} catch { /* fall through — validateModelAvailability handles empty strings */ }
|
|
1450
|
-
const modelResults = validateModelAvailability(orchConfig, runnerConfig, supervisorConfig, ctx, agentModels);
|
|
1451
|
-
const modelFailures = modelResults.filter(r => r.status === "not-found");
|
|
1452
|
-
ctx.ui.notify(formatModelValidation(modelResults), modelFailures.length > 0 ? "error" : "info");
|
|
1453
|
-
if (modelFailures.length > 0) {
|
|
1454
|
-
ctx.ui.notify(
|
|
1455
|
-
`❌ Cannot start batch — ${modelFailures.length} model(s) not found: ` +
|
|
1456
|
-
modelFailures.map(f => `${f.role} (${f.modelStr})`).join(", ") +
|
|
1457
|
-
`.\n\nFix the model configuration and try again.`,
|
|
1458
|
-
"error",
|
|
1459
|
-
);
|
|
1460
|
-
return;
|
|
1461
|
-
}
|
|
1462
|
-
|
|
1463
|
-
// Reset batch state for new execution
|
|
1464
|
-
orchBatchState = freshOrchBatchState();
|
|
1465
|
-
latestMonitorState = null;
|
|
1466
|
-
|
|
1467
|
-
// ── TP-040: Set launching phase synchronously ────────────
|
|
1468
|
-
// Mark as "launching" before the setTimeout detach so that
|
|
1469
|
-
// /orch-status, /orch-pause, /orch-abort issued immediately
|
|
1470
|
-
// after /orch returns can see that a batch is being started.
|
|
1471
|
-
// The engine will transition from "launching" → "planning"
|
|
1472
|
-
// on the next tick when it actually begins work.
|
|
1473
|
-
orchBatchState.phase = "launching";
|
|
1474
|
-
orchBatchState.startedAt = Date.now();
|
|
1475
|
-
updateOrchWidget();
|
|
1476
|
-
|
|
1477
|
-
// ── TP-040: Non-blocking engine launch ───────────────────
|
|
1478
|
-
// Start the engine without awaiting — the command handler returns
|
|
1479
|
-
// immediately so the pi session remains interactive (enables
|
|
1480
|
-
// supervisor agent and operator conversation during batch).
|
|
1481
|
-
// The .catch() error boundary ensures unhandled rejections from
|
|
1482
|
-
// the engine are surfaced to the operator and reflected in state.
|
|
1483
|
-
startBatchAsync(
|
|
1484
|
-
() => executeOrchBatch(
|
|
1485
|
-
args,
|
|
1486
|
-
orchConfig,
|
|
1487
|
-
runnerConfig,
|
|
1488
|
-
repoRoot,
|
|
1489
|
-
orchBatchState,
|
|
1490
|
-
(message, level) => {
|
|
1491
|
-
ctx.ui.notify(message, level);
|
|
1492
|
-
updateOrchWidget(); // Refresh widget on every phase message
|
|
1493
|
-
},
|
|
1494
|
-
(monState: MonitorState) => {
|
|
1495
|
-
const changed = !latestMonitorState ||
|
|
1496
|
-
latestMonitorState.totalDone !== monState.totalDone ||
|
|
1497
|
-
latestMonitorState.totalFailed !== monState.totalFailed ||
|
|
1498
|
-
latestMonitorState.lanes.some((l, i) =>
|
|
1499
|
-
l.currentTaskId !== monState.lanes[i]?.currentTaskId ||
|
|
1500
|
-
l.currentStep !== monState.lanes[i]?.currentStep ||
|
|
1501
|
-
l.completedChecks !== monState.lanes[i]?.completedChecks,
|
|
1502
|
-
);
|
|
1503
|
-
latestMonitorState = monState;
|
|
1504
|
-
if (changed) updateOrchWidget(); // Only refresh on actual state change
|
|
1505
|
-
},
|
|
1506
|
-
execCtx!.workspaceConfig,
|
|
1507
|
-
execCtx!.workspaceRoot,
|
|
1508
|
-
execCtx!.pointer?.agentRoot,
|
|
1509
|
-
),
|
|
1510
|
-
orchBatchState,
|
|
1511
|
-
ctx,
|
|
1512
|
-
updateOrchWidget,
|
|
1513
|
-
// TP-043: Deferred supervisor deactivation (R002-1).
|
|
1514
|
-
// Integration is ONLY triggered when batch completes successfully
|
|
1515
|
-
// (phase === "completed"). For paused/stopped/crash states, the
|
|
1516
|
-
// supervisor is deactivated immediately — no integration on partial
|
|
1517
|
-
// batches.
|
|
1518
|
-
// TP-043 Step 2: Batch summary is generated on all terminal paths
|
|
1519
|
-
// before supervisor deactivation.
|
|
1520
|
-
() => {
|
|
1521
|
-
const mode = orchConfig.orchestrator.integration;
|
|
1522
|
-
// TP-043: Build summary deps for all terminal paths
|
|
1523
|
-
const opId = resolveOperatorId(orchConfig);
|
|
1524
|
-
const sDeps: SummaryDeps = {
|
|
1525
|
-
opId,
|
|
1526
|
-
diagnostics: orchBatchState.diagnostics ?? null,
|
|
1527
|
-
mergeResults: (orchBatchState.mergeResults || []).map(mr => ({
|
|
1528
|
-
waveIndex: mr.waveIndex,
|
|
1529
|
-
status: mr.status,
|
|
1530
|
-
failedLane: mr.failedLane,
|
|
1531
|
-
failureReason: mr.failureReason,
|
|
1532
|
-
})),
|
|
1533
|
-
};
|
|
1534
|
-
if (
|
|
1535
|
-
orchBatchState.phase === "completed" &&
|
|
1536
|
-
(mode === "supervised" || mode === "auto")
|
|
1537
|
-
) {
|
|
1538
|
-
// Supervisor stays alive — trigger programmatic integration
|
|
1539
|
-
// flow. Supervisor deactivates itself after integration
|
|
1540
|
-
// completes (or fails) via the callback in
|
|
1541
|
-
// triggerSupervisorIntegration. Summary generated there.
|
|
1542
|
-
triggerSupervisorIntegration(
|
|
1543
|
-
pi,
|
|
1544
|
-
supervisorState,
|
|
1545
|
-
orchBatchState,
|
|
1546
|
-
mode,
|
|
1547
|
-
repoRoot,
|
|
1548
|
-
buildIntegrationExecutor(repoRoot, opId),
|
|
1549
|
-
buildCiDeps(repoRoot),
|
|
1550
|
-
sDeps,
|
|
1551
|
-
);
|
|
1552
|
-
return;
|
|
1553
|
-
}
|
|
1554
|
-
// Non-completed phase or manual mode — deactivate immediately.
|
|
1555
|
-
// Inform operator if integration was expected but skipped.
|
|
1556
|
-
if (
|
|
1557
|
-
(mode === "supervised" || mode === "auto") &&
|
|
1558
|
-
orchBatchState.phase !== "completed"
|
|
1559
|
-
) {
|
|
1560
|
-
pi.sendMessage(
|
|
1561
|
-
{
|
|
1562
|
-
customType: "supervisor-integration-skipped",
|
|
1563
|
-
content: [{
|
|
1564
|
-
type: "text",
|
|
1565
|
-
text:
|
|
1566
|
-
`📋 **Batch ended** (phase: ${orchBatchState.phase}). ` +
|
|
1567
|
-
`Integration skipped — only completed batches are eligible.\n` +
|
|
1568
|
-
`Use \`/orch-resume\` to continue or \`/orch-integrate\` manually after resolving issues.`,
|
|
1569
|
-
}],
|
|
1570
|
-
display: `Integration skipped — batch ${orchBatchState.phase}`,
|
|
1571
|
-
},
|
|
1572
|
-
{ triggerTurn: false },
|
|
1573
|
-
);
|
|
1574
|
-
}
|
|
1575
|
-
// TP-043: Generate summary before transition
|
|
1576
|
-
presentBatchSummary(pi, orchBatchState, execCtx!.workspaceRoot, opId, orchBatchState.diagnostics, sDeps.mergeResults);
|
|
1577
|
-
// TP-128: Transition to routing mode instead of deactivating.
|
|
1578
|
-
// The operator can continue the conversation (integrate, plan
|
|
1579
|
-
// next batch, create tasks) without re-invoking /orch.
|
|
1580
|
-
const postBatchContext: SupervisorRoutingContext = orchBatchState.phase === "completed"
|
|
1581
|
-
? {
|
|
1582
|
-
routingState: "completed-batch",
|
|
1583
|
-
contextMessage:
|
|
1584
|
-
`Batch **${orchBatchState.batchId}** completed — ` +
|
|
1585
|
-
`${orchBatchState.succeededTasks}/${orchBatchState.totalTasks} tasks succeeded.\n\n` +
|
|
1586
|
-
`The orch branch \`${orchBatchState.orchBranch}\` is ready to integrate.\n` +
|
|
1587
|
-
`Would you like me to integrate it, or would you prefer to review first?\n\n` +
|
|
1588
|
-
`You can also:\n` +
|
|
1589
|
-
`• Run \`/orch-integrate\` (or \`/orch-integrate --pr\`) to integrate\n` +
|
|
1590
|
-
`• Create new tasks for the next batch\n` +
|
|
1591
|
-
`• Run a health check`,
|
|
1592
|
-
}
|
|
1593
|
-
: {
|
|
1594
|
-
routingState: "no-tasks",
|
|
1595
|
-
contextMessage:
|
|
1596
|
-
`Batch **${orchBatchState.batchId}** ended (${orchBatchState.phase}).\n\n` +
|
|
1597
|
-
`${orchBatchState.succeededTasks} succeeded, ${orchBatchState.failedTasks} failed, ` +
|
|
1598
|
-
`${orchBatchState.skippedTasks} skipped.\n\n` +
|
|
1599
|
-
`What would you like to do next?`,
|
|
1600
|
-
};
|
|
1601
|
-
transitionToRoutingMode(pi, supervisorState, postBatchContext);
|
|
1602
|
-
},
|
|
1603
|
-
);
|
|
1604
|
-
|
|
1605
|
-
// ── TP-041: Activate supervisor agent ────────────────────
|
|
1606
|
-
// After the engine is launched (non-blocking), activate the
|
|
1607
|
-
// supervisor in this pi session. The system prompt is rebuilt
|
|
1608
|
-
// dynamically on each LLM turn from the live batchState ref,
|
|
1609
|
-
// ensuring batch metadata (batchId, wave/task counts) is always
|
|
1610
|
-
// current even though the engine populates it asynchronously.
|
|
1611
|
-
// Model override is resolved inside activateSupervisor via ctx.
|
|
1612
|
-
// Uses workspaceRoot (not repoRoot) so lockfile/events/batch-state
|
|
1613
|
-
// all resolve to the same .pi tree the engine writes to (R006-1).
|
|
1614
|
-
activateSupervisor(
|
|
1615
|
-
pi,
|
|
1616
|
-
supervisorState,
|
|
1617
|
-
orchBatchState,
|
|
1618
|
-
orchConfig,
|
|
1619
|
-
supervisorConfig,
|
|
1620
|
-
execCtx!.workspaceRoot,
|
|
1621
|
-
ctx,
|
|
1622
|
-
);
|
|
1623
1352
|
},
|
|
1624
1353
|
});
|
|
1625
1354
|
|
|
@@ -1734,6 +1463,261 @@ export default function (pi: ExtensionAPI) {
|
|
|
1734
1463
|
// Each helper extracts the core logic from its command handler so both
|
|
1735
1464
|
// the slash command and the registered tool can call the same function.
|
|
1736
1465
|
|
|
1466
|
+
/**
|
|
1467
|
+
* Core logic for starting a batch. Used by both `/orch <target>` command
|
|
1468
|
+
* and the `orch_start` tool.
|
|
1469
|
+
*
|
|
1470
|
+
* Performs all pre-start guards (execution context, concurrent batch,
|
|
1471
|
+
* routing-mode transition, orphan detection, model validation), then
|
|
1472
|
+
* launches the engine asynchronously and activates the supervisor.
|
|
1473
|
+
*
|
|
1474
|
+
* Returns an immediate ACK with batch ID, task count, and wave info,
|
|
1475
|
+
* or an error message if the batch cannot be started.
|
|
1476
|
+
*
|
|
1477
|
+
* @since TP-061
|
|
1478
|
+
*/
|
|
1479
|
+
async function doOrchStart(target: string, ctx: ExtensionContext): Promise<{ message: string; error?: boolean }> {
|
|
1480
|
+
// Target validation
|
|
1481
|
+
const trimmedTarget = target?.trim();
|
|
1482
|
+
if (!trimmedTarget) {
|
|
1483
|
+
return {
|
|
1484
|
+
message: "❌ Target is required. Use \"all\" to run all pending tasks, or specify a task area name or path.",
|
|
1485
|
+
error: true,
|
|
1486
|
+
};
|
|
1487
|
+
}
|
|
1488
|
+
|
|
1489
|
+
if (!execCtx) {
|
|
1490
|
+
return {
|
|
1491
|
+
message: "❌ Orchestrator not initialized. Workspace configuration failed at startup.\nFix the workspace config or remove it to use repo mode, then restart.",
|
|
1492
|
+
error: true,
|
|
1493
|
+
};
|
|
1494
|
+
}
|
|
1495
|
+
|
|
1496
|
+
// TP-128: Transition from routing-mode supervisor to batch execution
|
|
1497
|
+
if (supervisorState.active && supervisorState.routingContext) {
|
|
1498
|
+
await deactivateSupervisor(pi, supervisorState);
|
|
1499
|
+
}
|
|
1500
|
+
|
|
1501
|
+
// Prevent concurrent batch execution
|
|
1502
|
+
if (orchBatchState.phase !== "idle" && orchBatchState.phase !== "completed" && orchBatchState.phase !== "failed" && orchBatchState.phase !== "stopped") {
|
|
1503
|
+
return {
|
|
1504
|
+
message: `⚠️ A batch is already ${orchBatchState.phase} (${orchBatchState.batchId}). Use /orch-pause to pause or wait for completion.`,
|
|
1505
|
+
error: true,
|
|
1506
|
+
};
|
|
1507
|
+
}
|
|
1508
|
+
|
|
1509
|
+
const { repoRoot } = execCtx;
|
|
1510
|
+
|
|
1511
|
+
// Orphan detection
|
|
1512
|
+
const orphanResult = detectOrphanSessions(
|
|
1513
|
+
orchConfig.orchestrator.tmux_prefix,
|
|
1514
|
+
repoRoot,
|
|
1515
|
+
);
|
|
1516
|
+
|
|
1517
|
+
switch (orphanResult.recommendedAction) {
|
|
1518
|
+
case "resume": {
|
|
1519
|
+
const resumablePhases = ["paused", "executing", "merging"];
|
|
1520
|
+
const phase = orphanResult.loadedState?.phase ?? "";
|
|
1521
|
+
const hasOrphans = orphanResult.orphanSessions.length > 0;
|
|
1522
|
+
if (!hasOrphans && !resumablePhases.includes(phase)) {
|
|
1523
|
+
try { deleteBatchState(repoRoot); } catch { /* best effort */ }
|
|
1524
|
+
ctx.ui.notify(
|
|
1525
|
+
`🧹 Cleared non-resumable stale batch (${orphanResult.loadedState?.batchId}, phase=${phase}). Starting fresh.`,
|
|
1526
|
+
"info",
|
|
1527
|
+
);
|
|
1528
|
+
break;
|
|
1529
|
+
}
|
|
1530
|
+
return { message: orphanResult.userMessage, error: true };
|
|
1531
|
+
}
|
|
1532
|
+
case "abort-orphans":
|
|
1533
|
+
return { message: orphanResult.userMessage, error: true };
|
|
1534
|
+
case "cleanup-stale":
|
|
1535
|
+
try { deleteBatchState(repoRoot); } catch { /* best effort */ }
|
|
1536
|
+
if (orphanResult.userMessage) {
|
|
1537
|
+
ctx.ui.notify(orphanResult.userMessage, "info");
|
|
1538
|
+
}
|
|
1539
|
+
break;
|
|
1540
|
+
case "paused-corrupt":
|
|
1541
|
+
orchBatchState.phase = "paused";
|
|
1542
|
+
orchBatchState.errors.push(orphanResult.userMessage);
|
|
1543
|
+
updateOrchWidget();
|
|
1544
|
+
return { message: orphanResult.userMessage, error: true };
|
|
1545
|
+
case "start-fresh":
|
|
1546
|
+
break;
|
|
1547
|
+
}
|
|
1548
|
+
|
|
1549
|
+
// Model availability pre-flight
|
|
1550
|
+
let agentModels: { workerModel?: string; reviewerModel?: string } | undefined;
|
|
1551
|
+
try {
|
|
1552
|
+
const fullConfig = loadProjectConfig(execCtx.repoRoot);
|
|
1553
|
+
agentModels = {
|
|
1554
|
+
workerModel: fullConfig.taskRunner.worker.model || "",
|
|
1555
|
+
reviewerModel: fullConfig.taskRunner.reviewer.model || "",
|
|
1556
|
+
};
|
|
1557
|
+
} catch { /* fall through */ }
|
|
1558
|
+
const modelResults = validateModelAvailability(orchConfig, runnerConfig, supervisorConfig, ctx, agentModels);
|
|
1559
|
+
const modelFailures = modelResults.filter(r => r.status === "not-found");
|
|
1560
|
+
ctx.ui.notify(formatModelValidation(modelResults), modelFailures.length > 0 ? "error" : "info");
|
|
1561
|
+
if (modelFailures.length > 0) {
|
|
1562
|
+
return {
|
|
1563
|
+
message: `❌ Cannot start batch — ${modelFailures.length} model(s) not found: ` +
|
|
1564
|
+
modelFailures.map(f => `${f.role} (${f.modelStr})`).join(", ") +
|
|
1565
|
+
`.\n\nFix the model configuration and try again.`,
|
|
1566
|
+
error: true,
|
|
1567
|
+
};
|
|
1568
|
+
}
|
|
1569
|
+
|
|
1570
|
+
// Pre-discovery: count pending tasks for the ACK response.
|
|
1571
|
+
// This is a lightweight synchronous check before launching the async engine.
|
|
1572
|
+
let pendingTaskCount = 0;
|
|
1573
|
+
try {
|
|
1574
|
+
const preDiscovery = runDiscovery(trimmedTarget, runnerConfig.task_areas, execCtx.workspaceRoot, {
|
|
1575
|
+
dependencySource: orchConfig.dependencies.source,
|
|
1576
|
+
useDependencyCache: orchConfig.dependencies.cache,
|
|
1577
|
+
workspaceConfig: execCtx.workspaceConfig,
|
|
1578
|
+
});
|
|
1579
|
+
pendingTaskCount = preDiscovery.pending.size;
|
|
1580
|
+
if (pendingTaskCount === 0) {
|
|
1581
|
+
return {
|
|
1582
|
+
message: `No pending tasks found for target "${trimmedTarget}". Nothing to execute.`,
|
|
1583
|
+
error: true,
|
|
1584
|
+
};
|
|
1585
|
+
}
|
|
1586
|
+
} catch {
|
|
1587
|
+
// Non-fatal — engine will re-run discovery and handle errors
|
|
1588
|
+
}
|
|
1589
|
+
|
|
1590
|
+
// Reset batch state for new execution
|
|
1591
|
+
orchBatchState = freshOrchBatchState();
|
|
1592
|
+
latestMonitorState = null;
|
|
1593
|
+
|
|
1594
|
+
orchBatchState.phase = "launching";
|
|
1595
|
+
orchBatchState.startedAt = Date.now();
|
|
1596
|
+
updateOrchWidget();
|
|
1597
|
+
|
|
1598
|
+
// Non-blocking engine launch
|
|
1599
|
+
startBatchAsync(
|
|
1600
|
+
() => executeOrchBatch(
|
|
1601
|
+
trimmedTarget,
|
|
1602
|
+
orchConfig,
|
|
1603
|
+
runnerConfig,
|
|
1604
|
+
repoRoot,
|
|
1605
|
+
orchBatchState,
|
|
1606
|
+
(message, level) => {
|
|
1607
|
+
ctx.ui.notify(message, level);
|
|
1608
|
+
updateOrchWidget();
|
|
1609
|
+
},
|
|
1610
|
+
(monState: MonitorState) => {
|
|
1611
|
+
const changed = !latestMonitorState ||
|
|
1612
|
+
latestMonitorState.totalDone !== monState.totalDone ||
|
|
1613
|
+
latestMonitorState.totalFailed !== monState.totalFailed ||
|
|
1614
|
+
latestMonitorState.lanes.some((l, i) =>
|
|
1615
|
+
l.currentTaskId !== monState.lanes[i]?.currentTaskId ||
|
|
1616
|
+
l.currentStep !== monState.lanes[i]?.currentStep ||
|
|
1617
|
+
l.completedChecks !== monState.lanes[i]?.completedChecks,
|
|
1618
|
+
);
|
|
1619
|
+
latestMonitorState = monState;
|
|
1620
|
+
if (changed) updateOrchWidget();
|
|
1621
|
+
},
|
|
1622
|
+
execCtx!.workspaceConfig,
|
|
1623
|
+
execCtx!.workspaceRoot,
|
|
1624
|
+
execCtx!.pointer?.agentRoot,
|
|
1625
|
+
),
|
|
1626
|
+
orchBatchState,
|
|
1627
|
+
ctx,
|
|
1628
|
+
updateOrchWidget,
|
|
1629
|
+
() => {
|
|
1630
|
+
const mode = orchConfig.orchestrator.integration;
|
|
1631
|
+
const opId = resolveOperatorId(orchConfig);
|
|
1632
|
+
const sDeps: SummaryDeps = {
|
|
1633
|
+
opId,
|
|
1634
|
+
diagnostics: orchBatchState.diagnostics ?? null,
|
|
1635
|
+
mergeResults: (orchBatchState.mergeResults || []).map(mr => ({
|
|
1636
|
+
waveIndex: mr.waveIndex,
|
|
1637
|
+
status: mr.status,
|
|
1638
|
+
failedLane: mr.failedLane,
|
|
1639
|
+
failureReason: mr.failureReason,
|
|
1640
|
+
})),
|
|
1641
|
+
};
|
|
1642
|
+
if (
|
|
1643
|
+
orchBatchState.phase === "completed" &&
|
|
1644
|
+
(mode === "supervised" || mode === "auto")
|
|
1645
|
+
) {
|
|
1646
|
+
triggerSupervisorIntegration(
|
|
1647
|
+
pi,
|
|
1648
|
+
supervisorState,
|
|
1649
|
+
orchBatchState,
|
|
1650
|
+
mode,
|
|
1651
|
+
repoRoot,
|
|
1652
|
+
buildIntegrationExecutor(repoRoot, opId),
|
|
1653
|
+
buildCiDeps(repoRoot),
|
|
1654
|
+
sDeps,
|
|
1655
|
+
);
|
|
1656
|
+
return;
|
|
1657
|
+
}
|
|
1658
|
+
if (
|
|
1659
|
+
(mode === "supervised" || mode === "auto") &&
|
|
1660
|
+
orchBatchState.phase !== "completed"
|
|
1661
|
+
) {
|
|
1662
|
+
pi.sendMessage(
|
|
1663
|
+
{
|
|
1664
|
+
customType: "supervisor-integration-skipped",
|
|
1665
|
+
content: [{
|
|
1666
|
+
type: "text",
|
|
1667
|
+
text:
|
|
1668
|
+
`📋 **Batch ended** (phase: ${orchBatchState.phase}). ` +
|
|
1669
|
+
`Integration skipped — only completed batches are eligible.\n` +
|
|
1670
|
+
`Use \`/orch-resume\` to continue or \`/orch-integrate\` manually after resolving issues.`,
|
|
1671
|
+
}],
|
|
1672
|
+
display: `Integration skipped — batch ${orchBatchState.phase}`,
|
|
1673
|
+
},
|
|
1674
|
+
{ triggerTurn: false },
|
|
1675
|
+
);
|
|
1676
|
+
}
|
|
1677
|
+
presentBatchSummary(pi, orchBatchState, execCtx!.workspaceRoot, opId, orchBatchState.diagnostics, sDeps.mergeResults);
|
|
1678
|
+
const postBatchContext: SupervisorRoutingContext = orchBatchState.phase === "completed"
|
|
1679
|
+
? {
|
|
1680
|
+
routingState: "completed-batch",
|
|
1681
|
+
contextMessage:
|
|
1682
|
+
`Batch **${orchBatchState.batchId}** completed — ` +
|
|
1683
|
+
`${orchBatchState.succeededTasks}/${orchBatchState.totalTasks} tasks succeeded.\n\n` +
|
|
1684
|
+
`The orch branch \`${orchBatchState.orchBranch}\` is ready to integrate.\n` +
|
|
1685
|
+
`Would you like me to integrate it, or would you prefer to review first?\n\n` +
|
|
1686
|
+
`You can also:\n` +
|
|
1687
|
+
`• Run \`/orch-integrate\` (or \`/orch-integrate --pr\`) to integrate\n` +
|
|
1688
|
+
`• Create new tasks for the next batch\n` +
|
|
1689
|
+
`• Run a health check`,
|
|
1690
|
+
}
|
|
1691
|
+
: {
|
|
1692
|
+
routingState: "no-tasks",
|
|
1693
|
+
contextMessage:
|
|
1694
|
+
`Batch **${orchBatchState.batchId}** ended (${orchBatchState.phase}).\n\n` +
|
|
1695
|
+
`${orchBatchState.succeededTasks} succeeded, ${orchBatchState.failedTasks} failed, ` +
|
|
1696
|
+
`${orchBatchState.skippedTasks} skipped.\n\n` +
|
|
1697
|
+
`What would you like to do next?`,
|
|
1698
|
+
};
|
|
1699
|
+
transitionToRoutingMode(pi, supervisorState, postBatchContext);
|
|
1700
|
+
},
|
|
1701
|
+
);
|
|
1702
|
+
|
|
1703
|
+
// Activate supervisor agent
|
|
1704
|
+
activateSupervisor(
|
|
1705
|
+
pi,
|
|
1706
|
+
supervisorState,
|
|
1707
|
+
orchBatchState,
|
|
1708
|
+
orchConfig,
|
|
1709
|
+
supervisorConfig,
|
|
1710
|
+
execCtx!.workspaceRoot,
|
|
1711
|
+
ctx,
|
|
1712
|
+
);
|
|
1713
|
+
|
|
1714
|
+
return {
|
|
1715
|
+
message: `🚀 Batch launching (target: "${trimmedTarget}", ${pendingTaskCount} pending task${pendingTaskCount === 1 ? "" : "s"}). ` +
|
|
1716
|
+
`Batch ID will be assigned during planning. ` +
|
|
1717
|
+
`The engine is running asynchronously — use orch_status() to check progress.`,
|
|
1718
|
+
};
|
|
1719
|
+
}
|
|
1720
|
+
|
|
1737
1721
|
/**
|
|
1738
1722
|
* Core logic for orch-status. Returns a formatted status string.
|
|
1739
1723
|
* Reads in-memory state first, falls back to disk if idle.
|
|
@@ -2772,6 +2756,39 @@ export default function (pi: ExtensionAPI) {
|
|
|
2772
2756
|
},
|
|
2773
2757
|
});
|
|
2774
2758
|
|
|
2759
|
+
pi.registerTool({
|
|
2760
|
+
name: "orch_start",
|
|
2761
|
+
label: "Start Batch",
|
|
2762
|
+
description:
|
|
2763
|
+
"Start a new orchestration batch. Target is \"all\" to run all pending tasks, " +
|
|
2764
|
+
"or a specific task area name or path. The batch runs asynchronously — " +
|
|
2765
|
+
"use orch_status() to monitor progress.",
|
|
2766
|
+
promptSnippet: "orch_start(target) — start a new batch",
|
|
2767
|
+
promptGuidelines: [
|
|
2768
|
+
"Call orch_start to begin executing pending tasks as a batch.",
|
|
2769
|
+
'Use target="all" to run all pending tasks, or specify a task area name or path.',
|
|
2770
|
+
"Cannot start if a batch is already running — check orch_status() first.",
|
|
2771
|
+
"The batch runs asynchronously. The tool returns immediately with an ACK.",
|
|
2772
|
+
"After starting, use orch_status() to track progress.",
|
|
2773
|
+
],
|
|
2774
|
+
parameters: Type.Object({
|
|
2775
|
+
target: Type.String({
|
|
2776
|
+
description: 'Target to run: "all" for all pending tasks, or a task area name/path',
|
|
2777
|
+
}),
|
|
2778
|
+
}),
|
|
2779
|
+
async execute(_toolCallId, params, _signal, _onUpdate, ctx) {
|
|
2780
|
+
try {
|
|
2781
|
+
const result = await doOrchStart(params.target, ctx);
|
|
2782
|
+
return { content: [{ type: "text" as const, text: result.message }], details: undefined };
|
|
2783
|
+
} catch (err) {
|
|
2784
|
+
return {
|
|
2785
|
+
content: [{ type: "text" as const, text: `Error starting batch: ${err instanceof Error ? err.message : String(err)}` }],
|
|
2786
|
+
details: undefined,
|
|
2787
|
+
};
|
|
2788
|
+
}
|
|
2789
|
+
},
|
|
2790
|
+
});
|
|
2791
|
+
|
|
2775
2792
|
// ── Settings TUI ─────────────────────────────────────────────────
|
|
2776
2793
|
|
|
2777
2794
|
pi.registerCommand("taskplane-settings", {
|
|
@@ -2120,6 +2120,7 @@ ${guardrailsSection}
|
|
|
2120
2120
|
|
|
2121
2121
|
You can invoke these tools directly — no need to ask the operator or use slash commands:
|
|
2122
2122
|
|
|
2123
|
+
- **orch_start(target)** — Start a new batch. Target is \`"all"\` for all pending tasks, or a task area name/path.
|
|
2123
2124
|
- **orch_status()** — Check current batch status (phase, wave progress, task counts, elapsed time)
|
|
2124
2125
|
- **orch_pause()** — Pause the running batch (current tasks finish, no new tasks start)
|
|
2125
2126
|
- **orch_resume(force?)** — Resume a paused or interrupted batch. Use \`force=true\` for stuck batches.
|
|
@@ -2130,6 +2131,7 @@ You can invoke these tools directly — no need to ask the operator or use slash
|
|
|
2130
2131
|
### When to Use These Tools
|
|
2131
2132
|
|
|
2132
2133
|
Use tools **proactively** when the situation calls for it:
|
|
2134
|
+
- Operator asks to run tasks or start a batch → call \`orch_start(target="all")\` (or a specific area)
|
|
2133
2135
|
- Operator asks "how's it going?" → call \`orch_status()\` first, then summarize
|
|
2134
2136
|
- Batch paused due to a failure you diagnosed and fixed → call \`orch_resume()\`
|
|
2135
2137
|
- Batch completed successfully → offer to call \`orch_integrate(mode="pr")\` or the operator's preferred mode
|
|
@@ -2398,13 +2400,14 @@ Use these to:
|
|
|
2398
2400
|
### Orchestrator Tools
|
|
2399
2401
|
|
|
2400
2402
|
You also have orchestrator tools available for batch management:
|
|
2403
|
+
- **orch_start(target)** — Start a new batch (target: "all" or a task area name/path)
|
|
2401
2404
|
- **orch_status()** — Check batch status
|
|
2402
2405
|
- **orch_resume(force?)** — Resume a paused batch
|
|
2403
2406
|
- **orch_integrate(mode?, force?, branch?)** — Integrate completed batch (modes: "fast-forward", "merge", "pr")
|
|
2404
2407
|
- **orch_pause()** — Pause running batch
|
|
2405
2408
|
- **orch_abort(hard?)** — Abort running batch
|
|
2406
2409
|
|
|
2407
|
-
Use these when the conversation leads to batch operations (e.g., integrating a completed batch).
|
|
2410
|
+
Use these when the conversation leads to batch operations (e.g., starting a batch, integrating a completed batch).
|
|
2408
2411
|
|
|
2409
2412
|
## Operational Knowledge
|
|
2410
2413
|
|
package/package.json
CHANGED
|
@@ -311,7 +311,7 @@ Verify every task against this before reporting the launch command:
|
|
|
311
311
|
- [ ] `## Context to Read First` lists only needed Tier 3 docs
|
|
312
312
|
- [ ] `## File Scope` lists files/dirs the task will touch
|
|
313
313
|
- [ ] Each step has checkboxes with verifiable outcomes
|
|
314
|
-
- [ ] Explicit testing step with
|
|
314
|
+
- [ ] Explicit testing step with full-suite command (per-step targeted tests are in the worker prompt)
|
|
315
315
|
- [ ] `## Do NOT` guardrails
|
|
316
316
|
- [ ] "Must Update" and "Check If Affected" doc lists
|
|
317
317
|
- [ ] `## Git Commit Convention` section (from template)
|
|
@@ -360,6 +360,9 @@ files and make sure their file scopes reflect that.
|
|
|
360
360
|
lists, docs drift from reality and future tasks work from stale context.
|
|
361
361
|
- **Testing step required.** Workers can't distinguish pre-existing failures from
|
|
362
362
|
regressions they caused — every task needs a clean test pass to stay unblocked.
|
|
363
|
+
The Testing & Verification step runs the **full** test suite as a quality gate.
|
|
364
|
+
Implementation steps should use **targeted tests** (e.g., `--changed` or
|
|
365
|
+
specific test files) for fast feedback — the worker prompt handles this.
|
|
363
366
|
- **Self-contained PROMPT.md.** The worker starts with a fresh context and no
|
|
364
367
|
memory of the conversation that created the task. Everything it needs to begin
|
|
365
368
|
must be in PROMPT.md and the referenced docs.
|
|
@@ -80,15 +80,17 @@ Copy this template when creating a new task. Replace all `[bracketed]` fields.
|
|
|
80
80
|
- [ ] [Specific, verifiable task]
|
|
81
81
|
- [ ] [Specific, verifiable task]
|
|
82
82
|
- [ ] [Specific, verifiable task]
|
|
83
|
+
- [ ] Run targeted tests: `[test command] --changed` or specific test files
|
|
83
84
|
|
|
84
85
|
**Artifacts:**
|
|
85
86
|
- `path/to/file` (new | modified)
|
|
86
87
|
|
|
87
88
|
### Step [N-1]: Testing & Verification
|
|
88
89
|
|
|
89
|
-
> ZERO test failures allowed.
|
|
90
|
+
> ZERO test failures allowed. This step runs the FULL test suite as a quality gate.
|
|
91
|
+
> (Earlier steps should use targeted tests for fast feedback — see worker prompt.)
|
|
90
92
|
|
|
91
|
-
- [ ] Run
|
|
93
|
+
- [ ] Run FULL test suite: `[test command from task-runner.yaml]`
|
|
92
94
|
- [ ] Run integration tests (if applicable)
|
|
93
95
|
- [ ] Fix all failures
|
|
94
96
|
- [ ] Build passes: `[build command]`
|
|
@@ -193,7 +195,7 @@ this from PROMPT.md.
|
|
|
193
195
|
### Step [N-1]: Testing & Verification
|
|
194
196
|
**Status:** ⬜ Not Started
|
|
195
197
|
|
|
196
|
-
- [ ]
|
|
198
|
+
- [ ] FULL test suite passing
|
|
197
199
|
- [ ] Integration tests (if applicable)
|
|
198
200
|
- [ ] All failures fixed
|
|
199
201
|
- [ ] Build passes
|
|
@@ -18,6 +18,7 @@ name: task-worker
|
|
|
18
18
|
- Git commit conventions (per-step commits) and .DONE file creation
|
|
19
19
|
- Review protocol (inline reviews via review_step tool when available)
|
|
20
20
|
- Review response handling
|
|
21
|
+
- Test execution strategy (targeted tests during steps, full suite at gate)
|
|
21
22
|
|
|
22
23
|
Add project-specific rules below. Common examples:
|
|
23
24
|
- Preferred package manager (pnpm, yarn, bun)
|
|
@@ -38,6 +38,7 @@ Use these to:
|
|
|
38
38
|
### Orchestrator Tools
|
|
39
39
|
|
|
40
40
|
You also have orchestrator tools available for batch management:
|
|
41
|
+
- **orch_start(target)** — Start a new batch (target: "all" or a task area name/path)
|
|
41
42
|
- **orch_status()** — Check batch status
|
|
42
43
|
- **orch_resume(force?)** — Resume a paused batch
|
|
43
44
|
- **orch_integrate(mode?, force?, branch?)** — Integrate completed batch (modes: "fast-forward", "merge", "pr")
|
|
@@ -138,6 +138,7 @@ Read it now before doing anything else. It is your primary reference.
|
|
|
138
138
|
|
|
139
139
|
You can invoke these tools directly — no need to ask the operator or use slash commands:
|
|
140
140
|
|
|
141
|
+
- **orch_start(target)** — Start a new batch. Target is `"all"` for all pending tasks, or a task area name/path.
|
|
141
142
|
- **orch_status()** — Check current batch status (phase, wave progress, task counts, elapsed time)
|
|
142
143
|
- **orch_pause()** — Pause the running batch (current tasks finish, no new tasks start)
|
|
143
144
|
- **orch_resume(force?)** — Resume a paused or interrupted batch. Use `force=true` for stuck batches.
|
|
@@ -148,6 +149,7 @@ You can invoke these tools directly — no need to ask the operator or use slash
|
|
|
148
149
|
### When to Use These Tools
|
|
149
150
|
|
|
150
151
|
Use tools **proactively** when the situation calls for it:
|
|
152
|
+
- Operator asks to run tasks or start a batch → call `orch_start(target="all")` (or a specific area)
|
|
151
153
|
- Operator asks "how's it going?" → call `orch_status()` first, then summarize
|
|
152
154
|
- Batch paused due to a failure you diagnosed and fixed → call `orch_resume()`
|
|
153
155
|
- Batch completed successfully → offer to call `orch_integrate(mode="pr")` or the operator's preferred mode
|
|
@@ -219,3 +219,50 @@ Do NOT:
|
|
|
219
219
|
Blockers section and move to the next checkbox
|
|
220
220
|
- If a test fails, fix it. If the fix is out of scope, document and continue.
|
|
221
221
|
- If a dependency is missing, document in STATUS.md and stop.
|
|
222
|
+
|
|
223
|
+
## Test Execution Strategy
|
|
224
|
+
|
|
225
|
+
Run tests at two different scopes depending on where you are in the task:
|
|
226
|
+
|
|
227
|
+
### During implementation steps (targeted tests)
|
|
228
|
+
|
|
229
|
+
After implementing each step, run **targeted tests** for fast feedback:
|
|
230
|
+
|
|
231
|
+
```bash
|
|
232
|
+
cd extensions && npx vitest run --changed
|
|
233
|
+
```
|
|
234
|
+
|
|
235
|
+
- Vitest's `--changed` flag uses git to find modified files since the last commit
|
|
236
|
+
and runs only tests related to those files.
|
|
237
|
+
- Workers commit at step boundaries, so between commits the changed set is
|
|
238
|
+
exactly "what this step modified" — this naturally targets the right tests.
|
|
239
|
+
- Alternatively, run specific test files that cover the code you modified:
|
|
240
|
+
`npx vitest run tests/some-specific.test.ts`
|
|
241
|
+
- **If `--changed` returns no tests:** That's fine — it means your changes don't
|
|
242
|
+
have directly related test files. The full suite in the Testing step will catch
|
|
243
|
+
any indirect regressions.
|
|
244
|
+
- **If targeted tests fail:** Fix the failure before proceeding. Don't accumulate
|
|
245
|
+
failures across steps.
|
|
246
|
+
|
|
247
|
+
### During the Testing & Verification step (full suite)
|
|
248
|
+
|
|
249
|
+
Run the **full test suite** as a quality gate:
|
|
250
|
+
|
|
251
|
+
```bash
|
|
252
|
+
cd extensions && npx vitest run
|
|
253
|
+
```
|
|
254
|
+
|
|
255
|
+
- ALL tests must pass — zero failures allowed.
|
|
256
|
+
- This is the definitive check before marking the task complete.
|
|
257
|
+
- The merge agent and CI run the full suite again after this — you have safety nets,
|
|
258
|
+
but catch issues here first.
|
|
259
|
+
|
|
260
|
+
### Key principle
|
|
261
|
+
|
|
262
|
+
Fast feedback during implementation, full verification at the gate. Three full-suite
|
|
263
|
+
checkpoints protect against regressions even when intermediate steps use targeted tests:
|
|
264
|
+
1. The Testing & Verification step (before `.DONE`)
|
|
265
|
+
2. The merge agent (before merging to the orchestrator branch)
|
|
266
|
+
3. CI (before merging to main)
|
|
267
|
+
|
|
268
|
+
|