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