@tekmidian/pai 0.5.7 → 0.6.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.
Files changed (138) hide show
  1. package/ARCHITECTURE.md +198 -47
  2. package/FEATURE.md +2 -2
  3. package/README.md +87 -1
  4. package/dist/{auto-route-BG6I_4B1.mjs → auto-route-C-DrW6BL.mjs} +3 -3
  5. package/dist/{auto-route-BG6I_4B1.mjs.map → auto-route-C-DrW6BL.mjs.map} +1 -1
  6. package/dist/cli/index.mjs +1482 -1628
  7. package/dist/cli/index.mjs.map +1 -1
  8. package/dist/clusters-JIDQW65f.mjs +201 -0
  9. package/dist/clusters-JIDQW65f.mjs.map +1 -0
  10. package/dist/{config-Cf92lGX_.mjs → config-BuhHWyOK.mjs} +21 -6
  11. package/dist/config-BuhHWyOK.mjs.map +1 -0
  12. package/dist/daemon/index.mjs +11 -8
  13. package/dist/daemon/index.mjs.map +1 -1
  14. package/dist/{daemon-2ND5WO2j.mjs → daemon-D3hYb5_C.mjs} +669 -218
  15. package/dist/daemon-D3hYb5_C.mjs.map +1 -0
  16. package/dist/daemon-mcp/index.mjs +4597 -4
  17. package/dist/daemon-mcp/index.mjs.map +1 -1
  18. package/dist/db-DdUperSl.mjs +110 -0
  19. package/dist/db-DdUperSl.mjs.map +1 -0
  20. package/dist/{detect-BU3Nx_2L.mjs → detect-CdaA48EI.mjs} +1 -1
  21. package/dist/{detect-BU3Nx_2L.mjs.map → detect-CdaA48EI.mjs.map} +1 -1
  22. package/dist/{detector-Bp-2SM3x.mjs → detector-jGBuYQJM.mjs} +2 -2
  23. package/dist/{detector-Bp-2SM3x.mjs.map → detector-jGBuYQJM.mjs.map} +1 -1
  24. package/dist/{factory-Bzcy70G9.mjs → factory-Ygqe_bVZ.mjs} +7 -5
  25. package/dist/{factory-Bzcy70G9.mjs.map → factory-Ygqe_bVZ.mjs.map} +1 -1
  26. package/dist/helpers-BEST-4Gx.mjs +420 -0
  27. package/dist/helpers-BEST-4Gx.mjs.map +1 -0
  28. package/dist/hooks/capture-all-events.mjs +2 -2
  29. package/dist/hooks/capture-all-events.mjs.map +3 -3
  30. package/dist/hooks/capture-session-summary.mjs +38 -0
  31. package/dist/hooks/capture-session-summary.mjs.map +3 -3
  32. package/dist/hooks/cleanup-session-files.mjs +6 -12
  33. package/dist/hooks/cleanup-session-files.mjs.map +4 -4
  34. package/dist/hooks/context-compression-hook.mjs +93 -104
  35. package/dist/hooks/context-compression-hook.mjs.map +4 -4
  36. package/dist/hooks/initialize-session.mjs +14 -11
  37. package/dist/hooks/initialize-session.mjs.map +4 -4
  38. package/dist/hooks/inject-observations.mjs +220 -0
  39. package/dist/hooks/inject-observations.mjs.map +7 -0
  40. package/dist/hooks/load-core-context.mjs +2 -2
  41. package/dist/hooks/load-core-context.mjs.map +3 -3
  42. package/dist/hooks/load-project-context.mjs +90 -91
  43. package/dist/hooks/load-project-context.mjs.map +4 -4
  44. package/dist/hooks/observe.mjs +354 -0
  45. package/dist/hooks/observe.mjs.map +7 -0
  46. package/dist/hooks/stop-hook.mjs +94 -107
  47. package/dist/hooks/stop-hook.mjs.map +4 -4
  48. package/dist/hooks/sync-todo-to-md.mjs +31 -33
  49. package/dist/hooks/sync-todo-to-md.mjs.map +4 -4
  50. package/dist/index.d.mts +30 -7
  51. package/dist/index.d.mts.map +1 -1
  52. package/dist/index.mjs +5 -8
  53. package/dist/indexer-D53l5d1U.mjs +1 -0
  54. package/dist/{indexer-backend-CIMXedqk.mjs → indexer-backend-jcJFsmB4.mjs} +37 -127
  55. package/dist/indexer-backend-jcJFsmB4.mjs.map +1 -0
  56. package/dist/{ipc-client-Bjg_a1dc.mjs → ipc-client-CoyUHPod.mjs} +2 -7
  57. package/dist/{ipc-client-Bjg_a1dc.mjs.map → ipc-client-CoyUHPod.mjs.map} +1 -1
  58. package/dist/latent-ideas-bTJo6Omd.mjs +191 -0
  59. package/dist/latent-ideas-bTJo6Omd.mjs.map +1 -0
  60. package/dist/neighborhood-BYYbEkUJ.mjs +135 -0
  61. package/dist/neighborhood-BYYbEkUJ.mjs.map +1 -0
  62. package/dist/note-context-BK24bX8Y.mjs +126 -0
  63. package/dist/note-context-BK24bX8Y.mjs.map +1 -0
  64. package/dist/postgres-CKf-EDtS.mjs +846 -0
  65. package/dist/postgres-CKf-EDtS.mjs.map +1 -0
  66. package/dist/{reranker-D7bRAHi6.mjs → reranker-CMNZcfVx.mjs} +1 -1
  67. package/dist/{reranker-D7bRAHi6.mjs.map → reranker-CMNZcfVx.mjs.map} +1 -1
  68. package/dist/{search-_oHfguA5.mjs → search-DC1qhkKn.mjs} +2 -58
  69. package/dist/search-DC1qhkKn.mjs.map +1 -0
  70. package/dist/{sqlite-WWBq7_2C.mjs → sqlite-l-s9xPjY.mjs} +160 -3
  71. package/dist/sqlite-l-s9xPjY.mjs.map +1 -0
  72. package/dist/state-C6_vqz7w.mjs +102 -0
  73. package/dist/state-C6_vqz7w.mjs.map +1 -0
  74. package/dist/stop-words-BaMEGVeY.mjs +326 -0
  75. package/dist/stop-words-BaMEGVeY.mjs.map +1 -0
  76. package/dist/{indexer-CMPOiY1r.mjs → sync-BOsnEj2-.mjs} +14 -216
  77. package/dist/sync-BOsnEj2-.mjs.map +1 -0
  78. package/dist/themes-BvYF0W8T.mjs +148 -0
  79. package/dist/themes-BvYF0W8T.mjs.map +1 -0
  80. package/dist/{tools-DV_lsiCc.mjs → tools-DcaJlYDN.mjs} +162 -273
  81. package/dist/tools-DcaJlYDN.mjs.map +1 -0
  82. package/dist/trace-CRx9lPuc.mjs +137 -0
  83. package/dist/trace-CRx9lPuc.mjs.map +1 -0
  84. package/dist/{vault-indexer-k-kUlaZ-.mjs → vault-indexer-Bi2cRmn7.mjs} +134 -132
  85. package/dist/vault-indexer-Bi2cRmn7.mjs.map +1 -0
  86. package/dist/zettelkasten-cdajbnPr.mjs +708 -0
  87. package/dist/zettelkasten-cdajbnPr.mjs.map +1 -0
  88. package/package.json +1 -2
  89. package/src/hooks/ts/lib/project-utils/index.ts +50 -0
  90. package/src/hooks/ts/lib/project-utils/notify.ts +75 -0
  91. package/src/hooks/ts/lib/project-utils/paths.ts +218 -0
  92. package/src/hooks/ts/lib/project-utils/session-notes.ts +363 -0
  93. package/src/hooks/ts/lib/project-utils/todo.ts +178 -0
  94. package/src/hooks/ts/lib/project-utils/tokens.ts +39 -0
  95. package/src/hooks/ts/lib/project-utils.ts +40 -1018
  96. package/src/hooks/ts/post-tool-use/observe.ts +327 -0
  97. package/src/hooks/ts/session-end/capture-session-summary.ts +41 -0
  98. package/src/hooks/ts/session-start/inject-observations.ts +254 -0
  99. package/dist/chunker-CbnBe0s0.mjs +0 -191
  100. package/dist/chunker-CbnBe0s0.mjs.map +0 -1
  101. package/dist/config-Cf92lGX_.mjs.map +0 -1
  102. package/dist/daemon-2ND5WO2j.mjs.map +0 -1
  103. package/dist/db-Dp8VXIMR.mjs +0 -212
  104. package/dist/db-Dp8VXIMR.mjs.map +0 -1
  105. package/dist/indexer-CMPOiY1r.mjs.map +0 -1
  106. package/dist/indexer-backend-CIMXedqk.mjs.map +0 -1
  107. package/dist/mcp/index.d.mts +0 -1
  108. package/dist/mcp/index.mjs +0 -500
  109. package/dist/mcp/index.mjs.map +0 -1
  110. package/dist/postgres-FXrHDPcE.mjs +0 -358
  111. package/dist/postgres-FXrHDPcE.mjs.map +0 -1
  112. package/dist/schemas-BFIgGntb.mjs +0 -3405
  113. package/dist/schemas-BFIgGntb.mjs.map +0 -1
  114. package/dist/search-_oHfguA5.mjs.map +0 -1
  115. package/dist/sqlite-WWBq7_2C.mjs.map +0 -1
  116. package/dist/tools-DV_lsiCc.mjs.map +0 -1
  117. package/dist/vault-indexer-k-kUlaZ-.mjs.map +0 -1
  118. package/dist/zettelkasten-e-a4rW_6.mjs +0 -901
  119. package/dist/zettelkasten-e-a4rW_6.mjs.map +0 -1
  120. package/templates/README.md +0 -181
  121. package/templates/skills/CORE/Aesthetic.md +0 -333
  122. package/templates/skills/CORE/CONSTITUTION.md +0 -1502
  123. package/templates/skills/CORE/HistorySystem.md +0 -427
  124. package/templates/skills/CORE/HookSystem.md +0 -1082
  125. package/templates/skills/CORE/Prompting.md +0 -509
  126. package/templates/skills/CORE/ProsodyAgentTemplate.md +0 -53
  127. package/templates/skills/CORE/ProsodyGuide.md +0 -416
  128. package/templates/skills/CORE/SKILL.md +0 -741
  129. package/templates/skills/CORE/SkillSystem.md +0 -213
  130. package/templates/skills/CORE/TerminalTabs.md +0 -119
  131. package/templates/skills/CORE/VOICE.md +0 -106
  132. package/templates/skills/createskill-skill.template.md +0 -78
  133. package/templates/skills/history-system.template.md +0 -371
  134. package/templates/skills/hook-system.template.md +0 -913
  135. package/templates/skills/sessions-skill.template.md +0 -102
  136. package/templates/skills/skill-system.template.md +0 -214
  137. package/templates/skills/terminal-tabs.template.md +0 -120
  138. package/templates/templates.md +0 -20
