@zabaca/lattice 1.0.2 → 1.0.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -83,7 +83,7 @@ The goal is to ensure entities reflect the document's CURRENT state, not preserv
83
83
  - Preserve existing fields like `created`, `status`, `topic` (but update `updated` date)
84
84
  - **Replace** the `summary`, `entities` and `relationships` sections entirely
85
85
  - If no topic field exists, derive it from the directory name
86
- (e.g., `docs/claude-code/file.md` -> `topic: claude-code`)
86
+ (e.g., `~/.lattice/docs/claude-code/file.md` -> `topic: claude-code`)
87
87
 
88
88
  Frontmatter template:
89
89
  ```yaml
@@ -38,16 +38,16 @@ For each new or updated document identified:
38
38
  Task(
39
39
  subagent_type="general-purpose",
40
40
  model="haiku",
41
- prompt="Use /entity-extract docs/topic/document.md to extract entities. Follow all instructions and report completion."
41
+ prompt="Use /entity-extract ~/.lattice/docs/topic/document.md to extract entities. Follow all instructions and report completion."
42
42
  )
43
43
  ```
44
44
 
45
45
  **For multiple documents, launch agents in parallel:**
46
46
  ```
47
47
  // In a single message, launch multiple Task tool calls:
48
- Task(subagent_type="general-purpose", model="haiku", prompt="/entity-extract docs/topic-a/README.md ...")
49
- Task(subagent_type="general-purpose", model="haiku", prompt="/entity-extract docs/topic-b/notes.md ...")
50
- Task(subagent_type="general-purpose", model="haiku", prompt="/entity-extract docs/topic-c/README.md ...")
48
+ Task(subagent_type="general-purpose", model="haiku", prompt="/entity-extract ~/.lattice/docs/topic-a/README.md ...")
49
+ Task(subagent_type="general-purpose", model="haiku", prompt="/entity-extract ~/.lattice/docs/topic-b/notes.md ...")
50
+ Task(subagent_type="general-purpose", model="haiku", prompt="/entity-extract ~/.lattice/docs/topic-c/README.md ...")
51
51
  ```
52
52
 
53
53
  This is much faster than sequential execution for multiple documents.
@@ -87,15 +87,15 @@ Summarize what was processed:
87
87
 
88
88
  Processed 3 documents:
89
89
 
90
- 1. docs/american-holidays/README.md
90
+ 1. ~/.lattice/docs/american-holidays/README.md
91
91
  - 4 entities extracted
92
92
  - 3 relationships defined
93
93
 
94
- 2. docs/american-holidays/thanksgiving-vs-christmas.md
94
+ 2. ~/.lattice/docs/american-holidays/thanksgiving-vs-christmas.md
95
95
  - 8 entities extracted
96
96
  - 5 relationships defined
97
97
 
98
- 3. docs/bun-nestjs/notes.md
98
+ 3. ~/.lattice/docs/bun-nestjs/notes.md
99
99
  - 5 entities extracted
100
100
  - 4 relationships defined
101
101
 
@@ -65,7 +65,7 @@ If user wants new research:
65
65
  ### Step 6: Determine Topic and Filename
66
66
 
67
67
  **Identify the topic directory:**
68
- - Check if a relevant `docs/{topic-name}/` directory already exists
68
+ - Check if a relevant `~/.lattice/docs/{topic-name}/` directory already exists
69
69
  - If not, derive a new topic name from the query (kebab-case)
70
70
 
71
71
  **Derive the research filename:**
@@ -89,7 +89,7 @@ Auto-derive from the specific focus of the query:
89
89
 
90
90
  Create TWO files:
91
91
 
92
- **1. `docs/{topic-name}/README.md`** (index):
92
+ **1. `~/.lattice/docs/{topic-name}/README.md`** (index):
93
93
  ```markdown
94
94
  ---
95
95
  created: [TODAY'S DATE]
@@ -115,7 +115,7 @@ Brief description of what this topic covers.
115
115
  - [Related Topic](../related-topic/)
116
116
  ```
117
117
 
118
- **2. `docs/{topic-name}/{research-filename}.md`** (content):
118
+ **2. `~/.lattice/docs/{topic-name}/{research-filename}.md`** (content):
119
119
  ```markdown
120
120
  ---
121
121
  created: [TODAY'S DATE]
@@ -146,9 +146,9 @@ What this research addresses.
146
146
 
147
147
  #### For EXISTING Topics (directory exists)
148
148
 
149
- **1. Create** `docs/{topic-name}/{research-filename}.md` with content template above
149
+ **1. Create** `~/.lattice/docs/{topic-name}/{research-filename}.md` with content template above
150
150
 
151
- **2. Update** `docs/{topic-name}/README.md`:
151
+ **2. Update** `~/.lattice/docs/{topic-name}/README.md`:
152
152
  - Add new row to the Documents table
153
153
  - Update the `updated` date in frontmatter
154
154
 
@@ -173,7 +173,7 @@ After creating files, confirm:
173
173
  ## File Structure Standard
174
174
 
175
175
  ```
176
- docs/{topic-name}/
176
+ ~/.lattice/docs/{topic-name}/
177
177
  ├── README.md # Index: links to docs, brief overview
178
178
  ├── {research-1}.md # Specific research
179
179
  ├── {research-2}.md # Additional research
