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,114 @@
1
+ #!/usr/bin/env node
2
+ // @cap-feature(feature:F-073) Learn-Review Stop-Hook — flags pending pattern reviews after each session.
3
+ // Fires AFTER cap-memory's Stop hook (memory pipeline → learn pipeline →
4
+ // review board). cap-hook-version: {{CAP_VERSION}}
5
+ //
6
+ // Stop hook: at session end, compute shouldShowBoard(projectRoot). If the gate is met, write
7
+ // .cap/learning/board-pending.flag so /cap:status (and the next /cap:learn review run) can surface
8
+ // the pending review. We DO NOT spawn the skill from the hook — Claude Code hook subprocesses can't
9
+ // drive an interactive review flow.
10
+ //
11
+ // Skip via CAP_SKIP_LEARN_REVIEW_HOOK=1.
12
+ // Never exits non-zero: a failure here must not block the user's session end.
13
+
14
+ 'use strict';
15
+
16
+ const fs = require('node:fs');
17
+ const path = require('node:path');
18
+ const os = require('node:os');
19
+
20
+ // @cap-todo(ac:F-073/AC-3) Skip switch — keep parity with cap-learning-hook.js / cap-memory.js so
21
+ // ops can disable a single learning hook without touching the others.
22
+ if (process.env.CAP_SKIP_LEARN_REVIEW_HOOK === '1') {
23
+ process.exit(0);
24
+ }
25
+
26
+ let input = '';
27
+ const stdinTimeout = setTimeout(() => process.exit(0), 3000);
28
+ process.stdin.setEncoding('utf8');
29
+ process.stdin.on('data', (chunk) => { input += chunk; });
30
+ process.stdin.on('end', () => {
31
+ clearTimeout(stdinTimeout);
32
+ run(input);
33
+ });
34
+
35
+ function tryRequire(modulePath) {
36
+ try { return require(modulePath); } catch { return null; }
37
+ }
38
+
39
+ // @cap-decision(F-073/D1) Lib resolution mirrors cap-learning-hook.js exactly: env override →
40
+ // colocated in-tree → installed under ~/.claude. Keeps every CAP hook on the same
41
+ // resolution path so an ops change to one hook applies to the others for free.
42
+ function resolveReviewModule() {
43
+ const candidates = [];
44
+ if (process.env.CAP_LEARN_REVIEW_LIB) candidates.push(process.env.CAP_LEARN_REVIEW_LIB);
45
+ candidates.push(path.join(__dirname, '..', 'cap', 'bin', 'lib', 'cap-learn-review.cjs'));
46
+ candidates.push(path.join(os.homedir(), '.claude', 'cap', 'bin', 'lib', 'cap-learn-review.cjs'));
47
+ for (const p of candidates) {
48
+ const mod = tryRequire(p);
49
+ if (mod) return mod;
50
+ }
51
+ return null;
52
+ }
53
+
54
+ function run(raw) {
55
+ // @cap-todo(ac:F-073/AC-3) Whole hook body wrapped in try/catch; failures never escape.
56
+ try {
57
+ const data = raw ? JSON.parse(raw) : {};
58
+ const cwd = data.cwd || process.cwd();
59
+
60
+ const review = resolveReviewModule();
61
+ if (!review) process.exit(0); // library not installed — silent no-op (mirrors cap-memory.js).
62
+
63
+ // Compute the gate. shouldShowBoard reads SESSION.json internally for the per-session skip/reject
64
+ // sets — no need to pass anything explicit here.
65
+ let shouldShow = false;
66
+ try {
67
+ shouldShow = review.shouldShowBoard(cwd) === true;
68
+ } catch (_e) {
69
+ shouldShow = false;
70
+ }
71
+
72
+ if (!shouldShow) {
73
+ // Below threshold — NO flag. If a stale flag from a prior session is on disk we leave it
74
+ // alone here; /cap:learn review clears it after the user processes the board, and a fresh
75
+ // shouldShowBoard==false simply means there's nothing new to add.
76
+ process.exit(0);
77
+ }
78
+
79
+ // Threshold met — write the flag. Eligible count is recomputed for diagnostic purposes;
80
+ // the SKILL checks for FILE EXISTENCE, not content, so a half-written flag is harmless.
81
+ let eligibleCount = 0;
82
+ try {
83
+ const board = review.buildReviewBoard(cwd);
84
+ if (board && Array.isArray(board.eligible)) eligibleCount = board.eligible.length;
85
+ } catch (_e) {
86
+ eligibleCount = 0;
87
+ }
88
+
89
+ // @cap-risk(F-073/AC-3) writeBoardPendingFlag sanitises sessionId before persistence so a
90
+ // hostile SESSION.json can't smuggle bytes via the flag content.
91
+ review.writeBoardPendingFlag(cwd, { eligibleCount });
92
+ process.exit(0);
93
+ } catch (_err) {
94
+ // Best-effort error log to .cap/learning/.errors.log so we can diagnose without leaking
95
+ // through the tool surface. Mirrors cap-learning-hook.js's error-log strategy.
96
+ try {
97
+ const cwd = process.cwd();
98
+ const errDir = path.join(cwd, '.cap', 'learning');
99
+ if (!fs.existsSync(errDir)) fs.mkdirSync(errDir, { recursive: true });
100
+ fs.appendFileSync(
101
+ path.join(errDir, '.errors.log'),
102
+ JSON.stringify({
103
+ ts: new Date().toISOString(),
104
+ hook: 'cap-learn-review-hook',
105
+ message: _err && _err.message ? _err.message : String(_err),
106
+ }) + '\n',
107
+ 'utf8',
108
+ );
109
+ } catch {
110
+ // Even logging failed — stay silent.
111
+ }
112
+ process.exit(0);
113
+ }
114
+ }
@@ -0,0 +1,192 @@
1
+ #!/usr/bin/env node
2
+ // @cap-feature(feature:F-070) Learning-Signals Hook — PostToolUse entry point for AC-1 (editAfterWrite)
3
+ // and AC-2 (memory-ref). cap-hook-version: {{CAP_VERSION}}
4
+ //
5
+ // PostToolUse hook: fires after Edit / Write / MultiEdit / NotebookEdit / Read and emits learning
6
+ // signals into .cap/learning/signals/<type>.jsonl via the cap-learning-signals.cjs collector.
7
+ //
8
+ // Two responsibilities:
9
+ // 1. Cross-event editAfterWrite detection via a per-session persistent ledger
10
+ // (.cap/learning/signals/../state/written-files.jsonl). Hooks fire as fresh subprocesses, so an
11
+ // in-memory Set cannot bridge a Write event and a later Edit event — the ledger is the bridge.
12
+ // Write / MultiEdit / NotebookEdit append to the ledger; Edit checks the ledger and emits
13
+ // recordOverride({subType:'editAfterWrite'}) when there is a match for the same sessionId.
14
+ // 2. When a Read targets any path under .cap/memory/**/*.md (recursive), emit recordMemoryRef.
15
+ //
16
+ // AC-5 budget: <50ms per hook. The collector is sync JSONL append. The ledger read happens here, but
17
+ // the ledger is per-session (typical <100 lines) and we never read the signal JSONLs.
18
+ //
19
+ // Skip via CAP_SKIP_LEARNING_HOOK=1.
20
+ // Never exits non-zero: a failure here must not block the edit/read tool.
21
+ //
22
+ // Reject-Approval (AC-1 second flavour) is left as an integration gap — Claude Code's PreToolUse
23
+ // rejection signal is not observable from the matchers we have access to in this repo. See the
24
+ // @cap-decision below and the @cap-todo on AC-1.
25
+
26
+ 'use strict';
27
+
28
+ const fs = require('node:fs');
29
+ const path = require('node:path');
30
+ const os = require('node:os');
31
+
32
+ // @cap-todo(ac:F-070/AC-5) Skip switch for benchmarking and tests that don't want hook side effects.
33
+ if (process.env.CAP_SKIP_LEARNING_HOOK === '1') {
34
+ process.exit(0);
35
+ }
36
+
37
+ // Edit handled separately from Write/MultiEdit/NotebookEdit: only the latter three "create new content"
38
+ // in the file from the agent's perspective. An Edit is the user's correction. Both groups append to
39
+ // the ledger so a chain Edit→Edit on a previously-written file still trips editAfterWrite.
40
+ const WRITE_TOOLS = new Set(['Write', 'MultiEdit', 'NotebookEdit']);
41
+ const EDIT_TOOL = 'Edit';
42
+ const OBSERVED_WRITE_TOOLS = new Set([EDIT_TOOL, ...WRITE_TOOLS]);
43
+ const OBSERVED_READ_TOOLS = new Set(['Read']);
44
+
45
+ let input = '';
46
+ const stdinTimeout = setTimeout(() => process.exit(0), 3000);
47
+ process.stdin.setEncoding('utf8');
48
+ process.stdin.on('data', (chunk) => { input += chunk; });
49
+ process.stdin.on('end', () => {
50
+ clearTimeout(stdinTimeout);
51
+ run(input);
52
+ });
53
+
54
+ function tryRequire(modulePath) {
55
+ try { return require(modulePath); } catch { return null; }
56
+ }
57
+
58
+ // @cap-decision(F-070/D8) Lib resolution mirrors cap-tag-observer.js exactly: env override → colocated
59
+ // in-tree → installed under ~/.claude. Keeping the resolution path identical means an
60
+ // ops change to one hook applies to the other for free.
61
+ function resolveCollectorModule() {
62
+ const candidates = [];
63
+ if (process.env.CAP_LEARNING_LIB) candidates.push(process.env.CAP_LEARNING_LIB);
64
+ candidates.push(path.join(__dirname, '..', 'cap', 'bin', 'lib', 'cap-learning-signals.cjs'));
65
+ candidates.push(path.join(os.homedir(), '.claude', 'cap', 'bin', 'lib', 'cap-learning-signals.cjs'));
66
+ for (const p of candidates) {
67
+ const mod = tryRequire(p);
68
+ if (mod) return mod;
69
+ }
70
+ return null;
71
+ }
72
+
73
+ // @cap-decision(F-070/D9) Session id resolution: read .cap/SESSION.json synchronously. The file is small
74
+ // (~few hundred bytes) so the read is O(1) and well inside AC-5's 50ms budget.
75
+ function readSessionContext(cwd) {
76
+ try {
77
+ const p = path.join(cwd, '.cap', 'SESSION.json');
78
+ if (!fs.existsSync(p)) return { sessionId: null, featureId: null };
79
+ const parsed = JSON.parse(fs.readFileSync(p, 'utf8'));
80
+ return {
81
+ sessionId: typeof parsed.sessionId === 'string' ? parsed.sessionId : null,
82
+ featureId: typeof parsed.activeFeature === 'string' ? parsed.activeFeature : null,
83
+ };
84
+ } catch { return { sessionId: null, featureId: null }; }
85
+ }
86
+
87
+ function isUnderMemoryDir(absPath, cwd) {
88
+ // Match any file under <cwd>/.cap/memory/ regardless of subdirectory or extension.
89
+ // The collector hashes the path; we just need a routing decision here.
90
+ const memoryRoot = path.join(cwd, '.cap', 'memory');
91
+ // Use startsWith with a path separator suffix to avoid matching e.g. .cap/memory-foo.
92
+ return absPath === memoryRoot
93
+ || absPath.startsWith(memoryRoot + path.sep)
94
+ || absPath.startsWith(memoryRoot + '/');
95
+ }
96
+
97
+ function run(raw) {
98
+ // @cap-todo(ac:F-070/AC-7) Whole hook body wrapped in try/catch; failures never escape.
99
+ try {
100
+ const data = raw ? JSON.parse(raw) : {};
101
+ const toolName = data.tool_name;
102
+ const toolInput = data.tool_input || {};
103
+ const cwd = data.cwd || process.cwd();
104
+ const filePath = toolInput.file_path || toolInput.notebook_path;
105
+ if (!toolName || !filePath) process.exit(0);
106
+
107
+ const absPath = path.isAbsolute(filePath) ? filePath : path.join(cwd, filePath);
108
+
109
+ const collector = resolveCollectorModule();
110
+ if (!collector) process.exit(0); // library not installed — silent no-op, mirrors cap-memory.js
111
+
112
+ // @cap-todo(ac:F-070/AC-1) editAfterWrite detection across subprocess boundaries: the per-session
113
+ // ledger bridges what a single hook process cannot. Edit checks the ledger;
114
+ // all four tools append to the ledger so a subsequent Edit on the same path
115
+ // (this session) emits the override.
116
+ if (OBSERVED_WRITE_TOOLS.has(toolName)) {
117
+ const ctx = readSessionContext(cwd);
118
+ if (toolName === EDIT_TOOL && ctx.sessionId
119
+ && collector.wasWrittenInSession(cwd, ctx.sessionId, absPath)) {
120
+ // @cap-risk(F-070/AC-5) recordOverride is sync JSONL append, never reads signal JSONLs.
121
+ // The ledger read above IS in the hot path but stays bounded
122
+ // (per-session file, typical <100 entries). The performance suite
123
+ // brackets the full hook to confirm the 50ms budget holds.
124
+ collector.recordOverride({
125
+ projectRoot: cwd,
126
+ subType: 'editAfterWrite',
127
+ sessionId: ctx.sessionId,
128
+ featureId: ctx.featureId,
129
+ targetFile: absPath, // collector hashes; never persisted raw
130
+ });
131
+ }
132
+ // Append to the persistent ledger so future Edit events in this session can detect the chain.
133
+ // We append for ALL four tools (Edit included) so Edit→Edit on a previously-written file still
134
+ // produces an override on the second Edit.
135
+ if (ctx.sessionId) {
136
+ collector.recordWriteIntoLedger(cwd, ctx.sessionId, absPath);
137
+ }
138
+ process.exit(0);
139
+ }
140
+
141
+ // @cap-todo(ac:F-070/AC-2) memory-ref detection: a Read on .cap/memory/*.md → recordMemoryRef.
142
+ if (OBSERVED_READ_TOOLS.has(toolName)) {
143
+ if (!isUnderMemoryDir(absPath, cwd)) process.exit(0);
144
+ const ctx = readSessionContext(cwd);
145
+ collector.recordMemoryRef({
146
+ projectRoot: cwd,
147
+ sessionId: ctx.sessionId,
148
+ featureId: ctx.featureId,
149
+ memoryFile: absPath, // collector hashes
150
+ });
151
+ process.exit(0);
152
+ }
153
+
154
+ // Unobserved tool — exit silently.
155
+ process.exit(0);
156
+ } catch (_err) {
157
+ // AC-7: never propagate. Best-effort error log to .cap/learning/signals/.errors.log so we can
158
+ // diagnose without leaking through the tool surface.
159
+ try {
160
+ const cwd = process.cwd();
161
+ const errDir = path.join(cwd, '.cap', 'learning', 'signals');
162
+ if (!fs.existsSync(errDir)) fs.mkdirSync(errDir, { recursive: true });
163
+ fs.appendFileSync(
164
+ path.join(errDir, '.errors.log'),
165
+ JSON.stringify({
166
+ ts: new Date().toISOString(),
167
+ message: _err && _err.message ? _err.message : String(_err),
168
+ }) + '\n',
169
+ 'utf8',
170
+ );
171
+ } catch {
172
+ // Even logging failed — stay silent.
173
+ }
174
+ process.exit(0);
175
+ }
176
+ }
177
+
178
+ // @cap-todo(ac:F-070/AC-1) Reject-Approval flavour of recordOverride is INTENTIONALLY UNWIRED here.
179
+ // @cap-decision(F-070/D10) Reject-Approval is left as a documented integration gap.
180
+ // Why: PreToolUse rejection events in this repo's hook surface are not observable as a distinct
181
+ // tool_name / payload — the existing hooks (cap-prompt-guard, cap-workflow-guard) intercept BEFORE a
182
+ // tool runs but they do not report a "user rejected" signal back into the post-tool stream. Wiring a
183
+ // speculative shape would invent an interface that downstream Claude Code hook contract changes might
184
+ // silently drift away from.
185
+ // What's still good: the COLLECTOR exposes recordOverride({subType:'rejectApproval'}) and the unit
186
+ // tests cover that shape. Whoever wires the rejection signal later (whether via a distinct hook
187
+ // matcher or a stdin payload field we haven't seen yet) just needs to call the collector — no schema
188
+ // work, no module refactor. This keeps the gap honest: tested code path, undefined call site.
189
+ // @cap-risk(F-070/AC-1) The editAfterWrite half of AC-1 is fully wired across subprocess boundaries
190
+ // via the per-session ledger (cap-learning-signals#recordWriteIntoLedger / wasWrittenInSession) and
191
+ // covered by an end-to-end spawnSync test that drives Write→Edit through this hook. The rejectApproval
192
+ // half is collector-tested but has NO hook call site (D10).
@@ -0,0 +1,299 @@
1
+ #!/usr/bin/env node
2
+ // @cap-feature(feature:F-030) Memory Automation Hook — post-session hook that triggers memory accumulation pipeline
3
+ // @cap-history(sessions:2, edits:5, since:2026-05-06, learned:2026-05-08) Frequently modified — 2 sessions, 5 edits
4
+ // @cap-history(sessions:3, edits:9, since:2026-04-03, learned:2026-04-04) Frequently modified — 3 sessions, 9 edits
5
+ // cap-hook-version: {{CAP_VERSION}}
6
+ // Memory Hook - runs after session end to accumulate project memory.
7
+ //
8
+ // Pipeline: F-027 (Engine) → F-028 (Annotation Writer) → F-029 (Memory Directory)
9
+ //
10
+ // Two modes:
11
+ // Incremental (default): Only processes sessions newer than .cap/memory/.last-run
12
+ // Init (via /cap:memory init): Processes ALL sessions, builds initial memory
13
+ //
14
+ // Skip with CAP_SKIP_MEMORY=1 environment variable.
15
+
16
+ const fs = require('fs');
17
+ const path = require('path');
18
+ const os = require('os');
19
+
20
+ // @cap-todo(ref:F-030:AC-8) Hook skippable via CAP_SKIP_MEMORY=1
21
+ if (process.env.CAP_SKIP_MEMORY === '1') {
22
+ process.exit(0);
23
+ }
24
+
25
+ // Resolve installed module paths
26
+ const homeDir = os.homedir();
27
+ const capLib = path.join(homeDir, '.claude', 'cap', 'bin', 'lib');
28
+
29
+ function tryRequire(modulePath) {
30
+ try { return require(modulePath); } catch { return null; }
31
+ }
32
+
33
+ const LAST_RUN_FILE = '.last-run';
34
+
35
+ /**
36
+ * Read the last-run timestamp from .cap/memory/.last-run
37
+ * @param {string} cwd
38
+ * @returns {string|null} ISO timestamp or null if never run
39
+ */
40
+ function readLastRun(cwd) {
41
+ const fp = path.join(cwd, '.cap', 'memory', LAST_RUN_FILE);
42
+ try {
43
+ return fs.readFileSync(fp, 'utf8').trim() || null;
44
+ } catch { return null; }
45
+ }
46
+
47
+ /**
48
+ * Write the current timestamp to .cap/memory/.last-run
49
+ * @param {string} cwd
50
+ */
51
+ function writeLastRun(cwd) {
52
+ const dir = path.join(cwd, '.cap', 'memory');
53
+ if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true });
54
+ fs.writeFileSync(path.join(dir, LAST_RUN_FILE), new Date().toISOString(), 'utf8');
55
+ }
56
+
57
+ /**
58
+ * Filter session files to only those newer than a timestamp.
59
+ * @param {Array<{path: string, date: string|null}>} files
60
+ * @param {string|null} since - ISO timestamp
61
+ * @returns {Array}
62
+ */
63
+ function filterNewSessions(files, since) {
64
+ if (!since) return files; // No last-run = process all
65
+ return files.filter(f => f.date && f.date > since);
66
+ }
67
+
68
+ // @cap-todo(ref:F-030:AC-1) Post-session hook triggers F-027→F-028→F-029 pipeline
69
+ // @cap-todo(ref:F-030:AC-7) Hook completes within 5 seconds for up to 50 session files
70
+
71
+ /**
72
+ * Run the memory pipeline.
73
+ * @param {Object} [options]
74
+ * @param {boolean} [options.init] - If true, process ALL sessions (bootstrap mode)
75
+ */
76
+ function run(options = {}) {
77
+ const startTime = Date.now();
78
+ const cwd = process.cwd();
79
+
80
+ // Load modules
81
+ const extract = tryRequire(path.join(capLib, 'cap-session-extract.cjs'));
82
+ const engine = tryRequire(path.join(capLib, 'cap-memory-engine.cjs'));
83
+ const writer = tryRequire(path.join(capLib, 'cap-annotation-writer.cjs'));
84
+ const memDir = tryRequire(path.join(capLib, 'cap-memory-dir.cjs'));
85
+
86
+ if (!extract || !engine || !writer || !memDir) {
87
+ // Modules not installed — skip silently
88
+ return;
89
+ }
90
+
91
+ // Find project sessions — monorepo-aware for init, single-project for incremental
92
+ let allSessionFiles;
93
+ let projectInfo;
94
+
95
+ if (options.init && extract.getAllSessionFiles) {
96
+ // Init mode: scan all sub-project sessions (monorepo-aware)
97
+ const result = extract.getAllSessionFiles(cwd);
98
+ allSessionFiles = result.files;
99
+ projectInfo = result.projects;
100
+ } else {
101
+ // Incremental mode: single project only (fast)
102
+ const projectDir = extract.getProjectDir(cwd);
103
+ if (!projectDir) return;
104
+ allSessionFiles = extract.getSessionFiles(projectDir);
105
+ projectInfo = null;
106
+ }
107
+
108
+ if (allSessionFiles.length === 0) return;
109
+
110
+ // Incremental: only process sessions since last run (unless init mode)
111
+ const lastRun = options.init ? null : readLastRun(cwd);
112
+ const sessionFiles = filterNewSessions(allSessionFiles, lastRun);
113
+
114
+ if (sessionFiles.length === 0) return; // Nothing new
115
+
116
+ // Detect debug sessions from SESSION.json
117
+ let activeDebug = false;
118
+ try {
119
+ const sessionPath = path.join(cwd, '.cap', 'SESSION.json');
120
+ if (fs.existsSync(sessionPath)) {
121
+ const session = JSON.parse(fs.readFileSync(sessionPath, 'utf8'));
122
+ activeDebug = session.step === 'debug' || session.activeDebugSession != null;
123
+ }
124
+ } catch { /* ignore */ }
125
+
126
+ // --- Primary source: Code tags (single source of truth) ---
127
+ const scanner = tryRequire(path.join(capLib, 'cap-tag-scanner.cjs'));
128
+ let codeEntries = [];
129
+ if (scanner) {
130
+ const tags = scanner.scanDirectory(cwd, { projectRoot: cwd });
131
+ codeEntries = engine.accumulateFromCode(tags);
132
+ }
133
+
134
+ // --- Secondary source: Sessions (hotspots only — edit frequency) ---
135
+ // @cap-decision Hotspots are inherently cumulative (a "hotspot" requires >= minHotspot sessions
136
+ // of edits to a single file). In incremental mode the new-session window is too
137
+ // narrow — usually 1–2 sessions — so naive filtering produces zero hotspots, and
138
+ // writeMemoryDirectory then overwrites hotspots.md with an empty stub. Real-world
139
+ // evidence: GoetzeInvest had 61 hotspot nodes in graph.json from a prior init run,
140
+ // but hotspots.md was a 202-byte "_No hotspots recorded yet._" header after every
141
+ // incremental run. Compute hotspots from ALL sessions every run; the
142
+ // filterNewSessions short-circuit above (early-return when sessionFiles is empty)
143
+ // still gates whether we run at all, so we don't burn IO on no-op invocations.
144
+ const filesToProcess = allSessionFiles.map(f => ({
145
+ path: f.path,
146
+ isDebugSession: activeDebug,
147
+ }));
148
+
149
+ const sessionResult = engine.accumulateFromFiles(filesToProcess, { projectRoot: cwd });
150
+
151
+ // Merge: code-based decisions/pitfalls + session-based hotspots (full-history)
152
+ const allEntries = [...codeEntries, ...sessionResult.newEntries];
153
+
154
+ if (allEntries.length === 0 && sessionResult.staleEntries.length === 0) {
155
+ writeLastRun(cwd);
156
+ return;
157
+ }
158
+
159
+ // F-028: Write hotspot annotations into source files (only annotatable source code)
160
+ // Code-based decisions/pitfalls are ALREADY in the code as @cap-decision/@cap-todo — no need to re-annotate.
161
+ const NON_ANNOTATABLE_EXT = new Set(['.md', '.markdown', '.json', '.jsonl', '.lock', '.svg', '.xml', '.html', '.css', '.scss']);
162
+ const fileEntries = {};
163
+ for (const entry of allEntries) {
164
+ if (entry.category !== 'hotspot') continue; // Only write hotspot annotations
165
+ if (entry.file && fs.existsSync(entry.file)) {
166
+ const ext = path.extname(entry.file).toLowerCase();
167
+ if (NON_ANNOTATABLE_EXT.has(ext)) continue;
168
+ if (!fileEntries[entry.file]) fileEntries[entry.file] = [];
169
+ fileEntries[entry.file].push(entry);
170
+ }
171
+ }
172
+
173
+ if (Object.keys(fileEntries).length > 0) {
174
+ writer.writeAnnotations(fileEntries);
175
+ }
176
+
177
+ // F-028: Remove stale annotations
178
+ if (sessionResult.staleEntries.length > 0) {
179
+ writer.removeStaleAnnotations(sessionResult.staleEntries);
180
+ }
181
+
182
+ // F-029: Write memory directory (merge mode for multi-developer support)
183
+ // @cap-feature(feature:F-090, primary:true) Apply confidence-filter at the hook layer:
184
+ // only entries with confidence >= 0.6 OR pinned land in the .md output. graph.json is
185
+ // built independently and stays full (Cluster/Affinity components need every node).
186
+ // Real-world driver: GoetzeInvest hub had 568 KB / 2340 entries in decisions.md, ~95%
187
+ // Confidence:0.50/Evidence:1 heuristic noise. Filter brings agent session-start cost
188
+ // from ~150k tokens to ~5–15k tokens for typical projects.
189
+ // @cap-decision(F-090) Threshold lives in the hook (not in writeMemoryDirectory) so direct
190
+ // callers (tests, CLI tools, library consumers) keep backwards-compat behavior.
191
+ memDir.writeMemoryDirectory(cwd, allEntries, { merge: !options.init, minConfidence: 0.6 });
192
+
193
+ // @cap-decision(F-079/iter1) Stage-2 #1 fix: processSnapshots wired into memory-pipeline.
194
+ // Closes AC-4 — "Memory-Pipeline MUSS Snapshots ... referenzieren" — by invoking
195
+ // processSnapshots() after writeMemoryDirectory so per-feature/platform files get a
196
+ // populated linked_snapshots block on every pipeline run. Idempotent: byte-identical on
197
+ // re-run because processSnapshots groups by target and writes ONE upsert per target with
198
+ // the FULL set. Wrapped in try/catch so a snapshot-linkage failure never blocks memory.
199
+ // @cap-decision(F-079/followup) F-079-FIX-C: hook surfaces processSnapshots skip-count.
200
+ // Previously the hook ignored the `skipped` field from the result. If a snapshot fails
201
+ // to be linked (parse-error, malformed frontmatter), it silently disappeared from the
202
+ // diagnostic surface. Now we summarize the skipped names + reasons via the project's
203
+ // debug logger (CAP_DEBUG=1 surfaces it; default path stays silent to honor the
204
+ // "best-effort, never block" contract). Surfaced as a single line for grep-friendliness.
205
+ const linkage = tryRequire(path.join(capLib, 'cap-snapshot-linkage.cjs'));
206
+ if (linkage && linkage.processSnapshots) {
207
+ try {
208
+ const result = linkage.processSnapshots(cwd, {});
209
+ if (result && Array.isArray(result.skipped) && result.skipped.length > 0) {
210
+ const logger = tryRequire(path.join(capLib, 'cap-logger.cjs'));
211
+ if (logger && typeof logger.debug === 'function') {
212
+ const summary = result.skipped.map((s) => {
213
+ const name = (s && typeof s.name === 'string') ? s.name : '<unknown>';
214
+ const reason = (s && typeof s.reason === 'string') ? s.reason : '<no-reason>';
215
+ return `${name}=${reason}`;
216
+ }).join(',');
217
+ logger.debug({
218
+ op: 'cap-memory-hook.processSnapshots',
219
+ errorType: 'snapshot-skipped',
220
+ errorMessage: `processSnapshots skipped ${result.skipped.length} snapshot(s): ${summary}`,
221
+ recoveryAction: 'continued pipeline; per-feature/platform writes succeeded for non-skipped snapshots',
222
+ });
223
+ } else if (process.env.CAP_DEBUG) {
224
+ // Fallback when logger isn't available (defense-in-depth — CAP_DEBUG users still see something).
225
+ try {
226
+ process.stderr.write(`[cap:debug] processSnapshots skipped ${result.skipped.length} snapshot(s)\n`);
227
+ } catch (_eDbg) { /* ignore */ }
228
+ }
229
+ }
230
+ } catch (_e) {
231
+ // Snapshot linkage is best-effort; never block the rest of the pipeline.
232
+ }
233
+ }
234
+
235
+ // F-034: Update memory graph
236
+ const memGraph = tryRequire(path.join(capLib, 'cap-memory-graph.cjs'));
237
+ if (memGraph) {
238
+ try {
239
+ if (options.init) {
240
+ // Full rebuild from all sources
241
+ const graph = memGraph.buildFromMemory(cwd);
242
+ memGraph.saveGraph(cwd, graph);
243
+ } else {
244
+ // Incremental update with new entries
245
+ const graph = memGraph.loadGraph(cwd);
246
+ const staleNodeIds = sessionResult.staleEntries.map(
247
+ e => memGraph.generateNodeId(e.category, e.content)
248
+ );
249
+ memGraph.incrementalUpdate(graph, allEntries, { staleNodeIds });
250
+ memGraph.saveGraph(cwd, graph);
251
+ }
252
+ } catch (_e) {
253
+ // Graph update is non-critical — don't block session end
254
+ }
255
+ }
256
+
257
+ // Save last-run timestamp
258
+ writeLastRun(cwd);
259
+
260
+ // Stats for reporting
261
+ const stats = {
262
+ decisions: allEntries.filter(e => e.category === 'decision').length,
263
+ pitfalls: allEntries.filter(e => e.category === 'pitfall').length,
264
+ patterns: allEntries.filter(e => e.category === 'pattern').length,
265
+ hotspots: allEntries.filter(e => e.category === 'hotspot').length,
266
+ fromCode: codeEntries.length,
267
+ fromSessions: sessionResult.newEntries.length,
268
+ };
269
+
270
+ // Performance check
271
+ const elapsed = Date.now() - startTime;
272
+ if (elapsed > 5000 && !options.init) {
273
+ process.stderr.write(`cap-memory: warning — hook took ${elapsed}ms (target: <5000ms)\n`);
274
+ }
275
+
276
+ // Report in init mode
277
+ if (options.init) {
278
+ const elapsed2 = Date.now() - startTime;
279
+ if (projectInfo && projectInfo.length > 1) {
280
+ process.stdout.write(`cap-memory init: monorepo mode — ${projectInfo.length} sub-projects found\n`);
281
+ for (const p of projectInfo) {
282
+ process.stdout.write(` ${p}\n`);
283
+ }
284
+ }
285
+ process.stdout.write(`cap-memory init: ${sessionFiles.length} sessions, ${stats.fromCode} code tags processed in ${elapsed2}ms\n`);
286
+ process.stdout.write(` decisions: ${stats.decisions} (from code), pitfalls: ${stats.pitfalls} (from code), hotspots: ${stats.hotspots} (from sessions)\n`);
287
+ }
288
+ }
289
+
290
+ // CLI mode: support "init" argument for bootstrap
291
+ const args = process.argv.slice(2);
292
+ const isInit = args.includes('init') || args.includes('--init');
293
+
294
+ try {
295
+ run({ init: isInit });
296
+ } catch (err) {
297
+ // Never block session end — fail silently
298
+ process.stderr.write(`cap-memory: ${err.message}\n`);
299
+ }