gsd-pi 2.38.0-dev.eeb3520 → 2.39.0-dev.64cd3ed
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 +15 -11
- package/dist/app-paths.js +1 -1
- package/dist/cli.js +9 -0
- package/dist/extension-discovery.d.ts +5 -3
- package/dist/extension-discovery.js +14 -9
- package/dist/extension-registry.js +2 -2
- package/dist/remote-questions-config.js +2 -2
- package/dist/resource-loader.js +34 -1
- package/dist/resources/extensions/async-jobs/index.js +10 -0
- package/dist/resources/extensions/browser-tools/index.js +3 -1
- package/dist/resources/extensions/browser-tools/package.json +3 -1
- package/dist/resources/extensions/browser-tools/tools/verify.js +97 -0
- package/dist/resources/extensions/cmux/index.js +55 -1
- package/dist/resources/extensions/context7/package.json +1 -1
- package/dist/resources/extensions/env-utils.js +29 -0
- package/dist/resources/extensions/get-secrets-from-user.js +5 -24
- package/dist/resources/extensions/github-sync/cli.js +284 -0
- package/dist/resources/extensions/github-sync/index.js +73 -0
- package/dist/resources/extensions/github-sync/mapping.js +67 -0
- package/dist/resources/extensions/github-sync/sync.js +424 -0
- package/dist/resources/extensions/github-sync/templates.js +118 -0
- package/dist/resources/extensions/github-sync/types.js +7 -0
- package/dist/resources/extensions/google-search/package.json +3 -1
- package/dist/resources/extensions/gsd/auto/session.js +6 -23
- package/dist/resources/extensions/gsd/auto-dispatch.js +8 -9
- package/dist/resources/extensions/gsd/auto-loop.js +650 -588
- package/dist/resources/extensions/gsd/auto-post-unit.js +99 -70
- package/dist/resources/extensions/gsd/auto-prompts.js +202 -48
- package/dist/resources/extensions/gsd/auto-start.js +13 -2
- package/dist/resources/extensions/gsd/auto-worktree-sync.js +13 -5
- package/dist/resources/extensions/gsd/auto-worktree.js +3 -3
- package/dist/resources/extensions/gsd/auto.js +143 -96
- package/dist/resources/extensions/gsd/captures.js +9 -1
- package/dist/resources/extensions/gsd/commands-extensions.js +3 -2
- package/dist/resources/extensions/gsd/commands-handlers.js +16 -3
- package/dist/resources/extensions/gsd/commands-prefs-wizard.js +1 -1
- package/dist/resources/extensions/gsd/commands.js +24 -3
- package/dist/resources/extensions/gsd/context-budget.js +2 -10
- package/dist/resources/extensions/gsd/detection.js +1 -2
- package/dist/resources/extensions/gsd/docs/preferences-reference.md +0 -2
- package/dist/resources/extensions/gsd/doctor-checks.js +82 -0
- package/dist/resources/extensions/gsd/doctor-environment.js +78 -0
- package/dist/resources/extensions/gsd/doctor-format.js +15 -0
- package/dist/resources/extensions/gsd/doctor-providers.js +30 -11
- package/dist/resources/extensions/gsd/doctor.js +204 -12
- package/dist/resources/extensions/gsd/exit-command.js +2 -1
- package/dist/resources/extensions/gsd/export.js +1 -1
- package/dist/resources/extensions/gsd/files.js +48 -9
- package/dist/resources/extensions/gsd/forensics.js +1 -1
- package/dist/resources/extensions/gsd/git-service.js +30 -12
- package/dist/resources/extensions/gsd/gitignore.js +16 -3
- package/dist/resources/extensions/gsd/guided-flow.js +149 -38
- package/dist/resources/extensions/gsd/health-widget-core.js +32 -70
- package/dist/resources/extensions/gsd/health-widget.js +3 -86
- package/dist/resources/extensions/gsd/index.js +24 -20
- package/dist/resources/extensions/gsd/migrate/parsers.js +1 -1
- package/dist/resources/extensions/gsd/migrate-external.js +18 -1
- package/dist/resources/extensions/gsd/native-git-bridge.js +37 -0
- package/dist/resources/extensions/gsd/package.json +1 -1
- package/dist/resources/extensions/gsd/paths.js +3 -0
- package/dist/resources/extensions/gsd/preferences-models.js +0 -12
- package/dist/resources/extensions/gsd/preferences-types.js +1 -1
- package/dist/resources/extensions/gsd/preferences-validation.js +59 -11
- package/dist/resources/extensions/gsd/preferences.js +22 -11
- package/dist/resources/extensions/gsd/prompt-loader.js +6 -2
- package/dist/resources/extensions/gsd/prompts/complete-milestone.md +1 -1
- package/dist/resources/extensions/gsd/prompts/complete-slice.md +1 -1
- package/dist/resources/extensions/gsd/prompts/discuss.md +11 -14
- package/dist/resources/extensions/gsd/prompts/execute-task.md +5 -3
- package/dist/resources/extensions/gsd/prompts/guided-complete-slice.md +1 -1
- package/dist/resources/extensions/gsd/prompts/guided-discuss-milestone.md +11 -12
- package/dist/resources/extensions/gsd/prompts/guided-discuss-slice.md +8 -10
- package/dist/resources/extensions/gsd/prompts/guided-execute-task.md +1 -1
- package/dist/resources/extensions/gsd/prompts/guided-plan-milestone.md +1 -1
- package/dist/resources/extensions/gsd/prompts/guided-plan-slice.md +1 -1
- package/dist/resources/extensions/gsd/prompts/guided-research-slice.md +1 -1
- package/dist/resources/extensions/gsd/prompts/guided-resume-task.md +1 -1
- package/dist/resources/extensions/gsd/prompts/plan-milestone.md +1 -1
- package/dist/resources/extensions/gsd/prompts/plan-slice.md +1 -1
- package/dist/resources/extensions/gsd/prompts/queue.md +4 -8
- package/dist/resources/extensions/gsd/prompts/reactive-execute.md +11 -8
- package/dist/resources/extensions/gsd/prompts/reassess-roadmap.md +1 -1
- package/dist/resources/extensions/gsd/prompts/research-milestone.md +1 -1
- package/dist/resources/extensions/gsd/prompts/research-slice.md +1 -1
- package/dist/resources/extensions/gsd/prompts/run-uat.md +28 -11
- package/dist/resources/extensions/gsd/prompts/workflow-start.md +2 -2
- package/dist/resources/extensions/gsd/repo-identity.js +21 -4
- package/dist/resources/extensions/gsd/resource-version.js +2 -1
- package/dist/resources/extensions/gsd/roadmap-mutations.js +24 -0
- package/dist/resources/extensions/gsd/state.js +42 -23
- package/dist/resources/extensions/gsd/templates/runtime.md +21 -0
- package/dist/resources/extensions/gsd/templates/task-plan.md +3 -0
- package/dist/resources/extensions/gsd/visualizer-data.js +1 -1
- package/dist/resources/extensions/gsd/worktree.js +35 -16
- package/dist/resources/extensions/mcp-client/index.js +14 -1
- package/dist/resources/extensions/remote-questions/status.js +4 -1
- package/dist/resources/extensions/remote-questions/store.js +4 -1
- package/dist/resources/extensions/search-the-web/provider.js +2 -1
- package/dist/resources/extensions/shared/frontmatter.js +1 -1
- package/dist/resources/extensions/subagent/index.js +12 -3
- package/dist/resources/extensions/subagent/isolation.js +2 -1
- package/dist/resources/extensions/ttsr/rule-loader.js +2 -1
- package/dist/resources/extensions/universal-config/package.json +1 -1
- package/dist/welcome-screen.d.ts +12 -0
- package/dist/welcome-screen.js +53 -0
- package/package.json +1 -1
- package/packages/pi-ai/dist/utils/oauth/anthropic.js +2 -2
- package/packages/pi-ai/dist/utils/oauth/anthropic.js.map +1 -1
- package/packages/pi-ai/src/utils/oauth/anthropic.ts +2 -2
- package/packages/pi-coding-agent/dist/core/extensions/loader.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/extensions/loader.js +205 -7
- package/packages/pi-coding-agent/dist/core/extensions/loader.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/package-manager.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/package-manager.js +8 -4
- package/packages/pi-coding-agent/dist/core/package-manager.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/skills.d.ts +1 -0
- package/packages/pi-coding-agent/dist/core/skills.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/skills.js +6 -1
- package/packages/pi-coding-agent/dist/core/skills.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/package.json +1 -1
- package/packages/pi-coding-agent/src/core/extensions/loader.ts +223 -7
- package/packages/pi-coding-agent/src/core/package-manager.ts +8 -4
- package/packages/pi-coding-agent/src/core/skills.ts +9 -1
- package/packages/pi-coding-agent/src/index.ts +1 -0
- package/pkg/package.json +1 -1
- package/src/resources/extensions/async-jobs/index.ts +11 -0
- package/src/resources/extensions/browser-tools/index.ts +3 -0
- package/src/resources/extensions/browser-tools/tools/verify.ts +117 -0
- package/src/resources/extensions/cmux/index.ts +57 -1
- package/src/resources/extensions/env-utils.ts +31 -0
- package/src/resources/extensions/get-secrets-from-user.ts +5 -24
- package/src/resources/extensions/github-sync/cli.ts +364 -0
- package/src/resources/extensions/github-sync/index.ts +93 -0
- package/src/resources/extensions/github-sync/mapping.ts +81 -0
- package/src/resources/extensions/github-sync/sync.ts +556 -0
- package/src/resources/extensions/github-sync/templates.ts +183 -0
- package/src/resources/extensions/github-sync/tests/cli.test.ts +20 -0
- package/src/resources/extensions/github-sync/tests/commit-linking.test.ts +39 -0
- package/src/resources/extensions/github-sync/tests/mapping.test.ts +104 -0
- package/src/resources/extensions/github-sync/tests/templates.test.ts +110 -0
- package/src/resources/extensions/github-sync/types.ts +47 -0
- package/src/resources/extensions/gsd/auto/session.ts +7 -25
- package/src/resources/extensions/gsd/auto-dispatch.ts +7 -9
- package/src/resources/extensions/gsd/auto-loop.ts +553 -546
- package/src/resources/extensions/gsd/auto-post-unit.ts +80 -44
- package/src/resources/extensions/gsd/auto-prompts.ts +247 -50
- package/src/resources/extensions/gsd/auto-start.ts +18 -2
- package/src/resources/extensions/gsd/auto-worktree-sync.ts +15 -4
- package/src/resources/extensions/gsd/auto-worktree.ts +3 -3
- package/src/resources/extensions/gsd/auto.ts +139 -101
- package/src/resources/extensions/gsd/captures.ts +10 -1
- package/src/resources/extensions/gsd/commands-extensions.ts +4 -2
- package/src/resources/extensions/gsd/commands-handlers.ts +17 -2
- package/src/resources/extensions/gsd/commands-prefs-wizard.ts +1 -1
- package/src/resources/extensions/gsd/commands.ts +26 -4
- package/src/resources/extensions/gsd/context-budget.ts +2 -12
- package/src/resources/extensions/gsd/detection.ts +2 -2
- package/src/resources/extensions/gsd/docs/preferences-reference.md +0 -2
- package/src/resources/extensions/gsd/doctor-checks.ts +75 -0
- package/src/resources/extensions/gsd/doctor-environment.ts +82 -1
- package/src/resources/extensions/gsd/doctor-format.ts +20 -0
- package/src/resources/extensions/gsd/doctor-providers.ts +30 -9
- package/src/resources/extensions/gsd/doctor-types.ts +16 -1
- package/src/resources/extensions/gsd/doctor.ts +199 -14
- package/src/resources/extensions/gsd/exit-command.ts +2 -2
- package/src/resources/extensions/gsd/export.ts +1 -1
- package/src/resources/extensions/gsd/files.ts +51 -11
- package/src/resources/extensions/gsd/forensics.ts +1 -1
- package/src/resources/extensions/gsd/git-service.ts +44 -10
- package/src/resources/extensions/gsd/gitignore.ts +17 -3
- package/src/resources/extensions/gsd/guided-flow.ts +177 -44
- package/src/resources/extensions/gsd/health-widget-core.ts +28 -80
- package/src/resources/extensions/gsd/health-widget.ts +3 -89
- package/src/resources/extensions/gsd/index.ts +24 -17
- package/src/resources/extensions/gsd/migrate/parsers.ts +1 -1
- package/src/resources/extensions/gsd/migrate-external.ts +18 -1
- package/src/resources/extensions/gsd/native-git-bridge.ts +37 -0
- package/src/resources/extensions/gsd/paths.ts +4 -0
- package/src/resources/extensions/gsd/preferences-models.ts +0 -12
- package/src/resources/extensions/gsd/preferences-types.ts +4 -4
- package/src/resources/extensions/gsd/preferences-validation.ts +51 -11
- package/src/resources/extensions/gsd/preferences.ts +25 -11
- package/src/resources/extensions/gsd/prompt-loader.ts +7 -2
- package/src/resources/extensions/gsd/prompts/complete-milestone.md +1 -1
- package/src/resources/extensions/gsd/prompts/complete-slice.md +1 -1
- package/src/resources/extensions/gsd/prompts/discuss.md +11 -14
- package/src/resources/extensions/gsd/prompts/execute-task.md +5 -3
- package/src/resources/extensions/gsd/prompts/guided-complete-slice.md +1 -1
- package/src/resources/extensions/gsd/prompts/guided-discuss-milestone.md +11 -12
- package/src/resources/extensions/gsd/prompts/guided-discuss-slice.md +8 -10
- package/src/resources/extensions/gsd/prompts/guided-execute-task.md +1 -1
- package/src/resources/extensions/gsd/prompts/guided-plan-milestone.md +1 -1
- package/src/resources/extensions/gsd/prompts/guided-plan-slice.md +1 -1
- package/src/resources/extensions/gsd/prompts/guided-research-slice.md +1 -1
- package/src/resources/extensions/gsd/prompts/guided-resume-task.md +1 -1
- package/src/resources/extensions/gsd/prompts/plan-milestone.md +1 -1
- package/src/resources/extensions/gsd/prompts/plan-slice.md +1 -1
- package/src/resources/extensions/gsd/prompts/queue.md +4 -8
- package/src/resources/extensions/gsd/prompts/reactive-execute.md +11 -8
- package/src/resources/extensions/gsd/prompts/reassess-roadmap.md +1 -1
- package/src/resources/extensions/gsd/prompts/research-milestone.md +1 -1
- package/src/resources/extensions/gsd/prompts/research-slice.md +1 -1
- package/src/resources/extensions/gsd/prompts/run-uat.md +28 -11
- package/src/resources/extensions/gsd/prompts/workflow-start.md +2 -2
- package/src/resources/extensions/gsd/repo-identity.ts +23 -4
- package/src/resources/extensions/gsd/resource-version.ts +3 -1
- package/src/resources/extensions/gsd/roadmap-mutations.ts +29 -0
- package/src/resources/extensions/gsd/state.ts +39 -21
- package/src/resources/extensions/gsd/templates/runtime.md +21 -0
- package/src/resources/extensions/gsd/templates/task-plan.md +3 -0
- package/src/resources/extensions/gsd/tests/agent-end-retry.test.ts +21 -18
- package/src/resources/extensions/gsd/tests/auto-loop.test.ts +122 -68
- package/src/resources/extensions/gsd/tests/auto-worktree-milestone-merge.test.ts +4 -3
- package/src/resources/extensions/gsd/tests/cmux.test.ts +93 -0
- package/src/resources/extensions/gsd/tests/derive-state.test.ts +43 -0
- package/src/resources/extensions/gsd/tests/doctor-enhancements.test.ts +266 -0
- package/src/resources/extensions/gsd/tests/doctor-providers.test.ts +86 -3
- package/src/resources/extensions/gsd/tests/gitignore-tracked-gsd.test.ts +50 -0
- package/src/resources/extensions/gsd/tests/health-widget.test.ts +16 -54
- package/src/resources/extensions/gsd/tests/parsers.test.ts +131 -14
- package/src/resources/extensions/gsd/tests/plan-slice-prompt.test.ts +209 -0
- package/src/resources/extensions/gsd/tests/preferences.test.ts +2 -7
- package/src/resources/extensions/gsd/tests/prompt-contracts.test.ts +59 -0
- package/src/resources/extensions/gsd/tests/repo-identity-worktree.test.ts +21 -1
- package/src/resources/extensions/gsd/tests/run-uat.test.ts +16 -4
- package/src/resources/extensions/gsd/tests/skill-activation.test.ts +140 -0
- package/src/resources/extensions/gsd/tests/worktree.test.ts +47 -0
- package/src/resources/extensions/gsd/types.ts +18 -1
- package/src/resources/extensions/gsd/verification-evidence.ts +16 -0
- package/src/resources/extensions/gsd/visualizer-data.ts +1 -1
- package/src/resources/extensions/gsd/worktree.ts +35 -15
- package/src/resources/extensions/mcp-client/index.ts +17 -1
- package/src/resources/extensions/remote-questions/status.ts +5 -1
- package/src/resources/extensions/remote-questions/store.ts +5 -1
- package/src/resources/extensions/search-the-web/provider.ts +2 -1
- package/src/resources/extensions/shared/frontmatter.ts +1 -1
- package/src/resources/extensions/subagent/index.ts +12 -3
- package/src/resources/extensions/subagent/isolation.ts +3 -1
- package/src/resources/extensions/ttsr/rule-loader.ts +3 -1
- package/dist/resources/extensions/gsd/prompt-compressor.js +0 -393
- package/dist/resources/extensions/gsd/semantic-chunker.js +0 -254
- package/dist/resources/extensions/gsd/summary-distiller.js +0 -212
- package/src/resources/extensions/gsd/prompt-compressor.ts +0 -508
- package/src/resources/extensions/gsd/semantic-chunker.ts +0 -336
- package/src/resources/extensions/gsd/summary-distiller.ts +0 -258
- package/src/resources/extensions/gsd/tests/context-compression.test.ts +0 -193
- package/src/resources/extensions/gsd/tests/prompt-compressor.test.ts +0 -529
- package/src/resources/extensions/gsd/tests/semantic-chunker.test.ts +0 -426
- package/src/resources/extensions/gsd/tests/summary-distiller.test.ts +0 -323
- package/src/resources/extensions/gsd/tests/token-optimization-benchmark.test.ts +0 -1272
- package/src/resources/extensions/gsd/tests/token-optimization-prefs.test.ts +0 -164
|
@@ -0,0 +1,266 @@
|
|
|
1
|
+
import { mkdtempSync, mkdirSync, rmSync, writeFileSync, existsSync } from "node:fs";
|
|
2
|
+
import { join } from "node:path";
|
|
3
|
+
import { tmpdir } from "node:os";
|
|
4
|
+
|
|
5
|
+
import { runGSDDoctor } from "../doctor.js";
|
|
6
|
+
import { formatDoctorReportJson } from "../doctor-format.js";
|
|
7
|
+
import { createTestContext } from "./test-helpers.ts";
|
|
8
|
+
|
|
9
|
+
const { assertEq, assertTrue, assertMatch, report } = createTestContext();
|
|
10
|
+
|
|
11
|
+
// ── Helpers ─────────────────────────────────────────────────────────────────
|
|
12
|
+
|
|
13
|
+
function makeBase(): { base: string; gsd: string; mDir: string } {
|
|
14
|
+
const base = mkdtempSync(join(tmpdir(), "gsd-doctor-enh-"));
|
|
15
|
+
const gsd = join(base, ".gsd");
|
|
16
|
+
const mDir = join(gsd, "milestones", "M001");
|
|
17
|
+
mkdirSync(join(mDir, "slices"), { recursive: true });
|
|
18
|
+
return { base, gsd, mDir };
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
function writeRoadmap(mDir: string, content: string): void {
|
|
22
|
+
writeFileSync(join(mDir, "M001-ROADMAP.md"), content);
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
function writeSlice(mDir: string, sliceId: string, planContent: string): string {
|
|
26
|
+
const sDir = join(mDir, "slices", sliceId);
|
|
27
|
+
const tDir = join(sDir, "tasks");
|
|
28
|
+
mkdirSync(tDir, { recursive: true });
|
|
29
|
+
writeFileSync(join(sDir, `${sliceId}-PLAN.md`), planContent);
|
|
30
|
+
return sDir;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
async function main(): Promise<void> {
|
|
34
|
+
// ── 1. Circular dependency detection ──────────────────────────────────────
|
|
35
|
+
console.log("\n=== circular dependency detection ===");
|
|
36
|
+
{
|
|
37
|
+
const { base, mDir } = makeBase();
|
|
38
|
+
writeRoadmap(mDir, `# M001: Circular Test\n\n## Slices\n- [ ] **S01: Slice A** \`risk:low\` \`depends:[S02]\`\n > After this: done\n- [ ] **S02: Slice B** \`risk:low\` \`depends:[S01]\`\n > After this: done\n`);
|
|
39
|
+
writeSlice(mDir, "S01", "# S01: Slice A\n\n**Goal:** A\n**Demo:** A\n\n## Tasks\n- [ ] **T01: Task** `est:10m`\n Pending.\n");
|
|
40
|
+
writeSlice(mDir, "S02", "# S02: Slice B\n\n**Goal:** B\n**Demo:** B\n\n## Tasks\n- [ ] **T01: Task** `est:10m`\n Pending.\n");
|
|
41
|
+
|
|
42
|
+
const result = await runGSDDoctor(base, { fix: false });
|
|
43
|
+
assertTrue(
|
|
44
|
+
result.issues.some(i => i.code === "circular_slice_dependency"),
|
|
45
|
+
"detects circular dependency S01 → S02 → S01",
|
|
46
|
+
);
|
|
47
|
+
rmSync(base, { recursive: true, force: true });
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
// ── 2. Duplicate task IDs ──────────────────────────────────────────────────
|
|
51
|
+
console.log("\n=== duplicate task IDs ===");
|
|
52
|
+
{
|
|
53
|
+
const { base, mDir } = makeBase();
|
|
54
|
+
writeRoadmap(mDir, `# M001: Dup Test\n\n## Slices\n- [ ] **S01: Slice** \`risk:low\` \`depends:[]\`\n > After this: done\n`);
|
|
55
|
+
writeSlice(mDir, "S01", "# S01: Slice\n\n**Goal:** G\n**Demo:** D\n\n## Tasks\n- [ ] **T01: First** `est:10m`\n Task one.\n- [ ] **T01: Duplicate** `est:10m`\n Task dup.\n");
|
|
56
|
+
|
|
57
|
+
const result = await runGSDDoctor(base, { fix: false });
|
|
58
|
+
assertTrue(
|
|
59
|
+
result.issues.some(i => i.code === "duplicate_task_id"),
|
|
60
|
+
"detects duplicate task ID T01",
|
|
61
|
+
);
|
|
62
|
+
rmSync(base, { recursive: true, force: true });
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
// ── 3. Orphaned slice directory ──────────────────────────────────────────
|
|
66
|
+
console.log("\n=== orphaned slice directory ===");
|
|
67
|
+
{
|
|
68
|
+
const { base, mDir } = makeBase();
|
|
69
|
+
writeRoadmap(mDir, `# M001: Orphan Test\n\n## Slices\n- [ ] **S01: Slice** \`risk:low\` \`depends:[]\`\n > After this: done\n`);
|
|
70
|
+
writeSlice(mDir, "S01", "# S01: Slice\n\n**Goal:** G\n**Demo:** D\n\n## Tasks\n- [ ] **T01: Task** `est:10m`\n Pending.\n");
|
|
71
|
+
// Create an extra slice directory not in roadmap
|
|
72
|
+
mkdirSync(join(mDir, "slices", "S99"), { recursive: true });
|
|
73
|
+
|
|
74
|
+
const result = await runGSDDoctor(base, { fix: false });
|
|
75
|
+
assertTrue(
|
|
76
|
+
result.issues.some(i => i.code === "orphaned_slice_directory" && i.message.includes("S99")),
|
|
77
|
+
"detects orphaned slice directory S99",
|
|
78
|
+
);
|
|
79
|
+
rmSync(base, { recursive: true, force: true });
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
// ── 4. Task file not in plan ───────────────────────────────────────────────
|
|
83
|
+
console.log("\n=== task file not in plan ===");
|
|
84
|
+
{
|
|
85
|
+
const { base, mDir } = makeBase();
|
|
86
|
+
writeRoadmap(mDir, `# M001: Extra Task Test\n\n## Slices\n- [ ] **S01: Slice** \`risk:low\` \`depends:[]\`\n > After this: done\n`);
|
|
87
|
+
const sDir = writeSlice(mDir, "S01", "# S01: Slice\n\n**Goal:** G\n**Demo:** D\n\n## Tasks\n- [x] **T01: Task** `est:10m`\n Done.\n");
|
|
88
|
+
// T01 summary (matches plan)
|
|
89
|
+
writeFileSync(join(sDir, "tasks", "T01-SUMMARY.md"), "---\nstatus: done\n---\n# T01\nDone.\n");
|
|
90
|
+
// T99 summary (NOT in plan)
|
|
91
|
+
writeFileSync(join(sDir, "tasks", "T99-SUMMARY.md"), "---\nstatus: done\n---\n# T99\nExtra.\n");
|
|
92
|
+
|
|
93
|
+
const result = await runGSDDoctor(base, { fix: false });
|
|
94
|
+
assertTrue(
|
|
95
|
+
result.issues.some(i => i.code === "task_file_not_in_plan" && i.message.includes("T99")),
|
|
96
|
+
"detects task summary T99 not in plan",
|
|
97
|
+
);
|
|
98
|
+
rmSync(base, { recursive: true, force: true });
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
// ── 5. Stale REPLAN file ────────────────────────────────────────────────────
|
|
102
|
+
console.log("\n=== stale REPLAN detection ===");
|
|
103
|
+
{
|
|
104
|
+
const { base, mDir } = makeBase();
|
|
105
|
+
writeRoadmap(mDir, `# M001: Replan Test\n\n## Slices\n- [ ] **S01: Slice** \`risk:low\` \`depends:[]\`\n > After this: done\n`);
|
|
106
|
+
const sDir = writeSlice(mDir, "S01", "# S01: Slice\n\n**Goal:** G\n**Demo:** D\n\n## Tasks\n- [x] **T01: Task** `est:10m`\n Done.\n");
|
|
107
|
+
writeFileSync(join(sDir, "tasks", "T01-SUMMARY.md"), "---\nstatus: done\ncompleted_at: 2026-01-01T00:00:00Z\n---\n# T01\nDone.\n");
|
|
108
|
+
// Add a REPLAN file even though all tasks are done
|
|
109
|
+
writeFileSync(join(sDir, "S01-REPLAN.md"), "# S01 REPLAN\nSomething changed.\n");
|
|
110
|
+
|
|
111
|
+
const result = await runGSDDoctor(base, { fix: false });
|
|
112
|
+
assertTrue(
|
|
113
|
+
result.issues.some(i => i.code === "stale_replan_file"),
|
|
114
|
+
"detects stale REPLAN when all tasks are done",
|
|
115
|
+
);
|
|
116
|
+
rmSync(base, { recursive: true, force: true });
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
// ── 6. Metrics ledger corrupt ───────────────────────────────────────────────
|
|
120
|
+
console.log("\n=== metrics ledger corrupt ===");
|
|
121
|
+
{
|
|
122
|
+
const { base, gsd, mDir } = makeBase();
|
|
123
|
+
writeRoadmap(mDir, `# M001: Metrics Test\n\n## Slices\n- [ ] **S01: Slice** \`risk:low\` \`depends:[]\`\n > After this: done\n`);
|
|
124
|
+
writeSlice(mDir, "S01", "# S01: Slice\n\n**Goal:** G\n**Demo:** D\n\n## Tasks\n- [ ] **T01: Task** `est:10m`\n Pending.\n");
|
|
125
|
+
// Write invalid metrics.json
|
|
126
|
+
writeFileSync(join(gsd, "metrics.json"), '{"version":2,"data":[]}');
|
|
127
|
+
|
|
128
|
+
const result = await runGSDDoctor(base, { fix: false });
|
|
129
|
+
assertTrue(
|
|
130
|
+
result.issues.some(i => i.code === "metrics_ledger_corrupt"),
|
|
131
|
+
"detects corrupt metrics ledger (version != 1)",
|
|
132
|
+
);
|
|
133
|
+
rmSync(base, { recursive: true, force: true });
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
// ── 7. Large planning file ──────────────────────────────────────────────────
|
|
137
|
+
console.log("\n=== large planning file ===");
|
|
138
|
+
{
|
|
139
|
+
const { base, mDir } = makeBase();
|
|
140
|
+
writeRoadmap(mDir, `# M001: Large File Test\n\n## Slices\n- [ ] **S01: Slice** \`risk:low\` \`depends:[]\`\n > After this: done\n`);
|
|
141
|
+
const sDir = writeSlice(mDir, "S01", "# S01: Slice\n\n**Goal:** G\n**Demo:** D\n\n## Tasks\n- [ ] **T01: Task** `est:10m`\n Pending.\n");
|
|
142
|
+
// Write a 101KB .md file
|
|
143
|
+
const bigContent = "# Big File\n" + "x".repeat(101 * 1024);
|
|
144
|
+
writeFileSync(join(sDir, "BIGFILE.md"), bigContent);
|
|
145
|
+
|
|
146
|
+
const result = await runGSDDoctor(base, { fix: false });
|
|
147
|
+
assertTrue(
|
|
148
|
+
result.issues.some(i => i.code === "large_planning_file"),
|
|
149
|
+
"detects large planning file over 100KB",
|
|
150
|
+
);
|
|
151
|
+
rmSync(base, { recursive: true, force: true });
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
// ── 8. Future timestamp ─────────────────────────────────────────────────────
|
|
155
|
+
console.log("\n=== future timestamp ===");
|
|
156
|
+
{
|
|
157
|
+
const { base, mDir } = makeBase();
|
|
158
|
+
writeRoadmap(mDir, `# M001: Timestamp Test\n\n## Slices\n- [ ] **S01: Slice** \`risk:low\` \`depends:[]\`\n > After this: done\n`);
|
|
159
|
+
const sDir = writeSlice(mDir, "S01", "# S01: Slice\n\n**Goal:** G\n**Demo:** D\n\n## Tasks\n- [x] **T01: Task** `est:10m`\n Done.\n");
|
|
160
|
+
// completed_at is 2 days in the future
|
|
161
|
+
const futureDate = new Date(Date.now() + 2 * 24 * 60 * 60 * 1000).toISOString();
|
|
162
|
+
writeFileSync(
|
|
163
|
+
join(sDir, "tasks", "T01-SUMMARY.md"),
|
|
164
|
+
`---\nstatus: done\ncompleted_at: ${futureDate}\n---\n# T01\nDone.\n`,
|
|
165
|
+
);
|
|
166
|
+
|
|
167
|
+
const result = await runGSDDoctor(base, { fix: false });
|
|
168
|
+
assertTrue(
|
|
169
|
+
result.issues.some(i => i.code === "future_timestamp"),
|
|
170
|
+
"detects future completed_at timestamp",
|
|
171
|
+
);
|
|
172
|
+
rmSync(base, { recursive: true, force: true });
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
// ── 9. JSON output format ───────────────────────────────────────────────────
|
|
176
|
+
console.log("\n=== JSON output format ===");
|
|
177
|
+
{
|
|
178
|
+
const { base, mDir } = makeBase();
|
|
179
|
+
writeRoadmap(mDir, `# M001: JSON Test\n\n## Slices\n- [ ] **S01: Slice** \`risk:low\` \`depends:[]\`\n > After this: done\n`);
|
|
180
|
+
writeSlice(mDir, "S01", "# S01: Slice\n\n**Goal:** G\n**Demo:** D\n\n## Tasks\n- [ ] **T01: Task** `est:10m`\n Pending.\n");
|
|
181
|
+
|
|
182
|
+
const result = await runGSDDoctor(base, { fix: false });
|
|
183
|
+
const json = formatDoctorReportJson(result);
|
|
184
|
+
|
|
185
|
+
let parsed: unknown;
|
|
186
|
+
try {
|
|
187
|
+
parsed = JSON.parse(json);
|
|
188
|
+
} catch {
|
|
189
|
+
parsed = null;
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
assertTrue(parsed !== null, "formatDoctorReportJson produces valid JSON");
|
|
193
|
+
assertTrue(typeof (parsed as Record<string, unknown>)?.ok === "boolean", "JSON has ok field");
|
|
194
|
+
assertTrue(Array.isArray((parsed as Record<string, unknown>)?.issues), "JSON has issues array");
|
|
195
|
+
assertTrue(Array.isArray((parsed as Record<string, unknown>)?.fixesApplied), "JSON has fixesApplied array");
|
|
196
|
+
assertTrue(typeof (parsed as Record<string, unknown>)?.generatedAt === "string", "JSON has generatedAt field");
|
|
197
|
+
assertTrue(typeof (parsed as Record<string, unknown>)?.summary === "object", "JSON has summary object");
|
|
198
|
+
|
|
199
|
+
rmSync(base, { recursive: true, force: true });
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
// ── 10. Dry-run mode ────────────────────────────────────────────────────────
|
|
203
|
+
console.log("\n=== dry-run mode ===");
|
|
204
|
+
{
|
|
205
|
+
const { base, mDir } = makeBase();
|
|
206
|
+
writeRoadmap(mDir, `# M001: Dry Run Test\n\n## Slices\n- [ ] **S01: Slice** \`risk:low\` \`depends:[]\`\n > After this: done\n`);
|
|
207
|
+
const sDir = writeSlice(mDir, "S01", "# S01: Slice\n\n**Goal:** G\n**Demo:** D\n\n## Tasks\n- [x] **T01: Task** `est:10m`\n Done.\n");
|
|
208
|
+
|
|
209
|
+
const result = await runGSDDoctor(base, { fix: true, dryRun: true });
|
|
210
|
+
// In dry-run mode, no actual files should be created
|
|
211
|
+
assertTrue(!existsSync(join(sDir, "S01-SUMMARY.md")), "dry-run does not create slice summary");
|
|
212
|
+
assertTrue(
|
|
213
|
+
result.fixesApplied.some(f => f.startsWith("[dry-run]")),
|
|
214
|
+
"dry-run mode reports would-fix entries",
|
|
215
|
+
);
|
|
216
|
+
|
|
217
|
+
rmSync(base, { recursive: true, force: true });
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
// ── 11. Per-check timing ─────────────────────────────────────────────────────
|
|
221
|
+
console.log("\n=== per-check timing ===");
|
|
222
|
+
{
|
|
223
|
+
const { base, mDir } = makeBase();
|
|
224
|
+
writeRoadmap(mDir, `# M001: Timing Test\n\n## Slices\n- [ ] **S01: Slice** \`risk:low\` \`depends:[]\`\n > After this: done\n`);
|
|
225
|
+
writeSlice(mDir, "S01", "# S01: Slice\n\n**Goal:** G\n**Demo:** D\n\n## Tasks\n- [ ] **T01: Task** `est:10m`\n Pending.\n");
|
|
226
|
+
|
|
227
|
+
const result = await runGSDDoctor(base, { fix: false });
|
|
228
|
+
assertTrue(result.timing !== undefined, "report includes timing");
|
|
229
|
+
assertTrue(typeof result.timing?.git === "number", "timing.git is a number");
|
|
230
|
+
assertTrue(typeof result.timing?.runtime === "number", "timing.runtime is a number");
|
|
231
|
+
assertTrue(typeof result.timing?.environment === "number", "timing.environment is a number");
|
|
232
|
+
assertTrue(typeof result.timing?.gsdState === "number", "timing.gsdState is a number");
|
|
233
|
+
|
|
234
|
+
rmSync(base, { recursive: true, force: true });
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
// ── 12. Doctor history ───────────────────────────────────────────────────────
|
|
238
|
+
console.log("\n=== doctor history ===");
|
|
239
|
+
{
|
|
240
|
+
const { base, gsd, mDir } = makeBase();
|
|
241
|
+
writeRoadmap(mDir, `# M001: History Test\n\n## Slices\n- [ ] **S01: Slice** \`risk:low\` \`depends:[]\`\n > After this: done\n`);
|
|
242
|
+
writeSlice(mDir, "S01", "# S01: Slice\n\n**Goal:** G\n**Demo:** D\n\n## Tasks\n- [ ] **T01: Task** `est:10m`\n Pending.\n");
|
|
243
|
+
|
|
244
|
+
await runGSDDoctor(base, { fix: false });
|
|
245
|
+
|
|
246
|
+
const historyPath = join(gsd, "doctor-history.jsonl");
|
|
247
|
+
assertTrue(existsSync(historyPath), "doctor-history.jsonl is created after run");
|
|
248
|
+
|
|
249
|
+
const { readDoctorHistory } = await import("../doctor.js");
|
|
250
|
+
const history = await readDoctorHistory(base);
|
|
251
|
+
assertTrue(history.length >= 1, "history has at least one entry");
|
|
252
|
+
assertTrue(typeof history[0]?.ts === "string", "history entry has ts field");
|
|
253
|
+
assertTrue(typeof history[0]?.ok === "boolean", "history entry has ok field");
|
|
254
|
+
assertTrue(typeof history[0]?.errors === "number", "history entry has errors count");
|
|
255
|
+
assertTrue(Array.isArray(history[0]?.codes), "history entry has codes array");
|
|
256
|
+
|
|
257
|
+
rmSync(base, { recursive: true, force: true });
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
report();
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
main().catch(err => {
|
|
264
|
+
console.error(err);
|
|
265
|
+
process.exit(1);
|
|
266
|
+
});
|
|
@@ -47,6 +47,18 @@ function withEnv(vars: Record<string, string | undefined>, fn: () => void): void
|
|
|
47
47
|
}
|
|
48
48
|
}
|
|
49
49
|
|
|
50
|
+
function withCwd(nextCwd: string, fn: () => void): void {
|
|
51
|
+
const saved = process.cwd();
|
|
52
|
+
process.chdir(nextCwd);
|
|
53
|
+
try {
|
|
54
|
+
fn();
|
|
55
|
+
} finally {
|
|
56
|
+
process.chdir(saved);
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
const PRESENT_TEST_VALUE = "configured";
|
|
61
|
+
|
|
50
62
|
// ─── formatProviderReport ─────────────────────────────────────────────────────
|
|
51
63
|
|
|
52
64
|
test("formatProviderReport returns fallback for empty results", () => {
|
|
@@ -312,7 +324,7 @@ test("runProviderChecks reports ok for Anthropic when GitHub Copilot env var is
|
|
|
312
324
|
withEnv({
|
|
313
325
|
ANTHROPIC_API_KEY: undefined,
|
|
314
326
|
ANTHROPIC_OAUTH_TOKEN: undefined,
|
|
315
|
-
COPILOT_GITHUB_TOKEN:
|
|
327
|
+
COPILOT_GITHUB_TOKEN: PRESENT_TEST_VALUE,
|
|
316
328
|
GH_TOKEN: undefined,
|
|
317
329
|
GITHUB_TOKEN: undefined,
|
|
318
330
|
HOME: tmpHome,
|
|
@@ -336,7 +348,7 @@ test("runProviderChecks reports ok for Anthropic via GITHUB_TOKEN cross-provider
|
|
|
336
348
|
ANTHROPIC_OAUTH_TOKEN: undefined,
|
|
337
349
|
COPILOT_GITHUB_TOKEN: undefined,
|
|
338
350
|
GH_TOKEN: undefined,
|
|
339
|
-
GITHUB_TOKEN:
|
|
351
|
+
GITHUB_TOKEN: PRESENT_TEST_VALUE,
|
|
340
352
|
HOME: tmpHome,
|
|
341
353
|
}, () => {
|
|
342
354
|
try {
|
|
@@ -354,7 +366,7 @@ test("runProviderChecks detects ANTHROPIC_OAUTH_TOKEN as valid Anthropic auth",
|
|
|
354
366
|
const tmpHome = realpathSync(mkdtempSync(join(tmpdir(), "gsd-providers-oauth-test-")));
|
|
355
367
|
withEnv({
|
|
356
368
|
ANTHROPIC_API_KEY: undefined,
|
|
357
|
-
ANTHROPIC_OAUTH_TOKEN:
|
|
369
|
+
ANTHROPIC_OAUTH_TOKEN: PRESENT_TEST_VALUE,
|
|
358
370
|
COPILOT_GITHUB_TOKEN: undefined,
|
|
359
371
|
GH_TOKEN: undefined,
|
|
360
372
|
GITHUB_TOKEN: undefined,
|
|
@@ -401,3 +413,74 @@ test("runProviderChecks reports ok via Copilot auth.json for Anthropic", () => {
|
|
|
401
413
|
rmSync(tmpHome, { recursive: true, force: true });
|
|
402
414
|
});
|
|
403
415
|
});
|
|
416
|
+
|
|
417
|
+
test("runProviderChecks uses provider-qualified anthropic-vertex model IDs", () => {
|
|
418
|
+
const tmpHome = realpathSync(mkdtempSync(join(tmpdir(), "gsd-providers-vertex-prefix-home-")));
|
|
419
|
+
const repo = realpathSync(mkdtempSync(join(tmpdir(), "gsd-providers-vertex-prefix-repo-")));
|
|
420
|
+
mkdirSync(join(repo, ".gsd"), { recursive: true });
|
|
421
|
+
writeFileSync(
|
|
422
|
+
join(repo, ".gsd", "preferences.md"),
|
|
423
|
+
[
|
|
424
|
+
"---",
|
|
425
|
+
"models:",
|
|
426
|
+
" execution: anthropic-vertex/claude-sonnet-4-6",
|
|
427
|
+
"---",
|
|
428
|
+
"",
|
|
429
|
+
].join("\n"),
|
|
430
|
+
);
|
|
431
|
+
|
|
432
|
+
withEnv({
|
|
433
|
+
HOME: tmpHome,
|
|
434
|
+
ANTHROPIC_API_KEY: undefined,
|
|
435
|
+
ANTHROPIC_OAUTH_TOKEN: undefined,
|
|
436
|
+
ANTHROPIC_VERTEX_PROJECT_ID: "vertex-project",
|
|
437
|
+
}, () => {
|
|
438
|
+
withCwd(repo, () => {
|
|
439
|
+
const results = runProviderChecks();
|
|
440
|
+
const vertex = results.find(r => r.name === "anthropic-vertex");
|
|
441
|
+
const anthropic = results.find(r => r.name === "anthropic");
|
|
442
|
+
assert.ok(vertex, "anthropic-vertex result should exist");
|
|
443
|
+
assert.equal(vertex!.status, "ok", "should accept ANTHROPIC_VERTEX_PROJECT_ID as configured");
|
|
444
|
+
assert.ok(!anthropic || !anthropic.required, "plain anthropic should not be required for anthropic-vertex config");
|
|
445
|
+
});
|
|
446
|
+
});
|
|
447
|
+
|
|
448
|
+
rmSync(repo, { recursive: true, force: true });
|
|
449
|
+
rmSync(tmpHome, { recursive: true, force: true });
|
|
450
|
+
});
|
|
451
|
+
|
|
452
|
+
test("runProviderChecks uses object provider field for anthropic-vertex models", () => {
|
|
453
|
+
const tmpHome = realpathSync(mkdtempSync(join(tmpdir(), "gsd-providers-vertex-provider-home-")));
|
|
454
|
+
const repo = realpathSync(mkdtempSync(join(tmpdir(), "gsd-providers-vertex-provider-repo-")));
|
|
455
|
+
mkdirSync(join(repo, ".gsd"), { recursive: true });
|
|
456
|
+
writeFileSync(
|
|
457
|
+
join(repo, ".gsd", "preferences.md"),
|
|
458
|
+
[
|
|
459
|
+
"---",
|
|
460
|
+
"models:",
|
|
461
|
+
" execution:",
|
|
462
|
+
" model: claude-sonnet-4-6",
|
|
463
|
+
" provider: anthropic-vertex",
|
|
464
|
+
"---",
|
|
465
|
+
"",
|
|
466
|
+
].join("\n"),
|
|
467
|
+
);
|
|
468
|
+
|
|
469
|
+
withEnv({
|
|
470
|
+
HOME: tmpHome,
|
|
471
|
+
ANTHROPIC_API_KEY: undefined,
|
|
472
|
+
ANTHROPIC_OAUTH_TOKEN: undefined,
|
|
473
|
+
ANTHROPIC_VERTEX_PROJECT_ID: undefined,
|
|
474
|
+
}, () => {
|
|
475
|
+
withCwd(repo, () => {
|
|
476
|
+
const results = runProviderChecks();
|
|
477
|
+
const vertex = results.find(r => r.name === "anthropic-vertex");
|
|
478
|
+
assert.ok(vertex, "anthropic-vertex result should exist");
|
|
479
|
+
assert.equal(vertex!.status, "error", "missing vertex config should be reported against anthropic-vertex");
|
|
480
|
+
assert.ok(vertex!.detail?.includes("ANTHROPIC_VERTEX_PROJECT_ID"), "should point to vertex setup");
|
|
481
|
+
});
|
|
482
|
+
});
|
|
483
|
+
|
|
484
|
+
rmSync(repo, { recursive: true, force: true });
|
|
485
|
+
rmSync(tmpHome, { recursive: true, force: true });
|
|
486
|
+
});
|
|
@@ -183,6 +183,28 @@ test("ensureGitignore with tracked .gsd/ does not cause git to see files as dele
|
|
|
183
183
|
}
|
|
184
184
|
});
|
|
185
185
|
|
|
186
|
+
test("hasGitTrackedGsdFiles returns true (fail-safe) when git is not available", () => {
|
|
187
|
+
const dir = makeTempRepo();
|
|
188
|
+
try {
|
|
189
|
+
// Create and track .gsd/ files
|
|
190
|
+
mkdirSync(join(dir, ".gsd"), { recursive: true });
|
|
191
|
+
writeFileSync(join(dir, ".gsd", "PROJECT.md"), "# Project\n");
|
|
192
|
+
git(dir, "add", ".gsd/");
|
|
193
|
+
git(dir, "commit", "-m", "track gsd");
|
|
194
|
+
|
|
195
|
+
// Corrupt the git index to simulate git failure
|
|
196
|
+
const indexPath = join(dir, ".git", "index.lock");
|
|
197
|
+
writeFileSync(indexPath, "locked");
|
|
198
|
+
|
|
199
|
+
// Should fail safe — assume tracked rather than silently returning false
|
|
200
|
+
// (The index lock causes git ls-files to fail; rev-parse also fails → true)
|
|
201
|
+
const result = hasGitTrackedGsdFiles(dir);
|
|
202
|
+
assert.equal(result, true, "Should return true (fail-safe) when git is unavailable");
|
|
203
|
+
} finally {
|
|
204
|
+
cleanup(dir);
|
|
205
|
+
}
|
|
206
|
+
});
|
|
207
|
+
|
|
186
208
|
// ─── migrateToExternalState — tracked .gsd/ protection ──────────────
|
|
187
209
|
|
|
188
210
|
test("migrateToExternalState aborts when .gsd/ has tracked files (#1364)", () => {
|
|
@@ -212,3 +234,31 @@ test("migrateToExternalState aborts when .gsd/ has tracked files (#1364)", () =>
|
|
|
212
234
|
cleanup(dir);
|
|
213
235
|
}
|
|
214
236
|
});
|
|
237
|
+
|
|
238
|
+
test("migrateToExternalState cleans git index so tracked files don't show as deleted (#1364 path 2)", () => {
|
|
239
|
+
const dir = makeTempRepo();
|
|
240
|
+
try {
|
|
241
|
+
// Track .gsd/ files, then untrack them so migration proceeds
|
|
242
|
+
mkdirSync(join(dir, ".gsd", "milestones", "M001"), { recursive: true });
|
|
243
|
+
writeFileSync(join(dir, ".gsd", "PROJECT.md"), "# Project\n");
|
|
244
|
+
writeFileSync(join(dir, ".gsd", "milestones", "M001", "PLAN.md"), "# Plan\n");
|
|
245
|
+
git(dir, "add", ".gsd/");
|
|
246
|
+
git(dir, "commit", "-m", "track gsd state");
|
|
247
|
+
git(dir, "rm", "-r", "--cached", ".gsd/");
|
|
248
|
+
git(dir, "commit", "-m", "untrack gsd (simulates pre-migration project)");
|
|
249
|
+
|
|
250
|
+
const result = migrateToExternalState(dir);
|
|
251
|
+
assert.equal(result.migrated, true, "Migration should succeed");
|
|
252
|
+
|
|
253
|
+
// git status must show NO deleted files after migration
|
|
254
|
+
const status = git(dir, "status", "--porcelain");
|
|
255
|
+
const deletions = status.split("\n").filter((l) => /^\s*D\s/.test(l) || /^D\s/.test(l));
|
|
256
|
+
assert.equal(
|
|
257
|
+
deletions.length,
|
|
258
|
+
0,
|
|
259
|
+
`Expected no deleted files after migration, but found:\n${deletions.join("\n")}`,
|
|
260
|
+
);
|
|
261
|
+
} finally {
|
|
262
|
+
cleanup(dir);
|
|
263
|
+
}
|
|
264
|
+
});
|
|
@@ -80,66 +80,28 @@ test("buildHealthLines: initialized state shows continue setup copy", () => {
|
|
|
80
80
|
]);
|
|
81
81
|
});
|
|
82
82
|
|
|
83
|
-
test("buildHealthLines: active state
|
|
84
|
-
const lines = buildHealthLines(activeData({
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
milestones: { done: 0, total: 1 },
|
|
89
|
-
slices: { done: 0, total: 3 },
|
|
90
|
-
tasks: { done: 0, total: 5 },
|
|
91
|
-
},
|
|
92
|
-
}));
|
|
93
|
-
|
|
94
|
-
assert.equal(lines.length, 2);
|
|
95
|
-
assert.equal(lines[0], " GSD Executing - Plan S01");
|
|
96
|
-
assert.match(lines[1]!, /Progress: M 0\/1 · S 0\/3 · T 0\/5/);
|
|
97
|
-
});
|
|
98
|
-
|
|
99
|
-
test("buildHealthLines: active state keeps issues secondary", () => {
|
|
100
|
-
const lines = buildHealthLines(activeData({
|
|
101
|
-
executionStatus: "Planning",
|
|
102
|
-
executionTarget: "Execute T03",
|
|
103
|
-
providerIssue: "✗ Anthropic (Claude) key missing",
|
|
104
|
-
environmentWarningCount: 1,
|
|
105
|
-
budgetSpent: 0.42,
|
|
106
|
-
}));
|
|
107
|
-
|
|
108
|
-
assert.equal(lines.length, 2);
|
|
109
|
-
assert.equal(lines[0], " GSD Planning - Execute T03");
|
|
110
|
-
assert.match(lines[1]!, /✗ Anthropic \(Claude\) key missing/);
|
|
111
|
-
assert.match(lines[1]!, /Env: 1 warning/);
|
|
112
|
-
assert.match(lines[1]!, /Spent: 42\.0¢/);
|
|
113
|
-
});
|
|
114
|
-
|
|
115
|
-
test("buildHealthLines: blocked state explains wait reason", () => {
|
|
116
|
-
const lines = buildHealthLines(activeData({
|
|
117
|
-
executionStatus: "Blocked",
|
|
118
|
-
executionTarget: "waiting on unmet deps: M001",
|
|
119
|
-
blocker: "M002 is waiting on unmet deps: M001",
|
|
120
|
-
}));
|
|
121
|
-
|
|
122
|
-
assert.equal(lines[0], " GSD Blocked - waiting on unmet deps: M001");
|
|
83
|
+
test("buildHealthLines: active state with ledger-driven spend shows spent summary", () => {
|
|
84
|
+
const lines = buildHealthLines(activeData({ budgetSpent: 0.42 }));
|
|
85
|
+
assert.equal(lines.length, 1);
|
|
86
|
+
assert.match(lines[0]!, /● System OK/);
|
|
87
|
+
assert.match(lines[0]!, /Spent: 42\.0¢/);
|
|
123
88
|
});
|
|
124
89
|
|
|
125
|
-
test("buildHealthLines:
|
|
126
|
-
const lines = buildHealthLines(activeData({
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
}));
|
|
130
|
-
|
|
131
|
-
assert.deepEqual(lines, [" GSD Paused - waiting to resume"]);
|
|
90
|
+
test("buildHealthLines: active state with budget ceiling shows percent summary", () => {
|
|
91
|
+
const lines = buildHealthLines(activeData({ budgetSpent: 2.5, budgetCeiling: 10 }));
|
|
92
|
+
assert.equal(lines.length, 1);
|
|
93
|
+
assert.match(lines[0]!, /Budget: \$2\.50\/\$10\.00 \(25%\)/);
|
|
132
94
|
});
|
|
133
95
|
|
|
134
|
-
test("buildHealthLines: active state with
|
|
96
|
+
test("buildHealthLines: active state with issues reports issue summary", () => {
|
|
135
97
|
const lines = buildHealthLines(activeData({
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
budgetSpent: 2.5,
|
|
139
|
-
budgetCeiling: 10,
|
|
98
|
+
providerIssue: "✗ OpenAI key missing",
|
|
99
|
+
environmentErrorCount: 1,
|
|
140
100
|
}));
|
|
141
|
-
assert.equal(lines.length,
|
|
142
|
-
assert.match(lines[
|
|
101
|
+
assert.equal(lines.length, 1);
|
|
102
|
+
assert.match(lines[0]!, /✗ 2 issues/);
|
|
103
|
+
assert.match(lines[0]!, /✗ OpenAI key missing/);
|
|
104
|
+
assert.match(lines[0]!, /Env: 1 error/);
|
|
143
105
|
});
|
|
144
106
|
|
|
145
107
|
test("detectHealthWidgetProjectState: metrics file alone does not imply project", () => {
|