code-as-plan 2.0.2 → 2.0.3
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/bin/install.js +4 -79
- package/cap/bin/lib/cap-migrate.cjs +513 -0
- package/cap/bin/lib/cap-tag-scanner.cjs +72 -0
- package/commands/cap/migrate.md +177 -0
- package/package.json +1 -1
package/bin/install.js
CHANGED
|
@@ -61,7 +61,7 @@ const hasCopilot = args.includes('--copilot');
|
|
|
61
61
|
const hasAntigravity = args.includes('--antigravity');
|
|
62
62
|
const hasCursor = args.includes('--cursor');
|
|
63
63
|
const hasWindsurf = args.includes('--windsurf');
|
|
64
|
-
|
|
64
|
+
// SDK install removed in CAP v2.0 — not needed
|
|
65
65
|
const hasBoth = args.includes('--both'); // Legacy flag, keeps working
|
|
66
66
|
const hasAll = args.includes('--all');
|
|
67
67
|
const hasUninstall = args.includes('--uninstall') || args.includes('-u');
|
|
@@ -322,7 +322,7 @@ if (hasUninstall) {
|
|
|
322
322
|
|
|
323
323
|
// Show help if requested
|
|
324
324
|
if (hasHelp) {
|
|
325
|
-
console.log(` ${yellow}Usage:${reset} npx code-as-plan [options]\n\n ${yellow}Options:${reset}\n ${cyan}-g, --global${reset} Install globally (to config directory)\n ${cyan}-l, --local${reset} Install locally (to current directory)\n ${cyan}--claude${reset} Install for Claude Code only\n ${cyan}--opencode${reset} Install for OpenCode only\n ${cyan}--gemini${reset} Install for Gemini only\n ${cyan}--codex${reset} Install for Codex only\n ${cyan}--copilot${reset} Install for Copilot only\n ${cyan}--antigravity${reset} Install for Antigravity only\n ${cyan}--cursor${reset} Install for Cursor only\n ${cyan}--windsurf${reset} Install for Windsurf only\n ${cyan}--all${reset} Install for all runtimes\n ${cyan}
|
|
325
|
+
console.log(` ${yellow}Usage:${reset} npx code-as-plan [options]\n\n ${yellow}Options:${reset}\n ${cyan}-g, --global${reset} Install globally (to config directory)\n ${cyan}-l, --local${reset} Install locally (to current directory)\n ${cyan}--claude${reset} Install for Claude Code only\n ${cyan}--opencode${reset} Install for OpenCode only\n ${cyan}--gemini${reset} Install for Gemini only\n ${cyan}--codex${reset} Install for Codex only\n ${cyan}--copilot${reset} Install for Copilot only\n ${cyan}--antigravity${reset} Install for Antigravity only\n ${cyan}--cursor${reset} Install for Cursor only\n ${cyan}--windsurf${reset} Install for Windsurf only\n ${cyan}--all${reset} Install for all runtimes\n ${cyan}-u, --uninstall${reset} Uninstall CAP (remove all CAP files)\n ${cyan}-c, --config-dir <path>${reset} Specify custom config directory\n ${cyan}-h, --help${reset} Show this help message\n ${cyan}--force-statusline${reset} Replace existing statusline config\n\n ${yellow}Examples:${reset}\n ${dim}# Interactive install (prompts for runtime and location)${reset}\n npx code-as-plan\n\n ${dim}# Install for Claude Code globally${reset}\n npx code-as-plan --claude --global\n\n ${dim}# Install for Gemini globally${reset}\n npx code-as-plan --gemini --global\n\n ${dim}# Install for Codex globally${reset}\n npx code-as-plan --codex --global\n\n ${dim}# Install for Copilot globally${reset}\n npx code-as-plan --copilot --global\n\n ${dim}# Install for Copilot locally${reset}\n npx code-as-plan --copilot --local\n\n ${dim}# Install for Antigravity globally${reset}\n npx code-as-plan --antigravity --global\n\n ${dim}# Install for Antigravity locally${reset}\n npx code-as-plan --antigravity --local\n\n ${dim}# Install for Cursor globally${reset}\n npx code-as-plan --cursor --global\n\n ${dim}# Install for Cursor locally${reset}\n npx code-as-plan --cursor --local\n\n ${dim}# Install for Windsurf globally${reset}\n npx code-as-plan --windsurf --global\n\n ${dim}# Install for Windsurf locally${reset}\n npx code-as-plan --windsurf --local\n\n ${dim}# Install for all runtimes globally${reset}\n npx code-as-plan --all --global\n\n ${dim}# Install to custom config directory${reset}\n npx code-as-plan --codex --global --config-dir ~/.codex-work\n\n ${dim}# Install to current project only${reset}\n npx code-as-plan --claude --local\n\n ${dim}# Uninstall CAP from Cursor globally${reset}\n npx code-as-plan --cursor --global --uninstall\n\n ${yellow}Notes:${reset}\n The --config-dir option is useful when you have multiple configurations.\n It takes priority over CLAUDE_CONFIG_DIR / GEMINI_CONFIG_DIR / CODEX_HOME / COPILOT_CONFIG_DIR / ANTIGRAVITY_CONFIG_DIR / CURSOR_CONFIG_DIR / WINDSURF_CONFIG_DIR environment variables.\n`);
|
|
326
326
|
process.exit(0);
|
|
327
327
|
}
|
|
328
328
|
|
|
@@ -4691,70 +4691,6 @@ function handleStatusline(settings, isInteractive, callback) {
|
|
|
4691
4691
|
});
|
|
4692
4692
|
}
|
|
4693
4693
|
|
|
4694
|
-
/**
|
|
4695
|
-
* Install the CAP SDK globally via npm.
|
|
4696
|
-
* @returns {boolean} true if install succeeded
|
|
4697
|
-
*/
|
|
4698
|
-
function installSdk() {
|
|
4699
|
-
const sdkPkg = `@gsd-build/sdk@latest`;
|
|
4700
|
-
console.log(`\n ${cyan}Installing CAP SDK...${reset}`);
|
|
4701
|
-
console.log(` ${dim}npm install -g ${sdkPkg}${reset}\n`);
|
|
4702
|
-
try {
|
|
4703
|
-
require('child_process').execSync(`npm install -g ${sdkPkg}`, { stdio: 'inherit' });
|
|
4704
|
-
console.log(`\n ${green}✓${reset} CAP SDK installed (${cyan}cap-sdk${reset} command available)`);
|
|
4705
|
-
return true;
|
|
4706
|
-
} catch (e) {
|
|
4707
|
-
console.log(`\n ${yellow}⚠${reset} SDK install failed: ${e.message}`);
|
|
4708
|
-
console.log(` ${dim}You can install it manually: npm install -g ${sdkPkg}${reset}`);
|
|
4709
|
-
return false;
|
|
4710
|
-
}
|
|
4711
|
-
}
|
|
4712
|
-
|
|
4713
|
-
/**
|
|
4714
|
-
* Prompt the user to optionally install the CAP SDK.
|
|
4715
|
-
* Called after runtime installation completes.
|
|
4716
|
-
* @param {Function} callback - called with true/false
|
|
4717
|
-
*/
|
|
4718
|
-
function promptSdk(callback) {
|
|
4719
|
-
if (!process.stdin.isTTY) {
|
|
4720
|
-
callback(false);
|
|
4721
|
-
return;
|
|
4722
|
-
}
|
|
4723
|
-
|
|
4724
|
-
const rl = readline.createInterface({
|
|
4725
|
-
input: process.stdin,
|
|
4726
|
-
output: process.stdout
|
|
4727
|
-
});
|
|
4728
|
-
|
|
4729
|
-
let answered = false;
|
|
4730
|
-
|
|
4731
|
-
rl.on('close', () => {
|
|
4732
|
-
if (!answered) {
|
|
4733
|
-
answered = true;
|
|
4734
|
-
callback(false);
|
|
4735
|
-
}
|
|
4736
|
-
});
|
|
4737
|
-
|
|
4738
|
-
console.log(`
|
|
4739
|
-
${yellow}Also install the CAP SDK?${reset}
|
|
4740
|
-
|
|
4741
|
-
The SDK provides a standalone CLI for autonomous execution:
|
|
4742
|
-
${dim}cap-sdk init @prd.md${reset} Bootstrap a project from a PRD
|
|
4743
|
-
${dim}cap-sdk auto${reset} Run full autonomous lifecycle
|
|
4744
|
-
${dim}cap-sdk run "prompt"${reset} Execute a milestone from text
|
|
4745
|
-
|
|
4746
|
-
${cyan}1${reset}) No
|
|
4747
|
-
${cyan}2${reset}) Yes ${dim}(runs: npm install -g @gsd-build/sdk)${reset}
|
|
4748
|
-
`);
|
|
4749
|
-
|
|
4750
|
-
rl.question(` Choice ${dim}[1]${reset}: `, (answer) => {
|
|
4751
|
-
answered = true;
|
|
4752
|
-
rl.close();
|
|
4753
|
-
const choice = answer.trim() || '1';
|
|
4754
|
-
callback(choice === '2');
|
|
4755
|
-
});
|
|
4756
|
-
}
|
|
4757
|
-
|
|
4758
4694
|
/**
|
|
4759
4695
|
* Prompt for runtime selection
|
|
4760
4696
|
*/
|
|
@@ -4884,7 +4820,7 @@ function installAllRuntimes(runtimes, isGlobal, isInteractive) {
|
|
|
4884
4820
|
const primaryStatuslineResult = results.find(r => statuslineRuntimes.includes(r.runtime));
|
|
4885
4821
|
|
|
4886
4822
|
const finalize = (shouldInstallStatusline) => {
|
|
4887
|
-
//
|
|
4823
|
+
// Print final summaries
|
|
4888
4824
|
const printSummaries = () => {
|
|
4889
4825
|
for (const result of results) {
|
|
4890
4826
|
const useStatusline = statuslineRuntimes.includes(result.runtime) && shouldInstallStatusline;
|
|
@@ -4899,18 +4835,7 @@ function installAllRuntimes(runtimes, isGlobal, isInteractive) {
|
|
|
4899
4835
|
}
|
|
4900
4836
|
};
|
|
4901
4837
|
|
|
4902
|
-
|
|
4903
|
-
// --sdk flag: install without prompting
|
|
4904
|
-
installSdk();
|
|
4905
|
-
printSummaries();
|
|
4906
|
-
} else if (isInteractive) {
|
|
4907
|
-
promptSdk((wantsSdk) => {
|
|
4908
|
-
if (wantsSdk) installSdk();
|
|
4909
|
-
printSummaries();
|
|
4910
|
-
});
|
|
4911
|
-
} else {
|
|
4912
|
-
printSummaries();
|
|
4913
|
-
}
|
|
4838
|
+
printSummaries();
|
|
4914
4839
|
};
|
|
4915
4840
|
|
|
4916
4841
|
if (primaryStatuslineResult) {
|
|
@@ -0,0 +1,513 @@
|
|
|
1
|
+
// @cap-feature(feature:F-MIGRATE) GSD-to-CAP migration utility -- converts @gsd-* tags, planning artifacts, and session format to CAP v2.0.
|
|
2
|
+
// @cap-todo decision: Regex-based tag replacement (not AST) -- language-agnostic, zero dependencies, handles all comment styles.
|
|
3
|
+
// @cap-todo risk: Destructive file writes -- dry-run mode is the default safety net.
|
|
4
|
+
|
|
5
|
+
'use strict';
|
|
6
|
+
|
|
7
|
+
const fs = require('node:fs');
|
|
8
|
+
const path = require('node:path');
|
|
9
|
+
|
|
10
|
+
// --- Constants ---
|
|
11
|
+
|
|
12
|
+
const GSD_TAG_RE = /(@gsd-(feature|todo|risk|decision|context|status|depends|ref|pattern|api|constraint))(\([^)]*\))?\s*(.*)/;
|
|
13
|
+
|
|
14
|
+
const SUPPORTED_EXTENSIONS = ['.js', '.cjs', '.mjs', '.ts', '.tsx', '.jsx', '.py', '.rb', '.go', '.rs', '.sh', '.md'];
|
|
15
|
+
const EXCLUDE_DIRS = ['node_modules', '.git', '.cap', 'dist', 'build', 'coverage'];
|
|
16
|
+
|
|
17
|
+
const GSD_ARTIFACTS = [
|
|
18
|
+
'.planning/FEATURES.md',
|
|
19
|
+
'.planning/REQUIREMENTS.md',
|
|
20
|
+
'.planning/PRD.md',
|
|
21
|
+
'.planning/ROADMAP.md',
|
|
22
|
+
'.planning/STATE.md',
|
|
23
|
+
'.planning/CODE-INVENTORY.md',
|
|
24
|
+
'.planning/BRAINSTORM-LEDGER.md',
|
|
25
|
+
'.planning/SESSION.json',
|
|
26
|
+
];
|
|
27
|
+
|
|
28
|
+
// --- Tag migration ---
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* @typedef {Object} TagChange
|
|
32
|
+
* @property {string} file - Relative file path
|
|
33
|
+
* @property {number} line - 1-based line number
|
|
34
|
+
* @property {string} original - Original line content
|
|
35
|
+
* @property {string} replaced - Replacement line content (or null if removed)
|
|
36
|
+
* @property {string} action - 'converted' | 'removed' | 'plain-comment'
|
|
37
|
+
*/
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Apply tag migration to a single line.
|
|
41
|
+
* @param {string} line - Source line
|
|
42
|
+
* @returns {{ replaced: string, action: string } | null} - null if no @gsd- tag found
|
|
43
|
+
*/
|
|
44
|
+
function migrateLineTag(line) {
|
|
45
|
+
const match = line.match(GSD_TAG_RE);
|
|
46
|
+
if (!match) return null;
|
|
47
|
+
|
|
48
|
+
const fullTag = match[1]; // e.g., @gsd-feature
|
|
49
|
+
const tagType = match[2]; // e.g., feature
|
|
50
|
+
const metadata = match[3] || ''; // e.g., (ref:AC-20)
|
|
51
|
+
const description = match[4] || '';
|
|
52
|
+
|
|
53
|
+
switch (tagType) {
|
|
54
|
+
case 'feature':
|
|
55
|
+
return {
|
|
56
|
+
replaced: line.replace(fullTag, '@cap-feature'),
|
|
57
|
+
action: 'converted',
|
|
58
|
+
};
|
|
59
|
+
|
|
60
|
+
case 'todo':
|
|
61
|
+
return {
|
|
62
|
+
replaced: line.replace(fullTag, '@cap-todo'),
|
|
63
|
+
action: 'converted',
|
|
64
|
+
};
|
|
65
|
+
|
|
66
|
+
case 'risk':
|
|
67
|
+
// @gsd-risk Some risk → @cap-todo risk: Some risk
|
|
68
|
+
return {
|
|
69
|
+
replaced: line.replace(fullTag + metadata + (description ? ' ' : ''), '@cap-todo' + metadata + ' risk: ').replace(/ +/g, ' '),
|
|
70
|
+
action: 'converted',
|
|
71
|
+
};
|
|
72
|
+
|
|
73
|
+
case 'decision':
|
|
74
|
+
// @gsd-decision Some decision → @cap-todo decision: Some decision
|
|
75
|
+
return {
|
|
76
|
+
replaced: line.replace(fullTag + metadata + (description ? ' ' : ''), '@cap-todo' + metadata + ' decision: ').replace(/ +/g, ' '),
|
|
77
|
+
action: 'converted',
|
|
78
|
+
};
|
|
79
|
+
|
|
80
|
+
case 'constraint':
|
|
81
|
+
// @gsd-constraint Some constraint → @cap-todo risk: [constraint] Some constraint
|
|
82
|
+
return {
|
|
83
|
+
replaced: line.replace(fullTag + metadata + (description ? ' ' : ''), '@cap-todo' + metadata + ' risk: [constraint] ').replace(/ +/g, ' '),
|
|
84
|
+
action: 'converted',
|
|
85
|
+
};
|
|
86
|
+
|
|
87
|
+
case 'context':
|
|
88
|
+
// @gsd-context Some context → plain comment (remove the tag)
|
|
89
|
+
return {
|
|
90
|
+
replaced: line.replace(fullTag + metadata + ' ', '').replace(fullTag + metadata, ''),
|
|
91
|
+
action: 'plain-comment',
|
|
92
|
+
};
|
|
93
|
+
|
|
94
|
+
case 'status':
|
|
95
|
+
case 'depends':
|
|
96
|
+
// Remove entirely (convert to plain comment to avoid losing info)
|
|
97
|
+
return {
|
|
98
|
+
replaced: line.replace(fullTag + metadata + ' ', '').replace(fullTag + metadata, ''),
|
|
99
|
+
action: 'removed',
|
|
100
|
+
};
|
|
101
|
+
|
|
102
|
+
case 'ref':
|
|
103
|
+
// Keep as @cap-ref if it has content, otherwise remove
|
|
104
|
+
if (description.trim()) {
|
|
105
|
+
return {
|
|
106
|
+
replaced: line.replace(fullTag, '@cap-ref'),
|
|
107
|
+
action: 'converted',
|
|
108
|
+
};
|
|
109
|
+
}
|
|
110
|
+
return {
|
|
111
|
+
replaced: line.replace(fullTag + metadata + ' ', '').replace(fullTag + metadata, ''),
|
|
112
|
+
action: 'removed',
|
|
113
|
+
};
|
|
114
|
+
|
|
115
|
+
case 'pattern':
|
|
116
|
+
case 'api':
|
|
117
|
+
// Convert to plain comment (remove the tag prefix)
|
|
118
|
+
return {
|
|
119
|
+
replaced: line.replace(fullTag + metadata + ' ', '').replace(fullTag + metadata, ''),
|
|
120
|
+
action: 'plain-comment',
|
|
121
|
+
};
|
|
122
|
+
|
|
123
|
+
default:
|
|
124
|
+
return null;
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
/**
|
|
129
|
+
* Scan all source files and replace @gsd-* tags with @cap-* equivalents.
|
|
130
|
+
*
|
|
131
|
+
* Mapping:
|
|
132
|
+
* @gsd-feature → @cap-feature
|
|
133
|
+
* @gsd-todo → @cap-todo
|
|
134
|
+
* @gsd-risk → @cap-todo risk:
|
|
135
|
+
* @gsd-decision → @cap-todo decision:
|
|
136
|
+
* @gsd-context → plain comment (tag removed)
|
|
137
|
+
* @gsd-status → plain comment (tag removed)
|
|
138
|
+
* @gsd-depends → plain comment (tag removed)
|
|
139
|
+
* @gsd-ref → @cap-ref (if content exists) or removed
|
|
140
|
+
* @gsd-pattern → plain comment (tag removed)
|
|
141
|
+
* @gsd-api → plain comment (tag removed)
|
|
142
|
+
* @gsd-constraint → @cap-todo risk: [constraint]
|
|
143
|
+
*
|
|
144
|
+
* @param {string} projectRoot - Absolute path to project root
|
|
145
|
+
* @param {Object} [options]
|
|
146
|
+
* @param {boolean} [options.dryRun] - If true, report changes without writing
|
|
147
|
+
* @param {string[]} [options.extensions] - File extensions to process
|
|
148
|
+
* @returns {{ filesScanned: number, filesModified: number, tagsConverted: number, tagsRemoved: number, changes: TagChange[] }}
|
|
149
|
+
*/
|
|
150
|
+
function migrateTags(projectRoot, options = {}) {
|
|
151
|
+
const dryRun = options.dryRun || false;
|
|
152
|
+
const extensions = options.extensions || SUPPORTED_EXTENSIONS;
|
|
153
|
+
const result = {
|
|
154
|
+
filesScanned: 0,
|
|
155
|
+
filesModified: 0,
|
|
156
|
+
tagsConverted: 0,
|
|
157
|
+
tagsRemoved: 0,
|
|
158
|
+
changes: [],
|
|
159
|
+
};
|
|
160
|
+
|
|
161
|
+
function walk(dir) {
|
|
162
|
+
let entries;
|
|
163
|
+
try {
|
|
164
|
+
entries = fs.readdirSync(dir, { withFileTypes: true });
|
|
165
|
+
} catch (_e) {
|
|
166
|
+
return;
|
|
167
|
+
}
|
|
168
|
+
for (const entry of entries) {
|
|
169
|
+
const fullPath = path.join(dir, entry.name);
|
|
170
|
+
if (entry.isDirectory()) {
|
|
171
|
+
if (EXCLUDE_DIRS.includes(entry.name)) continue;
|
|
172
|
+
walk(fullPath);
|
|
173
|
+
} else if (entry.isFile()) {
|
|
174
|
+
const ext = path.extname(entry.name);
|
|
175
|
+
if (!extensions.includes(ext)) continue;
|
|
176
|
+
processFile(fullPath);
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
function processFile(filePath) {
|
|
182
|
+
let content;
|
|
183
|
+
try {
|
|
184
|
+
content = fs.readFileSync(filePath, 'utf8');
|
|
185
|
+
} catch (_e) {
|
|
186
|
+
return;
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
result.filesScanned++;
|
|
190
|
+
const relativePath = path.relative(projectRoot, filePath);
|
|
191
|
+
const lines = content.split('\n');
|
|
192
|
+
let modified = false;
|
|
193
|
+
|
|
194
|
+
for (let i = 0; i < lines.length; i++) {
|
|
195
|
+
const migration = migrateLineTag(lines[i]);
|
|
196
|
+
if (!migration) continue;
|
|
197
|
+
|
|
198
|
+
const change = {
|
|
199
|
+
file: relativePath,
|
|
200
|
+
line: i + 1,
|
|
201
|
+
original: lines[i],
|
|
202
|
+
replaced: migration.replaced,
|
|
203
|
+
action: migration.action,
|
|
204
|
+
};
|
|
205
|
+
result.changes.push(change);
|
|
206
|
+
|
|
207
|
+
if (migration.action === 'converted') {
|
|
208
|
+
result.tagsConverted++;
|
|
209
|
+
} else {
|
|
210
|
+
result.tagsRemoved++;
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
lines[i] = migration.replaced;
|
|
214
|
+
modified = true;
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
if (modified) {
|
|
218
|
+
result.filesModified++;
|
|
219
|
+
if (!dryRun) {
|
|
220
|
+
fs.writeFileSync(filePath, lines.join('\n'), 'utf8');
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
walk(projectRoot);
|
|
226
|
+
return result;
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
// --- Artifact migration ---
|
|
230
|
+
|
|
231
|
+
/**
|
|
232
|
+
* Convert .planning/FEATURES.md or REQUIREMENTS.md into FEATURE-MAP.md format.
|
|
233
|
+
*
|
|
234
|
+
* @param {string} projectRoot - Absolute path to project root
|
|
235
|
+
* @param {Object} [options]
|
|
236
|
+
* @param {boolean} [options.dryRun] - If true, report without writing
|
|
237
|
+
* @returns {{ featuresFound: number, featureMapCreated: boolean, source: string }}
|
|
238
|
+
*/
|
|
239
|
+
function migrateArtifacts(projectRoot, options = {}) {
|
|
240
|
+
const dryRun = options.dryRun || false;
|
|
241
|
+
const result = { featuresFound: 0, featureMapCreated: false, source: 'none' };
|
|
242
|
+
|
|
243
|
+
// Check if FEATURE-MAP.md already exists
|
|
244
|
+
const featureMapPath = path.join(projectRoot, 'FEATURE-MAP.md');
|
|
245
|
+
const featureMapExists = fs.existsSync(featureMapPath);
|
|
246
|
+
|
|
247
|
+
// Try reading source artifacts in priority order
|
|
248
|
+
let sourceContent = null;
|
|
249
|
+
let sourceName = null;
|
|
250
|
+
|
|
251
|
+
const sources = [
|
|
252
|
+
{ file: '.planning/FEATURES.md', name: 'FEATURES.md' },
|
|
253
|
+
{ file: '.planning/REQUIREMENTS.md', name: 'REQUIREMENTS.md' },
|
|
254
|
+
{ file: '.planning/PRD.md', name: 'PRD.md' },
|
|
255
|
+
];
|
|
256
|
+
|
|
257
|
+
for (const src of sources) {
|
|
258
|
+
const srcPath = path.join(projectRoot, src.file);
|
|
259
|
+
if (fs.existsSync(srcPath)) {
|
|
260
|
+
try {
|
|
261
|
+
sourceContent = fs.readFileSync(srcPath, 'utf8');
|
|
262
|
+
sourceName = src.name;
|
|
263
|
+
result.source = src.name;
|
|
264
|
+
break;
|
|
265
|
+
} catch (_e) {
|
|
266
|
+
continue;
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
if (!sourceContent) return result;
|
|
272
|
+
|
|
273
|
+
// Extract features from the source artifact
|
|
274
|
+
const features = extractFeaturesFromLegacy(sourceContent);
|
|
275
|
+
result.featuresFound = features.length;
|
|
276
|
+
|
|
277
|
+
if (features.length === 0) return result;
|
|
278
|
+
|
|
279
|
+
if (featureMapExists) {
|
|
280
|
+
// Merge into existing Feature Map
|
|
281
|
+
const capFeatureMap = require('./cap-feature-map.cjs');
|
|
282
|
+
if (!dryRun) {
|
|
283
|
+
const existing = capFeatureMap.readFeatureMap(projectRoot);
|
|
284
|
+
const existingTitles = new Set(existing.features.map(f => f.title.toLowerCase()));
|
|
285
|
+
|
|
286
|
+
for (const feature of features) {
|
|
287
|
+
if (!existingTitles.has(feature.title.toLowerCase())) {
|
|
288
|
+
capFeatureMap.addFeature(projectRoot, feature);
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
result.featureMapCreated = true;
|
|
292
|
+
}
|
|
293
|
+
} else {
|
|
294
|
+
// Create new Feature Map
|
|
295
|
+
if (!dryRun) {
|
|
296
|
+
const capFeatureMap = require('./cap-feature-map.cjs');
|
|
297
|
+
const template = capFeatureMap.generateTemplate();
|
|
298
|
+
fs.writeFileSync(featureMapPath, template, 'utf8');
|
|
299
|
+
for (const feature of features) {
|
|
300
|
+
capFeatureMap.addFeature(projectRoot, feature);
|
|
301
|
+
}
|
|
302
|
+
result.featureMapCreated = true;
|
|
303
|
+
} else {
|
|
304
|
+
result.featureMapCreated = true; // Would be created
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
return result;
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
/**
|
|
312
|
+
* Extract feature entries from legacy GSD planning artifacts.
|
|
313
|
+
* Looks for markdown headings, list items with feature-like patterns.
|
|
314
|
+
*
|
|
315
|
+
* @param {string} content - Markdown content of legacy artifact
|
|
316
|
+
* @returns {{ title: string, acs: Array, dependencies: string[] }[]}
|
|
317
|
+
*/
|
|
318
|
+
function extractFeaturesFromLegacy(content) {
|
|
319
|
+
const features = [];
|
|
320
|
+
const lines = content.split('\n');
|
|
321
|
+
|
|
322
|
+
// Match headings that look like features: ## Feature Name, ### Feature Name, ## 1. Feature Name
|
|
323
|
+
const featureHeadingRE = /^#{2,4}\s+(?:\d+\.\s*)?(?:Feature:\s*)?(.+?)(?:\s*\[.*\])?\s*$/;
|
|
324
|
+
// Match list items that look like acceptance criteria: - [ ] description, - [x] description
|
|
325
|
+
const acRE = /^[-*]\s+\[([x ])\]\s+(.+)/i;
|
|
326
|
+
// Match plain list items as potential ACs
|
|
327
|
+
const plainListRE = /^[-*]\s+(?!#)(.+)/;
|
|
328
|
+
|
|
329
|
+
let currentFeature = null;
|
|
330
|
+
let acCounter = 0;
|
|
331
|
+
|
|
332
|
+
for (const line of lines) {
|
|
333
|
+
const headingMatch = line.match(featureHeadingRE);
|
|
334
|
+
if (headingMatch) {
|
|
335
|
+
if (currentFeature && currentFeature.title) {
|
|
336
|
+
features.push(currentFeature);
|
|
337
|
+
}
|
|
338
|
+
currentFeature = {
|
|
339
|
+
title: headingMatch[1].trim(),
|
|
340
|
+
acs: [],
|
|
341
|
+
dependencies: [],
|
|
342
|
+
};
|
|
343
|
+
acCounter = 0;
|
|
344
|
+
continue;
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
if (currentFeature) {
|
|
348
|
+
const acMatch = line.match(acRE);
|
|
349
|
+
if (acMatch) {
|
|
350
|
+
acCounter++;
|
|
351
|
+
currentFeature.acs.push({
|
|
352
|
+
id: `AC-${acCounter}`,
|
|
353
|
+
description: acMatch[2].trim(),
|
|
354
|
+
status: acMatch[1] === 'x' || acMatch[1] === 'X' ? 'implemented' : 'pending',
|
|
355
|
+
});
|
|
356
|
+
continue;
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
// Empty line after ACs but before next heading -- stop collecting ACs
|
|
360
|
+
if (line.trim() === '' && currentFeature.acs.length > 0) {
|
|
361
|
+
// Keep collecting -- next heading or feature resets
|
|
362
|
+
}
|
|
363
|
+
}
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
if (currentFeature && currentFeature.title) {
|
|
367
|
+
features.push(currentFeature);
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
return features;
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
// --- Session migration ---
|
|
374
|
+
|
|
375
|
+
/**
|
|
376
|
+
* Migrate .planning/SESSION.json to .cap/SESSION.json format.
|
|
377
|
+
*
|
|
378
|
+
* @param {string} projectRoot - Absolute path to project root
|
|
379
|
+
* @param {Object} [options]
|
|
380
|
+
* @param {boolean} [options.dryRun] - If true, report without writing
|
|
381
|
+
* @returns {{ migrated: boolean, oldFormat: string, newFormat: string }}
|
|
382
|
+
*/
|
|
383
|
+
function migrateSession(projectRoot, options = {}) {
|
|
384
|
+
const dryRun = options.dryRun || false;
|
|
385
|
+
const result = { migrated: false, oldFormat: 'none', newFormat: 'none' };
|
|
386
|
+
|
|
387
|
+
const oldSessionPath = path.join(projectRoot, '.planning', 'SESSION.json');
|
|
388
|
+
if (!fs.existsSync(oldSessionPath)) return result;
|
|
389
|
+
|
|
390
|
+
let oldSession;
|
|
391
|
+
try {
|
|
392
|
+
const content = fs.readFileSync(oldSessionPath, 'utf8');
|
|
393
|
+
oldSession = JSON.parse(content);
|
|
394
|
+
result.oldFormat = 'v1.x';
|
|
395
|
+
} catch (_e) {
|
|
396
|
+
result.oldFormat = 'corrupt';
|
|
397
|
+
return result;
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
// Map old session fields to new CAP session format
|
|
401
|
+
const capSession = require('./cap-session.cjs');
|
|
402
|
+
const newSession = capSession.getDefaultSession();
|
|
403
|
+
|
|
404
|
+
// Map known v1.x fields
|
|
405
|
+
if (oldSession.current_app) {
|
|
406
|
+
newSession.metadata.legacyApp = oldSession.current_app;
|
|
407
|
+
}
|
|
408
|
+
if (oldSession.current_phase) {
|
|
409
|
+
newSession.step = `legacy-phase-${oldSession.current_phase}`;
|
|
410
|
+
}
|
|
411
|
+
if (oldSession.started_at || oldSession.startedAt) {
|
|
412
|
+
newSession.startedAt = oldSession.started_at || oldSession.startedAt;
|
|
413
|
+
}
|
|
414
|
+
if (oldSession.last_command || oldSession.lastCommand) {
|
|
415
|
+
newSession.lastCommand = oldSession.last_command || oldSession.lastCommand;
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
// Preserve all old fields as metadata for reference
|
|
419
|
+
for (const [key, value] of Object.entries(oldSession)) {
|
|
420
|
+
if (typeof value === 'string' || typeof value === 'number' || typeof value === 'boolean') {
|
|
421
|
+
newSession.metadata[`gsd_${key}`] = String(value);
|
|
422
|
+
}
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
result.newFormat = 'v2.0';
|
|
426
|
+
|
|
427
|
+
if (!dryRun) {
|
|
428
|
+
capSession.initCapDirectory(projectRoot);
|
|
429
|
+
capSession.saveSession(projectRoot, newSession);
|
|
430
|
+
result.migrated = true;
|
|
431
|
+
} else {
|
|
432
|
+
result.migrated = true; // Would be migrated
|
|
433
|
+
}
|
|
434
|
+
|
|
435
|
+
return result;
|
|
436
|
+
}
|
|
437
|
+
|
|
438
|
+
// --- Analysis ---
|
|
439
|
+
|
|
440
|
+
/**
|
|
441
|
+
* Generate a migration report summarizing what was found and what needs attention.
|
|
442
|
+
*
|
|
443
|
+
* @param {string} projectRoot - Absolute path to project root
|
|
444
|
+
* @returns {{ gsdTagCount: number, gsdArtifacts: string[], planningDir: boolean, sessionJson: boolean, recommendations: string[] }}
|
|
445
|
+
*/
|
|
446
|
+
function analyzeMigration(projectRoot) {
|
|
447
|
+
const result = {
|
|
448
|
+
gsdTagCount: 0,
|
|
449
|
+
gsdArtifacts: [],
|
|
450
|
+
planningDir: false,
|
|
451
|
+
sessionJson: false,
|
|
452
|
+
recommendations: [],
|
|
453
|
+
};
|
|
454
|
+
|
|
455
|
+
// Check for .planning/ directory
|
|
456
|
+
const planningDir = path.join(projectRoot, '.planning');
|
|
457
|
+
result.planningDir = fs.existsSync(planningDir);
|
|
458
|
+
|
|
459
|
+
// Check for known GSD artifacts
|
|
460
|
+
for (const artifact of GSD_ARTIFACTS) {
|
|
461
|
+
const artifactPath = path.join(projectRoot, artifact);
|
|
462
|
+
if (fs.existsSync(artifactPath)) {
|
|
463
|
+
result.gsdArtifacts.push(artifact);
|
|
464
|
+
}
|
|
465
|
+
}
|
|
466
|
+
|
|
467
|
+
// Check for .planning/SESSION.json specifically
|
|
468
|
+
result.sessionJson = fs.existsSync(path.join(projectRoot, '.planning', 'SESSION.json'));
|
|
469
|
+
|
|
470
|
+
// Count @gsd-* tags in source files
|
|
471
|
+
const tagResult = migrateTags(projectRoot, { dryRun: true });
|
|
472
|
+
result.gsdTagCount = tagResult.tagsConverted + tagResult.tagsRemoved;
|
|
473
|
+
|
|
474
|
+
// Build recommendations
|
|
475
|
+
if (result.gsdTagCount > 0) {
|
|
476
|
+
result.recommendations.push(`Found ${result.gsdTagCount} @gsd-* tags to migrate. Run /cap:migrate to convert them to @cap-* tags.`);
|
|
477
|
+
}
|
|
478
|
+
|
|
479
|
+
if (result.gsdArtifacts.length > 0) {
|
|
480
|
+
result.recommendations.push(`Found ${result.gsdArtifacts.length} legacy planning artifacts: ${result.gsdArtifacts.join(', ')}. These can be converted to FEATURE-MAP.md entries.`);
|
|
481
|
+
}
|
|
482
|
+
|
|
483
|
+
if (result.sessionJson) {
|
|
484
|
+
result.recommendations.push('Found .planning/SESSION.json. This can be migrated to .cap/SESSION.json format.');
|
|
485
|
+
}
|
|
486
|
+
|
|
487
|
+
if (!fs.existsSync(path.join(projectRoot, 'FEATURE-MAP.md'))) {
|
|
488
|
+
result.recommendations.push('No FEATURE-MAP.md found. Migration will create one from existing artifacts.');
|
|
489
|
+
}
|
|
490
|
+
|
|
491
|
+
if (!fs.existsSync(path.join(projectRoot, '.cap'))) {
|
|
492
|
+
result.recommendations.push('No .cap/ directory found. Migration will initialize it.');
|
|
493
|
+
}
|
|
494
|
+
|
|
495
|
+
if (result.gsdTagCount === 0 && result.gsdArtifacts.length === 0 && !result.sessionJson) {
|
|
496
|
+
result.recommendations.push('No GSD v1.x artifacts detected. This project may already be using CAP v2.0 or is a fresh project.');
|
|
497
|
+
}
|
|
498
|
+
|
|
499
|
+
return result;
|
|
500
|
+
}
|
|
501
|
+
|
|
502
|
+
module.exports = {
|
|
503
|
+
GSD_TAG_RE,
|
|
504
|
+
SUPPORTED_EXTENSIONS,
|
|
505
|
+
EXCLUDE_DIRS,
|
|
506
|
+
GSD_ARTIFACTS,
|
|
507
|
+
migrateLineTag,
|
|
508
|
+
migrateTags,
|
|
509
|
+
migrateArtifacts,
|
|
510
|
+
extractFeaturesFromLegacy,
|
|
511
|
+
migrateSession,
|
|
512
|
+
analyzeMigration,
|
|
513
|
+
};
|
|
@@ -439,11 +439,82 @@ function groupByPackage(tags, packages) {
|
|
|
439
439
|
return groups;
|
|
440
440
|
}
|
|
441
441
|
|
|
442
|
+
// @cap-todo Detect legacy @gsd-* tags and recommend /cap:migrate
|
|
443
|
+
const LEGACY_TAG_RE = /^[ \t]*(?:\/\/|\/\*|\*|#|--|"""|''')[ \t]*@gsd-(feature|todo|risk|decision|context|status|depends|ref|pattern|api|constraint)/;
|
|
444
|
+
|
|
445
|
+
/**
|
|
446
|
+
* Detect legacy @gsd-* tags in scanned files.
|
|
447
|
+
* Re-scans source files for @gsd-* patterns that the primary scanner ignores.
|
|
448
|
+
*
|
|
449
|
+
* @param {string} projectRoot - Absolute path to project root
|
|
450
|
+
* @param {Object} [options]
|
|
451
|
+
* @param {string[]} [options.extensions] - File extensions to include
|
|
452
|
+
* @param {string[]} [options.exclude] - Directory names to exclude
|
|
453
|
+
* @returns {{ count: number, files: string[], recommendation: string }}
|
|
454
|
+
*/
|
|
455
|
+
function detectLegacyTags(projectRoot, options = {}) {
|
|
456
|
+
const extensions = options.extensions || SUPPORTED_EXTENSIONS;
|
|
457
|
+
const exclude = options.exclude || DEFAULT_EXCLUDE;
|
|
458
|
+
const result = { count: 0, files: [], recommendation: '' };
|
|
459
|
+
const fileSet = new Set();
|
|
460
|
+
|
|
461
|
+
function walk(dir) {
|
|
462
|
+
let entries;
|
|
463
|
+
try {
|
|
464
|
+
entries = fs.readdirSync(dir, { withFileTypes: true });
|
|
465
|
+
} catch (_e) {
|
|
466
|
+
return;
|
|
467
|
+
}
|
|
468
|
+
for (const entry of entries) {
|
|
469
|
+
const fullPath = path.join(dir, entry.name);
|
|
470
|
+
if (entry.isDirectory()) {
|
|
471
|
+
if (exclude.includes(entry.name)) continue;
|
|
472
|
+
walk(fullPath);
|
|
473
|
+
} else if (entry.isFile()) {
|
|
474
|
+
const ext = path.extname(entry.name);
|
|
475
|
+
if (!extensions.includes(ext)) continue;
|
|
476
|
+
scanFileForLegacy(fullPath);
|
|
477
|
+
}
|
|
478
|
+
}
|
|
479
|
+
}
|
|
480
|
+
|
|
481
|
+
function scanFileForLegacy(filePath) {
|
|
482
|
+
let content;
|
|
483
|
+
try {
|
|
484
|
+
content = fs.readFileSync(filePath, 'utf8');
|
|
485
|
+
} catch (_e) {
|
|
486
|
+
return;
|
|
487
|
+
}
|
|
488
|
+
const lines = content.split('\n');
|
|
489
|
+
let found = false;
|
|
490
|
+
for (const line of lines) {
|
|
491
|
+
if (LEGACY_TAG_RE.test(line)) {
|
|
492
|
+
result.count++;
|
|
493
|
+
found = true;
|
|
494
|
+
}
|
|
495
|
+
}
|
|
496
|
+
if (found) {
|
|
497
|
+
const relativePath = path.relative(projectRoot, filePath);
|
|
498
|
+
fileSet.add(relativePath);
|
|
499
|
+
}
|
|
500
|
+
}
|
|
501
|
+
|
|
502
|
+
walk(projectRoot);
|
|
503
|
+
result.files = Array.from(fileSet).sort();
|
|
504
|
+
|
|
505
|
+
if (result.count > 0) {
|
|
506
|
+
result.recommendation = `Found ${result.count} legacy @gsd-* tag(s) in ${result.files.length} file(s). Run /cap:migrate to convert them to @cap-* format.`;
|
|
507
|
+
}
|
|
508
|
+
|
|
509
|
+
return result;
|
|
510
|
+
}
|
|
511
|
+
|
|
442
512
|
module.exports = {
|
|
443
513
|
CAP_TAG_TYPES,
|
|
444
514
|
CAP_TAG_RE,
|
|
445
515
|
SUPPORTED_EXTENSIONS,
|
|
446
516
|
DEFAULT_EXCLUDE,
|
|
517
|
+
LEGACY_TAG_RE,
|
|
447
518
|
scanFile,
|
|
448
519
|
scanDirectory,
|
|
449
520
|
extractTags,
|
|
@@ -455,4 +526,5 @@ module.exports = {
|
|
|
455
526
|
resolveWorkspaceGlobs,
|
|
456
527
|
scanMonorepo,
|
|
457
528
|
groupByPackage,
|
|
529
|
+
detectLegacyTags,
|
|
458
530
|
};
|
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: cap:migrate
|
|
3
|
+
description: "Migrate GSD Code-First v1.x projects to CAP v2.0 -- converts @gsd-* tags, planning artifacts, and session format."
|
|
4
|
+
argument-hint: "[--dry-run] [--tags-only] [--force]"
|
|
5
|
+
allowed-tools:
|
|
6
|
+
- Read
|
|
7
|
+
- Write
|
|
8
|
+
- Bash
|
|
9
|
+
- Glob
|
|
10
|
+
- Grep
|
|
11
|
+
- AskUserQuestion
|
|
12
|
+
---
|
|
13
|
+
|
|
14
|
+
<!-- @cap-feature(feature:F-MIGRATE) Migration command -- converts GSD v1.x projects to CAP v2.0 format. -->
|
|
15
|
+
<!-- @cap-todo Supports --dry-run, --tags-only, and --force flags. -->
|
|
16
|
+
|
|
17
|
+
<objective>
|
|
18
|
+
Migrate a GSD Code-First v1.x project to CAP v2.0 format. Converts @gsd-* tags to @cap-* equivalents, transforms planning artifacts into FEATURE-MAP.md entries, and migrates .planning/SESSION.json to .cap/SESSION.json.
|
|
19
|
+
</objective>
|
|
20
|
+
|
|
21
|
+
<context>
|
|
22
|
+
$ARGUMENTS
|
|
23
|
+
|
|
24
|
+
@FEATURE-MAP.md
|
|
25
|
+
</context>
|
|
26
|
+
|
|
27
|
+
<process>
|
|
28
|
+
|
|
29
|
+
## Step 0: Parse flags
|
|
30
|
+
|
|
31
|
+
Check `$ARGUMENTS` for:
|
|
32
|
+
- `--dry-run` — show what would change without writing files
|
|
33
|
+
- `--tags-only` — only migrate tags, skip artifact and session migration
|
|
34
|
+
- `--force` — skip user confirmation gate
|
|
35
|
+
|
|
36
|
+
## Step 1: Analyze migration scope
|
|
37
|
+
|
|
38
|
+
Run the analysis to determine what needs migrating:
|
|
39
|
+
|
|
40
|
+
```bash
|
|
41
|
+
node -e "
|
|
42
|
+
const migrate = require('./cap/bin/lib/cap-migrate.cjs');
|
|
43
|
+
const report = migrate.analyzeMigration(process.cwd());
|
|
44
|
+
console.log(JSON.stringify(report, null, 2));
|
|
45
|
+
"
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
Store as `analysis`. Present a summary to the user:
|
|
49
|
+
|
|
50
|
+
```
|
|
51
|
+
Migration Analysis
|
|
52
|
+
==================
|
|
53
|
+
|
|
54
|
+
@gsd-* tags found: {gsdTagCount}
|
|
55
|
+
Legacy artifacts: {gsdArtifacts.length} ({list})
|
|
56
|
+
.planning/ directory: {yes/no}
|
|
57
|
+
SESSION.json (v1.x): {yes/no}
|
|
58
|
+
|
|
59
|
+
Recommendations:
|
|
60
|
+
{For each recommendation:}
|
|
61
|
+
- {recommendation}
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
## Step 2: Confirm with user (unless --force)
|
|
65
|
+
|
|
66
|
+
If `--force` is NOT set and this is NOT `--dry-run`, use AskUserQuestion to confirm:
|
|
67
|
+
|
|
68
|
+
> "Proceed with migration? This will modify source files and create CAP v2.0 artifacts. Use --dry-run first if you want to preview changes."
|
|
69
|
+
|
|
70
|
+
If user declines, abort with message: "Migration cancelled. Run with --dry-run to preview changes."
|
|
71
|
+
|
|
72
|
+
## Step 3: Migrate tags
|
|
73
|
+
|
|
74
|
+
```bash
|
|
75
|
+
node -e "
|
|
76
|
+
const migrate = require('./cap/bin/lib/cap-migrate.cjs');
|
|
77
|
+
const dryRun = process.argv[1] === 'true';
|
|
78
|
+
const result = migrate.migrateTags(process.cwd(), { dryRun });
|
|
79
|
+
console.log(JSON.stringify(result, null, 2));
|
|
80
|
+
" '<DRY_RUN_VALUE>'
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
Store as `tag_result`. Report:
|
|
84
|
+
|
|
85
|
+
```
|
|
86
|
+
Tag Migration
|
|
87
|
+
=============
|
|
88
|
+
Files scanned: {filesScanned}
|
|
89
|
+
Files modified: {filesModified}
|
|
90
|
+
Tags converted: {tagsConverted}
|
|
91
|
+
Tags removed: {tagsRemoved}
|
|
92
|
+
|
|
93
|
+
{If changes.length > 0, show first 20 changes:}
|
|
94
|
+
Changes:
|
|
95
|
+
{file}:{line} [{action}]
|
|
96
|
+
- {original}
|
|
97
|
+
+ {replaced}
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
If `--tags-only` is set, skip to Step 6.
|
|
101
|
+
|
|
102
|
+
## Step 4: Migrate artifacts
|
|
103
|
+
|
|
104
|
+
```bash
|
|
105
|
+
node -e "
|
|
106
|
+
const migrate = require('./cap/bin/lib/cap-migrate.cjs');
|
|
107
|
+
const dryRun = process.argv[1] === 'true';
|
|
108
|
+
const result = migrate.migrateArtifacts(process.cwd(), { dryRun });
|
|
109
|
+
console.log(JSON.stringify(result, null, 2));
|
|
110
|
+
" '<DRY_RUN_VALUE>'
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
Store as `artifact_result`. Report:
|
|
114
|
+
|
|
115
|
+
```
|
|
116
|
+
Artifact Migration
|
|
117
|
+
==================
|
|
118
|
+
Source: {source}
|
|
119
|
+
Features found: {featuresFound}
|
|
120
|
+
Feature Map: {featureMapCreated ? 'created/updated' : 'no changes'}
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
## Step 5: Migrate session
|
|
124
|
+
|
|
125
|
+
```bash
|
|
126
|
+
node -e "
|
|
127
|
+
const migrate = require('./cap/bin/lib/cap-migrate.cjs');
|
|
128
|
+
const dryRun = process.argv[1] === 'true';
|
|
129
|
+
const result = migrate.migrateSession(process.cwd(), { dryRun });
|
|
130
|
+
console.log(JSON.stringify(result, null, 2));
|
|
131
|
+
" '<DRY_RUN_VALUE>'
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
Store as `session_result`. Report:
|
|
135
|
+
|
|
136
|
+
```
|
|
137
|
+
Session Migration
|
|
138
|
+
=================
|
|
139
|
+
Old format: {oldFormat}
|
|
140
|
+
New format: {newFormat}
|
|
141
|
+
Migrated: {migrated ? 'yes' : 'no'}
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
## Step 6: Final report and session update
|
|
145
|
+
|
|
146
|
+
```
|
|
147
|
+
Migration Complete
|
|
148
|
+
==================
|
|
149
|
+
|
|
150
|
+
Tags: {tagsConverted} converted, {tagsRemoved} removed
|
|
151
|
+
Artifacts: {featuresFound} features extracted → FEATURE-MAP.md
|
|
152
|
+
Session: {migrated ? 'migrated to .cap/SESSION.json' : 'no session to migrate'}
|
|
153
|
+
|
|
154
|
+
{If dry run:}
|
|
155
|
+
NOTE: This was a dry run. No files were modified. Run without --dry-run to apply changes.
|
|
156
|
+
|
|
157
|
+
{If not dry run:}
|
|
158
|
+
Next steps:
|
|
159
|
+
1. Review changes with `git diff`
|
|
160
|
+
2. Run /cap:scan to verify tag migration
|
|
161
|
+
3. Run /cap:status to see project state from Feature Map
|
|
162
|
+
4. Commit migrated files
|
|
163
|
+
```
|
|
164
|
+
|
|
165
|
+
Update session state (unless dry run):
|
|
166
|
+
|
|
167
|
+
```bash
|
|
168
|
+
node -e "
|
|
169
|
+
const session = require('./cap/bin/lib/cap-session.cjs');
|
|
170
|
+
session.updateSession(process.cwd(), {
|
|
171
|
+
lastCommand: '/cap:migrate',
|
|
172
|
+
lastCommandTimestamp: new Date().toISOString()
|
|
173
|
+
});
|
|
174
|
+
"
|
|
175
|
+
```
|
|
176
|
+
|
|
177
|
+
</process>
|