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,445 @@
1
+ // @cap-context CAP v2.0 session manager -- manages .cap/SESSION.json for cross-conversation workflow state.
2
+ // @cap-decision SESSION.json is ephemeral (gitignored) -- it tracks the current developer's workflow state, not project state. Project state lives in FEATURE-MAP.md.
3
+ // @cap-decision JSON format (not markdown) -- session state is machine-consumed, not human-read. JSON is faster to parse and type-safe.
4
+ // @cap-constraint Zero external dependencies -- uses only Node.js built-ins (fs, path).
5
+ // @cap-pattern All session reads/writes go through this module -- no direct fs.readFileSync of SESSION.json elsewhere.
6
+
7
+ 'use strict';
8
+
9
+ // @cap-feature(feature:F-003) Session State Management — .cap/SESSION.json for cross-conversation workflow state
10
+
11
+ // @cap-history(sessions:2, edits:4, since:2026-05-08, learned:2026-05-08) Frequently modified — 2 sessions, 4 edits
12
+ const fs = require('node:fs');
13
+ const path = require('node:path');
14
+
15
+ // @cap-decision Session schema is flat and extensible -- new workflow commands can add keys without schema migration.
16
+ // @cap-todo(ref:AC-16) SESSION.json tracks ephemeral workflow state: active feature ID, current workflow step, session timestamps
17
+ /**
18
+ * @typedef {Object} CapQuickMode
19
+ * @property {boolean} active - Whether quick-mode is currently active
20
+ * @property {string|null} feature - Feature ID being worked on (mirrors activeFeature when entered via /cap:quick)
21
+ * @property {string|null} startedAt - ISO timestamp when quick-mode started
22
+ * @property {string|null} startCommit - git HEAD SHA at quick-mode entry — used by /cap:finalize to compute changed files
23
+ */
24
+
25
+ /**
26
+ * @typedef {Object} CapSession
27
+ * @property {string} version - Session schema version (e.g., "2.0.0")
28
+ * @property {string|null} lastCommand - Last /cap: command executed
29
+ * @property {string|null} lastCommandTimestamp - ISO timestamp of last command
30
+ * @property {string|null} activeApp - Currently focused app path (e.g., "apps/flow") or null for single-repo/root
31
+ * @property {string|null} activeFeature - Currently focused feature ID
32
+ * @property {string|null} step - Current workflow step
33
+ * @property {string|null} startedAt - ISO timestamp of when session started
34
+ * @property {string|null} activeDebugSession - Active debug session ID
35
+ * @property {'A'|'B'|'C'} trustMode - @cap-feature(feature:F-075) Trust-Mode slot — mirrored from .cap/config.json by cap-trust-mode.
36
+ * @property {CapQuickMode} quickMode - @cap-feature(feature:F-092) Two-phase workflow state — Phase 1 fast-lane editing
37
+ * @property {Object<string,string>} metadata - Extensible key-value metadata
38
+ */
39
+
40
+ const CAP_DIR = '.cap';
41
+ const SESSION_FILE = 'SESSION.json';
42
+
43
+ // @cap-todo(ref:AC-3) .cap/.gitignore ignores SESSION.json (ephemeral state shall not be committed)
44
+ const GITIGNORE_CONTENT = `# CAP ephemeral state -- do not commit
45
+ SESSION.json
46
+ debug/
47
+ `;
48
+
49
+ const CAP_MEMORY_RULE = `## CAP Project Memory
50
+
51
+ This project uses CAP (Code as Plan) with a project memory system at \`.cap/memory/\`.
52
+
53
+ The pipeline supports **two layouts** (F-093). Detect which is active before reading:
54
+
55
+ ### V6 layout (per-feature, opt-in via \`.cap/config.json: { memory: { layout: "v6" } }\`)
56
+
57
+ When the top-level \`decisions.md\` or \`pitfalls.md\` contains the marker \`(V6 Index)\` in its title, this project is on V6:
58
+
59
+ 1. **Read \`.cap/memory/decisions.md\` and \`.cap/memory/pitfalls.md\` first** — these are short index tables (\`Destination | Count | File\`) listing per-feature memory files.
60
+ 2. **Then load only the per-feature file relevant to the current task** — e.g. \`.cap/memory/features/F-XXX-<topic>.md\` if you're working on F-XXX, or a \`platform/<topic>.md\` for cross-cutting concerns.
61
+ 3. Skip reading the index entries you don't need — that is the whole point of V6 (token-cost-of-read reduction).
62
+
63
+ ### V5 layout (legacy, default for projects without the V6 config flag)
64
+
65
+ When the top-level files do NOT have the \`(V6 Index)\` marker, read them directly as monolithic memory:
66
+
67
+ - \`.cap/memory/decisions.md\` — architectural decisions extracted from code tags and sessions
68
+ - \`.cap/memory/pitfalls.md\` — known pitfalls, gotchas, and things that broke before
69
+ - \`.cap/memory/patterns.md\` — recurring patterns and conventions observed in the codebase
70
+ - \`.cap/memory/hotspots.md\` — frequently edited files and areas of high churn
71
+
72
+ These files are auto-generated by the CAP memory pipeline after each session. They complement your auto-memory (user preferences, feedback) with project-specific technical context.
73
+
74
+ Use this knowledge to:
75
+ - Avoid repeating past mistakes documented in pitfalls
76
+ - Follow established patterns when writing new code
77
+ - Understand the reasoning behind architectural choices
78
+ - Focus attention on hotspot files that change frequently
79
+ `;
80
+
81
+ // @cap-api getDefaultSession() -- Returns a fresh default session object.
82
+ // @cap-todo(ref:AC-2) SESSION.json with valid JSON structure: { active_feature: null, step: null, started_at: null }
83
+ /**
84
+ * @returns {CapSession}
85
+ */
86
+ function getDefaultSession() {
87
+ return {
88
+ version: '2.0.0',
89
+ lastCommand: null,
90
+ lastCommandTimestamp: null,
91
+ activeApp: null,
92
+ activeFeature: null,
93
+ step: null,
94
+ startedAt: null,
95
+ activeDebugSession: null,
96
+ activeThread: null,
97
+ // @cap-feature(feature:F-075) Trust-Mode slot — mirrored from .cap/config.json.
98
+ // @cap-todo(ac:F-075/AC-1) SESSION.json carries trustMode field with default 'A'.
99
+ // Sessions created before F-075 will land here via the { ...default, ...parsed } merge
100
+ // in loadSession(), so backfill is automatic on first read.
101
+ trustMode: 'A',
102
+ // @cap-feature(feature:F-092) Two-phase workflow — Phase 1 (Visual Iteration) vs Phase 2 (Solidify).
103
+ // Default { active: false } so existing sessions / non-quick-mode workflows are unaffected.
104
+ quickMode: {
105
+ active: false,
106
+ feature: null,
107
+ startedAt: null,
108
+ startCommit: null,
109
+ },
110
+ metadata: {},
111
+ // @cap-feature(feature:F-057) Checkpoint persistence fields — additive, null by default.
112
+ // lastCheckpointAt = ISO timestamp of the most recent /cap:checkpoint that detected a breakpoint.
113
+ // lastCheckpointSnapshot = minimal snapshot of feature states + AC statuses at that moment,
114
+ // so future /cap:checkpoint runs can diff against it.
115
+ lastCheckpointAt: null,
116
+ lastCheckpointSnapshot: null,
117
+ };
118
+ }
119
+
120
+ // @cap-api loadSession(projectRoot) -- Loads .cap/SESSION.json. Returns default session if file missing or corrupt.
121
+ // @cap-todo(ref:AC-19) SESSION.json is the only mutable session artifact
122
+ /**
123
+ * @param {string} projectRoot - Absolute path to project root
124
+ * @returns {CapSession}
125
+ */
126
+ function loadSession(projectRoot) {
127
+ const sessionPath = path.join(projectRoot, CAP_DIR, SESSION_FILE);
128
+ try {
129
+ if (!fs.existsSync(sessionPath)) return getDefaultSession();
130
+ const content = fs.readFileSync(sessionPath, 'utf8');
131
+ const parsed = JSON.parse(content);
132
+ // Merge with defaults to handle missing keys from older versions
133
+ return { ...getDefaultSession(), ...parsed };
134
+ } catch (_e) {
135
+ // Corrupt JSON -- return default
136
+ return getDefaultSession();
137
+ }
138
+ }
139
+
140
+ // @cap-api saveSession(projectRoot, session) -- Writes .cap/SESSION.json. Creates .cap/ directory if needed.
141
+ // @cap-todo(ref:AC-18) SESSION.json shall not be committed to version control (enforced by .cap/.gitignore)
142
+ /**
143
+ * @param {string} projectRoot - Absolute path to project root
144
+ * @param {CapSession} session - Session data to persist
145
+ */
146
+ function saveSession(projectRoot, session) {
147
+ const capDir = path.join(projectRoot, CAP_DIR);
148
+ if (!fs.existsSync(capDir)) {
149
+ fs.mkdirSync(capDir, { recursive: true });
150
+ }
151
+ const sessionPath = path.join(capDir, SESSION_FILE);
152
+ fs.writeFileSync(sessionPath, JSON.stringify(session, null, 2) + '\n', 'utf8');
153
+ }
154
+
155
+ // @cap-api updateSession(projectRoot, updates) -- Partial update to session (merge, not overwrite).
156
+ /**
157
+ * @param {string} projectRoot - Absolute path to project root
158
+ * @param {Partial<CapSession>} updates - Fields to merge into current session
159
+ * @returns {CapSession} - The updated session
160
+ */
161
+ function updateSession(projectRoot, updates) {
162
+ const session = loadSession(projectRoot);
163
+ // Shallow merge -- metadata gets replaced if present in updates
164
+ const updated = { ...session, ...updates };
165
+ saveSession(projectRoot, updated);
166
+ return updated;
167
+ }
168
+
169
+ // @cap-api startSession(projectRoot, featureId, step) -- Set active feature and step with timestamp.
170
+ // @cap-todo(ref:AC-17) SESSION.json connects to FEATURE-MAP.md only via feature IDs (loose coupling)
171
+ /**
172
+ * @param {string} projectRoot - Absolute path to project root
173
+ * @param {string} featureId - Feature ID to focus on (e.g., "F-001")
174
+ * @param {string} step - Current workflow step name
175
+ * @returns {CapSession}
176
+ */
177
+ function startSession(projectRoot, featureId, step) {
178
+ return updateSession(projectRoot, {
179
+ activeFeature: featureId,
180
+ step,
181
+ startedAt: new Date().toISOString(),
182
+ });
183
+ }
184
+
185
+ // @cap-api updateStep(projectRoot, step) -- Update current workflow step.
186
+ /**
187
+ * @param {string} projectRoot - Absolute path to project root
188
+ * @param {string} step - New workflow step name
189
+ * @returns {CapSession}
190
+ */
191
+ function updateStep(projectRoot, step) {
192
+ return updateSession(projectRoot, { step });
193
+ }
194
+
195
+ // @cap-api endSession(projectRoot) -- Clear active feature and step.
196
+ /**
197
+ * @param {string} projectRoot - Absolute path to project root
198
+ * @returns {CapSession}
199
+ */
200
+ function endSession(projectRoot) {
201
+ return updateSession(projectRoot, {
202
+ activeFeature: null,
203
+ step: null,
204
+ startedAt: null,
205
+ });
206
+ }
207
+
208
+ // @cap-api isInitialized(projectRoot) -- Check if .cap/ exists.
209
+ /**
210
+ * @param {string} projectRoot - Absolute path to project root
211
+ * @returns {boolean}
212
+ */
213
+ function isInitialized(projectRoot) {
214
+ return fs.existsSync(path.join(projectRoot, CAP_DIR));
215
+ }
216
+
217
+ // @cap-api initCapDirectory(projectRoot) -- Creates .cap/ directory structure and .gitignore. Idempotent.
218
+ // @cap-todo(ref:AC-4) No prompts, questions, wizards, or configuration forms
219
+ // @cap-todo(ref:AC-5) Completes in a single invocation with no follow-up steps
220
+ // @cap-todo(ref:AC-6) Idempotent -- running on already-initialized project shall not overwrite existing content
221
+ /**
222
+ * @param {string} projectRoot - Absolute path to project root
223
+ */
224
+ function initCapDirectory(projectRoot) {
225
+ const capDir = path.join(projectRoot, CAP_DIR);
226
+ const stackDocsDir = path.join(capDir, 'stack-docs');
227
+ const debugDir = path.join(capDir, 'debug');
228
+ const gitignorePath = path.join(capDir, '.gitignore');
229
+ const sessionPath = path.join(capDir, SESSION_FILE);
230
+
231
+ // Create directories (idempotent via recursive:true)
232
+ fs.mkdirSync(capDir, { recursive: true });
233
+ fs.mkdirSync(stackDocsDir, { recursive: true });
234
+ fs.mkdirSync(debugDir, { recursive: true });
235
+
236
+ // Write .gitignore (always overwrite -- it's infrastructure, not user content)
237
+ fs.writeFileSync(gitignorePath, GITIGNORE_CONTENT, 'utf8');
238
+
239
+ // Write SESSION.json only if it doesn't exist (preserve existing session)
240
+ if (!fs.existsSync(sessionPath)) {
241
+ saveSession(projectRoot, getDefaultSession());
242
+ }
243
+
244
+ // Create .claude/rules/cap-memory.md -- bridges CAP project memory with Claude auto-memory
245
+ const rulesDir = path.join(projectRoot, '.claude', 'rules');
246
+ const memoryRulePath = path.join(rulesDir, 'cap-memory.md');
247
+ if (!fs.existsSync(memoryRulePath)) {
248
+ fs.mkdirSync(rulesDir, { recursive: true });
249
+ fs.writeFileSync(memoryRulePath, CAP_MEMORY_RULE, 'utf8');
250
+ }
251
+ }
252
+
253
+ // @cap-api setActiveApp(projectRoot, appPath) -- Set the active app in SESSION.json for monorepo scoping.
254
+ /**
255
+ * @param {string} projectRoot - Absolute path to project root
256
+ * @param {string|null} appPath - Relative app path (e.g., "apps/flow") or null to clear
257
+ * @returns {CapSession}
258
+ */
259
+ function setActiveApp(projectRoot, appPath) {
260
+ return updateSession(projectRoot, { activeApp: appPath || null });
261
+ }
262
+
263
+ // @cap-api getActiveApp(projectRoot) -- Get current active app path from SESSION.json.
264
+ /**
265
+ * @param {string} projectRoot - Absolute path to project root
266
+ * @returns {string|null} - Active app path or null
267
+ */
268
+ function getActiveApp(projectRoot) {
269
+ const session = loadSession(projectRoot);
270
+ return session.activeApp || null;
271
+ }
272
+
273
+ // @cap-api getAppRoot(projectRoot) -- Returns the effective root for app-scoped operations.
274
+ // If activeApp is set, returns projectRoot + activeApp. Otherwise returns projectRoot.
275
+ // This is the KEY function for all scoping decisions.
276
+ /**
277
+ * @param {string} projectRoot - Absolute path to project root
278
+ * @returns {string} - Absolute path to the active app root (or project root if no app)
279
+ */
280
+ function getAppRoot(projectRoot) {
281
+ const activeApp = getActiveApp(projectRoot);
282
+ if (activeApp) {
283
+ return path.join(projectRoot, activeApp);
284
+ }
285
+ return projectRoot;
286
+ }
287
+
288
+ // @cap-api listApps(projectRoot) -- List available apps/packages in a monorepo using detectWorkspaces.
289
+ /**
290
+ * @param {string} projectRoot - Absolute path to project root
291
+ * @returns {{ isMonorepo: boolean, apps: string[] }}
292
+ */
293
+ function listApps(projectRoot) {
294
+ // Lazy require to avoid circular dependency
295
+ const { detectWorkspaces } = require('./cap-tag-scanner.cjs');
296
+ const workspaces = detectWorkspaces(projectRoot);
297
+ return {
298
+ isMonorepo: workspaces.isMonorepo,
299
+ apps: workspaces.packages,
300
+ };
301
+ }
302
+
303
+ // @cap-feature(feature:F-092, primary:true) Two-Phase Workflow — /cap:quick + /cap:finalize
304
+ // @cap-decision(F-092) Quick-mode state lives in SESSION.json, not in a separate file. Mirrors
305
+ // how activeFeature, activeApp, etc. live in the existing schema. Single source of truth for
306
+ // ephemeral workflow state.
307
+ // @cap-decision(F-092/git-snapshot) startCommit captures git HEAD at /cap:quick time. /cap:finalize
308
+ // diffs against this commit to compute changed-files. This decouples quick-mode lifetime from
309
+ // commit cadence — Bastian can iterate freely, commit or not, and finalize sees the full set.
310
+
311
+ const { execSync } = require('node:child_process');
312
+
313
+ /**
314
+ * Resolve current git HEAD SHA for the given working directory.
315
+ * Returns null if not a git repo or git is unavailable — caller treats as "no snapshot, fall back to unstaged-only diff".
316
+ * @param {string} cwd
317
+ * @returns {string|null}
318
+ */
319
+ function _getGitHeadSha(cwd) {
320
+ try {
321
+ const out = execSync('git rev-parse HEAD', { cwd, stdio: ['ignore', 'pipe', 'ignore'] });
322
+ return String(out).trim() || null;
323
+ } catch (_e) {
324
+ return null;
325
+ }
326
+ }
327
+
328
+ // @cap-api startQuickMode(projectRoot, featureId) -- Enter Phase 1 (Visual Iteration) for a feature.
329
+ // @cap-todo(ac:F-092/AC-1) SESSION.json carries quickMode state with startCommit snapshot.
330
+ /**
331
+ * @param {string} projectRoot - Absolute path to project root
332
+ * @param {string} featureId - Feature ID being worked on (e.g., "F-Hub-Spotlight-Carousel")
333
+ * @returns {CapSession}
334
+ */
335
+ function startQuickMode(projectRoot, featureId) {
336
+ const startCommit = _getGitHeadSha(projectRoot);
337
+ return updateSession(projectRoot, {
338
+ activeFeature: featureId,
339
+ quickMode: {
340
+ active: true,
341
+ feature: featureId,
342
+ startedAt: new Date().toISOString(),
343
+ startCommit,
344
+ },
345
+ });
346
+ }
347
+
348
+ // @cap-api endQuickMode(projectRoot) -- Exit Phase 1 — typically called from /cap:finalize after consolidation.
349
+ // @cap-todo(ac:F-092/AC-10) Reset quickMode flag after successful finalize.
350
+ /**
351
+ * @param {string} projectRoot - Absolute path to project root
352
+ * @returns {CapSession}
353
+ */
354
+ function endQuickMode(projectRoot) {
355
+ return updateSession(projectRoot, {
356
+ quickMode: {
357
+ active: false,
358
+ feature: null,
359
+ startedAt: null,
360
+ startCommit: null,
361
+ },
362
+ });
363
+ }
364
+
365
+ // @cap-api isQuickModeActive(projectRoot) -- Check whether the current session is in Phase 1.
366
+ /**
367
+ * @param {string} projectRoot - Absolute path to project root
368
+ * @returns {boolean}
369
+ */
370
+ function isQuickModeActive(projectRoot) {
371
+ const session = loadSession(projectRoot);
372
+ return Boolean(session.quickMode && session.quickMode.active === true);
373
+ }
374
+
375
+ // @cap-api getChangedFilesSinceQuickStart(projectRoot) -- Compute files changed since /cap:quick was entered.
376
+ // @cap-todo(ac:F-092/AC-4) Diff = committed since startCommit + currently unstaged.
377
+ // @cap-decision(F-092) Returns DEDUPLICATED set — a file edited then committed then re-edited shows up once.
378
+ // Filters to source files (excludes .cap/, node_modules/, dist/, build/, .git/) so finalize doesn't
379
+ // try to annotate generated artifacts.
380
+ /**
381
+ * @param {string} projectRoot - Absolute path to project root
382
+ * @returns {{ files: string[], startCommit: string|null, error?: string }}
383
+ */
384
+ function getChangedFilesSinceQuickStart(projectRoot) {
385
+ const session = loadSession(projectRoot);
386
+ const startCommit = session.quickMode && session.quickMode.startCommit;
387
+ const set = new Set();
388
+ const exclude = (p) =>
389
+ /^\.cap\//.test(p) ||
390
+ /^node_modules\//.test(p) ||
391
+ /^dist\//.test(p) ||
392
+ /^build\//.test(p) ||
393
+ /^\.git\//.test(p);
394
+
395
+ try {
396
+ if (startCommit) {
397
+ // Files committed since quick-start (includes renames as both old + new path)
398
+ const committed = execSync(
399
+ `git diff --name-only ${startCommit} HEAD`,
400
+ { cwd: projectRoot, stdio: ['ignore', 'pipe', 'ignore'] }
401
+ );
402
+ String(committed).trim().split('\n').filter(Boolean).forEach(f => { if (!exclude(f)) set.add(f); });
403
+ }
404
+ // Currently unstaged changes
405
+ const unstaged = execSync(
406
+ 'git diff --name-only HEAD',
407
+ { cwd: projectRoot, stdio: ['ignore', 'pipe', 'ignore'] }
408
+ );
409
+ String(unstaged).trim().split('\n').filter(Boolean).forEach(f => { if (!exclude(f)) set.add(f); });
410
+ // Untracked files (newly created in quick-mode)
411
+ const untracked = execSync(
412
+ 'git ls-files --others --exclude-standard',
413
+ { cwd: projectRoot, stdio: ['ignore', 'pipe', 'ignore'] }
414
+ );
415
+ String(untracked).trim().split('\n').filter(Boolean).forEach(f => { if (!exclude(f)) set.add(f); });
416
+ } catch (e) {
417
+ return { files: [], startCommit: startCommit || null, error: String(e && e.message ? e.message : e).trim() };
418
+ }
419
+
420
+ return { files: Array.from(set).sort(), startCommit: startCommit || null };
421
+ }
422
+
423
+ module.exports = {
424
+ CAP_DIR,
425
+ SESSION_FILE,
426
+ GITIGNORE_CONTENT,
427
+ loadSession,
428
+ saveSession,
429
+ updateSession,
430
+ getDefaultSession,
431
+ startSession,
432
+ updateStep,
433
+ endSession,
434
+ isInitialized,
435
+ initCapDirectory,
436
+ setActiveApp,
437
+ getActiveApp,
438
+ getAppRoot,
439
+ listApps,
440
+ // F-092: two-phase workflow
441
+ startQuickMode,
442
+ endQuickMode,
443
+ isQuickModeActive,
444
+ getChangedFilesSinceQuickStart,
445
+ };