@tekmidian/pai 0.6.0 → 0.6.2

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 CHANGED
@@ -95,15 +95,23 @@ docker run -d \
95
95
  pai setup
96
96
  ```
97
97
 
98
- The interactive wizard walks through seven steps:
98
+ The interactive wizard walks through 14 steps:
99
99
 
100
- 1. PostgreSQL connection (host, port, credentials)
101
- 2. Embedding model selection (Snowflake Arctic recommended)
102
- 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
103
103
  4. CLAUDE.md template installation
104
- 5. Projects root directory
105
- 6. Obsidian vault location (optional)
106
- 7. MCP server registration with Claude Code
104
+ 5. PAI skill installation
105
+ 6. Steering rules installation
106
+ 7. MCP skill stub symlinks
107
+ 8. Hook system deployment
108
+ 9. TypeScript hook compilation
109
+ 10. Claude Code settings configuration
110
+ 11. Daemon installation
111
+ 12. MCP server registration
112
+ 13. Directory creation
113
+ 14. Initial indexing
114
+ 15. Verification
107
115
 
108
116
  ### 4. Install the Daemon
109
117
 
@@ -143,7 +151,7 @@ If both commands return healthy output, PAI is running. Open a new Claude Code s
143
151
 
144
152
  ## MCP Server
145
153
 
146
- 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.
154
+ 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.
147
155
 
148
156
  ```
149
157
  Claude Code (stdio)
@@ -164,14 +172,8 @@ Claude Code (stdio)
164
172
  | `session_list` | List session notes, optionally filtered by project |
165
173
  | `registry_search` | Search project metadata (names, paths, tags) |
166
174
  | `project_detect` | Identify which project a given path belongs to |
167
- | `observation_search` | Search classified observations by project, type, or session |
168
- | `observation_timeline` | Recent observation timeline with progressive context layers |
169
- | `zettel_explore` | BFS traversal of wikilink graph from a seed note |
170
- | `zettel_surprise` | Find semantically distant but graph-close notes |
171
- | `zettel_converse` | Hybrid search with graph expansion and cross-domain connections |
172
- | `zettel_themes` | Cluster vault notes into thematic groups by embedding similarity |
173
- | `zettel_health` | Audit vault for broken links, orphans, and isolated clusters |
174
- | `zettel_suggest` | Suggest link targets weighted by semantics, tags, and graph neighborhood |
175
+ | `project_health` | Audit all registered paths for moved or deleted directories |
176
+ | `project_todo` | Read a project's TODO.md and continuation prompt |
175
177
 
176
178
  ### Tool Reference
177
179
 
@@ -189,21 +191,52 @@ Claude Code (stdio)
189
191
 
190
192
  **`project_detect(path?)`** — Given a filesystem path (defaults to CWD), returns the matching project.
191
193
 
192
- **`observation_search(project?, type?, session_id?, limit?)`** — Search the observation store. Filter by project slug, observation type (`decision`, `bugfix`, `feature`, `refactor`, `discovery`, `change`), or session ID. Returns observations ordered by creation time descending.
194
+ **`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.
193
195
 
194
- **`observation_timeline(project?, limit?)`** — Returns a layered timeline: compact index (~100 tokens with type counts and active projects), recent timeline (~500 tokens with timestamped observations), and on-demand detail access via `observation_search`.
196
+ **`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.
195
197
 
196
- **`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`.
198
+ ### On-Demand Prompts (Skills)
197
199
 
