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,249 @@
1
+ import { readFile, writeFile, mkdir, readdir, stat } from 'node:fs/promises';
2
+ import { join, dirname } from 'node:path';
3
+ import { existsSync } from 'node:fs';
4
+
5
+ /**
6
+ * Lightweight file-based storage (Claudeception-inspired)
7
+ * Uses JSON files for simplicity and human-readability
8
+ */
9
+
10
+ export interface StorageOptions {
11
+ baseDir: string;
12
+ prettyPrint?: boolean;
13
+ }
14
+
15
+ /**
16
+ * Ensure directory exists
17
+ */
18
+ export async function ensureDir(path: string): Promise<void> {
19
+ if (!existsSync(path)) {
20
+ await mkdir(path, { recursive: true });
21
+ }
22
+ }
23
+
24
+ /**
25
+ * Save data to a JSON file
26
+ */
27
+ export async function saveJson<T>(
28
+ filePath: string,
29
+ data: T,
30
+ options?: { pretty?: boolean }
31
+ ): Promise<void> {
32
+ await ensureDir(dirname(filePath));
33
+ const content = options?.pretty
34
+ ? JSON.stringify(data, null, 2)
35
+ : JSON.stringify(data);
36
+ await writeFile(filePath, content, 'utf-8');
37
+ }
38
+
39
+ /**
40
+ * Load data from a JSON file
41
+ */
42
+ export async function loadJson<T>(filePath: string): Promise<T | null> {
43
+ try {
44
+ const content = await readFile(filePath, 'utf-8');
45
+ return JSON.parse(content) as T;
46
+ } catch {
47
+ return null;
48
+ }
49
+ }
50
+
51
+ /**
52
+ * List files in a directory
53
+ */
54
+ export async function listFiles(
55
+ dir: string,
56
+ extension?: string
57
+ ): Promise<string[]> {
58
+ try {
59
+ const files = await readdir(dir);
60
+ if (extension) {
61
+ return files.filter((f) => f.endsWith(extension));
62
+ }
63
+ return files;
64
+ } catch {
65
+ return [];
66
+ }
67
+ }
68
+
69
+ /**
70
+ * Check if a path exists
71
+ */
72
+ export async function exists(path: string): Promise<boolean> {
73
+ try {
74
+ await stat(path);
75
+ return true;
76
+ } catch {
77
+ return false;
78
+ }
79
+ }
80
+
81
+ /**
82
+ * Simple key-value store backed by JSON files
83
+ */
84
+ export class JsonStore<T> {
85
+ private cache: Map<string, T> = new Map();
86
+ private dirty: Set<string> = new Set();
87
+ private autoSaveTimer: ReturnType<typeof setInterval> | null = null;
88
+
89
+ constructor(
90
+ private baseDir: string,
91
+ private collection: string,
92
+ private options: { autoSaveInterval?: number; pretty?: boolean } = {}
93
+ ) {}
94
+
95
+ /**
96
+ * Get the file path for a key
97
+ */
98
+ private getPath(key: string): string {
99
+ return join(this.baseDir, this.collection, `${key}.json`);
100
+ }
101
+
102
+ /**
103
+ * Initialize the store and load existing data
104
+ */
105
+ async init(): Promise<void> {
106
+ const dir = join(this.baseDir, this.collection);
107
+ await ensureDir(dir);
108
+
109
+ // Load existing items
110
+ const files = await listFiles(dir, '.json');
111
+ for (const file of files) {
112
+ const key = file.replace('.json', '');
113
+ const data = await loadJson<T>(join(dir, file));
114
+ if (data) {
115
+ this.cache.set(key, data);
116
+ }
117
+ }
118
+
119
+ // Start auto-save if configured
120
+ if (this.options.autoSaveInterval) {
121
+ this.autoSaveTimer = setInterval(
122
+ () => this.flush(),
123
+ this.options.autoSaveInterval
124
+ );
125
+ }
126
+ }
127
+
128
+ /**
129
+ * Get an item by key
130
+ */
131
+ get(key: string): T | undefined {
132
+ return this.cache.get(key);
133
+ }
134
+
135
+ /**
136
+ * Set an item
137
+ */
138
+ set(key: string, value: T): void {
139
+ this.cache.set(key, value);
140
+ this.dirty.add(key);
141
+ }
142
+
143
+ /**
144
+ * Delete an item
145
+ */
146
+ delete(key: string): boolean {
147
+ this.dirty.add(key);
148
+ return this.cache.delete(key);
149
+ }
150
+
151
+ /**
152
+ * Check if key exists
153
+ */
154
+ has(key: string): boolean {
155
+ return this.cache.has(key);
156
+ }
157
+
158
+ /**
159
+ * Get all items
160
+ */
161
+ values(): T[] {
162
+ return Array.from(this.cache.values());
163
+ }
164
+
165
+ /**
166
+ * Get all keys
167
+ */
168
+ keys(): string[] {
169
+ return Array.from(this.cache.keys());
170
+ }
171
+
172
+ /**
173
+ * Get all entries
174
+ */
175
+ entries(): Array<[string, T]> {
176
+ return Array.from(this.cache.entries());
177
+ }
178
+
179
+ /**
180
+ * Get item count
181
+ */
182
+ size(): number {
183
+ return this.cache.size;
184
+ }
185
+
186
+ /**
187
+ * Flush dirty items to disk
188
+ */
189
+ async flush(): Promise<void> {
190
+ for (const key of this.dirty) {
191
+ const value = this.cache.get(key);
192
+ const path = this.getPath(key);
193
+
194
+ if (value === undefined) {
195
+ // Item was deleted - we could delete the file too
196
+ // For now, we'll leave it (soft delete)
197
+ continue;
198
+ }
199
+
200
+ await saveJson(path, value, { pretty: this.options.pretty });
201
+ }
202
+ this.dirty.clear();
203
+ }
204
+
205
+ /**
206
+ * Save a single item immediately
207
+ */
208
+ async save(key: string): Promise<void> {
209
+ const value = this.cache.get(key);
210
+ if (value) {
211
+ await saveJson(this.getPath(key), value, { pretty: this.options.pretty });
212
+ this.dirty.delete(key);
213
+ }
214
+ }
215
+
216
+ /**
217
+ * Close the store (flush and stop auto-save)
218
+ */
219
+ async close(): Promise<void> {
220
+ if (this.autoSaveTimer) {
221
+ clearInterval(this.autoSaveTimer);
222
+ this.autoSaveTimer = null;
223
+ }
224
+ await this.flush();
225
+ }
226
+ }
227
+
228
+ /**
229
+ * Create a store index file (for quick lookups without loading all items)
230
+ */
231
+ export async function saveIndex(
232
+ baseDir: string,
233
+ collection: string,
234
+ index: Record<string, unknown>
235
+ ): Promise<void> {
236
+ const path = join(baseDir, collection, '_index.json');
237
+ await saveJson(path, index, { pretty: true });
238
+ }
239
+
240
+ /**
241
+ * Load a store index
242
+ */
243
+ export async function loadIndex(
244
+ baseDir: string,
245
+ collection: string
246
+ ): Promise<Record<string, unknown> | null> {
247
+ const path = join(baseDir, collection, '_index.json');
248
+ return loadJson(path);
249
+ }
@@ -0,0 +1,286 @@
1
+ /**
2
+ * Input validation utilities
3
+ * Provides validation helpers for API boundaries
4
+ */
5
+
6
+ import { z } from 'zod';
7
+
8
+ /**
9
+ * Validation error with details
10
+ */
11
+ export class ValidationError extends Error {
12
+ constructor(
13
+ message: string,
14
+ public readonly field: string,
15
+ public readonly value: unknown
16
+ ) {
17
+ super(`Validation error for '${field}': ${message}`);
18
+ this.name = 'ValidationError';
19
+ }
20
+ }
21
+
22
+ /**
23
+ * Validate a string field
24
+ */
25
+ export function validateString(
26
+ value: unknown,
27
+ field: string,
28
+ options?: {
29
+ minLength?: number;
30
+ maxLength?: number;
31
+ pattern?: RegExp;
32
+ allowEmpty?: boolean;
33
+ }
34
+ ): string {
35
+ if (typeof value !== 'string') {
36
+ throw new ValidationError(`Expected string, got ${typeof value}`, field, value);
37
+ }
38
+
39
+ const str = value;
40
+ const opts = options ?? {};
41
+
42
+ if (!opts.allowEmpty && str.trim().length === 0) {
43
+ throw new ValidationError('Cannot be empty', field, value);
44
+ }
45
+
46
+ if (opts.minLength !== undefined && str.length < opts.minLength) {
47
+ throw new ValidationError(`Must be at least ${opts.minLength} characters`, field, value);
48
+ }
49
+
50
+ if (opts.maxLength !== undefined && str.length > opts.maxLength) {
51
+ throw new ValidationError(`Must be at most ${opts.maxLength} characters`, field, value);
52
+ }
53
+
54
+ if (opts.pattern && !opts.pattern.test(str)) {
55
+ throw new ValidationError(`Does not match required pattern`, field, value);
56
+ }
57
+
58
+ return str;
59
+ }
60
+
61
+ /**
62
+ * Validate an optional string field
63
+ */
64
+ export function validateOptionalString(
65
+ value: unknown,
66
+ field: string,
67
+ options?: {
68
+ minLength?: number;
69
+ maxLength?: number;
70
+ pattern?: RegExp;
71
+ }
72
+ ): string | undefined {
73
+ if (value === undefined || value === null) {
74
+ return undefined;
75
+ }
76
+ return validateString(value, field, options);
77
+ }
78
+
79
+ /**
80
+ * Validate a number field
81
+ */
82
+ export function validateNumber(
83
+ value: unknown,
84
+ field: string,
85
+ options?: {
86
+ min?: number;
87
+ max?: number;
88
+ integer?: boolean;
89
+ }
90
+ ): number {
91
+ if (typeof value !== 'number' || isNaN(value)) {
92
+ throw new ValidationError(`Expected number, got ${typeof value}`, field, value);
93
+ }
94
+
95
+ const opts = options ?? {};
96
+
97
+ if (opts.integer && !Number.isInteger(value)) {
98
+ throw new ValidationError('Must be an integer', field, value);
99
+ }
100
+
101
+ if (opts.min !== undefined && value < opts.min) {
102
+ throw new ValidationError(`Must be at least ${opts.min}`, field, value);
103
+ }
104
+
105
+ if (opts.max !== undefined && value > opts.max) {
106
+ throw new ValidationError(`Must be at most ${opts.max}`, field, value);
107
+ }
108
+
109
+ return value;
110
+ }
111
+
112
+ /**
113
+ * Validate an optional number field
114
+ */
115
+ export function validateOptionalNumber(
116
+ value: unknown,
117
+ field: string,
118
+ options?: {
119
+ min?: number;
120
+ max?: number;
121
+ integer?: boolean;
122
+ }
123
+ ): number | undefined {
124
+ if (value === undefined || value === null) {
125
+ return undefined;
126
+ }
127
+ return validateNumber(value, field, options);
128
+ }
129
+
130
+ /**
131
+ * Validate an array field
132
+ */
133
+ export function validateArray<T>(
134
+ value: unknown,
135
+ field: string,
136
+ itemValidator: (item: unknown, index: number) => T,
137
+ options?: {
138
+ minLength?: number;
139
+ maxLength?: number;
140
+ }
141
+ ): T[] {
142
+ if (!Array.isArray(value)) {
143
+ throw new ValidationError(`Expected array, got ${typeof value}`, field, value);
144
+ }
145
+
146
+ const opts = options ?? {};
147
+
148
+ if (opts.minLength !== undefined && value.length < opts.minLength) {
149
+ throw new ValidationError(`Must have at least ${opts.minLength} items`, field, value);
150
+ }
151
+
152
+ if (opts.maxLength !== undefined && value.length > opts.maxLength) {
153
+ throw new ValidationError(`Must have at most ${opts.maxLength} items`, field, value);
154
+ }
155
+
156
+ return value.map((item, index) => itemValidator(item, index));
157
+ }
158
+
159
+ /**
160
+ * Validate a vector (array of numbers) for embeddings
161
+ */
162
+ export function validateVector(
163
+ value: unknown,
164
+ field: string,
165
+ expectedDimension?: number
166
+ ): number[] {
167
+ if (!Array.isArray(value)) {
168
+ throw new ValidationError(`Expected array, got ${typeof value}`, field, value);
169
+ }
170
+
171
+ for (let i = 0; i < value.length; i++) {
172
+ if (typeof value[i] !== 'number' || isNaN(value[i])) {
173
+ throw new ValidationError(
174
+ `Element at index ${i} is not a valid number`,
175
+ field,
176
+ value[i]
177
+ );
178
+ }
179
+ }
180
+
181
+ if (expectedDimension !== undefined && value.length !== expectedDimension) {
182
+ throw new ValidationError(
183
+ `Expected dimension ${expectedDimension}, got ${value.length}`,
184
+ field,
185
+ value
186
+ );
187
+ }
188
+
189
+ return value as number[];
190
+ }
191
+
192
+ /**
193
+ * Validate task input parameters
194
+ */
195
+ export const TaskInputSchema = z.object({
196
+ description: z.string().min(1, 'Task description cannot be empty'),
197
+ domain: z.string().default('general'),
198
+ context: z.record(z.unknown()).default({}),
199
+ constraints: z.array(z.string()).default([]),
200
+ metadata: z.record(z.unknown()).default({}),
201
+ });
202
+
203
+ export type TaskInput = z.infer<typeof TaskInputSchema>;
204
+
205
+ /**
206
+ * Validate task input
207
+ */
208
+ export function validateTaskInput(input: unknown): TaskInput {
209
+ const result = TaskInputSchema.safeParse(input);
210
+ if (!result.success) {
211
+ const firstError = result.error.errors[0];
212
+ throw new ValidationError(
213
+ firstError.message,
214
+ firstError.path.join('.'),
215
+ input
216
+ );
217
+ }
218
+ return result.data;
219
+ }
220
+
221
+ /**
222
+ * Validate trajectory step count
223
+ */
224
+ export function validateTrajectoryStepCount(
225
+ stepCount: number,
226
+ options?: {
227
+ minSteps?: number;
228
+ maxSteps?: number;
229
+ }
230
+ ): void {
231
+ const opts = options ?? { minSteps: 1, maxSteps: 10000 };
232
+
233
+ if (stepCount < (opts.minSteps ?? 1)) {
234
+ throw new ValidationError(
235
+ `Must have at least ${opts.minSteps ?? 1} steps`,
236
+ 'steps',
237
+ stepCount
238
+ );
239
+ }
240
+
241
+ if (opts.maxSteps !== undefined && stepCount > opts.maxSteps) {
242
+ throw new ValidationError(
243
+ `Must have at most ${opts.maxSteps} steps`,
244
+ 'steps',
245
+ stepCount
246
+ );
247
+ }
248
+ }
249
+
250
+ /**
251
+ * MCP tool argument validation schemas
252
+ */
253
+ export const MCPSearchPlaybooksSchema = z.object({
254
+ query: z.string().min(1, 'Query cannot be empty'),
255
+ domain: z.string().optional(),
256
+ maxResults: z.number().int().min(1).max(50).optional(),
257
+ });
258
+
259
+ export const MCPGetPlaybookDetailsSchema = z.object({
260
+ playbookId: z.string().min(1, 'Playbook ID cannot be empty'),
261
+ });
262
+
263
+ export const MCPGetPlaybooksByDomainSchema = z.object({
264
+ domain: z.string().min(1, 'Domain cannot be empty'),
265
+ maxResults: z.number().int().min(1).max(100).optional(),
266
+ });
267
+
268
+ /**
269
+ * Validate MCP tool arguments
270
+ */
271
+ export function validateMCPArgs<T>(
272
+ schema: z.ZodSchema<T>,
273
+ args: unknown,
274
+ toolName: string
275
+ ): T {
276
+ const result = schema.safeParse(args);
277
+ if (!result.success) {
278
+ const firstError = result.error.errors[0];
279
+ throw new ValidationError(
280
+ `Invalid arguments for ${toolName}: ${firstError.message}`,
281
+ firstError.path.join('.') || 'arguments',
282
+ args
283
+ );
284
+ }
285
+ return result.data;
286
+ }
@@ -0,0 +1,130 @@
1
+ import { describe, it, expect, beforeEach } from 'vitest';
2
+ import { BM25Index, createBM25Index } from '../../src/embeddings/bm25.js';
3
+
4
+ describe('BM25Index', () => {
5
+ let index: BM25Index;
6
+
7
+ beforeEach(() => {
8
+ index = createBM25Index();
9
+ });
10
+
11
+ describe('basic operations', () => {
12
+ it('should add and retrieve documents', () => {
13
+ index.add('doc1', 'hello world', 'experience');
14
+ expect(index.has('doc1')).toBe(true);
15
+ expect(index.size).toBe(1);
16
+ });
17
+
18
+ it('should remove documents', () => {
19
+ index.add('doc1', 'hello world', 'experience');
20
+ expect(index.remove('doc1')).toBe(true);
21
+ expect(index.has('doc1')).toBe(false);
22
+ expect(index.size).toBe(0);
23
+ });
24
+
25
+ it('should handle non-existent document removal', () => {
26
+ expect(index.remove('non-existent')).toBe(false);
27
+ });
28
+ });
29
+
30
+ describe('search', () => {
31
+ beforeEach(() => {
32
+ index.add('doc1', 'machine learning algorithms for classification', 'experience');
33
+ index.add('doc2', 'deep learning neural networks', 'strategy');
34
+ index.add('doc3', 'database query optimization techniques', 'concept');
35
+ index.add('doc4', 'machine learning for natural language processing', 'skill');
36
+ });
37
+
38
+ it('should find relevant documents', () => {
39
+ const results = index.search('machine learning');
40
+ expect(results.length).toBeGreaterThan(0);
41
+
42
+ // Machine learning docs should be ranked higher
43
+ const ids = results.map(r => r.id);
44
+ expect(ids).toContain('doc1');
45
+ expect(ids).toContain('doc4');
46
+ });
47
+
48
+ it('should filter by type', () => {
49
+ const results = index.search('learning', { type: 'experience' });
50
+ expect(results.every(r => r.content.includes('machine'))).toBe(true);
51
+ });
52
+
53
+ it('should respect k limit', () => {
54
+ const results = index.search('learning', { k: 2 });
55
+ expect(results.length).toBeLessThanOrEqual(2);
56
+ });
57
+
58
+ it('should return scores in descending order', () => {
59
+ const results = index.search('machine learning');
60
+ for (let i = 1; i < results.length; i++) {
61
+ expect(results[i - 1].score).toBeGreaterThanOrEqual(results[i].score);
62
+ }
63
+ });
64
+
65
+ it('should return normalized scores between 0 and 1', () => {
66
+ const results = index.search('machine learning');
67
+ for (const result of results) {
68
+ expect(result.score).toBeGreaterThanOrEqual(0);
69
+ expect(result.score).toBeLessThanOrEqual(1);
70
+ }
71
+ });
72
+ });
73
+
74
+ describe('serialization', () => {
75
+ it('should serialize and deserialize correctly', () => {
76
+ index.add('doc1', 'hello world', 'experience');
77
+ index.add('doc2', 'goodbye world', 'strategy');
78
+
79
+ const json = index.toJSON();
80
+ const restored = BM25Index.fromJSON(json);
81
+
82
+ expect(restored.size).toBe(2);
83
+ expect(restored.has('doc1')).toBe(true);
84
+ expect(restored.has('doc2')).toBe(true);
85
+
86
+ // Search should work after restore
87
+ const results = restored.search('world');
88
+ expect(results.length).toBe(2);
89
+ });
90
+ });
91
+
92
+ describe('edge cases', () => {
93
+ it('should handle empty index', () => {
94
+ const results = index.search('anything');
95
+ expect(results).toEqual([]);
96
+ });
97
+
98
+ it('should handle empty query', () => {
99
+ index.add('doc1', 'hello world', 'experience');
100
+ const results = index.search('');
101
+ expect(results.length).toBe(0);
102
+ });
103
+
104
+ it('should handle special characters', () => {
105
+ index.add('doc1', 'hello! @world# $test%', 'experience');
106
+ const results = index.search('hello world test');
107
+ expect(results.length).toBe(1);
108
+ });
109
+
110
+ it('should filter stop words', () => {
111
+ index.add('doc1', 'the quick brown fox', 'experience');
112
+ index.add('doc2', 'a lazy brown dog', 'experience');
113
+
114
+ // "the" and "a" are stop words, so searching for them shouldn't find anything
115
+ // but "brown" should match both
116
+ const results = index.search('brown');
117
+ expect(results.length).toBe(2);
118
+ });
119
+ });
120
+
121
+ describe('BM25 parameters', () => {
122
+ it('should accept custom k1 and b parameters', () => {
123
+ const customIndex = createBM25Index({ k1: 2.0, b: 0.5 });
124
+ customIndex.add('doc1', 'test document', 'experience');
125
+
126
+ const results = customIndex.search('test');
127
+ expect(results.length).toBe(1);
128
+ });
129
+ });
130
+ });