squish-memory 1.0.1 → 1.1.5

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 (601) hide show
  1. package/.env.example +130 -0
  2. package/CHANGELOG.md +55 -0
  3. package/README.md +155 -274
  4. package/config/hooks/claude-code-hooks.json +39 -0
  5. package/config/hooks/cursor-hooks.json +30 -0
  6. package/config/hooks/opencode-hooks.json +30 -0
  7. package/config/hooks/windsurf-hooks.json +30 -0
  8. package/config/mcp-mode-semantics.json +23 -21
  9. package/config/plugin-manifest.json +101 -152
  10. package/{plugin.json → config/plugin.json} +2 -2
  11. package/config/settings.json +52 -51
  12. package/{commands → core/commands}/init.md +39 -39
  13. package/dist/config.d.ts +28 -4
  14. package/dist/config.js +97 -29
  15. package/dist/core/adapters/config/claude-code.d.ts +45 -0
  16. package/dist/core/adapters/config/claude-code.js +113 -0
  17. package/dist/core/adapters/config/cursor.d.ts +26 -0
  18. package/dist/core/adapters/config/cursor.js +74 -0
  19. package/dist/core/adapters/config/opencode.d.ts +23 -0
  20. package/dist/core/adapters/config/opencode.js +73 -0
  21. package/dist/core/adapters/config/windsurf.d.ts +26 -0
  22. package/dist/core/adapters/config/windsurf.js +74 -0
  23. package/dist/core/adapters/index.d.ts +45 -0
  24. package/dist/core/adapters/index.js +84 -0
  25. package/dist/core/adapters/scripts/install-adapter.d.ts +19 -0
  26. package/dist/core/adapters/scripts/install-adapter.js +149 -0
  27. package/dist/core/adapters/timeline.d.ts +23 -0
  28. package/dist/core/adapters/timeline.js +88 -0
  29. package/dist/core/adapters/types.d.ts +157 -0
  30. package/dist/core/adapters/types.js +50 -0
  31. package/dist/{algorithms → core/algorithms}/analytics/token-estimator.d.ts +1 -1
  32. package/dist/{algorithms → core/algorithms}/analytics/token-estimator.js +3 -3
  33. package/dist/{algorithms → core/algorithms}/detection/semantic-ranker.d.ts +1 -1
  34. package/dist/{algorithms → core/algorithms}/detection/semantic-ranker.js +1 -1
  35. package/dist/{algorithms → core/algorithms}/detection/two-stage-detector.d.ts +1 -1
  36. package/dist/{algorithms → core/algorithms}/detection/two-stage-detector.js +7 -10
  37. package/dist/{algorithms → core/algorithms}/handlers/approve-merge.js +4 -4
  38. package/dist/{algorithms → core/algorithms}/handlers/detect-duplicates.js +3 -3
  39. package/dist/{algorithms → core/algorithms}/handlers/get-stats.js +3 -3
  40. package/dist/{algorithms → core/algorithms}/handlers/list-proposals.js +3 -3
  41. package/dist/{algorithms → core/algorithms}/handlers/preview-merge.js +3 -3
  42. package/dist/{algorithms → core/algorithms}/handlers/reject-merge.js +3 -3
  43. package/dist/{algorithms → core/algorithms}/handlers/reverse-merge.js +3 -3
  44. package/dist/core/algorithms/index.d.ts +21 -0
  45. package/dist/core/algorithms/index.js +26 -0
  46. package/dist/core/algorithms/operations/cache-maintenance.d.ts +12 -0
  47. package/dist/core/algorithms/operations/cache-maintenance.js +157 -0
  48. package/dist/{algorithms → core/algorithms}/safety/safety-checks.d.ts +1 -1
  49. package/dist/{algorithms → core/algorithms}/strategies/merge-strategies.d.ts +19 -1
  50. package/dist/{algorithms → core/algorithms}/strategies/merge-strategies.js +74 -123
  51. package/dist/core/algorithms/types.d.ts +133 -0
  52. package/dist/core/algorithms/types.js +5 -0
  53. package/dist/core/associations.d.ts +1 -2
  54. package/dist/core/associations.js +1 -2
  55. package/dist/core/autosave.d.ts +19 -0
  56. package/dist/core/autosave.js +16 -0
  57. package/dist/{commands → core/commands}/managed-sync.js +5 -5
  58. package/dist/core/commands/mcp-server.js +739 -0
  59. package/dist/core/context/agent-context.d.ts +106 -0
  60. package/dist/core/context/agent-context.js +274 -0
  61. package/dist/core/{context-paging.d.ts → context/context-paging.d.ts} +2 -12
  62. package/dist/core/{context-paging.js → context/context-paging.js} +19 -39
  63. package/dist/core/context/context-window.d.ts +40 -0
  64. package/dist/core/context/context-window.js +177 -0
  65. package/dist/core/context/context.js +22 -0
  66. package/dist/core/embeddings.d.ts +1 -1
  67. package/dist/core/embeddings.js +54 -2
  68. package/dist/core/error-handling.d.ts +63 -0
  69. package/dist/core/error-handling.js +173 -0
  70. package/dist/core/external-folder/index.d.ts +102 -0
  71. package/dist/core/external-folder/index.js +294 -0
  72. package/dist/core/hooks/agent-hooks.d.ts +74 -0
  73. package/dist/core/hooks/agent-hooks.js +244 -0
  74. package/dist/core/hooks/auto-tagger.d.ts +19 -0
  75. package/dist/core/hooks/auto-tagger.js +155 -0
  76. package/dist/core/hooks/capture-filter.d.ts +41 -0
  77. package/dist/core/hooks/capture-filter.js +128 -0
  78. package/dist/core/index.d.ts +6 -6
  79. package/dist/core/index.js +6 -6
  80. package/dist/core/{agent-memory.js → ingestion/agent-memory.js} +5 -7
  81. package/dist/core/{core-memory.js → ingestion/core-memory.js} +4 -4
  82. package/dist/core/ingestion/learnings.d.ts +57 -0
  83. package/dist/core/ingestion/learnings.js +202 -0
  84. package/dist/core/lib/db-client.d.ts +114 -0
  85. package/dist/core/lib/db-client.js +130 -0
  86. package/dist/core/lib/schemas.d.ts +129 -0
  87. package/dist/core/lib/schemas.js +87 -0
  88. package/dist/core/{utils.d.ts → lib/utils.d.ts} +1 -0
  89. package/dist/core/{utils.js → lib/utils.js} +31 -15
  90. package/dist/core/lib/validation.d.ts +38 -0
  91. package/dist/core/lib/validation.js +151 -0
  92. package/dist/core/lifecycle.d.ts +7 -0
  93. package/dist/core/lifecycle.js +140 -20
  94. package/dist/core/local-embeddings.d.ts +6 -1
  95. package/dist/core/local-embeddings.js +6 -15
  96. package/dist/core/logger.js +7 -1
  97. package/dist/core/mcp/server.js +27 -1
  98. package/dist/core/mcp/tools.js +35 -3
  99. package/dist/core/mcp/types.d.ts +4 -4
  100. package/dist/core/memory/categorizer.js +1 -0
  101. package/dist/core/memory/conflict-detector.js +1 -1
  102. package/dist/core/memory/consolidation.d.ts +1 -10
  103. package/dist/core/memory/consolidation.js +2 -11
  104. package/dist/core/memory/context-collector.js +1 -1
  105. package/dist/core/memory/edit-workflow.js +1 -1
  106. package/dist/core/memory/entity-resolver.js +7 -7
  107. package/dist/core/memory/fact-extractor.js +12 -12
  108. package/dist/core/memory/feedback-tracker.js +1 -1
  109. package/dist/core/memory/hooks.d.ts +88 -0
  110. package/dist/core/memory/hooks.js +174 -0
  111. package/dist/core/memory/hybrid-retrieval.js +2 -2
  112. package/dist/core/memory/hybrid-search.d.ts +1 -6
  113. package/dist/core/memory/hybrid-search.js +70 -84
  114. package/dist/core/memory/importance.d.ts +8 -13
  115. package/dist/core/memory/importance.js +47 -74
  116. package/dist/core/memory/loader.d.ts +31 -0
  117. package/dist/core/memory/loader.js +141 -0
  118. package/dist/core/memory/markdown/markdown-storage.d.ts +72 -0
  119. package/dist/core/memory/markdown/markdown-storage.js +243 -0
  120. package/dist/core/memory/memories.d.ts +12 -4
  121. package/dist/core/memory/memories.js +196 -181
  122. package/dist/core/memory/memory-lifecycle.d.ts +8 -0
  123. package/dist/core/memory/memory-lifecycle.js +55 -0
  124. package/dist/core/memory/migrate.d.ts +21 -0
  125. package/dist/core/memory/migrate.js +134 -0
  126. package/dist/core/memory/normalization.d.ts +22 -0
  127. package/dist/core/memory/normalization.js +26 -0
  128. package/dist/core/memory/progressive-disclosure.js +1 -1
  129. package/dist/core/memory/query-rewriter.js +9 -9
  130. package/dist/core/memory/serialization.d.ts +4 -0
  131. package/dist/core/memory/serialization.js +49 -0
  132. package/dist/core/memory/stats.d.ts +5 -0
  133. package/dist/core/memory/stats.js +63 -12
  134. package/dist/core/memory/temporal-facts.js +21 -0
  135. package/dist/core/memory/write-gate.js +1 -1
  136. package/dist/core/obsidian-vault.d.ts +30 -0
  137. package/dist/core/obsidian-vault.js +94 -0
  138. package/dist/core/places/index.d.ts +14 -0
  139. package/dist/core/places/index.js +14 -0
  140. package/dist/core/places/memory-places.d.ts +68 -0
  141. package/dist/core/places/memory-places.js +261 -0
  142. package/dist/core/places/places.d.ts +88 -0
  143. package/dist/core/places/places.js +314 -0
  144. package/dist/core/places/rules.d.ts +74 -0
  145. package/dist/core/places/rules.js +240 -0
  146. package/dist/core/places/walking.d.ts +56 -0
  147. package/dist/core/places/walking.js +121 -0
  148. package/dist/core/projects.d.ts +5 -0
  149. package/dist/core/projects.js +39 -18
  150. package/dist/core/responses.d.ts +96 -0
  151. package/dist/core/responses.js +122 -0
  152. package/dist/core/scheduler/cron-scheduler.js +89 -14
  153. package/dist/core/scheduler/index.d.ts +1 -1
  154. package/dist/core/scheduler/index.js +1 -1
  155. package/dist/core/scheduler/job-runner.js +1 -1
  156. package/dist/core/search/conversations.js +40 -42
  157. package/dist/core/search/entities.js +6 -9
  158. package/dist/core/search/graph-boost.d.ts +7 -0
  159. package/dist/core/search/graph-boost.js +23 -0
  160. package/dist/core/search/qmd-search.js +4 -4
  161. package/dist/core/security/encrypt.d.ts +6 -0
  162. package/dist/core/security/encrypt.js +47 -0
  163. package/dist/core/{governance.d.ts → security/governance.d.ts} +6 -1
  164. package/dist/core/security/governance.js +79 -0
  165. package/dist/core/session/auto-load.js +6 -6
  166. package/dist/core/session/index.d.ts +1 -1
  167. package/dist/core/session/index.js +1 -1
  168. package/dist/core/session/self-iteration-job.d.ts +20 -0
  169. package/dist/core/session/self-iteration-job.js +282 -0
  170. package/dist/core/session/session-hooks.d.ts +18 -0
  171. package/dist/core/session/session-hooks.js +58 -0
  172. package/dist/core/session-hooks/self-iteration-job.js +35 -35
  173. package/dist/core/{cache.js → storage/cache.js} +2 -2
  174. package/dist/core/sync/qmd-sync.d.ts +1 -13
  175. package/dist/core/sync/qmd-sync.js +1 -13
  176. package/dist/core/toon.d.ts +43 -0
  177. package/dist/core/toon.js +160 -0
  178. package/dist/core/utils/memory-operations.js +1 -1
  179. package/dist/core/utils/vector-operations.d.ts +71 -0
  180. package/dist/core/utils/vector-operations.js +129 -0
  181. package/dist/db/adapter.d.ts +3 -3
  182. package/dist/db/adapter.js +115 -36
  183. package/dist/db/bootstrap.js +927 -555
  184. package/dist/{drizzle → db/drizzle}/schema-sqlite.d.ts +74 -25
  185. package/dist/{drizzle → db/drizzle}/schema-sqlite.js +91 -24
  186. package/dist/{drizzle → db/drizzle}/schema.d.ts +79 -32
  187. package/dist/{drizzle → db/drizzle}/schema.js +106 -35
  188. package/dist/db/drizzle.config.d.ts +3 -0
  189. package/dist/db/drizzle.config.js +12 -0
  190. package/dist/db/index.d.ts +1 -5
  191. package/dist/db/index.js +51 -8
  192. package/dist/db/neon.d.ts +8 -0
  193. package/dist/db/neon.js +20 -0
  194. package/dist/db/schema/index.d.ts +40 -0
  195. package/dist/db/schema/index.js +105 -0
  196. package/dist/db/schema/tables/context-sessions.d.ts +9 -0
  197. package/dist/db/schema/tables/context-sessions.js +37 -0
  198. package/dist/db/schema/tables/conversations.d.ts +9 -0
  199. package/dist/db/schema/tables/conversations.js +47 -0
  200. package/dist/db/schema/tables/core-memory.d.ts +9 -0
  201. package/dist/db/schema/tables/core-memory.js +41 -0
  202. package/dist/db/schema/tables/entities.d.ts +9 -0
  203. package/dist/db/schema/tables/entities.js +39 -0
  204. package/dist/db/schema/tables/entity-relations.d.ts +9 -0
  205. package/dist/db/schema/tables/entity-relations.js +31 -0
  206. package/dist/db/schema/tables/learnings.d.ts +9 -0
  207. package/dist/db/schema/tables/learnings.js +66 -0
  208. package/dist/db/schema/tables/memories.d.ts +9 -0
  209. package/dist/db/schema/tables/memories.js +161 -0
  210. package/dist/db/schema/tables/memory-associations.d.ts +9 -0
  211. package/dist/db/schema/tables/memory-associations.js +39 -0
  212. package/dist/db/schema/tables/memory-hash-cache.d.ts +9 -0
  213. package/dist/db/schema/tables/memory-hash-cache.js +29 -0
  214. package/dist/db/schema/tables/memory-merge-history.d.ts +9 -0
  215. package/dist/db/schema/tables/memory-merge-history.js +33 -0
  216. package/dist/db/schema/tables/memory-merge-proposals.d.ts +9 -0
  217. package/dist/db/schema/tables/memory-merge-proposals.js +39 -0
  218. package/dist/db/schema/tables/messages.d.ts +9 -0
  219. package/dist/db/schema/tables/messages.js +41 -0
  220. package/dist/db/schema/tables/namespaces.d.ts +9 -0
  221. package/dist/db/schema/tables/namespaces.js +37 -0
  222. package/dist/db/schema/tables/projects.d.ts +9 -0
  223. package/dist/db/schema/tables/projects.js +31 -0
  224. package/dist/db/schema/tables/users.d.ts +9 -0
  225. package/dist/db/schema/tables/users.js +27 -0
  226. package/dist/db/schema.d.ts +1 -1
  227. package/dist/db/schema.js +2 -2
  228. package/dist/db/supabase.d.ts +9 -0
  229. package/dist/db/supabase.js +24 -0
  230. package/dist/index.d.ts +2 -14
  231. package/dist/index.js +1457 -631
  232. package/dist/vendor/sql.js/sql-wasm.wasm +0 -0
  233. package/dist/webui/server.d.ts +5 -0
  234. package/dist/{api/web/web.js → webui/server.js} +538 -509
  235. package/generated/mcp/manifest.json +23 -23
  236. package/{.mcp.json → mcp.json.example} +1 -1
  237. package/package.json +159 -169
  238. package/scripts/README.md +60 -0
  239. package/scripts/copy-runtime-assets.mjs +26 -0
  240. package/scripts/generate-mcp.mjs +264 -264
  241. package/scripts/github-release.sh +4 -4
  242. package/scripts/install-claude-code.sh +85 -0
  243. package/scripts/install-cursor.sh +56 -0
  244. package/scripts/install-hooks.sh +73 -0
  245. package/scripts/install-interactive.mjs +357 -674
  246. package/scripts/install-opencode.sh +75 -0
  247. package/scripts/install-windsurf.sh +67 -0
  248. package/skills/squish-memory/SKILL.md +104 -100
  249. package/skills/squish-memory/{install.mjs → scripts/install.mjs} +2 -2
  250. package/skills/squish-memory/{install.sh → scripts/install.sh} +2 -2
  251. package/skills/squish-memory/write_skill.js +2 -0
  252. package/.claude-plugin/marketplace.json +0 -20
  253. package/.claude-plugin/plugin.json +0 -32
  254. package/.env.mcp.example +0 -56
  255. package/QUICK-START.md +0 -71
  256. package/bin/squish-add.mjs +0 -32
  257. package/bin/squish-rm.mjs +0 -21
  258. package/commands/observe.md +0 -5
  259. package/dist/algorithms/analytics/token-estimator.d.ts.map +0 -1
  260. package/dist/algorithms/analytics/token-estimator.js.map +0 -1
  261. package/dist/algorithms/detection/hash-filters.d.ts.map +0 -1
  262. package/dist/algorithms/detection/hash-filters.js.map +0 -1
  263. package/dist/algorithms/detection/semantic-ranker.d.ts.map +0 -1
  264. package/dist/algorithms/detection/semantic-ranker.js.map +0 -1
  265. package/dist/algorithms/detection/two-stage-detector.d.ts.map +0 -1
  266. package/dist/algorithms/detection/two-stage-detector.js.map +0 -1
  267. package/dist/algorithms/handlers/approve-merge.d.ts.map +0 -1
  268. package/dist/algorithms/handlers/approve-merge.js.map +0 -1
  269. package/dist/algorithms/handlers/detect-duplicates.d.ts.map +0 -1
  270. package/dist/algorithms/handlers/detect-duplicates.js.map +0 -1
  271. package/dist/algorithms/handlers/get-stats.d.ts.map +0 -1
  272. package/dist/algorithms/handlers/get-stats.js.map +0 -1
  273. package/dist/algorithms/handlers/list-proposals.d.ts.map +0 -1
  274. package/dist/algorithms/handlers/list-proposals.js.map +0 -1
  275. package/dist/algorithms/handlers/preview-merge.d.ts.map +0 -1
  276. package/dist/algorithms/handlers/preview-merge.js.map +0 -1
  277. package/dist/algorithms/handlers/reject-merge.d.ts.map +0 -1
  278. package/dist/algorithms/handlers/reject-merge.js.map +0 -1
  279. package/dist/algorithms/handlers/reverse-merge.d.ts.map +0 -1
  280. package/dist/algorithms/handlers/reverse-merge.js.map +0 -1
  281. package/dist/algorithms/safety/safety-checks.d.ts.map +0 -1
  282. package/dist/algorithms/safety/safety-checks.js.map +0 -1
  283. package/dist/algorithms/strategies/merge-strategies.d.ts.map +0 -1
  284. package/dist/algorithms/strategies/merge-strategies.js.map +0 -1
  285. package/dist/algorithms/utils/response-builder.d.ts.map +0 -1
  286. package/dist/algorithms/utils/response-builder.js.map +0 -1
  287. package/dist/api/web/index.d.ts +0 -3
  288. package/dist/api/web/index.d.ts.map +0 -1
  289. package/dist/api/web/index.js +0 -4
  290. package/dist/api/web/index.js.map +0 -1
  291. package/dist/api/web/web-server.d.ts +0 -3
  292. package/dist/api/web/web-server.d.ts.map +0 -1
  293. package/dist/api/web/web-server.js +0 -6
  294. package/dist/api/web/web-server.js.map +0 -1
  295. package/dist/api/web/web.d.ts +0 -4
  296. package/dist/api/web/web.d.ts.map +0 -1
  297. package/dist/api/web/web.js.map +0 -1
  298. package/dist/commands/managed-sync.d.ts.map +0 -1
  299. package/dist/commands/managed-sync.js.map +0 -1
  300. package/dist/commands/mcp-server.d.ts.map +0 -1
  301. package/dist/commands/mcp-server.js +0 -393
  302. package/dist/commands/mcp-server.js.map +0 -1
  303. package/dist/config.d.ts.map +0 -1
  304. package/dist/config.js.map +0 -1
  305. package/dist/core/agent-memory.d.ts.map +0 -1
  306. package/dist/core/agent-memory.js.map +0 -1
  307. package/dist/core/associations.d.ts.map +0 -1
  308. package/dist/core/associations.js.map +0 -1
  309. package/dist/core/cache.d.ts.map +0 -1
  310. package/dist/core/cache.js.map +0 -1
  311. package/dist/core/consolidation.d.ts.map +0 -1
  312. package/dist/core/consolidation.js.map +0 -1
  313. package/dist/core/context-paging.d.ts.map +0 -1
  314. package/dist/core/context-paging.js.map +0 -1
  315. package/dist/core/context.d.ts.map +0 -1
  316. package/dist/core/context.js +0 -24
  317. package/dist/core/context.js.map +0 -1
  318. package/dist/core/core-memory.d.ts.map +0 -1
  319. package/dist/core/core-memory.js.map +0 -1
  320. package/dist/core/database.d.ts.map +0 -1
  321. package/dist/core/database.js.map +0 -1
  322. package/dist/core/embeddings/google-multimodal.d.ts.map +0 -1
  323. package/dist/core/embeddings/google-multimodal.js.map +0 -1
  324. package/dist/core/embeddings/qmd-client.d.ts.map +0 -1
  325. package/dist/core/embeddings/qmd-client.js.map +0 -1
  326. package/dist/core/embeddings.d.ts.map +0 -1
  327. package/dist/core/embeddings.js.map +0 -1
  328. package/dist/core/governance.d.ts.map +0 -1
  329. package/dist/core/governance.js +0 -64
  330. package/dist/core/governance.js.map +0 -1
  331. package/dist/core/index.d.ts.map +0 -1
  332. package/dist/core/index.js.map +0 -1
  333. package/dist/core/layers/generator.d.ts.map +0 -1
  334. package/dist/core/layers/generator.js.map +0 -1
  335. package/dist/core/lifecycle.d.ts.map +0 -1
  336. package/dist/core/lifecycle.js.map +0 -1
  337. package/dist/core/local-embeddings.d.ts.map +0 -1
  338. package/dist/core/local-embeddings.js.map +0 -1
  339. package/dist/core/logger.d.ts.map +0 -1
  340. package/dist/core/logger.js.map +0 -1
  341. package/dist/core/mcp/client.d.ts.map +0 -1
  342. package/dist/core/mcp/client.js.map +0 -1
  343. package/dist/core/mcp/index.d.ts.map +0 -1
  344. package/dist/core/mcp/index.js.map +0 -1
  345. package/dist/core/mcp/server.d.ts.map +0 -1
  346. package/dist/core/mcp/server.js.map +0 -1
  347. package/dist/core/mcp/standalone-server.d.ts.map +0 -1
  348. package/dist/core/mcp/standalone-server.js.map +0 -1
  349. package/dist/core/mcp/tools.d.ts.map +0 -1
  350. package/dist/core/mcp/tools.js.map +0 -1
  351. package/dist/core/mcp/types.d.ts.map +0 -1
  352. package/dist/core/mcp/types.js.map +0 -1
  353. package/dist/core/memory/bridge-discovery.d.ts.map +0 -1
  354. package/dist/core/memory/bridge-discovery.js.map +0 -1
  355. package/dist/core/memory/categorizer.d.ts.map +0 -1
  356. package/dist/core/memory/categorizer.js.map +0 -1
  357. package/dist/core/memory/conflict-detector.d.ts.map +0 -1
  358. package/dist/core/memory/conflict-detector.js.map +0 -1
  359. package/dist/core/memory/consolidation.d.ts.map +0 -1
  360. package/dist/core/memory/consolidation.js.map +0 -1
  361. package/dist/core/memory/context-collector.d.ts.map +0 -1
  362. package/dist/core/memory/context-collector.js.map +0 -1
  363. package/dist/core/memory/contradiction-resolver.d.ts.map +0 -1
  364. package/dist/core/memory/contradiction-resolver.js.map +0 -1
  365. package/dist/core/memory/edit-workflow.d.ts.map +0 -1
  366. package/dist/core/memory/edit-workflow.js.map +0 -1
  367. package/dist/core/memory/entity-extractor.d.ts.map +0 -1
  368. package/dist/core/memory/entity-extractor.js.map +0 -1
  369. package/dist/core/memory/entity-resolver.d.ts.map +0 -1
  370. package/dist/core/memory/entity-resolver.js.map +0 -1
  371. package/dist/core/memory/fact-extractor.d.ts.map +0 -1
  372. package/dist/core/memory/fact-extractor.js.map +0 -1
  373. package/dist/core/memory/feedback-tracker.d.ts.map +0 -1
  374. package/dist/core/memory/feedback-tracker.js.map +0 -1
  375. package/dist/core/memory/hybrid-retrieval.d.ts.map +0 -1
  376. package/dist/core/memory/hybrid-retrieval.js.map +0 -1
  377. package/dist/core/memory/hybrid-scorer.d.ts.map +0 -1
  378. package/dist/core/memory/hybrid-scorer.js.map +0 -1
  379. package/dist/core/memory/hybrid-search.d.ts.map +0 -1
  380. package/dist/core/memory/hybrid-search.js.map +0 -1
  381. package/dist/core/memory/importance.d.ts.map +0 -1
  382. package/dist/core/memory/importance.js.map +0 -1
  383. package/dist/core/memory/index.d.ts.map +0 -1
  384. package/dist/core/memory/index.js.map +0 -1
  385. package/dist/core/memory/memories.d.ts.map +0 -1
  386. package/dist/core/memory/memories.js.map +0 -1
  387. package/dist/core/memory/memory-manager.d.ts.map +0 -1
  388. package/dist/core/memory/memory-manager.js.map +0 -1
  389. package/dist/core/memory/progressive-disclosure.d.ts.map +0 -1
  390. package/dist/core/memory/progressive-disclosure.js.map +0 -1
  391. package/dist/core/memory/query-processor.d.ts.map +0 -1
  392. package/dist/core/memory/query-processor.js.map +0 -1
  393. package/dist/core/memory/query-rewriter.d.ts.map +0 -1
  394. package/dist/core/memory/query-rewriter.js.map +0 -1
  395. package/dist/core/memory/response-analyzer.d.ts.map +0 -1
  396. package/dist/core/memory/response-analyzer.js.map +0 -1
  397. package/dist/core/memory/serialization.d.ts.map +0 -1
  398. package/dist/core/memory/serialization.js.map +0 -1
  399. package/dist/core/memory/stats.d.ts.map +0 -1
  400. package/dist/core/memory/stats.js.map +0 -1
  401. package/dist/core/memory/telemetry.d.ts.map +0 -1
  402. package/dist/core/memory/telemetry.js.map +0 -1
  403. package/dist/core/memory/temporal-facts.d.ts.map +0 -1
  404. package/dist/core/memory/temporal-facts.js.map +0 -1
  405. package/dist/core/memory/temporal-parser.d.ts.map +0 -1
  406. package/dist/core/memory/temporal-parser.js.map +0 -1
  407. package/dist/core/memory/trigger-detector.d.ts.map +0 -1
  408. package/dist/core/memory/trigger-detector.js.map +0 -1
  409. package/dist/core/memory/write-gate.d.ts.map +0 -1
  410. package/dist/core/memory/write-gate.js.map +0 -1
  411. package/dist/core/namespaces/index.d.ts.map +0 -1
  412. package/dist/core/namespaces/index.js.map +0 -1
  413. package/dist/core/namespaces/uri-parser.d.ts.map +0 -1
  414. package/dist/core/namespaces/uri-parser.js.map +0 -1
  415. package/dist/core/observations.d.ts +0 -26
  416. package/dist/core/observations.d.ts.map +0 -1
  417. package/dist/core/observations.js +0 -110
  418. package/dist/core/observations.js.map +0 -1
  419. package/dist/core/privacy.d.ts.map +0 -1
  420. package/dist/core/privacy.js.map +0 -1
  421. package/dist/core/projects.d.ts.map +0 -1
  422. package/dist/core/projects.js.map +0 -1
  423. package/dist/core/redis.d.ts.map +0 -1
  424. package/dist/core/redis.js.map +0 -1
  425. package/dist/core/requirements.d.ts +0 -20
  426. package/dist/core/requirements.d.ts.map +0 -1
  427. package/dist/core/requirements.js +0 -35
  428. package/dist/core/requirements.js.map +0 -1
  429. package/dist/core/scheduler/cron-scheduler.d.ts.map +0 -1
  430. package/dist/core/scheduler/cron-scheduler.js.map +0 -1
  431. package/dist/core/scheduler/heartbeat.d.ts.map +0 -1
  432. package/dist/core/scheduler/heartbeat.js.map +0 -1
  433. package/dist/core/scheduler/index.d.ts.map +0 -1
  434. package/dist/core/scheduler/index.js.map +0 -1
  435. package/dist/core/scheduler/job-runner.d.ts.map +0 -1
  436. package/dist/core/scheduler/job-runner.js.map +0 -1
  437. package/dist/core/search/conversations.d.ts.map +0 -1
  438. package/dist/core/search/conversations.js.map +0 -1
  439. package/dist/core/search/entities.d.ts.map +0 -1
  440. package/dist/core/search/entities.js.map +0 -1
  441. package/dist/core/search/folder-context.d.ts.map +0 -1
  442. package/dist/core/search/folder-context.js.map +0 -1
  443. package/dist/core/search/index.d.ts.map +0 -1
  444. package/dist/core/search/index.js.map +0 -1
  445. package/dist/core/search/qmd-search.d.ts.map +0 -1
  446. package/dist/core/search/qmd-search.js.map +0 -1
  447. package/dist/core/secret-detector.d.ts.map +0 -1
  448. package/dist/core/secret-detector.js.map +0 -1
  449. package/dist/core/session/auto-load.d.ts.map +0 -1
  450. package/dist/core/session/auto-load.js.map +0 -1
  451. package/dist/core/session/index.d.ts.map +0 -1
  452. package/dist/core/session/index.js.map +0 -1
  453. package/dist/core/session/types.d.ts.map +0 -1
  454. package/dist/core/session/types.js.map +0 -1
  455. package/dist/core/session-hooks/self-iteration-job.d.ts.map +0 -1
  456. package/dist/core/session-hooks/self-iteration-job.js.map +0 -1
  457. package/dist/core/session-hooks/session-hooks.d.ts.map +0 -1
  458. package/dist/core/session-hooks/session-hooks.js.map +0 -1
  459. package/dist/core/snapshots/cleanup.d.ts.map +0 -1
  460. package/dist/core/snapshots/cleanup.js.map +0 -1
  461. package/dist/core/snapshots/comparison.d.ts.map +0 -1
  462. package/dist/core/snapshots/comparison.js.map +0 -1
  463. package/dist/core/snapshots/creation.d.ts.map +0 -1
  464. package/dist/core/snapshots/creation.js.map +0 -1
  465. package/dist/core/snapshots/retrieval.d.ts.map +0 -1
  466. package/dist/core/snapshots/retrieval.js.map +0 -1
  467. package/dist/core/snapshots/stats.d.ts.map +0 -1
  468. package/dist/core/snapshots/stats.js.map +0 -1
  469. package/dist/core/snapshots.d.ts.map +0 -1
  470. package/dist/core/snapshots.js.map +0 -1
  471. package/dist/core/summarization/cleanup.d.ts.map +0 -1
  472. package/dist/core/summarization/cleanup.js.map +0 -1
  473. package/dist/core/summarization/queries.d.ts.map +0 -1
  474. package/dist/core/summarization/queries.js.map +0 -1
  475. package/dist/core/summarization/stats.d.ts.map +0 -1
  476. package/dist/core/summarization/stats.js.map +0 -1
  477. package/dist/core/summarization/strategies.d.ts.map +0 -1
  478. package/dist/core/summarization/strategies.js.map +0 -1
  479. package/dist/core/summarization.d.ts.map +0 -1
  480. package/dist/core/summarization.js.map +0 -1
  481. package/dist/core/sync/qmd-sync.d.ts.map +0 -1
  482. package/dist/core/sync/qmd-sync.js.map +0 -1
  483. package/dist/core/temporal-facts.d.ts.map +0 -1
  484. package/dist/core/temporal-facts.js.map +0 -1
  485. package/dist/core/tracing/collector.d.ts.map +0 -1
  486. package/dist/core/tracing/collector.js.map +0 -1
  487. package/dist/core/tracing/visualizer.d.ts.map +0 -1
  488. package/dist/core/tracing/visualizer.js.map +0 -1
  489. package/dist/core/utils/cleanup-operations.d.ts.map +0 -1
  490. package/dist/core/utils/cleanup-operations.js.map +0 -1
  491. package/dist/core/utils/content-extraction.d.ts.map +0 -1
  492. package/dist/core/utils/content-extraction.js.map +0 -1
  493. package/dist/core/utils/filter-builder.d.ts.map +0 -1
  494. package/dist/core/utils/filter-builder.js.map +0 -1
  495. package/dist/core/utils/history-traversal.d.ts.map +0 -1
  496. package/dist/core/utils/history-traversal.js.map +0 -1
  497. package/dist/core/utils/memory-operations.d.ts.map +0 -1
  498. package/dist/core/utils/memory-operations.js.map +0 -1
  499. package/dist/core/utils/query-operations.d.ts.map +0 -1
  500. package/dist/core/utils/query-operations.js.map +0 -1
  501. package/dist/core/utils/summarization-helpers.d.ts.map +0 -1
  502. package/dist/core/utils/summarization-helpers.js.map +0 -1
  503. package/dist/core/utils/temporal-queries.d.ts.map +0 -1
  504. package/dist/core/utils/temporal-queries.js.map +0 -1
  505. package/dist/core/utils/version-management.d.ts.map +0 -1
  506. package/dist/core/utils/version-management.js.map +0 -1
  507. package/dist/core/utils.d.ts.map +0 -1
  508. package/dist/core/utils.js.map +0 -1
  509. package/dist/core/worker.d.ts.map +0 -1
  510. package/dist/core/worker.js.map +0 -1
  511. package/dist/db/adapter.d.ts.map +0 -1
  512. package/dist/db/adapter.js.map +0 -1
  513. package/dist/db/bootstrap.d.ts.map +0 -1
  514. package/dist/db/bootstrap.js.map +0 -1
  515. package/dist/db/index.d.ts.map +0 -1
  516. package/dist/db/index.js.map +0 -1
  517. package/dist/db/schema.d.ts.map +0 -1
  518. package/dist/db/schema.js.map +0 -1
  519. package/dist/drizzle/schema-sqlite.d.ts.map +0 -1
  520. package/dist/drizzle/schema-sqlite.js.map +0 -1
  521. package/dist/drizzle/schema.d.ts.map +0 -1
  522. package/dist/drizzle/schema.js.map +0 -1
  523. package/dist/index.d.ts.map +0 -1
  524. package/dist/index.js.map +0 -1
  525. package/hooks/hooks.json +0 -52
  526. package/hooks/post-tool-use.js +0 -26
  527. package/hooks/session-end.js +0 -28
  528. package/hooks/session-start.js +0 -33
  529. package/hooks/user-prompt-submit.js +0 -26
  530. package/hooks/utils.js +0 -153
  531. package/npx-installer.js +0 -208
  532. package/packages/plugin-claude-code/README.md +0 -73
  533. package/packages/plugin-claude-code/dist/plugin-wrapper.d.ts +0 -35
  534. package/packages/plugin-claude-code/dist/plugin-wrapper.d.ts.map +0 -1
  535. package/packages/plugin-claude-code/dist/plugin-wrapper.js +0 -191
  536. package/packages/plugin-claude-code/dist/plugin-wrapper.js.map +0 -1
  537. package/packages/plugin-claude-code/package.json +0 -31
  538. package/packages/plugin-openclaw/README.md +0 -70
  539. package/packages/plugin-openclaw/dist/index.d.ts +0 -49
  540. package/packages/plugin-openclaw/dist/index.d.ts.map +0 -1
  541. package/packages/plugin-openclaw/dist/index.js +0 -262
  542. package/packages/plugin-openclaw/dist/index.js.map +0 -1
  543. package/packages/plugin-openclaw/openclaw.plugin.json +0 -94
  544. package/packages/plugin-openclaw/package.json +0 -31
  545. package/packages/plugin-opencode/install.mjs +0 -217
  546. package/packages/plugin-opencode/package.json +0 -21
  547. package/scripts/db/check-db.mjs +0 -88
  548. package/scripts/db/fix-all-columns.mjs +0 -52
  549. package/scripts/db/fix-schema-all.mjs +0 -55
  550. package/scripts/db/fix-schema-full.mjs +0 -46
  551. package/scripts/db/fix-schema.mjs +0 -38
  552. package/scripts/db/init-db.mjs +0 -13
  553. package/scripts/db/recreate-db.mjs +0 -14
  554. package/scripts/install-mcp.mjs +0 -116
  555. package/scripts/install-web.sh +0 -120
  556. package/scripts/install.mjs +0 -340
  557. package/scripts/openclaw-bootstrap.mjs +0 -127
  558. package/scripts/package-release.sh +0 -71
  559. package/scripts/test/test-all-systems.mjs +0 -139
  560. package/scripts/test/test-memory-system.mjs +0 -139
  561. package/scripts/test/test-v0.5.0.mjs +0 -210
  562. package/skills/memory-guide/SKILL.md +0 -256
  563. package/skills/squish-cli/SKILL.md +0 -200
  564. package/skills/squish-mcp/SKILL.md +0 -311
  565. package/skills/squish-memory/claude-desktop.json +0 -12
  566. package/skills/squish-memory/openclaw.json +0 -13
  567. package/skills/squish-memory/opencode.json +0 -14
  568. package/skills/squish-memory/skill.json +0 -32
  569. /package/{commands → core/commands}/context-paging.md +0 -0
  570. /package/{commands → core/commands}/context-status.md +0 -0
  571. /package/{commands → core/commands}/context.md +0 -0
  572. /package/{commands → core/commands}/core-memory.md +0 -0
  573. /package/{commands → core/commands}/health.md +0 -0
  574. /package/{commands → core/commands}/merge.md +0 -0
  575. /package/{commands → core/commands}/recall.md +0 -0
  576. /package/{commands → core/commands}/remember.md +0 -0
  577. /package/{commands → core/commands}/search.md +0 -0
  578. /package/dist/{algorithms → core/algorithms}/detection/hash-filters.d.ts +0 -0
  579. /package/dist/{algorithms → core/algorithms}/detection/hash-filters.js +0 -0
  580. /package/dist/{algorithms → core/algorithms}/handlers/approve-merge.d.ts +0 -0
  581. /package/dist/{algorithms → core/algorithms}/handlers/detect-duplicates.d.ts +0 -0
  582. /package/dist/{algorithms → core/algorithms}/handlers/get-stats.d.ts +0 -0
  583. /package/dist/{algorithms → core/algorithms}/handlers/list-proposals.d.ts +0 -0
  584. /package/dist/{algorithms → core/algorithms}/handlers/preview-merge.d.ts +0 -0
  585. /package/dist/{algorithms → core/algorithms}/handlers/reject-merge.d.ts +0 -0
  586. /package/dist/{algorithms → core/algorithms}/handlers/reverse-merge.d.ts +0 -0
  587. /package/dist/{algorithms → core/algorithms}/safety/safety-checks.js +0 -0
  588. /package/dist/{algorithms → core/algorithms}/utils/response-builder.d.ts +0 -0
  589. /package/dist/{algorithms → core/algorithms}/utils/response-builder.js +0 -0
  590. /package/dist/{commands → core/commands}/managed-sync.d.ts +0 -0
  591. /package/dist/{commands → core/commands}/mcp-server.d.ts +0 -0
  592. /package/dist/core/{context.d.ts → context/context.d.ts} +0 -0
  593. /package/dist/core/{agent-memory.d.ts → ingestion/agent-memory.d.ts} +0 -0
  594. /package/dist/core/{core-memory.d.ts → ingestion/core-memory.d.ts} +0 -0
  595. /package/dist/core/{privacy.d.ts → security/privacy.d.ts} +0 -0
  596. /package/dist/core/{privacy.js → security/privacy.js} +0 -0
  597. /package/dist/core/{secret-detector.d.ts → security/secret-detector.d.ts} +0 -0
  598. /package/dist/core/{secret-detector.js → security/secret-detector.js} +0 -0
  599. /package/dist/core/{cache.d.ts → storage/cache.d.ts} +0 -0
  600. /package/dist/core/{database.d.ts → storage/database.d.ts} +0 -0
  601. /package/dist/core/{database.js → storage/database.js} +0 -0