198
- **`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.
200
+ 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.
199
201
 
200
- **`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.
201
-
202
- **`zettel_themes(min_cluster_size?)`** Clusters all vault embeddings using agglomerative single-linkage clustering. Returns thematic groups with representative note titles and cluster size.
203
-
204
- **`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.
205
-
206
- **`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).
202
+ | Prompt | Purpose |
203
+ |--------|---------|
204
+ | `art` | Visual art direction and creative guidance |
205
+ | `createskill` | Scaffold new PAI skills |
206
+ | `journal` | Structured journaling workflow |
207
+ | `name` | Session and project naming conventions |
208
+ | `observability` | Observation system usage and querying |
209
+ | `plan` | Forward-looking planning from TODOs and recent activity |
210
+ | `research` | Structured research methodology |
211
+ | `review` | Retrospective review of work over a time period |
212
+ | `route` | Session note routing across projects |
213
+ | `search-history` | Search history analysis and patterns |
214
+ | `sessions` | Session lifecycle management |
215
+ | `share` | Generate social media posts from recent work |
216
+ | `story-explanation` | Narrative explanations of technical concepts |
217
+ | `vault-connect` | Suggest and create vault connections |
218
+ | `vault-context` | Use vault as conversational context |
219
+ | `vault-emerge` | Detect emerging themes in the vault |
220
+ | `vault-orphans` | Find and fix orphaned vault notes |
221
+ | `vault-trace` | Trace idea lineage through vault links |
222
+
223
+ ### Reference Resources
224
+
225
+ 11 resources available via `pai://` URIs. Claude reads these on demand for reference documentation.
226
+
227
+ | URI | Content |
228
+ |-----|---------|
229
+ | `pai://aesthetic` | Visual and output style guidelines |
230
+ | `pai://constitution` | Core philosophy and principles |
231
+ | `pai://history-system` | Search history tracking system |
232
+ | `pai://hook-system` | Hook architecture and development guide |
233
+ | `pai://mcp-dev-guide` | MCP server development patterns |
234
+ | `pai://prompting` | Prompt engineering best practices |
235
+ | `pai://prosody-agent-template` | Voice agent template |
236
+ | `pai://prosody-guide` | Voice and prosody guidelines |
237
+ | `pai://skill-system` | Skill authoring reference |
238
+ | `pai://terminal-tabs` | Terminal tab management |
239
+ | `pai://voice` | Voice configuration reference |
207
240
 
208
241
  ### Installation
209
242
 
@@ -418,7 +451,7 @@ pai observation stats
418
451
  ```bash
419
452
  pai backup # Backup registry, config, and Postgres
420
453
  pai restore <path> # Restore from backup (--no-postgres to skip DB)
421
- pai setup # Interactive 7-step setup wizard
454
+ pai setup # Interactive 14-step setup wizard
422
455
  pai search "query" # Quick full-text search shortcut
423
456
  ```
424
457
 
@@ -610,6 +643,68 @@ The build script compiles each `.ts` hook to a self-contained `.mjs` module usin
610
643
 
611
644
  ---
612
645
 
