@soleri/core 2.4.0 → 2.5.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 (300) hide show
  1. package/dist/brain/brain.d.ts +7 -0
  2. package/dist/brain/brain.d.ts.map +1 -1
  3. package/dist/brain/brain.js +56 -9
  4. package/dist/brain/brain.js.map +1 -1
  5. package/dist/brain/types.d.ts +2 -2
  6. package/dist/brain/types.d.ts.map +1 -1
  7. package/dist/cognee/client.d.ts +3 -0
  8. package/dist/cognee/client.d.ts.map +1 -1
  9. package/dist/cognee/client.js +17 -0
  10. package/dist/cognee/client.js.map +1 -1
  11. package/dist/cognee/sync-manager.d.ts +94 -0
  12. package/dist/cognee/sync-manager.d.ts.map +1 -0
  13. package/dist/cognee/sync-manager.js +293 -0
  14. package/dist/cognee/sync-manager.js.map +1 -0
  15. package/dist/curator/curator.d.ts +8 -1
  16. package/dist/curator/curator.d.ts.map +1 -1
  17. package/dist/curator/curator.js +64 -1
  18. package/dist/curator/curator.js.map +1 -1
  19. package/dist/errors/classify.d.ts +13 -0
  20. package/dist/errors/classify.d.ts.map +1 -0
  21. package/dist/errors/classify.js +97 -0
  22. package/dist/errors/classify.js.map +1 -0
  23. package/dist/errors/index.d.ts +6 -0
  24. package/dist/errors/index.d.ts.map +1 -0
  25. package/dist/errors/index.js +4 -0
  26. package/dist/errors/index.js.map +1 -0
  27. package/dist/errors/retry.d.ts +40 -0
  28. package/dist/errors/retry.d.ts.map +1 -0
  29. package/dist/errors/retry.js +97 -0
  30. package/dist/errors/retry.js.map +1 -0
  31. package/dist/errors/types.d.ts +48 -0
  32. package/dist/errors/types.d.ts.map +1 -0
  33. package/dist/errors/types.js +59 -0
  34. package/dist/errors/types.js.map +1 -0
  35. package/dist/index.d.ts +25 -5
  36. package/dist/index.d.ts.map +1 -1
  37. package/dist/index.js +21 -3
  38. package/dist/index.js.map +1 -1
  39. package/dist/intake/content-classifier.d.ts +14 -0
  40. package/dist/intake/content-classifier.d.ts.map +1 -0
  41. package/dist/intake/content-classifier.js +125 -0
  42. package/dist/intake/content-classifier.js.map +1 -0
  43. package/dist/intake/dedup-gate.d.ts +17 -0
  44. package/dist/intake/dedup-gate.d.ts.map +1 -0
  45. package/dist/intake/dedup-gate.js +66 -0
  46. package/dist/intake/dedup-gate.js.map +1 -0
  47. package/dist/intake/intake-pipeline.d.ts +63 -0
  48. package/dist/intake/intake-pipeline.d.ts.map +1 -0
  49. package/dist/intake/intake-pipeline.js +373 -0
  50. package/dist/intake/intake-pipeline.js.map +1 -0
  51. package/dist/intake/types.d.ts +65 -0
  52. package/dist/intake/types.d.ts.map +1 -0
  53. package/dist/intake/types.js +3 -0
  54. package/dist/intake/types.js.map +1 -0
  55. package/dist/intelligence/loader.js +1 -1
  56. package/dist/intelligence/loader.js.map +1 -1
  57. package/dist/intelligence/types.d.ts +3 -1
  58. package/dist/intelligence/types.d.ts.map +1 -1
  59. package/dist/loop/loop-manager.d.ts +58 -7
  60. package/dist/loop/loop-manager.d.ts.map +1 -1
  61. package/dist/loop/loop-manager.js +280 -6
  62. package/dist/loop/loop-manager.js.map +1 -1
  63. package/dist/loop/types.d.ts +69 -1
  64. package/dist/loop/types.d.ts.map +1 -1
  65. package/dist/loop/types.js +4 -1
  66. package/dist/loop/types.js.map +1 -1
  67. package/dist/persistence/index.d.ts +3 -0
  68. package/dist/persistence/index.d.ts.map +1 -0
  69. package/dist/persistence/index.js +2 -0
  70. package/dist/persistence/index.js.map +1 -0
  71. package/dist/persistence/sqlite-provider.d.ts +25 -0
  72. package/dist/persistence/sqlite-provider.d.ts.map +1 -0
  73. package/dist/persistence/sqlite-provider.js +59 -0
  74. package/dist/persistence/sqlite-provider.js.map +1 -0
  75. package/dist/persistence/types.d.ts +36 -0
  76. package/dist/persistence/types.d.ts.map +1 -0
  77. package/dist/persistence/types.js +8 -0
  78. package/dist/persistence/types.js.map +1 -0
  79. package/dist/planning/gap-analysis.d.ts +47 -4
  80. package/dist/planning/gap-analysis.d.ts.map +1 -1
  81. package/dist/planning/gap-analysis.js +190 -13
  82. package/dist/planning/gap-analysis.js.map +1 -1
  83. package/dist/planning/gap-types.d.ts +1 -1
  84. package/dist/planning/gap-types.d.ts.map +1 -1
  85. package/dist/planning/gap-types.js.map +1 -1
  86. package/dist/planning/planner.d.ts +277 -9
  87. package/dist/planning/planner.d.ts.map +1 -1
  88. package/dist/planning/planner.js +611 -46
  89. package/dist/planning/planner.js.map +1 -1
  90. package/dist/playbooks/generic/brainstorming.d.ts +9 -0
  91. package/dist/playbooks/generic/brainstorming.d.ts.map +1 -0
  92. package/dist/playbooks/generic/brainstorming.js +105 -0
  93. package/dist/playbooks/generic/brainstorming.js.map +1 -0
  94. package/dist/playbooks/generic/code-review.d.ts +11 -0
  95. package/dist/playbooks/generic/code-review.d.ts.map +1 -0
  96. package/dist/playbooks/generic/code-review.js +176 -0
  97. package/dist/playbooks/generic/code-review.js.map +1 -0
  98. package/dist/playbooks/generic/subagent-execution.d.ts +9 -0
  99. package/dist/playbooks/generic/subagent-execution.d.ts.map +1 -0
  100. package/dist/playbooks/generic/subagent-execution.js +68 -0
  101. package/dist/playbooks/generic/subagent-execution.js.map +1 -0
  102. package/dist/playbooks/generic/systematic-debugging.d.ts +9 -0
  103. package/dist/playbooks/generic/systematic-debugging.d.ts.map +1 -0
  104. package/dist/playbooks/generic/systematic-debugging.js +87 -0
  105. package/dist/playbooks/generic/systematic-debugging.js.map +1 -0
  106. package/dist/playbooks/generic/tdd.d.ts +9 -0
  107. package/dist/playbooks/generic/tdd.d.ts.map +1 -0
  108. package/dist/playbooks/generic/tdd.js +70 -0
  109. package/dist/playbooks/generic/tdd.js.map +1 -0
  110. package/dist/playbooks/generic/verification.d.ts +9 -0
  111. package/dist/playbooks/generic/verification.d.ts.map +1 -0
  112. package/dist/playbooks/generic/verification.js +74 -0
  113. package/dist/playbooks/generic/verification.js.map +1 -0
  114. package/dist/playbooks/index.d.ts +4 -0
  115. package/dist/playbooks/index.d.ts.map +1 -0
  116. package/dist/playbooks/index.js +5 -0
  117. package/dist/playbooks/index.js.map +1 -0
  118. package/dist/playbooks/playbook-registry.d.ts +42 -0
  119. package/dist/playbooks/playbook-registry.d.ts.map +1 -0
  120. package/dist/playbooks/playbook-registry.js +227 -0
  121. package/dist/playbooks/playbook-registry.js.map +1 -0
  122. package/dist/playbooks/playbook-seeder.d.ts +47 -0
  123. package/dist/playbooks/playbook-seeder.d.ts.map +1 -0
  124. package/dist/playbooks/playbook-seeder.js +104 -0
  125. package/dist/playbooks/playbook-seeder.js.map +1 -0
  126. package/dist/playbooks/playbook-types.d.ts +132 -0
  127. package/dist/playbooks/playbook-types.d.ts.map +1 -0
  128. package/dist/playbooks/playbook-types.js +12 -0
  129. package/dist/playbooks/playbook-types.js.map +1 -0
  130. package/dist/project/project-registry.d.ts.map +1 -1
  131. package/dist/project/project-registry.js +9 -11
  132. package/dist/project/project-registry.js.map +1 -1
  133. package/dist/prompts/index.d.ts +4 -0
  134. package/dist/prompts/index.d.ts.map +1 -0
  135. package/dist/prompts/index.js +3 -0
  136. package/dist/prompts/index.js.map +1 -0
  137. package/dist/prompts/parser.d.ts +17 -0
  138. package/dist/prompts/parser.d.ts.map +1 -0
  139. package/dist/prompts/parser.js +47 -0
  140. package/dist/prompts/parser.js.map +1 -0
  141. package/dist/prompts/template-manager.d.ts +25 -0
  142. package/dist/prompts/template-manager.d.ts.map +1 -0
  143. package/dist/prompts/template-manager.js +71 -0
  144. package/dist/prompts/template-manager.js.map +1 -0
  145. package/dist/prompts/types.d.ts +26 -0
  146. package/dist/prompts/types.d.ts.map +1 -0
  147. package/dist/prompts/types.js +5 -0
  148. package/dist/prompts/types.js.map +1 -0
  149. package/dist/runtime/admin-extra-ops.d.ts +5 -3
  150. package/dist/runtime/admin-extra-ops.d.ts.map +1 -1
  151. package/dist/runtime/admin-extra-ops.js +322 -11
  152. package/dist/runtime/admin-extra-ops.js.map +1 -1
  153. package/dist/runtime/admin-ops.d.ts.map +1 -1
  154. package/dist/runtime/admin-ops.js +10 -3
  155. package/dist/runtime/admin-ops.js.map +1 -1
  156. package/dist/runtime/capture-ops.d.ts.map +1 -1
  157. package/dist/runtime/capture-ops.js +20 -2
  158. package/dist/runtime/capture-ops.js.map +1 -1
  159. package/dist/runtime/cognee-sync-ops.d.ts +12 -0
  160. package/dist/runtime/cognee-sync-ops.d.ts.map +1 -0
  161. package/dist/runtime/cognee-sync-ops.js +55 -0
  162. package/dist/runtime/cognee-sync-ops.js.map +1 -0
  163. package/dist/runtime/core-ops.d.ts +8 -6
  164. package/dist/runtime/core-ops.d.ts.map +1 -1
  165. package/dist/runtime/core-ops.js +226 -9
  166. package/dist/runtime/core-ops.js.map +1 -1
  167. package/dist/runtime/curator-extra-ops.d.ts +2 -2
  168. package/dist/runtime/curator-extra-ops.d.ts.map +1 -1
  169. package/dist/runtime/curator-extra-ops.js +15 -3
  170. package/dist/runtime/curator-extra-ops.js.map +1 -1
  171. package/dist/runtime/domain-ops.js +2 -2
  172. package/dist/runtime/domain-ops.js.map +1 -1
  173. package/dist/runtime/grading-ops.d.ts.map +1 -1
  174. package/dist/runtime/grading-ops.js.map +1 -1
  175. package/dist/runtime/intake-ops.d.ts +14 -0
  176. package/dist/runtime/intake-ops.d.ts.map +1 -0
  177. package/dist/runtime/intake-ops.js +110 -0
  178. package/dist/runtime/intake-ops.js.map +1 -0
  179. package/dist/runtime/loop-ops.d.ts +5 -4
  180. package/dist/runtime/loop-ops.d.ts.map +1 -1
  181. package/dist/runtime/loop-ops.js +84 -12
  182. package/dist/runtime/loop-ops.js.map +1 -1
  183. package/dist/runtime/memory-cross-project-ops.d.ts.map +1 -1
  184. package/dist/runtime/memory-cross-project-ops.js.map +1 -1
  185. package/dist/runtime/memory-extra-ops.js +5 -5
  186. package/dist/runtime/memory-extra-ops.js.map +1 -1
  187. package/dist/runtime/orchestrate-ops.d.ts.map +1 -1
  188. package/dist/runtime/orchestrate-ops.js +8 -2
  189. package/dist/runtime/orchestrate-ops.js.map +1 -1
  190. package/dist/runtime/planning-extra-ops.d.ts +13 -5
  191. package/dist/runtime/planning-extra-ops.d.ts.map +1 -1
  192. package/dist/runtime/planning-extra-ops.js +381 -18
  193. package/dist/runtime/planning-extra-ops.js.map +1 -1
  194. package/dist/runtime/playbook-ops.d.ts +14 -0
  195. package/dist/runtime/playbook-ops.d.ts.map +1 -0
  196. package/dist/runtime/playbook-ops.js +141 -0
  197. package/dist/runtime/playbook-ops.js.map +1 -0
  198. package/dist/runtime/project-ops.d.ts.map +1 -1
  199. package/dist/runtime/project-ops.js +7 -2
  200. package/dist/runtime/project-ops.js.map +1 -1
  201. package/dist/runtime/runtime.d.ts.map +1 -1
  202. package/dist/runtime/runtime.js +27 -8
  203. package/dist/runtime/runtime.js.map +1 -1
  204. package/dist/runtime/types.d.ts +8 -0
  205. package/dist/runtime/types.d.ts.map +1 -1
  206. package/dist/runtime/vault-extra-ops.d.ts +3 -2
  207. package/dist/runtime/vault-extra-ops.d.ts.map +1 -1
  208. package/dist/runtime/vault-extra-ops.js +345 -4
  209. package/dist/runtime/vault-extra-ops.js.map +1 -1
  210. package/dist/vault/playbook.d.ts +34 -0
  211. package/dist/vault/playbook.d.ts.map +1 -0
  212. package/dist/vault/playbook.js +60 -0
  213. package/dist/vault/playbook.js.map +1 -0
  214. package/dist/vault/vault.d.ts +31 -32
  215. package/dist/vault/vault.d.ts.map +1 -1
  216. package/dist/vault/vault.js +201 -181
  217. package/dist/vault/vault.js.map +1 -1
  218. package/package.json +7 -3
  219. package/src/__tests__/admin-extra-ops.test.ts +62 -15
  220. package/src/__tests__/admin-ops.test.ts +2 -2
  221. package/src/__tests__/brain.test.ts +3 -3
  222. package/src/__tests__/cognee-integration.test.ts +80 -0
  223. package/src/__tests__/cognee-sync-manager.test.ts +103 -0
  224. package/src/__tests__/core-ops.test.ts +30 -4
  225. package/src/__tests__/curator-extra-ops.test.ts +24 -2
  226. package/src/__tests__/errors.test.ts +388 -0
  227. package/src/__tests__/grading-ops.test.ts +28 -7
  228. package/src/__tests__/intake-pipeline.test.ts +162 -0
  229. package/src/__tests__/loop-ops.test.ts +74 -3
  230. package/src/__tests__/memory-cross-project-ops.test.ts +3 -1
  231. package/src/__tests__/orchestrate-ops.test.ts +8 -3
  232. package/src/__tests__/persistence.test.ts +225 -0
  233. package/src/__tests__/planner.test.ts +99 -21
  234. package/src/__tests__/planning-extra-ops.test.ts +168 -10
  235. package/src/__tests__/playbook-registry.test.ts +326 -0
  236. package/src/__tests__/playbook-seeder.test.ts +163 -0
  237. package/src/__tests__/playbook.test.ts +389 -0
  238. package/src/__tests__/project-ops.test.ts +18 -4
  239. package/src/__tests__/template-manager.test.ts +222 -0
  240. package/src/__tests__/vault-extra-ops.test.ts +82 -7
  241. package/src/brain/brain.ts +71 -9
  242. package/src/brain/types.ts +2 -2
  243. package/src/cognee/client.ts +18 -0
  244. package/src/cognee/sync-manager.ts +389 -0
  245. package/src/curator/curator.ts +88 -7
  246. package/src/errors/classify.ts +102 -0
  247. package/src/errors/index.ts +5 -0
  248. package/src/errors/retry.ts +132 -0
  249. package/src/errors/types.ts +81 -0
  250. package/src/index.ts +114 -3
  251. package/src/intake/content-classifier.ts +146 -0
  252. package/src/intake/dedup-gate.ts +92 -0
  253. package/src/intake/intake-pipeline.ts +503 -0
  254. package/src/intake/types.ts +69 -0
  255. package/src/intelligence/loader.ts +1 -1
  256. package/src/intelligence/types.ts +3 -1
  257. package/src/loop/loop-manager.ts +325 -7
  258. package/src/loop/types.ts +72 -1
  259. package/src/persistence/index.ts +7 -0
  260. package/src/persistence/sqlite-provider.ts +62 -0
  261. package/src/persistence/types.ts +44 -0
  262. package/src/planning/gap-analysis.ts +286 -17
  263. package/src/planning/gap-types.ts +4 -1
  264. package/src/planning/planner.ts +828 -55
  265. package/src/playbooks/generic/brainstorming.ts +110 -0
  266. package/src/playbooks/generic/code-review.ts +181 -0
  267. package/src/playbooks/generic/subagent-execution.ts +74 -0
  268. package/src/playbooks/generic/systematic-debugging.ts +92 -0
  269. package/src/playbooks/generic/tdd.ts +75 -0
  270. package/src/playbooks/generic/verification.ts +79 -0
  271. package/src/playbooks/index.ts +27 -0
  272. package/src/playbooks/playbook-registry.ts +284 -0
  273. package/src/playbooks/playbook-seeder.ts +119 -0
  274. package/src/playbooks/playbook-types.ts +162 -0
  275. package/src/project/project-registry.ts +29 -17
  276. package/src/prompts/index.ts +3 -0
  277. package/src/prompts/parser.ts +59 -0
  278. package/src/prompts/template-manager.ts +77 -0
  279. package/src/prompts/types.ts +28 -0
  280. package/src/runtime/admin-extra-ops.ts +358 -13
  281. package/src/runtime/admin-ops.ts +17 -6
  282. package/src/runtime/capture-ops.ts +25 -6
  283. package/src/runtime/cognee-sync-ops.ts +63 -0
  284. package/src/runtime/core-ops.ts +258 -8
  285. package/src/runtime/curator-extra-ops.ts +17 -3
  286. package/src/runtime/domain-ops.ts +2 -2
  287. package/src/runtime/grading-ops.ts +11 -2
  288. package/src/runtime/intake-ops.ts +126 -0
  289. package/src/runtime/loop-ops.ts +96 -13
  290. package/src/runtime/memory-cross-project-ops.ts +1 -2
  291. package/src/runtime/memory-extra-ops.ts +5 -5
  292. package/src/runtime/orchestrate-ops.ts +8 -2
  293. package/src/runtime/planning-extra-ops.ts +414 -23
  294. package/src/runtime/playbook-ops.ts +169 -0
  295. package/src/runtime/project-ops.ts +9 -3
  296. package/src/runtime/runtime.ts +35 -9
  297. package/src/runtime/types.ts +8 -0
  298. package/src/runtime/vault-extra-ops.ts +385 -4
  299. package/src/vault/playbook.ts +87 -0
  300. package/src/vault/vault.ts +301 -235