package/dist/index.js CHANGED
@@ -1,62 +1,165 @@
1
1
  #!/usr/bin/env node
2
2
  /**
3
- * Squish v1.0.0 - Universal Memory Plugin System
4
- *
5
- * Modes:
6
- * - CLI Mode: For any MCP client bash execution (e.g., `squish remember "text"`)
7
- * - MCP Mode: For AI assistants (Claude Code, OpenClaw, OpenCode, Codex, etc.)
8
- *
9
- * Features:
10
- * - Hybrid Search: BM25 + vector search with RRF
11
- * - Importance Scoring: Auto-score memories with temporal decay
12
- * - Consolidation: Summarize old, low-importance memory clusters
13
- * - 16 MCP tools
14
- * - Local mode: SQLite with FTS5
15
- * - Team mode: PostgreSQL + pgvector
16
- * - Universal Plugin: Works with 7+ AI assistants
3
+ * Squish - Universal Memory Plugin System
4
+ * CLI + MCP server for persistent memory with hybrid search and encryption
17
5
  */
18
6
  import 'dotenv/config';
19
7
  import fs from 'node:fs';
8
+ import { existsSync } from 'node:fs';
20
9
  import os from 'node:os';
21
10
  import path from 'node:path';
22
11
  import { fileURLToPath } from 'node:url';
