cognitive-core 0.0.1 → 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.
- package/README.md +363 -2
- package/SKILL.md +193 -0
- package/dist/agents/index.d.ts +3 -0
- package/dist/agents/index.d.ts.map +1 -0
- package/dist/agents/index.js +5 -0
- package/dist/agents/index.js.map +1 -0
- package/dist/agents/mock-provider.d.ts +23 -0
- package/dist/agents/mock-provider.d.ts.map +1 -0
- package/dist/agents/mock-provider.js +71 -0
- package/dist/agents/mock-provider.js.map +1 -0
- package/dist/agents/types.d.ts +98 -0
- package/dist/agents/types.d.ts.map +1 -0
- package/dist/agents/types.js +44 -0
- package/dist/agents/types.js.map +1 -0
- package/dist/atlas.d.ts +196 -0
- package/dist/atlas.d.ts.map +1 -0
- package/dist/atlas.js +373 -0
- package/dist/atlas.js.map +1 -0
- package/dist/bin/cognitive-core.d.ts +18 -0
- package/dist/bin/cognitive-core.d.ts.map +1 -0
- package/dist/bin/cognitive-core.js +419 -0
- package/dist/bin/cognitive-core.js.map +1 -0
- package/dist/embeddings/bm25.d.ts +104 -0
- package/dist/embeddings/bm25.d.ts.map +1 -0
- package/dist/embeddings/bm25.js +264 -0
- package/dist/embeddings/bm25.js.map +1 -0
- package/dist/embeddings/index.d.ts +12 -0
- package/dist/embeddings/index.d.ts.map +1 -0
- package/dist/embeddings/index.js +16 -0
- package/dist/embeddings/index.js.map +1 -0
- package/dist/embeddings/manager.d.ts +112 -0
- package/dist/embeddings/manager.d.ts.map +1 -0
- package/dist/embeddings/manager.js +215 -0
- package/dist/embeddings/manager.js.map +1 -0
- package/dist/embeddings/provider.d.ts +101 -0
- package/dist/embeddings/provider.d.ts.map +1 -0
- package/dist/embeddings/provider.js +232 -0
- package/dist/embeddings/provider.js.map +1 -0
- package/dist/embeddings/vector-store.d.ts +101 -0
- package/dist/embeddings/vector-store.d.ts.map +1 -0
- package/dist/embeddings/vector-store.js +256 -0
- package/dist/embeddings/vector-store.js.map +1 -0
- package/dist/factory.d.ts +193 -0
- package/dist/factory.d.ts.map +1 -0
- package/dist/factory.js +109 -0
- package/dist/factory.js.map +1 -0
- package/dist/index.d.ts +43 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +84 -0
- package/dist/index.js.map +1 -0
- package/dist/learning/analyzer.d.ts +110 -0
- package/dist/learning/analyzer.d.ts.map +1 -0
- package/dist/learning/analyzer.js +213 -0
- package/dist/learning/analyzer.js.map +1 -0
- package/dist/learning/effectiveness.d.ts +158 -0
- package/dist/learning/effectiveness.d.ts.map +1 -0
- package/dist/learning/effectiveness.js +251 -0
- package/dist/learning/effectiveness.js.map +1 -0
- package/dist/learning/index.d.ts +8 -0
- package/dist/learning/index.d.ts.map +1 -0
- package/dist/learning/index.js +11 -0
- package/dist/learning/index.js.map +1 -0
- package/dist/learning/llm-extractor.d.ts +88 -0
- package/dist/learning/llm-extractor.d.ts.map +1 -0
- package/dist/learning/llm-extractor.js +372 -0
- package/dist/learning/llm-extractor.js.map +1 -0
- package/dist/learning/meta-learner.d.ts +80 -0
- package/dist/learning/meta-learner.d.ts.map +1 -0
- package/dist/learning/meta-learner.js +355 -0
- package/dist/learning/meta-learner.js.map +1 -0
- package/dist/learning/pipeline.d.ts +65 -0
- package/dist/learning/pipeline.d.ts.map +1 -0
- package/dist/learning/pipeline.js +170 -0
- package/dist/learning/pipeline.js.map +1 -0
- package/dist/learning/playbook-extractor.d.ts +113 -0
- package/dist/learning/playbook-extractor.d.ts.map +1 -0
- package/dist/learning/playbook-extractor.js +523 -0
- package/dist/learning/playbook-extractor.js.map +1 -0
- package/dist/learning/usage-inference.d.ts +82 -0
- package/dist/learning/usage-inference.d.ts.map +1 -0
- package/dist/learning/usage-inference.js +261 -0
- package/dist/learning/usage-inference.js.map +1 -0
- package/dist/mcp/index.d.ts +6 -0
- package/dist/mcp/index.d.ts.map +1 -0
- package/dist/mcp/index.js +6 -0
- package/dist/mcp/index.js.map +1 -0
- package/dist/mcp/playbook-server.d.ts +120 -0
- package/dist/mcp/playbook-server.d.ts.map +1 -0
- package/dist/mcp/playbook-server.js +427 -0
- package/dist/mcp/playbook-server.js.map +1 -0
- package/dist/memory/curated-loader.d.ts +62 -0
- package/dist/memory/curated-loader.d.ts.map +1 -0
- package/dist/memory/curated-loader.js +106 -0
- package/dist/memory/curated-loader.js.map +1 -0
- package/dist/memory/experience.d.ts +122 -0
- package/dist/memory/experience.d.ts.map +1 -0
- package/dist/memory/experience.js +392 -0
- package/dist/memory/experience.js.map +1 -0
- package/dist/memory/index.d.ts +6 -0
- package/dist/memory/index.d.ts.map +1 -0
- package/dist/memory/index.js +9 -0
- package/dist/memory/index.js.map +1 -0
- package/dist/memory/meta.d.ts +90 -0
- package/dist/memory/meta.d.ts.map +1 -0
- package/dist/memory/meta.js +362 -0
- package/dist/memory/meta.js.map +1 -0
- package/dist/memory/playbook.d.ts +133 -0
- package/dist/memory/playbook.d.ts.map +1 -0
- package/dist/memory/playbook.js +357 -0
- package/dist/memory/playbook.js.map +1 -0
- package/dist/memory/system.d.ts +167 -0
- package/dist/memory/system.d.ts.map +1 -0
- package/dist/memory/system.js +383 -0
- package/dist/memory/system.js.map +1 -0
- package/dist/runtime/backends/acp.d.ts +67 -0
- package/dist/runtime/backends/acp.d.ts.map +1 -0
- package/dist/runtime/backends/acp.js +290 -0
- package/dist/runtime/backends/acp.js.map +1 -0
- package/dist/runtime/backends/index.d.ts +5 -0
- package/dist/runtime/backends/index.d.ts.map +1 -0
- package/dist/runtime/backends/index.js +6 -0
- package/dist/runtime/backends/index.js.map +1 -0
- package/dist/runtime/backends/mock.d.ts +67 -0
- package/dist/runtime/backends/mock.d.ts.map +1 -0
- package/dist/runtime/backends/mock.js +153 -0
- package/dist/runtime/backends/mock.js.map +1 -0
- package/dist/runtime/backends/subprocess.d.ts +56 -0
- package/dist/runtime/backends/subprocess.d.ts.map +1 -0
- package/dist/runtime/backends/subprocess.js +260 -0
- package/dist/runtime/backends/subprocess.js.map +1 -0
- package/dist/runtime/flows/learning.d.ts +73 -0
- package/dist/runtime/flows/learning.d.ts.map +1 -0
- package/dist/runtime/flows/learning.js +116 -0
- package/dist/runtime/flows/learning.js.map +1 -0
- package/dist/runtime/flows/validation.d.ts +122 -0
- package/dist/runtime/flows/validation.d.ts.map +1 -0
- package/dist/runtime/flows/validation.js +223 -0
- package/dist/runtime/flows/validation.js.map +1 -0
- package/dist/runtime/index.d.ts +6 -0
- package/dist/runtime/index.d.ts.map +1 -0
- package/dist/runtime/index.js +8 -0
- package/dist/runtime/index.js.map +1 -0
- package/dist/runtime/manager.d.ts +116 -0
- package/dist/runtime/manager.d.ts.map +1 -0
- package/dist/runtime/manager.js +416 -0
- package/dist/runtime/manager.js.map +1 -0
- package/dist/runtime/types.d.ts +138 -0
- package/dist/runtime/types.d.ts.map +1 -0
- package/dist/runtime/types.js +2 -0
- package/dist/runtime/types.js.map +1 -0
- package/dist/search/evaluator.d.ts +102 -0
- package/dist/search/evaluator.d.ts.map +1 -0
- package/dist/search/evaluator.js +352 -0
- package/dist/search/evaluator.js.map +1 -0
- package/dist/search/index.d.ts +7 -0
- package/dist/search/index.d.ts.map +1 -0
- package/dist/search/index.js +11 -0
- package/dist/search/index.js.map +1 -0
- package/dist/search/refinement-loop.d.ts +73 -0
- package/dist/search/refinement-loop.d.ts.map +1 -0
- package/dist/search/refinement-loop.js +245 -0
- package/dist/search/refinement-loop.js.map +1 -0
- package/dist/search/refinement-types.d.ts +154 -0
- package/dist/search/refinement-types.d.ts.map +1 -0
- package/dist/search/refinement-types.js +99 -0
- package/dist/search/refinement-types.js.map +1 -0
- package/dist/search/router.d.ts +61 -0
- package/dist/search/router.d.ts.map +1 -0
- package/dist/search/router.js +197 -0
- package/dist/search/router.js.map +1 -0
- package/dist/search/solver.d.ts +75 -0
- package/dist/search/solver.d.ts.map +1 -0
- package/dist/search/solver.js +216 -0
- package/dist/search/solver.js.map +1 -0
- package/dist/search/verification-runner.d.ts +125 -0
- package/dist/search/verification-runner.d.ts.map +1 -0
- package/dist/search/verification-runner.js +440 -0
- package/dist/search/verification-runner.js.map +1 -0
- package/dist/surfacing/index.d.ts +2 -0
- package/dist/surfacing/index.d.ts.map +1 -0
- package/dist/surfacing/index.js +2 -0
- package/dist/surfacing/index.js.map +1 -0
- package/dist/surfacing/skill-library.d.ts +158 -0
- package/dist/surfacing/skill-library.d.ts.map +1 -0
- package/dist/surfacing/skill-library.js +429 -0
- package/dist/surfacing/skill-library.js.map +1 -0
- package/dist/types/config.d.ts +1113 -0
- package/dist/types/config.d.ts.map +1 -0
- package/dist/types/config.js +274 -0
- package/dist/types/config.js.map +1 -0
- package/dist/types/index.d.ts +9 -0
- package/dist/types/index.d.ts.map +1 -0
- package/dist/types/index.js +14 -0
- package/dist/types/index.js.map +1 -0
- package/dist/types/memory.d.ts +339 -0
- package/dist/types/memory.d.ts.map +1 -0
- package/dist/types/memory.js +207 -0
- package/dist/types/memory.js.map +1 -0
- package/dist/types/meta.d.ts +146 -0
- package/dist/types/meta.d.ts.map +1 -0
- package/dist/types/meta.js +51 -0
- package/dist/types/meta.js.map +1 -0
- package/dist/types/outcome.d.ts +42 -0
- package/dist/types/outcome.d.ts.map +1 -0
- package/dist/types/outcome.js +50 -0
- package/dist/types/outcome.js.map +1 -0
- package/dist/types/playbook.d.ts +119 -0
- package/dist/types/playbook.d.ts.map +1 -0
- package/dist/types/playbook.js +71 -0
- package/dist/types/playbook.js.map +1 -0
- package/dist/types/step.d.ts +44 -0
- package/dist/types/step.d.ts.map +1 -0
- package/dist/types/step.js +32 -0
- package/dist/types/step.js.map +1 -0
- package/dist/types/task.d.ts +91 -0
- package/dist/types/task.d.ts.map +1 -0
- package/dist/types/task.js +39 -0
- package/dist/types/task.js.map +1 -0
- package/dist/types/trajectory.d.ts +221 -0
- package/dist/types/trajectory.d.ts.map +1 -0
- package/dist/types/trajectory.js +60 -0
- package/dist/types/trajectory.js.map +1 -0
- package/dist/utils/index.d.ts +4 -0
- package/dist/utils/index.d.ts.map +1 -0
- package/dist/utils/index.js +4 -0
- package/dist/utils/index.js.map +1 -0
- package/dist/utils/similarity.d.ts +31 -0
- package/dist/utils/similarity.d.ts.map +1 -0
- package/dist/utils/similarity.js +107 -0
- package/dist/utils/similarity.js.map +1 -0
- package/dist/utils/storage.d.ts +106 -0
- package/dist/utils/storage.d.ts.map +1 -0
- package/dist/utils/storage.js +203 -0
- package/dist/utils/storage.js.map +1 -0
- package/dist/utils/validation.d.ts +129 -0
- package/dist/utils/validation.d.ts.map +1 -0
- package/dist/utils/validation.js +171 -0
- package/dist/utils/validation.js.map +1 -0
- package/package.json +61 -9
- package/scripts/migrate-to-playbooks.ts +307 -0
- package/src/agents/index.ts +14 -0
- package/src/agents/mock-provider.ts +93 -0
- package/src/agents/types.ts +137 -0
- package/src/atlas.ts +560 -0
- package/src/bin/cognitive-core.ts +470 -0
- package/src/embeddings/bm25.ts +337 -0
- package/src/embeddings/index.ts +39 -0
- package/src/embeddings/manager.ts +288 -0
- package/src/embeddings/provider.ts +311 -0
- package/src/embeddings/vector-store.ts +353 -0
- package/src/factory.ts +263 -0
- package/src/index.ts +246 -0
- package/src/learning/analyzer.ts +335 -0
- package/src/learning/effectiveness.ts +428 -0
- package/src/learning/index.ts +58 -0
- package/src/learning/llm-extractor.ts +542 -0
- package/src/learning/meta-learner.ts +516 -0
- package/src/learning/pipeline.ts +244 -0
- package/src/learning/playbook-extractor.ts +702 -0
- package/src/learning/usage-inference.ts +372 -0
- package/src/mcp/index.ts +12 -0
- package/src/mcp/playbook-server.ts +565 -0
- package/src/memory/curated-loader.ts +160 -0
- package/src/memory/experience.ts +515 -0
- package/src/memory/index.ts +27 -0
- package/src/memory/meta.ts +506 -0
- package/src/memory/playbook.ts +493 -0
- package/src/memory/system.ts +551 -0
- package/src/runtime/backends/acp.ts +378 -0
- package/src/runtime/backends/index.ts +24 -0
- package/src/runtime/backends/mock.ts +218 -0
- package/src/runtime/backends/subprocess.ts +356 -0
- package/src/runtime/flows/learning.ts +183 -0
- package/src/runtime/flows/validation.ts +381 -0
- package/src/runtime/index.ts +53 -0
- package/src/runtime/manager.ts +541 -0
- package/src/runtime/types.ts +157 -0
- package/src/search/evaluator.ts +474 -0
- package/src/search/index.ts +59 -0
- package/src/search/refinement-loop.ts +363 -0
- package/src/search/refinement-types.ts +159 -0
- package/src/search/router.ts +261 -0
- package/src/search/solver.ts +303 -0
- package/src/search/verification-runner.ts +570 -0
- package/src/surfacing/index.ts +6 -0
- package/src/surfacing/skill-library.ts +594 -0
- package/src/types/config.ts +333 -0
- package/src/types/index.ts +130 -0
- package/src/types/memory.ts +270 -0
- package/src/types/meta.ts +218 -0
- package/src/types/outcome.ts +66 -0
- package/src/types/playbook.ts +196 -0
- package/src/types/step.ts +40 -0
- package/src/types/task.ts +52 -0
- package/src/types/trajectory.ts +80 -0
- package/src/utils/index.ts +38 -0
- package/src/utils/similarity.ts +139 -0
- package/src/utils/storage.ts +249 -0
- package/src/utils/validation.ts +286 -0
- package/tests/embeddings/bm25.test.ts +130 -0
- package/tests/embeddings/manager.test.ts +205 -0
- package/tests/integration/atlas.test.ts +266 -0
- package/tests/integration/e2e.test.ts +929 -0
- package/tests/learning/analyzer.test.ts +426 -0
- package/tests/learning/effectiveness.test.ts +542 -0
- package/tests/learning/pipeline.test.ts +176 -0
- package/tests/learning/playbook-extractor-provenance.test.ts +114 -0
- package/tests/learning/usage-inference.test.ts +254 -0
- package/tests/mcp/playbook-server.test.ts +252 -0
- package/tests/memory/experience.test.ts +198 -0
- package/tests/memory/playbook.test.ts +338 -0
- package/tests/memory/provenance.test.ts +639 -0
- package/tests/memory/system.test.ts +325 -0
- package/tests/runtime/agent-manager.test.ts +512 -0
- package/tests/runtime/mock-backend.test.ts +248 -0
- package/tests/search/refinement-loop.test.ts +468 -0
- package/tests/search/refinement.test.ts +267 -0
- package/tests/search/router.test.ts +427 -0
- package/tests/surfacing/skill-library.test.ts +292 -0
- package/tests/types/outcome.test.ts +147 -0
- package/tests/types/step.test.ts +133 -0
- package/tests/types/task.test.ts +158 -0
- package/tests/types/trajectory.test.ts +253 -0
- package/tests/utils/similarity.test.ts +188 -0
- package/tests/utils/validation.test.ts +252 -0
- package/tsconfig.json +25 -0
- package/vitest.config.ts +22 -0
- package/index.d.ts +0 -4
- package/index.js +0 -4
|
@@ -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
|
+
});
|