@tekmidian/pai 0.10.1 → 0.12.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (126) hide show
  1. package/README.md +107 -31
  2. package/dist/{auto-route-D54Kv7yI.mjs → auto-route-CLYcToZ5.mjs} +4 -4
  3. package/dist/{auto-route-D54Kv7yI.mjs.map → auto-route-CLYcToZ5.mjs.map} +1 -1
  4. package/dist/cli/index.mjs +23 -10150
  5. package/dist/cli/index.mjs.map +1 -1
  6. package/dist/cli/program.d.mts +11 -0
  7. package/dist/cli/program.d.mts.map +1 -0
  8. package/dist/cli/program.mjs +249 -0
  9. package/dist/cli/program.mjs.map +1 -0
  10. package/dist/{clusters-BwmdKB-V.mjs → clusters-BYPw7vfW.mjs} +2 -2
  11. package/dist/{clusters-BwmdKB-V.mjs.map → clusters-BYPw7vfW.mjs.map} +1 -1
  12. package/dist/{config-DqBY3aT0.mjs → config-BuhHWyOK.mjs} +1 -1
  13. package/dist/{config-DqBY3aT0.mjs.map → config-BuhHWyOK.mjs.map} +1 -1
  14. package/dist/daemon/index.mjs +16 -18
  15. package/dist/daemon/index.mjs.map +1 -1
  16. package/dist/{daemon-CSKj8Xm9.mjs → daemon-DDplhnm9.mjs} +185 -31
  17. package/dist/daemon-DDplhnm9.mjs.map +1 -0
  18. package/dist/daemon-mcp/index.mjs +2 -2
  19. package/dist/{db-CYmBWcjh.mjs → db-CmYbAVCD.mjs} +1 -1
  20. package/dist/{db-CYmBWcjh.mjs.map → db-CmYbAVCD.mjs.map} +1 -1
  21. package/dist/{detect-CdaA48EI.mjs → detect-KjycLtXM.mjs} +1 -1
  22. package/dist/{detect-CdaA48EI.mjs.map → detect-KjycLtXM.mjs.map} +1 -1
  23. package/dist/{detector-Q_lVXtj-.mjs → detector-ZiOhHszd.mjs} +2 -2
  24. package/dist/{detector-Q_lVXtj-.mjs.map → detector-ZiOhHszd.mjs.map} +1 -1
  25. package/dist/{embeddings-DGRAPAYb.mjs → embeddings-BJPOcbik.mjs} +1 -1
  26. package/dist/{embeddings-DGRAPAYb.mjs.map → embeddings-BJPOcbik.mjs.map} +1 -1
  27. package/dist/{factory-BufouUQ1.mjs → factory-CayQCMoN.mjs} +5 -8
  28. package/dist/{factory-BufouUQ1.mjs.map → factory-CayQCMoN.mjs.map} +1 -1
  29. package/dist/{helpers-crDEr6S2.mjs → helpers-IjZkXBhj.mjs} +1 -1
  30. package/dist/{helpers-crDEr6S2.mjs.map → helpers-IjZkXBhj.mjs.map} +1 -1
  31. package/dist/hooks/observe.mjs +21 -0
  32. package/dist/hooks/observe.mjs.map +2 -2
  33. package/dist/index.mjs +9 -10
  34. package/dist/{indexer-backend-DcYThufd.mjs → indexer-backend-Cm5RS7y0.mjs} +3 -3
  35. package/dist/{indexer-backend-DcYThufd.mjs.map → indexer-backend-Cm5RS7y0.mjs.map} +1 -1
  36. package/dist/{ipc-client-C3pjwy2m.mjs → ipc-client-CoyUHPod.mjs} +1 -1
  37. package/dist/{ipc-client-C3pjwy2m.mjs.map → ipc-client-CoyUHPod.mjs.map} +1 -1
  38. package/dist/{kg-entity-DVzTsy6G.mjs → kg-entity-LXblD7LZ.mjs} +1 -1
  39. package/dist/{kg-entity-DVzTsy6G.mjs.map → kg-entity-LXblD7LZ.mjs.map} +1 -1
  40. package/dist/{kg-extraction-iR1BKKWK.mjs → kg-extraction-C8DEUHTS.mjs} +17 -6
  41. package/dist/kg-extraction-C8DEUHTS.mjs.map +1 -0
  42. package/dist/{latent-ideas-dWVuI_dg.mjs → latent-ideas-RyC6eyqI.mjs} +4 -4
  43. package/dist/{latent-ideas-dWVuI_dg.mjs.map → latent-ideas-RyC6eyqI.mjs.map} +1 -1
  44. package/dist/main-resolver-uFxNiDy7.mjs +1339 -0
  45. package/dist/main-resolver-uFxNiDy7.mjs.map +1 -0
  46. package/dist/{migrate-fLD6rAdO.mjs → migrate-B02wDDgS.mjs} +2 -2
  47. package/dist/{migrate-fLD6rAdO.mjs.map → migrate-B02wDDgS.mjs.map} +1 -1
  48. package/dist/{neighborhood-DixmpfPT.mjs → neighborhood-CklGIB8r.mjs} +2 -2
  49. package/dist/{neighborhood-DixmpfPT.mjs.map → neighborhood-CklGIB8r.mjs.map} +1 -1
  50. package/dist/{note-context-BY7rfGdl.mjs → note-context-qvZlVXVO.mjs} +1 -1
  51. package/dist/{note-context-BY7rfGdl.mjs.map → note-context-qvZlVXVO.mjs.map} +1 -1
  52. package/dist/{pai-marker-CXQPX2P6.mjs → pai-marker-HVBwwBW-.mjs} +1 -1
  53. package/dist/{pai-marker-CXQPX2P6.mjs.map → pai-marker-HVBwwBW-.mjs.map} +1 -1
  54. package/dist/pick-Dt21ojcW.mjs +8250 -0
  55. package/dist/pick-Dt21ojcW.mjs.map +1 -0
  56. package/dist/{postgres-Cp25xZes.mjs → postgres-B451Hnkm.mjs} +2 -2
  57. package/dist/{postgres-Cp25xZes.mjs.map → postgres-B451Hnkm.mjs.map} +1 -1
  58. package/dist/{query-feedback-C0hKUHVz.mjs → query-feedback-fapbqRgh.mjs} +1 -1
  59. package/dist/{query-feedback-C0hKUHVz.mjs.map → query-feedback-fapbqRgh.mjs.map} +1 -1
  60. package/dist/{reranker-CMNZcfVx.mjs → reranker-C08R99zn.mjs} +1 -1
  61. package/dist/{reranker-CMNZcfVx.mjs.map → reranker-C08R99zn.mjs.map} +1 -1
  62. package/dist/{search-i2nlQ-JM.mjs → search-HcdKtMla.mjs} +3 -3
  63. package/dist/{search-i2nlQ-JM.mjs.map → search-HcdKtMla.mjs.map} +1 -1
  64. package/dist/{sqlite-B3JHfJc2.mjs → sqlite-DQpY1Esi.mjs} +3 -3
  65. package/dist/{sqlite-B3JHfJc2.mjs.map → sqlite-DQpY1Esi.mjs.map} +1 -1
  66. package/dist/{state-BHZAhLou.mjs → state-BIlxNRUn.mjs} +1 -1
  67. package/dist/{state-BHZAhLou.mjs.map → state-BIlxNRUn.mjs.map} +1 -1
  68. package/dist/{stop-words-BaMEGVeY.mjs → stop-words-BwplsQ3z.mjs} +1 -1
  69. package/dist/{stop-words-BaMEGVeY.mjs.map → stop-words-BwplsQ3z.mjs.map} +1 -1
  70. package/dist/{sync-CmBKOL3K.mjs → sync-uPR4g438.mjs} +205 -6
  71. package/dist/sync-uPR4g438.mjs.map +1 -0
  72. package/dist/{themes-DO8mUVwD.mjs → themes-Cg8oG9Ra.mjs} +3 -3
  73. package/dist/{themes-DO8mUVwD.mjs.map → themes-Cg8oG9Ra.mjs.map} +1 -1
  74. package/dist/{tools-C1JNgya7.mjs → tools-Op3C2Nm6.mjs} +26 -26
  75. package/dist/{tools-C1JNgya7.mjs.map → tools-Op3C2Nm6.mjs.map} +1 -1
  76. package/dist/{trace-TYCLMCkY.mjs → trace-CLK-NPkb.mjs} +1 -1
  77. package/dist/{trace-TYCLMCkY.mjs.map → trace-CLK-NPkb.mjs.map} +1 -1
  78. package/dist/{utils-BAxjW3j8.mjs → utils-CqqgB0dH.mjs} +1 -1
  79. package/dist/{utils-BAxjW3j8.mjs.map → utils-CqqgB0dH.mjs.map} +1 -1
  80. package/dist/{vault-indexer-vNnL3Azb.mjs → vault-indexer-Dn_BccVK.mjs} +2 -2
  81. package/dist/{vault-indexer-vNnL3Azb.mjs.map → vault-indexer-Dn_BccVK.mjs.map} +1 -1
  82. package/dist/{work-queue-worker-BxsBtz6j.mjs → work-queue-worker-DHOIVVQ7.mjs} +5 -5
  83. package/dist/{work-queue-worker-BxsBtz6j.mjs.map → work-queue-worker-DHOIVVQ7.mjs.map} +1 -1
  84. package/dist/{zettelkasten-B-94vghH.mjs → zettelkasten-NwBD1NsT.mjs} +4 -4
  85. package/dist/{zettelkasten-B-94vghH.mjs.map → zettelkasten-NwBD1NsT.mjs.map} +1 -1
  86. package/docs/commands/README.md +151 -0
  87. package/docs/commands/_examples/kg.md +13 -0
  88. package/docs/commands/_examples/memory.md +13 -0
  89. package/docs/commands/_examples/observation.md +13 -0
  90. package/docs/commands/_examples/projects.md +16 -0
  91. package/docs/commands/_examples/skill.md +13 -0
  92. package/docs/commands/_examples/zettel.md +13 -0
  93. package/docs/commands/backup.md +24 -0
  94. package/docs/commands/clear-names.md +27 -0
  95. package/docs/commands/daemon.md +78 -0
  96. package/docs/commands/db.md +74 -0
  97. package/docs/commands/end.md +24 -0
  98. package/docs/commands/help.md +30 -0
  99. package/docs/commands/kg.md +89 -0
  100. package/docs/commands/mcp.md +35 -0
  101. package/docs/commands/memory.md +125 -0
  102. package/docs/commands/notify.md +83 -0
  103. package/docs/commands/observation.md +80 -0
  104. package/docs/commands/obsidian.md +60 -0
  105. package/docs/commands/pause.md +35 -0
  106. package/docs/commands/project.md +345 -0
  107. package/docs/commands/projects.md +364 -0
  108. package/docs/commands/registry.md +68 -0
  109. package/docs/commands/restore.md +31 -0
  110. package/docs/commands/sessions.md +25 -0
  111. package/docs/commands/setup.md +20 -0
  112. package/docs/commands/shell-init.md +18 -0
  113. package/docs/commands/skill.md +54 -0
  114. package/docs/commands/topic.md +46 -0
  115. package/docs/commands/update.md +18 -0
  116. package/docs/commands/zettel.md +151 -0
  117. package/docs/mcp-skill-guide.md +292 -0
  118. package/package.json +4 -2
  119. package/scripts/build-docs.mjs +371 -0
  120. package/src/hooks/ts/post-tool-use/observe.ts +43 -0
  121. package/dist/daemon-CSKj8Xm9.mjs.map +0 -1
  122. package/dist/db-BtuN768f.mjs +0 -206
  123. package/dist/db-BtuN768f.mjs.map +0 -1
  124. package/dist/indexer-D7MvSQPY.mjs +0 -1
  125. package/dist/kg-extraction-iR1BKKWK.mjs.map +0 -1
  126. package/dist/sync-CmBKOL3K.mjs.map +0 -1
