gsd-pi 2.22.0 → 2.24.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/README.md +25 -1
- package/dist/cli.js +74 -7
- package/dist/headless.d.ts +25 -0
- package/dist/headless.js +454 -0
- package/dist/help-text.js +47 -0
- package/dist/mcp-server.d.ts +20 -3
- package/dist/mcp-server.js +21 -1
- package/dist/models-resolver.d.ts +32 -0
- package/dist/models-resolver.js +50 -0
- package/dist/resource-loader.js +64 -9
- package/dist/resources/extensions/bg-shell/output-formatter.ts +36 -16
- package/dist/resources/extensions/bg-shell/process-manager.ts +6 -4
- package/dist/resources/extensions/bg-shell/types.ts +33 -1
- package/dist/resources/extensions/browser-tools/capture.ts +18 -16
- package/dist/resources/extensions/browser-tools/index.ts +20 -0
- package/dist/resources/extensions/browser-tools/tests/browser-tools-unit.test.cjs +25 -0
- package/dist/resources/extensions/browser-tools/tools/action-cache.ts +216 -0
- package/dist/resources/extensions/browser-tools/tools/codegen.ts +274 -0
- package/dist/resources/extensions/browser-tools/tools/device.ts +183 -0
- package/dist/resources/extensions/browser-tools/tools/extract.ts +229 -0
- package/dist/resources/extensions/browser-tools/tools/injection-detect.ts +221 -0
- package/dist/resources/extensions/browser-tools/tools/network-mock.ts +244 -0
- package/dist/resources/extensions/browser-tools/tools/pdf.ts +92 -0
- package/dist/resources/extensions/browser-tools/tools/state-persistence.ts +202 -0
- package/dist/resources/extensions/browser-tools/tools/visual-diff.ts +209 -0
- package/dist/resources/extensions/browser-tools/tools/zoom.ts +104 -0
- package/dist/resources/extensions/gsd/auto-dashboard.ts +2 -0
- package/dist/resources/extensions/gsd/auto-dispatch.ts +51 -2
- package/dist/resources/extensions/gsd/auto-prompts.ts +73 -0
- package/dist/resources/extensions/gsd/auto-recovery.ts +51 -2
- package/dist/resources/extensions/gsd/auto-worktree.ts +15 -3
- package/dist/resources/extensions/gsd/auto.ts +560 -52
- package/dist/resources/extensions/gsd/captures.ts +49 -0
- package/dist/resources/extensions/gsd/commands.ts +194 -11
- package/dist/resources/extensions/gsd/complexity.ts +1 -0
- package/dist/resources/extensions/gsd/dashboard-overlay.ts +54 -2
- package/dist/resources/extensions/gsd/diff-context.ts +73 -80
- package/dist/resources/extensions/gsd/doctor.ts +76 -12
- package/dist/resources/extensions/gsd/exit-command.ts +2 -2
- package/dist/resources/extensions/gsd/forensics.ts +95 -52
- package/dist/resources/extensions/gsd/gitignore.ts +1 -0
- package/dist/resources/extensions/gsd/guided-flow.ts +85 -5
- package/dist/resources/extensions/gsd/index.ts +34 -1
- package/dist/resources/extensions/gsd/mcp-server.ts +33 -12
- package/dist/resources/extensions/gsd/parallel-eligibility.ts +233 -0
- package/dist/resources/extensions/gsd/parallel-merge.ts +156 -0
- package/dist/resources/extensions/gsd/parallel-orchestrator.ts +496 -0
- package/dist/resources/extensions/gsd/post-unit-hooks.ts +2 -1
- package/dist/resources/extensions/gsd/preferences.ts +65 -1
- package/dist/resources/extensions/gsd/prompts/discuss-headless.md +86 -0
- package/dist/resources/extensions/gsd/prompts/execute-task.md +5 -0
- package/dist/resources/extensions/gsd/prompts/guided-discuss-milestone.md +104 -1
- package/dist/resources/extensions/gsd/prompts/plan-milestone.md +1 -0
- package/dist/resources/extensions/gsd/prompts/research-slice.md +1 -1
- package/dist/resources/extensions/gsd/prompts/system.md +2 -1
- package/dist/resources/extensions/gsd/prompts/validate-milestone.md +70 -0
- package/dist/resources/extensions/gsd/provider-error-pause.ts +29 -2
- package/dist/resources/extensions/gsd/roadmap-slices.ts +41 -1
- package/dist/resources/extensions/gsd/session-forensics.ts +36 -2
- package/dist/resources/extensions/gsd/session-status-io.ts +197 -0
- package/dist/resources/extensions/gsd/state.ts +72 -30
- package/dist/resources/extensions/gsd/templates/milestone-validation.md +62 -0
- package/dist/resources/extensions/gsd/tests/agent-end-provider-error.test.ts +81 -0
- package/dist/resources/extensions/gsd/tests/auto-budget-alerts.test.ts +20 -3
- package/dist/resources/extensions/gsd/tests/auto-lock-creation.test.ts +186 -0
- package/dist/resources/extensions/gsd/tests/auto-preflight.test.ts +1 -0
- package/dist/resources/extensions/gsd/tests/auto-recovery.test.ts +264 -0
- package/dist/resources/extensions/gsd/tests/auto-skip-loop.test.ts +123 -0
- package/dist/resources/extensions/gsd/tests/auto-worktree-milestone-merge.test.ts +34 -0
- package/dist/resources/extensions/gsd/tests/complete-milestone.test.ts +8 -1
- package/dist/resources/extensions/gsd/tests/derive-state-db.test.ts +9 -15
- package/dist/resources/extensions/gsd/tests/derive-state-deps.test.ts +9 -0
- package/dist/resources/extensions/gsd/tests/derive-state-draft.test.ts +8 -0
- package/dist/resources/extensions/gsd/tests/derive-state.test.ts +14 -0
- package/dist/resources/extensions/gsd/tests/doctor.test.ts +58 -0
- package/dist/resources/extensions/gsd/tests/in-flight-tool-tracking.test.ts +17 -6
- package/dist/resources/extensions/gsd/tests/integration/headless-command.ts +534 -0
- package/dist/resources/extensions/gsd/tests/integration-mixed-milestones.test.ts +8 -0
- package/dist/resources/extensions/gsd/tests/migrate-writer-integration.test.ts +5 -5
- package/dist/resources/extensions/gsd/tests/parallel-orchestration.test.ts +656 -0
- package/dist/resources/extensions/gsd/tests/parallel-workers-multi-milestone-e2e.test.ts +354 -0
- package/dist/resources/extensions/gsd/tests/queue-reorder-e2e.test.ts +1 -0
- package/dist/resources/extensions/gsd/tests/roadmap-slices.test.ts +43 -1
- package/dist/resources/extensions/gsd/tests/triage-dispatch.test.ts +120 -0
- package/dist/resources/extensions/gsd/tests/triage-resolution.test.ts +203 -2
- package/dist/resources/extensions/gsd/tests/validate-milestone.test.ts +316 -0
- package/dist/resources/extensions/gsd/tests/visualizer-overlay.test.ts +8 -3
- package/dist/resources/extensions/gsd/tests/worker-registry.test.ts +148 -0
- package/dist/resources/extensions/gsd/triage-resolution.ts +83 -0
- package/dist/resources/extensions/gsd/types.ts +15 -1
- package/dist/resources/extensions/gsd/visualizer-overlay.ts +8 -1
- package/dist/resources/extensions/gsd/workspace-index.ts +34 -6
- package/dist/resources/extensions/subagent/index.ts +5 -0
- package/dist/resources/extensions/subagent/worker-registry.ts +99 -0
- package/dist/update-check.d.ts +9 -0
- package/dist/update-check.js +97 -0
- package/package.json +6 -1
- package/packages/pi-ai/dist/providers/anthropic.d.ts.map +1 -1
- package/packages/pi-ai/dist/providers/anthropic.js +16 -7
- package/packages/pi-ai/dist/providers/anthropic.js.map +1 -1
- package/packages/pi-ai/dist/providers/azure-openai-responses.d.ts.map +1 -1
- package/packages/pi-ai/dist/providers/azure-openai-responses.js +12 -4
- package/packages/pi-ai/dist/providers/azure-openai-responses.js.map +1 -1
- package/packages/pi-ai/dist/providers/google-vertex.d.ts.map +1 -1
- package/packages/pi-ai/dist/providers/google-vertex.js +21 -9
- package/packages/pi-ai/dist/providers/google-vertex.js.map +1 -1
- package/packages/pi-ai/dist/providers/openai-completions.d.ts.map +1 -1
- package/packages/pi-ai/dist/providers/openai-completions.js +12 -4
- package/packages/pi-ai/dist/providers/openai-completions.js.map +1 -1
- package/packages/pi-ai/dist/providers/openai-responses.d.ts.map +1 -1
- package/packages/pi-ai/dist/providers/openai-responses.js +12 -4
- package/packages/pi-ai/dist/providers/openai-responses.js.map +1 -1
- package/packages/pi-ai/src/providers/anthropic.ts +21 -8
- package/packages/pi-ai/src/providers/azure-openai-responses.ts +16 -4
- package/packages/pi-ai/src/providers/google-vertex.ts +32 -17
- package/packages/pi-ai/src/providers/openai-completions.ts +16 -4
- package/packages/pi-ai/src/providers/openai-responses.ts +16 -4
- package/packages/pi-coding-agent/dist/core/agent-session.js +1 -1
- package/packages/pi-coding-agent/dist/core/agent-session.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/settings-manager.js +1 -1
- package/packages/pi-coding-agent/dist/core/settings-manager.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/tools/bash-background.test.d.ts +10 -0
- package/packages/pi-coding-agent/dist/core/tools/bash-background.test.d.ts.map +1 -0
- package/packages/pi-coding-agent/dist/core/tools/bash-background.test.js +79 -0
- package/packages/pi-coding-agent/dist/core/tools/bash-background.test.js.map +1 -0
- package/packages/pi-coding-agent/dist/core/tools/bash.d.ts +18 -0
- package/packages/pi-coding-agent/dist/core/tools/bash.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/tools/bash.js +77 -1
- package/packages/pi-coding-agent/dist/core/tools/bash.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/tools/index.d.ts +1 -1
- package/packages/pi-coding-agent/dist/core/tools/index.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/tools/index.js +1 -1
- package/packages/pi-coding-agent/dist/core/tools/index.js.map +1 -1
- package/packages/pi-coding-agent/dist/index.d.ts +1 -1
- package/packages/pi-coding-agent/dist/index.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/index.js +1 -1
- package/packages/pi-coding-agent/dist/index.js.map +1 -1
- package/packages/pi-coding-agent/src/core/agent-session.ts +1 -1
- package/packages/pi-coding-agent/src/core/settings-manager.ts +2 -2
- package/packages/pi-coding-agent/src/core/tools/bash-background.test.ts +91 -0
- package/packages/pi-coding-agent/src/core/tools/bash.ts +83 -1
- package/packages/pi-coding-agent/src/core/tools/index.ts +1 -0
- package/packages/pi-coding-agent/src/index.ts +1 -0
- package/scripts/postinstall.js +7 -109
- package/src/resources/extensions/bg-shell/output-formatter.ts +36 -16
- package/src/resources/extensions/bg-shell/process-manager.ts +6 -4
- package/src/resources/extensions/bg-shell/types.ts +33 -1
- package/src/resources/extensions/browser-tools/capture.ts +18 -16
- package/src/resources/extensions/browser-tools/index.ts +20 -0
- package/src/resources/extensions/browser-tools/tests/browser-tools-unit.test.cjs +25 -0
- package/src/resources/extensions/browser-tools/tools/action-cache.ts +216 -0
- package/src/resources/extensions/browser-tools/tools/codegen.ts +274 -0
- package/src/resources/extensions/browser-tools/tools/device.ts +183 -0
- package/src/resources/extensions/browser-tools/tools/extract.ts +229 -0
- package/src/resources/extensions/browser-tools/tools/injection-detect.ts +221 -0
- package/src/resources/extensions/browser-tools/tools/network-mock.ts +244 -0
- package/src/resources/extensions/browser-tools/tools/pdf.ts +92 -0
- package/src/resources/extensions/browser-tools/tools/state-persistence.ts +202 -0
- package/src/resources/extensions/browser-tools/tools/visual-diff.ts +209 -0
- package/src/resources/extensions/browser-tools/tools/zoom.ts +104 -0
- package/src/resources/extensions/gsd/auto-dashboard.ts +2 -0
- package/src/resources/extensions/gsd/auto-dispatch.ts +51 -2
- package/src/resources/extensions/gsd/auto-prompts.ts +73 -0
- package/src/resources/extensions/gsd/auto-recovery.ts +51 -2
- package/src/resources/extensions/gsd/auto-worktree.ts +15 -3
- package/src/resources/extensions/gsd/auto.ts +560 -52
- package/src/resources/extensions/gsd/captures.ts +49 -0
- package/src/resources/extensions/gsd/commands.ts +194 -11
- package/src/resources/extensions/gsd/complexity.ts +1 -0
- package/src/resources/extensions/gsd/dashboard-overlay.ts +54 -2
- package/src/resources/extensions/gsd/diff-context.ts +73 -80
- package/src/resources/extensions/gsd/doctor.ts +76 -12
- package/src/resources/extensions/gsd/exit-command.ts +2 -2
- package/src/resources/extensions/gsd/forensics.ts +95 -52
- package/src/resources/extensions/gsd/gitignore.ts +1 -0
- package/src/resources/extensions/gsd/guided-flow.ts +85 -5
- package/src/resources/extensions/gsd/index.ts +34 -1
- package/src/resources/extensions/gsd/mcp-server.ts +33 -12
- package/src/resources/extensions/gsd/parallel-eligibility.ts +233 -0
- package/src/resources/extensions/gsd/parallel-merge.ts +156 -0
- package/src/resources/extensions/gsd/parallel-orchestrator.ts +496 -0
- package/src/resources/extensions/gsd/post-unit-hooks.ts +2 -1
- package/src/resources/extensions/gsd/preferences.ts +65 -1
- package/src/resources/extensions/gsd/prompts/discuss-headless.md +86 -0
- package/src/resources/extensions/gsd/prompts/execute-task.md +5 -0
- package/src/resources/extensions/gsd/prompts/guided-discuss-milestone.md +104 -1
- package/src/resources/extensions/gsd/prompts/plan-milestone.md +1 -0
- package/src/resources/extensions/gsd/prompts/research-slice.md +1 -1
- package/src/resources/extensions/gsd/prompts/system.md +2 -1
- package/src/resources/extensions/gsd/prompts/validate-milestone.md +70 -0
- package/src/resources/extensions/gsd/provider-error-pause.ts +29 -2
- package/src/resources/extensions/gsd/roadmap-slices.ts +41 -1
- package/src/resources/extensions/gsd/session-forensics.ts +36 -2
- package/src/resources/extensions/gsd/session-status-io.ts +197 -0
- package/src/resources/extensions/gsd/state.ts +72 -30
- package/src/resources/extensions/gsd/templates/milestone-validation.md +62 -0
- package/src/resources/extensions/gsd/tests/agent-end-provider-error.test.ts +81 -0
- package/src/resources/extensions/gsd/tests/auto-budget-alerts.test.ts +20 -3
- package/src/resources/extensions/gsd/tests/auto-lock-creation.test.ts +186 -0
- package/src/resources/extensions/gsd/tests/auto-preflight.test.ts +1 -0
- package/src/resources/extensions/gsd/tests/auto-recovery.test.ts +264 -0
- package/src/resources/extensions/gsd/tests/auto-skip-loop.test.ts +123 -0
- package/src/resources/extensions/gsd/tests/auto-worktree-milestone-merge.test.ts +34 -0
- package/src/resources/extensions/gsd/tests/complete-milestone.test.ts +8 -1
- package/src/resources/extensions/gsd/tests/derive-state-db.test.ts +9 -15
- package/src/resources/extensions/gsd/tests/derive-state-deps.test.ts +9 -0
- package/src/resources/extensions/gsd/tests/derive-state-draft.test.ts +8 -0
- package/src/resources/extensions/gsd/tests/derive-state.test.ts +14 -0
- package/src/resources/extensions/gsd/tests/doctor.test.ts +58 -0
- package/src/resources/extensions/gsd/tests/in-flight-tool-tracking.test.ts +17 -6
- package/src/resources/extensions/gsd/tests/integration/headless-command.ts +534 -0
- package/src/resources/extensions/gsd/tests/integration-mixed-milestones.test.ts +8 -0
- package/src/resources/extensions/gsd/tests/migrate-writer-integration.test.ts +5 -5
- package/src/resources/extensions/gsd/tests/parallel-orchestration.test.ts +656 -0
- package/src/resources/extensions/gsd/tests/parallel-workers-multi-milestone-e2e.test.ts +354 -0
- package/src/resources/extensions/gsd/tests/queue-reorder-e2e.test.ts +1 -0
- package/src/resources/extensions/gsd/tests/roadmap-slices.test.ts +43 -1
- package/src/resources/extensions/gsd/tests/triage-dispatch.test.ts +120 -0
- package/src/resources/extensions/gsd/tests/triage-resolution.test.ts +203 -2
- package/src/resources/extensions/gsd/tests/validate-milestone.test.ts +316 -0
- package/src/resources/extensions/gsd/tests/visualizer-overlay.test.ts +8 -3
- package/src/resources/extensions/gsd/tests/worker-registry.test.ts +148 -0
- package/src/resources/extensions/gsd/triage-resolution.ts +83 -0
- package/src/resources/extensions/gsd/types.ts +15 -1
- package/src/resources/extensions/gsd/visualizer-overlay.ts +8 -1
- package/src/resources/extensions/gsd/workspace-index.ts +34 -6
- package/src/resources/extensions/subagent/index.ts +5 -0
- package/src/resources/extensions/subagent/worker-registry.ts +99 -0
|
@@ -0,0 +1,354 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* E2E test: Parallel workers across multiple milestones.
|
|
3
|
+
*
|
|
4
|
+
* Validates the full lifecycle of the worker registry + metrics + budget
|
|
5
|
+
* alerting across multiple milestone contexts. Uses real filesystem fixtures
|
|
6
|
+
* and the actual metrics/worker-registry modules (no mocking).
|
|
7
|
+
*
|
|
8
|
+
* Covers:
|
|
9
|
+
* - Worker registry tracking across parallel batches
|
|
10
|
+
* - Metrics ledger accumulation across milestones
|
|
11
|
+
* - Budget alert level transitions including the 80% threshold
|
|
12
|
+
* - Dashboard data aggregation with parallel worker context
|
|
13
|
+
* - Cost projection with budget ceiling awareness
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
import { mkdtempSync, mkdirSync, rmSync, writeFileSync, readFileSync } from 'node:fs';
|
|
17
|
+
import { join } from 'node:path';
|
|
18
|
+
import { tmpdir } from 'node:os';
|
|
19
|
+
|
|
20
|
+
import { createTestContext } from './test-helpers.ts';
|
|
21
|
+
import {
|
|
22
|
+
registerWorker,
|
|
23
|
+
updateWorker,
|
|
24
|
+
getActiveWorkers,
|
|
25
|
+
getWorkerBatches,
|
|
26
|
+
hasActiveWorkers,
|
|
27
|
+
resetWorkerRegistry,
|
|
28
|
+
} from '../../subagent/worker-registry.ts';
|
|
29
|
+
import {
|
|
30
|
+
getBudgetAlertLevel,
|
|
31
|
+
getNewBudgetAlertLevel,
|
|
32
|
+
getBudgetEnforcementAction,
|
|
33
|
+
} from '../auto.ts';
|
|
34
|
+
import {
|
|
35
|
+
type UnitMetrics,
|
|
36
|
+
type MetricsLedger,
|
|
37
|
+
getProjectTotals,
|
|
38
|
+
aggregateByPhase,
|
|
39
|
+
aggregateBySlice,
|
|
40
|
+
formatCost,
|
|
41
|
+
formatCostProjection,
|
|
42
|
+
getAverageCostPerUnitType,
|
|
43
|
+
predictRemainingCost,
|
|
44
|
+
} from '../metrics.ts';
|
|
45
|
+
|
|
46
|
+
const { assertEq, assertTrue, assertMatch, report } = createTestContext();
|
|
47
|
+
|
|
48
|
+
// ─── Fixture helpers ──────────────────────────────────────────────────────────
|
|
49
|
+
|
|
50
|
+
function createFixtureBase(): string {
|
|
51
|
+
const base = mkdtempSync(join(tmpdir(), 'gsd-e2e-parallel-'));
|
|
52
|
+
mkdirSync(join(base, '.gsd', 'milestones'), { recursive: true });
|
|
53
|
+
return base;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
function writeMetricsLedger(base: string, ledger: MetricsLedger): void {
|
|
57
|
+
writeFileSync(join(base, '.gsd', 'metrics.json'), JSON.stringify(ledger, null, 2));
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
function readMetricsLedger(base: string): MetricsLedger {
|
|
61
|
+
return JSON.parse(readFileSync(join(base, '.gsd', 'metrics.json'), 'utf-8'));
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
function makeUnit(overrides: Partial<UnitMetrics> = {}): UnitMetrics {
|
|
65
|
+
return {
|
|
66
|
+
type: "execute-task",
|
|
67
|
+
id: "M001/S01/T01",
|
|
68
|
+
model: "claude-sonnet-4-20250514",
|
|
69
|
+
startedAt: Date.now() - 5000,
|
|
70
|
+
finishedAt: Date.now(),
|
|
71
|
+
tokens: { input: 1000, output: 500, cacheRead: 200, cacheWrite: 100, total: 1800 },
|
|
72
|
+
cost: 0.05,
|
|
73
|
+
toolCalls: 3,
|
|
74
|
+
assistantMessages: 2,
|
|
75
|
+
userMessages: 1,
|
|
76
|
+
...overrides,
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
function cleanup(base: string): void {
|
|
81
|
+
rmSync(base, { recursive: true, force: true });
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
// ─── E2E: Parallel workers across M001 and M002 ──────────────────────────────
|
|
85
|
+
|
|
86
|
+
console.log("\n=== E2E: Parallel workers across milestones ===");
|
|
87
|
+
|
|
88
|
+
{
|
|
89
|
+
resetWorkerRegistry();
|
|
90
|
+
const base = createFixtureBase();
|
|
91
|
+
|
|
92
|
+
// Create milestone directories
|
|
93
|
+
mkdirSync(join(base, '.gsd', 'milestones', 'M001'), { recursive: true });
|
|
94
|
+
mkdirSync(join(base, '.gsd', 'milestones', 'M002'), { recursive: true });
|
|
95
|
+
|
|
96
|
+
// Simulate M001 parallel workers (batch 1)
|
|
97
|
+
const batch1Id = "batch-m001";
|
|
98
|
+
const w1 = registerWorker("scout", "Explore M001 codebase", 0, 3, batch1Id);
|
|
99
|
+
const w2 = registerWorker("researcher", "Research M001 APIs", 1, 3, batch1Id);
|
|
100
|
+
const w3 = registerWorker("worker", "Implement M001 feature", 2, 3, batch1Id);
|
|
101
|
+
|
|
102
|
+
assertEq(getActiveWorkers().length, 3, "M001: 3 parallel workers registered");
|
|
103
|
+
assertTrue(hasActiveWorkers(), "M001: has active workers");
|
|
104
|
+
|
|
105
|
+
const batches1 = getWorkerBatches();
|
|
106
|
+
assertEq(batches1.size, 1, "M001: single batch");
|
|
107
|
+
assertEq(batches1.get(batch1Id)!.length, 3, "M001: batch has 3 workers");
|
|
108
|
+
|
|
109
|
+
// Complete M001 workers
|
|
110
|
+
updateWorker(w1, "completed");
|
|
111
|
+
updateWorker(w2, "completed");
|
|
112
|
+
updateWorker(w3, "completed");
|
|
113
|
+
assertTrue(!hasActiveWorkers(), "M001: no active workers after completion");
|
|
114
|
+
|
|
115
|
+
// Simulate M002 parallel workers (batch 2) — overlapping with M001 cleanup
|
|
116
|
+
const batch2Id = "batch-m002";
|
|
117
|
+
const w4 = registerWorker("scout", "Explore M002 codebase", 0, 2, batch2Id);
|
|
118
|
+
const w5 = registerWorker("worker", "Implement M002 feature", 1, 2, batch2Id);
|
|
119
|
+
|
|
120
|
+
assertTrue(hasActiveWorkers(), "M002: has active workers");
|
|
121
|
+
const batches2 = getWorkerBatches();
|
|
122
|
+
// M001 workers may still be in cleanup window (5s timeout), M002 workers are active
|
|
123
|
+
assertTrue(batches2.has(batch2Id), "M002: batch exists");
|
|
124
|
+
assertEq(batches2.get(batch2Id)!.length, 2, "M002: batch has 2 workers");
|
|
125
|
+
|
|
126
|
+
// One worker fails in M002
|
|
127
|
+
updateWorker(w4, "completed");
|
|
128
|
+
updateWorker(w5, "failed");
|
|
129
|
+
assertTrue(!hasActiveWorkers(), "M002: no active workers after all finish");
|
|
130
|
+
|
|
131
|
+
// Verify worker statuses reflect correctly
|
|
132
|
+
const allWorkers = getActiveWorkers();
|
|
133
|
+
const m002Workers = allWorkers.filter(w => w.batchId === batch2Id);
|
|
134
|
+
if (m002Workers.length > 0) {
|
|
135
|
+
const failedWorker = m002Workers.find(w => w.status === "failed");
|
|
136
|
+
assertTrue(failedWorker !== undefined, "M002: failed worker tracked");
|
|
137
|
+
assertEq(failedWorker?.agent, "worker", "M002: failed worker is 'worker'");
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
cleanup(base);
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
// ─── E2E: Metrics accumulation across milestones ──────────────────────────────
|
|
144
|
+
|
|
145
|
+
console.log("\n=== E2E: Metrics across milestones ===");
|
|
146
|
+
|
|
147
|
+
{
|
|
148
|
+
const base = createFixtureBase();
|
|
149
|
+
|
|
150
|
+
// Build a ledger spanning two milestones
|
|
151
|
+
const ledger: MetricsLedger = {
|
|
152
|
+
version: 1,
|
|
153
|
+
projectStartedAt: Date.now() - 60000,
|
|
154
|
+
units: [
|
|
155
|
+
// M001 units
|
|
156
|
+
makeUnit({ type: "research-milestone", id: "M001", cost: 0.10 }),
|
|
157
|
+
makeUnit({ type: "plan-milestone", id: "M001", cost: 0.08 }),
|
|
158
|
+
makeUnit({ type: "plan-slice", id: "M001/S01", cost: 0.05 }),
|
|
159
|
+
makeUnit({ type: "execute-task", id: "M001/S01/T01", cost: 0.12 }),
|
|
160
|
+
makeUnit({ type: "execute-task", id: "M001/S01/T02", cost: 0.15 }),
|
|
161
|
+
makeUnit({ type: "complete-slice", id: "M001/S01", cost: 0.03 }),
|
|
162
|
+
makeUnit({ type: "plan-slice", id: "M001/S02", cost: 0.06 }),
|
|
163
|
+
makeUnit({ type: "execute-task", id: "M001/S02/T01", cost: 0.20 }),
|
|
164
|
+
makeUnit({ type: "complete-slice", id: "M001/S02", cost: 0.04 }),
|
|
165
|
+
// M002 units
|
|
166
|
+
makeUnit({ type: "research-milestone", id: "M002", cost: 0.12 }),
|
|
167
|
+
makeUnit({ type: "plan-milestone", id: "M002", cost: 0.09 }),
|
|
168
|
+
makeUnit({ type: "plan-slice", id: "M002/S01", cost: 0.07 }),
|
|
169
|
+
makeUnit({ type: "execute-task", id: "M002/S01/T01", cost: 0.18 }),
|
|
170
|
+
],
|
|
171
|
+
};
|
|
172
|
+
|
|
173
|
+
writeMetricsLedger(base, ledger);
|
|
174
|
+
const loaded = readMetricsLedger(base);
|
|
175
|
+
|
|
176
|
+
// Verify totals
|
|
177
|
+
const totals = getProjectTotals(loaded.units);
|
|
178
|
+
assertEq(totals.units, 13, "metrics: 13 total units across M001+M002");
|
|
179
|
+
const totalCost = loaded.units.reduce((sum, u) => sum + u.cost, 0);
|
|
180
|
+
assertTrue(Math.abs(totals.cost - totalCost) < 0.001, "metrics: total cost matches sum");
|
|
181
|
+
|
|
182
|
+
// Verify phase aggregation
|
|
183
|
+
const phases = aggregateByPhase(loaded.units);
|
|
184
|
+
const research = phases.find(p => p.phase === "research");
|
|
185
|
+
assertTrue(research !== undefined, "metrics: research phase exists");
|
|
186
|
+
assertEq(research!.units, 2, "metrics: 2 research units (M001 + M002)");
|
|
187
|
+
|
|
188
|
+
const execution = phases.find(p => p.phase === "execution");
|
|
189
|
+
assertTrue(execution !== undefined, "metrics: execution phase exists");
|
|
190
|
+
assertEq(execution!.units, 4, "metrics: 4 execution units across both milestones");
|
|
191
|
+
|
|
192
|
+
// Verify slice aggregation
|
|
193
|
+
const slices = aggregateBySlice(loaded.units);
|
|
194
|
+
assertTrue(slices.length >= 4, "metrics: at least 4 slice aggregates (M001/S01, M001/S02, M002/S01, milestone-level)");
|
|
195
|
+
|
|
196
|
+
const m001s01 = slices.find(s => s.sliceId === "M001/S01");
|
|
197
|
+
assertTrue(m001s01 !== undefined, "metrics: M001/S01 slice aggregate exists");
|
|
198
|
+
// M001/S01 has: plan-slice + T01 + T02 + complete-slice = 4 units
|
|
199
|
+
assertEq(m001s01!.units, 4, "metrics: M001/S01 has 4 units");
|
|
200
|
+
|
|
201
|
+
// Cost projection
|
|
202
|
+
const projLines = formatCostProjection(slices, 3, 2.0);
|
|
203
|
+
assertTrue(projLines.length >= 1, "metrics: cost projection generated");
|
|
204
|
+
assertMatch(projLines[0], /Projected remaining/, "metrics: projection line text");
|
|
205
|
+
|
|
206
|
+
cleanup(base);
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
// ─── E2E: Budget alert progression through all thresholds ─────────────────────
|
|
210
|
+
|
|
211
|
+
console.log("\n=== E2E: Budget alert progression 0→75→80→90→100 ===");
|
|
212
|
+
|
|
213
|
+
{
|
|
214
|
+
// Simulate spending progression against a $10 budget ceiling
|
|
215
|
+
const ceiling = 10.0;
|
|
216
|
+
|
|
217
|
+
// Start: 50% spent
|
|
218
|
+
let lastLevel = getBudgetAlertLevel(5.0 / ceiling);
|
|
219
|
+
assertEq(lastLevel, 0, "budget: 50% → level 0");
|
|
220
|
+
assertEq(getNewBudgetAlertLevel(0, 5.0 / ceiling), null, "budget: no alert at 50%");
|
|
221
|
+
|
|
222
|
+
// Spend to 75%
|
|
223
|
+
let newLevel = getNewBudgetAlertLevel(lastLevel, 7.5 / ceiling);
|
|
224
|
+
assertEq(newLevel, 75, "budget: alert fires at 75%");
|
|
225
|
+
lastLevel = newLevel!;
|
|
226
|
+
|
|
227
|
+
// Spend to 78% — no alert (between 75 and 80)
|
|
228
|
+
assertEq(getNewBudgetAlertLevel(lastLevel, 7.8 / ceiling), null, "budget: no alert at 78%");
|
|
229
|
+
|
|
230
|
+
// Spend to 80% — 80% approach alert
|
|
231
|
+
newLevel = getNewBudgetAlertLevel(lastLevel, 8.0 / ceiling);
|
|
232
|
+
assertEq(newLevel, 80, "budget: approach alert fires at 80%");
|
|
233
|
+
lastLevel = newLevel!;
|
|
234
|
+
|
|
235
|
+
// Spend to 85% — no alert (still at 80 level)
|
|
236
|
+
assertEq(getNewBudgetAlertLevel(lastLevel, 8.5 / ceiling), null, "budget: no alert at 85%");
|
|
237
|
+
|
|
238
|
+
// Spend to 90%
|
|
239
|
+
newLevel = getNewBudgetAlertLevel(lastLevel, 9.0 / ceiling);
|
|
240
|
+
assertEq(newLevel, 90, "budget: alert fires at 90%");
|
|
241
|
+
lastLevel = newLevel!;
|
|
242
|
+
|
|
243
|
+
// Spend to 100%
|
|
244
|
+
newLevel = getNewBudgetAlertLevel(lastLevel, 10.0 / ceiling);
|
|
245
|
+
assertEq(newLevel, 100, "budget: alert fires at 100%");
|
|
246
|
+
lastLevel = newLevel!;
|
|
247
|
+
|
|
248
|
+
// Over budget — no re-emission
|
|
249
|
+
assertEq(getNewBudgetAlertLevel(lastLevel, 12.0 / ceiling), null, "budget: no re-alert over 100%");
|
|
250
|
+
|
|
251
|
+
// Enforcement at 80% — still "none" (enforcement only at 100%)
|
|
252
|
+
assertEq(getBudgetEnforcementAction("pause", 0.80), "none", "budget: no enforcement at 80%");
|
|
253
|
+
assertEq(getBudgetEnforcementAction("halt", 0.80), "none", "budget: no enforcement at 80%");
|
|
254
|
+
assertEq(getBudgetEnforcementAction("warn", 0.80), "none", "budget: no enforcement at 80%");
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
// ─── E2E: Budget prediction with multi-milestone cost data ────────────────────
|
|
258
|
+
|
|
259
|
+
console.log("\n=== E2E: Budget prediction across milestones ===");
|
|
260
|
+
|
|
261
|
+
{
|
|
262
|
+
const units: UnitMetrics[] = [
|
|
263
|
+
makeUnit({ type: "execute-task", id: "M001/S01/T01", cost: 0.10 }),
|
|
264
|
+
makeUnit({ type: "execute-task", id: "M001/S01/T02", cost: 0.15 }),
|
|
265
|
+
makeUnit({ type: "plan-slice", id: "M001/S01", cost: 0.05 }),
|
|
266
|
+
makeUnit({ type: "execute-task", id: "M002/S01/T01", cost: 0.20 }),
|
|
267
|
+
makeUnit({ type: "plan-slice", id: "M002/S01", cost: 0.08 }),
|
|
268
|
+
];
|
|
269
|
+
|
|
270
|
+
const avgCosts = getAverageCostPerUnitType(units);
|
|
271
|
+
assertTrue(avgCosts.has("execute-task"), "prediction: has execute-task average");
|
|
272
|
+
assertTrue(avgCosts.has("plan-slice"), "prediction: has plan-slice average");
|
|
273
|
+
|
|
274
|
+
// Average execute-task cost: (0.10 + 0.15 + 0.20) / 3 = 0.15
|
|
275
|
+
const execAvg = avgCosts.get("execute-task")!;
|
|
276
|
+
assertTrue(Math.abs(execAvg - 0.15) < 0.001, `prediction: execute-task avg is $0.15 (got ${execAvg})`);
|
|
277
|
+
|
|
278
|
+
// Average plan-slice cost: (0.05 + 0.08) / 2 = 0.065
|
|
279
|
+
const planAvg = avgCosts.get("plan-slice")!;
|
|
280
|
+
assertTrue(Math.abs(planAvg - 0.065) < 0.001, `prediction: plan-slice avg is $0.065 (got ${planAvg})`);
|
|
281
|
+
|
|
282
|
+
// Predict remaining cost for 3 more execute-tasks and 1 plan-slice
|
|
283
|
+
const remaining = predictRemainingCost(avgCosts, [
|
|
284
|
+
"execute-task", "execute-task", "execute-task", "plan-slice",
|
|
285
|
+
]);
|
|
286
|
+
// Expected: 3 * 0.15 + 1 * 0.065 = 0.515
|
|
287
|
+
assertTrue(Math.abs(remaining - 0.515) < 0.001, `prediction: remaining cost ~$0.515 (got ${remaining})`);
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
// ─── E2E: Parallel workers + budget alerts combined scenario ──────────────────
|
|
291
|
+
|
|
292
|
+
console.log("\n=== E2E: Combined parallel workers + budget monitoring ===");
|
|
293
|
+
|
|
294
|
+
{
|
|
295
|
+
resetWorkerRegistry();
|
|
296
|
+
|
|
297
|
+
// Simulate a scenario: 3 parallel workers running while budget is at 78%
|
|
298
|
+
const batchId = "batch-combined";
|
|
299
|
+
const w1 = registerWorker("scout", "Research APIs", 0, 3, batchId);
|
|
300
|
+
const w2 = registerWorker("worker", "Implement feature", 1, 3, batchId);
|
|
301
|
+
const w3 = registerWorker("worker", "Write tests", 2, 3, batchId);
|
|
302
|
+
|
|
303
|
+
// Budget is at 78% — no alert yet (between 75 and 80)
|
|
304
|
+
const ceiling = 10.0;
|
|
305
|
+
let lastLevel: ReturnType<typeof getBudgetAlertLevel> = 75; // already got 75% alert
|
|
306
|
+
assertEq(getNewBudgetAlertLevel(lastLevel, 7.8 / ceiling), null, "combined: no alert at 78% with workers running");
|
|
307
|
+
assertTrue(hasActiveWorkers(), "combined: workers running during budget check");
|
|
308
|
+
|
|
309
|
+
// First worker completes, cost rises to 80%
|
|
310
|
+
updateWorker(w1, "completed");
|
|
311
|
+
const level80 = getNewBudgetAlertLevel(lastLevel, 8.0 / ceiling);
|
|
312
|
+
assertEq(level80, 80, "combined: 80% approach alert fires after worker completes");
|
|
313
|
+
lastLevel = level80!;
|
|
314
|
+
|
|
315
|
+
// Second worker completes, cost rises to 88%
|
|
316
|
+
updateWorker(w2, "completed");
|
|
317
|
+
assertEq(getNewBudgetAlertLevel(lastLevel, 8.8 / ceiling), null, "combined: no alert at 88%");
|
|
318
|
+
|
|
319
|
+
// Third worker completes, cost reaches 90%
|
|
320
|
+
updateWorker(w3, "completed");
|
|
321
|
+
const level90 = getNewBudgetAlertLevel(lastLevel, 9.0 / ceiling);
|
|
322
|
+
assertEq(level90, 90, "combined: 90% alert fires after all workers complete");
|
|
323
|
+
|
|
324
|
+
assertTrue(!hasActiveWorkers(), "combined: no active workers at end");
|
|
325
|
+
|
|
326
|
+
resetWorkerRegistry();
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
// ─── E2E: formatCostProjection with budget ceiling warnings ───────────────────
|
|
330
|
+
|
|
331
|
+
console.log("\n=== E2E: Cost projection ceiling warnings ===");
|
|
332
|
+
|
|
333
|
+
{
|
|
334
|
+
const slices = [
|
|
335
|
+
{ sliceId: "M001/S01", units: 4, tokens: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, total: 0 }, cost: 3.0, duration: 10000 },
|
|
336
|
+
{ sliceId: "M001/S02", units: 3, tokens: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, total: 0 }, cost: 4.0, duration: 8000 },
|
|
337
|
+
{ sliceId: "M002/S01", units: 3, tokens: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, total: 0 }, cost: 5.0, duration: 12000 },
|
|
338
|
+
];
|
|
339
|
+
|
|
340
|
+
// With ceiling NOT yet reached
|
|
341
|
+
const proj1 = formatCostProjection(slices, 2, 20.0);
|
|
342
|
+
assertTrue(proj1.length >= 1, "projection: has projection line");
|
|
343
|
+
assertMatch(proj1[0], /Projected remaining/, "projection: shows projection");
|
|
344
|
+
assertTrue(proj1.length === 1, "projection: no ceiling warning when under budget");
|
|
345
|
+
|
|
346
|
+
// With ceiling reached (spent 12.0 >= ceiling 10.0)
|
|
347
|
+
const proj2 = formatCostProjection(slices, 2, 10.0);
|
|
348
|
+
assertTrue(proj2.length >= 2, "projection: has ceiling warning when over budget");
|
|
349
|
+
assertMatch(proj2[1], /ceiling/, "projection: ceiling warning text");
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
// ─── Summary ──────────────────────────────────────────────────────────────────
|
|
353
|
+
|
|
354
|
+
report();
|
|
@@ -58,6 +58,7 @@ function writeCompleteMilestone(base: string, mid: string): void {
|
|
|
58
58
|
- [x] **S01: Done** \`risk:low\` \`depends:[]\`
|
|
59
59
|
> After this: Done.
|
|
60
60
|
`);
|
|
61
|
+
writeFileSync(join(dir, `${mid}-VALIDATION.md`), `---\nverdict: pass\nremediation_round: 0\n---\n\n# Validation\nPassed.`);
|
|
61
62
|
writeFileSync(join(dir, `${mid}-SUMMARY.md`), `# ${mid} Summary\n\nComplete.`);
|
|
62
63
|
}
|
|
63
64
|
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { parseRoadmap } from "../files.ts";
|
|
2
|
-
import { parseRoadmapSlices } from "../roadmap-slices.ts";
|
|
2
|
+
import { parseRoadmapSlices, expandDependencies } from "../roadmap-slices.ts";
|
|
3
3
|
import { createTestContext } from './test-helpers.ts';
|
|
4
4
|
|
|
5
5
|
const { assertEq, assertTrue, report } = createTestContext();
|
|
@@ -38,4 +38,46 @@ assertEq(roadmap.title, "M003: Current", "roadmap title preserved");
|
|
|
38
38
|
assertEq(roadmap.vision, "Build the thing.", "roadmap vision preserved");
|
|
39
39
|
assertTrue(roadmap.boundaryMap.length === 1, "boundary map still parsed");
|
|
40
40
|
|
|
41
|
+
// ─── expandDependencies unit tests ─────────────────────────────────────
|
|
42
|
+
|
|
43
|
+
console.log("\n=== expandDependencies: plain IDs pass through ===");
|
|
44
|
+
assertEq(expandDependencies([]), [], "empty list");
|
|
45
|
+
assertEq(expandDependencies(["S01"]), ["S01"], "single plain ID");
|
|
46
|
+
assertEq(expandDependencies(["S01", "S03"]), ["S01", "S03"], "multiple plain IDs");
|
|
47
|
+
|
|
48
|
+
console.log("\n=== expandDependencies: dash range expansion ===");
|
|
49
|
+
assertEq(expandDependencies(["S01-S04"]), ["S01", "S02", "S03", "S04"], "S01-S04 expands correctly");
|
|
50
|
+
assertEq(expandDependencies(["S01-S01"]), ["S01"], "single-element range");
|
|
51
|
+
assertEq(expandDependencies(["S03-S05"]), ["S03", "S04", "S05"], "mid-range expansion");
|
|
52
|
+
|
|
53
|
+
console.log("\n=== expandDependencies: dot-range expansion ===");
|
|
54
|
+
assertEq(expandDependencies(["S01..S03"]), ["S01", "S02", "S03"], "S01..S03 dot range");
|
|
55
|
+
|
|
56
|
+
console.log("\n=== expandDependencies: zero-padding preserved ===");
|
|
57
|
+
assertEq(expandDependencies(["S01-S03"]), ["S01", "S02", "S03"], "zero-padded IDs preserved");
|
|
58
|
+
|
|
59
|
+
console.log("\n=== expandDependencies: mixed list ===");
|
|
60
|
+
assertEq(expandDependencies(["S01-S03", "S05"]), ["S01", "S02", "S03", "S05"], "range + plain mixed");
|
|
61
|
+
|
|
62
|
+
console.log("\n=== expandDependencies: invalid range passes through unchanged ===");
|
|
63
|
+
assertEq(expandDependencies(["S04-S01"]), ["S04-S01"], "reversed range not expanded (start > end)");
|
|
64
|
+
assertEq(expandDependencies(["S01-T04"]), ["S01-T04"], "mismatched prefix not expanded");
|
|
65
|
+
|
|
66
|
+
// ─── parseRoadmapSlices: range syntax in depends ─────────────────────
|
|
67
|
+
|
|
68
|
+
console.log("\n=== parseRoadmapSlices: range syntax in depends expanded ===");
|
|
69
|
+
{
|
|
70
|
+
const rangeContent = `# M016: Test\n\n## Slices\n- [x] **S01: A** \`risk:low\` \`depends:[]\`\n- [x] **S02: B** \`risk:low\` \`depends:[]\`\n- [x] **S03: C** \`risk:low\` \`depends:[]\`\n- [x] **S04: D** \`risk:low\` \`depends:[]\`\n- [ ] **S05: E** \`risk:low\` \`depends:[S01-S04]\`\n > After this: all done\n`;
|
|
71
|
+
const rangeSlices = parseRoadmapSlices(rangeContent);
|
|
72
|
+
assertEq(rangeSlices.length, 5, "5 slices parsed");
|
|
73
|
+
assertEq(rangeSlices[4]?.depends, ["S01", "S02", "S03", "S04"], "S01-S04 range expanded to individual IDs");
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
console.log("\n=== parseRoadmapSlices: comma-separated depends still works ===");
|
|
77
|
+
{
|
|
78
|
+
const commaContent = `# M001: Test\n\n## Slices\n- [ ] **S05: E** \`risk:low\` \`depends:[S01,S02,S03,S04]\`\n > After this: done\n`;
|
|
79
|
+
const commaSlices = parseRoadmapSlices(commaContent);
|
|
80
|
+
assertEq(commaSlices[0]?.depends, ["S01", "S02", "S03", "S04"], "comma-separated depends unchanged");
|
|
81
|
+
}
|
|
82
|
+
|
|
41
83
|
report();
|
|
@@ -222,3 +222,123 @@ test("dashboard: overlay labels triage-captures and quick-task unit types", () =
|
|
|
222
222
|
"unitLabel should handle quick-task",
|
|
223
223
|
);
|
|
224
224
|
});
|
|
225
|
+
|
|
226
|
+
// ─── Post-triage resolution execution ─────────────────────────────────────────
|
|
227
|
+
|
|
228
|
+
test("dispatch: post-triage resolution executor fires after triage-captures unit", () => {
|
|
229
|
+
const triageCompletionBlock = autoSrc.slice(
|
|
230
|
+
autoSrc.indexOf("Post-triage: execute actionable resolutions"),
|
|
231
|
+
autoSrc.indexOf("Path A fix: verify artifact"),
|
|
232
|
+
);
|
|
233
|
+
assert.ok(
|
|
234
|
+
triageCompletionBlock.includes('currentUnit.type === "triage-captures"'),
|
|
235
|
+
"should check for triage-captures unit completion",
|
|
236
|
+
);
|
|
237
|
+
assert.ok(
|
|
238
|
+
triageCompletionBlock.includes("executeTriageResolutions"),
|
|
239
|
+
"should call executeTriageResolutions",
|
|
240
|
+
);
|
|
241
|
+
});
|
|
242
|
+
|
|
243
|
+
test("dispatch: post-triage executor handles inject results", () => {
|
|
244
|
+
const triageCompletionBlock = autoSrc.slice(
|
|
245
|
+
autoSrc.indexOf("Post-triage: execute actionable resolutions"),
|
|
246
|
+
autoSrc.indexOf("Path A fix: verify artifact"),
|
|
247
|
+
);
|
|
248
|
+
assert.ok(
|
|
249
|
+
triageCompletionBlock.includes("triageResult.injected"),
|
|
250
|
+
"should check injected count",
|
|
251
|
+
);
|
|
252
|
+
});
|
|
253
|
+
|
|
254
|
+
test("dispatch: post-triage executor handles replan results", () => {
|
|
255
|
+
const triageCompletionBlock = autoSrc.slice(
|
|
256
|
+
autoSrc.indexOf("Post-triage: execute actionable resolutions"),
|
|
257
|
+
autoSrc.indexOf("Path A fix: verify artifact"),
|
|
258
|
+
);
|
|
259
|
+
assert.ok(
|
|
260
|
+
triageCompletionBlock.includes("triageResult.replanned"),
|
|
261
|
+
"should check replanned count",
|
|
262
|
+
);
|
|
263
|
+
});
|
|
264
|
+
|
|
265
|
+
test("dispatch: post-triage executor queues quick-tasks", () => {
|
|
266
|
+
const triageCompletionBlock = autoSrc.slice(
|
|
267
|
+
autoSrc.indexOf("Post-triage: execute actionable resolutions"),
|
|
268
|
+
autoSrc.indexOf("Path A fix: verify artifact"),
|
|
269
|
+
);
|
|
270
|
+
assert.ok(
|
|
271
|
+
triageCompletionBlock.includes("pendingQuickTasks"),
|
|
272
|
+
"should push quick-tasks to pendingQuickTasks queue",
|
|
273
|
+
);
|
|
274
|
+
});
|
|
275
|
+
|
|
276
|
+
// ─── Quick-task dispatch ──────────────────────────────────────────────────────
|
|
277
|
+
|
|
278
|
+
test("dispatch: quick-task dispatch block exists after triage check", () => {
|
|
279
|
+
const quickTaskBlock = autoSrc.indexOf("Quick-task dispatch: execute queued quick-tasks");
|
|
280
|
+
const triageBlock = autoSrc.indexOf("Triage check: dispatch triage unit");
|
|
281
|
+
const stepModeBlock = autoSrc.indexOf("In step mode, pause and show a wizard");
|
|
282
|
+
|
|
283
|
+
assert.ok(quickTaskBlock > 0, "quick-task dispatch block should exist");
|
|
284
|
+
assert.ok(
|
|
285
|
+
quickTaskBlock > triageBlock,
|
|
286
|
+
"quick-task dispatch should come after triage check",
|
|
287
|
+
);
|
|
288
|
+
assert.ok(
|
|
289
|
+
quickTaskBlock < stepModeBlock,
|
|
290
|
+
"quick-task dispatch should come before step mode check",
|
|
291
|
+
);
|
|
292
|
+
});
|
|
293
|
+
|
|
294
|
+
test("dispatch: quick-task dispatch uses buildQuickTaskPrompt", () => {
|
|
295
|
+
const quickTaskSection = autoSrc.slice(
|
|
296
|
+
autoSrc.indexOf("Quick-task dispatch: execute queued quick-tasks"),
|
|
297
|
+
autoSrc.indexOf("In step mode, pause and show a wizard"),
|
|
298
|
+
);
|
|
299
|
+
assert.ok(
|
|
300
|
+
quickTaskSection.includes("buildQuickTaskPrompt"),
|
|
301
|
+
"should call buildQuickTaskPrompt for quick-task dispatch",
|
|
302
|
+
);
|
|
303
|
+
});
|
|
304
|
+
|
|
305
|
+
test("dispatch: quick-task dispatch marks capture as executed", () => {
|
|
306
|
+
const quickTaskSection = autoSrc.slice(
|
|
307
|
+
autoSrc.indexOf("Quick-task dispatch: execute queued quick-tasks"),
|
|
308
|
+
autoSrc.indexOf("In step mode, pause and show a wizard"),
|
|
309
|
+
);
|
|
310
|
+
assert.ok(
|
|
311
|
+
quickTaskSection.includes("markCaptureExecuted"),
|
|
312
|
+
"should mark capture as executed after dispatch",
|
|
313
|
+
);
|
|
314
|
+
});
|
|
315
|
+
|
|
316
|
+
test("dispatch: quick-task dispatch uses early-return pattern", () => {
|
|
317
|
+
const quickTaskSection = autoSrc.slice(
|
|
318
|
+
autoSrc.indexOf("Quick-task dispatch: execute queued quick-tasks"),
|
|
319
|
+
autoSrc.indexOf("In step mode, pause and show a wizard"),
|
|
320
|
+
);
|
|
321
|
+
assert.ok(
|
|
322
|
+
quickTaskSection.includes("return; // handleAgentEnd will fire again when quick-task session completes"),
|
|
323
|
+
"quick-task dispatch should return after sending message",
|
|
324
|
+
);
|
|
325
|
+
});
|
|
326
|
+
|
|
327
|
+
// ─── Post-unit hook exclusion for quick-task ──────────────────────────────────
|
|
328
|
+
|
|
329
|
+
test("dispatch: quick-task excluded from post-unit hook triggering", () => {
|
|
330
|
+
assert.ok(
|
|
331
|
+
hooksSrc.includes('"quick-task"'),
|
|
332
|
+
"post-unit-hooks.ts should reference quick-task",
|
|
333
|
+
);
|
|
334
|
+
});
|
|
335
|
+
|
|
336
|
+
// ─── pendingQuickTasks queue lifecycle ────────────────────────────────────────
|
|
337
|
+
|
|
338
|
+
test("dispatch: pendingQuickTasks queue is reset on auto-mode start/stop", () => {
|
|
339
|
+
const resetMatches = autoSrc.match(/pendingQuickTasks = \[\]/g);
|
|
340
|
+
assert.ok(
|
|
341
|
+
resetMatches && resetMatches.length >= 3,
|
|
342
|
+
"pendingQuickTasks should be reset in at least 3 places (start, stop, manual hook)",
|
|
343
|
+
);
|
|
344
|
+
});
|