code-as-plan 2.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 (188) hide show
  1. package/LICENSE +21 -0
  2. package/README.ja-JP.md +834 -0
  3. package/README.ko-KR.md +823 -0
  4. package/README.md +1006 -0
  5. package/README.pt-BR.md +452 -0
  6. package/README.zh-CN.md +800 -0
  7. package/agents/cap-brainstormer.md +154 -0
  8. package/agents/cap-debugger.md +221 -0
  9. package/agents/cap-prototyper.md +170 -0
  10. package/agents/cap-reviewer.md +230 -0
  11. package/agents/cap-tester.md +193 -0
  12. package/bin/install.js +5002 -0
  13. package/cap/bin/gsd-tools.cjs +1141 -0
  14. package/cap/bin/lib/arc-scanner.cjs +341 -0
  15. package/cap/bin/lib/cap-feature-map.cjs +506 -0
  16. package/cap/bin/lib/cap-session.cjs +191 -0
  17. package/cap/bin/lib/cap-stack-docs.cjs +598 -0
  18. package/cap/bin/lib/cap-tag-scanner.cjs +458 -0
  19. package/cap/bin/lib/commands.cjs +959 -0
  20. package/cap/bin/lib/config.cjs +466 -0
  21. package/cap/bin/lib/convention-reader.cjs +180 -0
  22. package/cap/bin/lib/core.cjs +1230 -0
  23. package/cap/bin/lib/feature-aggregator.cjs +422 -0
  24. package/cap/bin/lib/frontmatter.cjs +336 -0
  25. package/cap/bin/lib/init.cjs +1442 -0
  26. package/cap/bin/lib/manifest-generator.cjs +381 -0
  27. package/cap/bin/lib/milestone.cjs +252 -0
  28. package/cap/bin/lib/model-profiles.cjs +68 -0
  29. package/cap/bin/lib/monorepo-context.cjs +224 -0
  30. package/cap/bin/lib/monorepo-migrator.cjs +507 -0
  31. package/cap/bin/lib/phase.cjs +888 -0
  32. package/cap/bin/lib/profile-output.cjs +952 -0
  33. package/cap/bin/lib/profile-pipeline.cjs +539 -0
  34. package/cap/bin/lib/roadmap.cjs +329 -0
  35. package/cap/bin/lib/security.cjs +382 -0
  36. package/cap/bin/lib/session-manager.cjs +290 -0
  37. package/cap/bin/lib/skeleton-generator.cjs +177 -0
  38. package/cap/bin/lib/state.cjs +1031 -0
  39. package/cap/bin/lib/template.cjs +222 -0
  40. package/cap/bin/lib/test-detector.cjs +61 -0
  41. package/cap/bin/lib/uat.cjs +282 -0
  42. package/cap/bin/lib/verify.cjs +888 -0
  43. package/cap/bin/lib/workspace-detector.cjs +369 -0
  44. package/cap/bin/lib/workstream.cjs +491 -0
  45. package/cap/commands/gsd/workstreams.md +63 -0
  46. package/cap/references/arc-standard.md +315 -0
  47. package/cap/references/cap-agent-architecture.md +102 -0
  48. package/cap/references/cap-gitignore-template +9 -0
  49. package/cap/references/cap-zero-deps.md +158 -0
  50. package/cap/references/checkpoints.md +778 -0
  51. package/cap/references/continuation-format.md +249 -0
  52. package/cap/references/decimal-phase-calculation.md +64 -0
  53. package/cap/references/feature-map-template.md +25 -0
  54. package/cap/references/git-integration.md +295 -0
  55. package/cap/references/git-planning-commit.md +38 -0
  56. package/cap/references/model-profile-resolution.md +36 -0
  57. package/cap/references/model-profiles.md +139 -0
  58. package/cap/references/phase-argument-parsing.md +61 -0
  59. package/cap/references/planning-config.md +202 -0
  60. package/cap/references/questioning.md +162 -0
  61. package/cap/references/session-template.json +8 -0
  62. package/cap/references/tdd.md +263 -0
  63. package/cap/references/ui-brand.md +160 -0
  64. package/cap/references/user-profiling.md +681 -0
  65. package/cap/references/verification-patterns.md +612 -0
  66. package/cap/references/workstream-flag.md +58 -0
  67. package/cap/templates/DEBUG.md +164 -0
  68. package/cap/templates/UAT.md +265 -0
  69. package/cap/templates/UI-SPEC.md +100 -0
  70. package/cap/templates/VALIDATION.md +76 -0
  71. package/cap/templates/claude-md.md +122 -0
  72. package/cap/templates/codebase/architecture.md +255 -0
  73. package/cap/templates/codebase/concerns.md +310 -0
  74. package/cap/templates/codebase/conventions.md +307 -0
  75. package/cap/templates/codebase/integrations.md +280 -0
  76. package/cap/templates/codebase/stack.md +186 -0
  77. package/cap/templates/codebase/structure.md +285 -0
  78. package/cap/templates/codebase/testing.md +480 -0
  79. package/cap/templates/config.json +44 -0
  80. package/cap/templates/context.md +352 -0
  81. package/cap/templates/continue-here.md +78 -0
  82. package/cap/templates/copilot-instructions.md +7 -0
  83. package/cap/templates/debug-subagent-prompt.md +91 -0
  84. package/cap/templates/dev-preferences.md +21 -0
  85. package/cap/templates/discovery.md +146 -0
  86. package/cap/templates/discussion-log.md +63 -0
  87. package/cap/templates/milestone-archive.md +123 -0
  88. package/cap/templates/milestone.md +115 -0
  89. package/cap/templates/phase-prompt.md +610 -0
  90. package/cap/templates/planner-subagent-prompt.md +117 -0
  91. package/cap/templates/project.md +186 -0
  92. package/cap/templates/requirements.md +231 -0
  93. package/cap/templates/research-project/ARCHITECTURE.md +204 -0
  94. package/cap/templates/research-project/FEATURES.md +147 -0
  95. package/cap/templates/research-project/PITFALLS.md +200 -0
  96. package/cap/templates/research-project/STACK.md +120 -0
  97. package/cap/templates/research-project/SUMMARY.md +170 -0
  98. package/cap/templates/research.md +552 -0
  99. package/cap/templates/retrospective.md +54 -0
  100. package/cap/templates/roadmap.md +202 -0
  101. package/cap/templates/state.md +176 -0
  102. package/cap/templates/summary-complex.md +59 -0
  103. package/cap/templates/summary-minimal.md +41 -0
  104. package/cap/templates/summary-standard.md +48 -0
  105. package/cap/templates/summary.md +248 -0
  106. package/cap/templates/user-profile.md +146 -0
  107. package/cap/templates/user-setup.md +311 -0
  108. package/cap/templates/verification-report.md +322 -0
  109. package/cap/workflows/add-phase.md +112 -0
  110. package/cap/workflows/add-tests.md +351 -0
  111. package/cap/workflows/add-todo.md +158 -0
  112. package/cap/workflows/audit-milestone.md +340 -0
  113. package/cap/workflows/audit-uat.md +109 -0
  114. package/cap/workflows/autonomous.md +891 -0
  115. package/cap/workflows/check-todos.md +177 -0
  116. package/cap/workflows/cleanup.md +152 -0
  117. package/cap/workflows/complete-milestone.md +767 -0
  118. package/cap/workflows/diagnose-issues.md +231 -0
  119. package/cap/workflows/discovery-phase.md +289 -0
  120. package/cap/workflows/discuss-phase-assumptions.md +653 -0
  121. package/cap/workflows/discuss-phase.md +1049 -0
  122. package/cap/workflows/do.md +104 -0
  123. package/cap/workflows/execute-phase.md +846 -0
  124. package/cap/workflows/execute-plan.md +514 -0
  125. package/cap/workflows/fast.md +105 -0
  126. package/cap/workflows/forensics.md +265 -0
  127. package/cap/workflows/health.md +181 -0
  128. package/cap/workflows/help.md +660 -0
  129. package/cap/workflows/insert-phase.md +130 -0
  130. package/cap/workflows/list-phase-assumptions.md +178 -0
  131. package/cap/workflows/list-workspaces.md +56 -0
  132. package/cap/workflows/manager.md +362 -0
  133. package/cap/workflows/map-codebase.md +377 -0
  134. package/cap/workflows/milestone-summary.md +223 -0
  135. package/cap/workflows/new-milestone.md +486 -0
  136. package/cap/workflows/new-project.md +1250 -0
  137. package/cap/workflows/new-workspace.md +237 -0
  138. package/cap/workflows/next.md +97 -0
  139. package/cap/workflows/node-repair.md +92 -0
  140. package/cap/workflows/note.md +156 -0
  141. package/cap/workflows/pause-work.md +176 -0
  142. package/cap/workflows/plan-milestone-gaps.md +273 -0
  143. package/cap/workflows/plan-phase.md +859 -0
  144. package/cap/workflows/plant-seed.md +169 -0
  145. package/cap/workflows/pr-branch.md +129 -0
  146. package/cap/workflows/profile-user.md +450 -0
  147. package/cap/workflows/progress.md +507 -0
  148. package/cap/workflows/quick.md +757 -0
  149. package/cap/workflows/remove-phase.md +155 -0
  150. package/cap/workflows/remove-workspace.md +90 -0
  151. package/cap/workflows/research-phase.md +82 -0
  152. package/cap/workflows/resume-project.md +326 -0
  153. package/cap/workflows/review.md +228 -0
  154. package/cap/workflows/session-report.md +146 -0
  155. package/cap/workflows/settings.md +283 -0
  156. package/cap/workflows/ship.md +228 -0
  157. package/cap/workflows/stats.md +60 -0
  158. package/cap/workflows/transition.md +671 -0
  159. package/cap/workflows/ui-phase.md +302 -0
  160. package/cap/workflows/ui-review.md +165 -0
  161. package/cap/workflows/update.md +323 -0
  162. package/cap/workflows/validate-phase.md +174 -0
  163. package/cap/workflows/verify-phase.md +254 -0
  164. package/cap/workflows/verify-work.md +637 -0
  165. package/commands/cap/annotate.md +165 -0
  166. package/commands/cap/brainstorm.md +238 -0
  167. package/commands/cap/debug.md +297 -0
  168. package/commands/cap/init.md +262 -0
  169. package/commands/cap/iterate.md +234 -0
  170. package/commands/cap/prototype.md +281 -0
  171. package/commands/cap/refresh-docs.md +37 -0
  172. package/commands/cap/review.md +272 -0
  173. package/commands/cap/scan.md +249 -0
  174. package/commands/cap/start.md +234 -0
  175. package/commands/cap/status.md +189 -0
  176. package/commands/cap/test.md +250 -0
  177. package/hooks/dist/gsd-check-update.js +114 -0
  178. package/hooks/dist/gsd-context-monitor.js +156 -0
  179. package/hooks/dist/gsd-prompt-guard.js +96 -0
  180. package/hooks/dist/gsd-statusline.js +119 -0
  181. package/hooks/dist/gsd-workflow-guard.js +94 -0
  182. package/package.json +51 -0
  183. package/scripts/base64-scan.sh +262 -0
  184. package/scripts/build-hooks.js +82 -0
  185. package/scripts/cap-removal-checklist.md +202 -0
  186. package/scripts/prompt-injection-scan.sh +198 -0
  187. package/scripts/run-tests.cjs +29 -0
  188. package/scripts/secret-scan.sh +227 -0