package/README.md CHANGED
@@ -1,4 +1,4 @@
1
- # PAI Knowledge OS — v0.10.0
1
+ # PAI Knowledge OS
2
2
 
3
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. PAI fixes this.
4
4
 
@@ -54,6 +54,34 @@ That's it. Claude Code now has persistent memory across all sessions.
54
54
 
55
55
  ---
56
56
 
57
+ ## Command Reference
58
+
59
+ Every `pai` command area has its own man page, **generated from the live CLI** so it never drifts from the actual commands. Read them three ways:
60
+
61
+ ```bash
62
+ pai help # list all command areas (the index)
63
+ pai help memory # the full man page for one area, in your terminal
64
+ pai memory --help # terse Commander help for any command
65
+ ```
66
+
67
+ Browse the same pages on GitHub under [`docs/commands/`](docs/commands/README.md). Each page lists every subcommand, its arguments and options, and worked examples. The reference below in this README is the *guided tour*; `docs/commands/` is the *complete reference*.
68
+
69
+ | Area | What it covers |
70
+ |------|----------------|
71
+ | [`pai memory`](docs/commands/memory.md) | Federated search, indexing, embeddings |
72
+ | [`pai projects`](docs/commands/projects.md) | Project registry: add, cd, info, health, rebind |
73
+ | [`pai kg`](docs/commands/kg.md) | Temporal knowledge graph |
74
+ | [`pai zettel`](docs/commands/zettel.md) | Zettelkasten intelligence over your vault |
75
+ | [`pai observation`](docs/commands/observation.md) | Automatic tool-call observation capture |
76
+ | [`pai skill`](docs/commands/skill.md) | Skill telemetry (self-educating skill system) |
77
+ | [`pai obsidian`](docs/commands/obsidian.md) | Obsidian vault sync |
78
+ | [`pai daemon`](docs/commands/daemon.md) | Daemon lifecycle |
79
+ | [`pai notify`](docs/commands/notify.md) | Notification configuration |
80
+ | [`pai backup`](docs/commands/backup.md) · [`pai restore`](docs/commands/restore.md) | Data safety |
81
+ | … | See [the full index](docs/commands/README.md) for all areas |
82
+
83
+ ---
84
+
57
85
  ## Automatic Session Notes — by Topic