package/ARCHITECTURE.md CHANGED
@@ -21,6 +21,8 @@ Claude Code Session
21
21
  │ ↓
22
22
  │ PostgreSQL + pgvector
23
23
  │ (chunks, embeddings, files, FTS)
24
+ │ ├── Observation Store (PostgreSQL)
25
+ │ │ classify → store → query → inject
24
26
 
25
27
  ├── Registry (SQLite)
26
28
  │ ~/.pai/registry.db
@@ -28,7 +30,8 @@ Claude Code Session
28
30
 
29
31
  └── CLI (pai)
30
32
  project, session, registry, memory,
31
- daemon, obsidian, zettel, backup, restore, setup
33
+ daemon, obsidian, zettel, observation,
34
+ backup, restore, setup
32
35
  ```
33
36
 
34
37
  ### Key Components
@@ -92,15 +95,22 @@ docker run -d \
92
95
  pai setup
93
96
  ```
94
97
 
95
- The interactive wizard walks through seven steps:
98
+ The interactive wizard walks through 14 steps:
96
99
 
97
- 1. PostgreSQL connection (host, port, credentials)
98
- 2. Embedding model selection (Snowflake Arctic recommended)
99
- 3. Indexing interval (default: 5 minutes)
100
+ 1. Welcome and version check
101
+ 2. Storage backend selection (SQLite or PostgreSQL)
102
+ 3. Embedding model configuration
100
103
  4. CLAUDE.md template installation
