@tekmidian/pai 0.4.0 → 0.5.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 +16 -10
- package/README.md +74 -6
- package/dist/{auto-route-JjW3f7pV.mjs → auto-route-BG6I_4B1.mjs} +3 -3
- package/dist/{auto-route-JjW3f7pV.mjs.map → auto-route-BG6I_4B1.mjs.map} +1 -1
- package/dist/cli/index.mjs +121 -24
- package/dist/cli/index.mjs.map +1 -1
- package/dist/{config-DELNqq3Z.mjs → config-Cf92lGX_.mjs} +17 -3
- package/dist/config-Cf92lGX_.mjs.map +1 -0
- package/dist/daemon/index.mjs +7 -7
- package/dist/{daemon-CeTX4NpF.mjs → daemon-a1W4KgFq.mjs} +12 -12
- package/dist/{daemon-CeTX4NpF.mjs.map → daemon-a1W4KgFq.mjs.map} +1 -1
- package/dist/daemon-mcp/index.mjs +13 -4
- package/dist/daemon-mcp/index.mjs.map +1 -1
- package/dist/{detect-D7gPV3fQ.mjs → detect-BU3Nx_2L.mjs} +1 -1
- package/dist/{detect-D7gPV3fQ.mjs.map → detect-BU3Nx_2L.mjs.map} +1 -1
- package/dist/{detector-cYYhK2Mi.mjs → detector-Bp-2SM3x.mjs} +2 -2
- package/dist/{detector-cYYhK2Mi.mjs.map → detector-Bp-2SM3x.mjs.map} +1 -1
- package/dist/{factory-DZLvRf4m.mjs → factory-CeXQzlwn.mjs} +3 -3
- package/dist/{factory-DZLvRf4m.mjs.map → factory-CeXQzlwn.mjs.map} +1 -1
- package/dist/index.d.mts +29 -1
- package/dist/index.d.mts.map +1 -1
- package/dist/index.mjs +5 -3
- package/dist/{indexer-backend-BHztlJJg.mjs → indexer-backend-DQO-FqAI.mjs} +1 -1
- package/dist/{indexer-backend-BHztlJJg.mjs.map → indexer-backend-DQO-FqAI.mjs.map} +1 -1
- package/dist/{ipc-client-CLt2fNlC.mjs → ipc-client-Bjg_a1dc.mjs} +1 -1
- package/dist/{ipc-client-CLt2fNlC.mjs.map → ipc-client-Bjg_a1dc.mjs.map} +1 -1
- package/dist/mcp/index.mjs +19 -5
- package/dist/mcp/index.mjs.map +1 -1
- package/dist/{postgres-CRBe30Ag.mjs → postgres-CIxeqf_n.mjs} +1 -1
- package/dist/{postgres-CRBe30Ag.mjs.map → postgres-CIxeqf_n.mjs.map} +1 -1
- package/dist/reranker-D7bRAHi6.mjs +71 -0
- package/dist/reranker-D7bRAHi6.mjs.map +1 -0
- package/dist/{schemas-BY3Pjvje.mjs → schemas-BFIgGntb.mjs} +1 -1
- package/dist/{schemas-BY3Pjvje.mjs.map → schemas-BFIgGntb.mjs.map} +1 -1
- package/dist/{search-GK0ibTJy.mjs → search-_oHfguA5.mjs} +47 -4
- package/dist/search-_oHfguA5.mjs.map +1 -0
- package/dist/{sqlite-RyR8Up1v.mjs → sqlite-CymLKiDE.mjs} +2 -2
- package/dist/{sqlite-RyR8Up1v.mjs.map → sqlite-CymLKiDE.mjs.map} +1 -1
- package/dist/{tools-CUg0Lyg-.mjs → tools-DV_lsiCc.mjs} +29 -18
- package/dist/tools-DV_lsiCc.mjs.map +1 -0
- package/dist/{vault-indexer-Bo2aPSzP.mjs → vault-indexer-DXWs9pDn.mjs} +1 -1
- package/dist/{vault-indexer-Bo2aPSzP.mjs.map → vault-indexer-DXWs9pDn.mjs.map} +1 -1
- package/dist/{zettelkasten-Co-w0XSZ.mjs → zettelkasten-e-a4rW_6.mjs} +2 -2
- package/dist/{zettelkasten-Co-w0XSZ.mjs.map → zettelkasten-e-a4rW_6.mjs.map} +1 -1
- package/package.json +1 -1
- package/dist/config-DELNqq3Z.mjs.map +0 -1
- package/dist/search-GK0ibTJy.mjs.map +0 -1
- package/dist/tools-CUg0Lyg-.mjs.map +0 -1
package/ARCHITECTURE.md
CHANGED
|
@@ -1,12 +1,8 @@
|
|
|
1
|
-
---
|
|
2
|
-
links: "[[Ideaverse/AI/PAI/PAI|PAI]]"
|
|
3
|
-
---
|
|
4
|
-
|
|
5
1
|
# PAI Knowledge OS — Architecture
|
|
6
2
|
|
|
7
3
|
Technical reference for PAI's architecture, database schema, CLI commands, and development setup.
|
|
8
4
|
|
|
9
|
-
For user-facing documentation, see [README.md](
|
|
5
|
+
For user-facing documentation, see [README.md](README.md) and [MANUAL.md](MANUAL.md).
|
|
10
6
|
|
|
11
7
|
---
|
|
12
8
|
|
|
@@ -174,7 +170,7 @@ Claude Code (stdio)
|
|
|
174
170
|
|
|
175
171
|
### Tool Reference
|
|
176
172
|
|
|
177
|
-
**`memory_search(query, mode?, project?, limit?)`** — Search the indexed knowledge base. Returns ranked chunks with file paths and line numbers. `mode`: `keyword` (default), `semantic`, or `hybrid`.
|
|
173
|
+
**`memory_search(query, mode?, project?, limit?, rerank?)`** — Search the indexed knowledge base. Returns ranked chunks with file paths and line numbers. `mode`: `keyword` (default), `semantic`, or `hybrid`. Cross-encoder reranking is on by default; set `rerank: false` to skip it.
|
|
178
174
|
|
|
179
175
|
**`memory_get(project, path)`** — Retrieve the complete contents of a specific file from a project's memory index.
|
|
180
176
|
|
|
@@ -243,6 +239,17 @@ Runs both keyword and semantic pipelines, normalizes each result set to a 0–1
|
|
|
243
239
|
pai memory search --mode hybrid "rate limiting patterns"
|
|
244
240
|
```
|
|
245
241
|
|
|
242
|
+
### Cross-Encoder Reranking (on by default)
|
|
243
|
+
|
|
244
|
+
All search results are automatically re-scored using a cross-encoder model (`Xenova/ms-marco-MiniLM-L-6-v2`, 23 MB quantized). Cross-encoders process (query, document) pairs jointly — more accurate than BM25 or bi-encoder cosine but slower since each pair is scored independently. Use `--no-rerank` to skip this step.
|
|
245
|
+
|
|
246
|
+
```bash
|
|
247
|
+
pai memory search "PAI memory search implementation" --mode hybrid
|
|
248
|
+
pai memory search "PAI memory search implementation" --mode hybrid --no-rerank
|
|
249
|
+
```
|
|
250
|
+
|
|
251
|
+
The reranker loads lazily on first use (downloads the model once, ~23 MB). Subsequent calls reuse the cached model. The MCP tool defaults to `rerank: true`; pass `rerank: false` to skip.
|
|
252
|
+
|
|
246
253
|
### Mode Comparison
|
|
247
254
|
|
|
248
255
|
| Mode | Speed | Requires Embeddings | Best For |
|
|
@@ -250,6 +257,7 @@ pai memory search --mode hybrid "rate limiting patterns"
|
|
|
250
257
|
| keyword | Fast | No | Exact terms, IDs, session numbers |
|
|
251
258
|
| semantic | Medium | Yes | Concepts, paraphrases, cross-language |
|
|
252
259
|
| hybrid | Medium | Yes | General-purpose, best quality |
|
|
260
|
+
| any (rerank default) | Slower | Model auto-downloads | All modes — best relevance ordering |
|
|
253
261
|
|
|
254
262
|
---
|
|
255
263
|
|
|
@@ -675,7 +683,8 @@ src/
|
|
|
675
683
|
├── federation/ # Federation schema definitions
|
|
676
684
|
├── hooks/ # Lifecycle hooks (pre-compact, session-stop)
|
|
677
685
|
├── mcp/ # Direct MCP server (legacy)
|
|
678
|
-
├── memory/ # Indexer, chunker, embeddings, search
|
|
686
|
+
├── memory/ # Indexer, chunker, embeddings, search, reranker
|
|
687
|
+
│ ├── reranker.ts # Cross-encoder reranking (Xenova/ms-marco-MiniLM-L-6-v2)
|
|
679
688
|
│ └── vault-indexer.ts # Obsidian vault indexing into v3 vault tables
|
|
680
689
|
├── obsidian/ # Obsidian vault bridge
|
|
681
690
|
│ └── vault-fixer.ts # Repairs broken wikilinks and orphaned entries
|
|
@@ -704,6 +713,3 @@ src/
|
|
|
704
713
|
## License
|
|
705
714
|
|
|
706
715
|
MIT
|
|
707
|
-
|
|
708
|
-
---
|
|
709
|
-
*Links:* [[Ideaverse/AI/PAI/PAI|PAI]]
|
package/README.md
CHANGED
|
@@ -1,7 +1,3 @@
|
|
|
1
|
-
---
|
|
2
|
-
links: "[[Ideaverse/AI/PAI/PAI|PAI]]"
|
|
3
|
-
---
|
|
4
|
-
|
|
5
1
|
# PAI Knowledge OS
|
|
6
2
|
|
|
7
3
|
Claude Code has a memory problem. Every new session starts cold — no idea what you built yesterday, what decisions you made, or where you left off. You re-explain everything, every time. PAI fixes this.
|
|
@@ -127,6 +123,80 @@ For the technical deep-dive — architecture, database schema, CLI reference, an
|
|
|
127
123
|
|
|
128
124
|
---
|
|
129
125
|
|
|
126
|
+
## Search Intelligence
|
|
127
|
+
|
|
128
|
+
PAI doesn't just store your notes — it understands them. Three search modes work together, with reranking and recency boost on by default. All search settings are configurable.
|
|
129
|
+
|
|
130
|
+
### Search Modes
|
|
131
|
+
|
|
132
|
+
| Mode | How it works | Best for |
|
|
133
|
+
|------|-------------|----------|
|
|
134
|
+
| **Keyword** | Full-text search (BM25 via SQLite FTS5) | Exact terms, function names, error messages |
|
|
135
|
+
| **Semantic** | Vector similarity (Snowflake Arctic embeddings) | Finding things by meaning, even with different words |
|
|
136
|
+
| **Hybrid** | Keyword + semantic combined, scores normalized and blended | General use — the default |
|
|
137
|
+
|
|
138
|
+
### Cross-Encoder Reranking
|
|
139
|
+
|
|
140
|
+
Every search automatically runs a second pass: a cross-encoder model reads each (query, result) pair together and re-scores them for relevance. This catches results that keyword or vector search ranked too low.
|
|
141
|
+
|
|
142
|
+
```bash
|
|
143
|
+
# Search with reranking (default)
|
|
144
|
+
pai memory search "how does session routing work"
|
|
145
|
+
|
|
146
|
+
# Skip reranking for faster results
|
|
147
|
+
pai memory search "how does session routing work" --no-rerank
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
The reranker uses a small local model (~23 MB) that runs entirely on your machine. First use downloads it automatically. No API keys, no cloud calls.
|
|
151
|
+
|
|
152
|
+
### Recency Boost
|
|
153
|
+
|
|
154
|
+
Recent content scores higher than older content — on by default with a 90-day half-life. A 3-month-old result retains 50% of its score, a 6-month-old retains 25%, and a year-old retains ~6%.
|
|
155
|
+
|
|
156
|
+
```bash
|
|
157
|
+
# Search uses recency boost automatically (90-day half-life from config)
|
|
158
|
+
pai memory search "notification system"
|
|
159
|
+
|
|
160
|
+
# Override the half-life for this search
|
|
161
|
+
pai memory search "notification system" --recency 30
|
|
162
|
+
|
|
163
|
+
# Disable recency boost for this search
|
|
164
|
+
pai memory search "notification system" --recency 0
|
|
165
|
+
```
|
|
166
|
+
|
|
167
|
+
Via MCP, pass `recency_boost: 90` to the `memory_search` tool, or `recency_boost: 0` to disable.
|
|
168
|
+
|
|
169
|
+
Recency boost is applied after cross-encoder reranking, so relevance is scored first, then time-weighted. Scores are normalized before decay so the math works correctly regardless of the underlying score scale.
|
|
170
|
+
|
|
171
|
+
### Search Settings
|
|
172
|
+
|
|
173
|
+
All search defaults are configurable via `~/.config/pai/config.json` and can be viewed or changed from the command line.
|
|
174
|
+
|
|
175
|
+
```bash
|
|
176
|
+
# View all search settings
|
|
177
|
+
pai memory settings
|
|
178
|
+
|
|
179
|
+
# View a single setting
|
|
180
|
+
pai memory settings recencyBoostDays
|
|
181
|
+
|
|
182
|
+
# Change a setting
|
|
183
|
+
pai memory settings recencyBoostDays 60
|
|
184
|
+
pai memory settings mode hybrid
|
|
185
|
+
pai memory settings rerank false
|
|
186
|
+
```
|
|
187
|
+
|
|
188
|
+
| Setting | Default | Description |
|
|
189
|
+
|---------|---------|-------------|
|
|
190
|
+
| `mode` | `keyword` | Default search mode: `keyword`, `semantic`, or `hybrid` |
|
|
191
|
+
| `rerank` | `true` | Cross-encoder reranking on by default |
|
|
192
|
+
| `recencyBoostDays` | `90` | Recency half-life in days. `0` = off |
|
|
193
|
+
| `defaultLimit` | `10` | Default number of results |
|
|
194
|
+
| `snippetLength` | `200` | Max characters per snippet in MCP results |
|
|
195
|
+
|
|
196
|
+
Settings live in the `search` section of `~/.config/pai/config.json`. Per-call parameters (CLI flags or MCP tool arguments) always override config defaults.
|
|
197
|
+
|
|
198
|
+
---
|
|
199
|
+
|
|
130
200
|
## Zettelkasten Intelligence
|
|
131
201
|
|
|
132
202
|
PAI implements Niklas Luhmann's Zettelkasten principles as six computational operations on your Obsidian vault.
|
|
@@ -176,5 +246,3 @@ PAI Knowledge OS is inspired by [Daniel Miessler](https://github.com/danielmiess
|
|
|
176
246
|
|
|
177
247
|
MIT
|
|
178
248
|
|
|
179
|
-
---
|
|
180
|
-
*Links:* [[Ideaverse/AI/PAI/PAI|PAI]]
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { r as readPaiMarker } from "./pai-marker-CXQPX2P6.mjs";
|
|
2
|
-
import { t as detectProject } from "./detect-
|
|
2
|
+
import { t as detectProject } from "./detect-BU3Nx_2L.mjs";
|
|
3
3
|
import { existsSync } from "node:fs";
|
|
4
4
|
import { dirname, resolve } from "node:path";
|
|
5
5
|
|
|
@@ -26,7 +26,7 @@ async function autoRoute(registryDb, federation, cwd, context) {
|
|
|
26
26
|
const markerResult = findMarkerUpward(registryDb, target);
|
|
27
27
|
if (markerResult) return markerResult;
|
|
28
28
|
if (context && context.trim().length > 0) {
|
|
29
|
-
const { detectTopicShift } = await import("./detector-
|
|
29
|
+
const { detectTopicShift } = await import("./detector-Bp-2SM3x.mjs").then((n) => n.n);
|
|
30
30
|
const topicResult = await detectTopicShift(registryDb, federation, {
|
|
31
31
|
context,
|
|
32
32
|
threshold: .5
|
|
@@ -83,4 +83,4 @@ function formatAutoRouteJson(result) {
|
|
|
83
83
|
|
|
84
84
|
//#endregion
|
|
85
85
|
export { autoRoute, formatAutoRouteJson };
|
|
86
|
-
//# sourceMappingURL=auto-route-
|
|
86
|
+
//# sourceMappingURL=auto-route-BG6I_4B1.mjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"auto-route-
|
|
1
|
+
{"version":3,"file":"auto-route-BG6I_4B1.mjs","names":[],"sources":["../src/session/auto-route.ts"],"sourcesContent":["/**\n * Auto-route: automatic project routing suggestion on session start.\n *\n * Given a working directory (and optional conversation context), determine\n * which registered project the session belongs to.\n *\n * Strategy (in priority order):\n * 1. Path match — exact or parent-directory match in the project registry\n * 2. Marker walk — walk up from cwd looking for Notes/PAI.md, resolve slug\n * 3. Topic match — BM25 keyword search against memory (requires context text)\n *\n * The function is stateless and works with direct DB access (no daemon\n * required), making it fast and safe to call during session startup.\n */\n\nimport type { Database } from \"better-sqlite3\";\nimport type { StorageBackend } from \"../storage/interface.js\";\nimport { resolve, dirname } from \"node:path\";\nimport { existsSync } from \"node:fs\";\nimport { readPaiMarker } from \"../registry/pai-marker.js\";\nimport { detectProject } from \"../cli/commands/detect.js\";\n\n// ---------------------------------------------------------------------------\n// Types\n// ---------------------------------------------------------------------------\n\nexport type AutoRouteMethod = \"path\" | \"marker\" | \"topic\";\n\nexport interface AutoRouteResult {\n /** Project slug */\n slug: string;\n /** Human-readable project name */\n display_name: string;\n /** Absolute path to the project root */\n root_path: string;\n /** How the project was detected */\n method: AutoRouteMethod;\n /** Confidence [0,1]: 1.0 for path/marker matches, BM25 fraction for topic */\n confidence: number;\n}\n\n// ---------------------------------------------------------------------------\n// Core function\n// ---------------------------------------------------------------------------\n\n/**\n * Determine which project a session should be routed to.\n *\n * @param registryDb Open PAI registry database\n * @param federation Memory storage backend (needed only for topic fallback)\n * @param cwd Working directory to detect from (defaults to process.cwd())\n * @param context Optional conversation text for topic-based fallback\n * @returns Best project match, or null if nothing matched\n */\nexport async function autoRoute(\n registryDb: Database,\n federation: Database | StorageBackend,\n cwd?: string,\n context?: string\n): Promise<AutoRouteResult | null> {\n const target = resolve(cwd ?? process.cwd());\n\n // -------------------------------------------------------------------------\n // Strategy 1: Path match via registry\n // -------------------------------------------------------------------------\n\n const pathMatch = detectProject(registryDb, target);\n\n if (pathMatch) {\n return {\n slug: pathMatch.slug,\n display_name: pathMatch.display_name,\n root_path: pathMatch.root_path,\n method: \"path\",\n confidence: 1.0,\n };\n }\n\n // -------------------------------------------------------------------------\n // Strategy 2: PAI.md marker file walk\n //\n // Walk up from cwd, checking <dir>/Notes/PAI.md at each level.\n // Once found, resolve the slug against the registry to get full project info.\n // -------------------------------------------------------------------------\n\n const markerResult = findMarkerUpward(registryDb, target);\n if (markerResult) {\n return markerResult;\n }\n\n // -------------------------------------------------------------------------\n // Strategy 3: Topic detection (requires context text)\n // -------------------------------------------------------------------------\n\n if (context && context.trim().length > 0) {\n // Lazy import to avoid bundler pulling in daemon/index.mjs at module load time\n const { detectTopicShift } = await import(\"../topics/detector.js\");\n const topicResult = await detectTopicShift(registryDb, federation, {\n context,\n threshold: 0.5, // Lower threshold for initial routing (vs shift detection)\n });\n\n if (topicResult.suggestedProject && topicResult.confidence > 0) {\n // Look up the full project info from the registry\n const projectRow = registryDb\n .prepare(\n \"SELECT slug, display_name, root_path FROM projects WHERE slug = ? AND status != 'archived'\"\n )\n .get(topicResult.suggestedProject) as\n | { slug: string; display_name: string; root_path: string }\n | undefined;\n\n if (projectRow) {\n return {\n slug: projectRow.slug,\n display_name: projectRow.display_name,\n root_path: projectRow.root_path,\n method: \"topic\",\n confidence: topicResult.confidence,\n };\n }\n }\n }\n\n return null;\n}\n\n// ---------------------------------------------------------------------------\n// Marker walk helper\n// ---------------------------------------------------------------------------\n\n/**\n * Walk up the directory tree from `startDir`, checking each level for a\n * `Notes/PAI.md` file. If found, read the slug and look up the project.\n *\n * Stops at the filesystem root or after 20 levels (safety guard).\n */\nfunction findMarkerUpward(\n registryDb: Database,\n startDir: string\n): AutoRouteResult | null {\n let current = startDir;\n let depth = 0;\n\n while (depth < 20) {\n const markerPath = `${current}/Notes/PAI.md`;\n\n if (existsSync(markerPath)) {\n const marker = readPaiMarker(current);\n\n if (marker && marker.status !== \"archived\") {\n // Resolve slug to full project info in the registry\n const projectRow = registryDb\n .prepare(\n \"SELECT slug, display_name, root_path FROM projects WHERE slug = ? AND status != 'archived'\"\n )\n .get(marker.slug) as\n | { slug: string; display_name: string; root_path: string }\n | undefined;\n\n if (projectRow) {\n return {\n slug: projectRow.slug,\n display_name: projectRow.display_name,\n root_path: projectRow.root_path,\n method: \"marker\",\n confidence: 1.0,\n };\n }\n }\n }\n\n const parent = dirname(current);\n if (parent === current) break; // Reached filesystem root\n current = parent;\n depth++;\n }\n\n return null;\n}\n\n// ---------------------------------------------------------------------------\n// Format helpers\n// ---------------------------------------------------------------------------\n\n/**\n * Format an AutoRouteResult as a human-readable string for CLI output.\n */\nexport function formatAutoRoute(result: AutoRouteResult): string {\n const lines: string[] = [\n `slug: ${result.slug}`,\n `display_name: ${result.display_name}`,\n `root_path: ${result.root_path}`,\n `method: ${result.method}`,\n `confidence: ${(result.confidence * 100).toFixed(0)}%`,\n ];\n return lines.join(\"\\n\");\n}\n\n/**\n * Format an AutoRouteResult as JSON for machine consumption.\n */\nexport function formatAutoRouteJson(result: AutoRouteResult): string {\n return JSON.stringify(result, null, 2);\n}\n"],"mappings":";;;;;;;;;;;;;;;AAsDA,eAAsB,UACpB,YACA,YACA,KACA,SACiC;CACjC,MAAM,SAAS,QAAQ,OAAO,QAAQ,KAAK,CAAC;CAM5C,MAAM,YAAY,cAAc,YAAY,OAAO;AAEnD,KAAI,UACF,QAAO;EACL,MAAM,UAAU;EAChB,cAAc,UAAU;EACxB,WAAW,UAAU;EACrB,QAAQ;EACR,YAAY;EACb;CAUH,MAAM,eAAe,iBAAiB,YAAY,OAAO;AACzD,KAAI,aACF,QAAO;AAOT,KAAI,WAAW,QAAQ,MAAM,CAAC,SAAS,GAAG;EAExC,MAAM,EAAE,qBAAqB,MAAM,OAAO;EAC1C,MAAM,cAAc,MAAM,iBAAiB,YAAY,YAAY;GACjE;GACA,WAAW;GACZ,CAAC;AAEF,MAAI,YAAY,oBAAoB,YAAY,aAAa,GAAG;GAE9D,MAAM,aAAa,WAChB,QACC,6FACD,CACA,IAAI,YAAY,iBAAiB;AAIpC,OAAI,WACF,QAAO;IACL,MAAM,WAAW;IACjB,cAAc,WAAW;IACzB,WAAW,WAAW;IACtB,QAAQ;IACR,YAAY,YAAY;IACzB;;;AAKP,QAAO;;;;;;;;AAaT,SAAS,iBACP,YACA,UACwB;CACxB,IAAI,UAAU;CACd,IAAI,QAAQ;AAEZ,QAAO,QAAQ,IAAI;AAGjB,MAAI,WAFe,GAAG,QAAQ,eAEJ,EAAE;GAC1B,MAAM,SAAS,cAAc,QAAQ;AAErC,OAAI,UAAU,OAAO,WAAW,YAAY;IAE1C,MAAM,aAAa,WAChB,QACC,6FACD,CACA,IAAI,OAAO,KAAK;AAInB,QAAI,WACF,QAAO;KACL,MAAM,WAAW;KACjB,cAAc,WAAW;KACzB,WAAW,WAAW;KACtB,QAAQ;KACR,YAAY;KACb;;;EAKP,MAAM,SAAS,QAAQ,QAAQ;AAC/B,MAAI,WAAW,QAAS;AACxB,YAAU;AACV;;AAGF,QAAO;;;;;AAwBT,SAAgB,oBAAoB,QAAiC;AACnE,QAAO,KAAK,UAAU,QAAQ,MAAM,EAAE"}
|
package/dist/cli/index.mjs
CHANGED
|
@@ -6,11 +6,11 @@ import { n as ensurePaiMarker, t as discoverPaiMarkers } from "../pai-marker-CXQ
|
|
|
6
6
|
import { n as openFederation } from "../db-Dp8VXIMR.mjs";
|
|
7
7
|
import { a as indexProject, n as embedChunks, r as indexAll } from "../indexer-CKQcgKsz.mjs";
|
|
8
8
|
import "../embeddings-DGRAPAYb.mjs";
|
|
9
|
-
import { n as populateSlugs, r as searchMemory } from "../search-
|
|
10
|
-
import { n as
|
|
11
|
-
import { t as
|
|
12
|
-
import {
|
|
13
|
-
import { t as createStorageBackend } from "../factory-
|
|
9
|
+
import { n as populateSlugs, r as searchMemory } from "../search-_oHfguA5.mjs";
|
|
10
|
+
import { a as expandHome, i as ensureConfigDir, n as CONFIG_FILE$2, o as loadConfig$1, t as CONFIG_DIR } from "../config-Cf92lGX_.mjs";
|
|
11
|
+
import { n as formatDetection, r as formatDetectionJson, t as detectProject } from "../detect-BU3Nx_2L.mjs";
|
|
12
|
+
import { t as PaiClient } from "../ipc-client-Bjg_a1dc.mjs";
|
|
13
|
+
import { t as createStorageBackend } from "../factory-CeXQzlwn.mjs";
|
|
14
14
|
import { appendFileSync, chmodSync, copyFileSync, existsSync, lstatSync, mkdirSync, readFileSync, readdirSync, readlinkSync, renameSync, statSync, symlinkSync, unlinkSync, writeFileSync } from "node:fs";
|
|
15
15
|
import { homedir, tmpdir } from "node:os";
|
|
16
16
|
import { basename, dirname, join, relative, resolve } from "node:path";
|
|
@@ -1809,10 +1809,10 @@ function cmdActive(db, opts) {
|
|
|
1809
1809
|
], rows));
|
|
1810
1810
|
}
|
|
1811
1811
|
async function cmdAutoRoute(opts) {
|
|
1812
|
-
const { autoRoute, formatAutoRoute, formatAutoRouteJson } = await import("../auto-route-
|
|
1812
|
+
const { autoRoute, formatAutoRoute, formatAutoRouteJson } = await import("../auto-route-BG6I_4B1.mjs");
|
|
1813
1813
|
const { openRegistry } = await import("../db-4lSqLFb8.mjs").then((n) => n.t);
|
|
1814
|
-
const { createStorageBackend } = await import("../factory-
|
|
1815
|
-
const { loadConfig } = await import("../config-
|
|
1814
|
+
const { createStorageBackend } = await import("../factory-CeXQzlwn.mjs").then((n) => n.n);
|
|
1815
|
+
const { loadConfig } = await import("../config-Cf92lGX_.mjs").then((n) => n.r);
|
|
1816
1816
|
const config = loadConfig();
|
|
1817
1817
|
const registryDb = openRegistry();
|
|
1818
1818
|
const federation = await createStorageBackend(config);
|
|
@@ -2340,8 +2340,8 @@ async function displayDryRun(plans) {
|
|
|
2340
2340
|
async function countVectorDbPaths(oldPaths) {
|
|
2341
2341
|
if (oldPaths.length === 0) return 0;
|
|
2342
2342
|
try {
|
|
2343
|
-
const { loadConfig } = await import("../config-
|
|
2344
|
-
const { PostgresBackend } = await import("../postgres-
|
|
2343
|
+
const { loadConfig } = await import("../config-Cf92lGX_.mjs").then((n) => n.r);
|
|
2344
|
+
const { PostgresBackend } = await import("../postgres-CIxeqf_n.mjs");
|
|
2345
2345
|
const config = loadConfig();
|
|
2346
2346
|
if (config.storageBackend !== "postgres") return 0;
|
|
2347
2347
|
const pgBackend = new PostgresBackend(config.postgres ?? {});
|
|
@@ -2367,8 +2367,8 @@ async function countVectorDbPaths(oldPaths) {
|
|
|
2367
2367
|
async function updateVectorDbPaths(moves) {
|
|
2368
2368
|
if (moves.length === 0) return 0;
|
|
2369
2369
|
try {
|
|
2370
|
-
const { loadConfig } = await import("../config-
|
|
2371
|
-
const { PostgresBackend } = await import("../postgres-
|
|
2370
|
+
const { loadConfig } = await import("../config-Cf92lGX_.mjs").then((n) => n.r);
|
|
2371
|
+
const { PostgresBackend } = await import("../postgres-CIxeqf_n.mjs");
|
|
2372
2372
|
const config = loadConfig();
|
|
2373
2373
|
if (config.storageBackend !== "postgres") return 0;
|
|
2374
2374
|
const pgBackend = new PostgresBackend(config.postgres ?? {});
|
|
@@ -3039,6 +3039,15 @@ function registerRegistryCommands(registryCmd, getDb) {
|
|
|
3039
3039
|
|
|
3040
3040
|
//#endregion
|
|
3041
3041
|
//#region src/cli/commands/memory.ts
|
|
3042
|
+
/**
|
|
3043
|
+
* CLI commands for the PAI memory engine (Phase 2 / Phase 2.5).
|
|
3044
|
+
*
|
|
3045
|
+
* Commands:
|
|
3046
|
+
* pai memory index [project-slug] — index one or all projects
|
|
3047
|
+
* pai memory embed [project-slug] — generate embeddings for un-embedded chunks
|
|
3048
|
+
* pai memory search <query> — BM25/semantic/hybrid search across federation.db
|
|
3049
|
+
* pai memory status [project-slug] — show index stats
|
|
3050
|
+
*/
|
|
3042
3051
|
function tierColor(tier) {
|
|
3043
3052
|
switch (tier) {
|
|
3044
3053
|
case "evergreen": return chalk.green(tier);
|
|
@@ -3110,7 +3119,7 @@ function registerMemoryCommands(memoryCmd, getDb) {
|
|
|
3110
3119
|
await runEmbed(federation, project.id, project.slug, parseInt(opts.batchSize ?? "50", 10));
|
|
3111
3120
|
} else await runEmbed(federation, void 0, void 0, parseInt(opts.batchSize ?? "50", 10));
|
|
3112
3121
|
});
|
|
3113
|
-
memoryCmd.command("search <query>").description("Search indexed memory (BM25 keyword, semantic, or hybrid)").option("--project <slug>", "Restrict search to a specific project").option("--source <source>", "Restrict to 'memory' or 'notes'").option("--limit <n>", "Maximum results to return"
|
|
3122
|
+
memoryCmd.command("search <query>").description("Search indexed memory (BM25 keyword, semantic, or hybrid)").option("--project <slug>", "Restrict search to a specific project").option("--source <source>", "Restrict to 'memory' or 'notes'").option("--limit <n>", "Maximum results to return").option("--mode <mode>", "Search mode: keyword (default), semantic, hybrid").option("--no-rerank", "Skip cross-encoder reranking (reranking is on by default)").option("--recency <days>", "Apply recency boost: score halves every N days. 0 = off").action(async (query, opts) => {
|
|
3114
3123
|
const registryDb = getDb();
|
|
3115
3124
|
let federation;
|
|
3116
3125
|
try {
|
|
@@ -3119,8 +3128,9 @@ function registerMemoryCommands(memoryCmd, getDb) {
|
|
|
3119
3128
|
console.error(err(`Failed to open federation database: ${e}`));
|
|
3120
3129
|
process.exit(1);
|
|
3121
3130
|
}
|
|
3122
|
-
const
|
|
3123
|
-
const
|
|
3131
|
+
const searchConfig = loadConfig$1().search;
|
|
3132
|
+
const maxResults = parseInt(opts.limit ?? String(searchConfig.defaultLimit), 10);
|
|
3133
|
+
const mode = opts.mode ?? searchConfig.mode;
|
|
3124
3134
|
if (![
|
|
3125
3135
|
"keyword",
|
|
3126
3136
|
"semantic",
|
|
@@ -3194,8 +3204,20 @@ function registerMemoryCommands(memoryCmd, getDb) {
|
|
|
3194
3204
|
console.log(dim(`No results found for: "${query}" (mode: ${mode})`));
|
|
3195
3205
|
return;
|
|
3196
3206
|
}
|
|
3207
|
+
if (opts.rerank !== false) {
|
|
3208
|
+
const { rerankResults } = await import("../reranker-D7bRAHi6.mjs").then((n) => n.r);
|
|
3209
|
+
console.log(dim("Reranking with cross-encoder..."));
|
|
3210
|
+
results = await rerankResults(query, results, { topK: maxResults });
|
|
3211
|
+
}
|
|
3212
|
+
const recencyDays = parseInt(opts.recency ?? String(searchConfig.recencyBoostDays), 10);
|
|
3213
|
+
if (recencyDays > 0) {
|
|
3214
|
+
const { applyRecencyBoost } = await import("../search-_oHfguA5.mjs").then((n) => n.o);
|
|
3215
|
+
console.log(dim(`Applying recency boost (half-life: ${recencyDays} days)...`));
|
|
3216
|
+
results = applyRecencyBoost(results, recencyDays);
|
|
3217
|
+
}
|
|
3197
3218
|
const withSlugs = populateSlugs(results, registryDb);
|
|
3198
|
-
const
|
|
3219
|
+
const rerankLabel = opts.rerank !== false ? " +rerank" : "";
|
|
3220
|
+
const modeLabel = mode !== "keyword" ? ` [${mode}${rerankLabel}]` : opts.rerank !== false ? ` [rerank]` : "";
|
|
3199
3221
|
console.log(`\n ${bold(`Search results for: "${query}"`)}${modeLabel} ${dim(`(${withSlugs.length} found)`)}\n`);
|
|
3200
3222
|
for (const result of withSlugs) {
|
|
3201
3223
|
const projectLabel = result.projectSlug ? chalk.cyan(result.projectSlug) : chalk.cyan(String(result.projectId));
|
|
@@ -3277,6 +3299,81 @@ function registerMemoryCommands(memoryCmd, getDb) {
|
|
|
3277
3299
|
console.log();
|
|
3278
3300
|
}
|
|
3279
3301
|
});
|
|
3302
|
+
memoryCmd.command("settings [key] [value]").description("View or modify search settings in ~/.config/pai/config.json").action((key, value) => {
|
|
3303
|
+
const search = loadConfig$1().search;
|
|
3304
|
+
if (!key) {
|
|
3305
|
+
console.log(`\n ${bold("PAI Memory — Search Settings")}\n`);
|
|
3306
|
+
console.log(` ${bold("mode:")} ${search.mode}`);
|
|
3307
|
+
console.log(` ${bold("rerank:")} ${search.rerank}`);
|
|
3308
|
+
console.log(` ${bold("recencyBoostDays:")} ${search.recencyBoostDays}`);
|
|
3309
|
+
console.log(` ${bold("defaultLimit:")} ${search.defaultLimit}`);
|
|
3310
|
+
console.log(` ${bold("snippetLength:")} ${search.snippetLength}`);
|
|
3311
|
+
console.log();
|
|
3312
|
+
console.log(dim(` Config file: ${CONFIG_FILE$2}`));
|
|
3313
|
+
console.log(dim(` Edit directly or use: pai memory settings <key> <value>`));
|
|
3314
|
+
console.log();
|
|
3315
|
+
return;
|
|
3316
|
+
}
|
|
3317
|
+
if (!value) {
|
|
3318
|
+
const val = search[key];
|
|
3319
|
+
if (val === void 0) {
|
|
3320
|
+
console.error(err(`Unknown setting: ${key}`));
|
|
3321
|
+
console.log(dim(` Valid keys: mode, rerank, recencyBoostDays, defaultLimit, snippetLength`));
|
|
3322
|
+
process.exit(1);
|
|
3323
|
+
}
|
|
3324
|
+
console.log(String(val));
|
|
3325
|
+
return;
|
|
3326
|
+
}
|
|
3327
|
+
const validKeys = new Set([
|
|
3328
|
+
"mode",
|
|
3329
|
+
"rerank",
|
|
3330
|
+
"recencyBoostDays",
|
|
3331
|
+
"defaultLimit",
|
|
3332
|
+
"snippetLength"
|
|
3333
|
+
]);
|
|
3334
|
+
if (!validKeys.has(key)) {
|
|
3335
|
+
console.error(err(`Unknown setting: ${key}`));
|
|
3336
|
+
console.log(dim(` Valid keys: ${[...validKeys].join(", ")}`));
|
|
3337
|
+
process.exit(1);
|
|
3338
|
+
}
|
|
3339
|
+
let fileConfig = {};
|
|
3340
|
+
if (existsSync(CONFIG_FILE$2)) try {
|
|
3341
|
+
fileConfig = JSON.parse(readFileSync(CONFIG_FILE$2, "utf-8"));
|
|
3342
|
+
} catch {
|
|
3343
|
+
console.error(err(`Could not parse ${CONFIG_FILE$2}`));
|
|
3344
|
+
process.exit(1);
|
|
3345
|
+
}
|
|
3346
|
+
if (!fileConfig.search || typeof fileConfig.search !== "object") fileConfig.search = {};
|
|
3347
|
+
let parsed;
|
|
3348
|
+
if (key === "mode") {
|
|
3349
|
+
if (![
|
|
3350
|
+
"keyword",
|
|
3351
|
+
"semantic",
|
|
3352
|
+
"hybrid"
|
|
3353
|
+
].includes(value)) {
|
|
3354
|
+
console.error(err(`Invalid mode: ${value}. Must be keyword, semantic, or hybrid.`));
|
|
3355
|
+
process.exit(1);
|
|
3356
|
+
}
|
|
3357
|
+
parsed = value;
|
|
3358
|
+
} else if (key === "rerank") parsed = value === "true" || value === "1" || value === "on";
|
|
3359
|
+
else {
|
|
3360
|
+
parsed = parseInt(value, 10);
|
|
3361
|
+
if (isNaN(parsed)) {
|
|
3362
|
+
console.error(err(`Invalid number: ${value}`));
|
|
3363
|
+
process.exit(1);
|
|
3364
|
+
}
|
|
3365
|
+
}
|
|
3366
|
+
fileConfig.search[key] = parsed;
|
|
3367
|
+
try {
|
|
3368
|
+
ensureConfigDir();
|
|
3369
|
+
writeFileSync(CONFIG_FILE$2, JSON.stringify(fileConfig, null, 2) + "\n", "utf-8");
|
|
3370
|
+
console.log(ok(`Set search.${key} = ${parsed}`));
|
|
3371
|
+
console.log(dim(` Restart daemon to apply: pai daemon restart`));
|
|
3372
|
+
} catch (e) {
|
|
3373
|
+
console.error(err(`Could not write config: ${e}`));
|
|
3374
|
+
process.exit(1);
|
|
3375
|
+
}
|
|
3376
|
+
});
|
|
3280
3377
|
}
|
|
3281
3378
|
|
|
3282
3379
|
//#endregion
|
|
@@ -3640,8 +3737,8 @@ function cmdLogs(opts) {
|
|
|
3640
3737
|
}
|
|
3641
3738
|
function registerDaemonCommands(daemonCmd) {
|
|
3642
3739
|
daemonCmd.command("serve").description("Start the PAI daemon in the foreground").action(async () => {
|
|
3643
|
-
const { serve } = await import("../daemon-
|
|
3644
|
-
const { loadConfig: lc, ensureConfigDir } = await import("../config-
|
|
3740
|
+
const { serve } = await import("../daemon-a1W4KgFq.mjs").then((n) => n.t);
|
|
3741
|
+
const { loadConfig: lc, ensureConfigDir } = await import("../config-Cf92lGX_.mjs").then((n) => n.r);
|
|
3645
3742
|
ensureConfigDir();
|
|
3646
3743
|
await serve(lc());
|
|
3647
3744
|
});
|
|
@@ -6087,7 +6184,7 @@ async function cmdExplore(note, opts) {
|
|
|
6087
6184
|
const depth = parseInt(opts.depth ?? "3", 10);
|
|
6088
6185
|
const direction = opts.direction ?? "both";
|
|
6089
6186
|
const mode = opts.mode ?? "all";
|
|
6090
|
-
const { zettelExplore } = await import("../zettelkasten-
|
|
6187
|
+
const { zettelExplore } = await import("../zettelkasten-e-a4rW_6.mjs");
|
|
6091
6188
|
const result = zettelExplore(getFedDb(), {
|
|
6092
6189
|
startNote: note,
|
|
6093
6190
|
depth,
|
|
@@ -6138,7 +6235,7 @@ async function cmdHealth(opts) {
|
|
|
6138
6235
|
const projectPath = opts.project;
|
|
6139
6236
|
const recentDays = parseInt(opts.days ?? "30", 10);
|
|
6140
6237
|
const includeTypes = opts.include ? opts.include.split(",").map((s) => s.trim()) : void 0;
|
|
6141
|
-
const { zettelHealth } = await import("../zettelkasten-
|
|
6238
|
+
const { zettelHealth } = await import("../zettelkasten-e-a4rW_6.mjs");
|
|
6142
6239
|
const result = zettelHealth(getFedDb(), {
|
|
6143
6240
|
scope,
|
|
6144
6241
|
projectPath,
|
|
@@ -6194,7 +6291,7 @@ async function cmdSurprise(note, opts) {
|
|
|
6194
6291
|
const limit = parseInt(opts.limit ?? "10", 10);
|
|
6195
6292
|
const minSimilarity = parseFloat(opts.minSimilarity ?? "0.3");
|
|
6196
6293
|
const minGraphDistance = parseInt(opts.minDistance ?? "3", 10);
|
|
6197
|
-
const { zettelSurprise } = await import("../zettelkasten-
|
|
6294
|
+
const { zettelSurprise } = await import("../zettelkasten-e-a4rW_6.mjs");
|
|
6198
6295
|
const db = getFedDb();
|
|
6199
6296
|
console.log();
|
|
6200
6297
|
console.log(header(" PAI Zettel Surprise"));
|
|
@@ -6234,7 +6331,7 @@ async function cmdSuggest(note, opts) {
|
|
|
6234
6331
|
const vaultProjectId = parseInt(opts.vaultProjectId, 10);
|
|
6235
6332
|
const limit = parseInt(opts.limit ?? "5", 10);
|
|
6236
6333
|
const excludeLinked = opts.excludeLinked !== false;
|
|
6237
|
-
const { zettelSuggest } = await import("../zettelkasten-
|
|
6334
|
+
const { zettelSuggest } = await import("../zettelkasten-e-a4rW_6.mjs");
|
|
6238
6335
|
const db = getFedDb();
|
|
6239
6336
|
console.log();
|
|
6240
6337
|
console.log(header(" PAI Zettel Suggest"));
|
|
@@ -6272,7 +6369,7 @@ async function cmdConverse(question, opts) {
|
|
|
6272
6369
|
const vaultProjectId = parseInt(opts.vaultProjectId, 10);
|
|
6273
6370
|
const depth = parseInt(opts.depth ?? "2", 10);
|
|
6274
6371
|
const limit = parseInt(opts.limit ?? "15", 10);
|
|
6275
|
-
const { zettelConverse } = await import("../zettelkasten-
|
|
6372
|
+
const { zettelConverse } = await import("../zettelkasten-e-a4rW_6.mjs");
|
|
6276
6373
|
const db = getFedDb();
|
|
6277
6374
|
console.log();
|
|
6278
6375
|
console.log(header(" PAI Zettel Converse"));
|
|
@@ -6320,7 +6417,7 @@ async function cmdThemes(opts) {
|
|
|
6320
6417
|
const minClusterSize = parseInt(opts.minSize ?? "3", 10);
|
|
6321
6418
|
const maxThemes = parseInt(opts.maxThemes ?? "10", 10);
|
|
6322
6419
|
const similarityThreshold = parseFloat(opts.threshold ?? "0.65");
|
|
6323
|
-
const { zettelThemes } = await import("../zettelkasten-
|
|
6420
|
+
const { zettelThemes } = await import("../zettelkasten-e-a4rW_6.mjs");
|
|
6324
6421
|
const db = getFedDb();
|
|
6325
6422
|
console.log();
|
|
6326
6423
|
console.log(header(" PAI Zettel Themes"));
|