@@ -0,0 +1,163 @@
1
+ import { describe, it, expect, beforeEach, afterEach } from 'vitest';
2
+ import { Vault } from '../vault/vault.js';
3
+ import {
4
+ seedDefaultPlaybooks,
5
+ playbookDefinitionToEntry,
6
+ entryToPlaybookDefinition,
7
+ } from '../playbooks/playbook-seeder.js';
8
+ import { getAllBuiltinPlaybooks } from '../playbooks/playbook-registry.js';
9
+
10
+ describe('playbookDefinitionToEntry', () => {
11
+ it('should convert a PlaybookDefinition to IntelligenceEntry', () => {
12
+ const def = getAllBuiltinPlaybooks()[0];
13
+ const entry = playbookDefinitionToEntry(def);
14
+
15
+ expect(entry.id).toBe(def.id);
16
+ expect(entry.type).toBe('playbook');
17
+ expect(entry.domain).toBe(def.category);
18
+ expect(entry.title).toBe(def.title);
19
+ expect(entry.severity).toBe('suggestion');
20
+ expect(entry.description).toBe(def.description);
21
+ expect(entry.example).toBe(def.steps);
22
+ expect(entry.why).toBe(def.expectedOutcome);
23
+ expect(entry.tags).toEqual(def.tags);
24
+ });
25
+
26
+ it('should embed the full definition in context', () => {
27
+ const def = getAllBuiltinPlaybooks()[0];
28
+ const entry = playbookDefinitionToEntry(def);
29
+ expect(entry.context).toContain('__PLAYBOOK_DEF__');
30
+ expect(entry.context).toContain('__END_DEF__');
31
+ expect(entry.context).toContain(def.trigger);
32
+ });
33
+ });
34
+
35
+ describe('entryToPlaybookDefinition', () => {
36
+ it('should round-trip through entry and back', () => {
37
+ const original = getAllBuiltinPlaybooks()[0];
38
+ const entry = playbookDefinitionToEntry(original);
39
+ const restored = entryToPlaybookDefinition(entry);
40
+
41
+ expect(restored).not.toBeNull();
42
+ expect(restored!.id).toBe(original.id);
43
+ expect(restored!.tier).toBe(original.tier);
44
+ expect(restored!.title).toBe(original.title);
45
+ expect(restored!.matchIntents).toEqual(original.matchIntents);
46
+ expect(restored!.matchKeywords).toEqual(original.matchKeywords);
47
+ expect(restored!.gates).toEqual(original.gates);
48
+ expect(restored!.taskTemplates).toEqual(original.taskTemplates);
49
+ });
50
+
51
+ it('should return null for non-playbook entry', () => {
52
+ const result = entryToPlaybookDefinition({
53
+ id: 'pat-1',
54
+ type: 'pattern',
55
+ domain: 'test',
56
+ title: 'Test',
57
+ severity: 'warning',
58
+ description: 'Test',
59
+ tags: [],
60
+ });
61
+ expect(result).toBeNull();
62
+ });
63
+
64
+ it('should return null for playbook entry without metadata', () => {
65
+ const result = entryToPlaybookDefinition({
66
+ id: 'pb-1',
67
+ type: 'playbook',
68
+ domain: 'test',
69
+ title: 'Test',
70
+ severity: 'suggestion',
71
+ description: 'Test',
72
+ context: 'plain text without markers',
73
+ tags: [],
74
+ });
75
+ expect(result).toBeNull();
76
+ });
77
+
78
+ it('should return null for malformed JSON in metadata', () => {
79
+ const result = entryToPlaybookDefinition({
80
+ id: 'pb-1',
81
+ type: 'playbook',
82
+ domain: 'test',
83
+ title: 'Test',
84
+ severity: 'suggestion',
85
+ description: 'Test',
86
+ context: '__PLAYBOOK_DEF__{not valid json}__END_DEF__',
87
+ tags: [],
88
+ });
89
+ expect(result).toBeNull();
90
+ });
91
+ });
92
+
93
+ describe('seedDefaultPlaybooks', () => {
94
+ let vault: Vault;
95
+
96
+ beforeEach(() => {
97
+ vault = new Vault(':memory:');
98
+ });
99
+
100
+ afterEach(() => {
101
+ vault.close();
102
+ });
103
+
104
+ it('should seed 6 built-in playbooks into empty vault', () => {
105
+ const result = seedDefaultPlaybooks(vault);
106
+ expect(result.seeded).toBe(6);
107
+ expect(result.skipped).toBe(0);
108
+ expect(result.errors).toBe(0);
109
+ expect(result.details).toHaveLength(6);
110
+ expect(result.details.every((d) => d.action === 'seeded')).toBe(true);
111
+
112
+ // Verify they're in the vault
113
+ const entries = vault.list({ type: 'playbook' });
114
+ expect(entries).toHaveLength(6);
115
+ });
116
+
117
+ it('should be idempotent — second call skips all', () => {
118
+ seedDefaultPlaybooks(vault);
119
+ const result = seedDefaultPlaybooks(vault);
120
+
121
+ expect(result.seeded).toBe(0);
122
+ expect(result.skipped).toBe(6);
123
+ expect(result.errors).toBe(0);
124
+ expect(result.details.every((d) => d.action === 'skipped')).toBe(true);
125
+ });
126
+
127
+ it('should not overwrite user modifications', () => {
128
+ // Seed first
129
+ seedDefaultPlaybooks(vault);
130
+
131
+ // Simulate user modifying a playbook by removing and re-adding with different content
132
+ const builtins = getAllBuiltinPlaybooks();
133
+ vault.remove(builtins[0].id);
134
+ vault.add({
135
+ id: builtins[0].id,
136
+ type: 'playbook',
137
+ domain: 'user-custom',
138
+ title: 'User Modified Playbook',
139
+ severity: 'suggestion',
140
+ description: 'Modified by user',
141
+ tags: ['custom'],
142
+ });
143
+
144
+ // Re-seed should skip this one
145
+ const result = seedDefaultPlaybooks(vault);
146
+ expect(result.skipped).toBe(6); // all exist, including user-modified one
147
+
148
+ // Verify user's version is preserved
149
+ const entry = vault.get(builtins[0].id);
150
+ expect(entry?.title).toBe('User Modified Playbook');
151
+ });
152
+
153
+ it('should report correct counts in details', () => {
154
+ const result = seedDefaultPlaybooks(vault);
155
+ const ids = result.details.map((d) => d.id);
156
+ expect(ids).toContain('generic-tdd');
157
+ expect(ids).toContain('generic-brainstorming');
158
+ expect(ids).toContain('generic-code-review');
159
+ expect(ids).toContain('generic-subagent-execution');
160
+ expect(ids).toContain('generic-systematic-debugging');
161
+ expect(ids).toContain('generic-verification');
162
+ });
163
+ });
@@ -0,0 +1,389 @@
1
+ import { describe, it, expect, beforeEach, afterEach } from 'vitest';
2
+ import { mkdirSync, rmSync } from 'node:fs';
3
+ import { join } from 'node:path';
4
+ import { tmpdir } from 'node:os';
5
+ import { Vault } from '../vault/vault.js';
6
+ import { validatePlaybook, parsePlaybookFromEntry } from '../vault/playbook.js';
7
+ import type { Playbook } from '../vault/playbook.js';
8
+ import type { IntelligenceEntry } from '../intelligence/types.js';
9
+ import { createAgentRuntime } from '../runtime/runtime.js';
10
+ import { createCoreOps } from '../runtime/core-ops.js';
11
+ import type { AgentRuntime, OpDefinition } from '../runtime/types.js';
12
+
13
+ function makePlaybook(overrides: Partial<Playbook> = {}): Playbook {
14
+ return {
15
+ id: overrides.id ?? 'pb-1',
16
+ title: overrides.title ?? 'Deploy Checklist',
17
+ domain: overrides.domain ?? 'ops',
18
+ description: overrides.description ?? 'Standard deploy procedure.',
19
+ steps: overrides.steps ?? [
20
+ {
21
+ order: 1,
22
+ title: 'Run tests',
23
+ description: 'Execute full test suite',
24
+ validation: 'All tests pass',
25
+ },
26
+ { order: 2, title: 'Build', description: 'Run production build' },
27
+ {
28
+ order: 3,
29
+ title: 'Deploy',
30
+ description: 'Push to production',
31
+ validation: 'Health check passes',
32
+ },
33
+ ],
34
+ tags: overrides.tags ?? ['deploy', 'checklist'],
35
+ createdAt: overrides.createdAt ?? Date.now(),
36
+ updatedAt: overrides.updatedAt ?? Date.now(),
37
+ };
38
+ }
39
+
40
+ function playbookToEntry(pb: Playbook): IntelligenceEntry {
41
+ return {
42
+ id: pb.id,
43
+ type: 'playbook',
44
+ domain: pb.domain,
45
+ title: pb.title,
46
+ severity: 'suggestion',
47
+ description: pb.description,
48
+ context: JSON.stringify({ steps: pb.steps }),
49
+ tags: pb.tags,
50
+ };
51
+ }
52
+
53
+ describe('validatePlaybook', () => {
54
+ it('should pass for a valid playbook', () => {
55
+ const result = validatePlaybook(makePlaybook());
56
+ expect(result.valid).toBe(true);
57
+ expect(result.errors).toHaveLength(0);
58
+ });
59
+
60
+ it('should fail for empty title', () => {
61
+ const result = validatePlaybook(makePlaybook({ title: '' }));
62
+ expect(result.valid).toBe(false);
63
+ expect(result.errors).toContain('Playbook title must not be empty');
64
+ });
65
+
66
+ it('should fail for no steps', () => {
67
+ const result = validatePlaybook(makePlaybook({ steps: [] }));
68
+ expect(result.valid).toBe(false);
69
+ expect(result.errors).toContain('Playbook must have at least one step');
70
+ });
71
+
72
+ it('should fail for non-sequential step orders', () => {
73
+ const result = validatePlaybook(
74
+ makePlaybook({
75
+ steps: [
76
+ { order: 1, title: 'Step A', description: 'Desc A' },
77
+ { order: 3, title: 'Step B', description: 'Desc B' },
78
+ ],
79
+ }),
80
+ );
81
+ expect(result.valid).toBe(false);
82
+ expect(result.errors.some((e) => e.includes('order 3, expected 2'))).toBe(true);
83
+ });
84
+ });
85
+
86
+ describe('parsePlaybookFromEntry', () => {
87
+ it('should parse a valid playbook entry', () => {
88
+ const pb = makePlaybook();
89
+ const entry = playbookToEntry(pb);
90
+ const parsed = parsePlaybookFromEntry(entry);
91
+ expect(parsed).not.toBeNull();
92
+ expect(parsed!.id).toBe(pb.id);
93
+ expect(parsed!.title).toBe(pb.title);
94
+ expect(parsed!.steps).toHaveLength(3);
95
+ expect(parsed!.steps[0].title).toBe('Run tests');
96
+ });
97
+
98
+ it('should return null for a non-playbook entry', () => {
99
+ const entry: IntelligenceEntry = {
100
+ id: 'pat-1',
101
+ type: 'pattern',
102
+ domain: 'testing',
103
+ title: 'Some Pattern',
104
+ severity: 'warning',
105
+ description: 'A pattern.',
106
+ tags: [],
107
+ };
108
+ expect(parsePlaybookFromEntry(entry)).toBeNull();
109
+ });
110
+
111
+ it('should return null for invalid context JSON', () => {
112
+ const entry: IntelligenceEntry = {
113
+ id: 'pb-bad',
114
+ type: 'playbook',
115
+ domain: 'ops',
116
+ title: 'Bad Playbook',
117
+ severity: 'suggestion',
118
+ description: 'Has bad context.',
119
+ context: 'not valid json',
120
+ tags: [],
121
+ };
122
+ expect(parsePlaybookFromEntry(entry)).toBeNull();
123
+ });
124
+
125
+ it('should return null when context has no steps array', () => {
126
+ const entry: IntelligenceEntry = {
127
+ id: 'pb-nosteps',
128
+ type: 'playbook',
129
+ domain: 'ops',
130
+ title: 'No Steps',
131
+ severity: 'suggestion',
132
+ description: 'Missing steps.',
133
+ context: JSON.stringify({ something: 'else' }),
134
+ tags: [],
135
+ };
136
+ expect(parsePlaybookFromEntry(entry)).toBeNull();
137
+ });
138
+ });
139
+
140
+ describe('Vault playbook integration', () => {
141
+ let vault: Vault;
142
+
143
+ beforeEach(() => {
144
+ vault = new Vault(':memory:');
145
+ });
146
+
147
+ afterEach(() => {
148
+ vault.close();
149
+ });
150
+
151
+ it('should insert and retrieve a playbook via vault.list', () => {
152
+ const pb = makePlaybook();
153
+ const entry = playbookToEntry(pb);
154
+ vault.seed([entry]);
155
+
156
+ const results = vault.list({ type: 'playbook' });
157
+ expect(results).toHaveLength(1);
158
+ expect(results[0].id).toBe('pb-1');
159
+ expect(results[0].type).toBe('playbook');
160
+ });
161
+
162
+ it('should find a playbook via FTS5 search', () => {
163
+ const pb = makePlaybook({ title: 'Deploy Checklist Procedure' });
164
+ const entry = playbookToEntry(pb);
165
+ vault.seed([entry]);
166
+
167
+ const results = vault.search('deploy checklist');
168
+ expect(results.length).toBeGreaterThan(0);
169
+ expect(results[0].entry.id).toBe(pb.id);
170
+ });
171
+ });
172
+
173
+ describe('playbook_create op', () => {
174
+ let runtime: AgentRuntime;
175
+ let ops: OpDefinition[];
176
+ let plannerDir: string;
177
+
178
+ function findOp(name: string): OpDefinition {
179
+ const op = ops.find((o) => o.name === name);
180
+ if (!op) throw new Error(`Op "${name}" not found`);
181
+ return op;
182
+ }
183
+
184
+ beforeEach(() => {
185
+ plannerDir = join(tmpdir(), 'playbook-create-test-' + Date.now());
186
+ mkdirSync(plannerDir, { recursive: true });
187
+ runtime = createAgentRuntime({
188
+ agentId: 'test',
189
+ vaultPath: ':memory:',
190
+ plansPath: join(plannerDir, 'plans.json'),
191
+ });
192
+ ops = createCoreOps(runtime);
193
+ });
194
+
195
+ afterEach(() => {
196
+ runtime.close();
197
+ rmSync(plannerDir, { recursive: true, force: true });
198
+ });
199
+
200
+ it('should create a playbook and store it in the vault', async () => {
201
+ const result = (await findOp('playbook_create').handler({
202
+ title: 'PR Review',
203
+ domain: 'engineering',
204
+ description: 'Standard PR review procedure.',
205
+ steps: [
206
+ { title: 'Read diff', description: 'Review all changed files' },
207
+ { title: 'Run tests', description: 'Ensure CI passes', validation: 'All green' },
208
+ { title: 'Approve', description: 'Leave approval comment' },
209
+ ],
210
+ tags: ['review', 'pr'],
211
+ })) as { created: boolean; id: string; steps: number };
212
+
213
+ expect(result.created).toBe(true);
214
+ expect(result.steps).toBe(3);
215
+
216
+ // Verify it's in the vault
217
+ const entry = runtime.vault.get(result.id);
218
+ expect(entry).not.toBeNull();
219
+ expect(entry!.type).toBe('playbook');
220
+ expect(entry!.domain).toBe('engineering');
221
+
222
+ // Verify it parses back correctly
223
+ const playbook = parsePlaybookFromEntry(entry!);
224
+ expect(playbook).not.toBeNull();
225
+ expect(playbook!.steps).toHaveLength(3);
226
+ expect(playbook!.steps[0].order).toBe(1);
227
+ expect(playbook!.steps[2].order).toBe(3);
228
+ });
229
+
230
+ it('should reject a playbook with empty steps', async () => {
231
+ const result = (await findOp('playbook_create').handler({
232
+ title: 'Empty',
233
+ domain: 'test',
234
+ description: 'No steps.',
235
+ steps: [],
236
+ tags: [],
237
+ })) as { created: boolean; errors: string[] };
238
+
239
+ expect(result.created).toBe(false);
240
+ expect(result.errors.length).toBeGreaterThan(0);
241
+ });
242
+
243
+ it('should reject a playbook with empty title', async () => {
244
+ const result = (await findOp('playbook_create').handler({
245
+ title: '',
246
+ domain: 'test',
247
+ description: 'Has steps but no title.',
248
+ steps: [{ title: 'Step 1', description: 'Do something' }],
249
+ tags: [],
250
+ })) as { created: boolean; errors: string[] };
251
+
252
+ expect(result.created).toBe(false);
253
+ expect(result.errors).toContain('Playbook title must not be empty');
254
+ });
255
+
256
+ it('should auto-generate ID when not provided', async () => {
257
+ const result = (await findOp('playbook_create').handler({
258
+ title: 'Auto ID Test',
259
+ domain: 'test',
260
+ description: 'Should get an auto ID.',
261
+ steps: [{ title: 'Step', description: 'Do it' }],
262
+ tags: [],
263
+ })) as { created: boolean; id: string };
264
+
265
+ expect(result.created).toBe(true);
266
+ expect(result.id).toMatch(/^playbook-test-/);
267
+ });
268
+
269
+ it('should use provided ID when given', async () => {
270
+ const result = (await findOp('playbook_create').handler({
271
+ id: 'my-custom-id',
272
+ title: 'Custom ID Test',
273
+ domain: 'test',
274
+ description: 'Has a custom ID.',
275
+ steps: [{ title: 'Step', description: 'Do it' }],
276
+ tags: [],
277
+ })) as { created: boolean; id: string };
278
+
279
+ expect(result.created).toBe(true);
280
+ expect(result.id).toBe('my-custom-id');
281
+ });
282
+ });
283
+
284
+ describe('playbook_match op', () => {
285
+ let runtime: AgentRuntime;
286
+ let ops: OpDefinition[];
287
+ let plannerDir: string;
288
+
289
+ function findOp(name: string): OpDefinition {
290
+ const op = ops.find((o) => o.name === name);
291
+ if (!op) throw new Error(`Op "${name}" not found`);
292
+ return op;
293
+ }
294
+
295
+ beforeEach(() => {
296
+ plannerDir = join(tmpdir(), 'playbook-match-test-' + Date.now());
297
+ mkdirSync(plannerDir, { recursive: true });
298
+ runtime = createAgentRuntime({
299
+ agentId: 'test',
300
+ vaultPath: ':memory:',
301
+ plansPath: join(plannerDir, 'plans.json'),
302
+ });
303
+ ops = createCoreOps(runtime);
304
+ });
305
+
306
+ afterEach(() => {
307
+ runtime.close();
308
+ rmSync(plannerDir, { recursive: true, force: true });
309
+ });
310
+
311
+ it('should match TDD playbook for BUILD intent', async () => {
312
+ const result = (await findOp('playbook_match').handler({
313
+ intent: 'BUILD',
314
+ text: 'implement a new feature',
315
+ })) as { playbook: { label: string } | null; genericMatch?: { id: string } };
316
+
317
+ expect(result.playbook).not.toBeNull();
318
+ expect(result.genericMatch?.id).toBe('generic-tdd');
319
+ });
320
+
321
+ it('should match debugging playbook for FIX intent', async () => {
322
+ const result = (await findOp('playbook_match').handler({
323
+ intent: 'FIX',
324
+ text: 'fix the broken bug',
325
+ })) as { playbook: { label: string } | null; genericMatch?: { id: string } };
326
+
327
+ expect(result.playbook).not.toBeNull();
328
+ expect(result.genericMatch?.id).toBe('generic-systematic-debugging');
329
+ });
330
+
331
+ it('should return null playbook for unrelated text', async () => {
332
+ const result = (await findOp('playbook_match').handler({
333
+ text: 'random unrelated xyz',
334
+ })) as { playbook: null };
335
+
336
+ expect(result.playbook).toBeNull();
337
+ });
338
+ });
339
+
340
+ describe('playbook_seed op', () => {
341
+ let runtime: AgentRuntime;
342
+ let ops: OpDefinition[];
343
+ let plannerDir: string;
344
+
345
+ function findOp(name: string): OpDefinition {
346
+ const op = ops.find((o) => o.name === name);
347
+ if (!op) throw new Error(`Op "${name}" not found`);
348
+ return op;
349
+ }
350
+
351
+ beforeEach(() => {
352
+ plannerDir = join(tmpdir(), 'playbook-seed-test-' + Date.now());
353
+ mkdirSync(plannerDir, { recursive: true });
354
+ runtime = createAgentRuntime({
355
+ agentId: 'test',
356
+ vaultPath: ':memory:',
357
+ plansPath: join(plannerDir, 'plans.json'),
358
+ });
359
+ ops = createCoreOps(runtime);
360
+ });
361
+
362
+ afterEach(() => {
363
+ runtime.close();
364
+ rmSync(plannerDir, { recursive: true, force: true });
365
+ });
366
+
367
+ it('should seed built-in playbooks', async () => {
368
+ const result = (await findOp('playbook_seed').handler({})) as {
369
+ seeded: number;
370
+ skipped: number;
371
+ errors: number;
372
+ };
373
+
374
+ expect(result.seeded).toBe(6);
375
+ expect(result.skipped).toBe(0);
376
+ expect(result.errors).toBe(0);
377
+ });
378
+
379
+ it('should be idempotent', async () => {
380
+ await findOp('playbook_seed').handler({});
381
+ const result = (await findOp('playbook_seed').handler({})) as {
382
+ seeded: number;
383
+ skipped: number;
384
+ };
385
+
386
+ expect(result.seeded).toBe(0);
387
+ expect(result.skipped).toBe(6);
388
+ });
389
+ });
@@ -143,7 +143,10 @@ describe('createProjectOps', () => {
143
143
  category: 'behavior',
144
144
  text: 'Always use semantic tokens',
145
145
  priority: 10,
146
- })) as { added: boolean; rule: { id: string; category: string; text: string; priority: number } };
146
+ })) as {
147
+ added: boolean;
148
+ rule: { id: string; category: string; text: string; priority: number };
149
+ };
147
150
 