101
- 5. Projects root directory
102
- 6. Obsidian vault location (optional)
103
- 7. MCP server registration with Claude Code
104
+ 5. PAI skill installation
105
+ 6. Steering rules installation
106
+ 7. Hook system deployment
107
+ 8. TypeScript hook compilation
108
+ 9. Claude Code settings configuration
109
+ 10. Daemon installation
110
+ 11. MCP server registration
111
+ 12. Directory creation
112
+ 13. Initial indexing
113
+ 14. Verification
104
114
 
105
115
  ### 4. Install the Daemon
106
116
 
@@ -140,7 +150,7 @@ If both commands return healthy output, PAI is running. Open a new Claude Code s
140
150
 
141
151
  ## MCP Server
142
152
 
143
- PAI exposes 7 tools to Claude Code via a daemon-backed MCP shim. The shim speaks stdio (what Claude Code expects) and proxies each request to the background daemon over NDJSON on a Unix socket.
153
+ PAI exposes 9 tools, 18 on-demand prompts (skills), and 11 reference resources to Claude Code via a daemon-backed MCP shim. The shim speaks stdio (what Claude Code expects) and proxies each request to the background daemon over NDJSON on a Unix socket.
144
154
 
145
155
  ```
146
156
  Claude Code (stdio)
@@ -161,12 +171,8 @@ Claude Code (stdio)
161
171
  | `session_list` | List session notes, optionally filtered by project |
162
172
  | `registry_search` | Search project metadata (names, paths, tags) |
163
173
  | `project_detect` | Identify which project a given path belongs to |
164
- | `zettel_explore` | BFS traversal of wikilink graph from a seed note |
165
- | `zettel_surprise` | Find semantically distant but graph-close notes |
166
- | `zettel_converse` | Hybrid search with graph expansion and cross-domain connections |
167
- | `zettel_themes` | Cluster vault notes into thematic groups by embedding similarity |
168
- | `zettel_health` | Audit vault for broken links, orphans, and isolated clusters |
169
- | `zettel_suggest` | Suggest link targets weighted by semantics, tags, and graph neighborhood |
174
+ | `project_health` | Audit all registered paths for moved or deleted directories |
175
+ | `project_todo` | Read a project's TODO.md and continuation prompt |
170
176
 
171
177
  ### Tool Reference
172
178
 
@@ -184,17 +190,52 @@ Claude Code (stdio)
184
190
 
185
191
  **`project_detect(path?)`** — Given a filesystem path (defaults to CWD), returns the matching project.
186
192
 
187
- **`zettel_explore(note, depth?, direction?)`** — BFS walk from a seed note across `vault_links`. Returns a subgraph of neighboring notes with each edge classified as `sequential` or `associative`. `direction`: `outbound` (default), `inbound`, or `both`.
193
+ **`project_health(category?)`** — Audits all registered projects to find moved or deleted directories. Categorizes each as `active` (path exists), `stale` (path missing but candidate found nearby), or `dead` (path missing, no candidate). Also reports TODO.md presence and continuation prompts.
188
194
 
189
- **`zettel_surprise(note, limit?)`** — Returns notes that are semantically dissimilar to `note` but reachable within a short graph distance. Scored as `cosine_similarity × log2(graph_distance + 1)`. Useful for lateral discovery.
195
+ **`project_todo(project?)`** — Reads a project's TODO.md without needing the exact file path. Searches Notes/TODO.md, .claude/Notes/TODO.md, tasks/todo.md, and project-root TODO.md in order. Surfaces any `## Continue` section at the top for quick context recovery.
190
196
 
