@zabaca/lattice 1.1.1 → 1.2.1
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/commands/research.md
CHANGED
|
@@ -8,7 +8,7 @@ Research the topic "$ARGUMENTS" by first checking existing documentation, then p
|
|
|
8
8
|
|
|
9
9
|
## Configuration
|
|
10
10
|
|
|
11
|
-
|
|
11
|
+
**CRITICAL: All documentation lives in `~/.lattice/docs/`**
|
|
12
12
|
|
|
13
13
|
| Path | Purpose |
|
|
14
14
|
|------|---------|
|
|
@@ -21,22 +21,37 @@ Research the topic "$ARGUMENTS" by first checking existing documentation, then p
|
|
|
21
21
|
|
|
22
22
|
## Process
|
|
23
23
|
|
|
24
|
-
### Step 1:
|
|
24
|
+
### Step 1: Create or Find Question
|
|
25
25
|
|
|
26
|
-
|
|
26
|
+
First, search to see if this question (or similar) already exists:
|
|
27
27
|
|
|
28
28
|
```bash
|
|
29
|
-
lattice search "$ARGUMENTS" --limit
|
|
29
|
+
lattice search "$ARGUMENTS" --limit 5
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
Look for results with `[Question]` label and high similarity (>70%).
|
|
33
|
+
|
|
34
|
+
**If similar question exists:** Use that existing question (don't duplicate).
|
|
35
|
+
|
|
36
|
+
**If no similar question:** Create the question entity:
|
|
37
|
+
|
|
38
|
+
```bash
|
|
39
|
+
lattice question:add "$ARGUMENTS"
|
|
30
40
|
```
|
|
31
41
|
|
|
32
|
-
|
|
42
|
+
This ensures the question is tracked regardless of whether we find an answer.
|
|
33
43
|
|
|
34
|
-
|
|
44
|
+
### Step 2: Search for Answers
|
|
35
45
|
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
46
|
+
Search for documents that might answer this question:
|
|
47
|
+
|
|
48
|
+
```bash
|
|
49
|
+
lattice search "$ARGUMENTS" --limit 10
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
Review results focusing on:
|
|
53
|
+
- Documents (`[Document]` label) with relevant content
|
|
54
|
+
- High similarity scores (>40% often indicates relevance)
|
|
40
55
|
|
|
41
56
|
**Calibration notes:**
|
|
42
57
|
- Exact topic matches often show 30-40% similarity
|
|
@@ -48,13 +63,21 @@ For each promising result:
|
|
|
48
63
|
- Check if it answers the user's question
|
|
49
64
|
- Note relevant sections
|
|
50
65
|
|
|
51
|
-
### Step 3: Present Findings
|
|
66
|
+
### Step 3: Present Findings and Link Answer
|
|
52
67
|
|
|
53
68
|
Summarize what you found in existing docs:
|
|
54
69
|
- What topics are covered
|
|
55
70
|
- Quote relevant sections if helpful
|
|
56
71
|
- Identify gaps in existing research
|
|
57
72
|
|
|
73
|
+
**If existing documentation answers the question:**
|
|
74
|
+
|
|
75
|
+
Link the question to the answering document:
|
|
76
|
+
|
|
77
|
+
```bash
|
|
78
|
+
lattice question:link "$ARGUMENTS" --doc {path-to-doc}
|
|
79
|
+
```
|
|
80
|
+
|
|
58
81
|
Ask the user: **"Does this existing research cover your question?"**
|
|
59
82
|
|
|
60
83
|
### Step 4: Ask About New Research
|
|
@@ -148,23 +171,43 @@ What this research addresses.
|
|
|
148
171
|
**2. Update** `~/.lattice/docs/{topic-name}/README.md`:
|
|
149
172
|
- Add new row to the Documents table
|
|
150
173
|
|
|
151
|
-
### Step 8:
|
|
174
|
+
### Step 8: Sync and Link Question
|
|
175
|
+
|
|
176
|
+
After creating files, sync to the knowledge graph:
|
|
177
|
+
|
|
178
|
+
```bash
|
|
179
|
+
lattice sync
|
|
180
|
+
```
|
|
181
|
+
|
|
182
|
+
This will:
|
|
183
|
+
- Add documents to the graph
|
|
184
|
+
- Extract entities automatically via AI
|
|
185
|
+
- Generate embeddings for semantic search
|
|
152
186
|
|
|
153
|
-
|
|
187
|
+
Then link the question to the new document:
|
|
188
|
+
|
|
189
|
+
```bash
|
|
190
|
+
lattice question:link "$ARGUMENTS" --doc ~/.lattice/docs/{topic-name}/{research-filename}.md
|
|
191
|
+
```
|
|
192
|
+
|
|
193
|
+
### Step 9: Confirmation
|
|
194
|
+
|
|
195
|
+
Confirm to the user:
|
|
196
|
+
- Question entity created/found
|
|
154
197
|
- Topic directory path
|
|
155
|
-
-
|
|
156
|
-
-
|
|
157
|
-
-
|
|
198
|
+
- Research file created
|
|
199
|
+
- Question linked to document via ANSWERED_BY
|
|
200
|
+
- Sync completed
|
|
158
201
|
|
|
159
202
|
## Important Notes
|
|
160
203
|
|
|
161
|
-
- **Do NOT** auto-run graph sync - use `/graph-sync` separately
|
|
162
204
|
- **Always create README.md** for new topics (lightweight index)
|
|
163
205
|
- **Always create separate research file** (never put research content in README)
|
|
164
206
|
- Use kebab-case for all directory and file names
|
|
165
207
|
- Always cite sources with URLs
|
|
166
208
|
- Cross-link to related research topics when relevant
|
|
167
209
|
- **No frontmatter needed** - AI extracts entities automatically during sync
|
|
210
|
+
- **Questions track user intent** - even if a doc exists, the question helps future discovery
|
|
168
211
|
|
|
169
212
|
## File Structure Standard
|
|
170
213
|
|
|
@@ -177,3 +220,12 @@ After creating files, confirm:
|
|
|
177
220
|
```
|
|
178
221
|
|
|
179
222
|
This structure allows topics to grow organically while keeping README as a clean navigation index.
|
|
223
|
+
|
|
224
|
+
## Question Commands Reference
|
|
225
|
+
|
|
226
|
+
| Command | Purpose |
|
|
227
|
+
|---------|---------|
|
|
228
|
+
| `lattice question:add "question"` | Create a question entity |
|
|
229
|
+
| `lattice question:add "question" --answered-by path` | Create and link in one step |
|
|
230
|
+
| `lattice question:link "question" --doc path` | Link question to answering doc |
|
|
231
|
+
| `lattice question:unanswered` | List questions without answers |
|
package/dist/main.js
CHANGED
|
@@ -38,7 +38,38 @@ import {
|
|
|
38
38
|
tool
|
|
39
39
|
} from "@anthropic-ai/claude-agent-sdk";
|
|
40
40
|
import { Injectable, Logger } from "@nestjs/common";
|
|
41
|
+
import { z as z2 } from "zod";
|
|
42
|
+
|
|
43
|
+
// src/graph/graph.types.ts
|
|
41
44
|
import { z } from "zod";
|
|
45
|
+
var EntityTypeSchema = z.enum([
|
|
46
|
+
"Topic",
|
|
47
|
+
"Technology",
|
|
48
|
+
"Concept",
|
|
49
|
+
"Tool",
|
|
50
|
+
"Process",
|
|
51
|
+
"Person",
|
|
52
|
+
"Organization",
|
|
53
|
+
"Document",
|
|
54
|
+
"Question"
|
|
55
|
+
]);
|
|
56
|
+
var RelationTypeSchema = z.enum(["REFERENCES", "ANSWERED_BY"]);
|
|
57
|
+
var EntitySchema = z.object({
|
|
58
|
+
name: z.string().min(1),
|
|
59
|
+
type: EntityTypeSchema,
|
|
60
|
+
description: z.string().optional()
|
|
61
|
+
});
|
|
62
|
+
var RelationshipSchema = z.object({
|
|
63
|
+
source: z.string().min(1),
|
|
64
|
+
relation: RelationTypeSchema,
|
|
65
|
+
target: z.string().min(1)
|
|
66
|
+
});
|
|
67
|
+
var GraphMetadataSchema = z.object({
|
|
68
|
+
importance: z.enum(["high", "medium", "low"]).optional(),
|
|
69
|
+
domain: z.string().optional()
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
// src/sync/entity-extractor.service.ts
|
|
42
73
|
function validateExtraction(input, _filePath) {
|
|
43
74
|
const errors = [];
|
|
44
75
|
const { entities, relationships } = input ?? {};
|
|
@@ -65,26 +96,17 @@ function createValidationServer(filePath) {
|
|
|
65
96
|
version: "1.0.0",
|
|
66
97
|
tools: [
|
|
67
98
|
tool("validate_extraction", "Validate your extracted entities and relationships. Call this to check your work before finishing.", {
|
|
68
|
-
entities:
|
|
69
|
-
name:
|
|
70
|
-
type:
|
|
71
|
-
|
|
72
|
-
"Technology",
|
|
73
|
-
"Concept",
|
|
74
|
-
"Tool",
|
|
75
|
-
"Process",
|
|
76
|
-
"Person",
|
|
77
|
-
"Organization",
|
|
78
|
-
"Document"
|
|
79
|
-
]),
|
|
80
|
-
description: z.string().min(1)
|
|
99
|
+
entities: z2.array(z2.object({
|
|
100
|
+
name: z2.string().min(1),
|
|
101
|
+
type: EntityTypeSchema,
|
|
102
|
+
description: z2.string().min(1)
|
|
81
103
|
})),
|
|
82
|
-
relationships:
|
|
83
|
-
source:
|
|
84
|
-
relation:
|
|
85
|
-
target:
|
|
104
|
+
relationships: z2.array(z2.object({
|
|
105
|
+
source: z2.string().min(1),
|
|
106
|
+
relation: RelationTypeSchema,
|
|
107
|
+
target: z2.string().min(1)
|
|
86
108
|
})),
|
|
87
|
-
summary:
|
|
109
|
+
summary: z2.string().min(10)
|
|
88
110
|
}, async (args) => {
|
|
89
111
|
const errors = validateExtraction(args, filePath);
|
|
90
112
|
if (errors.length === 0) {
|
|
@@ -210,15 +232,17 @@ Extract the following and call the validation tool with EXACTLY this schema:
|
|
|
210
232
|
### 1. Entities (array of 3-10 objects)
|
|
211
233
|
Each entity must have:
|
|
212
234
|
- "name": string (entity name)
|
|
213
|
-
- "type": one of "Topic", "Technology", "Concept", "Tool", "Process", "Person", "Organization", "Document"
|
|
235
|
+
- "type": one of "Topic", "Technology", "Concept", "Tool", "Process", "Person", "Organization", "Document", "Question"
|
|
214
236
|
- "description": string (brief description)
|
|
215
237
|
|
|
216
238
|
### 2. Relationships (array of objects)
|
|
217
239
|
Each relationship must have:
|
|
218
240
|
- "source": "this" (for document-to-entity) or an entity name
|
|
219
|
-
- "relation": "REFERENCES" (IMPORTANT: use "relation", not "type")
|
|
241
|
+
- "relation": "REFERENCES" or "ANSWERED_BY" (IMPORTANT: use "relation", not "type")
|
|
220
242
|
- "target": an entity name from your entities list
|
|
221
243
|
|
|
244
|
+
Use ANSWERED_BY when a Question entity is answered by this document (source: Question name, target: "this").
|
|
245
|
+
|
|
222
246
|
### 3. Summary
|
|
223
247
|
A 50-100 word summary of the document's main purpose and key concepts.
|
|
224
248
|
|
|
@@ -405,7 +429,7 @@ function ensureLatticeHome() {
|
|
|
405
429
|
// src/commands/init.command.ts
|
|
406
430
|
var __filename2 = fileURLToPath(import.meta.url);
|
|
407
431
|
var __dirname2 = path.dirname(__filename2);
|
|
408
|
-
var COMMANDS = ["research.md", "
|
|
432
|
+
var COMMANDS = ["research.md", "entity-extract.md"];
|
|
409
433
|
var SITE_TEMPLATE_FILES = [
|
|
410
434
|
"astro.config.ts",
|
|
411
435
|
"package.json",
|
|
@@ -413,7 +437,8 @@ var SITE_TEMPLATE_FILES = [
|
|
|
413
437
|
"src/content.config.ts",
|
|
414
438
|
"src/collections/authors.ts",
|
|
415
439
|
"src/collections/documents.ts",
|
|
416
|
-
"src/collections/tags.ts"
|
|
440
|
+
"src/collections/tags.ts",
|
|
441
|
+
"src/plugins/rehype-strip-md-extension.ts"
|
|
417
442
|
];
|
|
418
443
|
|
|
419
444
|
class InitCommand extends CommandRunner2 {
|
|
@@ -465,9 +490,13 @@ OBSIDIAN_VAULT_DIR=docs
|
|
|
465
490
|
console.log();
|
|
466
491
|
console.log("Claude Code slash commands:");
|
|
467
492
|
console.log(" /research <topic> - AI-assisted research workflow");
|
|
468
|
-
console.log(" /graph-sync - Extract entities and sync to graph");
|
|
469
493
|
console.log(" /entity-extract - Extract entities from a document");
|
|
470
494
|
console.log();
|
|
495
|
+
console.log("Question tracking:");
|
|
496
|
+
console.log(" lattice question:add <question> - Add a question");
|
|
497
|
+
console.log(" lattice question:link <q> --doc <p> - Link question to answer");
|
|
498
|
+
console.log(" lattice question:unanswered - List unanswered questions");
|
|
499
|
+
console.log();
|
|
471
500
|
if (!(await fs.readFile(envPath, "utf-8")).includes("pa-")) {
|
|
472
501
|
console.log(`\u26A0\uFE0F Add your Voyage API key to: ${envPath}`);
|
|
473
502
|
console.log();
|
|
@@ -494,6 +523,9 @@ OBSIDIAN_VAULT_DIR=docs
|
|
|
494
523
|
await fs.mkdir(path.join(latticeHome, "src", "collections"), {
|
|
495
524
|
recursive: true
|
|
496
525
|
});
|
|
526
|
+
await fs.mkdir(path.join(latticeHome, "src", "plugins"), {
|
|
527
|
+
recursive: true
|
|
528
|
+
});
|
|
497
529
|
let copied = 0;
|
|
498
530
|
let skipped = 0;
|
|
499
531
|
for (const file of SITE_TEMPLATE_FILES) {
|
|
@@ -513,7 +545,7 @@ OBSIDIAN_VAULT_DIR=docs
|
|
|
513
545
|
await fs.mkdir(path.dirname(targetPath), { recursive: true });
|
|
514
546
|
await fs.copyFile(sourcePath, targetPath);
|
|
515
547
|
copied++;
|
|
516
|
-
} catch (
|
|
548
|
+
} catch (_err) {}
|
|
517
549
|
}
|
|
518
550
|
if (copied > 0) {
|
|
519
551
|
console.log(`\u2705 Site template: ${copied} file(s) installed`);
|
|
@@ -557,7 +589,7 @@ OBSIDIAN_VAULT_DIR=docs
|
|
|
557
589
|
await fs.copyFile(sourcePath, targetPath);
|
|
558
590
|
installed.push(file);
|
|
559
591
|
copied++;
|
|
560
|
-
} catch (
|
|
592
|
+
} catch (_err) {}
|
|
561
593
|
}
|
|
562
594
|
if (copied > 0) {
|
|
563
595
|
console.log(`\u2705 Claude commands: ${copied} installed to ${targetDir}`);
|
|
@@ -1081,18 +1113,18 @@ import { readFile as readFile3, writeFile } from "fs/promises";
|
|
|
1081
1113
|
import { Injectable as Injectable5 } from "@nestjs/common";
|
|
1082
1114
|
|
|
1083
1115
|
// src/schemas/manifest.schemas.ts
|
|
1084
|
-
import { z as
|
|
1085
|
-
var ManifestEntrySchema =
|
|
1086
|
-
contentHash:
|
|
1087
|
-
frontmatterHash:
|
|
1088
|
-
lastSynced:
|
|
1089
|
-
entityCount:
|
|
1090
|
-
relationshipCount:
|
|
1116
|
+
import { z as z3 } from "zod";
|
|
1117
|
+
var ManifestEntrySchema = z3.object({
|
|
1118
|
+
contentHash: z3.string(),
|
|
1119
|
+
frontmatterHash: z3.string(),
|
|
1120
|
+
lastSynced: z3.string(),
|
|
1121
|
+
entityCount: z3.number().int().nonnegative(),
|
|
1122
|
+
relationshipCount: z3.number().int().nonnegative()
|
|
1091
1123
|
});
|
|
1092
|
-
var SyncManifestSchema =
|
|
1093
|
-
version:
|
|
1094
|
-
lastSync:
|
|
1095
|
-
documents:
|
|
1124
|
+
var SyncManifestSchema = z3.object({
|
|
1125
|
+
version: z3.string(),
|
|
1126
|
+
lastSync: z3.string(),
|
|
1127
|
+
documents: z3.record(z3.string(), ManifestEntrySchema)
|
|
1096
1128
|
});
|
|
1097
1129
|
|
|
1098
1130
|
// src/sync/manifest.service.ts
|
|
@@ -1184,15 +1216,15 @@ import { Injectable as Injectable6, Logger as Logger3 } from "@nestjs/common";
|
|
|
1184
1216
|
import { ConfigService as ConfigService2 } from "@nestjs/config";
|
|
1185
1217
|
|
|
1186
1218
|
// src/schemas/config.schemas.ts
|
|
1187
|
-
import { z as
|
|
1188
|
-
var DuckDBConfigSchema =
|
|
1189
|
-
embeddingDimensions:
|
|
1219
|
+
import { z as z4 } from "zod";
|
|
1220
|
+
var DuckDBConfigSchema = z4.object({
|
|
1221
|
+
embeddingDimensions: z4.coerce.number().int().positive().default(512)
|
|
1190
1222
|
});
|
|
1191
|
-
var EmbeddingConfigSchema =
|
|
1192
|
-
provider:
|
|
1193
|
-
apiKey:
|
|
1194
|
-
model:
|
|
1195
|
-
dimensions:
|
|
1223
|
+
var EmbeddingConfigSchema = z4.object({
|
|
1224
|
+
provider: z4.enum(["openai", "voyage", "nomic", "mock"]).default("voyage"),
|
|
1225
|
+
apiKey: z4.string().optional(),
|
|
1226
|
+
model: z4.string().min(1).default("voyage-3.5-lite"),
|
|
1227
|
+
dimensions: z4.coerce.number().int().positive().default(512)
|
|
1196
1228
|
});
|
|
1197
1229
|
|
|
1198
1230
|
// src/embedding/embedding.types.ts
|
|
@@ -1285,17 +1317,17 @@ class OpenAIEmbeddingProvider {
|
|
|
1285
1317
|
}
|
|
1286
1318
|
|
|
1287
1319
|
// src/schemas/embedding.schemas.ts
|
|
1288
|
-
import { z as
|
|
1289
|
-
var VoyageEmbeddingResponseSchema =
|
|
1290
|
-
object:
|
|
1291
|
-
data:
|
|
1292
|
-
object:
|
|
1293
|
-
embedding:
|
|
1294
|
-
index:
|
|
1320
|
+
import { z as z5 } from "zod";
|
|
1321
|
+
var VoyageEmbeddingResponseSchema = z5.object({
|
|
1322
|
+
object: z5.string(),
|
|
1323
|
+
data: z5.array(z5.object({
|
|
1324
|
+
object: z5.string(),
|
|
1325
|
+
embedding: z5.array(z5.number()),
|
|
1326
|
+
index: z5.number().int().nonnegative()
|
|
1295
1327
|
})),
|
|
1296
|
-
model:
|
|
1297
|
-
usage:
|
|
1298
|
-
total_tokens:
|
|
1328
|
+
model: z5.string(),
|
|
1329
|
+
usage: z5.object({
|
|
1330
|
+
total_tokens: z5.number().int().nonnegative()
|
|
1299
1331
|
})
|
|
1300
1332
|
});
|
|
1301
1333
|
|
|
@@ -1606,8 +1638,8 @@ import { glob } from "glob";
|
|
|
1606
1638
|
|
|
1607
1639
|
// src/utils/frontmatter.ts
|
|
1608
1640
|
import matter from "gray-matter";
|
|
1609
|
-
import { z as
|
|
1610
|
-
var
|
|
1641
|
+
import { z as z6 } from "zod";
|
|
1642
|
+
var EntityTypeSchema2 = z6.enum([
|
|
1611
1643
|
"Topic",
|
|
1612
1644
|
"Technology",
|
|
1613
1645
|
"Concept",
|
|
@@ -1617,20 +1649,20 @@ var EntityTypeSchema = z5.enum([
|
|
|
1617
1649
|
"Organization",
|
|
1618
1650
|
"Document"
|
|
1619
1651
|
]);
|
|
1620
|
-
var
|
|
1621
|
-
var
|
|
1622
|
-
name:
|
|
1623
|
-
type:
|
|
1624
|
-
description:
|
|
1652
|
+
var RelationTypeSchema2 = z6.enum(["REFERENCES"]);
|
|
1653
|
+
var EntitySchema2 = z6.object({
|
|
1654
|
+
name: z6.string().min(1),
|
|
1655
|
+
type: EntityTypeSchema2,
|
|
1656
|
+
description: z6.string().min(1)
|
|
1625
1657
|
});
|
|
1626
|
-
var
|
|
1627
|
-
source:
|
|
1628
|
-
relation:
|
|
1629
|
-
target:
|
|
1658
|
+
var RelationshipSchema2 = z6.object({
|
|
1659
|
+
source: z6.string().min(1),
|
|
1660
|
+
relation: RelationTypeSchema2,
|
|
1661
|
+
target: z6.string().min(1)
|
|
1630
1662
|
});
|
|
1631
|
-
var
|
|
1632
|
-
importance:
|
|
1633
|
-
domain:
|
|
1663
|
+
var GraphMetadataSchema2 = z6.object({
|
|
1664
|
+
importance: z6.enum(["high", "medium", "low"]).optional(),
|
|
1665
|
+
domain: z6.string().optional()
|
|
1634
1666
|
});
|
|
1635
1667
|
var validateDateFormat = (dateStr) => {
|
|
1636
1668
|
const match = dateStr.match(/^(\d{4})-(\d{2})-(\d{2})$/);
|
|
@@ -1650,16 +1682,16 @@ var validateDateFormat = (dateStr) => {
|
|
|
1650
1682
|
}
|
|
1651
1683
|
return day <= daysInMonth[month - 1];
|
|
1652
1684
|
};
|
|
1653
|
-
var FrontmatterSchema =
|
|
1654
|
-
created:
|
|
1655
|
-
updated:
|
|
1656
|
-
status:
|
|
1657
|
-
topic:
|
|
1658
|
-
tags:
|
|
1659
|
-
summary:
|
|
1660
|
-
entities:
|
|
1661
|
-
relationships:
|
|
1662
|
-
graph:
|
|
1685
|
+
var FrontmatterSchema = z6.object({
|
|
1686
|
+
created: z6.string().refine(validateDateFormat, "Date must be in YYYY-MM-DD format"),
|
|
1687
|
+
updated: z6.string().refine(validateDateFormat, "Date must be in YYYY-MM-DD format"),
|
|
1688
|
+
status: z6.enum(["draft", "ongoing", "complete"]).optional(),
|
|
1689
|
+
topic: z6.string().optional(),
|
|
1690
|
+
tags: z6.array(z6.string()).optional(),
|
|
1691
|
+
summary: z6.string().optional(),
|
|
1692
|
+
entities: z6.array(EntitySchema2).optional(),
|
|
1693
|
+
relationships: z6.array(RelationshipSchema2).optional(),
|
|
1694
|
+
graph: GraphMetadataSchema2.optional()
|
|
1663
1695
|
}).passthrough();
|
|
1664
1696
|
function parseFrontmatter(content) {
|
|
1665
1697
|
try {
|
|
@@ -1775,7 +1807,7 @@ class DocumentParserService {
|
|
|
1775
1807
|
if (!fm?.graph) {
|
|
1776
1808
|
return;
|
|
1777
1809
|
}
|
|
1778
|
-
const result =
|
|
1810
|
+
const result = GraphMetadataSchema2.safeParse(fm.graph);
|
|
1779
1811
|
return result.success ? result.data : undefined;
|
|
1780
1812
|
}
|
|
1781
1813
|
computeHash(content) {
|
|
@@ -3197,13 +3229,244 @@ SqlCommand = __legacyDecorateClassTS([
|
|
|
3197
3229
|
typeof GraphService === "undefined" ? Object : GraphService
|
|
3198
3230
|
])
|
|
3199
3231
|
], SqlCommand);
|
|
3232
|
+
// src/commands/question.command.ts
|
|
3233
|
+
import { Injectable as Injectable16 } from "@nestjs/common";
|
|
3234
|
+
import { Command as Command6, CommandRunner as CommandRunner6, Option as Option4 } from "nest-commander";
|
|
3235
|
+
function escapeSql(value) {
|
|
3236
|
+
return value.replace(/'/g, "''");
|
|
3237
|
+
}
|
|
3238
|
+
|
|
3239
|
+
class QuestionAddCommand extends CommandRunner6 {
|
|
3240
|
+
graphService;
|
|
3241
|
+
embeddingService;
|
|
3242
|
+
constructor(graphService, embeddingService) {
|
|
3243
|
+
super();
|
|
3244
|
+
this.graphService = graphService;
|
|
3245
|
+
this.embeddingService = embeddingService;
|
|
3246
|
+
}
|
|
3247
|
+
async run(inputs, options) {
|
|
3248
|
+
const questionText = inputs[0];
|
|
3249
|
+
if (!questionText) {
|
|
3250
|
+
console.error("Error: Question text is required");
|
|
3251
|
+
console.error('Usage: lattice question:add "your question"');
|
|
3252
|
+
process.exit(1);
|
|
3253
|
+
}
|
|
3254
|
+
try {
|
|
3255
|
+
await this.graphService.upsertNode("Question", {
|
|
3256
|
+
name: questionText,
|
|
3257
|
+
text: questionText,
|
|
3258
|
+
createdAt: new Date().toISOString()
|
|
3259
|
+
});
|
|
3260
|
+
const embedding = await this.embeddingService.generateEmbedding(`Question: ${questionText}`);
|
|
3261
|
+
await this.graphService.updateNodeEmbedding("Question", questionText, embedding);
|
|
3262
|
+
console.log(`
|
|
3263
|
+
\u2705 Added question: "${questionText}"`);
|
|
3264
|
+
if (options.answeredBy) {
|
|
3265
|
+
const docResult = await this.graphService.query(`
|
|
3266
|
+
SELECT name FROM nodes
|
|
3267
|
+
WHERE label = 'Document' AND name = '${escapeSql(options.answeredBy)}'
|
|
3268
|
+
`);
|
|
3269
|
+
if (docResult.resultSet.length === 0) {
|
|
3270
|
+
console.log(`
|
|
3271
|
+
\u26A0\uFE0F Document not found: ${options.answeredBy}`);
|
|
3272
|
+
console.log(" Run 'lattice sync' first to add documents to the graph.");
|
|
3273
|
+
console.log(` Question was created but not linked.
|
|
3274
|
+
`);
|
|
3275
|
+
process.exit(0);
|
|
3276
|
+
}
|
|
3277
|
+
await this.graphService.upsertRelationship("Question", questionText, "ANSWERED_BY", "Document", options.answeredBy, {});
|
|
3278
|
+
console.log(` Linked to: ${options.answeredBy}`);
|
|
3279
|
+
}
|
|
3280
|
+
console.log();
|
|
3281
|
+
process.exit(0);
|
|
3282
|
+
} catch (error) {
|
|
3283
|
+
console.error("Error:", error instanceof Error ? error.message : String(error));
|
|
3284
|
+
process.exit(1);
|
|
3285
|
+
}
|
|
3286
|
+
}
|
|
3287
|
+
parseAnsweredBy(value) {
|
|
3288
|
+
return value;
|
|
3289
|
+
}
|
|
3290
|
+
}
|
|
3291
|
+
__legacyDecorateClassTS([
|
|
3292
|
+
Option4({
|
|
3293
|
+
flags: "--answered-by <path>",
|
|
3294
|
+
description: "Immediately link to a document that answers this question"
|
|
3295
|
+
}),
|
|
3296
|
+
__legacyMetadataTS("design:type", Function),
|
|
3297
|
+
__legacyMetadataTS("design:paramtypes", [
|
|
3298
|
+
String
|
|
3299
|
+
]),
|
|
3300
|
+
__legacyMetadataTS("design:returntype", String)
|
|
3301
|
+
], QuestionAddCommand.prototype, "parseAnsweredBy", null);
|
|
3302
|
+
QuestionAddCommand = __legacyDecorateClassTS([
|
|
3303
|
+
Injectable16(),
|
|
3304
|
+
Command6({
|
|
3305
|
+
name: "question:add",
|
|
3306
|
+
arguments: "<question>",
|
|
3307
|
+
description: "Add a new question to the knowledge graph"
|
|
3308
|
+
}),
|
|
3309
|
+
__legacyMetadataTS("design:paramtypes", [
|
|
3310
|
+
typeof GraphService === "undefined" ? Object : GraphService,
|
|
3311
|
+
typeof EmbeddingService === "undefined" ? Object : EmbeddingService
|
|
3312
|
+
])
|
|
3313
|
+
], QuestionAddCommand);
|
|
3314
|
+
|
|
3315
|
+
class QuestionLinkCommand extends CommandRunner6 {
|
|
3316
|
+
graphService;
|
|
3317
|
+
embeddingService;
|
|
3318
|
+
constructor(graphService, embeddingService) {
|
|
3319
|
+
super();
|
|
3320
|
+
this.graphService = graphService;
|
|
3321
|
+
this.embeddingService = embeddingService;
|
|
3322
|
+
}
|
|
3323
|
+
async run(inputs, options) {
|
|
3324
|
+
const questionText = inputs[0];
|
|
3325
|
+
if (!questionText) {
|
|
3326
|
+
console.error("Error: Question text is required");
|
|
3327
|
+
console.error('Usage: lattice question:link "your question" --doc path/to/doc.md');
|
|
3328
|
+
process.exit(1);
|
|
3329
|
+
}
|
|
3330
|
+
if (!options.doc) {
|
|
3331
|
+
console.error("Error: --doc flag is required");
|
|
3332
|
+
console.error('Usage: lattice question:link "your question" --doc path/to/doc.md');
|
|
3333
|
+
process.exit(1);
|
|
3334
|
+
}
|
|
3335
|
+
try {
|
|
3336
|
+
const existingQuestions = await this.graphService.query(`
|
|
3337
|
+
SELECT name FROM nodes
|
|
3338
|
+
WHERE label = 'Question' AND name = '${escapeSql(questionText)}'
|
|
3339
|
+
`);
|
|
3340
|
+
if (existingQuestions.resultSet.length === 0) {
|
|
3341
|
+
await this.graphService.upsertNode("Question", {
|
|
3342
|
+
name: questionText,
|
|
3343
|
+
text: questionText,
|
|
3344
|
+
createdAt: new Date().toISOString()
|
|
3345
|
+
});
|
|
3346
|
+
const embedding = await this.embeddingService.generateEmbedding(`Question: ${questionText}`);
|
|
3347
|
+
await this.graphService.updateNodeEmbedding("Question", questionText, embedding);
|
|
3348
|
+
console.log(`
|
|
3349
|
+
\u2705 Created question: "${questionText}"`);
|
|
3350
|
+
}
|
|
3351
|
+
const existingDocs = await this.graphService.query(`
|
|
3352
|
+
SELECT name FROM nodes
|
|
3353
|
+
WHERE label = 'Document' AND name = '${escapeSql(options.doc)}'
|
|
3354
|
+
`);
|
|
3355
|
+
if (existingDocs.resultSet.length === 0) {
|
|
3356
|
+
console.error(`
|
|
3357
|
+
\u274C Document not found in graph: ${options.doc}`);
|
|
3358
|
+
console.error(` Run 'lattice sync' first to add documents to the graph.
|
|
3359
|
+
`);
|
|
3360
|
+
process.exit(1);
|
|
3361
|
+
}
|
|
3362
|
+
await this.graphService.upsertRelationship("Question", questionText, "ANSWERED_BY", "Document", options.doc, {});
|
|
3363
|
+
console.log(`
|
|
3364
|
+
\u2705 Linked: "${questionText}"`);
|
|
3365
|
+
console.log(` \u2192 ${options.doc}
|
|
3366
|
+
`);
|
|
3367
|
+
process.exit(0);
|
|
3368
|
+
} catch (error) {
|
|
3369
|
+
console.error("Error:", error instanceof Error ? error.message : String(error));
|
|
3370
|
+
process.exit(1);
|
|
3371
|
+
}
|
|
3372
|
+
}
|
|
3373
|
+
parseDoc(value) {
|
|
3374
|
+
return value;
|
|
3375
|
+
}
|
|
3376
|
+
}
|
|
3377
|
+
__legacyDecorateClassTS([
|
|
3378
|
+
Option4({
|
|
3379
|
+
flags: "-d, --doc <path>",
|
|
3380
|
+
description: "Path to the document that answers this question"
|
|
3381
|
+
}),
|
|
3382
|
+
__legacyMetadataTS("design:type", Function),
|
|
3383
|
+
__legacyMetadataTS("design:paramtypes", [
|
|
3384
|
+
String
|
|
3385
|
+
]),
|
|
3386
|
+
__legacyMetadataTS("design:returntype", String)
|
|
3387
|
+
], QuestionLinkCommand.prototype, "parseDoc", null);
|
|
3388
|
+
QuestionLinkCommand = __legacyDecorateClassTS([
|
|
3389
|
+
Injectable16(),
|
|
3390
|
+
Command6({
|
|
3391
|
+
name: "question:link",
|
|
3392
|
+
arguments: "<question>",
|
|
3393
|
+
description: "Link a question to a document via ANSWERED_BY relationship"
|
|
3394
|
+
}),
|
|
3395
|
+
__legacyMetadataTS("design:paramtypes", [
|
|
3396
|
+
typeof GraphService === "undefined" ? Object : GraphService,
|
|
3397
|
+
typeof EmbeddingService === "undefined" ? Object : EmbeddingService
|
|
3398
|
+
])
|
|
3399
|
+
], QuestionLinkCommand);
|
|
3400
|
+
|
|
3401
|
+
class QuestionUnansweredCommand extends CommandRunner6 {
|
|
3402
|
+
graphService;
|
|
3403
|
+
constructor(graphService) {
|
|
3404
|
+
super();
|
|
3405
|
+
this.graphService = graphService;
|
|
3406
|
+
}
|
|
3407
|
+
async run() {
|
|
3408
|
+
try {
|
|
3409
|
+
const result = await this.graphService.query(`
|
|
3410
|
+
SELECT
|
|
3411
|
+
q.name as question,
|
|
3412
|
+
q.properties->>'createdAt' as created_at,
|
|
3413
|
+
q.created_at as db_created_at
|
|
3414
|
+
FROM nodes q
|
|
3415
|
+
WHERE q.label = 'Question'
|
|
3416
|
+
AND NOT EXISTS (
|
|
3417
|
+
SELECT 1 FROM relationships r
|
|
3418
|
+
WHERE r.source_label = 'Question'
|
|
3419
|
+
AND r.source_name = q.name
|
|
3420
|
+
AND r.relation_type = 'ANSWERED_BY'
|
|
3421
|
+
)
|
|
3422
|
+
ORDER BY COALESCE(q.properties->>'createdAt', q.created_at::VARCHAR) DESC
|
|
3423
|
+
`);
|
|
3424
|
+
console.log(`
|
|
3425
|
+
=== Unanswered Questions ===
|
|
3426
|
+
`);
|
|
3427
|
+
if (result.resultSet.length === 0) {
|
|
3428
|
+
console.log(`No unanswered questions found.
|
|
3429
|
+
`);
|
|
3430
|
+
console.log(`Add questions with: lattice question:add "your question"
|
|
3431
|
+
`);
|
|
3432
|
+
process.exit(0);
|
|
3433
|
+
}
|
|
3434
|
+
result.resultSet.forEach((row, idx) => {
|
|
3435
|
+
const [question, createdAt, dbCreatedAt] = row;
|
|
3436
|
+
const displayDate = createdAt || dbCreatedAt || "unknown";
|
|
3437
|
+
console.log(`${idx + 1}. ${question}`);
|
|
3438
|
+
console.log(` Created: ${displayDate}`);
|
|
3439
|
+
});
|
|
3440
|
+
console.log(`
|
|
3441
|
+
Total: ${result.resultSet.length} unanswered question(s)
|
|
3442
|
+
`);
|
|
3443
|
+
console.log("To link a question to an answer:");
|
|
3444
|
+
console.log(` lattice question:link "question text" --doc path/to/doc.md
|
|
3445
|
+
`);
|
|
3446
|
+
process.exit(0);
|
|
3447
|
+
} catch (error) {
|
|
3448
|
+
console.error("Error:", error instanceof Error ? error.message : String(error));
|
|
3449
|
+
process.exit(1);
|
|
3450
|
+
}
|
|
3451
|
+
}
|
|
3452
|
+
}
|
|
3453
|
+
QuestionUnansweredCommand = __legacyDecorateClassTS([
|
|
3454
|
+
Injectable16(),
|
|
3455
|
+
Command6({
|
|
3456
|
+
name: "question:unanswered",
|
|
3457
|
+
description: "List all questions without ANSWERED_BY relationships"
|
|
3458
|
+
}),
|
|
3459
|
+
__legacyMetadataTS("design:paramtypes", [
|
|
3460
|
+
typeof GraphService === "undefined" ? Object : GraphService
|
|
3461
|
+
])
|
|
3462
|
+
], QuestionUnansweredCommand);
|
|
3200
3463
|
// src/commands/site.command.ts
|
|
3201
3464
|
import { spawn } from "child_process";
|
|
3202
3465
|
import { existsSync as existsSync6, readFileSync as readFileSync2, unlinkSync, writeFileSync as writeFileSync2 } from "fs";
|
|
3203
3466
|
import * as path2 from "path";
|
|
3204
|
-
import { Injectable as
|
|
3205
|
-
import { Command as
|
|
3206
|
-
class SiteCommand extends
|
|
3467
|
+
import { Injectable as Injectable17 } from "@nestjs/common";
|
|
3468
|
+
import { Command as Command7, CommandRunner as CommandRunner7, Option as Option5 } from "nest-commander";
|
|
3469
|
+
class SiteCommand extends CommandRunner7 {
|
|
3207
3470
|
getPidFile() {
|
|
3208
3471
|
return path2.join(getLatticeHome(), "site.pid");
|
|
3209
3472
|
}
|
|
@@ -3336,7 +3599,7 @@ class SiteCommand extends CommandRunner6 {
|
|
|
3336
3599
|
}
|
|
3337
3600
|
unlinkSync(pidFile);
|
|
3338
3601
|
console.log(`\u2705 Killed Lattice site process (PID: ${pid})`);
|
|
3339
|
-
} catch (
|
|
3602
|
+
} catch (_err) {
|
|
3340
3603
|
try {
|
|
3341
3604
|
unlinkSync(pidFile);
|
|
3342
3605
|
} catch {}
|
|
@@ -3357,7 +3620,7 @@ class SiteCommand extends CommandRunner6 {
|
|
|
3357
3620
|
}
|
|
3358
3621
|
}
|
|
3359
3622
|
__legacyDecorateClassTS([
|
|
3360
|
-
|
|
3623
|
+
Option5({
|
|
3361
3624
|
flags: "-b, --build",
|
|
3362
3625
|
description: "Build the site without starting the server"
|
|
3363
3626
|
}),
|
|
@@ -3366,7 +3629,7 @@ __legacyDecorateClassTS([
|
|
|
3366
3629
|
__legacyMetadataTS("design:returntype", Boolean)
|
|
3367
3630
|
], SiteCommand.prototype, "parseBuild", null);
|
|
3368
3631
|
__legacyDecorateClassTS([
|
|
3369
|
-
|
|
3632
|
+
Option5({
|
|
3370
3633
|
flags: "-d, --dev",
|
|
3371
3634
|
description: "Run in development mode (hot reload, no search)"
|
|
3372
3635
|
}),
|
|
@@ -3375,7 +3638,7 @@ __legacyDecorateClassTS([
|
|
|
3375
3638
|
__legacyMetadataTS("design:returntype", Boolean)
|
|
3376
3639
|
], SiteCommand.prototype, "parseDev", null);
|
|
3377
3640
|
__legacyDecorateClassTS([
|
|
3378
|
-
|
|
3641
|
+
Option5({
|
|
3379
3642
|
flags: "-p, --port <port>",
|
|
3380
3643
|
description: "Port to run the server on (default: 4321)"
|
|
3381
3644
|
}),
|
|
@@ -3386,7 +3649,7 @@ __legacyDecorateClassTS([
|
|
|
3386
3649
|
__legacyMetadataTS("design:returntype", String)
|
|
3387
3650
|
], SiteCommand.prototype, "parsePort", null);
|
|
3388
3651
|
__legacyDecorateClassTS([
|
|
3389
|
-
|
|
3652
|
+
Option5({
|
|
3390
3653
|
flags: "-k, --kill",
|
|
3391
3654
|
description: "Kill the running Lattice site process"
|
|
3392
3655
|
}),
|
|
@@ -3395,16 +3658,16 @@ __legacyDecorateClassTS([
|
|
|
3395
3658
|
__legacyMetadataTS("design:returntype", Boolean)
|
|
3396
3659
|
], SiteCommand.prototype, "parseKill", null);
|
|
3397
3660
|
SiteCommand = __legacyDecorateClassTS([
|
|
3398
|
-
|
|
3399
|
-
|
|
3661
|
+
Injectable17(),
|
|
3662
|
+
Command7({
|
|
3400
3663
|
name: "site",
|
|
3401
3664
|
description: "Build and run the Lattice documentation site"
|
|
3402
3665
|
})
|
|
3403
3666
|
], SiteCommand);
|
|
3404
3667
|
// src/commands/status.command.ts
|
|
3405
|
-
import { Injectable as
|
|
3406
|
-
import { Command as
|
|
3407
|
-
class StatusCommand extends
|
|
3668
|
+
import { Injectable as Injectable18 } from "@nestjs/common";
|
|
3669
|
+
import { Command as Command8, CommandRunner as CommandRunner8, Option as Option6 } from "nest-commander";
|
|
3670
|
+
class StatusCommand extends CommandRunner8 {
|
|
3408
3671
|
syncService;
|
|
3409
3672
|
dbChangeDetector;
|
|
3410
3673
|
constructor(syncService, dbChangeDetector) {
|
|
@@ -3470,7 +3733,7 @@ class StatusCommand extends CommandRunner7 {
|
|
|
3470
3733
|
}
|
|
3471
3734
|
}
|
|
3472
3735
|
__legacyDecorateClassTS([
|
|
3473
|
-
|
|
3736
|
+
Option6({
|
|
3474
3737
|
flags: "-v, --verbose",
|
|
3475
3738
|
description: "Show all documents including unchanged"
|
|
3476
3739
|
}),
|
|
@@ -3479,8 +3742,8 @@ __legacyDecorateClassTS([
|
|
|
3479
3742
|
__legacyMetadataTS("design:returntype", Boolean)
|
|
3480
3743
|
], StatusCommand.prototype, "parseVerbose", null);
|
|
3481
3744
|
StatusCommand = __legacyDecorateClassTS([
|
|
3482
|
-
|
|
3483
|
-
|
|
3745
|
+
Injectable18(),
|
|
3746
|
+
Command8({
|
|
3484
3747
|
name: "status",
|
|
3485
3748
|
description: "Show documents that need syncing (new or updated)"
|
|
3486
3749
|
}),
|
|
@@ -3492,11 +3755,11 @@ StatusCommand = __legacyDecorateClassTS([
|
|
|
3492
3755
|
// src/commands/sync.command.ts
|
|
3493
3756
|
import { watch } from "fs";
|
|
3494
3757
|
import { join as join4 } from "path";
|
|
3495
|
-
import { Injectable as
|
|
3496
|
-
import { Command as
|
|
3758
|
+
import { Injectable as Injectable20 } from "@nestjs/common";
|
|
3759
|
+
import { Command as Command9, CommandRunner as CommandRunner9, Option as Option7 } from "nest-commander";
|
|
3497
3760
|
|
|
3498
3761
|
// src/sync/graph-validator.service.ts
|
|
3499
|
-
import { Injectable as
|
|
3762
|
+
import { Injectable as Injectable19, Logger as Logger9 } from "@nestjs/common";
|
|
3500
3763
|
class GraphValidatorService {
|
|
3501
3764
|
graph;
|
|
3502
3765
|
logger = new Logger9(GraphValidatorService.name);
|
|
@@ -3645,14 +3908,14 @@ class GraphValidatorService {
|
|
|
3645
3908
|
}
|
|
3646
3909
|
}
|
|
3647
3910
|
GraphValidatorService = __legacyDecorateClassTS([
|
|
3648
|
-
|
|
3911
|
+
Injectable19(),
|
|
3649
3912
|
__legacyMetadataTS("design:paramtypes", [
|
|
3650
3913
|
typeof GraphService === "undefined" ? Object : GraphService
|
|
3651
3914
|
])
|
|
3652
3915
|
], GraphValidatorService);
|
|
3653
3916
|
|
|
3654
3917
|
// src/commands/sync.command.ts
|
|
3655
|
-
class SyncCommand extends
|
|
3918
|
+
class SyncCommand extends CommandRunner9 {
|
|
3656
3919
|
syncService;
|
|
3657
3920
|
graphService;
|
|
3658
3921
|
_graphValidator;
|
|
@@ -3667,7 +3930,7 @@ class SyncCommand extends CommandRunner8 {
|
|
|
3667
3930
|
async safeExit(code) {
|
|
3668
3931
|
try {
|
|
3669
3932
|
await this.graphService.checkpoint();
|
|
3670
|
-
} catch (
|
|
3933
|
+
} catch (_error) {
|
|
3671
3934
|
console.error("Warning: checkpoint failed during exit");
|
|
3672
3935
|
}
|
|
3673
3936
|
process.exit(code);
|
|
@@ -3925,7 +4188,7 @@ class SyncCommand extends CommandRunner8 {
|
|
|
3925
4188
|
}
|
|
3926
4189
|
}
|
|
3927
4190
|
__legacyDecorateClassTS([
|
|
3928
|
-
|
|
4191
|
+
Option7({
|
|
3929
4192
|
flags: "-f, --force",
|
|
3930
4193
|
description: "Force re-sync specified documents (requires paths to be specified)"
|
|
3931
4194
|
}),
|
|
@@ -3934,7 +4197,7 @@ __legacyDecorateClassTS([
|
|
|
3934
4197
|
__legacyMetadataTS("design:returntype", Boolean)
|
|
3935
4198
|
], SyncCommand.prototype, "parseForce", null);
|
|
3936
4199
|
__legacyDecorateClassTS([
|
|
3937
|
-
|
|
4200
|
+
Option7({
|
|
3938
4201
|
flags: "-d, --dry-run",
|
|
3939
4202
|
description: "Show what would change without applying"
|
|
3940
4203
|
}),
|
|
@@ -3943,7 +4206,7 @@ __legacyDecorateClassTS([
|
|
|
3943
4206
|
__legacyMetadataTS("design:returntype", Boolean)
|
|
3944
4207
|
], SyncCommand.prototype, "parseDryRun", null);
|
|
3945
4208
|
__legacyDecorateClassTS([
|
|
3946
|
-
|
|
4209
|
+
Option7({
|
|
3947
4210
|
flags: "-v, --verbose",
|
|
3948
4211
|
description: "Show detailed output"
|
|
3949
4212
|
}),
|
|
@@ -3952,7 +4215,7 @@ __legacyDecorateClassTS([
|
|
|
3952
4215
|
__legacyMetadataTS("design:returntype", Boolean)
|
|
3953
4216
|
], SyncCommand.prototype, "parseVerbose", null);
|
|
3954
4217
|
__legacyDecorateClassTS([
|
|
3955
|
-
|
|
4218
|
+
Option7({
|
|
3956
4219
|
flags: "-w, --watch",
|
|
3957
4220
|
description: "Watch for file changes and sync automatically"
|
|
3958
4221
|
}),
|
|
@@ -3961,7 +4224,7 @@ __legacyDecorateClassTS([
|
|
|
3961
4224
|
__legacyMetadataTS("design:returntype", Boolean)
|
|
3962
4225
|
], SyncCommand.prototype, "parseWatch", null);
|
|
3963
4226
|
__legacyDecorateClassTS([
|
|
3964
|
-
|
|
4227
|
+
Option7({
|
|
3965
4228
|
flags: "--diff",
|
|
3966
4229
|
description: "Show only changed documents (alias for --dry-run)"
|
|
3967
4230
|
}),
|
|
@@ -3970,7 +4233,7 @@ __legacyDecorateClassTS([
|
|
|
3970
4233
|
__legacyMetadataTS("design:returntype", Boolean)
|
|
3971
4234
|
], SyncCommand.prototype, "parseDiff", null);
|
|
3972
4235
|
__legacyDecorateClassTS([
|
|
3973
|
-
|
|
4236
|
+
Option7({
|
|
3974
4237
|
flags: "--skip-cascade",
|
|
3975
4238
|
description: "Skip cascade analysis (faster for large repos)"
|
|
3976
4239
|
}),
|
|
@@ -3979,7 +4242,7 @@ __legacyDecorateClassTS([
|
|
|
3979
4242
|
__legacyMetadataTS("design:returntype", Boolean)
|
|
3980
4243
|
], SyncCommand.prototype, "parseSkipCascade", null);
|
|
3981
4244
|
__legacyDecorateClassTS([
|
|
3982
|
-
|
|
4245
|
+
Option7({
|
|
3983
4246
|
flags: "--no-embeddings",
|
|
3984
4247
|
description: "Disable embedding generation during sync"
|
|
3985
4248
|
}),
|
|
@@ -3988,7 +4251,7 @@ __legacyDecorateClassTS([
|
|
|
3988
4251
|
__legacyMetadataTS("design:returntype", Boolean)
|
|
3989
4252
|
], SyncCommand.prototype, "parseNoEmbeddings", null);
|
|
3990
4253
|
__legacyDecorateClassTS([
|
|
3991
|
-
|
|
4254
|
+
Option7({
|
|
3992
4255
|
flags: "--skip-extraction",
|
|
3993
4256
|
description: "Skip AI entity extraction (sync without re-extracting entities)"
|
|
3994
4257
|
}),
|
|
@@ -3997,8 +4260,8 @@ __legacyDecorateClassTS([
|
|
|
3997
4260
|
__legacyMetadataTS("design:returntype", Boolean)
|
|
3998
4261
|
], SyncCommand.prototype, "parseSkipExtraction", null);
|
|
3999
4262
|
SyncCommand = __legacyDecorateClassTS([
|
|
4000
|
-
|
|
4001
|
-
|
|
4263
|
+
Injectable20(),
|
|
4264
|
+
Command9({
|
|
4002
4265
|
name: "sync",
|
|
4003
4266
|
arguments: "[paths...]",
|
|
4004
4267
|
description: "Synchronize documents to the knowledge graph"
|
|
@@ -4037,7 +4300,7 @@ GraphModule = __legacyDecorateClassTS([
|
|
|
4037
4300
|
import { Module as Module3 } from "@nestjs/common";
|
|
4038
4301
|
|
|
4039
4302
|
// src/query/query.service.ts
|
|
4040
|
-
import { Injectable as
|
|
4303
|
+
import { Injectable as Injectable21, Logger as Logger10 } from "@nestjs/common";
|
|
4041
4304
|
class QueryService {
|
|
4042
4305
|
graphService;
|
|
4043
4306
|
logger = new Logger10(QueryService.name);
|
|
@@ -4050,7 +4313,7 @@ class QueryService {
|
|
|
4050
4313
|
}
|
|
4051
4314
|
}
|
|
4052
4315
|
QueryService = __legacyDecorateClassTS([
|
|
4053
|
-
|
|
4316
|
+
Injectable21(),
|
|
4054
4317
|
__legacyMetadataTS("design:paramtypes", [
|
|
4055
4318
|
typeof GraphService === "undefined" ? Object : GraphService
|
|
4056
4319
|
])
|
|
@@ -4124,7 +4387,10 @@ AppModule = __legacyDecorateClassTS([
|
|
|
4124
4387
|
OntologyCommand,
|
|
4125
4388
|
InitCommand,
|
|
4126
4389
|
MigrateCommand,
|
|
4127
|
-
SiteCommand
|
|
4390
|
+
SiteCommand,
|
|
4391
|
+
QuestionAddCommand,
|
|
4392
|
+
QuestionLinkCommand,
|
|
4393
|
+
QuestionUnansweredCommand
|
|
4128
4394
|
]
|
|
4129
4395
|
})
|
|
4130
4396
|
], AppModule);
|
package/package.json
CHANGED
|
@@ -1,9 +1,13 @@
|
|
|
1
1
|
import { defineConfig } from 'astro/config';
|
|
2
2
|
import { astroSpaceship } from 'astro-spaceship';
|
|
3
|
+
import rehypeStripMdExtension from './src/plugins/rehype-strip-md-extension';
|
|
3
4
|
|
|
4
5
|
import websiteConfig from 'astro-spaceship/config';
|
|
5
6
|
|
|
6
7
|
export default defineConfig({
|
|
8
|
+
markdown: {
|
|
9
|
+
rehypePlugins: [rehypeStripMdExtension],
|
|
10
|
+
},
|
|
7
11
|
integrations: [
|
|
8
12
|
astroSpaceship(websiteConfig)
|
|
9
13
|
]
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { visit } from 'unist-util-visit';
|
|
2
|
+
import type { Root, Element } from 'hast';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Rehype plugin that strips .md extensions from relative links.
|
|
6
|
+
* This allows standard markdown links like [text](./file.md) to work
|
|
7
|
+
* correctly in Astro where routes don't include the .md extension.
|
|
8
|
+
*/
|
|
9
|
+
export default function rehypeStripMdExtension() {
|
|
10
|
+
return (tree: Root) => {
|
|
11
|
+
visit(tree, 'element', (node: Element) => {
|
|
12
|
+
if (
|
|
13
|
+
node.tagName === 'a' &&
|
|
14
|
+
typeof node.properties?.href === 'string'
|
|
15
|
+
) {
|
|
16
|
+
const href = node.properties.href;
|
|
17
|
+
// Only process relative links ending in .md
|
|
18
|
+
if (
|
|
19
|
+
!href.startsWith('http://') &&
|
|
20
|
+
!href.startsWith('https://') &&
|
|
21
|
+
!href.startsWith('//') &&
|
|
22
|
+
href.endsWith('.md')
|
|
23
|
+
) {
|
|
24
|
+
node.properties.href = href.slice(0, -3);
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
});
|
|
28
|
+
};
|
|
29
|
+
}
|
package/commands/graph-sync.md
DELETED
|
@@ -1,87 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
description: Sync modified docs to knowledge graph
|
|
3
|
-
model: sonnet
|
|
4
|
-
---
|
|
5
|
-
|
|
6
|
-
Sync modified documents in `~/.lattice/docs/` to the knowledge graph.
|
|
7
|
-
|
|
8
|
-
## Configuration
|
|
9
|
-
|
|
10
|
-
**⚠️ CRITICAL: All documentation lives in `~/.lattice/docs/`**
|
|
11
|
-
|
|
12
|
-
| Path | Purpose |
|
|
13
|
-
|------|---------|
|
|
14
|
-
| `~/.lattice/docs/` | Root documentation directory (ALWAYS use this) |
|
|
15
|
-
| `~/.lattice/docs/{topic}/` | Topic directories |
|
|
16
|
-
| `~/.lattice/docs/{topic}/*.md` | Research documents |
|
|
17
|
-
|
|
18
|
-
**NEVER use project-local `docs/` directories. ALWAYS use absolute path `~/.lattice/docs/`.**
|
|
19
|
-
|
|
20
|
-
## Process
|
|
21
|
-
|
|
22
|
-
### Step 1: Check What Needs Syncing
|
|
23
|
-
|
|
24
|
-
Run the status command to identify modified documents:
|
|
25
|
-
|
|
26
|
-
```bash
|
|
27
|
-
lattice status
|
|
28
|
-
```
|
|
29
|
-
|
|
30
|
-
This will show:
|
|
31
|
-
- **New** documents not yet in the graph
|
|
32
|
-
- **Updated** documents that have changed since last sync
|
|
33
|
-
|
|
34
|
-
If no documents need syncing, report that and exit.
|
|
35
|
-
|
|
36
|
-
### Step 2: Sync to Graph
|
|
37
|
-
|
|
38
|
-
Run sync to process all changed documents:
|
|
39
|
-
|
|
40
|
-
```bash
|
|
41
|
-
lattice sync
|
|
42
|
-
```
|
|
43
|
-
|
|
44
|
-
This will automatically:
|
|
45
|
-
- **Extract entities** using AI (Claude Haiku) for each new/updated document
|
|
46
|
-
- **Generate embeddings** for semantic search
|
|
47
|
-
- **Create entity relationships** in the graph
|
|
48
|
-
- **Update the sync manifest** with new hashes
|
|
49
|
-
|
|
50
|
-
The sync command includes built-in rate limiting (500ms between extractions) to avoid API throttling.
|
|
51
|
-
|
|
52
|
-
### Step 3: Report Results
|
|
53
|
-
|
|
54
|
-
Summarize what was processed:
|
|
55
|
-
- Number of documents synced
|
|
56
|
-
- Entities extracted per document
|
|
57
|
-
- Graph sync statistics (added, updated, unchanged)
|
|
58
|
-
- Any errors encountered
|
|
59
|
-
|
|
60
|
-
## Example Output
|
|
61
|
-
|
|
62
|
-
```
|
|
63
|
-
## Graph Sync
|
|
64
|
-
|
|
65
|
-
lattice status:
|
|
66
|
-
- 3 documents need syncing (2 new, 1 updated)
|
|
67
|
-
|
|
68
|
-
lattice sync:
|
|
69
|
-
- ~/.lattice/docs/american-holidays/README.md → 4 entities extracted
|
|
70
|
-
- ~/.lattice/docs/american-holidays/thanksgiving-vs-christmas.md → 8 entities extracted
|
|
71
|
-
- ~/.lattice/docs/bun-nestjs/notes.md → 5 entities extracted
|
|
72
|
-
|
|
73
|
-
Summary:
|
|
74
|
-
- Added: 2
|
|
75
|
-
- Updated: 1
|
|
76
|
-
- Unchanged: 126
|
|
77
|
-
- Duration: 3.2s
|
|
78
|
-
```
|
|
79
|
-
|
|
80
|
-
## Important Notes
|
|
81
|
-
|
|
82
|
-
- **AI extraction is automatic** - no need for manual `/entity-extract` calls
|
|
83
|
-
- **Incremental sync** - only processes changed documents
|
|
84
|
-
- **Self-correcting** - Claude validates extractions and fixes errors automatically
|
|
85
|
-
- **Safe to run frequently** - won't duplicate or corrupt data
|
|
86
|
-
- **No frontmatter required** - documents are plain markdown
|
|
87
|
-
- **Batch syncing** - run once after multiple research sessions for efficiency
|