@@ -0,0 +1,507 @@
1
+ // @gsd-context Monorepo migration module -- audits existing .planning/ directories in apps, supports archive/replace/keep per app, analyzes root .planning/ for global vs app-specific split, regenerates scoped CODE-INVENTORY.md
2
+ // @gsd-decision Separate module from monorepo-context.cjs -- migration is a one-time destructive operation; context assembly is read-only and ongoing
3
+ // @gsd-constraint Zero external dependencies -- uses only Node.js built-ins (fs, path)
4
+ // @gsd-pattern Migration functions return audit/result objects -- callers (commands) handle user interaction and confirmation
5
+
6
+ 'use strict';
7
+
8
+ const fs = require('node:fs');
9
+ const path = require('node:path');
10
+ const arcScanner = require('./arc-scanner.cjs');
11
+
12
+ // ---------------------------------------------------------------------------
13
+ // Constants
14
+ // ---------------------------------------------------------------------------
15
+
16
+ // @gsd-decision Standard .planning/ files recognized during audit -- matches the set that monorepo-context.cjs and extract-plan produce
17
+ const KNOWN_PLANNING_FILES = [
18
+ 'PROJECT.md',
19
+ 'ROADMAP.md',
20
+ 'REQUIREMENTS.md',
21
+ 'PRD.md',
22
+ 'FEATURES.md',
23
+ 'STATE.md',
24
+ 'MILESTONES.md',
25
+ 'RETROSPECTIVE.md',
26
+ 'BRAINSTORM-LEDGER.md',
27
+ 'config.json',
28
+ ];
29
+
30
+ const KNOWN_PLANNING_DIRS = [
31
+ 'prototype',
32
+ 'phases',
33
+ 'milestones',
34
+ 'manifests',
35
+ 'research',
36
+ ];
37
+
38
+ // ---------------------------------------------------------------------------
39
+ // Audit types
40
+ // ---------------------------------------------------------------------------
41
+
42
+ /**
43
+ * @typedef {Object} PlanningAuditEntry
44
+ * @property {string} appPath - Relative path to the app (e.g., 'apps/dashboard')
45
+ * @property {string} planningDir - Absolute path to app's .planning/
46
+ * @property {boolean} exists - Whether .planning/ exists in this app
47
+ * @property {string[]} files - Filenames found in .planning/
48
+ * @property {string[]} directories - Subdirectory names found in .planning/
49
+ * @property {boolean} hasCodeInventory - Whether prototype/CODE-INVENTORY.md exists
50
+ * @property {boolean} hasPrd - Whether PRD.md exists
51
+ */
52
+
53
+ /**
54
+ * @typedef {Object} MigrationAudit
55
+ * @property {PlanningAuditEntry[]} apps - Audit entry per app
56
+ * @property {number} appsWithPlanning - Count of apps that have .planning/
57
+ * @property {number} appsWithoutPlanning - Count of apps that lack .planning/
58
+ * @property {RootAnalysis} rootAnalysis - Analysis of root .planning/ contents
59
+ */
60
+
61
+ /**
62
+ * @typedef {Object} RootAnalysis
63
+ * @property {string[]} globalFiles - Files that belong at root (PROJECT.md, ROADMAP.md, etc.)
64
+ * @property {string[]} appSpecificFiles - Files that likely belong to a specific app (PRD.md with app refs, etc.)
65
+ * @property {string[]} ambiguousFiles - Files that need user decision
66
+ * @property {string} rootPlanningDir - Absolute path to root .planning/
67
+ */
68
+
69
+ /**
70
+ * @typedef {Object} MigrationAction
71
+ * @property {string} appPath - Relative path to app
72
+ * @property {'keep'|'archive'|'replace'} action - What to do with existing .planning/
73
+ */
74
+
75
+ /**
76
+ * @typedef {Object} MigrationResult
77
+ * @property {string} appPath - Relative path to app
78
+ * @property {string} action - Action taken
79
+ * @property {boolean} success - Whether it succeeded
80
+ * @property {string|null} archivePath - Path to archive if action was 'archive'
81
+ * @property {string|null} error - Error message if failed
82
+ */
83
+
84
+ // ---------------------------------------------------------------------------
85
+ // Audit functions
86
+ // ---------------------------------------------------------------------------
87
+
88
+ // @gsd-todo(ref:AC-9) Implement full audit: scan all app directories for existing .planning/ folders and report what exists where
89
+ // @gsd-api auditAppPlanning(rootPath, apps) -- returns MigrationAudit describing existing .planning/ state across all apps
90
+ /**
91
+ * Scan all app directories for existing .planning/ folders.
92
+ * Produces a structured audit of what exists where.
93
+ *
94
+ * @param {string} rootPath - Monorepo root
95
+ * @param {Array<{path: string, name: string}>} apps - Apps from workspace detection
96
+ * @returns {MigrationAudit}
97
+ */
98
+ function auditAppPlanning(rootPath, apps) {
99
+ const entries = [];
100
+ let withPlanning = 0;
101
+ let withoutPlanning = 0;
102
+
103
+ for (const app of apps) {
104
+ const planningDir = path.join(rootPath, app.path, '.planning');
105
+ const exists = fs.existsSync(planningDir);
106
+
107
+ if (exists) {
108
+ withPlanning++;
109
+ const contents = safeReaddir(planningDir);
110
+ const files = contents.filter(name => {
111
+ const fullPath = path.join(planningDir, name);
112
+ return safeStat(fullPath, 'isFile');
113
+ });
114
+ const directories = contents.filter(name => {
115
+ const fullPath = path.join(planningDir, name);
116
+ return safeStat(fullPath, 'isDirectory');
117
+ });
118
+
119
+ entries.push({
120
+ appPath: app.path,
121
+ planningDir,
122
+ exists: true,
123
+ files,
124
+ directories,
125
+ hasCodeInventory: fs.existsSync(path.join(planningDir, 'prototype', 'CODE-INVENTORY.md')),
126
+ hasPrd: files.includes('PRD.md'),
127
+ });
128
+ } else {
129
+ withoutPlanning++;
130
+ entries.push({
131
+ appPath: app.path,
132
+ planningDir,
133
+ exists: false,
134
+ files: [],
135
+ directories: [],
136
+ hasCodeInventory: false,
137
+ hasPrd: false,
138
+ });
139
+ }
140
+ }
141
+
142
+ const rootAnalysis = analyzeRootPlanning(rootPath);
143
+
144
+ return {
145
+ apps: entries,
146
+ appsWithPlanning: withPlanning,
147
+ appsWithoutPlanning: withoutPlanning,
148
+ rootAnalysis,
149
+ };
150
+ }
151
+
152
+ // ---------------------------------------------------------------------------
153
+ // Root analysis
154
+ // ---------------------------------------------------------------------------
155
+
156
+ // @gsd-todo(ref:AC-11) Implement root .planning/ analysis: classify files as global vs app-specific and guide user to split
157
+ // @gsd-api analyzeRootPlanning(rootPath) -- returns RootAnalysis classifying root .planning/ contents
158
+ /**
159
+ * Analyze root .planning/ contents and classify them as global or app-specific.
160
+ * Global: PROJECT.md, ROADMAP.md, REQUIREMENTS.md, manifests/
161
+ * App-specific: PRD.md that references a single app, app-named subdirectories
162
+ * Ambiguous: everything else -- user decides
163
+ *
164
+ * @param {string} rootPath - Monorepo root
165
+ * @returns {RootAnalysis}
166
+ */
167
+ function analyzeRootPlanning(rootPath) {
168
+ const rootPlanningDir = path.join(rootPath, '.planning');
169
+ const globalFiles = [];
170
+ const appSpecificFiles = [];
171
+ const ambiguousFiles = [];
172
+
173
+ if (!fs.existsSync(rootPlanningDir)) {
174
+ return { globalFiles, appSpecificFiles, ambiguousFiles, rootPlanningDir };
175
+ }
176
+
177
+ // @gsd-decision Classify root files by name convention -- PROJECT.md, ROADMAP.md, REQUIREMENTS.md are always global; PRD.md and FEATURES.md at root are ambiguous in a monorepo
178
+ const GLOBAL_FILES = new Set(['PROJECT.md', 'ROADMAP.md', 'REQUIREMENTS.md', 'MILESTONES.md', 'config.json']);
179
+ const AMBIGUOUS_FILES = new Set(['PRD.md', 'FEATURES.md', 'STATE.md', 'BRAINSTORM-LEDGER.md', 'RETROSPECTIVE.md']);
180
+
181
+ const contents = safeReaddir(rootPlanningDir);
182
+
183
+ for (const name of contents) {
184
+ const fullPath = path.join(rootPlanningDir, name);
185
+
186
+ if (safeStat(fullPath, 'isDirectory')) {
187
+ // manifests/ is global; prototype/ with monolithic inventory is ambiguous
188
+ if (name === 'manifests') {
189
+ globalFiles.push(name + '/');
190
+ } else if (name === 'prototype') {
191
+ // @gsd-risk Root prototype/ may contain monolithic CODE-INVENTORY.md that should be split per app
192
+ ambiguousFiles.push(name + '/');
193
+ } else {
194
+ ambiguousFiles.push(name + '/');
195
+ }
196
+ continue;
197
+ }
198
+
199
+ if (GLOBAL_FILES.has(name)) {
200
+ globalFiles.push(name);
201
+ } else if (AMBIGUOUS_FILES.has(name)) {
202
+ // Further check: does the file reference a specific app?
203
+ const content = safeReadFile(fullPath);
204
+ if (content && looksAppSpecific(content)) {
205
+ appSpecificFiles.push(name);
206
+ } else {
207
+ ambiguousFiles.push(name);
208
+ }
209
+ } else {
210
+ ambiguousFiles.push(name);
211
+ }
212
+ }
213
+
214
+ return { globalFiles, appSpecificFiles, ambiguousFiles, rootPlanningDir };
215
+ }
216
+
217
+ /**
218
+ * Heuristic: does a planning file's content reference a single app?
219
+ * Checks for app-like path references (apps/something) in the first 20 lines.
220
+ *
221
+ * @param {string} content - File content
222
+ * @returns {boolean}
223
+ */
224
+ function looksAppSpecific(content) {
225
+ // @gsd-risk Heuristic detection of app-specific content may produce false positives -- user confirmation is always required
226
+ const lines = content.split('\n').slice(0, 20);
227
+ const appRefs = lines.filter(line => /\bapps\/\w+/.test(line));
228
+ return appRefs.length >= 2;
229
+ }
230
+
231
+ // ---------------------------------------------------------------------------
232
+ // Migration actions
233
+ // ---------------------------------------------------------------------------
234
+
235
+ // @gsd-todo(ref:AC-10) Implement per-app keep/archive/replace: user chooses action for each app's existing .planning/
236
+ // @gsd-api executeAppMigration(rootPath, action) -- performs keep, archive, or replace on one app's .planning/
237
+ /**
238
+ * Execute a migration action on a single app's .planning/ directory.
239
+ *
240
+ * @param {string} rootPath - Monorepo root
241
+ * @param {MigrationAction} action - Action to perform
242
+ * @returns {MigrationResult}
243
+ */
244
+ function executeAppMigration(rootPath, action) {
245
+ const planningDir = path.join(rootPath, action.appPath, '.planning');
246
+
247
+ try {
248
+ switch (action.action) {
249
+ case 'keep':
250
+ return { appPath: action.appPath, action: 'keep', success: true, archivePath: null, error: null };
251
+
252
+ case 'archive':
253
+ return archiveAppPlanning(rootPath, action.appPath);
254
+
255
+ case 'replace':
256
+ return replaceAppPlanning(rootPath, action.appPath);
257
+
258
+ default:
259
+ return { appPath: action.appPath, action: action.action, success: false, archivePath: null, error: `Unknown action: ${action.action}` };
260
+ }
261
+ } catch (err) {
262
+ return { appPath: action.appPath, action: action.action, success: false, archivePath: null, error: err.message };
263
+ }
264
+ }
265
+
266
+ // @gsd-pattern Archive uses timestamp-based directory naming (legacy-{timestamp}) -- ensures idempotent re-runs never collide
267
+ /**
268
+ * Archive an app's existing .planning/ to .planning/legacy-{timestamp}/.
269
+ *
270
+ * @param {string} rootPath - Monorepo root
271
+ * @param {string} appPath - Relative path to app
272
+ * @returns {MigrationResult}
273
+ */
274
+ function archiveAppPlanning(rootPath, appPath) {
275
+ const planningDir = path.join(rootPath, appPath, '.planning');
276
+
277
+ if (!fs.existsSync(planningDir)) {
278
+ return { appPath, action: 'archive', success: true, archivePath: null, error: null };
279
+ }
280
+
281
+ // @gsd-decision Archive to legacy-{timestamp} inside the app's .planning/ -- keeps history co-located with the app
282
+ const timestamp = Date.now();
283
+ const archiveDir = path.join(planningDir, `legacy-${timestamp}`);
284
+ fs.mkdirSync(archiveDir, { recursive: true });
285
+
286
+ const contents = safeReaddir(planningDir);
287
+ for (const name of contents) {
288
+ if (name.startsWith('legacy-')) continue; // Don't archive previous archives
289
+ const src = path.join(planningDir, name);
290
+ const dest = path.join(archiveDir, name);
291
+ // @gsd-decision Use cpSync+rmSync instead of renameSync -- cross-device safe for monorepos spanning mounts
292
+ fs.cpSync(src, dest, { recursive: true });
293
+ fs.rmSync(src, { recursive: true, force: true });
294
+ }
295
+
296
+ return { appPath, action: 'archive', success: true, archivePath: archiveDir, error: null };
297
+ }
298
+
299
+ /**
300
+ * Replace an app's existing .planning/ with a fresh structure.
301
+ * Archives first, then creates fresh stubs.
302
+ *
303
+ * @param {string} rootPath - Monorepo root
304
+ * @param {string} appPath - Relative path to app
305
+ * @returns {MigrationResult}
306
+ */
307
+ function replaceAppPlanning(rootPath, appPath) {
308
+ // Archive first so nothing is lost
309
+ const archiveResult = archiveAppPlanning(rootPath, appPath);
310
+ if (!archiveResult.success) return archiveResult;
311
+
312
+ // Create fresh structure using monorepo-context.cjs pattern
313
+ const planningDir = path.join(rootPath, appPath, '.planning');
314
+ const appName = path.basename(appPath);
315
+ fs.mkdirSync(planningDir, { recursive: true });
316
+ fs.mkdirSync(path.join(planningDir, 'prototype'), { recursive: true });
317
+
318
+ fs.writeFileSync(
319
+ path.join(planningDir, 'PRD.md'),
320
+ `# ${appName} -- PRD\n\nApp-scoped PRD. Generated by monorepo-init --migrate.\n`,
321
+ 'utf-8'
322
+ );
323
+ fs.writeFileSync(
324
+ path.join(planningDir, 'FEATURES.md'),
325
+ `# ${appName} -- Feature Map\n\nRun extract-plan --app ${appPath} to populate.\n`,
326
+ 'utf-8'
327
+ );
328
+ fs.writeFileSync(
329
+ path.join(planningDir, 'prototype', 'CODE-INVENTORY.md'),
330
+ `# CODE-INVENTORY.md\n\nRun /gsd:extract-plan --app ${appPath} to populate.\n`,
331
+ 'utf-8'
332
+ );
333
+
334
+ return { appPath, action: 'replace', success: true, archivePath: archiveResult.archivePath, error: null };
335
+ }
336
+
337
+ // ---------------------------------------------------------------------------
338
+ // Scoped CODE-INVENTORY regeneration
339
+ // ---------------------------------------------------------------------------
340
+
341
+ // @gsd-todo(ref:AC-12) Implement scoped CODE-INVENTORY.md regeneration per app after migration (replacing monolithic version)
342
+ // @gsd-api regenerateScopedInventories(rootPath, apps) -- triggers extract-tags per app to produce scoped CODE-INVENTORY.md files
343
+ /**
344
+ * Regenerate CODE-INVENTORY.md for each app by invoking the tag scanner
345
+ * scoped to each app directory.
346
+ *
347
+ * Note: This is a scaffold -- actual implementation calls extract-tags
348
+ * via the CLI or imports tag-scanner.cjs directly.
349
+ *
350
+ * @param {string} rootPath - Monorepo root
351
+ * @param {Array<{path: string}>} apps - Apps to regenerate inventories for
352
+ * @returns {Array<{appPath: string, inventoryPath: string, success: boolean, error: string|null}>}
353
+ */
354
+ function regenerateScopedInventories(rootPath, apps) {
355
+ // @gsd-constraint Must use existing tag-scanner.cjs for extraction -- no reimplementation of scanning logic
356
+ const results = [];
357
+
358
+ for (const app of apps) {
359
+ const appAbsPath = path.join(rootPath, app.path);
360
+ const planningDir = path.join(appAbsPath, '.planning');
361
+ const inventoryPath = path.join(planningDir, 'prototype', 'CODE-INVENTORY.md');
362
+
363
+ try {
364
+ // Ensure directory exists
365
+ fs.mkdirSync(path.join(planningDir, 'prototype'), { recursive: true });
366
+
367
+ // Delegate to arc-scanner for actual tag extraction scoped to this app
368
+ arcScanner.cmdExtractTags(appAbsPath, appAbsPath, {
369
+ format: 'md',
370
+ outputFile: inventoryPath,
371
+ });
372
+
373
+ results.push({
374
+ appPath: app.path,
375
+ inventoryPath,
376
+ success: true,
377
+ error: null,
378
+ });
379
+ } catch (err) {
380
+ results.push({
381
+ appPath: app.path,
382
+ inventoryPath,
383
+ success: false,
384
+ error: err.message,
385
+ });
386
+ }
387
+ }
388
+
389
+ return results;
390
+ }
391
+
392
+ // ---------------------------------------------------------------------------
393
+ // Batch migration
394
+ // ---------------------------------------------------------------------------
395
+
396
+ // @gsd-api executeMigration(rootPath, apps, actions) -- runs all migration actions and returns aggregate results
397
+ /**
398
+ * Execute a full migration given user-selected actions per app.
399
+ *
400
+ * @param {string} rootPath - Monorepo root
401
+ * @param {Array<{path: string, name: string}>} apps - All apps from workspace detection
402
+ * @param {MigrationAction[]} actions - User-selected action per app
403
+ * @returns {{results: MigrationResult[], regeneration: Array}}
404
+ */
405
+ function executeMigration(rootPath, apps, actions) {
406
+ const results = [];
407
+
408
+ for (const action of actions) {
409
+ results.push(executeAppMigration(rootPath, action));
410
+ }
411
+
412
+ // After migration, regenerate scoped inventories for all apps
413
+ const regeneration = regenerateScopedInventories(rootPath, apps);
414
+
415
+ return { results, regeneration };
416
+ }
417
+
418
+ // ---------------------------------------------------------------------------
419
+ // Formatting helpers (for command output)
420
+ // ---------------------------------------------------------------------------
421
+
422
+ // @gsd-api formatAuditReport(audit) -- returns human-readable string summarizing the migration audit
423
+ /**
424
+ * Format the audit into a human-readable report for display.
425
+ *
426
+ * @param {MigrationAudit} audit
427
+ * @returns {string}
428
+ */
429
+ function formatAuditReport(audit) {
430
+ const lines = [];
431
+ lines.push('## Migration Audit\n');
432
+ lines.push(`Apps with existing .planning/: ${audit.appsWithPlanning}`);
433
+ lines.push(`Apps without .planning/: ${audit.appsWithoutPlanning}\n`);
434
+
435
+ for (const entry of audit.apps) {
436
+ if (entry.exists) {
437
+ lines.push(`### ${entry.appPath}`);
438
+ lines.push(` Files: ${entry.files.join(', ') || '(none)'}`);
439
+ lines.push(` Directories: ${entry.directories.join(', ') || '(none)'}`);
440
+ lines.push(` Has CODE-INVENTORY: ${entry.hasCodeInventory ? 'yes' : 'no'}`);
441
+ lines.push(` Has PRD: ${entry.hasPrd ? 'yes' : 'no'}`);
442
+ lines.push('');
443
+ }
444
+ }
445
+
446
+ if (audit.rootAnalysis.globalFiles.length > 0 || audit.rootAnalysis.appSpecificFiles.length > 0 || audit.rootAnalysis.ambiguousFiles.length > 0) {
447
+ lines.push('### Root .planning/ Analysis\n');
448
+ if (audit.rootAnalysis.globalFiles.length > 0) {
449
+ lines.push(`Global (keep at root): ${audit.rootAnalysis.globalFiles.join(', ')}`);
450
+ }
451
+ if (audit.rootAnalysis.appSpecificFiles.length > 0) {
452
+ lines.push(`App-specific (move to app): ${audit.rootAnalysis.appSpecificFiles.join(', ')}`);
453
+ }
454
+ if (audit.rootAnalysis.ambiguousFiles.length > 0) {
455
+ lines.push(`Needs review: ${audit.rootAnalysis.ambiguousFiles.join(', ')}`);
456
+ }
457
+ }
458
+
459
+ return lines.join('\n');
460
+ }
461
+
462
+ // ---------------------------------------------------------------------------
463
+ // Utility helpers
464
+ // ---------------------------------------------------------------------------
465
+
466
+ function safeReaddir(dirPath) {
467
+ try {
468
+ return fs.readdirSync(dirPath);
469
+ } catch {
470
+ return [];
471
+ }
472
+ }
473
+
474
+ function safeStat(filePath, check) {
475
+ try {
476
+ const stat = fs.statSync(filePath);
477
+ return stat[check]();
478
+ } catch {
479
+ return false;
480
+ }
481
+ }
482
+
483
+ function safeReadFile(filePath) {
484
+ try {
485
+ return fs.readFileSync(filePath, 'utf-8');
486
+ } catch {
487
+ return null;
488
+ }
489
+ }
490
+
491
+ // ---------------------------------------------------------------------------
492
+ // Exports
493
+ // ---------------------------------------------------------------------------
494
+
495
+ module.exports = {
496
+ auditAppPlanning,
497
+ analyzeRootPlanning,
498
+ executeAppMigration,
499
+ archiveAppPlanning,
500
+ replaceAppPlanning,
501
+ regenerateScopedInventories,
502
+ executeMigration,
503
+ formatAuditReport,
504
+ looksAppSpecific,
505
+ KNOWN_PLANNING_FILES,
506
+ KNOWN_PLANNING_DIRS,
507
+ };