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,108 @@
1
+ /**
2
+ * Low-level file I/O with advisory locking.
3
+ * All writes use proper-lockfile for concurrent safety.
4
+ * Engine and store modules use these — never direct fs calls.
5
+ */
6
+ import fs from "node:fs";
7
+ import path from "node:path";
8
+ import lockfile from "proper-lockfile";
9
+ const LOCK_OPTIONS = {
10
+ retries: { retries: 10, factor: 1.5, minTimeout: 50, maxTimeout: 1000 },
11
+ stale: 10000,
12
+ onCompromised: (err) => {
13
+ console.error("[twining] Lock compromised:", err.message);
14
+ },
15
+ };
16
+ /** Read and parse a JSON file. Throws if file doesn't exist. */
17
+ export async function readJSON(filePath) {
18
+ const content = fs.readFileSync(filePath, "utf-8");
19
+ return JSON.parse(content);
20
+ }
21
+ /** Write JSON to file under advisory lock. */
22
+ export async function writeJSON(filePath, data) {
23
+ // Ensure parent directory exists
24
+ const dir = path.dirname(filePath);
25
+ if (!fs.existsSync(dir)) {
26
+ fs.mkdirSync(dir, { recursive: true });
27
+ }
28
+ // Ensure file exists for proper-lockfile (it locks based on file existence)
29
+ if (!fs.existsSync(filePath)) {
30
+ fs.writeFileSync(filePath, "");
31
+ }
32
+ const release = await lockfile.lock(filePath, LOCK_OPTIONS);
33
+ try {
34
+ fs.writeFileSync(filePath, JSON.stringify(data, null, 2));
35
+ }
36
+ finally {
37
+ await release();
38
+ }
39
+ }
40
+ /** Append a single JSON object as a line to a JSONL file under advisory lock. */
41
+ export async function appendJSONL(filePath, data) {
42
+ // Ensure file exists for locking
43
+ if (!fs.existsSync(filePath)) {
44
+ const dir = path.dirname(filePath);
45
+ if (!fs.existsSync(dir)) {
46
+ fs.mkdirSync(dir, { recursive: true });
47
+ }
48
+ fs.writeFileSync(filePath, "");
49
+ }
50
+ const release = await lockfile.lock(filePath, LOCK_OPTIONS);
51
+ try {
52
+ fs.appendFileSync(filePath, JSON.stringify(data) + "\n");
53
+ }
54
+ finally {
55
+ await release();
56
+ }
57
+ }
58
+ /**
59
+ * Read a JSONL file and parse each line.
60
+ * Corrupt lines are skipped with a warning to stderr.
61
+ * No locking needed for reads.
62
+ */
63
+ export async function readJSONL(filePath) {
64
+ if (!fs.existsSync(filePath))
65
+ return [];
66
+ const content = fs.readFileSync(filePath, "utf-8");
67
+ const lines = content.split("\n").filter((line) => line.trim().length > 0);
68
+ const results = [];
69
+ for (const line of lines) {
70
+ try {
71
+ results.push(JSON.parse(line));
72
+ }
73
+ catch {
74
+ console.error(`[twining] Skipping corrupt JSONL line in ${path.basename(filePath)}`);
75
+ }
76
+ }
77
+ return results;
78
+ }
79
+ /**
80
+ * Overwrite a JSONL file atomically under lock.
81
+ * Used by archiver to rewrite blackboard after removing archived entries.
82
+ */
83
+ export async function writeJSONL(filePath, data) {
84
+ // Ensure parent directory exists
85
+ const dir = path.dirname(filePath);
86
+ if (!fs.existsSync(dir)) {
87
+ fs.mkdirSync(dir, { recursive: true });
88
+ }
89
+ // Ensure file exists for proper-lockfile
90
+ if (!fs.existsSync(filePath)) {
91
+ fs.writeFileSync(filePath, "");
92
+ }
93
+ const release = await lockfile.lock(filePath, LOCK_OPTIONS);
94
+ try {
95
+ const content = data.length > 0
96
+ ? data.map((item) => JSON.stringify(item)).join("\n") + "\n"
97
+ : "";
98
+ fs.writeFileSync(filePath, content);
99
+ }
100
+ finally {
101
+ await release();
102
+ }
103
+ }
104
+ /** Ensure a directory exists, creating it recursively if needed. */
105
+ export function ensureDir(dirPath) {
106
+ fs.mkdirSync(dirPath, { recursive: true });
107
+ }
108
+ //# sourceMappingURL=file-store.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"file-store.js","sourceRoot":"","sources":["../../src/storage/file-store.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,39 @@
1
+ import type { Entity, Relation } from "../utils/types.js";
2
+ export declare class GraphStore {
3
+ private readonly entitiesPath;
4
+ private readonly relationsPath;
5
+ private readonly graphDir;
6
+ constructor(twiningDir: string);
7
+ /** Ensure graph directory and files exist. */
8
+ private ensureFiles;
9
+ /**
10
+ * Add or update an entity.
11
+ * Upsert: match by name+type. If found, merge properties and update timestamp.
12
+ * If not found, create new entity with generated ID.
13
+ */
14
+ addEntity(input: {
15
+ name: string;
16
+ type: Entity["type"];
17
+ properties?: Record<string, string>;
18
+ }): Promise<Entity>;
19
+ /**
20
+ * Add a relation between two entities.
21
+ * Source/target can be entity ID or name.
22
+ * Resolves by ID first, then by name. Throws AMBIGUOUS_ENTITY if name matches multiple.
23
+ */
24
+ addRelation(input: {
25
+ source: string;
26
+ target: string;
27
+ type: Relation["type"];
28
+ properties?: Record<string, string>;
29
+ }): Promise<Relation>;
30
+ /** Get all entities. Returns [] if file doesn't exist. */
31
+ getEntities(): Promise<Entity[]>;
32
+ /** Get all relations. Returns [] if file doesn't exist. */
33
+ getRelations(): Promise<Relation[]>;
34
+ /** Find entity by ID. Returns undefined if not found. */
35
+ getEntityById(id: string): Promise<Entity | undefined>;
36
+ /** Find entities by name, optionally filtered by type. */
37
+ getEntityByName(name: string, type?: string): Promise<Entity[]>;
38
+ }
39
+ //# sourceMappingURL=graph-store.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"graph-store.d.ts","sourceRoot":"","sources":["../../src/storage/graph-store.ts"],"names":[],"mappings":"AASA,OAAO,KAAK,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,mBAAmB,CAAC;AAS1D,qBAAa,UAAU;IACrB,OAAO,CAAC,QAAQ,CAAC,YAAY,CAAS;IACtC,OAAO,CAAC,QAAQ,CAAC,aAAa,CAAS;IACvC,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAS;gBAEtB,UAAU,EAAE,MAAM;IAM9B,8CAA8C;IAC9C,OAAO,CAAC,WAAW;IAYnB;;;;OAIG;IACG,SAAS,CAAC,KAAK,EAAE;QACrB,IAAI,EAAE,MAAM,CAAC;QACb,IAAI,EAAE,MAAM,CAAC,MAAM,CAAC,CAAC;QACrB,UAAU,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;KACrC,GAAG,OAAO,CAAC,MAAM,CAAC;IAgDnB;;;;OAIG;IACG,WAAW,CAAC,KAAK,EAAE;QACvB,MAAM,EAAE,MAAM,CAAC;QACf,MAAM,EAAE,MAAM,CAAC;QACf,IAAI,EAAE,QAAQ,CAAC,MAAM,CAAC,CAAC;QACvB,UAAU,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;KACrC,GAAG,OAAO,CAAC,QAAQ,CAAC;IA0DrB,0DAA0D;IACpD,WAAW,IAAI,OAAO,CAAC,MAAM,EAAE,CAAC;IAOtC,2DAA2D;IACrD,YAAY,IAAI,OAAO,CAAC,QAAQ,EAAE,CAAC;IAOzC,yDAAyD;IACnD,aAAa,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,GAAG,SAAS,CAAC;IAK5D,0DAA0D;IACpD,eAAe,CACnB,IAAI,EAAE,MAAM,EACZ,IAAI,CAAC,EAAE,MAAM,GACZ,OAAO,CAAC,MAAM,EAAE,CAAC;CAMrB"}
@@ -0,0 +1,143 @@
1
+ /**
2
+ * Knowledge graph storage layer.
3
+ * Manages entities (upsert semantics) and relations with entity resolution.
4
+ * Uses file-store readJSON/writeJSON with proper-lockfile for concurrent safety.
5
+ */
6
+ import fs from "node:fs";
7
+ import path from "node:path";
8
+ import lockfile from "proper-lockfile";
9
+ import { generateId } from "../utils/ids.js";
10
+ const LOCK_OPTIONS = {
11
+ retries: { retries: 10, factor: 1.5, minTimeout: 50, maxTimeout: 1000 },
12
+ stale: 10000,
13
+ };
14
+ import { TwiningError } from "../utils/errors.js";
15
+ export class GraphStore {
16
+ entitiesPath;
17
+ relationsPath;
18
+ graphDir;
19
+ constructor(twiningDir) {
20
+ this.graphDir = path.join(twiningDir, "graph");
21
+ this.entitiesPath = path.join(this.graphDir, "entities.json");
22
+ this.relationsPath = path.join(this.graphDir, "relations.json");
23
+ }
24
+ /** Ensure graph directory and files exist. */
25
+ ensureFiles() {
26
+ if (!fs.existsSync(this.graphDir)) {
27
+ fs.mkdirSync(this.graphDir, { recursive: true });
28
+ }
29
+ if (!fs.existsSync(this.entitiesPath)) {
30
+ fs.writeFileSync(this.entitiesPath, JSON.stringify([]));
31
+ }
32
+ if (!fs.existsSync(this.relationsPath)) {
33
+ fs.writeFileSync(this.relationsPath, JSON.stringify([]));
34
+ }
35
+ }
36
+ /**
37
+ * Add or update an entity.
38
+ * Upsert: match by name+type. If found, merge properties and update timestamp.
39
+ * If not found, create new entity with generated ID.
40
+ */
41
+ async addEntity(input) {
42
+ this.ensureFiles();
43
+ const release = await lockfile.lock(this.entitiesPath, LOCK_OPTIONS);
44
+ try {
45
+ const entities = JSON.parse(fs.readFileSync(this.entitiesPath, "utf-8"));
46
+ const now = new Date().toISOString();
47
+ const existing = entities.find((e) => e.name === input.name && e.type === input.type);
48
+ if (existing) {
49
+ // Upsert: merge properties and update timestamp
50
+ existing.properties = {
51
+ ...existing.properties,
52
+ ...(input.properties ?? {}),
53
+ };
54
+ existing.updated_at = now;
55
+ fs.writeFileSync(this.entitiesPath, JSON.stringify(entities, null, 2));
56
+ return existing;
57
+ }
58
+ // Create new
59
+ const entity = {
60
+ id: generateId(),
61
+ name: input.name,
62
+ type: input.type,
63
+ properties: input.properties ?? {},
64
+ created_at: now,
65
+ updated_at: now,
66
+ };
67
+ entities.push(entity);
68
+ fs.writeFileSync(this.entitiesPath, JSON.stringify(entities, null, 2));
69
+ return entity;
70
+ }
71
+ finally {
72
+ await release();
73
+ }
74
+ }
75
+ /**
76
+ * Add a relation between two entities.
77
+ * Source/target can be entity ID or name.
78
+ * Resolves by ID first, then by name. Throws AMBIGUOUS_ENTITY if name matches multiple.
79
+ */
80
+ async addRelation(input) {
81
+ this.ensureFiles();
82
+ const entities = JSON.parse(fs.readFileSync(this.entitiesPath, "utf-8"));
83
+ const resolveEntity = (ref) => {
84
+ // Try by ID first
85
+ const byId = entities.find((e) => e.id === ref);
86
+ if (byId)
87
+ return byId;
88
+ // Try by name
89
+ const byName = entities.filter((e) => e.name === ref);
90
+ if (byName.length === 0) {
91
+ throw new TwiningError(`Entity not found: "${ref}"`, "NOT_FOUND");
92
+ }
93
+ if (byName.length > 1) {
94
+ const matches = byName.map((e) => `${e.name} (${e.type})`).join(", ");
95
+ throw new TwiningError(`Ambiguous entity name "${ref}" matches: ${matches}`, "AMBIGUOUS_ENTITY");
96
+ }
97
+ return byName[0];
98
+ };
99
+ const sourceEntity = resolveEntity(input.source);
100
+ const targetEntity = resolveEntity(input.target);
101
+ const release = await lockfile.lock(this.relationsPath, LOCK_OPTIONS);
102
+ try {
103
+ const relations = JSON.parse(fs.readFileSync(this.relationsPath, "utf-8"));
104
+ const relation = {
105
+ id: generateId(),
106
+ source: sourceEntity.id,
107
+ target: targetEntity.id,
108
+ type: input.type,
109
+ properties: input.properties ?? {},
110
+ created_at: new Date().toISOString(),
111
+ };
112
+ relations.push(relation);
113
+ fs.writeFileSync(this.relationsPath, JSON.stringify(relations, null, 2));
114
+ return relation;
115
+ }
116
+ finally {
117
+ await release();
118
+ }
119
+ }
120
+ /** Get all entities. Returns [] if file doesn't exist. */
121
+ async getEntities() {
122
+ if (!fs.existsSync(this.entitiesPath))
123
+ return [];
124
+ return JSON.parse(fs.readFileSync(this.entitiesPath, "utf-8"));
125
+ }
126
+ /** Get all relations. Returns [] if file doesn't exist. */
127
+ async getRelations() {
128
+ if (!fs.existsSync(this.relationsPath))
129
+ return [];
130
+ return JSON.parse(fs.readFileSync(this.relationsPath, "utf-8"));
131
+ }
132
+ /** Find entity by ID. Returns undefined if not found. */
133
+ async getEntityById(id) {
134
+ const entities = await this.getEntities();
135
+ return entities.find((e) => e.id === id);
136
+ }
137
+ /** Find entities by name, optionally filtered by type. */
138
+ async getEntityByName(name, type) {
139
+ const entities = await this.getEntities();
140
+ return entities.filter((e) => e.name === name && (type === undefined || e.type === type));
141
+ }
142
+ }
143
+ //# sourceMappingURL=graph-store.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"graph-store.js","sourceRoot":"","sources":["../../src/storage/graph-store.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,44 @@
1
+ import type { HandoffRecord, HandoffIndexEntry } from "../utils/types.js";
2
+ export declare class HandoffStore {
3
+ private readonly handoffsDir;
4
+ private readonly indexPath;
5
+ constructor(twiningDir: string);
6
+ /**
7
+ * Create a new handoff record.
8
+ * Writes individual JSON file, then appends to JSONL index.
9
+ */
10
+ create(input: Omit<HandoffRecord, "id" | "created_at">): Promise<HandoffRecord>;
11
+ /**
12
+ * Get a single handoff by ID.
13
+ * Returns null if file doesn't exist.
14
+ */
15
+ get(id: string): Promise<HandoffRecord | null>;
16
+ /**
17
+ * List handoff index entries with optional filtering.
18
+ * Reads from JSONL index for fast listing.
19
+ */
20
+ list(filters?: {
21
+ source_agent?: string;
22
+ target_agent?: string;
23
+ scope?: string;
24
+ since?: string;
25
+ limit?: number;
26
+ }): Promise<HandoffIndexEntry[]>;
27
+ /**
28
+ * Acknowledge a handoff.
29
+ * Updates individual file and rewrites index entry.
30
+ */
31
+ acknowledge(id: string, acknowledgedBy: string): Promise<HandoffRecord>;
32
+ /**
33
+ * Compute aggregate result_status and create a lightweight index entry.
34
+ */
35
+ private toIndexEntry;
36
+ /**
37
+ * Compute aggregate result_status from results array.
38
+ * - If no results, "completed"
39
+ * - If all same status, that status
40
+ * - If mixed, "mixed"
41
+ */
42
+ private computeResultStatus;
43
+ }
44
+ //# sourceMappingURL=handoff-store.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"handoff-store.d.ts","sourceRoot":"","sources":["../../src/storage/handoff-store.ts"],"names":[],"mappings":"AAeA,OAAO,KAAK,EACV,aAAa,EAEb,iBAAiB,EAClB,MAAM,mBAAmB,CAAC;AAE3B,qBAAa,YAAY;IACvB,OAAO,CAAC,QAAQ,CAAC,WAAW,CAAS;IACrC,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAS;gBAEvB,UAAU,EAAE,MAAM;IAK9B;;;OAGG;IACG,MAAM,CACV,KAAK,EAAE,IAAI,CAAC,aAAa,EAAE,IAAI,GAAG,YAAY,CAAC,GAC9C,OAAO,CAAC,aAAa,CAAC;IAoBzB;;;OAGG;IACG,GAAG,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,aAAa,GAAG,IAAI,CAAC;IAUpD;;;OAGG;IACG,IAAI,CACR,OAAO,CAAC,EAAE;QACR,YAAY,CAAC,EAAE,MAAM,CAAC;QACtB,YAAY,CAAC,EAAE,MAAM,CAAC;QACtB,KAAK,CAAC,EAAE,MAAM,CAAC;QACf,KAAK,CAAC,EAAE,MAAM,CAAC;QACf,KAAK,CAAC,EAAE,MAAM,CAAC;KAChB,GACA,OAAO,CAAC,iBAAiB,EAAE,CAAC;IAuC/B;;;OAGG;IACG,WAAW,CACf,EAAE,EAAE,MAAM,EACV,cAAc,EAAE,MAAM,GACrB,OAAO,CAAC,aAAa,CAAC;IA0BzB;;OAEG;IACH,OAAO,CAAC,YAAY;IAcpB;;;;;OAKG;IACH,OAAO,CAAC,mBAAmB;CAW5B"}
@@ -0,0 +1,136 @@
1
+ /**
2
+ * Handoff CRUD operations.
3
+ * Individual JSON files per handoff with a JSONL index for fast listing.
4
+ * Follows DecisionStore's individual-file pattern.
5
+ */
6
+ import path from "node:path";
7
+ import { readJSON, writeJSON, appendJSONL, readJSONL, writeJSONL, ensureDir, } from "./file-store.js";
8
+ import { generateId } from "../utils/ids.js";
9
+ export class HandoffStore {
10
+ handoffsDir;
11
+ indexPath;
12
+ constructor(twiningDir) {
13
+ this.handoffsDir = path.join(twiningDir, "handoffs");
14
+ this.indexPath = path.join(this.handoffsDir, "index.jsonl");
15
+ }
16
+ /**
17
+ * Create a new handoff record.
18
+ * Writes individual JSON file, then appends to JSONL index.
19
+ */
20
+ async create(input) {
21
+ ensureDir(this.handoffsDir);
22
+ const record = {
23
+ ...input,
24
+ id: generateId(),
25
+ created_at: new Date().toISOString(),
26
+ };
27
+ // File-first: write individual file before updating index
28
+ const filePath = path.join(this.handoffsDir, `${record.id}.json`);
29
+ await writeJSON(filePath, record);
30
+ // Append lightweight index entry
31
+ const indexEntry = this.toIndexEntry(record);
32
+ await appendJSONL(this.indexPath, indexEntry);
33
+ return record;
34
+ }
35
+ /**
36
+ * Get a single handoff by ID.
37
+ * Returns null if file doesn't exist.
38
+ */
39
+ async get(id) {
40
+ const filePath = path.join(this.handoffsDir, `${id}.json`);
41
+ try {
42
+ return await readJSON(filePath);
43
+ }
44
+ catch {
45
+ // ENOENT or other error — return null
46
+ return null;
47
+ }
48
+ }
49
+ /**
50
+ * List handoff index entries with optional filtering.
51
+ * Reads from JSONL index for fast listing.
52
+ */
53
+ async list(filters) {
54
+ let entries = await readJSONL(this.indexPath);
55
+ if (filters) {
56
+ if (filters.source_agent) {
57
+ entries = entries.filter((e) => e.source_agent === filters.source_agent);
58
+ }
59
+ if (filters.target_agent) {
60
+ entries = entries.filter((e) => e.target_agent === filters.target_agent);
61
+ }
62
+ if (filters.scope) {
63
+ const filterScope = filters.scope;
64
+ entries = entries.filter((e) => e.scope?.startsWith(filterScope) ||
65
+ filterScope.startsWith(e.scope ?? ""));
66
+ }
67
+ if (filters.since) {
68
+ const since = filters.since;
69
+ entries = entries.filter((e) => e.created_at >= since);
70
+ }
71
+ }
72
+ // Sort by created_at descending (newest first)
73
+ entries.sort((a, b) => b.created_at.localeCompare(a.created_at));
74
+ // Apply limit
75
+ if (filters?.limit !== undefined) {
76
+ entries = entries.slice(0, filters.limit);
77
+ }
78
+ return entries;
79
+ }
80
+ /**
81
+ * Acknowledge a handoff.
82
+ * Updates individual file and rewrites index entry.
83
+ */
84
+ async acknowledge(id, acknowledgedBy) {
85
+ const filePath = path.join(this.handoffsDir, `${id}.json`);
86
+ let record;
87
+ try {
88
+ record = await readJSON(filePath);
89
+ }
90
+ catch {
91
+ throw new Error(`Handoff not found: ${id}`);
92
+ }
93
+ // Update acknowledgment fields
94
+ record.acknowledged_by = acknowledgedBy;
95
+ record.acknowledged_at = new Date().toISOString();
96
+ // Rewrite individual file
97
+ await writeJSON(filePath, record);
98
+ // Rewrite index with updated entry
99
+ const entries = await readJSONL(this.indexPath);
100
+ const updatedEntries = entries.map((e) => e.id === id ? { ...e, acknowledged: true } : e);
101
+ await writeJSONL(this.indexPath, updatedEntries);
102
+ return record;
103
+ }
104
+ /**
105
+ * Compute aggregate result_status and create a lightweight index entry.
106
+ */
107
+ toIndexEntry(record) {
108
+ const resultStatus = this.computeResultStatus(record.results);
109
+ return {
110
+ id: record.id,
111
+ created_at: record.created_at,
112
+ source_agent: record.source_agent,
113
+ target_agent: record.target_agent,
114
+ scope: record.scope,
115
+ summary: record.summary,
116
+ result_status: resultStatus,
117
+ acknowledged: false,
118
+ };
119
+ }
120
+ /**
121
+ * Compute aggregate result_status from results array.
122
+ * - If no results, "completed"
123
+ * - If all same status, that status
124
+ * - If mixed, "mixed"
125
+ */
126
+ computeResultStatus(results) {
127
+ if (results.length === 0)
128
+ return "completed";
129
+ const statuses = new Set(results.map((r) => r.status));
130
+ if (statuses.size === 1) {
131
+ return results[0].status;
132
+ }
133
+ return "mixed";
134
+ }
135
+ }
136
+ //# sourceMappingURL=handoff-store.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"handoff-store.js","sourceRoot":"","sources":["../../src/storage/handoff-store.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,10 @@
1
+ /**
2
+ * Create the .twining/ directory structure if it doesn't exist.
3
+ * Silent auto-create per user decision — no user interaction.
4
+ */
5
+ export declare function initTwiningDir(projectRoot: string): void;
6
+ /**
7
+ * Ensure .twining/ is initialized. Returns the .twining/ path.
8
+ */
9
+ export declare function ensureInitialized(projectRoot: string): string;
10
+ //# sourceMappingURL=init.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"init.d.ts","sourceRoot":"","sources":["../../src/storage/init.ts"],"names":[],"mappings":"AASA;;;GAGG;AACH,wBAAgB,cAAc,CAAC,WAAW,EAAE,MAAM,GAAG,IAAI,CA8CxD;AAED;;GAEG;AACH,wBAAgB,iBAAiB,CAAC,WAAW,EAAE,MAAM,GAAG,MAAM,CAG7D"}
@@ -0,0 +1,48 @@
1
+ /**
2
+ * Directory initialization for .twining/ structure.
3
+ * Creates all directories and default files on first tool call.
4
+ */
5
+ import fs from "node:fs";
6
+ import path from "node:path";
7
+ import yaml from "js-yaml";
8
+ import { DEFAULT_CONFIG } from "../config.js";
9
+ /**
10
+ * Create the .twining/ directory structure if it doesn't exist.
11
+ * Silent auto-create per user decision — no user interaction.
12
+ */
13
+ export function initTwiningDir(projectRoot) {
14
+ const twiningDir = path.join(projectRoot, ".twining");
15
+ // If already exists, nothing to do
16
+ if (fs.existsSync(twiningDir))
17
+ return;
18
+ // Create directory structure (spec section 2.2)
19
+ fs.mkdirSync(twiningDir, { recursive: true });
20
+ fs.mkdirSync(path.join(twiningDir, "decisions"), { recursive: true });
21
+ fs.mkdirSync(path.join(twiningDir, "graph"), { recursive: true });
22
+ fs.mkdirSync(path.join(twiningDir, "embeddings"), { recursive: true });
23
+ fs.mkdirSync(path.join(twiningDir, "archive"), { recursive: true });
24
+ fs.mkdirSync(path.join(twiningDir, "agents"), { recursive: true });
25
+ fs.mkdirSync(path.join(twiningDir, "handoffs"), { recursive: true });
26
+ // Config with project name auto-detected
27
+ const config = {
28
+ ...DEFAULT_CONFIG,
29
+ project_name: path.basename(projectRoot),
30
+ };
31
+ fs.writeFileSync(path.join(twiningDir, "config.yml"), yaml.dump(config));
32
+ // Empty data files
33
+ fs.writeFileSync(path.join(twiningDir, "blackboard.jsonl"), "");
34
+ fs.writeFileSync(path.join(twiningDir, "decisions", "index.json"), JSON.stringify([], null, 2));
35
+ fs.writeFileSync(path.join(twiningDir, "graph", "entities.json"), JSON.stringify([], null, 2));
36
+ fs.writeFileSync(path.join(twiningDir, "graph", "relations.json"), JSON.stringify([], null, 2));
37
+ fs.writeFileSync(path.join(twiningDir, "agents", "registry.json"), JSON.stringify([], null, 2));
38
+ // Gitignore (spec section 2.3 + model cache)
39
+ fs.writeFileSync(path.join(twiningDir, ".gitignore"), "embeddings/*.index\narchive/\nmodels/\n");
40
+ }
41
+ /**
42
+ * Ensure .twining/ is initialized. Returns the .twining/ path.
43
+ */
44
+ export function ensureInitialized(projectRoot) {
45
+ initTwiningDir(projectRoot);
46
+ return path.join(projectRoot, ".twining");
47
+ }
48
+ //# sourceMappingURL=init.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"init.js","sourceRoot":"","sources":["../../src/storage/init.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,4 @@
1
+ import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
2
+ import type { BlackboardEngine } from "../engine/blackboard.js";
3
+ export declare function registerBlackboardTools(server: McpServer, engine: BlackboardEngine): void;
4
+ //# sourceMappingURL=blackboard-tools.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"blackboard-tools.d.ts","sourceRoot":"","sources":["../../src/tools/blackboard-tools.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AACzE,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,yBAAyB,CAAC;AAIhE,wBAAgB,uBAAuB,CACrC,MAAM,EAAE,SAAS,EACjB,MAAM,EAAE,gBAAgB,GACvB,IAAI,CAyJN"}