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,344 @@
1
+ /**
2
+ * arc-scanner — Regex-based ARC annotation tag scanner
3
+ *
4
+ * Extracts @gsd-tags from source files anchored to comment tokens,
5
+ * preventing false positives from strings, URLs, and template literals.
6
+ *
7
+ * Requirements: SCAN-01, SCAN-02, SCAN-03, SCAN-04
8
+ */
9
+
10
+ 'use strict';
11
+
12
+ // @cap-feature(feature:F-015) Legacy ARC Scanner — regex-based @gsd-* tag extraction (predecessor to F-001)
13
+ // @cap-todo risk: This module uses the old @gsd-* tag prefix; consider deprecating once F-006 migration is complete
14
+
15
+ const fs = require('fs');
16
+ const path = require('path');
17
+ const { output, error } = require('./core.cjs');
18
+
19
+ // COPY THIS REGEX VERBATIM — do not modify
20
+ // Anchors to: leading whitespace + comment token + optional space + @gsd-<type>[(metadata)] description
21
+ const TAG_LINE_RE = /^[ \t]*(?:\/\/+|\/\*+|\*+|#+|--+|"{3}|'{3})[ \t]*@gsd-(\w+)(?:\(([^)]*)\))?[ \t]*(.*?)[ \t]*$/gm;
22
+
23
+ const DEFAULT_EXCLUDES = ['node_modules', 'dist', 'build', '.planning', '.git'];
24
+
25
+ const VALID_TAG_TYPES = new Set([
26
+ 'context', 'decision', 'todo', 'constraint', 'pattern', 'ref', 'risk', 'api'
27
+ ]);
28
+
29
+ /**
30
+ * Parse the parenthesized metadata block.
31
+ * e.g. "phase:2, priority:high" → { phase: '2', priority: 'high' }
32
+ *
33
+ * @param {string|undefined} raw - Captured metadata string or undefined
34
+ * @returns {Object} Key-value pairs, all values as strings
35
+ */
36
+ function parseMetadata(raw) {
37
+ if (!raw || !raw.trim()) return {};
38
+ const result = {};
39
+ const parts = raw.split(/,\s*/);
40
+ for (const part of parts) {
41
+ const colonIdx = part.indexOf(':');
42
+ if (colonIdx === -1) continue;
43
+ const key = part.slice(0, colonIdx).trim();
44
+ const value = part.slice(colonIdx + 1).trim();
45
+ if (key) result[key] = value;
46
+ }
47
+ return result;
48
+ }
49
+
50
+ /**
51
+ * Scan a single file for @gsd-tags anchored to comment tokens.
52
+ *
53
+ * @param {string} filePath - Absolute or relative path to the file
54
+ * @returns {Array<Object>} Array of tag objects
55
+ */
56
+ function scanFile(filePath) {
57
+ let content;
58
+ try {
59
+ content = fs.readFileSync(filePath, 'utf-8');
60
+ } catch (err) {
61
+ return [];
62
+ }
63
+
64
+ // Create a fresh regex instance per call to avoid lastIndex state issues with /gm flag
65
+ const re = new RegExp(TAG_LINE_RE.source, 'gm');
66
+ const tags = [];
67
+
68
+ for (const match of content.matchAll(re)) {
69
+ const type = match[1];
70
+
71
+ // Only include known ARC tag types
72
+ if (!VALID_TAG_TYPES.has(type)) continue;
73
+
74
+ // Compute 1-based line number by counting newlines before match position
75
+ const line = (content.slice(0, match.index).match(/\n/g) || []).length + 1;
76
+
77
+ tags.push({
78
+ type,
79
+ file: filePath,
80
+ line,
81
+ metadata: parseMetadata(match[2]),
82
+ description: (match[3] || '').trim(),
83
+ raw: match[0].trim(),
84
+ });
85
+ }
86
+
87
+ return tags;
88
+ }
89
+
90
+ /**
91
+ * Recursively walk a directory, scanning all files for @gsd-tags.
92
+ * Respects DEFAULT_EXCLUDES and optional .gsdignore at dirPath root.
93
+ *
94
+ * @param {string} dirPath - Root directory to scan
95
+ * @param {Object} [options] - Optional filters
96
+ * @param {string} [options.phaseFilter] - Only return tags where metadata.phase === phaseFilter
97
+ * @param {string} [options.typeFilter] - Only return tags where type === typeFilter
98
+ * @param {string[]} [options.excludes] - Additional directory/file names to skip
99
+ * @returns {Array<Object>} All matching tags across all files
100
+ */
101
+ function scanDirectory(dirPath, options) {
102
+ options = options || {};
103
+
104
+ // Load .gsdignore patterns from project root (simple line matching)
105
+ const gsdIgnorePath = path.join(dirPath, '.gsdignore');
106
+ const gsdIgnorePatterns = [];
107
+ try {
108
+ if (fs.existsSync(gsdIgnorePath)) {
109
+ const lines = fs.readFileSync(gsdIgnorePath, 'utf-8').split('\n');
110
+ for (const line of lines) {
111
+ const trimmed = line.trim();
112
+ if (trimmed && !trimmed.startsWith('#')) {
113
+ gsdIgnorePatterns.push(trimmed);
114
+ }
115
+ }
116
+ }
117
+ } catch {
118
+ // Ignore errors reading .gsdignore
119
+ }
120
+
121
+ /**
122
+ * Internal recursive walker.
123
+ *
124
+ * @param {string} dir - Current directory being walked
125
+ * @returns {Array<Object>} Tags found under dir
126
+ */
127
+ function walk(dir) {
128
+ let entries;
129
+ try {
130
+ entries = fs.readdirSync(dir, { withFileTypes: true });
131
+ } catch {
132
+ return [];
133
+ }
134
+
135
+ const allTags = [];
136
+ for (const entry of entries) {
137
+ const name = entry.name;
138
+
139
+ // Skip DEFAULT_EXCLUDES directories
140
+ if (DEFAULT_EXCLUDES.includes(name)) continue;
141
+
142
+ // Skip user-provided excludes
143
+ if (options.excludes && options.excludes.includes(name)) continue;
144
+
145
+ // Skip .gsdignore patterns
146
+ if (gsdIgnorePatterns.some(pat => name === pat || name.startsWith(pat))) continue;
147
+
148
+ const fullPath = path.join(dir, name);
149
+
150
+ if (entry.isDirectory()) {
151
+ allTags.push(...walk(fullPath));
152
+ } else if (entry.isFile()) {
153
+ allTags.push(...scanFile(fullPath));
154
+ }
155
+ }
156
+
157
+ return allTags;
158
+ }
159
+
160
+ let tags = walk(dirPath);
161
+
162
+ // Apply phase filter
163
+ if (options.phaseFilter !== undefined && options.phaseFilter !== null) {
164
+ const phaseStr = String(options.phaseFilter);
165
+ tags = tags.filter(tag => tag.metadata.phase === phaseStr);
166
+ }
167
+
168
+ // Apply type filter
169
+ if (options.typeFilter) {
170
+ tags = tags.filter(tag => tag.type === options.typeFilter);
171
+ }
172
+
173
+ return tags;
174
+ }
175
+
176
+ /**
177
+ * Format tags as a JSON string.
178
+ *
179
+ * @param {Array<Object>} tags - Array of tag objects
180
+ * @returns {string} Pretty-printed JSON string
181
+ */
182
+ function formatAsJson(tags) {
183
+ return JSON.stringify(tags, null, 2);
184
+ }
185
+
186
+ /**
187
+ * Format tags as a CODE-INVENTORY.md Markdown document.
188
+ *
189
+ * Structure:
190
+ * - Header with metadata
191
+ * - ## Summary Statistics (table of tag type counts)
192
+ * - ## Tags by Type (H3 per type, H4 per file, table of line/metadata/description)
193
+ * - ## Phase Reference Index (table of phase/count/files)
194
+ *
195
+ * @param {Array<Object>} tags - Array of tag objects
196
+ * @param {string} [projectName] - Project name for the header
197
+ * @returns {string} CODE-INVENTORY.md Markdown string
198
+ */
199
+ function formatAsMarkdown(tags, projectName) {
200
+ const now = new Date().toISOString();
201
+ const name = projectName || 'Unknown Project';
202
+
203
+ // Gather unique files
204
+ const fileSet = new Set(tags.map(t => t.file));
205
+ const totalFiles = fileSet.size;
206
+ const totalTags = tags.length;
207
+
208
+ // Build type → tags map (preserve ARC order)
209
+ const TYPE_ORDER = ['context', 'decision', 'todo', 'constraint', 'pattern', 'ref', 'risk', 'api'];
210
+ const byType = {};
211
+ for (const type of TYPE_ORDER) {
212
+ byType[type] = [];
213
+ }
214
+ for (const tag of tags) {
215
+ if (byType[tag.type]) {
216
+ byType[tag.type].push(tag);
217
+ } else {
218
+ byType[tag.type] = [tag];
219
+ }
220
+ }
221
+
222
+ // ── Header ────────────────────────────────────────────────────────────────
223
+ const lines = [
224
+ `# CODE-INVENTORY.md`,
225
+ ``,
226
+ `**Generated:** ${now}`,
227
+ `**Project:** ${name}`,
228
+ `**Schema version:** 1.0`,
229
+ `**Tags found:** ${totalTags} across ${totalFiles} file${totalFiles !== 1 ? 's' : ''}`,
230
+ ``,
231
+ ];
232
+
233
+ // ── Summary Statistics ────────────────────────────────────────────────────
234
+ lines.push(`## Summary Statistics`, ``);
235
+ lines.push(`| Tag Type | Count |`);
236
+ lines.push(`|----------|-------|`);
237
+ for (const type of TYPE_ORDER) {
238
+ const count = byType[type] ? byType[type].length : 0;
239
+ if (count > 0) {
240
+ lines.push(`| @gsd-${type} | ${count} |`);
241
+ }
242
+ }
243
+ lines.push(``);
244
+
245
+ // ── Tags by Type ──────────────────────────────────────────────────────────
246
+ lines.push(`## Tags by Type`, ``);
247
+
248
+ for (const type of TYPE_ORDER) {
249
+ const typeTags = byType[type];
250
+ if (!typeTags || typeTags.length === 0) continue;
251
+
252
+ lines.push(`### @gsd-${type}`, ``);
253
+
254
+ // Group by file
255
+ const byFile = {};
256
+ for (const tag of typeTags) {
257
+ if (!byFile[tag.file]) byFile[tag.file] = [];
258
+ byFile[tag.file].push(tag);
259
+ }
260
+
261
+ for (const [filePath, fileTags] of Object.entries(byFile)) {
262
+ lines.push(`#### ${filePath}`, ``);
263
+ lines.push(`| Line | Metadata | Description |`);
264
+ lines.push(`|------|----------|-------------|`);
265
+ for (const tag of fileTags) {
266
+ const metaStr = Object.keys(tag.metadata).length > 0
267
+ ? Object.entries(tag.metadata).map(([k, v]) => `${k}:${v}`).join(', ')
268
+ : '—';
269
+ lines.push(`| ${tag.line} | ${metaStr} | ${tag.description || '—'} |`);
270
+ }
271
+ lines.push(``);
272
+ }
273
+ }
274
+
275
+ // ── Phase Reference Index ─────────────────────────────────────────────────
276
+ lines.push(`## Phase Reference Index`, ``);
277
+ lines.push(`| Phase | Tag Count | Files |`);
278
+ lines.push(`|-------|-----------|-------|`);
279
+
280
+ // Group by phase value
281
+ const byPhase = {};
282
+ for (const tag of tags) {
283
+ const phase = tag.metadata.phase || '(untagged)';
284
+ if (!byPhase[phase]) byPhase[phase] = { count: 0, files: new Set() };
285
+ byPhase[phase].count++;
286
+ byPhase[phase].files.add(tag.file);
287
+ }
288
+
289
+ // Sort: numbered phases first, then (untagged)
290
+ const phases = Object.keys(byPhase).sort((a, b) => {
291
+ if (a === '(untagged)') return 1;
292
+ if (b === '(untagged)') return -1;
293
+ return Number(a) - Number(b);
294
+ });
295
+
296
+ for (const phase of phases) {
297
+ const { count, files } = byPhase[phase];
298
+ lines.push(`| ${phase} | ${count} | ${[...files].join(', ')} |`);
299
+ }
300
+
301
+ lines.push(``);
302
+
303
+ return lines.join('\n');
304
+ }
305
+
306
+ /**
307
+ * CLI entry point: scan a target path, format, and write output.
308
+ * Called by cap-tools.cjs case 'extract-tags'.
309
+ *
310
+ * @param {string} cwd - Current working directory
311
+ * @param {string} targetPath - Path to scan (file or directory)
312
+ * @param {Object} [opts] - Options
313
+ * @param {string} [opts.phaseFilter] - Phase filter
314
+ * @param {string} [opts.typeFilter] - Type filter
315
+ * @param {string} [opts.format] - 'json' (default) or 'md'
316
+ * @param {string} [opts.outputFile] - Write to file instead of stdout
317
+ */
318
+ function cmdExtractTags(cwd, targetPath, opts) {
319
+ opts = opts || {};
320
+ const format = opts.format || 'json';
321
+ const resolvedPath = path.resolve(cwd, targetPath || cwd);
322
+
323
+ const tags = scanDirectory(resolvedPath, {
324
+ phaseFilter: opts.phaseFilter,
325
+ typeFilter: opts.typeFilter,
326
+ });
327
+
328
+ let result;
329
+ if (format === 'md') {
330
+ result = formatAsMarkdown(tags, opts.projectName);
331
+ } else {
332
+ result = formatAsJson(tags);
333
+ }
334
+
335
+ if (opts.outputFile) {
336
+ const outDir = path.dirname(opts.outputFile);
337
+ fs.mkdirSync(outDir, { recursive: true });
338
+ fs.writeFileSync(opts.outputFile, result, 'utf-8');
339
+ } else {
340
+ process.stdout.write(result + '\n');
341
+ }
342
+ }
343
+
344
+ module.exports = { scanFile, scanDirectory, formatAsJson, formatAsMarkdown, cmdExtractTags };