@zabaca/lattice 0.3.3 → 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +68 -64
- package/dist/main.js +1675 -1585
- package/package.json +63 -59
package/dist/main.js
CHANGED
|
@@ -23,86 +23,890 @@ 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/
|
|
27
|
-
import
|
|
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 DuckDBConfigSchema = z.object({
|
|
144
|
+
dbPath: z.string().optional(),
|
|
145
|
+
embeddingDimensions: z.coerce.number().int().positive().default(512)
|
|
146
|
+
});
|
|
147
|
+
var EmbeddingConfigSchema = z.object({
|
|
148
|
+
provider: z.enum(["openai", "voyage", "nomic", "mock"]).default("voyage"),
|
|
149
|
+
apiKey: z.string().optional(),
|
|
150
|
+
model: z.string().min(1).default("voyage-3.5-lite"),
|
|
151
|
+
dimensions: z.coerce.number().int().positive().default(512)
|
|
152
|
+
});
|
|
153
|
+
var DocsConfigSchema = z.object({
|
|
154
|
+
projectRoot: z.string().default(process.cwd()),
|
|
155
|
+
docsPath: z.string().default("docs")
|
|
156
|
+
});
|
|
157
|
+
|
|
158
|
+
// src/utils/frontmatter.ts
|
|
159
|
+
import matter from "gray-matter";
|
|
160
|
+
import { z as z2 } from "zod";
|
|
161
|
+
var EntityTypeSchema = z2.enum([
|
|
162
|
+
"Topic",
|
|
163
|
+
"Technology",
|
|
164
|
+
"Concept",
|
|
165
|
+
"Tool",
|
|
166
|
+
"Process",
|
|
167
|
+
"Person",
|
|
168
|
+
"Organization",
|
|
169
|
+
"Document"
|
|
170
|
+
]);
|
|
171
|
+
var RelationTypeSchema = z2.enum(["REFERENCES"]);
|
|
172
|
+
var EntitySchema = z2.object({
|
|
173
|
+
name: z2.string().min(1),
|
|
174
|
+
type: EntityTypeSchema,
|
|
175
|
+
description: z2.string().optional()
|
|
176
|
+
});
|
|
177
|
+
var RelationshipSchema = z2.object({
|
|
178
|
+
source: z2.string().min(1),
|
|
179
|
+
relation: RelationTypeSchema,
|
|
180
|
+
target: z2.string().min(1)
|
|
181
|
+
});
|
|
182
|
+
var GraphMetadataSchema = z2.object({
|
|
183
|
+
importance: z2.enum(["high", "medium", "low"]).optional(),
|
|
184
|
+
domain: z2.string().optional()
|
|
185
|
+
});
|
|
186
|
+
var validateDateFormat = (dateStr) => {
|
|
187
|
+
const match = dateStr.match(/^(\d{4})-(\d{2})-(\d{2})$/);
|
|
188
|
+
if (!match)
|
|
189
|
+
return false;
|
|
190
|
+
const [, yearStr, monthStr, dayStr] = match;
|
|
191
|
+
const year = parseInt(yearStr, 10);
|
|
192
|
+
const month = parseInt(monthStr, 10);
|
|
193
|
+
const day = parseInt(dayStr, 10);
|
|
194
|
+
if (month < 1 || month > 12)
|
|
195
|
+
return false;
|
|
196
|
+
if (day < 1 || day > 31)
|
|
197
|
+
return false;
|
|
198
|
+
const daysInMonth = [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31];
|
|
199
|
+
if (year % 4 === 0 && year % 100 !== 0 || year % 400 === 0) {
|
|
200
|
+
daysInMonth[1] = 29;
|
|
201
|
+
}
|
|
202
|
+
return day <= daysInMonth[month - 1];
|
|
203
|
+
};
|
|
204
|
+
var FrontmatterSchema = z2.object({
|
|
205
|
+
created: z2.string().refine(validateDateFormat, "Date must be in YYYY-MM-DD format"),
|
|
206
|
+
updated: z2.string().refine(validateDateFormat, "Date must be in YYYY-MM-DD format"),
|
|
207
|
+
status: z2.enum(["draft", "ongoing", "complete"]).optional(),
|
|
208
|
+
topic: z2.string().optional(),
|
|
209
|
+
tags: z2.array(z2.string()).optional(),
|
|
210
|
+
summary: z2.string().optional(),
|
|
211
|
+
entities: z2.array(EntitySchema).optional(),
|
|
212
|
+
relationships: z2.array(RelationshipSchema).optional(),
|
|
213
|
+
graph: GraphMetadataSchema.optional()
|
|
214
|
+
}).passthrough();
|
|
215
|
+
function parseFrontmatter(content) {
|
|
216
|
+
try {
|
|
217
|
+
const { data, content: markdown } = matter(content);
|
|
218
|
+
if (Object.keys(data).length === 0) {
|
|
219
|
+
return {
|
|
220
|
+
frontmatter: null,
|
|
221
|
+
content: markdown.trim(),
|
|
222
|
+
raw: content
|
|
223
|
+
};
|
|
224
|
+
}
|
|
225
|
+
const normalizedData = normalizeData(data);
|
|
226
|
+
const validated = FrontmatterSchema.safeParse(normalizedData);
|
|
227
|
+
return {
|
|
228
|
+
frontmatter: validated.success ? validated.data : normalizedData,
|
|
229
|
+
content: markdown.trim(),
|
|
230
|
+
raw: content
|
|
231
|
+
};
|
|
232
|
+
} catch (error) {
|
|
233
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
234
|
+
throw new Error(`YAML parsing error: ${errorMessage}`);
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
function normalizeData(data) {
|
|
238
|
+
if (data instanceof Date) {
|
|
239
|
+
return data.toISOString().split("T")[0];
|
|
240
|
+
}
|
|
241
|
+
if (Array.isArray(data)) {
|
|
242
|
+
return data.map(normalizeData);
|
|
243
|
+
}
|
|
244
|
+
if (data !== null && typeof data === "object") {
|
|
245
|
+
const normalized = {};
|
|
246
|
+
for (const [key, value] of Object.entries(data)) {
|
|
247
|
+
normalized[key] = normalizeData(value);
|
|
248
|
+
}
|
|
249
|
+
return normalized;
|
|
250
|
+
}
|
|
251
|
+
return data;
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
// src/sync/document-parser.service.ts
|
|
255
|
+
class DocumentParserService {
|
|
256
|
+
logger = new Logger(DocumentParserService.name);
|
|
257
|
+
docsPath;
|
|
258
|
+
constructor() {
|
|
259
|
+
const config = DocsConfigSchema.parse({
|
|
260
|
+
projectRoot: process.env.PROJECT_ROOT,
|
|
261
|
+
docsPath: process.env.DOCS_PATH
|
|
262
|
+
});
|
|
263
|
+
if (config.docsPath.startsWith("/")) {
|
|
264
|
+
this.docsPath = config.docsPath;
|
|
265
|
+
} else {
|
|
266
|
+
this.docsPath = resolve2(config.projectRoot, config.docsPath);
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
getDocsPath() {
|
|
270
|
+
return this.docsPath;
|
|
271
|
+
}
|
|
272
|
+
async discoverDocuments() {
|
|
273
|
+
const pattern = `${this.docsPath}/**/*.md`;
|
|
274
|
+
const files = await glob(pattern, {
|
|
275
|
+
ignore: ["**/node_modules/**", "**/.git/**"]
|
|
276
|
+
});
|
|
277
|
+
return files.sort();
|
|
278
|
+
}
|
|
279
|
+
async parseDocument(filePath) {
|
|
280
|
+
const content = await readFile2(filePath, "utf-8");
|
|
281
|
+
const parsed = parseFrontmatter(content);
|
|
282
|
+
const title = this.extractTitle(content, filePath);
|
|
283
|
+
const contentHash = this.computeHash(content);
|
|
284
|
+
const frontmatterHash = this.computeHash(JSON.stringify(parsed.frontmatter || {}));
|
|
285
|
+
const entities = this.extractEntities(parsed.frontmatter, filePath);
|
|
286
|
+
const relationships = this.extractRelationships(parsed.frontmatter, filePath);
|
|
287
|
+
const graphMetadata = this.extractGraphMetadata(parsed.frontmatter);
|
|
288
|
+
return {
|
|
289
|
+
path: filePath,
|
|
290
|
+
title,
|
|
291
|
+
content: parsed.content,
|
|
292
|
+
contentHash,
|
|
293
|
+
frontmatterHash,
|
|
294
|
+
summary: parsed.frontmatter?.summary,
|
|
295
|
+
topic: parsed.frontmatter?.topic,
|
|
296
|
+
entities,
|
|
297
|
+
relationships,
|
|
298
|
+
graphMetadata,
|
|
299
|
+
tags: parsed.frontmatter?.tags || [],
|
|
300
|
+
created: parsed.frontmatter?.created,
|
|
301
|
+
updated: parsed.frontmatter?.updated,
|
|
302
|
+
status: parsed.frontmatter?.status
|
|
303
|
+
};
|
|
304
|
+
}
|
|
305
|
+
async parseAllDocuments() {
|
|
306
|
+
const { docs } = await this.parseAllDocumentsWithErrors();
|
|
307
|
+
return docs;
|
|
308
|
+
}
|
|
309
|
+
async parseAllDocumentsWithErrors() {
|
|
310
|
+
const files = await this.discoverDocuments();
|
|
311
|
+
const docs = [];
|
|
312
|
+
const errors = [];
|
|
313
|
+
for (const file of files) {
|
|
314
|
+
try {
|
|
315
|
+
const parsed = await this.parseDocument(file);
|
|
316
|
+
docs.push(parsed);
|
|
317
|
+
} catch (error) {
|
|
318
|
+
const errorMsg = error instanceof Error ? error.message : String(error);
|
|
319
|
+
errors.push({ path: file, error: errorMsg });
|
|
320
|
+
this.logger.warn(`Failed to parse ${file}: ${error}`);
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
return { docs, errors };
|
|
324
|
+
}
|
|
325
|
+
extractTitle(content, filePath) {
|
|
326
|
+
const h1Match = content.match(/^#\s+(.+)$/m);
|
|
327
|
+
if (h1Match) {
|
|
328
|
+
return h1Match[1];
|
|
329
|
+
}
|
|
330
|
+
const parts = filePath.split("/");
|
|
331
|
+
return parts[parts.length - 1].replace(".md", "");
|
|
332
|
+
}
|
|
333
|
+
extractEntities(frontmatter, docPath) {
|
|
334
|
+
const fm = frontmatter;
|
|
335
|
+
if (!fm?.entities || !Array.isArray(fm.entities)) {
|
|
336
|
+
return [];
|
|
337
|
+
}
|
|
338
|
+
const validEntities = [];
|
|
339
|
+
const errors = [];
|
|
340
|
+
for (let i = 0;i < fm.entities.length; i++) {
|
|
341
|
+
const e = fm.entities[i];
|
|
342
|
+
const result = EntitySchema.safeParse(e);
|
|
343
|
+
if (result.success) {
|
|
344
|
+
validEntities.push(result.data);
|
|
345
|
+
} else {
|
|
346
|
+
const entityPreview = typeof e === "string" ? `"${e}"` : JSON.stringify(e);
|
|
347
|
+
errors.push(`Entity[${i}]: ${entityPreview} - Expected object with {name, type}, got ${typeof e}`);
|
|
348
|
+
}
|
|
349
|
+
}
|
|
350
|
+
if (errors.length > 0) {
|
|
351
|
+
const errorMsg = `Invalid entity schema in ${docPath}:
|
|
352
|
+
${errors.join(`
|
|
353
|
+
`)}`;
|
|
354
|
+
throw new Error(errorMsg);
|
|
355
|
+
}
|
|
356
|
+
return validEntities;
|
|
357
|
+
}
|
|
358
|
+
extractRelationships(frontmatter, docPath) {
|
|
359
|
+
const fm = frontmatter;
|
|
360
|
+
if (!fm?.relationships || !Array.isArray(fm.relationships)) {
|
|
361
|
+
return [];
|
|
362
|
+
}
|
|
363
|
+
const validRelationships = [];
|
|
364
|
+
const errors = [];
|
|
365
|
+
const validRelationTypes = RelationTypeSchema.options;
|
|
366
|
+
for (let i = 0;i < fm.relationships.length; i++) {
|
|
367
|
+
const r = fm.relationships[i];
|
|
368
|
+
const result = RelationshipSchema.safeParse(r);
|
|
369
|
+
if (result.success) {
|
|
370
|
+
const rel = result.data;
|
|
371
|
+
if (rel.source === "this") {
|
|
372
|
+
rel.source = docPath;
|
|
373
|
+
}
|
|
374
|
+
if (rel.target === "this") {
|
|
375
|
+
rel.target = docPath;
|
|
376
|
+
}
|
|
377
|
+
validRelationships.push(rel);
|
|
378
|
+
} else {
|
|
379
|
+
if (typeof r === "string") {
|
|
380
|
+
errors.push(`Relationship[${i}]: "${r}" - Expected object with {source, relation, target}, got string`);
|
|
381
|
+
} else if (typeof r === "object" && r !== null) {
|
|
382
|
+
const issues = [];
|
|
383
|
+
if (!r.source)
|
|
384
|
+
issues.push("missing source");
|
|
385
|
+
if (!r.target)
|
|
386
|
+
issues.push("missing target");
|
|
387
|
+
if (!r.relation) {
|
|
388
|
+
issues.push("missing relation");
|
|
389
|
+
} else if (!validRelationTypes.includes(r.relation)) {
|
|
390
|
+
issues.push(`invalid relation "${r.relation}" (allowed: ${validRelationTypes.join(", ")})`);
|
|
391
|
+
}
|
|
392
|
+
errors.push(`Relationship[${i}]: ${issues.join(", ")}`);
|
|
393
|
+
} else {
|
|
394
|
+
errors.push(`Relationship[${i}]: Expected object, got ${typeof r}`);
|
|
395
|
+
}
|
|
396
|
+
}
|
|
397
|
+
}
|
|
398
|
+
if (errors.length > 0) {
|
|
399
|
+
const errorMsg = `Invalid relationship schema in ${docPath}:
|
|
400
|
+
${errors.join(`
|
|
401
|
+
`)}`;
|
|
402
|
+
throw new Error(errorMsg);
|
|
403
|
+
}
|
|
404
|
+
return validRelationships;
|
|
405
|
+
}
|
|
406
|
+
extractGraphMetadata(frontmatter) {
|
|
407
|
+
const fm = frontmatter;
|
|
408
|
+
if (!fm?.graph) {
|
|
409
|
+
return;
|
|
410
|
+
}
|
|
411
|
+
const result = GraphMetadataSchema.safeParse(fm.graph);
|
|
412
|
+
return result.success ? result.data : undefined;
|
|
413
|
+
}
|
|
414
|
+
computeHash(content) {
|
|
415
|
+
return createHash("sha256").update(content).digest("hex");
|
|
416
|
+
}
|
|
417
|
+
}
|
|
418
|
+
DocumentParserService = __legacyDecorateClassTS([
|
|
419
|
+
Injectable2(),
|
|
420
|
+
__legacyMetadataTS("design:paramtypes", [])
|
|
421
|
+
], DocumentParserService);
|
|
422
|
+
|
|
423
|
+
// src/sync/ontology.service.ts
|
|
424
|
+
class OntologyService {
|
|
425
|
+
parser;
|
|
426
|
+
constructor(parser) {
|
|
427
|
+
this.parser = parser;
|
|
428
|
+
}
|
|
429
|
+
async deriveOntology() {
|
|
430
|
+
const docs = await this.parser.parseAllDocuments();
|
|
431
|
+
return this.deriveFromDocuments(docs);
|
|
432
|
+
}
|
|
433
|
+
deriveFromDocuments(docs) {
|
|
434
|
+
const entityTypeSet = new Set;
|
|
435
|
+
const relationshipTypeSet = new Set;
|
|
436
|
+
const entityCounts = {};
|
|
437
|
+
const relationshipCounts = {};
|
|
438
|
+
const entityExamples = {};
|
|
439
|
+
let documentsWithEntities = 0;
|
|
440
|
+
let documentsWithoutEntities = 0;
|
|
441
|
+
let totalRelationships = 0;
|
|
442
|
+
for (const doc of docs) {
|
|
443
|
+
if (doc.entities.length > 0) {
|
|
444
|
+
documentsWithEntities++;
|
|
445
|
+
} else {
|
|
446
|
+
documentsWithoutEntities++;
|
|
447
|
+
}
|
|
448
|
+
for (const entity of doc.entities) {
|
|
449
|
+
entityTypeSet.add(entity.type);
|
|
450
|
+
entityCounts[entity.type] = (entityCounts[entity.type] || 0) + 1;
|
|
451
|
+
if (!entityExamples[entity.name]) {
|
|
452
|
+
entityExamples[entity.name] = { type: entity.type, documents: [] };
|
|
453
|
+
}
|
|
454
|
+
if (!entityExamples[entity.name].documents.includes(doc.path)) {
|
|
455
|
+
entityExamples[entity.name].documents.push(doc.path);
|
|
456
|
+
}
|
|
457
|
+
}
|
|
458
|
+
for (const rel of doc.relationships) {
|
|
459
|
+
relationshipTypeSet.add(rel.relation);
|
|
460
|
+
relationshipCounts[rel.relation] = (relationshipCounts[rel.relation] || 0) + 1;
|
|
461
|
+
totalRelationships++;
|
|
462
|
+
}
|
|
463
|
+
}
|
|
464
|
+
return {
|
|
465
|
+
entityTypes: Array.from(entityTypeSet).sort(),
|
|
466
|
+
relationshipTypes: Array.from(relationshipTypeSet).sort(),
|
|
467
|
+
entityCounts,
|
|
468
|
+
relationshipCounts,
|
|
469
|
+
totalEntities: Object.keys(entityExamples).length,
|
|
470
|
+
totalRelationships,
|
|
471
|
+
documentsWithEntities,
|
|
472
|
+
documentsWithoutEntities,
|
|
473
|
+
entityExamples
|
|
474
|
+
};
|
|
475
|
+
}
|
|
476
|
+
printSummary(ontology) {
|
|
477
|
+
console.log(`
|
|
478
|
+
Derived Ontology Summary
|
|
479
|
+
`);
|
|
480
|
+
console.log(`Documents: ${ontology.documentsWithEntities} with entities, ${ontology.documentsWithoutEntities} without`);
|
|
481
|
+
console.log(`Unique Entities: ${ontology.totalEntities}`);
|
|
482
|
+
console.log(`Total Relationships: ${ontology.totalRelationships}`);
|
|
483
|
+
console.log(`
|
|
484
|
+
Entity Types:`);
|
|
485
|
+
for (const type of ontology.entityTypes) {
|
|
486
|
+
console.log(` ${type}: ${ontology.entityCounts[type]} instances`);
|
|
487
|
+
}
|
|
488
|
+
console.log(`
|
|
489
|
+
Relationship Types:`);
|
|
490
|
+
for (const type of ontology.relationshipTypes) {
|
|
491
|
+
console.log(` ${type}: ${ontology.relationshipCounts[type]} instances`);
|
|
492
|
+
}
|
|
493
|
+
console.log(`
|
|
494
|
+
Top Entities (by document count):`);
|
|
495
|
+
const sorted = Object.entries(ontology.entityExamples).sort((a, b) => b[1].documents.length - a[1].documents.length).slice(0, 10);
|
|
496
|
+
for (const [name, info] of sorted) {
|
|
497
|
+
console.log(` ${name} (${info.type}): ${info.documents.length} docs`);
|
|
498
|
+
}
|
|
499
|
+
}
|
|
500
|
+
}
|
|
501
|
+
OntologyService = __legacyDecorateClassTS([
|
|
502
|
+
Injectable3(),
|
|
503
|
+
__legacyMetadataTS("design:paramtypes", [
|
|
504
|
+
typeof DocumentParserService === "undefined" ? Object : DocumentParserService
|
|
505
|
+
])
|
|
506
|
+
], OntologyService);
|
|
507
|
+
|
|
508
|
+
// src/commands/ontology.command.ts
|
|
509
|
+
class OntologyCommand extends CommandRunner2 {
|
|
510
|
+
ontologyService;
|
|
511
|
+
constructor(ontologyService) {
|
|
512
|
+
super();
|
|
513
|
+
this.ontologyService = ontologyService;
|
|
514
|
+
}
|
|
515
|
+
async run() {
|
|
516
|
+
try {
|
|
517
|
+
const ontology = await this.ontologyService.deriveOntology();
|
|
518
|
+
this.ontologyService.printSummary(ontology);
|
|
519
|
+
process.exit(0);
|
|
520
|
+
} catch (error) {
|
|
521
|
+
console.error(`
|
|
522
|
+
\u274C Ontology derivation failed:`, error instanceof Error ? error.message : String(error));
|
|
523
|
+
process.exit(1);
|
|
524
|
+
}
|
|
525
|
+
}
|
|
526
|
+
}
|
|
527
|
+
OntologyCommand = __legacyDecorateClassTS([
|
|
528
|
+
Injectable4(),
|
|
529
|
+
Command2({
|
|
530
|
+
name: "ontology",
|
|
531
|
+
description: "Derive and display ontology from all documents"
|
|
532
|
+
}),
|
|
533
|
+
__legacyMetadataTS("design:paramtypes", [
|
|
534
|
+
typeof OntologyService === "undefined" ? Object : OntologyService
|
|
535
|
+
])
|
|
536
|
+
], OntologyCommand);
|
|
537
|
+
// src/commands/query.command.ts
|
|
538
|
+
import { Injectable as Injectable7 } from "@nestjs/common";
|
|
539
|
+
import { Command as Command3, CommandRunner as CommandRunner3, Option as Option2 } from "nest-commander";
|
|
540
|
+
|
|
541
|
+
// src/embedding/embedding.service.ts
|
|
542
|
+
import { Injectable as Injectable5, Logger as Logger2 } from "@nestjs/common";
|
|
543
|
+
import { ConfigService } from "@nestjs/config";
|
|
544
|
+
|
|
545
|
+
// src/embedding/embedding.types.ts
|
|
546
|
+
var DEFAULT_EMBEDDING_CONFIG = {
|
|
547
|
+
provider: "voyage",
|
|
548
|
+
model: "voyage-3.5-lite",
|
|
549
|
+
dimensions: 512
|
|
550
|
+
};
|
|
551
|
+
|
|
552
|
+
// src/embedding/providers/mock.provider.ts
|
|
553
|
+
class MockEmbeddingProvider {
|
|
554
|
+
name = "mock";
|
|
555
|
+
dimensions;
|
|
556
|
+
constructor(dimensions = 1536) {
|
|
557
|
+
this.dimensions = dimensions;
|
|
558
|
+
}
|
|
559
|
+
async generateEmbedding(text) {
|
|
560
|
+
return this.generateDeterministicEmbedding(text);
|
|
561
|
+
}
|
|
562
|
+
async generateEmbeddings(texts) {
|
|
563
|
+
return Promise.all(texts.map((text) => this.generateEmbedding(text)));
|
|
564
|
+
}
|
|
565
|
+
generateDeterministicEmbedding(text) {
|
|
566
|
+
const embedding = new Array(this.dimensions);
|
|
567
|
+
let hash = 0;
|
|
568
|
+
for (let i = 0;i < text.length; i++) {
|
|
569
|
+
hash = (hash << 5) - hash + text.charCodeAt(i);
|
|
570
|
+
hash = hash & hash;
|
|
571
|
+
}
|
|
572
|
+
for (let i = 0;i < this.dimensions; i++) {
|
|
573
|
+
const seed = hash * (i + 1) ^ i * 31;
|
|
574
|
+
embedding[i] = (seed % 2000 - 1000) / 1000;
|
|
575
|
+
}
|
|
576
|
+
const magnitude = Math.sqrt(embedding.reduce((sum, val) => sum + val * val, 0));
|
|
577
|
+
return embedding.map((val) => val / magnitude);
|
|
578
|
+
}
|
|
579
|
+
}
|
|
580
|
+
|
|
581
|
+
// src/embedding/providers/openai.provider.ts
|
|
582
|
+
class OpenAIEmbeddingProvider {
|
|
583
|
+
name = "openai";
|
|
584
|
+
dimensions;
|
|
585
|
+
model;
|
|
586
|
+
apiKey;
|
|
587
|
+
baseUrl = "https://api.openai.com/v1";
|
|
588
|
+
constructor(config) {
|
|
589
|
+
const apiKey = config?.apiKey || process.env.OPENAI_API_KEY;
|
|
590
|
+
if (!apiKey) {
|
|
591
|
+
throw new Error("OpenAI API key is required. Set OPENAI_API_KEY environment variable or pass apiKey in config.");
|
|
592
|
+
}
|
|
593
|
+
this.apiKey = apiKey;
|
|
594
|
+
this.model = config?.model || "text-embedding-3-small";
|
|
595
|
+
this.dimensions = config?.dimensions || 1536;
|
|
596
|
+
}
|
|
597
|
+
async generateEmbedding(text) {
|
|
598
|
+
const embeddings = await this.generateEmbeddings([text]);
|
|
599
|
+
return embeddings[0];
|
|
600
|
+
}
|
|
601
|
+
async generateEmbeddings(texts) {
|
|
602
|
+
if (!texts || texts.length === 0) {
|
|
603
|
+
return [];
|
|
604
|
+
}
|
|
605
|
+
try {
|
|
606
|
+
const response = await fetch(`${this.baseUrl}/embeddings`, {
|
|
607
|
+
method: "POST",
|
|
608
|
+
headers: {
|
|
609
|
+
"Content-Type": "application/json",
|
|
610
|
+
Authorization: `Bearer ${this.apiKey}`
|
|
611
|
+
},
|
|
612
|
+
body: JSON.stringify({
|
|
613
|
+
model: this.model,
|
|
614
|
+
input: texts,
|
|
615
|
+
dimensions: this.dimensions
|
|
616
|
+
})
|
|
617
|
+
});
|
|
618
|
+
if (!response.ok) {
|
|
619
|
+
const error = await response.json().catch(() => ({}));
|
|
620
|
+
throw new Error(`OpenAI API error: ${response.status} ${JSON.stringify(error)}`);
|
|
621
|
+
}
|
|
622
|
+
const data = await response.json();
|
|
623
|
+
const sortedData = data.data.sort((a, b) => a.index - b.index);
|
|
624
|
+
return sortedData.map((item) => item.embedding);
|
|
625
|
+
} catch (error) {
|
|
626
|
+
if (error instanceof Error) {
|
|
627
|
+
throw new Error(`Failed to generate embeddings: ${error.message}`);
|
|
628
|
+
}
|
|
629
|
+
throw error;
|
|
630
|
+
}
|
|
631
|
+
}
|
|
632
|
+
}
|
|
633
|
+
|
|
634
|
+
// src/schemas/embedding.schemas.ts
|
|
635
|
+
import { z as z3 } from "zod";
|
|
636
|
+
var VoyageEmbeddingResponseSchema = z3.object({
|
|
637
|
+
object: z3.string(),
|
|
638
|
+
data: z3.array(z3.object({
|
|
639
|
+
object: z3.string(),
|
|
640
|
+
embedding: z3.array(z3.number()),
|
|
641
|
+
index: z3.number().int().nonnegative()
|
|
642
|
+
})),
|
|
643
|
+
model: z3.string(),
|
|
644
|
+
usage: z3.object({
|
|
645
|
+
total_tokens: z3.number().int().positive()
|
|
646
|
+
})
|
|
647
|
+
});
|
|
648
|
+
|
|
649
|
+
// src/embedding/providers/voyage.provider.ts
|
|
650
|
+
class VoyageEmbeddingProvider {
|
|
651
|
+
name = "voyage";
|
|
652
|
+
dimensions;
|
|
653
|
+
model;
|
|
654
|
+
apiKey;
|
|
655
|
+
inputType;
|
|
656
|
+
baseUrl = "https://api.voyageai.com/v1";
|
|
657
|
+
constructor(config) {
|
|
658
|
+
const apiKey = config?.apiKey || process.env.VOYAGE_API_KEY;
|
|
659
|
+
if (!apiKey) {
|
|
660
|
+
throw new Error("Voyage API key is required. Set VOYAGE_API_KEY environment variable or pass apiKey in config.");
|
|
661
|
+
}
|
|
662
|
+
this.apiKey = apiKey;
|
|
663
|
+
this.model = config?.model || "voyage-3.5-lite";
|
|
664
|
+
this.dimensions = config?.dimensions || 512;
|
|
665
|
+
this.inputType = config?.inputType || "document";
|
|
666
|
+
}
|
|
667
|
+
async generateEmbedding(text) {
|
|
668
|
+
const embeddings = await this.generateEmbeddings([text]);
|
|
669
|
+
return embeddings[0];
|
|
670
|
+
}
|
|
671
|
+
async generateEmbeddings(texts) {
|
|
672
|
+
if (!texts || texts.length === 0) {
|
|
673
|
+
return [];
|
|
674
|
+
}
|
|
675
|
+
try {
|
|
676
|
+
const response = await fetch(`${this.baseUrl}/embeddings`, {
|
|
677
|
+
method: "POST",
|
|
678
|
+
headers: {
|
|
679
|
+
"Content-Type": "application/json",
|
|
680
|
+
Authorization: `Bearer ${this.apiKey}`
|
|
681
|
+
},
|
|
682
|
+
body: JSON.stringify({
|
|
683
|
+
model: this.model,
|
|
684
|
+
input: texts,
|
|
685
|
+
output_dimension: this.dimensions,
|
|
686
|
+
input_type: this.inputType
|
|
687
|
+
})
|
|
688
|
+
});
|
|
689
|
+
if (!response.ok) {
|
|
690
|
+
const error = await response.json().catch(() => ({}));
|
|
691
|
+
throw new Error(`Voyage API error: ${response.status} ${JSON.stringify(error)}`);
|
|
692
|
+
}
|
|
693
|
+
const data = VoyageEmbeddingResponseSchema.parse(await response.json());
|
|
694
|
+
const sortedData = data.data.sort((a, b) => a.index - b.index);
|
|
695
|
+
return sortedData.map((item) => item.embedding);
|
|
696
|
+
} catch (error) {
|
|
697
|
+
if (error instanceof Error) {
|
|
698
|
+
throw new Error(`Failed to generate embeddings: ${error.message}`);
|
|
699
|
+
}
|
|
700
|
+
throw error;
|
|
701
|
+
}
|
|
702
|
+
}
|
|
703
|
+
}
|
|
704
|
+
|
|
705
|
+
// src/embedding/embedding.service.ts
|
|
706
|
+
class EmbeddingService {
|
|
707
|
+
configService;
|
|
708
|
+
logger = new Logger2(EmbeddingService.name);
|
|
709
|
+
provider;
|
|
710
|
+
config;
|
|
711
|
+
constructor(configService) {
|
|
712
|
+
this.configService = configService;
|
|
713
|
+
this.config = this.loadConfig();
|
|
714
|
+
this.provider = this.createProvider();
|
|
715
|
+
this.logger.log(`Initialized embedding service with provider: ${this.provider.name}`);
|
|
716
|
+
}
|
|
717
|
+
loadConfig() {
|
|
718
|
+
const providerEnv = this.configService.get("EMBEDDING_PROVIDER");
|
|
719
|
+
const provider = providerEnv ?? DEFAULT_EMBEDDING_CONFIG.provider;
|
|
720
|
+
let apiKey;
|
|
721
|
+
if (provider === "voyage") {
|
|
722
|
+
apiKey = this.configService.get("VOYAGE_API_KEY");
|
|
723
|
+
} else if (provider === "openai") {
|
|
724
|
+
apiKey = this.configService.get("OPENAI_API_KEY");
|
|
725
|
+
}
|
|
726
|
+
return EmbeddingConfigSchema.parse({
|
|
727
|
+
provider: providerEnv,
|
|
728
|
+
apiKey,
|
|
729
|
+
model: this.configService.get("EMBEDDING_MODEL"),
|
|
730
|
+
dimensions: this.configService.get("EMBEDDING_DIMENSIONS")
|
|
731
|
+
});
|
|
732
|
+
}
|
|
733
|
+
createProvider() {
|
|
734
|
+
switch (this.config.provider) {
|
|
735
|
+
case "openai":
|
|
736
|
+
if (!this.config.apiKey) {
|
|
737
|
+
throw new Error("OPENAI_API_KEY environment variable is required for embeddings. " + "Set it in .env or use --no-embeddings to skip embedding generation.");
|
|
738
|
+
}
|
|
739
|
+
return new OpenAIEmbeddingProvider({
|
|
740
|
+
apiKey: this.config.apiKey,
|
|
741
|
+
model: this.config.model,
|
|
742
|
+
dimensions: this.config.dimensions
|
|
743
|
+
});
|
|
744
|
+
case "mock":
|
|
745
|
+
return new MockEmbeddingProvider(this.config.dimensions);
|
|
746
|
+
case "voyage":
|
|
747
|
+
if (!this.config.apiKey) {
|
|
748
|
+
throw new Error("VOYAGE_API_KEY environment variable is required for embeddings. " + "Set it in .env or use --no-embeddings to skip embedding generation.");
|
|
749
|
+
}
|
|
750
|
+
return new VoyageEmbeddingProvider({
|
|
751
|
+
apiKey: this.config.apiKey,
|
|
752
|
+
model: this.config.model,
|
|
753
|
+
dimensions: this.config.dimensions
|
|
754
|
+
});
|
|
755
|
+
case "nomic":
|
|
756
|
+
throw new Error(`Provider ${this.config.provider} not yet implemented. Use 'voyage', 'openai', or 'mock'.`);
|
|
757
|
+
default:
|
|
758
|
+
throw new Error(`Unknown embedding provider: ${this.config.provider}. Use 'voyage', 'openai', or 'mock'.`);
|
|
759
|
+
}
|
|
760
|
+
}
|
|
761
|
+
getProviderName() {
|
|
762
|
+
return this.provider.name;
|
|
763
|
+
}
|
|
764
|
+
getDimensions() {
|
|
765
|
+
return this.provider.dimensions;
|
|
766
|
+
}
|
|
767
|
+
async generateEmbedding(text) {
|
|
768
|
+
if (!text || text.trim().length === 0) {
|
|
769
|
+
throw new Error("Cannot generate embedding for empty text");
|
|
770
|
+
}
|
|
771
|
+
return this.provider.generateEmbedding(text);
|
|
772
|
+
}
|
|
773
|
+
async generateEmbeddings(texts) {
|
|
774
|
+
const validTexts = texts.filter((t) => t && t.trim().length > 0);
|
|
775
|
+
if (validTexts.length === 0) {
|
|
776
|
+
return [];
|
|
777
|
+
}
|
|
778
|
+
return this.provider.generateEmbeddings(validTexts);
|
|
779
|
+
}
|
|
780
|
+
isRealProvider() {
|
|
781
|
+
return this.provider.name !== "mock";
|
|
782
|
+
}
|
|
783
|
+
}
|
|
784
|
+
EmbeddingService = __legacyDecorateClassTS([
|
|
785
|
+
Injectable5(),
|
|
786
|
+
__legacyMetadataTS("design:paramtypes", [
|
|
787
|
+
typeof ConfigService === "undefined" ? Object : ConfigService
|
|
788
|
+
])
|
|
789
|
+
], EmbeddingService);
|
|
28
790
|
|
|
29
791
|
// src/graph/graph.service.ts
|
|
30
|
-
import {
|
|
31
|
-
import {
|
|
32
|
-
import
|
|
792
|
+
import { DuckDBInstance } from "@duckdb/node-api";
|
|
793
|
+
import { Injectable as Injectable6, Logger as Logger3 } from "@nestjs/common";
|
|
794
|
+
import { ConfigService as ConfigService2 } from "@nestjs/config";
|
|
33
795
|
class GraphService {
|
|
34
796
|
configService;
|
|
35
|
-
logger = new
|
|
36
|
-
|
|
37
|
-
|
|
797
|
+
logger = new Logger3(GraphService.name);
|
|
798
|
+
instance = null;
|
|
799
|
+
connection = null;
|
|
800
|
+
dbPath;
|
|
38
801
|
connecting = null;
|
|
802
|
+
vectorIndexes = new Set;
|
|
803
|
+
embeddingDimensions;
|
|
39
804
|
constructor(configService) {
|
|
40
805
|
this.configService = configService;
|
|
41
|
-
this.
|
|
42
|
-
|
|
43
|
-
port: this.configService.get("FALKORDB_PORT", 6379),
|
|
44
|
-
graphName: this.configService.get("GRAPH_NAME", "research_knowledge")
|
|
45
|
-
};
|
|
806
|
+
this.dbPath = this.configService.get("DUCKDB_PATH") || "./.lattice.duckdb";
|
|
807
|
+
this.embeddingDimensions = this.configService.get("EMBEDDING_DIMENSIONS") || 512;
|
|
46
808
|
}
|
|
47
809
|
async onModuleDestroy() {
|
|
48
810
|
await this.disconnect();
|
|
49
811
|
}
|
|
50
812
|
async ensureConnected() {
|
|
51
|
-
if (this.
|
|
52
|
-
return this.
|
|
813
|
+
if (this.connection) {
|
|
814
|
+
return this.connection;
|
|
53
815
|
}
|
|
54
816
|
if (this.connecting) {
|
|
55
817
|
await this.connecting;
|
|
56
|
-
|
|
818
|
+
if (!this.connection) {
|
|
819
|
+
throw new Error("Connection failed to establish");
|
|
820
|
+
}
|
|
821
|
+
return this.connection;
|
|
57
822
|
}
|
|
58
823
|
this.connecting = this.connect();
|
|
59
824
|
await this.connecting;
|
|
60
825
|
this.connecting = null;
|
|
61
|
-
|
|
826
|
+
if (!this.connection) {
|
|
827
|
+
throw new Error("Connection failed to establish");
|
|
828
|
+
}
|
|
829
|
+
return this.connection;
|
|
62
830
|
}
|
|
63
831
|
async connect() {
|
|
64
832
|
try {
|
|
65
|
-
this.
|
|
66
|
-
|
|
67
|
-
port: this.config.port,
|
|
68
|
-
maxRetriesPerRequest: 3,
|
|
69
|
-
retryStrategy: (times) => {
|
|
70
|
-
if (times > 3) {
|
|
71
|
-
return null;
|
|
72
|
-
}
|
|
73
|
-
const delay = Math.min(times * 50, 2000);
|
|
74
|
-
return delay;
|
|
75
|
-
},
|
|
76
|
-
lazyConnect: true
|
|
77
|
-
});
|
|
78
|
-
this.redis.on("error", (err) => {
|
|
79
|
-
this.logger.debug(`Redis connection error: ${err.message}`);
|
|
833
|
+
this.instance = await DuckDBInstance.create(this.dbPath, {
|
|
834
|
+
allow_unsigned_extensions: "true"
|
|
80
835
|
});
|
|
81
|
-
await this.
|
|
82
|
-
this.
|
|
836
|
+
this.connection = await this.instance.connect();
|
|
837
|
+
await this.connection.run("INSTALL vss; LOAD vss;");
|
|
838
|
+
await this.connection.run("SET hnsw_enable_experimental_persistence = true;");
|
|
839
|
+
try {
|
|
840
|
+
await this.connection.run("SET custom_extension_repository = 'http://duckpgq.s3.eu-north-1.amazonaws.com';");
|
|
841
|
+
await this.connection.run("FORCE INSTALL 'duckpgq';");
|
|
842
|
+
await this.connection.run("LOAD 'duckpgq';");
|
|
843
|
+
this.logger.log("DuckPGQ extension loaded successfully");
|
|
844
|
+
} catch (e) {
|
|
845
|
+
this.logger.warn(`DuckPGQ extension not available: ${e instanceof Error ? e.message : String(e)}`);
|
|
846
|
+
}
|
|
847
|
+
await this.initializeSchema();
|
|
848
|
+
this.logger.log(`Connected to DuckDB at ${this.dbPath}`);
|
|
83
849
|
} catch (error) {
|
|
84
|
-
this.
|
|
85
|
-
this.
|
|
850
|
+
this.connection = null;
|
|
851
|
+
this.instance = null;
|
|
852
|
+
this.logger.error(`Failed to connect to DuckDB: ${error instanceof Error ? error.message : String(error)}`);
|
|
86
853
|
throw error;
|
|
87
854
|
}
|
|
88
855
|
}
|
|
89
856
|
async disconnect() {
|
|
90
|
-
if (this.
|
|
91
|
-
|
|
92
|
-
this.
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
857
|
+
if (this.connection) {
|
|
858
|
+
this.connection.closeSync();
|
|
859
|
+
this.connection = null;
|
|
860
|
+
this.logger.log("Disconnected from DuckDB");
|
|
861
|
+
}
|
|
862
|
+
if (this.instance) {
|
|
863
|
+
this.instance = null;
|
|
864
|
+
}
|
|
865
|
+
}
|
|
866
|
+
async initializeSchema() {
|
|
867
|
+
if (!this.connection) {
|
|
868
|
+
throw new Error("Cannot initialize schema: not connected");
|
|
869
|
+
}
|
|
870
|
+
const conn = this.connection;
|
|
871
|
+
await conn.run(`
|
|
872
|
+
CREATE TABLE IF NOT EXISTS nodes (
|
|
873
|
+
label VARCHAR NOT NULL,
|
|
874
|
+
name VARCHAR NOT NULL,
|
|
875
|
+
properties JSON,
|
|
876
|
+
embedding FLOAT[${this.embeddingDimensions}],
|
|
877
|
+
created_at TIMESTAMP DEFAULT NOW(),
|
|
878
|
+
updated_at TIMESTAMP DEFAULT NOW(),
|
|
879
|
+
PRIMARY KEY(label, name)
|
|
880
|
+
)
|
|
881
|
+
`);
|
|
882
|
+
await conn.run(`
|
|
883
|
+
CREATE TABLE IF NOT EXISTS relationships (
|
|
884
|
+
source_label VARCHAR NOT NULL,
|
|
885
|
+
source_name VARCHAR NOT NULL,
|
|
886
|
+
relation_type VARCHAR NOT NULL,
|
|
887
|
+
target_label VARCHAR NOT NULL,
|
|
888
|
+
target_name VARCHAR NOT NULL,
|
|
889
|
+
properties JSON,
|
|
890
|
+
created_at TIMESTAMP DEFAULT NOW(),
|
|
891
|
+
PRIMARY KEY(source_label, source_name, relation_type, target_label, target_name)
|
|
892
|
+
)
|
|
893
|
+
`);
|
|
894
|
+
await conn.run("CREATE INDEX IF NOT EXISTS idx_nodes_label ON nodes(label)");
|
|
895
|
+
await conn.run("CREATE INDEX IF NOT EXISTS idx_nodes_label_name ON nodes(label, name)");
|
|
896
|
+
await conn.run("CREATE INDEX IF NOT EXISTS idx_rels_source ON relationships(source_label, source_name)");
|
|
897
|
+
await conn.run("CREATE INDEX IF NOT EXISTS idx_rels_target ON relationships(target_label, target_name)");
|
|
898
|
+
}
|
|
899
|
+
async query(sql, _params) {
|
|
96
900
|
try {
|
|
97
|
-
const
|
|
98
|
-
const
|
|
99
|
-
const
|
|
901
|
+
const conn = await this.ensureConnected();
|
|
902
|
+
const reader = await conn.runAndReadAll(sql);
|
|
903
|
+
const rows = reader.getRows();
|
|
100
904
|
return {
|
|
101
|
-
resultSet:
|
|
102
|
-
stats:
|
|
905
|
+
resultSet: rows,
|
|
906
|
+
stats: undefined
|
|
103
907
|
};
|
|
104
908
|
} catch (error) {
|
|
105
|
-
this.logger.error(`
|
|
909
|
+
this.logger.error(`Query failed: ${error instanceof Error ? error.message : String(error)}`);
|
|
106
910
|
throw error;
|
|
107
911
|
}
|
|
108
912
|
}
|
|
@@ -112,18 +916,15 @@ class GraphService {
|
|
|
112
916
|
if (!name) {
|
|
113
917
|
throw new Error("Node must have a 'name' property");
|
|
114
918
|
}
|
|
115
|
-
const
|
|
116
|
-
const
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
}).join(", ");
|
|
125
|
-
const cypher = `MERGE (n:\`${escapedLabel}\` { name: '${escapedName}' }) ` + `SET ${propAssignments}`;
|
|
126
|
-
await this.query(cypher);
|
|
919
|
+
const conn = await this.ensureConnected();
|
|
920
|
+
const propsJson = JSON.stringify(otherProps);
|
|
921
|
+
await conn.run(`
|
|
922
|
+
INSERT INTO nodes (label, name, properties)
|
|
923
|
+
VALUES ('${this.escape(String(label))}', '${this.escape(String(name))}', '${this.escape(propsJson)}')
|
|
924
|
+
ON CONFLICT (label, name) DO UPDATE SET
|
|
925
|
+
properties = EXCLUDED.properties,
|
|
926
|
+
updated_at = NOW()
|
|
927
|
+
`);
|
|
127
928
|
} catch (error) {
|
|
128
929
|
this.logger.error(`Failed to upsert node: ${error instanceof Error ? error.message : String(error)}`);
|
|
129
930
|
throw error;
|
|
@@ -131,21 +932,31 @@ class GraphService {
|
|
|
131
932
|
}
|
|
132
933
|
async upsertRelationship(sourceLabel, sourceName, relation, targetLabel, targetName, properties) {
|
|
133
934
|
try {
|
|
134
|
-
const
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
935
|
+
const conn = await this.ensureConnected();
|
|
936
|
+
await conn.run(`
|
|
937
|
+
INSERT INTO nodes (label, name, properties)
|
|
938
|
+
VALUES ('${this.escape(sourceLabel)}', '${this.escape(sourceName)}', '{}')
|
|
939
|
+
ON CONFLICT (label, name) DO NOTHING
|
|
940
|
+
`);
|
|
941
|
+
await conn.run(`
|
|
942
|
+
INSERT INTO nodes (label, name, properties)
|
|
943
|
+
VALUES ('${this.escape(targetLabel)}', '${this.escape(targetName)}', '{}')
|
|
944
|
+
ON CONFLICT (label, name) DO NOTHING
|
|
945
|
+
`);
|
|
946
|
+
const propsJson = properties ? JSON.stringify(properties) : "{}";
|
|
947
|
+
await conn.run(`
|
|
948
|
+
INSERT INTO relationships (source_label, source_name, relation_type, target_label, target_name, properties)
|
|
949
|
+
VALUES (
|
|
950
|
+
'${this.escape(sourceLabel)}',
|
|
951
|
+
'${this.escape(sourceName)}',
|
|
952
|
+
'${this.escape(relation)}',
|
|
953
|
+
'${this.escape(targetLabel)}',
|
|
954
|
+
'${this.escape(targetName)}',
|
|
955
|
+
'${this.escape(propsJson)}'
|
|
956
|
+
)
|
|
957
|
+
ON CONFLICT (source_label, source_name, relation_type, target_label, target_name) DO UPDATE SET
|
|
958
|
+
properties = EXCLUDED.properties
|
|
959
|
+
`);
|
|
149
960
|
} catch (error) {
|
|
150
961
|
this.logger.error(`Failed to upsert relationship: ${error instanceof Error ? error.message : String(error)}`);
|
|
151
962
|
throw error;
|
|
@@ -153,10 +964,16 @@ class GraphService {
|
|
|
153
964
|
}
|
|
154
965
|
async deleteNode(label, name) {
|
|
155
966
|
try {
|
|
156
|
-
const
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
967
|
+
const conn = await this.ensureConnected();
|
|
968
|
+
await conn.run(`
|
|
969
|
+
DELETE FROM relationships
|
|
970
|
+
WHERE (source_label = '${this.escape(label)}' AND source_name = '${this.escape(name)}')
|
|
971
|
+
OR (target_label = '${this.escape(label)}' AND target_name = '${this.escape(name)}')
|
|
972
|
+
`);
|
|
973
|
+
await conn.run(`
|
|
974
|
+
DELETE FROM nodes
|
|
975
|
+
WHERE label = '${this.escape(label)}' AND name = '${this.escape(name)}'
|
|
976
|
+
`);
|
|
160
977
|
} catch (error) {
|
|
161
978
|
this.logger.error(`Failed to delete node: ${error instanceof Error ? error.message : String(error)}`);
|
|
162
979
|
throw error;
|
|
@@ -164,9 +981,11 @@ class GraphService {
|
|
|
164
981
|
}
|
|
165
982
|
async deleteDocumentRelationships(documentPath) {
|
|
166
983
|
try {
|
|
167
|
-
const
|
|
168
|
-
|
|
169
|
-
|
|
984
|
+
const conn = await this.ensureConnected();
|
|
985
|
+
await conn.run(`
|
|
986
|
+
DELETE FROM relationships
|
|
987
|
+
WHERE properties->>'documentPath' = '${this.escape(documentPath)}'
|
|
988
|
+
`);
|
|
170
989
|
} catch (error) {
|
|
171
990
|
this.logger.error(`Failed to delete document relationships: ${error instanceof Error ? error.message : String(error)}`);
|
|
172
991
|
throw error;
|
|
@@ -174,11 +993,18 @@ class GraphService {
|
|
|
174
993
|
}
|
|
175
994
|
async findNodesByLabel(label, limit) {
|
|
176
995
|
try {
|
|
177
|
-
const
|
|
996
|
+
const conn = await this.ensureConnected();
|
|
178
997
|
const limitClause = limit ? ` LIMIT ${limit}` : "";
|
|
179
|
-
const
|
|
180
|
-
|
|
181
|
-
|
|
998
|
+
const reader = await conn.runAndReadAll(`
|
|
999
|
+
SELECT name, properties
|
|
1000
|
+
FROM nodes
|
|
1001
|
+
WHERE label = '${this.escape(label)}'${limitClause}
|
|
1002
|
+
`);
|
|
1003
|
+
return reader.getRows().map((row) => {
|
|
1004
|
+
const [name, properties] = row;
|
|
1005
|
+
const props = properties ? JSON.parse(properties) : {};
|
|
1006
|
+
return { name, ...props };
|
|
1007
|
+
});
|
|
182
1008
|
} catch (error) {
|
|
183
1009
|
this.logger.error(`Failed to find nodes by label: ${error instanceof Error ? error.message : String(error)}`);
|
|
184
1010
|
return [];
|
|
@@ -186,10 +1012,17 @@ class GraphService {
|
|
|
186
1012
|
}
|
|
187
1013
|
async findRelationships(nodeName) {
|
|
188
1014
|
try {
|
|
189
|
-
const
|
|
190
|
-
const
|
|
191
|
-
|
|
192
|
-
|
|
1015
|
+
const conn = await this.ensureConnected();
|
|
1016
|
+
const reader = await conn.runAndReadAll(`
|
|
1017
|
+
SELECT relation_type, target_name, source_name
|
|
1018
|
+
FROM relationships
|
|
1019
|
+
WHERE source_name = '${this.escape(nodeName)}'
|
|
1020
|
+
OR target_name = '${this.escape(nodeName)}'
|
|
1021
|
+
`);
|
|
1022
|
+
return reader.getRows().map((row) => {
|
|
1023
|
+
const [relType, targetName, sourceName] = row;
|
|
1024
|
+
return [relType, sourceName === nodeName ? targetName : sourceName];
|
|
1025
|
+
});
|
|
193
1026
|
} catch (error) {
|
|
194
1027
|
this.logger.error(`Failed to find relationships: ${error instanceof Error ? error.message : String(error)}`);
|
|
195
1028
|
return [];
|
|
@@ -197,27 +1030,39 @@ class GraphService {
|
|
|
197
1030
|
}
|
|
198
1031
|
async createVectorIndex(label, property, dimensions) {
|
|
199
1032
|
try {
|
|
200
|
-
const
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
1033
|
+
const indexKey = `${label}_${property}`;
|
|
1034
|
+
if (this.vectorIndexes.has(indexKey)) {
|
|
1035
|
+
return;
|
|
1036
|
+
}
|
|
1037
|
+
const conn = await this.ensureConnected();
|
|
1038
|
+
try {
|
|
1039
|
+
await conn.run(`
|
|
1040
|
+
CREATE INDEX idx_embedding_${this.escape(label)}
|
|
1041
|
+
ON nodes USING HNSW (embedding)
|
|
1042
|
+
WITH (metric = 'cosine')
|
|
1043
|
+
`);
|
|
1044
|
+
} catch {
|
|
1045
|
+
this.logger.debug(`Vector index on ${label}.${property} already exists`);
|
|
1046
|
+
}
|
|
1047
|
+
this.vectorIndexes.add(indexKey);
|
|
204
1048
|
this.logger.log(`Created vector index on ${label}.${property} with ${dimensions} dimensions`);
|
|
205
1049
|
} catch (error) {
|
|
206
1050
|
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
207
|
-
if (!errorMessage.includes("already
|
|
1051
|
+
if (!errorMessage.includes("already exists")) {
|
|
208
1052
|
this.logger.error(`Failed to create vector index: ${errorMessage}`);
|
|
209
1053
|
throw error;
|
|
210
1054
|
}
|
|
211
|
-
this.logger.debug(`Vector index on ${label}.${property} already exists`);
|
|
212
1055
|
}
|
|
213
1056
|
}
|
|
214
1057
|
async updateNodeEmbedding(label, name, embedding) {
|
|
215
1058
|
try {
|
|
216
|
-
const
|
|
217
|
-
const escapedName = this.escapeCypher(name);
|
|
1059
|
+
const conn = await this.ensureConnected();
|
|
218
1060
|
const vectorStr = `[${embedding.join(", ")}]`;
|
|
219
|
-
|
|
220
|
-
|
|
1061
|
+
await conn.run(`
|
|
1062
|
+
UPDATE nodes
|
|
1063
|
+
SET embedding = ${vectorStr}::FLOAT[${this.embeddingDimensions}]
|
|
1064
|
+
WHERE label = '${this.escape(label)}' AND name = '${this.escape(name)}'
|
|
1065
|
+
`);
|
|
221
1066
|
} catch (error) {
|
|
222
1067
|
this.logger.error(`Failed to update node embedding: ${error instanceof Error ? error.message : String(error)}`);
|
|
223
1068
|
throw error;
|
|
@@ -225,133 +1070,286 @@ class GraphService {
|
|
|
225
1070
|
}
|
|
226
1071
|
async vectorSearch(label, queryVector, k = 10) {
|
|
227
1072
|
try {
|
|
228
|
-
const
|
|
1073
|
+
const conn = await this.ensureConnected();
|
|
229
1074
|
const vectorStr = `[${queryVector.join(", ")}]`;
|
|
230
|
-
const
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
1075
|
+
const reader = await conn.runAndReadAll(`
|
|
1076
|
+
SELECT
|
|
1077
|
+
name,
|
|
1078
|
+
properties->>'title' as title,
|
|
1079
|
+
array_cosine_similarity(embedding, ${vectorStr}::FLOAT[${this.embeddingDimensions}]) as similarity
|
|
1080
|
+
FROM nodes
|
|
1081
|
+
WHERE label = '${this.escape(label)}'
|
|
1082
|
+
AND embedding IS NOT NULL
|
|
1083
|
+
ORDER BY similarity DESC
|
|
1084
|
+
LIMIT ${k}
|
|
1085
|
+
`);
|
|
1086
|
+
return reader.getRows().map((row) => {
|
|
1087
|
+
const [name, title, similarity] = row;
|
|
1088
|
+
return {
|
|
1089
|
+
name,
|
|
1090
|
+
title: title || undefined,
|
|
1091
|
+
score: similarity
|
|
1092
|
+
};
|
|
1093
|
+
});
|
|
237
1094
|
} catch (error) {
|
|
238
1095
|
this.logger.error(`Vector search failed: ${error instanceof Error ? error.message : String(error)}`);
|
|
239
1096
|
throw error;
|
|
240
1097
|
}
|
|
241
1098
|
}
|
|
242
1099
|
async vectorSearchAll(queryVector, k = 10) {
|
|
243
|
-
const allLabels = ["Document", "Concept", "Process", "Tool", "Technology", "Organization", "Topic", "Person"];
|
|
244
1100
|
const allResults = [];
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
1101
|
+
const conn = await this.ensureConnected();
|
|
1102
|
+
const vectorStr = `[${queryVector.join(", ")}]`;
|
|
1103
|
+
try {
|
|
1104
|
+
const reader = await conn.runAndReadAll(`
|
|
1105
|
+
SELECT
|
|
1106
|
+
name,
|
|
1107
|
+
label,
|
|
1108
|
+
properties->>'title' as title,
|
|
1109
|
+
properties->>'description' as description,
|
|
1110
|
+
array_cosine_similarity(embedding, ${vectorStr}::FLOAT[${this.embeddingDimensions}]) as similarity
|
|
1111
|
+
FROM nodes
|
|
1112
|
+
WHERE embedding IS NOT NULL
|
|
1113
|
+
ORDER BY similarity DESC
|
|
1114
|
+
LIMIT ${k}
|
|
1115
|
+
`);
|
|
1116
|
+
for (const row of reader.getRows()) {
|
|
1117
|
+
const [name, label, title, description, similarity] = row;
|
|
1118
|
+
allResults.push({
|
|
1119
|
+
name,
|
|
253
1120
|
label,
|
|
254
|
-
title:
|
|
255
|
-
description:
|
|
256
|
-
score:
|
|
257
|
-
})
|
|
258
|
-
allResults.push(...labelResults);
|
|
259
|
-
} catch (error) {
|
|
260
|
-
this.logger.debug(`Vector search for ${label} skipped: ${error instanceof Error ? error.message : String(error)}`);
|
|
1121
|
+
title: title || undefined,
|
|
1122
|
+
description: description || undefined,
|
|
1123
|
+
score: similarity
|
|
1124
|
+
});
|
|
261
1125
|
}
|
|
1126
|
+
} catch (error) {
|
|
1127
|
+
this.logger.debug(`Vector search failed: ${error instanceof Error ? error.message : String(error)}`);
|
|
262
1128
|
}
|
|
263
1129
|
return allResults.sort((a, b) => b.score - a.score).slice(0, k);
|
|
264
1130
|
}
|
|
265
|
-
|
|
266
|
-
return value.replace(
|
|
1131
|
+
escape(value) {
|
|
1132
|
+
return value.replace(/'/g, "''");
|
|
267
1133
|
}
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
return `{${pairs}}`;
|
|
285
|
-
}
|
|
286
|
-
return String(value);
|
|
1134
|
+
}
|
|
1135
|
+
GraphService = __legacyDecorateClassTS([
|
|
1136
|
+
Injectable6(),
|
|
1137
|
+
__legacyMetadataTS("design:paramtypes", [
|
|
1138
|
+
typeof ConfigService2 === "undefined" ? Object : ConfigService2
|
|
1139
|
+
])
|
|
1140
|
+
], GraphService);
|
|
1141
|
+
|
|
1142
|
+
// src/commands/query.command.ts
|
|
1143
|
+
class SearchCommand extends CommandRunner3 {
|
|
1144
|
+
graphService;
|
|
1145
|
+
embeddingService;
|
|
1146
|
+
constructor(graphService, embeddingService) {
|
|
1147
|
+
super();
|
|
1148
|
+
this.graphService = graphService;
|
|
1149
|
+
this.embeddingService = embeddingService;
|
|
287
1150
|
}
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
1151
|
+
async run(inputs, options) {
|
|
1152
|
+
const query = inputs[0];
|
|
1153
|
+
const limit = Math.min(parseInt(options.limit || "20", 10), 100);
|
|
1154
|
+
try {
|
|
1155
|
+
const queryEmbedding = await this.embeddingService.generateEmbedding(query);
|
|
1156
|
+
let results;
|
|
1157
|
+
if (options.label) {
|
|
1158
|
+
const labelResults = await this.graphService.vectorSearch(options.label, queryEmbedding, limit);
|
|
1159
|
+
results = labelResults.map((r) => ({
|
|
1160
|
+
name: r.name,
|
|
1161
|
+
label: options.label,
|
|
1162
|
+
title: r.title,
|
|
1163
|
+
score: r.score
|
|
1164
|
+
}));
|
|
1165
|
+
} else {
|
|
1166
|
+
results = await this.graphService.vectorSearchAll(queryEmbedding, limit);
|
|
1167
|
+
}
|
|
1168
|
+
const labelSuffix = options.label ? ` (${options.label})` : "";
|
|
1169
|
+
console.log(`
|
|
1170
|
+
=== Semantic Search Results for "${query}"${labelSuffix} ===
|
|
1171
|
+
`);
|
|
1172
|
+
if (results.length === 0) {
|
|
1173
|
+
console.log(`No results found.
|
|
1174
|
+
`);
|
|
1175
|
+
if (options.label) {
|
|
1176
|
+
console.log(`Tip: Try without --label to search all entity types.
|
|
1177
|
+
`);
|
|
1178
|
+
}
|
|
1179
|
+
process.exit(0);
|
|
1180
|
+
}
|
|
1181
|
+
results.forEach((result, idx) => {
|
|
1182
|
+
console.log(`${idx + 1}. [${result.label}] ${result.name}`);
|
|
1183
|
+
if (result.title) {
|
|
1184
|
+
console.log(` Title: ${result.title}`);
|
|
1185
|
+
}
|
|
1186
|
+
if (result.description && result.label !== "Document") {
|
|
1187
|
+
const desc = result.description.length > 80 ? `${result.description.slice(0, 80)}...` : result.description;
|
|
1188
|
+
console.log(` ${desc}`);
|
|
1189
|
+
}
|
|
1190
|
+
console.log(` Similarity: ${(result.score * 100).toFixed(2)}%`);
|
|
1191
|
+
});
|
|
1192
|
+
console.log();
|
|
1193
|
+
process.exit(0);
|
|
1194
|
+
} catch (error) {
|
|
1195
|
+
const errorMsg = error instanceof Error ? error.message : String(error);
|
|
1196
|
+
console.error("Error:", errorMsg);
|
|
1197
|
+
if (errorMsg.includes("no embeddings") || errorMsg.includes("vector")) {
|
|
1198
|
+
console.log(`
|
|
1199
|
+
Note: Semantic search requires embeddings to be generated first.`);
|
|
1200
|
+
console.log(`Run 'lattice sync' to generate embeddings for documents.
|
|
1201
|
+
`);
|
|
1202
|
+
}
|
|
1203
|
+
process.exit(1);
|
|
318
1204
|
}
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
1205
|
+
}
|
|
1206
|
+
parseLabel(value) {
|
|
1207
|
+
return value;
|
|
1208
|
+
}
|
|
1209
|
+
parseLimit(value) {
|
|
1210
|
+
return value;
|
|
1211
|
+
}
|
|
1212
|
+
}
|
|
1213
|
+
__legacyDecorateClassTS([
|
|
1214
|
+
Option2({
|
|
1215
|
+
flags: "-l, --label <label>",
|
|
1216
|
+
description: "Filter by entity label (e.g., Technology, Concept, Document)"
|
|
1217
|
+
}),
|
|
1218
|
+
__legacyMetadataTS("design:type", Function),
|
|
1219
|
+
__legacyMetadataTS("design:paramtypes", [
|
|
1220
|
+
String
|
|
1221
|
+
]),
|
|
1222
|
+
__legacyMetadataTS("design:returntype", String)
|
|
1223
|
+
], SearchCommand.prototype, "parseLabel", null);
|
|
1224
|
+
__legacyDecorateClassTS([
|
|
1225
|
+
Option2({
|
|
1226
|
+
flags: "--limit <n>",
|
|
1227
|
+
description: "Limit results",
|
|
1228
|
+
defaultValue: "20"
|
|
1229
|
+
}),
|
|
1230
|
+
__legacyMetadataTS("design:type", Function),
|
|
1231
|
+
__legacyMetadataTS("design:paramtypes", [
|
|
1232
|
+
String
|
|
1233
|
+
]),
|
|
1234
|
+
__legacyMetadataTS("design:returntype", String)
|
|
1235
|
+
], SearchCommand.prototype, "parseLimit", null);
|
|
1236
|
+
SearchCommand = __legacyDecorateClassTS([
|
|
1237
|
+
Injectable7(),
|
|
1238
|
+
Command3({
|
|
1239
|
+
name: "search",
|
|
1240
|
+
arguments: "<query>",
|
|
1241
|
+
description: "Semantic search across the knowledge graph"
|
|
1242
|
+
}),
|
|
1243
|
+
__legacyMetadataTS("design:paramtypes", [
|
|
1244
|
+
typeof GraphService === "undefined" ? Object : GraphService,
|
|
1245
|
+
typeof EmbeddingService === "undefined" ? Object : EmbeddingService
|
|
1246
|
+
])
|
|
1247
|
+
], SearchCommand);
|
|
1248
|
+
|
|
1249
|
+
class RelsCommand extends CommandRunner3 {
|
|
1250
|
+
graphService;
|
|
1251
|
+
constructor(graphService) {
|
|
1252
|
+
super();
|
|
1253
|
+
this.graphService = graphService;
|
|
1254
|
+
}
|
|
1255
|
+
async run(inputs) {
|
|
1256
|
+
const name = inputs[0];
|
|
1257
|
+
try {
|
|
1258
|
+
const relationships = await this.graphService.findRelationships(name);
|
|
1259
|
+
console.log(`
|
|
1260
|
+
=== Relationships for "${name}" ===
|
|
1261
|
+
`);
|
|
1262
|
+
if (relationships.length === 0) {
|
|
1263
|
+
console.log(`No relationships found.
|
|
1264
|
+
`);
|
|
1265
|
+
process.exit(0);
|
|
1266
|
+
}
|
|
1267
|
+
console.log("Relationships:");
|
|
1268
|
+
for (const rel of relationships) {
|
|
1269
|
+
const [relType, targetName] = rel;
|
|
1270
|
+
console.log(` -[${relType}]-> ${targetName}`);
|
|
1271
|
+
}
|
|
1272
|
+
console.log();
|
|
1273
|
+
process.exit(0);
|
|
1274
|
+
} catch (error) {
|
|
1275
|
+
console.error("Error:", error instanceof Error ? error.message : String(error));
|
|
1276
|
+
process.exit(1);
|
|
322
1277
|
}
|
|
323
|
-
return stats;
|
|
324
1278
|
}
|
|
325
1279
|
}
|
|
326
|
-
|
|
327
|
-
|
|
1280
|
+
RelsCommand = __legacyDecorateClassTS([
|
|
1281
|
+
Injectable7(),
|
|
1282
|
+
Command3({
|
|
1283
|
+
name: "rels",
|
|
1284
|
+
arguments: "<name>",
|
|
1285
|
+
description: "Show relationships for a node"
|
|
1286
|
+
}),
|
|
1287
|
+
__legacyMetadataTS("design:paramtypes", [
|
|
1288
|
+
typeof GraphService === "undefined" ? Object : GraphService
|
|
1289
|
+
])
|
|
1290
|
+
], RelsCommand);
|
|
1291
|
+
|
|
1292
|
+
class SqlCommand extends CommandRunner3 {
|
|
1293
|
+
graphService;
|
|
1294
|
+
constructor(graphService) {
|
|
1295
|
+
super();
|
|
1296
|
+
this.graphService = graphService;
|
|
1297
|
+
}
|
|
1298
|
+
async run(inputs) {
|
|
1299
|
+
const query = inputs[0];
|
|
1300
|
+
try {
|
|
1301
|
+
const result = await this.graphService.query(query);
|
|
1302
|
+
console.log(`
|
|
1303
|
+
=== SQL Query Results ===
|
|
1304
|
+
`);
|
|
1305
|
+
const replacer = (_key, value) => typeof value === "bigint" ? Number(value) : value;
|
|
1306
|
+
console.log(JSON.stringify(result, replacer, 2));
|
|
1307
|
+
console.log();
|
|
1308
|
+
process.exit(0);
|
|
1309
|
+
} catch (error) {
|
|
1310
|
+
console.error("Error:", error instanceof Error ? error.message : String(error));
|
|
1311
|
+
process.exit(1);
|
|
1312
|
+
}
|
|
1313
|
+
}
|
|
1314
|
+
}
|
|
1315
|
+
SqlCommand = __legacyDecorateClassTS([
|
|
1316
|
+
Injectable7(),
|
|
1317
|
+
Command3({
|
|
1318
|
+
name: "sql",
|
|
1319
|
+
arguments: "<query>",
|
|
1320
|
+
description: "Execute raw SQL query against DuckDB"
|
|
1321
|
+
}),
|
|
328
1322
|
__legacyMetadataTS("design:paramtypes", [
|
|
329
|
-
typeof
|
|
1323
|
+
typeof GraphService === "undefined" ? Object : GraphService
|
|
330
1324
|
])
|
|
331
|
-
],
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
}
|
|
336
|
-
GraphModule = __legacyDecorateClassTS([
|
|
337
|
-
Module({
|
|
338
|
-
providers: [GraphService],
|
|
339
|
-
exports: [GraphService]
|
|
340
|
-
})
|
|
341
|
-
], GraphModule);
|
|
1325
|
+
], SqlCommand);
|
|
1326
|
+
// src/commands/status.command.ts
|
|
1327
|
+
import { Injectable as Injectable12 } from "@nestjs/common";
|
|
1328
|
+
import { Command as Command4, CommandRunner as CommandRunner4, Option as Option3 } from "nest-commander";
|
|
342
1329
|
|
|
343
|
-
// src/sync/
|
|
344
|
-
import {
|
|
1330
|
+
// src/sync/manifest.service.ts
|
|
1331
|
+
import { createHash as createHash2 } from "crypto";
|
|
1332
|
+
import { existsSync } from "fs";
|
|
1333
|
+
import { readFile as readFile3, writeFile } from "fs/promises";
|
|
1334
|
+
import { resolve as resolve3 } from "path";
|
|
1335
|
+
import { Injectable as Injectable8 } from "@nestjs/common";
|
|
345
1336
|
|
|
346
|
-
// src/
|
|
347
|
-
import {
|
|
1337
|
+
// src/schemas/manifest.schemas.ts
|
|
1338
|
+
import { z as z4 } from "zod";
|
|
1339
|
+
var ManifestEntrySchema = z4.object({
|
|
1340
|
+
contentHash: z4.string(),
|
|
1341
|
+
frontmatterHash: z4.string(),
|
|
1342
|
+
lastSynced: z4.string(),
|
|
1343
|
+
entityCount: z4.number().int().nonnegative(),
|
|
1344
|
+
relationshipCount: z4.number().int().nonnegative()
|
|
1345
|
+
});
|
|
1346
|
+
var SyncManifestSchema = z4.object({
|
|
1347
|
+
version: z4.string(),
|
|
1348
|
+
lastSync: z4.string(),
|
|
1349
|
+
documents: z4.record(z4.string(), ManifestEntrySchema)
|
|
1350
|
+
});
|
|
348
1351
|
|
|
349
1352
|
// 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";
|
|
353
|
-
import { existsSync } from "fs";
|
|
354
|
-
import { resolve } from "path";
|
|
355
1353
|
function getProjectRoot() {
|
|
356
1354
|
if (process.env.PROJECT_ROOT) {
|
|
357
1355
|
return process.env.PROJECT_ROOT;
|
|
@@ -364,17 +1362,17 @@ class ManifestService {
|
|
|
364
1362
|
manifest = null;
|
|
365
1363
|
constructor() {
|
|
366
1364
|
const docsPath = process.env.DOCS_PATH || "docs";
|
|
367
|
-
this.manifestPath =
|
|
1365
|
+
this.manifestPath = resolve3(getProjectRoot(), docsPath, ".sync-manifest.json");
|
|
368
1366
|
}
|
|
369
1367
|
async load() {
|
|
370
1368
|
try {
|
|
371
1369
|
if (existsSync(this.manifestPath)) {
|
|
372
|
-
const content = await
|
|
373
|
-
this.manifest = JSON.parse(content);
|
|
1370
|
+
const content = await readFile3(this.manifestPath, "utf-8");
|
|
1371
|
+
this.manifest = SyncManifestSchema.parse(JSON.parse(content));
|
|
374
1372
|
} else {
|
|
375
1373
|
this.manifest = this.createEmptyManifest();
|
|
376
1374
|
}
|
|
377
|
-
} catch (
|
|
1375
|
+
} catch (_error) {
|
|
378
1376
|
this.manifest = this.createEmptyManifest();
|
|
379
1377
|
}
|
|
380
1378
|
return this.manifest;
|
|
@@ -388,13 +1386,13 @@ class ManifestService {
|
|
|
388
1386
|
await writeFile(this.manifestPath, content, "utf-8");
|
|
389
1387
|
}
|
|
390
1388
|
getContentHash(content) {
|
|
391
|
-
return
|
|
1389
|
+
return createHash2("sha256").update(content).digest("hex");
|
|
392
1390
|
}
|
|
393
|
-
detectChange(
|
|
1391
|
+
detectChange(path2, contentHash, frontmatterHash) {
|
|
394
1392
|
if (!this.manifest) {
|
|
395
1393
|
throw new Error("Manifest not loaded. Call load() first.");
|
|
396
1394
|
}
|
|
397
|
-
const existing = this.manifest.documents[
|
|
1395
|
+
const existing = this.manifest.documents[path2];
|
|
398
1396
|
if (!existing) {
|
|
399
1397
|
return "new";
|
|
400
1398
|
}
|
|
@@ -403,11 +1401,11 @@ class ManifestService {
|
|
|
403
1401
|
}
|
|
404
1402
|
return "updated";
|
|
405
1403
|
}
|
|
406
|
-
updateEntry(
|
|
1404
|
+
updateEntry(path2, contentHash, frontmatterHash, entityCount, relationshipCount) {
|
|
407
1405
|
if (!this.manifest) {
|
|
408
1406
|
throw new Error("Manifest not loaded. Call load() first.");
|
|
409
1407
|
}
|
|
410
|
-
this.manifest.documents[
|
|
1408
|
+
this.manifest.documents[path2] = {
|
|
411
1409
|
contentHash,
|
|
412
1410
|
frontmatterHash,
|
|
413
1411
|
lastSynced: new Date().toISOString(),
|
|
@@ -415,11 +1413,11 @@ class ManifestService {
|
|
|
415
1413
|
relationshipCount
|
|
416
1414
|
};
|
|
417
1415
|
}
|
|
418
|
-
removeEntry(
|
|
1416
|
+
removeEntry(path2) {
|
|
419
1417
|
if (!this.manifest) {
|
|
420
1418
|
throw new Error("Manifest not loaded. Call load() first.");
|
|
421
1419
|
}
|
|
422
|
-
delete this.manifest.documents[
|
|
1420
|
+
delete this.manifest.documents[path2];
|
|
423
1421
|
}
|
|
424
1422
|
getTrackedPaths() {
|
|
425
1423
|
if (!this.manifest) {
|
|
@@ -436,291 +1434,119 @@ class ManifestService {
|
|
|
436
1434
|
}
|
|
437
1435
|
}
|
|
438
1436
|
ManifestService = __legacyDecorateClassTS([
|
|
439
|
-
|
|
1437
|
+
Injectable8(),
|
|
440
1438
|
__legacyMetadataTS("design:paramtypes", [])
|
|
441
1439
|
], ManifestService);
|
|
442
1440
|
|
|
443
|
-
// src/sync/
|
|
444
|
-
import { Injectable as
|
|
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";
|
|
1441
|
+
// src/sync/sync.service.ts
|
|
1442
|
+
import { Injectable as Injectable11, Logger as Logger5 } from "@nestjs/common";
|
|
449
1443
|
|
|
450
|
-
// src/
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
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;
|
|
1444
|
+
// src/pure/embedding-text.ts
|
|
1445
|
+
function composeDocumentEmbeddingText(doc) {
|
|
1446
|
+
const parts = [];
|
|
1447
|
+
if (doc.title) {
|
|
1448
|
+
parts.push(`Title: ${doc.title}`);
|
|
495
1449
|
}
|
|
496
|
-
|
|
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}`);
|
|
1450
|
+
if (doc.topic) {
|
|
1451
|
+
parts.push(`Topic: ${doc.topic}`);
|
|
529
1452
|
}
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
if (data instanceof Date) {
|
|
533
|
-
return data.toISOString().split("T")[0];
|
|
1453
|
+
if (doc.tags && doc.tags.length > 0) {
|
|
1454
|
+
parts.push(`Tags: ${doc.tags.join(", ")}`);
|
|
534
1455
|
}
|
|
535
|
-
if (
|
|
536
|
-
|
|
1456
|
+
if (doc.entities && doc.entities.length > 0) {
|
|
1457
|
+
const entityNames = doc.entities.map((e) => e.name).join(", ");
|
|
1458
|
+
parts.push(`Entities: ${entityNames}`);
|
|
537
1459
|
}
|
|
538
|
-
if (
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
}
|
|
543
|
-
return normalized;
|
|
1460
|
+
if (doc.summary) {
|
|
1461
|
+
parts.push(doc.summary);
|
|
1462
|
+
} else {
|
|
1463
|
+
parts.push(doc.content.slice(0, 500));
|
|
544
1464
|
}
|
|
545
|
-
return
|
|
1465
|
+
return parts.join(" | ");
|
|
546
1466
|
}
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
return process.env.PROJECT_ROOT;
|
|
1467
|
+
function composeEntityEmbeddingText(entity) {
|
|
1468
|
+
const parts = [`${entity.type}: ${entity.name}`];
|
|
1469
|
+
if (entity.description) {
|
|
1470
|
+
parts.push(entity.description);
|
|
552
1471
|
}
|
|
553
|
-
return
|
|
1472
|
+
return parts.join(". ");
|
|
554
1473
|
}
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
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);
|
|
1474
|
+
function collectUniqueEntities(docs) {
|
|
1475
|
+
const entities = new Map;
|
|
1476
|
+
for (const doc of docs) {
|
|
1477
|
+
for (const entity of doc.entities) {
|
|
1478
|
+
const key = `${entity.type}:${entity.name}`;
|
|
1479
|
+
const existing = entities.get(key);
|
|
1480
|
+
if (!existing) {
|
|
1481
|
+
entities.set(key, {
|
|
1482
|
+
type: entity.type,
|
|
1483
|
+
name: entity.name,
|
|
1484
|
+
description: entity.description,
|
|
1485
|
+
documentPaths: [doc.path]
|
|
1486
|
+
});
|
|
642
1487
|
} else {
|
|
643
|
-
|
|
644
|
-
|
|
1488
|
+
existing.documentPaths.push(doc.path);
|
|
1489
|
+
if (entity.description && (!existing.description || entity.description.length > existing.description.length)) {
|
|
1490
|
+
existing.description = entity.description;
|
|
1491
|
+
}
|
|
645
1492
|
}
|
|
646
1493
|
}
|
|
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
1494
|
}
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
for (
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
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
|
-
}
|
|
1495
|
+
return entities;
|
|
1496
|
+
}
|
|
1497
|
+
// src/pure/validation.ts
|
|
1498
|
+
function validateDocuments(docs) {
|
|
1499
|
+
const errors = [];
|
|
1500
|
+
const entityIndex = new Map;
|
|
1501
|
+
for (const doc of docs) {
|
|
1502
|
+
for (const entity of doc.entities) {
|
|
1503
|
+
let docSet = entityIndex.get(entity.name);
|
|
1504
|
+
if (!docSet) {
|
|
1505
|
+
docSet = new Set;
|
|
1506
|
+
entityIndex.set(entity.name, docSet);
|
|
692
1507
|
}
|
|
1508
|
+
docSet.add(doc.path);
|
|
693
1509
|
}
|
|
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
1510
|
}
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
1511
|
+
for (const doc of docs) {
|
|
1512
|
+
for (const rel of doc.relationships) {
|
|
1513
|
+
if (rel.source !== doc.path && !entityIndex.has(rel.source)) {
|
|
1514
|
+
errors.push({
|
|
1515
|
+
path: doc.path,
|
|
1516
|
+
error: `Relationship source "${rel.source}" not found in any document`
|
|
1517
|
+
});
|
|
1518
|
+
}
|
|
1519
|
+
const isDocPath = rel.target.endsWith(".md");
|
|
1520
|
+
const isKnownEntity = entityIndex.has(rel.target);
|
|
1521
|
+
const isSelfReference = rel.target === doc.path;
|
|
1522
|
+
if (!isDocPath && !isKnownEntity && !isSelfReference) {
|
|
1523
|
+
errors.push({
|
|
1524
|
+
path: doc.path,
|
|
1525
|
+
error: `Relationship target "${rel.target}" not found as entity`
|
|
1526
|
+
});
|
|
1527
|
+
}
|
|
705
1528
|
}
|
|
706
|
-
const result = GraphMetadataSchema.safeParse(frontmatter.graph);
|
|
707
|
-
return result.success ? result.data : undefined;
|
|
708
1529
|
}
|
|
709
|
-
|
|
710
|
-
|
|
1530
|
+
return errors;
|
|
1531
|
+
}
|
|
1532
|
+
function getChangeReason(changeType) {
|
|
1533
|
+
switch (changeType) {
|
|
1534
|
+
case "new":
|
|
1535
|
+
return "New document";
|
|
1536
|
+
case "updated":
|
|
1537
|
+
return "Content or frontmatter changed";
|
|
1538
|
+
case "deleted":
|
|
1539
|
+
return "File no longer exists";
|
|
1540
|
+
case "unchanged":
|
|
1541
|
+
return "No changes detected";
|
|
711
1542
|
}
|
|
712
1543
|
}
|
|
713
|
-
DocumentParserService = __legacyDecorateClassTS([
|
|
714
|
-
Injectable3(),
|
|
715
|
-
__legacyMetadataTS("design:paramtypes", [])
|
|
716
|
-
], DocumentParserService);
|
|
717
|
-
|
|
718
1544
|
// src/sync/cascade.service.ts
|
|
719
|
-
import { Injectable as
|
|
1545
|
+
import { Injectable as Injectable9, Logger as Logger4 } from "@nestjs/common";
|
|
720
1546
|
class CascadeService {
|
|
721
1547
|
graph;
|
|
722
1548
|
_parser;
|
|
723
|
-
logger = new
|
|
1549
|
+
logger = new Logger4(CascadeService.name);
|
|
724
1550
|
constructor(graph, _parser) {
|
|
725
1551
|
this.graph = graph;
|
|
726
1552
|
this._parser = _parser;
|
|
@@ -828,10 +1654,13 @@ class CascadeService {
|
|
|
828
1654
|
}
|
|
829
1655
|
async findAffectedByRename(entityName, _newName) {
|
|
830
1656
|
try {
|
|
831
|
-
const escapedName = this.
|
|
1657
|
+
const escapedName = this.escapeForSql(entityName);
|
|
832
1658
|
const query = `
|
|
833
|
-
|
|
834
|
-
|
|
1659
|
+
SELECT DISTINCT n.name, n.properties->>'title' as title
|
|
1660
|
+
FROM nodes n
|
|
1661
|
+
INNER JOIN relationships r ON r.target_label = n.label AND r.target_name = n.name
|
|
1662
|
+
WHERE r.source_name = '${escapedName}'
|
|
1663
|
+
AND n.label = 'Document'
|
|
835
1664
|
`.trim();
|
|
836
1665
|
const result = await this.graph.query(query);
|
|
837
1666
|
return (result.resultSet || []).map((row) => ({
|
|
@@ -848,10 +1677,13 @@ class CascadeService {
|
|
|
848
1677
|
}
|
|
849
1678
|
async findAffectedByDeletion(entityName) {
|
|
850
1679
|
try {
|
|
851
|
-
const escapedName = this.
|
|
1680
|
+
const escapedName = this.escapeForSql(entityName);
|
|
852
1681
|
const query = `
|
|
853
|
-
|
|
854
|
-
|
|
1682
|
+
SELECT DISTINCT n.name, n.properties->>'title' as title
|
|
1683
|
+
FROM nodes n
|
|
1684
|
+
INNER JOIN relationships r ON r.target_label = n.label AND r.target_name = n.name
|
|
1685
|
+
WHERE r.source_name = '${escapedName}'
|
|
1686
|
+
AND n.label = 'Document'
|
|
855
1687
|
`.trim();
|
|
856
1688
|
const result = await this.graph.query(query);
|
|
857
1689
|
return (result.resultSet || []).map((row) => ({
|
|
@@ -868,10 +1700,13 @@ class CascadeService {
|
|
|
868
1700
|
}
|
|
869
1701
|
async findAffectedByTypeChange(entityName, oldType, newType) {
|
|
870
1702
|
try {
|
|
871
|
-
const escapedName = this.
|
|
1703
|
+
const escapedName = this.escapeForSql(entityName);
|
|
872
1704
|
const query = `
|
|
873
|
-
|
|
874
|
-
|
|
1705
|
+
SELECT DISTINCT n.name, n.properties->>'title' as title
|
|
1706
|
+
FROM nodes n
|
|
1707
|
+
INNER JOIN relationships r ON r.target_label = n.label AND r.target_name = n.name
|
|
1708
|
+
WHERE r.source_name = '${escapedName}'
|
|
1709
|
+
AND n.label = 'Document'
|
|
875
1710
|
`.trim();
|
|
876
1711
|
const result = await this.graph.query(query);
|
|
877
1712
|
return (result.resultSet || []).map((row) => ({
|
|
@@ -888,10 +1723,13 @@ class CascadeService {
|
|
|
888
1723
|
}
|
|
889
1724
|
async findAffectedByRelationshipChange(entityName) {
|
|
890
1725
|
try {
|
|
891
|
-
const escapedName = this.
|
|
1726
|
+
const escapedName = this.escapeForSql(entityName);
|
|
892
1727
|
const query = `
|
|
893
|
-
|
|
894
|
-
|
|
1728
|
+
SELECT DISTINCT n.name, n.properties->>'title' as title, r.relation_type
|
|
1729
|
+
FROM nodes n
|
|
1730
|
+
INNER JOIN relationships r ON r.target_label = n.label AND r.target_name = n.name
|
|
1731
|
+
WHERE r.source_name = '${escapedName}'
|
|
1732
|
+
AND n.label = 'Document'
|
|
895
1733
|
`.trim();
|
|
896
1734
|
const result = await this.graph.query(query);
|
|
897
1735
|
return (result.resultSet || []).map((row) => ({
|
|
@@ -908,10 +1746,13 @@ class CascadeService {
|
|
|
908
1746
|
}
|
|
909
1747
|
async findAffectedByDocumentDeletion(documentPath) {
|
|
910
1748
|
try {
|
|
911
|
-
const escapedPath = this.
|
|
1749
|
+
const escapedPath = this.escapeForSql(documentPath);
|
|
912
1750
|
const query = `
|
|
913
|
-
|
|
914
|
-
|
|
1751
|
+
SELECT DISTINCT n.name, r.relation_type
|
|
1752
|
+
FROM nodes n
|
|
1753
|
+
INNER JOIN relationships r ON r.source_label = n.label AND r.source_name = n.name
|
|
1754
|
+
WHERE r.target_name = '${escapedPath}'
|
|
1755
|
+
AND n.label = 'Document'
|
|
915
1756
|
`.trim();
|
|
916
1757
|
const result = await this.graph.query(query);
|
|
917
1758
|
return (result.resultSet || []).map((row) => ({
|
|
@@ -998,12 +1839,12 @@ class CascadeService {
|
|
|
998
1839
|
return action;
|
|
999
1840
|
}
|
|
1000
1841
|
}
|
|
1001
|
-
|
|
1002
|
-
return value.replace(
|
|
1842
|
+
escapeForSql(value) {
|
|
1843
|
+
return value.replace(/'/g, "''");
|
|
1003
1844
|
}
|
|
1004
1845
|
}
|
|
1005
1846
|
CascadeService = __legacyDecorateClassTS([
|
|
1006
|
-
|
|
1847
|
+
Injectable9(),
|
|
1007
1848
|
__legacyMetadataTS("design:paramtypes", [
|
|
1008
1849
|
typeof GraphService === "undefined" ? Object : GraphService,
|
|
1009
1850
|
typeof DocumentParserService === "undefined" ? Object : DocumentParserService
|
|
@@ -1011,12 +1852,11 @@ CascadeService = __legacyDecorateClassTS([
|
|
|
1011
1852
|
], CascadeService);
|
|
1012
1853
|
|
|
1013
1854
|
// 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
1855
|
import { existsSync as existsSync2 } from "fs";
|
|
1856
|
+
import { isAbsolute, resolve as resolve4 } from "path";
|
|
1857
|
+
import { Injectable as Injectable10 } from "@nestjs/common";
|
|
1017
1858
|
class PathResolverService {
|
|
1018
1859
|
parser;
|
|
1019
|
-
logger = new Logger4(PathResolverService.name);
|
|
1020
1860
|
constructor(parser) {
|
|
1021
1861
|
this.parser = parser;
|
|
1022
1862
|
}
|
|
@@ -1029,10 +1869,10 @@ class PathResolverService {
|
|
|
1029
1869
|
if (isAbsolute(userPath)) {
|
|
1030
1870
|
resolvedPath = userPath;
|
|
1031
1871
|
} else {
|
|
1032
|
-
const fromCwd =
|
|
1033
|
-
const fromDocs =
|
|
1872
|
+
const fromCwd = resolve4(process.cwd(), userPath);
|
|
1873
|
+
const fromDocs = resolve4(this.getDocsPath(), userPath);
|
|
1034
1874
|
const docsPrefix = "docs/";
|
|
1035
|
-
const strippedFromDocs = userPath.startsWith(docsPrefix) ?
|
|
1875
|
+
const strippedFromDocs = userPath.startsWith(docsPrefix) ? resolve4(this.getDocsPath(), userPath.slice(docsPrefix.length)) : null;
|
|
1036
1876
|
if (this.isUnderDocs(fromCwd) && existsSync2(fromCwd)) {
|
|
1037
1877
|
resolvedPath = fromCwd;
|
|
1038
1878
|
} else if (strippedFromDocs && existsSync2(strippedFromDocs)) {
|
|
@@ -1053,268 +1893,31 @@ class PathResolverService {
|
|
|
1053
1893
|
if (requireExists && !existsSync2(resolvedPath)) {
|
|
1054
1894
|
throw new Error(`Path "${userPath}" does not exist (resolved to: ${resolvedPath})`);
|
|
1055
1895
|
}
|
|
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);
|
|
1137
|
-
} catch (error) {
|
|
1138
|
-
if (error instanceof Error) {
|
|
1139
|
-
throw new Error(`Failed to generate embeddings: ${error.message}`);
|
|
1140
|
-
}
|
|
1141
|
-
throw error;
|
|
1142
|
-
}
|
|
1143
|
-
}
|
|
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.");
|
|
1158
|
-
}
|
|
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
|
-
}
|
|
1164
|
-
async generateEmbedding(text) {
|
|
1165
|
-
const embeddings = await this.generateEmbeddings([text]);
|
|
1166
|
-
return embeddings[0];
|
|
1167
|
-
}
|
|
1168
|
-
async generateEmbeddings(texts) {
|
|
1169
|
-
if (!texts || texts.length === 0) {
|
|
1170
|
-
return [];
|
|
1171
|
-
}
|
|
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}`);
|
|
1196
|
-
}
|
|
1197
|
-
throw error;
|
|
1198
|
-
}
|
|
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;
|
|
1221
|
-
}
|
|
1222
|
-
for (let i = 0;i < this.dimensions; i++) {
|
|
1223
|
-
const seed = hash * (i + 1) ^ i * 31;
|
|
1224
|
-
embedding[i] = (seed % 2000 - 1000) / 1000;
|
|
1225
|
-
}
|
|
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}`);
|
|
1242
|
-
}
|
|
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");
|
|
1253
|
-
}
|
|
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.");
|
|
1266
|
-
}
|
|
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.");
|
|
1277
|
-
}
|
|
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'.`);
|
|
1285
|
-
default:
|
|
1286
|
-
throw new Error(`Unknown embedding provider: ${this.config.provider}. Use 'voyage', 'openai', or 'mock'.`);
|
|
1287
|
-
}
|
|
1288
|
-
}
|
|
1289
|
-
getProviderName() {
|
|
1290
|
-
return this.provider.name;
|
|
1896
|
+
return resolvedPath;
|
|
1291
1897
|
}
|
|
1292
|
-
|
|
1293
|
-
return this.
|
|
1898
|
+
resolveDocPaths(userPaths, options = {}) {
|
|
1899
|
+
return userPaths.map((p) => this.resolveDocPath(p, options));
|
|
1294
1900
|
}
|
|
1295
|
-
|
|
1296
|
-
|
|
1297
|
-
|
|
1298
|
-
|
|
1299
|
-
return
|
|
1901
|
+
isUnderDocs(absolutePath) {
|
|
1902
|
+
const docsPath = this.getDocsPath();
|
|
1903
|
+
const normalizedPath = absolutePath.replace(/\/$/, "");
|
|
1904
|
+
const normalizedDocs = docsPath.replace(/\/$/, "");
|
|
1905
|
+
return normalizedPath.startsWith(`${normalizedDocs}/`) || normalizedPath === normalizedDocs;
|
|
1300
1906
|
}
|
|
1301
|
-
|
|
1302
|
-
const
|
|
1303
|
-
if (
|
|
1304
|
-
return
|
|
1907
|
+
getRelativePath(absolutePath) {
|
|
1908
|
+
const docsPath = this.getDocsPath();
|
|
1909
|
+
if (absolutePath.startsWith(docsPath)) {
|
|
1910
|
+
return absolutePath.slice(docsPath.length + 1);
|
|
1305
1911
|
}
|
|
1306
|
-
return
|
|
1307
|
-
}
|
|
1308
|
-
isRealProvider() {
|
|
1309
|
-
return this.provider.name !== "mock";
|
|
1912
|
+
return absolutePath;
|
|
1310
1913
|
}
|
|
1311
1914
|
}
|
|
1312
|
-
|
|
1313
|
-
|
|
1915
|
+
PathResolverService = __legacyDecorateClassTS([
|
|
1916
|
+
Injectable10(),
|
|
1314
1917
|
__legacyMetadataTS("design:paramtypes", [
|
|
1315
|
-
typeof
|
|
1918
|
+
typeof DocumentParserService === "undefined" ? Object : DocumentParserService
|
|
1316
1919
|
])
|
|
1317
|
-
],
|
|
1920
|
+
], PathResolverService);
|
|
1318
1921
|
|
|
1319
1922
|
// src/sync/sync.service.ts
|
|
1320
1923
|
var ENTITY_TYPES = [
|
|
@@ -1326,37 +1929,8 @@ var ENTITY_TYPES = [
|
|
|
1326
1929
|
"Person",
|
|
1327
1930
|
"Organization"
|
|
1328
1931
|
];
|
|
1329
|
-
function
|
|
1330
|
-
|
|
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;
|
|
1932
|
+
function validateDocuments2(docs) {
|
|
1933
|
+
return validateDocuments(docs);
|
|
1360
1934
|
}
|
|
1361
1935
|
|
|
1362
1936
|
class SyncService {
|
|
@@ -1366,7 +1940,7 @@ class SyncService {
|
|
|
1366
1940
|
cascade;
|
|
1367
1941
|
pathResolver;
|
|
1368
1942
|
embeddingService;
|
|
1369
|
-
logger = new
|
|
1943
|
+
logger = new Logger5(SyncService.name);
|
|
1370
1944
|
constructor(manifest, parser, graph, cascade, pathResolver, embeddingService) {
|
|
1371
1945
|
this.manifest = manifest;
|
|
1372
1946
|
this.parser = parser;
|
|
@@ -1421,7 +1995,7 @@ class SyncService {
|
|
|
1421
1995
|
}
|
|
1422
1996
|
}
|
|
1423
1997
|
}
|
|
1424
|
-
const validationErrors =
|
|
1998
|
+
const validationErrors = validateDocuments2(docsToSync);
|
|
1425
1999
|
if (validationErrors.length > 0) {
|
|
1426
2000
|
for (const err of validationErrors) {
|
|
1427
2001
|
result.errors.push(err);
|
|
@@ -1431,7 +2005,7 @@ class SyncService {
|
|
|
1431
2005
|
result.duration = Date.now() - startTime;
|
|
1432
2006
|
return result;
|
|
1433
2007
|
}
|
|
1434
|
-
const uniqueEntities =
|
|
2008
|
+
const uniqueEntities = collectUniqueEntities(docsToSync);
|
|
1435
2009
|
if (options.verbose) {
|
|
1436
2010
|
this.logger.log(`Collected ${uniqueEntities.size} unique entities from ${docsToSync.length} documents`);
|
|
1437
2011
|
}
|
|
@@ -1508,7 +2082,7 @@ class SyncService {
|
|
|
1508
2082
|
changes.push({
|
|
1509
2083
|
path: docPath,
|
|
1510
2084
|
changeType,
|
|
1511
|
-
reason:
|
|
2085
|
+
reason: getChangeReason(changeType)
|
|
1512
2086
|
});
|
|
1513
2087
|
trackedPaths.delete(docPath);
|
|
1514
2088
|
} catch (error) {
|
|
@@ -1537,9 +2111,9 @@ class SyncService {
|
|
|
1537
2111
|
await this.graph.deleteDocumentRelationships(doc.path);
|
|
1538
2112
|
const documentProps = {
|
|
1539
2113
|
name: doc.path,
|
|
1540
|
-
title: doc.title,
|
|
2114
|
+
title: doc.title ?? "",
|
|
1541
2115
|
contentHash: doc.contentHash,
|
|
1542
|
-
tags: doc.tags
|
|
2116
|
+
tags: doc.tags ?? []
|
|
1543
2117
|
};
|
|
1544
2118
|
if (doc.summary)
|
|
1545
2119
|
documentProps.summary = doc.summary;
|
|
@@ -1561,7 +2135,7 @@ class SyncService {
|
|
|
1561
2135
|
let embeddingGenerated = false;
|
|
1562
2136
|
if (options.embeddings && !options.skipEmbeddings && this.embeddingService) {
|
|
1563
2137
|
try {
|
|
1564
|
-
const textForEmbedding =
|
|
2138
|
+
const textForEmbedding = composeDocumentEmbeddingText(doc);
|
|
1565
2139
|
if (textForEmbedding.trim()) {
|
|
1566
2140
|
const embedding = await this.embeddingService.generateEmbedding(textForEmbedding);
|
|
1567
2141
|
await this.graph.updateNodeEmbedding("Document", doc.path, embedding);
|
|
@@ -1623,9 +2197,9 @@ class SyncService {
|
|
|
1623
2197
|
}
|
|
1624
2198
|
return embeddingGenerated;
|
|
1625
2199
|
}
|
|
1626
|
-
async removeDocument(
|
|
1627
|
-
await this.graph.deleteDocumentRelationships(
|
|
1628
|
-
await this.graph.deleteNode("Document",
|
|
2200
|
+
async removeDocument(path2) {
|
|
2201
|
+
await this.graph.deleteDocumentRelationships(path2);
|
|
2202
|
+
await this.graph.deleteNode("Document", path2);
|
|
1629
2203
|
}
|
|
1630
2204
|
async processChange(change, options, preloadedDoc) {
|
|
1631
2205
|
const cascadeWarnings = [];
|
|
@@ -1672,14 +2246,10 @@ class SyncService {
|
|
|
1672
2246
|
}
|
|
1673
2247
|
return cascadeWarnings;
|
|
1674
2248
|
}
|
|
1675
|
-
async clearGraph() {
|
|
1676
|
-
await this.graph.query("MATCH (n) DETACH DELETE n");
|
|
1677
|
-
this.logger.log("Graph cleared");
|
|
1678
|
-
}
|
|
1679
2249
|
async clearManifest() {
|
|
1680
2250
|
const manifest = await this.manifest.load();
|
|
1681
|
-
for (const
|
|
1682
|
-
this.manifest.removeEntry(
|
|
2251
|
+
for (const path2 of Object.keys(manifest.documents)) {
|
|
2252
|
+
this.manifest.removeEntry(path2);
|
|
1683
2253
|
}
|
|
1684
2254
|
this.logger.log("Manifest cleared");
|
|
1685
2255
|
}
|
|
@@ -1694,80 +2264,23 @@ class SyncService {
|
|
|
1694
2264
|
}
|
|
1695
2265
|
this.logger.log(`Marked ${normalizedPaths.length} document(s) for re-sync`);
|
|
1696
2266
|
}
|
|
1697
|
-
|
|
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) {
|
|
2267
|
+
async getOldDocumentFromManifest(path2) {
|
|
1732
2268
|
try {
|
|
1733
2269
|
const manifest = await this.manifest.load();
|
|
1734
|
-
const entry = manifest.documents[
|
|
2270
|
+
const entry = manifest.documents[path2];
|
|
1735
2271
|
if (!entry) {
|
|
1736
2272
|
return null;
|
|
1737
2273
|
}
|
|
1738
2274
|
try {
|
|
1739
|
-
return await this.parser.parseDocument(
|
|
2275
|
+
return await this.parser.parseDocument(path2);
|
|
1740
2276
|
} catch {
|
|
1741
2277
|
return null;
|
|
1742
2278
|
}
|
|
1743
2279
|
} catch (error) {
|
|
1744
|
-
this.logger.warn(`Failed to retrieve old document for ${
|
|
2280
|
+
this.logger.warn(`Failed to retrieve old document for ${path2}: ${error instanceof Error ? error.message : String(error)}`);
|
|
1745
2281
|
return null;
|
|
1746
2282
|
}
|
|
1747
2283
|
}
|
|
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
2284
|
async syncEntities(entities, options) {
|
|
1772
2285
|
let embeddingsGenerated = 0;
|
|
1773
2286
|
for (const [_key, entity] of entities) {
|
|
@@ -1780,7 +2293,7 @@ class SyncService {
|
|
|
1780
2293
|
await this.graph.upsertNode(entity.type, entityProps);
|
|
1781
2294
|
if (options.embeddings && !options.skipEmbeddings && this.embeddingService) {
|
|
1782
2295
|
try {
|
|
1783
|
-
const text =
|
|
2296
|
+
const text = composeEntityEmbeddingText(entity);
|
|
1784
2297
|
const embedding = await this.embeddingService.generateEmbedding(text);
|
|
1785
2298
|
await this.graph.updateNodeEmbedding(entity.type, entity.name, embedding);
|
|
1786
2299
|
embeddingsGenerated++;
|
|
@@ -1793,13 +2306,6 @@ class SyncService {
|
|
|
1793
2306
|
}
|
|
1794
2307
|
return embeddingsGenerated;
|
|
1795
2308
|
}
|
|
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
2309
|
async createEntityVectorIndices() {
|
|
1804
2310
|
if (!this.embeddingService)
|
|
1805
2311
|
return;
|
|
@@ -1814,7 +2320,7 @@ class SyncService {
|
|
|
1814
2320
|
}
|
|
1815
2321
|
}
|
|
1816
2322
|
SyncService = __legacyDecorateClassTS([
|
|
1817
|
-
|
|
2323
|
+
Injectable11(),
|
|
1818
2324
|
__legacyMetadataTS("design:paramtypes", [
|
|
1819
2325
|
typeof ManifestService === "undefined" ? Object : ManifestService,
|
|
1820
2326
|
typeof DocumentParserService === "undefined" ? Object : DocumentParserService,
|
|
@@ -1825,170 +2331,98 @@ SyncService = __legacyDecorateClassTS([
|
|
|
1825
2331
|
])
|
|
1826
2332
|
], SyncService);
|
|
1827
2333
|
|
|
1828
|
-
// src/
|
|
1829
|
-
|
|
1830
|
-
|
|
1831
|
-
|
|
1832
|
-
constructor(
|
|
1833
|
-
|
|
1834
|
-
|
|
1835
|
-
|
|
1836
|
-
const docs = await this.parser.parseAllDocuments();
|
|
1837
|
-
return this.deriveFromDocuments(docs);
|
|
2334
|
+
// src/commands/status.command.ts
|
|
2335
|
+
class StatusCommand extends CommandRunner4 {
|
|
2336
|
+
syncService;
|
|
2337
|
+
manifestService;
|
|
2338
|
+
constructor(syncService, manifestService) {
|
|
2339
|
+
super();
|
|
2340
|
+
this.syncService = syncService;
|
|
2341
|
+
this.manifestService = manifestService;
|
|
1838
2342
|
}
|
|
1839
|
-
|
|
1840
|
-
|
|
1841
|
-
|
|
1842
|
-
|
|
1843
|
-
|
|
1844
|
-
|
|
1845
|
-
|
|
1846
|
-
|
|
1847
|
-
|
|
1848
|
-
|
|
1849
|
-
|
|
1850
|
-
|
|
1851
|
-
|
|
1852
|
-
|
|
1853
|
-
|
|
1854
|
-
|
|
1855
|
-
|
|
1856
|
-
|
|
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
|
-
}
|
|
2343
|
+
async run(_inputs, options) {
|
|
2344
|
+
try {
|
|
2345
|
+
await this.manifestService.load();
|
|
2346
|
+
const changes = await this.syncService.detectChanges();
|
|
2347
|
+
const newDocs = changes.filter((c) => c.changeType === "new");
|
|
2348
|
+
const updatedDocs = changes.filter((c) => c.changeType === "updated");
|
|
2349
|
+
const deletedDocs = changes.filter((c) => c.changeType === "deleted");
|
|
2350
|
+
const unchangedDocs = changes.filter((c) => c.changeType === "unchanged");
|
|
2351
|
+
const pendingCount = newDocs.length + updatedDocs.length + deletedDocs.length;
|
|
2352
|
+
console.log(`
|
|
2353
|
+
\uD83D\uDCCA Graph Status
|
|
2354
|
+
`);
|
|
2355
|
+
if (newDocs.length > 0) {
|
|
2356
|
+
console.log(`New (${newDocs.length}):`);
|
|
2357
|
+
newDocs.forEach((doc) => {
|
|
2358
|
+
console.log(` + ${doc.path}`);
|
|
2359
|
+
});
|
|
2360
|
+
console.log();
|
|
1863
2361
|
}
|
|
1864
|
-
|
|
1865
|
-
|
|
1866
|
-
|
|
1867
|
-
|
|
2362
|
+
if (updatedDocs.length > 0) {
|
|
2363
|
+
console.log(`Updated (${updatedDocs.length}):`);
|
|
2364
|
+
updatedDocs.forEach((doc) => {
|
|
2365
|
+
console.log(` ~ ${doc.path}`);
|
|
2366
|
+
});
|
|
2367
|
+
console.log();
|
|
1868
2368
|
}
|
|
1869
|
-
|
|
1870
|
-
|
|
1871
|
-
|
|
1872
|
-
|
|
1873
|
-
|
|
1874
|
-
|
|
1875
|
-
|
|
1876
|
-
|
|
1877
|
-
|
|
1878
|
-
|
|
1879
|
-
|
|
1880
|
-
|
|
1881
|
-
|
|
1882
|
-
|
|
1883
|
-
|
|
1884
|
-
|
|
1885
|
-
`);
|
|
1886
|
-
|
|
1887
|
-
|
|
1888
|
-
|
|
1889
|
-
|
|
1890
|
-
|
|
1891
|
-
|
|
1892
|
-
console.
|
|
1893
|
-
|
|
1894
|
-
|
|
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
|
-
}
|
|
1905
|
-
}
|
|
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;
|
|
2369
|
+
if (deletedDocs.length > 0) {
|
|
2370
|
+
console.log(`Deleted (${deletedDocs.length}):`);
|
|
2371
|
+
deletedDocs.forEach((doc) => {
|
|
2372
|
+
console.log(` - ${doc.path}`);
|
|
2373
|
+
});
|
|
2374
|
+
console.log();
|
|
2375
|
+
}
|
|
2376
|
+
if (options.verbose && unchangedDocs.length > 0) {
|
|
2377
|
+
console.log(`Unchanged (${unchangedDocs.length}):`);
|
|
2378
|
+
unchangedDocs.forEach((doc) => {
|
|
2379
|
+
console.log(` \xB7 ${doc.path}`);
|
|
2380
|
+
});
|
|
2381
|
+
console.log();
|
|
2382
|
+
}
|
|
2383
|
+
if (pendingCount === 0) {
|
|
2384
|
+
console.log(`\u2705 All documents are in sync
|
|
2385
|
+
`);
|
|
2386
|
+
} else {
|
|
2387
|
+
console.log(`Total: ${pendingCount} document(s) need syncing`);
|
|
2388
|
+
console.log("\uD83D\uDCA1 Run `lattice sync` to apply changes\n");
|
|
2389
|
+
}
|
|
2390
|
+
process.exit(0);
|
|
2391
|
+
} catch (error) {
|
|
2392
|
+
console.error("Error:", error instanceof Error ? error.message : String(error));
|
|
2393
|
+
process.exit(1);
|
|
2394
|
+
}
|
|
1962
2395
|
}
|
|
1963
|
-
|
|
1964
|
-
|
|
1965
|
-
return await this.graphService.query(cypher);
|
|
2396
|
+
parseVerbose() {
|
|
2397
|
+
return true;
|
|
1966
2398
|
}
|
|
1967
2399
|
}
|
|
1968
|
-
|
|
1969
|
-
|
|
2400
|
+
__legacyDecorateClassTS([
|
|
2401
|
+
Option3({
|
|
2402
|
+
flags: "-v, --verbose",
|
|
2403
|
+
description: "Show all documents including unchanged"
|
|
2404
|
+
}),
|
|
2405
|
+
__legacyMetadataTS("design:type", Function),
|
|
2406
|
+
__legacyMetadataTS("design:paramtypes", []),
|
|
2407
|
+
__legacyMetadataTS("design:returntype", Boolean)
|
|
2408
|
+
], StatusCommand.prototype, "parseVerbose", null);
|
|
2409
|
+
StatusCommand = __legacyDecorateClassTS([
|
|
2410
|
+
Injectable12(),
|
|
2411
|
+
Command4({
|
|
2412
|
+
name: "status",
|
|
2413
|
+
description: "Show documents that need syncing (new or updated)"
|
|
2414
|
+
}),
|
|
1970
2415
|
__legacyMetadataTS("design:paramtypes", [
|
|
1971
|
-
typeof
|
|
2416
|
+
typeof SyncService === "undefined" ? Object : SyncService,
|
|
2417
|
+
typeof ManifestService === "undefined" ? Object : ManifestService
|
|
1972
2418
|
])
|
|
1973
|
-
],
|
|
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
|
-
|
|
2419
|
+
], StatusCommand);
|
|
1986
2420
|
// src/commands/sync.command.ts
|
|
1987
|
-
import { Injectable as Injectable10 } from "@nestjs/common";
|
|
1988
|
-
import { Command, CommandRunner, Option } from "nest-commander";
|
|
1989
2421
|
import { watch } from "fs";
|
|
1990
|
-
import { join } from "path";
|
|
1991
|
-
|
|
2422
|
+
import { join as join2 } from "path";
|
|
2423
|
+
import { Injectable as Injectable13 } from "@nestjs/common";
|
|
2424
|
+
import { Command as Command5, CommandRunner as CommandRunner5, Option as Option4 } from "nest-commander";
|
|
2425
|
+
class SyncCommand extends CommandRunner5 {
|
|
1992
2426
|
syncService;
|
|
1993
2427
|
watcher = null;
|
|
1994
2428
|
isShuttingDown = false;
|
|
@@ -2110,8 +2544,8 @@ class SyncCommand extends CommandRunner {
|
|
|
2110
2544
|
\uD83D\uDC41\uFE0F Watch mode enabled
|
|
2111
2545
|
`);
|
|
2112
2546
|
this.watcher = watch(docsPath, { recursive: true }, (event, filename) => {
|
|
2113
|
-
if (filename
|
|
2114
|
-
const fullPath =
|
|
2547
|
+
if (filename?.endsWith(".md")) {
|
|
2548
|
+
const fullPath = join2(docsPath, filename);
|
|
2115
2549
|
trackedFiles.add(fullPath);
|
|
2116
2550
|
debouncedSync();
|
|
2117
2551
|
}
|
|
@@ -2150,464 +2584,166 @@ class SyncCommand extends CommandRunner {
|
|
|
2150
2584
|
result.errors.forEach((e) => {
|
|
2151
2585
|
console.log(` ${e.path}: ${e.error}`);
|
|
2152
2586
|
});
|
|
2153
|
-
}
|
|
2154
|
-
if (result.changes && result.changes.length > 0) {
|
|
2155
|
-
console.log(`
|
|
2156
|
-
\uD83D\uDCDD Changes:
|
|
2157
|
-
`);
|
|
2158
|
-
|
|
2159
|
-
|
|
2160
|
-
|
|
2161
|
-
|
|
2162
|
-
|
|
2163
|
-
|
|
2164
|
-
|
|
2165
|
-
|
|
2166
|
-
|
|
2167
|
-
|
|
2168
|
-
|
|
2169
|
-
|
|
2170
|
-
|
|
2171
|
-
|
|
2172
|
-
|
|
2173
|
-
|
|
2174
|
-
|
|
2175
|
-
|
|
2176
|
-
|
|
2177
|
-
|
|
2178
|
-
existing.
|
|
2179
|
-
|
|
2180
|
-
|
|
2181
|
-
for (const [trigger, warnings] of warningsByTrigger) {
|
|
2182
|
-
const triggerLabel = trigger.split("_").map((word) => word.charAt(0).toUpperCase() + word.slice(1)).join(" ");
|
|
2183
|
-
console.log(` \uD83D\uDCCC ${triggerLabel}
|
|
2184
|
-
`);
|
|
2185
|
-
for (const analysis of warnings) {
|
|
2186
|
-
console.log(` ${analysis.summary}`);
|
|
2187
|
-
console.log(` Source: ${analysis.sourceDocument}
|
|
2188
|
-
`);
|
|
2189
|
-
if (analysis.affectedDocuments.length > 0) {
|
|
2190
|
-
for (const affected of analysis.affectedDocuments) {
|
|
2191
|
-
const icon = affected.confidence === "high" ? "\uD83D\uDD34" : affected.confidence === "medium" ? "\uD83D\uDFE1" : "\uD83D\uDFE2";
|
|
2192
|
-
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();
|
|
2587
|
+
}
|
|
2588
|
+
if (result.changes && result.changes.length > 0) {
|
|
2589
|
+
console.log(`
|
|
2590
|
+
\uD83D\uDCDD Changes:
|
|
2591
|
+
`);
|
|
2592
|
+
const icons = {
|
|
2593
|
+
new: "\u2795",
|
|
2594
|
+
updated: "\uD83D\uDD04",
|
|
2595
|
+
deleted: "\uD83D\uDDD1\uFE0F",
|
|
2596
|
+
unchanged: "\u23ED\uFE0F"
|
|
2597
|
+
};
|
|
2598
|
+
result.changes.forEach((c) => {
|
|
2599
|
+
const icon = icons[c.changeType];
|
|
2600
|
+
console.log(` ${icon} ${c.changeType}: ${c.path}`);
|
|
2601
|
+
if (c.reason) {
|
|
2602
|
+
console.log(` ${c.reason}`);
|
|
2603
|
+
}
|
|
2604
|
+
});
|
|
2605
|
+
}
|
|
2606
|
+
if (result.cascadeWarnings && result.cascadeWarnings.length > 0) {
|
|
2607
|
+
console.log(`
|
|
2608
|
+
\u26A0\uFE0F Cascade Impacts Detected:
|
|
2609
|
+
`);
|
|
2610
|
+
const warningsByTrigger = new Map;
|
|
2611
|
+
for (const warning of result.cascadeWarnings) {
|
|
2612
|
+
const existing = warningsByTrigger.get(warning.trigger) || [];
|
|
2613
|
+
existing.push(warning);
|
|
2614
|
+
warningsByTrigger.set(warning.trigger, existing);
|
|
2358
2615
|
}
|
|
2359
|
-
|
|
2360
|
-
|
|
2616
|
+
for (const [trigger, warnings] of warningsByTrigger) {
|
|
2617
|
+
const triggerLabel = trigger.split("_").map((word) => word.charAt(0).toUpperCase() + word.slice(1)).join(" ");
|
|
2618
|
+
console.log(` \uD83D\uDCCC ${triggerLabel}
|
|
2361
2619
|
`);
|
|
2362
|
-
|
|
2363
|
-
|
|
2364
|
-
|
|
2620
|
+
for (const analysis of warnings) {
|
|
2621
|
+
console.log(` ${analysis.summary}`);
|
|
2622
|
+
console.log(` Source: ${analysis.sourceDocument}
|
|
2623
|
+
`);
|
|
2624
|
+
if (analysis.affectedDocuments.length > 0) {
|
|
2625
|
+
for (const affected of analysis.affectedDocuments) {
|
|
2626
|
+
const icon = affected.confidence === "high" ? "\uD83D\uDD34" : affected.confidence === "medium" ? "\uD83D\uDFE1" : "\uD83D\uDFE2";
|
|
2627
|
+
console.log(` ${icon} [${affected.confidence.toUpperCase()}] ${affected.path}`);
|
|
2628
|
+
console.log(` ${affected.reason}`);
|
|
2629
|
+
const suggestedAction = affected.suggestedAction.split("_").join(" ").replace(/\b\w/g, (char) => char.toUpperCase());
|
|
2630
|
+
console.log(` \u2192 ${suggestedAction}`);
|
|
2631
|
+
}
|
|
2632
|
+
} else {
|
|
2633
|
+
console.log(` \u2139\uFE0F No directly affected documents detected`);
|
|
2634
|
+
}
|
|
2635
|
+
console.log();
|
|
2636
|
+
}
|
|
2365
2637
|
}
|
|
2366
|
-
|
|
2367
|
-
|
|
2368
|
-
|
|
2369
|
-
|
|
2638
|
+
console.log(` \uD83D\uDCA1 Run /update-related to apply suggested changes
|
|
2639
|
+
`);
|
|
2640
|
+
}
|
|
2641
|
+
if (isWatchMode) {
|
|
2642
|
+
console.log(`
|
|
2643
|
+
\u23F3 Watching for changes... (Ctrl+C to stop)
|
|
2644
|
+
`);
|
|
2370
2645
|
}
|
|
2371
2646
|
}
|
|
2647
|
+
parseForce() {
|
|
2648
|
+
return true;
|
|
2649
|
+
}
|
|
2650
|
+
parseDryRun() {
|
|
2651
|
+
return true;
|
|
2652
|
+
}
|
|
2372
2653
|
parseVerbose() {
|
|
2373
2654
|
return true;
|
|
2374
2655
|
}
|
|
2656
|
+
parseWatch() {
|
|
2657
|
+
return true;
|
|
2658
|
+
}
|
|
2659
|
+
parseDiff() {
|
|
2660
|
+
return true;
|
|
2661
|
+
}
|
|
2662
|
+
parseSkipCascade() {
|
|
2663
|
+
return true;
|
|
2664
|
+
}
|
|
2665
|
+
parseNoEmbeddings() {
|
|
2666
|
+
return false;
|
|
2667
|
+
}
|
|
2375
2668
|
}
|
|
2376
2669
|
__legacyDecorateClassTS([
|
|
2377
|
-
|
|
2378
|
-
flags: "-
|
|
2379
|
-
description: "
|
|
2670
|
+
Option4({
|
|
2671
|
+
flags: "-f, --force",
|
|
2672
|
+
description: "Force re-sync: with paths, clears only those docs; without paths, rebuilds entire graph"
|
|
2380
2673
|
}),
|
|
2381
2674
|
__legacyMetadataTS("design:type", Function),
|
|
2382
2675
|
__legacyMetadataTS("design:paramtypes", []),
|
|
2383
2676
|
__legacyMetadataTS("design:returntype", Boolean)
|
|
2384
|
-
],
|
|
2385
|
-
|
|
2386
|
-
|
|
2387
|
-
|
|
2388
|
-
|
|
2389
|
-
description: "Show documents that need syncing (new or updated)"
|
|
2677
|
+
], SyncCommand.prototype, "parseForce", null);
|
|
2678
|
+
__legacyDecorateClassTS([
|
|
2679
|
+
Option4({
|
|
2680
|
+
flags: "-d, --dry-run",
|
|
2681
|
+
description: "Show what would change without applying"
|
|
2390
2682
|
}),
|
|
2391
|
-
__legacyMetadataTS("design:
|
|
2392
|
-
|
|
2393
|
-
|
|
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
|
-
}
|
|
2683
|
+
__legacyMetadataTS("design:type", Function),
|
|
2684
|
+
__legacyMetadataTS("design:paramtypes", []),
|
|
2685
|
+
__legacyMetadataTS("design:returntype", Boolean)
|
|
2686
|
+
], SyncCommand.prototype, "parseDryRun", null);
|
|
2469
2687
|
__legacyDecorateClassTS([
|
|
2470
|
-
|
|
2471
|
-
flags: "-
|
|
2472
|
-
description: "
|
|
2688
|
+
Option4({
|
|
2689
|
+
flags: "-v, --verbose",
|
|
2690
|
+
description: "Show detailed output"
|
|
2473
2691
|
}),
|
|
2474
2692
|
__legacyMetadataTS("design:type", Function),
|
|
2475
|
-
__legacyMetadataTS("design:paramtypes", [
|
|
2476
|
-
|
|
2477
|
-
|
|
2478
|
-
__legacyMetadataTS("design:returntype", String)
|
|
2479
|
-
], SearchCommand.prototype, "parseLabel", null);
|
|
2693
|
+
__legacyMetadataTS("design:paramtypes", []),
|
|
2694
|
+
__legacyMetadataTS("design:returntype", Boolean)
|
|
2695
|
+
], SyncCommand.prototype, "parseVerbose", null);
|
|
2480
2696
|
__legacyDecorateClassTS([
|
|
2481
|
-
|
|
2482
|
-
flags: "--
|
|
2483
|
-
description: "
|
|
2484
|
-
defaultValue: "20"
|
|
2697
|
+
Option4({
|
|
2698
|
+
flags: "-w, --watch",
|
|
2699
|
+
description: "Watch for file changes and sync automatically"
|
|
2485
2700
|
}),
|
|
2486
2701
|
__legacyMetadataTS("design:type", Function),
|
|
2487
|
-
__legacyMetadataTS("design:paramtypes", [
|
|
2488
|
-
|
|
2489
|
-
|
|
2490
|
-
|
|
2491
|
-
|
|
2492
|
-
|
|
2493
|
-
|
|
2494
|
-
Command3({
|
|
2495
|
-
name: "search",
|
|
2496
|
-
arguments: "<query>",
|
|
2497
|
-
description: "Semantic search across the knowledge graph"
|
|
2702
|
+
__legacyMetadataTS("design:paramtypes", []),
|
|
2703
|
+
__legacyMetadataTS("design:returntype", Boolean)
|
|
2704
|
+
], SyncCommand.prototype, "parseWatch", null);
|
|
2705
|
+
__legacyDecorateClassTS([
|
|
2706
|
+
Option4({
|
|
2707
|
+
flags: "--diff",
|
|
2708
|
+
description: "Show only changed documents (alias for --dry-run)"
|
|
2498
2709
|
}),
|
|
2499
|
-
__legacyMetadataTS("design:
|
|
2500
|
-
|
|
2501
|
-
|
|
2502
|
-
|
|
2503
|
-
|
|
2504
|
-
|
|
2505
|
-
|
|
2506
|
-
|
|
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"
|
|
2710
|
+
__legacyMetadataTS("design:type", Function),
|
|
2711
|
+
__legacyMetadataTS("design:paramtypes", []),
|
|
2712
|
+
__legacyMetadataTS("design:returntype", Boolean)
|
|
2713
|
+
], SyncCommand.prototype, "parseDiff", null);
|
|
2714
|
+
__legacyDecorateClassTS([
|
|
2715
|
+
Option4({
|
|
2716
|
+
flags: "--skip-cascade",
|
|
2717
|
+
description: "Skip cascade analysis (faster for large repos)"
|
|
2568
2718
|
}),
|
|
2569
|
-
__legacyMetadataTS("design:
|
|
2570
|
-
|
|
2571
|
-
|
|
2572
|
-
],
|
|
2573
|
-
|
|
2574
|
-
|
|
2575
|
-
|
|
2576
|
-
|
|
2577
|
-
|
|
2578
|
-
|
|
2579
|
-
|
|
2580
|
-
|
|
2581
|
-
|
|
2582
|
-
|
|
2583
|
-
|
|
2584
|
-
|
|
2585
|
-
|
|
2586
|
-
|
|
2587
|
-
|
|
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"
|
|
2719
|
+
__legacyMetadataTS("design:type", Function),
|
|
2720
|
+
__legacyMetadataTS("design:paramtypes", []),
|
|
2721
|
+
__legacyMetadataTS("design:returntype", Boolean)
|
|
2722
|
+
], SyncCommand.prototype, "parseSkipCascade", null);
|
|
2723
|
+
__legacyDecorateClassTS([
|
|
2724
|
+
Option4({
|
|
2725
|
+
flags: "--no-embeddings",
|
|
2726
|
+
description: "Disable embedding generation during sync"
|
|
2727
|
+
}),
|
|
2728
|
+
__legacyMetadataTS("design:type", Function),
|
|
2729
|
+
__legacyMetadataTS("design:paramtypes", []),
|
|
2730
|
+
__legacyMetadataTS("design:returntype", Boolean)
|
|
2731
|
+
], SyncCommand.prototype, "parseNoEmbeddings", null);
|
|
2732
|
+
SyncCommand = __legacyDecorateClassTS([
|
|
2733
|
+
Injectable13(),
|
|
2734
|
+
Command5({
|
|
2735
|
+
name: "sync",
|
|
2736
|
+
arguments: "[paths...]",
|
|
2737
|
+
description: "Synchronize documents to the knowledge graph"
|
|
2602
2738
|
}),
|
|
2603
2739
|
__legacyMetadataTS("design:paramtypes", [
|
|
2604
|
-
typeof
|
|
2740
|
+
typeof SyncService === "undefined" ? Object : SyncService
|
|
2605
2741
|
])
|
|
2606
|
-
],
|
|
2742
|
+
], SyncCommand);
|
|
2607
2743
|
// src/commands/validate.command.ts
|
|
2608
|
-
import { Injectable as
|
|
2609
|
-
import { Command as
|
|
2610
|
-
class ValidateCommand extends
|
|
2744
|
+
import { Injectable as Injectable14 } from "@nestjs/common";
|
|
2745
|
+
import { Command as Command6, CommandRunner as CommandRunner6, Option as Option5 } from "nest-commander";
|
|
2746
|
+
class ValidateCommand extends CommandRunner6 {
|
|
2611
2747
|
parserService;
|
|
2612
2748
|
constructor(parserService) {
|
|
2613
2749
|
super();
|
|
@@ -2629,13 +2765,15 @@ class ValidateCommand extends CommandRunner4 {
|
|
|
2629
2765
|
const entityIndex = new Map;
|
|
2630
2766
|
for (const doc of docs) {
|
|
2631
2767
|
for (const entity of doc.entities) {
|
|
2632
|
-
|
|
2633
|
-
|
|
2768
|
+
let docPaths = entityIndex.get(entity.name);
|
|
2769
|
+
if (!docPaths) {
|
|
2770
|
+
docPaths = new Set;
|
|
2771
|
+
entityIndex.set(entity.name, docPaths);
|
|
2634
2772
|
}
|
|
2635
|
-
|
|
2773
|
+
docPaths.add(doc.path);
|
|
2636
2774
|
}
|
|
2637
2775
|
}
|
|
2638
|
-
const validationErrors =
|
|
2776
|
+
const validationErrors = validateDocuments2(docs);
|
|
2639
2777
|
for (const err of validationErrors) {
|
|
2640
2778
|
issues.push({
|
|
2641
2779
|
type: "error",
|
|
@@ -2673,7 +2811,7 @@ class ValidateCommand extends CommandRunner4 {
|
|
|
2673
2811
|
}
|
|
2674
2812
|
}
|
|
2675
2813
|
__legacyDecorateClassTS([
|
|
2676
|
-
|
|
2814
|
+
Option5({
|
|
2677
2815
|
flags: "--fix",
|
|
2678
2816
|
description: "Show suggestions for common issues"
|
|
2679
2817
|
}),
|
|
@@ -2682,8 +2820,8 @@ __legacyDecorateClassTS([
|
|
|
2682
2820
|
__legacyMetadataTS("design:returntype", Boolean)
|
|
2683
2821
|
], ValidateCommand.prototype, "parseFix", null);
|
|
2684
2822
|
ValidateCommand = __legacyDecorateClassTS([
|
|
2685
|
-
|
|
2686
|
-
|
|
2823
|
+
Injectable14(),
|
|
2824
|
+
Command6({
|
|
2687
2825
|
name: "validate",
|
|
2688
2826
|
description: "Validate entity references and relationships across documents"
|
|
2689
2827
|
}),
|
|
@@ -2691,138 +2829,90 @@ ValidateCommand = __legacyDecorateClassTS([
|
|
|
2691
2829
|
typeof DocumentParserService === "undefined" ? Object : DocumentParserService
|
|
2692
2830
|
])
|
|
2693
2831
|
], ValidateCommand);
|
|
2694
|
-
// src/
|
|
2695
|
-
import {
|
|
2696
|
-
import {
|
|
2697
|
-
class
|
|
2698
|
-
|
|
2699
|
-
|
|
2700
|
-
|
|
2701
|
-
|
|
2832
|
+
// src/embedding/embedding.module.ts
|
|
2833
|
+
import { Module } from "@nestjs/common";
|
|
2834
|
+
import { ConfigModule } from "@nestjs/config";
|
|
2835
|
+
class EmbeddingModule {
|
|
2836
|
+
}
|
|
2837
|
+
EmbeddingModule = __legacyDecorateClassTS([
|
|
2838
|
+
Module({
|
|
2839
|
+
imports: [ConfigModule],
|
|
2840
|
+
providers: [EmbeddingService],
|
|
2841
|
+
exports: [EmbeddingService]
|
|
2842
|
+
})
|
|
2843
|
+
], EmbeddingModule);
|
|
2844
|
+
|
|
2845
|
+
// src/graph/graph.module.ts
|
|
2846
|
+
import { Module as Module2 } from "@nestjs/common";
|
|
2847
|
+
class GraphModule {
|
|
2848
|
+
}
|
|
2849
|
+
GraphModule = __legacyDecorateClassTS([
|
|
2850
|
+
Module2({
|
|
2851
|
+
providers: [GraphService],
|
|
2852
|
+
exports: [GraphService]
|
|
2853
|
+
})
|
|
2854
|
+
], GraphModule);
|
|
2855
|
+
|
|
2856
|
+
// src/query/query.module.ts
|
|
2857
|
+
import { Module as Module3 } from "@nestjs/common";
|
|
2858
|
+
|
|
2859
|
+
// src/query/query.service.ts
|
|
2860
|
+
import { Injectable as Injectable15, Logger as Logger6 } from "@nestjs/common";
|
|
2861
|
+
class QueryService {
|
|
2862
|
+
graphService;
|
|
2863
|
+
logger = new Logger6(QueryService.name);
|
|
2864
|
+
constructor(graphService) {
|
|
2865
|
+
this.graphService = graphService;
|
|
2702
2866
|
}
|
|
2703
|
-
async
|
|
2704
|
-
|
|
2705
|
-
|
|
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
|
-
}
|
|
2867
|
+
async query(sql) {
|
|
2868
|
+
this.logger.debug(`Executing query: ${sql}`);
|
|
2869
|
+
return await this.graphService.query(sql);
|
|
2713
2870
|
}
|
|
2714
2871
|
}
|
|
2715
|
-
|
|
2716
|
-
|
|
2717
|
-
Command5({
|
|
2718
|
-
name: "ontology",
|
|
2719
|
-
description: "Derive and display ontology from all documents"
|
|
2720
|
-
}),
|
|
2872
|
+
QueryService = __legacyDecorateClassTS([
|
|
2873
|
+
Injectable15(),
|
|
2721
2874
|
__legacyMetadataTS("design:paramtypes", [
|
|
2722
|
-
typeof
|
|
2875
|
+
typeof GraphService === "undefined" ? Object : GraphService
|
|
2723
2876
|
])
|
|
2724
|
-
],
|
|
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"];
|
|
2877
|
+
], QueryService);
|
|
2735
2878
|
|
|
2736
|
-
|
|
2737
|
-
|
|
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
|
-
}
|
|
2879
|
+
// src/query/query.module.ts
|
|
2880
|
+
class QueryModule {
|
|
2809
2881
|
}
|
|
2810
|
-
__legacyDecorateClassTS([
|
|
2811
|
-
|
|
2812
|
-
|
|
2813
|
-
|
|
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"
|
|
2882
|
+
QueryModule = __legacyDecorateClassTS([
|
|
2883
|
+
Module3({
|
|
2884
|
+
imports: [GraphModule, EmbeddingModule],
|
|
2885
|
+
providers: [QueryService, GraphService],
|
|
2886
|
+
exports: [QueryService, GraphService]
|
|
2824
2887
|
})
|
|
2825
|
-
],
|
|
2888
|
+
], QueryModule);
|
|
2889
|
+
|
|
2890
|
+
// src/sync/sync.module.ts
|
|
2891
|
+
import { Module as Module4 } from "@nestjs/common";
|
|
2892
|
+
class SyncModule {
|
|
2893
|
+
}
|
|
2894
|
+
SyncModule = __legacyDecorateClassTS([
|
|
2895
|
+
Module4({
|
|
2896
|
+
imports: [GraphModule, EmbeddingModule],
|
|
2897
|
+
providers: [
|
|
2898
|
+
SyncService,
|
|
2899
|
+
ManifestService,
|
|
2900
|
+
DocumentParserService,
|
|
2901
|
+
OntologyService,
|
|
2902
|
+
CascadeService,
|
|
2903
|
+
PathResolverService
|
|
2904
|
+
],
|
|
2905
|
+
exports: [
|
|
2906
|
+
SyncService,
|
|
2907
|
+
ManifestService,
|
|
2908
|
+
DocumentParserService,
|
|
2909
|
+
OntologyService,
|
|
2910
|
+
CascadeService,
|
|
2911
|
+
PathResolverService
|
|
2912
|
+
]
|
|
2913
|
+
})
|
|
2914
|
+
], SyncModule);
|
|
2915
|
+
|
|
2826
2916
|
// src/app.module.ts
|
|
2827
2917
|
class AppModule {
|
|
2828
2918
|
}
|
|
@@ -2842,7 +2932,7 @@ AppModule = __legacyDecorateClassTS([
|
|
|
2842
2932
|
StatusCommand,
|
|
2843
2933
|
SearchCommand,
|
|
2844
2934
|
RelsCommand,
|
|
2845
|
-
|
|
2935
|
+
SqlCommand,
|
|
2846
2936
|
ValidateCommand,
|
|
2847
2937
|
OntologyCommand,
|
|
2848
2938
|
InitCommand
|