@tuan_son.dinh/gsd 2.6.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/LICENSE +21 -0
- package/README.md +453 -0
- package/dist/app-paths.d.ts +4 -0
- package/dist/app-paths.js +6 -0
- package/dist/cli.d.ts +1 -0
- package/dist/cli.js +269 -0
- package/dist/loader.d.ts +2 -0
- package/dist/loader.js +70 -0
- package/dist/logo.d.ts +16 -0
- package/dist/logo.js +25 -0
- package/dist/onboarding.d.ts +43 -0
- package/dist/onboarding.js +418 -0
- package/dist/pi-migration.d.ts +14 -0
- package/dist/pi-migration.js +57 -0
- package/dist/resource-loader.d.ts +22 -0
- package/dist/resource-loader.js +60 -0
- package/dist/tool-bootstrap.d.ts +4 -0
- package/dist/tool-bootstrap.js +74 -0
- package/dist/wizard.d.ts +7 -0
- package/dist/wizard.js +25 -0
- package/package.json +60 -0
- package/patches/@mariozechner+pi-coding-agent+0.57.1.patch +108 -0
- package/patches/@mariozechner+pi-tui+0.57.1.patch +47 -0
- package/pkg/dist/modes/interactive/theme/dark.json +85 -0
- package/pkg/dist/modes/interactive/theme/light.json +84 -0
- package/pkg/dist/modes/interactive/theme/theme-schema.json +335 -0
- package/pkg/dist/modes/interactive/theme/theme.d.ts +78 -0
- package/pkg/dist/modes/interactive/theme/theme.d.ts.map +1 -0
- package/pkg/dist/modes/interactive/theme/theme.js +949 -0
- package/pkg/dist/modes/interactive/theme/theme.js.map +1 -0
- package/pkg/package.json +8 -0
- package/scripts/postinstall.js +127 -0
- package/src/resources/GSD-WORKFLOW.md +661 -0
- package/src/resources/agents/researcher.md +29 -0
- package/src/resources/agents/scout.md +56 -0
- package/src/resources/agents/worker.md +31 -0
- package/src/resources/extensions/ask-user-questions.ts +249 -0
- package/src/resources/extensions/bg-shell/index.ts +2808 -0
- package/src/resources/extensions/browser-tools/BROWSER-TOOLS-V2-PROPOSAL.md +1277 -0
- package/src/resources/extensions/browser-tools/core.js +1057 -0
- package/src/resources/extensions/browser-tools/index.ts +4989 -0
- package/src/resources/extensions/browser-tools/package.json +20 -0
- package/src/resources/extensions/context7/index.ts +428 -0
- package/src/resources/extensions/context7/package.json +11 -0
- package/src/resources/extensions/get-secrets-from-user.ts +352 -0
- package/src/resources/extensions/google-search/index.ts +323 -0
- package/src/resources/extensions/google-search/package.json +9 -0
- package/src/resources/extensions/gsd/activity-log.ts +69 -0
- package/src/resources/extensions/gsd/auto.ts +2744 -0
- package/src/resources/extensions/gsd/commands.ts +313 -0
- package/src/resources/extensions/gsd/crash-recovery.ts +85 -0
- package/src/resources/extensions/gsd/dashboard-overlay.ts +521 -0
- package/src/resources/extensions/gsd/docs/preferences-reference.md +176 -0
- package/src/resources/extensions/gsd/doctor.ts +690 -0
- package/src/resources/extensions/gsd/files.ts +732 -0
- package/src/resources/extensions/gsd/git-service.ts +597 -0
- package/src/resources/extensions/gsd/gitignore.ts +168 -0
- package/src/resources/extensions/gsd/guided-flow.ts +817 -0
- package/src/resources/extensions/gsd/index.ts +558 -0
- package/src/resources/extensions/gsd/metrics.ts +374 -0
- package/src/resources/extensions/gsd/migrate/command.ts +218 -0
- package/src/resources/extensions/gsd/migrate/index.ts +42 -0
- package/src/resources/extensions/gsd/migrate/parser.ts +323 -0
- package/src/resources/extensions/gsd/migrate/parsers.ts +624 -0
- package/src/resources/extensions/gsd/migrate/preview.ts +48 -0
- package/src/resources/extensions/gsd/migrate/transformer.ts +346 -0
- package/src/resources/extensions/gsd/migrate/types.ts +370 -0
- package/src/resources/extensions/gsd/migrate/validator.ts +55 -0
- package/src/resources/extensions/gsd/migrate/writer.ts +539 -0
- package/src/resources/extensions/gsd/observability-validator.ts +408 -0
- package/src/resources/extensions/gsd/package.json +11 -0
- package/src/resources/extensions/gsd/paths.ts +308 -0
- package/src/resources/extensions/gsd/preferences.ts +757 -0
- package/src/resources/extensions/gsd/prompt-loader.ts +50 -0
- package/src/resources/extensions/gsd/prompts/complete-milestone.md +25 -0
- package/src/resources/extensions/gsd/prompts/complete-slice.md +29 -0
- package/src/resources/extensions/gsd/prompts/discuss.md +189 -0
- package/src/resources/extensions/gsd/prompts/doctor-heal.md +29 -0
- package/src/resources/extensions/gsd/prompts/execute-task.md +61 -0
- package/src/resources/extensions/gsd/prompts/guided-complete-slice.md +1 -0
- package/src/resources/extensions/gsd/prompts/guided-discuss-milestone.md +3 -0
- package/src/resources/extensions/gsd/prompts/guided-discuss-slice.md +59 -0
- package/src/resources/extensions/gsd/prompts/guided-execute-task.md +1 -0
- package/src/resources/extensions/gsd/prompts/guided-plan-milestone.md +23 -0
- package/src/resources/extensions/gsd/prompts/guided-plan-slice.md +1 -0
- package/src/resources/extensions/gsd/prompts/guided-research-slice.md +11 -0
- package/src/resources/extensions/gsd/prompts/guided-resume-task.md +1 -0
- package/src/resources/extensions/gsd/prompts/plan-milestone.md +65 -0
- package/src/resources/extensions/gsd/prompts/plan-slice.md +51 -0
- package/src/resources/extensions/gsd/prompts/queue.md +85 -0
- package/src/resources/extensions/gsd/prompts/reassess-roadmap.md +48 -0
- package/src/resources/extensions/gsd/prompts/replan-slice.md +39 -0
- package/src/resources/extensions/gsd/prompts/research-milestone.md +37 -0
- package/src/resources/extensions/gsd/prompts/research-slice.md +28 -0
- package/src/resources/extensions/gsd/prompts/review-migration.md +66 -0
- package/src/resources/extensions/gsd/prompts/run-uat.md +109 -0
- package/src/resources/extensions/gsd/prompts/system.md +187 -0
- package/src/resources/extensions/gsd/prompts/worktree-merge.md +123 -0
- package/src/resources/extensions/gsd/session-forensics.ts +487 -0
- package/src/resources/extensions/gsd/skill-discovery.ts +137 -0
- package/src/resources/extensions/gsd/state.ts +460 -0
- package/src/resources/extensions/gsd/templates/context.md +76 -0
- package/src/resources/extensions/gsd/templates/decisions.md +8 -0
- package/src/resources/extensions/gsd/templates/milestone-summary.md +73 -0
- package/src/resources/extensions/gsd/templates/plan.md +131 -0
- package/src/resources/extensions/gsd/templates/preferences.md +24 -0
- package/src/resources/extensions/gsd/templates/project.md +31 -0
- package/src/resources/extensions/gsd/templates/reassessment.md +28 -0
- package/src/resources/extensions/gsd/templates/requirements.md +81 -0
- package/src/resources/extensions/gsd/templates/research.md +46 -0
- package/src/resources/extensions/gsd/templates/roadmap.md +118 -0
- package/src/resources/extensions/gsd/templates/slice-context.md +58 -0
- package/src/resources/extensions/gsd/templates/slice-summary.md +99 -0
- package/src/resources/extensions/gsd/templates/state.md +19 -0
- package/src/resources/extensions/gsd/templates/task-plan.md +52 -0
- package/src/resources/extensions/gsd/templates/task-summary.md +57 -0
- package/src/resources/extensions/gsd/templates/uat.md +54 -0
- package/src/resources/extensions/gsd/tests/activity-log-prune.test.ts +327 -0
- package/src/resources/extensions/gsd/tests/auto-preflight.test.ts +56 -0
- package/src/resources/extensions/gsd/tests/auto-supervisor.test.mjs +53 -0
- package/src/resources/extensions/gsd/tests/complete-milestone.test.ts +225 -0
- package/src/resources/extensions/gsd/tests/cost-projection.test.ts +160 -0
- package/src/resources/extensions/gsd/tests/derive-state-deps.test.ts +341 -0
- package/src/resources/extensions/gsd/tests/derive-state.test.ts +689 -0
- package/src/resources/extensions/gsd/tests/discuss-prompt.test.ts +38 -0
- package/src/resources/extensions/gsd/tests/doctor.test.ts +505 -0
- package/src/resources/extensions/gsd/tests/git-service.test.ts +1313 -0
- package/src/resources/extensions/gsd/tests/idle-recovery.test.ts +308 -0
- package/src/resources/extensions/gsd/tests/metrics-io.test.ts +201 -0
- package/src/resources/extensions/gsd/tests/metrics.test.ts +217 -0
- package/src/resources/extensions/gsd/tests/migrate-command.test.ts +390 -0
- package/src/resources/extensions/gsd/tests/migrate-parser.test.ts +786 -0
- package/src/resources/extensions/gsd/tests/migrate-transformer.test.ts +657 -0
- package/src/resources/extensions/gsd/tests/migrate-validator-parsers.test.ts +443 -0
- package/src/resources/extensions/gsd/tests/migrate-writer-integration.test.ts +318 -0
- package/src/resources/extensions/gsd/tests/migrate-writer.test.ts +420 -0
- package/src/resources/extensions/gsd/tests/must-have-parser.test.ts +309 -0
- package/src/resources/extensions/gsd/tests/parsers.test.ts +1351 -0
- package/src/resources/extensions/gsd/tests/plan-milestone.test.ts +163 -0
- package/src/resources/extensions/gsd/tests/plan-quality-validator.test.ts +386 -0
- package/src/resources/extensions/gsd/tests/reassess-prompt.test.ts +171 -0
- package/src/resources/extensions/gsd/tests/remote-questions.test.ts +155 -0
- package/src/resources/extensions/gsd/tests/remote-status.test.ts +99 -0
- package/src/resources/extensions/gsd/tests/replan-slice.test.ts +521 -0
- package/src/resources/extensions/gsd/tests/requirements.test.ts +125 -0
- package/src/resources/extensions/gsd/tests/resolve-ts-hooks.mjs +34 -0
- package/src/resources/extensions/gsd/tests/resolve-ts.mjs +11 -0
- package/src/resources/extensions/gsd/tests/run-uat.test.ts +348 -0
- package/src/resources/extensions/gsd/tests/unit-runtime.test.ts +247 -0
- package/src/resources/extensions/gsd/tests/workflow-config.test.mjs +53 -0
- package/src/resources/extensions/gsd/tests/workspace-index.test.ts +94 -0
- package/src/resources/extensions/gsd/tests/worktree-integration.test.ts +253 -0
- package/src/resources/extensions/gsd/tests/worktree-manager.test.ts +160 -0
- package/src/resources/extensions/gsd/tests/worktree.test.ts +264 -0
- package/src/resources/extensions/gsd/types.ts +159 -0
- package/src/resources/extensions/gsd/unit-runtime.ts +184 -0
- package/src/resources/extensions/gsd/workspace-index.ts +203 -0
- package/src/resources/extensions/gsd/worktree-command.ts +845 -0
- package/src/resources/extensions/gsd/worktree-manager.ts +392 -0
- package/src/resources/extensions/gsd/worktree.ts +183 -0
- package/src/resources/extensions/mac-tools/index.ts +852 -0
- package/src/resources/extensions/mac-tools/swift-cli/Package.swift +22 -0
- package/src/resources/extensions/mac-tools/swift-cli/Sources/main.swift +1318 -0
- package/src/resources/extensions/mcporter/index.ts +429 -0
- package/src/resources/extensions/remote-questions/config.ts +81 -0
- package/src/resources/extensions/remote-questions/discord-adapter.ts +128 -0
- package/src/resources/extensions/remote-questions/format.ts +163 -0
- package/src/resources/extensions/remote-questions/manager.ts +192 -0
- package/src/resources/extensions/remote-questions/remote-command.ts +307 -0
- package/src/resources/extensions/remote-questions/slack-adapter.ts +92 -0
- package/src/resources/extensions/remote-questions/status.ts +31 -0
- package/src/resources/extensions/remote-questions/store.ts +77 -0
- package/src/resources/extensions/remote-questions/types.ts +75 -0
- package/src/resources/extensions/search-the-web/cache.ts +78 -0
- package/src/resources/extensions/search-the-web/command-search-provider.ts +95 -0
- package/src/resources/extensions/search-the-web/format.ts +258 -0
- package/src/resources/extensions/search-the-web/http.ts +238 -0
- package/src/resources/extensions/search-the-web/index.ts +65 -0
- package/src/resources/extensions/search-the-web/native-search.ts +157 -0
- package/src/resources/extensions/search-the-web/provider.ts +118 -0
- package/src/resources/extensions/search-the-web/tavily.ts +116 -0
- package/src/resources/extensions/search-the-web/tool-fetch-page.ts +519 -0
- package/src/resources/extensions/search-the-web/tool-llm-context.ts +561 -0
- package/src/resources/extensions/search-the-web/tool-search.ts +576 -0
- package/src/resources/extensions/search-the-web/url-utils.ts +91 -0
- package/src/resources/extensions/shared/confirm-ui.ts +126 -0
- package/src/resources/extensions/shared/interview-ui.ts +613 -0
- package/src/resources/extensions/shared/next-action-ui.ts +197 -0
- package/src/resources/extensions/shared/progress-widget.ts +282 -0
- package/src/resources/extensions/shared/terminal.ts +23 -0
- package/src/resources/extensions/shared/thinking-widget.ts +107 -0
- package/src/resources/extensions/shared/ui.ts +400 -0
- package/src/resources/extensions/shared/wizard-ui.ts +551 -0
- package/src/resources/extensions/slash-commands/audit.ts +88 -0
- package/src/resources/extensions/slash-commands/clear.ts +10 -0
- package/src/resources/extensions/slash-commands/create-extension.ts +297 -0
- package/src/resources/extensions/slash-commands/create-slash-command.ts +234 -0
- package/src/resources/extensions/slash-commands/index.ts +12 -0
- package/src/resources/extensions/subagent/agents.ts +126 -0
- package/src/resources/extensions/subagent/index.ts +1020 -0
- package/src/resources/extensions/voice/index.ts +195 -0
- package/src/resources/extensions/voice/speech-recognizer.swift +154 -0
- package/src/resources/skills/debug-like-expert/SKILL.md +231 -0
- package/src/resources/skills/debug-like-expert/references/debugging-mindset.md +253 -0
- package/src/resources/skills/debug-like-expert/references/hypothesis-testing.md +373 -0
- package/src/resources/skills/debug-like-expert/references/investigation-techniques.md +337 -0
- package/src/resources/skills/debug-like-expert/references/verification-patterns.md +425 -0
- package/src/resources/skills/debug-like-expert/references/when-to-research.md +361 -0
- package/src/resources/skills/frontend-design/SKILL.md +45 -0
- package/src/resources/skills/swiftui/SKILL.md +208 -0
- package/src/resources/skills/swiftui/references/animations.md +921 -0
- package/src/resources/skills/swiftui/references/architecture.md +1561 -0
- package/src/resources/skills/swiftui/references/layout-system.md +1186 -0
- package/src/resources/skills/swiftui/references/navigation.md +1492 -0
- package/src/resources/skills/swiftui/references/networking-async.md +214 -0
- package/src/resources/skills/swiftui/references/performance.md +1706 -0
- package/src/resources/skills/swiftui/references/platform-integration.md +204 -0
- package/src/resources/skills/swiftui/references/state-management.md +1443 -0
- package/src/resources/skills/swiftui/references/swiftdata.md +297 -0
- package/src/resources/skills/swiftui/references/testing-debugging.md +247 -0
- package/src/resources/skills/swiftui/references/uikit-appkit-interop.md +218 -0
- package/src/resources/skills/swiftui/workflows/add-feature.md +191 -0
- package/src/resources/skills/swiftui/workflows/build-new-app.md +311 -0
- package/src/resources/skills/swiftui/workflows/debug-swiftui.md +192 -0
- package/src/resources/skills/swiftui/workflows/optimize-performance.md +197 -0
- package/src/resources/skills/swiftui/workflows/ship-app.md +203 -0
- package/src/resources/skills/swiftui/workflows/write-tests.md +235 -0
|
@@ -0,0 +1,217 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tests for GSD metrics aggregation logic.
|
|
3
|
+
* Tests the pure functions — no file I/O, no extension context.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import {
|
|
7
|
+
type UnitMetrics,
|
|
8
|
+
type TokenCounts,
|
|
9
|
+
classifyUnitPhase,
|
|
10
|
+
aggregateByPhase,
|
|
11
|
+
aggregateBySlice,
|
|
12
|
+
aggregateByModel,
|
|
13
|
+
getProjectTotals,
|
|
14
|
+
formatCost,
|
|
15
|
+
formatTokenCount,
|
|
16
|
+
} from "../metrics.js";
|
|
17
|
+
|
|
18
|
+
// ─── Test helpers ─────────────────────────────────────────────────────────────
|
|
19
|
+
|
|
20
|
+
function makeUnit(overrides: Partial<UnitMetrics> = {}): UnitMetrics {
|
|
21
|
+
return {
|
|
22
|
+
type: "execute-task",
|
|
23
|
+
id: "M001/S01/T01",
|
|
24
|
+
model: "claude-sonnet-4-20250514",
|
|
25
|
+
startedAt: 1000,
|
|
26
|
+
finishedAt: 2000,
|
|
27
|
+
tokens: { input: 1000, output: 500, cacheRead: 200, cacheWrite: 100, total: 1800 },
|
|
28
|
+
cost: 0.05,
|
|
29
|
+
toolCalls: 3,
|
|
30
|
+
assistantMessages: 2,
|
|
31
|
+
userMessages: 1,
|
|
32
|
+
...overrides,
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
let passed = 0;
|
|
37
|
+
let failed = 0;
|
|
38
|
+
|
|
39
|
+
function assert(condition: boolean, message: string): void {
|
|
40
|
+
if (condition) {
|
|
41
|
+
passed++;
|
|
42
|
+
} else {
|
|
43
|
+
failed++;
|
|
44
|
+
console.error(` FAIL: ${message}`);
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function assertEq<T>(actual: T, expected: T, message: string): void {
|
|
49
|
+
if (actual === expected) {
|
|
50
|
+
passed++;
|
|
51
|
+
} else {
|
|
52
|
+
failed++;
|
|
53
|
+
console.error(` FAIL: ${message} — expected ${JSON.stringify(expected)}, got ${JSON.stringify(actual)}`);
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
function assertClose(actual: number, expected: number, tolerance: number, message: string): void {
|
|
58
|
+
if (Math.abs(actual - expected) <= tolerance) {
|
|
59
|
+
passed++;
|
|
60
|
+
} else {
|
|
61
|
+
failed++;
|
|
62
|
+
console.error(` FAIL: ${message} — expected ~${expected}, got ${actual}`);
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
// ─── Phase classification ─────────────────────────────────────────────────────
|
|
67
|
+
|
|
68
|
+
console.log("\n=== classifyUnitPhase ===");
|
|
69
|
+
|
|
70
|
+
assertEq(classifyUnitPhase("research-milestone"), "research", "research-milestone → research");
|
|
71
|
+
assertEq(classifyUnitPhase("research-slice"), "research", "research-slice → research");
|
|
72
|
+
assertEq(classifyUnitPhase("plan-milestone"), "planning", "plan-milestone → planning");
|
|
73
|
+
assertEq(classifyUnitPhase("plan-slice"), "planning", "plan-slice → planning");
|
|
74
|
+
assertEq(classifyUnitPhase("execute-task"), "execution", "execute-task → execution");
|
|
75
|
+
assertEq(classifyUnitPhase("complete-slice"), "completion", "complete-slice → completion");
|
|
76
|
+
assertEq(classifyUnitPhase("reassess-roadmap"), "reassessment", "reassess-roadmap → reassessment");
|
|
77
|
+
assertEq(classifyUnitPhase("unknown-thing"), "execution", "unknown → execution (fallback)");
|
|
78
|
+
|
|
79
|
+
// ─── getProjectTotals ─────────────────────────────────────────────────────────
|
|
80
|
+
|
|
81
|
+
console.log("\n=== getProjectTotals ===");
|
|
82
|
+
|
|
83
|
+
{
|
|
84
|
+
const units = [
|
|
85
|
+
makeUnit({ tokens: { input: 1000, output: 500, cacheRead: 200, cacheWrite: 100, total: 1800 }, cost: 0.05, toolCalls: 3, startedAt: 1000, finishedAt: 2000 }),
|
|
86
|
+
makeUnit({ tokens: { input: 2000, output: 1000, cacheRead: 400, cacheWrite: 200, total: 3600 }, cost: 0.10, toolCalls: 5, startedAt: 2000, finishedAt: 4000 }),
|
|
87
|
+
];
|
|
88
|
+
const totals = getProjectTotals(units);
|
|
89
|
+
|
|
90
|
+
assertEq(totals.units, 2, "total units");
|
|
91
|
+
assertEq(totals.tokens.input, 3000, "total input tokens");
|
|
92
|
+
assertEq(totals.tokens.output, 1500, "total output tokens");
|
|
93
|
+
assertEq(totals.tokens.cacheRead, 600, "total cacheRead");
|
|
94
|
+
assertEq(totals.tokens.cacheWrite, 300, "total cacheWrite");
|
|
95
|
+
assertEq(totals.tokens.total, 5400, "total tokens");
|
|
96
|
+
assertClose(totals.cost, 0.15, 0.001, "total cost");
|
|
97
|
+
assertEq(totals.toolCalls, 8, "total tool calls");
|
|
98
|
+
assertEq(totals.duration, 3000, "total duration");
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
{
|
|
102
|
+
const totals = getProjectTotals([]);
|
|
103
|
+
assertEq(totals.units, 0, "empty: zero units");
|
|
104
|
+
assertEq(totals.cost, 0, "empty: zero cost");
|
|
105
|
+
assertEq(totals.tokens.total, 0, "empty: zero tokens");
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
// ─── aggregateByPhase ─────────────────────────────────────────────────────────
|
|
109
|
+
|
|
110
|
+
console.log("\n=== aggregateByPhase ===");
|
|
111
|
+
|
|
112
|
+
{
|
|
113
|
+
const units = [
|
|
114
|
+
makeUnit({ type: "research-milestone", cost: 0.02 }),
|
|
115
|
+
makeUnit({ type: "research-slice", cost: 0.03 }),
|
|
116
|
+
makeUnit({ type: "plan-milestone", cost: 0.01 }),
|
|
117
|
+
makeUnit({ type: "plan-slice", cost: 0.02 }),
|
|
118
|
+
makeUnit({ type: "execute-task", cost: 0.10 }),
|
|
119
|
+
makeUnit({ type: "execute-task", cost: 0.08 }),
|
|
120
|
+
makeUnit({ type: "complete-slice", cost: 0.01 }),
|
|
121
|
+
makeUnit({ type: "reassess-roadmap", cost: 0.005 }),
|
|
122
|
+
];
|
|
123
|
+
const phases = aggregateByPhase(units);
|
|
124
|
+
|
|
125
|
+
assertEq(phases.length, 5, "5 phases");
|
|
126
|
+
assertEq(phases[0].phase, "research", "first phase is research");
|
|
127
|
+
assertEq(phases[0].units, 2, "2 research units");
|
|
128
|
+
assertClose(phases[0].cost, 0.05, 0.001, "research cost");
|
|
129
|
+
|
|
130
|
+
assertEq(phases[1].phase, "planning", "second phase is planning");
|
|
131
|
+
assertEq(phases[1].units, 2, "2 planning units");
|
|
132
|
+
|
|
133
|
+
assertEq(phases[2].phase, "execution", "third phase is execution");
|
|
134
|
+
assertEq(phases[2].units, 2, "2 execution units");
|
|
135
|
+
assertClose(phases[2].cost, 0.18, 0.001, "execution cost");
|
|
136
|
+
|
|
137
|
+
assertEq(phases[3].phase, "completion", "fourth phase is completion");
|
|
138
|
+
assertEq(phases[4].phase, "reassessment", "fifth phase is reassessment");
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
// ─── aggregateBySlice ─────────────────────────────────────────────────────────
|
|
142
|
+
|
|
143
|
+
console.log("\n=== aggregateBySlice ===");
|
|
144
|
+
|
|
145
|
+
{
|
|
146
|
+
const units = [
|
|
147
|
+
makeUnit({ id: "M001/S01/T01", cost: 0.05 }),
|
|
148
|
+
makeUnit({ id: "M001/S01/T02", cost: 0.04 }),
|
|
149
|
+
makeUnit({ id: "M001/S02/T01", cost: 0.10 }),
|
|
150
|
+
makeUnit({ id: "M001", type: "research-milestone", cost: 0.02 }),
|
|
151
|
+
];
|
|
152
|
+
const slices = aggregateBySlice(units);
|
|
153
|
+
|
|
154
|
+
assertEq(slices.length, 3, "3 slice groups");
|
|
155
|
+
|
|
156
|
+
const s01 = slices.find(s => s.sliceId === "M001/S01");
|
|
157
|
+
assert(!!s01, "M001/S01 exists");
|
|
158
|
+
assertEq(s01!.units, 2, "M001/S01 has 2 units");
|
|
159
|
+
assertClose(s01!.cost, 0.09, 0.001, "M001/S01 cost");
|
|
160
|
+
|
|
161
|
+
const s02 = slices.find(s => s.sliceId === "M001/S02");
|
|
162
|
+
assert(!!s02, "M001/S02 exists");
|
|
163
|
+
assertEq(s02!.units, 1, "M001/S02 has 1 unit");
|
|
164
|
+
|
|
165
|
+
const mLevel = slices.find(s => s.sliceId === "M001");
|
|
166
|
+
assert(!!mLevel, "M001 (milestone-level) exists");
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
// ─── aggregateByModel ─────────────────────────────────────────────────────────
|
|
170
|
+
|
|
171
|
+
console.log("\n=== aggregateByModel ===");
|
|
172
|
+
|
|
173
|
+
{
|
|
174
|
+
const units = [
|
|
175
|
+
makeUnit({ model: "claude-sonnet-4-20250514", cost: 0.05 }),
|
|
176
|
+
makeUnit({ model: "claude-sonnet-4-20250514", cost: 0.04 }),
|
|
177
|
+
makeUnit({ model: "claude-opus-4-20250514", cost: 0.30 }),
|
|
178
|
+
];
|
|
179
|
+
const models = aggregateByModel(units);
|
|
180
|
+
|
|
181
|
+
assertEq(models.length, 2, "2 models");
|
|
182
|
+
// Sorted by cost desc — opus should be first
|
|
183
|
+
assertEq(models[0].model, "claude-opus-4-20250514", "opus first (higher cost)");
|
|
184
|
+
assertClose(models[0].cost, 0.30, 0.001, "opus cost");
|
|
185
|
+
assertEq(models[1].model, "claude-sonnet-4-20250514", "sonnet second");
|
|
186
|
+
assertEq(models[1].units, 2, "sonnet has 2 units");
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
// ─── formatCost ───────────────────────────────────────────────────────────────
|
|
190
|
+
|
|
191
|
+
console.log("\n=== formatCost ===");
|
|
192
|
+
|
|
193
|
+
assertEq(formatCost(0), "$0.0000", "zero cost");
|
|
194
|
+
assertEq(formatCost(0.001), "$0.0010", "sub-cent cost");
|
|
195
|
+
assertEq(formatCost(0.05), "$0.050", "5 cents");
|
|
196
|
+
assertEq(formatCost(1.50), "$1.50", "dollar+");
|
|
197
|
+
assertEq(formatCost(14.20), "$14.20", "double digits");
|
|
198
|
+
|
|
199
|
+
// ─── formatTokenCount ─────────────────────────────────────────────────────────
|
|
200
|
+
|
|
201
|
+
console.log("\n=== formatTokenCount ===");
|
|
202
|
+
|
|
203
|
+
assertEq(formatTokenCount(0), "0", "zero tokens");
|
|
204
|
+
assertEq(formatTokenCount(500), "500", "sub-k");
|
|
205
|
+
assertEq(formatTokenCount(1500), "1.5k", "1.5k");
|
|
206
|
+
assertEq(formatTokenCount(150000), "150.0k", "150k");
|
|
207
|
+
assertEq(formatTokenCount(1500000), "1.50M", "1.5M");
|
|
208
|
+
|
|
209
|
+
// ─── Summary ──────────────────────────────────────────────────────────────────
|
|
210
|
+
|
|
211
|
+
console.log(`\n${"=".repeat(40)}`);
|
|
212
|
+
console.log(`Results: ${passed} passed, ${failed} failed`);
|
|
213
|
+
if (failed > 0) {
|
|
214
|
+
process.exit(1);
|
|
215
|
+
} else {
|
|
216
|
+
console.log("All tests passed ✓");
|
|
217
|
+
}
|
|
@@ -0,0 +1,390 @@
|
|
|
1
|
+
// Migration command integration test
|
|
2
|
+
// Tests the pipeline functions as the command handler uses them:
|
|
3
|
+
// path resolution, validation gating, full parse→transform→preview→write→deriveState round-trip.
|
|
4
|
+
// Exercises pipeline modules directly — no TUI context dependency.
|
|
5
|
+
|
|
6
|
+
import { mkdtempSync, mkdirSync, rmSync, writeFileSync, existsSync } from 'node:fs';
|
|
7
|
+
import { join, resolve } from 'node:path';
|
|
8
|
+
import { tmpdir } from 'node:os';
|
|
9
|
+
|
|
10
|
+
import {
|
|
11
|
+
validatePlanningDirectory,
|
|
12
|
+
parsePlanningDirectory,
|
|
13
|
+
transformToGSD,
|
|
14
|
+
generatePreview,
|
|
15
|
+
writeGSDDirectory,
|
|
16
|
+
} from '../migrate/index.ts';
|
|
17
|
+
import { deriveState } from '../state.ts';
|
|
18
|
+
|
|
19
|
+
let passed = 0;
|
|
20
|
+
let failed = 0;
|
|
21
|
+
|
|
22
|
+
function assert(condition: boolean, message: string): void {
|
|
23
|
+
if (condition) {
|
|
24
|
+
passed++;
|
|
25
|
+
} else {
|
|
26
|
+
failed++;
|
|
27
|
+
console.error(` FAIL: ${message}`);
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function assertEq<T>(actual: T, expected: T, message: string): void {
|
|
32
|
+
if (JSON.stringify(actual) === JSON.stringify(expected)) {
|
|
33
|
+
passed++;
|
|
34
|
+
} else {
|
|
35
|
+
failed++;
|
|
36
|
+
console.error(` FAIL: ${message} — expected ${JSON.stringify(expected)}, got ${JSON.stringify(actual)}`);
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
// ─── Fixture Helpers ───────────────────────────────────────────────────────
|
|
41
|
+
|
|
42
|
+
const SAMPLE_PROJECT = `# Integration Test Project
|
|
43
|
+
|
|
44
|
+
A project used for command pipeline integration testing.
|
|
45
|
+
|
|
46
|
+
## Goals
|
|
47
|
+
|
|
48
|
+
- Test the full migration pipeline
|
|
49
|
+
`;
|
|
50
|
+
|
|
51
|
+
const SAMPLE_ROADMAP = `# Project Roadmap
|
|
52
|
+
|
|
53
|
+
## Phases
|
|
54
|
+
|
|
55
|
+
- [x] 10 — Foundation
|
|
56
|
+
- [ ] 20 — Features
|
|
57
|
+
`;
|
|
58
|
+
|
|
59
|
+
const SAMPLE_REQUIREMENTS = `# Requirements
|
|
60
|
+
|
|
61
|
+
## Active
|
|
62
|
+
|
|
63
|
+
### R001 — Core Pipeline
|
|
64
|
+
- Status: active
|
|
65
|
+
- Description: Pipeline must work end-to-end.
|
|
66
|
+
|
|
67
|
+
## Validated
|
|
68
|
+
|
|
69
|
+
### R002 — Output Format
|
|
70
|
+
- Status: validated
|
|
71
|
+
- Description: Output matches GSD format.
|
|
72
|
+
`;
|
|
73
|
+
|
|
74
|
+
const SAMPLE_STATE = `# State
|
|
75
|
+
|
|
76
|
+
**Current Phase:** 20-features
|
|
77
|
+
**Status:** in-progress
|
|
78
|
+
`;
|
|
79
|
+
|
|
80
|
+
const SAMPLE_CONFIG = JSON.stringify({
|
|
81
|
+
projectName: 'pipeline-test',
|
|
82
|
+
version: '1.0',
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
const SAMPLE_PLAN_10_01 = `---
|
|
86
|
+
phase: "10-foundation"
|
|
87
|
+
plan: "01"
|
|
88
|
+
type: "implementation"
|
|
89
|
+
wave: 1
|
|
90
|
+
depends_on: []
|
|
91
|
+
files_modified: [src/core.ts]
|
|
92
|
+
autonomous: true
|
|
93
|
+
must_haves:
|
|
94
|
+
truths:
|
|
95
|
+
- Core module works
|
|
96
|
+
artifacts:
|
|
97
|
+
- src/core.ts
|
|
98
|
+
key_links: []
|
|
99
|
+
---
|
|
100
|
+
|
|
101
|
+
# 10-01: Build Foundation
|
|
102
|
+
|
|
103
|
+
<objective>
|
|
104
|
+
Set up the project foundation and core module.
|
|
105
|
+
</objective>
|
|
106
|
+
|
|
107
|
+
<tasks>
|
|
108
|
+
<task>Create core module</task>
|
|
109
|
+
<task>Add configuration loader</task>
|
|
110
|
+
</tasks>
|
|
111
|
+
|
|
112
|
+
<context>
|
|
113
|
+
Foundation work needed before features.
|
|
114
|
+
</context>
|
|
115
|
+
|
|
116
|
+
<verification>
|
|
117
|
+
- Core module loads
|
|
118
|
+
- Config is parsed
|
|
119
|
+
</verification>
|
|
120
|
+
|
|
121
|
+
<success_criteria>
|
|
122
|
+
Core is operational.
|
|
123
|
+
</success_criteria>
|
|
124
|
+
`;
|
|
125
|
+
|
|
126
|
+
const SAMPLE_SUMMARY_10_01 = `---
|
|
127
|
+
phase: "10-foundation"
|
|
128
|
+
plan: "01"
|
|
129
|
+
subsystem: "core"
|
|
130
|
+
tags:
|
|
131
|
+
- foundation
|
|
132
|
+
requires: []
|
|
133
|
+
provides:
|
|
134
|
+
- core-module
|
|
135
|
+
affects:
|
|
136
|
+
- features
|
|
137
|
+
tech-stack:
|
|
138
|
+
- typescript
|
|
139
|
+
key-files:
|
|
140
|
+
- src/core.ts
|
|
141
|
+
key-decisions:
|
|
142
|
+
- Use TypeScript strict mode
|
|
143
|
+
patterns-established:
|
|
144
|
+
- Module pattern
|
|
145
|
+
duration: "1h"
|
|
146
|
+
completed: "2026-01-10"
|
|
147
|
+
---
|
|
148
|
+
|
|
149
|
+
# 10-01: Foundation Summary
|
|
150
|
+
|
|
151
|
+
Core module built and operational.
|
|
152
|
+
|
|
153
|
+
## What Happened
|
|
154
|
+
|
|
155
|
+
Created core module and configuration loader.
|
|
156
|
+
|
|
157
|
+
## Files Modified
|
|
158
|
+
|
|
159
|
+
- \`src/core.ts\` — Core module
|
|
160
|
+
`;
|
|
161
|
+
|
|
162
|
+
const SAMPLE_PLAN_20_01 = `---
|
|
163
|
+
phase: "20-features"
|
|
164
|
+
plan: "01"
|
|
165
|
+
type: "implementation"
|
|
166
|
+
wave: 1
|
|
167
|
+
depends_on: [10-01]
|
|
168
|
+
files_modified: []
|
|
169
|
+
autonomous: false
|
|
170
|
+
---
|
|
171
|
+
|
|
172
|
+
# 20-01: Build Feature A
|
|
173
|
+
|
|
174
|
+
<objective>
|
|
175
|
+
Implement the first feature.
|
|
176
|
+
</objective>
|
|
177
|
+
|
|
178
|
+
<tasks>
|
|
179
|
+
<task>Design feature API</task>
|
|
180
|
+
<task>Implement feature logic</task>
|
|
181
|
+
</tasks>
|
|
182
|
+
|
|
183
|
+
<context>
|
|
184
|
+
Depends on foundation work.
|
|
185
|
+
</context>
|
|
186
|
+
`;
|
|
187
|
+
|
|
188
|
+
function createCompleteFixture(): string {
|
|
189
|
+
const base = mkdtempSync(join(tmpdir(), 'gsd-cmd-test-'));
|
|
190
|
+
const planning = join(base, '.planning');
|
|
191
|
+
mkdirSync(planning, { recursive: true });
|
|
192
|
+
|
|
193
|
+
writeFileSync(join(planning, 'PROJECT.md'), SAMPLE_PROJECT);
|
|
194
|
+
writeFileSync(join(planning, 'ROADMAP.md'), SAMPLE_ROADMAP);
|
|
195
|
+
writeFileSync(join(planning, 'REQUIREMENTS.md'), SAMPLE_REQUIREMENTS);
|
|
196
|
+
writeFileSync(join(planning, 'STATE.md'), SAMPLE_STATE);
|
|
197
|
+
writeFileSync(join(planning, 'config.json'), SAMPLE_CONFIG);
|
|
198
|
+
|
|
199
|
+
// Phase 10: done — has plan + summary
|
|
200
|
+
const phase10 = join(planning, 'phases', '10-foundation');
|
|
201
|
+
mkdirSync(phase10, { recursive: true });
|
|
202
|
+
writeFileSync(join(phase10, '10-01-PLAN.md'), SAMPLE_PLAN_10_01);
|
|
203
|
+
writeFileSync(join(phase10, '10-01-SUMMARY.md'), SAMPLE_SUMMARY_10_01);
|
|
204
|
+
|
|
205
|
+
// Phase 20: in-progress — has plan, no summary
|
|
206
|
+
const phase20 = join(planning, 'phases', '20-features');
|
|
207
|
+
mkdirSync(phase20, { recursive: true });
|
|
208
|
+
writeFileSync(join(phase20, '20-01-PLAN.md'), SAMPLE_PLAN_20_01);
|
|
209
|
+
|
|
210
|
+
return base;
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
214
|
+
// Tests
|
|
215
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
216
|
+
|
|
217
|
+
async function main(): Promise<void> {
|
|
218
|
+
|
|
219
|
+
// ─── Test 1: Path resolution — .planning appended when missing ─────────
|
|
220
|
+
console.log('\n=== Path resolution: .planning appended when source path lacks it ===');
|
|
221
|
+
{
|
|
222
|
+
const base = createCompleteFixture();
|
|
223
|
+
try {
|
|
224
|
+
// Simulate the command's path resolution logic
|
|
225
|
+
let sourcePath = resolve(base); // no .planning suffix
|
|
226
|
+
if (!sourcePath.endsWith('.planning')) {
|
|
227
|
+
sourcePath = join(sourcePath, '.planning');
|
|
228
|
+
}
|
|
229
|
+
assert(sourcePath.endsWith('.planning'), 'path-resolution: .planning appended');
|
|
230
|
+
assert(existsSync(sourcePath), 'path-resolution: appended path exists');
|
|
231
|
+
} finally {
|
|
232
|
+
rmSync(base, { recursive: true, force: true });
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
// ─── Test 2: Path resolution — .planning used as-is ────────────────────
|
|
237
|
+
console.log('\n=== Path resolution: .planning used as-is when already present ===');
|
|
238
|
+
{
|
|
239
|
+
const base = createCompleteFixture();
|
|
240
|
+
try {
|
|
241
|
+
const planningPath = join(base, '.planning');
|
|
242
|
+
let sourcePath = resolve(planningPath);
|
|
243
|
+
if (!sourcePath.endsWith('.planning')) {
|
|
244
|
+
sourcePath = join(sourcePath, '.planning');
|
|
245
|
+
}
|
|
246
|
+
assertEq(sourcePath, resolve(planningPath), 'path-resolution: .planning not double-appended');
|
|
247
|
+
assert(existsSync(sourcePath), 'path-resolution: direct path exists');
|
|
248
|
+
} finally {
|
|
249
|
+
rmSync(base, { recursive: true, force: true });
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
// ─── Test 3: Validation gating — non-existent path ─────────────────────
|
|
254
|
+
console.log('\n=== Validation gating: non-existent path returns invalid ===');
|
|
255
|
+
{
|
|
256
|
+
const fakePath = join(tmpdir(), 'gsd-cmd-nonexistent-' + Date.now(), '.planning');
|
|
257
|
+
const result = await validatePlanningDirectory(fakePath);
|
|
258
|
+
assertEq(result.valid, false, 'validation: non-existent path is invalid');
|
|
259
|
+
assert(result.issues.length > 0, 'validation: has issues for non-existent path');
|
|
260
|
+
const hasFatal = result.issues.some(i => i.severity === 'fatal');
|
|
261
|
+
assert(hasFatal, 'validation: non-existent path has fatal issue');
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
// ─── Test 4: Validation gating — valid fixture passes ──────────────────
|
|
265
|
+
console.log('\n=== Validation gating: valid fixture passes validation ===');
|
|
266
|
+
{
|
|
267
|
+
const base = createCompleteFixture();
|
|
268
|
+
try {
|
|
269
|
+
const result = await validatePlanningDirectory(join(base, '.planning'));
|
|
270
|
+
assert(result.valid === true, 'validation: valid fixture passes');
|
|
271
|
+
} finally {
|
|
272
|
+
rmSync(base, { recursive: true, force: true });
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
// ─── Test 5: Full pipeline round-trip ──────────────────────────────────
|
|
277
|
+
console.log('\n=== Full pipeline: parse → transform → preview → write → deriveState ===');
|
|
278
|
+
{
|
|
279
|
+
const base = createCompleteFixture();
|
|
280
|
+
const writeTarget = mkdtempSync(join(tmpdir(), 'gsd-cmd-write-'));
|
|
281
|
+
try {
|
|
282
|
+
const planningPath = join(base, '.planning');
|
|
283
|
+
|
|
284
|
+
// (a) Validate
|
|
285
|
+
const validation = await validatePlanningDirectory(planningPath);
|
|
286
|
+
assert(validation.valid === true, 'pipeline: validation passes');
|
|
287
|
+
|
|
288
|
+
// (b) Parse
|
|
289
|
+
const parsed = await parsePlanningDirectory(planningPath);
|
|
290
|
+
assert(parsed.roadmap !== null, 'pipeline: roadmap parsed');
|
|
291
|
+
assert(Object.keys(parsed.phases).length >= 2, 'pipeline: phases parsed');
|
|
292
|
+
|
|
293
|
+
// (c) Transform
|
|
294
|
+
const project = transformToGSD(parsed);
|
|
295
|
+
assert(project.milestones.length >= 1, 'pipeline: has milestones');
|
|
296
|
+
assert(project.milestones[0].slices.length >= 1, 'pipeline: has slices');
|
|
297
|
+
|
|
298
|
+
// Count totals for preview verification
|
|
299
|
+
let totalTasks = 0;
|
|
300
|
+
let doneTasks = 0;
|
|
301
|
+
let totalSlices = 0;
|
|
302
|
+
let doneSlices = 0;
|
|
303
|
+
for (const m of project.milestones) {
|
|
304
|
+
for (const s of m.slices) {
|
|
305
|
+
totalSlices++;
|
|
306
|
+
if (s.done) doneSlices++;
|
|
307
|
+
for (const t of s.tasks) {
|
|
308
|
+
totalTasks++;
|
|
309
|
+
if (t.done) doneTasks++;
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
// (d) Preview — verify counts match project data
|
|
315
|
+
const preview = generatePreview(project);
|
|
316
|
+
assertEq(preview.milestoneCount, project.milestones.length, 'pipeline: preview milestoneCount');
|
|
317
|
+
assertEq(preview.totalSlices, totalSlices, 'pipeline: preview totalSlices');
|
|
318
|
+
assertEq(preview.totalTasks, totalTasks, 'pipeline: preview totalTasks');
|
|
319
|
+
assertEq(preview.doneSlices, doneSlices, 'pipeline: preview doneSlices');
|
|
320
|
+
assertEq(preview.doneTasks, doneTasks, 'pipeline: preview doneTasks');
|
|
321
|
+
|
|
322
|
+
// Completion percentages
|
|
323
|
+
const expectedSlicePct = totalSlices > 0 ? Math.round((doneSlices / totalSlices) * 100) : 0;
|
|
324
|
+
const expectedTaskPct = totalTasks > 0 ? Math.round((doneTasks / totalTasks) * 100) : 0;
|
|
325
|
+
assertEq(preview.sliceCompletionPct, expectedSlicePct, 'pipeline: preview sliceCompletionPct');
|
|
326
|
+
assertEq(preview.taskCompletionPct, expectedTaskPct, 'pipeline: preview taskCompletionPct');
|
|
327
|
+
|
|
328
|
+
// Requirements in preview
|
|
329
|
+
assertEq(preview.requirements.active, 1, 'pipeline: preview requirements active');
|
|
330
|
+
assertEq(preview.requirements.validated, 1, 'pipeline: preview requirements validated');
|
|
331
|
+
assertEq(preview.requirements.total, 2, 'pipeline: preview requirements total');
|
|
332
|
+
|
|
333
|
+
// (e) Write
|
|
334
|
+
const result = await writeGSDDirectory(project, writeTarget);
|
|
335
|
+
assert(result.paths.length > 0, 'pipeline: files written');
|
|
336
|
+
|
|
337
|
+
// Key files exist
|
|
338
|
+
const gsd = join(writeTarget, '.gsd');
|
|
339
|
+
assert(existsSync(join(gsd, 'PROJECT.md')), 'pipeline: PROJECT.md written');
|
|
340
|
+
assert(existsSync(join(gsd, 'STATE.md')), 'pipeline: STATE.md written');
|
|
341
|
+
assert(existsSync(join(gsd, 'REQUIREMENTS.md')), 'pipeline: REQUIREMENTS.md written');
|
|
342
|
+
|
|
343
|
+
const m001 = join(gsd, 'milestones', 'M001');
|
|
344
|
+
assert(existsSync(join(m001, 'M001-ROADMAP.md')), 'pipeline: M001-ROADMAP.md written');
|
|
345
|
+
assert(existsSync(join(m001, 'M001-CONTEXT.md')), 'pipeline: M001-CONTEXT.md written');
|
|
346
|
+
|
|
347
|
+
// At least one slice plan exists
|
|
348
|
+
const s01Plan = join(m001, 'slices', 'S01', 'S01-PLAN.md');
|
|
349
|
+
assert(existsSync(s01Plan), 'pipeline: S01-PLAN.md written');
|
|
350
|
+
|
|
351
|
+
// (f) deriveState — coherent state from written output
|
|
352
|
+
console.log(' --- deriveState ---');
|
|
353
|
+
const state = await deriveState(writeTarget);
|
|
354
|
+
assert(state.phase !== undefined, 'pipeline: deriveState returns phase');
|
|
355
|
+
assert(state.activeMilestone !== null, 'pipeline: deriveState has activeMilestone');
|
|
356
|
+
assertEq(state.activeMilestone!.id, 'M001', 'pipeline: deriveState activeMilestone is M001');
|
|
357
|
+
assert(state.progress.slices !== undefined, 'pipeline: deriveState has slices progress');
|
|
358
|
+
assert(state.progress.tasks !== undefined, 'pipeline: deriveState has tasks progress');
|
|
359
|
+
|
|
360
|
+
} finally {
|
|
361
|
+
rmSync(base, { recursive: true, force: true });
|
|
362
|
+
rmSync(writeTarget, { recursive: true, force: true });
|
|
363
|
+
}
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
// ─── Test 6: .gsd/ exists detection ────────────────────────────────────
|
|
367
|
+
console.log('\n=== .gsd/ exists detection ===');
|
|
368
|
+
{
|
|
369
|
+
const base = mkdtempSync(join(tmpdir(), 'gsd-cmd-exists-'));
|
|
370
|
+
try {
|
|
371
|
+
// No .gsd/ yet
|
|
372
|
+
assert(!existsSync(join(base, '.gsd')), 'exists-detection: .gsd absent initially');
|
|
373
|
+
|
|
374
|
+
// Create .gsd/
|
|
375
|
+
mkdirSync(join(base, '.gsd'), { recursive: true });
|
|
376
|
+
assert(existsSync(join(base, '.gsd')), 'exists-detection: .gsd detected after creation');
|
|
377
|
+
} finally {
|
|
378
|
+
rmSync(base, { recursive: true, force: true });
|
|
379
|
+
}
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
// ─── Results ─────────────────────────────────────────────────────────────
|
|
383
|
+
console.log(`\n${passed + failed} assertions: ${passed} passed, ${failed} failed`);
|
|
384
|
+
if (failed > 0) process.exit(1);
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
main().catch((err) => {
|
|
388
|
+
console.error('Unhandled error:', err);
|
|
389
|
+
process.exit(1);
|
|
390
|
+
});
|