191
- **`zettel_converse(query, limit?)`** — Runs a hybrid memory search, expands the result set via graph neighborhood, then surfaces cross-domain connections — notes from unrelated clusters that are semantically close to the query.
197
+ ### On-Demand Prompts (Skills)
192
198
 
193
- **`zettel_themes(min_cluster_size?)`** Clusters all vault embeddings using agglomerative single-linkage clustering. Returns thematic groups with representative note titles and cluster size.
199
+ The MCP server registers 18 prompts that Claude can invoke as on-demand skills. Each prompt provides a focused workflow with instructions, examples, and constraints — loaded only when needed to conserve context.
194
200
 
195
- **`zettel_health()`** Full structural audit of the vault. Reports broken links (target not in `vault_files`), orphaned notes (no inbound or outbound edges), notes missing embeddings, and isolated clusters detected via union-find.
196
-
197
- **`zettel_suggest(note, limit?)`** Ranks candidate link targets for a given note. Score is a weighted sum: semantic embedding similarity (0.5), shared tags (0.2), graph neighborhood overlap with existing links (0.3).
201
+ | Prompt | Purpose |
202
+ |--------|---------|
203
+ | `art` | Visual art direction and creative guidance |
204
+ | `createskill` | Scaffold new PAI skills |
205
+ | `journal` | Structured journaling workflow |
206
+ | `name` | Session and project naming conventions |
207
+ | `observability` | Observation system usage and querying |
208
+ | `plan` | Forward-looking planning from TODOs and recent activity |
209
+ | `research` | Structured research methodology |
210
+ | `review` | Retrospective review of work over a time period |
211
+ | `route` | Session note routing across projects |
212
+ | `search-history` | Search history analysis and patterns |
213
+ | `sessions` | Session lifecycle management |
214
+ | `share` | Generate social media posts from recent work |
215
+ | `story-explanation` | Narrative explanations of technical concepts |
216
+ | `vault-connect` | Suggest and create vault connections |
217
+ | `vault-context` | Use vault as conversational context |
218
+ | `vault-emerge` | Detect emerging themes in the vault |
219
+ | `vault-orphans` | Find and fix orphaned vault notes |
220
+ | `vault-trace` | Trace idea lineage through vault links |
221
+
222
+ ### Reference Resources
223
+
224
+ 11 resources available via `pai://` URIs. Claude reads these on demand for reference documentation.
225
+
226
+ | URI | Content |
227
+ |-----|---------|
228
+ | `pai://aesthetic` | Visual and output style guidelines |
229
+ | `pai://constitution` | Core philosophy and principles |
230
+ | `pai://history-system` | Search history tracking system |
231
+ | `pai://hook-system` | Hook architecture and development guide |
232
+ | `pai://mcp-dev-guide` | MCP server development patterns |
233
+ | `pai://prompting` | Prompt engineering best practices |
234
+ | `pai://prosody-agent-template` | Voice agent template |
235
+ | `pai://prosody-guide` | Voice and prosody guidelines |
236
+ | `pai://skill-system` | Skill authoring reference |
237
+ | `pai://terminal-tabs` | Terminal tab management |
238
+ | `pai://voice` | Voice configuration reference |
198
239
 
199
240
  ### Installation
200
241
 
@@ -389,12 +430,27 @@ pai zettel health
389
430
  pai zettel suggest "My Seed Note" --limit 5
390
431
  ```
391
432
 
433
+ ### Observation Management
434
+
435
+ | Subcommand | Description |
436
+ |------------|-------------|
437
+ | `observation list` | List recent observations with optional filters |
438
+ | `observation search <query>` | Search observations by title or narrative text |
439
+ | `observation stats` | Show totals, breakdowns by type and project |
440
+
441
+ ```bash
442
+ pai observation list --type decision --limit 10
443
+ pai observation list --project my-app
444
+ pai observation search "database migration"
445
+ pai observation stats
446
+ ```
447
+
392
448
  ### Other Commands
393
449
 
394
450
  ```bash
395
451
  pai backup # Backup registry, config, and Postgres