package/dist/main.js CHANGED
@@ -844,11 +844,35 @@ class GraphService {
844
844
  connecting = null;
845
845
  vectorIndexes = new Set;
846
846
  embeddingDimensions;
847
+ signalHandlersRegistered = false;
847
848
  constructor(configService) {
848
849
  this.configService = configService;
849
850
  ensureLatticeHome();
850
851
  this.dbPath = getDatabasePath();
851
852
  this.embeddingDimensions = this.configService.get("EMBEDDING_DIMENSIONS") || 512;
853
+ this.registerSignalHandlers();
854
+ }
855
+ registerSignalHandlers() {
856
+ if (this.signalHandlersRegistered)
857
+ return;
858
+ this.signalHandlersRegistered = true;
859
+ const gracefulShutdown = async (signal) => {
860
+ this.logger.log(`Received ${signal}, checkpointing before exit...`);
861
+ try {
862
+ await this.checkpoint();
863
+ await this.disconnect();
864
+ } catch (error) {
865
+ this.logger.error(`Error during graceful shutdown: ${error instanceof Error ? error.message : String(error)}`);
866
+ }
867
+ process.exit(0);
868
+ };
869
+ process.on("SIGINT", () => gracefulShutdown("SIGINT"));
870
+ process.on("SIGTERM", () => gracefulShutdown("SIGTERM"));
871
+ process.on("beforeExit", async () => {
872
+ if (this.connection) {
873
+ await this.checkpoint();
874
+ }
875
+ });
852
876
  }
853
877
  async onModuleDestroy() {
854
878
  await this.disconnect();
@@ -874,7 +898,7 @@ class GraphService {
874
898
  }
875
899
  async connect() {
876
900
  try {
877
- this.instance = await DuckDBInstance.create(this.dbPath, {
901
+ this.instance = await DuckDBInstance.create(":memory:", {
878
902
  allow_unsigned_extensions: "true"
879
903
  });
880
904
  this.connection = await this.instance.connect();
@@ -888,8 +912,10 @@ class GraphService {
888
912
  } catch (e) {
889
913
  this.logger.warn(`DuckPGQ extension not available: ${e instanceof Error ? e.message : String(e)}`);
890
914
  }
915
+ await this.connection.run(`ATTACH '${this.dbPath}' AS lattice (READ_WRITE);`);
916
+ await this.connection.run("USE lattice;");
891
917
  await this.initializeSchema();
892
- this.logger.log(`Connected to DuckDB at ${this.dbPath}`);
918
+ this.logger.log(`Connected to DuckDB (in-memory + ATTACH) at ${this.dbPath}`);
893
919
  } catch (error) {
894
920
  this.connection = null;
895
921
  this.instance = null;
@@ -899,6 +925,7 @@ class GraphService {
899
925
  }
900
926
  async disconnect() {
901
927
  if (this.connection) {
928
+ await this.checkpoint();
902
929
  this.connection.closeSync();
903
930
  this.connection = null;
904
931
  this.logger.log("Disconnected from DuckDB");
@@ -907,6 +934,17 @@ class GraphService {
907
934
  this.instance = null;
908
935
  }
909
936
  }
937
+ async checkpoint() {
938
+ if (!this.connection) {
939
+ return;
940
+ }
941
+ try {
942
+ await this.connection.run("CHECKPOINT;");
943
+ this.logger.debug("Checkpoint completed");
944
+ } catch (error) {
945
+ this.logger.warn(`Checkpoint failed: ${error instanceof Error ? error.message : String(error)}`);
946
+ }
947
+ }
910
948
  async initializeSchema() {
911
949
  if (!this.connection) {
912
950
  throw new Error("Cannot initialize schema: not connected");
@@ -2037,10 +2075,13 @@ class SyncService {
2037
2075
  }
2038
2076
  if (!options.dryRun) {
2039
2077
  result.entityEmbeddingsGenerated = await this.syncEntities(uniqueEntities, options);
2078
+ await this.graph.checkpoint();
2040
2079
  if (options.verbose) {
2041
2080
  this.logger.log(`Synced ${uniqueEntities.size} entities, generated ${result.entityEmbeddingsGenerated} embeddings`);
2042
2081
  }
2043
2082
  }
2083
+ const CHECKPOINT_BATCH_SIZE = 10;
2084
+ let processedCount = 0;
2044
2085
  for (const change of changes) {
2045
2086
  try {
2046
2087
  const doc = docsByPath.get(change.path);
@@ -2063,12 +2104,19 @@ class SyncService {
2063
2104
  if (change.embeddingGenerated) {
2064
2105
  result.embeddingsGenerated++;
2065
2106
  }
2107
+ processedCount++;
2108
+ if (!options.dryRun && processedCount % CHECKPOINT_BATCH_SIZE === 0) {
2109
+ await this.graph.checkpoint();
2110
+ }
2066
2111
  } catch (error) {
2067
2112
  const errorMessage = error instanceof Error ? error.message : String(error);
2068
2113
  result.errors.push({ path: change.path, error: errorMessage });
2069
2114
  this.logger.warn(`Error processing ${change.path}: ${errorMessage}`);
2070
2115
  }
2071
2116
  }
2117
+ if (!options.dryRun && processedCount > 0) {
2118
+ await this.graph.checkpoint();
2119
+ }
2072
2120
  if (!options.dryRun) {
2073
2121
  await this.manifest.save();
2074
2122
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@zabaca/lattice",
3
- "version": "1.0.2",
3
+ "version": "1.0.4",
4
4
  "description": "Human-initiated, AI-powered knowledge graph for markdown documentation",
5
5
  "type": "module",
6
6
  "bin": {