58
86
 
59
87
  PAI's headline feature: **every session is automatically documented.** No manual note-taking, no "pause session" commands, no forgetting to save what you did.
@@ -299,34 +327,32 @@ All hooks are TypeScript compiled to `.mjs` modules. They run as separate proces
299
327
 
300
328
  PAI gives you a complete picture of every Claude Code session running on your machine — live tabs in iTerm2, paused snapshots on disk, and everything in between.
301
329
 
302
- ### The Core Idea: Think in Topics
330
+ ### The Core Idea: One Entry Point
303
331
 
304
- The user's mental model is simple: "I did something on a given subject. Where was I working on it? Let me continue."
332
+ Two ways in, both forgiving:
305
333
 
306
- PAI v0.10.0 is built around this. You think in **topics**, not project names or session UUIDs. PAI handles the lookup.
334
+ - **`pai`** (no args) opens the **interactive picker**: type to search across projects *and* sessions, then act on the highlighted row with a single key.
335
+ - **`pai <name>`** — the universal session command when you already know the name. It does the right thing based on session state:
336
+ - **Live session** — switches the iTerm2 tab to front (no new Claude launched)
337
+ - **Resumable session** — probes `claude --resume`, launches with the saved UUID
338
+ - **Transcript/stub** — starts a fresh Claude in the same project directory
339
+ - **No match** — searches `~/.claude/history.jsonl`, shows a candidate picker
307
340
 