646
+ ## Skill Stub System
647
+
648
+ PAI's 18 MCP prompts are exposed to Claude Code as discoverable skills via auto-generated SKILL.md files. This bridges the gap between MCP prompts (protocol-level, invoked via `prompts/get`) and Claude Code's skill scanner (filesystem-based, scans `~/.claude/skills/`).
649
+
650
+ ### How It Works
651
+
652
+ ```
653
+ Source (TypeScript) Build Claude Code
654
+ ───────────────── ───── ──────────
655
+ src/daemon-mcp/prompts/*.ts → dist/skills/<Name>/ → ~/.claude/skills/<Name>/
656
+ src/daemon-mcp/prompts/ SKILL.md (symlink)
657
+ custom/*.ts (gitignored)
658
+ ```
659
+
660
+ 1. **Source of truth**: TypeScript files in `src/daemon-mcp/prompts/`. Each exports `{ description, content }`.
661
+ 2. **Build**: `node scripts/build-skill-stubs.mjs --sync` extracts content and generates `dist/skills/<TitleCase>/SKILL.md` with YAML frontmatter.
662
+ 3. **Sync**: The `--sync` flag creates/updates symlinks in `~/.claude/skills/`. Runs automatically on every `bun run build`.
663
+ 4. **Discovery**: Claude Code scans `~/.claude/skills/<Name>/SKILL.md` at session start, loads descriptions, and auto-invokes matching skills.
664
+
665
+ **Important**: Skills MUST be at `~/.claude/skills/<Name>/SKILL.md` (one level deep). Subdirectories like `~/.claude/skills/user/<Name>/` are NOT discovered by Claude Code's scanner.
666
+
667
+ ### Adding a New Skill
668
+
669
+ **Built-in (shipped with PAI):**
670
+
671
+ 1. Create `src/daemon-mcp/prompts/my-skill.ts`:
672
+ ```typescript
673
+ export const mySkill = {
674
+ description: "What the skill does",
675
+ content: `## My Skill
676
+
677
+ USE WHEN user says 'trigger phrase', 'another trigger', ...
678
+
679
+ ### Instructions
680
+ ...your skill content here...`,
681
+ };
682
+ ```
683
+ 2. Add export to `src/daemon-mcp/prompts/index.ts`:
684
+ ```typescript
685
+ export { mySkill } from "./my-skill.js";
686
+ ```
687
+ 3. Run `bun run build` — generates the stub AND syncs the symlink.
688
+
689
+ **Custom (user-created, survives `git pull`):**
690
+
691
+ 1. Create `src/daemon-mcp/prompts/custom/my-local-skill.ts` (same format as above).
692
+ 2. Run `bun run build` — custom prompts are picked up automatically.
693
+
694
+ The `custom/` directory is gitignored (only `.gitkeep` is tracked), so your local skills survive PAI updates.
695
+
696
+ ### Updating After Changes
697
+
698
+ Symlinks point to `dist/skills/`, so any `bun run build` automatically updates what Claude Code sees. No manual steps needed.
699
+
700
+ If you reorganize prompts (rename, delete, add), the build script regenerates all stubs and the `--sync` flag updates symlinks accordingly. Stale symlinks pointing to removed stubs are cleaned up automatically.
701
+
702
+ ### Setup Integration
703
+
704
+ `pai setup` (Step 7) runs the same symlink logic interactively, asking before creating symlinks. It also cleans up legacy symlinks from the old `~/.claude/skills/user/` location.
705
+
706
+ ---
707
+
613
708
  ## Templates
614
709
 
615
710
  PAI ships three templates used during setup and customizable for your workflow.
@@ -819,41 +914,91 @@ bun run lint # tsc --noEmit
819
914
  | Output | Purpose |
820
915
  |--------|---------|
821
916
  | `dist/cli/index.mjs` | `pai` CLI |
822
- | `dist/mcp/index.mjs` | Direct MCP server (legacy) |
823
917
  | `dist/daemon/index.mjs` | Daemon server |
824
918
  | `dist/daemon-mcp/index.mjs` | MCP shim (stdio → daemon socket) |
919
+ | `dist/hooks/*.mjs` | Compiled lifecycle hooks |
920
+ | `dist/skills/<Name>/SKILL.md` | Generated skill stubs (symlinked to ~/.claude/skills/) |
825
921
 
826
922
  ### Source Structure
827
923
 
