pi-context-engine 0.2.0 → 0.3.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/README.md +14 -0
- package/package.json +1 -1
- package/src/config.ts +4 -0
- package/src/engine.ts +32 -1
- package/src/extension.ts +108 -8
- package/src/indexer/embedder.ts +16 -4
- package/src/indexer/manifest.ts +35 -17
- package/src/indexer/pipeline.ts +317 -284
- package/src/memory/db.ts +45 -59
- package/src/storage/local-backend.ts +0 -4
- package/src/storage/vector-store.ts +13 -29
- package/src/utils.ts +13 -0
package/src/memory/db.ts
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Per-project memory.db — port of upstream `memory/db.py` (MIT), schema
|
|
2
|
+
* Per-project memory.db — port of upstream `memory/db.py` (MIT), schema v4
|
|
3
3
|
* (minus `migrated_files`, which only served upstream's session-file import).
|
|
4
|
+
* Schema is enforced idempotently on every open (no version-step migrations),
|
|
5
|
+
* so databases from any earlier release converge to the current shape in place.
|
|
4
6
|
* Synchronous better-sqlite3; vec tables degrade to FTS-only when the
|
|
5
7
|
* sqlite-vec extension can't load.
|
|
6
8
|
*/
|
|
@@ -27,7 +29,7 @@ export const BUCKETS = [
|
|
|
27
29
|
export type Bucket = (typeof BUCKETS)[number];
|
|
28
30
|
|
|
29
31
|
const SCHEMA_V1 = `
|
|
30
|
-
CREATE TABLE sessions (
|
|
32
|
+
CREATE TABLE IF NOT EXISTS sessions (
|
|
31
33
|
id TEXT PRIMARY KEY,
|
|
32
34
|
project TEXT NOT NULL,
|
|
33
35
|
started_at_epoch INTEGER NOT NULL,
|
|
@@ -40,9 +42,9 @@ CREATE TABLE sessions (
|
|
|
40
42
|
rollup_summary TEXT,
|
|
41
43
|
rollup_summary_at_epoch INTEGER
|
|
42
44
|
);
|
|
43
|
-
CREATE INDEX idx_sessions_started ON sessions(started_at_epoch DESC);
|
|
45
|
+
CREATE INDEX IF NOT EXISTS idx_sessions_started ON sessions(started_at_epoch DESC);
|
|
44
46
|
|
|
45
|
-
CREATE TABLE prompts (
|
|
47
|
+
CREATE TABLE IF NOT EXISTS prompts (
|
|
46
48
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
47
49
|
session_id TEXT NOT NULL REFERENCES sessions(id) ON DELETE CASCADE,
|
|
48
50
|
prompt_number INTEGER NOT NULL,
|
|
@@ -51,16 +53,16 @@ CREATE TABLE prompts (
|
|
|
51
53
|
created_at TEXT NOT NULL,
|
|
52
54
|
UNIQUE(session_id, prompt_number)
|
|
53
55
|
);
|
|
54
|
-
CREATE INDEX idx_prompts_session ON prompts(session_id, prompt_number);
|
|
56
|
+
CREATE INDEX IF NOT EXISTS idx_prompts_session ON prompts(session_id, prompt_number);
|
|
55
57
|
|
|
56
|
-
CREATE TABLE tool_event_payloads (
|
|
58
|
+
CREATE TABLE IF NOT EXISTS tool_event_payloads (
|
|
57
59
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
58
60
|
raw_input TEXT NOT NULL,
|
|
59
61
|
raw_output TEXT,
|
|
60
62
|
size_bytes INTEGER NOT NULL
|
|
61
63
|
);
|
|
62
64
|
|
|
63
|
-
CREATE TABLE tool_events (
|
|
65
|
+
CREATE TABLE IF NOT EXISTS tool_events (
|
|
64
66
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
65
67
|
session_id TEXT NOT NULL REFERENCES sessions(id) ON DELETE CASCADE,
|
|
66
68
|
prompt_number INTEGER NOT NULL,
|
|
@@ -70,9 +72,9 @@ CREATE TABLE tool_events (
|
|
|
70
72
|
created_at_epoch INTEGER NOT NULL,
|
|
71
73
|
created_at TEXT NOT NULL
|
|
72
74
|
);
|
|
73
|
-
CREATE INDEX idx_events_session_turn ON tool_events(session_id, prompt_number);
|
|
75
|
+
CREATE INDEX IF NOT EXISTS idx_events_session_turn ON tool_events(session_id, prompt_number);
|
|
74
76
|
|
|
75
|
-
CREATE TABLE turn_summaries (
|
|
77
|
+
CREATE TABLE IF NOT EXISTS turn_summaries (
|
|
76
78
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
77
79
|
session_id TEXT NOT NULL REFERENCES sessions(id) ON DELETE CASCADE,
|
|
78
80
|
prompt_number INTEGER NOT NULL,
|
|
@@ -82,7 +84,7 @@ CREATE TABLE turn_summaries (
|
|
|
82
84
|
UNIQUE(session_id, prompt_number)
|
|
83
85
|
);
|
|
84
86
|
|
|
85
|
-
CREATE TABLE decisions (
|
|
87
|
+
CREATE TABLE IF NOT EXISTS decisions (
|
|
86
88
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
87
89
|
session_id TEXT REFERENCES sessions(id) ON DELETE SET NULL,
|
|
88
90
|
decision TEXT NOT NULL,
|
|
@@ -91,10 +93,10 @@ CREATE TABLE decisions (
|
|
|
91
93
|
created_at_epoch INTEGER NOT NULL,
|
|
92
94
|
created_at TEXT NOT NULL
|
|
93
95
|
);
|
|
94
|
-
CREATE INDEX idx_decisions_created ON decisions(created_at_epoch DESC);
|
|
95
|
-
CREATE INDEX idx_decisions_source ON decisions(source);
|
|
96
|
+
CREATE INDEX IF NOT EXISTS idx_decisions_created ON decisions(created_at_epoch DESC);
|
|
97
|
+
CREATE INDEX IF NOT EXISTS idx_decisions_source ON decisions(source);
|
|
96
98
|
|
|
97
|
-
CREATE TABLE code_areas (
|
|
99
|
+
CREATE TABLE IF NOT EXISTS code_areas (
|
|
98
100
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
99
101
|
session_id TEXT REFERENCES sessions(id) ON DELETE SET NULL,
|
|
100
102
|
file_path TEXT NOT NULL,
|
|
@@ -102,9 +104,9 @@ CREATE TABLE code_areas (
|
|
|
102
104
|
source TEXT NOT NULL CHECK(source IN ('manual','migrated','auto')) DEFAULT 'manual',
|
|
103
105
|
created_at_epoch INTEGER NOT NULL
|
|
104
106
|
);
|
|
105
|
-
CREATE INDEX idx_code_areas_file ON code_areas(file_path);
|
|
107
|
+
CREATE INDEX IF NOT EXISTS idx_code_areas_file ON code_areas(file_path);
|
|
106
108
|
|
|
107
|
-
CREATE TABLE pending_compressions (
|
|
109
|
+
CREATE TABLE IF NOT EXISTS pending_compressions (
|
|
108
110
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
109
111
|
kind TEXT NOT NULL CHECK(kind IN ('turn','session_rollup')),
|
|
110
112
|
session_id TEXT NOT NULL,
|
|
@@ -115,44 +117,44 @@ CREATE TABLE pending_compressions (
|
|
|
115
117
|
UNIQUE(kind, session_id, prompt_number)
|
|
116
118
|
);
|
|
117
119
|
|
|
118
|
-
CREATE VIRTUAL TABLE prompts_fts USING fts5(prompt_text, content='prompts', content_rowid='id');
|
|
119
|
-
CREATE VIRTUAL TABLE decisions_fts USING fts5(decision, reason, content='decisions', content_rowid='id');
|
|
120
|
-
CREATE VIRTUAL TABLE turn_summaries_fts USING fts5(summary, content='turn_summaries', content_rowid='id');
|
|
120
|
+
CREATE VIRTUAL TABLE IF NOT EXISTS prompts_fts USING fts5(prompt_text, content='prompts', content_rowid='id');
|
|
121
|
+
CREATE VIRTUAL TABLE IF NOT EXISTS decisions_fts USING fts5(decision, reason, content='decisions', content_rowid='id');
|
|
122
|
+
CREATE VIRTUAL TABLE IF NOT EXISTS turn_summaries_fts USING fts5(summary, content='turn_summaries', content_rowid='id');
|
|
121
123
|
|
|
122
|
-
CREATE TRIGGER prompts_ai AFTER INSERT ON prompts BEGIN
|
|
124
|
+
CREATE TRIGGER IF NOT EXISTS prompts_ai AFTER INSERT ON prompts BEGIN
|
|
123
125
|
INSERT INTO prompts_fts(rowid, prompt_text) VALUES (new.id, new.prompt_text);
|
|
124
126
|
END;
|
|
125
|
-
CREATE TRIGGER prompts_ad AFTER DELETE ON prompts BEGIN
|
|
127
|
+
CREATE TRIGGER IF NOT EXISTS prompts_ad AFTER DELETE ON prompts BEGIN
|
|
126
128
|
INSERT INTO prompts_fts(prompts_fts, rowid, prompt_text) VALUES('delete', old.id, old.prompt_text);
|
|
127
129
|
END;
|
|
128
|
-
CREATE TRIGGER prompts_au AFTER UPDATE ON prompts BEGIN
|
|
130
|
+
CREATE TRIGGER IF NOT EXISTS prompts_au AFTER UPDATE ON prompts BEGIN
|
|
129
131
|
INSERT INTO prompts_fts(prompts_fts, rowid, prompt_text) VALUES('delete', old.id, old.prompt_text);
|
|
130
132
|
INSERT INTO prompts_fts(rowid, prompt_text) VALUES (new.id, new.prompt_text);
|
|
131
133
|
END;
|
|
132
134
|
|
|
133
|
-
CREATE TRIGGER decisions_ai AFTER INSERT ON decisions BEGIN
|
|
135
|
+
CREATE TRIGGER IF NOT EXISTS decisions_ai AFTER INSERT ON decisions BEGIN
|
|
134
136
|
INSERT INTO decisions_fts(rowid, decision, reason) VALUES (new.id, new.decision, new.reason);
|
|
135
137
|
END;
|
|
136
|
-
CREATE TRIGGER decisions_ad AFTER DELETE ON decisions BEGIN
|
|
138
|
+
CREATE TRIGGER IF NOT EXISTS decisions_ad AFTER DELETE ON decisions BEGIN
|
|
137
139
|
INSERT INTO decisions_fts(decisions_fts, rowid, decision, reason) VALUES('delete', old.id, old.decision, old.reason);
|
|
138
140
|
END;
|
|
139
|
-
CREATE TRIGGER decisions_au AFTER UPDATE ON decisions BEGIN
|
|
141
|
+
CREATE TRIGGER IF NOT EXISTS decisions_au AFTER UPDATE ON decisions BEGIN
|
|
140
142
|
INSERT INTO decisions_fts(decisions_fts, rowid, decision, reason) VALUES('delete', old.id, old.decision, old.reason);
|
|
141
143
|
INSERT INTO decisions_fts(rowid, decision, reason) VALUES (new.id, new.decision, new.reason);
|
|
142
144
|
END;
|
|
143
145
|
|
|
144
|
-
CREATE TRIGGER turn_summaries_ai AFTER INSERT ON turn_summaries BEGIN
|
|
146
|
+
CREATE TRIGGER IF NOT EXISTS turn_summaries_ai AFTER INSERT ON turn_summaries BEGIN
|
|
145
147
|
INSERT INTO turn_summaries_fts(rowid, summary) VALUES (new.id, new.summary);
|
|
146
148
|
END;
|
|
147
|
-
CREATE TRIGGER turn_summaries_ad AFTER DELETE ON turn_summaries BEGIN
|
|
149
|
+
CREATE TRIGGER IF NOT EXISTS turn_summaries_ad AFTER DELETE ON turn_summaries BEGIN
|
|
148
150
|
INSERT INTO turn_summaries_fts(turn_summaries_fts, rowid, summary) VALUES('delete', old.id, old.summary);
|
|
149
151
|
END;
|
|
150
|
-
CREATE TRIGGER turn_summaries_au AFTER UPDATE ON turn_summaries BEGIN
|
|
152
|
+
CREATE TRIGGER IF NOT EXISTS turn_summaries_au AFTER UPDATE ON turn_summaries BEGIN
|
|
151
153
|
INSERT INTO turn_summaries_fts(turn_summaries_fts, rowid, summary) VALUES('delete', old.id, old.summary);
|
|
152
154
|
INSERT INTO turn_summaries_fts(rowid, summary) VALUES (new.id, new.summary);
|
|
153
155
|
END;
|
|
154
156
|
|
|
155
|
-
CREATE TABLE schema_versions (
|
|
157
|
+
CREATE TABLE IF NOT EXISTS schema_versions (
|
|
156
158
|
version INTEGER PRIMARY KEY,
|
|
157
159
|
applied_at_epoch INTEGER NOT NULL
|
|
158
160
|
);
|
|
@@ -213,47 +215,31 @@ export class MemoryDb {
|
|
|
213
215
|
}
|
|
214
216
|
|
|
215
217
|
private ensureSchema(): void {
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
if (this.hasVec) {
|
|
223
|
-
this.db.exec(this.vecTableStmts(VEC_DIM));
|
|
224
|
-
this.db.exec(VEC_TRIGGERS);
|
|
225
|
-
}
|
|
226
|
-
this.db.exec(SCHEMA_V3);
|
|
227
|
-
this.applyV4Columns();
|
|
228
|
-
this.db.exec(SCHEMA_V4);
|
|
229
|
-
this.db
|
|
230
|
-
.prepare("INSERT INTO schema_versions (version, applied_at_epoch) VALUES (?, ?)")
|
|
231
|
-
.run(this.hasVec ? CURRENT_VERSION : 1, epochSeconds());
|
|
232
|
-
});
|
|
233
|
-
run();
|
|
234
|
-
return;
|
|
235
|
-
}
|
|
236
|
-
const current = this.schemaVersion();
|
|
237
|
-
if (current >= CURRENT_VERSION) return;
|
|
218
|
+
// Shape enforcement instead of a version ladder: every statement below
|
|
219
|
+
// is idempotent, so a database from ANY earlier release converges to the
|
|
220
|
+
// current schema on open without version bookkeeping or stepwise
|
|
221
|
+
// migrations. Vec tables are created whenever sqlite-vec is available
|
|
222
|
+
// (and skipped when it isn't), which subsumes the old "created without
|
|
223
|
+
// vec, upgrade later" version dance.
|
|
238
224
|
const run = this.db.transaction(() => {
|
|
239
|
-
|
|
225
|
+
this.db.exec(SCHEMA_V1);
|
|
226
|
+
this.applyV4Columns();
|
|
227
|
+
this.db.exec(SCHEMA_V3);
|
|
228
|
+
this.db.exec(SCHEMA_V4);
|
|
229
|
+
if (this.hasVec) {
|
|
240
230
|
this.db.exec(this.vecTableStmts(VEC_DIM));
|
|
241
231
|
this.db.exec(VEC_TRIGGERS);
|
|
242
232
|
}
|
|
243
|
-
|
|
244
|
-
if (current < 4) {
|
|
245
|
-
this.applyV4Columns();
|
|
246
|
-
this.db.exec(SCHEMA_V4);
|
|
247
|
-
}
|
|
248
|
-
if (current < 2 && !this.hasVec) return; // vec step still pending
|
|
233
|
+
// Informational stamp only — nothing branches on it anymore.
|
|
249
234
|
this.db
|
|
250
|
-
.prepare("INSERT INTO schema_versions (version, applied_at_epoch) VALUES (?, ?)")
|
|
235
|
+
.prepare("INSERT OR REPLACE INTO schema_versions (version, applied_at_epoch) VALUES (?, ?)")
|
|
251
236
|
.run(CURRENT_VERSION, epochSeconds());
|
|
252
237
|
});
|
|
253
238
|
run();
|
|
254
239
|
}
|
|
255
240
|
|
|
256
|
-
/**
|
|
241
|
+
/** `decisions.tags` / `decisions.files` (JSON arrays) on databases created
|
|
242
|
+
* before R13; guarded so re-runs and partial states stay safe. */
|
|
257
243
|
private applyV4Columns(): void {
|
|
258
244
|
const cols = new Set(
|
|
259
245
|
(this.db.prepare("PRAGMA table_info(decisions)").all() as { name: string }[]).map((c) => c.name),
|
|
@@ -20,10 +20,6 @@ export class LocalBackend {
|
|
|
20
20
|
this.graphStore = new GraphStore(path.join(basePath, "graph"));
|
|
21
21
|
}
|
|
22
22
|
|
|
23
|
-
get metricRebuilt(): boolean {
|
|
24
|
-
return this.vectorStore.metricRebuilt;
|
|
25
|
-
}
|
|
26
|
-
|
|
27
23
|
ingest(chunks: Chunk[], nodes: GraphNode[], edges: GraphEdge[]): void {
|
|
28
24
|
this.vectorStore.ingest(chunks);
|
|
29
25
|
this.ftsStore.ingest(chunks);
|
|
@@ -27,8 +27,6 @@ type Sqlite = SqliteHandle;
|
|
|
27
27
|
export class VectorStore {
|
|
28
28
|
private db: Sqlite;
|
|
29
29
|
private dim: number | null = null;
|
|
30
|
-
/** Set when a legacy L2 vec table was wiped — pipeline forces a full reindex. */
|
|
31
|
-
metricRebuilt = false;
|
|
32
30
|
constructor(dbPath: string) {
|
|
33
31
|
fs.mkdirSync(dbPath, { recursive: true });
|
|
34
32
|
// Without sqlite-vec the store still keeps chunk metadata; search()
|
|
@@ -37,10 +35,6 @@ export class VectorStore {
|
|
|
37
35
|
this.ensureTables();
|
|
38
36
|
}
|
|
39
37
|
|
|
40
|
-
private get vecAvailable(): boolean {
|
|
41
|
-
return this.dim !== null || this.vecTableExists();
|
|
42
|
-
}
|
|
43
|
-
|
|
44
38
|
private vecTableExists(): boolean {
|
|
45
39
|
const row = this.db
|
|
46
40
|
.prepare("SELECT sql FROM sqlite_master WHERE type='table' AND name='chunks_vec'")
|
|
@@ -61,10 +55,6 @@ export class VectorStore {
|
|
|
61
55
|
modified_ts REAL
|
|
62
56
|
)
|
|
63
57
|
`);
|
|
64
|
-
const cols = (this.db.pragma("table_info(chunks)") as { name: string }[]).map((c) => c.name);
|
|
65
|
-
if (!cols.includes("modified_ts")) {
|
|
66
|
-
this.db.exec("ALTER TABLE chunks ADD COLUMN modified_ts REAL");
|
|
67
|
-
}
|
|
68
58
|
this.db.exec("CREATE INDEX IF NOT EXISTS idx_chunks_file_path ON chunks(file_path)");
|
|
69
59
|
this.db.exec(`
|
|
70
60
|
CREATE TABLE IF NOT EXISTS chunk_compressions (
|
|
@@ -75,25 +65,19 @@ export class VectorStore {
|
|
|
75
65
|
)
|
|
76
66
|
`);
|
|
77
67
|
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
if (r) {
|
|
92
|
-
const len = this.db
|
|
93
|
-
.prepare("SELECT vec_length(embedding) AS len FROM chunks_vec LIMIT 1")
|
|
94
|
-
.get() as { len: number };
|
|
95
|
-
this.dim = len.len;
|
|
96
|
-
}
|
|
68
|
+
if (this.vecTableExists()) {
|
|
69
|
+
// The table exists — read its dimension. There is no migration path
|
|
70
|
+
// for tables this package did not write (older metric/column shapes):
|
|
71
|
+
// an unrecognized shape cannot occur from our own DDL, and foreign
|
|
72
|
+
// stores are unsupported by design.
|
|
73
|
+
const r = this.db.prepare("SELECT rowid FROM chunks_vec LIMIT 1").get() as
|
|
74
|
+
| { rowid: number }
|
|
75
|
+
| undefined;
|
|
76
|
+
if (r) {
|
|
77
|
+
const len = this.db
|
|
78
|
+
.prepare("SELECT vec_length(embedding) AS len FROM chunks_vec LIMIT 1")
|
|
79
|
+
.get() as { len: number };
|
|
80
|
+
this.dim = len.len;
|
|
97
81
|
}
|
|
98
82
|
}
|
|
99
83
|
}
|
package/src/utils.ts
CHANGED
|
@@ -79,6 +79,19 @@ export function epochSeconds(): number {
|
|
|
79
79
|
return Math.floor(Date.now() / 1000);
|
|
80
80
|
}
|
|
81
81
|
|
|
82
|
+
/**
|
|
83
|
+
* Cooperative macrotask yield for long-running indexing work. ONNX
|
|
84
|
+
* inference, tokenization and tree-sitter parsing all run on the main
|
|
85
|
+
* thread, so a long stretch of synchronous work starves pi's UI loop
|
|
86
|
+
* (measured by scripts/bench-event-loop.mts: ~0.5-0.9 s stalls per 64-text
|
|
87
|
+
* embed batch). setImmediate keeps the yield cheap — same loop turn's check
|
|
88
|
+
* phase — while still letting pending timers and I/O (keystrokes, renders)
|
|
89
|
+
* run between units of work.
|
|
90
|
+
*/
|
|
91
|
+
export function yieldToEventLoop(): Promise<void> {
|
|
92
|
+
return new Promise((resolve) => setImmediate(resolve));
|
|
93
|
+
}
|
|
94
|
+
|
|
82
95
|
/**
|
|
83
96
|
* Fractional seconds. Session-capture ordering needs sub-second resolution:
|
|
84
97
|
* several decisions are commonly recorded within the same wall-clock second,
|