twining-mcp 1.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (171) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +165 -0
  3. package/dist/config.d.ts +8 -0
  4. package/dist/config.d.ts.map +1 -0
  5. package/dist/config.js +78 -0
  6. package/dist/config.js.map +1 -0
  7. package/dist/dashboard/api-routes.d.ts +21 -0
  8. package/dist/dashboard/api-routes.d.ts.map +1 -0
  9. package/dist/dashboard/api-routes.js +516 -0
  10. package/dist/dashboard/api-routes.js.map +1 -0
  11. package/dist/dashboard/dashboard-config.d.ts +19 -0
  12. package/dist/dashboard/dashboard-config.d.ts.map +1 -0
  13. package/dist/dashboard/dashboard-config.js +17 -0
  14. package/dist/dashboard/dashboard-config.js.map +1 -0
  15. package/dist/dashboard/http-server.d.ts +29 -0
  16. package/dist/dashboard/http-server.d.ts.map +1 -0
  17. package/dist/dashboard/http-server.js +177 -0
  18. package/dist/dashboard/http-server.js.map +1 -0
  19. package/dist/dashboard/public/app.js +2396 -0
  20. package/dist/dashboard/public/assets/favicon.svg +44 -0
  21. package/dist/dashboard/public/assets/icon-512.png +0 -0
  22. package/dist/dashboard/public/assets/logo.svg +45 -0
  23. package/dist/dashboard/public/favicon.ico +0 -0
  24. package/dist/dashboard/public/index.html +338 -0
  25. package/dist/dashboard/public/style.css +858 -0
  26. package/dist/dashboard/public/vendor/cytoscape.min.js +1 -0
  27. package/dist/dashboard/public/vendor/vis-timeline-graph2d.min.css +2 -0
  28. package/dist/dashboard/public/vendor/vis-timeline-graph2d.min.js +48 -0
  29. package/dist/embeddings/embedder.d.ts +24 -0
  30. package/dist/embeddings/embedder.d.ts.map +1 -0
  31. package/dist/embeddings/embedder.js +109 -0
  32. package/dist/embeddings/embedder.js.map +1 -0
  33. package/dist/embeddings/index-manager.d.ts +27 -0
  34. package/dist/embeddings/index-manager.d.ts.map +1 -0
  35. package/dist/embeddings/index-manager.js +115 -0
  36. package/dist/embeddings/index-manager.js.map +1 -0
  37. package/dist/embeddings/search.d.ts +52 -0
  38. package/dist/embeddings/search.d.ts.map +1 -0
  39. package/dist/embeddings/search.js +170 -0
  40. package/dist/embeddings/search.js.map +1 -0
  41. package/dist/engine/archiver.d.ts +28 -0
  42. package/dist/engine/archiver.d.ts.map +1 -0
  43. package/dist/engine/archiver.js +153 -0
  44. package/dist/engine/archiver.js.map +1 -0
  45. package/dist/engine/blackboard.d.ts +55 -0
  46. package/dist/engine/blackboard.d.ts.map +1 -0
  47. package/dist/engine/blackboard.js +90 -0
  48. package/dist/engine/blackboard.js.map +1 -0
  49. package/dist/engine/context-assembler.d.ts +50 -0
  50. package/dist/engine/context-assembler.d.ts.map +1 -0
  51. package/dist/engine/context-assembler.js +483 -0
  52. package/dist/engine/context-assembler.js.map +1 -0
  53. package/dist/engine/coordination.d.ts +53 -0
  54. package/dist/engine/coordination.d.ts.map +1 -0
  55. package/dist/engine/coordination.js +239 -0
  56. package/dist/engine/coordination.js.map +1 -0
  57. package/dist/engine/decisions.d.ts +147 -0
  58. package/dist/engine/decisions.d.ts.map +1 -0
  59. package/dist/engine/decisions.js +540 -0
  60. package/dist/engine/decisions.js.map +1 -0
  61. package/dist/engine/exporter.d.ts +30 -0
  62. package/dist/engine/exporter.d.ts.map +1 -0
  63. package/dist/engine/exporter.js +192 -0
  64. package/dist/engine/exporter.js.map +1 -0
  65. package/dist/engine/graph.d.ts +55 -0
  66. package/dist/engine/graph.d.ts.map +1 -0
  67. package/dist/engine/graph.js +119 -0
  68. package/dist/engine/graph.js.map +1 -0
  69. package/dist/engine/planning-bridge.d.ts +53 -0
  70. package/dist/engine/planning-bridge.d.ts.map +1 -0
  71. package/dist/engine/planning-bridge.js +170 -0
  72. package/dist/engine/planning-bridge.js.map +1 -0
  73. package/dist/index.d.ts +3 -0
  74. package/dist/index.d.ts.map +1 -0
  75. package/dist/index.js +32 -0
  76. package/dist/index.js.map +1 -0
  77. package/dist/server.d.ts +7 -0
  78. package/dist/server.d.ts.map +1 -0
  79. package/dist/server.js +82 -0
  80. package/dist/server.js.map +1 -0
  81. package/dist/storage/agent-store.d.ts +36 -0
  82. package/dist/storage/agent-store.d.ts.map +1 -0
  83. package/dist/storage/agent-store.js +119 -0
  84. package/dist/storage/agent-store.js.map +1 -0
  85. package/dist/storage/blackboard-store.d.ts +21 -0
  86. package/dist/storage/blackboard-store.d.ts.map +1 -0
  87. package/dist/storage/blackboard-store.js +56 -0
  88. package/dist/storage/blackboard-store.js.map +1 -0
  89. package/dist/storage/decision-store.d.ts +23 -0
  90. package/dist/storage/decision-store.d.ts.map +1 -0
  91. package/dist/storage/decision-store.js +179 -0
  92. package/dist/storage/decision-store.js.map +1 -0
  93. package/dist/storage/file-store.d.ts +20 -0
  94. package/dist/storage/file-store.d.ts.map +1 -0
  95. package/dist/storage/file-store.js +108 -0
  96. package/dist/storage/file-store.js.map +1 -0
  97. package/dist/storage/graph-store.d.ts +39 -0
  98. package/dist/storage/graph-store.d.ts.map +1 -0
  99. package/dist/storage/graph-store.js +143 -0
  100. package/dist/storage/graph-store.js.map +1 -0
  101. package/dist/storage/handoff-store.d.ts +44 -0
  102. package/dist/storage/handoff-store.d.ts.map +1 -0
  103. package/dist/storage/handoff-store.js +136 -0
  104. package/dist/storage/handoff-store.js.map +1 -0
  105. package/dist/storage/init.d.ts +10 -0
  106. package/dist/storage/init.d.ts.map +1 -0
  107. package/dist/storage/init.js +48 -0
  108. package/dist/storage/init.js.map +1 -0
  109. package/dist/tools/blackboard-tools.d.ts +4 -0
  110. package/dist/tools/blackboard-tools.d.ts.map +1 -0
  111. package/dist/tools/blackboard-tools.js +131 -0
  112. package/dist/tools/blackboard-tools.js.map +1 -0
  113. package/dist/tools/context-tools.d.ts +4 -0
  114. package/dist/tools/context-tools.d.ts.map +1 -0
  115. package/dist/tools/context-tools.js +76 -0
  116. package/dist/tools/context-tools.js.map +1 -0
  117. package/dist/tools/coordination-tools.d.ts +6 -0
  118. package/dist/tools/coordination-tools.d.ts.map +1 -0
  119. package/dist/tools/coordination-tools.js +220 -0
  120. package/dist/tools/coordination-tools.js.map +1 -0
  121. package/dist/tools/decision-tools.d.ts +4 -0
  122. package/dist/tools/decision-tools.d.ts.map +1 -0
  123. package/dist/tools/decision-tools.js +265 -0
  124. package/dist/tools/decision-tools.js.map +1 -0
  125. package/dist/tools/export-tools.d.ts +4 -0
  126. package/dist/tools/export-tools.d.ts.map +1 -0
  127. package/dist/tools/export-tools.js +30 -0
  128. package/dist/tools/export-tools.js.map +1 -0
  129. package/dist/tools/graph-tools.d.ts +4 -0
  130. package/dist/tools/graph-tools.d.ts.map +1 -0
  131. package/dist/tools/graph-tools.js +127 -0
  132. package/dist/tools/graph-tools.js.map +1 -0
  133. package/dist/tools/lifecycle-tools.d.ts +9 -0
  134. package/dist/tools/lifecycle-tools.d.ts.map +1 -0
  135. package/dist/tools/lifecycle-tools.js +142 -0
  136. package/dist/tools/lifecycle-tools.js.map +1 -0
  137. package/dist/utils/errors.d.ts +24 -0
  138. package/dist/utils/errors.d.ts.map +1 -0
  139. package/dist/utils/errors.js +31 -0
  140. package/dist/utils/errors.js.map +1 -0
  141. package/dist/utils/ids.d.ts +2 -0
  142. package/dist/utils/ids.d.ts.map +1 -0
  143. package/dist/utils/ids.js +10 -0
  144. package/dist/utils/ids.js.map +1 -0
  145. package/dist/utils/liveness.d.ts +14 -0
  146. package/dist/utils/liveness.d.ts.map +1 -0
  147. package/dist/utils/liveness.js +19 -0
  148. package/dist/utils/liveness.js.map +1 -0
  149. package/dist/utils/tags.d.ts +9 -0
  150. package/dist/utils/tags.d.ts.map +1 -0
  151. package/dist/utils/tags.js +12 -0
  152. package/dist/utils/tags.js.map +1 -0
  153. package/dist/utils/tokens.d.ts +6 -0
  154. package/dist/utils/tokens.d.ts.map +1 -0
  155. package/dist/utils/tokens.js +8 -0
  156. package/dist/utils/tokens.js.map +1 -0
  157. package/dist/utils/types.d.ts +308 -0
  158. package/dist/utils/types.d.ts.map +1 -0
  159. package/dist/utils/types.js +18 -0
  160. package/dist/utils/types.js.map +1 -0
  161. package/package.json +60 -0
  162. package/src/dashboard/public/app.js +2396 -0
  163. package/src/dashboard/public/assets/favicon.svg +44 -0
  164. package/src/dashboard/public/assets/icon-512.png +0 -0
  165. package/src/dashboard/public/assets/logo.svg +45 -0
  166. package/src/dashboard/public/favicon.ico +0 -0
  167. package/src/dashboard/public/index.html +338 -0
  168. package/src/dashboard/public/style.css +858 -0
  169. package/src/dashboard/public/vendor/cytoscape.min.js +1 -0
  170. package/src/dashboard/public/vendor/vis-timeline-graph2d.min.css +2 -0
  171. package/src/dashboard/public/vendor/vis-timeline-graph2d.min.js +48 -0