828
924
  ```
829
925
  src/
830
- ├── cli/commands/ # CLI command implementations
831
- └── zettel.ts # `pai zettel` with 6 subcommands
832
- ├── daemon/ # Daemon server and index scheduler
833
- ├── daemon-mcp/ # MCP shim (stdio → daemon socket)
834
- ├── federation/ # Federation schema definitions
835
- ├── hooks/ # Lifecycle hooks (pre-compact, session-stop)
836
- ├── mcp/ # Direct MCP server (legacy)
837
- ├── memory/ # Indexer, chunker, embeddings, search, reranker
838
- │ ├── reranker.ts # Cross-encoder reranking (Xenova/ms-marco-MiniLM-L-6-v2)
839
- └── vault-indexer.ts # Obsidian vault indexing into v3 vault tables
840
- ├── observations/ # Automatic observation capture
841
- │ ├── classifier.ts # Rule-based tool call classifier (decision/bugfix/feature/refactor/discovery/change)
842
- ├── store.ts # PostgreSQL persistence with content-hash deduplication
843
- │ └── schema.sql # Observation + session summary table DDL
844
- ├── obsidian/ # Obsidian vault bridge
845
- │ └── vault-fixer.ts # Repairs broken wikilinks and orphaned entries
846
- ├── registry/ # Registry migrations and queries
847
- ├── session/ # Session slug generator
848
- ├── storage/ # Storage backend interface (SQLite/Postgres)
849
- └── zettelkasten/ # Luhmann-inspired graph + semantic operations
850
- ├── explore.ts # BFS traversal classifying sequential/associative edges
851
- ├── surprise.ts # Serendipitous bridge discovery via cosine × graph distance
852
- ├── converse.ts # Hybrid search → graph expansion → cross-domain connections
853
- ├── themes.ts # Agglomerative embedding clustering for thematic groups
854
- ├── health.ts # SQL-driven vault audit with union-find cluster detection
855
- ├── suggest.ts # Weighted link suggestions (semantic + tags + graph)
856
- └── index.ts # Barrel export for all zettelkasten operations
926
+ ├── cli/
927
+ ├── commands/ # CLI command modules
928
+ │ │ ├── backup.ts
929
+ │ │ ├── daemon.ts
930
+ │ │ ├── memory.ts
931
+ │ │ ├── observation.ts
932
+ │ │ ├── obsidian.ts
933
+ │ │ ├── project.ts
934
+ ├── registry.ts
935
+ │ ├── session.ts
936
+ │ │ ├── setup/ # 14-step interactive wizard
937
+ │ │ ├── steps/ # 01-welcome through 15-verify
938
+ │ │ └── index.ts
939
+ └── zettel.ts
940
+ │ └── index.ts # CLI entry point
941
+ ├── daemon/
942
+ ├── daemon/ # Daemon server internals
943
+ │ │ ├── dispatcher.ts # Tool dispatch (zettel, observation, memory)
944
+ │ │ ├── handler.ts # NDJSON request handler
945
+ │ │ └── server.ts # Socket server
946
+ ├── indexer/ # Background index scheduler
947
+ ├── config.ts # Runtime configuration
948
+ │ └── index.ts # Daemon entry point
949
+ ├── daemon-mcp/
950
+ ├── instructions.ts # MCP server instructions (~1.5KB routing table)
951
+ ├── prompts/ # 18 on-demand skill prompts
952
+ │ │ └── custom/ # User-created prompts (gitignored)
953
+ │ ├── resources/ # 11 reference resources (pai:// URIs)
954
+ │ └── index.ts # MCP shim entry point (stdio → socket)
955
+ ├── hooks/
956
+ │ └── ts/ # TypeScript hook sources by event
957
+ │ ├── PreCompact/
958
+ │ ├── PreToolUse/
959
+ │ ├── PostToolUse/
960
+ │ ├── SessionStart/
961
+ │ ├── Stop/
962
+ │ └── UserPromptSubmit/
963
+ ├── mcp/
964
+ │ └── tools/ # Shared tool implementations
965
+ │ ├── memory.ts
966
+ │ ├── observations.ts
967
+ │ ├── projects.ts
968
+ │ ├── registry.ts
969
+ │ ├── sessions.ts
970
+ │ └── zettel.ts
971
+ ├── memory/
972
+ │ ├── chunker/ # Text chunking strategies
973
+ │ ├── embeddings.ts # Snowflake Arctic embedding generation
974
+ │ ├── indexer.ts # File indexer with change detection
975
+ │ ├── reranker.ts # Cross-encoder reranking (ms-marco-MiniLM)
976
+ │ ├── search.ts # Multi-mode search (keyword/semantic/hybrid)
977
+ │ └── vault-indexer.ts # Obsidian vault indexing
978
+ ├── observations/ # Automatic observation capture
979
+ │ ├── classifier.ts # Rule-based tool call classifier
980
+ │ ├── store.ts # PostgreSQL persistence with deduplication
981
+ │ └── schema.sql # DDL for observation tables
982
+ ├── obsidian/ # Obsidian vault bridge
983
+ │ └── vault-fixer.ts # Repairs broken wikilinks and orphans
984
+ ├── registry/ # SQLite registry queries and migrations
985
+ ├── session/ # Session slug generator
986
+ ├── storage/ # Pluggable storage backend
987
+ │ ├── factory.ts # Backend selection (SQLite/PostgreSQL)
988
+ │ ├── interface.ts # StorageInterface contract
989
+ │ ├── postgres.ts # PostgreSQL + pgvector backend
990
+ │ └── sqlite.ts # SQLite backend
991
+ ├── utils/ # Shared utilities
992
+ │ ├── hash.ts # SHA-256 hashing
993
+ │ └── stop-words.ts # Stop word lists for search
994
+ ├── zettelkasten/ # Luhmann-inspired operations
995
+ │ ├── explore.ts # BFS traversal
996
+ │ ├── surprise.ts # Serendipitous bridge discovery
997
+ │ ├── converse.ts # Hybrid search + graph expansion
998
+ │ ├── themes.ts # Embedding clustering
999
+ │ ├── health.ts # Vault structural audit
1000
+ │ └── suggest.ts # Weighted link suggestions
1001
+ └── index.ts # Package entry point
857
1002
  ```