23
- import { spawnSync } from 'node:child_process';
12
+ import { spawn, spawnSync } from 'node:child_process';
24
13
  import { Command } from 'commander';
25
- import { Server } from '@modelcontextprotocol/sdk/server/index.js';
26
- import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
27
- import { CallToolRequestSchema, ErrorCode, ListToolsRequestSchema, McpError, } from '@modelcontextprotocol/sdk/types.js';
28
14
  import { logger } from './core/logger.js';
29
- import { checkDatabaseHealth, config } from './db/index.js';
30
- import { checkRedisHealth, closeCache } from './core/cache.js';
31
- import { rememberMemory, getMemoryById, searchMemories } from './core/memory/memories.js';
32
- import { createObservation } from './core/observations.js';
33
- import { getProjectContext } from './core/context.js';
34
- import { setImportanceScore } from './core/memory/importance.js';
15
+ import { checkDatabaseHealth, config, getDb } from './db/index.js';
16
+ import { getSchema } from './db/schema.js';
17
+ import { eq } from 'drizzle-orm';
18
+ import { checkRedisHealth } from './core/storage/cache.js';
19
+ import { rememberMemory, getMemory, search, getRecent, setConfidence } from './core/memory/memories.js';
20
+ import { serializeTags } from './core/memory/serialization.js';
21
+ import { createLearning } from './core/ingestion/learnings.js';
35
22
  import { getMemoryStats } from './core/memory/stats.js';
36
- import { ensureProject } from './core/projects.js';
37
- import { consolidateMemories as consolidateMemoriesImpl, getConsolidationStats } from './core/memory/consolidation.js';
38
- import { startWebServer } from './api/web/web.js';
39
- import { handleDetectDuplicates } from './algorithms/handlers/detect-duplicates.js';
40
- import { handleListProposals } from './algorithms/handlers/list-proposals.js';
41
- import { handlePreviewMerge } from './algorithms/handlers/preview-merge.js';
42
- import { handleApproveMerge } from './algorithms/handlers/approve-merge.js';
43
- import { handleRejectMerge } from './algorithms/handlers/reject-merge.js';
44
- import { handleReverseMerge } from './algorithms/handlers/reverse-merge.js';
45
- import { handleGetMergeStats } from './algorithms/handlers/get-stats.js';
46
- import { pinMemory, unpinMemory } from './core/governance.js';
47
- import { searchWithQMD, isQMDAvailable } from './core/search/qmd-search.js';
48
- import { initializeCoreMemory, getCoreMemory, editCoreMemorySection, appendCoreMemorySection, getCoreMemoryStats, } from './core/core-memory.js';
49
- import { loadMemoryToContext, evictMemoryFromContext, viewLoadedMemories, getContextStatus, } from './core/context-paging.js';
23
+ import { ensureProject, getAllProjects, getOrCreateProject } from './core/projects.js';
24
+ import { startWebServer } from './webui/server.js';
25
+ import { getRelatedMemories, createAssociation } from './core/associations.js';
26
+ import { pinMemory, unpinMemory } from './core/security/governance.js';
27
+ import { determineOverallStatus } from './core/lib/utils.js';
28
+ import { validateLimit } from './core/lib/validation.js';
29
+ import { runDeduplicationJob, runFullConsolidationJob } from './core/consolidation.js';
50
30
  import { ensureDataDirectory } from './db/bootstrap.js';
51
- import { performAutoLoad, shouldAutoLoad, getAutoLoadConfig } from './core/session/auto-load.js';
52
- import { initializeScheduler, registerJobHandler } from './core/scheduler/cron-scheduler.js';
53
- import { startHeartbeatChecking, heartbeat } from './core/scheduler/heartbeat.js';
54
- import { runNightlyJob, runWeeklyJob } from './core/scheduler/job-runner.js';
55
- const VERSION = '1.0.1';
31
+ import { getDataDir } from './config.js';
32
+ import { handleSessionStart, handlePostToolUse, handleSessionEnd, handlePreCompact, } from './core/hooks/agent-hooks.js';
33
+ import { initializeDefaultPlaces, walkPlace, walkAllPlaces, quickTour, } from './core/places/index.js';
34
+ const VERSION = '1.1.5';
35
+ // Output Formatting Utilities
36
+ // ============================================================================
37
+ function formatOutput(data, pretty = false) {
38
+ if (!pretty) {
39
+ return JSON.stringify(data, null, 2);
40
+ }
41
+ if (Array.isArray(data)) {
42
+ return data.map((item, i) => `${i + 1}. ${formatItem(item)}`).join('\n');
43
+ }
44
+ if (data.results) {
45
+ return data.results.map((item, i) => `${i + 1}. ${formatItem(item)}`).join('\n');
46
+ }
47
+ if (data.matches) {
48
+ return data.matches.map((item, i) => `${i + 1}. ${formatItem(item)}`).join('\n');
49
+ }
50
+ if (data.count !== undefined) {
51
+ let output = `\nFound ${data.count} results:\n`;
52
+ if (data.results) {
53
+ output += data.results.map((item, i) => ` ${i + 1}. ${formatItem(item)}`).join('\n');
54
+ }
55
+ return output;
56
+ }
57
+ return JSON.stringify(data, null, 2);
58
+ }
59
+ function formatItem(item) {
60
+ if (typeof item === 'string')
61
+ return item.substring(0, 100);
62
+ const content = item.content || item.summary || item.memory?.content || '';
63
+ const type = item.type || '';
64
+ return `[${type}] ${content.substring(0, 80)}${content.length > 80 ? '...' : ''}`;
65
+ }
66
+ function printSuccess(message) {
67
+ console.log(`\n ✓ ${message}\n`);
68
+ }
69
+ function printError(message) {
70
+ console.error(`\n ✗ ${message}\n`);
71
+ }
72
+ function printInfo(message) {
73
+ console.log(`\n ℹ ${message}\n`);
74
+ }
75
+ // Config Management (for project path persistence)
76
+ // ============================================================================
77
+ const USER_CONFIG_PATH = path.join(os.homedir(), '.squish', 'config.json');
78
+ function loadUserConfig() {
79
+ try {
80
+ if (fs.existsSync(USER_CONFIG_PATH)) {
81
+ return JSON.parse(fs.readFileSync(USER_CONFIG_PATH, 'utf-8'));
82
+ }
83
+ }
84
+ catch (e) { }
85
+ return {};
86
+ }
87
+ function saveUserConfig(config) {
88
+ const dir = path.dirname(USER_CONFIG_PATH);
89
+ if (!fs.existsSync(dir))
90
+ fs.mkdirSync(dir, { recursive: true });
91
+ fs.writeFileSync(USER_CONFIG_PATH, JSON.stringify(config, null, 2));
92
+ }
93
+ function getDefaultProjectPath() {
94
+ const userConfig = loadUserConfig();
95
+ if (userConfig.project)
96
+ return userConfig.project;
97
+ return process.cwd();
98
+ }
99
+ function resolveProjectPath(projectOption) {
100
+ if (projectOption)
101
+ return projectOption;
102
+ return getDefaultProjectPath();
103
+ }
104
+ // Date parsing for time-based queries
105
+ // ============================================================================
106
+ function parseDate(input) {
107
+ if (!input)
108
+ return null;
109
+ const now = new Date();
110
+ const lower = input.toLowerCase().trim();
111
+ // Direct date parse
112
+ const parsed = new Date(input);
113
+ if (!isNaN(parsed.getTime()))
114
+ return parsed;
115
+ // Relative parsing
116
+ const dayMatch = lower.match(/(\d+)\s*day/i);
117
+ const weekMatch = lower.match(/(\d+)\s*week/i);
118
+ const monthMatch = lower.match(/(\d+)\s*month/i);
119
+ if (lower === 'today') {
120
+ const d = new Date(now);
121
+ d.setHours(0, 0, 0, 0);
122
+ return d;
123
+ }
124
+ if (lower === 'yesterday')
125
+ return new Date(now.getTime() - 86400000);
126
+ if (lower === 'thisweek' || lower === 'this week') {
127
+ const d = new Date(now);
128
+ d.setDate(d.getDate() - d.getDay());
129
+ d.setHours(0, 0, 0, 0);
130
+ return d;
131
+ }
132
+ if (lower === 'lastweek' || lower === 'last week') {
133
+ const d = new Date(now);
134
+ d.setDate(d.getDate() - d.getDay() - 7);
135
+ return d;
136
+ }
137
+ if (dayMatch)
138
+ return new Date(now.getTime() - parseInt(dayMatch[1]) * 86400000);
139
+ if (weekMatch)
140
+ return new Date(now.getTime() - parseInt(weekMatch[1]) * 604800000);
141
+ if (monthMatch)
142
+ return new Date(now.getTime() - parseInt(monthMatch[1]) * 2592000000);
143
+ return null;
144
+ }
145
+ function filterByDateRange(items, since, until) {
146
+ const sinceDate = parseDate(since || '');
147
+ const untilDate = parseDate(until || '');
148
+ return items.filter(item => {
149
+ if (!item.createdAt)
150
+ return true;
151
+ const created = new Date(item.createdAt);
152
+ if (sinceDate && created < sinceDate)
153
+ return false;
154
+ if (untilDate && created > untilDate)
155
+ return false;
156
+ return true;
157
+ });
158
+ }
56
159
  // Load plugin manifest for self-verification
57
160
  function loadPluginManifest() {
58
161
  try {
59
- const manifestPath = path.join(process.cwd(), 'config', 'plugin-manifest.json');
162
+ const manifestPath = path.join(getDefaultProjectPath(), 'config', 'plugin-manifest.json');
60
163
  if (fs.existsSync(manifestPath)) {
61
164
  return JSON.parse(fs.readFileSync(manifestPath, 'utf8'));
62
165
  }
@@ -88,26 +191,258 @@ function verifyManifest(manifest) {
88
191
  });
89
192
  return { ok: errors.length === 0, errors };
90
193
  }
194
+ async function buildHealthStatus() {
195
+ const dbHealth = await checkDatabaseHealth();
196
+ const redisHealth = await checkRedisHealth();
197
+ const database = dbHealth ? 'ok' : 'error';
198
+ const cache = config.redisEnabled ? (redisHealth ? 'ok' : 'error') : 'unavailable';
199
+ const overallStatus = config.redisEnabled
200
+ ? determineOverallStatus(database, redisHealth)
201
+ : (dbHealth ? 'ok' : 'error');
202
+ return {
203
+ ok: overallStatus === 'ok',
204
+ version: VERSION,
205
+ mode: config.isTeamMode ? 'team' : 'local',
206
+ database,
207
+ cache,
208
+ dataDirectory: config.dataDir,
209
+ dataDirectoryExists: fs.existsSync(config.dataDir),
210
+ status: overallStatus,
211
+ timestamp: new Date().toISOString(),
212
+ };
213
+ }
214
+ // HELPER FUNCTIONS
91
215
  // ============================================================================
