cap-pro 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (275) hide show
  1. package/.claude-plugin/README.md +26 -0
  2. package/.claude-plugin/marketplace.json +24 -0
  3. package/.claude-plugin/plugin.json +24 -0
  4. package/LICENSE +21 -0
  5. package/README.ja-JP.md +834 -0
  6. package/README.ko-KR.md +823 -0
  7. package/README.md +806 -0
  8. package/README.pt-BR.md +452 -0
  9. package/README.zh-CN.md +800 -0
  10. package/agents/cap-architect.md +269 -0
  11. package/agents/cap-brainstormer.md +207 -0
  12. package/agents/cap-curator.md +276 -0
  13. package/agents/cap-debugger.md +365 -0
  14. package/agents/cap-designer.md +246 -0
  15. package/agents/cap-historian.md +464 -0
  16. package/agents/cap-migrator.md +291 -0
  17. package/agents/cap-prototyper.md +197 -0
  18. package/agents/cap-validator.md +308 -0
  19. package/bin/install.js +5433 -0
  20. package/cap/bin/cap-tools.cjs +853 -0
  21. package/cap/bin/lib/arc-scanner.cjs +344 -0
  22. package/cap/bin/lib/cap-affinity-engine.cjs +862 -0
  23. package/cap/bin/lib/cap-anchor.cjs +228 -0
  24. package/cap/bin/lib/cap-annotation-writer.cjs +340 -0
  25. package/cap/bin/lib/cap-checkpoint.cjs +434 -0
  26. package/cap/bin/lib/cap-cluster-detect.cjs +945 -0
  27. package/cap/bin/lib/cap-cluster-display.cjs +52 -0
  28. package/cap/bin/lib/cap-cluster-format.cjs +245 -0
  29. package/cap/bin/lib/cap-cluster-helpers.cjs +295 -0
  30. package/cap/bin/lib/cap-cluster-io.cjs +212 -0
  31. package/cap/bin/lib/cap-completeness.cjs +540 -0
  32. package/cap/bin/lib/cap-deps.cjs +583 -0
  33. package/cap/bin/lib/cap-design-families.cjs +332 -0
  34. package/cap/bin/lib/cap-design.cjs +966 -0
  35. package/cap/bin/lib/cap-divergence-detector.cjs +400 -0
  36. package/cap/bin/lib/cap-doctor.cjs +752 -0
  37. package/cap/bin/lib/cap-feature-map-internals.cjs +19 -0
  38. package/cap/bin/lib/cap-feature-map-migrate.cjs +335 -0
  39. package/cap/bin/lib/cap-feature-map-monorepo.cjs +885 -0
  40. package/cap/bin/lib/cap-feature-map-shard.cjs +315 -0
  41. package/cap/bin/lib/cap-feature-map.cjs +1943 -0
  42. package/cap/bin/lib/cap-fitness-score.cjs +1075 -0
  43. package/cap/bin/lib/cap-impact-analysis.cjs +652 -0
  44. package/cap/bin/lib/cap-learn-review.cjs +1072 -0
  45. package/cap/bin/lib/cap-learning-signals.cjs +627 -0
  46. package/cap/bin/lib/cap-loader.cjs +227 -0
  47. package/cap/bin/lib/cap-logger.cjs +57 -0
  48. package/cap/bin/lib/cap-memory-bridge.cjs +764 -0
  49. package/cap/bin/lib/cap-memory-confidence.cjs +452 -0
  50. package/cap/bin/lib/cap-memory-dir.cjs +987 -0
  51. package/cap/bin/lib/cap-memory-engine.cjs +698 -0
  52. package/cap/bin/lib/cap-memory-extends.cjs +398 -0
  53. package/cap/bin/lib/cap-memory-graph.cjs +790 -0
  54. package/cap/bin/lib/cap-memory-migrate.cjs +2015 -0
  55. package/cap/bin/lib/cap-memory-pin.cjs +183 -0
  56. package/cap/bin/lib/cap-memory-platform.cjs +490 -0
  57. package/cap/bin/lib/cap-memory-prune.cjs +707 -0
  58. package/cap/bin/lib/cap-memory-schema.cjs +812 -0
  59. package/cap/bin/lib/cap-migrate-tags.cjs +309 -0
  60. package/cap/bin/lib/cap-migrate.cjs +540 -0
  61. package/cap/bin/lib/cap-pattern-apply.cjs +1203 -0
  62. package/cap/bin/lib/cap-pattern-pipeline.cjs +1034 -0
  63. package/cap/bin/lib/cap-plugin-manifest.cjs +80 -0
  64. package/cap/bin/lib/cap-realtime-affinity.cjs +399 -0
  65. package/cap/bin/lib/cap-reconcile.cjs +570 -0
  66. package/cap/bin/lib/cap-research-gate.cjs +218 -0
  67. package/cap/bin/lib/cap-scope-filter.cjs +402 -0
  68. package/cap/bin/lib/cap-semantic-pipeline.cjs +1038 -0
  69. package/cap/bin/lib/cap-session-extract.cjs +987 -0
  70. package/cap/bin/lib/cap-session.cjs +445 -0
  71. package/cap/bin/lib/cap-snapshot-linkage.cjs +963 -0
  72. package/cap/bin/lib/cap-stack-docs.cjs +646 -0
  73. package/cap/bin/lib/cap-tag-observer.cjs +371 -0
  74. package/cap/bin/lib/cap-tag-scanner.cjs +1766 -0
  75. package/cap/bin/lib/cap-telemetry.cjs +466 -0
  76. package/cap/bin/lib/cap-test-audit.cjs +1438 -0
  77. package/cap/bin/lib/cap-thread-migrator.cjs +307 -0
  78. package/cap/bin/lib/cap-thread-synthesis.cjs +545 -0
  79. package/cap/bin/lib/cap-thread-tracker.cjs +519 -0
  80. package/cap/bin/lib/cap-trace.cjs +399 -0
  81. package/cap/bin/lib/cap-trust-mode.cjs +336 -0
  82. package/cap/bin/lib/cap-ui-design-editor.cjs +642 -0
  83. package/cap/bin/lib/cap-ui-mind-map.cjs +712 -0
  84. package/cap/bin/lib/cap-ui-thread-nav.cjs +693 -0
  85. package/cap/bin/lib/cap-ui.cjs +1245 -0
  86. package/cap/bin/lib/cap-upgrade.cjs +1028 -0
  87. package/cap/bin/lib/cli/arg-helpers.cjs +49 -0
  88. package/cap/bin/lib/cli/frontmatter-router.cjs +31 -0
  89. package/cap/bin/lib/cli/init-router.cjs +68 -0
  90. package/cap/bin/lib/cli/phase-router.cjs +102 -0
  91. package/cap/bin/lib/cli/state-router.cjs +61 -0
  92. package/cap/bin/lib/cli/template-router.cjs +37 -0
  93. package/cap/bin/lib/cli/uat-router.cjs +29 -0
  94. package/cap/bin/lib/cli/validation-router.cjs +26 -0
  95. package/cap/bin/lib/cli/verification-router.cjs +31 -0
  96. package/cap/bin/lib/cli/workstream-router.cjs +39 -0
  97. package/cap/bin/lib/commands.cjs +961 -0
  98. package/cap/bin/lib/config.cjs +467 -0
  99. package/cap/bin/lib/convention-reader.cjs +258 -0
  100. package/cap/bin/lib/core.cjs +1241 -0
  101. package/cap/bin/lib/feature-aggregator.cjs +423 -0
  102. package/cap/bin/lib/frontmatter.cjs +337 -0
  103. package/cap/bin/lib/init.cjs +1443 -0
  104. package/cap/bin/lib/manifest-generator.cjs +383 -0
  105. package/cap/bin/lib/milestone.cjs +253 -0
  106. package/cap/bin/lib/model-profiles.cjs +69 -0
  107. package/cap/bin/lib/monorepo-context.cjs +226 -0
  108. package/cap/bin/lib/monorepo-migrator.cjs +509 -0
  109. package/cap/bin/lib/phase.cjs +889 -0
  110. package/cap/bin/lib/profile-output.cjs +989 -0
  111. package/cap/bin/lib/profile-pipeline.cjs +540 -0
  112. package/cap/bin/lib/roadmap.cjs +330 -0
  113. package/cap/bin/lib/security.cjs +394 -0
  114. package/cap/bin/lib/session-manager.cjs +292 -0
  115. package/cap/bin/lib/skeleton-generator.cjs +179 -0
  116. package/cap/bin/lib/state.cjs +1032 -0
  117. package/cap/bin/lib/template.cjs +231 -0
  118. package/cap/bin/lib/test-detector.cjs +62 -0
  119. package/cap/bin/lib/uat.cjs +283 -0
  120. package/cap/bin/lib/verify.cjs +889 -0
  121. package/cap/bin/lib/workspace-detector.cjs +371 -0
  122. package/cap/bin/lib/workstream.cjs +492 -0
  123. package/cap/commands/gsd/workstreams.md +63 -0
  124. package/cap/references/arc-standard.md +315 -0
  125. package/cap/references/cap-agent-architecture.md +101 -0
  126. package/cap/references/cap-gitignore-template +9 -0
  127. package/cap/references/cap-zero-deps.md +158 -0
  128. package/cap/references/checkpoints.md +778 -0
  129. package/cap/references/continuation-format.md +249 -0
  130. package/cap/references/contract-test-templates.md +312 -0
  131. package/cap/references/feature-map-template.md +25 -0
  132. package/cap/references/git-integration.md +295 -0
  133. package/cap/references/git-planning-commit.md +38 -0
  134. package/cap/references/model-profiles.md +174 -0
  135. package/cap/references/phase-numbering.md +126 -0
  136. package/cap/references/planning-config.md +202 -0
  137. package/cap/references/property-test-templates.md +316 -0
  138. package/cap/references/security-test-templates.md +347 -0
  139. package/cap/references/session-template.json +8 -0
  140. package/cap/references/tdd.md +263 -0
  141. package/cap/references/user-profiling.md +681 -0
  142. package/cap/references/verification-patterns.md +612 -0
  143. package/cap/templates/UAT.md +265 -0
  144. package/cap/templates/claude-md.md +175 -0
  145. package/cap/templates/codebase/architecture.md +255 -0
  146. package/cap/templates/codebase/concerns.md +310 -0
  147. package/cap/templates/codebase/conventions.md +307 -0
  148. package/cap/templates/codebase/integrations.md +280 -0
  149. package/cap/templates/codebase/stack.md +186 -0
  150. package/cap/templates/codebase/structure.md +285 -0
  151. package/cap/templates/codebase/testing.md +480 -0
  152. package/cap/templates/config.json +44 -0
  153. package/cap/templates/context.md +352 -0
  154. package/cap/templates/continue-here.md +78 -0
  155. package/cap/templates/copilot-instructions.md +7 -0
  156. package/cap/templates/debug-subagent-prompt.md +91 -0
  157. package/cap/templates/discussion-log.md +63 -0
  158. package/cap/templates/milestone-archive.md +123 -0
  159. package/cap/templates/milestone.md +115 -0
  160. package/cap/templates/phase-prompt.md +610 -0
  161. package/cap/templates/planner-subagent-prompt.md +117 -0
  162. package/cap/templates/project.md +186 -0
  163. package/cap/templates/requirements.md +231 -0
  164. package/cap/templates/research-project/ARCHITECTURE.md +204 -0
  165. package/cap/templates/research-project/FEATURES.md +147 -0
  166. package/cap/templates/research-project/PITFALLS.md +200 -0
  167. package/cap/templates/research-project/STACK.md +120 -0
  168. package/cap/templates/research-project/SUMMARY.md +170 -0
  169. package/cap/templates/research.md +552 -0
  170. package/cap/templates/roadmap.md +202 -0
  171. package/cap/templates/state.md +176 -0
  172. package/cap/templates/summary.md +364 -0
  173. package/cap/templates/user-preferences.md +498 -0
  174. package/cap/templates/verification-report.md +322 -0
  175. package/cap/workflows/add-phase.md +112 -0
  176. package/cap/workflows/add-tests.md +351 -0
  177. package/cap/workflows/add-todo.md +158 -0
  178. package/cap/workflows/audit-milestone.md +340 -0
  179. package/cap/workflows/audit-uat.md +109 -0
  180. package/cap/workflows/autonomous.md +891 -0
  181. package/cap/workflows/check-todos.md +177 -0
  182. package/cap/workflows/cleanup.md +152 -0
  183. package/cap/workflows/complete-milestone.md +767 -0
  184. package/cap/workflows/diagnose-issues.md +231 -0
  185. package/cap/workflows/discovery-phase.md +289 -0
  186. package/cap/workflows/discuss-phase-assumptions.md +653 -0
  187. package/cap/workflows/discuss-phase.md +1049 -0
  188. package/cap/workflows/do.md +104 -0
  189. package/cap/workflows/execute-phase.md +846 -0
  190. package/cap/workflows/execute-plan.md +514 -0
  191. package/cap/workflows/fast.md +105 -0
  192. package/cap/workflows/forensics.md +265 -0
  193. package/cap/workflows/health.md +181 -0
  194. package/cap/workflows/help.md +660 -0
  195. package/cap/workflows/insert-phase.md +130 -0
  196. package/cap/workflows/list-phase-assumptions.md +178 -0
  197. package/cap/workflows/list-workspaces.md +56 -0
  198. package/cap/workflows/manager.md +362 -0
  199. package/cap/workflows/map-codebase.md +377 -0
  200. package/cap/workflows/milestone-summary.md +223 -0
  201. package/cap/workflows/new-milestone.md +486 -0
  202. package/cap/workflows/new-project.md +1250 -0
  203. package/cap/workflows/new-workspace.md +237 -0
  204. package/cap/workflows/next.md +97 -0
  205. package/cap/workflows/node-repair.md +92 -0
  206. package/cap/workflows/note.md +156 -0
  207. package/cap/workflows/pause-work.md +176 -0
  208. package/cap/workflows/plan-milestone-gaps.md +273 -0
  209. package/cap/workflows/plan-phase.md +857 -0
  210. package/cap/workflows/plant-seed.md +169 -0
  211. package/cap/workflows/pr-branch.md +129 -0
  212. package/cap/workflows/profile-user.md +449 -0
  213. package/cap/workflows/progress.md +507 -0
  214. package/cap/workflows/quick.md +757 -0
  215. package/cap/workflows/remove-phase.md +155 -0
  216. package/cap/workflows/remove-workspace.md +90 -0
  217. package/cap/workflows/research-phase.md +82 -0
  218. package/cap/workflows/resume-project.md +326 -0
  219. package/cap/workflows/review.md +228 -0
  220. package/cap/workflows/session-report.md +146 -0
  221. package/cap/workflows/settings.md +283 -0
  222. package/cap/workflows/ship.md +228 -0
  223. package/cap/workflows/stats.md +60 -0
  224. package/cap/workflows/transition.md +671 -0
  225. package/cap/workflows/ui-phase.md +298 -0
  226. package/cap/workflows/ui-review.md +161 -0
  227. package/cap/workflows/update.md +323 -0
  228. package/cap/workflows/validate-phase.md +170 -0
  229. package/cap/workflows/verify-phase.md +254 -0
  230. package/cap/workflows/verify-work.md +637 -0
  231. package/commands/cap/annotate.md +165 -0
  232. package/commands/cap/brainstorm.md +393 -0
  233. package/commands/cap/checkpoint.md +106 -0
  234. package/commands/cap/completeness.md +94 -0
  235. package/commands/cap/continue.md +72 -0
  236. package/commands/cap/debug.md +588 -0
  237. package/commands/cap/deps.md +169 -0
  238. package/commands/cap/design.md +479 -0
  239. package/commands/cap/init.md +354 -0
  240. package/commands/cap/iterate.md +249 -0
  241. package/commands/cap/learn.md +459 -0
  242. package/commands/cap/memory.md +275 -0
  243. package/commands/cap/migrate-feature-map.md +91 -0
  244. package/commands/cap/migrate-memory.md +108 -0
  245. package/commands/cap/migrate-tags.md +91 -0
  246. package/commands/cap/migrate.md +131 -0
  247. package/commands/cap/prototype.md +510 -0
  248. package/commands/cap/reconcile.md +121 -0
  249. package/commands/cap/review.md +360 -0
  250. package/commands/cap/save.md +72 -0
  251. package/commands/cap/scan.md +404 -0
  252. package/commands/cap/start.md +356 -0
  253. package/commands/cap/status.md +118 -0
  254. package/commands/cap/test-audit.md +262 -0
  255. package/commands/cap/test.md +394 -0
  256. package/commands/cap/trace.md +133 -0
  257. package/commands/cap/ui.md +167 -0
  258. package/hooks/dist/cap-check-update.js +115 -0
  259. package/hooks/dist/cap-context-monitor.js +185 -0
  260. package/hooks/dist/cap-learn-review-hook.js +114 -0
  261. package/hooks/dist/cap-learning-hook.js +192 -0
  262. package/hooks/dist/cap-memory.js +299 -0
  263. package/hooks/dist/cap-prompt-guard.js +97 -0
  264. package/hooks/dist/cap-statusline.js +157 -0
  265. package/hooks/dist/cap-tag-observer.js +115 -0
  266. package/hooks/dist/cap-version-check.js +112 -0
  267. package/hooks/dist/cap-workflow-guard.js +175 -0
  268. package/hooks/hooks.json +55 -0
  269. package/package.json +58 -0
  270. package/scripts/base64-scan.sh +262 -0
  271. package/scripts/build-hooks.js +93 -0
  272. package/scripts/cap-removal-checklist.md +202 -0
  273. package/scripts/prompt-injection-scan.sh +199 -0
  274. package/scripts/run-tests.cjs +181 -0
  275. package/scripts/secret-scan.sh +227 -0
