@ktpartners/dgs-platform 2.6.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +38 -0
- package/README.md +851 -0
- package/agents/dgs-codebase-cross-analyzer.md +183 -0
- package/agents/dgs-codebase-mapper.md +782 -0
- package/agents/dgs-codebase-synthesizer.md +156 -0
- package/agents/dgs-debugger.md +1256 -0
- package/agents/dgs-executor.md +550 -0
- package/agents/dgs-integration-checker.md +481 -0
- package/agents/dgs-nyquist-auditor.md +178 -0
- package/agents/dgs-phase-researcher.md +563 -0
- package/agents/dgs-phase-verifier.md +450 -0
- package/agents/dgs-plan-checker.md +708 -0
- package/agents/dgs-planner.md +1324 -0
- package/agents/dgs-project-researcher.md +631 -0
- package/agents/dgs-research-synthesizer.md +249 -0
- package/agents/dgs-roadmapper.md +652 -0
- package/agents/dgs-verifier.md +607 -0
- package/bin/install.js +2073 -0
- package/commands/dgs/add-doc.md +45 -0
- package/commands/dgs/add-idea.md +38 -0
- package/commands/dgs/add-phase.md +43 -0
- package/commands/dgs/add-repo.md +54 -0
- package/commands/dgs/add-tests.md +41 -0
- package/commands/dgs/add-todo.md +47 -0
- package/commands/dgs/approve-spec.md +38 -0
- package/commands/dgs/audit-milestone.md +36 -0
- package/commands/dgs/audit-phase.md +37 -0
- package/commands/dgs/cancel-job.md +23 -0
- package/commands/dgs/capture-principle.md +143 -0
- package/commands/dgs/check-todos.md +45 -0
- package/commands/dgs/cleanup.md +18 -0
- package/commands/dgs/complete-milestone.md +136 -0
- package/commands/dgs/complete-project.md +70 -0
- package/commands/dgs/consolidate-ideas.md +50 -0
- package/commands/dgs/create-milestone-job.md +37 -0
- package/commands/dgs/debug.md +164 -0
- package/commands/dgs/develop-idea.md +53 -0
- package/commands/dgs/discuss-idea.md +41 -0
- package/commands/dgs/discuss-phase.md +83 -0
- package/commands/dgs/execute-phase.md +41 -0
- package/commands/dgs/fast.md +38 -0
- package/commands/dgs/find-related-ideas.md +43 -0
- package/commands/dgs/health.md +28 -0
- package/commands/dgs/help.md +22 -0
- package/commands/dgs/import-spec.md +36 -0
- package/commands/dgs/init-product.md +28 -0
- package/commands/dgs/insert-phase.md +32 -0
- package/commands/dgs/join-discord.md +18 -0
- package/commands/dgs/list-docs.md +40 -0
- package/commands/dgs/list-ideas.md +42 -0
- package/commands/dgs/list-jobs.md +22 -0
- package/commands/dgs/list-phase-assumptions.md +46 -0
- package/commands/dgs/list-projects.md +57 -0
- package/commands/dgs/list-specs.md +40 -0
- package/commands/dgs/map-codebase.md +92 -0
- package/commands/dgs/new-milestone.md +44 -0
- package/commands/dgs/new-project.md +42 -0
- package/commands/dgs/node-repair.md +26 -0
- package/commands/dgs/overlap-check.md +20 -0
- package/commands/dgs/pause-work.md +38 -0
- package/commands/dgs/plan-milestone-gaps.md +34 -0
- package/commands/dgs/plan-phase.md +44 -0
- package/commands/dgs/progress.md +24 -0
- package/commands/dgs/quick.md +41 -0
- package/commands/dgs/reactivate-project.md +70 -0
- package/commands/dgs/reapply-patches.md +110 -0
- package/commands/dgs/refine-spec.md +38 -0
- package/commands/dgs/reject-idea.md +43 -0
- package/commands/dgs/remove-doc.md +44 -0
- package/commands/dgs/remove-phase.md +31 -0
- package/commands/dgs/remove-repo.md +69 -0
- package/commands/dgs/research-idea.md +43 -0
- package/commands/dgs/research-phase.md +189 -0
- package/commands/dgs/restore-idea.md +45 -0
- package/commands/dgs/resume-work.md +40 -0
- package/commands/dgs/rollback-job.md +24 -0
- package/commands/dgs/run-job.md +35 -0
- package/commands/dgs/search.md +40 -0
- package/commands/dgs/set-profile.md +34 -0
- package/commands/dgs/settings.md +38 -0
- package/commands/dgs/switch-project.md +58 -0
- package/commands/dgs/undo-consolidation.md +42 -0
- package/commands/dgs/update-idea.md +44 -0
- package/commands/dgs/update.md +37 -0
- package/commands/dgs/validate-phase.md +35 -0
- package/commands/dgs/verify-work.md +39 -0
- package/commands/dgs/write-spec.md +49 -0
- package/deliver-great-systems/.planning/phases/09-backend-wiring-and-error-handling/09-01-SUMMARY.md +84 -0
- package/deliver-great-systems/.planning/phases/09-backend-wiring-and-error-handling/09-02-SUMMARY.md +86 -0
- package/deliver-great-systems/.planning/phases/10-v1-to-v2-migration-flow/10-01-SUMMARY.md +85 -0
- package/deliver-great-systems/bin/dgs-tools.cjs +1444 -0
- package/deliver-great-systems/bin/lib/auto-test.cjs +1365 -0
- package/deliver-great-systems/bin/lib/commands.cjs +570 -0
- package/deliver-great-systems/bin/lib/config.cjs +417 -0
- package/deliver-great-systems/bin/lib/conflict-agent.cjs +1063 -0
- package/deliver-great-systems/bin/lib/conflict-agent.test.cjs +554 -0
- package/deliver-great-systems/bin/lib/context.cjs +929 -0
- package/deliver-great-systems/bin/lib/context.test.cjs +693 -0
- package/deliver-great-systems/bin/lib/core.cjs +744 -0
- package/deliver-great-systems/bin/lib/core.test.cjs +822 -0
- package/deliver-great-systems/bin/lib/docs.cjs +919 -0
- package/deliver-great-systems/bin/lib/docs.test.cjs +211 -0
- package/deliver-great-systems/bin/lib/execution.cjs +705 -0
- package/deliver-great-systems/bin/lib/execution.test.cjs +1472 -0
- package/deliver-great-systems/bin/lib/frontmatter.cjs +324 -0
- package/deliver-great-systems/bin/lib/ideas.cjs +1406 -0
- package/deliver-great-systems/bin/lib/ideas.test.cjs +1417 -0
- package/deliver-great-systems/bin/lib/identity.cjs +125 -0
- package/deliver-great-systems/bin/lib/init.cjs +1114 -0
- package/deliver-great-systems/bin/lib/init.test.cjs +1271 -0
- package/deliver-great-systems/bin/lib/jobs.cjs +2015 -0
- package/deliver-great-systems/bin/lib/jobs.test.cjs +2619 -0
- package/deliver-great-systems/bin/lib/merge-conflicts.cjs +654 -0
- package/deliver-great-systems/bin/lib/merge-conflicts.test.cjs +370 -0
- package/deliver-great-systems/bin/lib/migration.cjs +352 -0
- package/deliver-great-systems/bin/lib/migration.test.cjs +582 -0
- package/deliver-great-systems/bin/lib/milestone.cjs +243 -0
- package/deliver-great-systems/bin/lib/overlap.cjs +437 -0
- package/deliver-great-systems/bin/lib/overlap.test.cjs +747 -0
- package/deliver-great-systems/bin/lib/path-audit.test.cjs +384 -0
- package/deliver-great-systems/bin/lib/paths.cjs +144 -0
- package/deliver-great-systems/bin/lib/paths.test.cjs +486 -0
- package/deliver-great-systems/bin/lib/phase.cjs +910 -0
- package/deliver-great-systems/bin/lib/projects.cjs +691 -0
- package/deliver-great-systems/bin/lib/projects.test.cjs +871 -0
- package/deliver-great-systems/bin/lib/repos.cjs +1432 -0
- package/deliver-great-systems/bin/lib/repos.test.cjs +1882 -0
- package/deliver-great-systems/bin/lib/roadmap.cjs +305 -0
- package/deliver-great-systems/bin/lib/search.cjs +570 -0
- package/deliver-great-systems/bin/lib/specs.cjs +1303 -0
- package/deliver-great-systems/bin/lib/state.cjs +893 -0
- package/deliver-great-systems/bin/lib/template.cjs +228 -0
- package/deliver-great-systems/bin/lib/test-helpers.cjs +291 -0
- package/deliver-great-systems/bin/lib/verify.cjs +796 -0
- package/deliver-great-systems/references/checkpoints.md +776 -0
- package/deliver-great-systems/references/conflict-resolution.md +66 -0
- package/deliver-great-systems/references/context-tiers.md +166 -0
- package/deliver-great-systems/references/continuation-format.md +249 -0
- package/deliver-great-systems/references/decimal-phase-calculation.md +67 -0
- package/deliver-great-systems/references/git-integration.md +250 -0
- package/deliver-great-systems/references/git-planning-commit.md +40 -0
- package/deliver-great-systems/references/model-profile-resolution.md +36 -0
- package/deliver-great-systems/references/model-profiles.md +95 -0
- package/deliver-great-systems/references/phase-argument-parsing.md +61 -0
- package/deliver-great-systems/references/planning-config.md +224 -0
- package/deliver-great-systems/references/questioning.md +162 -0
- package/deliver-great-systems/references/spec-review-loop.md +177 -0
- package/deliver-great-systems/references/tdd.md +265 -0
- package/deliver-great-systems/references/ui-brand.md +160 -0
- package/deliver-great-systems/references/verification-patterns.md +612 -0
- package/deliver-great-systems/templates/DEBUG.md +166 -0
- package/deliver-great-systems/templates/UAT.md +251 -0
- package/deliver-great-systems/templates/VALIDATION.md +95 -0
- package/deliver-great-systems/templates/claude-md.md +74 -0
- package/deliver-great-systems/templates/codebase/architecture.md +257 -0
- package/deliver-great-systems/templates/codebase/concerns.md +312 -0
- package/deliver-great-systems/templates/codebase/conventions.md +309 -0
- package/deliver-great-systems/templates/codebase/integrations.md +282 -0
- package/deliver-great-systems/templates/codebase/stack.md +188 -0
- package/deliver-great-systems/templates/codebase/structure.md +287 -0
- package/deliver-great-systems/templates/codebase/testing.md +482 -0
- package/deliver-great-systems/templates/config.json +38 -0
- package/deliver-great-systems/templates/context.md +354 -0
- package/deliver-great-systems/templates/continue-here.md +80 -0
- package/deliver-great-systems/templates/debug-subagent-prompt.md +93 -0
- package/deliver-great-systems/templates/discovery.md +148 -0
- package/deliver-great-systems/templates/milestone-archive.md +125 -0
- package/deliver-great-systems/templates/milestone.md +117 -0
- package/deliver-great-systems/templates/phase-prompt.md +615 -0
- package/deliver-great-systems/templates/planner-subagent-prompt.md +119 -0
- package/deliver-great-systems/templates/project.md +186 -0
- package/deliver-great-systems/templates/requirements.md +233 -0
- package/deliver-great-systems/templates/research-project/ARCHITECTURE.md +206 -0
- package/deliver-great-systems/templates/research-project/FEATURES.md +149 -0
- package/deliver-great-systems/templates/research-project/PITFALLS.md +202 -0
- package/deliver-great-systems/templates/research-project/STACK.md +122 -0
- package/deliver-great-systems/templates/research-project/SUMMARY.md +172 -0
- package/deliver-great-systems/templates/research.md +554 -0
- package/deliver-great-systems/templates/retrospective.md +54 -0
- package/deliver-great-systems/templates/roadmap.md +204 -0
- package/deliver-great-systems/templates/state.md +178 -0
- package/deliver-great-systems/templates/summary-complex.md +59 -0
- package/deliver-great-systems/templates/summary-minimal.md +41 -0
- package/deliver-great-systems/templates/summary-standard.md +48 -0
- package/deliver-great-systems/templates/summary.md +253 -0
- package/deliver-great-systems/templates/user-setup.md +313 -0
- package/deliver-great-systems/templates/verification-report.md +324 -0
- package/deliver-great-systems/workflows/add-doc.md +151 -0
- package/deliver-great-systems/workflows/add-idea.md +96 -0
- package/deliver-great-systems/workflows/add-phase.md +120 -0
- package/deliver-great-systems/workflows/add-tests.md +359 -0
- package/deliver-great-systems/workflows/add-todo.md +162 -0
- package/deliver-great-systems/workflows/approve-spec.md +194 -0
- package/deliver-great-systems/workflows/audit-milestone.md +364 -0
- package/deliver-great-systems/workflows/audit-phase.md +462 -0
- package/deliver-great-systems/workflows/cancel-job.md +108 -0
- package/deliver-great-systems/workflows/check-todos.md +181 -0
- package/deliver-great-systems/workflows/cleanup.md +247 -0
- package/deliver-great-systems/workflows/codereview.md +526 -0
- package/deliver-great-systems/workflows/complete-milestone.md +1298 -0
- package/deliver-great-systems/workflows/consolidate-ideas.md +365 -0
- package/deliver-great-systems/workflows/create-milestone-job.md +177 -0
- package/deliver-great-systems/workflows/develop-idea.md +544 -0
- package/deliver-great-systems/workflows/diagnose-issues.md +231 -0
- package/deliver-great-systems/workflows/discovery-phase.md +301 -0
- package/deliver-great-systems/workflows/discuss-idea.md +263 -0
- package/deliver-great-systems/workflows/discuss-phase.md +733 -0
- package/deliver-great-systems/workflows/execute-phase.md +571 -0
- package/deliver-great-systems/workflows/execute-plan.md +592 -0
- package/deliver-great-systems/workflows/find-related-ideas.md +271 -0
- package/deliver-great-systems/workflows/health.md +173 -0
- package/deliver-great-systems/workflows/help.md +997 -0
- package/deliver-great-systems/workflows/import-spec.md +381 -0
- package/deliver-great-systems/workflows/init-product.md +767 -0
- package/deliver-great-systems/workflows/insert-phase.md +138 -0
- package/deliver-great-systems/workflows/list-docs.md +119 -0
- package/deliver-great-systems/workflows/list-ideas.md +154 -0
- package/deliver-great-systems/workflows/list-jobs.md +89 -0
- package/deliver-great-systems/workflows/list-phase-assumptions.md +192 -0
- package/deliver-great-systems/workflows/list-specs.md +101 -0
- package/deliver-great-systems/workflows/map-codebase.md +621 -0
- package/deliver-great-systems/workflows/new-milestone.md +591 -0
- package/deliver-great-systems/workflows/new-project.md +1113 -0
- package/deliver-great-systems/workflows/node-repair.md +94 -0
- package/deliver-great-systems/workflows/overlap-check.md +86 -0
- package/deliver-great-systems/workflows/pause-work.md +134 -0
- package/deliver-great-systems/workflows/plan-milestone-gaps.md +306 -0
- package/deliver-great-systems/workflows/plan-phase.md +698 -0
- package/deliver-great-systems/workflows/progress.md +386 -0
- package/deliver-great-systems/workflows/quick.md +845 -0
- package/deliver-great-systems/workflows/refine-spec.md +275 -0
- package/deliver-great-systems/workflows/reject-idea.md +109 -0
- package/deliver-great-systems/workflows/remove-doc.md +117 -0
- package/deliver-great-systems/workflows/remove-phase.md +163 -0
- package/deliver-great-systems/workflows/research-idea.md +325 -0
- package/deliver-great-systems/workflows/research-phase.md +81 -0
- package/deliver-great-systems/workflows/restore-idea.md +101 -0
- package/deliver-great-systems/workflows/resume-project.md +311 -0
- package/deliver-great-systems/workflows/rollback-job.md +130 -0
- package/deliver-great-systems/workflows/run-job.md +498 -0
- package/deliver-great-systems/workflows/search.md +130 -0
- package/deliver-great-systems/workflows/set-profile.md +83 -0
- package/deliver-great-systems/workflows/settings.md +470 -0
- package/deliver-great-systems/workflows/transition.md +563 -0
- package/deliver-great-systems/workflows/undo-consolidation.md +155 -0
- package/deliver-great-systems/workflows/update-idea.md +157 -0
- package/deliver-great-systems/workflows/update.md +242 -0
- package/deliver-great-systems/workflows/validate-phase.md +177 -0
- package/deliver-great-systems/workflows/verify-phase.md +253 -0
- package/deliver-great-systems/workflows/verify-work.md +671 -0
- package/deliver-great-systems/workflows/write-spec.md +450 -0
- package/hooks/dist/dgs-check-update.js +62 -0
- package/hooks/dist/dgs-context-monitor.js +141 -0
- package/hooks/dist/dgs-statusline.js +115 -0
- package/package.json +60 -0
- package/scripts/build-hooks.js +43 -0
|
@@ -0,0 +1,582 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tests for migration.cjs — v1-to-v2 migration with git mv history preservation
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
const { describe, it, beforeEach, afterEach } = require('node:test');
|
|
6
|
+
const assert = require('node:assert');
|
|
7
|
+
const fs = require('fs');
|
|
8
|
+
const path = require('path');
|
|
9
|
+
const os = require('os');
|
|
10
|
+
const { execSync } = require('child_process');
|
|
11
|
+
|
|
12
|
+
const { createTempDir, cleanupDir, writeFile, initGitRepo } = require('./test-helpers.cjs');
|
|
13
|
+
|
|
14
|
+
// Helper to create a v1 install structure and commit it
|
|
15
|
+
function createV1Install(dir, projectName) {
|
|
16
|
+
writeFile(dir, '.planning/PROJECT.md', `# Project: ${projectName}\n\nA test project.\n`);
|
|
17
|
+
writeFile(dir, '.planning/REQUIREMENTS.md', `# Requirements\n\n## Functional\n\n- REQ-01: Do something\n`);
|
|
18
|
+
writeFile(dir, '.planning/ROADMAP.md', `# Roadmap\n\n## Phase 1\n\nDo things.\n`);
|
|
19
|
+
writeFile(dir, '.planning/STATE.md', `# Project State\n\nPhase: 1\nStatus: Ready\nProgress: [░░░░░░░░░░] 0%\n`);
|
|
20
|
+
writeFile(dir, '.planning/config.json', '{"model_profile":"balanced"}');
|
|
21
|
+
writeFile(dir, '.planning/phases/01-setup/01-01-PLAN.md', '---\nphase: 01\n---\n');
|
|
22
|
+
writeFile(dir, '.planning/research/notes.md', '# Research Notes\n');
|
|
23
|
+
execSync('git add .', { cwd: dir, stdio: 'pipe' });
|
|
24
|
+
execSync('git commit -m "v1 install"', { cwd: dir, stdio: 'pipe' });
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
const {
|
|
28
|
+
detectV1Install,
|
|
29
|
+
validateMigrationPreconditions,
|
|
30
|
+
createBackupTag,
|
|
31
|
+
collectMigrationMoves,
|
|
32
|
+
validateMoves,
|
|
33
|
+
migrateV1ToV2,
|
|
34
|
+
} = require('./migration.cjs');
|
|
35
|
+
|
|
36
|
+
// ─── detectV1Install ─────────────────────────────────────────────────────────
|
|
37
|
+
|
|
38
|
+
describe('detectV1Install', () => {
|
|
39
|
+
let tmpDir;
|
|
40
|
+
beforeEach(() => { tmpDir = createTempDir(); });
|
|
41
|
+
afterEach(() => { cleanupDir(tmpDir); });
|
|
42
|
+
|
|
43
|
+
it('returns isV1 true when PROJECT.md exists but no PROJECTS.md or REPOS.md', () => {
|
|
44
|
+
writeFile(tmpDir, '.planning/PROJECT.md', '# Project: My Cool App\n');
|
|
45
|
+
const result = detectV1Install(tmpDir);
|
|
46
|
+
assert.strictEqual(result.isV1, true);
|
|
47
|
+
assert.strictEqual(result.projectName, 'My Cool App');
|
|
48
|
+
assert.strictEqual(result.slug, 'my-cool-app');
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
it('returns isV1 false when already v2 (PROJECTS.md exists)', () => {
|
|
52
|
+
writeFile(tmpDir, '.planning/PROJECT.md', '# Project: Test\n');
|
|
53
|
+
writeFile(tmpDir, '.planning/PROJECTS.md', '# Projects\n');
|
|
54
|
+
const result = detectV1Install(tmpDir);
|
|
55
|
+
assert.strictEqual(result.isV1, false);
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
it('returns isV1 false when already v2 (REPOS.md exists)', () => {
|
|
59
|
+
writeFile(tmpDir, '.planning/PROJECT.md', '# Project: Test\n');
|
|
60
|
+
writeFile(tmpDir, '.planning/REPOS.md', '# Repos\n');
|
|
61
|
+
const result = detectV1Install(tmpDir);
|
|
62
|
+
assert.strictEqual(result.isV1, false);
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
it('returns isV1 false when no .planning directory exists', () => {
|
|
66
|
+
const result = detectV1Install(tmpDir);
|
|
67
|
+
assert.strictEqual(result.isV1, false);
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
it('returns isV1 false when .planning exists but no PROJECT.md', () => {
|
|
71
|
+
fs.mkdirSync(path.join(tmpDir, '.planning'), { recursive: true });
|
|
72
|
+
writeFile(tmpDir, '.planning/config.json', '{}');
|
|
73
|
+
const result = detectV1Install(tmpDir);
|
|
74
|
+
assert.strictEqual(result.isV1, false);
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
it('extracts project name from "# Project: Name" heading', () => {
|
|
78
|
+
writeFile(tmpDir, '.planning/PROJECT.md', '# Project: Auth Overhaul\n\nDetails here.\n');
|
|
79
|
+
const result = detectV1Install(tmpDir);
|
|
80
|
+
assert.strictEqual(result.projectName, 'Auth Overhaul');
|
|
81
|
+
assert.strictEqual(result.slug, 'auth-overhaul');
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
it('falls back to directory name if no heading found', () => {
|
|
85
|
+
writeFile(tmpDir, '.planning/PROJECT.md', 'Random content without heading\n');
|
|
86
|
+
const result = detectV1Install(tmpDir);
|
|
87
|
+
assert.strictEqual(result.isV1, true);
|
|
88
|
+
// slug should be derived from the directory basename
|
|
89
|
+
assert.ok(result.slug);
|
|
90
|
+
assert.ok(result.slug.length > 0);
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
it('handles PROJECT.md with "# Project Name" (no colon) heading', () => {
|
|
94
|
+
writeFile(tmpDir, '.planning/PROJECT.md', '# Project Name\n\nSome project.\n');
|
|
95
|
+
const result = detectV1Install(tmpDir);
|
|
96
|
+
assert.strictEqual(result.isV1, true);
|
|
97
|
+
// Should parse the heading somehow
|
|
98
|
+
assert.ok(result.slug);
|
|
99
|
+
});
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
// ─── validateMigrationPreconditions ──────────────────────────────────────────
|
|
103
|
+
|
|
104
|
+
describe('validateMigrationPreconditions', () => {
|
|
105
|
+
let tmpDir;
|
|
106
|
+
beforeEach(() => {
|
|
107
|
+
tmpDir = createTempDir();
|
|
108
|
+
initGitRepo(tmpDir);
|
|
109
|
+
});
|
|
110
|
+
afterEach(() => { cleanupDir(tmpDir); });
|
|
111
|
+
|
|
112
|
+
it('passes when v1 install with clean working tree', () => {
|
|
113
|
+
writeFile(tmpDir, '.planning/PROJECT.md', '# Project: Test\n');
|
|
114
|
+
execSync('git add .', { cwd: tmpDir, stdio: 'pipe' });
|
|
115
|
+
execSync('git commit -m "add planning"', { cwd: tmpDir, stdio: 'pipe' });
|
|
116
|
+
const result = validateMigrationPreconditions(tmpDir);
|
|
117
|
+
assert.strictEqual(result.valid, true);
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
it('fails when working tree has uncommitted changes', () => {
|
|
121
|
+
writeFile(tmpDir, '.planning/PROJECT.md', '# Project: Test\n');
|
|
122
|
+
// Don't commit — dirty tree
|
|
123
|
+
const result = validateMigrationPreconditions(tmpDir);
|
|
124
|
+
assert.strictEqual(result.valid, false);
|
|
125
|
+
assert.ok(result.reason.toLowerCase().includes('uncommitted') || result.reason.toLowerCase().includes('dirty') || result.reason.toLowerCase().includes('clean'));
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
it('fails when already v2 install', () => {
|
|
129
|
+
writeFile(tmpDir, '.planning/PROJECTS.md', '# Projects\n');
|
|
130
|
+
writeFile(tmpDir, '.planning/REPOS.md', '# Repos\n');
|
|
131
|
+
execSync('git add .', { cwd: tmpDir, stdio: 'pipe' });
|
|
132
|
+
execSync('git commit -m "v2"', { cwd: tmpDir, stdio: 'pipe' });
|
|
133
|
+
const result = validateMigrationPreconditions(tmpDir);
|
|
134
|
+
assert.strictEqual(result.valid, false);
|
|
135
|
+
assert.ok(result.reason.toLowerCase().includes('v2') || result.reason.toLowerCase().includes('already'));
|
|
136
|
+
});
|
|
137
|
+
|
|
138
|
+
it('fails when no .planning/PROJECT.md exists', () => {
|
|
139
|
+
const result = validateMigrationPreconditions(tmpDir);
|
|
140
|
+
assert.strictEqual(result.valid, false);
|
|
141
|
+
});
|
|
142
|
+
});
|
|
143
|
+
|
|
144
|
+
// ─── createBackupTag ─────────────────────────────────────────────────────────
|
|
145
|
+
|
|
146
|
+
describe('createBackupTag', () => {
|
|
147
|
+
let tmpDir;
|
|
148
|
+
beforeEach(() => {
|
|
149
|
+
tmpDir = createTempDir();
|
|
150
|
+
initGitRepo(tmpDir);
|
|
151
|
+
});
|
|
152
|
+
afterEach(() => { cleanupDir(tmpDir); });
|
|
153
|
+
|
|
154
|
+
it('creates dgs-pre-v2-migration tag successfully', () => {
|
|
155
|
+
const result = createBackupTag(tmpDir);
|
|
156
|
+
assert.strictEqual(result.tagged, true);
|
|
157
|
+
// Verify tag actually exists
|
|
158
|
+
const tags = execSync('git tag', { cwd: tmpDir, encoding: 'utf-8' }).trim();
|
|
159
|
+
assert.ok(tags.includes('dgs-pre-v2-migration'));
|
|
160
|
+
});
|
|
161
|
+
|
|
162
|
+
it('returns tagged false if tag already exists', () => {
|
|
163
|
+
execSync('git tag dgs-pre-v2-migration', { cwd: tmpDir, stdio: 'pipe' });
|
|
164
|
+
const result = createBackupTag(tmpDir);
|
|
165
|
+
assert.strictEqual(result.tagged, false);
|
|
166
|
+
assert.ok(result.reason);
|
|
167
|
+
});
|
|
168
|
+
|
|
169
|
+
it('returns tagged false if not a git repo', () => {
|
|
170
|
+
const noGitDir = createTempDir();
|
|
171
|
+
try {
|
|
172
|
+
const result = createBackupTag(noGitDir);
|
|
173
|
+
assert.strictEqual(result.tagged, false);
|
|
174
|
+
assert.ok(result.reason);
|
|
175
|
+
} finally {
|
|
176
|
+
cleanupDir(noGitDir);
|
|
177
|
+
}
|
|
178
|
+
});
|
|
179
|
+
});
|
|
180
|
+
|
|
181
|
+
// ─── migrateV1ToV2 ──────────────────────────────────────────────────────────
|
|
182
|
+
|
|
183
|
+
describe('migrateV1ToV2', () => {
|
|
184
|
+
let tmpDir;
|
|
185
|
+
beforeEach(() => {
|
|
186
|
+
tmpDir = createTempDir();
|
|
187
|
+
initGitRepo(tmpDir);
|
|
188
|
+
});
|
|
189
|
+
afterEach(() => { cleanupDir(tmpDir); });
|
|
190
|
+
|
|
191
|
+
it('moves PROJECT.md, REQUIREMENTS.md, ROADMAP.md, STATE.md into project subfolder', () => {
|
|
192
|
+
createV1Install(tmpDir, 'Test App');
|
|
193
|
+
const result = migrateV1ToV2(tmpDir, 'test-app');
|
|
194
|
+
assert.strictEqual(result.migrated, true);
|
|
195
|
+
assert.strictEqual(result.slug, 'test-app');
|
|
196
|
+
|
|
197
|
+
// Files should exist in new location
|
|
198
|
+
assert.ok(fs.existsSync(path.join(tmpDir, '.planning', 'projects', 'test-app', 'PROJECT.md')));
|
|
199
|
+
assert.ok(fs.existsSync(path.join(tmpDir, '.planning', 'projects', 'test-app', 'REQUIREMENTS.md')));
|
|
200
|
+
assert.ok(fs.existsSync(path.join(tmpDir, '.planning', 'projects', 'test-app', 'ROADMAP.md')));
|
|
201
|
+
assert.ok(fs.existsSync(path.join(tmpDir, '.planning', 'projects', 'test-app', 'STATE.md')));
|
|
202
|
+
|
|
203
|
+
// Files should NOT exist in old location
|
|
204
|
+
assert.ok(!fs.existsSync(path.join(tmpDir, '.planning', 'PROJECT.md')));
|
|
205
|
+
assert.ok(!fs.existsSync(path.join(tmpDir, '.planning', 'REQUIREMENTS.md')));
|
|
206
|
+
assert.ok(!fs.existsSync(path.join(tmpDir, '.planning', 'ROADMAP.md')));
|
|
207
|
+
assert.ok(!fs.existsSync(path.join(tmpDir, '.planning', 'STATE.md')));
|
|
208
|
+
});
|
|
209
|
+
|
|
210
|
+
it('moves phases/ directory into project subfolder', () => {
|
|
211
|
+
createV1Install(tmpDir, 'Test App');
|
|
212
|
+
const result = migrateV1ToV2(tmpDir, 'test-app');
|
|
213
|
+
assert.strictEqual(result.migrated, true);
|
|
214
|
+
|
|
215
|
+
// phases/ should exist in new location
|
|
216
|
+
assert.ok(fs.existsSync(path.join(tmpDir, '.planning', 'projects', 'test-app', 'phases', '01-setup', '01-01-PLAN.md')));
|
|
217
|
+
// phases/ should NOT exist in old location
|
|
218
|
+
assert.ok(!fs.existsSync(path.join(tmpDir, '.planning', 'phases')));
|
|
219
|
+
});
|
|
220
|
+
|
|
221
|
+
it('moves research/ directory into project subfolder', () => {
|
|
222
|
+
createV1Install(tmpDir, 'Test App');
|
|
223
|
+
const result = migrateV1ToV2(tmpDir, 'test-app');
|
|
224
|
+
assert.strictEqual(result.migrated, true);
|
|
225
|
+
|
|
226
|
+
assert.ok(fs.existsSync(path.join(tmpDir, '.planning', 'projects', 'test-app', 'research', 'notes.md')));
|
|
227
|
+
assert.ok(!fs.existsSync(path.join(tmpDir, '.planning', 'research')));
|
|
228
|
+
});
|
|
229
|
+
|
|
230
|
+
it('skips files/dirs that do not exist (partial v1 install)', () => {
|
|
231
|
+
// Create minimal v1 install with only PROJECT.md and STATE.md
|
|
232
|
+
writeFile(tmpDir, '.planning/PROJECT.md', '# Project: Minimal\n');
|
|
233
|
+
writeFile(tmpDir, '.planning/STATE.md', '# Project State\n\nPhase: 1\nStatus: Ready\nProgress: [░░░░░░░░░░] 0%\n');
|
|
234
|
+
writeFile(tmpDir, '.planning/config.json', '{}');
|
|
235
|
+
execSync('git add .', { cwd: tmpDir, stdio: 'pipe' });
|
|
236
|
+
execSync('git commit -m "partial v1"', { cwd: tmpDir, stdio: 'pipe' });
|
|
237
|
+
|
|
238
|
+
const result = migrateV1ToV2(tmpDir, 'minimal');
|
|
239
|
+
assert.strictEqual(result.migrated, true);
|
|
240
|
+
// Moved files should be in new location
|
|
241
|
+
assert.ok(fs.existsSync(path.join(tmpDir, '.planning', 'projects', 'minimal', 'PROJECT.md')));
|
|
242
|
+
assert.ok(fs.existsSync(path.join(tmpDir, '.planning', 'projects', 'minimal', 'STATE.md')));
|
|
243
|
+
// Skipped files should not cause errors
|
|
244
|
+
assert.ok(!fs.existsSync(path.join(tmpDir, '.planning', 'projects', 'minimal', 'REQUIREMENTS.md')));
|
|
245
|
+
});
|
|
246
|
+
|
|
247
|
+
it('does NOT move config.json (product-level)', () => {
|
|
248
|
+
createV1Install(tmpDir, 'Test App');
|
|
249
|
+
migrateV1ToV2(tmpDir, 'test-app');
|
|
250
|
+
|
|
251
|
+
// config.json should remain at .planning/config.json
|
|
252
|
+
assert.ok(fs.existsSync(path.join(tmpDir, '.planning', 'config.json')));
|
|
253
|
+
// config.json should NOT be in project subfolder
|
|
254
|
+
assert.ok(!fs.existsSync(path.join(tmpDir, '.planning', 'projects', 'test-app', 'config.json')));
|
|
255
|
+
});
|
|
256
|
+
|
|
257
|
+
it('does NOT move codebase/ directory (product-level)', () => {
|
|
258
|
+
writeFile(tmpDir, '.planning/codebase/overview.md', '# Codebase\n');
|
|
259
|
+
createV1Install(tmpDir, 'Test App');
|
|
260
|
+
migrateV1ToV2(tmpDir, 'test-app');
|
|
261
|
+
|
|
262
|
+
// codebase/ should remain at product level
|
|
263
|
+
assert.ok(fs.existsSync(path.join(tmpDir, '.planning', 'codebase', 'overview.md')));
|
|
264
|
+
});
|
|
265
|
+
|
|
266
|
+
it('does NOT move milestones/ directory (product-level)', () => {
|
|
267
|
+
writeFile(tmpDir, '.planning/milestones/v1.0.md', '# Milestone v1.0\n');
|
|
268
|
+
createV1Install(tmpDir, 'Test App');
|
|
269
|
+
migrateV1ToV2(tmpDir, 'test-app');
|
|
270
|
+
|
|
271
|
+
// milestones/ should remain at product level
|
|
272
|
+
assert.ok(fs.existsSync(path.join(tmpDir, '.planning', 'milestones', 'v1.0.md')));
|
|
273
|
+
});
|
|
274
|
+
|
|
275
|
+
it('creates REPOS.md with current directory as single repo', () => {
|
|
276
|
+
createV1Install(tmpDir, 'Test App');
|
|
277
|
+
migrateV1ToV2(tmpDir, 'test-app');
|
|
278
|
+
|
|
279
|
+
const reposMdPath = path.join(tmpDir, '.planning', 'REPOS.md');
|
|
280
|
+
assert.ok(fs.existsSync(reposMdPath));
|
|
281
|
+
const content = fs.readFileSync(reposMdPath, 'utf-8');
|
|
282
|
+
assert.ok(content.startsWith('# Repos'));
|
|
283
|
+
assert.ok(content.includes('.')); // path is '.'
|
|
284
|
+
});
|
|
285
|
+
|
|
286
|
+
it('creates PROJECTS.md from migrated project', () => {
|
|
287
|
+
createV1Install(tmpDir, 'Test App');
|
|
288
|
+
migrateV1ToV2(tmpDir, 'test-app');
|
|
289
|
+
|
|
290
|
+
const projectsMdPath = path.join(tmpDir, '.planning', 'PROJECTS.md');
|
|
291
|
+
assert.ok(fs.existsSync(projectsMdPath));
|
|
292
|
+
const content = fs.readFileSync(projectsMdPath, 'utf-8');
|
|
293
|
+
assert.ok(content.startsWith('# Projects'));
|
|
294
|
+
});
|
|
295
|
+
|
|
296
|
+
it('sets current_project in config.json', () => {
|
|
297
|
+
createV1Install(tmpDir, 'Test App');
|
|
298
|
+
migrateV1ToV2(tmpDir, 'test-app');
|
|
299
|
+
|
|
300
|
+
const configPath = path.join(tmpDir, '.planning', 'dgs.config.json');
|
|
301
|
+
const config = JSON.parse(fs.readFileSync(configPath, 'utf-8'));
|
|
302
|
+
assert.strictEqual(config.current_project, 'test-app');
|
|
303
|
+
});
|
|
304
|
+
|
|
305
|
+
it('returns structured result with files_moved', () => {
|
|
306
|
+
createV1Install(tmpDir, 'Test App');
|
|
307
|
+
const result = migrateV1ToV2(tmpDir, 'test-app');
|
|
308
|
+
assert.strictEqual(result.migrated, true);
|
|
309
|
+
assert.strictEqual(result.slug, 'test-app');
|
|
310
|
+
assert.ok(Array.isArray(result.files_moved));
|
|
311
|
+
assert.ok(result.files_moved.length > 0);
|
|
312
|
+
assert.strictEqual(result.repos_created, true);
|
|
313
|
+
assert.strictEqual(result.project_created, true);
|
|
314
|
+
});
|
|
315
|
+
|
|
316
|
+
it('uses git mv to preserve history', () => {
|
|
317
|
+
createV1Install(tmpDir, 'Test App');
|
|
318
|
+
migrateV1ToV2(tmpDir, 'test-app');
|
|
319
|
+
|
|
320
|
+
// Check git log for the moved files — git should show rename detection
|
|
321
|
+
const log = execSync('git log --diff-filter=R --summary --format="" HEAD', {
|
|
322
|
+
cwd: tmpDir,
|
|
323
|
+
encoding: 'utf-8',
|
|
324
|
+
}).trim();
|
|
325
|
+
// Should have rename entries
|
|
326
|
+
assert.ok(log.includes('rename'));
|
|
327
|
+
});
|
|
328
|
+
|
|
329
|
+
it('moves todos/ directory if it exists', () => {
|
|
330
|
+
writeFile(tmpDir, '.planning/todos/pending/task1.md', '# Todo 1\n');
|
|
331
|
+
createV1Install(tmpDir, 'Test App');
|
|
332
|
+
migrateV1ToV2(tmpDir, 'test-app');
|
|
333
|
+
|
|
334
|
+
assert.ok(fs.existsSync(path.join(tmpDir, '.planning', 'projects', 'test-app', 'todos', 'pending', 'task1.md')));
|
|
335
|
+
assert.ok(!fs.existsSync(path.join(tmpDir, '.planning', 'todos')));
|
|
336
|
+
});
|
|
337
|
+
|
|
338
|
+
it('moves quick/ directory if it exists', () => {
|
|
339
|
+
writeFile(tmpDir, '.planning/quick/fix1.md', '# Quick Fix\n');
|
|
340
|
+
createV1Install(tmpDir, 'Test App');
|
|
341
|
+
migrateV1ToV2(tmpDir, 'test-app');
|
|
342
|
+
|
|
343
|
+
assert.ok(fs.existsSync(path.join(tmpDir, '.planning', 'projects', 'test-app', 'quick', 'fix1.md')));
|
|
344
|
+
assert.ok(!fs.existsSync(path.join(tmpDir, '.planning', 'quick')));
|
|
345
|
+
});
|
|
346
|
+
|
|
347
|
+
it('moves debug/ directory if it exists', () => {
|
|
348
|
+
writeFile(tmpDir, '.planning/debug/issue1.md', '# Debug Session\n');
|
|
349
|
+
createV1Install(tmpDir, 'Test App');
|
|
350
|
+
migrateV1ToV2(tmpDir, 'test-app');
|
|
351
|
+
|
|
352
|
+
assert.ok(fs.existsSync(path.join(tmpDir, '.planning', 'projects', 'test-app', 'debug', 'issue1.md')));
|
|
353
|
+
assert.ok(!fs.existsSync(path.join(tmpDir, '.planning', 'debug')));
|
|
354
|
+
});
|
|
355
|
+
});
|
|
356
|
+
|
|
357
|
+
// ─── collectMigrationMoves ──────────────────────────────────────────────────
|
|
358
|
+
|
|
359
|
+
describe('collectMigrationMoves', () => {
|
|
360
|
+
let tmpDir;
|
|
361
|
+
beforeEach(() => { tmpDir = createTempDir(); });
|
|
362
|
+
afterEach(() => { cleanupDir(tmpDir); });
|
|
363
|
+
|
|
364
|
+
it('returns correct move entries for a full v1 install', () => {
|
|
365
|
+
// Create a full v1 install (without git — just files on disk)
|
|
366
|
+
writeFile(tmpDir, '.planning/PROJECT.md', '# Project: Test\n');
|
|
367
|
+
writeFile(tmpDir, '.planning/REQUIREMENTS.md', '# Requirements\n');
|
|
368
|
+
writeFile(tmpDir, '.planning/ROADMAP.md', '# Roadmap\n');
|
|
369
|
+
writeFile(tmpDir, '.planning/STATE.md', '# State\n');
|
|
370
|
+
writeFile(tmpDir, '.planning/phases/01-setup/01-01-PLAN.md', '---\n---\n');
|
|
371
|
+
writeFile(tmpDir, '.planning/research/notes.md', '# Notes\n');
|
|
372
|
+
|
|
373
|
+
const { moves, targetDir } = collectMigrationMoves(tmpDir, 'test-app');
|
|
374
|
+
|
|
375
|
+
// Should have 2 directories + 4 files = 6 moves
|
|
376
|
+
assert.strictEqual(moves.length, 6);
|
|
377
|
+
assert.ok(targetDir.endsWith(path.join('.planning', 'projects', 'test-app')));
|
|
378
|
+
|
|
379
|
+
// Directories should come first
|
|
380
|
+
const dirMoves = moves.filter(m => m.isDir);
|
|
381
|
+
const fileMoves = moves.filter(m => !m.isDir);
|
|
382
|
+
assert.strictEqual(dirMoves.length, 2); // phases, research
|
|
383
|
+
assert.strictEqual(fileMoves.length, 4); // PROJECT.md, REQUIREMENTS.md, ROADMAP.md, STATE.md
|
|
384
|
+
|
|
385
|
+
// First moves should be dirs
|
|
386
|
+
assert.strictEqual(moves[0].isDir, true);
|
|
387
|
+
assert.strictEqual(moves[1].isDir, true);
|
|
388
|
+
});
|
|
389
|
+
|
|
390
|
+
it('omits entries for non-existent items (partial v1 install)', () => {
|
|
391
|
+
// Create only PROJECT.md and STATE.md
|
|
392
|
+
writeFile(tmpDir, '.planning/PROJECT.md', '# Project: Partial\n');
|
|
393
|
+
writeFile(tmpDir, '.planning/STATE.md', '# State\n');
|
|
394
|
+
|
|
395
|
+
const { moves } = collectMigrationMoves(tmpDir, 'partial');
|
|
396
|
+
|
|
397
|
+
// Should have only 2 moves (the 2 files that exist)
|
|
398
|
+
assert.strictEqual(moves.length, 2);
|
|
399
|
+
assert.ok(moves.every(m => !m.isDir));
|
|
400
|
+
const sources = moves.map(m => path.basename(m.relSource));
|
|
401
|
+
assert.ok(sources.includes('PROJECT.md'));
|
|
402
|
+
assert.ok(sources.includes('STATE.md'));
|
|
403
|
+
});
|
|
404
|
+
|
|
405
|
+
it('sets isDir flag correctly for directories and files', () => {
|
|
406
|
+
writeFile(tmpDir, '.planning/PROJECT.md', '# Project: Test\n');
|
|
407
|
+
writeFile(tmpDir, '.planning/phases/01-setup/01-01-PLAN.md', '---\n---\n');
|
|
408
|
+
writeFile(tmpDir, '.planning/research/notes.md', '# Notes\n');
|
|
409
|
+
writeFile(tmpDir, '.planning/todos/pending/task.md', '# Todo\n');
|
|
410
|
+
|
|
411
|
+
const { moves } = collectMigrationMoves(tmpDir, 'test');
|
|
412
|
+
|
|
413
|
+
const phasesMove = moves.find(m => m.relSource.endsWith('phases'));
|
|
414
|
+
const researchMove = moves.find(m => m.relSource.endsWith('research'));
|
|
415
|
+
const todosMove = moves.find(m => m.relSource.endsWith('todos'));
|
|
416
|
+
const projectMove = moves.find(m => m.relSource.endsWith('PROJECT.md'));
|
|
417
|
+
|
|
418
|
+
assert.strictEqual(phasesMove.isDir, true);
|
|
419
|
+
assert.strictEqual(researchMove.isDir, true);
|
|
420
|
+
assert.strictEqual(todosMove.isDir, true);
|
|
421
|
+
assert.strictEqual(projectMove.isDir, false);
|
|
422
|
+
});
|
|
423
|
+
});
|
|
424
|
+
|
|
425
|
+
// ─── validateMoves ──────────────────────────────────────────────────────────
|
|
426
|
+
|
|
427
|
+
describe('validateMoves', () => {
|
|
428
|
+
let tmpDir;
|
|
429
|
+
beforeEach(() => { tmpDir = createTempDir(); });
|
|
430
|
+
afterEach(() => { cleanupDir(tmpDir); });
|
|
431
|
+
|
|
432
|
+
it('returns valid when all sources exist and no targets exist', () => {
|
|
433
|
+
writeFile(tmpDir, '.planning/PROJECT.md', '# Project: Test\n');
|
|
434
|
+
writeFile(tmpDir, '.planning/STATE.md', '# State\n');
|
|
435
|
+
|
|
436
|
+
const moves = [
|
|
437
|
+
{ relSource: '.planning/PROJECT.md', relTarget: '.planning/test/PROJECT.md', isDir: false },
|
|
438
|
+
{ relSource: '.planning/STATE.md', relTarget: '.planning/test/STATE.md', isDir: false },
|
|
439
|
+
];
|
|
440
|
+
|
|
441
|
+
const result = validateMoves(tmpDir, moves);
|
|
442
|
+
assert.strictEqual(result.valid, true);
|
|
443
|
+
assert.strictEqual(result.errors.length, 0);
|
|
444
|
+
});
|
|
445
|
+
|
|
446
|
+
it('returns invalid when target already exists', () => {
|
|
447
|
+
writeFile(tmpDir, '.planning/PROJECT.md', '# Project: Test\n');
|
|
448
|
+
// Pre-create a target that would collide
|
|
449
|
+
writeFile(tmpDir, '.planning/test/PROJECT.md', '# Already here\n');
|
|
450
|
+
|
|
451
|
+
const moves = [
|
|
452
|
+
{ relSource: '.planning/PROJECT.md', relTarget: '.planning/test/PROJECT.md', isDir: false },
|
|
453
|
+
];
|
|
454
|
+
|
|
455
|
+
const result = validateMoves(tmpDir, moves);
|
|
456
|
+
assert.strictEqual(result.valid, false);
|
|
457
|
+
assert.ok(result.errors.length > 0);
|
|
458
|
+
assert.ok(result.errors[0].includes('Target already exists'));
|
|
459
|
+
});
|
|
460
|
+
});
|
|
461
|
+
|
|
462
|
+
// ─── Rollback on git mv failure ─────────────────────────────────────────────
|
|
463
|
+
|
|
464
|
+
describe('migrateV1ToV2 rollback', () => {
|
|
465
|
+
let tmpDir;
|
|
466
|
+
beforeEach(() => {
|
|
467
|
+
tmpDir = createTempDir();
|
|
468
|
+
initGitRepo(tmpDir);
|
|
469
|
+
});
|
|
470
|
+
afterEach(() => { cleanupDir(tmpDir); });
|
|
471
|
+
|
|
472
|
+
it('returns migrated false and rolls back when git mv fails mid-migration', () => {
|
|
473
|
+
createV1Install(tmpDir, 'Test App');
|
|
474
|
+
|
|
475
|
+
// Create a file at the target path for phases/ to force git mv to fail.
|
|
476
|
+
// The phases/ dir move will fail because the target .planning/bad-slug/phases
|
|
477
|
+
// already exists as a FILE, preventing the directory move.
|
|
478
|
+
const targetPhasesPath = path.join(tmpDir, '.planning', 'projects', 'bad-slug', 'phases');
|
|
479
|
+
fs.mkdirSync(path.join(tmpDir, '.planning', 'projects', 'bad-slug'), { recursive: true });
|
|
480
|
+
fs.writeFileSync(targetPhasesPath, 'blocker file');
|
|
481
|
+
execSync('git add .', { cwd: tmpDir, stdio: 'pipe' });
|
|
482
|
+
execSync('git commit -m "add blocker"', { cwd: tmpDir, stdio: 'pipe' });
|
|
483
|
+
|
|
484
|
+
const result = migrateV1ToV2(tmpDir, 'bad-slug');
|
|
485
|
+
|
|
486
|
+
// Should report validation failure (target already exists)
|
|
487
|
+
assert.strictEqual(result.migrated, false);
|
|
488
|
+
assert.ok(result.error);
|
|
489
|
+
});
|
|
490
|
+
|
|
491
|
+
it('restores original files after rollback from git mv failure', () => {
|
|
492
|
+
createV1Install(tmpDir, 'Test App');
|
|
493
|
+
|
|
494
|
+
// Force a validation failure by creating a conflicting target
|
|
495
|
+
const targetProjectPath = path.join(tmpDir, '.planning', 'projects', 'fail-slug', 'PROJECT.md');
|
|
496
|
+
fs.mkdirSync(path.join(tmpDir, '.planning', 'projects', 'fail-slug'), { recursive: true });
|
|
497
|
+
fs.writeFileSync(targetProjectPath, 'conflict');
|
|
498
|
+
execSync('git add .', { cwd: tmpDir, stdio: 'pipe' });
|
|
499
|
+
execSync('git commit -m "add conflict"', { cwd: tmpDir, stdio: 'pipe' });
|
|
500
|
+
|
|
501
|
+
migrateV1ToV2(tmpDir, 'fail-slug');
|
|
502
|
+
|
|
503
|
+
// Original files should still exist (validation catches it before any moves)
|
|
504
|
+
assert.ok(fs.existsSync(path.join(tmpDir, '.planning', 'PROJECT.md')));
|
|
505
|
+
assert.ok(fs.existsSync(path.join(tmpDir, '.planning', 'STATE.md')));
|
|
506
|
+
assert.ok(fs.existsSync(path.join(tmpDir, '.planning', 'phases', '01-setup', '01-01-PLAN.md')));
|
|
507
|
+
});
|
|
508
|
+
});
|
|
509
|
+
|
|
510
|
+
// ─── Scale test: 50+ files with rename detection ────────────────────────────
|
|
511
|
+
|
|
512
|
+
describe('migrateV1ToV2 scale test', () => {
|
|
513
|
+
let tmpDir;
|
|
514
|
+
beforeEach(() => {
|
|
515
|
+
tmpDir = createTempDir();
|
|
516
|
+
initGitRepo(tmpDir);
|
|
517
|
+
});
|
|
518
|
+
afterEach(() => { cleanupDir(tmpDir); });
|
|
519
|
+
|
|
520
|
+
it('migrates 60+ phase files and git detects renames with renameLimit=1000', () => {
|
|
521
|
+
// Create base v1 install files
|
|
522
|
+
writeFile(tmpDir, '.planning/PROJECT.md', '# Project: Scale Test\n\nA large project.\n');
|
|
523
|
+
writeFile(tmpDir, '.planning/REQUIREMENTS.md', '# Requirements\n\n- REQ-01\n');
|
|
524
|
+
writeFile(tmpDir, '.planning/ROADMAP.md', '# Roadmap\n\n## Phase 1\n');
|
|
525
|
+
writeFile(tmpDir, '.planning/STATE.md', '# Project State\n\nPhase: 1\nStatus: Active\nProgress: [░░░░░░░░░░] 0%\n');
|
|
526
|
+
writeFile(tmpDir, '.planning/config.json', '{"model_profile":"balanced"}');
|
|
527
|
+
|
|
528
|
+
// Create 60 plan files across 10 phases (6 files each)
|
|
529
|
+
for (let phase = 1; phase <= 10; phase++) {
|
|
530
|
+
const phaseNum = String(phase).padStart(2, '0');
|
|
531
|
+
const phaseName = phaseNum + '-phase-' + phase;
|
|
532
|
+
for (let plan = 1; plan <= 6; plan++) {
|
|
533
|
+
const planNum = String(plan).padStart(2, '0');
|
|
534
|
+
const filename = phaseNum + '-' + planNum + '-PLAN.md';
|
|
535
|
+
writeFile(
|
|
536
|
+
tmpDir,
|
|
537
|
+
'.planning/phases/' + phaseName + '/' + filename,
|
|
538
|
+
'---\nphase: ' + phaseNum + '\nplan: ' + planNum + '\n---\n\n# Plan ' + phaseNum + '-' + planNum + '\n\nContent for plan ' + phase + '.' + plan + '\n'
|
|
539
|
+
);
|
|
540
|
+
}
|
|
541
|
+
}
|
|
542
|
+
|
|
543
|
+
// Commit the v1 install
|
|
544
|
+
execSync('git add .', { cwd: tmpDir, stdio: 'pipe' });
|
|
545
|
+
execSync('git commit -m "v1 install with 60 plan files"', { cwd: tmpDir, stdio: 'pipe' });
|
|
546
|
+
|
|
547
|
+
// Run migration
|
|
548
|
+
const result = migrateV1ToV2(tmpDir, 'scale-test');
|
|
549
|
+
assert.strictEqual(result.migrated, true);
|
|
550
|
+
|
|
551
|
+
// Verify all 60 plan files exist in new location
|
|
552
|
+
let foundCount = 0;
|
|
553
|
+
for (let phase = 1; phase <= 10; phase++) {
|
|
554
|
+
const phaseNum = String(phase).padStart(2, '0');
|
|
555
|
+
const phaseName = phaseNum + '-phase-' + phase;
|
|
556
|
+
for (let plan = 1; plan <= 6; plan++) {
|
|
557
|
+
const planNum = String(plan).padStart(2, '0');
|
|
558
|
+
const filename = phaseNum + '-' + planNum + '-PLAN.md';
|
|
559
|
+
const filePath = path.join(tmpDir, '.planning', 'projects', 'scale-test', 'phases', phaseName, filename);
|
|
560
|
+
if (fs.existsSync(filePath)) {
|
|
561
|
+
foundCount++;
|
|
562
|
+
}
|
|
563
|
+
}
|
|
564
|
+
}
|
|
565
|
+
assert.strictEqual(foundCount, 60, 'All 60 plan files should be in new location');
|
|
566
|
+
|
|
567
|
+
// Verify git detects renames (not delete+add)
|
|
568
|
+
// Look at the migration commit (HEAD~1 is the move commit, HEAD is the markers commit)
|
|
569
|
+
const renameLog = execSync(
|
|
570
|
+
'git log --diff-filter=R --summary --format="" HEAD~1',
|
|
571
|
+
{ cwd: tmpDir, encoding: 'utf-8' }
|
|
572
|
+
).trim();
|
|
573
|
+
assert.ok(renameLog.includes('rename'), 'Git should detect renames in the migration commit');
|
|
574
|
+
|
|
575
|
+
// Count rename entries — should be at least 50 (60 plan files + 4 markdown files + directories)
|
|
576
|
+
const renameLines = renameLog.split('\n').filter(line => line.includes('rename'));
|
|
577
|
+
assert.ok(
|
|
578
|
+
renameLines.length >= 50,
|
|
579
|
+
'Should detect at least 50 renames, got ' + renameLines.length
|
|
580
|
+
);
|
|
581
|
+
});
|
|
582
|
+
});
|