308
341
  ```bash
309
- pai mdf # Find every session where you worked on MDF
310
- pai solar panels # Free-text search across your prompt history
311
- pai 0856d40b # Resume by UUID prefix (from pai sessions)
312
- pai # Show all recent sessions (interactive picker)
342
+ pai # Interactive picker search, then go / new / cd / finder / remove
343
+ pai aibroker # Switch to the live AIBroker tab (iTerm comes to front)
344
+ pai youdrill # Resume the youdrill session, or fresh if snapshot is stale
345
+ pai mdf # Free-text search across your prompt history
346
+ pai 0856d40b # Resume by UUID prefix
347
+ pai --list # Static deduped table (the old no-args behaviour)
313
348
  ```
314
349
 
315
- When you type `pai mdf`, PAI checks:
316
- 1. Is "mdf" a named session in the catalog? → launch immediately, no picker
317
- 2. Is "mdf" a UUID prefix? → direct filesystem resume
318
- 3. Otherwise → grep `~/.claude/history.jsonl` for matching prompts, show candidate list, you pick a number
319
-
320
- The picked session resolves to the best resumable snapshot in the same project directory. PAI probes `claude --resume` automatically. If the snapshot is stale, it starts a fresh Claude in the same directory.
321
-
322
- ### Daily Verbs
323
-
324
- The commands you'll use every day:
350
+ ### Daily Commands
325
351
 