216
+ function showHelp() {
217
+ console.log(`
218
+ Squish Memory v${VERSION} - Universal Memory Plugin System
219
+
220
+ Usage:
221
+ squish Start interactive wizard
222
+ squish run mcp Start MCP server
223
+ squish run web Start Web UI only
224
+ squish <command> [options] Run CLI commands for agents
225
+
226
+ CLI Commands (for agents):
227
+ squish config [action] Manage Squish configuration
228
+ squish remember <content> Store a memory
229
+ squish note <content> Save a quick note
230
+ squish learn <type> <text> Record learning: success, failure, fix, insight
231
+ squish search <query> Search memories (--pretty for human output)
232
+ squish recall <query> Search or get by ID (--pretty for human output)
233
+ squish recent --period Recent memories (today/yesterday/thisweek/7days/30days)
234
+ squish update <memoryId> Update memory
235
+ squish forget <memoryId> Delete memory (single or bulk with --older-than --search)
236
+ squish pin <memoryId> Pin/unpin memory
237
+ squish confidence <id> Set confidence
238
+ squish tag <action> Manage tags
239
+ squish stale Show stale memories
240
+ squish link <action> Manage links (find/add/list)
241
+ squish migrate Migrate memories between .squish directories
242
+ squish clean Dedup + consolidate (maintenance)
243
+ squish context Show context or list projects
244
+ squish stats View memory statistics
245
+
246
+ Examples:
247
+ squish run mcp # Start MCP server (for agents)
248
+ squish run web # Start Web UI only
249
+ squish config set project /repo/path
250
+ squish remember "Hello" # Store memory
251
+ squish note "Ship v1 first" # Save a quick note
252
+ squish learn observation "Updated auth flow" --action edit
253
+ squish learn fix "Patched auth middleware" --target middleware.ts
254
+ squish search "query" # Search memories
255
+ squish context --list-projects
256
+ squish clean # Run deduplication and consolidation
257
+
258
+ For more info: https://github.com/michielhdoteth/squish
259
+ `);
260
+ }
261
+ async function runInteractiveInstaller() {
262
+ const { select } = await import('@clack/prompts');
263
+ const { isCancel } = await import('@clack/prompts');
264
+ const { log } = await import('@clack/prompts');
265
+ const { intro, outro } = await import('@clack/prompts');
266
+ intro(`Squish Memory v${VERSION}`);
267
+ const options = [
268
+ { value: 'mcp', label: 'Start MCP Server (for AI Assistants: Claude Code, OpenCode, etc.)' },
269
+ { value: 'web', label: 'Start Web UI Only' },
270
+ { value: 'health', label: 'Health & Stats' },
271
+ { value: 'help', label: 'Show Help' },
272
+ { value: 'exit', label: 'Exit' }
273
+ ];
274
+ const selected = await select({
275
+ message: 'What would you like to do?',
276
+ options: options,
277
+ });
278
+ if (isCancel(selected)) {
279
+ outro('Cancelled');
280
+ process.exit(0);
281
+ return;
282
+ }
283
+ switch (selected) {
284
+ case 'mcp':
285
+ log.step('Starting MCP server...');
286
+ const { spawn } = await import('child_process');
287
+ spawn('npx', ['squish-mcp'], { stdio: 'inherit', shell: true });
288
+ break;
289
+ case 'web':
290
+ log.step('Starting Web UI...');
291
+ await runWebOnly();
292
+ break;
293
+ case 'health':
294
+ log.step('Checking health...');
295
+ await runCliCommand('health');
296
+ await runCliCommand('stats');
297
+ break;
298
+ case 'help':
299
+ showHelp();
300
+ process.exit(0);
301
+ break;
302
+ case 'exit':
303
+ outro('Goodbye! 👋');
304
+ process.exit(0);
305
+ break;
306
+ }
307
+ }
308
+ async function runCliCommand(command) {
309
+ // Run CLI command programmatically
310
+ const program = new Command();
311
+ program.hook('preAction', async () => {
312
+ await ensureDataDirectory();
313
+ });
314
+ if (command === 'health') {
315
+ const status = await buildHealthStatus();
316
+ console.log(`\n Squish Memory v${VERSION}`);
317
+ console.log(` ====================`);
318
+ console.log(` Mode: ${status.mode}`);
319
+ console.log(` Database: ${status.database}`);
320
+ console.log(` Cache: ${status.cache}`);
321
+ console.log(` Data Dir: ${status.dataDirectory}`);
322
+ console.log(` Status: ${status.ok ? 'HEALTHY' : 'UNHEALTHY'}\n`);
323
+ }
324
+ else if (command === 'stats') {
325
+ const stats = await getMemoryStats(getDefaultProjectPath());
326
+ console.log(JSON.stringify({ ok: true, ...stats }, null, 2));
327
+ }
328
+ }
329
+ async function spawnInstallerWizard() {
330
+ const distDir = path.dirname(fileURLToPath(import.meta.url));
331
+ const packageDir = path.dirname(distDir);
332
+ const installScript = path.join(packageDir, 'scripts', 'install-interactive.mjs');
333
+ if (!fs.existsSync(installScript)) {
334
+ console.error('Installer not found at:', installScript);
335
+ process.exit(1);
336
+ }
337
+ console.log('\nLaunching full installer wizard...\n');
338
+ const result = spawnSync('node', [`"${installScript}"`], {
339
+ stdio: 'inherit',
340
+ shell: true,
341
+ cwd: packageDir
342
+ });
343
+ process.exit(result.status || 0);
344
+ }
345
+ function isDatabaseInitialized() {
346
+ try {
347
+ const dataDir = getDataDir();
348
+ const dbPath = path.join(dataDir, 'squish.db');
349
+ return existsSync(dataDir) && existsSync(dbPath);
350
+ }
351
+ catch (error) {
352
+ return false;
353
+ }
354
+ }
355
+ async function runWebOnly() {
356
+ console.log(`[squish] Starting Web UI only...`);
357
+ await ensureDataDirectory();
358
+ await startWebServer();
359
+ }
92
360
  // CLI MODE DETECTION
93
361
  // ============================================================================
94
362
  const args = process.argv.slice(2);
