cognitive-core 0.0.2 → 0.1.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 (329) hide show
  1. package/README.md +302 -116
  2. package/SKILL.md +193 -0
  3. package/dist/agents/index.d.ts +3 -0
  4. package/dist/agents/index.d.ts.map +1 -0
  5. package/dist/agents/index.js +5 -0
  6. package/dist/agents/index.js.map +1 -0
  7. package/dist/agents/mock-provider.d.ts +23 -0
  8. package/dist/agents/mock-provider.d.ts.map +1 -0
  9. package/dist/agents/mock-provider.js +71 -0
  10. package/dist/agents/mock-provider.js.map +1 -0
  11. package/dist/agents/types.d.ts +98 -0
  12. package/dist/agents/types.d.ts.map +1 -0
  13. package/dist/agents/types.js +44 -0
  14. package/dist/agents/types.js.map +1 -0
  15. package/dist/atlas.d.ts +196 -0
  16. package/dist/atlas.d.ts.map +1 -0
  17. package/dist/atlas.js +373 -0
  18. package/dist/atlas.js.map +1 -0
  19. package/dist/bin/cognitive-core.d.ts +18 -0
  20. package/dist/bin/cognitive-core.d.ts.map +1 -0
  21. package/dist/bin/cognitive-core.js +419 -0
  22. package/dist/bin/cognitive-core.js.map +1 -0
  23. package/dist/embeddings/bm25.d.ts +104 -0
  24. package/dist/embeddings/bm25.d.ts.map +1 -0
  25. package/dist/embeddings/bm25.js +264 -0
  26. package/dist/embeddings/bm25.js.map +1 -0
  27. package/dist/embeddings/index.d.ts +12 -0
  28. package/dist/embeddings/index.d.ts.map +1 -0
  29. package/dist/embeddings/index.js +16 -0
  30. package/dist/embeddings/index.js.map +1 -0
  31. package/dist/embeddings/manager.d.ts +112 -0
  32. package/dist/embeddings/manager.d.ts.map +1 -0
  33. package/dist/embeddings/manager.js +215 -0
  34. package/dist/embeddings/manager.js.map +1 -0
  35. package/dist/embeddings/provider.d.ts +101 -0
  36. package/dist/embeddings/provider.d.ts.map +1 -0
  37. package/dist/embeddings/provider.js +232 -0
  38. package/dist/embeddings/provider.js.map +1 -0
  39. package/dist/embeddings/vector-store.d.ts +101 -0
  40. package/dist/embeddings/vector-store.d.ts.map +1 -0
  41. package/dist/embeddings/vector-store.js +256 -0
  42. package/dist/embeddings/vector-store.js.map +1 -0
  43. package/dist/factory.d.ts +193 -0
  44. package/dist/factory.d.ts.map +1 -0
  45. package/dist/factory.js +109 -0
  46. package/dist/factory.js.map +1 -0
  47. package/dist/index.d.ts +30 -453
  48. package/dist/index.d.ts.map +1 -0
  49. package/dist/index.js +84 -509
  50. package/dist/index.js.map +1 -0
  51. package/dist/learning/analyzer.d.ts +110 -0
  52. package/dist/learning/analyzer.d.ts.map +1 -0
  53. package/dist/learning/analyzer.js +213 -0
  54. package/dist/learning/analyzer.js.map +1 -0
  55. package/dist/learning/effectiveness.d.ts +158 -0
  56. package/dist/learning/effectiveness.d.ts.map +1 -0
  57. package/dist/learning/effectiveness.js +251 -0
  58. package/dist/learning/effectiveness.js.map +1 -0
  59. package/dist/learning/index.d.ts +8 -0
  60. package/dist/learning/index.d.ts.map +1 -0
  61. package/dist/learning/index.js +11 -0
  62. package/dist/learning/index.js.map +1 -0
  63. package/dist/learning/llm-extractor.d.ts +88 -0
  64. package/dist/learning/llm-extractor.d.ts.map +1 -0
  65. package/dist/learning/llm-extractor.js +372 -0
  66. package/dist/learning/llm-extractor.js.map +1 -0
  67. package/dist/learning/meta-learner.d.ts +80 -0
  68. package/dist/learning/meta-learner.d.ts.map +1 -0
  69. package/dist/learning/meta-learner.js +355 -0
  70. package/dist/learning/meta-learner.js.map +1 -0
  71. package/dist/learning/pipeline.d.ts +65 -0
  72. package/dist/learning/pipeline.d.ts.map +1 -0
  73. package/dist/learning/pipeline.js +170 -0
  74. package/dist/learning/pipeline.js.map +1 -0
  75. package/dist/learning/playbook-extractor.d.ts +113 -0
  76. package/dist/learning/playbook-extractor.d.ts.map +1 -0
  77. package/dist/learning/playbook-extractor.js +523 -0
  78. package/dist/learning/playbook-extractor.js.map +1 -0
  79. package/dist/learning/usage-inference.d.ts +82 -0
  80. package/dist/learning/usage-inference.d.ts.map +1 -0
  81. package/dist/learning/usage-inference.js +261 -0
  82. package/dist/learning/usage-inference.js.map +1 -0
  83. package/dist/mcp/index.d.ts +6 -0
  84. package/dist/mcp/index.d.ts.map +1 -0
  85. package/dist/mcp/index.js +6 -0
  86. package/dist/mcp/index.js.map +1 -0
  87. package/dist/mcp/playbook-server.d.ts +120 -0
  88. package/dist/mcp/playbook-server.d.ts.map +1 -0
  89. package/dist/mcp/playbook-server.js +427 -0
  90. package/dist/mcp/playbook-server.js.map +1 -0
  91. package/dist/memory/curated-loader.d.ts +62 -0
  92. package/dist/memory/curated-loader.d.ts.map +1 -0
  93. package/dist/memory/curated-loader.js +106 -0
  94. package/dist/memory/curated-loader.js.map +1 -0
  95. package/dist/memory/experience.d.ts +122 -0
  96. package/dist/memory/experience.d.ts.map +1 -0
  97. package/dist/memory/experience.js +392 -0
  98. package/dist/memory/experience.js.map +1 -0
  99. package/dist/memory/index.d.ts +6 -0
  100. package/dist/memory/index.d.ts.map +1 -0
  101. package/dist/memory/index.js +9 -0
  102. package/dist/memory/index.js.map +1 -0
  103. package/dist/memory/meta.d.ts +90 -0
  104. package/dist/memory/meta.d.ts.map +1 -0
  105. package/dist/memory/meta.js +362 -0
  106. package/dist/memory/meta.js.map +1 -0
  107. package/dist/memory/playbook.d.ts +133 -0
  108. package/dist/memory/playbook.d.ts.map +1 -0
  109. package/dist/memory/playbook.js +357 -0
  110. package/dist/memory/playbook.js.map +1 -0
  111. package/dist/memory/system.d.ts +167 -0
  112. package/dist/memory/system.d.ts.map +1 -0
  113. package/dist/memory/system.js +383 -0
  114. package/dist/memory/system.js.map +1 -0
  115. package/dist/runtime/backends/acp.d.ts +67 -0
  116. package/dist/runtime/backends/acp.d.ts.map +1 -0
  117. package/dist/runtime/backends/acp.js +290 -0
  118. package/dist/runtime/backends/acp.js.map +1 -0
  119. package/dist/runtime/backends/index.d.ts +5 -0
  120. package/dist/runtime/backends/index.d.ts.map +1 -0
  121. package/dist/runtime/backends/index.js +6 -0
  122. package/dist/runtime/backends/index.js.map +1 -0
  123. package/dist/runtime/backends/mock.d.ts +67 -0
  124. package/dist/runtime/backends/mock.d.ts.map +1 -0
  125. package/dist/runtime/backends/mock.js +153 -0
  126. package/dist/runtime/backends/mock.js.map +1 -0
  127. package/dist/runtime/backends/subprocess.d.ts +56 -0
  128. package/dist/runtime/backends/subprocess.d.ts.map +1 -0
  129. package/dist/runtime/backends/subprocess.js +260 -0
  130. package/dist/runtime/backends/subprocess.js.map +1 -0
  131. package/dist/runtime/flows/learning.d.ts +73 -0
  132. package/dist/runtime/flows/learning.d.ts.map +1 -0
  133. package/dist/runtime/flows/learning.js +116 -0
  134. package/dist/runtime/flows/learning.js.map +1 -0
  135. package/dist/runtime/flows/validation.d.ts +122 -0
  136. package/dist/runtime/flows/validation.d.ts.map +1 -0
  137. package/dist/runtime/flows/validation.js +223 -0
  138. package/dist/runtime/flows/validation.js.map +1 -0
  139. package/dist/runtime/index.d.ts +6 -0
  140. package/dist/runtime/index.d.ts.map +1 -0
  141. package/dist/runtime/index.js +8 -0
  142. package/dist/runtime/index.js.map +1 -0
  143. package/dist/runtime/manager.d.ts +116 -0
  144. package/dist/runtime/manager.d.ts.map +1 -0
  145. package/dist/runtime/manager.js +416 -0
  146. package/dist/runtime/manager.js.map +1 -0
  147. package/dist/runtime/types.d.ts +138 -0
  148. package/dist/runtime/types.d.ts.map +1 -0
  149. package/dist/runtime/types.js +2 -0
  150. package/dist/runtime/types.js.map +1 -0
  151. package/dist/search/evaluator.d.ts +102 -0
  152. package/dist/search/evaluator.d.ts.map +1 -0
  153. package/dist/search/evaluator.js +352 -0
  154. package/dist/search/evaluator.js.map +1 -0
  155. package/dist/search/index.d.ts +7 -0
  156. package/dist/search/index.d.ts.map +1 -0
  157. package/dist/search/index.js +11 -0
  158. package/dist/search/index.js.map +1 -0
  159. package/dist/search/refinement-loop.d.ts +73 -0
  160. package/dist/search/refinement-loop.d.ts.map +1 -0
  161. package/dist/search/refinement-loop.js +245 -0
  162. package/dist/search/refinement-loop.js.map +1 -0
  163. package/dist/search/refinement-types.d.ts +154 -0
  164. package/dist/search/refinement-types.d.ts.map +1 -0
  165. package/dist/search/refinement-types.js +99 -0
  166. package/dist/search/refinement-types.js.map +1 -0
  167. package/dist/search/router.d.ts +61 -0
  168. package/dist/search/router.d.ts.map +1 -0
  169. package/dist/search/router.js +197 -0
  170. package/dist/search/router.js.map +1 -0
  171. package/dist/search/solver.d.ts +75 -0
  172. package/dist/search/solver.d.ts.map +1 -0
  173. package/dist/search/solver.js +216 -0
  174. package/dist/search/solver.js.map +1 -0
  175. package/dist/search/verification-runner.d.ts +125 -0
  176. package/dist/search/verification-runner.d.ts.map +1 -0
  177. package/dist/search/verification-runner.js +440 -0
  178. package/dist/search/verification-runner.js.map +1 -0
  179. package/dist/surfacing/index.d.ts +2 -0
  180. package/dist/surfacing/index.d.ts.map +1 -0
  181. package/dist/surfacing/index.js +2 -0
  182. package/dist/surfacing/index.js.map +1 -0
  183. package/dist/surfacing/skill-library.d.ts +158 -0
  184. package/dist/surfacing/skill-library.d.ts.map +1 -0
  185. package/dist/surfacing/skill-library.js +429 -0
  186. package/dist/surfacing/skill-library.js.map +1 -0
  187. package/dist/types/config.d.ts +1113 -0
  188. package/dist/types/config.d.ts.map +1 -0
  189. package/dist/types/config.js +274 -0
  190. package/dist/types/config.js.map +1 -0
  191. package/dist/types/index.d.ts +9 -0
  192. package/dist/types/index.d.ts.map +1 -0
  193. package/dist/types/index.js +14 -0
  194. package/dist/types/index.js.map +1 -0
  195. package/dist/types/memory.d.ts +339 -0
  196. package/dist/types/memory.d.ts.map +1 -0
  197. package/dist/types/memory.js +207 -0
  198. package/dist/types/memory.js.map +1 -0
  199. package/dist/types/meta.d.ts +146 -0
  200. package/dist/types/meta.d.ts.map +1 -0
  201. package/dist/types/meta.js +51 -0
  202. package/dist/types/meta.js.map +1 -0
  203. package/dist/types/outcome.d.ts +42 -0
  204. package/dist/types/outcome.d.ts.map +1 -0
  205. package/dist/types/outcome.js +50 -0
  206. package/dist/types/outcome.js.map +1 -0
  207. package/dist/types/playbook.d.ts +119 -0
  208. package/dist/types/playbook.d.ts.map +1 -0
  209. package/dist/types/playbook.js +71 -0
  210. package/dist/types/playbook.js.map +1 -0
  211. package/dist/types/step.d.ts +44 -0
  212. package/dist/types/step.d.ts.map +1 -0
  213. package/dist/types/step.js +32 -0
  214. package/dist/types/step.js.map +1 -0
  215. package/dist/types/task.d.ts +91 -0
  216. package/dist/types/task.d.ts.map +1 -0
  217. package/dist/types/task.js +39 -0
  218. package/dist/types/task.js.map +1 -0
  219. package/dist/types/trajectory.d.ts +221 -0
  220. package/dist/types/trajectory.d.ts.map +1 -0
  221. package/dist/types/trajectory.js +60 -0
  222. package/dist/types/trajectory.js.map +1 -0
  223. package/dist/utils/index.d.ts +4 -0
  224. package/dist/utils/index.d.ts.map +1 -0
  225. package/dist/utils/index.js +4 -0
  226. package/dist/utils/index.js.map +1 -0
  227. package/dist/utils/similarity.d.ts +31 -0
  228. package/dist/utils/similarity.d.ts.map +1 -0
  229. package/dist/utils/similarity.js +107 -0
  230. package/dist/utils/similarity.js.map +1 -0
  231. package/dist/utils/storage.d.ts +106 -0
  232. package/dist/utils/storage.d.ts.map +1 -0
  233. package/dist/utils/storage.js +203 -0
  234. package/dist/utils/storage.js.map +1 -0
  235. package/dist/utils/validation.d.ts +129 -0
  236. package/dist/utils/validation.d.ts.map +1 -0
  237. package/dist/utils/validation.js +171 -0
  238. package/dist/utils/validation.js.map +1 -0
  239. package/package.json +50 -34
  240. package/scripts/migrate-to-playbooks.ts +307 -0
  241. package/src/agents/index.ts +14 -0
  242. package/src/agents/mock-provider.ts +93 -0
  243. package/src/agents/types.ts +137 -0
  244. package/src/atlas.ts +560 -0
  245. package/src/bin/cognitive-core.ts +470 -0
  246. package/src/embeddings/bm25.ts +337 -0
  247. package/src/embeddings/index.ts +39 -0
  248. package/src/embeddings/manager.ts +288 -0
  249. package/src/embeddings/provider.ts +311 -0
  250. package/src/embeddings/vector-store.ts +353 -0
  251. package/src/factory.ts +263 -0
  252. package/src/index.ts +246 -0
  253. package/src/learning/analyzer.ts +335 -0
  254. package/src/learning/effectiveness.ts +428 -0
  255. package/src/learning/index.ts +58 -0
  256. package/src/learning/llm-extractor.ts +542 -0
  257. package/src/learning/meta-learner.ts +516 -0
  258. package/src/learning/pipeline.ts +244 -0
  259. package/src/learning/playbook-extractor.ts +702 -0
  260. package/src/learning/usage-inference.ts +372 -0
  261. package/src/mcp/index.ts +12 -0
  262. package/src/mcp/playbook-server.ts +565 -0
  263. package/src/memory/curated-loader.ts +160 -0
  264. package/src/memory/experience.ts +515 -0
  265. package/src/memory/index.ts +27 -0
  266. package/src/memory/meta.ts +506 -0
  267. package/src/memory/playbook.ts +493 -0
  268. package/src/memory/system.ts +551 -0
  269. package/src/runtime/backends/acp.ts +378 -0
  270. package/src/runtime/backends/index.ts +24 -0
  271. package/src/runtime/backends/mock.ts +218 -0
  272. package/src/runtime/backends/subprocess.ts +356 -0
  273. package/src/runtime/flows/learning.ts +183 -0
  274. package/src/runtime/flows/validation.ts +381 -0
  275. package/src/runtime/index.ts +53 -0
  276. package/src/runtime/manager.ts +541 -0
  277. package/src/runtime/types.ts +157 -0
  278. package/src/search/evaluator.ts +474 -0
  279. package/src/search/index.ts +59 -0
  280. package/src/search/refinement-loop.ts +363 -0
  281. package/src/search/refinement-types.ts +159 -0
  282. package/src/search/router.ts +261 -0
  283. package/src/search/solver.ts +303 -0
  284. package/src/search/verification-runner.ts +570 -0
  285. package/src/surfacing/index.ts +6 -0
  286. package/src/surfacing/skill-library.ts +594 -0
  287. package/src/types/config.ts +333 -0
  288. package/src/types/index.ts +130 -0
  289. package/src/types/memory.ts +270 -0
  290. package/src/types/meta.ts +218 -0
  291. package/src/types/outcome.ts +66 -0
  292. package/src/types/playbook.ts +196 -0
  293. package/src/types/step.ts +40 -0
  294. package/src/types/task.ts +52 -0
  295. package/src/types/trajectory.ts +80 -0
  296. package/src/utils/index.ts +38 -0
  297. package/src/utils/similarity.ts +139 -0
  298. package/src/utils/storage.ts +249 -0
  299. package/src/utils/validation.ts +286 -0
  300. package/tests/embeddings/bm25.test.ts +130 -0
  301. package/tests/embeddings/manager.test.ts +205 -0
  302. package/tests/integration/atlas.test.ts +266 -0
  303. package/tests/integration/e2e.test.ts +929 -0
  304. package/tests/learning/analyzer.test.ts +426 -0
  305. package/tests/learning/effectiveness.test.ts +542 -0
  306. package/tests/learning/pipeline.test.ts +176 -0
  307. package/tests/learning/playbook-extractor-provenance.test.ts +114 -0
  308. package/tests/learning/usage-inference.test.ts +254 -0
  309. package/tests/mcp/playbook-server.test.ts +252 -0
  310. package/tests/memory/experience.test.ts +198 -0
  311. package/tests/memory/playbook.test.ts +338 -0
  312. package/tests/memory/provenance.test.ts +639 -0
  313. package/tests/memory/system.test.ts +325 -0
  314. package/tests/runtime/agent-manager.test.ts +512 -0
  315. package/tests/runtime/mock-backend.test.ts +248 -0
  316. package/tests/search/refinement-loop.test.ts +468 -0
  317. package/tests/search/refinement.test.ts +267 -0
  318. package/tests/search/router.test.ts +427 -0
  319. package/tests/surfacing/skill-library.test.ts +292 -0
  320. package/tests/types/outcome.test.ts +147 -0
  321. package/tests/types/step.test.ts +133 -0
  322. package/tests/types/task.test.ts +158 -0
  323. package/tests/types/trajectory.test.ts +253 -0
  324. package/tests/utils/similarity.test.ts +188 -0
  325. package/tests/utils/validation.test.ts +252 -0
  326. package/tsconfig.json +25 -0
  327. package/vitest.config.ts +22 -0
  328. package/dist/index.d.mts +0 -466
  329. package/dist/index.mjs +0 -478