148
151
  expect(addResult.added).toBe(true);
149
152
  expect(addResult.rule.category).toBe('behavior');
@@ -197,8 +200,16 @@ describe('createProjectOps', () => {
197
200
  const p1 = runtime.projectRegistry.register('/tmp/p1', 'P1');
198
201
  const p2 = runtime.projectRegistry.register('/tmp/p2', 'P2');
199
202
  runtime.projectRegistry.addRule(p1.id, { category: 'behavior', text: 'Rule A', priority: 0 });
200
- runtime.projectRegistry.addRule(p1.id, { category: 'convention', text: 'Rule B', priority: 1 });
201
- runtime.projectRegistry.addRule(p2.id, { category: 'preference', text: 'Rule C', priority: 0 });
203
+ runtime.projectRegistry.addRule(p1.id, {
204
+ category: 'convention',
205
+ text: 'Rule B',
206
+ priority: 1,
207
+ });
208
+ runtime.projectRegistry.addRule(p2.id, {
209
+ category: 'preference',
210
+ text: 'Rule C',
211
+ priority: 0,
212
+ });
202
213
 
203
214
  const result = (await findOp('project_list_rules').handler({})) as {
204
215
  count: number;
@@ -225,7 +236,10 @@ describe('createProjectOps', () => {
225
236
  sourceId: p1.id,
226
237
  targetId: p2.id,
227
238
  linkType: 'related',
228
- })) as { linked: boolean; link: { sourceProjectId: string; targetProjectId: string; linkType: string } };
239
+ })) as {
240
+ linked: boolean;
241
+ link: { sourceProjectId: string; targetProjectId: string; linkType: string };
242
+ };
229
243
 
230
244
  expect(linkResult.linked).toBe(true);
231
245
  expect(linkResult.link.sourceProjectId).toBe(p1.id);