@soleri/core 9.2.0 → 9.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (298) hide show
  1. package/data/flows/build.flow.yaml +8 -9
  2. package/data/flows/deliver.flow.yaml +9 -10
  3. package/data/flows/design.flow.yaml +3 -4
  4. package/data/flows/enhance.flow.yaml +5 -6
  5. package/data/flows/explore.flow.yaml +3 -4
  6. package/data/flows/fix.flow.yaml +5 -6
  7. package/data/flows/plan.flow.yaml +4 -5
  8. package/data/flows/review.flow.yaml +3 -4
  9. package/dist/curator/curator.d.ts.map +1 -1
  10. package/dist/curator/curator.js +98 -22
  11. package/dist/curator/curator.js.map +1 -1
  12. package/dist/engine/bin/soleri-engine.js.map +1 -1
  13. package/dist/engine/module-manifest.d.ts.map +1 -1
  14. package/dist/engine/module-manifest.js +21 -1
  15. package/dist/engine/module-manifest.js.map +1 -1
  16. package/dist/engine/register-engine.d.ts.map +1 -1
  17. package/dist/engine/register-engine.js +25 -1
  18. package/dist/engine/register-engine.js.map +1 -1
  19. package/dist/flows/gate-evaluator.js.map +1 -1
  20. package/dist/operator/operator-profile.d.ts.map +1 -1
  21. package/dist/operator/operator-profile.js +11 -5
  22. package/dist/operator/operator-profile.js.map +1 -1
  23. package/dist/operator/operator-signals.d.ts.map +1 -1
  24. package/dist/operator/operator-signals.js.map +1 -1
  25. package/dist/planning/evidence-collector.js.map +1 -1
  26. package/dist/planning/gap-passes.d.ts.map +1 -1
  27. package/dist/planning/gap-passes.js +23 -6
  28. package/dist/planning/gap-passes.js.map +1 -1
  29. package/dist/planning/gap-patterns.d.ts.map +1 -1
  30. package/dist/planning/gap-patterns.js +57 -11
  31. package/dist/planning/gap-patterns.js.map +1 -1
  32. package/dist/planning/github-projection.d.ts.map +1 -1
  33. package/dist/planning/github-projection.js +39 -20
  34. package/dist/planning/github-projection.js.map +1 -1
  35. package/dist/planning/impact-analyzer.d.ts.map +1 -1
  36. package/dist/planning/impact-analyzer.js +20 -18
  37. package/dist/planning/impact-analyzer.js.map +1 -1
  38. package/dist/planning/plan-lifecycle.d.ts.map +1 -1
  39. package/dist/planning/plan-lifecycle.js +22 -9
  40. package/dist/planning/plan-lifecycle.js.map +1 -1
  41. package/dist/planning/planner.d.ts.map +1 -1
  42. package/dist/planning/planner.js +60 -17
  43. package/dist/planning/planner.js.map +1 -1
  44. package/dist/planning/rationalization-detector.d.ts.map +1 -1
  45. package/dist/planning/rationalization-detector.js.map +1 -1
  46. package/dist/planning/reconciliation-engine.d.ts.map +1 -1
  47. package/dist/planning/reconciliation-engine.js.map +1 -1
  48. package/dist/planning/task-verifier.d.ts.map +1 -1
  49. package/dist/planning/task-verifier.js +14 -6
  50. package/dist/planning/task-verifier.js.map +1 -1
  51. package/dist/runtime/admin-setup-ops.d.ts.map +1 -1
  52. package/dist/runtime/admin-setup-ops.js +2 -1
  53. package/dist/runtime/admin-setup-ops.js.map +1 -1
  54. package/dist/runtime/branching-ops.d.ts +12 -0
  55. package/dist/runtime/branching-ops.d.ts.map +1 -0
  56. package/dist/runtime/branching-ops.js +100 -0
  57. package/dist/runtime/branching-ops.js.map +1 -0
  58. package/dist/runtime/context-health.d.ts.map +1 -1
  59. package/dist/runtime/context-health.js.map +1 -1
  60. package/dist/runtime/facades/branching-facade.d.ts +7 -0
  61. package/dist/runtime/facades/branching-facade.d.ts.map +1 -0
  62. package/dist/runtime/facades/branching-facade.js +8 -0
  63. package/dist/runtime/facades/branching-facade.js.map +1 -0
  64. package/dist/runtime/facades/chat-service-ops.d.ts.map +1 -1
  65. package/dist/runtime/facades/chat-service-ops.js +3 -1
  66. package/dist/runtime/facades/chat-service-ops.js.map +1 -1
  67. package/dist/runtime/facades/chat-transport-ops.d.ts.map +1 -1
  68. package/dist/runtime/facades/chat-transport-ops.js.map +1 -1
  69. package/dist/runtime/facades/index.d.ts.map +1 -1
  70. package/dist/runtime/facades/index.js +42 -0
  71. package/dist/runtime/facades/index.js.map +1 -1
  72. package/dist/runtime/facades/intake-facade.d.ts +9 -0
  73. package/dist/runtime/facades/intake-facade.d.ts.map +1 -0
  74. package/dist/runtime/facades/intake-facade.js +11 -0
  75. package/dist/runtime/facades/intake-facade.js.map +1 -0
  76. package/dist/runtime/facades/links-facade.d.ts +9 -0
  77. package/dist/runtime/facades/links-facade.d.ts.map +1 -0
  78. package/dist/runtime/facades/links-facade.js +10 -0
  79. package/dist/runtime/facades/links-facade.js.map +1 -0
  80. package/dist/runtime/facades/operator-facade.d.ts.map +1 -1
  81. package/dist/runtime/facades/operator-facade.js.map +1 -1
  82. package/dist/runtime/facades/plan-facade.d.ts.map +1 -1
  83. package/dist/runtime/facades/plan-facade.js +4 -1
  84. package/dist/runtime/facades/plan-facade.js.map +1 -1
  85. package/dist/runtime/facades/tier-facade.d.ts +7 -0
  86. package/dist/runtime/facades/tier-facade.d.ts.map +1 -0
  87. package/dist/runtime/facades/tier-facade.js +8 -0
  88. package/dist/runtime/facades/tier-facade.js.map +1 -0
  89. package/dist/runtime/facades/vault-facade.d.ts +9 -1
  90. package/dist/runtime/facades/vault-facade.d.ts.map +1 -1
  91. package/dist/runtime/facades/vault-facade.js +44 -187
  92. package/dist/runtime/facades/vault-facade.js.map +1 -1
  93. package/dist/runtime/github-integration.d.ts.map +1 -1
  94. package/dist/runtime/github-integration.js +11 -4
  95. package/dist/runtime/github-integration.js.map +1 -1
  96. package/dist/runtime/orchestrate-ops.d.ts.map +1 -1
  97. package/dist/runtime/orchestrate-ops.js +32 -10
  98. package/dist/runtime/orchestrate-ops.js.map +1 -1
  99. package/dist/runtime/planning-extra-ops.d.ts.map +1 -1
  100. package/dist/runtime/planning-extra-ops.js.map +1 -1
  101. package/dist/runtime/runtime.d.ts.map +1 -1
  102. package/dist/runtime/runtime.js +3 -1
  103. package/dist/runtime/runtime.js.map +1 -1
  104. package/dist/runtime/session-briefing.d.ts.map +1 -1
  105. package/dist/runtime/session-briefing.js +5 -1
  106. package/dist/runtime/session-briefing.js.map +1 -1
  107. package/dist/runtime/tier-ops.d.ts +13 -0
  108. package/dist/runtime/tier-ops.d.ts.map +1 -0
  109. package/dist/runtime/tier-ops.js +110 -0
  110. package/dist/runtime/tier-ops.js.map +1 -0
  111. package/dist/skills/sync-skills.d.ts.map +1 -1
  112. package/dist/skills/sync-skills.js +1 -1
  113. package/dist/skills/sync-skills.js.map +1 -1
  114. package/dist/vault/linking.d.ts.map +1 -1
  115. package/dist/vault/linking.js +41 -5
  116. package/dist/vault/linking.js.map +1 -1
  117. package/dist/vault/vault-entries.d.ts.map +1 -1
  118. package/dist/vault/vault-entries.js +68 -26
  119. package/dist/vault/vault-entries.js.map +1 -1
  120. package/dist/vault/vault-maintenance.d.ts.map +1 -1
  121. package/dist/vault/vault-maintenance.js +6 -2
  122. package/dist/vault/vault-maintenance.js.map +1 -1
  123. package/dist/vault/vault-markdown-sync.d.ts.map +1 -1
  124. package/dist/vault/vault-markdown-sync.js.map +1 -1
  125. package/dist/vault/vault-memories.d.ts.map +1 -1
  126. package/dist/vault/vault-memories.js +3 -1
  127. package/dist/vault/vault-memories.js.map +1 -1
  128. package/dist/vault/vault-schema.js +36 -10
  129. package/dist/vault/vault-schema.js.map +1 -1
  130. package/dist/vault/vault.d.ts.map +1 -1
  131. package/dist/vault/vault.js +5 -1
  132. package/dist/vault/vault.js.map +1 -1
  133. package/package.json +7 -7
  134. package/src/agency/agency-manager.test.ts +60 -40
  135. package/src/agency/default-rules.test.ts +17 -9
  136. package/src/capabilities/registry.test.ts +2 -12
  137. package/src/chat/agent-loop.test.ts +33 -43
  138. package/src/chat/mcp-bridge.test.ts +7 -2
  139. package/src/claudemd/inject.test.ts +2 -12
  140. package/src/context/context-engine.test.ts +96 -51
  141. package/src/control/intent-router.test.ts +3 -3
  142. package/src/curator/classifier.test.ts +14 -8
  143. package/src/curator/contradiction-detector.test.ts +30 -5
  144. package/src/curator/curator.ts +278 -56
  145. package/src/curator/duplicate-detector.test.ts +77 -15
  146. package/src/curator/quality-gate.test.ts +71 -31
  147. package/src/curator/tag-manager.test.ts +12 -4
  148. package/src/domain-packs/knowledge-installer.test.ts +2 -10
  149. package/src/domain-packs/token-resolver.test.ts +1 -3
  150. package/src/domain-packs/types.test.ts +16 -2
  151. package/src/enforcement/registry.test.ts +2 -8
  152. package/src/engine/bin/soleri-engine.ts +3 -1
  153. package/src/engine/module-manifest.test.ts +5 -4
  154. package/src/engine/module-manifest.ts +21 -1
  155. package/src/engine/register-engine.test.ts +6 -1
  156. package/src/engine/register-engine.ts +26 -3
  157. package/src/errors/classify.test.ts +6 -2
  158. package/src/errors/retry.test.ts +1 -4
  159. package/src/facades/facade-factory.test.ts +110 -64
  160. package/src/flows/epilogue.test.ts +16 -10
  161. package/src/flows/gate-evaluator.test.ts +12 -6
  162. package/src/flows/gate-evaluator.ts +1 -3
  163. package/src/governance/governance.test.ts +137 -21
  164. package/src/health/health-registry.test.ts +8 -1
  165. package/src/intake/content-classifier.test.ts +121 -51
  166. package/src/intake/dedup-gate.test.ts +38 -22
  167. package/src/intake/intake-pipeline.test.ts +5 -3
  168. package/src/intake/text-ingester.test.ts +26 -20
  169. package/src/llm/key-pool.test.ts +1 -3
  170. package/src/llm/llm-client.test.ts +1 -4
  171. package/src/llm/oauth-discovery.test.ts +16 -16
  172. package/src/llm/utils.test.ts +62 -18
  173. package/src/logging/logger.test.ts +4 -1
  174. package/src/loop/loop-manager.test.ts +2 -6
  175. package/src/migrations/migration-runner.edge-cases.test.ts +2 -7
  176. package/src/operator/operator-profile-extended.test.ts +15 -5
  177. package/src/operator/operator-profile.test.ts +26 -8
  178. package/src/operator/operator-profile.ts +38 -22
  179. package/src/operator/operator-signals-extended.test.ts +35 -23
  180. package/src/operator/operator-signals.test.ts +6 -10
  181. package/src/operator/operator-signals.ts +2 -1
  182. package/src/operator/prompts/hook-precompact-operator-dispatch.md +10 -6
  183. package/src/operator/prompts/subagent-soft-signal-extractor.md +5 -0
  184. package/src/operator/prompts/subagent-synthesis-cognition.md +19 -10
  185. package/src/operator/prompts/subagent-synthesis-communication.md +13 -7
  186. package/src/operator/prompts/subagent-synthesis-technical.md +19 -9
  187. package/src/operator/prompts/subagent-synthesis-trust.md +27 -21
  188. package/src/persona/defaults.test.ts +1 -5
  189. package/src/planning/evidence-collector.test.ts +147 -38
  190. package/src/planning/evidence-collector.ts +1 -4
  191. package/src/planning/gap-analysis-alternatives.test.ts +41 -11
  192. package/src/planning/gap-passes.test.ts +215 -33
  193. package/src/planning/gap-passes.ts +115 -46
  194. package/src/planning/gap-patterns.test.ts +87 -13
  195. package/src/planning/gap-patterns.ts +114 -31
  196. package/src/planning/github-projection.test.ts +6 -1
  197. package/src/planning/github-projection.ts +41 -20
  198. package/src/planning/impact-analyzer.test.ts +10 -23
  199. package/src/planning/impact-analyzer.ts +33 -46
  200. package/src/planning/plan-lifecycle.test.ts +103 -36
  201. package/src/planning/plan-lifecycle.ts +49 -18
  202. package/src/planning/planner.test.ts +12 -2
  203. package/src/planning/planner.ts +198 -58
  204. package/src/planning/rationalization-detector.test.ts +5 -20
  205. package/src/planning/rationalization-detector.ts +14 -16
  206. package/src/planning/reconciliation-engine.test.ts +20 -3
  207. package/src/planning/reconciliation-engine.ts +1 -2
  208. package/src/planning/task-verifier.test.ts +59 -27
  209. package/src/planning/task-verifier.ts +15 -9
  210. package/src/playbooks/playbook-executor.test.ts +1 -3
  211. package/src/plugins/plugin-loader.test.ts +19 -14
  212. package/src/plugins/plugin-registry.test.ts +45 -33
  213. package/src/project/project-registry.test.ts +23 -12
  214. package/src/prompts/template-manager.test.ts +4 -1
  215. package/src/queue/job-queue.test.ts +10 -14
  216. package/src/runtime/admin-extra-ops.test.ts +5 -19
  217. package/src/runtime/admin-ops.test.ts +1 -3
  218. package/src/runtime/admin-setup-ops.test.ts +3 -4
  219. package/src/runtime/admin-setup-ops.ts +9 -2
  220. package/src/runtime/archive-ops.test.ts +4 -1
  221. package/src/runtime/branching-ops.test.ts +144 -0
  222. package/src/runtime/branching-ops.ts +107 -0
  223. package/src/runtime/capture-ops.test.ts +7 -21
  224. package/src/runtime/chain-ops.test.ts +16 -6
  225. package/src/runtime/claude-md-helpers.test.ts +1 -3
  226. package/src/runtime/context-health.test.ts +1 -3
  227. package/src/runtime/context-health.ts +1 -3
  228. package/src/runtime/curator-extra-ops.test.ts +3 -1
  229. package/src/runtime/domain-ops.test.ts +46 -36
  230. package/src/runtime/facades/admin-facade.test.ts +1 -4
  231. package/src/runtime/facades/archive-facade.test.ts +21 -7
  232. package/src/runtime/facades/brain-facade.test.ts +176 -72
  233. package/src/runtime/facades/branching-facade.test.ts +43 -0
  234. package/src/runtime/facades/branching-facade.ts +11 -0
  235. package/src/runtime/facades/chat-facade.test.ts +81 -28
  236. package/src/runtime/facades/chat-service-ops.test.ts +178 -73
  237. package/src/runtime/facades/chat-service-ops.ts +3 -1
  238. package/src/runtime/facades/chat-session-ops.test.ts +25 -10
  239. package/src/runtime/facades/chat-transport-ops.test.ts +101 -34
  240. package/src/runtime/facades/chat-transport-ops.ts +0 -1
  241. package/src/runtime/facades/context-facade.test.ts +19 -4
  242. package/src/runtime/facades/control-facade.test.ts +3 -3
  243. package/src/runtime/facades/index.ts +42 -0
  244. package/src/runtime/facades/intake-facade.test.ts +215 -0
  245. package/src/runtime/facades/intake-facade.ts +14 -0
  246. package/src/runtime/facades/links-facade.test.ts +203 -0
  247. package/src/runtime/facades/links-facade.ts +13 -0
  248. package/src/runtime/facades/loop-facade.test.ts +22 -5
  249. package/src/runtime/facades/memory-facade.test.ts +19 -5
  250. package/src/runtime/facades/operator-facade.test.ts +17 -4
  251. package/src/runtime/facades/operator-facade.ts +11 -3
  252. package/src/runtime/facades/orchestrate-facade.test.ts +7 -1
  253. package/src/runtime/facades/plan-facade.test.ts +29 -12
  254. package/src/runtime/facades/plan-facade.ts +7 -2
  255. package/src/runtime/facades/tier-facade.test.ts +47 -0
  256. package/src/runtime/facades/tier-facade.ts +11 -0
  257. package/src/runtime/facades/vault-facade.test.ts +174 -242
  258. package/src/runtime/facades/vault-facade.ts +55 -199
  259. package/src/runtime/github-integration.ts +11 -8
  260. package/src/runtime/grading-ops.test.ts +39 -8
  261. package/src/runtime/intake-ops.test.ts +69 -16
  262. package/src/runtime/loop-ops.test.ts +16 -6
  263. package/src/runtime/memory-cross-project-ops.test.ts +25 -14
  264. package/src/runtime/orchestrate-ops.ts +54 -27
  265. package/src/runtime/pack-ops.test.ts +23 -6
  266. package/src/runtime/planning-extra-ops.test.ts +17 -7
  267. package/src/runtime/planning-extra-ops.ts +3 -1
  268. package/src/runtime/playbook-ops.test.ts +26 -3
  269. package/src/runtime/plugin-ops.test.ts +83 -25
  270. package/src/runtime/project-ops.test.ts +26 -6
  271. package/src/runtime/runtime.ts +3 -1
  272. package/src/runtime/session-briefing.test.ts +183 -54
  273. package/src/runtime/session-briefing.ts +8 -2
  274. package/src/runtime/sync-ops.test.ts +3 -12
  275. package/src/runtime/telemetry-ops.test.ts +31 -6
  276. package/src/runtime/tier-ops.test.ts +159 -0
  277. package/src/runtime/tier-ops.ts +119 -0
  278. package/src/runtime/vault-extra-ops.test.ts +32 -8
  279. package/src/runtime/vault-sharing-ops.test.ts +1 -4
  280. package/src/skills/sync-skills.ts +2 -12
  281. package/src/transport/ws-server.test.ts +7 -4
  282. package/src/vault/__tests__/vault-characterization.test.ts +492 -81
  283. package/src/vault/linking.test.ts +50 -17
  284. package/src/vault/linking.ts +48 -7
  285. package/src/vault/obsidian-sync.test.ts +6 -3
  286. package/src/vault/scope-detector.test.ts +1 -3
  287. package/src/vault/vault-branching.test.ts +9 -7
  288. package/src/vault/vault-entries.ts +209 -65
  289. package/src/vault/vault-maintenance.ts +7 -12
  290. package/src/vault/vault-manager.test.ts +10 -10
  291. package/src/vault/vault-markdown-sync.ts +4 -1
  292. package/src/vault/vault-memories.ts +7 -7
  293. package/src/vault/vault-schema.ts +72 -15
  294. package/src/vault/vault.ts +55 -9
  295. package/src/brain/strength-scorer.ts +0 -404
  296. package/src/engine/index.ts +0 -21
  297. package/src/persona/index.ts +0 -9
  298. package/src/vault/vault-interfaces.ts +0 -56