@@ -0,0 +1,639 @@
1
+ import { describe, it, expect, beforeEach, afterEach } from 'vitest';
2
+ import { PlaybookLibrary, createPlaybookLibrary } from '../../src/memory/playbook.js';
3
+ import { loadCuratedPlaybooks, type CuratedPlaybookFile } from '../../src/memory/curated-loader.js';
4
+ import type { Playbook, PlaybookProvenance } from '../../src/types/playbook.js';
5
+ import { createPlaybook } from '../../src/types/playbook.js';
6
+ import { mkdtemp, rm, mkdir, writeFile } from 'node:fs/promises';
7
+ import { join } from 'node:path';
8
+ import { tmpdir } from 'node:os';
9
+
10
+ describe('Playbook Provenance', () => {
11
+ let tempDir: string;
12
+ let library: PlaybookLibrary;
13
+
14
+ beforeEach(async () => {
15
+ tempDir = await mkdtemp(join(tmpdir(), 'atlas-prov-test-'));
16
+ library = createPlaybookLibrary(tempDir);
17
+ await library.init();
18
+ });
19
+
20
+ afterEach(async () => {
21
+ await library.close();
22
+ await rm(tempDir, { recursive: true, force: true });
23
+ });
24
+
25
+ function createSamplePlaybook(overrides?: Partial<Playbook>): Playbook {
26
+ return createPlaybook({
27
+ name: `playbook-${Date.now()}-${Math.random().toString(36).slice(2, 6)}`,
28
+ applicability: {
29
+ situations: ['Test situation'],
30
+ triggers: [],
31
+ antiPatterns: [],
32
+ domains: ['test'],
33
+ },
34
+ guidance: {
35
+ strategy: 'Test strategy',
36
+ tactics: ['Step 1'],
37
+ },
38
+ ...overrides,
39
+ });
40
+ }
41
+
42
+ describe('createPlaybook default provenance', () => {
43
+ it('should default to extracted origin', () => {
44
+ const playbook = createPlaybook({
45
+ name: 'test-playbook',
46
+ applicability: {
47
+ situations: ['Test'],
48
+ triggers: [],
49
+ antiPatterns: [],
50
+ domains: ['test'],
51
+ },
52
+ guidance: {
53
+ strategy: 'Test',
54
+ tactics: [],
55
+ },
56
+ });
57
+
58
+ expect(playbook.provenance).toBeDefined();
59
+ expect(playbook.provenance.origin).toBe('extracted');
60
+ expect(playbook.provenance.recordedAt).toBeInstanceOf(Date);
61
+ });
62
+
63
+ it('should accept custom provenance', () => {
64
+ const provenance: PlaybookProvenance = {
65
+ origin: 'curated',
66
+ sourceFile: 'playbooks/test.json',
67
+ curatedBy: 'test-user',
68
+ recordedAt: new Date(),
69
+ };
70
+
71
+ const playbook = createPlaybook({
72
+ name: 'curated-playbook',
73
+ applicability: {
74
+ situations: ['Test'],
75
+ triggers: [],
76
+ antiPatterns: [],
77
+ domains: ['test'],
78
+ },
79
+ guidance: {
80
+ strategy: 'Test',
81
+ tactics: [],
82
+ },
83
+ provenance,
84
+ });
85
+
86
+ expect(playbook.provenance.origin).toBe('curated');
87
+ expect(playbook.provenance.sourceFile).toBe('playbooks/test.json');
88
+ expect(playbook.provenance.curatedBy).toBe('test-user');
89
+ });
90
+
91
+ it('should accept imported provenance with importedFrom', () => {
92
+ const playbook = createPlaybook({
93
+ name: 'imported-playbook',
94
+ applicability: {
95
+ situations: ['Test'],
96
+ triggers: [],
97
+ antiPatterns: [],
98
+ domains: ['test'],
99
+ },
100
+ guidance: { strategy: 'Test', tactics: [] },
101
+ provenance: {
102
+ origin: 'imported',
103
+ importedFrom: 'community-pack-v2',
104
+ recordedAt: new Date(),
105
+ },
106
+ });
107
+
108
+ expect(playbook.provenance.origin).toBe('imported');
109
+ expect(playbook.provenance.importedFrom).toBe('community-pack-v2');
110
+ expect(playbook.provenance.sourceFile).toBeUndefined();
111
+ });
112
+ });
113
+
114
+ describe('provenance preserved through evolution', () => {
115
+ it('should preserve provenance after recordSuccess', async () => {
116
+ const playbook = createSamplePlaybook({
117
+ name: 'curated-evolving',
118
+ provenance: { origin: 'curated', sourceFile: 'test.json', recordedAt: new Date() },
119
+ });
120
+ await library.add(playbook);
121
+
122
+ await library.recordSuccess(playbook.id, 'traj-1');
123
+
124
+ const updated = await library.get(playbook.id);
125
+ expect(updated!.provenance.origin).toBe('curated');
126
+ expect(updated!.provenance.sourceFile).toBe('test.json');
127
+ });
128
+
129
+ it('should preserve provenance after recordFailure', async () => {
130
+ const playbook = createSamplePlaybook({
131
+ name: 'imported-failing',
132
+ provenance: { origin: 'imported', importedFrom: 'dash', recordedAt: new Date() },
133
+ });
134
+ await library.add(playbook);
135
+
136
+ await library.recordFailure(playbook.id, 'traj-1', 'monorepo', 'symlink issue');
137
+
138
+ const updated = await library.get(playbook.id);
139
+ expect(updated!.provenance.origin).toBe('imported');
140
+ expect(updated!.provenance.importedFrom).toBe('dash');
141
+ });
142
+
143
+ it('should preserve provenance after addRefinement', async () => {
144
+ const playbook = createSamplePlaybook({
145
+ name: 'manual-refined',
146
+ provenance: { origin: 'manual', recordedAt: new Date() },
147
+ });
148
+ await library.add(playbook);
149
+
150
+ await library.addRefinement(playbook.id, 'ESM projects', 'Add .js extensions', 'failure');
151
+
152
+ const updated = await library.get(playbook.id);
153
+ expect(updated!.provenance.origin).toBe('manual');
154
+ expect(updated!.evolution.refinements).toHaveLength(1);
155
+ });
156
+
157
+ it('should preserve provenance after evolve', async () => {
158
+ const playbook = createSamplePlaybook({
159
+ name: 'curated-evolved',
160
+ provenance: { origin: 'curated', sourceFile: 'debug.json', curatedBy: 'alice', recordedAt: new Date() },
161
+ });
162
+ await library.add(playbook);
163
+
164
+ await library.evolve(playbook.id, { strategy: 'Updated strategy' });
165
+
166
+ const updated = await library.get(playbook.id);
167
+ expect(updated!.provenance.origin).toBe('curated');
168
+ expect(updated!.provenance.curatedBy).toBe('alice');
169
+ expect(updated!.guidance.strategy).toBe('Updated strategy');
170
+ });
171
+ });
172
+
173
+ describe('getByOrigin', () => {
174
+ it('should filter playbooks by origin', async () => {
175
+ await library.add(createSamplePlaybook({
176
+ name: 'extracted-1',
177
+ provenance: { origin: 'extracted', recordedAt: new Date() },
178
+ }));
179
+ await library.add(createSamplePlaybook({
180
+ name: 'curated-1',
181
+ provenance: { origin: 'curated', sourceFile: 'test.json', recordedAt: new Date() },
182
+ }));
183
+ await library.add(createSamplePlaybook({
184
+ name: 'curated-2',
185
+ provenance: { origin: 'curated', sourceFile: 'test2.json', recordedAt: new Date() },
186
+ }));
187
+ await library.add(createSamplePlaybook({
188
+ name: 'imported-1',
189
+ provenance: { origin: 'imported', importedFrom: 'dash', recordedAt: new Date() },
190
+ }));
191
+
192
+ const curated = await library.getByOrigin('curated');
193
+ expect(curated).toHaveLength(2);
194
+ expect(curated.every(p => p.provenance.origin === 'curated')).toBe(true);
195
+
196
+ const extracted = await library.getByOrigin('extracted');
197
+ expect(extracted).toHaveLength(1);
198
+
199
+ const imported = await library.getByOrigin('imported');
200
+ expect(imported).toHaveLength(1);
201
+ expect(imported[0].provenance.importedFrom).toBe('dash');
202
+ });
203
+
204
+ it('should return empty array when no playbooks match origin', async () => {
205
+ await library.add(createSamplePlaybook({
206
+ name: 'extracted-only',
207
+ provenance: { origin: 'extracted', recordedAt: new Date() },
208
+ }));
209
+
210
+ const curated = await library.getByOrigin('curated');
211
+ expect(curated).toHaveLength(0);
212
+
213
+ const manual = await library.getByOrigin('manual');
214
+ expect(manual).toHaveLength(0);
215
+ });
216
+ });
217
+
218
+ describe('deleteByOrigin', () => {
219
+ it('should delete only playbooks with the specified origin', async () => {
220
+ await library.add(createSamplePlaybook({
221
+ name: 'extracted-1',
222
+ provenance: { origin: 'extracted', recordedAt: new Date() },
223
+ }));
224
+ await library.add(createSamplePlaybook({
225
+ name: 'curated-1',
226
+ provenance: { origin: 'curated', sourceFile: 'test.json', recordedAt: new Date() },
227
+ }));
228
+ await library.add(createSamplePlaybook({
229
+ name: 'curated-2',
230
+ provenance: { origin: 'curated', sourceFile: 'test2.json', recordedAt: new Date() },
231
+ }));
232
+
233
+ const deleted = await library.deleteByOrigin('curated');
234
+ expect(deleted).toBe(2);
235
+
236
+ const remaining = await library.getAll();
237
+ expect(remaining).toHaveLength(1);
238
+ expect(remaining[0].provenance.origin).toBe('extracted');
239
+ });
240
+
241
+ it('should return 0 when no playbooks match the origin', async () => {
242
+ await library.add(createSamplePlaybook({
243
+ name: 'extracted-1',
244
+ provenance: { origin: 'extracted', recordedAt: new Date() },
245
+ }));
246
+
247
+ const deleted = await library.deleteByOrigin('imported');
248
+ expect(deleted).toBe(0);
249
+ });
250
+
251
+ it('should leave other origins untouched when deleting one', async () => {
252
+ await library.add(createSamplePlaybook({
253
+ name: 'e1',
254
+ provenance: { origin: 'extracted', recordedAt: new Date() },
255
+ }));
256
+ await library.add(createSamplePlaybook({
257
+ name: 'c1',
258
+ provenance: { origin: 'curated', recordedAt: new Date() },
259
+ }));
260
+ await library.add(createSamplePlaybook({
261
+ name: 'i1',
262
+ provenance: { origin: 'imported', importedFrom: 'x', recordedAt: new Date() },
263
+ }));
264
+ await library.add(createSamplePlaybook({
265
+ name: 'm1',
266
+ provenance: { origin: 'manual', recordedAt: new Date() },
267
+ }));
268
+
269
+ await library.deleteByOrigin('extracted');
270
+
271
+ expect(await library.count()).toBe(3);
272
+ expect(await library.getByOrigin('curated')).toHaveLength(1);
273
+ expect(await library.getByOrigin('imported')).toHaveLength(1);
274
+ expect(await library.getByOrigin('manual')).toHaveLength(1);
275
+ });
276
+ });
277
+
278
+ describe('playbooks without provenance', () => {
279
+ it('should not appear in getByOrigin results for any origin', async () => {
280
+ // Manually construct a playbook without provenance (pre-existing data scenario)
281
+ const noProvPlaybook = createSamplePlaybook({ name: 'no-prov' });
282
+ delete (noProvPlaybook as Record<string, unknown>).provenance;
283
+ await library.add(noProvPlaybook);
284
+
285
+ const extracted = await library.getByOrigin('extracted');
286
+ const curated = await library.getByOrigin('curated');
287
+ const imported = await library.getByOrigin('imported');
288
+ const manual = await library.getByOrigin('manual');
289
+
290
+ expect(extracted).toHaveLength(0);
291
+ expect(curated).toHaveLength(0);
292
+ expect(imported).toHaveLength(0);
293
+ expect(manual).toHaveLength(0);
294
+ });
295
+
296
+ it('should count as extracted in getProvenanceSummary', async () => {
297
+ const noProvPlaybook = createSamplePlaybook({ name: 'no-prov-summary' });
298
+ delete (noProvPlaybook as Record<string, unknown>).provenance;
299
+ await library.add(noProvPlaybook);
300
+
301
+ const summary = await library.getProvenanceSummary();
302
+ // Falls back to 'extracted' when provenance is undefined
303
+ expect(summary.extracted).toBe(1);
304
+ });
305
+
306
+ it('should not be deleted by deleteByOrigin for any origin', async () => {
307
+ const noProvPlaybook = createSamplePlaybook({ name: 'no-prov-delete' });
308
+ delete (noProvPlaybook as Record<string, unknown>).provenance;
309
+ await library.add(noProvPlaybook);
310
+
311
+ await library.deleteByOrigin('extracted');
312
+ await library.deleteByOrigin('curated');
313
+
314
+ expect(await library.count()).toBe(1);
315
+ });
316
+
317
+ it('should coexist with playbooks that have provenance', async () => {
318
+ const noProvPlaybook = createSamplePlaybook({ name: 'no-prov-coexist' });
319
+ delete (noProvPlaybook as Record<string, unknown>).provenance;
320
+ await library.add(noProvPlaybook);
321
+
322
+ await library.add(createSamplePlaybook({
323
+ name: 'with-prov',
324
+ provenance: { origin: 'curated', sourceFile: 'test.json', recordedAt: new Date() },
325
+ }));
326
+
327
+ const all = await library.getAll();
328
+ expect(all).toHaveLength(2);
329
+
330
+ const curated = await library.getByOrigin('curated');
331
+ expect(curated).toHaveLength(1);
332
+ expect(curated[0].name).toBe('with-prov');
333
+ });
334
+ });
335
+
336
+ describe('getProvenanceSummary', () => {
337
+ it('should return counts by origin', async () => {
338
+ await library.add(createSamplePlaybook({
339
+ name: 'e1',
340
+ provenance: { origin: 'extracted', recordedAt: new Date() },
341
+ }));
342
+ await library.add(createSamplePlaybook({
343
+ name: 'e2',
344
+ provenance: { origin: 'extracted', recordedAt: new Date() },
345
+ }));
346
+ await library.add(createSamplePlaybook({
347
+ name: 'c1',
348
+ provenance: { origin: 'curated', recordedAt: new Date() },
349
+ }));
350
+ await library.add(createSamplePlaybook({
351
+ name: 'm1',
352
+ provenance: { origin: 'manual', recordedAt: new Date() },
353
+ }));
354
+
355
+ const summary = await library.getProvenanceSummary();
356
+ expect(summary.extracted).toBe(2);
357
+ expect(summary.curated).toBe(1);
358
+ expect(summary.manual).toBe(1);
359
+ expect(summary.imported).toBe(0);
360
+ });
361
+
362
+ it('should return all zeros for empty library', async () => {
363
+ const summary = await library.getProvenanceSummary();
364
+ expect(summary.extracted).toBe(0);
365
+ expect(summary.curated).toBe(0);
366
+ expect(summary.imported).toBe(0);
367
+ expect(summary.manual).toBe(0);
368
+ });
369
+ });
370
+ });
371
+
372
+ describe('Curated Playbook Loader', () => {
373
+ let tempDir: string;
374
+ let curatedDir: string;
375
+ let library: PlaybookLibrary;
376
+
377
+ beforeEach(async () => {
378
+ tempDir = await mkdtemp(join(tmpdir(), 'atlas-curated-test-'));
379
+ curatedDir = join(tempDir, 'curated');
380
+ await mkdir(curatedDir, { recursive: true });
381
+ library = createPlaybookLibrary(tempDir);
382
+ await library.init();
383
+ });
384
+
385
+ afterEach(async () => {
386
+ await library.close();
387
+ await rm(tempDir, { recursive: true, force: true });
388
+ });
389
+
390
+ async function writeCuratedFile(filename: string, content: CuratedPlaybookFile): Promise<void> {
391
+ await writeFile(join(curatedDir, filename), JSON.stringify(content, null, 2));
392
+ }
393
+
394
+ it('should load curated playbooks from JSON files', async () => {
395
+ await writeCuratedFile('fix-ts-errors.json', {
396
+ name: 'fix-typescript-errors',
397
+ applicability: {
398
+ situations: ['Debugging TypeScript type errors'],
399
+ triggers: ['TS2322', 'TS2307'],
400
+ domains: ['typescript'],
401
+ },
402
+ guidance: {
403
+ strategy: 'Read error, trace to source, fix type mismatch',
404
+ tactics: ['Read full error message', 'Check type declarations'],
405
+ },
406
+ curatedBy: 'test-author',
407
+ });
408
+
409
+ await writeCuratedFile('optimize-react.json', {
410
+ name: 'optimize-react-renders',
411
+ applicability: {
412
+ situations: ['React component re-renders too often'],
413
+ domains: ['react'],
414
+ },
415
+ guidance: {
416
+ strategy: 'Profile renders, memoize expensive computations',
417
+ tactics: ['Use React.memo', 'Use useMemo for expensive calculations'],
418
+ },
419
+ });
420
+
421
+ const result = await loadCuratedPlaybooks(curatedDir, library);
422
+
423
+ expect(result.loaded).toBe(2);
424
+ expect(result.skipped).toBe(0);
425
+ expect(result.errors).toHaveLength(0);
426
+
427
+ const all = await library.getAll();
428
+ expect(all).toHaveLength(2);
429
+
430
+ const tsPlaybook = await library.getByName('fix-typescript-errors');
431
+ expect(tsPlaybook).toBeDefined();
432
+ expect(tsPlaybook!.provenance.origin).toBe('curated');
433
+ expect(tsPlaybook!.provenance.curatedBy).toBe('test-author');
434
+ expect(tsPlaybook!.confidence).toBe(0.7); // Curated default
435
+ });
436
+
437
+ it('should set sourceFile on loaded curated playbooks', async () => {
438
+ await writeCuratedFile('my-playbook.json', {
439
+ name: 'sourced-playbook',
440
+ applicability: {
441
+ situations: ['Test'],
442
+ domains: ['test'],
443
+ },
444
+ guidance: {
445
+ strategy: 'Test strategy',
446
+ tactics: ['Step 1'],
447
+ },
448
+ });
449
+
450
+ await loadCuratedPlaybooks(curatedDir, library);
451
+
452
+ const playbook = await library.getByName('sourced-playbook');
453
+ expect(playbook).toBeDefined();
454
+ expect(playbook!.provenance.sourceFile).toBeDefined();
455
+ expect(playbook!.provenance.sourceFile).toContain('my-playbook.json');
456
+ });
457
+
458
+ it('should load optional fields from curated files', async () => {
459
+ await writeCuratedFile('full.json', {
460
+ name: 'full-playbook',
461
+ applicability: {
462
+ situations: ['Full test'],
463
+ triggers: ['trigger-1', 'trigger-2'],
464
+ antiPatterns: ['not-this'],
465
+ domains: ['test'],
466
+ },
467
+ guidance: {
468
+ strategy: 'Full strategy',
469
+ tactics: ['Tactic A', 'Tactic B'],
470
+ steps: ['Step 1', 'Step 2', 'Step 3'],
471
+ codeExample: 'const x = 1;',
472
+ },
473
+ verification: {
474
+ successIndicators: ['Tests pass'],
475
+ failureIndicators: ['Build fails'],
476
+ rollbackStrategy: 'git revert',
477
+ },
478
+ confidence: 0.95,
479
+ complexity: 'complex',
480
+ estimatedEffort: 10,
481
+ curatedBy: 'expert',
482
+ });
483
+
484
+ await loadCuratedPlaybooks(curatedDir, library);
485
+
486
+ const playbook = await library.getByName('full-playbook');
487
+ expect(playbook).toBeDefined();
488
+ expect(playbook!.applicability.triggers).toEqual(['trigger-1', 'trigger-2']);
489
+ expect(playbook!.applicability.antiPatterns).toEqual(['not-this']);
490
+ expect(playbook!.guidance.steps).toEqual(['Step 1', 'Step 2', 'Step 3']);
491
+ expect(playbook!.guidance.codeExample).toBe('const x = 1;');
492
+ expect(playbook!.verification.successIndicators).toEqual(['Tests pass']);
493
+ expect(playbook!.verification.failureIndicators).toEqual(['Build fails']);
494
+ expect(playbook!.verification.rollbackStrategy).toBe('git revert');
495
+ expect(playbook!.confidence).toBe(0.95);
496
+ expect(playbook!.complexity).toBe('complex');
497
+ expect(playbook!.estimatedEffort).toBe(10);
498
+ });
499
+
500
+ it('should skip already-existing playbooks by name', async () => {
501
+ await writeCuratedFile('existing.json', {
502
+ name: 'already-exists',
503
+ applicability: {
504
+ situations: ['Test'],
505
+ domains: ['test'],
506
+ },
507
+ guidance: {
508
+ strategy: 'Original strategy',
509
+ tactics: ['Original tactic'],
510
+ },
511
+ });
512
+
513
+ // Load once
514
+ await loadCuratedPlaybooks(curatedDir, library);
515
+ const firstLoad = await library.getByName('already-exists');
516
+ expect(firstLoad).toBeDefined();
517
+
518
+ // Load again — should skip
519
+ const result = await loadCuratedPlaybooks(curatedDir, library);
520
+ expect(result.skipped).toBe(1);
521
+ expect(result.loaded).toBe(0);
522
+ });
523
+
524
+ it('should recreate curated playbooks when recreate=true', async () => {
525
+ await writeCuratedFile('recreatable.json', {
526
+ name: 'recreatable-playbook',
527
+ applicability: {
528
+ situations: ['Test situation'],
529
+ domains: ['test'],
530
+ },
531
+ guidance: {
532
+ strategy: 'V1 strategy',
533
+ tactics: ['V1 tactic'],
534
+ },
535
+ });
536
+
537
+ // Load initial
538
+ await loadCuratedPlaybooks(curatedDir, library);
539
+
540
+ // Add an extracted playbook too
541
+ const extracted = createPlaybook({
542
+ name: 'extracted-playbook',
543
+ applicability: { situations: ['Extracted'], triggers: [], antiPatterns: [], domains: ['test'] },
544
+ guidance: { strategy: 'Extracted', tactics: [] },
545
+ provenance: { origin: 'extracted', recordedAt: new Date() },
546
+ });
547
+ await library.add(extracted);
548
+
549
+ // Update the curated file
550
+ await writeCuratedFile('recreatable.json', {
551
+ name: 'recreatable-playbook',
552
+ applicability: {
553
+ situations: ['Test situation - updated'],
554
+ domains: ['test'],
555
+ },
556
+ guidance: {
557
+ strategy: 'V2 strategy',
558
+ tactics: ['V2 tactic'],
559
+ },
560
+ });
561
+
562
+ // Recreate
563
+ const result = await loadCuratedPlaybooks(curatedDir, library, { recreate: true });
564
+ expect(result.recreated).toBe(true);
565
+ expect(result.loaded).toBe(1);
566
+
567
+ // Curated was rebuilt
568
+ const reloaded = await library.getByName('recreatable-playbook');
569
+ expect(reloaded).toBeDefined();
570
+ expect(reloaded!.guidance.strategy).toBe('V2 strategy');
571
+
572
+ // Extracted playbook is untouched
573
+ const still = await library.getByOrigin('extracted');
574
+ expect(still).toHaveLength(1);
575
+ expect(still[0].name).toBe('extracted-playbook');
576
+ });
577
+
578
+ it('should ignore non-JSON files', async () => {
579
+ await writeFile(join(curatedDir, 'readme.md'), '# Not a playbook');
580
+ await writeFile(join(curatedDir, 'notes.txt'), 'some notes');
581
+ await writeCuratedFile('real-playbook.json', {
582
+ name: 'real-one',
583
+ applicability: { situations: ['Test'], domains: ['test'] },
584
+ guidance: { strategy: 'Test', tactics: ['Do it'] },
585
+ });
586
+
587
+ const result = await loadCuratedPlaybooks(curatedDir, library);
588
+
589
+ expect(result.loaded).toBe(1);
590
+ expect(result.errors).toHaveLength(0);
591
+ expect(await library.count()).toBe(1);
592
+ });
593
+
594
+ it('should handle invalid JSON files gracefully', async () => {
595
+ await writeFile(join(curatedDir, 'invalid.json'), '{ invalid json }}}');
596
+
597
+ const result = await loadCuratedPlaybooks(curatedDir, library);
598
+ expect(result.errors).toHaveLength(1);
599
+ expect(result.errors[0].file).toBe('invalid.json');
600
+ expect(result.loaded).toBe(0);
601
+ });
602
+
603
+ it('should handle missing required fields', async () => {
604
+ await writeCuratedFile('incomplete.json', {
605
+ name: 'incomplete',
606
+ } as unknown as CuratedPlaybookFile);
607
+
608
+ const result = await loadCuratedPlaybooks(curatedDir, library);
609
+ expect(result.errors).toHaveLength(1);
610
+ expect(result.errors[0].error).toContain('Missing required fields');
611
+ });
612
+
613
+ it('should return empty result for non-existent directory', async () => {
614
+ const result = await loadCuratedPlaybooks('/nonexistent/path', library);
615
+ expect(result.loaded).toBe(0);
616
+ expect(result.skipped).toBe(0);
617
+ expect(result.errors).toHaveLength(0);
618
+ });
619
+
620
+ it('should load valid files even when some are invalid', async () => {
621
+ await writeCuratedFile('good.json', {
622
+ name: 'good-playbook',
623
+ applicability: { situations: ['Test'], domains: ['test'] },
624
+ guidance: { strategy: 'Good', tactics: ['Do it'] },
625
+ });
626
+ await writeFile(join(curatedDir, 'bad.json'), '{ broken }');
627
+ await writeCuratedFile('also-good.json', {
628
+ name: 'also-good-playbook',
629
+ applicability: { situations: ['Another test'], domains: ['test'] },
630
+ guidance: { strategy: 'Also good', tactics: ['Do it too'] },
631
+ });
632
+
633
+ const result = await loadCuratedPlaybooks(curatedDir, library);
634
+
635
+ expect(result.loaded).toBe(2);
636
+ expect(result.errors).toHaveLength(1);
637
+ expect(await library.count()).toBe(2);
638
+ });
639
+ });