@a13xu/lucid 1.1.0 → 1.4.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/LICENSE CHANGED
@@ -1,21 +1,21 @@
1
- MIT License
2
-
3
- Copyright (c) 2026 a13xu
4
-
5
- Permission is hereby granted, free of charge, to any person obtaining a copy
6
- of this software and associated documentation files (the "Software"), to deal
7
- in the Software without restriction, including without limitation the rights
8
- to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
- copies of the Software, and to permit persons to whom the Software is
10
- furnished to do so, subject to the following conditions:
11
-
12
- The above copyright notice and this permission notice shall be included in all
13
- copies or substantial portions of the Software.
14
-
15
- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
- SOFTWARE.
1
+ MIT License
2
+
3
+ Copyright (c) 2026 a13xu
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md CHANGED
@@ -1,99 +1,117 @@
1
- # @a13xu/lucid
2
-
3
- Persistent memory for Claude Code agents, backed by **SQLite + FTS5**.
4
-
5
- Stores a knowledge graph (entities, relations, observations) with full-text search indexed queries under 1ms, no JSON files, no linear scans.
6
-
7
- ## Install
8
-
9
- **Requirements:** Node.js 18+
10
-
11
- ```bash
12
- # Run directly (no install needed)
13
- npx @a13xu/lucid
14
-
15
- # Or install globally
16
- npm install -g @a13xu/lucid
17
- lucid
18
- ```
19
-
20
- ### Add to Claude Code
21
-
22
- ```bash
23
- claude mcp add --transport stdio lucid -- npx -y @a13xu/lucid
24
- ```
25
-
26
- Or add manually to `.mcp.json` in your project root:
27
-
28
- ```json
29
- {
30
- "mcpServers": {
31
- "lucid": {
32
- "type": "stdio",
33
- "command": "npx",
34
- "args": ["-y", "@a13xu/lucid"],
35
- "env": {
36
- "MEMORY_DB_PATH": "/your/project/.claude/memory.db"
37
- }
38
- }
39
- }
40
- }
41
- ```
42
-
43
- Default DB path: `~/.claude/memory.db`
44
-
45
- ## Why SQLite + FTS5 instead of JSON?
46
-
47
- | | JSON file | SQLite + FTS5 |
48
- |---|---|---|
49
- | Search | O(n) linear scan | O(log n) indexed |
50
- | Write | Rewrite entire file | Atomic incremental |
51
- | Concurrent reads | Lock entire file | WAL mode |
52
- | Stemming / unicode | Manual | Built-in |
53
-
54
- ## Tools (6)
55
-
56
- | Tool | Description |
57
- |---|---|
58
- | `remember` | Store a fact about an entity (project, person, tool, decision…) |
59
- | `relate` | Create a directed relationship between two entities |
60
- | `recall` | Full-text search across all memory (FTS5 + LIKE fallback) |
61
- | `recall_all` | Return the entire knowledge graph with stats |
62
- | `forget` | Remove an entity and all its relations |
63
- | `memory_stats` | DB size, WAL status, entity/relation counts |
64
-
65
- ### Entity types
66
- `person` · `project` · `decision` · `pattern` · `tool` · `config` · `bug` · `convention`
67
-
68
- ### Relation types
69
- `uses` · `depends_on` · `created_by` · `part_of` · `replaced_by` · `conflicts_with` · `tested_by`
70
-
71
- ## Usage examples
72
-
73
- ```
74
- You: "Remember that this project uses PostgreSQL with Prisma ORM"
75
- Claude: [calls remember] Created "PostgreSQL" [tool]
76
-
77
- You: "What do you know about the database?"
78
- Claude: [calls recall("database PostgreSQL")] returns entity + observations
79
-
80
- You: "How are the services connected?"
81
- Claude: [calls recall_all] → returns full knowledge graph
82
- ```
83
-
84
- ## Debugging
85
-
86
- ```bash
87
- echo '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"capabilities":{},"clientInfo":{"name":"test","version":"1.0"},"protocolVersion":"2024-11-05"}}' \
88
- | npx @a13xu/lucid
89
- ```
90
-
91
- In Claude Code: run `/mcp` you should see `lucid` listed with 6 tools.
92
-
93
- ## Tech stack
94
-
95
- - **Runtime:** Node.js 18+, TypeScript, ES modules
96
- - **MCP SDK:** `@modelcontextprotocol/sdk`
97
- - **Database:** `better-sqlite3` (synchronous, no async overhead)
98
- - **Validation:** `zod`
99
- - **Transport:** stdio
1
+ # @a13xu/lucid
2
+
3
+ Memory, code indexing, and validation for Claude Code agents backed by **SQLite + FTS5**.
4
+
5
+ Stores a persistent knowledge graph (entities, relations, observations), indexes source files as compressed binary with change detection, and validates code for LLM drift patterns.
6
+
7
+ ## Install
8
+
9
+ **Requirements:** Node.js 18+
10
+
11
+ ```bash
12
+ # Add to Claude Code (no install needed)
13
+ claude mcp add --transport stdio lucid -- npx -y @a13xu/lucid
14
+ ```
15
+
16
+ Or add to `.mcp.json` in your project root:
17
+
18
+ ```json
19
+ {
20
+ "mcpServers": {
21
+ "lucid": {
22
+ "type": "stdio",
23
+ "command": "npx",
24
+ "args": ["-y", "@a13xu/lucid"],
25
+ "env": {
26
+ "MEMORY_DB_PATH": "/your/project/.claude/memory.db"
27
+ }
28
+ }
29
+ }
30
+ }
31
+ ```
32
+
33
+ Default DB path: `~/.claude/memory.db`
34
+
35
+ ## Quick start
36
+
37
+ ```
38
+ 1. "Index this project" → init_project() → scans CLAUDE.md, package.json, src/
39
+ 2. Write code → sync_file(path) → compressed + hashed in DB
40
+ 3. "Where is X used?" → grep_code("X") → matching lines only, no full file
41
+ 4. "What do we know?" → recall("query") → knowledge graph search
42
+ ```
43
+
44
+ ## Tools (13)
45
+
46
+ ### Memory
47
+ | Tool | Description |
48
+ |---|---|
49
+ | `remember` | Store a fact about an entity (project, person, tool, decision…) |
50
+ | `relate` | Create a directed relationship between two entities |
51
+ | `recall` | Full-text search across all memory (FTS5 + LIKE fallback) |
52
+ | `recall_all` | Return the entire knowledge graph with statistics |
53
+ | `forget` | Remove an entity and all its relations |
54
+ | `memory_stats` | DB size, WAL status, entity/relation counts |
55
+
56
+ ### Code indexing
57
+ | Tool | Description |
58
+ |---|---|
59
+ | `init_project` | Scan project directory and bootstrap knowledge graph. Reads `CLAUDE.md`, `package.json`/`pyproject.toml`, `README.md`, `.mcp.json`, `logic-guardian.yaml`, source files. Also installs a Claude Code hook for auto-sync. |
60
+ | `sync_file` | Index or re-index a single file after writing/editing. Stores compressed binary (zlib-9), skips instantly if SHA-256 hash unchanged. |
61
+ | `sync_project` | Re-index entire project incrementally. Reports compression ratio. |
62
+ | `grep_code` | Regex search across all indexed files. Decompresses binary on-the-fly, returns only matching lines with context — ~20-50 tokens vs reading full files. |
63
+
64
+ ### Logic Guardian
65
+ | Tool | Description |
66
+ |---|---|
67
+ | `validate_file` | Detect LLM drift patterns in a source file: logic inversions, null propagation, type confusion, copy-paste drift, silent exceptions. Supports Python, JS, TS. |
68
+ | `check_drift` | Analyze a code snippet inline without saving to disk. |
69
+ | `get_checklist` | Return the full 5-pass validation protocol (Logic Trace, Contract Verification, Stupid Mistakes, Integration Sanity, Explain It). |
70
+
71
+ ## Why no vectors?
72
+
73
+ Code has explicit structure — no NLP needed:
74
+
75
+ | Need | Approach | Tokens |
76
+ |---|---|---|
77
+ | "Where is X defined?" | `grep_code("export.*X")` | ~30 |
78
+ | "What does auth.ts export?" | `recall("auth.ts")` | ~50 |
79
+ | "Project conventions?" | `recall("CLAUDE.md conventions")` | ~80 |
80
+ | Read full file | `Read tool` | ~500-2000 |
81
+
82
+ Source files are stored as **zlib-deflate level 9 BLOBs** (~70% smaller than plain text). Change detection via SHA-256 means `sync_file` is instant on unchanged files.
83
+
84
+ ## Why SQLite + FTS5?
85
+
86
+ | | JSON file | SQLite + FTS5 |
87
+ |---|---|---|
88
+ | Search | O(n) linear scan | O(log n) indexed |
89
+ | Write | Rewrite entire file | Atomic incremental |
90
+ | Concurrent reads | Lock entire file | WAL mode |
91
+ | Code storage | Plain text | Compressed BLOB + hash |
92
+ | Change detection | Manual diff | SHA-256 per file |
93
+
94
+ ## Entity types
95
+ `person` · `project` · `decision` · `pattern` · `tool` · `config` · `bug` · `convention`
96
+
97
+ ## Relation types
98
+ `uses` · `depends_on` · `created_by` · `part_of` · `replaced_by` · `conflicts_with` · `tested_by`
99
+
100
+ ## Debugging
101
+
102
+ ```bash
103
+ echo '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"capabilities":{},"clientInfo":{"name":"test","version":"1.0"},"protocolVersion":"2024-11-05"}}' \
104
+ | npx @a13xu/lucid
105
+ ```
106
+
107
+ In Claude Code: run `/mcp` — you should see `lucid` with 13 tools.
108
+
109
+ ## Tech stack
110
+
111
+ - **Runtime:** Node.js 18+, TypeScript, ES modules
112
+ - **MCP SDK:** `@modelcontextprotocol/sdk`
113
+ - **Database:** `better-sqlite3` (synchronous, WAL mode)
114
+ - **Compression:** Node.js built-in `zlib` (deflate level 9)
115
+ - **Hashing:** SHA-256 via `crypto` (change detection)
116
+ - **Validation:** `zod`
117
+ - **Transport:** stdio
@@ -3,7 +3,26 @@ import type { EntityRow, RelationRow } from "./types.js";
3
3
  export declare function initDatabase(): Database.Database;