396
452
  pai restore <path> # Restore from backup (--no-postgres to skip DB)
397
- pai setup # Interactive 7-step setup wizard
453
+ pai setup # Interactive 14-step setup wizard
398
454
  pai search "query" # Quick full-text search shortcut
399
455
  ```
400
456
 
@@ -531,6 +587,8 @@ Claude Code Event
531
587
  | `post-compact-inject.mjs` | SessionStart (compact) | Reads saved state and injects into post-compaction context |
532
588
  | `security-validator.mjs` | PreToolUse (Bash) | Validates shell commands against security rules |
533
589
  | `capture-all-events.mjs` | All events | Observability — logs every hook event to session timeline |
590
+ | `observe.mjs` | PostToolUse | Classifies tool calls into typed observations (decision/bugfix/feature/refactor/discovery/change) |
591
+ | `inject-observations.mjs` | SessionStart | Injects recent observation context (compact index + timeline) |
534
592
  | `context-compression-hook.mjs` | PreCompact | Extracts session state, saves checkpoint, writes temp file for relay |
535
593
  | `capture-tool-output.mjs` | PostToolUse | Records tool inputs/outputs for observability dashboard |
536
594
  | `update-tab-on-action.mjs` | PostToolUse | Updates terminal tab title based on current activity |
@@ -704,6 +762,47 @@ These tables are populated by `src/memory/vault-indexer.ts` and queried by all s
704
762
  | `detail` | TEXT | Human-readable description |
705
763
  | `checked_at` | BIGINT | Timestamp of the audit run |
706
764
 
765
+ ### Observation Tables (PostgreSQL)
766
+
767
+ These tables are populated by the PostToolUse hook classifier and queried by the CLI and MCP tools.
768
+
769
+ **`pai_observations`** — Classified tool call events:
770
+
771
+ | Column | Type | Description |
772
+ |--------|------|-------------|
773
+ | `id` | SERIAL | Primary key |
774
+ | `session_id` | TEXT | Claude Code session identifier |
775
+ | `project_id` | INTEGER | Owning project (nullable) |
776
+ | `project_slug` | TEXT | Project slug for display |
777
+ | `type` | TEXT | Classification: decision, bugfix, feature, refactor, discovery, change |
778
+ | `title` | TEXT | Human-readable observation title |
779
+ | `narrative` | TEXT | Extended description (nullable) |
780
+ | `tool_name` | TEXT | Claude Code tool that triggered the observation |
781
+ | `tool_input_summary` | TEXT | Abbreviated tool input |
782
+ | `files_read` | JSONB | Array of file paths read |
783
+ | `files_modified` | JSONB | Array of file paths modified |
784
+ | `concepts` | JSONB | Extracted concept tags |
785
+ | `content_hash` | TEXT | SHA-256 hash for 30-second deduplication window |
786
+ | `created_at` | TIMESTAMPTZ | Observation timestamp |
787
+
788
+ **`pai_session_summaries`** — Structured end-of-session summaries:
789
+
790
+ | Column | Type | Description |
791
+ |--------|------|-------------|
792
+ | `id` | SERIAL | Primary key |
793
+ | `session_id` | TEXT | Claude Code session identifier (unique) |
794
+ | `project_id` | INTEGER | Owning project (nullable) |
795
+ | `project_slug` | TEXT | Project slug for display |
796
+ | `request` | TEXT | What was requested |
797
+ | `investigated` | TEXT | What was investigated |
798
+ | `learned` | TEXT | What was learned |
799
+ | `completed` | TEXT | What was completed |
800
+ | `next_steps` | TEXT | Recommended next steps |
801
+ | `observation_count` | INTEGER | Number of observations in the session |
802
+ | `created_at` | TIMESTAMPTZ | Summary timestamp |
803
+
804
+ **Indexes:** B-tree on project_id, session_id, type, created_at DESC, content_hash.
805
+
707
806
  **Content Tiers:**
708
807
 
709
808
  | Tier | Description | Example |
@@ -752,37 +851,89 @@ bun run lint # tsc --noEmit
752
851
  | Output | Purpose |
753
852
  |--------|---------|
754
853
  | `dist/cli/index.mjs` | `pai` CLI |
755
- | `dist/mcp/index.mjs` | Direct MCP server (legacy) |
756
854
  | `dist/daemon/index.mjs` | Daemon server |
757
855
  | `dist/daemon-mcp/index.mjs` | MCP shim (stdio → daemon socket) |
856
+ | `dist/hooks/*.mjs` | Compiled lifecycle hooks |
758
857
 
759
858
  ### Source Structure
760
859
 
761
860
  ```
762
861
  src/
763
- ├── cli/commands/ # CLI command implementations
764
- └── zettel.ts # `pai zettel` with 6 subcommands
765
- ├── daemon/ # Daemon server and index scheduler
766
- ├── daemon-mcp/ # MCP shim (stdio → daemon socket)
767
- ├── federation/ # Federation schema definitions
768
- ├── hooks/ # Lifecycle hooks (pre-compact, session-stop)
769
- ├── mcp/ # Direct MCP server (legacy)
770
- ├── memory/ # Indexer, chunker, embeddings, search, reranker
771
- │ ├── reranker.ts # Cross-encoder reranking (Xenova/ms-marco-MiniLM-L-6-v2)
772
- └── vault-indexer.ts # Obsidian vault indexing into v3 vault tables
773
- ├── obsidian/ # Obsidian vault bridge
774
- └── vault-fixer.ts # Repairs broken wikilinks and orphaned entries
775
- ├── registry/ # Registry migrations and queries
776
- ├── session/ # Session slug generator
777
- ├── storage/ # Storage backend interface (SQLite/Postgres)
778
- └── zettelkasten/ # Luhmann-inspired graph + semantic operations
779
- ├── explore.ts # BFS traversal classifying sequential/associative edges
780
- ├── surprise.ts # Serendipitous bridge discovery via cosine × graph distance
781
- ├── converse.ts # Hybrid search → graph expansion → cross-domain connections
782
- ├── themes.ts # Agglomerative embedding clustering for thematic groups
783
- ├── health.ts # SQL-driven vault audit with union-find cluster detection
784
- ├── suggest.ts # Weighted link suggestions (semantic + tags + graph)
785
- └── index.ts # Barrel export for all zettelkasten operations
862
+ ├── cli/
863
+ ├── commands/ # CLI command modules
864
+ │ │ ├── backup.ts
865
+ │ │ ├── daemon.ts
866
+ │ │ ├── memory.ts
867
+ │ │ ├── observation.ts
868
+ │ │ ├── obsidian.ts
869
+ │ │ ├── project.ts
870
+ ├── registry.ts
871
+ │ ├── session.ts
872
+ │ │ ├── setup/ # 14-step interactive wizard
873
+ │ │ ├── steps/ # 01-welcome through 15-verify
874
+ │ │ │ └── index.ts
875
+ │ │ └── zettel.ts
876
+ │ └── index.ts # CLI entry point
877
+ ├── daemon/
878
+ ├── daemon/ # Daemon server internals
879
+ │ │ ├── dispatcher.ts # Tool dispatch (zettel, observation, memory)
880
+ │ │ ├── handler.ts # NDJSON request handler
881
+ │ │ └── server.ts # Socket server
882
+ ├── indexer/ # Background index scheduler
883
+ ├── config.ts # Runtime configuration
884
+ └── index.ts # Daemon entry point
885
+ ├── daemon-mcp/
886
+ │ ├── instructions.ts # MCP server instructions (~1.5KB routing table)
887
+ │ ├── prompts/ # 18 on-demand skill prompts
888
+ │ ├── resources/ # 11 reference resources (pai:// URIs)
889
+ │ └── index.ts # MCP shim entry point (stdio → socket)
890
+ ├── hooks/
891
+ │ └── ts/ # TypeScript hook sources by event
892
+ │ ├── PreCompact/
893
+ │ ├── PreToolUse/
894
+ │ ├── PostToolUse/
895
+ │ ├── SessionStart/
896
+ │ ├── Stop/
897
+ │ └── UserPromptSubmit/
898
+ ├── mcp/
899
+ │ └── tools/ # Shared tool implementations
900
+ │ ├── memory.ts
901
+ │ ├── observations.ts
902
+ │ ├── projects.ts
903
+ │ ├── registry.ts
904
+ │ ├── sessions.ts
905
+ │ └── zettel.ts
906
+ ├── memory/
907
+ │ ├── chunker/ # Text chunking strategies
908
+ │ ├── embeddings.ts # Snowflake Arctic embedding generation
909
+ │ ├── indexer.ts # File indexer with change detection
910
+ │ ├── reranker.ts # Cross-encoder reranking (ms-marco-MiniLM)
911
+ │ ├── search.ts # Multi-mode search (keyword/semantic/hybrid)
912
+ │ └── vault-indexer.ts # Obsidian vault indexing
913
+ ├── observations/ # Automatic observation capture
914
+ │ ├── classifier.ts # Rule-based tool call classifier
915
+ │ ├── store.ts # PostgreSQL persistence with deduplication
916
+ │ └── schema.sql # DDL for observation tables
917
+ ├── obsidian/ # Obsidian vault bridge
918
+ │ └── vault-fixer.ts # Repairs broken wikilinks and orphans
919
+ ├── registry/ # SQLite registry queries and migrations
920
+ ├── session/ # Session slug generator
921
+ ├── storage/ # Pluggable storage backend
922
+ │ ├── factory.ts # Backend selection (SQLite/PostgreSQL)
923
+ │ ├── interface.ts # StorageInterface contract
924
+ │ ├── postgres.ts # PostgreSQL + pgvector backend
925
+ │ └── sqlite.ts # SQLite backend
926
+ ├── utils/ # Shared utilities
927
+ │ ├── hash.ts # SHA-256 hashing
928
+ │ └── stop-words.ts # Stop word lists for search
929
+ ├── zettelkasten/ # Luhmann-inspired operations
930
+ │ ├── explore.ts # BFS traversal
931
+ │ ├── surprise.ts # Serendipitous bridge discovery
932
+ │ ├── converse.ts # Hybrid search + graph expansion
933
+ │ ├── themes.ts # Embedding clustering
934
+ │ ├── health.ts # Vault structural audit
935
+ │ └── suggest.ts # Weighted link suggestions
936
+ └── index.ts # Package entry point
786
937
  ```
787
938
 
788
939
  ### Important Notes
package/FEATURE.md CHANGED
@@ -28,13 +28,13 @@ different direction: persistent memory, session continuity, and deep Claude Code
28
28
  | **Persistent session memory** | No | Yes — auto-indexed, 449K+ chunks |
29
29
  | **Session registry** | No | Yes — SQLite, tracks 77+ projects |
30
30
  | **Background daemon** | No | Yes — launchd, IPC via Unix socket |
31
- | **MCP server** | No | Yes — 7+ tools exposed to Claude Code |
31
+ | **MCP server** | No | Yes — 9 tools, 18 prompts, 11 resources exposed to Claude Code |
32
32
  | **Keyword search (BM25)** | No | Yes — GIN full-text index, PostgreSQL |
33
33
  | **Semantic search (vector)** | No | Yes — pgvector HNSW, Snowflake Arctic 768-dim |
34
34
  | **Multi-backend storage** | No | Yes — SQLite (simple) or PostgreSQL (full) |
35
35
  | **Obsidian vault bridge** | No | Yes — symlinks + auto-generated topic pages |
36
36
  | **Project lifecycle** | No | Yes — promote, archive, move, detect from cwd |
37
- | **Setup wizard** | No | Yes — idempotent 7-step interactive wizard |
37
+ | **Setup wizard** | No | Yes — idempotent 14-step interactive wizard |
38
38
  | **Hook system** | No | Yes — pre-compact, session-stop, auto-cleanup |
39
39
  | **Backup / restore** | No | Yes — timestamped pg_dump + registry export |
40
40
  | **Multi-session concurrency** | n/a | Yes — daemon multiplexes Claude sessions |
package/README.md CHANGED
@@ -35,6 +35,27 @@ Install PAI and Claude remembers. Ask it what you were working on. Ask it to fin
35
35
  - "What did I do today?" — daily review across all projects
36
36
  - "Journal this thought" — capture freeform reflections with timestamps
37
37
  - "Plan my week" — forward-looking priorities based on open TODOs and recent activity
38
+ - "What themes are emerging in my work?" — spot patterns across sessions and projects
39
+
40
+ ### Sharing Your Work
41
+
42
+ - "Share on LinkedIn today" — generates a professional post about what you shipped, with real numbers and technical substance
43
+ - "Tweet about the vault migration" — punchy X/Twitter post or thread, with option to post directly
44
+ - "Share on Bluesky this week" — conversational technical post for the Bluesky audience
45
+ - Platform-aware formatting: LinkedIn gets hashtags and narrative, X gets threads and hooks, Bluesky gets conversational tone
46
+
47
+ ### Tracking Your Activity
48
+
49
+ - "What changes did I make to the daemon today?" — automatic observation capture tracks every tool call
50
+ - "Show me all decisions from the last session" — observations are classified: decision, bugfix, feature, refactor, discovery, change
51
+ - "What files did I modify in the PAI project this week?" — searchable timeline of every edit, commit, and search
52
+ - "Show observation stats" — totals, breakdowns by type and project, with visual bar charts
53
+
54
+ ### Continuing Where You Left Off
55
+
56
+ - "Go" — reads your TODO.md continuation prompt and picks up exactly where the last session stopped
57
+ - "What was I working on?" — progressive context injection loads recent observations at session start
58
+ - "Continue the daemon refactor" — session summaries give Claude full context without re-explaining
38
59
 
39
60
  ### Keeping Things Safe
40
61
 
@@ -97,7 +118,7 @@ PAI runs hooks at every stage of a Claude Code session:
97
118
  | **User Prompt** | Cleans up temp files, updates terminal tab titles |
98
119
  | **Pre-Compact** | Saves session state checkpoint, sends notification |
99
120
  | **Post-Compact** | Injects preserved state back into Claude's context |
100
- | **Tool Use** | Captures tool outputs for observability |
121
+ | **Tool Use** | Classifies tool calls into structured observations (decision/bugfix/feature/refactor/discovery/change) |
101
122
  | **Session End** | Summarizes work done, finalizes session note |
102
123
  | **Stop** | Writes work items to session note, sends notification |
103
124
 
@@ -105,6 +126,67 @@ All hooks are TypeScript compiled to `.mjs` modules. They run as separate proces
105
126
 
106
127
  ---
107
128
 
129
+ ## Automatic Observation Capture
130
+
131
+ PAI automatically classifies and stores every significant tool call during your sessions. When you edit a file, run a command, or make a decision, PAI captures it as a structured observation — building a searchable timeline of everything you've done across all projects.
132
+
133
+ ### How it works
134
+
135
+ A PostToolUse hook fires after every Claude Code tool call. A rule-based classifier (no AI needed, under 50ms) categorizes each action:
136
+
137
+ | Type | What triggers it | Examples |
138
+ |------|-----------------|----------|
139
+ | **decision** | Git commits, config changes | `git commit`, writing to config files |
140
+ | **bugfix** | Test runs, error investigation | `npm test`, debugging commands |
141
+ | **feature** | New file creation, feature work | Creating components, adding endpoints |
142
+ | **refactor** | Code restructuring | Renaming, moving files, reorganizing |
143
+ | **discovery** | File reads, searches | Reading code, grep searches, glob patterns |
144
+ | **change** | File edits | Editing source files, updating configs |
145
+
146
+ Observations are stored in PostgreSQL with content-hash deduplication (30-second window) to prevent duplicates from rapid tool calls.
147
+
148
+ ### Progressive context injection
149
+
150
+ At session start, PAI injects recent observations as layered context:
151
+
152
+ 1. **Compact index** (~100 tokens) — observation type counts and active projects
153
+ 2. **Timeline** (~500 tokens) — recent observations with timestamps
154
+ 3. **On-demand** — full details available via MCP tools
155
+
156
+ This means Claude starts every session already knowing what you were working on, without you re-explaining anything.
157
+
158
+ ### Searching observations
159
+
160
+ Ask Claude naturally:
161
+
162
+ ```
163
+ "What changes did I make to the daemon today?"
164
+ "Show me all decisions from the last session"
165
+ "What files did I modify in the PAI project this week?"
166
+ ```
167
+
168
+ Or use the CLI:
169
+
170
+ ```bash
171
+ # List recent observations
172
+ pai observation list
173
+
174
+ # Filter by type
175
+ pai observation list --type decision
176
+
177
+ # Filter by project
178
+ pai observation list --project pai
179
+
180
+ # Show stats
181
+ pai observation stats
182
+ ```
183
+
184
+ ### Session summaries
185
+
186
+ When a session ends, PAI generates a structured summary capturing what was requested, investigated, learned, completed, and what the next steps are. These summaries feed into the progressive context system, giving future sessions a concise picture of past work.
187
+
188
+ ---
189
+
108
190
  ## Auto-Compact Context Window
109
191
 
110
192
  Claude Code can automatically compact your context window when it fills up, preventing session interruptions mid-task. PAI's statusline shows you at a glance whether auto-compact is active.
@@ -342,7 +424,9 @@ External URLs (`https://`, `mailto:`, etc.) are excluded — only relative paths
342
424
 
343
425
  PAI works great alongside these tools (also by the same author):
344
426
 
427
+ - **[AIBroker](https://github.com/mnott/AIBroker)** — Unified message bridge for Claude Code (WhatsApp, Telegram, PAILot — text and voice routing)
345
428
  - **[Whazaa](https://github.com/mnott/Whazaa)** — WhatsApp bridge for Claude Code (voice notes, screenshots, session routing)
429
+ - **[Telex](https://github.com/mnott/Telex)** — Telegram bridge for Claude Code (text and voice messaging)
346
430
  - **[Coogle](https://github.com/mnott/Coogle)** — Google Workspace MCP daemon (Gmail, Calendar, Drive multiplexing)
347
431
  - **[DEVONthink MCP](https://github.com/mnott/devonthink-mcp)** — DEVONthink integration for document search and archival
348
432
 
@@ -352,6 +436,8 @@ PAI works great alongside these tools (also by the same author):
352
436
 
353
437
  PAI Knowledge OS is inspired by [Daniel Miessler](https://github.com/danielmiessler)'s concept of Personal AI Infrastructure and his [Fabric](https://github.com/danielmiessler/fabric) project — a Python CLI for augmenting human capabilities with reusable AI prompt patterns. Fabric is excellent and solves a different problem; PAI takes the same philosophy in a different direction: persistent memory, session continuity, and deep Claude Code integration. See [FEATURE.md](FEATURE.md) for a detailed comparison.
354
438
 
439
+ The automatic observation capture system — classifying tool calls into structured observations with progressive context injection — is inspired by [claude-mem](https://github.com/thedotmack/claude-mem) by [thedotmack](https://github.com/thedotmack). claude-mem demonstrated that automatic memory capture during Claude Code sessions dramatically improves continuity. PAI adapts this concept with a rule-based classifier, PostgreSQL storage, and three-layer progressive disclosure.
440
+
355
441
  ---
356
442
 
357
443
  ## License
@@ -1,5 +1,5 @@
1
1
  import { r as readPaiMarker } from "./pai-marker-CXQPX2P6.mjs";
2
- import { t as detectProject } from "./detect-BU3Nx_2L.mjs";
2
+ import { t as detectProject } from "./detect-CdaA48EI.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-Bp-2SM3x.mjs").then((n) => n.n);
29
+ const { detectTopicShift } = await import("./detector-jGBuYQJM.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-BG6I_4B1.mjs.map
86
+ //# sourceMappingURL=auto-route-C-DrW6BL.mjs.map
@@ -1 +1 @@
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"}
1
+ {"version":3,"file":"auto-route-C-DrW6BL.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"}