326
352
  ```bash
327
- pai # Recent sessions picker (live + disk)
328
- pai <topic> # Find + launch session by topic/keyword/UUID
329
- pai cd <name> # cd to a project directory (no Claude launch)
353
+ pai # Interactive picker (projects + sessions; search then act)
354
+ pai --list # Static deduped listing (one row per name)
355
+ pai <name> # Switch / resume / fresh universal
330
356
  pai pause # Save state checkpoint (write ## Continue to TODO.md)
331
357
  pai pause all # Pause every live Claude session at once
332
358
  pai end # Finalize: save state + mark session note Completed
@@ -339,15 +365,64 @@ And inside Claude Code, the two slash commands that matter:
339
365
  /end → same as /pause, plus marks the session note Completed
340
366
  ```
341
367
 
342
- ### Finding Sessions
368
+ ### The Interactive Picker
369
+
370
+ Run `pai` with no arguments to open a self-contained terminal selector (no `fzf` or other dependency) over a **unified, deduped list of both projects and sessions** — tagged so the two stay distinct. It's the one place to answer "where did I work on X, and take me there."
371
+
372
+ ```
373
+ pai — find a project or session
374
+ search > samba
375
+
376
+ live Chenarlier now …/Raspi/Chenarlier samba setup monster reverse proxy
377
+ project Glidr 2d …/apps/glidr claude pai research
378
+
379
+ ────────────────────────────────────────
380
+ Chenarlier /Users/…/Raspi/Chenarlier
381
+ recent notes:
382
+ 10 - Samba Setup/01 - Samba Server Setup.md 1mo
383
+ 00 - Monster/00 - Monster.md 3mo
384
+ ────────────────────────────────────────
385
+ g go to tab · n new · c cd · f finder · d remove · s search · ↑↓ move · q quit
386
+ ```
387
+
388
+ **Two modes.** You start in *command mode* (single keys are actions). Press `s` (or `/`) to enter *search mode* (type a topic — it filters by name, path, **and folded-in note file/folder names**, so `samba` finds a project literally named "Chenarlier"); `Enter` or `esc` returns to command mode.
389
+
390
+ **Command keys** act immediately on the highlighted row:
343
391
 
344
- `pai` (no args) shows two sections:
392
+ | Key | Action |
393
+ |-----|--------|
394
+ | `g` | **Go to** the running iTerm2 tab (for live rows) |
395
+ | `n` | **New** Claude session in that directory (current terminal) |
396
+ | `c` | **cd** into the folder only — no Claude (your shell stays there) |
397
+ | `f` | Open the folder in **Finder** / Explorer / `xdg-open` (keeps the picker open) |
398
+ | `d` | **Remove** from PAI's list — archives the project (reversible, files untouched); asks `y/N` first |
399
+ | `s` `/` | Enter **search** mode |
400
+ | `↑↓` `j` `k` | Move the highlight |
401
+ | `q` `esc` | Quit |
345
402
 
346
- **Live Sessions** pulled from AIBroker in real time. One row per active iTerm2 pane running Claude Code. Columns: `#`, `id`, `name`, `at prompt`.
403
+ `Enter` on a row takes the smart default: a live row go to its tab, otherwise new session.
347
404
 