@@ -0,0 +1,24 @@
1
+ export declare class Embedder {
2
+ private static instances;
3
+ private readonly twiningDir;
4
+ private pipeline;
5
+ private fallbackMode;
6
+ private initPromise;
7
+ constructor(twiningDir: string);
8
+ /** Get or create a singleton instance for a given twiningDir. */
9
+ static getInstance(twiningDir: string): Embedder;
10
+ /** Reset singleton instances (for testing). */
11
+ static resetInstances(): void;
12
+ /** Generate a 384-dimensional embedding for the given text. Returns null if in fallback mode. */
13
+ embed(text: string): Promise<number[] | null>;
14
+ /** Generate embeddings for multiple texts. Returns null for any that fail. */
15
+ embedBatch(texts: string[]): Promise<(number[] | null)[]>;
16
+ /** Whether the embedder has fallen back to keyword-only mode. */
17
+ isFallbackMode(): boolean;
18
+ /** Whether the pipeline has been initialized (for testing). */
19
+ isInitialized(): boolean;
20
+ /** Initialize the ONNX pipeline. Called lazily on first embed(). */
21
+ private initialize;
22
+ private doInitialize;
23
+ }
24
+ //# sourceMappingURL=embedder.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"embedder.d.ts","sourceRoot":"","sources":["../../src/embeddings/embedder.ts"],"names":[],"mappings":"AAaA,qBAAa,QAAQ;IACnB,OAAO,CAAC,MAAM,CAAC,SAAS,CAA+B;IAEvD,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAS;IACpC,OAAO,CAAC,QAAQ,CAA0C;IAC1D,OAAO,CAAC,YAAY,CAAS;IAC7B,OAAO,CAAC,WAAW,CAA8B;gBAErC,UAAU,EAAE,MAAM;IAI9B,iEAAiE;IACjE,MAAM,CAAC,WAAW,CAAC,UAAU,EAAE,MAAM,GAAG,QAAQ;IAQhD,+CAA+C;IAC/C,MAAM,CAAC,cAAc,IAAI,IAAI;IAI7B,iGAAiG;IAC3F,KAAK,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,EAAE,GAAG,IAAI,CAAC;IAsBnD,8EAA8E;IACxE,UAAU,CAAC,KAAK,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,CAAC,MAAM,EAAE,GAAG,IAAI,CAAC,EAAE,CAAC;IAyB/D,iEAAiE;IACjE,cAAc,IAAI,OAAO;IAIzB,+DAA+D;IAC/D,aAAa,IAAI,OAAO;IAIxB,oEAAoE;YACtD,UAAU;YAWV,YAAY;CAqB3B"}
@@ -0,0 +1,109 @@
1
+ /**
2
+ * Lazy-loaded singleton embedding pipeline using @huggingface/transformers.
3
+ * Uses all-MiniLM-L6-v2 (384 dimensions) via ONNX runtime.
4
+ * Falls back gracefully to keyword-only mode if ONNX fails.
5
+ */
6
+ import path from "node:path";
7
+ export class Embedder {
8
+ static instances = new Map();
9
+ twiningDir;
10
+ pipeline = null;
11
+ fallbackMode = false;
12
+ initPromise = null;
13
+ constructor(twiningDir) {
14
+ this.twiningDir = twiningDir;
15
+ }
16
+ /** Get or create a singleton instance for a given twiningDir. */
17
+ static getInstance(twiningDir) {
18
+ const existing = Embedder.instances.get(twiningDir);
19
+ if (existing)
20
+ return existing;
21
+ const instance = new Embedder(twiningDir);
22
+ Embedder.instances.set(twiningDir, instance);
23
+ return instance;
24
+ }
25
+ /** Reset singleton instances (for testing). */
26
+ static resetInstances() {
27
+ Embedder.instances.clear();
28
+ }
29
+ /** Generate a 384-dimensional embedding for the given text. Returns null if in fallback mode. */
30
+ async embed(text) {
31
+ if (this.fallbackMode)
32
+ return null;
33
+ if (!this.pipeline) {
34
+ await this.initialize();
35
+ }
36
+ if (this.fallbackMode || !this.pipeline)
37
+ return null;
38
+ try {
39
+ const output = await this.pipeline(text, {
40
+ pooling: "mean",
41
+ normalize: true,
42
+ });
43
+ return Array.from(output.data);
44
+ }
45
+ catch (error) {
46
+ // Transient embedding errors don't trigger fallback mode
47
+ console.error("[twining] Embedding error (non-fatal):", error);
48
+ return null;
49
+ }
50
+ }
51
+ /** Generate embeddings for multiple texts. Returns null for any that fail. */
52
+ async embedBatch(texts) {
53
+ if (this.fallbackMode)
54
+ return texts.map(() => null);
55
+ if (!this.pipeline) {
56
+ await this.initialize();
57
+ }
58
+ if (this.fallbackMode || !this.pipeline)
59
+ return texts.map(() => null);
60
+ const results = [];
61
+ for (const text of texts) {
62
+ try {
63
+ const output = await this.pipeline(text, {
64
+ pooling: "mean",
65
+ normalize: true,
66
+ });
67
+ results.push(Array.from(output.data));
68
+ }
69
+ catch (error) {
70
+ console.error("[twining] Batch embedding error (non-fatal):", error);
71
+ results.push(null);
72
+ }
73
+ }
74
+ return results;
75
+ }
76
+ /** Whether the embedder has fallen back to keyword-only mode. */
77
+ isFallbackMode() {
78
+ return this.fallbackMode;
79
+ }
80
+ /** Whether the pipeline has been initialized (for testing). */
81
+ isInitialized() {
82
+ return this.pipeline !== null || this.fallbackMode;
83
+ }
84
+ /** Initialize the ONNX pipeline. Called lazily on first embed(). */
85
+ async initialize() {
86
+ // Prevent concurrent initialization
87
+ if (this.initPromise) {
88
+ await this.initPromise;
89
+ return;
90
+ }
91
+ this.initPromise = this.doInitialize();
92
+ await this.initPromise;
93
+ }
94
+ async doInitialize() {
95
+ try {
96
+ // Dynamic import to avoid loading ONNX at module evaluation time
97
+ const { pipeline, env } = await import("@huggingface/transformers");
98
+ // Configure model cache within .twining/
99
+ env.cacheDir = path.join(this.twiningDir, "models");
100
+ // Create the feature extraction pipeline
101
+ this.pipeline = (await pipeline("feature-extraction", "Xenova/all-MiniLM-L6-v2"));
102
+ }
103
+ catch (error) {
104
+ console.error("[twining] ONNX embedding initialization failed. Falling back to keyword search:", error);
105
+ this.fallbackMode = true;
106
+ }
107
+ }
108
+ }
109
+ //# sourceMappingURL=embedder.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"embedder.js","sourceRoot":"","sources":["../../src/embeddings/embedder.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,27 @@
1
+ /** Embedding index structure — spec section 5.3 */
2
+ export interface EmbeddingIndex {
3
+ model: string;
4
+ dimension: number;
5
+ entries: {
6
+ id: string;
7
+ vector: number[];
8
+ }[];
9
+ }
10
+ export type IndexName = "blackboard" | "decisions";
11
+ export declare class IndexManager {
12
+ private readonly embeddingsDir;
13
+ constructor(twiningDir: string);
14
+ /** Get the file path for a named index. */
15
+ private indexPath;
16
+ /** Load an embedding index. Returns empty index if file doesn't exist. */
17
+ load(indexName: IndexName): Promise<EmbeddingIndex>;
18
+ /** Save an embedding index atomically with file locking. */
19
+ save(indexName: IndexName, index: EmbeddingIndex): Promise<void>;
20
+ /** Add a single entry to an index. Loads, appends, saves atomically. */
21
+ addEntry(indexName: IndexName, id: string, vector: number[]): Promise<void>;
22
+ /** Remove entries by IDs from an index. */
23
+ removeEntries(indexName: IndexName, ids: string[]): Promise<void>;
24
+ /** Get a single vector by ID. Returns null if not found. */
25
+ getVector(indexName: IndexName, id: string): Promise<number[] | null>;
26
+ }
27
+ //# sourceMappingURL=index-manager.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index-manager.d.ts","sourceRoot":"","sources":["../../src/embeddings/index-manager.ts"],"names":[],"mappings":"AAUA,mDAAmD;AACnD,MAAM,WAAW,cAAc;IAC7B,KAAK,EAAE,MAAM,CAAC;IACd,SAAS,EAAE,MAAM,CAAC;IAClB,OAAO,EAAE;QACP,EAAE,EAAE,MAAM,CAAC;QACX,MAAM,EAAE,MAAM,EAAE,CAAC;KAClB,EAAE,CAAC;CACL;AAaD,MAAM,MAAM,SAAS,GAAG,YAAY,GAAG,WAAW,CAAC;AAEnD,qBAAa,YAAY;IACvB,OAAO,CAAC,QAAQ,CAAC,aAAa,CAAS;gBAE3B,UAAU,EAAE,MAAM;IAK9B,2CAA2C;IAC3C,OAAO,CAAC,SAAS;IAIjB,0EAA0E;IACpE,IAAI,CAAC,SAAS,EAAE,SAAS,GAAG,OAAO,CAAC,cAAc,CAAC;IAoBzD,4DAA4D;IACtD,IAAI,CAAC,SAAS,EAAE,SAAS,EAAE,KAAK,EAAE,cAAc,GAAG,OAAO,CAAC,IAAI,CAAC;IAgBtE,wEAAwE;IAClE,QAAQ,CACZ,SAAS,EAAE,SAAS,EACpB,EAAE,EAAE,MAAM,EACV,MAAM,EAAE,MAAM,EAAE,GACf,OAAO,CAAC,IAAI,CAAC;IA8BhB,2CAA2C;IACrC,aAAa,CAAC,SAAS,EAAE,SAAS,EAAE,GAAG,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,IAAI,CAAC;IAmBvE,4DAA4D;IACtD,SAAS,CACb,SAAS,EAAE,SAAS,EACpB,EAAE,EAAE,MAAM,GACT,OAAO,CAAC,MAAM,EAAE,GAAG,IAAI,CAAC;CAK5B"}
@@ -0,0 +1,115 @@
1
+ /**
2
+ * Embedding index CRUD operations.
3
+ * JSON-based storage in .twining/embeddings/ with file locking.
4
+ * Matches spec section 5.3 index structure.
5
+ */
6
+ import fs from "node:fs";
7
+ import path from "node:path";
8
+ import lockfile from "proper-lockfile";
9
+ import { ensureDir } from "../storage/file-store.js";
10
+ const LOCK_OPTIONS = {
11
+ retries: { retries: 10, factor: 1.5, minTimeout: 50, maxTimeout: 1000 },
12
+ stale: 10000,
13
+ };
14
+ const DEFAULT_INDEX = {
15
+ model: "all-MiniLM-L6-v2",
16
+ dimension: 384,
17
+ entries: [],
18
+ };
19
+ export class IndexManager {
20
+ embeddingsDir;
21
+ constructor(twiningDir) {
22
+ this.embeddingsDir = path.join(twiningDir, "embeddings");
23
+ ensureDir(this.embeddingsDir);
24
+ }
25
+ /** Get the file path for a named index. */
26
+ indexPath(indexName) {
27
+ return path.join(this.embeddingsDir, `${indexName}.index`);
28
+ }
29
+ /** Load an embedding index. Returns empty index if file doesn't exist. */
30
+ async load(indexName) {
31
+ const filePath = this.indexPath(indexName);
32
+ if (!fs.existsSync(filePath)) {
33
+ return { ...DEFAULT_INDEX, entries: [] };
34
+ }
35
+ try {
36
+ const content = fs.readFileSync(filePath, "utf-8");
37
+ if (!content.trim()) {
38
+ return { ...DEFAULT_INDEX, entries: [] };
39
+ }
40
+ return JSON.parse(content);
41
+ }
42
+ catch {
43
+ console.error(`[twining] Corrupt embedding index ${indexName}, returning empty`);
44
+ return { ...DEFAULT_INDEX, entries: [] };
45
+ }
46
+ }
47
+ /** Save an embedding index atomically with file locking. */
48
+ async save(indexName, index) {
49
+ const filePath = this.indexPath(indexName);
50
+ // Ensure file exists for proper-lockfile
51
+ if (!fs.existsSync(filePath)) {
52
+ fs.writeFileSync(filePath, "");
53
+ }
54
+ const release = await lockfile.lock(filePath, LOCK_OPTIONS);
55
+ try {
56
+ fs.writeFileSync(filePath, JSON.stringify(index));
57
+ }
58
+ finally {
59
+ await release();
60
+ }
61
+ }
62
+ /** Add a single entry to an index. Loads, appends, saves atomically. */
63
+ async addEntry(indexName, id, vector) {
64
+ const filePath = this.indexPath(indexName);
65
+ // Ensure file exists for proper-lockfile
66
+ if (!fs.existsSync(filePath)) {
67
+ fs.writeFileSync(filePath, "");
68
+ }
69
+ const release = await lockfile.lock(filePath, LOCK_OPTIONS);
70
+ try {
71
+ // Read current index
72
+ const content = fs.readFileSync(filePath, "utf-8");
73
+ const index = content.trim() ? JSON.parse(content) : { ...DEFAULT_INDEX, entries: [] };
74
+ // Replace existing entry with same ID, or append
75
+ const existingIdx = index.entries.findIndex((e) => e.id === id);
76
+ if (existingIdx >= 0) {
77
+ index.entries[existingIdx] = { id, vector };
78
+ }
79
+ else {
80
+ index.entries.push({ id, vector });
81
+ }
82
+ // Write back
83
+ fs.writeFileSync(filePath, JSON.stringify(index));
84
+ }
85
+ finally {
86
+ await release();
87
+ }
88
+ }
89
+ /** Remove entries by IDs from an index. */
90
+ async removeEntries(indexName, ids) {
91
+ const filePath = this.indexPath(indexName);
92
+ if (!fs.existsSync(filePath))
93
+ return;
94
+ const release = await lockfile.lock(filePath, LOCK_OPTIONS);
95
+ try {
96
+ const content = fs.readFileSync(filePath, "utf-8");
97
+ if (!content.trim())
98
+ return;
99
+ const index = JSON.parse(content);
100
+ const idSet = new Set(ids);
101
+ index.entries = index.entries.filter((e) => !idSet.has(e.id));
102
+ fs.writeFileSync(filePath, JSON.stringify(index));
103
+ }
104
+ finally {
105
+ await release();
106
+ }
107
+ }
108
+ /** Get a single vector by ID. Returns null if not found. */
109
+ async getVector(indexName, id) {
110
+ const index = await this.load(indexName);
111
+ const entry = index.entries.find((e) => e.id === id);
112
+ return entry?.vector ?? null;
113
+ }
114
+ }
115
+ //# sourceMappingURL=index-manager.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index-manager.js","sourceRoot":"","sources":["../../src/embeddings/index-manager.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,52 @@
1
+ /**
2
+ * Semantic search with cosine similarity and keyword fallback.
3
+ * Uses embedder for vector search, falls back to term-frequency
4
+ * keyword search when ONNX is unavailable.
5
+ */
6
+ import type { Embedder } from "./embedder.js";
7
+ import type { IndexManager } from "./index-manager.js";
8
+ import type { BlackboardEntry, Decision } from "../utils/types.js";
9
+ export interface BlackboardSearchResult {
10
+ entry: BlackboardEntry;
11
+ relevance: number;
12
+ }
13
+ export interface DecisionSearchResult {
14
+ decision: Decision;
15
+ relevance: number;
16
+ }
17
+ export interface SearchResults<T> {
18
+ results: T[];
19
+ fallback_mode: boolean;
20
+ }
21
+ export declare class SearchEngine {
22
+ private readonly embedder;
23
+ private readonly indexManager;
24
+ constructor(embedder: Embedder, indexManager: IndexManager);
25
+ /** Search blackboard entries by semantic similarity or keyword fallback. */
26
+ searchBlackboard(query: string, entries: BlackboardEntry[], options?: {
27
+ entry_types?: string[];
28
+ limit?: number;
29
+ }): Promise<SearchResults<BlackboardSearchResult>>;
30
+ /** Search decisions by semantic similarity or keyword fallback. */
31
+ searchDecisions(query: string, decisions: Decision[], options?: {
32
+ limit?: number;
33
+ }): Promise<SearchResults<DecisionSearchResult>>;
34
+ }
35
+ /**
36
+ * Cosine similarity for pre-normalized vectors (dot product).
37
+ * Since all-MiniLM-L6-v2 outputs normalized vectors, cosine similarity
38
+ * simplifies to the dot product.
39
+ */
40
+ export declare function cosineSimilarity(a: number[], b: number[]): number;
41
+ /**
42
+ * Term-frequency based keyword search for fallback mode.
43
+ * Scores each item by how many query terms appear and how often.
44
+ */
45
+ export declare function keywordSearch(query: string, items: {
46
+ id: string;
47
+ text: string;
48
+ }[], limit: number): {
49
+ id: string;
50
+ score: number;
51
+ }[];
52
+ //# sourceMappingURL=search.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"search.d.ts","sourceRoot":"","sources":["../../src/embeddings/search.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AACH,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAC;AAC9C,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;AACvD,OAAO,KAAK,EAAE,eAAe,EAAE,QAAQ,EAAE,MAAM,mBAAmB,CAAC;AAEnE,MAAM,WAAW,sBAAsB;IACrC,KAAK,EAAE,eAAe,CAAC;IACvB,SAAS,EAAE,MAAM,CAAC;CACnB;AAED,MAAM,WAAW,oBAAoB;IACnC,QAAQ,EAAE,QAAQ,CAAC;IACnB,SAAS,EAAE,MAAM,CAAC;CACnB;AAED,MAAM,WAAW,aAAa,CAAC,CAAC;IAC9B,OAAO,EAAE,CAAC,EAAE,CAAC;IACb,aAAa,EAAE,OAAO,CAAC;CACxB;AAED,qBAAa,YAAY;IACvB,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAW;IACpC,OAAO,CAAC,QAAQ,CAAC,YAAY,CAAe;gBAEhC,QAAQ,EAAE,QAAQ,EAAE,YAAY,EAAE,YAAY;IAK1D,4EAA4E;IACtE,gBAAgB,CACpB,KAAK,EAAE,MAAM,EACb,OAAO,EAAE,eAAe,EAAE,EAC1B,OAAO,CAAC,EAAE;QAAE,WAAW,CAAC,EAAE,MAAM,EAAE,CAAC;QAAC,KAAK,CAAC,EAAE,MAAM,CAAA;KAAE,GACnD,OAAO,CAAC,aAAa,CAAC,sBAAsB,CAAC,CAAC;IA4EjD,mEAAmE;IAC7D,eAAe,CACnB,KAAK,EAAE,MAAM,EACb,SAAS,EAAE,QAAQ,EAAE,EACrB,OAAO,CAAC,EAAE;QAAE,KAAK,CAAC,EAAE,MAAM,CAAA;KAAE,GAC3B,OAAO,CAAC,aAAa,CAAC,oBAAoB,CAAC,CAAC;CAmEhD;AAED;;;;GAIG;AACH,wBAAgB,gBAAgB,CAAC,CAAC,EAAE,MAAM,EAAE,EAAE,CAAC,EAAE,MAAM,EAAE,GAAG,MAAM,CAOjE;AAED;;;GAGG;AACH,wBAAgB,aAAa,CAC3B,KAAK,EAAE,MAAM,EACb,KAAK,EAAE;IAAE,EAAE,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,EAAE,EACrC,KAAK,EAAE,MAAM,GACZ;IAAE,EAAE,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAAE,EAAE,CAiCjC"}
@@ -0,0 +1,170 @@
1
+ export class SearchEngine {
2
+ embedder;
3
+ indexManager;
4
+ constructor(embedder, indexManager) {
5
+ this.embedder = embedder;
6
+ this.indexManager = indexManager;
7
+ }
8
+ /** Search blackboard entries by semantic similarity or keyword fallback. */
9
+ async searchBlackboard(query, entries, options) {
10
+ const limit = options?.limit ?? 10;
11
+ let filtered = entries;
12
+ // Apply type filter
13
+ if (options?.entry_types && options.entry_types.length > 0) {
14
+ filtered = filtered.filter((e) => options.entry_types.includes(e.entry_type));
15
+ }
16
+ if (filtered.length === 0) {
17
+ return { results: [], fallback_mode: this.embedder.isFallbackMode() };
18
+ }
19
+ // Try semantic search first
20
+ if (!this.embedder.isFallbackMode()) {
21
+ const queryVector = await this.embedder.embed(query);
22
+ if (queryVector) {
23
+ const index = await this.indexManager.load("blackboard");
24
+ const vectorMap = new Map(index.entries.map((e) => [e.id, e.vector]));
25
+ const scored = [];
26
+ for (const entry of filtered) {
27
+ const entryVector = vectorMap.get(entry.id);
28
+ if (entryVector) {
29
+ const relevance = cosineSimilarity(queryVector, entryVector);
30
+ scored.push({ entry, relevance });
31
+ }
32
+ else {
33
+ // Entry has no embedding — use keyword as individual fallback
34
+ const text = entry.summary + " " + entry.detail;
35
+ const kwResults = keywordSearch(query, [{ id: entry.id, text }], 1);
36
+ const score = kwResults[0]?.score ?? 0;
37
+ if (score > 0) {
38
+ scored.push({ entry, relevance: score * 0.5 }); // Discount keyword scores
39
+ }
40
+ }
41
+ }
42
+ scored.sort((a, b) => b.relevance - a.relevance);
43
+ return {
44
+ results: scored.slice(0, limit),
45
+ fallback_mode: false,
46
+ };
47
+ }
48
+ }
49
+ // Keyword fallback
50
+ const items = filtered.map((e) => ({
51
+ id: e.id,
52
+ text: e.summary + " " + e.detail,
53
+ }));
54
+ const kwResults = keywordSearch(query, items, limit);
55
+ const idToScore = new Map(kwResults.map((r) => [r.id, r.score]));
56
+ const results = [];
57
+ for (const entry of filtered) {
58
+ const score = idToScore.get(entry.id);
59
+ if (score !== undefined && score > 0) {
60
+ results.push({ entry, relevance: score });
61
+ }
62
+ }
63
+ results.sort((a, b) => b.relevance - a.relevance);
64
+ return {
65
+ results: results.slice(0, limit),
66
+ fallback_mode: true,
67
+ };
68
+ }
69
+ /** Search decisions by semantic similarity or keyword fallback. */
70
+ async searchDecisions(query, decisions, options) {
71
+ const limit = options?.limit ?? 10;
72
+ if (decisions.length === 0) {
73
+ return { results: [], fallback_mode: this.embedder.isFallbackMode() };
74
+ }
75
+ // Try semantic search first
76
+ if (!this.embedder.isFallbackMode()) {
77
+ const queryVector = await this.embedder.embed(query);
78
+ if (queryVector) {
79
+ const index = await this.indexManager.load("decisions");
80
+ const vectorMap = new Map(index.entries.map((e) => [e.id, e.vector]));
81
+ const scored = [];
82
+ for (const decision of decisions) {
83
+ const decisionVector = vectorMap.get(decision.id);
84
+ if (decisionVector) {
85
+ const relevance = cosineSimilarity(queryVector, decisionVector);
86
+ scored.push({ decision, relevance });
87
+ }
88
+ else {
89
+ const text = decision.summary + " " + decision.rationale + " " + decision.context;
90
+ const kwResults = keywordSearch(query, [{ id: decision.id, text }], 1);
91
+ const score = kwResults[0]?.score ?? 0;
92
+ if (score > 0) {
93
+ scored.push({ decision, relevance: score * 0.5 });
94
+ }
95
+ }
96
+ }
97
+ scored.sort((a, b) => b.relevance - a.relevance);
98
+ return {
99
+ results: scored.slice(0, limit),
100
+ fallback_mode: false,
101
+ };
102
+ }
103
+ }
104
+ // Keyword fallback
105
+ const items = decisions.map((d) => ({
106
+ id: d.id,
107
+ text: d.summary + " " + d.rationale + " " + d.context,
108
+ }));
109
+ const kwResults = keywordSearch(query, items, limit);
110
+ const idToScore = new Map(kwResults.map((r) => [r.id, r.score]));
111
+ const results = [];
112
+ for (const decision of decisions) {
113
+ const score = idToScore.get(decision.id);
114
+ if (score !== undefined && score > 0) {
115
+ results.push({ decision, relevance: score });
116
+ }
117
+ }
118
+ results.sort((a, b) => b.relevance - a.relevance);
119
+ return {
120
+ results: results.slice(0, limit),
121
+ fallback_mode: true,
122
+ };
123
+ }
124
+ }
125
+ /**
126
+ * Cosine similarity for pre-normalized vectors (dot product).
127
+ * Since all-MiniLM-L6-v2 outputs normalized vectors, cosine similarity
128
+ * simplifies to the dot product.
129
+ */
130
+ export function cosineSimilarity(a, b) {
131
+ let sum = 0;
132
+ const len = Math.min(a.length, b.length);
133
+ for (let i = 0; i < len; i++) {
134
+ sum += a[i] * b[i];
135
+ }
136
+ return sum;
137
+ }
138
+ /**
139
+ * Term-frequency based keyword search for fallback mode.
140
+ * Scores each item by how many query terms appear and how often.
141
+ */
142
+ export function keywordSearch(query, items, limit) {
143
+ const queryTerms = query
144
+ .toLowerCase()
145
+ .split(/\s+/)
146
+ .filter((t) => t.length > 0);
147
+ if (queryTerms.length === 0)
148
+ return [];
149
+ const results = [];
150
+ for (const item of items) {
151
+ const textLower = item.text.toLowerCase();
152
+ let score = 0;
153
+ for (const term of queryTerms) {
154
+ if (textLower.includes(term)) {
155
+ // Count occurrences with diminishing returns
156
+ const parts = textLower.split(term);
157
+ const matches = parts.length - 1;
158
+ score += Math.log(1 + matches);
159
+ }
160
+ }
161
+ // Normalize by number of query terms
162
+ const normalizedScore = score / queryTerms.length;
163
+ if (normalizedScore > 0) {
164
+ results.push({ id: item.id, score: normalizedScore });
165
+ }
166
+ }
167
+ results.sort((a, b) => b.score - a.score);
168
+ return results.slice(0, limit);
169
+ }
170
+ //# sourceMappingURL=search.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"search.js","sourceRoot":"","sources":["../../src/embeddings/search.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,28 @@
1
+ import type { BlackboardStore } from "../storage/blackboard-store.js";
2
+ import type { BlackboardEngine } from "./blackboard.js";
3
+ import type { IndexManager } from "../embeddings/index-manager.js";
4
+ export declare class Archiver {
5
+ private readonly twiningDir;
6
+ private readonly blackboardStore;
7
+ private readonly blackboardEngine;
8
+ private readonly indexManager;
9
+ constructor(twiningDir: string, blackboardStore: BlackboardStore, blackboardEngine: BlackboardEngine, indexManager: IndexManager | null);
10
+ /**
11
+ * Archive old blackboard entries.
12
+ * Decision entries are never archived (LIFE-03).
13
+ * Archived entries are moved to archive/{YYYY-MM-DD}-blackboard.jsonl.
14
+ * Optionally posts a summary finding (LIFE-02).
15
+ */
16
+ archive(options?: {
17
+ before?: string;
18
+ keep_decisions?: boolean;
19
+ summarize?: boolean;
20
+ }): Promise<{
21
+ archived_count: number;
22
+ archive_file: string;
23
+ summary?: string;
24
+ }>;
25
+ /** Build a human-readable summary of archived entries. */
26
+ private buildSummary;
27
+ }
28
+ //# sourceMappingURL=archiver.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"archiver.d.ts","sourceRoot":"","sources":["../../src/engine/archiver.ts"],"names":[],"mappings":"AAUA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,gCAAgC,CAAC;AACtE,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,iBAAiB,CAAC;AACxD,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,gCAAgC,CAAC;AAQnE,qBAAa,QAAQ;IACnB,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAS;IACpC,OAAO,CAAC,QAAQ,CAAC,eAAe,CAAkB;IAClD,OAAO,CAAC,QAAQ,CAAC,gBAAgB,CAAmB;IACpD,OAAO,CAAC,QAAQ,CAAC,YAAY,CAAsB;gBAGjD,UAAU,EAAE,MAAM,EAClB,eAAe,EAAE,eAAe,EAChC,gBAAgB,EAAE,gBAAgB,EAClC,YAAY,EAAE,YAAY,GAAG,IAAI;IAQnC;;;;;OAKG;IACG,OAAO,CAAC,OAAO,CAAC,EAAE;QACtB,MAAM,CAAC,EAAE,MAAM,CAAC;QAChB,cAAc,CAAC,EAAE,OAAO,CAAC;QACzB,SAAS,CAAC,EAAE,OAAO,CAAC;KACrB,GAAG,OAAO,CAAC;QACV,cAAc,EAAE,MAAM,CAAC;QACvB,YAAY,EAAE,MAAM,CAAC;QACrB,OAAO,CAAC,EAAE,MAAM,CAAC;KAClB,CAAC;IA0GF,0DAA0D;IAC1D,OAAO,CAAC,YAAY;CA8BrB"}