@zabaca/lattice 1.1.0 → 1.2.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/commands/research.md +69 -17
- package/dist/main.js +375 -113
- package/package.json +2 -1
- package/site-template/astro.config.ts +10 -0
- package/site-template/package.json +19 -0
- package/site-template/src/collections/authors.ts +15 -0
- package/site-template/src/collections/documents.ts +27 -0
- package/site-template/src/collections/tags.ts +13 -0
- package/site-template/src/content.config.ts +9 -0
- package/site-template/tsconfig.json +13 -0
- package/commands/graph-sync.md +0 -87
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",
|
|
@@ -465,9 +489,13 @@ OBSIDIAN_VAULT_DIR=docs
|
|
|
465
489
|
console.log();
|
|
466
490
|
console.log("Claude Code slash commands:");
|
|
467
491
|
console.log(" /research <topic> - AI-assisted research workflow");
|
|
468
|
-
console.log(" /graph-sync - Extract entities and sync to graph");
|
|
469
492
|
console.log(" /entity-extract - Extract entities from a document");
|
|
470
493
|
console.log();
|
|
494
|
+
console.log("Question tracking:");
|
|
495
|
+
console.log(" lattice question:add <question> - Add a question");
|
|
496
|
+
console.log(" lattice question:link <q> --doc <p> - Link question to answer");
|
|
497
|
+
console.log(" lattice question:unanswered - List unanswered questions");
|
|
498
|
+
console.log();
|
|
471
499
|
if (!(await fs.readFile(envPath, "utf-8")).includes("pa-")) {
|
|
472
500
|
console.log(`\u26A0\uFE0F Add your Voyage API key to: ${envPath}`);
|
|
473
501
|
console.log();
|
|
@@ -513,7 +541,7 @@ OBSIDIAN_VAULT_DIR=docs
|
|
|
513
541
|
await fs.mkdir(path.dirname(targetPath), { recursive: true });
|
|
514
542
|
await fs.copyFile(sourcePath, targetPath);
|
|
515
543
|
copied++;
|
|
516
|
-
} catch (
|
|
544
|
+
} catch (_err) {}
|
|
517
545
|
}
|
|
518
546
|
if (copied > 0) {
|
|
519
547
|
console.log(`\u2705 Site template: ${copied} file(s) installed`);
|
|
@@ -557,7 +585,7 @@ OBSIDIAN_VAULT_DIR=docs
|
|
|
557
585
|
await fs.copyFile(sourcePath, targetPath);
|
|
558
586
|
installed.push(file);
|
|
559
587
|
copied++;
|
|
560
|
-
} catch (
|
|
588
|
+
} catch (_err) {}
|
|
561
589
|
}
|
|
562
590
|
if (copied > 0) {
|
|
563
591
|
console.log(`\u2705 Claude commands: ${copied} installed to ${targetDir}`);
|
|
@@ -1081,18 +1109,18 @@ import { readFile as readFile3, writeFile } from "fs/promises";
|
|
|
1081
1109
|
import { Injectable as Injectable5 } from "@nestjs/common";
|
|
1082
1110
|
|
|
1083
1111
|
// src/schemas/manifest.schemas.ts
|
|
1084
|
-
import { z as
|
|
1085
|
-
var ManifestEntrySchema =
|
|
1086
|
-
contentHash:
|
|
1087
|
-
frontmatterHash:
|
|
1088
|
-
lastSynced:
|
|
1089
|
-
entityCount:
|
|
1090
|
-
relationshipCount:
|
|
1112
|
+
import { z as z3 } from "zod";
|
|
1113
|
+
var ManifestEntrySchema = z3.object({
|
|
1114
|
+
contentHash: z3.string(),
|
|
1115
|
+
frontmatterHash: z3.string(),
|
|
1116
|
+
lastSynced: z3.string(),
|
|
1117
|
+
entityCount: z3.number().int().nonnegative(),
|
|
1118
|
+
relationshipCount: z3.number().int().nonnegative()
|
|
1091
1119
|
});
|
|
1092
|
-
var SyncManifestSchema =
|
|
1093
|
-
version:
|
|
1094
|
-
lastSync:
|
|
1095
|
-
documents:
|
|
1120
|
+
var SyncManifestSchema = z3.object({
|
|
1121
|
+
version: z3.string(),
|
|
1122
|
+
lastSync: z3.string(),
|
|
1123
|
+
documents: z3.record(z3.string(), ManifestEntrySchema)
|
|
1096
1124
|
});
|
|
1097
1125
|
|
|
1098
1126
|
// src/sync/manifest.service.ts
|
|
@@ -1184,15 +1212,15 @@ import { Injectable as Injectable6, Logger as Logger3 } from "@nestjs/common";
|
|
|
1184
1212
|
import { ConfigService as ConfigService2 } from "@nestjs/config";
|
|
1185
1213
|
|
|
1186
1214
|
// src/schemas/config.schemas.ts
|
|
1187
|
-
import { z as
|
|
1188
|
-
var DuckDBConfigSchema =
|
|
1189
|
-
embeddingDimensions:
|
|
1215
|
+
import { z as z4 } from "zod";
|
|
1216
|
+
var DuckDBConfigSchema = z4.object({
|
|
1217
|
+
embeddingDimensions: z4.coerce.number().int().positive().default(512)
|
|
1190
1218
|
});
|
|
1191
|
-
var EmbeddingConfigSchema =
|
|
1192
|
-
provider:
|
|
1193
|
-
apiKey:
|
|
1194
|
-
model:
|
|
1195
|
-
dimensions:
|
|
1219
|
+
var EmbeddingConfigSchema = z4.object({
|
|
1220
|
+
provider: z4.enum(["openai", "voyage", "nomic", "mock"]).default("voyage"),
|
|
1221
|
+
apiKey: z4.string().optional(),
|
|
1222
|
+
model: z4.string().min(1).default("voyage-3.5-lite"),
|
|
1223
|
+
dimensions: z4.coerce.number().int().positive().default(512)
|
|
1196
1224
|
});
|
|
1197
1225
|
|
|
1198
1226
|
// src/embedding/embedding.types.ts
|
|
@@ -1285,17 +1313,17 @@ class OpenAIEmbeddingProvider {
|
|
|
1285
1313
|
}
|
|
1286
1314
|
|
|
1287
1315
|
// src/schemas/embedding.schemas.ts
|
|
1288
|
-
import { z as
|
|
1289
|
-
var VoyageEmbeddingResponseSchema =
|
|
1290
|
-
object:
|
|
1291
|
-
data:
|
|
1292
|
-
object:
|
|
1293
|
-
embedding:
|
|
1294
|
-
index:
|
|
1316
|
+
import { z as z5 } from "zod";
|
|
1317
|
+
var VoyageEmbeddingResponseSchema = z5.object({
|
|
1318
|
+
object: z5.string(),
|
|
1319
|
+
data: z5.array(z5.object({
|
|
1320
|
+
object: z5.string(),
|
|
1321
|
+
embedding: z5.array(z5.number()),
|
|
1322
|
+
index: z5.number().int().nonnegative()
|
|
1295
1323
|
})),
|
|
1296
|
-
model:
|
|
1297
|
-
usage:
|
|
1298
|
-
total_tokens:
|
|
1324
|
+
model: z5.string(),
|
|
1325
|
+
usage: z5.object({
|
|
1326
|
+
total_tokens: z5.number().int().nonnegative()
|
|
1299
1327
|
})
|
|
1300
1328
|
});
|
|
1301
1329
|
|
|
@@ -1606,8 +1634,8 @@ import { glob } from "glob";
|
|
|
1606
1634
|
|
|
1607
1635
|
// src/utils/frontmatter.ts
|
|
1608
1636
|
import matter from "gray-matter";
|
|
1609
|
-
import { z as
|
|
1610
|
-
var
|
|
1637
|
+
import { z as z6 } from "zod";
|
|
1638
|
+
var EntityTypeSchema2 = z6.enum([
|
|
1611
1639
|
"Topic",
|
|
1612
1640
|
"Technology",
|
|
1613
1641
|
"Concept",
|
|
@@ -1617,20 +1645,20 @@ var EntityTypeSchema = z5.enum([
|
|
|
1617
1645
|
"Organization",
|
|
1618
1646
|
"Document"
|
|
1619
1647
|
]);
|
|
1620
|
-
var
|
|
1621
|
-
var
|
|
1622
|
-
name:
|
|
1623
|
-
type:
|
|
1624
|
-
description:
|
|
1648
|
+
var RelationTypeSchema2 = z6.enum(["REFERENCES"]);
|
|
1649
|
+
var EntitySchema2 = z6.object({
|
|
1650
|
+
name: z6.string().min(1),
|
|
1651
|
+
type: EntityTypeSchema2,
|
|
1652
|
+
description: z6.string().min(1)
|
|
1625
1653
|
});
|
|
1626
|
-
var
|
|
1627
|
-
source:
|
|
1628
|
-
relation:
|
|
1629
|
-
target:
|
|
1654
|
+
var RelationshipSchema2 = z6.object({
|
|
1655
|
+
source: z6.string().min(1),
|
|
1656
|
+
relation: RelationTypeSchema2,
|
|
1657
|
+
target: z6.string().min(1)
|
|
1630
1658
|
});
|
|
1631
|
-
var
|
|
1632
|
-
importance:
|
|
1633
|
-
domain:
|
|
1659
|
+
var GraphMetadataSchema2 = z6.object({
|
|
1660
|
+
importance: z6.enum(["high", "medium", "low"]).optional(),
|
|
1661
|
+
domain: z6.string().optional()
|
|
1634
1662
|
});
|
|
1635
1663
|
var validateDateFormat = (dateStr) => {
|
|
1636
1664
|
const match = dateStr.match(/^(\d{4})-(\d{2})-(\d{2})$/);
|
|
@@ -1650,16 +1678,16 @@ var validateDateFormat = (dateStr) => {
|
|
|
1650
1678
|
}
|
|
1651
1679
|
return day <= daysInMonth[month - 1];
|
|
1652
1680
|
};
|
|
1653
|
-
var FrontmatterSchema =
|
|
1654
|
-
created:
|
|
1655
|
-
updated:
|
|
1656
|
-
status:
|
|
1657
|
-
topic:
|
|
1658
|
-
tags:
|
|
1659
|
-
summary:
|
|
1660
|
-
entities:
|
|
1661
|
-
relationships:
|
|
1662
|
-
graph:
|
|
1681
|
+
var FrontmatterSchema = z6.object({
|
|
1682
|
+
created: z6.string().refine(validateDateFormat, "Date must be in YYYY-MM-DD format"),
|
|
1683
|
+
updated: z6.string().refine(validateDateFormat, "Date must be in YYYY-MM-DD format"),
|
|
1684
|
+
status: z6.enum(["draft", "ongoing", "complete"]).optional(),
|
|
1685
|
+
topic: z6.string().optional(),
|
|
1686
|
+
tags: z6.array(z6.string()).optional(),
|
|
1687
|
+
summary: z6.string().optional(),
|
|
1688
|
+
entities: z6.array(EntitySchema2).optional(),
|
|
1689
|
+
relationships: z6.array(RelationshipSchema2).optional(),
|
|
1690
|
+
graph: GraphMetadataSchema2.optional()
|
|
1663
1691
|
}).passthrough();
|
|
1664
1692
|
function parseFrontmatter(content) {
|
|
1665
1693
|
try {
|
|
@@ -1775,7 +1803,7 @@ class DocumentParserService {
|
|
|
1775
1803
|
if (!fm?.graph) {
|
|
1776
1804
|
return;
|
|
1777
1805
|
}
|
|
1778
|
-
const result =
|
|
1806
|
+
const result = GraphMetadataSchema2.safeParse(fm.graph);
|
|
1779
1807
|
return result.success ? result.data : undefined;
|
|
1780
1808
|
}
|
|
1781
1809
|
computeHash(content) {
|
|
@@ -3197,13 +3225,244 @@ SqlCommand = __legacyDecorateClassTS([
|
|
|
3197
3225
|
typeof GraphService === "undefined" ? Object : GraphService
|
|
3198
3226
|
])
|
|
3199
3227
|
], SqlCommand);
|
|
3228
|
+
// src/commands/question.command.ts
|
|
3229
|
+
import { Injectable as Injectable16 } from "@nestjs/common";
|
|
3230
|
+
import { Command as Command6, CommandRunner as CommandRunner6, Option as Option4 } from "nest-commander";
|
|
3231
|
+
function escapeSql(value) {
|
|
3232
|
+
return value.replace(/'/g, "''");
|
|
3233
|
+
}
|
|
3234
|
+
|
|
3235
|
+
class QuestionAddCommand extends CommandRunner6 {
|
|
3236
|
+
graphService;
|
|
3237
|
+
embeddingService;
|
|
3238
|
+
constructor(graphService, embeddingService) {
|
|
3239
|
+
super();
|
|
3240
|
+
this.graphService = graphService;
|
|
3241
|
+
this.embeddingService = embeddingService;
|
|
3242
|
+
}
|
|
3243
|
+
async run(inputs, options) {
|
|
3244
|
+
const questionText = inputs[0];
|
|
3245
|
+
if (!questionText) {
|
|
3246
|
+
console.error("Error: Question text is required");
|
|
3247
|
+
console.error('Usage: lattice question:add "your question"');
|
|
3248
|
+
process.exit(1);
|
|
3249
|
+
}
|
|
3250
|
+
try {
|
|
3251
|
+
await this.graphService.upsertNode("Question", {
|
|
3252
|
+
name: questionText,
|
|
3253
|
+
text: questionText,
|
|
3254
|
+
createdAt: new Date().toISOString()
|
|
3255
|
+
});
|
|
3256
|
+
const embedding = await this.embeddingService.generateEmbedding(`Question: ${questionText}`);
|
|
3257
|
+
await this.graphService.updateNodeEmbedding("Question", questionText, embedding);
|
|
3258
|
+
console.log(`
|
|
3259
|
+
\u2705 Added question: "${questionText}"`);
|
|
3260
|
+
if (options.answeredBy) {
|
|
3261
|
+
const docResult = await this.graphService.query(`
|
|
3262
|
+
SELECT name FROM nodes
|
|
3263
|
+
WHERE label = 'Document' AND name = '${escapeSql(options.answeredBy)}'
|
|
3264
|
+
`);
|
|
3265
|
+
if (docResult.resultSet.length === 0) {
|
|
3266
|
+
console.log(`
|
|
3267
|
+
\u26A0\uFE0F Document not found: ${options.answeredBy}`);
|
|
3268
|
+
console.log(" Run 'lattice sync' first to add documents to the graph.");
|
|
3269
|
+
console.log(` Question was created but not linked.
|
|
3270
|
+
`);
|
|
3271
|
+
process.exit(0);
|
|
3272
|
+
}
|
|
3273
|
+
await this.graphService.upsertRelationship("Question", questionText, "ANSWERED_BY", "Document", options.answeredBy, {});
|
|
3274
|
+
console.log(` Linked to: ${options.answeredBy}`);
|
|
3275
|
+
}
|
|
3276
|
+
console.log();
|
|
3277
|
+
process.exit(0);
|
|
3278
|
+
} catch (error) {
|
|
3279
|
+
console.error("Error:", error instanceof Error ? error.message : String(error));
|
|
3280
|
+
process.exit(1);
|
|
3281
|
+
}
|
|
3282
|
+
}
|
|
3283
|
+
parseAnsweredBy(value) {
|
|
3284
|
+
return value;
|
|
3285
|
+
}
|
|
3286
|
+
}
|
|
3287
|
+
__legacyDecorateClassTS([
|
|
3288
|
+
Option4({
|
|
3289
|
+
flags: "--answered-by <path>",
|
|
3290
|
+
description: "Immediately link to a document that answers this question"
|
|
3291
|
+
}),
|
|
3292
|
+
__legacyMetadataTS("design:type", Function),
|
|
3293
|
+
__legacyMetadataTS("design:paramtypes", [
|
|
3294
|
+
String
|
|
3295
|
+
]),
|
|
3296
|
+
__legacyMetadataTS("design:returntype", String)
|
|
3297
|
+
], QuestionAddCommand.prototype, "parseAnsweredBy", null);
|
|
3298
|
+
QuestionAddCommand = __legacyDecorateClassTS([
|
|
3299
|
+
Injectable16(),
|
|
3300
|
+
Command6({
|
|
3301
|
+
name: "question:add",
|
|
3302
|
+
arguments: "<question>",
|
|
3303
|
+
description: "Add a new question to the knowledge graph"
|
|
3304
|
+
}),
|
|
3305
|
+
__legacyMetadataTS("design:paramtypes", [
|
|
3306
|
+
typeof GraphService === "undefined" ? Object : GraphService,
|
|
3307
|
+
typeof EmbeddingService === "undefined" ? Object : EmbeddingService
|
|
3308
|
+
])
|
|
3309
|
+
], QuestionAddCommand);
|
|
3310
|
+
|
|
3311
|
+
class QuestionLinkCommand extends CommandRunner6 {
|
|
3312
|
+
graphService;
|
|
3313
|
+
embeddingService;
|
|
3314
|
+
constructor(graphService, embeddingService) {
|
|
3315
|
+
super();
|
|
3316
|
+
this.graphService = graphService;
|
|
3317
|
+
this.embeddingService = embeddingService;
|
|
3318
|
+
}
|
|
3319
|
+
async run(inputs, options) {
|
|
3320
|
+
const questionText = inputs[0];
|
|
3321
|
+
if (!questionText) {
|
|
3322
|
+
console.error("Error: Question text is required");
|
|
3323
|
+
console.error('Usage: lattice question:link "your question" --doc path/to/doc.md');
|
|
3324
|
+
process.exit(1);
|
|
3325
|
+
}
|
|
3326
|
+
if (!options.doc) {
|
|
3327
|
+
console.error("Error: --doc flag is required");
|
|
3328
|
+
console.error('Usage: lattice question:link "your question" --doc path/to/doc.md');
|
|
3329
|
+
process.exit(1);
|
|
3330
|
+
}
|
|
3331
|
+
try {
|
|
3332
|
+
const existingQuestions = await this.graphService.query(`
|
|
3333
|
+
SELECT name FROM nodes
|
|
3334
|
+
WHERE label = 'Question' AND name = '${escapeSql(questionText)}'
|
|
3335
|
+
`);
|
|
3336
|
+
if (existingQuestions.resultSet.length === 0) {
|
|
3337
|
+
await this.graphService.upsertNode("Question", {
|
|
3338
|
+
name: questionText,
|
|
3339
|
+
text: questionText,
|
|
3340
|
+
createdAt: new Date().toISOString()
|
|
3341
|
+
});
|
|
3342
|
+
const embedding = await this.embeddingService.generateEmbedding(`Question: ${questionText}`);
|
|
3343
|
+
await this.graphService.updateNodeEmbedding("Question", questionText, embedding);
|
|
3344
|
+
console.log(`
|
|
3345
|
+
\u2705 Created question: "${questionText}"`);
|
|
3346
|
+
}
|
|
3347
|
+
const existingDocs = await this.graphService.query(`
|
|
3348
|
+
SELECT name FROM nodes
|
|
3349
|
+
WHERE label = 'Document' AND name = '${escapeSql(options.doc)}'
|
|
3350
|
+
`);
|
|
3351
|
+
if (existingDocs.resultSet.length === 0) {
|
|
3352
|
+
console.error(`
|
|
3353
|
+
\u274C Document not found in graph: ${options.doc}`);
|
|
3354
|
+
console.error(` Run 'lattice sync' first to add documents to the graph.
|
|
3355
|
+
`);
|
|
3356
|
+
process.exit(1);
|
|
3357
|
+
}
|
|
3358
|
+
await this.graphService.upsertRelationship("Question", questionText, "ANSWERED_BY", "Document", options.doc, {});
|
|
3359
|
+
console.log(`
|
|
3360
|
+
\u2705 Linked: "${questionText}"`);
|
|
3361
|
+
console.log(` \u2192 ${options.doc}
|
|
3362
|
+
`);
|
|
3363
|
+
process.exit(0);
|
|
3364
|
+
} catch (error) {
|
|
3365
|
+
console.error("Error:", error instanceof Error ? error.message : String(error));
|
|
3366
|
+
process.exit(1);
|
|
3367
|
+
}
|
|
3368
|
+
}
|
|
3369
|
+
parseDoc(value) {
|
|
3370
|
+
return value;
|
|
3371
|
+
}
|
|
3372
|
+
}
|
|
3373
|
+
__legacyDecorateClassTS([
|
|
3374
|
+
Option4({
|
|
3375
|
+
flags: "-d, --doc <path>",
|
|
3376
|
+
description: "Path to the document that answers this question"
|
|
3377
|
+
}),
|
|
3378
|
+
__legacyMetadataTS("design:type", Function),
|
|
3379
|
+
__legacyMetadataTS("design:paramtypes", [
|
|
3380
|
+
String
|
|
3381
|
+
]),
|
|
3382
|
+
__legacyMetadataTS("design:returntype", String)
|
|
3383
|
+
], QuestionLinkCommand.prototype, "parseDoc", null);
|
|
3384
|
+
QuestionLinkCommand = __legacyDecorateClassTS([
|
|
3385
|
+
Injectable16(),
|
|
3386
|
+
Command6({
|
|
3387
|
+
name: "question:link",
|
|
3388
|
+
arguments: "<question>",
|
|
3389
|
+
description: "Link a question to a document via ANSWERED_BY relationship"
|
|
3390
|
+
}),
|
|
3391
|
+
__legacyMetadataTS("design:paramtypes", [
|
|
3392
|
+
typeof GraphService === "undefined" ? Object : GraphService,
|
|
3393
|
+
typeof EmbeddingService === "undefined" ? Object : EmbeddingService
|
|
3394
|
+
])
|
|
3395
|
+
], QuestionLinkCommand);
|
|
3396
|
+
|
|
3397
|
+
class QuestionUnansweredCommand extends CommandRunner6 {
|
|
3398
|
+
graphService;
|
|
3399
|
+
constructor(graphService) {
|
|
3400
|
+
super();
|
|
3401
|
+
this.graphService = graphService;
|
|
3402
|
+
}
|
|
3403
|
+
async run() {
|
|
3404
|
+
try {
|
|
3405
|
+
const result = await this.graphService.query(`
|
|
3406
|
+
SELECT
|
|
3407
|
+
q.name as question,
|
|
3408
|
+
q.properties->>'createdAt' as created_at,
|
|
3409
|
+
q.created_at as db_created_at
|
|
3410
|
+
FROM nodes q
|
|
3411
|
+
WHERE q.label = 'Question'
|
|
3412
|
+
AND NOT EXISTS (
|
|
3413
|
+
SELECT 1 FROM relationships r
|
|
3414
|
+
WHERE r.source_label = 'Question'
|
|
3415
|
+
AND r.source_name = q.name
|
|
3416
|
+
AND r.relation_type = 'ANSWERED_BY'
|
|
3417
|
+
)
|
|
3418
|
+
ORDER BY COALESCE(q.properties->>'createdAt', q.created_at::VARCHAR) DESC
|
|
3419
|
+
`);
|
|
3420
|
+
console.log(`
|
|
3421
|
+
=== Unanswered Questions ===
|
|
3422
|
+
`);
|
|
3423
|
+
if (result.resultSet.length === 0) {
|
|
3424
|
+
console.log(`No unanswered questions found.
|
|
3425
|
+
`);
|
|
3426
|
+
console.log(`Add questions with: lattice question:add "your question"
|
|
3427
|
+
`);
|
|
3428
|
+
process.exit(0);
|
|
3429
|
+
}
|
|
3430
|
+
result.resultSet.forEach((row, idx) => {
|
|
3431
|
+
const [question, createdAt, dbCreatedAt] = row;
|
|
3432
|
+
const displayDate = createdAt || dbCreatedAt || "unknown";
|
|
3433
|
+
console.log(`${idx + 1}. ${question}`);
|
|
3434
|
+
console.log(` Created: ${displayDate}`);
|
|
3435
|
+
});
|
|
3436
|
+
console.log(`
|
|
3437
|
+
Total: ${result.resultSet.length} unanswered question(s)
|
|
3438
|
+
`);
|
|
3439
|
+
console.log("To link a question to an answer:");
|
|
3440
|
+
console.log(` lattice question:link "question text" --doc path/to/doc.md
|
|
3441
|
+
`);
|
|
3442
|
+
process.exit(0);
|
|
3443
|
+
} catch (error) {
|
|
3444
|
+
console.error("Error:", error instanceof Error ? error.message : String(error));
|
|
3445
|
+
process.exit(1);
|
|
3446
|
+
}
|
|
3447
|
+
}
|
|
3448
|
+
}
|
|
3449
|
+
QuestionUnansweredCommand = __legacyDecorateClassTS([
|
|
3450
|
+
Injectable16(),
|
|
3451
|
+
Command6({
|
|
3452
|
+
name: "question:unanswered",
|
|
3453
|
+
description: "List all questions without ANSWERED_BY relationships"
|
|
3454
|
+
}),
|
|
3455
|
+
__legacyMetadataTS("design:paramtypes", [
|
|
3456
|
+
typeof GraphService === "undefined" ? Object : GraphService
|
|
3457
|
+
])
|
|
3458
|
+
], QuestionUnansweredCommand);
|
|
3200
3459
|
// src/commands/site.command.ts
|
|
3201
3460
|
import { spawn } from "child_process";
|
|
3202
3461
|
import { existsSync as existsSync6, readFileSync as readFileSync2, unlinkSync, writeFileSync as writeFileSync2 } from "fs";
|
|
3203
3462
|
import * as path2 from "path";
|
|
3204
|
-
import { Injectable as
|
|
3205
|
-
import { Command as
|
|
3206
|
-
class SiteCommand extends
|
|
3463
|
+
import { Injectable as Injectable17 } from "@nestjs/common";
|
|
3464
|
+
import { Command as Command7, CommandRunner as CommandRunner7, Option as Option5 } from "nest-commander";
|
|
3465
|
+
class SiteCommand extends CommandRunner7 {
|
|
3207
3466
|
getPidFile() {
|
|
3208
3467
|
return path2.join(getLatticeHome(), "site.pid");
|
|
3209
3468
|
}
|
|
@@ -3336,7 +3595,7 @@ class SiteCommand extends CommandRunner6 {
|
|
|
3336
3595
|
}
|
|
3337
3596
|
unlinkSync(pidFile);
|
|
3338
3597
|
console.log(`\u2705 Killed Lattice site process (PID: ${pid})`);
|
|
3339
|
-
} catch (
|
|
3598
|
+
} catch (_err) {
|
|
3340
3599
|
try {
|
|
3341
3600
|
unlinkSync(pidFile);
|
|
3342
3601
|
} catch {}
|
|
@@ -3357,7 +3616,7 @@ class SiteCommand extends CommandRunner6 {
|
|
|
3357
3616
|
}
|
|
3358
3617
|
}
|
|
3359
3618
|
__legacyDecorateClassTS([
|
|
3360
|
-
|
|
3619
|
+
Option5({
|
|
3361
3620
|
flags: "-b, --build",
|
|
3362
3621
|
description: "Build the site without starting the server"
|
|
3363
3622
|
}),
|
|
@@ -3366,7 +3625,7 @@ __legacyDecorateClassTS([
|
|
|
3366
3625
|
__legacyMetadataTS("design:returntype", Boolean)
|
|
3367
3626
|
], SiteCommand.prototype, "parseBuild", null);
|
|
3368
3627
|
__legacyDecorateClassTS([
|
|
3369
|
-
|
|
3628
|
+
Option5({
|
|
3370
3629
|
flags: "-d, --dev",
|
|
3371
3630
|
description: "Run in development mode (hot reload, no search)"
|
|
3372
3631
|
}),
|
|
@@ -3375,7 +3634,7 @@ __legacyDecorateClassTS([
|
|
|
3375
3634
|
__legacyMetadataTS("design:returntype", Boolean)
|
|
3376
3635
|
], SiteCommand.prototype, "parseDev", null);
|
|
3377
3636
|
__legacyDecorateClassTS([
|
|
3378
|
-
|
|
3637
|
+
Option5({
|
|
3379
3638
|
flags: "-p, --port <port>",
|
|
3380
3639
|
description: "Port to run the server on (default: 4321)"
|
|
3381
3640
|
}),
|
|
@@ -3386,7 +3645,7 @@ __legacyDecorateClassTS([
|
|
|
3386
3645
|
__legacyMetadataTS("design:returntype", String)
|
|
3387
3646
|
], SiteCommand.prototype, "parsePort", null);
|
|
3388
3647
|
__legacyDecorateClassTS([
|
|
3389
|
-
|
|
3648
|
+
Option5({
|
|
3390
3649
|
flags: "-k, --kill",
|
|
3391
3650
|
description: "Kill the running Lattice site process"
|
|
3392
3651
|
}),
|
|
@@ -3395,16 +3654,16 @@ __legacyDecorateClassTS([
|
|
|
3395
3654
|
__legacyMetadataTS("design:returntype", Boolean)
|
|
3396
3655
|
], SiteCommand.prototype, "parseKill", null);
|
|
3397
3656
|
SiteCommand = __legacyDecorateClassTS([
|
|
3398
|
-
|
|
3399
|
-
|
|
3657
|
+
Injectable17(),
|
|
3658
|
+
Command7({
|
|
3400
3659
|
name: "site",
|
|
3401
3660
|
description: "Build and run the Lattice documentation site"
|
|
3402
3661
|
})
|
|
3403
3662
|
], SiteCommand);
|
|
3404
3663
|
// src/commands/status.command.ts
|
|
3405
|
-
import { Injectable as
|
|
3406
|
-
import { Command as
|
|
3407
|
-
class StatusCommand extends
|
|
3664
|
+
import { Injectable as Injectable18 } from "@nestjs/common";
|
|
3665
|
+
import { Command as Command8, CommandRunner as CommandRunner8, Option as Option6 } from "nest-commander";
|
|
3666
|
+
class StatusCommand extends CommandRunner8 {
|
|
3408
3667
|
syncService;
|
|
3409
3668
|
dbChangeDetector;
|
|
3410
3669
|
constructor(syncService, dbChangeDetector) {
|
|
@@ -3470,7 +3729,7 @@ class StatusCommand extends CommandRunner7 {
|
|
|
3470
3729
|
}
|
|
3471
3730
|
}
|
|
3472
3731
|
__legacyDecorateClassTS([
|
|
3473
|
-
|
|
3732
|
+
Option6({
|
|
3474
3733
|
flags: "-v, --verbose",
|
|
3475
3734
|
description: "Show all documents including unchanged"
|
|
3476
3735
|
}),
|
|
@@ -3479,8 +3738,8 @@ __legacyDecorateClassTS([
|
|
|
3479
3738
|
__legacyMetadataTS("design:returntype", Boolean)
|
|
3480
3739
|
], StatusCommand.prototype, "parseVerbose", null);
|
|
3481
3740
|
StatusCommand = __legacyDecorateClassTS([
|
|
3482
|
-
|
|
3483
|
-
|
|
3741
|
+
Injectable18(),
|
|
3742
|
+
Command8({
|
|
3484
3743
|
name: "status",
|
|
3485
3744
|
description: "Show documents that need syncing (new or updated)"
|
|
3486
3745
|
}),
|
|
@@ -3492,11 +3751,11 @@ StatusCommand = __legacyDecorateClassTS([
|
|
|
3492
3751
|
// src/commands/sync.command.ts
|
|
3493
3752
|
import { watch } from "fs";
|
|
3494
3753
|
import { join as join4 } from "path";
|
|
3495
|
-
import { Injectable as
|
|
3496
|
-
import { Command as
|
|
3754
|
+
import { Injectable as Injectable20 } from "@nestjs/common";
|
|
3755
|
+
import { Command as Command9, CommandRunner as CommandRunner9, Option as Option7 } from "nest-commander";
|
|
3497
3756
|
|
|
3498
3757
|
// src/sync/graph-validator.service.ts
|
|
3499
|
-
import { Injectable as
|
|
3758
|
+
import { Injectable as Injectable19, Logger as Logger9 } from "@nestjs/common";
|
|
3500
3759
|
class GraphValidatorService {
|
|
3501
3760
|
graph;
|
|
3502
3761
|
logger = new Logger9(GraphValidatorService.name);
|
|
@@ -3645,14 +3904,14 @@ class GraphValidatorService {
|
|
|
3645
3904
|
}
|
|
3646
3905
|
}
|
|
3647
3906
|
GraphValidatorService = __legacyDecorateClassTS([
|
|
3648
|
-
|
|
3907
|
+
Injectable19(),
|
|
3649
3908
|
__legacyMetadataTS("design:paramtypes", [
|
|
3650
3909
|
typeof GraphService === "undefined" ? Object : GraphService
|
|
3651
3910
|
])
|
|
3652
3911
|
], GraphValidatorService);
|
|
3653
3912
|
|
|
3654
3913
|
// src/commands/sync.command.ts
|
|
3655
|
-
class SyncCommand extends
|
|
3914
|
+
class SyncCommand extends CommandRunner9 {
|
|
3656
3915
|
syncService;
|
|
3657
3916
|
graphService;
|
|
3658
3917
|
_graphValidator;
|
|
@@ -3667,7 +3926,7 @@ class SyncCommand extends CommandRunner8 {
|
|
|
3667
3926
|
async safeExit(code) {
|
|
3668
3927
|
try {
|
|
3669
3928
|
await this.graphService.checkpoint();
|
|
3670
|
-
} catch (
|
|
3929
|
+
} catch (_error) {
|
|
3671
3930
|
console.error("Warning: checkpoint failed during exit");
|
|
3672
3931
|
}
|
|
3673
3932
|
process.exit(code);
|
|
@@ -3925,7 +4184,7 @@ class SyncCommand extends CommandRunner8 {
|
|
|
3925
4184
|
}
|
|
3926
4185
|
}
|
|
3927
4186
|
__legacyDecorateClassTS([
|
|
3928
|
-
|
|
4187
|
+
Option7({
|
|
3929
4188
|
flags: "-f, --force",
|
|
3930
4189
|
description: "Force re-sync specified documents (requires paths to be specified)"
|
|
3931
4190
|
}),
|
|
@@ -3934,7 +4193,7 @@ __legacyDecorateClassTS([
|
|
|
3934
4193
|
__legacyMetadataTS("design:returntype", Boolean)
|
|
3935
4194
|
], SyncCommand.prototype, "parseForce", null);
|
|
3936
4195
|
__legacyDecorateClassTS([
|
|
3937
|
-
|
|
4196
|
+
Option7({
|
|
3938
4197
|
flags: "-d, --dry-run",
|
|
3939
4198
|
description: "Show what would change without applying"
|
|
3940
4199
|
}),
|
|
@@ -3943,7 +4202,7 @@ __legacyDecorateClassTS([
|
|
|
3943
4202
|
__legacyMetadataTS("design:returntype", Boolean)
|
|
3944
4203
|
], SyncCommand.prototype, "parseDryRun", null);
|
|
3945
4204
|
__legacyDecorateClassTS([
|
|
3946
|
-
|
|
4205
|
+
Option7({
|
|
3947
4206
|
flags: "-v, --verbose",
|
|
3948
4207
|
description: "Show detailed output"
|
|
3949
4208
|
}),
|
|
@@ -3952,7 +4211,7 @@ __legacyDecorateClassTS([
|
|
|
3952
4211
|
__legacyMetadataTS("design:returntype", Boolean)
|
|
3953
4212
|
], SyncCommand.prototype, "parseVerbose", null);
|
|
3954
4213
|
__legacyDecorateClassTS([
|
|
3955
|
-
|
|
4214
|
+
Option7({
|
|
3956
4215
|
flags: "-w, --watch",
|
|
3957
4216
|
description: "Watch for file changes and sync automatically"
|
|
3958
4217
|
}),
|
|
@@ -3961,7 +4220,7 @@ __legacyDecorateClassTS([
|
|
|
3961
4220
|
__legacyMetadataTS("design:returntype", Boolean)
|
|
3962
4221
|
], SyncCommand.prototype, "parseWatch", null);
|
|
3963
4222
|
__legacyDecorateClassTS([
|
|
3964
|
-
|
|
4223
|
+
Option7({
|
|
3965
4224
|
flags: "--diff",
|
|
3966
4225
|
description: "Show only changed documents (alias for --dry-run)"
|
|
3967
4226
|
}),
|
|
@@ -3970,7 +4229,7 @@ __legacyDecorateClassTS([
|
|
|
3970
4229
|
__legacyMetadataTS("design:returntype", Boolean)
|
|
3971
4230
|
], SyncCommand.prototype, "parseDiff", null);
|
|
3972
4231
|
__legacyDecorateClassTS([
|
|
3973
|
-
|
|
4232
|
+
Option7({
|
|
3974
4233
|
flags: "--skip-cascade",
|
|
3975
4234
|
description: "Skip cascade analysis (faster for large repos)"
|
|
3976
4235
|
}),
|
|
@@ -3979,7 +4238,7 @@ __legacyDecorateClassTS([
|
|
|
3979
4238
|
__legacyMetadataTS("design:returntype", Boolean)
|
|
3980
4239
|
], SyncCommand.prototype, "parseSkipCascade", null);
|
|
3981
4240
|
__legacyDecorateClassTS([
|
|
3982
|
-
|
|
4241
|
+
Option7({
|
|
3983
4242
|
flags: "--no-embeddings",
|
|
3984
4243
|
description: "Disable embedding generation during sync"
|
|
3985
4244
|
}),
|
|
@@ -3988,7 +4247,7 @@ __legacyDecorateClassTS([
|
|
|
3988
4247
|
__legacyMetadataTS("design:returntype", Boolean)
|
|
3989
4248
|
], SyncCommand.prototype, "parseNoEmbeddings", null);
|
|
3990
4249
|
__legacyDecorateClassTS([
|
|
3991
|
-
|
|
4250
|
+
Option7({
|
|
3992
4251
|
flags: "--skip-extraction",
|
|
3993
4252
|
description: "Skip AI entity extraction (sync without re-extracting entities)"
|
|
3994
4253
|
}),
|
|
@@ -3997,8 +4256,8 @@ __legacyDecorateClassTS([
|
|
|
3997
4256
|
__legacyMetadataTS("design:returntype", Boolean)
|
|
3998
4257
|
], SyncCommand.prototype, "parseSkipExtraction", null);
|
|
3999
4258
|
SyncCommand = __legacyDecorateClassTS([
|
|
4000
|
-
|
|
4001
|
-
|
|
4259
|
+
Injectable20(),
|
|
4260
|
+
Command9({
|
|
4002
4261
|
name: "sync",
|
|
4003
4262
|
arguments: "[paths...]",
|
|
4004
4263
|
description: "Synchronize documents to the knowledge graph"
|
|
@@ -4037,7 +4296,7 @@ GraphModule = __legacyDecorateClassTS([
|
|
|
4037
4296
|
import { Module as Module3 } from "@nestjs/common";
|
|
4038
4297
|
|
|
4039
4298
|
// src/query/query.service.ts
|
|
4040
|
-
import { Injectable as
|
|
4299
|
+
import { Injectable as Injectable21, Logger as Logger10 } from "@nestjs/common";
|
|
4041
4300
|
class QueryService {
|
|
4042
4301
|
graphService;
|
|
4043
4302
|
logger = new Logger10(QueryService.name);
|
|
@@ -4050,7 +4309,7 @@ class QueryService {
|
|
|
4050
4309
|
}
|
|
4051
4310
|
}
|
|
4052
4311
|
QueryService = __legacyDecorateClassTS([
|
|
4053
|
-
|
|
4312
|
+
Injectable21(),
|
|
4054
4313
|
__legacyMetadataTS("design:paramtypes", [
|
|
4055
4314
|
typeof GraphService === "undefined" ? Object : GraphService
|
|
4056
4315
|
])
|
|
@@ -4124,7 +4383,10 @@ AppModule = __legacyDecorateClassTS([
|
|
|
4124
4383
|
OntologyCommand,
|
|
4125
4384
|
InitCommand,
|
|
4126
4385
|
MigrateCommand,
|
|
4127
|
-
SiteCommand
|
|
4386
|
+
SiteCommand,
|
|
4387
|
+
QuestionAddCommand,
|
|
4388
|
+
QuestionLinkCommand,
|
|
4389
|
+
QuestionUnansweredCommand
|
|
4128
4390
|
]
|
|
4129
4391
|
})
|
|
4130
4392
|
], AppModule);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@zabaca/lattice",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.2.0",
|
|
4
4
|
"description": "Human-initiated, AI-powered knowledge graph for markdown documentation",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -9,6 +9,7 @@
|
|
|
9
9
|
"files": [
|
|
10
10
|
"dist",
|
|
11
11
|
"commands",
|
|
12
|
+
"site-template",
|
|
12
13
|
"README.md"
|
|
13
14
|
],
|
|
14
15
|
"scripts": {
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "lattice-site",
|
|
3
|
+
"type": "module",
|
|
4
|
+
"version": "1.0.0",
|
|
5
|
+
"private": true,
|
|
6
|
+
"scripts": {
|
|
7
|
+
"dev": "astro dev",
|
|
8
|
+
"build": "astro build --sync",
|
|
9
|
+
"preview": "astro preview"
|
|
10
|
+
},
|
|
11
|
+
"dependencies": {
|
|
12
|
+
"astro": "^5.12.8",
|
|
13
|
+
"astro-loader-obsidian": "^0.9.1",
|
|
14
|
+
"astro-spaceship": "^0.9.8",
|
|
15
|
+
"sharp": "^0.32.5",
|
|
16
|
+
"spaceship-monolith": "^0.9.8",
|
|
17
|
+
"varlock": "^0.0.11"
|
|
18
|
+
}
|
|
19
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { defineCollection } from 'astro:content';
|
|
2
|
+
import { glob } from "astro/loaders";
|
|
3
|
+
|
|
4
|
+
import { AUTHORS_COLLECTION_NAME } from 'astro-spaceship/constants';
|
|
5
|
+
import { AuthorSchema } from 'astro-spaceship/schemas';
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
export default {
|
|
9
|
+
[AUTHORS_COLLECTION_NAME]: defineCollection({
|
|
10
|
+
loader: glob({ pattern: "**/*.yml", base: "./src/content/authors" }),
|
|
11
|
+
schema: ({ image }) => AuthorSchema.extend({
|
|
12
|
+
avatar: image().optional(),
|
|
13
|
+
})
|
|
14
|
+
}),
|
|
15
|
+
};
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { defineCollection } from 'astro:content';
|
|
2
|
+
|
|
3
|
+
import { ObsidianMdLoader, ObsidianWikiLinkSchema } from "astro-loader-obsidian";
|
|
4
|
+
|
|
5
|
+
import { DOCUMENTS_COLLECTION_NAME, DEFAULT_VAULT_DIR } from 'astro-spaceship/constants';
|
|
6
|
+
import { DocumentSchema } from 'astro-spaceship/schemas';
|
|
7
|
+
import config from 'astro-spaceship/config';
|
|
8
|
+
|
|
9
|
+
import { ENV } from 'varlock/env';
|
|
10
|
+
|
|
11
|
+
export default {
|
|
12
|
+
[DOCUMENTS_COLLECTION_NAME]: defineCollection({
|
|
13
|
+
loader: ObsidianMdLoader({
|
|
14
|
+
author: config.author,
|
|
15
|
+
base: ENV.OBSIDIAN_VAULT_DIR ?? DEFAULT_VAULT_DIR,
|
|
16
|
+
url: '',
|
|
17
|
+
wikilinkFields: ['relateds']
|
|
18
|
+
}),
|
|
19
|
+
schema: ({ image }) => DocumentSchema.extend({
|
|
20
|
+
images: ObsidianWikiLinkSchema.extend({
|
|
21
|
+
href: image().optional(),
|
|
22
|
+
}).array().optional(),
|
|
23
|
+
cover: image().optional(),
|
|
24
|
+
image: image().optional(),
|
|
25
|
+
}),
|
|
26
|
+
})
|
|
27
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { defineCollection, z } from 'astro:content';
|
|
2
|
+
import { glob } from "astro/loaders";
|
|
3
|
+
|
|
4
|
+
import { TAGS_COLLECTION_NAME } from 'astro-spaceship/constants';
|
|
5
|
+
import { TagSchema } from 'astro-spaceship/schemas';
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
export default {
|
|
9
|
+
[TAGS_COLLECTION_NAME]: defineCollection({
|
|
10
|
+
loader: glob({ pattern: "**/*.yml", base: "./src/content/tags" }),
|
|
11
|
+
schema: () => TagSchema,
|
|
12
|
+
})
|
|
13
|
+
};
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import authorsCollection from './collections/authors';
|
|
2
|
+
import documentsCollection from './collections/documents';
|
|
3
|
+
import tagsCollection from './collections/tags';
|
|
4
|
+
|
|
5
|
+
export const collections = {
|
|
6
|
+
...authorsCollection,
|
|
7
|
+
...documentsCollection,
|
|
8
|
+
...tagsCollection,
|
|
9
|
+
};
|
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
|