@zabaca/lattice 1.0.9 → 1.0.10

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 (3) hide show
  1. package/dist/main.js +18 -34
  2. package/package.json +1 -1
  3. package/dist/cli.js +0 -3035
package/dist/cli.js DELETED
@@ -1,3035 +0,0 @@
1
- #!/usr/bin/env node
2
- // @bun
3
- var __legacyDecorateClassTS = function(decorators, target, key, desc) {
4
- var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
5
- if (typeof Reflect === "object" && typeof Reflect.decorate === "function")
6
- r = Reflect.decorate(decorators, target, key, desc);
7
- else
8
- for (var i = decorators.length - 1;i >= 0; i--)
9
- if (d = decorators[i])
10
- r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
11
- return c > 3 && r && Object.defineProperty(target, key, r), r;
12
- };
13
- var __legacyMetadataTS = (k, v) => {
14
- if (typeof Reflect === "object" && typeof Reflect.metadata === "function")
15
- return Reflect.metadata(k, v);
16
- };
17
-
18
- // src/main.ts
19
- import"reflect-metadata";
20
- import { CommandFactory } from "nest-commander";
21
-
22
- // src/app.module.ts
23
- import { Module as Module5 } from "@nestjs/common";
24
- import { ConfigModule as ConfigModule2 } from "@nestjs/config";
25
-
26
- // src/graph/graph.module.ts
27
- import { Module } from "@nestjs/common";
28
-
29
- // src/graph/graph.service.ts
30
- import { Injectable, Logger } from "@nestjs/common";
31
- import { ConfigService } from "@nestjs/config";
32
- import Redis from "ioredis";
33
- class GraphService {
34
- configService;
35
- logger = new Logger(GraphService.name);
36
- redis = null;
37
- config;
38
- connecting = null;
39
- constructor(configService) {
40
- this.configService = configService;
41
- this.config = {
42
- host: this.configService.get("FALKORDB_HOST", "localhost"),
43
- port: this.configService.get("FALKORDB_PORT", 6379),
44
- graphName: this.configService.get("GRAPH_NAME", "research_knowledge")
45
- };
46
- }
47
- async onModuleDestroy() {
48
- await this.disconnect();
49
- }
50
- async ensureConnected() {
51
- if (this.redis) {
52
- return this.redis;
53
- }
54
- if (this.connecting) {
55
- await this.connecting;
56
- return this.redis;
57
- }
58
- this.connecting = this.connect();
59
- await this.connecting;
60
- this.connecting = null;
61
- return this.redis;
62
- }
63
- async connect() {
64
- try {
65
- this.redis = new Redis({
66
- host: this.config.host,
67
- port: this.config.port,
68
- maxRetriesPerRequest: 3,
69
- retryStrategy: (times) => {
70
- if (times > 3) {
71
- return null;
72
- }
73
- const delay = Math.min(times * 50, 2000);
74
- return delay;
75
- },
76
- lazyConnect: true
77
- });
78
- this.redis.on("error", (err) => {
79
- this.logger.debug(`Redis connection error: ${err.message}`);
80
- });
81
- await this.redis.ping();
82
- this.logger.log(`Connected to FalkorDB at ${this.config.host}:${this.config.port}`);
83
- } catch (error) {
84
- this.redis = null;
85
- this.logger.error(`Failed to connect to FalkorDB: ${error instanceof Error ? error.message : String(error)}`);
86
- throw error;
87
- }
88
- }
89
- async disconnect() {
90
- if (this.redis) {
91
- await this.redis.quit();
92
- this.logger.log("Disconnected from FalkorDB");
93
- }
94
- }
95
- async query(cypher, _params) {
96
- try {
97
- const redis = await this.ensureConnected();
98
- const result = await redis.call("GRAPH.QUERY", this.config.graphName, cypher);
99
- const resultArray = Array.isArray(result) ? result : [];
100
- return {
101
- resultSet: Array.isArray(resultArray[1]) ? resultArray[1] : [],
102
- stats: this.parseStats(result)
103
- };
104
- } catch (error) {
105
- this.logger.error(`Cypher query failed: ${error instanceof Error ? error.message : String(error)}`);
106
- throw error;
107
- }
108
- }
109
- async getStats() {
110
- try {
111
- const labels = await this.getLabels();
112
- const relationshipTypes = await this.getRelationshipTypes();
113
- const nodeCountResult = await this.query("MATCH (n) RETURN count(n) as count");
114
- const nodeCount = nodeCountResult.resultSet?.[0]?.[0] || 0;
115
- const edgeCountResult = await this.query("MATCH ()-[r]-() RETURN count(r) as count");
116
- const edgeCount = edgeCountResult.resultSet?.[0]?.[0] || 0;
117
- const entityCounts = {};
118
- for (const label of labels) {
119
- const result = await this.query(`MATCH (n:\`${label}\`) RETURN count(n) as count`);
120
- entityCounts[label] = result.resultSet?.[0]?.[0] || 0;
121
- }
122
- const relationshipCounts = {};
123
- for (const relType of relationshipTypes) {
124
- const result = await this.query(`MATCH ()-[r:\`${relType}\`]-() RETURN count(r) as count`);
125
- relationshipCounts[relType] = result.resultSet?.[0]?.[0] || 0;
126
- }
127
- return {
128
- nodeCount,
129
- edgeCount,
130
- labels,
131
- relationshipTypes,
132
- entityCounts,
133
- relationshipCounts
134
- };
135
- } catch (error) {
136
- this.logger.error(`Failed to get stats: ${error instanceof Error ? error.message : String(error)}`);
137
- throw error;
138
- }
139
- }
140
- async getLabels() {
141
- try {
142
- const result = await this.query("CALL db.labels() YIELD label RETURN label");
143
- return (result.resultSet || []).map((row) => row[0]);
144
- } catch (error) {
145
- this.logger.error(`Failed to get labels: ${error instanceof Error ? error.message : String(error)}`);
146
- return [];
147
- }
148
- }
149
- async getRelationshipTypes() {
150
- try {
151
- const result = await this.query("CALL db.relationshipTypes() YIELD relationshipType RETURN relationshipType");
152
- return (result.resultSet || []).map((row) => row[0]);
153
- } catch (error) {
154
- this.logger.error(`Failed to get relationship types: ${error instanceof Error ? error.message : String(error)}`);
155
- return [];
156
- }
157
- }
158
- async upsertNode(label, properties) {
159
- try {
160
- const { name, ...otherProps } = properties;
161
- if (!name) {
162
- throw new Error("Node must have a 'name' property");
163
- }
164
- const escapedName = this.escapeCypher(String(name));
165
- const escapedLabel = this.escapeCypher(label);
166
- const propAssignments = Object.entries({
167
- name,
168
- ...otherProps
169
- }).map(([key, value]) => {
170
- const escapedKey = this.escapeCypher(key);
171
- const escapedValue = this.escapeCypherValue(value);
172
- return `n.\`${escapedKey}\` = ${escapedValue}`;
173
- }).join(", ");
174
- const cypher = `MERGE (n:\`${escapedLabel}\` { name: '${escapedName}' }) ` + `SET ${propAssignments}`;
175
- await this.query(cypher);
176
- } catch (error) {
177
- this.logger.error(`Failed to upsert node: ${error instanceof Error ? error.message : String(error)}`);
178
- throw error;
179
- }
180
- }
181
- async upsertRelationship(sourceLabel, sourceName, relation, targetLabel, targetName, properties) {
182
- try {
183
- const escapedSourceLabel = this.escapeCypher(sourceLabel);
184
- const escapedSourceName = this.escapeCypher(sourceName);
185
- const escapedRelation = this.escapeCypher(relation);
186
- const escapedTargetLabel = this.escapeCypher(targetLabel);
187
- const escapedTargetName = this.escapeCypher(targetName);
188
- let relPropAssignments = "";
189
- if (properties && Object.keys(properties).length > 0) {
190
- relPropAssignments = ` SET ` + Object.entries(properties).map(([key, value]) => {
191
- const escapedKey = this.escapeCypher(key);
192
- const escapedValue = this.escapeCypherValue(value);
193
- return `r.\`${escapedKey}\` = ${escapedValue}`;
194
- }).join(", ");
195
- }
196
- const cypher = `MERGE (source:\`${escapedSourceLabel}\` { name: '${escapedSourceName}' }) ` + `MERGE (target:\`${escapedTargetLabel}\` { name: '${escapedTargetName}' }) ` + `MERGE (source)-[r:\`${escapedRelation}\`]->(target)` + relPropAssignments;
197
- await this.query(cypher);
198
- } catch (error) {
199
- this.logger.error(`Failed to upsert relationship: ${error instanceof Error ? error.message : String(error)}`);
200
- throw error;
201
- }
202
- }
203
- async deleteNode(label, name) {
204
- try {
205
- const escapedLabel = this.escapeCypher(label);
206
- const escapedName = this.escapeCypher(name);
207
- const cypher = `MATCH (n:\`${escapedLabel}\` { name: '${escapedName}' }) ` + `DETACH DELETE n`;
208
- await this.query(cypher);
209
- } catch (error) {
210
- this.logger.error(`Failed to delete node: ${error instanceof Error ? error.message : String(error)}`);
211
- throw error;
212
- }
213
- }
214
- async deleteDocumentRelationships(documentPath) {
215
- try {
216
- const escapedPath = this.escapeCypher(documentPath);
217
- const cypher = `MATCH ()-[r { documentPath: '${escapedPath}' }]-() ` + `DELETE r`;
218
- await this.query(cypher);
219
- } catch (error) {
220
- this.logger.error(`Failed to delete document relationships: ${error instanceof Error ? error.message : String(error)}`);
221
- throw error;
222
- }
223
- }
224
- async findNodesByLabel(label, limit) {
225
- try {
226
- const escapedLabel = this.escapeCypher(label);
227
- const limitClause = limit ? ` LIMIT ${limit}` : "";
228
- const cypher = `MATCH (n:\`${escapedLabel}\`) RETURN n${limitClause}`;
229
- const result = await this.query(cypher);
230
- return (result.resultSet || []).map((row) => row[0]);
231
- } catch (error) {
232
- this.logger.error(`Failed to find nodes by label: ${error instanceof Error ? error.message : String(error)}`);
233
- return [];
234
- }
235
- }
236
- async findRelationships(nodeName) {
237
- try {
238
- const escapedName = this.escapeCypher(nodeName);
239
- const cypher = `MATCH (n { name: '${escapedName}' })-[r]-(m) ` + `RETURN type(r), m.name`;
240
- const result = await this.query(cypher);
241
- return result.resultSet || [];
242
- } catch (error) {
243
- this.logger.error(`Failed to find relationships: ${error instanceof Error ? error.message : String(error)}`);
244
- return [];
245
- }
246
- }
247
- async createVectorIndex(label, property, dimensions) {
248
- try {
249
- const escapedLabel = this.escapeCypher(label);
250
- const escapedProperty = this.escapeCypher(property);
251
- const cypher = `CREATE VECTOR INDEX FOR (n:\`${escapedLabel}\`) ON (n.\`${escapedProperty}\`) OPTIONS { dimension: ${dimensions}, similarityFunction: 'cosine' }`;
252
- await this.query(cypher);
253
- this.logger.log(`Created vector index on ${label}.${property} with ${dimensions} dimensions`);
254
- } catch (error) {
255
- const errorMessage = error instanceof Error ? error.message : String(error);
256
- if (!errorMessage.includes("already indexed")) {
257
- this.logger.error(`Failed to create vector index: ${errorMessage}`);
258
- throw error;
259
- }
260
- this.logger.debug(`Vector index on ${label}.${property} already exists`);
261
- }
262
- }
263
- async updateNodeEmbedding(label, name, embedding) {
264
- try {
265
- const escapedLabel = this.escapeCypher(label);
266
- const escapedName = this.escapeCypher(name);
267
- const vectorStr = `[${embedding.join(", ")}]`;
268
- const cypher = `MATCH (n:\`${escapedLabel}\` { name: '${escapedName}' }) ` + `SET n.embedding = vecf32(${vectorStr})`;
269
- await this.query(cypher);
270
- } catch (error) {
271
- this.logger.error(`Failed to update node embedding: ${error instanceof Error ? error.message : String(error)}`);
272
- throw error;
273
- }
274
- }
275
- async vectorSearch(label, queryVector, k = 10) {
276
- try {
277
- const escapedLabel = this.escapeCypher(label);
278
- const vectorStr = `[${queryVector.join(", ")}]`;
279
- const cypher = `CALL db.idx.vector.queryNodes('${escapedLabel}', 'embedding', ${k}, vecf32(${vectorStr})) ` + `YIELD node, score ` + `RETURN node.name AS name, node.title AS title, (2 - score) / 2 AS similarity ` + `ORDER BY similarity DESC`;
280
- const result = await this.query(cypher);
281
- return (result.resultSet || []).map((row) => ({
282
- name: row[0],
283
- title: row[1],
284
- score: row[2]
285
- }));
286
- } catch (error) {
287
- this.logger.error(`Vector search failed: ${error instanceof Error ? error.message : String(error)}`);
288
- throw error;
289
- }
290
- }
291
- async vectorSearchAll(queryVector, k = 10) {
292
- const allLabels = ["Document", "Concept", "Process", "Tool", "Technology", "Organization", "Topic", "Person"];
293
- const allResults = [];
294
- for (const label of allLabels) {
295
- try {
296
- const escapedLabel = this.escapeCypher(label);
297
- const vectorStr = `[${queryVector.join(", ")}]`;
298
- const cypher = `CALL db.idx.vector.queryNodes('${escapedLabel}', 'embedding', ${k}, vecf32(${vectorStr})) ` + `YIELD node, score ` + `RETURN node.name AS name, node.title AS title, node.description AS description, (2 - score) / 2 AS similarity ` + `ORDER BY similarity DESC`;
299
- const result = await this.query(cypher);
300
- const labelResults = (result.resultSet || []).map((row) => ({
301
- name: row[0],
302
- label,
303
- title: row[1],
304
- description: row[2],
305
- score: row[3]
306
- }));
307
- allResults.push(...labelResults);
308
- } catch (error) {
309
- this.logger.debug(`Vector search for ${label} skipped: ${error instanceof Error ? error.message : String(error)}`);
310
- }
311
- }
312
- return allResults.sort((a, b) => b.score - a.score).slice(0, k);
313
- }
314
- escapeCypher(value) {
315
- return value.replace(/\\/g, "\\\\").replace(/'/g, "\\'").replace(/"/g, "\\\"");
316
- }
317
- escapeCypherValue(value) {
318
- if (value === null || value === undefined) {
319
- return "null";
320
- }
321
- if (typeof value === "string") {
322
- const escaped = this.escapeCypher(value);
323
- return `'${escaped}'`;
324
- }
325
- if (typeof value === "number" || typeof value === "boolean") {
326
- return String(value);
327
- }
328
- if (Array.isArray(value)) {
329
- return `[${value.map((v) => this.escapeCypherValue(v)).join(", ")}]`;
330
- }
331
- if (typeof value === "object") {
332
- const pairs = Object.entries(value).map(([k, v]) => `${k}: ${this.escapeCypherValue(v)}`).join(", ");
333
- return `{${pairs}}`;
334
- }
335
- return String(value);
336
- }
337
- parseStats(result) {
338
- if (!Array.isArray(result) || result.length < 3) {
339
- return;
340
- }
341
- const statsStr = result[2];
342
- if (!statsStr || typeof statsStr !== "string") {
343
- return;
344
- }
345
- const stats = {
346
- nodesCreated: 0,
347
- nodesDeleted: 0,
348
- relationshipsCreated: 0,
349
- relationshipsDeleted: 0,
350
- propertiesSet: 0
351
- };
352
- const nodeCreatedMatch = statsStr.match(/Nodes created: (\d+)/);
353
- if (nodeCreatedMatch) {
354
- stats.nodesCreated = parseInt(nodeCreatedMatch[1], 10);
355
- }
356
- const nodeDeletedMatch = statsStr.match(/Nodes deleted: (\d+)/);
357
- if (nodeDeletedMatch) {
358
- stats.nodesDeleted = parseInt(nodeDeletedMatch[1], 10);
359
- }
360
- const relCreatedMatch = statsStr.match(/Relationships created: (\d+)/);
361
- if (relCreatedMatch) {
362
- stats.relationshipsCreated = parseInt(relCreatedMatch[1], 10);
363
- }
364
- const relDeletedMatch = statsStr.match(/Relationships deleted: (\d+)/);
365
- if (relDeletedMatch) {
366
- stats.relationshipsDeleted = parseInt(relDeletedMatch[1], 10);
367
- }
368
- const propSetMatch = statsStr.match(/Properties set: (\d+)/);
369
- if (propSetMatch) {
370
- stats.propertiesSet = parseInt(propSetMatch[1], 10);
371
- }
372
- return stats;
373
- }
374
- }
375
- GraphService = __legacyDecorateClassTS([
376
- Injectable(),
377
- __legacyMetadataTS("design:paramtypes", [
378
- typeof ConfigService === "undefined" ? Object : ConfigService
379
- ])
380
- ], GraphService);
381
-
382
- // src/graph/graph.module.ts
383
- class GraphModule {
384
- }
385
- GraphModule = __legacyDecorateClassTS([
386
- Module({
387
- providers: [GraphService],
388
- exports: [GraphService]
389
- })
390
- ], GraphModule);
391
-
392
- // src/sync/sync.module.ts
393
- import { Module as Module3 } from "@nestjs/common";
394
-
395
- // src/sync/sync.service.ts
396
- import { Injectable as Injectable7, Logger as Logger6 } from "@nestjs/common";
397
-
398
- // src/sync/manifest.service.ts
399
- import { Injectable as Injectable2 } from "@nestjs/common";
400
- import { createHash } from "crypto";
401
- import { readFile, writeFile } from "fs/promises";
402
- import { existsSync } from "fs";
403
- import { resolve } from "path";
404
- function getProjectRoot() {
405
- if (process.env.PROJECT_ROOT) {
406
- return process.env.PROJECT_ROOT;
407
- }
408
- return process.cwd();
409
- }
410
-
411
- class ManifestService {
412
- manifestPath;
413
- manifest = null;
414
- constructor() {
415
- const docsPath = process.env.DOCS_PATH || "docs";
416
- this.manifestPath = resolve(getProjectRoot(), docsPath, ".sync-manifest.json");
417
- }
418
- async load() {
419
- try {
420
- if (existsSync(this.manifestPath)) {
421
- const content = await readFile(this.manifestPath, "utf-8");
422
- this.manifest = JSON.parse(content);
423
- } else {
424
- this.manifest = this.createEmptyManifest();
425
- }
426
- } catch (error) {
427
- this.manifest = this.createEmptyManifest();
428
- }
429
- return this.manifest;
430
- }
431
- async save() {
432
- if (!this.manifest) {
433
- throw new Error("Manifest not loaded. Call load() first.");
434
- }
435
- this.manifest.lastSync = new Date().toISOString();
436
- const content = JSON.stringify(this.manifest, null, 2);
437
- await writeFile(this.manifestPath, content, "utf-8");
438
- }
439
- getContentHash(content) {
440
- return createHash("sha256").update(content).digest("hex");
441
- }
442
- detectChange(path, contentHash, frontmatterHash) {
443
- if (!this.manifest) {
444
- throw new Error("Manifest not loaded. Call load() first.");
445
- }
446
- const existing = this.manifest.documents[path];
447
- if (!existing) {
448
- return "new";
449
- }
450
- if (existing.contentHash === contentHash && existing.frontmatterHash === frontmatterHash) {
451
- return "unchanged";
452
- }
453
- return "updated";
454
- }
455
- updateEntry(path, contentHash, frontmatterHash, entityCount, relationshipCount) {
456
- if (!this.manifest) {
457
- throw new Error("Manifest not loaded. Call load() first.");
458
- }
459
- this.manifest.documents[path] = {
460
- contentHash,
461
- frontmatterHash,
462
- lastSynced: new Date().toISOString(),
463
- entityCount,
464
- relationshipCount
465
- };
466
- }
467
- removeEntry(path) {
468
- if (!this.manifest) {
469
- throw new Error("Manifest not loaded. Call load() first.");
470
- }
471
- delete this.manifest.documents[path];
472
- }
473
- getTrackedPaths() {
474
- if (!this.manifest) {
475
- throw new Error("Manifest not loaded. Call load() first.");
476
- }
477
- return Object.keys(this.manifest.documents);
478
- }
479
- createEmptyManifest() {
480
- return {
481
- version: "1.0",
482
- lastSync: new Date().toISOString(),
483
- documents: {}
484
- };
485
- }
486
- }
487
- ManifestService = __legacyDecorateClassTS([
488
- Injectable2(),
489
- __legacyMetadataTS("design:paramtypes", [])
490
- ], ManifestService);
491
-
492
- // src/sync/document-parser.service.ts
493
- import { Injectable as Injectable3, Logger as Logger2 } from "@nestjs/common";
494
- import { glob } from "glob";
495
- import { readFile as readFile2 } from "fs/promises";
496
- import { createHash as createHash2 } from "crypto";
497
- import { resolve as resolve2 } from "path";
498
-
499
- // src/utils/frontmatter.ts
500
- import { z } from "zod";
501
- import matter from "gray-matter";
502
- var EntityTypeSchema = z.enum([
503
- "Topic",
504
- "Technology",
505
- "Concept",
506
- "Tool",
507
- "Process",
508
- "Person",
509
- "Organization",
510
- "Document"
511
- ]);
512
- var RelationTypeSchema = z.enum([
513
- "REFERENCES"
514
- ]);
515
- var EntitySchema = z.object({
516
- name: z.string().min(1),
517
- type: EntityTypeSchema,
518
- description: z.string().optional()
519
- });
520
- var RelationshipSchema = z.object({
521
- source: z.string().min(1),
522
- relation: RelationTypeSchema,
523
- target: z.string().min(1)
524
- });
525
- var GraphMetadataSchema = z.object({
526
- importance: z.enum(["high", "medium", "low"]).optional(),
527
- domain: z.string().optional()
528
- });
529
- var validateDateFormat = (dateStr) => {
530
- const match = dateStr.match(/^(\d{4})-(\d{2})-(\d{2})$/);
531
- if (!match)
532
- return false;
533
- const [, yearStr, monthStr, dayStr] = match;
534
- const year = parseInt(yearStr, 10);
535
- const month = parseInt(monthStr, 10);
536
- const day = parseInt(dayStr, 10);
537
- if (month < 1 || month > 12)
538
- return false;
539
- if (day < 1 || day > 31)
540
- return false;
541
- const daysInMonth = [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31];
542
- if (year % 4 === 0 && year % 100 !== 0 || year % 400 === 0) {
543
- daysInMonth[1] = 29;
544
- }
545
- return day <= daysInMonth[month - 1];
546
- };
547
- var FrontmatterSchema = z.object({
548
- created: z.string().refine(validateDateFormat, "Date must be in YYYY-MM-DD format"),
549
- updated: z.string().refine(validateDateFormat, "Date must be in YYYY-MM-DD format"),
550
- status: z.enum(["draft", "ongoing", "complete"]).optional(),
551
- topic: z.string().optional(),
552
- tags: z.array(z.string()).optional(),
553
- summary: z.string().optional(),
554
- entities: z.array(EntitySchema).optional(),
555
- relationships: z.array(RelationshipSchema).optional(),
556
- graph: GraphMetadataSchema.optional()
557
- }).passthrough();
558
- function parseFrontmatter(content) {
559
- try {
560
- const { data, content: markdown } = matter(content);
561
- if (Object.keys(data).length === 0) {
562
- return {
563
- frontmatter: null,
564
- content: markdown.trim(),
565
- raw: content
566
- };
567
- }
568
- const normalizedData = normalizeData(data);
569
- const validated = FrontmatterSchema.safeParse(normalizedData);
570
- return {
571
- frontmatter: validated.success ? validated.data : normalizedData,
572
- content: markdown.trim(),
573
- raw: content
574
- };
575
- } catch (error) {
576
- const errorMessage = error instanceof Error ? error.message : String(error);
577
- throw new Error(`YAML parsing error: ${errorMessage}`);
578
- }
579
- }
580
- function normalizeData(data) {
581
- if (data instanceof Date) {
582
- return data.toISOString().split("T")[0];
583
- }
584
- if (Array.isArray(data)) {
585
- return data.map(normalizeData);
586
- }
587
- if (data !== null && typeof data === "object") {
588
- const normalized = {};
589
- for (const [key, value] of Object.entries(data)) {
590
- normalized[key] = normalizeData(value);
591
- }
592
- return normalized;
593
- }
594
- return data;
595
- }
596
-
597
- // src/sync/document-parser.service.ts
598
- function getProjectRoot2() {
599
- if (process.env.PROJECT_ROOT) {
600
- return process.env.PROJECT_ROOT;
601
- }
602
- return process.cwd();
603
- }
604
-
605
- class DocumentParserService {
606
- logger = new Logger2(DocumentParserService.name);
607
- docsPath;
608
- constructor() {
609
- const docsPathEnv = process.env.DOCS_PATH || "docs";
610
- if (docsPathEnv.startsWith("/")) {
611
- this.docsPath = docsPathEnv;
612
- } else {
613
- this.docsPath = resolve2(getProjectRoot2(), docsPathEnv);
614
- }
615
- }
616
- getDocsPath() {
617
- return this.docsPath;
618
- }
619
- async discoverDocuments() {
620
- const pattern = `${this.docsPath}/**/*.md`;
621
- const files = await glob(pattern, {
622
- ignore: ["**/node_modules/**", "**/.git/**"]
623
- });
624
- return files.sort();
625
- }
626
- async parseDocument(filePath) {
627
- const content = await readFile2(filePath, "utf-8");
628
- const parsed = parseFrontmatter(content);
629
- const title = this.extractTitle(content, filePath);
630
- const contentHash = this.computeHash(content);
631
- const frontmatterHash = this.computeHash(JSON.stringify(parsed.frontmatter || {}));
632
- const entities = this.extractEntities(parsed.frontmatter, filePath);
633
- const relationships = this.extractRelationships(parsed.frontmatter, filePath);
634
- const graphMetadata = this.extractGraphMetadata(parsed.frontmatter);
635
- return {
636
- path: filePath,
637
- title,
638
- content: parsed.content,
639
- contentHash,
640
- frontmatterHash,
641
- summary: parsed.frontmatter?.summary,
642
- topic: parsed.frontmatter?.topic,
643
- entities,
644
- relationships,
645
- graphMetadata,
646
- tags: parsed.frontmatter?.tags || [],
647
- created: parsed.frontmatter?.created,
648
- updated: parsed.frontmatter?.updated,
649
- status: parsed.frontmatter?.status
650
- };
651
- }
652
- async parseAllDocuments() {
653
- const { docs } = await this.parseAllDocumentsWithErrors();
654
- return docs;
655
- }
656
- async parseAllDocumentsWithErrors() {
657
- const files = await this.discoverDocuments();
658
- const docs = [];
659
- const errors = [];
660
- for (const file of files) {
661
- try {
662
- const parsed = await this.parseDocument(file);
663
- docs.push(parsed);
664
- } catch (error) {
665
- const errorMsg = error instanceof Error ? error.message : String(error);
666
- errors.push({ path: file, error: errorMsg });
667
- this.logger.warn(`Failed to parse ${file}: ${error}`);
668
- }
669
- }
670
- return { docs, errors };
671
- }
672
- extractTitle(content, filePath) {
673
- const h1Match = content.match(/^#\s+(.+)$/m);
674
- if (h1Match) {
675
- return h1Match[1];
676
- }
677
- const parts = filePath.split("/");
678
- return parts[parts.length - 1].replace(".md", "");
679
- }
680
- extractEntities(frontmatter, docPath) {
681
- if (!frontmatter?.entities || !Array.isArray(frontmatter.entities)) {
682
- return [];
683
- }
684
- const validEntities = [];
685
- const errors = [];
686
- for (let i = 0;i < frontmatter.entities.length; i++) {
687
- const e = frontmatter.entities[i];
688
- const result = EntitySchema.safeParse(e);
689
- if (result.success) {
690
- validEntities.push(result.data);
691
- } else {
692
- const entityPreview = typeof e === "string" ? `"${e}"` : JSON.stringify(e);
693
- errors.push(`Entity[${i}]: ${entityPreview} - Expected object with {name, type}, got ${typeof e}`);
694
- }
695
- }
696
- if (errors.length > 0) {
697
- const errorMsg = `Invalid entity schema in ${docPath}:
698
- ${errors.join(`
699
- `)}`;
700
- throw new Error(errorMsg);
701
- }
702
- return validEntities;
703
- }
704
- extractRelationships(frontmatter, docPath) {
705
- if (!frontmatter?.relationships || !Array.isArray(frontmatter.relationships)) {
706
- return [];
707
- }
708
- const validRelationships = [];
709
- const errors = [];
710
- const validRelationTypes = RelationTypeSchema.options;
711
- for (let i = 0;i < frontmatter.relationships.length; i++) {
712
- const r = frontmatter.relationships[i];
713
- const result = RelationshipSchema.safeParse(r);
714
- if (result.success) {
715
- const rel = result.data;
716
- if (rel.source === "this") {
717
- rel.source = docPath;
718
- }
719
- if (rel.target === "this") {
720
- rel.target = docPath;
721
- }
722
- validRelationships.push(rel);
723
- } else {
724
- if (typeof r === "string") {
725
- errors.push(`Relationship[${i}]: "${r}" - Expected object with {source, relation, target}, got string`);
726
- } else if (typeof r === "object" && r !== null) {
727
- const issues = [];
728
- if (!r.source)
729
- issues.push("missing source");
730
- if (!r.target)
731
- issues.push("missing target");
732
- if (!r.relation) {
733
- issues.push("missing relation");
734
- } else if (!validRelationTypes.includes(r.relation)) {
735
- issues.push(`invalid relation "${r.relation}" (allowed: ${validRelationTypes.join(", ")})`);
736
- }
737
- errors.push(`Relationship[${i}]: ${issues.join(", ")}`);
738
- } else {
739
- errors.push(`Relationship[${i}]: Expected object, got ${typeof r}`);
740
- }
741
- }
742
- }
743
- if (errors.length > 0) {
744
- const errorMsg = `Invalid relationship schema in ${docPath}:
745
- ${errors.join(`
746
- `)}`;
747
- throw new Error(errorMsg);
748
- }
749
- return validRelationships;
750
- }
751
- extractGraphMetadata(frontmatter) {
752
- if (!frontmatter?.graph) {
753
- return;
754
- }
755
- const result = GraphMetadataSchema.safeParse(frontmatter.graph);
756
- return result.success ? result.data : undefined;
757
- }
758
- computeHash(content) {
759
- return createHash2("sha256").update(content).digest("hex");
760
- }
761
- }
762
- DocumentParserService = __legacyDecorateClassTS([
763
- Injectable3(),
764
- __legacyMetadataTS("design:paramtypes", [])
765
- ], DocumentParserService);
766
-
767
- // src/sync/cascade.service.ts
768
- import { Injectable as Injectable4, Logger as Logger3 } from "@nestjs/common";
769
- class CascadeService {
770
- graph;
771
- _parser;
772
- logger = new Logger3(CascadeService.name);
773
- constructor(graph, _parser) {
774
- this.graph = graph;
775
- this._parser = _parser;
776
- }
777
- async analyzeEntityChange(change) {
778
- let affectedDocuments = [];
779
- let summary;
780
- switch (change.trigger) {
781
- case "entity_renamed":
782
- affectedDocuments = await this.findAffectedByRename(change.entityName, change.newValue || "");
783
- summary = `Entity "${change.oldValue}" was renamed to "${change.newValue}"`;
784
- break;
785
- case "entity_deleted":
786
- affectedDocuments = await this.findAffectedByDeletion(change.entityName);
787
- summary = `Entity "${change.entityName}" was deleted`;
788
- break;
789
- case "entity_type_changed":
790
- affectedDocuments = await this.findAffectedByTypeChange(change.entityName, change.oldValue || "", change.newValue || "");
791
- summary = `Entity "${change.entityName}" type changed from "${change.oldValue}" to "${change.newValue}"`;
792
- break;
793
- case "relationship_changed":
794
- affectedDocuments = await this.findAffectedByRelationshipChange(change.entityName);
795
- summary = `Relationship involving "${change.entityName}" was changed`;
796
- break;
797
- case "document_deleted":
798
- affectedDocuments = await this.findAffectedByDocumentDeletion(change.documentPath);
799
- summary = `Document "${change.documentPath}" was deleted`;
800
- break;
801
- default:
802
- summary = `Unknown change type for entity "${change.entityName}"`;
803
- }
804
- affectedDocuments = affectedDocuments.filter((doc) => doc.path !== change.documentPath);
805
- return {
806
- trigger: change.trigger,
807
- sourceDocument: change.documentPath,
808
- affectedDocuments,
809
- summary
810
- };
811
- }
812
- detectEntityRenames(oldDoc, newDoc) {
813
- const changes = [];
814
- const oldNames = new Set(oldDoc.entities.map((e) => e.name));
815
- const newNames = new Set(newDoc.entities.map((e) => e.name));
816
- const removedEntities = oldDoc.entities.filter((e) => !newNames.has(e.name));
817
- const addedEntities = newDoc.entities.filter((e) => !oldNames.has(e.name));
818
- const removedByType = new Map;
819
- for (const entity of removedEntities) {
820
- const existing = removedByType.get(entity.type) || [];
821
- existing.push(entity);
822
- removedByType.set(entity.type, existing);
823
- }
824
- const addedByType = new Map;
825
- for (const entity of addedEntities) {
826
- const existing = addedByType.get(entity.type) || [];
827
- existing.push(entity);
828
- addedByType.set(entity.type, existing);
829
- }
830
- for (const [type, removed] of removedByType) {
831
- const added = addedByType.get(type) || [];
832
- const pairCount = Math.min(removed.length, added.length);
833
- for (let i = 0;i < pairCount; i++) {
834
- changes.push({
835
- trigger: "entity_renamed",
836
- entityName: removed[i].name,
837
- oldValue: removed[i].name,
838
- newValue: added[i].name,
839
- documentPath: oldDoc.path
840
- });
841
- }
842
- }
843
- return changes;
844
- }
845
- detectEntityDeletions(oldDoc, newDoc) {
846
- const changes = [];
847
- const newNames = new Set(newDoc.entities.map((e) => e.name));
848
- const renames = this.detectEntityRenames(oldDoc, newDoc);
849
- const renamedNames = new Set(renames.map((r) => r.oldValue));
850
- for (const entity of oldDoc.entities) {
851
- if (!newNames.has(entity.name) && !renamedNames.has(entity.name)) {
852
- changes.push({
853
- trigger: "entity_deleted",
854
- entityName: entity.name,
855
- documentPath: oldDoc.path
856
- });
857
- }
858
- }
859
- return changes;
860
- }
861
- detectEntityTypeChanges(oldDoc, newDoc) {
862
- const changes = [];
863
- const newEntityMap = new Map(newDoc.entities.map((e) => [e.name, e]));
864
- for (const oldEntity of oldDoc.entities) {
865
- const newEntity = newEntityMap.get(oldEntity.name);
866
- if (newEntity && newEntity.type !== oldEntity.type) {
867
- changes.push({
868
- trigger: "entity_type_changed",
869
- entityName: oldEntity.name,
870
- oldValue: oldEntity.type,
871
- newValue: newEntity.type,
872
- documentPath: oldDoc.path
873
- });
874
- }
875
- }
876
- return changes;
877
- }
878
- async findAffectedByRename(entityName, _newName) {
879
- try {
880
- const escapedName = this.escapeForCypher(entityName);
881
- const query = `
882
- MATCH (e {name: '${escapedName}'})-[:APPEARS_IN]->(d:Document)
883
- RETURN d.name, d.title
884
- `.trim();
885
- const result = await this.graph.query(query);
886
- return (result.resultSet || []).map((row) => ({
887
- path: row[0],
888
- reason: `References "${entityName}" in entities`,
889
- suggestedAction: "update_reference",
890
- confidence: "high",
891
- affectedEntities: [entityName]
892
- }));
893
- } catch (error) {
894
- this.logger.warn(`Failed to find documents affected by rename: ${error instanceof Error ? error.message : String(error)}`);
895
- return [];
896
- }
897
- }
898
- async findAffectedByDeletion(entityName) {
899
- try {
900
- const escapedName = this.escapeForCypher(entityName);
901
- const query = `
902
- MATCH (e {name: '${escapedName}'})-[:APPEARS_IN]->(d:Document)
903
- RETURN d.name, d.title
904
- `.trim();
905
- const result = await this.graph.query(query);
906
- return (result.resultSet || []).map((row) => ({
907
- path: row[0],
908
- reason: `References deleted entity "${entityName}"`,
909
- suggestedAction: "review_content",
910
- confidence: "high",
911
- affectedEntities: [entityName]
912
- }));
913
- } catch (error) {
914
- this.logger.warn(`Failed to find documents affected by deletion: ${error instanceof Error ? error.message : String(error)}`);
915
- return [];
916
- }
917
- }
918
- async findAffectedByTypeChange(entityName, oldType, newType) {
919
- try {
920
- const escapedName = this.escapeForCypher(entityName);
921
- const query = `
922
- MATCH (e {name: '${escapedName}'})-[:APPEARS_IN]->(d:Document)
923
- RETURN d.name, d.title
924
- `.trim();
925
- const result = await this.graph.query(query);
926
- return (result.resultSet || []).map((row) => ({
927
- path: row[0],
928
- reason: `References "${entityName}" with type "${oldType}" (now "${newType}")`,
929
- suggestedAction: "review_content",
930
- confidence: "medium",
931
- affectedEntities: [entityName]
932
- }));
933
- } catch (error) {
934
- this.logger.warn(`Failed to find documents affected by type change: ${error instanceof Error ? error.message : String(error)}`);
935
- return [];
936
- }
937
- }
938
- async findAffectedByRelationshipChange(entityName) {
939
- try {
940
- const escapedName = this.escapeForCypher(entityName);
941
- const query = `
942
- MATCH (e {name: '${escapedName}'})-[r]->(d:Document)
943
- RETURN d.name, d.title, type(r) as relType
944
- `.trim();
945
- const result = await this.graph.query(query);
946
- return (result.resultSet || []).map((row) => ({
947
- path: row[0],
948
- reason: `Has relationship with "${entityName}"`,
949
- suggestedAction: "review_content",
950
- confidence: "medium",
951
- affectedEntities: [entityName]
952
- }));
953
- } catch (error) {
954
- this.logger.warn(`Failed to find documents affected by relationship change: ${error instanceof Error ? error.message : String(error)}`);
955
- return [];
956
- }
957
- }
958
- async findAffectedByDocumentDeletion(documentPath) {
959
- try {
960
- const escapedPath = this.escapeForCypher(documentPath);
961
- const query = `
962
- MATCH (d:Document)-[r]->(deleted:Document {name: '${escapedPath}'})
963
- RETURN d.name, type(r) as relType
964
- `.trim();
965
- const result = await this.graph.query(query);
966
- return (result.resultSet || []).map((row) => ({
967
- path: row[0],
968
- reason: `Links to deleted document "${documentPath}"`,
969
- suggestedAction: "remove_reference",
970
- confidence: "high",
971
- affectedEntities: []
972
- }));
973
- } catch (error) {
974
- this.logger.warn(`Failed to find documents affected by document deletion: ${error instanceof Error ? error.message : String(error)}`);
975
- return [];
976
- }
977
- }
978
- async analyzeDocumentChange(oldDoc, newDoc) {
979
- if (!oldDoc) {
980
- return [];
981
- }
982
- const analyses = [];
983
- const renames = this.detectEntityRenames(oldDoc, newDoc);
984
- const deletions = this.detectEntityDeletions(oldDoc, newDoc);
985
- const typeChanges = this.detectEntityTypeChanges(oldDoc, newDoc);
986
- for (const change of renames) {
987
- const analysis = await this.analyzeEntityChange(change);
988
- if (analysis.affectedDocuments.length > 0) {
989
- analyses.push(analysis);
990
- }
991
- }
992
- for (const change of deletions) {
993
- const analysis = await this.analyzeEntityChange(change);
994
- if (analysis.affectedDocuments.length > 0) {
995
- analyses.push(analysis);
996
- }
997
- }
998
- for (const change of typeChanges) {
999
- const analysis = await this.analyzeEntityChange(change);
1000
- if (analysis.affectedDocuments.length > 0) {
1001
- analyses.push(analysis);
1002
- }
1003
- }
1004
- return analyses;
1005
- }
1006
- formatWarnings(analyses) {
1007
- if (analyses.length === 0) {
1008
- return "";
1009
- }
1010
- const lines = [];
1011
- lines.push(`
1012
- === Cascade Impact Detected ===
1013
- `);
1014
- for (const analysis of analyses) {
1015
- lines.push(analysis.summary);
1016
- lines.push(` Source: ${analysis.sourceDocument}`);
1017
- lines.push("");
1018
- if (analysis.affectedDocuments.length > 0) {
1019
- lines.push(` Affected documents (${analysis.affectedDocuments.length}):`);
1020
- for (const doc of analysis.affectedDocuments) {
1021
- lines.push(` [${doc.confidence}] ${doc.path}`);
1022
- lines.push(` ${doc.reason}`);
1023
- lines.push(` -> Suggested: ${this.formatSuggestedAction(doc.suggestedAction, analysis)}`);
1024
- }
1025
- }
1026
- lines.push("");
1027
- }
1028
- return lines.join(`
1029
- `);
1030
- }
1031
- formatSuggestedAction(action, analysis) {
1032
- switch (action) {
1033
- case "update_reference":
1034
- if (analysis.trigger === "entity_renamed") {
1035
- const match = analysis.summary.match(/renamed to "([^"]+)"/);
1036
- const newName = match ? match[1] : "new name";
1037
- return `Update reference to "${newName}"`;
1038
- }
1039
- return "Update reference";
1040
- case "remove_reference":
1041
- return "Remove broken reference";
1042
- case "review_content":
1043
- return "Review content for consistency";
1044
- case "add_entity":
1045
- return "Consider adding entity definition";
1046
- default:
1047
- return action;
1048
- }
1049
- }
1050
- escapeForCypher(value) {
1051
- return value.replace(/\\/g, "\\\\").replace(/'/g, "\\'").replace(/"/g, "\\\"");
1052
- }
1053
- }
1054
- CascadeService = __legacyDecorateClassTS([
1055
- Injectable4(),
1056
- __legacyMetadataTS("design:paramtypes", [
1057
- typeof GraphService === "undefined" ? Object : GraphService,
1058
- typeof DocumentParserService === "undefined" ? Object : DocumentParserService
1059
- ])
1060
- ], CascadeService);
1061
-
1062
- // src/sync/path-resolver.service.ts
1063
- import { Injectable as Injectable5, Logger as Logger4 } from "@nestjs/common";
1064
- import { resolve as resolve3, isAbsolute } from "path";
1065
- import { existsSync as existsSync2 } from "fs";
1066
- class PathResolverService {
1067
- parser;
1068
- logger = new Logger4(PathResolverService.name);
1069
- constructor(parser) {
1070
- this.parser = parser;
1071
- }
1072
- getDocsPath() {
1073
- return this.parser.getDocsPath();
1074
- }
1075
- resolveDocPath(userPath, options = {}) {
1076
- const { requireExists = true, requireInDocs = true } = options;
1077
- let resolvedPath;
1078
- if (isAbsolute(userPath)) {
1079
- resolvedPath = userPath;
1080
- } else {
1081
- const fromCwd = resolve3(process.cwd(), userPath);
1082
- const fromDocs = resolve3(this.getDocsPath(), userPath);
1083
- const docsPrefix = "docs/";
1084
- const strippedFromDocs = userPath.startsWith(docsPrefix) ? resolve3(this.getDocsPath(), userPath.slice(docsPrefix.length)) : null;
1085
- if (this.isUnderDocs(fromCwd) && existsSync2(fromCwd)) {
1086
- resolvedPath = fromCwd;
1087
- } else if (strippedFromDocs && existsSync2(strippedFromDocs)) {
1088
- resolvedPath = strippedFromDocs;
1089
- } else if (existsSync2(fromDocs)) {
1090
- resolvedPath = fromDocs;
1091
- } else if (this.isUnderDocs(fromCwd)) {
1092
- resolvedPath = fromCwd;
1093
- } else if (strippedFromDocs) {
1094
- resolvedPath = strippedFromDocs;
1095
- } else {
1096
- resolvedPath = fromDocs;
1097
- }
1098
- }
1099
- if (requireInDocs && !this.isUnderDocs(resolvedPath)) {
1100
- throw new Error(`Path "${userPath}" resolves to "${resolvedPath}" which is outside the docs directory (${this.getDocsPath()})`);
1101
- }
1102
- if (requireExists && !existsSync2(resolvedPath)) {
1103
- throw new Error(`Path "${userPath}" does not exist (resolved to: ${resolvedPath})`);
1104
- }
1105
- return resolvedPath;
1106
- }
1107
- resolveDocPaths(userPaths, options = {}) {
1108
- return userPaths.map((p) => this.resolveDocPath(p, options));
1109
- }
1110
- isUnderDocs(absolutePath) {
1111
- const docsPath = this.getDocsPath();
1112
- const normalizedPath = absolutePath.replace(/\/$/, "");
1113
- const normalizedDocs = docsPath.replace(/\/$/, "");
1114
- return normalizedPath.startsWith(normalizedDocs + "/") || normalizedPath === normalizedDocs;
1115
- }
1116
- getRelativePath(absolutePath) {
1117
- const docsPath = this.getDocsPath();
1118
- if (absolutePath.startsWith(docsPath)) {
1119
- return absolutePath.slice(docsPath.length + 1);
1120
- }
1121
- return absolutePath;
1122
- }
1123
- }
1124
- PathResolverService = __legacyDecorateClassTS([
1125
- Injectable5(),
1126
- __legacyMetadataTS("design:paramtypes", [
1127
- typeof DocumentParserService === "undefined" ? Object : DocumentParserService
1128
- ])
1129
- ], PathResolverService);
1130
-
1131
- // src/embedding/embedding.service.ts
1132
- import { Injectable as Injectable6, Logger as Logger5 } from "@nestjs/common";
1133
- import { ConfigService as ConfigService2 } from "@nestjs/config";
1134
-
1135
- // src/embedding/embedding.types.ts
1136
- var DEFAULT_EMBEDDING_CONFIG = {
1137
- provider: "voyage",
1138
- model: "voyage-3.5-lite",
1139
- dimensions: 512
1140
- };
1141
-
1142
- // src/embedding/providers/openai.provider.ts
1143
- class OpenAIEmbeddingProvider {
1144
- name = "openai";
1145
- dimensions;
1146
- model;
1147
- apiKey;
1148
- baseUrl = "https://api.openai.com/v1";
1149
- constructor(config) {
1150
- const apiKey = config?.apiKey || process.env.OPENAI_API_KEY;
1151
- if (!apiKey) {
1152
- throw new Error("OpenAI API key is required. Set OPENAI_API_KEY environment variable or pass apiKey in config.");
1153
- }
1154
- this.apiKey = apiKey;
1155
- this.model = config?.model || "text-embedding-3-small";
1156
- this.dimensions = config?.dimensions || 1536;
1157
- }
1158
- async generateEmbedding(text) {
1159
- const embeddings = await this.generateEmbeddings([text]);
1160
- return embeddings[0];
1161
- }
1162
- async generateEmbeddings(texts) {
1163
- if (!texts || texts.length === 0) {
1164
- return [];
1165
- }
1166
- try {
1167
- const response = await fetch(`${this.baseUrl}/embeddings`, {
1168
- method: "POST",
1169
- headers: {
1170
- "Content-Type": "application/json",
1171
- Authorization: `Bearer ${this.apiKey}`
1172
- },
1173
- body: JSON.stringify({
1174
- model: this.model,
1175
- input: texts,
1176
- dimensions: this.dimensions
1177
- })
1178
- });
1179
- if (!response.ok) {
1180
- const error = await response.json().catch(() => ({}));
1181
- throw new Error(`OpenAI API error: ${response.status} ${JSON.stringify(error)}`);
1182
- }
1183
- const data = await response.json();
1184
- const sortedData = data.data.sort((a, b) => a.index - b.index);
1185
- return sortedData.map((item) => item.embedding);
1186
- } catch (error) {
1187
- if (error instanceof Error) {
1188
- throw new Error(`Failed to generate embeddings: ${error.message}`);
1189
- }
1190
- throw error;
1191
- }
1192
- }
1193
- }
1194
-
1195
- // src/embedding/providers/voyage.provider.ts
1196
- class VoyageEmbeddingProvider {
1197
- name = "voyage";
1198
- dimensions;
1199
- model;
1200
- apiKey;
1201
- inputType;
1202
- baseUrl = "https://api.voyageai.com/v1";
1203
- constructor(config) {
1204
- const apiKey = config?.apiKey || process.env.VOYAGE_API_KEY;
1205
- if (!apiKey) {
1206
- throw new Error("Voyage API key is required. Set VOYAGE_API_KEY environment variable or pass apiKey in config.");
1207
- }
1208
- this.apiKey = apiKey;
1209
- this.model = config?.model || "voyage-3.5-lite";
1210
- this.dimensions = config?.dimensions || 512;
1211
- this.inputType = config?.inputType || "document";
1212
- }
1213
- async generateEmbedding(text) {
1214
- const embeddings = await this.generateEmbeddings([text]);
1215
- return embeddings[0];
1216
- }
1217
- async generateEmbeddings(texts) {
1218
- if (!texts || texts.length === 0) {
1219
- return [];
1220
- }
1221
- try {
1222
- const response = await fetch(`${this.baseUrl}/embeddings`, {
1223
- method: "POST",
1224
- headers: {
1225
- "Content-Type": "application/json",
1226
- Authorization: `Bearer ${this.apiKey}`
1227
- },
1228
- body: JSON.stringify({
1229
- model: this.model,
1230
- input: texts,
1231
- output_dimension: this.dimensions,
1232
- input_type: this.inputType
1233
- })
1234
- });
1235
- if (!response.ok) {
1236
- const error = await response.json().catch(() => ({}));
1237
- throw new Error(`Voyage API error: ${response.status} ${JSON.stringify(error)}`);
1238
- }
1239
- const data = await response.json();
1240
- const sortedData = data.data.sort((a, b) => a.index - b.index);
1241
- return sortedData.map((item) => item.embedding);
1242
- } catch (error) {
1243
- if (error instanceof Error) {
1244
- throw new Error(`Failed to generate embeddings: ${error.message}`);
1245
- }
1246
- throw error;
1247
- }
1248
- }
1249
- }
1250
-
1251
- // src/embedding/providers/mock.provider.ts
1252
- class MockEmbeddingProvider {
1253
- name = "mock";
1254
- dimensions;
1255
- constructor(dimensions = 1536) {
1256
- this.dimensions = dimensions;
1257
- }
1258
- async generateEmbedding(text) {
1259
- return this.generateDeterministicEmbedding(text);
1260
- }
1261
- async generateEmbeddings(texts) {
1262
- return Promise.all(texts.map((text) => this.generateEmbedding(text)));
1263
- }
1264
- generateDeterministicEmbedding(text) {
1265
- const embedding = new Array(this.dimensions);
1266
- let hash = 0;
1267
- for (let i = 0;i < text.length; i++) {
1268
- hash = (hash << 5) - hash + text.charCodeAt(i);
1269
- hash = hash & hash;
1270
- }
1271
- for (let i = 0;i < this.dimensions; i++) {
1272
- const seed = hash * (i + 1) ^ i * 31;
1273
- embedding[i] = (seed % 2000 - 1000) / 1000;
1274
- }
1275
- const magnitude = Math.sqrt(embedding.reduce((sum, val) => sum + val * val, 0));
1276
- return embedding.map((val) => val / magnitude);
1277
- }
1278
- }
1279
-
1280
- // src/embedding/embedding.service.ts
1281
- class EmbeddingService {
1282
- configService;
1283
- logger = new Logger5(EmbeddingService.name);
1284
- provider;
1285
- config;
1286
- constructor(configService) {
1287
- this.configService = configService;
1288
- this.config = this.loadConfig();
1289
- this.provider = this.createProvider();
1290
- this.logger.log(`Initialized embedding service with provider: ${this.provider.name}`);
1291
- }
1292
- loadConfig() {
1293
- const providerEnv = this.configService.get("EMBEDDING_PROVIDER");
1294
- const modelEnv = this.configService.get("EMBEDDING_MODEL");
1295
- const dimensionsEnv = this.configService.get("EMBEDDING_DIMENSIONS");
1296
- const provider = providerEnv ?? DEFAULT_EMBEDDING_CONFIG.provider;
1297
- let apiKey;
1298
- if (provider === "voyage") {
1299
- apiKey = this.configService.get("VOYAGE_API_KEY");
1300
- } else if (provider === "openai") {
1301
- apiKey = this.configService.get("OPENAI_API_KEY");
1302
- }
1303
- return {
1304
- provider,
1305
- apiKey,
1306
- model: modelEnv ?? DEFAULT_EMBEDDING_CONFIG.model,
1307
- dimensions: dimensionsEnv ? Number(dimensionsEnv) : DEFAULT_EMBEDDING_CONFIG.dimensions
1308
- };
1309
- }
1310
- createProvider() {
1311
- switch (this.config.provider) {
1312
- case "openai":
1313
- if (!this.config.apiKey) {
1314
- throw new Error("OPENAI_API_KEY environment variable is required for embeddings. " + "Set it in .env or use --no-embeddings to skip embedding generation.");
1315
- }
1316
- return new OpenAIEmbeddingProvider({
1317
- apiKey: this.config.apiKey,
1318
- model: this.config.model,
1319
- dimensions: this.config.dimensions
1320
- });
1321
- case "mock":
1322
- return new MockEmbeddingProvider(this.config.dimensions);
1323
- case "voyage":
1324
- if (!this.config.apiKey) {
1325
- throw new Error("VOYAGE_API_KEY environment variable is required for embeddings. " + "Set it in .env or use --no-embeddings to skip embedding generation.");
1326
- }
1327
- return new VoyageEmbeddingProvider({
1328
- apiKey: this.config.apiKey,
1329
- model: this.config.model,
1330
- dimensions: this.config.dimensions
1331
- });
1332
- case "nomic":
1333
- throw new Error(`Provider ${this.config.provider} not yet implemented. Use 'voyage', 'openai', or 'mock'.`);
1334
- default:
1335
- throw new Error(`Unknown embedding provider: ${this.config.provider}. Use 'voyage', 'openai', or 'mock'.`);
1336
- }
1337
- }
1338
- getProviderName() {
1339
- return this.provider.name;
1340
- }
1341
- getDimensions() {
1342
- return this.provider.dimensions;
1343
- }
1344
- async generateEmbedding(text) {
1345
- if (!text || text.trim().length === 0) {
1346
- throw new Error("Cannot generate embedding for empty text");
1347
- }
1348
- return this.provider.generateEmbedding(text);
1349
- }
1350
- async generateEmbeddings(texts) {
1351
- const validTexts = texts.filter((t) => t && t.trim().length > 0);
1352
- if (validTexts.length === 0) {
1353
- return [];
1354
- }
1355
- return this.provider.generateEmbeddings(validTexts);
1356
- }
1357
- isRealProvider() {
1358
- return this.provider.name !== "mock";
1359
- }
1360
- }
1361
- EmbeddingService = __legacyDecorateClassTS([
1362
- Injectable6(),
1363
- __legacyMetadataTS("design:paramtypes", [
1364
- typeof ConfigService2 === "undefined" ? Object : ConfigService2
1365
- ])
1366
- ], EmbeddingService);
1367
-
1368
- // src/sync/sync.service.ts
1369
- var ENTITY_TYPES = [
1370
- "Topic",
1371
- "Technology",
1372
- "Concept",
1373
- "Tool",
1374
- "Process",
1375
- "Person",
1376
- "Organization"
1377
- ];
1378
- function validateDocuments(docs) {
1379
- const errors = [];
1380
- const entityIndex = new Map;
1381
- for (const doc of docs) {
1382
- for (const entity of doc.entities) {
1383
- if (!entityIndex.has(entity.name)) {
1384
- entityIndex.set(entity.name, new Set);
1385
- }
1386
- entityIndex.get(entity.name).add(doc.path);
1387
- }
1388
- }
1389
- for (const doc of docs) {
1390
- for (const rel of doc.relationships) {
1391
- if (rel.source !== doc.path && !entityIndex.has(rel.source)) {
1392
- errors.push({
1393
- path: doc.path,
1394
- error: `Relationship source "${rel.source}" not found in any document`
1395
- });
1396
- }
1397
- const isDocPath = rel.target.endsWith(".md");
1398
- const isKnownEntity = entityIndex.has(rel.target);
1399
- const isSelfReference = rel.target === doc.path;
1400
- if (!isDocPath && !isKnownEntity && !isSelfReference) {
1401
- errors.push({
1402
- path: doc.path,
1403
- error: `Relationship target "${rel.target}" not found as entity`
1404
- });
1405
- }
1406
- }
1407
- }
1408
- return errors;
1409
- }
1410
-
1411
- class SyncService {
1412
- manifest;
1413
- parser;
1414
- graph;
1415
- cascade;
1416
- pathResolver;
1417
- embeddingService;
1418
- logger = new Logger6(SyncService.name);
1419
- constructor(manifest, parser, graph, cascade, pathResolver, embeddingService) {
1420
- this.manifest = manifest;
1421
- this.parser = parser;
1422
- this.graph = graph;
1423
- this.cascade = cascade;
1424
- this.pathResolver = pathResolver;
1425
- this.embeddingService = embeddingService;
1426
- }
1427
- async sync(options = {}) {
1428
- const startTime = Date.now();
1429
- const result = {
1430
- added: 0,
1431
- updated: 0,
1432
- deleted: 0,
1433
- unchanged: 0,
1434
- errors: [],
1435
- duration: 0,
1436
- changes: [],
1437
- cascadeWarnings: [],
1438
- embeddingsGenerated: 0,
1439
- entityEmbeddingsGenerated: 0
1440
- };
1441
- try {
1442
- await this.manifest.load();
1443
- if (options.force) {
1444
- if (options.paths && options.paths.length > 0) {
1445
- if (options.verbose) {
1446
- this.logger.log(`Force mode: marking ${options.paths.length} document(s) for re-sync`);
1447
- }
1448
- await this.clearManifestEntries(options.paths);
1449
- } else {
1450
- if (options.verbose) {
1451
- this.logger.log("Force mode: clearing manifest to force full re-sync");
1452
- }
1453
- await this.clearManifest();
1454
- }
1455
- }
1456
- const changes = await this.detectChanges(options.paths);
1457
- result.changes = changes;
1458
- const docsToSync = [];
1459
- const docsByPath = new Map;
1460
- for (const change of changes) {
1461
- if (change.changeType === "new" || change.changeType === "updated") {
1462
- try {
1463
- const doc = await this.parser.parseDocument(change.path);
1464
- docsToSync.push(doc);
1465
- docsByPath.set(change.path, doc);
1466
- } catch (error) {
1467
- const errorMessage = error instanceof Error ? error.message : String(error);
1468
- result.errors.push({ path: change.path, error: errorMessage });
1469
- this.logger.warn(`Failed to parse ${change.path}: ${errorMessage}`);
1470
- }
1471
- }
1472
- }
1473
- const validationErrors = validateDocuments(docsToSync);
1474
- if (validationErrors.length > 0) {
1475
- for (const err of validationErrors) {
1476
- result.errors.push(err);
1477
- this.logger.error(`Validation error in ${err.path}: ${err.error}`);
1478
- }
1479
- this.logger.error(`Sync aborted: ${validationErrors.length} validation error(s) found. Fix the errors and try again.`);
1480
- result.duration = Date.now() - startTime;
1481
- return result;
1482
- }
1483
- const uniqueEntities = this.collectUniqueEntities(docsToSync);
1484
- if (options.verbose) {
1485
- this.logger.log(`Collected ${uniqueEntities.size} unique entities from ${docsToSync.length} documents`);
1486
- }
1487
- if (options.embeddings && !options.skipEmbeddings && this.embeddingService) {
1488
- try {
1489
- const dimensions = this.embeddingService.getDimensions();
1490
- await this.graph.createVectorIndex("Document", "embedding", dimensions);
1491
- } catch (error) {
1492
- this.logger.debug(`Vector index setup for Document: ${error instanceof Error ? error.message : String(error)}`);
1493
- }
1494
- await this.createEntityVectorIndices();
1495
- }
1496
- if (!options.dryRun) {
1497
- result.entityEmbeddingsGenerated = await this.syncEntities(uniqueEntities, options);
1498
- if (options.verbose) {
1499
- this.logger.log(`Synced ${uniqueEntities.size} entities, generated ${result.entityEmbeddingsGenerated} embeddings`);
1500
- }
1501
- }
1502
- for (const change of changes) {
1503
- try {
1504
- const doc = docsByPath.get(change.path);
1505
- const cascadeWarnings = await this.processChange(change, options, doc);
1506
- result.cascadeWarnings.push(...cascadeWarnings);
1507
- switch (change.changeType) {
1508
- case "new":
1509
- result.added++;
1510
- break;
1511
- case "updated":
1512
- result.updated++;
1513
- break;
1514
- case "deleted":
1515
- result.deleted++;
1516
- break;
1517
- case "unchanged":
1518
- result.unchanged++;
1519
- break;
1520
- }
1521
- if (change.embeddingGenerated) {
1522
- result.embeddingsGenerated++;
1523
- }
1524
- } catch (error) {
1525
- const errorMessage = error instanceof Error ? error.message : String(error);
1526
- result.errors.push({ path: change.path, error: errorMessage });
1527
- this.logger.warn(`Error processing ${change.path}: ${errorMessage}`);
1528
- }
1529
- }
1530
- if (!options.dryRun) {
1531
- await this.manifest.save();
1532
- }
1533
- } catch (error) {
1534
- const errorMessage = error instanceof Error ? error.message : String(error);
1535
- this.logger.error(`Sync failed: ${errorMessage}`);
1536
- result.errors.push({ path: "sync", error: errorMessage });
1537
- }
1538
- result.duration = Date.now() - startTime;
1539
- return result;
1540
- }
1541
- async detectChanges(paths) {
1542
- const changes = [];
1543
- let allDocPaths = await this.parser.discoverDocuments();
1544
- if (paths && paths.length > 0) {
1545
- const normalizedPaths = this.pathResolver.resolveDocPaths(paths, {
1546
- requireExists: true,
1547
- requireInDocs: true
1548
- });
1549
- const pathSet = new Set(normalizedPaths);
1550
- allDocPaths = allDocPaths.filter((p) => pathSet.has(p));
1551
- }
1552
- const trackedPaths = new Set(this.manifest.getTrackedPaths());
1553
- for (const docPath of allDocPaths) {
1554
- try {
1555
- const doc = await this.parser.parseDocument(docPath);
1556
- const changeType = this.manifest.detectChange(docPath, doc.contentHash, doc.frontmatterHash);
1557
- changes.push({
1558
- path: docPath,
1559
- changeType,
1560
- reason: this.getChangeReason(changeType)
1561
- });
1562
- trackedPaths.delete(docPath);
1563
- } catch (error) {
1564
- const errorMessage = error instanceof Error ? error.message : String(error);
1565
- this.logger.warn(`Failed to parse ${docPath}: ${errorMessage}`);
1566
- changes.push({
1567
- path: docPath,
1568
- changeType: "new",
1569
- reason: `Parse error: ${errorMessage}`
1570
- });
1571
- trackedPaths.delete(docPath);
1572
- }
1573
- }
1574
- if (!paths || paths.length === 0) {
1575
- for (const deletedPath of trackedPaths) {
1576
- changes.push({
1577
- path: deletedPath,
1578
- changeType: "deleted",
1579
- reason: "File no longer exists"
1580
- });
1581
- }
1582
- }
1583
- return changes;
1584
- }
1585
- async syncDocument(doc, options = {}, skipEntityCreation = false) {
1586
- await this.graph.deleteDocumentRelationships(doc.path);
1587
- const documentProps = {
1588
- name: doc.path,
1589
- title: doc.title,
1590
- contentHash: doc.contentHash,
1591
- tags: doc.tags
1592
- };
1593
- if (doc.summary)
1594
- documentProps.summary = doc.summary;
1595
- if (doc.created)
1596
- documentProps.created = doc.created;
1597
- if (doc.updated)
1598
- documentProps.updated = doc.updated;
1599
- if (doc.status)
1600
- documentProps.status = doc.status;
1601
- if (doc.graphMetadata) {
1602
- if (doc.graphMetadata.importance) {
1603
- documentProps.importance = doc.graphMetadata.importance;
1604
- }
1605
- if (doc.graphMetadata.domain) {
1606
- documentProps.domain = doc.graphMetadata.domain;
1607
- }
1608
- }
1609
- await this.graph.upsertNode("Document", documentProps);
1610
- let embeddingGenerated = false;
1611
- if (options.embeddings && !options.skipEmbeddings && this.embeddingService) {
1612
- try {
1613
- const textForEmbedding = this.composeEmbeddingText(doc);
1614
- if (textForEmbedding.trim()) {
1615
- const embedding = await this.embeddingService.generateEmbedding(textForEmbedding);
1616
- await this.graph.updateNodeEmbedding("Document", doc.path, embedding);
1617
- embeddingGenerated = true;
1618
- this.logger.debug(`Generated embedding for ${doc.path}`);
1619
- }
1620
- } catch (error) {
1621
- const errorMessage = error instanceof Error ? error.message : String(error);
1622
- this.logger.warn(`Failed to generate embedding for ${doc.path}: ${errorMessage}`);
1623
- }
1624
- }
1625
- const entityTypeMap = new Map;
1626
- entityTypeMap.set(doc.path, "Document");
1627
- this.logger.debug(`syncDocument: ${doc.path} has ${doc.entities.length} entities, skipEntityCreation=${skipEntityCreation}`);
1628
- for (const entity of doc.entities) {
1629
- entityTypeMap.set(entity.name, entity.type);
1630
- if (!skipEntityCreation) {
1631
- const entityProps = {
1632
- name: entity.name
1633
- };
1634
- if (entity.description) {
1635
- entityProps.description = entity.description;
1636
- }
1637
- await this.graph.upsertNode(entity.type, entityProps);
1638
- }
1639
- this.logger.debug(`Creating APPEARS_IN: ${entity.type}:${entity.name} -> Document:${doc.path}`);
1640
- await this.graph.upsertRelationship(entity.type, entity.name, "APPEARS_IN", "Document", doc.path, { documentPath: doc.path });
1641
- }
1642
- for (const rel of doc.relationships) {
1643
- if (rel.source === "this") {
1644
- const targetType2 = entityTypeMap.get(rel.target);
1645
- if (!targetType2) {
1646
- this.logger.warn(`Unknown target entity "${rel.target}" in relationship, document: ${doc.path}`);
1647
- continue;
1648
- }
1649
- await this.graph.upsertRelationship("Document", doc.path, rel.relation, targetType2, rel.target, { documentPath: doc.path });
1650
- continue;
1651
- }
1652
- if (rel.target.endsWith(".md")) {
1653
- const sourceType2 = entityTypeMap.get(rel.source);
1654
- if (!sourceType2) {
1655
- this.logger.warn(`Unknown source entity "${rel.source}" in relationship, document: ${doc.path}`);
1656
- continue;
1657
- }
1658
- await this.graph.upsertRelationship(sourceType2, rel.source, rel.relation, "Document", rel.target, { documentPath: doc.path });
1659
- continue;
1660
- }
1661
- const sourceType = entityTypeMap.get(rel.source);
1662
- const targetType = entityTypeMap.get(rel.target);
1663
- if (!sourceType) {
1664
- this.logger.warn(`Unknown source entity "${rel.source}" in relationship, document: ${doc.path}`);
1665
- continue;
1666
- }
1667
- if (!targetType) {
1668
- this.logger.warn(`Unknown target entity "${rel.target}" in relationship, document: ${doc.path}`);
1669
- continue;
1670
- }
1671
- await this.graph.upsertRelationship(sourceType, rel.source, rel.relation, targetType, rel.target, { documentPath: doc.path });
1672
- }
1673
- return embeddingGenerated;
1674
- }
1675
- async removeDocument(path) {
1676
- await this.graph.deleteDocumentRelationships(path);
1677
- await this.graph.deleteNode("Document", path);
1678
- }
1679
- async processChange(change, options, preloadedDoc) {
1680
- const cascadeWarnings = [];
1681
- if (options.verbose) {
1682
- this.logger.log(`Processing ${change.changeType}: ${change.path}`);
1683
- }
1684
- if (options.dryRun) {
1685
- this.logger.log(`[DRY-RUN] Would ${change.changeType}: ${change.path}`);
1686
- return cascadeWarnings;
1687
- }
1688
- switch (change.changeType) {
1689
- case "new":
1690
- case "updated": {
1691
- const doc = preloadedDoc || await this.parser.parseDocument(change.path);
1692
- if (change.changeType === "updated" && !options.skipCascade) {
1693
- try {
1694
- const oldDoc = await this.getOldDocumentFromManifest(change.path);
1695
- if (oldDoc) {
1696
- const cascadeAnalyses = await this.cascade.analyzeDocumentChange(oldDoc, doc);
1697
- if (cascadeAnalyses.length > 0) {
1698
- cascadeWarnings.push(...cascadeAnalyses);
1699
- cascadeAnalyses.forEach((cascade) => {
1700
- this.logger.debug(`Cascade detected: ${cascade.trigger} in ${cascade.sourceDocument}`);
1701
- });
1702
- }
1703
- }
1704
- } catch (error) {
1705
- const errorMessage = error instanceof Error ? error.message : String(error);
1706
- this.logger.warn(`Failed to analyze cascade impacts for ${change.path}: ${errorMessage}`);
1707
- }
1708
- }
1709
- const embeddingGenerated = await this.syncDocument(doc, options, preloadedDoc !== undefined);
1710
- change.embeddingGenerated = embeddingGenerated;
1711
- this.manifest.updateEntry(doc.path, doc.contentHash, doc.frontmatterHash, doc.entities.length, doc.relationships.length);
1712
- break;
1713
- }
1714
- case "deleted": {
1715
- await this.removeDocument(change.path);
1716
- this.manifest.removeEntry(change.path);
1717
- break;
1718
- }
1719
- case "unchanged":
1720
- break;
1721
- }
1722
- return cascadeWarnings;
1723
- }
1724
- async clearGraph() {
1725
- await this.graph.query("MATCH (n) DETACH DELETE n");
1726
- this.logger.log("Graph cleared");
1727
- }
1728
- async clearManifest() {
1729
- const manifest = await this.manifest.load();
1730
- for (const path of Object.keys(manifest.documents)) {
1731
- this.manifest.removeEntry(path);
1732
- }
1733
- this.logger.log("Manifest cleared");
1734
- }
1735
- async clearManifestEntries(paths) {
1736
- const normalizedPaths = this.pathResolver.resolveDocPaths(paths, {
1737
- requireExists: true,
1738
- requireInDocs: true
1739
- });
1740
- for (const docPath of normalizedPaths) {
1741
- this.manifest.removeEntry(docPath);
1742
- this.logger.debug(`Cleared manifest entry: ${docPath}`);
1743
- }
1744
- this.logger.log(`Marked ${normalizedPaths.length} document(s) for re-sync`);
1745
- }
1746
- composeEmbeddingText(doc) {
1747
- const parts = [];
1748
- if (doc.title) {
1749
- parts.push(`Title: ${doc.title}`);
1750
- }
1751
- if (doc.topic) {
1752
- parts.push(`Topic: ${doc.topic}`);
1753
- }
1754
- if (doc.tags && doc.tags.length > 0) {
1755
- parts.push(`Tags: ${doc.tags.join(", ")}`);
1756
- }
1757
- if (doc.entities && doc.entities.length > 0) {
1758
- const entityNames = doc.entities.map((e) => e.name).join(", ");
1759
- parts.push(`Entities: ${entityNames}`);
1760
- }
1761
- if (doc.summary) {
1762
- parts.push(doc.summary);
1763
- } else {
1764
- parts.push(doc.content.slice(0, 500));
1765
- }
1766
- return parts.join(" | ");
1767
- }
1768
- getChangeReason(changeType) {
1769
- switch (changeType) {
1770
- case "new":
1771
- return "New document";
1772
- case "updated":
1773
- return "Content or frontmatter changed";
1774
- case "deleted":
1775
- return "File no longer exists";
1776
- case "unchanged":
1777
- return "No changes detected";
1778
- }
1779
- }
1780
- async getOldDocumentFromManifest(path) {
1781
- try {
1782
- const manifest = await this.manifest.load();
1783
- const entry = manifest.documents[path];
1784
- if (!entry) {
1785
- return null;
1786
- }
1787
- try {
1788
- return await this.parser.parseDocument(path);
1789
- } catch {
1790
- return null;
1791
- }
1792
- } catch (error) {
1793
- this.logger.warn(`Failed to retrieve old document for ${path}: ${error instanceof Error ? error.message : String(error)}`);
1794
- return null;
1795
- }
1796
- }
1797
- collectUniqueEntities(docs) {
1798
- const entities = new Map;
1799
- for (const doc of docs) {
1800
- for (const entity of doc.entities) {
1801
- const key = `${entity.type}:${entity.name}`;
1802
- if (!entities.has(key)) {
1803
- entities.set(key, {
1804
- type: entity.type,
1805
- name: entity.name,
1806
- description: entity.description,
1807
- documentPaths: [doc.path]
1808
- });
1809
- } else {
1810
- const existing = entities.get(key);
1811
- existing.documentPaths.push(doc.path);
1812
- if (entity.description && (!existing.description || entity.description.length > existing.description.length)) {
1813
- existing.description = entity.description;
1814
- }
1815
- }
1816
- }
1817
- }
1818
- return entities;
1819
- }
1820
- async syncEntities(entities, options) {
1821
- let embeddingsGenerated = 0;
1822
- for (const [_key, entity] of entities) {
1823
- const entityProps = {
1824
- name: entity.name
1825
- };
1826
- if (entity.description) {
1827
- entityProps.description = entity.description;
1828
- }
1829
- await this.graph.upsertNode(entity.type, entityProps);
1830
- if (options.embeddings && !options.skipEmbeddings && this.embeddingService) {
1831
- try {
1832
- const text = this.composeEntityEmbeddingText(entity);
1833
- const embedding = await this.embeddingService.generateEmbedding(text);
1834
- await this.graph.updateNodeEmbedding(entity.type, entity.name, embedding);
1835
- embeddingsGenerated++;
1836
- this.logger.debug(`Generated embedding for ${entity.type}:${entity.name}`);
1837
- } catch (error) {
1838
- const errorMessage = error instanceof Error ? error.message : String(error);
1839
- this.logger.warn(`Failed to generate embedding for ${entity.type}:${entity.name}: ${errorMessage}`);
1840
- }
1841
- }
1842
- }
1843
- return embeddingsGenerated;
1844
- }
1845
- composeEntityEmbeddingText(entity) {
1846
- const parts = [`${entity.type}: ${entity.name}`];
1847
- if (entity.description) {
1848
- parts.push(entity.description);
1849
- }
1850
- return parts.join(". ");
1851
- }
1852
- async createEntityVectorIndices() {
1853
- if (!this.embeddingService)
1854
- return;
1855
- const dimensions = this.embeddingService.getDimensions();
1856
- for (const entityType of ENTITY_TYPES) {
1857
- try {
1858
- await this.graph.createVectorIndex(entityType, "embedding", dimensions);
1859
- } catch (error) {
1860
- this.logger.debug(`Vector index setup for ${entityType}: ${error instanceof Error ? error.message : String(error)}`);
1861
- }
1862
- }
1863
- }
1864
- }
1865
- SyncService = __legacyDecorateClassTS([
1866
- Injectable7(),
1867
- __legacyMetadataTS("design:paramtypes", [
1868
- typeof ManifestService === "undefined" ? Object : ManifestService,
1869
- typeof DocumentParserService === "undefined" ? Object : DocumentParserService,
1870
- typeof GraphService === "undefined" ? Object : GraphService,
1871
- typeof CascadeService === "undefined" ? Object : CascadeService,
1872
- typeof PathResolverService === "undefined" ? Object : PathResolverService,
1873
- typeof EmbeddingService === "undefined" ? Object : EmbeddingService
1874
- ])
1875
- ], SyncService);
1876
-
1877
- // src/sync/ontology.service.ts
1878
- import { Injectable as Injectable8 } from "@nestjs/common";
1879
- class OntologyService {
1880
- parser;
1881
- constructor(parser) {
1882
- this.parser = parser;
1883
- }
1884
- async deriveOntology() {
1885
- const docs = await this.parser.parseAllDocuments();
1886
- return this.deriveFromDocuments(docs);
1887
- }
1888
- deriveFromDocuments(docs) {
1889
- const entityTypeSet = new Set;
1890
- const relationshipTypeSet = new Set;
1891
- const entityCounts = {};
1892
- const relationshipCounts = {};
1893
- const entityExamples = {};
1894
- let documentsWithEntities = 0;
1895
- let documentsWithoutEntities = 0;
1896
- let totalRelationships = 0;
1897
- for (const doc of docs) {
1898
- if (doc.entities.length > 0) {
1899
- documentsWithEntities++;
1900
- } else {
1901
- documentsWithoutEntities++;
1902
- }
1903
- for (const entity of doc.entities) {
1904
- entityTypeSet.add(entity.type);
1905
- entityCounts[entity.type] = (entityCounts[entity.type] || 0) + 1;
1906
- if (!entityExamples[entity.name]) {
1907
- entityExamples[entity.name] = { type: entity.type, documents: [] };
1908
- }
1909
- if (!entityExamples[entity.name].documents.includes(doc.path)) {
1910
- entityExamples[entity.name].documents.push(doc.path);
1911
- }
1912
- }
1913
- for (const rel of doc.relationships) {
1914
- relationshipTypeSet.add(rel.relation);
1915
- relationshipCounts[rel.relation] = (relationshipCounts[rel.relation] || 0) + 1;
1916
- totalRelationships++;
1917
- }
1918
- }
1919
- return {
1920
- entityTypes: Array.from(entityTypeSet).sort(),
1921
- relationshipTypes: Array.from(relationshipTypeSet).sort(),
1922
- entityCounts,
1923
- relationshipCounts,
1924
- totalEntities: Object.keys(entityExamples).length,
1925
- totalRelationships,
1926
- documentsWithEntities,
1927
- documentsWithoutEntities,
1928
- entityExamples
1929
- };
1930
- }
1931
- printSummary(ontology) {
1932
- console.log(`
1933
- Derived Ontology Summary
1934
- `);
1935
- console.log(`Documents: ${ontology.documentsWithEntities} with entities, ${ontology.documentsWithoutEntities} without`);
1936
- console.log(`Unique Entities: ${ontology.totalEntities}`);
1937
- console.log(`Total Relationships: ${ontology.totalRelationships}`);
1938
- console.log(`
1939
- Entity Types:`);
1940
- for (const type of ontology.entityTypes) {
1941
- console.log(` ${type}: ${ontology.entityCounts[type]} instances`);
1942
- }
1943
- console.log(`
1944
- Relationship Types:`);
1945
- for (const type of ontology.relationshipTypes) {
1946
- console.log(` ${type}: ${ontology.relationshipCounts[type]} instances`);
1947
- }
1948
- console.log(`
1949
- Top Entities (by document count):`);
1950
- const sorted = Object.entries(ontology.entityExamples).sort((a, b) => b[1].documents.length - a[1].documents.length).slice(0, 10);
1951
- for (const [name, info] of sorted) {
1952
- console.log(` ${name} (${info.type}): ${info.documents.length} docs`);
1953
- }
1954
- }
1955
- }
1956
- OntologyService = __legacyDecorateClassTS([
1957
- Injectable8(),
1958
- __legacyMetadataTS("design:paramtypes", [
1959
- typeof DocumentParserService === "undefined" ? Object : DocumentParserService
1960
- ])
1961
- ], OntologyService);
1962
-
1963
- // src/embedding/embedding.module.ts
1964
- import { Module as Module2 } from "@nestjs/common";
1965
- import { ConfigModule } from "@nestjs/config";
1966
- class EmbeddingModule {
1967
- }
1968
- EmbeddingModule = __legacyDecorateClassTS([
1969
- Module2({
1970
- imports: [ConfigModule],
1971
- providers: [EmbeddingService],
1972
- exports: [EmbeddingService]
1973
- })
1974
- ], EmbeddingModule);
1975
-
1976
- // src/sync/sync.module.ts
1977
- class SyncModule {
1978
- }
1979
- SyncModule = __legacyDecorateClassTS([
1980
- Module3({
1981
- imports: [GraphModule, EmbeddingModule],
1982
- providers: [
1983
- SyncService,
1984
- ManifestService,
1985
- DocumentParserService,
1986
- OntologyService,
1987
- CascadeService,
1988
- PathResolverService
1989
- ],
1990
- exports: [
1991
- SyncService,
1992
- ManifestService,
1993
- DocumentParserService,
1994
- OntologyService,
1995
- CascadeService,
1996
- PathResolverService
1997
- ]
1998
- })
1999
- ], SyncModule);
2000
-
2001
- // src/query/query.module.ts
2002
- import { Module as Module4 } from "@nestjs/common";
2003
-
2004
- // src/query/query.service.ts
2005
- import { Injectable as Injectable9, Logger as Logger7 } from "@nestjs/common";
2006
- class QueryService {
2007
- graphService;
2008
- logger = new Logger7(QueryService.name);
2009
- constructor(graphService) {
2010
- this.graphService = graphService;
2011
- }
2012
- async query(cypher) {
2013
- this.logger.debug(`Executing query: ${cypher}`);
2014
- return await this.graphService.query(cypher);
2015
- }
2016
- }
2017
- QueryService = __legacyDecorateClassTS([
2018
- Injectable9(),
2019
- __legacyMetadataTS("design:paramtypes", [
2020
- typeof GraphService === "undefined" ? Object : GraphService
2021
- ])
2022
- ], QueryService);
2023
-
2024
- // src/query/query.module.ts
2025
- class QueryModule {
2026
- }
2027
- QueryModule = __legacyDecorateClassTS([
2028
- Module4({
2029
- imports: [GraphModule, EmbeddingModule],
2030
- providers: [QueryService, GraphService],
2031
- exports: [QueryService, GraphService]
2032
- })
2033
- ], QueryModule);
2034
-
2035
- // src/commands/sync.command.ts
2036
- import { Injectable as Injectable10 } from "@nestjs/common";
2037
- import { Command, CommandRunner, Option } from "nest-commander";
2038
- import { watch } from "fs";
2039
- import { join } from "path";
2040
- class SyncCommand extends CommandRunner {
2041
- syncService;
2042
- watcher = null;
2043
- isShuttingDown = false;
2044
- constructor(syncService) {
2045
- super();
2046
- this.syncService = syncService;
2047
- }
2048
- async run(paths, options) {
2049
- if (options.watch && options.dryRun) {
2050
- console.log(`
2051
- \u26A0\uFE0F Watch mode is not compatible with --dry-run mode
2052
- `);
2053
- process.exit(1);
2054
- }
2055
- if (options.watch && options.force) {
2056
- console.log(`
2057
- \u26A0\uFE0F Watch mode is not compatible with --force mode (for safety)
2058
- `);
2059
- process.exit(1);
2060
- }
2061
- const syncOptions = {
2062
- force: options.force,
2063
- dryRun: options.dryRun || options.diff,
2064
- verbose: options.verbose,
2065
- paths: paths.length > 0 ? paths : undefined,
2066
- skipCascade: options.skipCascade,
2067
- embeddings: options.embeddings !== false
2068
- };
2069
- console.log(`
2070
- \uD83D\uDD04 Graph Sync
2071
- `);
2072
- if (syncOptions.force) {
2073
- if (syncOptions.paths && syncOptions.paths.length > 0) {
2074
- console.log(`\u26A0\uFE0F Force mode: ${syncOptions.paths.length} document(s) will be cleared and re-synced
2075
- `);
2076
- } else {
2077
- console.log(`\u26A0\uFE0F Force mode: Entire graph will be cleared and rebuilt
2078
- `);
2079
- }
2080
- }
2081
- if (syncOptions.dryRun) {
2082
- console.log(`\uD83D\uDCCB Dry run mode: No changes will be applied
2083
- `);
2084
- }
2085
- if (syncOptions.skipCascade) {
2086
- console.log(`\u26A1 Cascade analysis skipped
2087
- `);
2088
- }
2089
- if (!syncOptions.embeddings) {
2090
- console.log(`\uD83D\uDEAB Embedding generation disabled
2091
- `);
2092
- }
2093
- if (syncOptions.paths) {
2094
- console.log(`\uD83D\uDCC1 Syncing specific paths: ${syncOptions.paths.join(", ")}
2095
- `);
2096
- }
2097
- try {
2098
- const initialResult = await this.syncService.sync(syncOptions);
2099
- this.printSyncResults(initialResult, options.watch);
2100
- if (options.dryRun) {
2101
- console.log("\uD83D\uDCA1 Run without --dry-run to apply changes");
2102
- }
2103
- if (options.watch) {
2104
- await this.enterWatchMode(syncOptions);
2105
- } else {
2106
- process.exit(initialResult.errors.length > 0 ? 1 : 0);
2107
- }
2108
- } catch (error) {
2109
- console.error(`
2110
- \u274C Sync failed:`, error instanceof Error ? error.message : String(error));
2111
- process.exit(1);
2112
- }
2113
- }
2114
- async enterWatchMode(syncOptions) {
2115
- const docsPath = process.env.DOCS_PATH || "docs";
2116
- let debounceTimeout = null;
2117
- const trackedFiles = new Set;
2118
- const debouncedSync = () => {
2119
- if (debounceTimeout) {
2120
- clearTimeout(debounceTimeout);
2121
- }
2122
- debounceTimeout = setTimeout(async () => {
2123
- if (this.isShuttingDown)
2124
- return;
2125
- if (trackedFiles.size === 0)
2126
- return;
2127
- try {
2128
- const changedPaths = Array.from(trackedFiles);
2129
- trackedFiles.clear();
2130
- console.log(`
2131
- \uD83D\uDCDD Changes detected (${changedPaths.length} file${changedPaths.length !== 1 ? "s" : ""})`);
2132
- const watchResult = await this.syncService.sync({
2133
- ...syncOptions,
2134
- paths: changedPaths,
2135
- dryRun: false
2136
- });
2137
- const hasChanges = watchResult.added > 0 || watchResult.updated > 0 || watchResult.deleted > 0;
2138
- if (hasChanges) {
2139
- console.log(` \u2705 Synced: +${watchResult.added} ~${watchResult.updated} -${watchResult.deleted}`);
2140
- if (watchResult.errors.length > 0) {
2141
- console.log(` \u274C Errors: ${watchResult.errors.map((e) => e.path).join(", ")}`);
2142
- }
2143
- if (watchResult.cascadeWarnings && watchResult.cascadeWarnings.length > 0) {
2144
- console.log(` \u26A0\uFE0F Cascade impacts detected: ${watchResult.cascadeWarnings.length} warning(s)`);
2145
- }
2146
- } else {
2147
- console.log(" \u23ED\uFE0F No changes detected");
2148
- }
2149
- console.log(`\u23F3 Watching for changes...
2150
- `);
2151
- } catch (error) {
2152
- console.error(` \u274C Sync failed: ${error instanceof Error ? error.message : String(error)}`);
2153
- console.log(`\u23F3 Watching for changes...
2154
- `);
2155
- }
2156
- }, 500);
2157
- };
2158
- console.log(`
2159
- \uD83D\uDC41\uFE0F Watch mode enabled
2160
- `);
2161
- this.watcher = watch(docsPath, { recursive: true }, (event, filename) => {
2162
- if (filename && filename.endsWith(".md")) {
2163
- const fullPath = join(docsPath, filename);
2164
- trackedFiles.add(fullPath);
2165
- debouncedSync();
2166
- }
2167
- });
2168
- process.on("SIGINT", () => this.shutdown());
2169
- await new Promise(() => {});
2170
- }
2171
- shutdown() {
2172
- if (this.isShuttingDown)
2173
- return;
2174
- this.isShuttingDown = true;
2175
- console.log(`
2176
-
2177
- \uD83D\uDC4B Stopping watch mode...`);
2178
- if (this.watcher) {
2179
- this.watcher.close();
2180
- }
2181
- process.exit(0);
2182
- }
2183
- printSyncResults(result, isWatchMode = false) {
2184
- console.log(`
2185
- \uD83D\uDCCA Sync Results:
2186
- `);
2187
- console.log(` \u2705 Added: ${result.added}`);
2188
- console.log(` \uD83D\uDD04 Updated: ${result.updated}`);
2189
- console.log(` \uD83D\uDDD1\uFE0F Deleted: ${result.deleted}`);
2190
- console.log(` \u23ED\uFE0F Unchanged: ${result.unchanged}`);
2191
- if (result.embeddingsGenerated > 0) {
2192
- console.log(` \uD83E\uDDE0 Embeddings: ${result.embeddingsGenerated}`);
2193
- }
2194
- console.log(` \u23F1\uFE0F Duration: ${result.duration}ms`);
2195
- if (result.errors.length > 0) {
2196
- console.log(`
2197
- \u274C Errors (${result.errors.length}):
2198
- `);
2199
- result.errors.forEach((e) => {
2200
- console.log(` ${e.path}: ${e.error}`);
2201
- });
2202
- }
2203
- if (result.changes && result.changes.length > 0) {
2204
- console.log(`
2205
- \uD83D\uDCDD Changes:
2206
- `);
2207
- result.changes.forEach((c) => {
2208
- const icon = {
2209
- new: "\u2795",
2210
- updated: "\uD83D\uDD04",
2211
- deleted: "\uD83D\uDDD1\uFE0F",
2212
- unchanged: "\u23ED\uFE0F"
2213
- }[c.changeType];
2214
- console.log(` ${icon} ${c.changeType}: ${c.path}`);
2215
- if (c.reason) {
2216
- console.log(` ${c.reason}`);
2217
- }
2218
- });
2219
- }
2220
- if (result.cascadeWarnings && result.cascadeWarnings.length > 0) {
2221
- console.log(`
2222
- \u26A0\uFE0F Cascade Impacts Detected:
2223
- `);
2224
- const warningsByTrigger = new Map;
2225
- for (const warning of result.cascadeWarnings) {
2226
- const existing = warningsByTrigger.get(warning.trigger) || [];
2227
- existing.push(warning);
2228
- warningsByTrigger.set(warning.trigger, existing);
2229
- }
2230
- for (const [trigger, warnings] of warningsByTrigger) {
2231
- const triggerLabel = trigger.split("_").map((word) => word.charAt(0).toUpperCase() + word.slice(1)).join(" ");
2232
- console.log(` \uD83D\uDCCC ${triggerLabel}
2233
- `);
2234
- for (const analysis of warnings) {
2235
- console.log(` ${analysis.summary}`);
2236
- console.log(` Source: ${analysis.sourceDocument}
2237
- `);
2238
- if (analysis.affectedDocuments.length > 0) {
2239
- for (const affected of analysis.affectedDocuments) {
2240
- const icon = affected.confidence === "high" ? "\uD83D\uDD34" : affected.confidence === "medium" ? "\uD83D\uDFE1" : "\uD83D\uDFE2";
2241
- console.log(` ${icon} [${affected.confidence.toUpperCase()}] ${affected.path}`);
2242
- console.log(` ${affected.reason}`);
2243
- const suggestedAction = affected.suggestedAction.split("_").join(" ").replace(/\b\w/g, (char) => char.toUpperCase());
2244
- console.log(` \u2192 ${suggestedAction}`);
2245
- }
2246
- } else {
2247
- console.log(` \u2139\uFE0F No directly affected documents detected`);
2248
- }
2249
- console.log();
2250
- }
2251
- }
2252
- console.log(` \uD83D\uDCA1 Run /update-related to apply suggested changes
2253
- `);
2254
- }
2255
- if (isWatchMode) {
2256
- console.log(`
2257
- \u23F3 Watching for changes... (Ctrl+C to stop)
2258
- `);
2259
- }
2260
- }
2261
- parseForce() {
2262
- return true;
2263
- }
2264
- parseDryRun() {
2265
- return true;
2266
- }
2267
- parseVerbose() {
2268
- return true;
2269
- }
2270
- parseWatch() {
2271
- return true;
2272
- }
2273
- parseDiff() {
2274
- return true;
2275
- }
2276
- parseSkipCascade() {
2277
- return true;
2278
- }
2279
- parseNoEmbeddings() {
2280
- return false;
2281
- }
2282
- }
2283
- __legacyDecorateClassTS([
2284
- Option({
2285
- flags: "-f, --force",
2286
- description: "Force re-sync: with paths, clears only those docs; without paths, rebuilds entire graph"
2287
- }),
2288
- __legacyMetadataTS("design:type", Function),
2289
- __legacyMetadataTS("design:paramtypes", []),
2290
- __legacyMetadataTS("design:returntype", Boolean)
2291
- ], SyncCommand.prototype, "parseForce", null);
2292
- __legacyDecorateClassTS([
2293
- Option({
2294
- flags: "-d, --dry-run",
2295
- description: "Show what would change without applying"
2296
- }),
2297
- __legacyMetadataTS("design:type", Function),
2298
- __legacyMetadataTS("design:paramtypes", []),
2299
- __legacyMetadataTS("design:returntype", Boolean)
2300
- ], SyncCommand.prototype, "parseDryRun", null);
2301
- __legacyDecorateClassTS([
2302
- Option({
2303
- flags: "-v, --verbose",
2304
- description: "Show detailed output"
2305
- }),
2306
- __legacyMetadataTS("design:type", Function),
2307
- __legacyMetadataTS("design:paramtypes", []),
2308
- __legacyMetadataTS("design:returntype", Boolean)
2309
- ], SyncCommand.prototype, "parseVerbose", null);
2310
- __legacyDecorateClassTS([
2311
- Option({
2312
- flags: "-w, --watch",
2313
- description: "Watch for file changes and sync automatically"
2314
- }),
2315
- __legacyMetadataTS("design:type", Function),
2316
- __legacyMetadataTS("design:paramtypes", []),
2317
- __legacyMetadataTS("design:returntype", Boolean)
2318
- ], SyncCommand.prototype, "parseWatch", null);
2319
- __legacyDecorateClassTS([
2320
- Option({
2321
- flags: "--diff",
2322
- description: "Show only changed documents (alias for --dry-run)"
2323
- }),
2324
- __legacyMetadataTS("design:type", Function),
2325
- __legacyMetadataTS("design:paramtypes", []),
2326
- __legacyMetadataTS("design:returntype", Boolean)
2327
- ], SyncCommand.prototype, "parseDiff", null);
2328
- __legacyDecorateClassTS([
2329
- Option({
2330
- flags: "--skip-cascade",
2331
- description: "Skip cascade analysis (faster for large repos)"
2332
- }),
2333
- __legacyMetadataTS("design:type", Function),
2334
- __legacyMetadataTS("design:paramtypes", []),
2335
- __legacyMetadataTS("design:returntype", Boolean)
2336
- ], SyncCommand.prototype, "parseSkipCascade", null);
2337
- __legacyDecorateClassTS([
2338
- Option({
2339
- flags: "--no-embeddings",
2340
- description: "Disable embedding generation during sync"
2341
- }),
2342
- __legacyMetadataTS("design:type", Function),
2343
- __legacyMetadataTS("design:paramtypes", []),
2344
- __legacyMetadataTS("design:returntype", Boolean)
2345
- ], SyncCommand.prototype, "parseNoEmbeddings", null);
2346
- SyncCommand = __legacyDecorateClassTS([
2347
- Injectable10(),
2348
- Command({
2349
- name: "sync",
2350
- arguments: "[paths...]",
2351
- description: "Synchronize documents to the knowledge graph"
2352
- }),
2353
- __legacyMetadataTS("design:paramtypes", [
2354
- typeof SyncService === "undefined" ? Object : SyncService
2355
- ])
2356
- ], SyncCommand);
2357
- // src/commands/status.command.ts
2358
- import { Injectable as Injectable11 } from "@nestjs/common";
2359
- import { Command as Command2, CommandRunner as CommandRunner2, Option as Option2 } from "nest-commander";
2360
- class StatusCommand extends CommandRunner2 {
2361
- syncService;
2362
- manifestService;
2363
- constructor(syncService, manifestService) {
2364
- super();
2365
- this.syncService = syncService;
2366
- this.manifestService = manifestService;
2367
- }
2368
- async run(_inputs, options) {
2369
- try {
2370
- await this.manifestService.load();
2371
- const changes = await this.syncService.detectChanges();
2372
- const newDocs = changes.filter((c) => c.changeType === "new");
2373
- const updatedDocs = changes.filter((c) => c.changeType === "updated");
2374
- const deletedDocs = changes.filter((c) => c.changeType === "deleted");
2375
- const unchangedDocs = changes.filter((c) => c.changeType === "unchanged");
2376
- const pendingCount = newDocs.length + updatedDocs.length + deletedDocs.length;
2377
- console.log(`
2378
- \uD83D\uDCCA Graph Status
2379
- `);
2380
- if (newDocs.length > 0) {
2381
- console.log(`New (${newDocs.length}):`);
2382
- newDocs.forEach((doc) => {
2383
- console.log(` + ${doc.path}`);
2384
- });
2385
- console.log();
2386
- }
2387
- if (updatedDocs.length > 0) {
2388
- console.log(`Updated (${updatedDocs.length}):`);
2389
- updatedDocs.forEach((doc) => {
2390
- console.log(` ~ ${doc.path}`);
2391
- });
2392
- console.log();
2393
- }
2394
- if (deletedDocs.length > 0) {
2395
- console.log(`Deleted (${deletedDocs.length}):`);
2396
- deletedDocs.forEach((doc) => {
2397
- console.log(` - ${doc.path}`);
2398
- });
2399
- console.log();
2400
- }
2401
- if (options.verbose && unchangedDocs.length > 0) {
2402
- console.log(`Unchanged (${unchangedDocs.length}):`);
2403
- unchangedDocs.forEach((doc) => {
2404
- console.log(` \xB7 ${doc.path}`);
2405
- });
2406
- console.log();
2407
- }
2408
- if (pendingCount === 0) {
2409
- console.log(`\u2705 All documents are in sync
2410
- `);
2411
- } else {
2412
- console.log(`Total: ${pendingCount} document(s) need syncing`);
2413
- console.log("\uD83D\uDCA1 Run `lattice sync` to apply changes\n");
2414
- }
2415
- process.exit(0);
2416
- } catch (error) {
2417
- console.error("Error:", error instanceof Error ? error.message : String(error));
2418
- process.exit(1);
2419
- }
2420
- }
2421
- parseVerbose() {
2422
- return true;
2423
- }
2424
- }
2425
- __legacyDecorateClassTS([
2426
- Option2({
2427
- flags: "-v, --verbose",
2428
- description: "Show all documents including unchanged"
2429
- }),
2430
- __legacyMetadataTS("design:type", Function),
2431
- __legacyMetadataTS("design:paramtypes", []),
2432
- __legacyMetadataTS("design:returntype", Boolean)
2433
- ], StatusCommand.prototype, "parseVerbose", null);
2434
- StatusCommand = __legacyDecorateClassTS([
2435
- Injectable11(),
2436
- Command2({
2437
- name: "status",
2438
- description: "Show documents that need syncing (new or updated)"
2439
- }),
2440
- __legacyMetadataTS("design:paramtypes", [
2441
- typeof SyncService === "undefined" ? Object : SyncService,
2442
- typeof ManifestService === "undefined" ? Object : ManifestService
2443
- ])
2444
- ], StatusCommand);
2445
- // src/commands/query.command.ts
2446
- import { Injectable as Injectable12 } from "@nestjs/common";
2447
- import { Command as Command3, CommandRunner as CommandRunner3, Option as Option3 } from "nest-commander";
2448
- class StatsCommand extends CommandRunner3 {
2449
- graphService;
2450
- constructor(graphService) {
2451
- super();
2452
- this.graphService = graphService;
2453
- }
2454
- async run() {
2455
- try {
2456
- const stats = await this.graphService.getStats();
2457
- console.log(`
2458
- === Graph Statistics ===
2459
- `);
2460
- console.log(`Total Nodes: ${stats.nodeCount}`);
2461
- console.log(`Total Relationships: ${stats.edgeCount}
2462
- `);
2463
- console.log(`Node Labels (${stats.labels.length}):`);
2464
- stats.labels.forEach((label) => {
2465
- const count = stats.entityCounts[label] || 0;
2466
- console.log(` - ${label}: ${count}`);
2467
- });
2468
- console.log(`
2469
- Relationship Types (${stats.relationshipTypes.length}):`);
2470
- stats.relationshipTypes.forEach((relType) => {
2471
- const count = stats.relationshipCounts[relType] || 0;
2472
- console.log(` - ${relType}: ${count}`);
2473
- });
2474
- console.log();
2475
- process.exit(0);
2476
- } catch (error) {
2477
- console.error("Error:", error instanceof Error ? error.message : String(error));
2478
- process.exit(1);
2479
- }
2480
- }
2481
- }
2482
- StatsCommand = __legacyDecorateClassTS([
2483
- Injectable12(),
2484
- Command3({
2485
- name: "stats",
2486
- description: "Show graph statistics"
2487
- }),
2488
- __legacyMetadataTS("design:paramtypes", [
2489
- typeof GraphService === "undefined" ? Object : GraphService
2490
- ])
2491
- ], StatsCommand);
2492
-
2493
- class SearchCommand extends CommandRunner3 {
2494
- graphService;
2495
- embeddingService;
2496
- constructor(graphService, embeddingService) {
2497
- super();
2498
- this.graphService = graphService;
2499
- this.embeddingService = embeddingService;
2500
- }
2501
- async run(inputs, options) {
2502
- const query = inputs[0];
2503
- const limit = Math.min(parseInt(options.limit || "20", 10), 100);
2504
- try {
2505
- const queryEmbedding = await this.embeddingService.generateEmbedding(query);
2506
- let results;
2507
- if (options.label) {
2508
- const labelResults = await this.graphService.vectorSearch(options.label, queryEmbedding, limit);
2509
- results = labelResults.map((r) => ({
2510
- name: r.name,
2511
- label: options.label,
2512
- title: r.title,
2513
- score: r.score
2514
- }));
2515
- } else {
2516
- results = await this.graphService.vectorSearchAll(queryEmbedding, limit);
2517
- }
2518
- const labelSuffix = options.label ? ` (${options.label})` : "";
2519
- console.log(`
2520
- === Semantic Search Results for "${query}"${labelSuffix} ===
2521
- `);
2522
- if (results.length === 0) {
2523
- console.log(`No results found.
2524
- `);
2525
- if (options.label) {
2526
- console.log(`Tip: Try without --label to search all entity types.
2527
- `);
2528
- }
2529
- process.exit(0);
2530
- }
2531
- results.forEach((result, idx) => {
2532
- console.log(`${idx + 1}. [${result.label}] ${result.name}`);
2533
- if (result.title) {
2534
- console.log(` Title: ${result.title}`);
2535
- }
2536
- if (result.description && result.label !== "Document") {
2537
- const desc = result.description.length > 80 ? result.description.slice(0, 80) + "..." : result.description;
2538
- console.log(` ${desc}`);
2539
- }
2540
- console.log(` Similarity: ${(result.score * 100).toFixed(2)}%`);
2541
- });
2542
- console.log();
2543
- process.exit(0);
2544
- } catch (error) {
2545
- const errorMsg = error instanceof Error ? error.message : String(error);
2546
- console.error("Error:", errorMsg);
2547
- if (errorMsg.includes("no embeddings") || errorMsg.includes("vector")) {
2548
- console.log(`
2549
- Note: Semantic search requires embeddings to be generated first.`);
2550
- console.log(`Run 'lattice sync' to generate embeddings for documents.
2551
- `);
2552
- }
2553
- process.exit(1);
2554
- }
2555
- }
2556
- parseLabel(value) {
2557
- return value;
2558
- }
2559
- parseLimit(value) {
2560
- return value;
2561
- }
2562
- }
2563
- __legacyDecorateClassTS([
2564
- Option3({
2565
- flags: "-l, --label <label>",
2566
- description: "Filter by entity label (e.g., Technology, Concept, Document)"
2567
- }),
2568
- __legacyMetadataTS("design:type", Function),
2569
- __legacyMetadataTS("design:paramtypes", [
2570
- String
2571
- ]),
2572
- __legacyMetadataTS("design:returntype", String)
2573
- ], SearchCommand.prototype, "parseLabel", null);
2574
- __legacyDecorateClassTS([
2575
- Option3({
2576
- flags: "--limit <n>",
2577
- description: "Limit results",
2578
- defaultValue: "20"
2579
- }),
2580
- __legacyMetadataTS("design:type", Function),
2581
- __legacyMetadataTS("design:paramtypes", [
2582
- String
2583
- ]),
2584
- __legacyMetadataTS("design:returntype", String)
2585
- ], SearchCommand.prototype, "parseLimit", null);
2586
- SearchCommand = __legacyDecorateClassTS([
2587
- Injectable12(),
2588
- Command3({
2589
- name: "search",
2590
- arguments: "<query>",
2591
- description: "Semantic search across the knowledge graph"
2592
- }),
2593
- __legacyMetadataTS("design:paramtypes", [
2594
- typeof GraphService === "undefined" ? Object : GraphService,
2595
- typeof EmbeddingService === "undefined" ? Object : EmbeddingService
2596
- ])
2597
- ], SearchCommand);
2598
-
2599
- class RelsCommand extends CommandRunner3 {
2600
- graphService;
2601
- constructor(graphService) {
2602
- super();
2603
- this.graphService = graphService;
2604
- }
2605
- async run(inputs) {
2606
- const name = inputs[0];
2607
- try {
2608
- const escapedName = name.replace(/'/g, "\\'");
2609
- const cypher = `MATCH (a { name: '${escapedName}' })-[r]-(b) RETURN a, r, b`;
2610
- const result = await this.graphService.query(cypher);
2611
- const results = result.resultSet || [];
2612
- console.log(`
2613
- === Relationships for "${name}" ===
2614
- `);
2615
- if (results.length === 0) {
2616
- console.log(`No relationships found.
2617
- `);
2618
- process.exit(0);
2619
- }
2620
- const incoming = [];
2621
- const outgoing = [];
2622
- results.forEach((row) => {
2623
- const [source, rel, target] = row;
2624
- const sourceObj = Object.fromEntries(source);
2625
- const targetObj = Object.fromEntries(target);
2626
- const relObj = Object.fromEntries(rel);
2627
- const sourceProps = Object.fromEntries(sourceObj.properties || []);
2628
- const targetProps = Object.fromEntries(targetObj.properties || []);
2629
- const sourceName = sourceProps.name || "unknown";
2630
- const targetName = targetProps.name || "unknown";
2631
- const relType = relObj.type || "UNKNOWN";
2632
- if (sourceName === name) {
2633
- outgoing.push(` -[${relType}]-> ${targetName}`);
2634
- } else {
2635
- incoming.push(` <-[${relType}]- ${sourceName}`);
2636
- }
2637
- });
2638
- if (outgoing.length > 0) {
2639
- console.log("Outgoing:");
2640
- outgoing.forEach((r) => console.log(r));
2641
- }
2642
- if (incoming.length > 0) {
2643
- if (outgoing.length > 0)
2644
- console.log();
2645
- console.log("Incoming:");
2646
- incoming.forEach((r) => console.log(r));
2647
- }
2648
- console.log();
2649
- process.exit(0);
2650
- } catch (error) {
2651
- console.error("Error:", error instanceof Error ? error.message : String(error));
2652
- process.exit(1);
2653
- }
2654
- }
2655
- }
2656
- RelsCommand = __legacyDecorateClassTS([
2657
- Injectable12(),
2658
- Command3({
2659
- name: "rels",
2660
- arguments: "<name>",
2661
- description: "Show relationships for a node"
2662
- }),
2663
- __legacyMetadataTS("design:paramtypes", [
2664
- typeof GraphService === "undefined" ? Object : GraphService
2665
- ])
2666
- ], RelsCommand);
2667
-
2668
- class CypherCommand extends CommandRunner3 {
2669
- graphService;
2670
- constructor(graphService) {
2671
- super();
2672
- this.graphService = graphService;
2673
- }
2674
- async run(inputs) {
2675
- const query = inputs[0];
2676
- try {
2677
- const result = await this.graphService.query(query);
2678
- console.log(`
2679
- === Cypher Query Results ===
2680
- `);
2681
- console.log(JSON.stringify(result, null, 2));
2682
- console.log();
2683
- process.exit(0);
2684
- } catch (error) {
2685
- console.error("Error:", error instanceof Error ? error.message : String(error));
2686
- process.exit(1);
2687
- }
2688
- }
2689
- }
2690
- CypherCommand = __legacyDecorateClassTS([
2691
- Injectable12(),
2692
- Command3({
2693
- name: "cypher",
2694
- arguments: "<query>",
2695
- description: "Execute raw Cypher query"
2696
- }),
2697
- __legacyMetadataTS("design:paramtypes", [
2698
- typeof GraphService === "undefined" ? Object : GraphService
2699
- ])
2700
- ], CypherCommand);
2701
-
2702
- class RelatedCommand extends CommandRunner3 {
2703
- graphService;
2704
- pathResolverService;
2705
- constructor(graphService, pathResolverService) {
2706
- super();
2707
- this.graphService = graphService;
2708
- this.pathResolverService = pathResolverService;
2709
- }
2710
- async run(inputs, options) {
2711
- const path = inputs[0];
2712
- const limit = Math.min(parseInt(options.limit || "10", 10), 50);
2713
- try {
2714
- const absolutePath = this.pathResolverService.resolveDocPath(path, {
2715
- requireExists: true,
2716
- requireInDocs: true
2717
- });
2718
- const escapedPath = absolutePath.replace(/'/g, "\\'");
2719
- const cypher = `
2720
- MATCH (d:Document { name: '${escapedPath}' })<-[:APPEARS_IN]-(e)-[:APPEARS_IN]->(other:Document)
2721
- WHERE other.name <> '${escapedPath}'
2722
- RETURN DISTINCT other.name as path, other.title as title, count(e) as shared
2723
- ORDER BY shared DESC
2724
- LIMIT ${limit}
2725
- `;
2726
- const result = await this.graphService.query(cypher);
2727
- const results = result.resultSet || [];
2728
- console.log(`
2729
- === Documents Related to "${path}" ===
2730
- `);
2731
- if (results.length === 0) {
2732
- console.log(`No related documents found.
2733
- `);
2734
- process.exit(0);
2735
- }
2736
- results.forEach((row) => {
2737
- const docPath = row[0];
2738
- const title = row[1];
2739
- const shared = row[2];
2740
- console.log(`[${shared} shared entities] ${docPath}`);
2741
- if (title) {
2742
- console.log(` Title: ${title}`);
2743
- }
2744
- });
2745
- console.log();
2746
- process.exit(0);
2747
- } catch (error) {
2748
- console.error("Error:", error instanceof Error ? error.message : String(error));
2749
- process.exit(1);
2750
- }
2751
- }
2752
- parseLimit(value) {
2753
- return value;
2754
- }
2755
- }
2756
- __legacyDecorateClassTS([
2757
- Option3({
2758
- flags: "--limit <n>",
2759
- description: "Limit results",
2760
- defaultValue: "10"
2761
- }),
2762
- __legacyMetadataTS("design:type", Function),
2763
- __legacyMetadataTS("design:paramtypes", [
2764
- String
2765
- ]),
2766
- __legacyMetadataTS("design:returntype", String)
2767
- ], RelatedCommand.prototype, "parseLimit", null);
2768
- RelatedCommand = __legacyDecorateClassTS([
2769
- Injectable12(),
2770
- Command3({
2771
- name: "related",
2772
- arguments: "<path>",
2773
- description: "Find documents related to the given document"
2774
- }),
2775
- __legacyMetadataTS("design:paramtypes", [
2776
- typeof GraphService === "undefined" ? Object : GraphService,
2777
- typeof PathResolverService === "undefined" ? Object : PathResolverService
2778
- ])
2779
- ], RelatedCommand);
2780
- // src/commands/validate.command.ts
2781
- import { Injectable as Injectable13 } from "@nestjs/common";
2782
- import { Command as Command4, CommandRunner as CommandRunner4, Option as Option4 } from "nest-commander";
2783
- class ValidateCommand extends CommandRunner4 {
2784
- parserService;
2785
- constructor(parserService) {
2786
- super();
2787
- this.parserService = parserService;
2788
- }
2789
- async run(_inputs, options) {
2790
- try {
2791
- console.log(`Validating entities and relationships...
2792
- `);
2793
- const { docs, errors: schemaErrors } = await this.parserService.parseAllDocumentsWithErrors();
2794
- const issues = [];
2795
- for (const schemaError of schemaErrors) {
2796
- issues.push({
2797
- type: "error",
2798
- path: schemaError.path,
2799
- message: schemaError.error
2800
- });
2801
- }
2802
- const entityIndex = new Map;
2803
- for (const doc of docs) {
2804
- for (const entity of doc.entities) {
2805
- if (!entityIndex.has(entity.name)) {
2806
- entityIndex.set(entity.name, new Set);
2807
- }
2808
- entityIndex.get(entity.name).add(doc.path);
2809
- }
2810
- }
2811
- const validationErrors = validateDocuments(docs);
2812
- for (const err of validationErrors) {
2813
- issues.push({
2814
- type: "error",
2815
- path: err.path,
2816
- message: err.error,
2817
- suggestion: "Add entity definition or fix the reference"
2818
- });
2819
- }
2820
- console.log(`Scanned ${docs.length} documents`);
2821
- console.log(`Found ${entityIndex.size} unique entities
2822
- `);
2823
- if (issues.length > 0) {
2824
- console.log(`Errors (${issues.length}):
2825
- `);
2826
- issues.forEach((i) => {
2827
- console.log(` ${i.path}`);
2828
- console.log(` Error: ${i.message}`);
2829
- if (options.fix && i.suggestion) {
2830
- console.log(` Suggestion: ${i.suggestion}`);
2831
- }
2832
- console.log("");
2833
- });
2834
- }
2835
- if (issues.length === 0) {
2836
- console.log("All validations passed!");
2837
- }
2838
- process.exit(issues.length > 0 ? 1 : 0);
2839
- } catch (error) {
2840
- console.error("Validation failed:", error instanceof Error ? error.message : String(error));
2841
- process.exit(1);
2842
- }
2843
- }
2844
- parseFix() {
2845
- return true;
2846
- }
2847
- }
2848
- __legacyDecorateClassTS([
2849
- Option4({
2850
- flags: "--fix",
2851
- description: "Show suggestions for common issues"
2852
- }),
2853
- __legacyMetadataTS("design:type", Function),
2854
- __legacyMetadataTS("design:paramtypes", []),
2855
- __legacyMetadataTS("design:returntype", Boolean)
2856
- ], ValidateCommand.prototype, "parseFix", null);
2857
- ValidateCommand = __legacyDecorateClassTS([
2858
- Injectable13(),
2859
- Command4({
2860
- name: "validate",
2861
- description: "Validate entity references and relationships across documents"
2862
- }),
2863
- __legacyMetadataTS("design:paramtypes", [
2864
- typeof DocumentParserService === "undefined" ? Object : DocumentParserService
2865
- ])
2866
- ], ValidateCommand);
2867
- // src/commands/ontology.command.ts
2868
- import { Injectable as Injectable14 } from "@nestjs/common";
2869
- import { Command as Command5, CommandRunner as CommandRunner5 } from "nest-commander";
2870
- class OntologyCommand extends CommandRunner5 {
2871
- ontologyService;
2872
- constructor(ontologyService) {
2873
- super();
2874
- this.ontologyService = ontologyService;
2875
- }
2876
- async run() {
2877
- try {
2878
- const ontology = await this.ontologyService.deriveOntology();
2879
- this.ontologyService.printSummary(ontology);
2880
- process.exit(0);
2881
- } catch (error) {
2882
- console.error(`
2883
- \u274C Ontology derivation failed:`, error instanceof Error ? error.message : String(error));
2884
- process.exit(1);
2885
- }
2886
- }
2887
- }
2888
- OntologyCommand = __legacyDecorateClassTS([
2889
- Injectable14(),
2890
- Command5({
2891
- name: "ontology",
2892
- description: "Derive and display ontology from all documents"
2893
- }),
2894
- __legacyMetadataTS("design:paramtypes", [
2895
- typeof OntologyService === "undefined" ? Object : OntologyService
2896
- ])
2897
- ], OntologyCommand);
2898
- // src/commands/init.command.ts
2899
- import { Injectable as Injectable15 } from "@nestjs/common";
2900
- import { Command as Command6, CommandRunner as CommandRunner6, Option as Option5 } from "nest-commander";
2901
- import * as fs from "fs/promises";
2902
- import * as path from "path";
2903
- import { fileURLToPath } from "url";
2904
- import { homedir } from "os";
2905
- var __filename2 = fileURLToPath(import.meta.url);
2906
- var __dirname2 = path.dirname(__filename2);
2907
- var COMMANDS = ["research.md", "graph-sync.md", "entity-extract.md"];
2908
-
2909
- class InitCommand extends CommandRunner6 {
2910
- async run(_inputs, options) {
2911
- try {
2912
- const targetDir = options.global ? path.join(homedir(), ".claude", "commands") : path.join(process.cwd(), ".claude", "commands");
2913
- let commandsSourceDir = path.resolve(__dirname2, "..", "commands");
2914
- try {
2915
- await fs.access(commandsSourceDir);
2916
- } catch {
2917
- commandsSourceDir = path.resolve(__dirname2, "..", "..", "commands");
2918
- }
2919
- try {
2920
- await fs.access(commandsSourceDir);
2921
- } catch {
2922
- console.error("Error: Commands source directory not found at", commandsSourceDir);
2923
- console.error("This may indicate a corrupted installation. Try reinstalling @zabaca/lattice.");
2924
- process.exit(1);
2925
- }
2926
- await fs.mkdir(targetDir, { recursive: true });
2927
- let copied = 0;
2928
- let skipped = 0;
2929
- const installed = [];
2930
- for (const file of COMMANDS) {
2931
- const sourcePath = path.join(commandsSourceDir, file);
2932
- const targetPath = path.join(targetDir, file);
2933
- try {
2934
- await fs.access(sourcePath);
2935
- try {
2936
- await fs.access(targetPath);
2937
- const sourceContent = await fs.readFile(sourcePath, "utf-8");
2938
- const targetContent = await fs.readFile(targetPath, "utf-8");
2939
- if (sourceContent === targetContent) {
2940
- skipped++;
2941
- continue;
2942
- }
2943
- } catch {}
2944
- await fs.copyFile(sourcePath, targetPath);
2945
- installed.push(file);
2946
- copied++;
2947
- } catch (err) {
2948
- console.error(`Warning: Could not copy ${file}:`, err instanceof Error ? err.message : String(err));
2949
- }
2950
- }
2951
- console.log();
2952
- console.log(`\u2705 Lattice commands installed to ${targetDir}`);
2953
- console.log();
2954
- if (copied > 0) {
2955
- console.log(`Installed ${copied} command(s):`);
2956
- installed.forEach((f) => {
2957
- const name = f.replace(".md", "");
2958
- console.log(` - /${name}`);
2959
- });
2960
- }
2961
- if (skipped > 0) {
2962
- console.log(`Skipped ${skipped} unchanged command(s)`);
2963
- }
2964
- console.log();
2965
- console.log("Available commands in Claude Code:");
2966
- console.log(" /research <topic> - AI-assisted research workflow");
2967
- console.log(" /graph-sync - Extract entities and sync to graph");
2968
- console.log(" /entity-extract - Extract entities from a single document");
2969
- console.log();
2970
- if (!options.global) {
2971
- console.log("\uD83D\uDCA1 Tip: Use 'lattice init --global' to install for all projects");
2972
- }
2973
- process.exit(0);
2974
- } catch (error) {
2975
- console.error("Error:", error instanceof Error ? error.message : String(error));
2976
- process.exit(1);
2977
- }
2978
- }
2979
- parseGlobal() {
2980
- return true;
2981
- }
2982
- }
2983
- __legacyDecorateClassTS([
2984
- Option5({
2985
- flags: "-g, --global",
2986
- description: "Install to ~/.claude/commands/ (available in all projects)"
2987
- }),
2988
- __legacyMetadataTS("design:type", Function),
2989
- __legacyMetadataTS("design:paramtypes", []),
2990
- __legacyMetadataTS("design:returntype", Boolean)
2991
- ], InitCommand.prototype, "parseGlobal", null);
2992
- InitCommand = __legacyDecorateClassTS([
2993
- Injectable15(),
2994
- Command6({
2995
- name: "init",
2996
- description: "Install Claude Code slash commands for Lattice"
2997
- })
2998
- ], InitCommand);
2999
- // src/app.module.ts
3000
- class AppModule {
3001
- }
3002
- AppModule = __legacyDecorateClassTS([
3003
- Module5({
3004
- imports: [
3005
- ConfigModule2.forRoot({
3006
- isGlobal: true
3007
- }),
3008
- GraphModule,
3009
- SyncModule,
3010
- EmbeddingModule,
3011
- QueryModule
3012
- ],
3013
- providers: [
3014
- SyncCommand,
3015
- StatusCommand,
3016
- StatsCommand,
3017
- SearchCommand,
3018
- RelsCommand,
3019
- CypherCommand,
3020
- RelatedCommand,
3021
- ValidateCommand,
3022
- OntologyCommand,
3023
- InitCommand
3024
- ]
3025
- })
3026
- ], AppModule);
3027
-
3028
- // src/main.ts
3029
- async function bootstrap() {
3030
- await CommandFactory.run(AppModule, ["error"]);
3031
- }
3032
- bootstrap().catch((err) => {
3033
- console.error("\u274C CLI failed:", err);
3034
- process.exit(1);
3035
- });