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,371 @@
1
+ // @cap-context Workspace detector for monorepo mode -- discovers NX, Turbo, and pnpm workspaces and enumerates apps/packages
2
+ // @cap-decision Regex-parses pnpm-workspace.yaml instead of adding a YAML parser -- keeps zero-dep constraint
3
+ // @cap-constraint Zero external dependencies -- uses only Node.js built-ins (fs, path)
4
+ // @cap-ref(ref:AC-1) GSD auto-detects NX/Turbo/pnpm workspaces and lists available apps and packages on project initialization
5
+ // @cap-pattern Workspace detection returns a structured WorkspaceInfo object that downstream modules consume uniformly
6
+
7
+ 'use strict';
8
+
9
+ // @cap-feature(feature:F-012) Monorepo Support — workspace detection for NX, Turbo, and pnpm workspaces
10
+
11
+ const fs = require('node:fs');
12
+ const path = require('node:path');
13
+
14
+ // @cap-api detectWorkspace(projectRoot) -- returns WorkspaceInfo | null describing the monorepo type, apps, and packages
15
+
16
+ /**
17
+ * @typedef {Object} WorkspaceApp
18
+ * @property {string} name - Package name from package.json or directory name
19
+ * @property {string} path - Relative path from project root (e.g., 'apps/dashboard')
20
+ * @property {string} absolutePath - Absolute path on disk
21
+ */
22
+
23
+ /**
24
+ * @typedef {Object} WorkspacePackage
25
+ * @property {string} name - Package name from package.json or directory name
26
+ * @property {string} path - Relative path from project root (e.g., 'packages/ui')
27
+ * @property {string} absolutePath - Absolute path on disk
28
+ * @property {string[]} exports - Exported entry points (from package.json exports field)
29
+ */
30
+
31
+ /**
32
+ * @typedef {Object} WorkspaceInfo
33
+ * @property {'nx'|'turbo'|'pnpm'|'npm'|null} type - Detected workspace manager
34
+ * @property {string} rootPath - Absolute path to monorepo root
35
+ * @property {WorkspaceApp[]} apps - Detected applications
36
+ * @property {WorkspacePackage[]} packages - Detected shared packages
37
+ * @property {string[]} workspaceGlobs - Raw glob patterns from workspace config
38
+ */
39
+
40
+ /**
41
+ * Detect the workspace type by checking for config files.
42
+ *
43
+ * @param {string} projectRoot - Absolute path to project root
44
+ * @returns {WorkspaceInfo|null} Workspace info or null if not a monorepo
45
+ */
46
+ function detectWorkspace(projectRoot) {
47
+ // @cap-decision Check nx.json first, then turbo.json, then pnpm-workspace.yaml, then package.json workspaces -- priority matches market share
48
+ const nxPath = path.join(projectRoot, 'nx.json');
49
+ const turboPath = path.join(projectRoot, 'turbo.json');
50
+ const pnpmWsPath = path.join(projectRoot, 'pnpm-workspace.yaml');
51
+ const pkgPath = path.join(projectRoot, 'package.json');
52
+
53
+ let type = null;
54
+ let workspaceGlobs = [];
55
+
56
+ if (fs.existsSync(nxPath)) {
57
+ type = 'nx';
58
+ workspaceGlobs = resolveNxWorkspaces(nxPath, pkgPath);
59
+ } else if (fs.existsSync(turboPath)) {
60
+ type = 'turbo';
61
+ workspaceGlobs = resolveTurboWorkspaces(pkgPath);
62
+ } else if (fs.existsSync(pnpmWsPath)) {
63
+ type = 'pnpm';
64
+ workspaceGlobs = resolvePnpmWorkspaces(pnpmWsPath);
65
+ } else if (fs.existsSync(pkgPath)) {
66
+ const pkg = safeReadJson(pkgPath);
67
+ if (pkg && Array.isArray(pkg.workspaces)) {
68
+ type = 'npm';
69
+ workspaceGlobs = pkg.workspaces;
70
+ } else if (pkg && pkg.workspaces && Array.isArray(pkg.workspaces.packages)) {
71
+ type = 'npm';
72
+ workspaceGlobs = pkg.workspaces.packages;
73
+ }
74
+ }
75
+
76
+ if (!type) return null;
77
+
78
+ // @cap-risk Glob expansion uses simple fs.readdirSync matching, not full glob semantics -- patterns like apps/** work but complex negations do not
79
+ const resolved = expandWorkspaceGlobs(projectRoot, workspaceGlobs);
80
+
81
+ // @cap-decision Classify directories under apps/ or packages/ by convention -- NX/Turbo monorepos use this standard structure
82
+ const apps = [];
83
+ const packages = [];
84
+
85
+ for (const entry of resolved) {
86
+ const relPath = entry.relativePath;
87
+ const pkgJson = safeReadJson(path.join(entry.absolutePath, 'package.json'));
88
+ const name = (pkgJson && pkgJson.name) || path.basename(relPath);
89
+ const exports = (pkgJson && pkgJson.exports) ? Object.keys(pkgJson.exports) : [];
90
+
91
+ const item = {
92
+ name,
93
+ path: relPath,
94
+ absolutePath: entry.absolutePath,
95
+ };
96
+
97
+ if (relPath.startsWith('apps/') || relPath.startsWith('apps\\')) {
98
+ apps.push(item);
99
+ } else if (relPath.startsWith('packages/') || relPath.startsWith('libs/') || relPath.startsWith('packages\\') || relPath.startsWith('libs\\')) {
100
+ packages.push({ ...item, exports });
101
+ } else {
102
+ // @cap-risk Directories not under apps/ or packages/ are classified as packages by default -- may misclassify standalone tools
103
+ packages.push({ ...item, exports });
104
+ }
105
+ }
106
+
107
+ return {
108
+ type,
109
+ rootPath: projectRoot,
110
+ apps,
111
+ packages,
112
+ workspaceGlobs,
113
+ };
114
+ }
115
+
116
+ /**
117
+ * Resolve NX workspace globs. NX uses package.json workspaces or project.json files.
118
+ *
119
+ * @param {string} nxPath - Path to nx.json
120
+ * @param {string} pkgPath - Path to root package.json
121
+ * @returns {string[]}
122
+ */
123
+ function resolveNxWorkspaces(nxPath, pkgPath) {
124
+ const pkg = safeReadJson(pkgPath);
125
+ if (pkg && Array.isArray(pkg.workspaces)) return pkg.workspaces;
126
+ if (pkg && pkg.workspaces && Array.isArray(pkg.workspaces.packages)) return pkg.workspaces.packages;
127
+
128
+ // NX project.json-based discovery: scan first-level subdirectories for project.json files
129
+ const projectRoot = path.dirname(nxPath);
130
+ const discoveredGlobs = new Set();
131
+
132
+ try {
133
+ const topEntries = fs.readdirSync(projectRoot, { withFileTypes: true });
134
+ for (const topEntry of topEntries) {
135
+ if (!topEntry.isDirectory()) continue;
136
+ if (topEntry.name === 'node_modules' || topEntry.name === '.git') continue;
137
+
138
+ const topDir = path.join(projectRoot, topEntry.name);
139
+ try {
140
+ const subEntries = fs.readdirSync(topDir, { withFileTypes: true });
141
+ for (const subEntry of subEntries) {
142
+ if (!subEntry.isDirectory()) continue;
143
+ const projectJsonPath = path.join(topDir, subEntry.name, 'project.json');
144
+ if (fs.existsSync(projectJsonPath)) {
145
+ // Add the parent-level glob pattern (e.g., 'apps/*')
146
+ discoveredGlobs.add(`${topEntry.name}/*`);
147
+ }
148
+ }
149
+ } catch {
150
+ // Permission errors on subdirectory
151
+ }
152
+ }
153
+ } catch {
154
+ // Permission errors on project root
155
+ }
156
+
157
+ if (discoveredGlobs.size > 0) {
158
+ return Array.from(discoveredGlobs);
159
+ }
160
+
161
+ // Fallback: NX convention
162
+ return ['apps/*', 'packages/*', 'libs/*'];
163
+ }
164
+
165
+ /**
166
+ * Resolve Turbo workspace globs. Turbo reads from package.json workspaces.
167
+ *
168
+ * @param {string} pkgPath - Path to root package.json
169
+ * @returns {string[]}
170
+ */
171
+ function resolveTurboWorkspaces(pkgPath) {
172
+ const pkg = safeReadJson(pkgPath);
173
+ if (pkg && Array.isArray(pkg.workspaces)) return pkg.workspaces;
174
+ if (pkg && pkg.workspaces && Array.isArray(pkg.workspaces.packages)) return pkg.workspaces.packages;
175
+ return ['apps/*', 'packages/*'];
176
+ }
177
+
178
+ /**
179
+ * Parse pnpm-workspace.yaml to extract workspace globs.
180
+ * Uses simple regex instead of a YAML parser to maintain zero-dep constraint.
181
+ *
182
+ * @param {string} pnpmWsPath - Path to pnpm-workspace.yaml
183
+ * @returns {string[]}
184
+ */
185
+ function resolvePnpmWorkspaces(pnpmWsPath) {
186
+ // @cap-decision Parse pnpm-workspace.yaml with regex -- avoids adding js-yaml dependency; works for the simple list format pnpm uses
187
+ // @cap-risk Regex YAML parsing will break on complex YAML features (anchors, flow sequences) -- sufficient for pnpm-workspace.yaml which is always a simple list
188
+ try {
189
+ const content = fs.readFileSync(pnpmWsPath, 'utf-8');
190
+ const globs = [];
191
+ const lines = content.split('\n');
192
+ let inPackages = false;
193
+ for (const line of lines) {
194
+ const trimmed = line.trim();
195
+ if (trimmed === 'packages:') {
196
+ inPackages = true;
197
+ continue;
198
+ }
199
+ if (inPackages) {
200
+ if (trimmed.startsWith('- ')) {
201
+ const glob = trimmed.slice(2).replace(/['"]/g, '').trim();
202
+ if (glob) globs.push(glob);
203
+ } else if (trimmed && !trimmed.startsWith('#')) {
204
+ // Non-list line ends the packages section
205
+ break;
206
+ }
207
+ }
208
+ }
209
+ return globs.length > 0 ? globs : ['packages/*', 'apps/*'];
210
+ } catch {
211
+ return ['packages/*', 'apps/*'];
212
+ }
213
+ }
214
+
215
+ /**
216
+ * Expand workspace globs to actual directories on disk.
217
+ *
218
+ * @param {string} rootPath - Absolute path to monorepo root
219
+ * @param {string[]} globs - Workspace glob patterns (e.g., ['apps/*', 'packages/*'])
220
+ * @returns {Array<{relativePath: string, absolutePath: string}>}
221
+ */
222
+ function expandWorkspaceGlobs(rootPath, globs) {
223
+ // @cap-constraint Uses readdirSync (not glob library) per project zero-dep constraint
224
+ const results = [];
225
+ const seen = new Set();
226
+
227
+ for (const glob of globs) {
228
+ // Skip negation patterns (e.g., '!packages/internal')
229
+ if (glob.startsWith('!')) continue;
230
+
231
+ // Detect two-level glob patterns like 'packages/*/sub/*'
232
+ const segments = glob.split('/');
233
+ const starPositions = segments.reduce((acc, seg, i) => {
234
+ if (seg === '*' || seg === '**') acc.push(i);
235
+ return acc;
236
+ }, []);
237
+
238
+ if (starPositions.length >= 2) {
239
+ // Two-level pattern: walk two directory levels
240
+ const firstParent = path.join(rootPath, segments.slice(0, starPositions[0]).join('/'));
241
+ if (!fs.existsSync(firstParent)) continue;
242
+
243
+ try {
244
+ const level1Entries = fs.readdirSync(firstParent, { withFileTypes: true });
245
+ for (const l1 of level1Entries) {
246
+ if (!l1.isDirectory() || l1.name === 'node_modules' || l1.name === '.git') continue;
247
+ // Build the path to the second-level parent (may have fixed segments between stars)
248
+ const midSegments = segments.slice(starPositions[0] + 1, starPositions[1]);
249
+ const level2Parent = path.join(firstParent, l1.name, ...midSegments);
250
+ if (!fs.existsSync(level2Parent)) continue;
251
+
252
+ try {
253
+ const level2Entries = fs.readdirSync(level2Parent, { withFileTypes: true });
254
+ for (const l2 of level2Entries) {
255
+ if (!l2.isDirectory() || l2.name === 'node_modules' || l2.name === '.git') continue;
256
+ const absPath = path.join(level2Parent, l2.name);
257
+ const relPath = path.relative(rootPath, absPath);
258
+ if (!seen.has(relPath)) {
259
+ seen.add(relPath);
260
+ results.push({ relativePath: relPath, absolutePath: absPath });
261
+ }
262
+ }
263
+ } catch {
264
+ // Permission errors
265
+ }
266
+ }
267
+ } catch {
268
+ // Permission errors
269
+ }
270
+ } else {
271
+ // Single-level pattern: 'apps/*', 'packages/*', 'libs/*'
272
+ const cleanGlob = glob.replace(/\/\*\*?$/, '').replace(/\\\*\*?$/, '');
273
+ const parentDir = path.join(rootPath, cleanGlob);
274
+
275
+ if (!fs.existsSync(parentDir)) continue;
276
+
277
+ try {
278
+ const entries = fs.readdirSync(parentDir, { withFileTypes: true });
279
+ for (const entry of entries) {
280
+ if (!entry.isDirectory()) continue;
281
+ if (entry.name === 'node_modules' || entry.name === '.git') continue;
282
+
283
+ const absPath = path.join(parentDir, entry.name);
284
+ const relPath = path.relative(rootPath, absPath);
285
+
286
+ if (!seen.has(relPath)) {
287
+ seen.add(relPath);
288
+ results.push({ relativePath: relPath, absolutePath: absPath });
289
+ }
290
+ }
291
+ } catch {
292
+ // Permission errors, etc.
293
+ }
294
+ }
295
+ }
296
+
297
+ return results;
298
+ }
299
+
300
+ /**
301
+ * Safely read and parse a JSON file.
302
+ *
303
+ * @param {string} filePath
304
+ * @returns {Object|null}
305
+ */
306
+ function safeReadJson(filePath) {
307
+ try {
308
+ return JSON.parse(fs.readFileSync(filePath, 'utf-8'));
309
+ } catch {
310
+ return null;
311
+ }
312
+ }
313
+
314
+ /**
315
+ * Validate that an --app path exists within the workspace.
316
+ *
317
+ * @param {WorkspaceInfo} workspace - Detected workspace info
318
+ * @param {string} appPath - User-provided app path (e.g., 'apps/dashboard')
319
+ * @returns {{valid: boolean, resolved: WorkspaceApp|null, error: string|null}}
320
+ */
321
+ // @cap-api validateAppPath(workspace, appPath) -- returns {valid, resolved, error} for --app flag validation
322
+ function validateAppPath(workspace, appPath) {
323
+ if (!workspace) {
324
+ return { valid: false, resolved: null, error: 'No workspace detected. Run monorepo-init first.' };
325
+ }
326
+
327
+ const normalized = appPath.replace(/\\/g, '/').replace(/\/$/, '');
328
+ const allEntries = [...workspace.apps, ...workspace.packages];
329
+ const match = allEntries.find(e => e.path.replace(/\\/g, '/') === normalized);
330
+
331
+ if (match) {
332
+ return { valid: true, resolved: match, error: null };
333
+ }
334
+
335
+ return {
336
+ valid: false,
337
+ resolved: null,
338
+ error: `App '${appPath}' not found in workspace. Available: ${allEntries.map(e => e.path).join(', ')}`,
339
+ };
340
+ }
341
+
342
+ /**
343
+ * CLI entry point for detect-workspace subcommand.
344
+ *
345
+ * @param {string} cwd - Current working directory
346
+ * @param {boolean} raw - Whether to output raw JSON
347
+ */
348
+ function cmdDetectWorkspace(cwd, raw) {
349
+ const workspace = detectWorkspace(cwd);
350
+ if (!workspace) {
351
+ if (raw) {
352
+ process.stdout.write('null\n');
353
+ } else {
354
+ process.stderr.write('No workspace detected. Not a monorepo or missing workspace config.\n');
355
+ }
356
+ return;
357
+ }
358
+
359
+ const output = JSON.stringify(workspace, null, 2);
360
+ process.stdout.write(output + '\n');
361
+ }
362
+
363
+ module.exports = {
364
+ detectWorkspace,
365
+ validateAppPath,
366
+ expandWorkspaceGlobs,
367
+ resolvePnpmWorkspaces,
368
+ resolveNxWorkspaces,
369
+ resolveTurboWorkspaces,
370
+ cmdDetectWorkspace,
371
+ };