348
- **Recent Sessions**disk scan of `~/.claude/projects/`, sorted by last-modified.
405
+ The `c` (cd) action needs PAI's shell integration to change your shell's directory see [Finding the Claude Binary](#finding-the-claude-binary) / `pai shell-init`. On a non-interactive terminal (piped output), `pai` falls back to the static listing automatically.
349
406
 
350
- `pai <topic>` searches your prompt history in `~/.claude/history.jsonl`, groups results by session, and shows a table:
407
+ ### Static Listing
408
+
409
+ `pai --list` shows a single deduped table — one row per session name, regardless of how many snapshots exist on disk:
410
+
411
+ ```
412
+ Sessions:
413
+
414
+ # name status age project last prompt
415
+ -- ---------- ---------- -------- ---------------------------- --------------------------
416
+ 1 AIBroker live now — —
417
+ 2 PAI resumable 2m ago /…dev/ai/PAI "refactor session listing…"
418
+ 3 MDF transcript 3d ago /…MDF/Infrastruktur/Webseiten "ok so we recently had…"
419
+ ```
420
+
421
+ Status values: `live` (active iTerm tab), `resumable` (clean snapshot on disk), `transcript` (history available, not resumable), `stub` (empty or minimal).
422
+
423
+ ### Finding Sessions by Topic
424
+
425
+ `pai <topic>` first checks session names, then falls back to searching your prompt history:
351
426
 
352
427
  ```
353
428
  Sessions matching "mdf":
@@ -360,16 +435,14 @@ Sessions matching "mdf":
360
435
  Enter # to launch (1-2), or press Enter to cancel:
361
436
  ```
362
437
 
363
- Type the number and press Enter. PAI launches Claude, resumes if possible, starts fresh if not.
364
-
365
- Use `pai <topic> --auto` (or `-y`) to auto-pick #1 without the prompt. Use `pai <topic> 2` to pick #2 directly.
438
+ Use `pai <topic> --auto` (or `-y`) to auto-pick #1. Use `pai <topic> 2` to pick directly.
366
439
 
367
440
  ### Power User Access
368
441
 
369
- The full session management namespace is still available for power users:
442
+ The full session management namespace is still available:
370
443
 
371
444
  ```bash
372
- pai sessions # Live + disk listing (same as bare pai, with more columns)
445
+ pai sessions # Live + disk listing (with more columns)
373
446
  pai sessions --all # Include unnamed orphan sessions
374
447
  pai sessions --all-tabs # Include shell tabs in the live section
375
448
  pai sessions goto <name> # Named-session resolver (same as pai <name>)
@@ -950,6 +1023,9 @@ External URLs (`https://`, `mailto:`, etc.) are excluded — only relative paths
950
1023
  | v0.9.17 | Switch live-session listing to `sessions` IPC (metadata-only, faster); `--all-tabs` flag |
951
1024
  | v0.9.18 | `pai projects`: moved-project auto-detect, rebind command, active-only default listing |
952
1025
  | v0.10.0 | Topic-first redesign: `pai <topic>` universal resolver, history search, sticky tab titles |
1026
+ | v0.10.1 | `pai sessions clear-names` recovery command |
1027
+ | v0.11.0 | Deduped session listing + universal `pai <name>` (switch / resume / fresh) |
1028
+ | v0.12.0 | Interactive picker: `pai` opens a modal search-and-act selector over projects + sessions (g go · n new · c cd · f finder · d remove); note-keyword filtering; quoted exit-dir path |
953
1029
 
954
1030
  ---
955
1031
 
@@ -1,5 +1,5 @@
1
- import { r as readPaiMarker } from "./pai-marker-CXQPX2P6.mjs";
2
- import { t as detectProject } from "./detect-CdaA48EI.mjs";
1
+ import { r as readPaiMarker } from "./pai-marker-HVBwwBW-.mjs";
2
+ import { t as detectProject } from "./detect-KjycLtXM.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-Q_lVXtj-.mjs").then((n) => n.n);
29
+ const { detectTopicShift } = await import("./detector-ZiOhHszd.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-D54Kv7yI.mjs.map
86
+ //# sourceMappingURL=auto-route-CLYcToZ5.mjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"auto-route-D54Kv7yI.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"}
1
+ {"version":3,"file":"auto-route-CLYcToZ5.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"}