@zabaca/lattice 0.3.3 → 0.3.4

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