@@ -0,0 +1,540 @@
1
+ // @cap-feature(feature:F-006) GSD-to-CAP Migration — convert @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|todos?|risk|decision|context|status|depends|ref|pattern|api|constraint|placeholder|concern))(\([^)]*\))?\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
+ case 'todos':
124
+ // @gsd-todos (plural typo) → @cap-todo
125
+ return {
126
+ replaced: line.replace(fullTag, '@cap-todo'),
127
+ action: 'converted',
128
+ };
129
+
130
+ case 'placeholder':
131
+ // @gsd-placeholder → @cap-todo (placeholder is a todo variant)
132
+ return {
133
+ replaced: line.replace(fullTag, '@cap-todo'),
134
+ action: 'converted',
135
+ };
136
+
137
+ case 'concern':
138
+ // @gsd-concern → @cap-todo risk: (concerns are risks)
139
+ return {
140
+ replaced: line.replace(fullTag + metadata + (description ? ' ' : ''), '@cap-todo' + metadata + ' risk: ').replace(/ +/g, ' '),
141
+ action: 'converted',
142
+ };
143
+
144
+ default:
145
+ return null;
146
+ }
147
+ }
148
+
149
+ /**
150
+ * Scan all source files and replace @gsd-* tags with @cap-* equivalents.
151
+ *
152
+ * Mapping:
153
+ * @gsd-feature → @cap-feature
154
+ * @gsd-todo → @cap-todo
155
+ * @gsd-risk → @cap-todo risk:
156
+ * @gsd-decision → @cap-todo decision:
157
+ * @gsd-context → plain comment (tag removed)
158
+ * @gsd-status → plain comment (tag removed)
159
+ * @gsd-depends → plain comment (tag removed)
160
+ * @gsd-ref → @cap-ref (if content exists) or removed
161
+ * @gsd-pattern → plain comment (tag removed)
162
+ * @gsd-api → plain comment (tag removed)
163
+ * @gsd-constraint → @cap-todo risk: [constraint]
164
+ *
165
+ * @param {string} projectRoot - Absolute path to project root
166
+ * @param {Object} [options]
167
+ * @param {boolean} [options.dryRun] - If true, report changes without writing
168
+ * @param {string[]} [options.extensions] - File extensions to process
169
+ * @returns {{ filesScanned: number, filesModified: number, tagsConverted: number, tagsRemoved: number, changes: TagChange[] }}
170
+ */
171
+ function migrateTags(projectRoot, options = {}) {
172
+ const dryRun = options.dryRun || false;
173
+ const extensions = options.extensions || SUPPORTED_EXTENSIONS;
174
+ const result = {
175
+ filesScanned: 0,
176
+ filesModified: 0,
177
+ tagsConverted: 0,
178
+ tagsRemoved: 0,
179
+ changes: [],
180
+ };
181
+
182
+ function walk(dir) {
183
+ let entries;
184
+ try {
185
+ entries = fs.readdirSync(dir, { withFileTypes: true });
186
+ } catch (_e) {
187
+ return;
188
+ }
189
+ for (const entry of entries) {
190
+ const fullPath = path.join(dir, entry.name);
191
+ if (entry.isDirectory()) {
192
+ if (EXCLUDE_DIRS.includes(entry.name)) continue;
193
+ walk(fullPath);
194
+ } else if (entry.isFile()) {
195
+ const ext = path.extname(entry.name);
196
+ if (!extensions.includes(ext)) continue;
197
+ processFile(fullPath);
198
+ }
199
+ }
200
+ }
201
+
202
+ function processFile(filePath) {
203
+ let content;
204
+ try {
205
+ content = fs.readFileSync(filePath, 'utf8');
206
+ } catch (_e) {
207
+ return;
208
+ }
209
+
210
+ result.filesScanned++;
211
+ const relativePath = path.relative(projectRoot, filePath);
212
+ const lines = content.split('\n');
213
+ let modified = false;
214
+
215
+ for (let i = 0; i < lines.length; i++) {
216
+ const migration = migrateLineTag(lines[i]);
217
+ if (!migration) continue;
218
+
219
+ const change = {
220
+ file: relativePath,
221
+ line: i + 1,
222
+ original: lines[i],
223
+ replaced: migration.replaced,
224
+ action: migration.action,
225
+ };
226
+ result.changes.push(change);
227
+
228
+ if (migration.action === 'converted') {
229
+ result.tagsConverted++;
230
+ } else {
231
+ result.tagsRemoved++;
232
+ }
233
+
234
+ lines[i] = migration.replaced;
235
+ modified = true;
236
+ }
237
+
238
+ if (modified) {
239
+ result.filesModified++;
240
+ if (!dryRun) {
241
+ fs.writeFileSync(filePath, lines.join('\n'), 'utf8');
242
+ }
243
+ }
244
+ }
245
+
246
+ walk(projectRoot);
247
+ return result;
248
+ }
249
+
250
+ // --- Artifact migration ---
251
+
252
+ /**
253
+ * Convert .planning/FEATURES.md or REQUIREMENTS.md into FEATURE-MAP.md format.
254
+ *
255
+ * @param {string} projectRoot - Absolute path to project root
256
+ * @param {Object} [options]
257
+ * @param {boolean} [options.dryRun] - If true, report without writing
258
+ * @returns {{ featuresFound: number, featureMapCreated: boolean, source: string }}
259
+ */
260
+ function migrateArtifacts(projectRoot, options = {}) {
261
+ const dryRun = options.dryRun || false;
262
+ const result = { featuresFound: 0, featureMapCreated: false, source: 'none' };
263
+
264
+ // Check if FEATURE-MAP.md already exists
265
+ const featureMapPath = path.join(projectRoot, 'FEATURE-MAP.md');
266
+ const featureMapExists = fs.existsSync(featureMapPath);
267
+
268
+ // Try reading source artifacts in priority order
269
+ let sourceContent = null;
270
+ let sourceName = null;
271
+
272
+ const sources = [
273
+ { file: '.planning/FEATURES.md', name: 'FEATURES.md' },
274
+ { file: '.planning/REQUIREMENTS.md', name: 'REQUIREMENTS.md' },
275
+ { file: '.planning/PRD.md', name: 'PRD.md' },
276
+ ];
277
+
278
+ for (const src of sources) {
279
+ const srcPath = path.join(projectRoot, src.file);
280
+ if (fs.existsSync(srcPath)) {
281
+ try {
282
+ sourceContent = fs.readFileSync(srcPath, 'utf8');
283
+ sourceName = src.name;
284
+ result.source = src.name;
285
+ break;
286
+ } catch (_e) {
287
+ continue;
288
+ }
289
+ }
290
+ }
291
+
292
+ if (!sourceContent) return result;
293
+
294
+ // Extract features from the source artifact
295
+ const features = extractFeaturesFromLegacy(sourceContent);
296
+ result.featuresFound = features.length;
297
+
298
+ if (features.length === 0) return result;
299
+
300
+ if (featureMapExists) {
301
+ // Merge into existing Feature Map
302
+ const capFeatureMap = require('./cap-feature-map.cjs');
303
+ if (!dryRun) {
304
+ // @cap-todo(ac:F-081/AC-4 iter:2) Migrated to {safe: true} opt-in to preserve CLI on duplicate-ID FEATURE-MAP.
305
+ // @cap-decision(F-081/iter2) Bail on parseError — do not persist partial enrichment.
306
+ const existing = capFeatureMap.readFeatureMap(projectRoot, undefined, { safe: true });
307
+ if (existing && existing.parseError) {
308
+ console.warn('cap: migrate aborted — duplicate feature ID detected: ' + String(existing.parseError.message).trim());
309
+ return result;
310
+ }
311
+ const existingTitles = new Set(existing.features.map(f => f.title.toLowerCase()));
312
+
313
+ for (const feature of features) {
314
+ if (!existingTitles.has(feature.title.toLowerCase())) {
315
+ capFeatureMap.addFeature(projectRoot, feature);
316
+ }
317
+ }
318
+ result.featureMapCreated = true;
319
+ }
320
+ } else {
321
+ // Create new Feature Map
322
+ if (!dryRun) {
323
+ const capFeatureMap = require('./cap-feature-map.cjs');
324
+ const template = capFeatureMap.generateTemplate();
325
+ fs.writeFileSync(featureMapPath, template, 'utf8');
326
+ for (const feature of features) {
327
+ capFeatureMap.addFeature(projectRoot, feature);
328
+ }
329
+ result.featureMapCreated = true;
330
+ } else {
331
+ result.featureMapCreated = true; // Would be created
332
+ }
333
+ }
334
+
335
+ return result;
336
+ }
337
+
338
+ /**
339
+ * Extract feature entries from legacy GSD planning artifacts.
340
+ * Looks for markdown headings, list items with feature-like patterns.
341
+ *
342
+ * @param {string} content - Markdown content of legacy artifact
343
+ * @returns {{ title: string, acs: Array, dependencies: string[] }[]}
344
+ */
345
+ function extractFeaturesFromLegacy(content) {
346
+ const features = [];
347
+ const lines = content.split('\n');
348
+
349
+ // Match headings that look like features: ## Feature Name, ### Feature Name, ## 1. Feature Name
350
+ const featureHeadingRE = /^#{2,4}\s+(?:\d+\.\s*)?(?:Feature:\s*)?(.+?)(?:\s*\[.*\])?\s*$/;
351
+ // Match list items that look like acceptance criteria: - [ ] description, - [x] description
352
+ const acRE = /^[-*]\s+\[([x ])\]\s+(.+)/i;
353
+ // Match plain list items as potential ACs
354
+ const plainListRE = /^[-*]\s+(?!#)(.+)/;
355
+
356
+ let currentFeature = null;
357
+ let acCounter = 0;
358
+
359
+ for (const line of lines) {
360
+ const headingMatch = line.match(featureHeadingRE);
361
+ if (headingMatch) {
362
+ if (currentFeature && currentFeature.title) {
363
+ features.push(currentFeature);
364
+ }
365
+ currentFeature = {
366
+ title: headingMatch[1].trim(),
367
+ acs: [],
368
+ dependencies: [],
369
+ };
370
+ acCounter = 0;
371
+ continue;
372
+ }
373
+
374
+ if (currentFeature) {
375
+ const acMatch = line.match(acRE);
376
+ if (acMatch) {
377
+ acCounter++;
378
+ currentFeature.acs.push({
379
+ id: `AC-${acCounter}`,
380
+ description: acMatch[2].trim(),
381
+ status: acMatch[1] === 'x' || acMatch[1] === 'X' ? 'implemented' : 'pending',
382
+ });
383
+ continue;
384
+ }
385
+
386
+ // Empty line after ACs but before next heading -- stop collecting ACs
387
+ if (line.trim() === '' && currentFeature.acs.length > 0) {
388
+ // Keep collecting -- next heading or feature resets
389
+ }
390
+ }
391
+ }
392
+
393
+ if (currentFeature && currentFeature.title) {
394
+ features.push(currentFeature);
395
+ }
396
+
397
+ return features;
398
+ }
399
+
400
+ // --- Session migration ---
401
+
402
+ /**
403
+ * Migrate .planning/SESSION.json to .cap/SESSION.json format.
404
+ *
405
+ * @param {string} projectRoot - Absolute path to project root
406
+ * @param {Object} [options]
407
+ * @param {boolean} [options.dryRun] - If true, report without writing
408
+ * @returns {{ migrated: boolean, oldFormat: string, newFormat: string }}
409
+ */
410
+ function migrateSession(projectRoot, options = {}) {
411
+ const dryRun = options.dryRun || false;
412
+ const result = { migrated: false, oldFormat: 'none', newFormat: 'none' };
413
+
414
+ const oldSessionPath = path.join(projectRoot, '.planning', 'SESSION.json');
415
+ if (!fs.existsSync(oldSessionPath)) return result;
416
+
417
+ let oldSession;
418
+ try {
419
+ const content = fs.readFileSync(oldSessionPath, 'utf8');
420
+ oldSession = JSON.parse(content);
421
+ result.oldFormat = 'v1.x';
422
+ } catch (_e) {
423
+ result.oldFormat = 'corrupt';
424
+ return result;
425
+ }
426
+
427
+ // Map old session fields to new CAP session format
428
+ const capSession = require('./cap-session.cjs');
429
+ const newSession = capSession.getDefaultSession();
430
+
431
+ // Map known v1.x fields
432
+ if (oldSession.current_app) {
433
+ newSession.metadata.legacyApp = oldSession.current_app;
434
+ }
435
+ if (oldSession.current_phase) {
436
+ newSession.step = `legacy-phase-${oldSession.current_phase}`;
437
+ }
438
+ if (oldSession.started_at || oldSession.startedAt) {
439
+ newSession.startedAt = oldSession.started_at || oldSession.startedAt;
440
+ }
441
+ if (oldSession.last_command || oldSession.lastCommand) {
442
+ newSession.lastCommand = oldSession.last_command || oldSession.lastCommand;
443
+ }
444
+
445
+ // Preserve all old fields as metadata for reference
446
+ for (const [key, value] of Object.entries(oldSession)) {
447
+ if (typeof value === 'string' || typeof value === 'number' || typeof value === 'boolean') {
448
+ newSession.metadata[`gsd_${key}`] = String(value);
449
+ }
450
+ }
451
+
452
+ result.newFormat = 'v2.0';
453
+
454
+ if (!dryRun) {
455
+ capSession.initCapDirectory(projectRoot);
456
+ capSession.saveSession(projectRoot, newSession);
457
+ result.migrated = true;
458
+ } else {
459
+ result.migrated = true; // Would be migrated
460
+ }
461
+
462
+ return result;
463
+ }
464
+
465
+ // --- Analysis ---
466
+
467
+ /**
468
+ * Generate a migration report summarizing what was found and what needs attention.
469
+ *
470
+ * @param {string} projectRoot - Absolute path to project root
471
+ * @returns {{ gsdTagCount: number, gsdArtifacts: string[], planningDir: boolean, sessionJson: boolean, recommendations: string[] }}
472
+ */
473
+ function analyzeMigration(projectRoot) {
474
+ const result = {
475
+ gsdTagCount: 0,
476
+ gsdArtifacts: [],
477
+ planningDir: false,
478
+ sessionJson: false,
479
+ recommendations: [],
480
+ };
481
+
482
+ // Check for .planning/ directory
483
+ const planningDir = path.join(projectRoot, '.planning');
484
+ result.planningDir = fs.existsSync(planningDir);
485
+
486
+ // Check for known GSD artifacts
487
+ for (const artifact of GSD_ARTIFACTS) {
488
+ const artifactPath = path.join(projectRoot, artifact);
489
+ if (fs.existsSync(artifactPath)) {
490
+ result.gsdArtifacts.push(artifact);
491
+ }
492
+ }
493
+
494
+ // Check for .planning/SESSION.json specifically
495
+ result.sessionJson = fs.existsSync(path.join(projectRoot, '.planning', 'SESSION.json'));
496
+
497
+ // Count @gsd-* tags in source files
498
+ const tagResult = migrateTags(projectRoot, { dryRun: true });
499
+ result.gsdTagCount = tagResult.tagsConverted + tagResult.tagsRemoved;
500
+
501
+ // Build recommendations
502
+ if (result.gsdTagCount > 0) {
503
+ result.recommendations.push(`Found ${result.gsdTagCount} @gsd-* tags to migrate. Run /cap:migrate to convert them to @cap-* tags.`);
504
+ }
505
+
506
+ if (result.gsdArtifacts.length > 0) {
507
+ result.recommendations.push(`Found ${result.gsdArtifacts.length} legacy planning artifacts: ${result.gsdArtifacts.join(', ')}. These can be converted to FEATURE-MAP.md entries.`);
508
+ }
509
+
510
+ if (result.sessionJson) {
511
+ result.recommendations.push('Found .planning/SESSION.json. This can be migrated to .cap/SESSION.json format.');
512
+ }
513
+
514
+ if (!fs.existsSync(path.join(projectRoot, 'FEATURE-MAP.md'))) {
515
+ result.recommendations.push('No FEATURE-MAP.md found. Migration will create one from existing artifacts.');
516
+ }
517
+
518
+ if (!fs.existsSync(path.join(projectRoot, '.cap'))) {
519
+ result.recommendations.push('No .cap/ directory found. Migration will initialize it.');
520
+ }
521
+
522
+ if (result.gsdTagCount === 0 && result.gsdArtifacts.length === 0 && !result.sessionJson) {
523
+ result.recommendations.push('No GSD v1.x artifacts detected. This project may already be using CAP v2.0 or is a fresh project.');
524
+ }
525
+
526
+ return result;
527
+ }
528
+
529
+ module.exports = {
530
+ GSD_TAG_RE,
531
+ SUPPORTED_EXTENSIONS,
532
+ EXCLUDE_DIRS,
533
+ GSD_ARTIFACTS,
534
+ migrateLineTag,
535
+ migrateTags,
536
+ migrateArtifacts,
537
+ extractFeaturesFromLegacy,
538
+ migrateSession,
539
+ analyzeMigration,
540
+ };