@tekmidian/pai 0.2.2 → 0.3.1
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/ARCHITECTURE.md +148 -6
- package/FEATURE.md +1 -1
- package/README.md +79 -0
- package/dist/{auto-route-D7W6RE06.mjs → auto-route-JjW3f7pV.mjs} +4 -4
- package/dist/{auto-route-D7W6RE06.mjs.map → auto-route-JjW3f7pV.mjs.map} +1 -1
- package/dist/chunker-CbnBe0s0.mjs +191 -0
- package/dist/chunker-CbnBe0s0.mjs.map +1 -0
- package/dist/cli/index.mjs +835 -40
- package/dist/cli/index.mjs.map +1 -1
- package/dist/{config-DBh1bYM2.mjs → config-DELNqq3Z.mjs} +4 -2
- package/dist/{config-DBh1bYM2.mjs.map → config-DELNqq3Z.mjs.map} +1 -1
- package/dist/daemon/index.mjs +9 -9
- package/dist/{daemon-v5O897D4.mjs → daemon-CeTX4NpF.mjs} +94 -13
- package/dist/daemon-CeTX4NpF.mjs.map +1 -0
- package/dist/daemon-mcp/index.mjs +3 -3
- package/dist/db-Dp8VXIMR.mjs +212 -0
- package/dist/db-Dp8VXIMR.mjs.map +1 -0
- package/dist/{detect-BHqYcjJ1.mjs → detect-D7gPV3fQ.mjs} +1 -1
- package/dist/{detect-BHqYcjJ1.mjs.map → detect-D7gPV3fQ.mjs.map} +1 -1
- package/dist/{detector-DKA83aTZ.mjs → detector-cYYhK2Mi.mjs} +2 -2
- package/dist/{detector-DKA83aTZ.mjs.map → detector-cYYhK2Mi.mjs.map} +1 -1
- package/dist/{embeddings-mfqv-jFu.mjs → embeddings-DGRAPAYb.mjs} +2 -2
- package/dist/{embeddings-mfqv-jFu.mjs.map → embeddings-DGRAPAYb.mjs.map} +1 -1
- package/dist/{factory-BDAiKtYR.mjs → factory-DZLvRf4m.mjs} +4 -4
- package/dist/{factory-BDAiKtYR.mjs.map → factory-DZLvRf4m.mjs.map} +1 -1
- package/dist/index.d.mts +1 -1
- package/dist/index.d.mts.map +1 -1
- package/dist/index.mjs +9 -7
- package/dist/{indexer-B20bPHL-.mjs → indexer-CKQcgKsz.mjs} +4 -190
- package/dist/indexer-CKQcgKsz.mjs.map +1 -0
- package/dist/{indexer-backend-BXaocO5r.mjs → indexer-backend-BHztlJJg.mjs} +4 -3
- package/dist/{indexer-backend-BXaocO5r.mjs.map → indexer-backend-BHztlJJg.mjs.map} +1 -1
- package/dist/{ipc-client-DPy7s3iu.mjs → ipc-client-CLt2fNlC.mjs} +1 -1
- package/dist/ipc-client-CLt2fNlC.mjs.map +1 -0
- package/dist/mcp/index.mjs +118 -5
- package/dist/mcp/index.mjs.map +1 -1
- package/dist/{migrate-Bwj7qPaE.mjs → migrate-jokLenje.mjs} +8 -1
- package/dist/migrate-jokLenje.mjs.map +1 -0
- package/dist/{pai-marker-DX_mFLum.mjs → pai-marker-CXQPX2P6.mjs} +1 -1
- package/dist/{pai-marker-DX_mFLum.mjs.map → pai-marker-CXQPX2P6.mjs.map} +1 -1
- package/dist/{postgres-Ccvpc6fC.mjs → postgres-CRBe30Ag.mjs} +1 -1
- package/dist/{postgres-Ccvpc6fC.mjs.map → postgres-CRBe30Ag.mjs.map} +1 -1
- package/dist/{schemas-DjdwzIQ8.mjs → schemas-BY3Pjvje.mjs} +1 -1
- package/dist/{schemas-DjdwzIQ8.mjs.map → schemas-BY3Pjvje.mjs.map} +1 -1
- package/dist/{search-PjftDxxs.mjs → search-GK0ibTJy.mjs} +2 -2
- package/dist/{search-PjftDxxs.mjs.map → search-GK0ibTJy.mjs.map} +1 -1
- package/dist/{sqlite-CHUrNtbI.mjs → sqlite-RyR8Up1v.mjs} +3 -3
- package/dist/{sqlite-CHUrNtbI.mjs.map → sqlite-RyR8Up1v.mjs.map} +1 -1
- package/dist/{tools-CLK4080-.mjs → tools-CUg0Lyg-.mjs} +175 -11
- package/dist/{tools-CLK4080-.mjs.map → tools-CUg0Lyg-.mjs.map} +1 -1
- package/dist/{utils-DEWdIFQ0.mjs → utils-QSfKagcj.mjs} +62 -2
- package/dist/utils-QSfKagcj.mjs.map +1 -0
- package/dist/vault-indexer-Bo2aPSzP.mjs +499 -0
- package/dist/vault-indexer-Bo2aPSzP.mjs.map +1 -0
- package/dist/zettelkasten-Co-w0XSZ.mjs +901 -0
- package/dist/zettelkasten-Co-w0XSZ.mjs.map +1 -0
- package/package.json +2 -1
- package/src/hooks/README.md +99 -0
- package/src/hooks/hooks.md +13 -0
- package/src/hooks/pre-compact.sh +95 -0
- package/src/hooks/session-stop.sh +93 -0
- package/statusline-command.sh +9 -4
- package/templates/pai-skill.template.md +428 -0
- package/templates/templates.md +20 -0
- package/dist/daemon-v5O897D4.mjs.map +0 -1
- package/dist/db-BcDxXVBu.mjs +0 -110
- package/dist/db-BcDxXVBu.mjs.map +0 -1
- package/dist/indexer-B20bPHL-.mjs.map +0 -1
- package/dist/ipc-client-DPy7s3iu.mjs.map +0 -1
- package/dist/migrate-Bwj7qPaE.mjs.map +0 -1
- package/dist/utils-DEWdIFQ0.mjs.map +0 -1
|
@@ -0,0 +1,212 @@
|
|
|
1
|
+
import { t as __exportAll } from "./rolldown-runtime-95iHPtFO.mjs";
|
|
2
|
+
import { mkdirSync } from "node:fs";
|
|
3
|
+
import { homedir } from "node:os";
|
|
4
|
+
import { dirname, join } from "node:path";
|
|
5
|
+
import BetterSqlite3 from "better-sqlite3";
|
|
6
|
+
|
|
7
|
+
//#region src/memory/schema.ts
|
|
8
|
+
const FEDERATION_SCHEMA_SQL = `
|
|
9
|
+
PRAGMA journal_mode = WAL;
|
|
10
|
+
PRAGMA foreign_keys = ON;
|
|
11
|
+
|
|
12
|
+
CREATE TABLE IF NOT EXISTS memory_files (
|
|
13
|
+
project_id INTEGER NOT NULL,
|
|
14
|
+
path TEXT NOT NULL,
|
|
15
|
+
source TEXT NOT NULL DEFAULT 'memory',
|
|
16
|
+
tier TEXT NOT NULL DEFAULT 'topic',
|
|
17
|
+
hash TEXT NOT NULL,
|
|
18
|
+
mtime INTEGER NOT NULL,
|
|
19
|
+
size INTEGER NOT NULL,
|
|
20
|
+
PRIMARY KEY (project_id, path)
|
|
21
|
+
);
|
|
22
|
+
|
|
23
|
+
CREATE TABLE IF NOT EXISTS memory_chunks (
|
|
24
|
+
id TEXT PRIMARY KEY,
|
|
25
|
+
project_id INTEGER NOT NULL,
|
|
26
|
+
source TEXT NOT NULL DEFAULT 'memory',
|
|
27
|
+
tier TEXT NOT NULL DEFAULT 'topic',
|
|
28
|
+
path TEXT NOT NULL,
|
|
29
|
+
start_line INTEGER NOT NULL,
|
|
30
|
+
end_line INTEGER NOT NULL,
|
|
31
|
+
hash TEXT NOT NULL,
|
|
32
|
+
text TEXT NOT NULL,
|
|
33
|
+
updated_at INTEGER NOT NULL,
|
|
34
|
+
embedding BLOB
|
|
35
|
+
);
|
|
36
|
+
|
|
37
|
+
CREATE VIRTUAL TABLE IF NOT EXISTS memory_fts USING fts5(
|
|
38
|
+
text,
|
|
39
|
+
id UNINDEXED,
|
|
40
|
+
project_id UNINDEXED,
|
|
41
|
+
path UNINDEXED,
|
|
42
|
+
source UNINDEXED,
|
|
43
|
+
tier UNINDEXED,
|
|
44
|
+
start_line UNINDEXED,
|
|
45
|
+
end_line UNINDEXED
|
|
46
|
+
);
|
|
47
|
+
|
|
48
|
+
CREATE INDEX IF NOT EXISTS idx_mc_project ON memory_chunks(project_id);
|
|
49
|
+
CREATE INDEX IF NOT EXISTS idx_mc_source ON memory_chunks(project_id, source);
|
|
50
|
+
CREATE INDEX IF NOT EXISTS idx_mc_tier ON memory_chunks(tier);
|
|
51
|
+
CREATE INDEX IF NOT EXISTS idx_mf_project ON memory_files(project_id);
|
|
52
|
+
|
|
53
|
+
-- Vault file inventory with inode dedup
|
|
54
|
+
CREATE TABLE IF NOT EXISTS vault_files (
|
|
55
|
+
vault_path TEXT PRIMARY KEY,
|
|
56
|
+
inode INTEGER NOT NULL,
|
|
57
|
+
device INTEGER NOT NULL,
|
|
58
|
+
hash TEXT NOT NULL,
|
|
59
|
+
title TEXT,
|
|
60
|
+
indexed_at INTEGER NOT NULL
|
|
61
|
+
);
|
|
62
|
+
CREATE INDEX IF NOT EXISTS idx_vf_inode ON vault_files(inode, device);
|
|
63
|
+
|
|
64
|
+
-- Alternate paths to same inode (dual-symlink handling)
|
|
65
|
+
CREATE TABLE IF NOT EXISTS vault_aliases (
|
|
66
|
+
vault_path TEXT PRIMARY KEY,
|
|
67
|
+
canonical_path TEXT NOT NULL,
|
|
68
|
+
inode INTEGER NOT NULL,
|
|
69
|
+
device INTEGER NOT NULL
|
|
70
|
+
);
|
|
71
|
+
CREATE INDEX IF NOT EXISTS idx_va_canonical ON vault_aliases(canonical_path);
|
|
72
|
+
|
|
73
|
+
-- Wikilink graph: directed edges
|
|
74
|
+
CREATE TABLE IF NOT EXISTS vault_links (
|
|
75
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
76
|
+
source_path TEXT NOT NULL,
|
|
77
|
+
target_raw TEXT NOT NULL,
|
|
78
|
+
target_path TEXT,
|
|
79
|
+
link_type TEXT NOT NULL DEFAULT 'wikilink',
|
|
80
|
+
line_number INTEGER NOT NULL DEFAULT 0,
|
|
81
|
+
UNIQUE(source_path, target_raw, line_number)
|
|
82
|
+
);
|
|
83
|
+
CREATE INDEX IF NOT EXISTS idx_vl_source ON vault_links(source_path);
|
|
84
|
+
CREATE INDEX IF NOT EXISTS idx_vl_target ON vault_links(target_path);
|
|
85
|
+
CREATE INDEX IF NOT EXISTS idx_vl_dead ON vault_links(target_path) WHERE target_path IS NULL;
|
|
86
|
+
|
|
87
|
+
-- Obsidian shortest-match resolution lookup
|
|
88
|
+
CREATE TABLE IF NOT EXISTS vault_name_index (
|
|
89
|
+
name TEXT NOT NULL,
|
|
90
|
+
vault_path TEXT NOT NULL,
|
|
91
|
+
PRIMARY KEY (name, vault_path)
|
|
92
|
+
);
|
|
93
|
+
CREATE INDEX IF NOT EXISTS idx_vni_name ON vault_name_index(name);
|
|
94
|
+
|
|
95
|
+
-- Per-file health metrics
|
|
96
|
+
CREATE TABLE IF NOT EXISTS vault_health (
|
|
97
|
+
vault_path TEXT PRIMARY KEY,
|
|
98
|
+
inbound_count INTEGER NOT NULL DEFAULT 0,
|
|
99
|
+
outbound_count INTEGER NOT NULL DEFAULT 0,
|
|
100
|
+
dead_link_count INTEGER NOT NULL DEFAULT 0,
|
|
101
|
+
is_orphan INTEGER NOT NULL DEFAULT 0,
|
|
102
|
+
computed_at INTEGER NOT NULL
|
|
103
|
+
);
|
|
104
|
+
CREATE INDEX IF NOT EXISTS idx_vh_orphan ON vault_health(is_orphan) WHERE is_orphan = 1;
|
|
105
|
+
`;
|
|
106
|
+
/**
|
|
107
|
+
* Apply the full federation schema to an open database.
|
|
108
|
+
*
|
|
109
|
+
* Idempotent — all statements use IF NOT EXISTS so calling this on an
|
|
110
|
+
* already-initialised database is safe.
|
|
111
|
+
*
|
|
112
|
+
* Also runs any necessary migrations for existing databases (e.g. adding the
|
|
113
|
+
* embedding column to an older schema that was created without it).
|
|
114
|
+
*/
|
|
115
|
+
function initializeFederationSchema(db) {
|
|
116
|
+
db.exec(FEDERATION_SCHEMA_SQL);
|
|
117
|
+
runMigrations(db);
|
|
118
|
+
}
|
|
119
|
+
/**
|
|
120
|
+
* Apply incremental migrations to an existing database.
|
|
121
|
+
*
|
|
122
|
+
* Each migration is idempotent — safe to call on a database that has already
|
|
123
|
+
* been migrated.
|
|
124
|
+
*/
|
|
125
|
+
function runMigrations(db) {
|
|
126
|
+
if (!db.prepare("PRAGMA table_info(memory_chunks)").all().some((c) => c.name === "embedding")) db.exec("ALTER TABLE memory_chunks ADD COLUMN embedding BLOB");
|
|
127
|
+
db.exec("CREATE INDEX IF NOT EXISTS idx_mc_embedding ON memory_chunks(id) WHERE embedding IS NOT NULL");
|
|
128
|
+
if (db.prepare("SELECT name FROM sqlite_master WHERE type='table' AND name='vault_files'").all().length === 0) db.exec(`
|
|
129
|
+
CREATE TABLE IF NOT EXISTS vault_files (
|
|
130
|
+
vault_path TEXT PRIMARY KEY,
|
|
131
|
+
inode INTEGER NOT NULL,
|
|
132
|
+
device INTEGER NOT NULL,
|
|
133
|
+
hash TEXT NOT NULL,
|
|
134
|
+
title TEXT,
|
|
135
|
+
indexed_at INTEGER NOT NULL
|
|
136
|
+
);
|
|
137
|
+
CREATE INDEX IF NOT EXISTS idx_vf_inode ON vault_files(inode, device);
|
|
138
|
+
|
|
139
|
+
CREATE TABLE IF NOT EXISTS vault_aliases (
|
|
140
|
+
vault_path TEXT PRIMARY KEY,
|
|
141
|
+
canonical_path TEXT NOT NULL,
|
|
142
|
+
inode INTEGER NOT NULL,
|
|
143
|
+
device INTEGER NOT NULL
|
|
144
|
+
);
|
|
145
|
+
CREATE INDEX IF NOT EXISTS idx_va_canonical ON vault_aliases(canonical_path);
|
|
146
|
+
|
|
147
|
+
CREATE TABLE IF NOT EXISTS vault_links (
|
|
148
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
149
|
+
source_path TEXT NOT NULL,
|
|
150
|
+
target_raw TEXT NOT NULL,
|
|
151
|
+
target_path TEXT,
|
|
152
|
+
link_type TEXT NOT NULL DEFAULT 'wikilink',
|
|
153
|
+
line_number INTEGER NOT NULL DEFAULT 0,
|
|
154
|
+
UNIQUE(source_path, target_raw, line_number)
|
|
155
|
+
);
|
|
156
|
+
CREATE INDEX IF NOT EXISTS idx_vl_source ON vault_links(source_path);
|
|
157
|
+
CREATE INDEX IF NOT EXISTS idx_vl_target ON vault_links(target_path);
|
|
158
|
+
CREATE INDEX IF NOT EXISTS idx_vl_dead ON vault_links(target_path) WHERE target_path IS NULL;
|
|
159
|
+
|
|
160
|
+
CREATE TABLE IF NOT EXISTS vault_name_index (
|
|
161
|
+
name TEXT NOT NULL,
|
|
162
|
+
vault_path TEXT NOT NULL,
|
|
163
|
+
PRIMARY KEY (name, vault_path)
|
|
164
|
+
);
|
|
165
|
+
CREATE INDEX IF NOT EXISTS idx_vni_name ON vault_name_index(name);
|
|
166
|
+
|
|
167
|
+
CREATE TABLE IF NOT EXISTS vault_health (
|
|
168
|
+
vault_path TEXT PRIMARY KEY,
|
|
169
|
+
inbound_count INTEGER NOT NULL DEFAULT 0,
|
|
170
|
+
outbound_count INTEGER NOT NULL DEFAULT 0,
|
|
171
|
+
dead_link_count INTEGER NOT NULL DEFAULT 0,
|
|
172
|
+
is_orphan INTEGER NOT NULL DEFAULT 0,
|
|
173
|
+
computed_at INTEGER NOT NULL
|
|
174
|
+
);
|
|
175
|
+
CREATE INDEX IF NOT EXISTS idx_vh_orphan ON vault_health(is_orphan) WHERE is_orphan = 1;
|
|
176
|
+
`);
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
//#endregion
|
|
180
|
+
//#region src/memory/db.ts
|
|
181
|
+
/**
|
|
182
|
+
* Database connection helper for the PAI federation DB.
|
|
183
|
+
*
|
|
184
|
+
* Uses better-sqlite3 (synchronous API) to open or create federation.db.
|
|
185
|
+
* On first open it runs the full DDL via initializeFederationSchema().
|
|
186
|
+
*/
|
|
187
|
+
var db_exports = /* @__PURE__ */ __exportAll({ openFederation: () => openFederation });
|
|
188
|
+
/** Default federation DB path inside the ~/.pai/ directory. */
|
|
189
|
+
const DEFAULT_FEDERATION_PATH = join(homedir(), ".pai", "federation.db");
|
|
190
|
+
/**
|
|
191
|
+
* Open (or create) the PAI federation database.
|
|
192
|
+
*
|
|
193
|
+
* @param path Absolute path to federation.db. Defaults to ~/.pai/federation.db.
|
|
194
|
+
* @returns An open better-sqlite3 Database instance.
|
|
195
|
+
*
|
|
196
|
+
* Side effects on first call:
|
|
197
|
+
* - Creates the parent directory if it does not exist.
|
|
198
|
+
* - Enables WAL journal mode.
|
|
199
|
+
* - Runs initializeFederationSchema() to ensure tables exist.
|
|
200
|
+
*/
|
|
201
|
+
function openFederation(path = DEFAULT_FEDERATION_PATH) {
|
|
202
|
+
mkdirSync(dirname(path), { recursive: true });
|
|
203
|
+
const db = new BetterSqlite3(path);
|
|
204
|
+
db.pragma("journal_mode = WAL");
|
|
205
|
+
db.pragma("foreign_keys = ON");
|
|
206
|
+
initializeFederationSchema(db);
|
|
207
|
+
return db;
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
//#endregion
|
|
211
|
+
export { initializeFederationSchema as i, openFederation as n, FEDERATION_SCHEMA_SQL as r, db_exports as t };
|
|
212
|
+
//# sourceMappingURL=db-Dp8VXIMR.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"db-Dp8VXIMR.mjs","names":[],"sources":["../src/memory/schema.ts","../src/memory/db.ts"],"sourcesContent":["/**\n * SQLite DDL for the PAI federation database (federation.db).\n *\n * The federation DB is the cross-project search index — a single SQLite file\n * at ~/.pai/federation.db that holds chunked text from every registered\n * project's memory/ and Notes/ directories.\n *\n * Tables:\n * - memory_files — file-level metadata (hash, mtime, size) for change detection\n * - memory_chunks — chunked text with line numbers, tier classification, and optional embedding\n * - memory_fts — FTS5 virtual table backed by memory_chunks text\n * - vault_files — Obsidian vault file inventory with inode-based dedup\n * - vault_aliases — Alternate paths to same inode (dual-symlink handling)\n * - vault_links — Directed wikilink graph edges\n * - vault_name_index — Obsidian shortest-match resolution lookup\n * - vault_health — Per-file health metrics (orphan detection, dead links)\n *\n * Schema version history:\n * v1 — initial schema (BM25 search only)\n * v2 — added embedding BLOB column to memory_chunks (Phase 2.5, vector search)\n * v3 — added vault tables for Zettelkasten file graph (vault_files, vault_aliases,\n * vault_links, vault_name_index, vault_health)\n */\n\nimport type { Database } from \"better-sqlite3\";\n\n/** Current schema version. Bump when adding new columns or tables. */\nexport const SCHEMA_VERSION = 3;\n\nexport const FEDERATION_SCHEMA_SQL = `\nPRAGMA journal_mode = WAL;\nPRAGMA foreign_keys = ON;\n\nCREATE TABLE IF NOT EXISTS memory_files (\n project_id INTEGER NOT NULL,\n path TEXT NOT NULL,\n source TEXT NOT NULL DEFAULT 'memory',\n tier TEXT NOT NULL DEFAULT 'topic',\n hash TEXT NOT NULL,\n mtime INTEGER NOT NULL,\n size INTEGER NOT NULL,\n PRIMARY KEY (project_id, path)\n);\n\nCREATE TABLE IF NOT EXISTS memory_chunks (\n id TEXT PRIMARY KEY,\n project_id INTEGER NOT NULL,\n source TEXT NOT NULL DEFAULT 'memory',\n tier TEXT NOT NULL DEFAULT 'topic',\n path TEXT NOT NULL,\n start_line INTEGER NOT NULL,\n end_line INTEGER NOT NULL,\n hash TEXT NOT NULL,\n text TEXT NOT NULL,\n updated_at INTEGER NOT NULL,\n embedding BLOB\n);\n\nCREATE VIRTUAL TABLE IF NOT EXISTS memory_fts USING fts5(\n text,\n id UNINDEXED,\n project_id UNINDEXED,\n path UNINDEXED,\n source UNINDEXED,\n tier UNINDEXED,\n start_line UNINDEXED,\n end_line UNINDEXED\n);\n\nCREATE INDEX IF NOT EXISTS idx_mc_project ON memory_chunks(project_id);\nCREATE INDEX IF NOT EXISTS idx_mc_source ON memory_chunks(project_id, source);\nCREATE INDEX IF NOT EXISTS idx_mc_tier ON memory_chunks(tier);\nCREATE INDEX IF NOT EXISTS idx_mf_project ON memory_files(project_id);\n\n-- Vault file inventory with inode dedup\nCREATE TABLE IF NOT EXISTS vault_files (\n vault_path TEXT PRIMARY KEY,\n inode INTEGER NOT NULL,\n device INTEGER NOT NULL,\n hash TEXT NOT NULL,\n title TEXT,\n indexed_at INTEGER NOT NULL\n);\nCREATE INDEX IF NOT EXISTS idx_vf_inode ON vault_files(inode, device);\n\n-- Alternate paths to same inode (dual-symlink handling)\nCREATE TABLE IF NOT EXISTS vault_aliases (\n vault_path TEXT PRIMARY KEY,\n canonical_path TEXT NOT NULL,\n inode INTEGER NOT NULL,\n device INTEGER NOT NULL\n);\nCREATE INDEX IF NOT EXISTS idx_va_canonical ON vault_aliases(canonical_path);\n\n-- Wikilink graph: directed edges\nCREATE TABLE IF NOT EXISTS vault_links (\n id INTEGER PRIMARY KEY AUTOINCREMENT,\n source_path TEXT NOT NULL,\n target_raw TEXT NOT NULL,\n target_path TEXT,\n link_type TEXT NOT NULL DEFAULT 'wikilink',\n line_number INTEGER NOT NULL DEFAULT 0,\n UNIQUE(source_path, target_raw, line_number)\n);\nCREATE INDEX IF NOT EXISTS idx_vl_source ON vault_links(source_path);\nCREATE INDEX IF NOT EXISTS idx_vl_target ON vault_links(target_path);\nCREATE INDEX IF NOT EXISTS idx_vl_dead ON vault_links(target_path) WHERE target_path IS NULL;\n\n-- Obsidian shortest-match resolution lookup\nCREATE TABLE IF NOT EXISTS vault_name_index (\n name TEXT NOT NULL,\n vault_path TEXT NOT NULL,\n PRIMARY KEY (name, vault_path)\n);\nCREATE INDEX IF NOT EXISTS idx_vni_name ON vault_name_index(name);\n\n-- Per-file health metrics\nCREATE TABLE IF NOT EXISTS vault_health (\n vault_path TEXT PRIMARY KEY,\n inbound_count INTEGER NOT NULL DEFAULT 0,\n outbound_count INTEGER NOT NULL DEFAULT 0,\n dead_link_count INTEGER NOT NULL DEFAULT 0,\n is_orphan INTEGER NOT NULL DEFAULT 0,\n computed_at INTEGER NOT NULL\n);\nCREATE INDEX IF NOT EXISTS idx_vh_orphan ON vault_health(is_orphan) WHERE is_orphan = 1;\n`;\n\n/**\n * Apply the full federation schema to an open database.\n *\n * Idempotent — all statements use IF NOT EXISTS so calling this on an\n * already-initialised database is safe.\n *\n * Also runs any necessary migrations for existing databases (e.g. adding the\n * embedding column to an older schema that was created without it).\n */\nexport function initializeFederationSchema(db: Database): void {\n db.exec(FEDERATION_SCHEMA_SQL);\n runMigrations(db);\n}\n\n// ---------------------------------------------------------------------------\n// Migrations\n// ---------------------------------------------------------------------------\n\n/**\n * Apply incremental migrations to an existing database.\n *\n * Each migration is idempotent — safe to call on a database that has already\n * been migrated.\n */\nfunction runMigrations(db: Database): void {\n // Migration: add embedding BLOB column if it does not already exist.\n // This handles databases created before Phase 2.5 (schema v1).\n const columns = db.prepare(\"PRAGMA table_info(memory_chunks)\").all() as Array<{\n name: string;\n }>;\n const hasEmbedding = columns.some((c) => c.name === \"embedding\");\n if (!hasEmbedding) {\n db.exec(\"ALTER TABLE memory_chunks ADD COLUMN embedding BLOB\");\n }\n\n // Create the partial index for embedded chunks (safe now that the column exists)\n db.exec(\n \"CREATE INDEX IF NOT EXISTS idx_mc_embedding ON memory_chunks(id) WHERE embedding IS NOT NULL\",\n );\n\n // Migration v2 -> v3: add vault tables if vault_files does not yet exist.\n // All vault table DDL uses CREATE TABLE/INDEX IF NOT EXISTS, so this is idempotent.\n const tables = db.prepare(\n \"SELECT name FROM sqlite_master WHERE type='table' AND name='vault_files'\",\n ).all() as Array<{ name: string }>;\n if (tables.length === 0) {\n db.exec(`\nCREATE TABLE IF NOT EXISTS vault_files (\n vault_path TEXT PRIMARY KEY,\n inode INTEGER NOT NULL,\n device INTEGER NOT NULL,\n hash TEXT NOT NULL,\n title TEXT,\n indexed_at INTEGER NOT NULL\n);\nCREATE INDEX IF NOT EXISTS idx_vf_inode ON vault_files(inode, device);\n\nCREATE TABLE IF NOT EXISTS vault_aliases (\n vault_path TEXT PRIMARY KEY,\n canonical_path TEXT NOT NULL,\n inode INTEGER NOT NULL,\n device INTEGER NOT NULL\n);\nCREATE INDEX IF NOT EXISTS idx_va_canonical ON vault_aliases(canonical_path);\n\nCREATE TABLE IF NOT EXISTS vault_links (\n id INTEGER PRIMARY KEY AUTOINCREMENT,\n source_path TEXT NOT NULL,\n target_raw TEXT NOT NULL,\n target_path TEXT,\n link_type TEXT NOT NULL DEFAULT 'wikilink',\n line_number INTEGER NOT NULL DEFAULT 0,\n UNIQUE(source_path, target_raw, line_number)\n);\nCREATE INDEX IF NOT EXISTS idx_vl_source ON vault_links(source_path);\nCREATE INDEX IF NOT EXISTS idx_vl_target ON vault_links(target_path);\nCREATE INDEX IF NOT EXISTS idx_vl_dead ON vault_links(target_path) WHERE target_path IS NULL;\n\nCREATE TABLE IF NOT EXISTS vault_name_index (\n name TEXT NOT NULL,\n vault_path TEXT NOT NULL,\n PRIMARY KEY (name, vault_path)\n);\nCREATE INDEX IF NOT EXISTS idx_vni_name ON vault_name_index(name);\n\nCREATE TABLE IF NOT EXISTS vault_health (\n vault_path TEXT PRIMARY KEY,\n inbound_count INTEGER NOT NULL DEFAULT 0,\n outbound_count INTEGER NOT NULL DEFAULT 0,\n dead_link_count INTEGER NOT NULL DEFAULT 0,\n is_orphan INTEGER NOT NULL DEFAULT 0,\n computed_at INTEGER NOT NULL\n);\nCREATE INDEX IF NOT EXISTS idx_vh_orphan ON vault_health(is_orphan) WHERE is_orphan = 1;\n`);\n }\n}\n","/**\n * Database connection helper for the PAI federation DB.\n *\n * Uses better-sqlite3 (synchronous API) to open or create federation.db.\n * On first open it runs the full DDL via initializeFederationSchema().\n */\n\nimport { mkdirSync } from \"node:fs\";\nimport { homedir } from \"node:os\";\nimport { dirname, join } from \"node:path\";\nimport BetterSqlite3 from \"better-sqlite3\";\nimport type { Database } from \"better-sqlite3\";\nimport { initializeFederationSchema } from \"./schema.js\";\n\nexport type { Database };\n\n/** Default federation DB path inside the ~/.pai/ directory. */\nconst DEFAULT_FEDERATION_PATH = join(homedir(), \".pai\", \"federation.db\");\n\n/**\n * Open (or create) the PAI federation database.\n *\n * @param path Absolute path to federation.db. Defaults to ~/.pai/federation.db.\n * @returns An open better-sqlite3 Database instance.\n *\n * Side effects on first call:\n * - Creates the parent directory if it does not exist.\n * - Enables WAL journal mode.\n * - Runs initializeFederationSchema() to ensure tables exist.\n */\nexport function openFederation(path: string = DEFAULT_FEDERATION_PATH): Database {\n // Ensure the directory exists before SQLite tries to create the file\n mkdirSync(dirname(path), { recursive: true });\n\n const db = new BetterSqlite3(path);\n\n // WAL gives better concurrent read performance and crash safety\n db.pragma(\"journal_mode = WAL\");\n db.pragma(\"foreign_keys = ON\");\n\n // Apply schema (idempotent — all statements use IF NOT EXISTS)\n initializeFederationSchema(db);\n\n return db;\n}\n"],"mappings":";;;;;;;AA6BA,MAAa,wBAAwB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA4GrC,SAAgB,2BAA2B,IAAoB;AAC7D,IAAG,KAAK,sBAAsB;AAC9B,eAAc,GAAG;;;;;;;;AAanB,SAAS,cAAc,IAAoB;AAOzC,KAAI,CAJY,GAAG,QAAQ,mCAAmC,CAAC,KAAK,CAGvC,MAAM,MAAM,EAAE,SAAS,YAAY,CAE9D,IAAG,KAAK,sDAAsD;AAIhE,IAAG,KACD,+FACD;AAOD,KAHe,GAAG,QAChB,2EACD,CAAC,KAAK,CACI,WAAW,EACpB,IAAG,KAAK;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAgDV;;;;;;;;;;;;;AC7MF,MAAM,0BAA0B,KAAK,SAAS,EAAE,QAAQ,gBAAgB;;;;;;;;;;;;AAaxE,SAAgB,eAAe,OAAe,yBAAmC;AAE/E,WAAU,QAAQ,KAAK,EAAE,EAAE,WAAW,MAAM,CAAC;CAE7C,MAAM,KAAK,IAAI,cAAc,KAAK;AAGlC,IAAG,OAAO,qBAAqB;AAC/B,IAAG,OAAO,oBAAoB;AAG9B,4BAA2B,GAAG;AAE9B,QAAO"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"detect-
|
|
1
|
+
{"version":3,"file":"detect-D7gPV3fQ.mjs","names":[],"sources":["../src/cli/commands/detect.ts"],"sourcesContent":["/**\n * Project detection logic for PAI.\n *\n * detectProject(cwd) — given a filesystem path, returns the best matching\n * project from the registry:\n * 1. Exact path match\n * 2. Longest parent match (project whose root_path is an ancestor of cwd)\n *\n * Exported for use by the CLI `pai project detect` command and the MCP\n * `project_detect` tool.\n */\n\nimport type { Database } from \"better-sqlite3\";\nimport { resolve } from \"node:path\";\n\n// ---------------------------------------------------------------------------\n// Types\n// ---------------------------------------------------------------------------\n\nexport interface DetectedProject {\n id: number;\n slug: string;\n display_name: string;\n root_path: string;\n encoded_dir: string;\n type: string;\n status: string;\n session_count: number;\n last_session_date: string | null;\n match_type: \"exact\" | \"parent\";\n /** Only set when match_type is 'parent' — the portion of cwd below root_path */\n relative_path: string | null;\n}\n\ninterface ProjectRow {\n id: number;\n slug: string;\n display_name: string;\n root_path: string;\n encoded_dir: string;\n type: string;\n status: string;\n}\n\n// ---------------------------------------------------------------------------\n// Core detection function\n// ---------------------------------------------------------------------------\n\n/**\n * Detect which registered project a filesystem path belongs to.\n *\n * @param db Open registry database\n * @param cwd Absolute path to detect (defaults to process.cwd())\n * @returns The best matching project, or null if no match\n */\nexport function detectProject(\n db: Database,\n cwd?: string\n): DetectedProject | null {\n const target = resolve(cwd ?? process.cwd());\n\n // Load all active projects ordered by root_path length descending\n // so the longest (most specific) match wins in a linear scan.\n const projects = db\n .prepare(\n `SELECT id, slug, display_name, root_path, encoded_dir, type, status\n FROM projects\n WHERE status != 'archived'\n ORDER BY LENGTH(root_path) DESC`\n )\n .all() as ProjectRow[];\n\n let matched: ProjectRow | null = null;\n let matchType: \"exact\" | \"parent\" = \"exact\";\n\n for (const p of projects) {\n const root = resolve(p.root_path);\n if (target === root) {\n matched = p;\n matchType = \"exact\";\n break;\n }\n if (!matched && target.startsWith(root + \"/\")) {\n matched = p;\n matchType = \"parent\";\n // Keep scanning — a longer root_path match might exist (but shouldn't\n // since we sorted by length desc). Safety break anyway once found.\n break;\n }\n }\n\n if (!matched) return null;\n\n // Enrich with session stats\n const sessionStats = db\n .prepare(\n `SELECT COUNT(*) AS cnt, MAX(date) AS last_date\n FROM sessions WHERE project_id = ?`\n )\n .get(matched.id) as { cnt: number; last_date: string | null };\n\n const relative =\n matchType === \"parent\"\n ? target.slice(resolve(matched.root_path).length + 1)\n : null;\n\n return {\n id: matched.id,\n slug: matched.slug,\n display_name: matched.display_name,\n root_path: matched.root_path,\n encoded_dir: matched.encoded_dir,\n type: matched.type,\n status: matched.status,\n session_count: sessionStats.cnt,\n last_session_date: sessionStats.last_date,\n match_type: matchType,\n relative_path: relative,\n };\n}\n\n// ---------------------------------------------------------------------------\n// Format helpers\n// ---------------------------------------------------------------------------\n\n/**\n * Format a DetectedProject for human-readable CLI output.\n */\nexport function formatDetection(d: DetectedProject): string {\n const lines: string[] = [\n `slug: ${d.slug}`,\n `display_name: ${d.display_name}`,\n `root_path: ${d.root_path}`,\n `type: ${d.type}`,\n `status: ${d.status}`,\n `match: ${d.match_type}${d.relative_path ? ` (+${d.relative_path})` : \"\"}`,\n `sessions: ${d.session_count}`,\n ];\n if (d.last_session_date) {\n lines.push(`last_session: ${d.last_session_date}`);\n }\n return lines.join(\"\\n\");\n}\n\n/**\n * Format a DetectedProject as JSON for machine consumption.\n */\nexport function formatDetectionJson(d: DetectedProject): string {\n return JSON.stringify(\n {\n slug: d.slug,\n display_name: d.display_name,\n root_path: d.root_path,\n encoded_dir: d.encoded_dir,\n type: d.type,\n status: d.status,\n match_type: d.match_type,\n relative_path: d.relative_path,\n session_count: d.session_count,\n last_session_date: d.last_session_date,\n },\n null,\n 2\n );\n}\n"],"mappings":";;;;;;;;;;AAuDA,SAAgB,cACd,IACA,KACwB;CACxB,MAAM,SAAS,QAAQ,OAAO,QAAQ,KAAK,CAAC;CAI5C,MAAM,WAAW,GACd,QACC;;;wCAID,CACA,KAAK;CAER,IAAI,UAA6B;CACjC,IAAI,YAAgC;AAEpC,MAAK,MAAM,KAAK,UAAU;EACxB,MAAM,OAAO,QAAQ,EAAE,UAAU;AACjC,MAAI,WAAW,MAAM;AACnB,aAAU;AACV,eAAY;AACZ;;AAEF,MAAI,CAAC,WAAW,OAAO,WAAW,OAAO,IAAI,EAAE;AAC7C,aAAU;AACV,eAAY;AAGZ;;;AAIJ,KAAI,CAAC,QAAS,QAAO;CAGrB,MAAM,eAAe,GAClB,QACC;2CAED,CACA,IAAI,QAAQ,GAAG;CAElB,MAAM,WACJ,cAAc,WACV,OAAO,MAAM,QAAQ,QAAQ,UAAU,CAAC,SAAS,EAAE,GACnD;AAEN,QAAO;EACL,IAAI,QAAQ;EACZ,MAAM,QAAQ;EACd,cAAc,QAAQ;EACtB,WAAW,QAAQ;EACnB,aAAa,QAAQ;EACrB,MAAM,QAAQ;EACd,QAAQ,QAAQ;EAChB,eAAe,aAAa;EAC5B,mBAAmB,aAAa;EAChC,YAAY;EACZ,eAAe;EAChB;;;;;AAUH,SAAgB,gBAAgB,GAA4B;CAC1D,MAAM,QAAkB;EACtB,iBAAiB,EAAE;EACnB,iBAAiB,EAAE;EACnB,iBAAiB,EAAE;EACnB,iBAAiB,EAAE;EACnB,iBAAiB,EAAE;EACnB,iBAAiB,EAAE,aAAa,EAAE,gBAAgB,MAAM,EAAE,cAAc,KAAK;EAC7E,iBAAiB,EAAE;EACpB;AACD,KAAI,EAAE,kBACJ,OAAM,KAAK,iBAAiB,EAAE,oBAAoB;AAEpD,QAAO,MAAM,KAAK,KAAK;;;;;AAMzB,SAAgB,oBAAoB,GAA4B;AAC9D,QAAO,KAAK,UACV;EACE,MAAM,EAAE;EACR,cAAc,EAAE;EAChB,WAAW,EAAE;EACb,aAAa,EAAE;EACf,MAAM,EAAE;EACR,QAAQ,EAAE;EACV,YAAY,EAAE;EACd,eAAe,EAAE;EACjB,eAAe,EAAE;EACjB,mBAAmB,EAAE;EACtB,EACD,MACA,EACD"}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { t as __exportAll } from "./rolldown-runtime-95iHPtFO.mjs";
|
|
2
|
-
import { n as populateSlugs, r as searchMemory } from "./search-
|
|
2
|
+
import { n as populateSlugs, r as searchMemory } from "./search-GK0ibTJy.mjs";
|
|
3
3
|
|
|
4
4
|
//#region src/topics/detector.ts
|
|
5
5
|
var detector_exports = /* @__PURE__ */ __exportAll({ detectTopicShift: () => detectTopicShift });
|
|
@@ -71,4 +71,4 @@ async function detectTopicShift(registryDb, federation, params) {
|
|
|
71
71
|
|
|
72
72
|
//#endregion
|
|
73
73
|
export { detector_exports as n, detectTopicShift as t };
|
|
74
|
-
//# sourceMappingURL=detector-
|
|
74
|
+
//# sourceMappingURL=detector-cYYhK2Mi.mjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"detector-
|
|
1
|
+
{"version":3,"file":"detector-cYYhK2Mi.mjs","names":[],"sources":["../src/topics/detector.ts"],"sourcesContent":["/**\n * Topic shift detection engine.\n *\n * Accepts a context summary (recent conversation text) and determines whether\n * the conversation has drifted away from the currently-routed project.\n *\n * Algorithm:\n * 1. Run keyword memory_search against the context text (no project filter)\n * 2. Score results by project — sum of BM25 scores per project\n * 3. Compare the top-scoring project against the current project\n * 4. If a different project dominates by more than the confidence threshold,\n * report a topic shift.\n *\n * Design decisions:\n * - Keyword search only (no semantic) — fast, no embedding requirement\n * - Works with or without an active daemon (direct DB access path)\n * - Stateless: callers supply currentProject; detector has no session memory\n * - Minimal: returns a plain result object, not MCP content arrays\n */\n\nimport type { Database } from \"better-sqlite3\";\nimport type { StorageBackend } from \"../storage/interface.js\";\nimport { searchMemory, populateSlugs } from \"../memory/search.js\";\nimport type { SearchResult } from \"../memory/search.js\";\n\n// ---------------------------------------------------------------------------\n// Types\n// ---------------------------------------------------------------------------\n\nexport interface TopicCheckParams {\n /** Recent conversation context (a few sentences or tool call summaries) */\n context: string;\n /** The project slug the session is currently routed to. May be null/empty. */\n currentProject?: string;\n /**\n * Minimum confidence [0,1] to declare a shift. Default: 0.6.\n * Higher = less sensitive, fewer false positives.\n */\n threshold?: number;\n /**\n * Maximum results to draw from memory search (candidates). Default: 20.\n * More candidates = more accurate scoring, slightly slower.\n */\n candidates?: number;\n}\n\nexport interface TopicCheckResult {\n /** Whether a significant topic shift was detected. */\n shifted: boolean;\n /** The project slug the session is currently routed to (echoed from input). */\n currentProject: string | null;\n /** The project slug that best matches the context, or null if no clear match. */\n suggestedProject: string | null;\n /**\n * Confidence score for the suggested project [0,1].\n * Represents the fraction of total score mass held by the top project.\n * 1.0 = all matching chunks belong to one project.\n * 0.5 = two projects are equally matched.\n */\n confidence: number;\n /** Number of memory chunks that contributed to scoring. */\n chunkCount: number;\n /** Top-3 scoring projects with their normalised scores (for debugging). */\n topProjects: Array<{ slug: string; score: number }>;\n}\n\n// ---------------------------------------------------------------------------\n// Core algorithm\n// ---------------------------------------------------------------------------\n\n/**\n * Detect whether the provided context text best matches a different project\n * than the session's current routing.\n *\n * Works with either a raw SQLite Database or a StorageBackend.\n * For the StorageBackend path, keyword search is used.\n * For the raw Database path (legacy/direct), searchMemory() is called.\n */\nexport async function detectTopicShift(\n registryDb: Database,\n federation: Database | StorageBackend,\n params: TopicCheckParams\n): Promise<TopicCheckResult> {\n const threshold = params.threshold ?? 0.6;\n const candidates = params.candidates ?? 20;\n const currentProject = params.currentProject?.trim() || null;\n\n if (!params.context || params.context.trim().length === 0) {\n return {\n shifted: false,\n currentProject,\n suggestedProject: null,\n confidence: 0,\n chunkCount: 0,\n topProjects: [],\n };\n }\n\n // -------------------------------------------------------------------------\n // Run memory search across ALL projects (no project filter)\n // -------------------------------------------------------------------------\n\n let results: SearchResult[];\n\n const isBackend = (x: Database | StorageBackend): x is StorageBackend =>\n \"backendType\" in x;\n\n if (isBackend(federation)) {\n results = await federation.searchKeyword(params.context, {\n maxResults: candidates,\n });\n } else {\n results = searchMemory(federation, params.context, {\n maxResults: candidates,\n });\n }\n\n if (results.length === 0) {\n return {\n shifted: false,\n currentProject,\n suggestedProject: null,\n confidence: 0,\n chunkCount: 0,\n topProjects: [],\n };\n }\n\n // Populate project slugs from the registry\n const withSlugs = populateSlugs(results, registryDb);\n\n // -------------------------------------------------------------------------\n // Score projects by summing BM25 scores of matching chunks\n // -------------------------------------------------------------------------\n\n const projectScores = new Map<string, number>();\n\n for (const r of withSlugs) {\n const slug = r.projectSlug;\n if (!slug) continue;\n projectScores.set(slug, (projectScores.get(slug) ?? 0) + r.score);\n }\n\n if (projectScores.size === 0) {\n return {\n shifted: false,\n currentProject,\n suggestedProject: null,\n confidence: 0,\n chunkCount: withSlugs.length,\n topProjects: [],\n };\n }\n\n // Sort by total score descending\n const ranked = Array.from(projectScores.entries())\n .sort((a, b) => b[1] - a[1]);\n\n const totalScore = ranked.reduce((sum, [, s]) => sum + s, 0);\n\n // Top-3 for reporting (normalised to [0,1] fraction of total mass)\n const topProjects = ranked.slice(0, 3).map(([slug, score]) => ({\n slug,\n score: totalScore > 0 ? score / totalScore : 0,\n }));\n\n const topSlug = ranked[0][0];\n const topRawScore = ranked[0][1];\n const confidence = totalScore > 0 ? topRawScore / totalScore : 0;\n\n // -------------------------------------------------------------------------\n // Determine if a shift occurred\n // -------------------------------------------------------------------------\n\n // A shift is detected when:\n // 1. confidence >= threshold (the top project dominates)\n // 2. The top project is different from currentProject\n // 3. There is a currentProject to compare against\n // (if no current project, we still return the best match but no \"shift\")\n\n const isDifferent =\n currentProject !== null &&\n topSlug !== currentProject;\n\n const shifted = isDifferent && confidence >= threshold;\n\n return {\n shifted,\n currentProject,\n suggestedProject: topSlug,\n confidence,\n chunkCount: withSlugs.length,\n topProjects,\n };\n}\n"],"mappings":";;;;;;;;;;;;;AA8EA,eAAsB,iBACpB,YACA,YACA,QAC2B;CAC3B,MAAM,YAAY,OAAO,aAAa;CACtC,MAAM,aAAa,OAAO,cAAc;CACxC,MAAM,iBAAiB,OAAO,gBAAgB,MAAM,IAAI;AAExD,KAAI,CAAC,OAAO,WAAW,OAAO,QAAQ,MAAM,CAAC,WAAW,EACtD,QAAO;EACL,SAAS;EACT;EACA,kBAAkB;EAClB,YAAY;EACZ,YAAY;EACZ,aAAa,EAAE;EAChB;CAOH,IAAI;CAEJ,MAAM,aAAa,MACjB,iBAAiB;AAEnB,KAAI,UAAU,WAAW,CACvB,WAAU,MAAM,WAAW,cAAc,OAAO,SAAS,EACvD,YAAY,YACb,CAAC;KAEF,WAAU,aAAa,YAAY,OAAO,SAAS,EACjD,YAAY,YACb,CAAC;AAGJ,KAAI,QAAQ,WAAW,EACrB,QAAO;EACL,SAAS;EACT;EACA,kBAAkB;EAClB,YAAY;EACZ,YAAY;EACZ,aAAa,EAAE;EAChB;CAIH,MAAM,YAAY,cAAc,SAAS,WAAW;CAMpD,MAAM,gCAAgB,IAAI,KAAqB;AAE/C,MAAK,MAAM,KAAK,WAAW;EACzB,MAAM,OAAO,EAAE;AACf,MAAI,CAAC,KAAM;AACX,gBAAc,IAAI,OAAO,cAAc,IAAI,KAAK,IAAI,KAAK,EAAE,MAAM;;AAGnE,KAAI,cAAc,SAAS,EACzB,QAAO;EACL,SAAS;EACT;EACA,kBAAkB;EAClB,YAAY;EACZ,YAAY,UAAU;EACtB,aAAa,EAAE;EAChB;CAIH,MAAM,SAAS,MAAM,KAAK,cAAc,SAAS,CAAC,CAC/C,MAAM,GAAG,MAAM,EAAE,KAAK,EAAE,GAAG;CAE9B,MAAM,aAAa,OAAO,QAAQ,KAAK,GAAG,OAAO,MAAM,GAAG,EAAE;CAG5D,MAAM,cAAc,OAAO,MAAM,GAAG,EAAE,CAAC,KAAK,CAAC,MAAM,YAAY;EAC7D;EACA,OAAO,aAAa,IAAI,QAAQ,aAAa;EAC9C,EAAE;CAEH,MAAM,UAAU,OAAO,GAAG;CAC1B,MAAM,cAAc,OAAO,GAAG;CAC9B,MAAM,aAAa,aAAa,IAAI,cAAc,aAAa;AAkB/D,QAAO;EACL,SANA,mBAAmB,QACnB,YAAY,kBAEiB,cAAc;EAI3C;EACA,kBAAkB;EAClB;EACA,YAAY,UAAU;EACtB;EACD"}
|
|
@@ -87,5 +87,5 @@ function cosineSimilarity(a, b) {
|
|
|
87
87
|
}
|
|
88
88
|
|
|
89
89
|
//#endregion
|
|
90
|
-
export { embeddings_exports as i, cosineSimilarity as n, deserializeEmbedding as r, configureEmbeddingModel as t };
|
|
91
|
-
//# sourceMappingURL=embeddings-
|
|
90
|
+
export { generateEmbedding as a, embeddings_exports as i, cosineSimilarity as n, deserializeEmbedding as r, configureEmbeddingModel as t };
|
|
91
|
+
//# sourceMappingURL=embeddings-DGRAPAYb.mjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"embeddings-
|
|
1
|
+
{"version":3,"file":"embeddings-DGRAPAYb.mjs","names":[],"sources":["../src/memory/embeddings.ts"],"sourcesContent":["/**\n * Embedding generation for the PAI federation memory engine (Phase 2.5).\n *\n * Uses @huggingface/transformers with the Snowflake/snowflake-arctic-embed-m-v1.5 model\n * (768 dims, q8 quantization, MTEB strong retrieval quality).\n *\n * The model uses CLS pooling (first token) — NOT mean pooling.\n * For retrieval, queries require a prefix: \"Represent this sentence for searching relevant passages: \"\n * Documents should be embedded WITHOUT a prefix.\n *\n * The pipeline is a lazy singleton — loaded on first call, reused thereafter.\n * This avoids loading the heavy ML model on every CLI invocation.\n */\n\n// ---------------------------------------------------------------------------\n// Constants\n// ---------------------------------------------------------------------------\n\nexport const EMBEDDING_DIM = 768;\nconst DEFAULT_EMBEDDING_MODEL = \"Snowflake/snowflake-arctic-embed-m-v1.5\";\n\n/** Query prefix required by Snowflake Arctic Embed for retrieval tasks. */\nconst QUERY_PREFIX = \"Represent this sentence for searching relevant passages: \";\n\n// ---------------------------------------------------------------------------\n// Lazy pipeline singleton\n// ---------------------------------------------------------------------------\n\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\nlet _embeddingPipeline: any = null;\nlet _currentModel: string | null = null;\n\n/**\n * Configure the embedding model to use.\n * Must be called before the first generateEmbedding() call.\n * If the pipeline is already loaded with a different model, it will be reloaded.\n *\n * @param model HuggingFace model ID (e.g. \"Snowflake/snowflake-arctic-embed-m-v1.5\").\n * Pass undefined or empty string to use the default model.\n */\nexport function configureEmbeddingModel(model?: string): void {\n const resolved = model?.trim() || DEFAULT_EMBEDDING_MODEL;\n if (_currentModel !== null && _currentModel !== resolved) {\n // Model changed — force reload on next call\n _embeddingPipeline = null;\n }\n _currentModel = resolved;\n}\n\nasync function getEmbedder() {\n const model = _currentModel ?? DEFAULT_EMBEDDING_MODEL;\n if (!_embeddingPipeline) {\n // Dynamic import to avoid loading the ML runtime on startup\n const { pipeline } = await import(\"@huggingface/transformers\");\n _embeddingPipeline = await pipeline(\n \"feature-extraction\",\n model,\n { dtype: \"q8\" },\n );\n }\n return _embeddingPipeline;\n}\n\n// ---------------------------------------------------------------------------\n// Embedding generation\n// ---------------------------------------------------------------------------\n\n/**\n * Generate a normalized 768-dim embedding for the given text.\n *\n * Uses CLS pooling (first token) and L2 normalization (cosine similarity ready).\n *\n * @param text The text to embed.\n * @param isQuery If true, prepend the Snowflake query prefix. Use for search queries.\n * Documents should be embedded without the prefix (default: false).\n */\nexport async function generateEmbedding(text: string, isQuery: boolean = false): Promise<Float32Array> {\n const prefix = isQuery ? QUERY_PREFIX : \"\";\n const input = prefix + text;\n const extractor = await getEmbedder();\n // Snowflake Arctic Embed uses CLS pooling (first token), not mean pooling\n const output = await extractor(input, { pooling: \"cls\", normalize: true });\n return new Float32Array(output.data);\n}\n\n// ---------------------------------------------------------------------------\n// Serialization helpers\n// ---------------------------------------------------------------------------\n\n/**\n * Serialize a Float32Array to a Buffer for storage in a SQLite BLOB column.\n */\nexport function serializeEmbedding(vec: Float32Array): Buffer {\n return Buffer.from(vec.buffer, vec.byteOffset, vec.byteLength);\n}\n\n/**\n * Deserialize a Buffer (from a SQLite BLOB column) back into a Float32Array.\n */\nexport function deserializeEmbedding(blob: Buffer): Float32Array {\n return new Float32Array(blob.buffer, blob.byteOffset, blob.byteLength / 4);\n}\n\n// ---------------------------------------------------------------------------\n// Similarity computation\n// ---------------------------------------------------------------------------\n\n/**\n * Compute cosine similarity between two normalized embedding vectors.\n *\n * Since both vectors are already L2-normalized by the embedding model,\n * cosine similarity reduces to a dot product — but we compute the full\n * formula for correctness when embeddings may not be pre-normalized.\n *\n * Returns a value in [-1, 1] where 1 = identical.\n */\nexport function cosineSimilarity(a: Float32Array, b: Float32Array): number {\n let dot = 0;\n let normA = 0;\n let normB = 0;\n for (let i = 0; i < a.length; i++) {\n dot += a[i] * b[i];\n normA += a[i] * a[i];\n normB += b[i] * b[i];\n }\n const denom = Math.sqrt(normA) * Math.sqrt(normB);\n if (denom === 0) return 0;\n return dot / denom;\n}\n"],"mappings":";;;;;;;;;;AAmBA,MAAM,0BAA0B;;AAGhC,MAAM,eAAe;AAOrB,IAAI,qBAA0B;AAC9B,IAAI,gBAA+B;;;;;;;;;AAUnC,SAAgB,wBAAwB,OAAsB;CAC5D,MAAM,WAAW,OAAO,MAAM,IAAI;AAClC,KAAI,kBAAkB,QAAQ,kBAAkB,SAE9C,sBAAqB;AAEvB,iBAAgB;;AAGlB,eAAe,cAAc;CAC3B,MAAM,QAAQ,iBAAiB;AAC/B,KAAI,CAAC,oBAAoB;EAEvB,MAAM,EAAE,aAAa,MAAM,OAAO;AAClC,uBAAqB,MAAM,SACzB,sBACA,OACA,EAAE,OAAO,MAAM,CAChB;;AAEH,QAAO;;;;;;;;;;;AAgBT,eAAsB,kBAAkB,MAAc,UAAmB,OAA8B;CAErG,MAAM,SADS,UAAU,eAAe,MACjB;CAGvB,MAAM,SAAS,OAFG,MAAM,aAAa,EAEN,OAAO;EAAE,SAAS;EAAO,WAAW;EAAM,CAAC;AAC1E,QAAO,IAAI,aAAa,OAAO,KAAK;;;;;AAUtC,SAAgB,mBAAmB,KAA2B;AAC5D,QAAO,OAAO,KAAK,IAAI,QAAQ,IAAI,YAAY,IAAI,WAAW;;;;;AAMhE,SAAgB,qBAAqB,MAA4B;AAC/D,QAAO,IAAI,aAAa,KAAK,QAAQ,KAAK,YAAY,KAAK,aAAa,EAAE;;;;;;;;;;;AAgB5E,SAAgB,iBAAiB,GAAiB,GAAyB;CACzE,IAAI,MAAM;CACV,IAAI,QAAQ;CACZ,IAAI,QAAQ;AACZ,MAAK,IAAI,IAAI,GAAG,IAAI,EAAE,QAAQ,KAAK;AACjC,SAAO,EAAE,KAAK,EAAE;AAChB,WAAS,EAAE,KAAK,EAAE;AAClB,WAAS,EAAE,KAAK,EAAE;;CAEpB,MAAM,QAAQ,KAAK,KAAK,MAAM,GAAG,KAAK,KAAK,MAAM;AACjD,KAAI,UAAU,EAAG,QAAO;AACxB,QAAO,MAAM"}
|
|
@@ -15,7 +15,7 @@ async function createStorageBackend(config) {
|
|
|
15
15
|
}
|
|
16
16
|
async function tryPostgres(config) {
|
|
17
17
|
try {
|
|
18
|
-
const { PostgresBackend } = await import("./postgres-
|
|
18
|
+
const { PostgresBackend } = await import("./postgres-CRBe30Ag.mjs");
|
|
19
19
|
const backend = new PostgresBackend(config.postgres ?? {});
|
|
20
20
|
const err = await backend.testConnection();
|
|
21
21
|
if (err) {
|
|
@@ -32,11 +32,11 @@ async function tryPostgres(config) {
|
|
|
32
32
|
}
|
|
33
33
|
}
|
|
34
34
|
async function createSQLiteBackend() {
|
|
35
|
-
const { openFederation } = await import("./db-
|
|
36
|
-
const { SQLiteBackend } = await import("./sqlite-
|
|
35
|
+
const { openFederation } = await import("./db-Dp8VXIMR.mjs").then((n) => n.t);
|
|
36
|
+
const { SQLiteBackend } = await import("./sqlite-RyR8Up1v.mjs");
|
|
37
37
|
return new SQLiteBackend(openFederation());
|
|
38
38
|
}
|
|
39
39
|
|
|
40
40
|
//#endregion
|
|
41
41
|
export { factory_exports as n, createStorageBackend as t };
|
|
42
|
-
//# sourceMappingURL=factory-
|
|
42
|
+
//# sourceMappingURL=factory-DZLvRf4m.mjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"factory-
|
|
1
|
+
{"version":3,"file":"factory-DZLvRf4m.mjs","names":[],"sources":["../src/storage/factory.ts"],"sourcesContent":["/**\n * Storage backend factory.\n *\n * Reads the daemon config and returns the appropriate StorageBackend.\n * If Postgres is configured but unavailable, falls back to SQLite with\n * a warning log — the daemon never crashes due to a missing Postgres.\n */\n\nimport type { PaiDaemonConfig } from \"../daemon/config.js\";\nimport type { StorageBackend } from \"./interface.js\";\n\n/**\n * Create and return the configured StorageBackend.\n *\n * Auto-fallback behaviour:\n * - storageBackend = \"sqlite\" → SQLiteBackend always\n * - storageBackend = \"postgres\" → PostgresBackend if reachable, else SQLiteBackend\n */\nexport async function createStorageBackend(\n config: PaiDaemonConfig\n): Promise<StorageBackend> {\n if (config.storageBackend === \"postgres\") {\n return await tryPostgres(config);\n }\n\n // Default: SQLite\n return createSQLiteBackend();\n}\n\nasync function tryPostgres(config: PaiDaemonConfig): Promise<StorageBackend> {\n try {\n const { PostgresBackend } = await import(\"./postgres.js\");\n const pgConfig = config.postgres ?? {};\n const backend = new PostgresBackend(pgConfig);\n\n const err = await backend.testConnection();\n if (err) {\n process.stderr.write(\n `[pai-daemon] Postgres unavailable (${err}). Falling back to SQLite.\\n`\n );\n await backend.close();\n return createSQLiteBackend();\n }\n\n process.stderr.write(\"[pai-daemon] Connected to PostgreSQL backend.\\n\");\n return backend;\n } catch (e) {\n const msg = e instanceof Error ? e.message : String(e);\n process.stderr.write(\n `[pai-daemon] Postgres init error (${msg}). Falling back to SQLite.\\n`\n );\n return createSQLiteBackend();\n }\n}\n\nasync function createSQLiteBackend(): Promise<StorageBackend> {\n const { openFederation } = await import(\"../memory/db.js\");\n const { SQLiteBackend } = await import(\"./sqlite.js\");\n const db = openFederation();\n return new SQLiteBackend(db);\n}\n"],"mappings":";;;;;;;;;;;AAkBA,eAAsB,qBACpB,QACyB;AACzB,KAAI,OAAO,mBAAmB,WAC5B,QAAO,MAAM,YAAY,OAAO;AAIlC,QAAO,qBAAqB;;AAG9B,eAAe,YAAY,QAAkD;AAC3E,KAAI;EACF,MAAM,EAAE,oBAAoB,MAAM,OAAO;EAEzC,MAAM,UAAU,IAAI,gBADH,OAAO,YAAY,EAAE,CACO;EAE7C,MAAM,MAAM,MAAM,QAAQ,gBAAgB;AAC1C,MAAI,KAAK;AACP,WAAQ,OAAO,MACb,sCAAsC,IAAI,8BAC3C;AACD,SAAM,QAAQ,OAAO;AACrB,UAAO,qBAAqB;;AAG9B,UAAQ,OAAO,MAAM,kDAAkD;AACvE,SAAO;UACA,GAAG;EACV,MAAM,MAAM,aAAa,QAAQ,EAAE,UAAU,OAAO,EAAE;AACtD,UAAQ,OAAO,MACb,qCAAqC,IAAI,8BAC1C;AACD,SAAO,qBAAqB;;;AAIhC,eAAe,sBAA+C;CAC5D,MAAM,EAAE,mBAAmB,MAAM,OAAO;CACxC,MAAM,EAAE,kBAAkB,MAAM,OAAO;AAEvC,QAAO,IAAI,cADA,gBAAgB,CACC"}
|
package/dist/index.d.mts
CHANGED
|
@@ -144,7 +144,7 @@ declare function readPaiMarker(projectRoot: string): {
|
|
|
144
144
|
declare function discoverPaiMarkers(searchDirs: string[]): PaiMarker[];
|
|
145
145
|
//#endregion
|
|
146
146
|
//#region src/memory/schema.d.ts
|
|
147
|
-
declare const FEDERATION_SCHEMA_SQL = "\nPRAGMA journal_mode = WAL;\nPRAGMA foreign_keys = ON;\n\nCREATE TABLE IF NOT EXISTS memory_files (\n project_id INTEGER NOT NULL,\n path TEXT NOT NULL,\n source TEXT NOT NULL DEFAULT 'memory',\n tier TEXT NOT NULL DEFAULT 'topic',\n hash TEXT NOT NULL,\n mtime INTEGER NOT NULL,\n size INTEGER NOT NULL,\n PRIMARY KEY (project_id, path)\n);\n\nCREATE TABLE IF NOT EXISTS memory_chunks (\n id TEXT PRIMARY KEY,\n project_id INTEGER NOT NULL,\n source TEXT NOT NULL DEFAULT 'memory',\n tier TEXT NOT NULL DEFAULT 'topic',\n path TEXT NOT NULL,\n start_line INTEGER NOT NULL,\n end_line INTEGER NOT NULL,\n hash TEXT NOT NULL,\n text TEXT NOT NULL,\n updated_at INTEGER NOT NULL,\n embedding BLOB\n);\n\nCREATE VIRTUAL TABLE IF NOT EXISTS memory_fts USING fts5(\n text,\n id UNINDEXED,\n project_id UNINDEXED,\n path UNINDEXED,\n source UNINDEXED,\n tier UNINDEXED,\n start_line UNINDEXED,\n end_line UNINDEXED\n);\n\nCREATE INDEX IF NOT EXISTS idx_mc_project ON memory_chunks(project_id);\nCREATE INDEX IF NOT EXISTS idx_mc_source ON memory_chunks(project_id, source);\nCREATE INDEX IF NOT EXISTS idx_mc_tier ON memory_chunks(tier);\nCREATE INDEX IF NOT EXISTS idx_mf_project ON memory_files(project_id);\n";
|
|
147
|
+
declare const FEDERATION_SCHEMA_SQL = "\nPRAGMA journal_mode = WAL;\nPRAGMA foreign_keys = ON;\n\nCREATE TABLE IF NOT EXISTS memory_files (\n project_id INTEGER NOT NULL,\n path TEXT NOT NULL,\n source TEXT NOT NULL DEFAULT 'memory',\n tier TEXT NOT NULL DEFAULT 'topic',\n hash TEXT NOT NULL,\n mtime INTEGER NOT NULL,\n size INTEGER NOT NULL,\n PRIMARY KEY (project_id, path)\n);\n\nCREATE TABLE IF NOT EXISTS memory_chunks (\n id TEXT PRIMARY KEY,\n project_id INTEGER NOT NULL,\n source TEXT NOT NULL DEFAULT 'memory',\n tier TEXT NOT NULL DEFAULT 'topic',\n path TEXT NOT NULL,\n start_line INTEGER NOT NULL,\n end_line INTEGER NOT NULL,\n hash TEXT NOT NULL,\n text TEXT NOT NULL,\n updated_at INTEGER NOT NULL,\n embedding BLOB\n);\n\nCREATE VIRTUAL TABLE IF NOT EXISTS memory_fts USING fts5(\n text,\n id UNINDEXED,\n project_id UNINDEXED,\n path UNINDEXED,\n source UNINDEXED,\n tier UNINDEXED,\n start_line UNINDEXED,\n end_line UNINDEXED\n);\n\nCREATE INDEX IF NOT EXISTS idx_mc_project ON memory_chunks(project_id);\nCREATE INDEX IF NOT EXISTS idx_mc_source ON memory_chunks(project_id, source);\nCREATE INDEX IF NOT EXISTS idx_mc_tier ON memory_chunks(tier);\nCREATE INDEX IF NOT EXISTS idx_mf_project ON memory_files(project_id);\n\n-- Vault file inventory with inode dedup\nCREATE TABLE IF NOT EXISTS vault_files (\n vault_path TEXT PRIMARY KEY,\n inode INTEGER NOT NULL,\n device INTEGER NOT NULL,\n hash TEXT NOT NULL,\n title TEXT,\n indexed_at INTEGER NOT NULL\n);\nCREATE INDEX IF NOT EXISTS idx_vf_inode ON vault_files(inode, device);\n\n-- Alternate paths to same inode (dual-symlink handling)\nCREATE TABLE IF NOT EXISTS vault_aliases (\n vault_path TEXT PRIMARY KEY,\n canonical_path TEXT NOT NULL,\n inode INTEGER NOT NULL,\n device INTEGER NOT NULL\n);\nCREATE INDEX IF NOT EXISTS idx_va_canonical ON vault_aliases(canonical_path);\n\n-- Wikilink graph: directed edges\nCREATE TABLE IF NOT EXISTS vault_links (\n id INTEGER PRIMARY KEY AUTOINCREMENT,\n source_path TEXT NOT NULL,\n target_raw TEXT NOT NULL,\n target_path TEXT,\n link_type TEXT NOT NULL DEFAULT 'wikilink',\n line_number INTEGER NOT NULL DEFAULT 0,\n UNIQUE(source_path, target_raw, line_number)\n);\nCREATE INDEX IF NOT EXISTS idx_vl_source ON vault_links(source_path);\nCREATE INDEX IF NOT EXISTS idx_vl_target ON vault_links(target_path);\nCREATE INDEX IF NOT EXISTS idx_vl_dead ON vault_links(target_path) WHERE target_path IS NULL;\n\n-- Obsidian shortest-match resolution lookup\nCREATE TABLE IF NOT EXISTS vault_name_index (\n name TEXT NOT NULL,\n vault_path TEXT NOT NULL,\n PRIMARY KEY (name, vault_path)\n);\nCREATE INDEX IF NOT EXISTS idx_vni_name ON vault_name_index(name);\n\n-- Per-file health metrics\nCREATE TABLE IF NOT EXISTS vault_health (\n vault_path TEXT PRIMARY KEY,\n inbound_count INTEGER NOT NULL DEFAULT 0,\n outbound_count INTEGER NOT NULL DEFAULT 0,\n dead_link_count INTEGER NOT NULL DEFAULT 0,\n is_orphan INTEGER NOT NULL DEFAULT 0,\n computed_at INTEGER NOT NULL\n);\nCREATE INDEX IF NOT EXISTS idx_vh_orphan ON vault_health(is_orphan) WHERE is_orphan = 1;\n";
|
|
148
148
|
/**
|
|
149
149
|
* Apply the full federation schema to an open database.
|
|
150
150
|
*
|
package/dist/index.d.mts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.mts","names":[],"sources":["../src/registry/schema.ts","../src/registry/db.ts","../src/registry/migrate.ts","../src/registry/pai-marker.ts","../src/memory/schema.ts","../src/memory/db.ts","../src/memory/chunker.ts","../src/memory/indexer.ts","../src/memory/search.ts"],"mappings":";;;cAgBa,cAAA;AAAA,cAEA,iBAAA;;;;;;ACYb;;;iBDyGgB,gBAAA,CAAiB,EAAA,EAAI,QAAA;;;AArHrC;;;;;AAqHA;;;;;;AArHA,iBCYgB,YAAA,CAAa,IAAA,YAAuC,UAAA;;;;;;;
|
|
1
|
+
{"version":3,"file":"index.d.mts","names":[],"sources":["../src/registry/schema.ts","../src/registry/db.ts","../src/registry/migrate.ts","../src/registry/pai-marker.ts","../src/memory/schema.ts","../src/memory/db.ts","../src/memory/chunker.ts","../src/memory/indexer.ts","../src/memory/search.ts"],"mappings":";;;cAgBa,cAAA;AAAA,cAEA,iBAAA;;;;;;ACYb;;;iBDyGgB,gBAAA,CAAiB,EAAA,EAAI,QAAA;;;AArHrC;;;;;AAqHA;;;;;;AArHA,iBCYgB,YAAA,CAAa,IAAA,YAAuC,UAAA;;;;;;;ACkIpE;;;;;AAUC;;;;;;;;;;;AAyBD;;;;;AAmCA;iBA5GgB,gBAAA,CACd,OAAA,UACA,SAAA,GAAY,GAAA;;;;;;iBAoCE,OAAA,CAAQ,KAAA;AAAA,UAgBd,aAAA;EACR,MAAA;EACA,IAAA;EACA,IAAA;EACA,KAAA;EACA,QAAA;AAAA;;;;;;iBAcc,oBAAA,CACd,QAAA,WACC,aAAA;AAAA,UAiCc,eAAA;EACf,gBAAA;EACA,eAAA;EACA,gBAAA;EACA,MAAA;AAAA;;;;;;AC1DF;;;;;iBDuEgB,eAAA,CACd,EAAA,EAAI,QAAA,EACJ,YAAA,YACC,eAAA;;;;;;AF1OH;;;;;AAEA;UGMiB,SAAA;;EAEf,IAAA;EHR4B;EGU5B,IAAA;EH2G8B;EGzG9B,WAAA;AAAA;;;;;AFAF;;;;;;;iBEkJgB,eAAA,CACd,WAAA,UACA,IAAA,UACA,WAAA;ADzDF;;;;AAAA,iBC0HgB,aAAA,CACd,WAAA;EACG,IAAA;EAAc,UAAA;EAAoB,MAAA;AAAA;ADtFvC;;;;;AAUC;;;;;AAVD,iBCmHgB,kBAAA,CAAmB,UAAA,aAAuB,SAAA;;;cCtP7C,qBAAA;;;AF6Fb;;;;;;;iBEegB,0BAAA,CAA2B,EAAA,EAAI,QAAA;;;AJvH/C;;;;;AAqHA;;;;;;AArHA,iBKYgB,cAAA,CAAe,IAAA,YAAyC,UAAA;;;;;;ALdxE;;;;UMNiB,KAAA;EACf,IAAA;EACA,SAAA;EACA,OAAA;EACA,IAAA;AAAA;AAAA,UAGe,YAAA;ENsHe;EMpH9B,SAAA;ENoHmC;EMlHnC,OAAA;AAAA;;;ALSF;;iBKCgB,cAAA,CAAe,IAAA;;;;;;AJ2F/B;;;;;iBImFgB,aAAA,CAAc,OAAA,UAAiB,IAAA,GAAO,YAAA,GAAe,KAAA;;;UCrLpD,WAAA;EACf,cAAA;EACA,aAAA;EACA,YAAA;AAAA;;;;ANGF;;;;;;iBMagB,UAAA,CACd,YAAA;;AL8EF;;;;iBKXgB,SAAA,CACd,EAAA,EAAI,QAAA,EACJ,SAAA,UACA,QAAA,UACA,YAAA,UACA,MAAA,UACA,IAAA;AAAA,iBA6UoB,YAAA,CACpB,EAAA,EAAI,QAAA,EACJ,SAAA,UACA,QAAA,UACA,cAAA,mBACC,OAAA,CAAQ,WAAA;;;;ALvSX;;;iBK+dsB,QAAA,CACpB,EAAA,EAAI,QAAA,EACJ,UAAA,EAAY,QAAA,GACX,OAAA;EAAU,QAAA;EAAkB,MAAA,EAAQ,WAAA;AAAA;;;UC/mBtB,YAAA;EACf,SAAA;EACA,WAAA;EACA,IAAA;EACA,SAAA;EACA,OAAA;EACA,OAAA;EACA,KAAA;EACA,IAAA;EACA,MAAA;AAAA;AAAA,UAGe,aAAA;EPDY;EOG3B,UAAA;;EAEA,OAAA;;EAEA,KAAA;ENqF8B;EMnF9B,UAAA;ENqFe;EMnFf,QAAA;AAAA;;;;ANuHF;;;;;AAUC;;;;;;;;;;iBM3Fe,aAAA,CAAc,KAAA;ANoH9B;;;;;AAmCA;;;;;;;AAnCA,iBMlFgB,YAAA,CACd,EAAA,EAAI,QAAA,EACJ,KAAA,UACA,IAAA,GAAO,aAAA,GACN,YAAA;AL2DH;;;;AAAA,iBKwNgB,aAAA,CACd,OAAA,EAAS,YAAA,IACT,UAAA,EAAY,QAAA,GACX,YAAA"}
|
package/dist/index.mjs
CHANGED
|
@@ -1,11 +1,13 @@
|
|
|
1
1
|
import { a as initializeSchema, i as SCHEMA_VERSION, n as openRegistry, r as CREATE_TABLES_SQL } from "./db-4lSqLFb8.mjs";
|
|
2
|
-
import
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
6
|
-
import "./
|
|
7
|
-
import {
|
|
8
|
-
import "./
|
|
2
|
+
import "./utils-QSfKagcj.mjs";
|
|
3
|
+
import { a as slugify, i as parseSessionFilename, n as decodeEncodedDir, r as migrateFromJson } from "./migrate-jokLenje.mjs";
|
|
4
|
+
import { n as ensurePaiMarker, r as readPaiMarker, t as discoverPaiMarkers } from "./pai-marker-CXQPX2P6.mjs";
|
|
5
|
+
import { i as initializeFederationSchema, n as openFederation, r as FEDERATION_SCHEMA_SQL } from "./db-Dp8VXIMR.mjs";
|
|
6
|
+
import { n as estimateTokens, t as chunkMarkdown } from "./chunker-CbnBe0s0.mjs";
|
|
7
|
+
import { a as indexProject, i as indexFile, r as indexAll, t as detectTier } from "./indexer-CKQcgKsz.mjs";
|
|
8
|
+
import "./embeddings-DGRAPAYb.mjs";
|
|
9
|
+
import { n as populateSlugs, r as searchMemory, t as buildFtsQuery } from "./search-GK0ibTJy.mjs";
|
|
10
|
+
import "./tools-CUg0Lyg-.mjs";
|
|
9
11
|
import "./mcp/index.mjs";
|
|
10
12
|
|
|
11
13
|
export { CREATE_TABLES_SQL, FEDERATION_SCHEMA_SQL, SCHEMA_VERSION, buildFtsQuery, chunkMarkdown, decodeEncodedDir, detectTier, discoverPaiMarkers, ensurePaiMarker, estimateTokens, indexAll, indexFile, indexProject, initializeFederationSchema, initializeSchema, migrateFromJson, openFederation, openRegistry, parseSessionFilename, populateSlugs, readPaiMarker, searchMemory, slugify };
|