95
- const hasCliArgs = args.length > 0 && args[0] !== '--mcp';
96
- if (hasCliArgs) {
97
- // === CLI MODE (for OpenClaw) ===
98
- runCliMode().catch((e) => {
99
- console.error(JSON.stringify({ error: e.message }, null, 2));
100
- process.exit(1);
101
- });
363
+ const firstArg = args[0];
364
+ // Detect command type
365
+ const isNoArgs = args.length === 0;
366
+ const isRunCommand = firstArg === 'run';
367
+ const isHelpCommand = firstArg === '--help' || firstArg === '-h' || firstArg === 'help';
368
+ if (isNoArgs) {
369
+ // Check if database exists - if not, run installer automatically
370
+ if (!isDatabaseInitialized()) {
371
+ console.log(`[squish] No existing database found. Launching installer wizard...\n`);
372
+ await spawnInstallerWizard();
373
+ }
374
+ else {
375
+ // INTERACTIVE WIZARD (default when no args) ===
376
+ runInteractiveInstaller().catch((e) => {
377
+ console.error('Installer error:', e.message);
378
+ process.exit(1);
379
+ });
380
+ }
381
+ }
382
+ else if (isRunCommand) {
383
+ // RUN SUBCOMMAND ===
384
+ const subcommand = args[1];
385
+ if (subcommand === 'mcp') {
386
+ (async () => {
387
+ try {
388
+ // Initialize data directory before starting MCP
389
+ await ensureDataDirectory();
390
+ // Start MCP server as child process (stdio mode for agents)
391
+ const mcpProcess = spawn('npx', ['squish-mcp'], {
392
+ stdio: 'inherit',
393
+ shell: true
394
+ });
395
+ // Forward MCP exit code when it exits
396
+ mcpProcess.on('exit', (code) => {
397
+ process.exit(code ?? 0);
398
+ });
399
+ // Clean shutdown: forward signals to MCP child
400
+ const cleanup = () => {
401
+ mcpProcess.kill('SIGTERM');
402
+ setTimeout(() => process.exit(0), 100);
403
+ };
404
+ process.on('SIGINT', cleanup);
405
+ process.on('SIGTERM', cleanup);
406
+ }
407
+ catch (error) {
408
+ console.error('[squish] Failed to start MCP server:', error.message);
409
+ process.exit(1);
410
+ }
411
+ })();
412
+ }
413
+ else if (subcommand === 'web') {
414
+ runWebOnly().catch((e) => {
415
+ logger.error('Web server error', e);
416
+ process.exit(1);
417
+ });
418
+ }
419
+ else {
420
+ console.log(`
421
+ Usage: squish run <command>
422
+
423
+ Commands:
424
+ mcp Start MCP server (for agents like Claude Code)
425
+ web Start Web UI only
426
+
427
+ Examples:
428
+ squish run mcp # Start MCP server (agents connect automatically)
429
+ squish run web # Start Web UI at http://localhost:37777
430
+ `);
431
+ process.exit(subcommand ? 1 : 0);
432
+ }
433
+ }
434
+ else if (isHelpCommand) {
435
+ // SHOW HELP ===
436
+ showHelp();
437
+ process.exit(0);
102
438
  }
103
439
  else {
104
- // === MCP MODE (for Claude Code) - DEFAULT ===
105
- runMcpMode().catch((e) => {
106
- logger.error('Fatal error', e);
440
+ // CLI MODE (for agents/OpenClaw) ===
441
+ runCliMode().catch((e) => {
442
+ console.error(JSON.stringify({ error: e.message }, null, 2));
107
443
  process.exit(1);
108
444
  });
109
445
  }
110
- // ============================================================================
111
446
  // CLI MODE (for OpenClaw bash execution)
112
447
  // ============================================================================
113
448
  async function runCliMode() {
@@ -120,20 +455,136 @@ async function runCliMode() {
120
455
  program.hook('preAction', async () => {
121
456
  await ensureDataDirectory();
122
457
  });
458
+ // squish config [get] [key] or squish config set <key> <value>
459
+ program
460
+ .command('config')
461
+ .description('Manage Squish configuration')
462
+ .argument('[action]', 'get, set, or list', 'list')
463
+ .argument('[key]', 'Config key (e.g., project)')
464
+ .argument('[value]', 'Config value (for set action)')
465
+ .action(async (action, key, value) => {
466
+ const userConfig = loadUserConfig();
467
+ if (action === 'set') {
468
+ if (!key || value === undefined) {
469
+ console.log(JSON.stringify({ ok: false, error: 'Usage: squish config set <key> <value>' }, null, 2));
470
+ process.exit(1);
471
+ }
472
+ userConfig[key] = value;
473
+ saveUserConfig(userConfig);
474
+ console.log(JSON.stringify({ ok: true, message: `Set ${key} = ${value}` }, null, 2));
475
+ }
476
+ else if (action === 'get') {
477
+ if (!key) {
478
+ console.log(JSON.stringify({ ok: false, error: 'Usage: squish config get <key>' }, null, 2));
479
+ process.exit(1);
480
+ }
481
+ console.log(JSON.stringify({ ok: true, [key]: userConfig[key] || null }, null, 2));
482
+ }
483
+ else {
484
+ console.log(JSON.stringify({ ok: true, config: userConfig }, null, 2));
485
+ }
486
+ });
487
+ // squish mount /path/to/folder - Enable external memory
488
+ program
489
+ .command('mount')
490
+ .description('Mount an external folder as memory storage')
491
+ .argument('[path]', 'Path to external folder (or "status" or "unmount")')
492
+ .action(async (pathOrAction) => {
493
+ const { getExternalMemory } = await import('./core/external-folder/index.js');
494
+ const externalMemory = getExternalMemory();
495
+ if (pathOrAction === 'status') {
496
+ // Show mount status
497
+ const status = await externalMemory.getStatus();
498
+ console.log(JSON.stringify({ ok: true, status }, null, 2));
499
+ }
500
+ else if (pathOrAction === 'unmount') {
501
+ // Unmount
502
+ externalMemory.unmount();
503
+ console.log(JSON.stringify({ ok: true, message: 'External memory unmounted' }, null, 2));
504
+ }
505
+ else if (pathOrAction) {
506
+ // Mount at path
507
+ const result = await externalMemory.mount(pathOrAction);
508
+ if (result.success) {
509
+ console.log(JSON.stringify({ ok: true, message: `Mounted at ${pathOrAction}` }, null, 2));
510
+ }
511
+ else {
512
+ console.log(JSON.stringify({ ok: false, error: result.error }, null, 2));
513
+ }
514
+ }
515
+ else {
516
+ console.log(JSON.stringify({ ok: false, error: 'Usage: squish mount <path> or squish mount status' }, null, 2));
517
+ }
518
+ });
123
519
  // squish remember "content" --type fact --tags tag1,tag2
124
520
  program
125
521
  .command('remember <content>')
126
522
  .description('Store a memory')
127
523
  .option('-t, --type <type>', 'Memory type (observation, fact, decision, context, preference)', 'observation')
128
524
  .option('-T, --tags <tags>', 'Comma-separated tags', '')
129
- .option('-p, --project <project>', 'Project path', process.cwd())
525
+ .option('-p, --project <project>', 'Project path', getDefaultProjectPath())
526
+ .option('-s, --source <source>', 'Source of this memory (e.g., "voice", "chat", "document")')
527
+ .option('-r, --reasoning <reasoning>', 'Why this memory is important')
528
+ .option('-c, --context <context>', 'What triggered this memory')
529
+ .option('-e, --examples <examples>', 'When to apply this knowledge')
530
+ .option('-x, --exceptions <exceptions>', 'When NOT to apply this')
531
+ .option('-m, --memory', 'Store as markdown file in .squish/memory/ (not in database)', false)
532
+ .option('-H, --hot', 'Store in hot tier (active, high priority) in database', false)
533
+ .option('-C, --cold', 'Store in cold tier (archived, lower priority) in database', false)
130
534
  .action(async (content, options) => {
131
535
  try {
536
+ // Markdown file storage (not in database)
537
+ if (options.memory) {
538
+ const { saveToMarkdown } = await import('./core/memory/markdown/markdown-storage.js');
539
+ const memoryFile = await saveToMarkdown({
540
+ content,
541
+ type: options.type,
542
+ tags: options.tags ? options.tags.split(',').map((t) => t.trim()) : [],
543
+ project: options.project,
544
+ source: options.source,
545
+ reasoning: options.reasoning,
546
+ memoryContext: options.context,
547
+ examples: options.examples,
548
+ exceptions: options.exceptions,
549
+ });
550
+ // Trigger hooks
551
+ const { triggerMemoryCreated } = await import('./core/memory/hooks.js');
552
+ await triggerMemoryCreated({
553
+ memoryId: memoryFile.id,
554
+ content: memoryFile.content,
555
+ type: memoryFile.type,
556
+ tags: memoryFile.tags,
557
+ project: memoryFile.project,
558
+ source: memoryFile.source,
559
+ tier: 'hot',
560
+ });
561
+ console.log(JSON.stringify({ ok: true, memory: true, ...memoryFile }, null, 2));
562
+ return;
563
+ }
564
+ // Database storage: determine tier
565
+ const tier = options.cold ? 'cold' : 'hot';
132
566
  const result = await rememberMemory({
133
567
  content,
134
568
  type: options.type,
135
569
  tags: options.tags ? options.tags.split(',').map((t) => t.trim()) : [],
136
570
  project: options.project,
571
+ source: options.source,
572
+ reasoning: options.reasoning,
573
+ memoryContext: options.context,
574
+ examples: options.examples,
575
+ exceptions: options.exceptions,
576
+ tier,
577
+ });
578
+ // Trigger hooks for DB storage
579
+ const { triggerMemoryCreated } = await import('./core/memory/hooks.js');
580
+ await triggerMemoryCreated({
581
+ memoryId: result.id,
582
+ content: result.content,
583
+ type: result.type,
584
+ tags: result.tags,
585
+ project: result.projectId || undefined,
586
+ source: options.source || 'cli',
587
+ tier,
137
588
  });
138
589
  console.log(JSON.stringify({ ok: true, ...result }, null, 2));
139
590
  }
@@ -142,218 +593,559 @@ async function runCliMode() {
142
593
  process.exit(1);
143
594
  }
144
595
  });
145
- // squish search "query" --type fact --limit 10
596
+ // squish search "query" --type fact --limit 10 --since "3 days ago" --place workshop
146
597
  program
147
598
  .command('search <query>')
148
599
  .description('Search memories')
149
600
  .option('-t, --type <type>', 'Filter by memory type')
150
601
  .option('-l, --limit <number>', 'Max results', '10')
151
- .option('-p, --project <project>', 'Project path', process.cwd())
602
+ .option('-p, --project <project>', 'Project path', getDefaultProjectPath())
603
+ .option('-s, --since <date>', 'Filter: created after this date (e.g., "3 days ago", "2026-01-01")')
604
+ .option('-u, --until <date>', 'Filter: created before this date (e.g., "yesterday", "2026-01-15")')
605
+ .option('-P, --pretty', 'Human-friendly output', false)
606
+ .option('-m, --memory', 'Search memory files instead of database', false)
607
+ .option('--place <type>', 'Filter by place type: entry_hall, library, workshop, lab, office, garden, archive')
152
608
  .action(async (query, options) => {
153
609
  try {
154
- const results = await searchMemories({
610
+ // Markdown file search
611
+ if (options.memory) {
612
+ const { getMarkdownMemories } = await import('./core/memory/markdown/markdown-storage.js');
613
+ const memoryFiles = await getMarkdownMemories({
614
+ type: options.type,
615
+ project: options.project,
616
+ });
617
+ // Simple text search (can be enhanced with QMD later)
618
+ const searchLower = query.toLowerCase();
619
+ const filtered = memoryFiles.filter(m => m.content.toLowerCase().includes(searchLower) ||
620
+ m.tags.some(t => t.toLowerCase().includes(searchLower))).slice(0, validateLimit(options.limit, 10, 1, 100));
621
+ if (options.pretty) {
622
+ console.log(`\n Memory Search: "${query}"`);
623
+ console.log(` Found ${filtered.length} results:\n`);
624
+ filtered.forEach((r, i) => {
625
+ console.log(` ${i + 1}. [${r.type || 'memory'}] ${(r.content || '').substring(0, 60)}...`);
626
+ });
627
+ console.log('');
628
+ }
629
+ else {
630
+ console.log(JSON.stringify({ ok: true, query, source: 'memory', count: filtered.length, results: filtered }, null, 2));
631
+ }
632
+ return;
633
+ }
634
+ // Database search
635
+ const results = await search({
155
636
  query,
156
637
  type: options.type,
157
- limit: parseInt(options.limit, 10),
638
+ limit: validateLimit(options.limit, 10, 1, 100) * 2,
158
639
  project: options.project,
159
640
  });
160
- console.log(JSON.stringify({ ok: true, query, count: results?.length || 0, results }, null, 2));
641
+ const filtered = filterByDateRange(results, options.since, options.until);
642
+ let limited = filtered.slice(0, validateLimit(options.limit, 10, 1, 100));
643
+ // Add place info to results
644
+ const { getMemoryPlace } = await import('./core/places/index.js');
645
+ const limitedWithPlace = await Promise.all(limited.map(async (r) => {
646
+ const placeId = await getMemoryPlace(r.id);
647
+ return { ...r, placeId };
648
+ }));
649
+ // Filter by place if specified
650
+ if (options.place) {
651
+ const placeFiltered = [];
652
+ for (const r of limitedWithPlace) {
653
+ if (r.placeId) {
654
+ const { getPlace } = await import('./core/places/index.js');
655
+ const place = await getPlace(r.placeId);
656
+ if (place && place.placeType === options.place) {
657
+ placeFiltered.push({ ...r, place: place.name || null, placeType: place.placeType || null });
658
+ }
659
+ }
660
+ }
661
+ limited = placeFiltered;
662
+ }
663
+ else if (limitedWithPlace.length > 0) {
664
+ // Add place info to results even without filter
665
+ for (const r of limitedWithPlace) {
666
+ if (r.placeId) {
667
+ const { getPlace } = await import('./core/places/index.js');
668
+ const place = await getPlace(r.placeId);
669
+ if (place) {
670
+ r.place = place.name || null;
671
+ r.placeType = place.placeType || null;
672
+ }
673
+ }
674
+ }
675
+ limited = limitedWithPlace;
676
+ }
677
+ if (options.pretty) {
678
+ console.log(`\n Search: "${query}"`);
679
+ console.log(` Found ${limited.length} results:\n`);
680
+ limited.forEach((r, i) => {
681
+ const placeTag = r.place ? ` (${r.place})` : '';
682
+ console.log(` ${i + 1}. [${r.type || 'memory'}] ${(r.content || '').substring(0, 60)}...${placeTag}`);
683
+ });
684
+ console.log('');
685
+ }
686
+ else {
687
+ console.log(JSON.stringify({ ok: true, query, count: limited.length, since: options.since, until: options.until, placeFilter: options.place || null, results: limited }, null, 2));
688
+ }
161
689
  }
162
690
  catch (error) {
163
691
  console.log(JSON.stringify({ ok: false, error: error.message }, null, 2));
164
692
  process.exit(1);
165
693
  }
166
694
  });
167
- // squish recall <memoryId>
695
+ // squish forget <memoryId> -- Delete single or bulk delete memories
168
696
  program
169
- .command('recall <memoryId>')
170
- .description('Retrieve a memory by ID')
171
- .action(async (memoryId) => {
697
+ .command('forget [memoryId]')
698
+ .description('Delete a memory by ID, or bulk delete with filters')
699
+ .option('-o, --older-than <date>', 'Bulk delete memories older than (e.g., "30 days", "6 months")')
700
+ .option('-t, --type <type>', 'Filter by memory type')
701
+ .option('-s, --search <query>', 'Search query to match specific memories')
702
+ .option('-c, --confirm', 'Actually delete (default is dry-run)', false)
703
+ .option('-l, --limit <number>', 'Max memories to delete', '100')
704
+ .option('-p, --project <project>', 'Project path', getDefaultProjectPath())
705
+ .action(async (memoryId, options) => {
172
706
  try {
173
- const memory = await getMemoryById(String(memoryId));
174
- console.log(JSON.stringify({ ok: true, found: !!memory, memory }, null, 2));
707
+ // Single memory deletion
708
+ if (memoryId) {
709
+ const db = await getDb();
710
+ const schema = await getSchema();
711
+ const sqliteDb = db;
712
+ // Get memory content before deleting for hook
713
+ const [memory] = await sqliteDb.select().from(schema.memories).where(eq(schema.memories.id, memoryId));
714
+ await sqliteDb.delete(schema.memories).where(eq(schema.memories.id, memoryId));
715
+ // Trigger memoryDeleted hook
716
+ if (memory) {
717
+ const { triggerMemoryDeleted } = await import('./core/memory/hooks.js');
718
+ await triggerMemoryDeleted({
719
+ memoryId: memory.id,
720
+ content: memory.content,
721
+ type: memory.type,
722
+ tags: typeof memory.tags === 'string' ? memory.tags.split(',') : [],
723
+ project: memory.projectId || undefined,
724
+ source: memory.source || undefined,
725
+ tier: memory.tier,
726
+ });
727
+ }
728
+ console.log(JSON.stringify({ ok: true, message: `Memory ${memoryId} deleted` }, null, 2));
729
+ return;
730
+ }
731
+ // Bulk deletion
732
+ if (!options.olderThan && !options.search) {
733
+ console.log(JSON.stringify({ ok: false, error: 'Provide memory ID or use --older-than / --search for bulk delete' }, null, 2));
734
+ process.exit(1);
735
+ }
736
+ const query = options.search || '';
737
+ const limit = validateLimit(options.limit, 100, 1, 100);
738
+ const results = await search({ query, type: options.type, limit, project: options.project });
739
+ let filtered = results;
740
+ if (options.olderThan) {
741
+ filtered = filterByDateRange(results, '', options.olderThan);
742
+ }
743
+ const db = await getDb();
744
+ const schema = await getSchema();
745
+ const sqliteDb = db;
746
+ const deleted = [];
747
+ for (const mem of filtered) {
748
+ await sqliteDb.delete(schema.memories).where(eq(schema.memories.id, mem.id));
749
+ deleted.push(mem.id);
750
+ }
751
+ console.log(JSON.stringify({ ok: true, matched: filtered.length, deleted: deleted.length, dryRun: !options.confirm }, null, 2));
175
752
  }
176
753
  catch (error) {
177
754
  console.log(JSON.stringify({ ok: false, error: error.message }, null, 2));
178
755
  process.exit(1);
179
756
  }
180
757
  });
181
- // squish core_memory view
182
- // squish core_memory edit persona --content "I am helpful"
183
- // squish core_memory append user_info --text "Prefers TypeScript"
758
+ // squish link - Unified graph operations (find related, add links, list associations)
184
759
  program
185
- .command('core_memory')
186
- .description('Manage core memory (always-visible context)')
187
- .argument('[action]', 'view, edit, append', 'view')
188
- .option('-s, --section <section>', 'Section: persona, user_info, project_context, working_notes')
189
- .option('-c, --content <content>', 'New content (for edit)')
190
- .option('-t, --text <text>', 'Text to append (for append)')
191
- .option('-p, --project <project>', 'Project path', process.cwd())
192
- .action(async (action, options) => {
760
+ .command('link')
761
+ .description('Manage memory associations: find, add, list')
762
+ .argument('<action>', 'Action: find, add, or list')
763
+ .argument('[args...]', 'Additional arguments')
764
+ .option('-p, --project <project>', 'Project path', getDefaultProjectPath())
765
+ .action(async (action, args, options) => {
193
766
  try {
194
- const projectPath = options.project;
195
- const projectRecord = await ensureProject(projectPath);
196
- if (!projectRecord) {
197
- console.log(JSON.stringify({ ok: false, error: 'Project not found and could not be created' }, null, 2));
198
- process.exit(1);
767
+ // link find <memoryId> [--depth N] [--min-weight N]
768
+ if (action === 'find') {
769
+ const memoryId = args[0];
770
+ if (!memoryId) {
771
+ console.log(JSON.stringify({ ok: false, error: 'Usage: squish link find <memoryId> [--depth N] [--min-weight N]' }, null, 2));
772
+ process.exit(1);
773
+ }
774
+ const depth = validateLimit(args[1], 2, 1, 5);
775
+ const minWeight = parseFloat(args[2]) || 0.3;
776
+ const related = await getRelatedMemories(memoryId, depth * 5);
777
+ const filtered = related.filter((r) => r.weight >= minWeight);
778
+ console.log(JSON.stringify({ ok: true, count: filtered.length, related: filtered }, null, 2));
779
+ return;
199
780
  }
200
- const projectId = projectRecord.id;
201
- switch (action) {
202
- case 'view':
203
- await initializeCoreMemory(projectId);
204
- const core = await getCoreMemory(projectId);
205
- const stats = await getCoreMemoryStats(projectId);
206
- console.log(JSON.stringify({ ok: true, action, content: core, stats }, null, 2));
207
- break;
208
- case 'edit':
209
- if (!options.section || !options.content) {
210
- console.log(JSON.stringify({ ok: false, error: '--section and --content required for edit' }, null, 2));
211
- process.exit(1);
212
- }
213
- await initializeCoreMemory(projectId);
214
- const editResult = await editCoreMemorySection(projectId, options.section, String(options.content));
215
- console.log(JSON.stringify({ ok: editResult.success, action: 'edit', section: options.section, ...editResult }, null, 2));
216
- break;
217
- case 'append':
218
- if (!options.section || !options.text) {
219
- console.log(JSON.stringify({ ok: false, error: '--section and --text required for append' }, null, 2));
220
- process.exit(1);
221
- }
222
- await initializeCoreMemory(projectId);
223
- const appendResult = await appendCoreMemorySection(projectId, options.section, String(options.text));
224
- console.log(JSON.stringify({ ok: appendResult.success, action: 'append', section: options.section, ...appendResult }, null, 2));
225
- break;
226
- default:
227
- console.log(JSON.stringify({ ok: false, error: `Unknown action: ${action}` }, null, 2));
781
+ // link add <fromId> <toId> <type>
782
+ if (action === 'add') {
783
+ const fromMemoryId = args[0];
784
+ const toMemoryId = args[1];
785
+ const type = args[2] || 'relates_to';
786
+ if (!fromMemoryId || !toMemoryId) {
787
+ console.log(JSON.stringify({ ok: false, error: 'Usage: squish link add <fromId> <toId> <type>' }, null, 2));
228
788
  process.exit(1);
789
+ }
790
+ await createAssociation(fromMemoryId, toMemoryId, type, 0.5);
791
+ console.log(JSON.stringify({ ok: true, message: `Linked ${fromMemoryId} -> ${toMemoryId} (${type})` }, null, 2));
792
+ return;
229
793
  }
794
+ // link list - List all associations
795
+ if (action === 'list') {
796
+ const db = await getDb();
797
+ const schema = await getSchema();
798
+ const sqliteDb = db;
799
+ const associations = await sqliteDb.select().from(schema.memoryAssociations).limit(100);
800
+ console.log(JSON.stringify({ ok: true, count: associations.length, associations }, null, 2));
801
+ return;
802
+ }
803
+ console.log(JSON.stringify({ ok: false, error: 'Usage: squish link <find|add|list> [args]' }, null, 2));
230
804
  }
231
805
  catch (error) {
232
806
  console.log(JSON.stringify({ ok: false, error: error.message }, null, 2));
233
807
  process.exit(1);
234
808
  }
235
809
  });
236
- // squish set-importance <memoryId> --importance 80
810
+ // squish learn <type> <content> - Record learning: success, failure, fix, or insight
237
811
  program
238
- .command('set-importance <memoryId>')
239
- .description('Manually set importance score for a memory (0-100)')
240
- .option('-i, --importance <number>', 'Importance score (0-100)', '50')
241
- .action(async (memoryId, options) => {
812
+ .command('learn <type> <content>')
813
+ .description('Record learning: success, failure, fix, or insight')
814
+ .option('-c, --context <context>', 'Additional context about what happened')
815
+ .option('-a, --action <action>', 'Action performed')
816
+ .option('-t, --target <target>', 'Target file or resource')
817
+ .option('-m, --memory-id <memoryId>', 'Optional memory ID to link this learning to')
818
+ .option('-p, --project <project>', 'Project path', getDefaultProjectPath())
819
+ .action(async (type, content, options) => {
242
820
  try {
243
- const score = parseInt(options.importance, 10);
244
- if (isNaN(score) || score < 0 || score > 100) {
245
- console.log(JSON.stringify({ ok: false, error: 'Importance must be between 0 and 100' }, null, 2));
821
+ const validTypes = ['success', 'failure', 'fix', 'insight'];
822
+ if (!validTypes.includes(type)) {
823
+ console.log(JSON.stringify({ ok: false, error: `Invalid type. Must be: ${validTypes.join(', ')}` }, null, 2));
246
824
  process.exit(1);
247
825
  }
248
- await setImportanceScore(String(memoryId), score);
249
- console.log(JSON.stringify({ ok: true, memoryId, importanceScore: score }, null, 2));
826
+ const learning = await createLearning({
827
+ type: type,
828
+ content,
829
+ context: options.context,
830
+ action: options.action,
831
+ target: options.target,
832
+ project: options.project,
833
+ memoryId: options.memoryId,
834
+ });
835
+ console.log(JSON.stringify({ ok: true, learning }, null, 2));
250
836
  }
251
837
  catch (error) {
252
838
  console.log(JSON.stringify({ ok: false, error: error.message }, null, 2));
253
839
  process.exit(1);
254
840
  }
255
841
  });
256
- // squish pin <memoryId>
257
842
  program
258
- .command('pin <memoryId>')
259
- .description('Pin a memory to prevent pruning/consolidation')
260
- .action(async (memoryId) => {
843
+ .command('update <memoryId>')
844
+ .description('Update a memory')
845
+ .option('-c, --content <content>', 'New content')
846
+ .option('-t, --type <type>', 'New type (observation, fact, decision, context, preference)')
847
+ .option('-T, --tags <tags>', 'Comma-separated tags')
848
+ .action(async (memoryId, options) => {
261
849
  try {
262
- await pinMemory(String(memoryId));
263
- console.log(JSON.stringify({ ok: true, memoryId, pinned: true }, null, 2));
850
+ const updates = {};
851
+ if (options.content)
852
+ updates.content = options.content;
853
+ if (options.type)
854
+ updates.type = options.type;
855
+ if (options.tags)
856
+ updates.tags = serializeTags(options.tags.split(','));
857
+ const db = await getDb();
858
+ const schema = await getSchema();
859
+ const sqliteDb = db;
860
+ // Get old memory for hook
861
+ const [oldMemory] = await sqliteDb.select().from(schema.memories).where(eq(schema.memories.id, memoryId));
862
+ await sqliteDb.update(schema.memories).set(updates).where(eq(schema.memories.id, memoryId));
863
+ // Trigger memoryUpdated hook
864
+ if (oldMemory) {
865
+ const { triggerMemoryUpdated } = await import('./core/memory/hooks.js');
866
+ const newContent = options.content || oldMemory.content;
867
+ await triggerMemoryUpdated({
868
+ memoryId: oldMemory.id,
869
+ content: newContent,
870
+ type: options.type || oldMemory.type,
871
+ tags: options.tags ? options.tags.split(',') : (typeof oldMemory.tags === 'string' ? oldMemory.tags.split(',') : []),
872
+ project: oldMemory.projectId || undefined,
873
+ source: oldMemory.source || undefined,
874
+ tier: oldMemory.tier,
875
+ importance: oldMemory.importanceScore || oldMemory.relevanceScore || 50,
876
+ }, oldMemory.content);
877
+ }
878
+ console.log(JSON.stringify({ ok: true, message: `Memory ${memoryId} updated` }, null, 2));
264
879
  }
265
880
  catch (error) {
266
881
  console.log(JSON.stringify({ ok: false, error: error.message }, null, 2));
267
882
  process.exit(1);
268
883
  }
269
884
  });
270
- // squish unpin <memoryId>
885
+ // squish recall <query or memoryId> - Search or get by ID
271
886
  program
272
- .command('unpin <memoryId>')
273
- .description('Unpin a memory')
274
- .action(async (memoryId) => {
887
+ .command('recall <query>')
888
+ .description('Search memories by query or get by ID (if UUID provided)')
889
+ .option('-l, --limit <number>', 'Max results', '5')
890
+ .option('-t, --type <type>', 'Filter by memory type')
891
+ .option('-p, --project <project>', 'Project path', getDefaultProjectPath())
892
+ .option('-s, --since <date>', 'Filter: created after this date (e.g., "3 days ago", "yesterday")')
893
+ .option('-u, --until <date>', 'Filter: created before this date (e.g., "today", "2026-01-15")')
894
+ .option('-P, --pretty', 'Human-friendly output', false)
895
+ .option('--place <type>', 'Filter by place type: entry_hall, library, workshop, lab, office, garden, archive')
896
+ .action(async (query, options) => {
275
897
  try {
276
- await unpinMemory(String(memoryId));
277
- console.log(JSON.stringify({ ok: true, memoryId, pinned: false }, null, 2));
898
+ const isUUID = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test(query);
899
+ if (isUUID) {
900
+ const memory = await getMemory(query);
901
+ // Add place info to single memory retrieval
902
+ if (memory) {
903
+ const { getMemoryPlace, getPlace } = await import('./core/places/index.js');
904
+ const placeId = await getMemoryPlace(memory.id);
905
+ if (placeId) {
906
+ const place = await getPlace(placeId);
907
+ memory.place = place?.name || null;
908
+ memory.placeType = place?.placeType || null;
909
+ }
910
+ }
911
+ if (options.pretty && memory) {
912
+ const placeInfo = memory.place ? ` (${memory.place})` : '';
913
+ console.log(`\n Memory: ${memory.id}`);
914
+ console.log(` Type: ${memory.type}`);
915
+ console.log(` Content: ${memory.content}\n`);
916
+ if (placeInfo)
917
+ console.log(` Place: ${memory.place}\n`);
918
+ }
919
+ else {
920
+ console.log(JSON.stringify({ ok: true, found: !!memory, memory }, null, 2));
921
+ }
922
+ }
923
+ else {
924
+ const results = await search({
925
+ query,
926
+ type: options.type,
927
+ limit: validateLimit(options.limit, 5, 1, 100) * 2,
928
+ project: options.project,
929
+ });
930
+ const filtered = filterByDateRange(results, options.since, options.until);
931
+ let limited = filtered.slice(0, validateLimit(options.limit, 5, 1, 100));
932
+ // Add place info to results
933
+ const { getMemoryPlace, getPlace } = await import('./core/places/index.js');
934
+ const limitedWithPlace = await Promise.all(limited.map(async (r) => {
935
+ const placeId = await getMemoryPlace(r.id);
936
+ let placeInfo = {};
937
+ if (placeId) {
938
+ const place = await getPlace(placeId);
939
+ placeInfo = { place: place?.name || null, placeType: place?.placeType || null };
940
+ }
941
+ return { ...r, ...placeInfo };
942
+ }));
943
+ // Filter by place if specified
944
+ if (options.place) {
945
+ limited = limitedWithPlace.filter((r) => r.placeType === options.place);
946
+ }
947
+ else {
948
+ limited = limitedWithPlace;
949
+ }
950
+ if (options.pretty) {
951
+ console.log(`\n Recall: "${query}"`);
952
+ console.log(` Found ${limited.length} matches:\n`);
953
+ limited.forEach((r, i) => {
954
+ const placeTag = r.place ? ` (${r.place})` : '';
955
+ console.log(` ${i + 1}. [${r.type || 'memory'}] ${(r.content || '').substring(0, 60)}...${placeTag} (${(r.similarity ?? 0).toFixed(2)})`);
956
+ });
957
+ console.log('');
958
+ }
959
+ else {
960
+ const matches = limited.map((r) => ({
961
+ id: r.id,
962
+ score: r.similarity ?? 0,
963
+ type: r.type,
964
+ content: r.content.length > 200 ? r.content.slice(0, 200) + '...' : r.content,
965
+ tags: r.tags,
966
+ place: r.place,
967
+ placeType: r.placeType,
968
+ }));
969
+ console.log(JSON.stringify({ ok: true, query, count: matches.length, since: options.since, until: options.until, placeFilter: options.place || null, matches }, null, 2));
970
+ }
971
+ }
278
972
  }
279
973
  catch (error) {
280
974
  console.log(JSON.stringify({ ok: false, error: error.message }, null, 2));
281
975
  process.exit(1);
282
976
  }
283
977
  });
284
- // squish consolidate --project-id <id> --min-age 90
978
+ // squish recent --period <period> - Show recent memories
285
979
  program
286
- .command('consolidate')
287
- .description('Trigger manual memory consolidation')
288
- .option('-p, --project-id <id>', 'Project ID', process.cwd())
289
- .option('-a, --min-age <number>', 'Minimum age in days', '90')
290
- .option('-i, --max-importance <number>', 'Maximum importance to consolidate', '30')
291
- .option('-t, --threshold <number>', 'Similarity threshold (0-1)', '0.7')
292
- .option('-l, --limit <number>', 'Max memories to process', '100')
980
+ .command('recent')
981
+ .description('Show recent memories by period')
982
+ .option('-p, --period <period>', 'Period: today, yesterday, thisweek, 7days, 30days, or custom like "3 days"', 'today')
983
+ .option('-s, --since <date>', 'Start date (alternative to --period)')
984
+ .option('-u, --until <date>', 'End date (alternative to --period)')
985
+ .option('-l, --limit <number>', 'Max results', '10')
986
+ .option('-P, --project <project>', 'Project path', getDefaultProjectPath())
293
987
  .action(async (options) => {
294
988
  try {
295
- const results = await consolidateMemoriesImpl({
296
- projectId: String(options.projectId),
297
- minAge: parseInt(options.minAge, 10),
298
- maxImportance: parseInt(options.maxImportance, 10),
299
- similarityThreshold: parseFloat(options.threshold),
300
- limit: parseInt(options.limit, 10),
301
- });
302
- console.log(JSON.stringify({ ok: true, consolidated: results.length, results }, null, 2));
989
+ let since, until;
990
+ if (options.since && options.until) {
991
+ since = options.since;
992
+ until = options.until;
993
+ }
994
+ else if (options.since) {
995
+ since = options.since;
996
+ until = 'now';
997
+ }
998
+ else {
999
+ const periodMap = {
1000
+ today: ['today', 'now'],
1001
+ yesterday: ['yesterday', 'today'],
1002
+ thisweek: ['thisweek', 'now'],
1003
+ '7days': ['7 days', 'now'],
1004
+ '14days': ['14 days', 'now'],
1005
+ '30days': ['30 days', 'now'],
1006
+ '90days': ['90 days', 'now'],
1007
+ };
1008
+ const mapped = periodMap[options.period];
1009
+ if (mapped) {
1010
+ [since, until] = mapped;
1011
+ }
1012
+ else {
1013
+ since = options.period;
1014
+ until = 'now';
1015
+ }
1016
+ }
1017
+ const results = await getRecent(options.project, 100);
1018
+ const filtered = filterByDateRange(results, since, until);
1019
+ const limited = filtered.slice(0, validateLimit(options.limit, 10, 1, 100));
1020
+ console.log(JSON.stringify({ ok: true, period: options.period, since, until, count: limited.length, results: limited }, null, 2));
303
1021
  }
304
1022
  catch (error) {
305
1023
  console.log(JSON.stringify({ ok: false, error: error.message }, null, 2));
306
1024
  process.exit(1);
307
1025
  }
308
1026
  });
309
- // squish consolidation-stats --project-id <id>
1027
+ // squish confidence <memoryId> [level] - Set or view confidence level
310
1028
  program
311
- .command('consolidation-stats')
312
- .description('Get consolidation statistics for a project')
313
- .option('-p, --project-id <id>', 'Project ID', process.cwd())
314
- .action(async (options) => {
1029
+ .command('confidence <memoryId> [level]')
1030
+ .description('Set or view confidence level (certain/speculative/outdated)')
1031
+ .action(async (memoryId, level) => {
315
1032
  try {
316
- const stats = await getConsolidationStats(String(options.projectId));
317
- console.log(JSON.stringify({ ok: true, ...stats }, null, 2));
1033
+ if (!level) {
1034
+ const memory = await getMemory(String(memoryId));
1035
+ if (!memory) {
1036
+ console.log(JSON.stringify({ ok: false, error: 'Memory not found' }, null, 2));
1037
+ process.exit(1);
1038
+ }
1039
+ console.log(JSON.stringify({ ok: true, memoryId, confidenceLevel: memory.confidenceLevel ?? 'certain' }, null, 2));
1040
+ }
1041
+ else {
1042
+ const validLevels = ['certain', 'speculative', 'outdated'];
1043
+ if (!validLevels.includes(level)) {
1044
+ console.log(JSON.stringify({ ok: false, error: 'Invalid level. Use: certain, speculative, or outdated' }, null, 2));
1045
+ process.exit(1);
1046
+ }
1047
+ await setConfidence(String(memoryId), level);
1048
+ console.log(JSON.stringify({ ok: true, memoryId, confidenceLevel: level }, null, 2));
1049
+ }
318
1050
  }
319
1051
  catch (error) {
320
1052
  console.log(JSON.stringify({ ok: false, error: error.message }, null, 2));
321
1053
  process.exit(1);
322
1054
  }
323
1055
  });
324
- // squish health
1056
+ // squish pin <memoryId> [--unpin]
325
1057
  program
326
- .command('health')
327
- .description('Check service health and configuration')
328
- .option('-j, --json', 'Output as JSON', false)
329
- .action(async (options) => {
1058
+ .command('pin <memoryId>')
1059
+ .description('Pin/unpin a memory to prevent pruning/consolidation')
1060
+ .option('-u, --unpin', 'Unpin the memory instead of pinning', false)
1061
+ .action(async (memoryId, options) => {
330
1062
  try {
331
- const dbHealth = await checkDatabaseHealth();
332
- const redisHealth = await checkRedisHealth();
333
- const dataDir = process.env.SQUISH_DATA_DIR || path.join(os.homedir(), '.squish');
334
- const dirExists = fs.existsSync(dataDir);
335
- const status = {
336
- version: VERSION,
337
- mode: config.isTeamMode ? 'team' : 'local',
338
- database: dbHealth ? 'ok' : 'error',
339
- cache: redisHealth ? 'ok' : 'unavailable',
340
- dataDirectory: dataDir,
341
- dataDirectoryExists: dirExists,
342
- timestamp: new Date().toISOString()
343
- };
344
- if (options.json) {
345
- console.log(JSON.stringify({ ok: true, ...status }, null, 2));
1063
+ if (options.unpin) {
1064
+ await unpinMemory(String(memoryId));
1065
+ console.log(JSON.stringify({ ok: true, memoryId, pinned: false }, null, 2));
346
1066
  }
347
1067
  else {
348
- console.log(`\n Squish Memory v${VERSION}`);
349
- console.log(` ====================`);
350
- console.log(` Mode: ${status.mode}`);
351
- console.log(` Database: ${status.database}`);
352
- console.log(` Cache: ${status.cache}`);
353
- console.log(` Data Dir: ${status.dataDirectory}`);
354
- console.log(` Status: ${dbHealth ? 'HEALTHY' : 'UNHEALTHY'}\n`);
355
- }
356
- if (!dbHealth) {
1068
+ await pinMemory(String(memoryId));
1069
+ console.log(JSON.stringify({ ok: true, memoryId, pinned: true }, null, 2));
1070
+ }
1071
+ }
1072
+ catch (error) {
1073
+ console.log(JSON.stringify({ ok: false, error: error.message }, null, 2));
1074
+ process.exit(1);
1075
+ }
1076
+ });
1077
+ // squish tag add <tag> --search <query> --confirm
1078
+ // squish tag remove <tag> --older-than "30 days" --confirm
1079
+ program
1080
+ .command('tag')
1081
+ .description('Manage tags on memories (bulk)')
1082
+ .argument('<action>', 'add or remove')
1083
+ .argument('<tag>', 'Tag name')
1084
+ .option('-s, --search <query>', 'Search query to match memories')
1085
+ .option('-o, --older-than <date>', 'Only tag memories older than (e.g., "30 days")')
1086
+ .option('-t, --type <type>', 'Filter by memory type')
1087
+ .option('-c, --confirm', 'Actually execute the changes (default is dry-run)', false)
1088
+ .option('-l, --limit <number>', 'Max memories to process', '50')
1089
+ .option('-p, --project <project>', 'Project path', getDefaultProjectPath())
1090
+ .action(async (action, tag, options) => {
1091
+ try {
1092
+ if (!options.search && !options.olderThan) {
1093
+ console.log(JSON.stringify({ ok: false, error: 'Provide --search <query> or --older-than <date>' }, null, 2));
1094
+ process.exit(1);
1095
+ }
1096
+ const limit = validateLimit(options.limit, 50, 1, 100);
1097
+ let results;
1098
+ const searchInput = { query: options.search, limit, project: options.project };
1099
+ if (options.type)
1100
+ searchInput.type = options.type;
1101
+ if (options.search) {
1102
+ results = await search(searchInput);
1103
+ }
1104
+ else {
1105
+ results = await getRecent(options.project, limit * 2);
1106
+ }
1107
+ let filtered = results;
1108
+ if (options.olderThan) {
1109
+ filtered = filterByDateRange(results, '', options.olderThan);
1110
+ }
1111
+ const db = await getDb();
1112
+ const schema = await getSchema();
1113
+ if (action === 'add') {
1114
+ const updated = [];
1115
+ for (const mem of filtered) {
1116
+ try {
1117
+ const tags = new Set((mem.tags || []));
1118
+ if (!tags.has(tag)) {
1119
+ tags.add(tag);
1120
+ await db.update(schema.memories)
1121
+ .set({ tags: serializeTags(Array.from(tags)), updatedAt: new Date() })
1122
+ .where(eq(schema.memories.id, mem.id));
1123
+ updated.push(mem.id);
1124
+ }
1125
+ }
1126
+ catch (e) {
1127
+ console.error('DEBUG: error updating', mem.id, e.message);
1128
+ throw e;
1129
+ }
1130
+ }
1131
+ console.log(JSON.stringify({ ok: true, action: 'add', tag, matched: filtered.length, updated: updated.length, dryRun: !options.confirm }, null, 2));
1132
+ }
1133
+ else if (action === 'remove') {
1134
+ const updated = [];
1135
+ for (const mem of filtered) {
1136
+ const tags = new Set((mem.tags || []));
1137
+ if (tags.has(tag)) {
1138
+ tags.delete(tag);
1139
+ await db.update(schema.memories)
1140
+ .set({ tags: serializeTags(Array.from(tags)), updatedAt: new Date() })
1141
+ .where(eq(schema.memories.id, mem.id));
1142
+ updated.push(mem.id);
1143
+ }
1144
+ }
1145
+ console.log(JSON.stringify({ ok: true, action: 'remove', tag, matched: filtered.length, updated: updated.length, dryRun: !options.confirm }, null, 2));
1146
+ }
1147
+ else {
1148
+ console.log(JSON.stringify({ ok: false, error: 'Use: squish tag add <tag> or squish tag remove <tag>' }, null, 2));
357
1149
  process.exit(1);
358
1150
  }
359
1151
  }
@@ -362,13 +1154,62 @@ async function runCliMode() {
362
1154
  process.exit(1);
363
1155
  }
364
1156
  });
1157
+ // squish stale --days 30 - Show old, low-confidence, unaccessed memories
1158
+ program
1159
+ .command('stale')
1160
+ .description('Show stale memories (old, low-confidence, or rarely accessed)')
1161
+ .option('-d, --days <number>', 'Show memories older than N days', '30')
1162
+ .option('-c, --confidence <level>', 'Max confidence level to show (outdated, speculative)', 'speculative')
1163
+ .option('-l, --limit <number>', 'Max results', '20')
1164
+ .option('-p, --project <project>', 'Project path', getDefaultProjectPath())
1165
+ .action(async (options) => {
1166
+ try {
1167
+ const days = validateLimit(options.days, 30, 1, 365);
1168
+ const cutoffDate = new Date(Date.now() - days * 86400000);
1169
+ // Get recent memories - larger limit to find stale ones
1170
+ const results = await getRecent(options.project, 500);
1171
+ const stale = results.filter((m) => {
1172
+ const created = m.createdAt ? new Date(m.createdAt) : null;
1173
+ const isOld = created && created < cutoffDate;
1174
+ const isLowConfidence = m.confidenceLevel === 'outdated' || m.confidenceLevel === 'speculative';
1175
+ const hasLowImportance = (m.importance || 50) < 40;
1176
+ return isOld || isLowConfidence || hasLowImportance;
1177
+ });
1178
+ const limited = stale.slice(0, validateLimit(options.limit, 20, 1, 100));
1179
+ const summary = {
1180
+ totalStale: stale.length,
1181
+ old: stale.filter((m) => m.createdAt && new Date(m.createdAt) < cutoffDate).length,
1182
+ lowConfidence: stale.filter((m) => m.confidenceLevel === 'outdated' || m.confidenceLevel === 'speculative').length,
1183
+ lowImportance: stale.filter((m) => (m.importance || 50) < 40).length,
1184
+ };
1185
+ console.log(JSON.stringify({ ok: true, summary, memories: limited }, null, 2));
1186
+ }
1187
+ catch (error) {
1188
+ console.log(JSON.stringify({ ok: false, error: error.message }, null, 2));
1189
+ process.exit(1);
1190
+ }
1191
+ });
365
1192
  // squish stats
366
1193
  program
367
1194
  .command('stats')
368
- .description('Get memory statistics')
369
- .option('-p, --project <project>', 'Project path', process.cwd())
1195
+ .description('View statistics')
1196
+ .option('-p, --project <project>', 'Project path', getDefaultProjectPath())
1197
+ .option('-m, --memory', 'Show memory file storage stats instead of database', false)
370
1198
  .action(async (options) => {
371
1199
  try {
1200
+ // Memory file stats
1201
+ if (options.memory) {
1202
+ const { getMemoryStats, isMemoryStorageAvailable } = await import('./core/memory/markdown/markdown-storage.js');
1203
+ const available = isMemoryStorageAvailable();
1204
+ if (!available) {
1205
+ console.log(JSON.stringify({ ok: false, error: 'Memory file storage not available' }, null, 2));
1206
+ process.exit(1);
1207
+ }
1208
+ const stats = await getMemoryStats();
1209
+ console.log(JSON.stringify({ ok: true, source: 'memory', ...stats }, null, 2));
1210
+ return;
1211
+ }
1212
+ // Database stats
372
1213
  const stats = await getMemoryStats(options.project);
373
1214
  console.log(JSON.stringify({ ok: true, ...stats }, null, 2));
374
1215
  }
@@ -377,475 +1218,460 @@ async function runCliMode() {
377
1218
  process.exit(1);
378
1219
  }
379
1220
  });
380
- // squish install (for OpenClaw self-install)
1221
+ // squish install
381
1222
  program
382
1223
  .command('install')
383
- .description('Install Squish for OpenClaw (self-configure MCP)')
384
- .option('-o, --openclaw-dir <dir>', 'OpenClaw directory')
385
- .option('-n, --dry-run', 'Show what would be done without making changes', false)
386
- .option('--skip-install', 'Skip global npm install step', false)
387
- .action(async (options) => {
388
- // Find the install script - it's in scripts/ relative to the package root
389
- // When running from dist/index.js, scripts is at ../scripts/
390
- const distDir = path.dirname(fileURLToPath(import.meta.url));
391
- const packageDir = path.dirname(distDir);
392
- const installScript = path.join(packageDir, 'scripts', 'install.mjs');
393
- // Check if the script exists
394
- if (!fs.existsSync(installScript)) {
395
- console.log(JSON.stringify({
396
- ok: false,
397
- error: `Install script not found at: ${installScript}`,
398
- hint: 'Please ensure squish-memory is installed correctly'
399
- }, null, 2));
400
- process.exit(1);
1224
+ .description('Run the interactive installer wizard')
1225
+ .action(async () => {
1226
+ await spawnInstallerWizard();
1227
+ });
1228
+ // squish note "my thought here" - quick brain dump
1229
+ program
1230
+ .command('note <content>')
1231
+ .description('Quick brain dump - store a raw memory to process later')
1232
+ .option('-p, --project <project>', 'Project path', getDefaultProjectPath())
1233
+ .action(async (content, options) => {
1234
+ try {
1235
+ const result = await rememberMemory({
1236
+ content,
1237
+ type: 'observation',
1238
+ tags: ['note', 'quick'],
1239
+ project: options.project,
1240
+ });
1241
+ console.log(JSON.stringify({ ok: true, message: 'Note saved', id: result.id }, null, 2));
401
1242
  }
402
- // Build command with quoted path for Windows compatibility
403
- const args = [`"${installScript}"`];
404
- if (options.dryRun)
405
- args.push('--dry-run');
406
- if (options.openclawDir)
407
- args.push('--openclaw-dir', `"${options.openclawDir}"`);
408
- if (options.skipInstall)
409
- args.push('--skip-install');
410
- const result = spawnSync('node', args, {
411
- stdio: 'inherit',
412
- shell: true,
413
- cwd: packageDir
414
- });
415
- if (result.status !== 0) {
416
- process.exit(result.status || 1);
1243
+ catch (error) {
1244
+ console.log(JSON.stringify({ ok: false, error: error.message }, null, 2));
1245
+ process.exit(1);
417
1246
  }
418
1247
  });
419
- await program.parseAsync(process.argv);
420
- }
421
- // ============================================================================
422
- // MCP MODE (for Claude Code) - DEFAULT
423
- // ============================================================================
424
- async function runMcpMode() {
425
- const TOOLS = [
426
- // Core Memory Tool
427
- {
428
- name: 'core_memory',
429
- description: 'View or edit your core memory (always-visible). Use this to see your persona, user info, project context, and working notes.',
430
- inputSchema: {
431
- type: 'object',
432
- properties: {
433
- action: { type: 'string', enum: ['view', 'edit', 'append'] },
434
- projectId: { type: 'string' },
435
- section: { type: 'string', enum: ['persona', 'user_info', 'project_context', 'working_notes'] },
436
- content: { type: 'string' },
437
- text: { type: 'string' },
438
- },
439
- required: ['action', 'projectId']
440
- }
441
- },
442
- // Context Paging
443
- {
444
- name: 'context_paging',
445
- description: 'Manage your working memory set. Load, evict, or view loaded memories.',
446
- inputSchema: {
447
- type: 'object',
448
- properties: {
449
- action: { type: 'string', enum: ['load', 'evict', 'view'] },
450
- sessionId: { type: 'string' },
451
- memoryId: { type: 'string' },
452
- },
453
- required: ['action', 'sessionId']
454
- }
455
- },
456
- {
457
- name: 'context_status',
458
- description: 'View comprehensive context window status and token usage',
459
- inputSchema: {
460
- type: 'object',
461
- properties: {
462
- sessionId: { type: 'string' },
463
- projectId: { type: 'string' },
464
- },
465
- required: ['sessionId', 'projectId']
466
- }
467
- },
468
- // Memory Tools
469
- {
470
- name: 'remember',
471
- description: 'Store information for future use. Perfect for facts, decisions, code snippets, configuration details, or user preferences.',
472
- inputSchema: {
473
- type: 'object',
474
- properties: {
475
- content: { type: 'string' },
476
- type: { type: 'string', enum: ['observation', 'fact', 'decision', 'context', 'preference'] },
477
- tags: { type: 'array', items: { type: 'string' } },
478
- project: { type: 'string' },
479
- metadata: { type: 'object' },
480
- },
481
- required: ['content']
482
- }
483
- },
484
- {
485
- name: 'recall',
486
- description: 'Retrieve a specific stored memory by ID',
487
- inputSchema: {
488
- type: 'object',
489
- properties: { id: { type: 'string' } },
490
- required: ['id']
491
- }
492
- },
493
- {
494
- name: 'search',
495
- description: 'Search your stored memories. Leave query empty to list recent memories.',
496
- inputSchema: {
497
- type: 'object',
498
- properties: {
499
- query: { type: 'string' },
500
- scope: { type: 'string', enum: ['memories', 'conversations', 'recent'], default: 'memories' },
501
- type: { type: 'string', enum: ['observation', 'fact', 'decision', 'context', 'preference'] },
502
- tags: { type: 'array', items: { type: 'string' } },
503
- limit: { type: 'number', default: 10 },
504
- project: { type: 'string' },
505
- }
506
- }
507
- },
508
- {
509
- name: 'observe',
510
- description: 'Record an observation about your work (tool usage, patterns, errors)',
511
- inputSchema: {
512
- type: 'object',
513
- properties: {
514
- type: { type: 'string', enum: ['tool_use', 'file_change', 'error', 'pattern', 'insight'] },
515
- action: { type: 'string' },
516
- target: { type: 'string' },
517
- summary: { type: 'string' },
518
- details: { type: 'object' },
519
- },
520
- required: ['type', 'action', 'summary']
521
- }
522
- },
523
- {
524
- name: 'context',
525
- description: 'Get project context',
526
- inputSchema: {
527
- type: 'object',
528
- properties: {
529
- project: { type: 'string' },
530
- include: { type: 'array', items: { type: 'string' }, default: ['memories', 'observations'] },
531
- limit: { type: 'number', default: 10 }
532
- },
533
- required: ['project']
534
- }
535
- },
536
- {
537
- name: 'init',
538
- description: 'Initialize Squish memory system for the current project',
539
- inputSchema: {
540
- type: 'object',
541
- properties: { projectPath: { type: 'string' } }
542
- }
543
- },
544
- {
545
- name: 'health',
546
- description: 'Check service status',
547
- inputSchema: { type: 'object', properties: {} }
548
- },
549
- {
550
- name: 'merge',
551
- description: 'Manage memory merges: detect, list, preview, approve, reject, reverse',
552
- inputSchema: {
553
- type: 'object',
554
- properties: {
555
- action: { type: 'string', enum: ['detect', 'list', 'preview', 'stats', 'approve', 'reject', 'reverse'] },
556
- projectId: { type: 'string' },
557
- proposalId: { type: 'string' },
558
- threshold: { type: 'number' },
559
- },
560
- required: ['action']
561
- }
562
- },
563
- {
564
- name: 'qmd_search',
565
- description: 'Search memories using QMD hybrid search (BM25 + vector + rerank)',
566
- inputSchema: {
567
- type: 'object',
568
- properties: {
569
- query: { type: 'string' },
570
- type: { type: 'string', enum: ['observation', 'fact', 'decision', 'context', 'preference'] },
571
- limit: { type: 'number', default: 10 },
572
- },
573
- required: ['query']
574
- }
575
- },
576
- // v0.8.0: Importance Scoring Tools
577
- {
578
- name: 'set_importance',
579
- description: 'Manually set importance score for a memory (0-100)',
580
- inputSchema: {
581
- type: 'object',
582
- properties: {
583
- memoryId: { type: 'string' },
584
- importance: { type: 'number', minimum: 0, maximum: 100 },
585
- },
586
- required: ['memoryId', 'importance']
587
- }
588
- },
589
- {
590
- name: 'pin_memory',
591
- description: 'Pin a memory to prevent pruning/consolidation (or unpin it)',
592
- inputSchema: {
593
- type: 'object',
594
- properties: {
595
- memoryId: { type: 'string' },
596
- pinned: { type: 'boolean', default: true },
597
- },
598
- required: ['memoryId']
599
- }
600
- },
601
- // v0.8.0: Consolidation Tool
602
- {
603
- name: 'consolidate',
604
- description: 'Trigger manual memory consolidation - summarizes old, low-importance memories',
605
- inputSchema: {
606
- type: 'object',
607
- properties: {
608
- projectId: { type: 'string' },
609
- threshold: { type: 'number', default: 0.7 },
610
- minAge: { type: 'number', default: 90 },
611
- limit: { type: 'number', default: 100 },
612
- },
613
- required: ['projectId']
614
- }
615
- },
616
- {
617
- name: 'consolidation_stats',
618
- description: 'Get consolidation statistics for a project',
619
- inputSchema: {
620
- type: 'object',
621
- properties: {
622
- projectId: { type: 'string' },
623
- },
624
- required: ['projectId']
1248
+ // squish context - Show project context (memories + observations + places)
1249
+ program
1250
+ .command('context')
1251
+ .description('Show project context or list available projects')
1252
+ .option('-p, --project <project>', 'Project path', getDefaultProjectPath())
1253
+ .option('-l, --limit <number>', 'Number of items to show', '10')
1254
+ .option('-i, --include <items>', 'What to include: memories, observations, entities, places', 'memories,observations,places')
1255
+ .option('--list-projects', 'List registered projects instead of loading context', false)
1256
+ .option('-j, --json', 'Output as JSON', false)
1257
+ .option('--place <type>', 'Filter by place type: entry_hall, library, workshop, lab, office, garden, archive')
1258
+ .option('--tier <level>', 'Disclosure level: quick (place names), medium (top 3), full (all)', 'medium')
1259
+ .option('--has-memories', 'Only show places with memories', true)
1260
+ .option('--sync', 'Recalculate memory counts for all places', false)
1261
+ .option('--archive', 'Move memories > 30 days to Archive place', false)
1262
+ .option('--task <description>', 'Task description for auto-place detection (e.g., "fix bug", "design API")')
1263
+ .action(async (options) => {
1264
+ try {
1265
+ // Auto-detect place from task if provided
1266
+ let placeFilter = options.place || null;
1267
+ if (options.task && !placeFilter) {
1268
+ // Simple keyword detection
1269
+ const task = options.task.toLowerCase();
1270
+ if (task.includes('fix') || task.includes('bug') || task.includes('error'))
1271
+ placeFilter = 'workshop';
1272
+ else if (task.includes('design') || task.includes('plan') || task.includes('api'))
1273
+ placeFilter = 'library';
1274
+ else if (task.includes('task') || task.includes('todo') || task.includes('manage'))
1275
+ placeFilter = 'office';
1276
+ else if (task.includes('test') || task.includes('experiment'))
1277
+ placeFilter = 'lab';
1278
+ if (placeFilter)
1279
+ console.log(`Auto-detected place: ${placeFilter}`);
625
1280
  }
626
- }
627
- ];
628
- class Squish {
629
- server;
630
- projectPath;
631
- constructor() {
632
- this.projectPath = process.env.CLAUDE_WORKING_DIRECTORY || process.cwd();
633
- this.server = new Server({ name: 'squish', version: VERSION }, {
634
- capabilities: { tools: {} },
635
- });
636
- this.setup();
637
- }
638
- async onSessionInitialized() {
639
- if (!shouldAutoLoad()) {
640
- logger.info('[Session] Auto-load disabled');
1281
+ if (options.listProjects) {
1282
+ const projects = await getAllProjects();
1283
+ if (options.json) {
1284
+ console.log(JSON.stringify({ ok: true, count: projects.length, projects }, null, 2));
1285
+ }
1286
+ else {
1287
+ console.log(`\n Registered Projects (${projects.length})`);
1288
+ console.log(` ================================`);
1289
+ for (const project of projects) {
1290
+ console.log(`\n ${project.name}`);
1291
+ console.log(` Path: ${project.path}`);
1292
+ console.log(` ID: ${project.id}`);
1293
+ }
1294
+ console.log('');
1295
+ }
641
1296
  return;
642
1297
  }
643
- try {
644
- logger.info('[Session] Performing auto-load...');
645
- const result = await performAutoLoad(this.projectPath, getAutoLoadConfig());
646
- if (result.warnings.length > 0) {
647
- logger.warn('[Session] Auto-load warnings:', result.warnings);
648
- }
649
- logger.info(`[Session] Auto-load complete: ${result.memoriesLoaded} memories, ~${result.tokensUsed} tokens`);
1298
+ // Get project context
1299
+ const projectPath = resolveProjectPath(options.project);
1300
+ await ensureProject(projectPath);
1301
+ const project = await getOrCreateProject(projectPath);
1302
+ if (!project) {
1303
+ console.log(JSON.stringify({ ok: false, error: 'Project not found' }, null, 2));
1304
+ process.exit(1);
650
1305
  }
651
- catch (error) {
652
- logger.error('[Session] Auto-load failed:', error);
1306
+ const limit = parseInt(options.limit);
1307
+ const include = (options.include || 'memories,observations,places').split(',');
1308
+ const tier = options.tier || 'full';
1309
+ const hasMemoriesOnly = options.hasMemories !== false; // Default true now
1310
+ const existingPlaceFilter = options.place || null;
1311
+ const result = { project: project.name, tier };
1312
+ // Get memories
1313
+ if (include.includes('memories')) {
1314
+ const memories = await getRecent(projectPath, limit);
1315
+ result.memories = memories.map((m) => ({
1316
+ id: m.id,
1317
+ type: m.type,
1318
+ content: m.content?.substring(0, 100),
1319
+ tags: m.tags,
1320
+ }));
653
1321
  }
654
- }
655
- setup() {
656
- this.server.setRequestHandler(ListToolsRequestSchema, async () => ({
657
- tools: TOOLS
658
- }));
659
- this.server.setRequestHandler(CallToolRequestSchema, async (req) => {
660
- const { name } = req.params;
661
- const args = (req.params.arguments ?? {});
662
- try {
663
- switch (name) {
664
- case 'core_memory':
665
- return await this.handleCoreMemory(args);
666
- case 'context_paging':
667
- return await this.handleContextPaging(args);
668
- case 'context_status': {
669
- const result = await getContextStatus(String(args.sessionId), String(args.projectId));
670
- return this.jsonResponse({ ok: true, ...result });
671
- }
672
- case 'remember': {
673
- return this.jsonResponse({ ok: true, data: await rememberMemory(args) });
674
- }
675
- case 'recall': {
676
- const memory = await getMemoryById(String(args.id));
677
- return this.jsonResponse({ ok: true, found: !!memory, data: memory });
678
- }
679
- case 'search': {
680
- return this.jsonResponse({ ok: true, data: await searchMemories(args) });
681
- }
682
- case 'observe':
683
- return this.jsonResponse({ ok: true, data: await createObservation(args) });
684
- case 'context':
685
- return this.jsonResponse({ ok: true, data: await getProjectContext(args) });
686
- case 'init': {
687
- await ensureDataDirectory();
688
- const project = await ensureProject(args.projectPath || process.cwd());
689
- return this.jsonResponse({ success: true, project });
690
- }
691
- case 'health':
692
- return this.health();
693
- case 'merge':
694
- return await this.handleMerge(args);
695
- case 'qmd_search': {
696
- const available = await isQMDAvailable();
697
- if (!available) {
698
- return this.jsonResponse({ ok: true, qmdAvailable: false, data: await searchMemories(args) });
699
- }
700
- return this.jsonResponse({ ok: true, qmdAvailable: true, data: await searchWithQMD(args) });
701
- }
702
- // v0.8.0: Importance scoring tools
703
- case 'set_importance': {
704
- await setImportanceScore(String(args.memoryId), Number(args.importance));
705
- return this.jsonResponse({
706
- ok: true,
707
- message: `Importance score set to ${args.importance} for memory ${args.memoryId}`
1322
+ // Get observations (learnings)
1323
+ if (include.includes('observations')) {
1324
+ const { getObservations } = await import('./core/ingestion/learnings.js');
1325
+ const observations = await getObservations(projectPath, limit);
1326
+ result.observations = observations.map((o) => ({
1327
+ id: o.id,
1328
+ type: o.type,
1329
+ content: o.content?.substring(0, 100),
1330
+ }));
1331
+ }
1332
+ // Get places (spatial memory) with filtering
1333
+ if (include.includes('places')) {
1334
+ const { initializeDefaultPlaces, getProjectPlaces, walkPlace, getPlaceByType, syncAllPlaceMemoryCounts } = await import('./core/places/index.js');
1335
+ await initializeDefaultPlaces(project.id);
1336
+ // Sync memory counts if requested
1337
+ if (options.sync) {
1338
+ await syncAllPlaceMemoryCounts(project.id);
1339
+ console.log('Synced memory counts for all places.');
1340
+ }
1341
+ // Auto-archive old memories if requested
1342
+ if (options.archive) {
1343
+ const { autoArchiveOldMemories } = await import('./core/places/index.js');
1344
+ const archiveResult = await autoArchiveOldMemories(project.id, 30);
1345
+ console.log(`Archived ${archiveResult.archived} old memories to Archive (${archiveResult.failed} failed).`);
1346
+ }
1347
+ let places = await getProjectPlaces(project.id);
1348
+ // Apply --has-memories filter
1349
+ if (hasMemoriesOnly) {
1350
+ places = places.filter((p) => p.memoryCount > 0);
1351
+ }
1352
+ // Apply --place filter (from --place or --task auto-detect)
1353
+ if (placeFilter) {
1354
+ const filtered = places.filter((p) => p.placeType === placeFilter);
1355
+ if (filtered.length > 0) {
1356
+ places = filtered;
1357
+ }
1358
+ }
1359
+ // Format places based on tier
1360
+ if (tier === 'quick') {
1361
+ // Just place names (~50 tokens)
1362
+ result.places = places.map((p) => ({
1363
+ name: p.name,
1364
+ type: p.placeType,
1365
+ }));
1366
+ }
1367
+ else if (tier === 'medium') {
1368
+ // Top 3 memories per place (~170 tokens)
1369
+ const placesWithMemories = [];
1370
+ for (const p of places) {
1371
+ if (p.memoryCount > 0) {
1372
+ const walkResult = await walkPlace(project.id, p.placeType, {
1373
+ tokenBudget: 170,
1374
+ maxMemoriesPerPlace: 3,
1375
+ compressWithToon: false,
708
1376
  });
709
- }
710
- case 'pin_memory': {
711
- const pinned = args.pinned !== undefined ? Boolean(args.pinned) : true;
712
- if (pinned) {
713
- await pinMemory(String(args.memoryId));
714
- }
715
- else {
716
- await unpinMemory(String(args.memoryId));
717
- }
718
- return this.jsonResponse({
719
- ok: true,
720
- message: `Memory ${args.memoryId} ${pinned ? 'pinned' : 'unpinned'}`
1377
+ placesWithMemories.push({
1378
+ name: p.name,
1379
+ type: p.placeType,
1380
+ purpose: p.purpose,
1381
+ memories: p.memoryCount,
1382
+ preview: walkResult?.memories.slice(0, 3).map((m) => m.content?.substring(0, 80)) || [],
721
1383
  });
722
1384
  }
723
- // v0.8.0: Consolidation tools
724
- case 'consolidate': {
725
- const results = await consolidateMemoriesImpl({
726
- projectId: String(args.projectId),
727
- minAge: args.minAge ? Number(args.minAge) : 90,
728
- maxImportance: 30,
729
- similarityThreshold: args.threshold ? Number(args.threshold) : 0.7,
730
- limit: args.limit ? Number(args.limit) : 100,
731
- });
732
- return this.jsonResponse({
733
- ok: true,
734
- consolidated: results.length,
735
- results
736
- });
1385
+ }
1386
+ result.places = placesWithMemories;
1387
+ }
1388
+ else {
1389
+ // Full - all memories (~500 tokens)
1390
+ result.places = places.map((p) => ({
1391
+ name: p.name,
1392
+ type: p.placeType,
1393
+ purpose: p.purpose,
1394
+ memories: p.memoryCount,
1395
+ }));
1396
+ }
1397
+ }
1398
+ if (options.json) {
1399
+ console.log(JSON.stringify({ ok: true, ...result }, null, 2));
1400
+ }
1401
+ else {
1402
+ // Human readable output
1403
+ console.log(`\n=== ${project.name} Context ===\n`);
1404
+ if (result.places && result.places.length > 0) {
1405
+ console.log('Spatial Memory Places:');
1406
+ result.places.forEach((p) => {
1407
+ if (tier === 'quick') {
1408
+ console.log(` ${p.name} (${p.type})`);
1409
+ }
1410
+ else if (tier === 'medium') {
1411
+ console.log(` ${p.name} (${p.memories} memories) - ${p.purpose}`);
1412
+ if (p.preview && p.preview.length > 0) {
1413
+ p.preview.forEach((m) => {
1414
+ console.log(` - ${m}...`);
1415
+ });
1416
+ }
737
1417
  }
738
- case 'consolidation_stats': {
739
- const stats = await getConsolidationStats(String(args.projectId));
740
- return this.jsonResponse({ ok: true, ...stats });
1418
+ else {
1419
+ console.log(` ${p.name} (${p.memories} memories) - ${p.purpose}`);
741
1420
  }
742
- default:
743
- throw new McpError(ErrorCode.MethodNotFound, `Unknown tool: ${name}`);
744
- }
1421
+ });
1422
+ console.log('');
745
1423
  }
746
- catch (error) {
747
- if (error instanceof McpError)
748
- throw error;
749
- throw new McpError(ErrorCode.InternalError, `Tool '${name}' failed`);
1424
+ if (result.memories && result.memories.length > 0) {
1425
+ console.log('Recent Memories:');
1426
+ result.memories.slice(0, 5).forEach((m) => {
1427
+ console.log(` [${m.type}] ${m.content}`);
1428
+ });
1429
+ console.log('');
750
1430
  }
1431
+ }
1432
+ }
1433
+ catch (error) {
1434
+ console.log(JSON.stringify({ ok: false, error: error.message }, null, 2));
1435
+ process.exit(1);
1436
+ }
1437
+ });
1438
+ // squish migrate - Migrate memories between databases
1439
+ program
1440
+ .command('migrate')
1441
+ .description('Migrate memories from one .squish directory to another')
1442
+ .option('-f, --from <path>', 'Source .squish directory (read from)', '')
1443
+ .option('-t, --to <path>', 'Target .squish directory (write to)', '')
1444
+ .option('--delete-source', 'Delete source after migration (use with caution)', false)
1445
+ .option('--dry-run', 'Preview migration without applying', false)
1446
+ .action(async (options) => {
1447
+ try {
1448
+ if (!options.from || !options.to) {
1449
+ console.log(JSON.stringify({
1450
+ ok: false,
1451
+ error: 'Usage: squish migrate --from /path/to/old/.squish --to /path/to/new/.squish'
1452
+ }, null, 2));
1453
+ process.exit(1);
1454
+ }
1455
+ const sourcePath = path.join(options.from, 'squish.db');
1456
+ const targetPath = path.join(options.to, 'squish.db');
1457
+ if (!existsSync(sourcePath)) {
1458
+ console.log(JSON.stringify({ ok: false, error: `Source database not found: ${sourcePath}` }, null, 2));
1459
+ process.exit(1);
1460
+ }
1461
+ if (!existsSync(targetPath)) {
1462
+ console.log(JSON.stringify({ ok: false, error: `Target database not found: ${targetPath}` }, null, 2));
1463
+ process.exit(1);
1464
+ }
1465
+ console.log(`Migrating memories from:\n ${options.from}\nto:\n ${options.to}\n`);
1466
+ // Import database modules dynamically
1467
+ const { migrateMemories } = await import('./core/memory/migrate.js');
1468
+ const result = await migrateMemories(options.from, options.to, {
1469
+ dryRun: options.dryRun,
1470
+ deleteSource: options.deleteSource
751
1471
  });
752
- this.server.onerror = (e) => logger.error('MCP Server error', e);
753
- process.on('SIGINT', () => this.shutdown());
754
- process.on('SIGTERM', () => this.shutdown());
1472
+ console.log(JSON.stringify({ ok: true, ...result }, null, 2));
755
1473
  }
756
- jsonResponse(payload) {
757
- return {
758
- content: [{ type: 'text', text: JSON.stringify(payload, null, 2) }]
759
- };
1474
+ catch (error) {
1475
+ console.log(JSON.stringify({ ok: false, error: error.message }, null, 2));
1476
+ process.exit(1);
760
1477
  }
761
- async handleCoreMemory(args) {
762
- const action = args.action;
763
- const projectId = String(args.projectId);
764
- await initializeCoreMemory(projectId);
765
- const actions = {
766
- view: async () => {
767
- const content = await getCoreMemory(projectId);
768
- const stats = await getCoreMemoryStats(projectId);
769
- return this.jsonResponse({ ok: true, action: 'view', content, stats });
770
- },
771
- edit: async () => {
772
- const result = await editCoreMemorySection(projectId, args.section, String(args.content));
773
- return this.jsonResponse({ ok: true, action: 'edit', ...result });
774
- },
775
- append: async () => {
776
- const result = await appendCoreMemorySection(projectId, args.section, String(args.text));
777
- return this.jsonResponse({ ok: true, action: 'append', ...result });
1478
+ });
1479
+ // squish clean - Run deduplication and consolidation
1480
+ program
1481
+ .command('clean')
1482
+ .description('Run maintenance: deduplication + consolidation')
1483
+ .option('-t, --threshold <number>', 'Similarity threshold for dedup (0-1)', '0.85')
1484
+ .option('-d, --min-age <days>', 'Minimum age for consolidation', '90')
1485
+ .option('-i, --max-importance <number>', 'Max importance to consolidate (0-100)', '30')
1486
+ .option('-c, --min-cluster <number>', 'Minimum cluster size', '3')
1487
+ .option('-p, --project <project>', 'Project path', getDefaultProjectPath())
1488
+ .option('--dry-run', 'Preview changes without applying', false)
1489
+ .action(async (options) => {
1490
+ try {
1491
+ console.log('Running maintenance: deduplication + consolidation...\n');
1492
+ // Step 1: Deduplication
1493
+ console.log('Step 1: Finding duplicate memories...');
1494
+ const dedupResult = await runDeduplicationJob(options.project);
1495
+ console.log(` Found ${dedupResult.duplicatesFound} duplicates, merged ${dedupResult.mergedCount}`);
1496
+ // Step 2: Consolidation
1497
+ console.log('\nStep 2: Consolidating old memories...');
1498
+ const consolidateResult = await runFullConsolidationJob(options.project);
1499
+ console.log(` Clustered ${consolidateResult.clustered}, merged ${consolidateResult.merged}, consolidated ${consolidateResult.consolidated}`);
1500
+ console.log(JSON.stringify({
1501
+ ok: true,
1502
+ dedup: {
1503
+ duplicatesFound: dedupResult.duplicatesFound,
1504
+ mergedCount: dedupResult.mergedCount,
1505
+ tokensRecovered: dedupResult.tokensRecovered
778
1506
  },
779
- };
780
- const handler = actions[action];
781
- if (!handler)
782
- throw new McpError(ErrorCode.InvalidParams, `Unknown action: ${action}`);
783
- return handler();
784
- }
785
- async handleContextPaging(args) {
786
- const action = args.action;
787
- const sessionId = String(args.sessionId);
788
- const actions = {
789
- load: () => loadMemoryToContext(sessionId, String(args.memoryId)),
790
- evict: () => evictMemoryFromContext(sessionId, String(args.memoryId)),
791
- view: () => viewLoadedMemories(sessionId),
792
- };
793
- const handler = actions[action];
794
- if (!handler)
795
- throw new McpError(ErrorCode.InvalidParams, `Unknown action: ${action}`);
796
- return this.jsonResponse(await handler());
797
- }
798
- async handleMerge(args) {
799
- const action = args.action;
800
- const handlers = {
801
- detect: () => handleDetectDuplicates(args),
802
- list: () => handleListProposals(args),
803
- preview: () => handlePreviewMerge(args),
804
- stats: () => handleGetMergeStats(args),
805
- approve: () => handleApproveMerge(args),
806
- reject: () => handleRejectMerge(args),
807
- reverse: () => handleReverseMerge(args),
808
- };
809
- const handler = handlers[action];
810
- if (!handler)
811
- throw new McpError(ErrorCode.InvalidParams, `Unknown action: ${action}`);
812
- return this.jsonResponse(await handler());
1507
+ consolidate: {
1508
+ clustered: consolidateResult.clustered,
1509
+ merged: consolidateResult.merged,
1510
+ consolidated: consolidateResult.consolidated
1511
+ }
1512
+ }, null, 2));
813
1513
  }
814
- async shutdown() {
815
- await closeCache();
816
- process.exit(0);
1514
+ catch (error) {
1515
+ console.log(JSON.stringify({ ok: false, error: error.message }, null, 2));
817
1516
  }
818
- async health() {
819
- const dbOk = await checkDatabaseHealth();
820
- const redisOk = await checkRedisHealth();
821
- return this.jsonResponse({
822
- version: VERSION,
823
- mode: config.isTeamMode ? 'team' : 'local',
824
- status: dbOk ? 'ok' : 'error',
825
- });
1517
+ });
1518
+ // squish hooks session-start --agent claude-code --mode startup
1519
+ program
1520
+ .command('hooks')
1521
+ .description('Handle agent hooks (session-start, post-tool-use, session-end, pre-compact)')
1522
+ .argument('<event>', 'Event type: session-start, post-tool-use, session-end, pre-compact')
1523
+ .option('-a, --agent <agent>', 'Agent type: claude-code, opencode, cursor, windsurf', 'claude-code')
1524
+ .option('-m, --mode <mode>', 'Mode for session-start: startup, resume, compact', 'startup')
1525
+ .option('-p, --project <project>', 'Project path', getDefaultProjectPath())
1526
+ .option('-t, --tool <tool>', 'Tool name for post-tool-use')
1527
+ .option('--tool-input <json>', 'Tool input as JSON string')
1528
+ .option('--tool-result <json>', 'Tool result as JSON string')
1529
+ .option('--wip <work>', 'Work in progress for session-end')
1530
+ .action(async (event, options) => {
1531
+ try {
1532
+ const agentType = options.agent;
1533
+ const projectPath = options.project;
1534
+ let result;
1535
+ switch (event) {
1536
+ case 'session-start':
1537
+ result = await handleSessionStart({
1538
+ projectPath,
1539
+ mode: options.mode,
1540
+ agentType,
1541
+ });
1542
+ console.log(JSON.stringify({ ok: true, ...result }, null, 2));
1543
+ break;
1544
+ case 'post-tool-use':
1545
+ if (!options.tool) {
1546
+ console.log(JSON.stringify({ ok: false, error: '--tool required for post-tool-use' }, null, 2));
1547
+ process.exit(1);
1548
+ }
1549
+ const toolInput = options.toolInput ? JSON.parse(options.toolInput) : {};
1550
+ const toolResult = options.toolResult ? JSON.parse(options.toolResult) : {};
1551
+ result = await handlePostToolUse({
1552
+ toolName: options.tool,
1553
+ toolInput,
1554
+ toolResult,
1555
+ projectPath,
1556
+ agentType,
1557
+ });
1558
+ console.log(JSON.stringify({ ok: true, ...result }, null, 2));
1559
+ break;
1560
+ case 'session-end':
1561
+ result = await handleSessionEnd({
1562
+ projectPath,
1563
+ agentType,
1564
+ workInProgress: options.wip,
1565
+ });
1566
+ console.log(JSON.stringify({ ok: true, ...result }, null, 2));
1567
+ break;
1568
+ case 'pre-compact':
1569
+ result = await handlePreCompact({
1570
+ projectPath,
1571
+ agentType,
1572
+ });
1573
+ console.log(JSON.stringify({ ok: true, ...result }, null, 2));
1574
+ break;
1575
+ default:
1576
+ console.log(JSON.stringify({ ok: false, error: `Unknown event: ${event}` }, null, 2));
1577
+ process.exit(1);
1578
+ }
1579
+ }
1580
+ catch (error) {
1581
+ console.log(JSON.stringify({ ok: false, error: error.message }, null, 2));
1582
+ process.exit(1);
826
1583
  }
827
- async run() {
828
- // Verify plugin manifest (universal plugin self-check)
829
- const manifest = loadPluginManifest();
830
- const verification = verifyManifest(manifest);
831
- if (!verification.ok) {
832
- logger.warn('Plugin manifest verification failed:', verification.errors);
1584
+ });
1585
+ // squish walk - Walk through spatial memory places
1586
+ program
1587
+ .command('walk [place]')
1588
+ .description('Walk through spatial memory places (entry_hall, library, workshop, lab, office, garden, archive, or --all)')
1589
+ .option('-a, --all', 'Walk all places in order', false)
1590
+ .option('-t, --tokens <number>', 'Max tokens budget (default: 170)', '170')
1591
+ .option('-m, --max <number>', 'Max memories per place', '10')
1592
+ .option('-p, --project <project>', 'Project path', getDefaultProjectPath())
1593
+ .option('-q, --quick', 'Quick tour (place names only)', false)
1594
+ .option('-j, --json', 'Output as JSON', false)
1595
+ .action(async (place, options) => {
1596
+ try {
1597
+ const projectPath = resolveProjectPath(options.project);
1598
+ await ensureProject(projectPath);
1599
+ const project = await getOrCreateProject(projectPath);
1600
+ if (!project) {
1601
+ console.log(JSON.stringify({ ok: false, error: 'Project not found' }, null, 2));
1602
+ process.exit(1);
1603
+ }
1604
+ // Ensure places are initialized
1605
+ await initializeDefaultPlaces(project.id);
1606
+ if (options.quick) {
1607
+ const tour = await quickTour(project.id);
1608
+ if (options.json) {
1609
+ console.log(JSON.stringify({ ok: true, ...tour }, null, 2));
1610
+ }
1611
+ else {
1612
+ console.log(`\n=== Spatial Memory Tour ===\n`);
1613
+ console.log(`Total Memories: ${tour.totalMemories}\n`);
1614
+ for (const p of tour.places) {
1615
+ console.log(`${p.name} (${p.memoryCount} memories)`);
1616
+ console.log(` ${p.purpose}\n`);
1617
+ }
1618
+ }
1619
+ return;
1620
+ }
1621
+ const tokenBudget = parseInt(options.tokens);
1622
+ const maxMemories = parseInt(options.max);
1623
+ if (options.all) {
1624
+ const results = await walkAllPlaces(project.id, {
1625
+ tokenBudget: Math.floor(tokenBudget / 7),
1626
+ maxMemoriesPerPlace: maxMemories,
1627
+ compressWithToon: true,
1628
+ });
1629
+ if (options.json) {
1630
+ console.log(JSON.stringify({ ok: true, places: results }, null, 2));
1631
+ }
1632
+ else {
1633
+ console.log(`\n=== Walking All Places ===\n`);
1634
+ for (const r of results) {
1635
+ console.log(`## ${r.place.name} (${r.memories.length} memories, ~${r.totalTokens} tokens)`);
1636
+ r.memories.forEach((m, i) => {
1637
+ console.log(` ${i + 1}. ${m.content.substring(0, 60)}...`);
1638
+ });
1639
+ console.log('');
1640
+ }
1641
+ }
1642
+ }
1643
+ else if (place) {
1644
+ const validPlaces = ['entry_hall', 'library', 'workshop', 'lab', 'office', 'garden', 'archive'];
1645
+ const placeType = validPlaces.includes(place) ? place : 'workshop';
1646
+ const result = await walkPlace(project.id, placeType, {
1647
+ tokenBudget,
1648
+ maxMemoriesPerPlace: maxMemories,
1649
+ compressWithToon: true,
1650
+ });
1651
+ if (options.json) {
1652
+ console.log(JSON.stringify({ ok: true, ...result }, null, 2));
1653
+ }
1654
+ else if (result) {
1655
+ console.log(`\n=== ${result.place.name} ===\n`);
1656
+ console.log(`Purpose: ${result.place.purpose || 'N/A'}\n`);
1657
+ result.memories.forEach((m, i) => {
1658
+ console.log(`${i + 1}. [${m.type}] ${m.content.substring(0, 80)}`);
1659
+ });
1660
+ console.log(`\n~${result.totalTokens} tokens`);
1661
+ }
1662
+ else {
1663
+ console.log(JSON.stringify({ ok: false, error: `Place not found: ${place}` }, null, 2));
1664
+ }
833
1665
  }
834
- else {
835
- logger.info(`Squish v${VERSION} - Plugin manifest verified`);
836
- }
837
- const transport = new StdioServerTransport();
838
- await this.server.connect(transport);
839
- logger.info(`v${VERSION}`);
840
- registerJobHandler('nightly_maintenance', runNightlyJob);
841
- registerJobHandler('weekly_maintenance', runWeeklyJob);
842
- await initializeScheduler();
843
- startHeartbeatChecking();
844
- await this.onSessionInitialized();
845
- await heartbeat();
846
- startWebServer();
847
1666
  }
848
- }
849
- new Squish().run();
1667
+ catch (error) {
1668
+ console.log(JSON.stringify({ ok: false, error: error.message }, null, 2));
1669
+ process.exit(1);
1670
+ }
1671
+ });
1672
+ await program.parseAsync(process.argv);
850
1673
  }
1674
+ // MCP server: core/commands/mcp-server.ts
1675
+ // Run with: npx squish-mcp
1676
+ // ============================================================================
851
1677
  //# sourceMappingURL=index.js.map