@@ -9,160 +9,571 @@ import type { IntelligenceEntry } from '../../intelligence/types.js';
9
9
 
10
10
  function makeEntry(overrides: Partial<IntelligenceEntry> = {}): IntelligenceEntry {
11
11
  const id = overrides.id ?? `test-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;
12
- return { id, type: 'pattern', domain: 'testing', title: 'Test Pattern', severity: 'suggestion', description: 'A test pattern for characterization', tags: ['test'], ...overrides };
12
+ return {
13
+ id,
14
+ type: 'pattern',
15
+ domain: 'testing',
16
+ title: 'Test Pattern',
17
+ severity: 'suggestion',
18
+ description: 'A test pattern for characterization',
19
+ tags: ['test'],
20
+ ...overrides,
21
+ };
13
22
  }
14
23
 
15
- function makeMemoryInput(overrides: Partial<Omit<Memory, 'id' | 'createdAt' | 'archivedAt'>> = {}): Omit<Memory, 'id' | 'createdAt' | 'archivedAt'> {
16
- return { projectPath: '/test/project', type: 'session', context: 'test context', summary: 'test summary', topics: ['testing'], filesModified: ['file.ts'], toolsUsed: ['vault'], intent: null, decisions: [], currentState: null, nextSteps: [], vaultEntriesReferenced: [], ...overrides };
24
+ function makeMemoryInput(
25
+ overrides: Partial<Omit<Memory, 'id' | 'createdAt' | 'archivedAt'>> = {},
26
+ ): Omit<Memory, 'id' | 'createdAt' | 'archivedAt'> {
27
+ return {
28
+ projectPath: '/test/project',
29
+ type: 'session',
30
+ context: 'test context',
31
+ summary: 'test summary',
32
+ topics: ['testing'],
33
+ filesModified: ['file.ts'],
34
+ toolsUsed: ['vault'],
35
+ intent: null,
36
+ decisions: [],
37
+ currentState: null,
38
+ nextSteps: [],
39
+ vaultEntriesReferenced: [],
40
+ ...overrides,
41
+ };
17
42
  }
18
43
 
19
44
  describe('Vault Characterization Tests', () => {
20
45
  let vault: Vault;
21
- beforeEach(() => { vault = new Vault(':memory:'); });
22
- afterEach(() => { vault.close(); });
46
+ beforeEach(() => {
47
+ vault = new Vault(':memory:');
48
+ });
49
+ afterEach(() => {
50
+ vault.close();
51
+ });
23
52
 
24
53
  describe('constructor', () => {
25
- it('creates an in-memory vault', () => { const v = new Vault(':memory:'); expect(v).toBeInstanceOf(Vault); v.close(); });
26
- it('stamps FORMAT_VERSION', () => { expect(Vault.FORMAT_VERSION).toBe(1); });
27
- it('createWithSQLite factory', () => { const v = Vault.createWithSQLite(':memory:'); expect(v).toBeInstanceOf(Vault); v.close(); });
54
+ it('creates an in-memory vault', () => {
55
+ const v = new Vault(':memory:');
56
+ expect(v).toBeInstanceOf(Vault);
57
+ v.close();
58
+ });
59
+ it('stamps FORMAT_VERSION', () => {
60
+ expect(Vault.FORMAT_VERSION).toBe(1);
61
+ });
62
+ it('createWithSQLite factory', () => {
63
+ const v = Vault.createWithSQLite(':memory:');
64
+ expect(v).toBeInstanceOf(Vault);
65
+ v.close();
66
+ });
28
67
  });
29
68
 
30
69
  describe('setLinkManager / isAutoLinkEnabled', () => {
31
- it('defaults to no link manager', () => { expect(vault.isAutoLinkEnabled()).toBe(false); });
32
- it('enables auto-link when set', () => { vault.setLinkManager({ suggestLinks: () => [], addLink: () => {} } as unknown); expect(vault.isAutoLinkEnabled()).toBe(true); });
33
- it('respects enabled:false', () => { vault.setLinkManager({ suggestLinks: () => [], addLink: () => {} } as unknown, { enabled: false }); expect(vault.isAutoLinkEnabled()).toBe(false); });
70
+ it('defaults to no link manager', () => {
71
+ expect(vault.isAutoLinkEnabled()).toBe(false);
72
+ });
73
+ it('enables auto-link when set', () => {
74
+ vault.setLinkManager({ suggestLinks: () => [], addLink: () => {} } as unknown);
75
+ expect(vault.isAutoLinkEnabled()).toBe(true);
76
+ });
77
+ it('respects enabled:false', () => {
78
+ vault.setLinkManager({ suggestLinks: () => [], addLink: () => {} } as unknown, {
79
+ enabled: false,
80
+ });
81
+ expect(vault.isAutoLinkEnabled()).toBe(false);
82
+ });
34
83
  });
35
84
 
36
85
  describe('seed', () => {
37
- it('inserts entries', () => { expect(vault.seed([makeEntry({ id: 'seed-1' })])).toBe(1); });
38
- it('upserts on conflict', () => { vault.seed([makeEntry({ id: 'u1', title: 'Old' })]); vault.seed([makeEntry({ id: 'u1', title: 'New' })]); expect(vault.get('u1')?.title).toBe('New'); });
39
- it('handles multiple', () => { expect(vault.seed([makeEntry({ id: 'a1' }), makeEntry({ id: 'a2' }), makeEntry({ id: 'a3' })])).toBe(3); });
86
+ it('inserts entries', () => {
87
+ expect(vault.seed([makeEntry({ id: 'seed-1' })])).toBe(1);
88
+ });
89
+ it('upserts on conflict', () => {
90
+ vault.seed([makeEntry({ id: 'u1', title: 'Old' })]);
91
+ vault.seed([makeEntry({ id: 'u1', title: 'New' })]);
92
+ expect(vault.get('u1')?.title).toBe('New');
93
+ });
94
+ it('handles multiple', () => {
95
+ expect(
96
+ vault.seed([makeEntry({ id: 'a1' }), makeEntry({ id: 'a2' }), makeEntry({ id: 'a3' })]),
97
+ ).toBe(3);
98
+ });
40
99
  });
41
100
 
42
- describe('add', () => { it('delegates to seed', () => { vault.add(makeEntry({ id: 'add-1' })); expect(vault.get('add-1')).toBeTruthy(); }); });
101
+ describe('add', () => {
102
+ it('delegates to seed', () => {
103
+ vault.add(makeEntry({ id: 'add-1' }));
104
+ expect(vault.get('add-1')).toBeTruthy();
105
+ });
106
+ });
43
107
 
44
108
  describe('get', () => {
45
- it('returns null for missing', () => { expect(vault.get('x')).toBeNull(); });
46
- it('returns parsed entry', () => { vault.seed([makeEntry({ id: 'g1', tags: ['a', 'b'], appliesTo: ['react'] })]); const g = vault.get('g1'); expect(g!.tags).toEqual(['a', 'b']); expect(g!.appliesTo).toEqual(['react']); });
109
+ it('returns null for missing', () => {
110
+ expect(vault.get('x')).toBeNull();
111
+ });
112
+ it('returns parsed entry', () => {
113
+ vault.seed([makeEntry({ id: 'g1', tags: ['a', 'b'], appliesTo: ['react'] })]);
114
+ const g = vault.get('g1');
115
+ expect(g!.tags).toEqual(['a', 'b']);
116
+ expect(g!.appliesTo).toEqual(['react']);
117
+ });
47
118
  });
48
119
 
49
120
  describe('remove', () => {
50
- it('returns true on delete', () => { vault.seed([makeEntry({ id: 'rm-1' })]); expect(vault.remove('rm-1')).toBe(true); });
51
- it('returns false if missing', () => { expect(vault.remove('x')).toBe(false); });
121
+ it('returns true on delete', () => {
122
+ vault.seed([makeEntry({ id: 'rm-1' })]);
123
+ expect(vault.remove('rm-1')).toBe(true);
124
+ });
125
+ it('returns false if missing', () => {
126
+ expect(vault.remove('x')).toBe(false);
127
+ });
52
128
  });
53
129
 
54
130
  describe('update', () => {
55
- it('updates and returns', () => { vault.seed([makeEntry({ id: 'up1', title: 'Old' })]); expect(vault.update('up1', { title: 'New' })?.title).toBe('New'); });
56
- it('returns null if missing', () => { expect(vault.update('x', { title: 'X' })).toBeNull(); });
131
+ it('updates and returns', () => {
132
+ vault.seed([makeEntry({ id: 'up1', title: 'Old' })]);
133
+ expect(vault.update('up1', { title: 'New' })?.title).toBe('New');
134
+ });
135
+ it('returns null if missing', () => {
136
+ expect(vault.update('x', { title: 'X' })).toBeNull();
137
+ });
57
138
  });
58
139
 
59
- describe('bulkRemove', () => { it('removes multiple', () => { vault.seed([makeEntry({ id: 'b1' }), makeEntry({ id: 'b2' }), makeEntry({ id: 'b3' })]); expect(vault.bulkRemove(['b1', 'b3'])).toBe(2); expect(vault.get('b2')).toBeTruthy(); }); });
140
+ describe('bulkRemove', () => {
141
+ it('removes multiple', () => {
142
+ vault.seed([makeEntry({ id: 'b1' }), makeEntry({ id: 'b2' }), makeEntry({ id: 'b3' })]);
143
+ expect(vault.bulkRemove(['b1', 'b3'])).toBe(2);
144
+ expect(vault.get('b2')).toBeTruthy();
145
+ });
146
+ });
60
147
 
61
148
  describe('search', () => {
62
- it('returns empty for no matches', () => { expect(vault.search('xyzzy-nonexistent')).toEqual([]); });
63
- it('finds by FTS', () => { vault.seed([makeEntry({ id: 's1', title: 'React perf opt', description: 'Memoize' })]); const r = vault.search('React perf'); expect(r.length).toBeGreaterThanOrEqual(1); expect(typeof r[0].score).toBe('number'); });
64
- it('respects domain filter', () => { vault.seed([makeEntry({ id: 'sf1', domain: 'react', title: 'hooks' }), makeEntry({ id: 'sf2', domain: 'css', title: 'hooks for css' })]); expect(vault.search('hooks', { domain: 'react' }).every(r => r.entry.domain === 'react')).toBe(true); });
65
- it('respects limit', () => { for (let i = 0; i < 15; i++) vault.seed([makeEntry({ id: `l${i}`, title: `pat ${i}`, description: 'perf' })]); expect(vault.search('perf', { limit: 5 }).length).toBeLessThanOrEqual(5); });
149
+ it('returns empty for no matches', () => {
150
+ expect(vault.search('xyzzy-nonexistent')).toEqual([]);
151
+ });
152
+ it('finds by FTS', () => {
153
+ vault.seed([makeEntry({ id: 's1', title: 'React perf opt', description: 'Memoize' })]);
154
+ const r = vault.search('React perf');
155
+ expect(r.length).toBeGreaterThanOrEqual(1);
156
+ expect(typeof r[0].score).toBe('number');
157
+ });
158
+ it('respects domain filter', () => {
159
+ vault.seed([
160
+ makeEntry({ id: 'sf1', domain: 'react', title: 'hooks' }),
161
+ makeEntry({ id: 'sf2', domain: 'css', title: 'hooks for css' }),
162
+ ]);
163
+ expect(
164
+ vault.search('hooks', { domain: 'react' }).every((r) => r.entry.domain === 'react'),
165
+ ).toBe(true);
166
+ });
167
+ it('respects limit', () => {
168
+ for (let i = 0; i < 15; i++)
169
+ vault.seed([makeEntry({ id: `l${i}`, title: `pat ${i}`, description: 'perf' })]);
170
+ expect(vault.search('perf', { limit: 5 }).length).toBeLessThanOrEqual(5);
171
+ });
66
172
  });
67
173
 
68
174
  describe('list', () => {
69
- it('returns entries', () => { vault.seed([makeEntry({ id: 'l1' }), makeEntry({ id: 'l2' })]); expect(vault.list().length).toBe(2); });
70
- it('filters by domain', () => { vault.seed([makeEntry({ id: 'ld1', domain: 'react' }), makeEntry({ id: 'ld2', domain: 'css' })]); expect(vault.list({ domain: 'react' }).length).toBe(1); });
71
- it('filters by type', () => { vault.seed([makeEntry({ id: 'lt1', type: 'pattern' }), makeEntry({ id: 'lt2', type: 'rule' })]); expect(vault.list({ type: 'rule' }).length).toBe(1); });
72
- it('filters by tags', () => { vault.seed([makeEntry({ id: 'tg1', tags: ['react', 'hooks'] }), makeEntry({ id: 'tg2', tags: ['css'] })]); expect(vault.list({ tags: ['react'] }).length).toBe(1); });
73
- it('respects limit/offset', () => { for (let i = 0; i < 10; i++) vault.seed([makeEntry({ id: `p${i}` })]); expect(vault.list({ limit: 3, offset: 2 }).length).toBe(3); });
175
+ it('returns entries', () => {
176
+ vault.seed([makeEntry({ id: 'l1' }), makeEntry({ id: 'l2' })]);
177
+ expect(vault.list().length).toBe(2);
178
+ });
179
+ it('filters by domain', () => {
180
+ vault.seed([
181
+ makeEntry({ id: 'ld1', domain: 'react' }),
182
+ makeEntry({ id: 'ld2', domain: 'css' }),
183
+ ]);
184
+ expect(vault.list({ domain: 'react' }).length).toBe(1);
185
+ });
186
+ it('filters by type', () => {
187
+ vault.seed([
188
+ makeEntry({ id: 'lt1', type: 'pattern' }),
189
+ makeEntry({ id: 'lt2', type: 'rule' }),
190
+ ]);
191
+ expect(vault.list({ type: 'rule' }).length).toBe(1);
192
+ });
193
+ it('filters by tags', () => {
194
+ vault.seed([
195
+ makeEntry({ id: 'tg1', tags: ['react', 'hooks'] }),
196
+ makeEntry({ id: 'tg2', tags: ['css'] }),
197
+ ]);
198
+ expect(vault.list({ tags: ['react'] }).length).toBe(1);
199
+ });
200
+ it('respects limit/offset', () => {
201
+ for (let i = 0; i < 10; i++) vault.seed([makeEntry({ id: `p${i}` })]);
202
+ expect(vault.list({ limit: 3, offset: 2 }).length).toBe(3);
203
+ });
74
204
  });
75
205
 
76
206
  describe('stats', () => {
77
- it('zero for empty', () => { const s = vault.stats(); expect(s.totalEntries).toBe(0); });
78
- it('counts correctly', () => { vault.seed([makeEntry({ id: 's1', type: 'pattern', domain: 'react', severity: 'warning' }), makeEntry({ id: 's2', type: 'rule', domain: 'react', severity: 'critical' }), makeEntry({ id: 's3', type: 'pattern', domain: 'css', severity: 'warning' })]); const s = vault.stats(); expect(s.totalEntries).toBe(3); expect(s.byType).toEqual({ pattern: 2, rule: 1 }); });
207
+ it('zero for empty', () => {
208
+ const s = vault.stats();
209
+ expect(s.totalEntries).toBe(0);
210
+ });
211
+ it('counts correctly', () => {
212
+ vault.seed([
213
+ makeEntry({ id: 's1', type: 'pattern', domain: 'react', severity: 'warning' }),
214
+ makeEntry({ id: 's2', type: 'rule', domain: 'react', severity: 'critical' }),
215
+ makeEntry({ id: 's3', type: 'pattern', domain: 'css', severity: 'warning' }),
216
+ ]);
217
+ const s = vault.stats();
218
+ expect(s.totalEntries).toBe(3);
219
+ expect(s.byType).toEqual({ pattern: 2, rule: 1 });
220
+ });
79
221
  });
80
222
 
81
- describe('getTags', () => { it('returns sorted counts', () => { vault.seed([makeEntry({ id: 't1', tags: ['react', 'hooks'] }), makeEntry({ id: 't2', tags: ['react', 'css'] }), makeEntry({ id: 't3', tags: ['css'] })]); const t = vault.getTags(); expect(t[0].tag).toBe('react'); expect(t[0].count).toBe(2); }); });
82
- describe('getDomains', () => { it('returns counts', () => { vault.seed([makeEntry({ id: 'd1', domain: 'react' }), makeEntry({ id: 'd2', domain: 'react' }), makeEntry({ id: 'd3', domain: 'css' })]); expect(vault.getDomains()).toEqual([{ domain: 'react', count: 2 }, { domain: 'css', count: 1 }]); }); });
83
- describe('getRecent', () => { it('returns entries', () => { vault.seed([makeEntry({ id: 'r1' })]); vault.seed([makeEntry({ id: 'r2' })]); expect(vault.getRecent(5).length).toBe(2); }); });
223
+ describe('getTags', () => {
224
+ it('returns sorted counts', () => {
225
+ vault.seed([
226
+ makeEntry({ id: 't1', tags: ['react', 'hooks'] }),
227
+ makeEntry({ id: 't2', tags: ['react', 'css'] }),
228
+ makeEntry({ id: 't3', tags: ['css'] }),
229
+ ]);
230
+ const t = vault.getTags();
231
+ expect(t[0].tag).toBe('react');
232
+ expect(t[0].count).toBe(2);
233
+ });
234
+ });
235
+ describe('getDomains', () => {
236
+ it('returns counts', () => {
237
+ vault.seed([
238
+ makeEntry({ id: 'd1', domain: 'react' }),
239
+ makeEntry({ id: 'd2', domain: 'react' }),
240
+ makeEntry({ id: 'd3', domain: 'css' }),
241
+ ]);
242
+ expect(vault.getDomains()).toEqual([
243
+ { domain: 'react', count: 2 },
244
+ { domain: 'css', count: 1 },
245
+ ]);
246
+ });
247
+ });
248
+ describe('getRecent', () => {
249
+ it('returns entries', () => {
250
+ vault.seed([makeEntry({ id: 'r1' })]);
251
+ vault.seed([makeEntry({ id: 'r2' })]);
252
+ expect(vault.getRecent(5).length).toBe(2);
253
+ });
254
+ });
84
255
 
85
256
  describe('setTemporal', () => {
86
- it('sets temporal fields', () => { vault.seed([makeEntry({ id: 'tm1' })]); expect(vault.setTemporal('tm1', 1000, 2000)).toBe(true); const e = vault.get('tm1'); expect(e?.validFrom).toBe(1000); expect(e?.validUntil).toBe(2000); });
87
- it('returns false for empty', () => { vault.seed([makeEntry({ id: 'tm2' })]); expect(vault.setTemporal('tm2')).toBe(false); });
257
+ it('sets temporal fields', () => {
258
+ vault.seed([makeEntry({ id: 'tm1' })]);
259
+ expect(vault.setTemporal('tm1', 1000, 2000)).toBe(true);
260
+ const e = vault.get('tm1');
261
+ expect(e?.validFrom).toBe(1000);
262
+ expect(e?.validUntil).toBe(2000);
263
+ });
264
+ it('returns false for empty', () => {
265
+ vault.seed([makeEntry({ id: 'tm2' })]);
266
+ expect(vault.setTemporal('tm2')).toBe(false);
267
+ });
88
268
  });
89
269
 
90
- describe('findExpiring', () => { it('finds expiring', () => { const now = Math.floor(Date.now() / 1000); vault.seed([makeEntry({ id: 'e1', validUntil: now + 3600 }), makeEntry({ id: 'e2', validUntil: now + 86400 * 100 })]); expect(vault.findExpiring(7).length).toBe(1); }); });
91
- describe('findExpired', () => { it('finds expired', () => { const now = Math.floor(Date.now() / 1000); vault.seed([makeEntry({ id: 'x1', validUntil: now - 3600 }), makeEntry({ id: 'x2' })]); expect(vault.findExpired().length).toBe(1); }); });
270
+ describe('findExpiring', () => {
271
+ it('finds expiring', () => {
272
+ const now = Math.floor(Date.now() / 1000);
273
+ vault.seed([
274
+ makeEntry({ id: 'e1', validUntil: now + 3600 }),
275
+ makeEntry({ id: 'e2', validUntil: now + 86400 * 100 }),
276
+ ]);
277
+ expect(vault.findExpiring(7).length).toBe(1);
278
+ });
279
+ });
280
+ describe('findExpired', () => {
281
+ it('finds expired', () => {
282
+ const now = Math.floor(Date.now() / 1000);
283
+ vault.seed([makeEntry({ id: 'x1', validUntil: now - 3600 }), makeEntry({ id: 'x2' })]);
284
+ expect(vault.findExpired().length).toBe(1);
285
+ });
286
+ });
92
287
 
93
288
  describe('installPack', () => {
94
- it('installs with origin:pack', () => { const r = vault.installPack([makeEntry({ id: 'pk1', title: 'Pack one' }), makeEntry({ id: 'pk2', title: 'Pack two' })]); expect(r.installed).toBe(2); expect(vault.get('pk1')?.origin).toBe('pack'); });
95
- it('skips duplicates', () => { const e = makeEntry({ id: 'pd1' }); vault.installPack([e]); expect(vault.installPack([{ ...e, id: 'pd2' }]).skipped).toBe(1); });
289
+ it('installs with origin:pack', () => {
290
+ const r = vault.installPack([
291
+ makeEntry({ id: 'pk1', title: 'Pack one' }),
292
+ makeEntry({ id: 'pk2', title: 'Pack two' }),
293
+ ]);
294
+ expect(r.installed).toBe(2);
295
+ expect(vault.get('pk1')?.origin).toBe('pack');
296
+ });
297
+ it('skips duplicates', () => {
298
+ const e = makeEntry({ id: 'pd1' });
299
+ vault.installPack([e]);
300
+ expect(vault.installPack([{ ...e, id: 'pd2' }]).skipped).toBe(1);
301
+ });
96
302
  });
97
303
 
98
304
  describe('seedDedup', () => {
99
- it('inserts new', () => { expect(vault.seedDedup([makeEntry({ id: 'sd1' })])[0].action).toBe('inserted'); });
100
- it('detects duplicate', () => { const e = makeEntry({ id: 'so' }); vault.seed([e]); const r = vault.seedDedup([{ ...e, id: 'sd' }]); expect(r[0].action).toBe('duplicate'); expect(r[0].existingId).toBe('so'); });
305
+ it('inserts new', () => {
306
+ expect(vault.seedDedup([makeEntry({ id: 'sd1' })])[0].action).toBe('inserted');
307
+ });
308
+ it('detects duplicate', () => {
309
+ const e = makeEntry({ id: 'so' });
310
+ vault.seed([e]);
311
+ const r = vault.seedDedup([{ ...e, id: 'sd' }]);
312
+ expect(r[0].action).toBe('duplicate');
313
+ expect(r[0].existingId).toBe('so');
314
+ });
101
315
  });
102
316
 
103
- describe('findByContentHash', () => { it('returns null for unknown', () => { expect(vault.findByContentHash('x')).toBeNull(); }); });
317
+ describe('findByContentHash', () => {
318
+ it('returns null for unknown', () => {
319
+ expect(vault.findByContentHash('x')).toBeNull();
320
+ });
321
+ });
104
322
  describe('contentHashStats', () => {
105
- it('zeros for empty', () => { const s = vault.contentHashStats(); expect(s.total).toBe(0); });
106
- it('counts', () => { vault.seed([makeEntry({ id: 'c1', title: 'First' }), makeEntry({ id: 'c2', title: 'Second' })]); const s = vault.contentHashStats(); expect(s.total).toBe(2); expect(s.uniqueHashes).toBe(2); });
323
+ it('zeros for empty', () => {
324
+ const s = vault.contentHashStats();
325
+ expect(s.total).toBe(0);
326
+ });
327
+ it('counts', () => {
328
+ vault.seed([
329
+ makeEntry({ id: 'c1', title: 'First' }),
330
+ makeEntry({ id: 'c2', title: 'Second' }),
331
+ ]);
332
+ const s = vault.contentHashStats();
333
+ expect(s.total).toBe(2);
334
+ expect(s.uniqueHashes).toBe(2);
335
+ });
107
336
  });
108
337
 
109
- describe('exportAll', () => { it('exports all', () => { vault.seed([makeEntry({ id: 'e1' }), makeEntry({ id: 'e2' })]); const r = vault.exportAll(); expect(r.count).toBe(2); expect(typeof r.exportedAt).toBe('number'); }); });
338
+ describe('exportAll', () => {
339
+ it('exports all', () => {
340
+ vault.seed([makeEntry({ id: 'e1' }), makeEntry({ id: 'e2' })]);
341
+ const r = vault.exportAll();
342
+ expect(r.count).toBe(2);
343
+ expect(typeof r.exportedAt).toBe('number');
344
+ });
345
+ });
110
346
  describe('getAgeReport', () => {
111
- it('empty report', () => { const r = vault.getAgeReport(); expect(r.total).toBe(0); expect(r.oldestTimestamp).toBeNull(); expect(r.buckets.length).toBe(5); });
112
- it('buckets entries', () => { vault.seed([makeEntry({ id: 'a1' })]); const r = vault.getAgeReport(); expect(r.total).toBe(1); expect(r.buckets[0].label).toBe('today'); expect(r.buckets[0].count).toBe(1); });
347
+ it('empty report', () => {
348
+ const r = vault.getAgeReport();
349
+ expect(r.total).toBe(0);
350
+ expect(r.oldestTimestamp).toBeNull();
351
+ expect(r.buckets.length).toBe(5);
352
+ });
353
+ it('buckets entries', () => {
354
+ vault.seed([makeEntry({ id: 'a1' })]);
355
+ const r = vault.getAgeReport();
356
+ expect(r.total).toBe(1);
357
+ expect(r.buckets[0].label).toBe('today');
358
+ expect(r.buckets[0].count).toBe(1);
359
+ });
113
360
  });
114
361
 
115
362
  describe('archive', () => {
116
- it('archives old entries', () => { vault.seed([makeEntry({ id: 'ar1' })]); vault.getProvider().run("UPDATE entries SET updated_at = unixepoch() - 200 * 86400 WHERE id = 'ar1'"); expect(vault.archive({ olderThanDays: 100 }).archived).toBe(1); expect(vault.get('ar1')).toBeNull(); });
117
- it('returns 0 if none qualify', () => { vault.seed([makeEntry({ id: 'ar2' })]); expect(vault.archive({ olderThanDays: 100 }).archived).toBe(0); });
363
+ it('archives old entries', () => {
364
+ vault.seed([makeEntry({ id: 'ar1' })]);
365
+ vault
366
+ .getProvider()
367
+ .run("UPDATE entries SET updated_at = unixepoch() - 200 * 86400 WHERE id = 'ar1'");
368
+ expect(vault.archive({ olderThanDays: 100 }).archived).toBe(1);
369
+ expect(vault.get('ar1')).toBeNull();
370
+ });
371
+ it('returns 0 if none qualify', () => {
372
+ vault.seed([makeEntry({ id: 'ar2' })]);
373
+ expect(vault.archive({ olderThanDays: 100 }).archived).toBe(0);
374
+ });
118
375
  });
119
376
 
120
377
  describe('restore', () => {
121
- it('restores archived', () => { vault.seed([makeEntry({ id: 'rs1' })]); vault.getProvider().run("UPDATE entries SET updated_at = unixepoch() - 200 * 86400 WHERE id = 'rs1'"); vault.archive({ olderThanDays: 100 }); expect(vault.restore('rs1')).toBe(true); expect(vault.get('rs1')).toBeTruthy(); });
122
- it('returns false for missing', () => { expect(vault.restore('x')).toBe(false); });
378
+ it('restores archived', () => {
379
+ vault.seed([makeEntry({ id: 'rs1' })]);
380
+ vault
381
+ .getProvider()
382
+ .run("UPDATE entries SET updated_at = unixepoch() - 200 * 86400 WHERE id = 'rs1'");
383
+ vault.archive({ olderThanDays: 100 });
384
+ expect(vault.restore('rs1')).toBe(true);
385
+ expect(vault.get('rs1')).toBeTruthy();
386
+ });
387
+ it('returns false for missing', () => {
388
+ expect(vault.restore('x')).toBe(false);
389
+ });
123
390
  });
124
391
 
125
- describe('optimize', () => { it('returns status', () => { const r = vault.optimize(); expect(typeof r.vacuumed).toBe('boolean'); }); });
126
- describe('rebuildFtsIndex', () => { it('does not throw', () => { expect(() => vault.rebuildFtsIndex()).not.toThrow(); }); });
392
+ describe('optimize', () => {
393
+ it('returns status', () => {
394
+ const r = vault.optimize();
395
+ expect(typeof r.vacuumed).toBe('boolean');
396
+ });
397
+ });
398
+ describe('rebuildFtsIndex', () => {
399
+ it('does not throw', () => {
400
+ expect(() => vault.rebuildFtsIndex()).not.toThrow();
401
+ });
402
+ });
127
403
 
128
404
  describe('registerProject', () => {
129
- it('registers new', () => { const p = vault.registerProject('/t', 'test'); expect(p.sessionCount).toBe(1); });
130
- it('increments on re-register', () => { vault.registerProject('/t'); expect(vault.registerProject('/t').sessionCount).toBe(2); });
131
- it('derives name', () => { expect(vault.registerProject('/home/user/proj').name).toBe('proj'); });
405
+ it('registers new', () => {
406
+ const p = vault.registerProject('/t', 'test');
407
+ expect(p.sessionCount).toBe(1);
408
+ });
409
+ it('increments on re-register', () => {
410
+ vault.registerProject('/t');
411
+ expect(vault.registerProject('/t').sessionCount).toBe(2);
412
+ });
413
+ it('derives name', () => {
414
+ expect(vault.registerProject('/home/user/proj').name).toBe('proj');
415
+ });
132
416
  });
133
417
 
134
- describe('getProject', () => { it('returns null for unknown', () => { expect(vault.getProject('/x')).toBeNull(); }); it('returns info', () => { vault.registerProject('/t', 'T'); expect(vault.getProject('/t')!.name).toBe('T'); }); });
135
- describe('listProjects', () => { it('lists all', () => { vault.registerProject('/a'); vault.registerProject('/b'); expect(vault.listProjects().length).toBe(2); }); });
418
+ describe('getProject', () => {
419
+ it('returns null for unknown', () => {
420
+ expect(vault.getProject('/x')).toBeNull();
421
+ });
422
+ it('returns info', () => {
423
+ vault.registerProject('/t', 'T');
424
+ expect(vault.getProject('/t')!.name).toBe('T');
425
+ });
426
+ });
427
+ describe('listProjects', () => {
428
+ it('lists all', () => {
429
+ vault.registerProject('/a');
430
+ vault.registerProject('/b');
431
+ expect(vault.listProjects().length).toBe(2);
432
+ });
433
+ });
136
434
 
137
- describe('captureMemory', () => { it('creates with id', () => { const m = vault.captureMemory(makeMemoryInput()); expect(m.id).toMatch(/^mem-/); expect(m.archivedAt).toBeNull(); }); });
138
- describe('getMemory', () => { it('returns null', () => { expect(vault.getMemory('x')).toBeNull(); }); it('returns parsed', () => { const m = vault.captureMemory(makeMemoryInput({ topics: ['a', 'b'] })); expect(vault.getMemory(m.id)!.topics).toEqual(['a', 'b']); }); });
139
- describe('deleteMemory', () => { it('deletes', () => { const m = vault.captureMemory(makeMemoryInput()); expect(vault.deleteMemory(m.id)).toBe(true); }); it('returns false', () => { expect(vault.deleteMemory('x')).toBe(false); }); });
435
+ describe('captureMemory', () => {
436
+ it('creates with id', () => {
437
+ const m = vault.captureMemory(makeMemoryInput());
438
+ expect(m.id).toMatch(/^mem-/);
439
+ expect(m.archivedAt).toBeNull();
440
+ });
441
+ });
442
+ describe('getMemory', () => {
443
+ it('returns null', () => {
444
+ expect(vault.getMemory('x')).toBeNull();
445
+ });
446
+ it('returns parsed', () => {
447
+ const m = vault.captureMemory(makeMemoryInput({ topics: ['a', 'b'] }));
448
+ expect(vault.getMemory(m.id)!.topics).toEqual(['a', 'b']);
449
+ });
450
+ });
451
+ describe('deleteMemory', () => {
452
+ it('deletes', () => {
453
+ const m = vault.captureMemory(makeMemoryInput());
454
+ expect(vault.deleteMemory(m.id)).toBe(true);
455
+ });
456
+ it('returns false', () => {
457
+ expect(vault.deleteMemory('x')).toBe(false);
458
+ });
459
+ });
140
460
 
141
461
  describe('searchMemories', () => {
142
- it('empty for no match', () => { expect(vault.searchMemories('xyzzy')).toEqual([]); });
143
- it('finds by FTS', () => { vault.captureMemory(makeMemoryInput({ summary: 'React hooks opt' })); expect(vault.searchMemories('React hooks').length).toBeGreaterThanOrEqual(1); });
144
- it('filters by project', () => { vault.captureMemory(makeMemoryInput({ projectPath: '/a', summary: 'hooks alpha' })); vault.captureMemory(makeMemoryInput({ projectPath: '/b', summary: 'hooks beta' })); expect(vault.searchMemories('hooks', { projectPath: '/a' }).every(m => m.projectPath === '/a')).toBe(true); });
462
+ it('empty for no match', () => {
463
+ expect(vault.searchMemories('xyzzy')).toEqual([]);
464
+ });
465
+ it('finds by FTS', () => {
466
+ vault.captureMemory(makeMemoryInput({ summary: 'React hooks opt' }));
467
+ expect(vault.searchMemories('React hooks').length).toBeGreaterThanOrEqual(1);
468
+ });
469
+ it('filters by project', () => {
470
+ vault.captureMemory(makeMemoryInput({ projectPath: '/a', summary: 'hooks alpha' }));
471
+ vault.captureMemory(makeMemoryInput({ projectPath: '/b', summary: 'hooks beta' }));
472
+ expect(
473
+ vault.searchMemories('hooks', { projectPath: '/a' }).every((m) => m.projectPath === '/a'),
474
+ ).toBe(true);
475
+ });
145
476
  });
146
477
 
147
478
  describe('listMemories', () => {
148
- it('lists non-archived', () => { vault.captureMemory(makeMemoryInput()); vault.captureMemory(makeMemoryInput()); expect(vault.listMemories().length).toBe(2); });
149
- it('filters by type', () => { vault.captureMemory(makeMemoryInput({ type: 'session' })); vault.captureMemory(makeMemoryInput({ type: 'lesson' })); expect(vault.listMemories({ type: 'lesson' }).length).toBe(1); });
479
+ it('lists non-archived', () => {
480
+ vault.captureMemory(makeMemoryInput());
481
+ vault.captureMemory(makeMemoryInput());
482
+ expect(vault.listMemories().length).toBe(2);
483
+ });
484
+ it('filters by type', () => {
485
+ vault.captureMemory(makeMemoryInput({ type: 'session' }));
486
+ vault.captureMemory(makeMemoryInput({ type: 'lesson' }));
487
+ expect(vault.listMemories({ type: 'lesson' }).length).toBe(1);
488
+ });
150
489
  });
151
490
 
152
491
  describe('memoryStats', () => {
153
- it('zeros for empty', () => { expect(vault.memoryStats().total).toBe(0); });
154
- it('counts', () => { vault.captureMemory(makeMemoryInput({ projectPath: '/a', type: 'session' })); vault.captureMemory(makeMemoryInput({ projectPath: '/a', type: 'lesson' })); vault.captureMemory(makeMemoryInput({ projectPath: '/b', type: 'session' })); const s = vault.memoryStats(); expect(s.total).toBe(3); expect(s.byType).toEqual({ session: 2, lesson: 1 }); });
492
+ it('zeros for empty', () => {
493
+ expect(vault.memoryStats().total).toBe(0);
494
+ });
495
+ it('counts', () => {
496
+ vault.captureMemory(makeMemoryInput({ projectPath: '/a', type: 'session' }));
497
+ vault.captureMemory(makeMemoryInput({ projectPath: '/a', type: 'lesson' }));
498
+ vault.captureMemory(makeMemoryInput({ projectPath: '/b', type: 'session' }));
499
+ const s = vault.memoryStats();
500
+ expect(s.total).toBe(3);
501
+ expect(s.byType).toEqual({ session: 2, lesson: 1 });
502
+ });
155
503
  });
156
504
 
157
- describe('memoryStatsDetailed', () => { it('includes extended fields', () => { vault.captureMemory(makeMemoryInput()); const s = vault.memoryStatsDetailed(); expect(typeof s.oldest).toBe('number'); expect(s.archivedCount).toBe(0); }); });
158
- describe('exportMemories', () => { it('exports', () => { vault.captureMemory(makeMemoryInput()); expect(vault.exportMemories().length).toBe(1); }); });
159
- describe('importMemories', () => { it('imports and deduplicates', () => { vault.captureMemory(makeMemoryInput()); const exp = vault.exportMemories(); const v2 = new Vault(':memory:'); expect(v2.importMemories(exp).imported).toBe(1); expect(v2.importMemories(exp).skipped).toBe(1); v2.close(); }); });
160
- describe('pruneMemories', () => { it('removes old', () => { vault.captureMemory(makeMemoryInput()); vault.getProvider().run('UPDATE memories SET created_at = unixepoch() - 200 * 86400'); expect(vault.pruneMemories(100).pruned).toBe(1); }); it('leaves recent', () => { vault.captureMemory(makeMemoryInput()); expect(vault.pruneMemories(100).pruned).toBe(0); }); });
161
- describe('deduplicateMemories', () => { it('removes dupes', () => { vault.captureMemory(makeMemoryInput({ summary: 'same' })); vault.captureMemory(makeMemoryInput({ summary: 'same' })); expect(vault.deduplicateMemories().removed).toBe(1); }); });
162
- describe('memoryTopics', () => { it('returns frequency', () => { vault.captureMemory(makeMemoryInput({ topics: ['react', 'hooks'] })); vault.captureMemory(makeMemoryInput({ topics: ['react', 'css'] })); expect(vault.memoryTopics()[0]).toEqual({ topic: 'react', count: 2 }); }); });
163
- describe('memoriesByProject', () => { it('groups by project', () => { vault.captureMemory(makeMemoryInput({ projectPath: '/a' })); vault.captureMemory(makeMemoryInput({ projectPath: '/a' })); vault.captureMemory(makeMemoryInput({ projectPath: '/b' })); expect(vault.memoriesByProject().find(p => p.project === '/a')?.count).toBe(2); }); });
505
+ describe('memoryStatsDetailed', () => {
506
+ it('includes extended fields', () => {
507
+ vault.captureMemory(makeMemoryInput());
508
+ const s = vault.memoryStatsDetailed();
509
+ expect(typeof s.oldest).toBe('number');
510
+ expect(s.archivedCount).toBe(0);
511
+ });
512
+ });
513
+ describe('exportMemories', () => {
514
+ it('exports', () => {
515
+ vault.captureMemory(makeMemoryInput());
516
+ expect(vault.exportMemories().length).toBe(1);
517
+ });
518
+ });
519
+ describe('importMemories', () => {
520
+ it('imports and deduplicates', () => {
521
+ vault.captureMemory(makeMemoryInput());
522
+ const exp = vault.exportMemories();
523
+ const v2 = new Vault(':memory:');
524
+ expect(v2.importMemories(exp).imported).toBe(1);
525
+ expect(v2.importMemories(exp).skipped).toBe(1);
526
+ v2.close();
527
+ });
528
+ });
529
+ describe('pruneMemories', () => {
530
+ it('removes old', () => {
531
+ vault.captureMemory(makeMemoryInput());
532
+ vault.getProvider().run('UPDATE memories SET created_at = unixepoch() - 200 * 86400');
533
+ expect(vault.pruneMemories(100).pruned).toBe(1);
534
+ });
535
+ it('leaves recent', () => {
536
+ vault.captureMemory(makeMemoryInput());
537
+ expect(vault.pruneMemories(100).pruned).toBe(0);
538
+ });
539
+ });
540
+ describe('deduplicateMemories', () => {
541
+ it('removes dupes', () => {
542
+ vault.captureMemory(makeMemoryInput({ summary: 'same' }));
543
+ vault.captureMemory(makeMemoryInput({ summary: 'same' }));
544
+ expect(vault.deduplicateMemories().removed).toBe(1);
545
+ });
546
+ });
547
+ describe('memoryTopics', () => {
548
+ it('returns frequency', () => {
549
+ vault.captureMemory(makeMemoryInput({ topics: ['react', 'hooks'] }));
550
+ vault.captureMemory(makeMemoryInput({ topics: ['react', 'css'] }));
551
+ expect(vault.memoryTopics()[0]).toEqual({ topic: 'react', count: 2 });
552
+ });
553
+ });
554
+ describe('memoriesByProject', () => {
555
+ it('groups by project', () => {
556
+ vault.captureMemory(makeMemoryInput({ projectPath: '/a' }));
557
+ vault.captureMemory(makeMemoryInput({ projectPath: '/a' }));
558
+ vault.captureMemory(makeMemoryInput({ projectPath: '/b' }));
559
+ expect(vault.memoriesByProject().find((p) => p.project === '/a')?.count).toBe(2);
560
+ });
561
+ });
164
562
 
165
- describe('getProvider', () => { it('returns provider', () => { expect(vault.getProvider().backend).toBe('sqlite'); }); });
166
- describe('getDb', () => { it('returns db', () => { expect(vault.getDb()).toBeTruthy(); }); });
167
- describe('close', () => { it('does not throw', () => { const v = new Vault(':memory:'); expect(() => v.close()).not.toThrow(); }); });
563
+ describe('getProvider', () => {
564
+ it('returns provider', () => {
565
+ expect(vault.getProvider().backend).toBe('sqlite');
566
+ });
567
+ });
568
+ describe('getDb', () => {
569
+ it('returns db', () => {
570
+ expect(vault.getDb()).toBeTruthy();
571
+ });
572
+ });
573
+ describe('close', () => {
574
+ it('does not throw', () => {
575
+ const v = new Vault(':memory:');
576
+ expect(() => v.close()).not.toThrow();
577
+ });
578
+ });
168
579
  });