858
1003
 
859
1004
  ### 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 |
@@ -15,7 +15,7 @@ import { t as PaiClient } from "../ipc-client-CoyUHPod.mjs";
15
15
  import { a as expandHome, i as ensureConfigDir, n as CONFIG_FILE$2, o as loadConfig, t as CONFIG_DIR } from "../config-BuhHWyOK.mjs";
16
16
  import { t as createStorageBackend } from "../factory-Ygqe_bVZ.mjs";
17
17
  import { appendFileSync, chmodSync, copyFileSync, existsSync, lstatSync, mkdirSync, readFileSync, readdirSync, readlinkSync, renameSync, statSync, symlinkSync, unlinkSync, writeFileSync } from "node:fs";
18
- import { homedir, tmpdir } from "node:os";
18
+ import { homedir, platform, tmpdir } from "node:os";
19
19
  import { basename, dirname, join, relative, resolve } from "node:path";
20
20
  import chalk from "chalk";
21
21
  import { Command } from "commander";
@@ -4310,6 +4310,18 @@ function getDistHooksDir() {
4310
4310
  for (const candidate of candidates) if (existsSync(join(candidate, "stop-hook.mjs"))) return candidate;
4311
4311
  return fromModule;
4312
4312
  }
4313
+ function getDistDir() {
4314
+ const moduleDir = new URL(".", import.meta.url).pathname;
4315
+ const fromModule = join(moduleDir, "..", "..", "..");
4316
+ const candidates = [
4317
+ fromModule,
4318
+ join(process.cwd(), "dist"),
4319
+ join(homedir(), "dev", "ai", "PAI", "dist"),
4320
+ join("/", "usr", "local", "lib", "node_modules", "@tekmidian", "pai", "dist")
4321
+ ];
4322
+ for (const candidate of candidates) if (existsSync(join(candidate, "skills"))) return candidate;
4323
+ return fromModule;
4324
+ }
4313
4325
  function getStatuslineScript() {
4314
4326
  const candidates = [
4315
4327
  join(process.cwd(), "statusline-command.sh"),
@@ -4685,6 +4697,89 @@ async function stepAiSteeringRules(rl) {
4685
4697
  return true;
4686
4698
  }
4687
4699
 
4700
+ //#endregion
4701
+ //#region src/cli/commands/setup/steps/07-skill-stubs.ts
4702
+ /**
4703
+ * Step 7: Install PAI MCP skill stubs to ~/.claude/skills/.
4704
+ *
4705
+ * Each PAI MCP prompt has a SKILL.md stub generated at build time
4706
+ * (dist/skills/<Name>/SKILL.md). This step symlinks (macOS/Linux) or
4707
+ * copies (Windows) them into Claude Code's skill discovery directory.
4708
+ *
4709
+ * Skills MUST live at ~/.claude/skills/<Name>/SKILL.md (top level),
4710
+ * NOT under a user/ subdirectory — Claude Code only scans one level deep.
4711
+ *
4712
+ * Symlinks keep the stubs in sync — rebuilding PAI updates them automatically.
4713
+ * Existing non-symlink directories with the same name are never overwritten.
4714
+ */
4715
+ async function stepSkillStubs(rl) {
4716
+ section("Step 7: MCP Skill Stubs");
4717
+ line$1();
4718
+ line$1(" PAI MCP prompts need SKILL.md stubs so Claude Code can discover them.");
4719
+ line$1(" Stubs are symlinked from dist/skills/ — rebuilding PAI keeps them in sync.");
4720
+ line$1();
4721
+ const distSkills = join(getDistDir(), "skills");
4722
+ if (!existsSync(distSkills)) {
4723
+ console.log(c.warn("dist/skills/ not found — run `bun run build` first."));
4724
+ return false;
4725
+ }
4726
+ const skillNames = readdirSync(distSkills).filter((name) => {
4727
+ return existsSync(join(join(distSkills, name), "SKILL.md"));
4728
+ });
4729
+ if (skillNames.length === 0) {
4730
+ console.log(c.warn("No skill stubs found in dist/skills/."));
4731
+ return false;
4732
+ }
4733
+ console.log(c.dim(` Found ${skillNames.length} skill stubs to install.`));
4734
+ line$1();
4735
+ if (!await promptYesNo(rl, `Symlink ${skillNames.length} PAI skill stubs to ~/.claude/skills/?`, true)) {
4736
+ console.log(c.dim(" Skipping skill stub installation."));
4737
+ return false;
4738
+ }
4739
+ const targetBase = join(homedir(), ".claude", "skills");
4740
+ mkdirSync(targetBase, { recursive: true });
4741
+ const oldUserBase = join(targetBase, "user");
4742
+ if (existsSync(oldUserBase)) for (const name of skillNames) {
4743
+ const oldTarget = join(oldUserBase, name);
4744
+ if (existsSync(oldTarget) && lstatSync(oldTarget).isSymbolicLink()) unlinkSync(oldTarget);
4745
+ }
4746
+ const useSymlinks = platform() !== "win32";
4747
+ let installed = 0;
4748
+ let skipped = 0;
4749
+ let updated = 0;
4750
+ for (const name of skillNames) {
4751
+ const source = resolve(join(distSkills, name));
4752
+ const target = join(targetBase, name);
4753
+ if (existsSync(target) && !lstatSync(target).isSymbolicLink()) {
4754
+ console.log(c.dim(` SKIP ${name} (existing user skill, not a symlink)`));
4755
+ skipped++;
4756
+ continue;
4757
+ }
4758
+ if (existsSync(target) && lstatSync(target).isSymbolicLink()) {
4759
+ if (resolve(readlinkSync(target)) === source) {
4760
+ installed++;
4761
+ continue;
4762
+ }
4763
+ unlinkSync(target);
4764
+ updated++;
4765
+ }
4766
+ if (useSymlinks) symlinkSync(source, target);
4767
+ else {
4768
+ mkdirSync(target, { recursive: true });
4769
+ copyFileSync(join(source, "SKILL.md"), join(target, "SKILL.md"));
4770
+ }
4771
+ installed++;
4772
+ }
4773
+ line$1();
4774
+ const method = useSymlinks ? "symlinked" : "copied";
4775
+ if (updated > 0) console.log(c.ok(`${installed} stubs ${method}, ${updated} updated, ${skipped} skipped (user skills).`));
4776
+ else {
4777
+ console.log(c.ok(`${installed} skill stubs ${method} to ~/.claude/skills/.`));
4778
+ if (skipped > 0) console.log(c.dim(` ${skipped} skipped (existing user skills not managed by PAI).`));
4779
+ }
4780
+ return true;
4781
+ }
4782
+
4688
4783
  //#endregion
4689
4784
  //#region src/cli/commands/setup/steps/08-hooks.ts
4690
4785
  /** Step 7: Shell lifecycle hooks installation (pre-compact, session-stop, statusline). */
@@ -5317,7 +5412,7 @@ async function stepInitialIndex(rl) {
5317
5412
  //#endregion
5318
5413
  //#region src/cli/commands/setup/steps/15-verify.ts
5319
5414
  /** Step 13: Setup summary — displays all configuration choices made during setup. */
5320
- function stepSummary(configUpdates, claudeMdGenerated, paiSkillInstalled, aiSteeringRulesInstalled, hooksInstalled, tsHooksInstalled, settingsPatched, daName, daemonInstalled, mcpRegistered) {
5415
+ function stepSummary(configUpdates, claudeMdGenerated, paiSkillInstalled, aiSteeringRulesInstalled, skillStubsInstalled, hooksInstalled, tsHooksInstalled, settingsPatched, daName, daemonInstalled, mcpRegistered) {
5321
5416
  section("Setup Complete");
5322
5417
  line$1();
5323
5418
  console.log(chalk.green(" PAI Knowledge OS is configured!"));
@@ -5331,6 +5426,7 @@ function stepSummary(configUpdates, claudeMdGenerated, paiSkillInstalled, aiStee
5331
5426
  console.log(chalk.dim(" CLAUDE.md: ") + chalk.cyan(claudeMdGenerated ? "~/.claude/CLAUDE.md (generated)" : "(unchanged)"));
5332
5427
  console.log(chalk.dim(" PAI skill: ") + chalk.cyan(paiSkillInstalled ? "~/.claude/skills/PAI/SKILL.md (installed)" : "(unchanged)"));
5333
5428
  console.log(chalk.dim(" Steering rules: ") + chalk.cyan(aiSteeringRulesInstalled ? "~/.claude/skills/PAI/AI-STEERING-RULES.md (installed)" : "(unchanged)"));
5429
+ console.log(chalk.dim(" Skill stubs: ") + chalk.cyan(skillStubsInstalled ? "18 MCP prompt stubs symlinked to ~/.claude/skills/" : "(unchanged)"));
5334
5430
  console.log(chalk.dim(" Hooks (shell): ") + chalk.cyan(hooksInstalled ? "pai-pre-compact.sh, pai-session-stop.sh (installed)" : "(unchanged)"));
5335
5431
  console.log(chalk.dim(" Hooks (TS): ") + chalk.cyan(tsHooksInstalled ? "14 .mjs hooks installed to ~/.claude/Hooks/" : "(unchanged)"));
5336
5432
  console.log(chalk.dim(" Assistant name: ") + chalk.cyan(daName));
@@ -5394,6 +5490,7 @@ async function runSetup() {
5394
5490
  const claudeMdGenerated = await stepClaudeMd(rl);
5395
5491
  const paiSkillInstalled = await stepPaiSkill(rl);
5396
5492
  const aiSteeringRulesInstalled = await stepAiSteeringRules(rl);
5493
+ const skillStubsInstalled = await stepSkillStubs(rl);
5397
5494
  const hooksInstalled = await stepHooks(rl);
5398
5495
  const tsHooksInstalled = await stepTsHooks(rl);
5399
5496
  const daName = await stepDaName(rl);
@@ -5409,7 +5506,7 @@ async function runSetup() {
5409
5506
  line$1();
5410
5507
  console.log(chalk.green(" Configuration saved."));
5411
5508
  await stepInitialIndex(rl);
5412
- stepSummary(allUpdates, claudeMdGenerated, paiSkillInstalled, aiSteeringRulesInstalled, hooksInstalled, tsHooksInstalled, settingsPatched, daName, daemonInstalled, mcpRegistered);
5509
+ stepSummary(allUpdates, claudeMdGenerated, paiSkillInstalled, aiSteeringRulesInstalled, skillStubsInstalled, hooksInstalled, tsHooksInstalled, settingsPatched, daName, daemonInstalled, mcpRegistered);
5413
5510
  } finally {
5414
5511
  rl.close();
5415
5512
  }