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,399 @@
1
+ // @cap-context CAP v2.0 trace module — implements `/cap:trace AC-N` by walking the call graph from the primary file of an AC across its referenced files.
2
+ // @cap-decision Separated from cap-tag-scanner.cjs because: (a) scanner is already 800+ lines and approaching the 300-line guideline boundary at function level; (b) trace requires file IO + import resolution that the pure-aggregation scanner avoids; (c) splitting lets us test the call-graph walker in isolation.
3
+ // @cap-decision Designated as the primary implementation file for F-045 — `primary:true` tag below. The scanner extension in cap-tag-scanner.cjs is supporting infrastructure that F-045 reuses.
4
+
5
+ 'use strict';
6
+
7
+ // @cap-feature(feature:F-045, primary:true) Trace AC-to-Code call graph from the primary file across referenced files.
8
+
9
+ const fs = require('node:fs');
10
+ const path = require('node:path');
11
+ const { scanDirectory, buildAcFileMap } = require('./cap-tag-scanner.cjs');
12
+
13
+ // @cap-decision Trace depth defaults to 3 hops. Justification: 1 hop is too shallow to see indirect collaborators, 2 misses common library wrapper patterns, 3 catches transitive dependencies one level past wrappers. Depth >3 explodes output size and rarely surfaces useful traceability information; users who want full graphs should use a dedicated dependency tool.
14
+ const DEFAULT_MAX_DEPTH = 3;
15
+
16
+ // Regex captures both CommonJS require() and ES module static imports.
17
+ // @cap-risk Dynamic imports (require(variable), import(expr)) and conditional requires inside functions cannot be detected by a static regex — they will silently be omitted from the call graph. This is documented in the trace output.
18
+ // @cap-risk TypeScript path aliases (e.g. "@/utils") cannot be resolved without reading tsconfig.json — out of scope for F-045; the edge will be marked external.
19
+ const REQUIRE_RE = /\brequire\(\s*['"]([^'"]+)['"]\s*\)/g;
20
+ const IMPORT_RE = /^\s*import\b[^'"]*['"]([^'"]+)['"]/gm;
21
+ const EXPORT_FROM_RE = /^\s*export\b[^'"]*from\s+['"]([^'"]+)['"]/gm;
22
+
23
+ const CODE_EXTENSIONS = ['.js', '.cjs', '.mjs', '.ts', '.tsx', '.jsx'];
24
+
25
+ /**
26
+ * @typedef {Object} CallGraphEdge
27
+ * @property {string} from - Source file (relative to project root)
28
+ * @property {string} to - Target file (relative to project root, or external module name)
29
+ * @property {('require'|'import'|'export-from')} type - How the edge was declared
30
+ * @property {number} line - Line number in `from` where the edge is declared
31
+ * @property {boolean} external - True if `to` could not be resolved to a project file
32
+ */
33
+
34
+ /**
35
+ * @typedef {Object} TraceResult
36
+ * @property {string} featureId - e.g. "F-045"
37
+ * @property {string} acId - e.g. "AC-1"
38
+ * @property {string} key - Composite "F-045/AC-1"
39
+ * @property {{file: string|null, role: ('designated'|'inferred'|null)}} primary
40
+ * @property {string[]} allFiles - Every file contributing tags to this AC
41
+ * @property {CallGraphEdge[]} callGraph - Edges discovered from primary outward (BFS, deduped)
42
+ * @property {string[]} warnings - Human-readable warnings (heuristic primary, missing AC, etc.)
43
+ * @property {number} depthLimit - Max BFS depth used
44
+ */
45
+
46
+ // @cap-api resolveImport(spec, fromAbsFile, projectRoot) -- Resolve an import specifier to an absolute file inside the project, or null if external.
47
+ // @cap-decision Resolution is intentionally minimal: only relative paths are resolved (./, ../). Bare specifiers (npm packages, TS aliases) are treated as external. This matches how F-045 trace is meant to be used — to map intra-project AC implementations, not to chase node_modules.
48
+ /**
49
+ * @param {string} spec - Raw import/require specifier
50
+ * @param {string} fromAbsFile - Absolute path of the file containing the import
51
+ * @param {string} projectRoot - Absolute project root
52
+ * @returns {string|null} - Absolute path inside project, or null if unresolvable / external
53
+ */
54
+ function resolveImport(spec, fromAbsFile, projectRoot) {
55
+ if (!spec.startsWith('./') && !spec.startsWith('../')) return null;
56
+ const fromDir = path.dirname(fromAbsFile);
57
+ const baseAbs = path.resolve(fromDir, spec);
58
+
59
+ // Reject anything outside project root.
60
+ const rel = path.relative(projectRoot, baseAbs);
61
+ if (rel.startsWith('..') || path.isAbsolute(rel)) return null;
62
+
63
+ // Try direct file
64
+ if (fs.existsSync(baseAbs) && fs.statSync(baseAbs).isFile()) return baseAbs;
65
+
66
+ // Try with code extensions
67
+ for (const ext of CODE_EXTENSIONS) {
68
+ const withExt = baseAbs + ext;
69
+ if (fs.existsSync(withExt) && fs.statSync(withExt).isFile()) return withExt;
70
+ }
71
+
72
+ // Try as directory with index file
73
+ if (fs.existsSync(baseAbs) && fs.statSync(baseAbs).isDirectory()) {
74
+ for (const ext of CODE_EXTENSIONS) {
75
+ const indexFile = path.join(baseAbs, 'index' + ext);
76
+ if (fs.existsSync(indexFile) && fs.statSync(indexFile).isFile()) return indexFile;
77
+ }
78
+ }
79
+
80
+ return null;
81
+ }
82
+
83
+ // @cap-api extractEdges(absFile, projectRoot) -- Extract require/import/export-from edges from a single file.
84
+ /**
85
+ * @param {string} absFile - Absolute file path to scan
86
+ * @param {string} projectRoot - Absolute project root
87
+ * @returns {CallGraphEdge[]}
88
+ */
89
+ function extractEdges(absFile, projectRoot) {
90
+ let content;
91
+ try {
92
+ content = fs.readFileSync(absFile, 'utf8');
93
+ } catch (_e) {
94
+ return [];
95
+ }
96
+ const fromRel = path.relative(projectRoot, absFile);
97
+ const lines = content.split('\n');
98
+ const edges = [];
99
+
100
+ // Walk line-by-line so we can capture line numbers.
101
+ // @cap-decision Line-by-line over multi-line regex on full content because per-line iteration gives us cheap accurate line numbers without computing offsets.
102
+ for (let i = 0; i < lines.length; i++) {
103
+ const line = lines[i];
104
+ REQUIRE_RE.lastIndex = 0;
105
+ let m;
106
+ while ((m = REQUIRE_RE.exec(line)) !== null) {
107
+ const spec = m[1];
108
+ const resolved = resolveImport(spec, absFile, projectRoot);
109
+ edges.push({
110
+ from: fromRel,
111
+ to: resolved ? path.relative(projectRoot, resolved) : spec,
112
+ type: 'require',
113
+ line: i + 1,
114
+ external: !resolved,
115
+ });
116
+ }
117
+ // For import / export-from regex, run on the whole line as well.
118
+ IMPORT_RE.lastIndex = 0;
119
+ while ((m = IMPORT_RE.exec(line)) !== null) {
120
+ const spec = m[1];
121
+ const resolved = resolveImport(spec, absFile, projectRoot);
122
+ edges.push({
123
+ from: fromRel,
124
+ to: resolved ? path.relative(projectRoot, resolved) : spec,
125
+ type: 'import',
126
+ line: i + 1,
127
+ external: !resolved,
128
+ });
129
+ }
130
+ EXPORT_FROM_RE.lastIndex = 0;
131
+ while ((m = EXPORT_FROM_RE.exec(line)) !== null) {
132
+ const spec = m[1];
133
+ const resolved = resolveImport(spec, absFile, projectRoot);
134
+ edges.push({
135
+ from: fromRel,
136
+ to: resolved ? path.relative(projectRoot, resolved) : spec,
137
+ type: 'export-from',
138
+ line: i + 1,
139
+ external: !resolved,
140
+ });
141
+ }
142
+ }
143
+
144
+ return edges;
145
+ }
146
+
147
+ // @cap-api walkCallGraph(rootRelFile, projectRoot, options) -- BFS walk of import/require edges from a root file.
148
+ // @cap-todo(ac:F-045/AC-4) Walk the call graph from the primary file across referenced files for a given AC.
149
+ /**
150
+ * Breadth-first traversal of import/require edges starting at the given file.
151
+ * Returns a deduped, depth-limited list of edges.
152
+ *
153
+ * @param {string} rootRelFile - Relative path of starting file
154
+ * @param {string} projectRoot - Absolute project root
155
+ * @param {Object} [options]
156
+ * @param {number} [options.maxDepth] - Max hops (default 3)
157
+ * @param {Set<string>} [options.allowedFiles] - If set, restrict traversal to these relative paths plus their immediate edges
158
+ * @returns {CallGraphEdge[]}
159
+ */
160
+ function walkCallGraph(rootRelFile, projectRoot, options = {}) {
161
+ const maxDepth = Number.isFinite(options.maxDepth) ? options.maxDepth : DEFAULT_MAX_DEPTH;
162
+ const allowedFiles = options.allowedFiles || null;
163
+
164
+ const edges = [];
165
+ const seen = new Set(); // file:line:type:to
166
+ const visited = new Set(); // visited files
167
+
168
+ /** @type {Array<{file: string, depth: number}>} */
169
+ const queue = [{ file: rootRelFile, depth: 0 }];
170
+
171
+ while (queue.length > 0) {
172
+ const { file, depth } = queue.shift();
173
+ if (visited.has(file)) continue;
174
+ visited.add(file);
175
+ if (depth >= maxDepth) continue;
176
+
177
+ const absFile = path.resolve(projectRoot, file);
178
+ if (!fs.existsSync(absFile)) continue;
179
+ const fileEdges = extractEdges(absFile, projectRoot);
180
+ for (const edge of fileEdges) {
181
+ const dedupeKey = `${edge.from}:${edge.line}:${edge.type}:${edge.to}`;
182
+ if (seen.has(dedupeKey)) continue;
183
+ seen.add(dedupeKey);
184
+ edges.push(edge);
185
+
186
+ // Enqueue resolved internal targets for further traversal.
187
+ // @cap-decision Skip external edges for traversal but keep them in the output — they are useful documentation of "what this AC depends on" without bloating the graph.
188
+ if (!edge.external) {
189
+ if (allowedFiles && !allowedFiles.has(edge.to)) {
190
+ // Outside the AC scope — record the edge but don't recurse into it.
191
+ continue;
192
+ }
193
+ queue.push({ file: edge.to, depth: depth + 1 });
194
+ }
195
+ }
196
+ }
197
+
198
+ return edges;
199
+ }
200
+
201
+ // @cap-api traceAc(projectRoot, acRef, options) -- Build a TraceResult for the given AC reference.
202
+ // @cap-todo(ac:F-045/AC-4) /cap:trace AC-N command shall print the call graph from the primary file.
203
+ /**
204
+ * Trace the call graph for an AC across its contributing files.
205
+ *
206
+ * @param {string} projectRoot - Absolute project root
207
+ * @param {string} acRef - "F-NNN/AC-M" or just "AC-M" (caller must resolve feature)
208
+ * @param {Object} [options]
209
+ * @param {number} [options.maxDepth] - Max BFS depth (default 3)
210
+ * @param {boolean} [options.restrictToAcFiles] - Only follow edges into other AC-contributing files (default false)
211
+ * @param {CapTag[]} [options.tags] - Pre-scanned tags (for testing); if omitted, scans projectRoot fresh
212
+ * @returns {TraceResult}
213
+ */
214
+ function traceAc(projectRoot, acRef, options = {}) {
215
+ const maxDepth = Number.isFinite(options.maxDepth) ? options.maxDepth : DEFAULT_MAX_DEPTH;
216
+
217
+ // Normalize key — caller may pass "AC-1" without feature prefix; we cannot resolve that without context.
218
+ if (!acRef || typeof acRef !== 'string') {
219
+ return {
220
+ featureId: null,
221
+ acId: null,
222
+ key: acRef,
223
+ primary: { file: null, role: null },
224
+ allFiles: [],
225
+ callGraph: [],
226
+ warnings: ['Invalid AC reference: must be a non-empty string like "F-045/AC-1"'],
227
+ depthLimit: maxDepth,
228
+ };
229
+ }
230
+ if (!acRef.includes('/')) {
231
+ return {
232
+ featureId: null,
233
+ acId: acRef,
234
+ key: acRef,
235
+ primary: { file: null, role: null },
236
+ allFiles: [],
237
+ callGraph: [],
238
+ warnings: [
239
+ `AC reference "${acRef}" lacks a feature prefix. Pass "F-NNN/${acRef}" or set an active feature in SESSION.json.`,
240
+ ],
241
+ depthLimit: maxDepth,
242
+ };
243
+ }
244
+
245
+ const [featureId, acId] = acRef.split('/');
246
+
247
+ const tags = options.tags || scanDirectory(projectRoot);
248
+ const acFileMap = buildAcFileMap(tags);
249
+ const entry = acFileMap[acRef];
250
+
251
+ if (!entry) {
252
+ return {
253
+ featureId,
254
+ acId,
255
+ key: acRef,
256
+ primary: { file: null, role: null },
257
+ allFiles: [],
258
+ callGraph: [],
259
+ warnings: [
260
+ `No tags reference AC ${acRef}. Add @cap-todo(ac:${acRef}) at the implementation site.`,
261
+ ],
262
+ depthLimit: maxDepth,
263
+ };
264
+ }
265
+
266
+ const result = {
267
+ featureId,
268
+ acId,
269
+ key: acRef,
270
+ primary: { file: entry.primary, role: entry.primarySource },
271
+ allFiles: entry.files.slice(),
272
+ callGraph: [],
273
+ warnings: entry.warnings.slice(),
274
+ depthLimit: maxDepth,
275
+ };
276
+
277
+ if (!entry.primary) {
278
+ result.warnings.push('No primary file resolvable; cannot walk call graph.');
279
+ return result;
280
+ }
281
+
282
+ const allowedFiles = options.restrictToAcFiles ? new Set(entry.files) : null;
283
+ result.callGraph = walkCallGraph(entry.primary, projectRoot, { maxDepth, allowedFiles });
284
+
285
+ return result;
286
+ }
287
+
288
+ // @cap-api formatTraceResult(traceResult) -- Render a TraceResult as human-readable markdown.
289
+ /**
290
+ * @param {TraceResult} t
291
+ * @returns {string}
292
+ */
293
+ function formatTraceResult(t) {
294
+ const lines = [];
295
+ lines.push(`Trace: ${t.key}`);
296
+ lines.push('');
297
+ if (t.primary.file) {
298
+ lines.push(`Primary: ${t.primary.file} (${t.primary.role})`);
299
+ } else {
300
+ lines.push('Primary: (none)');
301
+ }
302
+ lines.push('');
303
+
304
+ if (t.callGraph.length === 0) {
305
+ lines.push('Call graph: (no internal edges from primary)');
306
+ } else {
307
+ lines.push('Call graph:');
308
+ // Group by `from` for a tree-ish view
309
+ const byFrom = {};
310
+ for (const edge of t.callGraph) {
311
+ if (!byFrom[edge.from]) byFrom[edge.from] = [];
312
+ byFrom[edge.from].push(edge);
313
+ }
314
+ // Print starting at primary and following BFS order of visited files.
315
+ const printed = new Set();
316
+ const order = [];
317
+ const queue = [t.primary.file];
318
+ while (queue.length) {
319
+ const f = queue.shift();
320
+ if (printed.has(f)) continue;
321
+ printed.add(f);
322
+ order.push(f);
323
+ const out = byFrom[f] || [];
324
+ for (const e of out) {
325
+ if (!e.external) queue.push(e.to);
326
+ }
327
+ }
328
+ for (const f of order) {
329
+ lines.push(` ${f}`);
330
+ const out = byFrom[f] || [];
331
+ for (const e of out) {
332
+ const marker = e.external ? '(external)' : '';
333
+ lines.push(` ${e.type} ${e.to} [line ${e.line}] ${marker}`.trimEnd());
334
+ }
335
+ }
336
+ }
337
+
338
+ lines.push('');
339
+ lines.push('All files contributing to AC:');
340
+ if (t.allFiles.length === 0) {
341
+ lines.push(' (none)');
342
+ } else {
343
+ for (const f of t.allFiles) {
344
+ lines.push(` - ${f}`);
345
+ }
346
+ }
347
+
348
+ if (t.warnings.length > 0) {
349
+ lines.push('');
350
+ lines.push('Warnings:');
351
+ for (const w of t.warnings) {
352
+ lines.push(` ! ${w}`);
353
+ }
354
+ }
355
+
356
+ lines.push('');
357
+ lines.push(`Depth limit: ${t.depthLimit}`);
358
+
359
+ return lines.join('\n');
360
+ }
361
+
362
+ // @cap-feature(feature:F-063) Design-Usage line for /cap:trace output.
363
+ // @cap-todo(ac:F-063/AC-5) /cap:trace and /cap:status shall emit a "Design-Usage" summary per feature
364
+ // when the feature declares `usesDesign` in FEATURE-MAP.md.
365
+ // @cap-decision Renderer is data-in / string-out. Callers pass the feature and (optionally) a DESIGN.md
366
+ // parse result so token/component labels (e.g. "primary-color", "Button") accompany the bare IDs.
367
+ /**
368
+ * @param {{id:string,usesDesign?:string[]}} feature
369
+ * @param {{ byToken?: Object<string,{key:string}>, byComponent?: Object<string,{name:string}> }} [designIndex]
370
+ * @returns {string} - single-line summary, or '' if no design usage declared
371
+ */
372
+ function formatDesignUsage(feature, designIndex) {
373
+ if (!feature || !Array.isArray(feature.usesDesign) || feature.usesDesign.length === 0) return '';
374
+ const idx = designIndex || {};
375
+ const parts = feature.usesDesign.map(id => {
376
+ if (/^DT-/.test(id)) {
377
+ const label = idx.byToken && idx.byToken[id] ? ` ${idx.byToken[id].key}` : '';
378
+ return `${id}${label}`;
379
+ }
380
+ if (/^DC-/.test(id)) {
381
+ const label = idx.byComponent && idx.byComponent[id] ? ` ${idx.byComponent[id].name}` : '';
382
+ return `${id}${label}`;
383
+ }
384
+ return id;
385
+ });
386
+ return `${feature.id} nutzt: ${parts.join(', ')}`;
387
+ }
388
+
389
+ module.exports = {
390
+ DEFAULT_MAX_DEPTH,
391
+ CODE_EXTENSIONS,
392
+ resolveImport,
393
+ extractEdges,
394
+ walkCallGraph,
395
+ traceAc,
396
+ formatTraceResult,
397
+ // F-063
398
+ formatDesignUsage,
399
+ };