4
4
  type Stmt<P extends unknown[], R> = Database.Statement<P, R>;
5
5
  type WriteStmt<P extends unknown[]> = Database.Statement<P, unknown>;
6
+ export interface FileContentRow {
7
+ id: number;
8
+ filepath: string;
9
+ content: Buffer;
10
+ content_hash: string;
11
+ original_size: number;
12
+ compressed_size: number;
13
+ language: string;
14
+ indexed_at: number;
15
+ }
6
16
  export interface Statements {
17
+ getFileByPath: Stmt<[string], FileContentRow>;
18
+ upsertFile: WriteStmt<[string, Buffer, string, number, number, string]>;
19
+ getAllFiles: Stmt<[], Pick<FileContentRow, "filepath" | "content" | "language" | "content_hash">>;
20
+ deleteFile: WriteStmt<[string]>;
21
+ fileStorageStats: Stmt<[], {
22
+ count: number;
23
+ total_original: number;
24
+ total_compressed: number;
25
+ }>;
7
26
  getEntityByName: Stmt<[string], EntityRow>;
8
27
  insertEntity: WriteStmt<[string, string, string]>;
9
28
  updateEntity: WriteStmt<[string, number]>;
package/build/database.js CHANGED
@@ -30,85 +30,114 @@ export function initDatabase() {
30
30
  return db;
31
31
  }
32
32
  function createSchema(db) {
33
- db.exec(`
34
- CREATE TABLE IF NOT EXISTS entities (
35
- id INTEGER PRIMARY KEY,
36
- name TEXT NOT NULL UNIQUE COLLATE NOCASE,
37
- type TEXT NOT NULL,
38
- observations TEXT NOT NULL DEFAULT '[]',
39
- created_at INTEGER DEFAULT (unixepoch()),
40
- updated_at INTEGER DEFAULT (unixepoch())
41
- );
42
-
43
- CREATE TABLE IF NOT EXISTS relations (
44
- id INTEGER PRIMARY KEY,
45
- from_entity INTEGER NOT NULL REFERENCES entities(id) ON DELETE CASCADE,
46
- to_entity INTEGER NOT NULL REFERENCES entities(id) ON DELETE CASCADE,
47
- relation_type TEXT NOT NULL,
48
- created_at INTEGER DEFAULT (unixepoch()),
49
- UNIQUE(from_entity, to_entity, relation_type)
50
- );
51
-
52
- CREATE VIRTUAL TABLE IF NOT EXISTS entities_fts USING fts5(
53
- name,
54
- type,
55
- observations,
56
- content='entities',
57
- content_rowid='id',
58
- tokenize='porter unicode61'
59
- );
60
-
61
- CREATE TRIGGER IF NOT EXISTS entities_ai AFTER INSERT ON entities BEGIN
62
- INSERT INTO entities_fts(rowid, name, type, observations)
63
- VALUES (new.id, new.name, new.type, new.observations);
64
- END;
65
-
66
- CREATE TRIGGER IF NOT EXISTS entities_au AFTER UPDATE ON entities BEGIN
67
- INSERT INTO entities_fts(entities_fts, rowid, name, type, observations)
68
- VALUES('delete', old.id, old.name, old.type, old.observations);
69
- INSERT INTO entities_fts(rowid, name, type, observations)
70
- VALUES (new.id, new.name, new.type, new.observations);
71
- END;
72
-
73
- CREATE TRIGGER IF NOT EXISTS entities_ad AFTER DELETE ON entities BEGIN
74
- INSERT INTO entities_fts(entities_fts, rowid, name, type, observations)
75
- VALUES('delete', old.id, old.name, old.type, old.observations);
76
- END;
77
-
78
- CREATE INDEX IF NOT EXISTS idx_relations_from ON relations(from_entity);
79
- CREATE INDEX IF NOT EXISTS idx_relations_to ON relations(to_entity);
80
- CREATE INDEX IF NOT EXISTS idx_entities_type ON entities(type);
33
+ db.exec(`
34
+ CREATE TABLE IF NOT EXISTS entities (
35
+ id INTEGER PRIMARY KEY,
36
+ name TEXT NOT NULL UNIQUE COLLATE NOCASE,
37
+ type TEXT NOT NULL,
38
+ observations TEXT NOT NULL DEFAULT '[]',
39
+ created_at INTEGER DEFAULT (unixepoch()),
40
+ updated_at INTEGER DEFAULT (unixepoch())
41
+ );
42
+
43
+ CREATE TABLE IF NOT EXISTS relations (
44
+ id INTEGER PRIMARY KEY,
45
+ from_entity INTEGER NOT NULL REFERENCES entities(id) ON DELETE CASCADE,
46
+ to_entity INTEGER NOT NULL REFERENCES entities(id) ON DELETE CASCADE,
47
+ relation_type TEXT NOT NULL,
48
+ created_at INTEGER DEFAULT (unixepoch()),
49
+ UNIQUE(from_entity, to_entity, relation_type)
50
+ );
51
+
52
+ CREATE VIRTUAL TABLE IF NOT EXISTS entities_fts USING fts5(
53
+ name,
54
+ type,
55
+ observations,
56
+ content='entities',
57
+ content_rowid='id',
58
+ tokenize='porter unicode61'
59
+ );
60
+
61
+ CREATE TRIGGER IF NOT EXISTS entities_ai AFTER INSERT ON entities BEGIN
62
+ INSERT INTO entities_fts(rowid, name, type, observations)
63
+ VALUES (new.id, new.name, new.type, new.observations);
64
+ END;
65
+
66
+ CREATE TRIGGER IF NOT EXISTS entities_au AFTER UPDATE ON entities BEGIN
67
+ INSERT INTO entities_fts(entities_fts, rowid, name, type, observations)
68
+ VALUES('delete', old.id, old.name, old.type, old.observations);
69
+ INSERT INTO entities_fts(rowid, name, type, observations)
70
+ VALUES (new.id, new.name, new.type, new.observations);
71
+ END;
72
+
73
+ CREATE TRIGGER IF NOT EXISTS entities_ad AFTER DELETE ON entities BEGIN
74
+ INSERT INTO entities_fts(entities_fts, rowid, name, type, observations)
75
+ VALUES('delete', old.id, old.name, old.type, old.observations);
76
+ END;
77
+
78
+ CREATE INDEX IF NOT EXISTS idx_relations_from ON relations(from_entity);
79
+ CREATE INDEX IF NOT EXISTS idx_relations_to ON relations(to_entity);
80
+ CREATE INDEX IF NOT EXISTS idx_entities_type ON entities(type);
81
+
82
+ -- Conținut sursă comprimat (zlib deflate nivel 9)
83
+ CREATE TABLE IF NOT EXISTS file_contents (
84
+ id INTEGER PRIMARY KEY,
85
+ filepath TEXT NOT NULL UNIQUE,
86
+ content BLOB NOT NULL,
87
+ content_hash TEXT NOT NULL,
88
+ original_size INTEGER NOT NULL,
89
+ compressed_size INTEGER NOT NULL,
90
+ language TEXT NOT NULL DEFAULT 'generic',
91
+ indexed_at INTEGER DEFAULT (unixepoch())
92
+ );
93
+ CREATE INDEX IF NOT EXISTS idx_fc_filepath ON file_contents(filepath);
94
+ CREATE INDEX IF NOT EXISTS idx_fc_hash ON file_contents(content_hash);
81
95
  `);
82
96
  }
83
97
  export function prepareStatements(db) {
84
98
  return {
99
+ // file_contents
100
+ getFileByPath: db.prepare("SELECT * FROM file_contents WHERE filepath = ?"),
101
+ upsertFile: db.prepare(`INSERT INTO file_contents (filepath, content, content_hash, original_size, compressed_size, language)
102
+ VALUES (?, ?, ?, ?, ?, ?)
103
+ ON CONFLICT(filepath) DO UPDATE SET
104
+ content = excluded.content,
105
+ content_hash = excluded.content_hash,
106
+ original_size = excluded.original_size,
107
+ compressed_size = excluded.compressed_size,
108
+ language = excluded.language,
109
+ indexed_at = unixepoch()`),
110
+ getAllFiles: db.prepare("SELECT filepath, content, language, content_hash FROM file_contents"),
111
+ deleteFile: db.prepare("DELETE FROM file_contents WHERE filepath = ?"),
112
+ fileStorageStats: db.prepare("SELECT COUNT(*) as count, SUM(original_size) as total_original, SUM(compressed_size) as total_compressed FROM file_contents"),
113
+ // entities
85
114
  getEntityByName: db.prepare("SELECT * FROM entities WHERE name = ? COLLATE NOCASE"),
86
115
  insertEntity: db.prepare("INSERT INTO entities (name, type, observations) VALUES (?, ?, ?)"),
87
116
  // SQL: SET observations = ?, WHERE id = ? → 2 params
88
117
  updateEntity: db.prepare("UPDATE entities SET observations = ?, updated_at = unixepoch() WHERE id = ?"),
89
118
  deleteEntity: db.prepare("DELETE FROM entities WHERE name = ? COLLATE NOCASE"),
90
119
  getAllEntities: db.prepare("SELECT * FROM entities ORDER BY updated_at DESC"),
91
- getRelationsForEntity: db.prepare(`SELECT r.*, ef.name AS from_name, et.name AS to_name
92
- FROM relations r
93
- JOIN entities ef ON r.from_entity = ef.id
94
- JOIN entities et ON r.to_entity = et.id
120
+ getRelationsForEntity: db.prepare(`SELECT r.*, ef.name AS from_name, et.name AS to_name
121
+ FROM relations r
122
+ JOIN entities ef ON r.from_entity = ef.id
123
+ JOIN entities et ON r.to_entity = et.id
95
124
  WHERE r.from_entity = ? OR r.to_entity = ?`),
96
125
  insertRelation: db.prepare("INSERT OR IGNORE INTO relations (from_entity, to_entity, relation_type) VALUES (?, ?, ?)"),
97
- searchFTS: db.prepare(`SELECT e.* FROM entities_fts
98
- JOIN entities e ON entities_fts.rowid = e.id
99
- WHERE entities_fts MATCH ?
100
- ORDER BY rank
126
+ searchFTS: db.prepare(`SELECT e.* FROM entities_fts
127
+ JOIN entities e ON entities_fts.rowid = e.id
128
+ WHERE entities_fts MATCH ?
129
+ ORDER BY rank
101
130
  LIMIT 20`),
102
- searchLike: db.prepare(`SELECT * FROM entities
103
- WHERE name LIKE ? OR type LIKE ? OR observations LIKE ?
104
- ORDER BY updated_at DESC
131
+ searchLike: db.prepare(`SELECT * FROM entities
132
+ WHERE name LIKE ? OR type LIKE ? OR observations LIKE ?
133
+ ORDER BY updated_at DESC
105
134
  LIMIT 20`),
106
135
  countEntities: db.prepare("SELECT COUNT(*) as count FROM entities"),
107
136
  countRelations: db.prepare("SELECT COUNT(*) as count FROM relations"),
108
137
  getWalMode: db.prepare("PRAGMA journal_mode"),
109
- getAllRelations: db.prepare(`SELECT r.*, ef.name AS from_name, et.name AS to_name
110
- FROM relations r
111
- JOIN entities ef ON r.from_entity = ef.id
138
+ getAllRelations: db.prepare(`SELECT r.*, ef.name AS from_name, et.name AS to_name
139
+ FROM relations r
140
+ JOIN entities ef ON r.from_entity = ef.id
112
141
  JOIN entities et ON r.to_entity = et.id`),
113
142
  };
114
143
  }
@@ -1,67 +1,67 @@
1
- export const CHECKLIST = `# Logic Guardian — Validation Checklist (5 passes)
2
-
3
- ## Pass 1: Logic Trace
4
- Trace through the code with CONCRETE values:
5
- - Happy path → real values, write each variable state
6
- - Empty/zero → null, 0, "", []
7
- - Boundary → first element, last element, max int, single char
8
- - Error case → network down, file missing, permission denied
9
-
10
- STOP if any trace produces unexpected output. Fix before continuing.
11
-
12
- ## Pass 2: Contract Verification
13
- - [ ] Preconditions: what must be true BEFORE this runs? Is it checked?
14
- - [ ] Postconditions: what must be true AFTER? Can you prove it?
15
- - [ ] Invariants: what must ALWAYS be true? Does the code maintain it?
16
- - [ ] Return type: does EVERY code path return the expected type?
17
- - [ ] Side effects: are all side effects intentional?
18
-
19
- ## Pass 3: Stupid Mistakes Checklist
20
-
21
- ### Off-by-one
22
- - [ ] < vs <= — verify with boundary values
23
- - [ ] Array indices — last is length - 1
24
- - [ ] Loop iterations — exactly N times?
25
-
26
- ### Null/Undefined Propagation
27
- - [ ] Every .property access — can the object be null?
28
- - [ ] Every array index — can the array be empty?
29
- - [ ] Every map lookup — can the key be missing?
30
-
31
- ### Type Confusion
32
- - [ ] String vs Number comparisons
33
- - [ ] Integer vs Float division
34
- - [ ] Boolean coercion edge cases
35
-
36
- ### Logic Inversions (THE #1 LLM drift pattern)
37
- - [ ] if/else — is the condition testing what you THINK?
38
- - [ ] Early returns — does the guard return the RIGHT value?
39
- - [ ] filter/find/some — keeping the RIGHT elements?
40
- - [ ] Error handling — catching and re-throwing correctly?
41
-
42
- ### State & Mutation
43
- - [ ] Mutating shared object when you should copy?
44
- - [ ] Async state read after it might have changed?
45
-
46
- ### Copy-Paste Drift
47
- - [ ] ALL variable names updated in copied blocks?
48
- - [ ] Conditions changed, not just variable names?
49
-
50
- ## Pass 4: Integration Sanity
51
- - [ ] Breaks existing callers?
52
- - [ ] Imports/exports correct?
53
- - [ ] If async, all callers awaiting it?
54
- - [ ] If type changed, all usages updated?
55
-
56
- ## Pass 5: Explain It Test
57
- In ONE sentence: what does this code do?
58
- If you can't explain it, or the sentence doesn't match the code → something is wrong.
59
-
60
- ## Anti-Drift Triggers
61
- STOP if you find yourself thinking:
62
- - "This is similar to..." → You're pattern-matching. TRACE THE LOGIC.
63
- - "This should work because the other one does" → VERIFY INDEPENDENTLY.
64
- - "I'll just copy and change the names" → CHECK EVERY DIFFERENCE.
65
- - "The error handling is probably fine" → TRACE THE ERROR PATH.
66
- - "This is standard boilerplate" → Verify it fits this context.
1
+ export const CHECKLIST = `# Logic Guardian — Validation Checklist (5 passes)
2
+
3
+ ## Pass 1: Logic Trace
4
+ Trace through the code with CONCRETE values:
5
+ - Happy path → real values, write each variable state
6
+ - Empty/zero → null, 0, "", []
7
+ - Boundary → first element, last element, max int, single char
8
+ - Error case → network down, file missing, permission denied
9
+
10
+ STOP if any trace produces unexpected output. Fix before continuing.
11
+
12
+ ## Pass 2: Contract Verification
13
+ - [ ] Preconditions: what must be true BEFORE this runs? Is it checked?
14
+ - [ ] Postconditions: what must be true AFTER? Can you prove it?
15
+ - [ ] Invariants: what must ALWAYS be true? Does the code maintain it?
16
+ - [ ] Return type: does EVERY code path return the expected type?
17
+ - [ ] Side effects: are all side effects intentional?
18
+
19
+ ## Pass 3: Stupid Mistakes Checklist
20
+
21
+ ### Off-by-one
22
+ - [ ] < vs <= — verify with boundary values
23
+ - [ ] Array indices — last is length - 1
24
+ - [ ] Loop iterations — exactly N times?
25
+
26
+ ### Null/Undefined Propagation
27
+ - [ ] Every .property access — can the object be null?
28
+ - [ ] Every array index — can the array be empty?
29
+ - [ ] Every map lookup — can the key be missing?
30
+
31
+ ### Type Confusion
32
+ - [ ] String vs Number comparisons
33
+ - [ ] Integer vs Float division
34
+ - [ ] Boolean coercion edge cases
35
+
36
+ ### Logic Inversions (THE #1 LLM drift pattern)
37
+ - [ ] if/else — is the condition testing what you THINK?
38
+ - [ ] Early returns — does the guard return the RIGHT value?
39
+ - [ ] filter/find/some — keeping the RIGHT elements?
40
+ - [ ] Error handling — catching and re-throwing correctly?
41
+
42
+ ### State & Mutation
43
+ - [ ] Mutating shared object when you should copy?
44
+ - [ ] Async state read after it might have changed?
45
+
46
+ ### Copy-Paste Drift
47
+ - [ ] ALL variable names updated in copied blocks?
48
+ - [ ] Conditions changed, not just variable names?
49
+
50
+ ## Pass 4: Integration Sanity
51
+ - [ ] Breaks existing callers?
52
+ - [ ] Imports/exports correct?
53
+ - [ ] If async, all callers awaiting it?
54
+ - [ ] If type changed, all usages updated?
55
+
56
+ ## Pass 5: Explain It Test
57
+ In ONE sentence: what does this code do?
58
+ If you can't explain it, or the sentence doesn't match the code → something is wrong.
59
+
60
+ ## Anti-Drift Triggers
61
+ STOP if you find yourself thinking:
62
+ - "This is similar to..." → You're pattern-matching. TRACE THE LOGIC.
63
+ - "This should work because the other one does" → VERIFY INDEPENDENTLY.
64
+ - "I'll just copy and change the names" → CHECK EVERY DIFFERENCE.
65
+ - "The error handling is probably fine" → TRACE THE ERROR PATH.
66
+ - "This is standard boilerplate" → Verify it fits this context.
67
67
  `;