@a13xu/lucid 1.0.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 +21 -21
- package/README.md +117 -99
- package/build/database.d.ts +19 -0
- package/build/database.js +91 -62
- package/build/guardian/checklist.d.ts +1 -0
- package/build/guardian/checklist.js +67 -0
- package/build/guardian/validator.d.ts +21 -0
- package/build/guardian/validator.js +332 -0
- package/build/index.js +142 -32
- package/build/indexer/file.d.ts +15 -0
- package/build/indexer/file.js +100 -0
- package/build/indexer/project.d.ts +8 -0
- package/build/indexer/project.js +312 -0
- package/build/store/content.d.ts +3 -0
- package/build/store/content.js +11 -0
- package/build/tools/grep.d.ts +17 -0
- package/build/tools/grep.js +65 -0
- package/build/tools/guardian.d.ts +21 -0
- package/build/tools/guardian.js +58 -0
- package/build/tools/init.d.ts +11 -0
- package/build/tools/init.js +110 -0
- package/build/tools/sync.d.ts +18 -0
- package/build/tools/sync.js +61 -0
- package/package.json +48 -48
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
|
-
|
|
4
|
-
|
|
5
|
-
Stores a knowledge graph (entities, relations, observations)
|
|
6
|
-
|
|
7
|
-
## Install
|
|
8
|
-
|
|
9
|
-
**Requirements:** Node.js 18+
|
|
10
|
-
|
|
11
|
-
```bash
|
|
12
|
-
#
|
|
13
|
-
npx @a13xu/lucid
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
```
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
|
48
|
-
|
|
49
|
-
|
|
|
50
|
-
|
|
|
51
|
-
|
|
|
52
|
-
|
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
| `
|
|
60
|
-
| `
|
|
61
|
-
| `
|
|
62
|
-
| `
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
`
|
|
70
|
-
|
|
71
|
-
##
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
##
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
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
|
package/build/database.d.ts
CHANGED
|
@@ -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
|
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare const CHECKLIST = "# Logic Guardian \u2014 Validation Checklist (5 passes)\n\n## Pass 1: Logic Trace\nTrace through the code with CONCRETE values:\n- Happy path \u2192 real values, write each variable state\n- Empty/zero \u2192 null, 0, \"\", []\n- Boundary \u2192 first element, last element, max int, single char\n- Error case \u2192 network down, file missing, permission denied\n\nSTOP if any trace produces unexpected output. Fix before continuing.\n\n## Pass 2: Contract Verification\n- [ ] Preconditions: what must be true BEFORE this runs? Is it checked?\n- [ ] Postconditions: what must be true AFTER? Can you prove it?\n- [ ] Invariants: what must ALWAYS be true? Does the code maintain it?\n- [ ] Return type: does EVERY code path return the expected type?\n- [ ] Side effects: are all side effects intentional?\n\n## Pass 3: Stupid Mistakes Checklist\n\n### Off-by-one\n- [ ] < vs <= \u2014 verify with boundary values\n- [ ] Array indices \u2014 last is length - 1\n- [ ] Loop iterations \u2014 exactly N times?\n\n### Null/Undefined Propagation\n- [ ] Every .property access \u2014 can the object be null?\n- [ ] Every array index \u2014 can the array be empty?\n- [ ] Every map lookup \u2014 can the key be missing?\n\n### Type Confusion\n- [ ] String vs Number comparisons\n- [ ] Integer vs Float division\n- [ ] Boolean coercion edge cases\n\n### Logic Inversions (THE #1 LLM drift pattern)\n- [ ] if/else \u2014 is the condition testing what you THINK?\n- [ ] Early returns \u2014 does the guard return the RIGHT value?\n- [ ] filter/find/some \u2014 keeping the RIGHT elements?\n- [ ] Error handling \u2014 catching and re-throwing correctly?\n\n### State & Mutation\n- [ ] Mutating shared object when you should copy?\n- [ ] Async state read after it might have changed?\n\n### Copy-Paste Drift\n- [ ] ALL variable names updated in copied blocks?\n- [ ] Conditions changed, not just variable names?\n\n## Pass 4: Integration Sanity\n- [ ] Breaks existing callers?\n- [ ] Imports/exports correct?\n- [ ] If async, all callers awaiting it?\n- [ ] If type changed, all usages updated?\n\n## Pass 5: Explain It Test\nIn ONE sentence: what does this code do?\nIf you can't explain it, or the sentence doesn't match the code \u2192 something is wrong.\n\n## Anti-Drift Triggers\nSTOP if you find yourself thinking:\n- \"This is similar to...\" \u2192 You're pattern-matching. TRACE THE LOGIC.\n- \"This should work because the other one does\" \u2192 VERIFY INDEPENDENTLY.\n- \"I'll just copy and change the names\" \u2192 CHECK EVERY DIFFERENCE.\n- \"The error handling is probably fine\" \u2192 TRACE THE ERROR PATH.\n- \"This is standard boilerplate\" \u2192 Verify it fits this context.\n";
|
|
@@ -0,0 +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.
|
|
67
|
+
`;
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
export type Severity = "critical" | "high" | "medium" | "low" | "info";
|
|
2
|
+
export interface Issue {
|
|
3
|
+
file: string;
|
|
4
|
+
line: number;
|
|
5
|
+
severity: Severity;
|
|
6
|
+
driftId: string;
|
|
7
|
+
message: string;
|
|
8
|
+
suggestion?: string;
|
|
9
|
+
snippet?: string;
|
|
10
|
+
}
|
|
11
|
+
export declare function formatIssue(issue: Issue): string;
|
|
12
|
+
export declare function detectLanguage(filepath: string): string;
|
|
13
|
+
export interface ValidationResult {
|
|
14
|
+
issues: Issue[];
|
|
15
|
+
filesChecked: number;
|
|
16
|
+
linesChecked: number;
|
|
17
|
+
passed: boolean;
|
|
18
|
+
}
|
|
19
|
+
export declare function validateSource(filepath: string, source: string, lang?: string): Issue[];
|
|
20
|
+
export declare function validateFile(filepath: string): Issue[];
|
|
21
|
+
export declare function formatReport(filepath: string, issues: Issue[]): string;
|