@tekmidian/pai 0.66.0 → 0.66.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/dist/{aibroker-client-B8c42Lh8.mjs → aibroker-client-CHEEvJZW.mjs} +2 -2
- package/dist/{aibroker-client-C5Fw7DNz.mjs → aibroker-client-Dfv7j1Od.mjs} +3 -3
- package/dist/{aibroker-client-C5Fw7DNz.mjs.map → aibroker-client-Dfv7j1Od.mjs.map} +1 -1
- package/dist/{auto-route-o0BOsXn7.mjs → auto-route-BnizyALK.mjs} +65 -4
- package/dist/auto-route-BnizyALK.mjs.map +1 -0
- package/dist/auto-route-D6fW1Q8z.mjs +3 -0
- package/dist/{chain-D4PuQ2c-.mjs → chain-D5876UQX.mjs} +3 -4
- package/dist/{chain-D4PuQ2c-.mjs.map → chain-D5876UQX.mjs.map} +1 -1
- package/dist/cli/index.mjs +18 -23
- package/dist/cli/index.mjs.map +1 -1
- package/dist/cli/program.mjs +17 -22
- package/dist/{config-BbLFD7Uf.mjs → config-C-LiGlop.mjs} +2 -2
- package/dist/{config-BbLFD7Uf.mjs.map → config-C-LiGlop.mjs.map} +1 -1
- package/dist/{config-YinjgXEJ.mjs → config-DKIA7wgF.mjs} +1 -1
- package/dist/{context-handover-cache-pkHzmL3c.mjs → context-handover-cache-9PGvXRIw.mjs} +984 -10
- package/dist/context-handover-cache-9PGvXRIw.mjs.map +1 -0
- package/dist/daemon/index.mjs +17 -19
- package/dist/daemon/index.mjs.map +1 -1
- package/dist/daemon-BUgNvMUh.mjs +5029 -0
- package/dist/daemon-BUgNvMUh.mjs.map +1 -0
- package/dist/daemon-DL0eglMJ.mjs +18 -0
- package/dist/daemon-mcp/index.mjs +15 -10
- package/dist/daemon-mcp/index.mjs.map +1 -1
- package/dist/{embeddings-CcscYWwk.mjs → embeddings-CEBGrzwu.mjs} +1 -1
- package/dist/{embeddings-Bx3q0QOY.mjs → embeddings-DOLZnT1X.mjs} +1 -1
- package/dist/{embeddings-Bx3q0QOY.mjs.map → embeddings-DOLZnT1X.mjs.map} +1 -1
- package/dist/{env-JNEIrQWg.mjs → env-DiolswKQ.mjs} +1 -1
- package/dist/{env-JNEIrQWg.mjs.map → env-DiolswKQ.mjs.map} +1 -1
- package/dist/factory-DrBg24UT.mjs +8 -0
- package/dist/factory-Ypf8r0fK.mjs +2892 -0
- package/dist/factory-Ypf8r0fK.mjs.map +1 -0
- package/dist/{fallback-B5svk75f.mjs → fallback-D51R4shY.mjs} +27 -13
- package/dist/fallback-D51R4shY.mjs.map +1 -0
- package/dist/{chunker-BH4i-F2b.mjs → helpers-CZsi_49C.mjs} +221 -2
- package/dist/helpers-CZsi_49C.mjs.map +1 -0
- package/dist/hooks/block-sleep-poll.mjs.map +2 -2
- package/dist/hooks/load-project-context.mjs.map +2 -2
- package/dist/hooks/worker-guard.mjs +5 -0
- package/dist/hooks/worker-guard.mjs.map +2 -2
- package/dist/hooks/worker-status-line.mjs +1 -0
- package/dist/hooks/worker-status-line.mjs.map +2 -2
- package/dist/index.mjs +6 -7
- package/dist/{ipc-client-D16Xw6Uo.mjs → ipc-client-BLYX51nG.mjs} +2 -2
- package/dist/{ipc-client-D16Xw6Uo.mjs.map → ipc-client-BLYX51nG.mjs.map} +1 -1
- package/dist/main-resolver-BYgP86C1.mjs +7 -0
- package/dist/{main-resolver-DPPwHNtn.mjs → main-resolver-kVkMJZUD.mjs} +6 -6
- package/dist/{main-resolver-DPPwHNtn.mjs.map → main-resolver-kVkMJZUD.mjs.map} +1 -1
- package/dist/{migrate-BD7D8EEh.mjs → migrate-Bq0esMOa.mjs} +2 -2
- package/dist/{migrate-BD7D8EEh.mjs.map → migrate-Bq0esMOa.mjs.map} +1 -1
- package/dist/{pai-marker-D1MMswkz.mjs → pai-marker-DXVpFsYz.mjs} +1 -1
- package/dist/{pai-marker-D1MMswkz.mjs.map → pai-marker-DXVpFsYz.mjs.map} +1 -1
- package/dist/{planner-EJPZnnif.mjs → planner-D5yt0EfA.mjs} +10 -7
- package/dist/{planner-EJPZnnif.mjs.map → planner-D5yt0EfA.mjs.map} +1 -1
- package/dist/postgres-BmLr0MUm.mjs +5 -0
- package/dist/{postgres-Ceqsa64C.mjs → postgres-D3xc2RB4.mjs} +364 -9
- package/dist/postgres-D3xc2RB4.mjs.map +1 -0
- package/dist/{program-DSETzrWg.mjs → program-DIXRtAFy.mjs} +80 -70
- package/dist/program-DIXRtAFy.mjs.map +1 -0
- package/dist/{query-feedback-BIaZTTFO.mjs → query-feedback-B7FYE4JR.mjs} +1 -1
- package/dist/{query-feedback-BIaZTTFO.mjs.map → query-feedback-B7FYE4JR.mjs.map} +1 -1
- package/dist/{reranker-DKv80KO5.mjs → reranker-3lnggwgq.mjs} +1 -1
- package/dist/{reranker-DKv80KO5.mjs.map → reranker-3lnggwgq.mjs.map} +1 -1
- package/dist/{reranker-CFiEzHQu.mjs → reranker-CZ2mP4cf.mjs} +1 -1
- package/dist/router-BK-hFeQ6.mjs +3 -0
- package/dist/{router-a5G7q8_3.mjs → router-BaTbc9VX.mjs} +2 -2
- package/dist/{router-a5G7q8_3.mjs.map → router-BaTbc9VX.mjs.map} +1 -1
- package/dist/{run-Z4akoa8G.mjs → run-CLYTOm0x.mjs} +764 -25
- package/dist/run-CLYTOm0x.mjs.map +1 -0
- package/dist/{run-env-DRcp7A8K.mjs → run-env-Bnmf6bRI.mjs} +2 -2
- package/dist/{run-env-DRcp7A8K.mjs.map → run-env-Bnmf6bRI.mjs.map} +1 -1
- package/dist/{runtime-paths-CHTg3ywb.mjs → runtime-paths-D3FKAm69.mjs} +1 -1
- package/dist/{runtime-paths-CHTg3ywb.mjs.map → runtime-paths-D3FKAm69.mjs.map} +1 -1
- package/dist/{search-BOSphCJ1.mjs → search-CNAGTiJP.mjs} +2 -2
- package/dist/{search-BOSphCJ1.mjs.map → search-CNAGTiJP.mjs.map} +1 -1
- package/dist/{sources-CijVso3n.mjs → sources-CM2g-CLT.mjs} +2 -2
- package/dist/{sources-CijVso3n.mjs.map → sources-CM2g-CLT.mjs.map} +1 -1
- package/dist/{stop-words-BdQuaE9K.mjs → stop-words-DtxaTWU_.mjs} +1 -1
- package/dist/{stop-words-BdQuaE9K.mjs.map → stop-words-DtxaTWU_.mjs.map} +1 -1
- package/dist/{utils-C9HsYDpO.mjs → utils-DddRwMsG.mjs} +2 -2
- package/dist/{utils-C9HsYDpO.mjs.map → utils-DddRwMsG.mjs.map} +1 -1
- package/dist/{zettelkasten-CtGHQWPU.mjs → zettelkasten-BhZMvmoK.mjs} +149 -7
- package/dist/zettelkasten-BhZMvmoK.mjs.map +1 -0
- package/dist/zettelkasten-BwV1pluL.mjs +5 -0
- package/docs/commands/worker.md +2 -0
- package/package.json +1 -1
- package/src/hooks/ts/lib/worker-guard.test.ts +12 -0
- package/src/hooks/ts/lib/worker-guard.ts +9 -0
- package/dist/async-C2Bm_Lal.mjs +0 -300
- package/dist/async-C2Bm_Lal.mjs.map +0 -1
- package/dist/auto-route-o0BOsXn7.mjs.map +0 -1
- package/dist/chunker-BH4i-F2b.mjs.map +0 -1
- package/dist/clusters-BCtD3fbe.mjs +0 -169
- package/dist/clusters-BCtD3fbe.mjs.map +0 -1
- package/dist/context-handover-cache-pkHzmL3c.mjs.map +0 -1
- package/dist/daemon-BEoVgVfJ.mjs +0 -1741
- package/dist/daemon-BEoVgVfJ.mjs.map +0 -1
- package/dist/daemon-DPAYU9hp.mjs +0 -19
- package/dist/detector-BEPFsINR.mjs +0 -3
- package/dist/detector-BRTtAWrM.mjs +0 -65
- package/dist/detector-BRTtAWrM.mjs.map +0 -1
- package/dist/factory-CNpPQQ-5.mjs +0 -249
- package/dist/factory-CNpPQQ-5.mjs.map +0 -1
- package/dist/factory-DY7x73mE.mjs +0 -3
- package/dist/fallback-B5svk75f.mjs.map +0 -1
- package/dist/federation-db-BTyoufBh.mjs +0 -139
- package/dist/federation-db-BTyoufBh.mjs.map +0 -1
- package/dist/federation-db-HfIFc7FG.mjs +0 -3
- package/dist/helpers-BRCJg0G3.mjs +0 -223
- package/dist/helpers-BRCJg0G3.mjs.map +0 -1
- package/dist/indexer-backend-DFF2FrYx.mjs +0 -5
- package/dist/indexer-backend-isSLg6yE.mjs +0 -1
- package/dist/kg-entity-DCOcsVFD.mjs +0 -29
- package/dist/kg-entity-DCOcsVFD.mjs.map +0 -1
- package/dist/latent-ideas-wwAXquGe.mjs +0 -191
- package/dist/latent-ideas-wwAXquGe.mjs.map +0 -1
- package/dist/link-boost-CnI7UVMJ.mjs +0 -34
- package/dist/link-boost-CnI7UVMJ.mjs.map +0 -1
- package/dist/main-resolver-BKz_OuEh.mjs +0 -7
- package/dist/merge-2gqRPFu2.mjs +0 -3
- package/dist/merge-DgU9OgZy.mjs +0 -6
- package/dist/merge-DgU9OgZy.mjs.map +0 -1
- package/dist/module-paths-DdRzbkUI.mjs +0 -44
- package/dist/module-paths-DdRzbkUI.mjs.map +0 -1
- package/dist/neighborhood-2FsmzoxG.mjs +0 -114
- package/dist/neighborhood-2FsmzoxG.mjs.map +0 -1
- package/dist/note-context-De63k8na.mjs +0 -106
- package/dist/note-context-De63k8na.mjs.map +0 -1
- package/dist/postgres-Ceqsa64C.mjs.map +0 -1
- package/dist/program-DSETzrWg.mjs.map +0 -1
- package/dist/query-feedback-B_iigYj-.mjs +0 -3
- package/dist/registry-db-C7voqML9.mjs +0 -213
- package/dist/registry-db-C7voqML9.mjs.map +0 -1
- package/dist/registry-db-JHPhA8vF.mjs +0 -3
- package/dist/registry-postgres-nOfyBSIW.mjs +0 -795
- package/dist/registry-postgres-nOfyBSIW.mjs.map +0 -1
- package/dist/registry-sqlite-DrCW1aRK.mjs +0 -593
- package/dist/registry-sqlite-DrCW1aRK.mjs.map +0 -1
- package/dist/router-CXUGsv85.mjs +0 -3
- package/dist/run-Z4akoa8G.mjs.map +0 -1
- package/dist/search-Bf3Kub3F.mjs +0 -3
- package/dist/server-DgmAHyFK.mjs +0 -403
- package/dist/server-DgmAHyFK.mjs.map +0 -1
- package/dist/session-keepalive-BDhytXEo.mjs +0 -582
- package/dist/session-keepalive-BDhytXEo.mjs.map +0 -1
- package/dist/sqlite-CqsTy6xo.mjs +0 -936
- package/dist/sqlite-CqsTy6xo.mjs.map +0 -1
- package/dist/state-DW8zdweW.mjs +0 -3
- package/dist/state-HyjqTihC.mjs +0 -76
- package/dist/state-HyjqTihC.mjs.map +0 -1
- package/dist/themes-CTaOj3e1.mjs +0 -148
- package/dist/themes-CTaOj3e1.mjs.map +0 -1
- package/dist/tools-BcOKPBtg.mjs +0 -1489
- package/dist/tools-BcOKPBtg.mjs.map +0 -1
- package/dist/tools-C-_l0o8Z.mjs +0 -6
- package/dist/trace-CtV0RO6n.mjs +0 -137
- package/dist/trace-CtV0RO6n.mjs.map +0 -1
- package/dist/utils-BrX9FLeK.mjs +0 -3
- package/dist/vault-indexer-BG1sEVyM.mjs +0 -537
- package/dist/vault-indexer-BG1sEVyM.mjs.map +0 -1
- package/dist/wakeup-D8n33hYV.mjs +0 -335
- package/dist/wakeup-D8n33hYV.mjs.map +0 -1
- package/dist/work-queue-worker-4tLa5gpQ.mjs +0 -567
- package/dist/work-queue-worker-4tLa5gpQ.mjs.map +0 -1
- package/dist/work-queue-worker-rFZvTO3p.mjs +0 -11
- package/dist/zettelkasten-CtGHQWPU.mjs.map +0 -1
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"program-DSETzrWg.mjs","names":["dim","ok","bold","getProject","upsertTag","dim","warn","bold","ok","cmdList","cmdInfo","cmdTag","upsertTag","getProject","getProject","ok","bold","dim","warn","cmdMcp","norm","cmdHealth","bold","dim","warn","ok","bold","dim","warn","ok","dim","bold","ok","bold","dim","warn","ok","cmdList","warn","dim","ok","cmdInfo","cmdTag","cmdHealth","cmdMcp","cmdMigrate","warn","dim","slugify","ok","bold","ok","dim","warn","ok","dim","warn","cmdStats","bold","warn","dim","ok","cmdMigrate","dim","ok","bold","ok","dim","bold","warn","dim","bold","bold","dim","ok","dim","warn","bold","dim","ok","loadState","saveState","HOME","CONFIG_FILE","BACKUPS_DIR","DOCKER_CONTAINER","PG_DATABASE","PG_USER","dim","ok","warn","bold","findClaudeNotesDir","findClaudeNotesDir","dim","ok","warn","cmdStatus","bold","CONFIG_FILE","bold","schedulerStatePath","dim","ok","expandHome","cmdInstall","warn","dim","ok","cmdStatus","bold","HOME","PLIST_PATH","ok","warn","dim","cmdStatus","bold","dim","bold","ok","warn","line","prompt","getTemplatesDir","prompt","CONFIG_FILE","getTemplatesDir","getTemplatesDir","getTemplatesDir","useSymlinks","prompt","CONFIG_FILE","normalize","normalizeName","prompt","line","step","CONFIG_FILE","prompt","dim","warn","bold","ok","dim","bold","ok","warn","dim","warn","bold","dim","warn","bold","dim","warn","bold","dim","warn","bold","ipcCall","fmtTs","dim","trunc","cmdList","warn","bold","cmdStats","unquote","readEntry","warn","ok","dim","bold","stripAnsi","fmtTs","dim","statusColor","warn","bold","dim","ok","warn","run","makeClient","warn","bold","ok","dim","run","CONFIG_FILE","loadState","bold","dim","warn","ok","dim","CONFIG_FILE","shorten","bold","warn","ok","cmdQuery","cmdList","dim","bold","dim","tokenize","getProject","toTitleCase","getProject","warn","dim","bold","toTitleCase","ok","findNotesDir","dim","fmtAge","fmtAge","bold","ok","dim","warn","fail","parseIntArg","dim","ok","fail","ok","dim","fail","ok","fail","ok","dim","CONFIG_FILE","warn","expandHome","sleep","dim","bold","dim","ok","dim","bold","warn","ok","dim","ok","findNotesDir","dim","warn","ok","dim","ok","warn","warn","dim","normalizeName","dim","fmtAge","warn","dim","ok","cmdNotesList"],"sources":["../src/registry/registrable.ts","../src/session/promote.ts","../src/cli/commands/project/helpers.ts","../src/cli/commands/project/commands.ts","../src/cli/commands/project/session-config.ts","../src/cli/commands/project/relocate.ts","../src/cli/commands/project/health.ts","../src/cli/commands/project/merge.ts","../src/cli/commands/project/here.ts","../src/cli/commands/project/unregister.ts","../src/cli/commands/project/projects-index.ts","../src/cli/commands/registry/migrate.ts","../src/cli/commands/registry/dedupe.ts","../src/storage/paths.ts","../src/storage/sqlite/legacy-migration.ts","../src/cli/commands/registry/reconnect.ts","../src/cli/commands/registry/index.ts","../src/cli/commands/memory/embed.ts","../src/cli/commands/memory/index-cmd.ts","../src/cli/commands/memory/search.ts","../src/cli/commands/memory/stats.ts","../src/cli/commands/memory/sources-cmd.ts","../src/cli/commands/memory/index.ts","../src/identity/index.ts","../src/cli/commands/identity.ts","../src/config/voices-config.ts","../src/tasks/dispatch.ts","../src/tasks/scheduler.ts","../src/tasks/poller.ts","../src/storage/backup.ts","../src/cli/commands/backup.ts","../src/obsidian/sync/symlinks.ts","../src/obsidian/sync/generate.ts","../src/obsidian/sync/walk.ts","../src/obsidian/sync/master.ts","../src/obsidian/status.ts","../src/cli/commands/obsidian.ts","../src/config/adapter-content.ts","../src/config/pai-files.ts","../src/memory/kg-backfill.ts","../src/workers/logs-migrate.ts","../src/cli/commands/config.ts","../src/config/claude-json.ts","../src/cli/commands/mcp.ts","../src/service-manager.ts","../src/cli/commands/daemon-status.ts","../src/cli/commands/daemon.ts","../src/cli/commands/hooks-db.ts","../src/cli/commands/restore.ts","../src/cli/lib/exit.ts","../src/cli/commands/setup/utils.ts","../src/cli/commands/setup/steps/01-welcome.ts","../src/cli/commands/setup/steps/02-storage.ts","../src/cli/commands/setup/steps/03-embedding.ts","../src/cli/commands/setup/steps/04-claude-md.ts","../src/cli/commands/setup/steps/05-pai-skill.ts","../src/cli/commands/setup/steps/06-steering-rules.ts","../src/cli/commands/setup/steps/07-skill-stubs.ts","../src/cli/commands/setup/steps/08-hooks.ts","../src/cli/commands/setup/steps/09-ts-hooks.ts","../src/cli/commands/settings-manager.ts","../src/cli/commands/setup/steps/10-settings.ts","../src/cli/commands/setup/steps/11-daemon.ts","../src/cli/commands/setup/steps/12-mcp.ts","../src/cli/commands/setup/steps/13-directories.ts","../src/cli/commands/setup/steps/14-initial-index.ts","../src/cli/commands/setup/steps/15-verify.ts","../src/tasks/resolver.ts","../src/tasks/providers/todoist.ts","../src/cli/commands/setup/steps/16-task-bus.ts","../src/workers/install.ts","../src/cli/commands/setup/steps/17-workers.ts","../src/cli/commands/setup/index.ts","../src/cli/commands/zettel/utils.ts","../src/cli/commands/zettel/explore.ts","../src/cli/commands/zettel/health.ts","../src/cli/commands/zettel/surprise.ts","../src/cli/commands/zettel/suggest.ts","../src/cli/commands/zettel/converse.ts","../src/cli/commands/zettel/themes.ts","../src/cli/commands/zettel/index.ts","../src/cli/commands/observation.ts","../src/audit/tokens.ts","../src/audit/files.ts","../src/audit/hooks.ts","../src/audit/first-turn.ts","../src/hooks/ts/lib/context-fill.ts","../src/audit/context-trigger.ts","../src/audit/session.ts","../src/audit/spawn.ts","../src/audit/daemon.ts","../src/audit/env.ts","../src/audit/schedule.ts","../src/audit/skills.ts","../src/audit/severity.ts","../src/audit/ladder.ts","../src/audit/subagents.ts","../src/audit/mcp.ts","../src/cli/commands/audit.ts","../src/cli/commands/skill.ts","../src/cli/commands/update.ts","../src/cli/commands/notify.ts","../src/tasks/transport/aibroker.ts","../src/tasks/schedule-install.ts","../src/tasks/archive.ts","../src/tasks/projects.ts","../src/cli/commands/task.ts","../src/cli/commands/topic.ts","../src/cli/commands/kg.ts","../src/storage/migrate-to-postgres.ts","../src/storage/db-admin.ts","../src/cli/commands/db.ts","../src/cli/commands/help.ts","../src/session/slug-generator.ts","../src/cli/commands/session/helpers.ts","../src/cli/commands/session/commands.ts","../src/cli/commands/session/checkpoint.ts","../src/cli/commands/session/pause.ts","../src/session/autosave.ts","../src/cli/commands/session/handover.ts","../src/hooks/ts/lib/worker-session.ts","../src/session/context-handover-trigger.ts","../src/cli/commands/session/autosave.ts","../src/cli/commands/session/recent.ts","../src/cli/commands/session/goto.ts","../src/cli/commands/session/restore.ts","../src/cli/commands/session/index.ts","../src/workers/agents.ts","../src/cli/commands/worker/providers.ts","../src/cli/commands/worker/model.ts","../src/cli/commands/worker/capability.ts","../src/cli/commands/worker/config.ts","../src/workers/pending.ts","../src/workers/wait.ts","../src/cli/commands/worker/index.ts","../src/workers/launch.ts","../src/cli/commands/launch.ts","../src/cli/commands/session-cleanup/types.ts","../src/cli/commands/session-cleanup/rename.ts","../src/cli/commands/session-cleanup/scanner.ts","../src/cli/commands/session-cleanup/executor.ts","../src/cli/commands/session-cleanup/index.ts","../src/cli/commands/session/end.ts","../src/cli/commands/session/pause-all.ts","../src/cli/commands/session/clear-names.ts","../src/cli/commands/find.ts","../src/cli/commands/pick.ts","../src/cli/program.ts"],"sourcesContent":["/**\n * registrable.ts — directories that must never become registered projects.\n *\n * A project is a durable thing. The registry, however, has been accepting\n * whatever directory a session happened to start in, and some of those\n * directories are disposable by construction. Measured on the real registry,\n * 2026-08-04:\n *\n * 08 - Others/MDF/Infrastruktur/.claude/worktrees/cool-haibt 1 session\n * 08 - Others/MDF/Infrastruktur/.claude/worktrees/strange-haibt 7 sessions\n * /private/tmp/ops-webui dead\n * /private/tmp/claude-501/-Users-…-AIBroker/aae854c6-… dead\n *\n * The worktrees are agent isolation directories, created to be removed. The temp\n * paths are exactly what their name says. Registering them attributes session\n * history to a location with no future, and lets `pai <name>` route someone into\n * a directory a cleanup can delete underneath them.\n *\n * So this refuses at the point of registration rather than reclassifying\n * afterwards. It is a guard, not a policy about what health should call things —\n * it stops the set growing while the vocabulary question (dead vs duplicate vs\n * misnamed vs ephemeral) is decided separately.\n *\n * Found by the AIBroker session while we were dividing up the dead-path work.\n */\n\nimport { sep } from \"node:path\";\n\n/** Path fragments that mark a location as disposable, with why. */\nconst EPHEMERAL: ReadonlyArray<{ fragment: string; because: string }> = [\n {\n // Agent worktrees. `.claude/worktrees/<name>` is created for isolation during\n // one task and removed afterwards.\n fragment: `${sep}.claude${sep}worktrees${sep}`,\n because: \"a git worktree created for agent isolation — it is meant to be removed\",\n },\n {\n fragment: `${sep}private${sep}tmp${sep}`,\n because: \"a system temp directory\",\n },\n {\n fragment: `${sep}var${sep}folders${sep}`,\n because: \"a macOS per-user temp directory\",\n },\n];\n\n/**\n * Why this path cannot be a project, or undefined if it can.\n *\n * Returns the reason rather than a boolean so the caller can tell the user which\n * rule caught them — \"refused\" without a reason invites someone to work around it\n * rather than move their project somewhere durable.\n */\nexport function unregistrableReason(rootPath: string): string | undefined {\n // Compare with trailing separators so a fragment cannot match a partial\n // directory name: `/tmp/` must not match `/tmpdir/`.\n const padded = rootPath.endsWith(sep) ? rootPath : rootPath + sep;\n\n for (const { fragment, because } of EPHEMERAL) {\n if (padded.includes(fragment)) return because;\n }\n\n // A bare `/tmp/...` too. Kept separate from the list above because on macOS\n // /tmp is a symlink to /private/tmp, so both spellings reach the same place and\n // only one of them contains \"private\".\n if (padded.startsWith(`${sep}tmp${sep}`)) return \"a system temp directory\";\n\n return undefined;\n}\n","/**\n * pai project promote --from-session <path> --to <new-project-path> [--name \"Name\"]\n *\n * Promotes a session note into a new standalone project:\n * 1. Validates inputs\n * 2. Scaffolds the new project directory\n * 3. Copies the session note and creates a MEMORY.md\n * 4. Registers the new project in the PAI registry\n * 5. Optionally backlinks from the source project's TODO.md\n */\n\nimport {\n existsSync,\n mkdirSync,\n copyFileSync,\n readFileSync,\n appendFileSync,\n writeFileSync,\n} from \"node:fs\";\nimport { join, basename, dirname, resolve } from \"node:path\";\nimport {\n ok,\n warn,\n err,\n dim,\n bold,\n slugify,\n encodeDir,\n resolvePath,\n scaffoldProjectDirs,\n now,\n} from \"../cli/utils.js\";\nimport { unregistrableReason } from \"../registry/registrable.js\";\n\n// ---------------------------------------------------------------------------\n// Types\n// ---------------------------------------------------------------------------\n\nexport interface PromoteOptions {\n fromSession: string;\n to: string;\n name?: string;\n}\n\n// ---------------------------------------------------------------------------\n// Helpers\n// ---------------------------------------------------------------------------\n\n/**\n * Derive a human-readable project name from a session note filename.\n * \"0042 - 2026-02-24 - Dark Mode Implementation.md\" → \"Dark Mode Implementation\"\n */\nfunction deriveNameFromFilename(filename: string): string {\n const base = basename(filename, \".md\");\n // Strip leading \"NNNN - YYYY-MM-DD - \" prefix if present\n const match = base.match(/^\\d+ - \\d{4}-\\d{2}-\\d{2} - (.+)$/);\n return match ? match[1] : base;\n}\n\n/**\n * Extract the first N H2 (##) section headings from markdown content.\n */\nfunction extractH2Headings(content: string, limit = 2): string[] {\n const headings: string[] = [];\n for (const m of content.matchAll(/^## .+$/gm)) {\n headings.push(m[0]);\n if (headings.length >= limit) break;\n }\n return headings;\n}\n\n/**\n * Find the source project root from the session note path.\n * Convention: session note lives at {project-root}/Notes/{filename}\n * So: dirname(dirname(sessionPath)) = project root.\n */\nfunction findSourceProjectRoot(sessionPath: string): string {\n const notesDir = dirname(resolve(sessionPath));\n return dirname(notesDir);\n}\n\n// ---------------------------------------------------------------------------\n// Main implementation\n// ---------------------------------------------------------------------------\n\nexport async function cmdPromote(opts: PromoteOptions): Promise<void> {\n const sessionPath = resolvePath(opts.fromSession);\n const targetPath = resolvePath(opts.to);\n\n // ---- Validate inputs ----\n\n if (!existsSync(sessionPath)) {\n console.error(err(`Session note not found: ${sessionPath}`));\n process.exit(1);\n }\n if (!sessionPath.endsWith(\".md\")) {\n console.error(err(`--from-session must be a markdown (.md) file: ${sessionPath}`));\n process.exit(1);\n }\n if (existsSync(targetPath)) {\n console.error(err(`Target path already exists: ${targetPath}`));\n process.exit(1);\n }\n\n // ---- Derive project name and slug ----\n\n const displayName = opts.name ?? deriveNameFromFilename(sessionPath);\n const slug = slugify(displayName);\n\n if (!slug) {\n console.error(err(`Could not derive a valid slug from name: \"${displayName}\"`));\n process.exit(1);\n }\n\n // ---- Refuse disposable locations ----\n //\n // Same guard as `pai project add`. Promoting a session INTO a worktree or a\n // temp directory would create a project whose home is designed to be deleted.\n const ephemeral = unregistrableReason(targetPath);\n if (ephemeral) {\n console.error(err(`Refusing to promote into ${targetPath}`));\n console.error(dim(` That is ${ephemeral}.`));\n console.error(dim(` Pick a durable location for the new project.`));\n process.exit(1);\n }\n\n // ---- Check registry for conflicts ----\n\n const { getRegistryBackend } = await import(\"../storage/factory.js\");\n const registryBackend = await getRegistryBackend();\n\n const encodedDir = encodeDir(targetPath);\n const existing =\n (await registryBackend.getProjectBySlug(slug)) ??\n (await registryBackend.getProjectByRootPath(targetPath)) ??\n (await registryBackend.getProjectByEncodedDir(encodedDir));\n if (existing) {\n console.error(\n err(`A project with slug \"${slug}\" or path \"${targetPath}\" is already registered.`)\n );\n process.exit(1);\n }\n\n // ---- Scaffold new project ----\n\n scaffoldProjectDirs(targetPath);\n\n // ---- Copy session note ----\n\n const today = new Date().toISOString().slice(0, 10);\n const sourceBasename = basename(sessionPath);\n const sourceProjectRoot = findSourceProjectRoot(sessionPath);\n const sourceProjectName = basename(sourceProjectRoot);\n\n const destNoteName = `0001 - ${today} - Promoted from ${sourceProjectName}.md`;\n const destNotePath = join(targetPath, \"Notes\", destNoteName);\n\n copyFileSync(sessionPath, destNotePath);\n\n // ---- Create MEMORY.md ----\n\n const sessionContent = readFileSync(sessionPath, \"utf-8\");\n const headings = extractH2Headings(sessionContent);\n\n const memoryContent = [\n `# Project Memory`,\n ``,\n `## Origin`,\n ``,\n `- **Promoted from:** ${sessionPath}`,\n `- **Source project:** ${sourceProjectRoot}`,\n `- **Date of promotion:** ${today}`,\n `- **Original session note:** ${sourceBasename}`,\n ``,\n `## Initial Context`,\n ``,\n headings.length > 0\n ? `Key topics from the source session:\\n\\n${headings.map((h) => `- ${h.replace(/^## /, \"\")}`).join(\"\\n\")}`\n : `(Extracted from session note — see ${destNoteName})`,\n ``,\n ].join(\"\\n\");\n\n writeFileSync(join(targetPath, \"Notes\", \"MEMORY.md\"), memoryContent, \"utf-8\");\n\n // ---- Register in PAI registry ----\n\n const ts = now();\n await registryBackend.createProject({\n slug,\n displayName,\n rootPath: targetPath,\n encodedDir,\n type: \"local\",\n status: \"active\",\n createdAt: ts,\n updatedAt: ts,\n });\n\n // ---- Backlink in source TODO.md ----\n\n const sourceTodoPath = join(sourceProjectRoot, \"Notes\", \"TODO.md\");\n if (existsSync(sourceTodoPath)) {\n const backlink = `\\n- Promoted to project: [${slug}](${targetPath})\\n`;\n appendFileSync(sourceTodoPath, backlink, \"utf-8\");\n console.log(dim(` Backlink added to: ${sourceTodoPath}`));\n }\n\n // ---- Success output ----\n\n console.log();\n console.log(ok(`Project promoted: ${bold(slug)}`));\n console.log(dim(` Display name: ${displayName}`));\n console.log(dim(` Path: ${targetPath}`));\n console.log(dim(` Slug: ${slug}`));\n console.log(dim(` Session note: ${destNoteName}`));\n console.log(dim(` Memory: Notes/MEMORY.md created`));\n console.log();\n console.log(dim(` Next: cd ${targetPath} && pai session list ${slug}`));\n}\n","/**\n * Shared registry helper functions for project sub-commands.\n * Each function asks the process-wide RegistryBackend itself rather than\n * taking a database handle — callers just `await` them.\n */\n\nimport { getRegistryBackend } from \"../../../storage/factory.js\";\nimport type { ProjectRow } from \"./types.js\";\n\n/**\n * Find a project by slug or alias, case-insensitively.\n *\n * Slugs are conventionally lowercase, so exact matching went unnoticed until a\n * project was deliberately named `Solar` — after which `pai project info solar`\n * reported \"Project not found\" for a project sitting right there. Nobody types\n * a name back with its capitalisation intact, and a lookup that demands it will\n * be wrong more often than the user is.\n *\n * Exact match is still tried first: where two projects differ only by case, the\n * one asked for by its exact name wins rather than whichever the database\n * returns first.\n */\nexport async function getProject(slug: string): Promise<ProjectRow | undefined> {\n const backend = await getRegistryBackend();\n\n const direct = await backend.getProjectBySlug(slug);\n if (direct) return direct;\n\n const alias = await backend.getProjectByAlias(slug);\n if (alias) return alias;\n\n const insensitive = await backend.getProjectBySlug(slug, { caseInsensitive: true });\n if (insensitive) return insensitive;\n\n const aliasInsensitive = await backend.getProjectByAlias(slug, { caseInsensitive: true });\n return aliasInsensitive ?? undefined;\n}\n\nexport async function requireProject(slug: string): Promise<ProjectRow> {\n const project = await getProject(slug);\n if (!project) {\n // Throw rather than process.exit(1): it propagates to the top-level\n // catch in cli/index.ts, which prints the message and sets\n // process.exitCode, letting the process exit naturally so piped\n // stdout/stderr is flushed (process.exit() does not guarantee that).\n throw new Error(`Project not found: ${slug}`);\n }\n return project;\n}\n\n/**\n * Resolve an identifier that may be a list index number or a slug.\n */\nexport async function resolveIdentifier(identifier: string): Promise<ProjectRow | undefined> {\n const num = parseInt(identifier, 10);\n if (!isNaN(num) && num > 0 && String(num) === identifier) {\n const backend = await getRegistryBackend();\n const rows = await backend.listProjects({ orderBy: \"status_updated\" });\n if (num <= rows.length) return rows[num - 1];\n }\n return getProject(identifier);\n}\n\nexport async function getProjectTags(projectId: number): Promise<string[]> {\n const backend = await getRegistryBackend();\n return backend.listTagsForProject(projectId);\n}\n\nexport async function getProjectAliases(projectId: number): Promise<string[]> {\n const backend = await getRegistryBackend();\n return backend.listAliasesForProject(projectId);\n}\n\nexport async function getSessionCount(projectId: number): Promise<number> {\n const backend = await getRegistryBackend();\n return backend.countSessionsForProject(projectId);\n}\n\nexport async function getLastSessionDate(projectId: number): Promise<number | null> {\n const backend = await getRegistryBackend();\n const sessions = await backend.listSessionsForProject(projectId, { orderBy: \"created_desc\", limit: 1 });\n return sessions[0]?.created_at ?? null;\n}\n\nexport async function upsertTag(tagName: string): Promise<number> {\n const backend = await getRegistryBackend();\n return backend.upsertTag(tagName);\n}\n","/**\n * Core project CRUD commands: add, list, info, archive, unarchive, move, tag,\n * alias, edit, detect, consolidate, go, rebind — plus private helpers levenshtein,\n * containsIgnoreCase, findProjectNotesDirs, and findMovedPath.\n */\n\nimport {\n existsSync,\n readdirSync,\n statSync,\n mkdirSync,\n renameSync,\n} from \"node:fs\";\nimport { join, basename, resolve } from \"node:path\";\nimport { homedir } from \"node:os\";\nimport chalk from \"chalk\";\nimport { unregistrableReason } from \"../../../registry/registrable.js\";\nimport { getRegistryBackend } from \"../../../storage/factory.js\";\nimport type { Project } from \"../../../storage/registry-interface.js\";\nimport {\n ok,\n warn,\n err,\n dim,\n bold,\n header,\n slugFromPath,\n encodeDir,\n resolvePath,\n scaffoldProjectDirs,\n renderTable,\n shortenPath,\n fmtDate,\n now,\n} from \"../../utils.js\";\nimport {\n detectProject,\n formatDetection,\n formatDetectionJson,\n} from \"../detect.js\";\nimport { ensurePaiMarker } from \"../../../registry/pai-marker.js\";\nimport type { ProjectRow, SessionRow } from \"./types.js\";\nimport {\n requireProject,\n resolveIdentifier,\n getProject,\n getProjectTags,\n getProjectAliases,\n getSessionCount,\n getLastSessionDate,\n upsertTag,\n} from \"./helpers.js\";\nimport { loadScanConfig, resolveHome } from \"../registry/scan.js\";\n\n// ---------------------------------------------------------------------------\n// Private helpers\n// ---------------------------------------------------------------------------\n\nfunction levenshtein(a: string, b: string): number {\n const m = a.length;\n const n = b.length;\n const dp: number[][] = Array.from({ length: m + 1 }, (_, i) =>\n Array.from({ length: n + 1 }, (_, j) => (i === 0 ? j : j === 0 ? i : 0))\n );\n for (let i = 1; i <= m; i++) {\n for (let j = 1; j <= n; j++) {\n dp[i][j] =\n a[i - 1] === b[j - 1]\n ? dp[i - 1][j - 1]\n : 1 + Math.min(dp[i - 1][j], dp[i][j - 1], dp[i - 1][j - 1]);\n }\n }\n return dp[m][n];\n}\n\nfunction containsIgnoreCase(haystack: string, needle: string): boolean {\n return haystack.toLowerCase().includes(needle.toLowerCase());\n}\n\nfunction findProjectNotesDirs(project: ProjectRow): {\n encodedDir: string;\n fullPath: string;\n notesPath: string;\n noteCount: number;\n}[] {\n const claudeProjects = join(homedir(), \".claude\", \"projects\");\n if (!existsSync(claudeProjects)) return [];\n\n const results: {\n encodedDir: string;\n fullPath: string;\n notesPath: string;\n noteCount: number;\n }[] = [];\n const rootEncoded = encodeDir(project.root_path);\n\n try {\n for (const entry of readdirSync(claudeProjects)) {\n const full = join(claudeProjects, entry);\n try {\n if (!statSync(full).isDirectory()) continue;\n } catch {\n continue;\n }\n\n if (entry !== rootEncoded && !entry.startsWith(rootEncoded)) continue;\n\n const notesPath = join(full, \"Notes\");\n if (!existsSync(notesPath)) continue;\n\n let noteCount = 0;\n try {\n noteCount = readdirSync(notesPath).filter(\n (f) => f.endsWith(\".md\") || f.endsWith(\".txt\")\n ).length;\n } catch {\n // count stays 0\n }\n\n results.push({ encodedDir: entry, fullPath: full, notesPath, noteCount });\n }\n } catch {\n // Unreadable — ignore\n }\n\n return results;\n}\n\n// ---------------------------------------------------------------------------\n// Moved-project detection\n// ---------------------------------------------------------------------------\n\n// Directories to skip during filesystem walk\nconst SKIP_DIRS = new Set([\n \".git\", \"node_modules\", \".next\", \".nuxt\", \"dist\", \"build\", \"coverage\",\n \".cache\", \"__pycache__\", \"vendor\", \".svn\", \".hg\", \"venv\", \".venv\",\n \"target\", // Rust/Java build dir\n \"Notes\", // PAI session notes — not project roots\n]);\n\n/**\n * Walk `dir` up to `maxDepth` levels deep, collecting all subdirectory paths\n * whose basename matches `targetBasename` (case-insensitive on macOS/case-sensitive\n * on Linux, whichever is appropriate — we use exact case matching to keep it\n * correct and fast).\n *\n * Skips hidden directories and known build/tool dirs.\n * Stops walking a branch if the deadline is exceeded (returns partial results).\n */\nfunction walkForBasename(\n dir: string,\n targetBasename: string,\n maxDepth: number,\n deadline: number,\n results: string[]\n): void {\n if (Date.now() > deadline) return;\n if (maxDepth <= 0) return;\n\n let entries: string[];\n try {\n entries = readdirSync(dir);\n } catch {\n return;\n }\n\n for (const entry of entries) {\n if (Date.now() > deadline) return;\n if (entry.startsWith(\".\")) continue;\n if (SKIP_DIRS.has(entry)) continue;\n\n const full = join(dir, entry);\n let isDir = false;\n try {\n isDir = statSync(full).isDirectory();\n } catch {\n continue;\n }\n if (!isDir) continue;\n\n // Case-insensitive comparison for the target basename (handles macOS APFS)\n if (entry.toLowerCase() === targetBasename.toLowerCase()) {\n results.push(full);\n // Don't recurse into the match itself — we want the directory, not children\n continue;\n }\n\n walkForBasename(full, targetBasename, maxDepth - 1, deadline, results);\n }\n}\n\n/**\n * Try to find where a project has moved.\n *\n * Searches scan_dirs from config (plus a set of common fallback dirs) for a\n * directory whose basename matches the basename of the registered root_path.\n *\n * Returns:\n * { found: string } — exactly one match found\n * { ambiguous: string[] } — multiple matches found\n * { found: undefined } — no match found (or timed out)\n */\nexport function findMovedPath(registeredPath: string): { found?: string; ambiguous?: string[] } {\n const targetBasename = basename(registeredPath);\n const config = loadScanConfig();\n const deadline = Date.now() + 5_000; // 5-second max\n\n // Build the list of top-level directories to search\n const home = homedir();\n const configDirs = (config.scan_dirs ?? []).map((d) => resolveHome(d));\n\n // Walk UP the registered path to find the deepest existing ancestor.\n // This is the best search root when a project has moved within the same tree.\n // e.g. ~/Cloud/Ablage/2025/Project → ~/Cloud/Ablage/2026/Project\n // Deepest existing ancestor: ~/Cloud/Ablage (if 2025 is gone but Ablage exists)\n function deepestExistingAncestor(p: string): string | null {\n let current = p;\n while (true) {\n const parent = join(current, \"..\");\n if (parent === current) return null; // filesystem root\n if (parent === home) return null; // don't use home itself as search root\n if (existsSync(parent)) return parent;\n current = parent;\n }\n }\n\n const ancestorRoot = deepestExistingAncestor(registeredPath);\n\n const fallbackDirs = [\n join(home, \"dev\"),\n join(home, \"Desktop\"),\n join(home, \"Projects\"),\n join(home, \"Documents\"),\n // Common cloud-storage root folders (Synology Drive, iCloud, etc.)\n join(home, \"Cloud\"),\n join(home, \"Daten\", \"Cloud\"),\n join(home, \"Daten\"),\n join(home, \"Library\", \"Mobile Documents\", \"com~apple~CloudDocs\"), // iCloud Drive\n // The deepest ancestor of the registered path that still exists on disk\n ...(ancestorRoot ? [ancestorRoot] : []),\n ];\n\n // Deduplicate and filter to existing directories\n const searchRoots = [...new Set([...configDirs, ...fallbackDirs])]\n .map((d) => {\n try { return resolve(d); } catch { return null; }\n })\n .filter((d): d is string => d !== null && existsSync(d));\n\n const results: string[] = [];\n for (const root of searchRoots) {\n if (Date.now() > deadline) break;\n walkForBasename(root, targetBasename, 6, deadline, results);\n }\n\n // Deduplicate results (different roots might overlap)\n const unique = [...new Set(results)];\n\n if (unique.length === 0) return {};\n if (unique.length === 1) return { found: unique[0] };\n return { ambiguous: unique };\n}\n\n// ---------------------------------------------------------------------------\n// Command implementations\n// ---------------------------------------------------------------------------\n\nexport async function cmdAdd(\n rawPath: string,\n opts: { slug?: string; type?: string; displayName?: string }\n): Promise<void> {\n const backend = await getRegistryBackend();\n const rootPath = resolvePath(rawPath);\n const slug = opts.slug ?? slugFromPath(rootPath);\n const encodedDir = encodeDir(rootPath);\n const displayName = opts.displayName ?? slug;\n const type = opts.type ?? \"local\";\n\n const validTypes = [\"local\", \"central\", \"obsidian-linked\", \"external\"];\n if (!validTypes.includes(type)) {\n console.error(err(`Invalid type \"${type}\". Valid: ${validTypes.join(\", \")}`));\n process.exitCode = 1;\n return;\n }\n\n // Disposable directories must not become projects. Two agent worktrees and two\n // /private/tmp paths got in this way and between them carried 8 sessions\n // attributed to locations with no future — and `pai <name>` will happily route\n // someone into a worktree a cleanup can delete underneath them.\n const ephemeral = unregistrableReason(rootPath);\n if (ephemeral) {\n console.error(err(`Refusing to register ${shortenPath(rootPath, 60)}`));\n console.error(dim(` That is ${ephemeral}.`));\n console.error(dim(` A project needs a durable location — move it, then register.`));\n process.exitCode = 1;\n return;\n }\n\n const existing = (await backend.getProjectBySlug(slug)) ?? (await backend.getProjectByRootPath(rootPath));\n if (existing) {\n console.error(\n err(`Project already registered (slug: ${slug} or path: ${rootPath})`)\n );\n process.exitCode = 1;\n return;\n }\n\n const dirName = basename(rootPath).toLowerCase();\n const active = await backend.listProjects({ status: \"active\" });\n const similar = active.filter((p) => p.slug !== slug).map((p) => ({ slug: p.slug, root_path: p.root_path }));\n const matches = similar.filter(\n (s) =>\n basename(s.root_path).toLowerCase() === dirName ||\n s.slug.replace(/-\\d+$/, \"\") === slug.replace(/-\\d+$/, \"\")\n );\n if (matches.length > 0) {\n console.log(warn(`Similar project(s) already registered:`));\n for (const m of matches) {\n console.log(dim(` ${bold(m.slug)} ${shortenPath(m.root_path, 50)}`));\n }\n console.log(\n dim(\n ` Consider: pai project alias ${matches[0].slug} <name> (to link them)`\n )\n );\n console.log(\n dim(` Or: pai project archive ${slug} (if this is a duplicate)`)\n );\n console.log();\n }\n\n const ts = now();\n await backend.createProject({\n slug,\n displayName,\n rootPath,\n encodedDir,\n type: type as Project[\"type\"],\n status: \"active\",\n createdAt: ts,\n updatedAt: ts,\n });\n\n scaffoldProjectDirs(rootPath);\n\n try {\n ensurePaiMarker(rootPath, slug, displayName);\n } catch {\n // Non-fatal — warn but don't fail the add command.\n }\n\n console.log(ok(`Project added: ${bold(slug)}`));\n console.log(dim(` Path: ${rootPath}`));\n console.log(dim(` Encoded dir: ${encodedDir}`));\n console.log(dim(` Type: ${type}`));\n}\n\nexport async function cmdList(\n opts: { status?: string; tag?: string; type?: string; all?: boolean }\n): Promise<void> {\n const backend = await getRegistryBackend();\n\n let tagId: number | undefined;\n if (opts.tag) {\n const tags = await backend.listAllTags();\n tagId = tags.find((t) => t.name === opts.tag)?.id;\n if (tagId === undefined) {\n console.log(warn(\"No projects found.\"));\n return;\n }\n }\n\n const rows = (\n await backend.listProjectsWithSessionStats({\n status: opts.status ? (opts.status as Project[\"status\"]) : opts.all ? undefined : \"active\",\n tagId,\n orderBy: \"status_updated\",\n })\n ).filter((p) => !opts.type || p.type === opts.type);\n\n if (!rows.length) {\n console.log(warn(\"No projects found.\"));\n return;\n }\n\n const tableRows = rows.map((r, i) => [\n dim(String(i + 1)),\n bold(r.display_name ?? r.slug),\n dim(r.slug),\n dim(shortenPath(r.root_path, 44)),\n r.status === \"active\" ? chalk.green(r.status) : chalk.yellow(r.status),\n dim(r.type),\n String(r.session_count),\n fmtDate(r.last_active),\n ]);\n\n console.log(\n renderTable(\n [\"#\", \"Name\", \"Slug\", \"Path\", \"Status\", \"Type\", \"Sessions\", \"Last Active\"],\n tableRows\n )\n );\n console.log();\n\n // When showing active-only, inform the user about hidden archived projects\n const total = await backend.countProjects();\n const hiddenCount = total - rows.length;\n if (!opts.all && !opts.status && hiddenCount > 0) {\n console.log(dim(` ${rows.length} active project(s) (${hiddenCount} archived — use --all to show)`));\n } else {\n console.log(dim(` ${rows.length} project(s)`));\n }\n}\n\nexport async function cmdInfo(identifier: string): Promise<void> {\n const project =\n (await resolveIdentifier(identifier)) ?? (await requireProject(identifier));\n const backend = await getRegistryBackend();\n const tags = await getProjectTags(project.id);\n const aliases = await getProjectAliases(project.id);\n const sessionCount = await getSessionCount(project.id);\n const lastSession = await getLastSessionDate(project.id);\n\n const recentSessions: SessionRow[] = await backend.listSessionsForProject(project.id, {\n orderBy: \"created_desc\",\n limit: 5,\n });\n\n console.log();\n console.log(header(` ${project.display_name}`));\n console.log();\n console.log(` ${bold(\"Slug:\")} ${project.slug}`);\n console.log(` ${bold(\"Path:\")} ${project.root_path}`);\n console.log(` ${bold(\"Encoded dir:\")} ${project.encoded_dir}`);\n console.log(` ${bold(\"Type:\")} ${project.type}`);\n console.log(\n ` ${bold(\"Status:\")} ${\n project.status === \"active\"\n ? chalk.green(project.status)\n : chalk.yellow(project.status)\n }`\n );\n console.log(\n ` ${bold(\"Tags:\")} ${\n tags.length ? tags.map((t) => chalk.cyan(t)).join(\", \") : dim(\"none\")\n }`\n );\n console.log(\n ` ${bold(\"Aliases:\")} ${aliases.length ? aliases.join(\", \") : dim(\"none\")}`\n );\n console.log(` ${bold(\"Sessions:\")} ${sessionCount}`);\n console.log(` ${bold(\"Last active:\")} ${fmtDate(lastSession)}`);\n console.log(` ${bold(\"Created:\")} ${fmtDate(project.created_at)}`);\n if (project.archived_at) {\n console.log(` ${bold(\"Archived:\")} ${fmtDate(project.archived_at)}`);\n }\n\n if (recentSessions.length) {\n console.log();\n console.log(` ${bold(\"Recent sessions:\")}`);\n const sessionRows = recentSessions.map((s) => [\n dim(`#${s.number}`),\n s.date,\n s.title.length > 50 ? s.title.slice(0, 47) + \"...\" : s.title,\n s.status === \"completed\"\n ? chalk.green(s.status)\n : chalk.yellow(s.status),\n ]);\n console.log(\n renderTable([\"#\", \"Date\", \"Title\", \"Status\"], sessionRows)\n .split(\"\\n\")\n .map((l) => \" \" + l)\n .join(\"\\n\")\n );\n }\n console.log();\n}\n\nexport async function cmdArchive(slug: string): Promise<void> {\n const project = await requireProject(slug);\n if (project.status === \"archived\") {\n console.log(warn(`Project ${slug} is already archived.`));\n return;\n }\n const ts = now();\n const backend = await getRegistryBackend();\n await backend.updateProjectStatus(project.id, \"archived\", { archivedAt: ts, updatedAt: ts });\n console.log(ok(`Archived: ${bold(slug)}`));\n}\n\nexport async function cmdUnarchive(slug: string): Promise<void> {\n const project = await requireProject(slug);\n if (project.status !== \"archived\") {\n console.log(\n warn(`Project ${slug} is not archived (status: ${project.status}).`)\n );\n return;\n }\n const ts = now();\n const backend = await getRegistryBackend();\n await backend.updateProjectStatus(project.id, \"active\", { archivedAt: null, updatedAt: ts });\n console.log(ok(`Unarchived: ${bold(slug)}`));\n}\n\nexport async function cmdMove(slug: string, newPath: string): Promise<void> {\n const project = await requireProject(slug);\n const resolvedNew = resolvePath(newPath);\n const newEncoded = encodeDir(resolvedNew);\n const ts = now();\n\n const backend = await getRegistryBackend();\n await backend.updateProjectPath(project.id, { rootPath: resolvedNew, encodedDir: newEncoded }, ts);\n\n console.log(ok(`Moved: ${bold(slug)}`));\n console.log(dim(` Old path: ${project.root_path}`));\n console.log(dim(` New path: ${resolvedNew}`));\n}\n\nexport async function cmdTag(\n slug: string,\n tags: string[]\n): Promise<void> {\n const project = await requireProject(slug);\n const backend = await getRegistryBackend();\n const added: string[] = [];\n const skipped: string[] = [];\n\n for (const tagName of tags) {\n const tagId = await upsertTag(tagName);\n const exists = await backend.projectHasTag(project.id, tagId);\n if (exists) {\n skipped.push(tagName);\n } else {\n await backend.addProjectTag(project.id, tagId);\n added.push(tagName);\n }\n }\n\n if (added.length) {\n console.log(\n ok(\n `Tagged ${bold(slug)}: ${added.map((t) => chalk.cyan(t)).join(\", \")}`\n )\n );\n }\n if (skipped.length) {\n console.log(dim(` Already present: ${skipped.join(\", \")}`));\n }\n}\n\nexport async function cmdAlias(\n slug: string,\n alias: string\n): Promise<void> {\n await requireProject(slug);\n const backend = await getRegistryBackend();\n\n const conflict = await backend.getProjectBySlug(alias);\n if (conflict) {\n console.error(\n err(`\"${alias}\" is already a project slug — cannot use as alias.`)\n );\n process.exitCode = 1;\n return;\n }\n\n const project = (await getProject(slug))!;\n try {\n await backend.addAlias(alias, project.id);\n console.log(ok(`Alias added: ${bold(alias)} → ${slug}`));\n } catch {\n console.error(err(`Alias \"${alias}\" is already registered.`));\n process.exitCode = 1;\n }\n}\n\nexport async function cmdEdit(\n slug: string,\n opts: { displayName?: string; type?: string }\n): Promise<void> {\n const project = await requireProject(slug);\n\n if (!opts.displayName && !opts.type) {\n console.log(warn(\"Nothing to update. Use --display-name or --type.\"));\n return;\n }\n\n const validTypes = [\"local\", \"central\", \"obsidian-linked\", \"external\"];\n if (opts.type && !validTypes.includes(opts.type)) {\n console.error(\n err(`Invalid type \"${opts.type}\". Valid: ${validTypes.join(\", \")}`)\n );\n process.exitCode = 1;\n return;\n }\n\n const ts = now();\n const backend = await getRegistryBackend();\n if (opts.displayName) {\n await backend.updateProjectDisplayName(project.id, opts.displayName, ts);\n console.log(ok(`Display name updated: ${bold(opts.displayName)}`));\n }\n if (opts.type) {\n await backend.updateProjectType(project.id, opts.type as Project[\"type\"], ts);\n console.log(ok(`Type updated: ${bold(opts.type)}`));\n }\n}\n\nexport async function cmdDetect(\n pathArg: string | undefined,\n opts: { json?: boolean }\n): Promise<void> {\n const cwd = pathArg ? resolvePath(pathArg) : process.cwd();\n const detection = await detectProject(cwd);\n\n if (!detection) {\n if (opts.json) {\n console.log(JSON.stringify({ error: \"no_match\", cwd }, null, 2));\n } else {\n console.log(warn(`No registered project found for: ${cwd}`));\n console.log(dim(\" Run 'pai project add .' to register this directory.\"));\n }\n return;\n }\n\n if (opts.json) {\n console.log(formatDetectionJson(detection));\n return;\n }\n\n console.log();\n console.log(header(\" Project Detection Result\"));\n console.log();\n console.log(\n formatDetection(detection)\n .split(\"\\n\")\n .map((l) => \" \" + l)\n .join(\"\\n\")\n );\n console.log();\n}\n\nexport async function cmdConsolidate(\n identifier: string,\n opts: { yes?: boolean; dryRun?: boolean }\n): Promise<void> {\n const project =\n (await resolveIdentifier(identifier)) ?? (await requireProject(identifier));\n\n console.log();\n console.log(header(` Consolidate: ${project.slug}`));\n console.log(` Target: ${project.root_path}`);\n console.log();\n\n const dirs = findProjectNotesDirs(project);\n\n if (dirs.length === 0) {\n console.log(warn(\" No scattered notes directories found for this project.\"));\n return;\n }\n\n const canonicalNotes = join(project.root_path, \"Notes\");\n const toMerge = dirs.filter((d) => d.notesPath !== canonicalNotes);\n\n if (toMerge.length === 0) {\n console.log(ok(\" All notes are already in the canonical location.\"));\n console.log(dim(` ${canonicalNotes}`));\n return;\n }\n\n console.log(\n ` Found ${toMerge.length} scattered Notes directory(ies) to consolidate:`\n );\n console.log();\n\n for (const d of toMerge) {\n console.log(` ${bold(d.encodedDir)}`);\n console.log(dim(` Notes: ${d.notesPath} (${d.noteCount} file(s))`));\n }\n\n console.log();\n console.log(` Destination: ${canonicalNotes}`);\n console.log();\n\n if (opts.dryRun) {\n console.log(warn(\" Dry run — no changes made. Remove --dry-run to proceed.\"));\n return;\n }\n\n if (!opts.yes) {\n console.log(\n warn(\n \" Run with --yes to perform consolidation, or --dry-run to preview changes.\"\n )\n );\n return;\n }\n\n mkdirSync(canonicalNotes, { recursive: true });\n\n let movedCount = 0;\n for (const d of toMerge) {\n try {\n const files = readdirSync(d.notesPath);\n for (const f of files) {\n if (!f.endsWith(\".md\") && !f.endsWith(\".txt\")) continue;\n const src = join(d.notesPath, f);\n const dest = join(canonicalNotes, f);\n if (!existsSync(dest)) {\n renameSync(src, dest);\n console.log(ok(` Moved: ${f}`));\n movedCount++;\n } else {\n console.log(warn(` Skipped (exists): ${f}`));\n }\n }\n } catch (e) {\n console.error(err(` Error reading ${d.notesPath}: ${e}`));\n }\n }\n\n console.log();\n console.log(ok(` Consolidated ${movedCount} file(s) into ${canonicalNotes}`));\n}\n\n/**\n * Attempt moved-path recovery for a project whose registered root_path is\n * missing. Updates the DB if exactly one candidate is found.\n *\n * Returns the new path on success, or undefined if ambiguous/not found.\n * Prints all messages to stderr (safe for shell-wrapper stdout capture).\n */\nasync function tryRecoverMovedProject(project: ProjectRow): Promise<string | undefined> {\n process.stderr.write(\n warn(`Path not found: ${project.root_path}\\n`) +\n dim(\" Searching for moved location...\\n\")\n );\n\n const result = findMovedPath(project.root_path);\n\n if (result.found) {\n const newPath = result.found;\n const newEncoded = encodeDir(newPath);\n const ts = now();\n const backend = await getRegistryBackend();\n await backend.updateProjectPath(project.id, { rootPath: newPath, encodedDir: newEncoded }, ts);\n process.stderr.write(\n ok(`Project moved: ${shortenPath(project.root_path, 50)}\\n`) +\n dim(` → ${newPath}\\n`) +\n ok(\"Registry updated.\\n\")\n );\n return newPath;\n }\n\n if (result.ambiguous) {\n process.stderr.write(\n warn(`Multiple directories named \"${basename(project.root_path)}\" found:\\n`)\n );\n for (const candidate of result.ambiguous) {\n process.stderr.write(dim(` ${candidate}\\n`));\n }\n process.stderr.write(\n dim(`\\n Disambiguate with: pai projects rebind ${project.slug} <path>\\n`)\n );\n return undefined;\n }\n\n process.stderr.write(\n err(\n `Project \"${project.slug}\" root_path \"${project.root_path}\" does not exist on disk\\n` +\n ` and no folder named \"${basename(project.root_path)}\" was found in scan dirs.\\n`\n ) +\n dim(` Fix with: pai projects rebind ${project.slug} <new-path>\\n`)\n );\n return undefined;\n}\n\nexport async function cmdGo(query: string): Promise<void> {\n const backend = await getRegistryBackend();\n const all: ProjectRow[] = await backend.listProjects({ status: \"active\", orderBy: \"updated_desc\" });\n\n if (!all.length) {\n console.error(\n err(\"No active projects registered. Run: pai project add <path>\")\n );\n process.exitCode = 1;\n return;\n }\n\n const q = query.trim().toLowerCase();\n\n // 1. Exact slug or alias match\n const exact = await getProject(query);\n if (exact) {\n if (!existsSync(exact.root_path)) {\n const recovered = await tryRecoverMovedProject(exact);\n if (!recovered) { process.exitCode = 1; return; }\n process.stdout.write(recovered + \"\\n\");\n return;\n }\n process.stdout.write(exact.root_path + \"\\n\");\n return;\n }\n\n // 2. Substring match against slug, display_name, or root_path basename\n const partial = all.filter(\n (p) =>\n containsIgnoreCase(p.slug, q) ||\n containsIgnoreCase(p.display_name, q) ||\n containsIgnoreCase(basename(p.root_path), q)\n );\n\n if (partial.length === 1) {\n const p = partial[0];\n if (!existsSync(p.root_path)) {\n const recovered = await tryRecoverMovedProject(p);\n if (!recovered) { process.exitCode = 1; return; }\n process.stdout.write(recovered + \"\\n\");\n return;\n }\n process.stdout.write(p.root_path + \"\\n\");\n return;\n }\n\n if (partial.length > 1) {\n console.error(\n err(`Ambiguous: \"${query}\" matches ${partial.length} projects:\\n`)\n );\n partial.forEach((p, i) => {\n console.error(\n ` ${dim(String(i + 1).padStart(2))} ${bold(p.slug.padEnd(30))} ${dim(\n shortenPath(p.root_path, 50)\n )}`\n );\n });\n console.error();\n console.error(dim(\" Use a more specific name or the exact slug.\"));\n process.exitCode = 1;\n return;\n }\n\n // 3. No match — Levenshtein suggestions\n const scored = all\n .map((p) => {\n const distSlug = levenshtein(q, p.slug.toLowerCase());\n const distName = levenshtein(q, p.display_name.toLowerCase());\n return { project: p, dist: Math.min(distSlug, distName) };\n })\n .sort((a, b) => a.dist - b.dist);\n\n const threshold = 4;\n const suggestions =\n scored.filter((s) => s.dist <= threshold).length > 0\n ? scored.filter((s) => s.dist <= threshold).slice(0, 3)\n : scored.slice(0, 3);\n\n console.error(err(`Project not found: \"${query}\"\\n`));\n if (suggestions.length) {\n console.error(warn(\" Did you mean?\"));\n for (const s of suggestions) {\n console.error(\n ` ${bold(s.project.slug.padEnd(30))} ${dim(\n shortenPath(s.project.root_path, 50)\n )}`\n );\n }\n console.error();\n console.error(dim(\" Run: pai project list (to see all projects)\"));\n }\n process.exitCode = 1;\n}\n\nexport async function cmdRebind(\n slug: string,\n newPath: string\n): Promise<void> {\n const project = await requireProject(slug);\n const resolved = resolve(newPath.startsWith(\"~/\") ? join(homedir(), newPath.slice(2)) : newPath);\n\n if (!existsSync(resolved)) {\n console.error(err(`Path does not exist: ${resolved}`));\n process.exitCode = 1;\n return;\n }\n\n let isDir = false;\n try {\n isDir = statSync(resolved).isDirectory();\n } catch {\n // stat failed\n }\n if (!isDir) {\n console.error(err(`Path is not a directory: ${resolved}`));\n process.exitCode = 1;\n return;\n }\n\n const newEncoded = encodeDir(resolved);\n const backend = await getRegistryBackend();\n\n // Check if another project already owns this path\n const conflict = await backend.getProjectByEncodedDir(newEncoded, { excludeId: project.id });\n if (conflict) {\n console.error(\n err(`Path is already registered to project: ${bold(conflict.slug)}\\n`) +\n dim(` ${resolved}\\n`) +\n dim(` Archive or move that project first, or choose a different path.`)\n );\n process.exitCode = 1;\n return;\n }\n\n const ts = now();\n await backend.updateProjectPath(project.id, { rootPath: resolved, encodedDir: newEncoded }, ts);\n\n console.log(ok(`Rebound: ${bold(slug)}`));\n console.log(dim(` Old path: ${project.root_path}`));\n console.log(dim(` New path: ${resolved}`));\n console.log(dim(` Encoded: ${newEncoded}`));\n}\n","/**\n * Session launch configuration for projects.\n * Implements per-project and global config CRUD (pai project config),\n * and the name/unname/names commands for curated project shortlists.\n */\n\nimport { existsSync, readFileSync, writeFileSync, mkdirSync } from \"node:fs\";\nimport { dirname } from \"node:path\";\nimport chalk from \"chalk\";\nimport { ok, warn, err, dim, bold, header, shortenPath, fmtDate, now, renderTable } from \"../../utils.js\";\nimport type { ProjectRow, SessionConfig, ConfigOption } from \"./types.js\";\nimport { resolveIdentifier, requireProject, getProjectAliases, getProject } from \"./helpers.js\";\nimport { paiConfigFilePath } from \"../../../daemon/config.js\";\nimport { detectProject } from \"../detect.js\";\nimport { expandMcpNames } from \"../../../workers/mcp.js\";\nimport { readWorkersSection } from \"../../../workers/config.js\";\nimport { getRegistryBackend } from \"../../../storage/factory.js\";\n\n// ---------------------------------------------------------------------------\n// Constants\n// ---------------------------------------------------------------------------\n\nexport const CONFIG_OPTIONS: ConfigOption[] = [\n { key: 'permission', type: 'string', description: 'Permission preset: full | trusted | default', examples: ['full', 'trusted', 'default'] },\n { key: 'flags', type: 'string', description: 'Raw Claude CLI flags', examples: ['--dangerously-skip-permissions', '--allowedTools Edit,Read'] },\n { key: 'env', type: 'object', description: 'Environment variables as key=value pairs', examples: ['IS_SANDBOX=1', 'CLAUDE_MODEL=opus'] },\n { key: 'autoStart', type: 'boolean', description: 'Auto-start session (skip interactive prompt)', examples: ['true', 'false'] },\n { key: 'prompt', type: 'string', description: 'Initial prompt sent to Claude on launch', examples: ['go', 'continue', 'run tests'] },\n { key: 'model', type: 'string', description: 'Model override for the session', examples: ['opus', 'sonnet', 'haiku'] },\n];\n\nexport const PERMISSION_PRESETS: Record<string, Partial<SessionConfig>> = {\n full: { permission: 'full', flags: '--dangerously-skip-permissions', env: { IS_SANDBOX: '1' }, autoStart: true, prompt: 'go' },\n trusted: { permission: 'trusted', flags: '', env: {}, autoStart: true, prompt: 'go' },\n default: { permission: 'default', flags: '', env: {}, autoStart: false },\n};\n\n// ---------------------------------------------------------------------------\n// Helpers\n// ---------------------------------------------------------------------------\n\nexport function getSessionConfig(project: ProjectRow): SessionConfig {\n return project.session_config ? JSON.parse(project.session_config) : {};\n}\n\nexport function getGlobalDefaults(): SessionConfig {\n try {\n const configPath = paiConfigFilePath();\n if (existsSync(configPath)) {\n const config = JSON.parse(readFileSync(configPath, 'utf-8'));\n return config.sessionDefaults ?? {};\n }\n } catch { /* ignore */ }\n return {};\n}\n\nexport function saveGlobalDefaults(defaults: SessionConfig): void {\n const configPath = paiConfigFilePath();\n let config: Record<string, unknown> = {};\n try {\n if (existsSync(configPath)) {\n config = JSON.parse(readFileSync(configPath, 'utf-8'));\n }\n } catch { /* ignore */ }\n config.sessionDefaults = defaults;\n mkdirSync(dirname(configPath), { recursive: true });\n writeFileSync(configPath, JSON.stringify(config, null, 2) + '\\n');\n}\n\nfunction applyPreset(config: SessionConfig, preset: string): SessionConfig {\n const presetConfig = PERMISSION_PRESETS[preset];\n if (!presetConfig) {\n return { ...config, permission: 'custom', flags: preset };\n }\n return { ...config, ...presetConfig };\n}\n\n/** The registered project whose root_path covers `cwd` (default: process.cwd()). */\nasync function currentProject(cwd?: string): Promise<ProjectRow | undefined> {\n const detected = await detectProject(cwd);\n if (!detected) return undefined;\n return getProject(detected.slug);\n}\n\nasync function saveSessionConfig(project: ProjectRow, config: SessionConfig): Promise<void> {\n const backend = await getRegistryBackend();\n await backend.updateProjectSessionConfig(project.id, JSON.stringify(config), now());\n}\n\nfunction splitNames(names: string[]): string[] {\n return names.flatMap((n) => n.split(\",\").map((s) => s.trim()).filter(Boolean));\n}\n\nfunction parseConfigValue(key: string, value: string): unknown {\n const option = CONFIG_OPTIONS.find(o => o.key === key);\n if (!option) return value;\n\n switch (option.type) {\n case 'boolean':\n return value === 'true' || value === '1' || value === 'yes';\n case 'object':\n if (key === 'env') {\n const [k, ...vParts] = value.split('=');\n return { [k]: vParts.join('=') || '1' };\n }\n return value;\n default:\n return value;\n }\n}\n\n// ---------------------------------------------------------------------------\n// Commands\n// ---------------------------------------------------------------------------\n\nexport async function cmdName(\n identifier: string,\n shortname: string,\n opts: { permission?: string }\n): Promise<void> {\n const project = (await resolveIdentifier(identifier)) ?? (await requireProject(identifier));\n const backend = await getRegistryBackend();\n\n if (!/^[a-zA-Z][a-zA-Z0-9_-]*$/.test(shortname)) {\n console.error(err(`Invalid name \"${shortname}\". Use letters, digits, hyphens, underscores. Must start with a letter.`));\n process.exitCode = 1;\n return;\n }\n\n const conflictProject = await backend.getProjectBySlug(shortname, { excludeId: project.id });\n if (conflictProject) {\n console.error(err(`\"${shortname}\" is already a project slug.`));\n process.exitCode = 1;\n return;\n }\n\n const conflictAliasProjectId = await backend.resolveAlias(shortname);\n if (conflictAliasProjectId !== null && conflictAliasProjectId !== project.id) {\n console.error(err(`\"${shortname}\" is already used by another project.`));\n process.exitCode = 1;\n return;\n }\n\n if (conflictAliasProjectId === null) {\n await backend.addAlias(shortname, project.id);\n }\n\n if (opts.permission) {\n const existing = getSessionConfig(project);\n const config = applyPreset(existing, opts.permission);\n await backend.updateProjectSessionConfig(project.id, JSON.stringify(config), now());\n }\n\n console.log(ok(`Named: ${bold(shortname)} → ${project.slug} (${shortenPath(project.root_path, 50)})`));\n if (opts.permission) {\n console.log(dim(` Permission: ${opts.permission}`));\n }\n}\n\nexport async function cmdUnname(shortname: string): Promise<void> {\n const backend = await getRegistryBackend();\n const projectId = await backend.resolveAlias(shortname);\n\n if (projectId === null) {\n console.error(err(`No named project found: \"${shortname}\"`));\n process.exitCode = 1;\n return;\n }\n\n await backend.removeAlias(shortname);\n\n const remaining = await backend.countAliasesForProject(projectId);\n\n console.log(ok(`Removed name: ${bold(shortname)}`));\n if (remaining === 0) {\n console.log(dim(\" Project has no remaining names.\"));\n }\n}\n\nexport async function cmdNames(opts: { json?: boolean; all?: boolean }): Promise<void> {\n // --all: includeUnnamed so projects WITHOUT a curated alias are included too\n // (for AIBroker's picker to search everything). Unnamed projects get\n // their slug as the launch key (pai project config resolves by slug).\n const backend = await getRegistryBackend();\n const rows = await backend.listNamedProjects({ includeUnnamed: opts.all });\n\n if (opts.json) {\n const grouped = new Map<number, unknown>();\n for (const row of rows) {\n if (!grouped.has(row.id)) {\n grouped.set(row.id, {\n name: row.name ?? row.slug, // launch key: alias if named, else slug\n names: row.name ? [row.name] : [],\n slug: row.slug,\n display_name: row.display_name,\n root_path: row.root_path,\n session_count: row.session_count,\n last_active: row.last_active ? new Date(row.last_active).toISOString() : null,\n session_config: row.session_config ? JSON.parse(row.session_config) : null,\n });\n } else if (row.name) {\n (grouped.get(row.id) as { names: string[] }).names.push(row.name);\n }\n }\n console.log(JSON.stringify([...grouped.values()], null, 2));\n return;\n }\n\n if (!rows.length) {\n console.log(warn(\"No named projects. Use: pai project name <slug-or-number> <shortname>\"));\n return;\n }\n\n const seen = new Set<number>();\n const tableRows: string[][] = [];\n for (const row of rows) {\n if (seen.has(row.id)) continue;\n seen.add(row.id);\n\n const allNames = rows.filter(r => r.id === row.id).map(r => r.name).filter(Boolean) as string[];\n const config = row.session_config ? JSON.parse(row.session_config) as SessionConfig : null;\n const perm = config?.permission ?? dim('default');\n\n tableRows.push([\n bold(allNames.length ? allNames.join(', ') : row.slug),\n row.slug,\n dim(shortenPath(row.root_path, 40)),\n typeof perm === 'string' ? perm : dim('default'),\n String(row.session_count),\n fmtDate(row.last_active),\n ]);\n }\n\n console.log();\n console.log(header(\" Named Projects\"));\n console.log();\n console.log(renderTable([\"Name(s)\", \"Slug\", \"Path\", \"Permission\", \"Sessions\", \"Last Active\"], tableRows));\n console.log();\n console.log(dim(` ${tableRows.length} named project(s)`));\n}\n\nexport async function cmdConfig(\n identifier: string | undefined,\n opts: {\n set?: string[];\n unset?: string[];\n preset?: string;\n defaults?: boolean;\n options?: boolean;\n json?: boolean;\n reset?: boolean;\n }\n): Promise<void> {\n // Discovery: list available options\n if (opts.options) {\n if (opts.json) {\n console.log(JSON.stringify({\n options: CONFIG_OPTIONS,\n presets: Object.entries(PERMISSION_PRESETS).map(([name, config]) => ({ name, ...config })),\n }, null, 2));\n return;\n }\n\n console.log();\n console.log(header(\" Session Config Options\"));\n console.log();\n console.log(bold(\" Available keys:\"));\n console.log();\n for (const opt of CONFIG_OPTIONS) {\n console.log(` ${bold(opt.key.padEnd(14))} ${dim(`(${opt.type})`)} ${opt.description}`);\n console.log(` ${' '.repeat(14)} ${dim('e.g.')} ${opt.examples.map(e => chalk.cyan(e)).join(', ')}`);\n }\n console.log();\n console.log(bold(\" Permission presets:\"));\n console.log();\n for (const [name, config] of Object.entries(PERMISSION_PRESETS)) {\n const parts = [];\n if (config.flags) parts.push(`flags: ${config.flags}`);\n if (config.env && Object.keys(config.env).length) parts.push(`env: ${Object.entries(config.env).map(([k, v]) => `${k}=${v}`).join(' ')}`);\n if (config.autoStart) parts.push('autoStart');\n if (config.prompt) parts.push(`prompt: \"${config.prompt}\"`);\n console.log(` ${bold(name.padEnd(10))} ${dim(parts.join(', ') || '(vanilla)')}`);\n }\n console.log();\n return;\n }\n\n // Global defaults mode\n if (opts.defaults) {\n let defaults = getGlobalDefaults();\n\n if (opts.reset) {\n saveGlobalDefaults({});\n console.log(ok(\"Global session defaults reset.\"));\n return;\n }\n\n if (opts.preset) {\n defaults = applyPreset(defaults, opts.preset);\n saveGlobalDefaults(defaults);\n console.log(ok(`Global defaults set to preset: ${bold(opts.preset)}`));\n return;\n }\n\n if (opts.set?.length) {\n for (const pair of opts.set) {\n const eqIdx = pair.indexOf('=');\n if (eqIdx === -1) { console.error(err(`Invalid format: \"${pair}\". Use key=value.`)); continue; }\n const key = pair.substring(0, eqIdx);\n const value = pair.substring(eqIdx + 1);\n if (!CONFIG_OPTIONS.find(o => o.key === key)) { console.error(err(`Unknown key: \"${key}\". Run: pai project config --options`)); continue; }\n if (key === 'env') {\n defaults.env = { ...(defaults.env ?? {}), ...(parseConfigValue('env', value) as Record<string, string>) };\n } else {\n (defaults as Record<string, unknown>)[key] = parseConfigValue(key, value);\n }\n }\n saveGlobalDefaults(defaults);\n console.log(ok(\"Global defaults updated.\"));\n }\n\n if (opts.unset?.length) {\n for (const key of opts.unset) {\n if (key.startsWith('env.')) {\n const envKey = key.substring(4);\n if (defaults.env) delete defaults.env[envKey];\n } else {\n delete (defaults as Record<string, unknown>)[key];\n }\n }\n saveGlobalDefaults(defaults);\n console.log(ok(\"Global defaults updated.\"));\n }\n\n if (opts.json) {\n console.log(JSON.stringify(defaults, null, 2));\n } else {\n console.log();\n console.log(header(\" Global Session Defaults\"));\n console.log();\n if (Object.keys(defaults).length === 0) {\n console.log(dim(\" No defaults set. New sessions use vanilla Claude.\"));\n console.log(dim(\" Set with: pai project config --defaults --preset full\"));\n } else {\n for (const [key, value] of Object.entries(defaults)) {\n const display = typeof value === 'object' ? JSON.stringify(value) : String(value);\n console.log(` ${bold(key.padEnd(14))} ${display}`);\n }\n }\n console.log();\n }\n return;\n }\n\n // Per-project config\n if (!identifier) {\n console.error(err(\"Specify a project: pai project config <name-or-slug>\"));\n console.error(dim(\" Or use --defaults for global defaults, --options for available keys.\"));\n process.exitCode = 1;\n return;\n }\n\n const project = (await resolveIdentifier(identifier)) ?? (await requireProject(identifier));\n const backend = await getRegistryBackend();\n let config = getSessionConfig(project);\n\n if (opts.reset) {\n await backend.updateProjectSessionConfig(project.id, null, now());\n console.log(ok(`Config reset for ${bold(project.slug)}. Will use global defaults.`));\n return;\n }\n\n if (opts.preset) {\n config = applyPreset(config, opts.preset);\n await backend.updateProjectSessionConfig(project.id, JSON.stringify(config), now());\n console.log(ok(`Applied preset ${bold(opts.preset)} to ${bold(project.slug)}`));\n }\n\n if (opts.set?.length) {\n for (const pair of opts.set) {\n const eqIdx = pair.indexOf('=');\n if (eqIdx === -1) { console.error(err(`Invalid format: \"${pair}\". Use key=value.`)); continue; }\n const key = pair.substring(0, eqIdx);\n const value = pair.substring(eqIdx + 1);\n if (!CONFIG_OPTIONS.find(o => o.key === key)) { console.error(err(`Unknown key: \"${key}\". Run: pai project config --options`)); continue; }\n if (key === 'env') {\n config.env = { ...(config.env ?? {}), ...(parseConfigValue('env', value) as Record<string, string>) };\n } else {\n (config as Record<string, unknown>)[key] = parseConfigValue(key, value);\n }\n }\n await backend.updateProjectSessionConfig(project.id, JSON.stringify(config), now());\n console.log(ok(`Config updated for ${bold(project.slug)}`));\n }\n\n if (opts.unset?.length) {\n for (const key of opts.unset) {\n if (key.startsWith('env.')) {\n const envKey = key.substring(4);\n if (config.env) delete config.env[envKey];\n } else {\n delete (config as Record<string, unknown>)[key];\n }\n }\n await backend.updateProjectSessionConfig(project.id, JSON.stringify(config), now());\n console.log(ok(`Config updated for ${bold(project.slug)}`));\n }\n\n const effective = { ...getGlobalDefaults(), ...config };\n const aliases = await getProjectAliases(project.id);\n\n if (opts.json) {\n console.log(JSON.stringify({\n project: project.slug,\n names: aliases,\n root_path: project.root_path,\n config,\n global_defaults: getGlobalDefaults(),\n effective,\n }, null, 2));\n return;\n }\n\n console.log();\n console.log(header(` Config: ${project.slug}`));\n if (aliases.length) console.log(dim(` Names: ${aliases.join(', ')}`));\n console.log(dim(` Path: ${project.root_path}`));\n console.log();\n\n if (Object.keys(config).length === 0) {\n console.log(dim(\" No project-specific config. Using global defaults.\"));\n } else {\n console.log(bold(\" Project config:\"));\n for (const [key, value] of Object.entries(config)) {\n const display = typeof value === 'object' ? JSON.stringify(value) : String(value);\n console.log(` ${bold(key.padEnd(14))} ${display}`);\n }\n }\n\n const globalDefaults = getGlobalDefaults();\n if (Object.keys(globalDefaults).length > 0) {\n console.log();\n console.log(dim(\" Global defaults (overridden by project config):\"));\n for (const [key, value] of Object.entries(globalDefaults)) {\n const overridden = key in config;\n const display = typeof value === 'object' ? JSON.stringify(value) : String(value);\n console.log(` ${dim(key.padEnd(14))} ${overridden ? chalk.strikethrough(display) + ' ' + dim('(overridden)') : display}`);\n }\n }\n\n console.log();\n console.log(bold(\" Effective (what AIBroker uses):\"));\n for (const [key, value] of Object.entries(effective)) {\n const display = typeof value === 'object' ? JSON.stringify(value) : String(value);\n console.log(` ${bold(key.padEnd(14))} ${display}`);\n }\n console.log();\n}\n\n// ---------------------------------------------------------------------------\n// MCP / tools pin for the interactive supervisor launch (pai worker run, no -p)\n// ---------------------------------------------------------------------------\n\n/**\n * `pai project mcp [--set names | --clear]` — the MCP servers this project's interactive\n * supervisor session loads. Operates on whichever registered project's\n * root_path covers `opts.cwd` (default: process.cwd()) — run it from inside\n * the project, same as `pai projects here`. Unset (the default) means every\n * configured server loads, today's behavior. Names are validated against\n * ~/.claude.json's mcpServers / workers.mcpSets the same way `--mcp` is.\n */\nexport async function cmdMcp(\n names: string[],\n opts: { clear?: boolean; set?: string; cwd?: string } = {}\n): Promise<void> {\n if (names.length) {\n console.error(err(\"use --set <names> to change the pin; `pai project mcp` shows it\"));\n process.exitCode = 1;\n return;\n }\n const project = await currentProject(opts.cwd);\n if (!project) {\n console.error(err(\"No registered project matches the current directory.\"));\n console.error(dim(\" Register it first: pai projects here <name>\"));\n process.exitCode = 1;\n return;\n }\n const config = getSessionConfig(project);\n\n if (opts.clear) {\n delete config.mcp;\n await saveSessionConfig(project, config);\n console.log(ok(`MCP servers unset for ${bold(project.slug)} — all servers load again.`));\n return;\n }\n\n if (opts.set === undefined) {\n console.log(config.mcp?.length ? config.mcp.join(\",\") : \"unset (all)\");\n return;\n }\n\n const { workers } = readWorkersSection();\n config.mcp = expandMcpNames(splitNames([opts.set]), workers); // unknown names fail fast\n await saveSessionConfig(project, config);\n console.log(ok(`MCP servers set for ${bold(project.slug)}: ${config.mcp.join(\", \")}`));\n}\n\n/**\n * `pai project tools [--set names | --clear]` — the built-in tool schemas this project's\n * interactive supervisor session loads (Read, Edit, Bash, …). Same cwd-based\n * project resolution and unset semantics as `cmdMcp`. Unlike MCP names, tool\n * names are not validated against a fixed list — the harness itself rejects\n * an unrecognised one at launch.\n */\nexport async function cmdTools(\n names: string[],\n opts: { clear?: boolean; set?: string; cwd?: string } = {}\n): Promise<void> {\n if (names.length) {\n console.error(err(\"use --set <names> to change the pin; `pai project tools` shows it\"));\n process.exitCode = 1;\n return;\n }\n const project = await currentProject(opts.cwd);\n if (!project) {\n console.error(err(\"No registered project matches the current directory.\"));\n console.error(dim(\" Register it first: pai projects here <name>\"));\n process.exitCode = 1;\n return;\n }\n const config = getSessionConfig(project);\n\n if (opts.clear) {\n delete config.tools;\n await saveSessionConfig(project, config);\n console.log(ok(`Tools unset for ${bold(project.slug)} — all built-in tools load again.`));\n return;\n }\n\n if (opts.set === undefined) {\n console.log(config.tools?.length ? config.tools.join(\",\") : \"unset (all)\");\n return;\n }\n\n config.tools = splitNames([opts.set]);\n await saveSessionConfig(project, config);\n console.log(ok(`Tools set for ${bold(project.slug)}: ${config.tools.join(\", \")}`));\n}\n","/**\n * relocate.ts — find a registered project whose ancestor directory was renamed.\n *\n * The registry stores absolute root paths. Rename any directory ABOVE a project\n * and every project underneath it goes missing at once — `existsSync` fails, and\n * the health check reported them as dead and offered to archive them. Renaming\n * `Ideaverse` to `🧠 Ideaverse` orphaned a whole subtree that way: 32 entries, all\n * still on disk, none of them findable.\n *\n * The old suggestion logic could not see this. It took the project's BASENAME and\n * looked for it in four hardcoded directories (`~/dev`, `~/dev/ai`, `~/Desktop`,\n * `~/Projects`), so it could only recognise \"the leaf moved into a place I already\n * know about\". A renamed ancestor leaves the leaf exactly where it was.\n *\n * So walk down from the root while the path still exists — that lands on the\n * deepest surviving ancestor, which is precisely where the rename happened — then\n * match the remaining segments by NORMALISED name. Normalising strips the emoji,\n * the space and the case, so `🧠 Ideaverse` and `Ideaverse` both reduce to\n * \"ideaverse\". The general case is \"an ancestor was renamed decoratively\", which\n * is the whole class of failure rather than this one instance of it.\n *\n * Algorithm and the measurements behind it come from the AIBroker session, which\n * probed it and handed it over rather than implementing it in a file that was not\n * theirs.\n */\n\nimport { existsSync, readdirSync, statSync, realpathSync } from \"node:fs\";\nimport { join, sep, basename } from \"node:path\";\nimport { homedir } from \"node:os\";\n\n/**\n * A directory name reduced to what a rename is unlikely to have changed.\n *\n * NFC first because macOS hands back decomposed Unicode: an \"ä\" typed in one\n * place and read from a directory listing in another are different byte\n * sequences, and comparing them raw fails for reasons no one can see. Then case\n * and every non-alphanumeric are dropped, which is what makes `🧠 Ideaverse`\n * match `Ideaverse` — and, unavoidably, what makes `my-project` match\n * `my project`. That looseness is the point, and the uniqueness rule below is\n * what keeps it safe.\n */\nexport function norm(s: string): string {\n return s\n .normalize(\"NFC\")\n .toLowerCase()\n .replace(/[^\\p{Letter}\\p{Number}]+/gu, \"\");\n}\n\n/** Hidden by Unix convention — tool state rather than a project directory. */\nfunction isHidden(name: string): boolean {\n return name.startsWith(\".\");\n}\n\n/**\n * Drop a leading ordering prefix: `20 - Webseiten` -> `Webseiten`.\n *\n * This vault numbers directories to force sort order — `04 - Ablage`,\n * `08 - Others`, `01 - Base Setup`, `20 - Webseiten` — and adding or removing that\n * prefix is a rename of exactly the same kind as adding an emoji. `norm()` alone\n * cannot see it, because the digits survive normalisation: \"webseiten\" against\n * \"20webseiten\".\n *\n * Found because `MDF.md` links to `Infrastruktur/20 - Webseiten` while the\n * registry has a dead entry for plain `Infrastruktur/Webseiten`. The note files\n * knew where it went.\n *\n * Deliberately NOT a general suffix match, and the reason is worth spelling out\n * because \"why not just match on the suffix\" is the obvious next question.\n *\n * `MDF/Infrastruktur/` is wall-to-wall numbered: `00 - Migration Analysis`,\n * `01 - Base Setup`, `02 - Storage Setup`, `03 - Network Setup`, and so on. A\n * suffix rule on a wanted `Setup` hits eight of them at once — so the uniqueness\n * rule below would veto it, and the danger would never show itself. The one that\n * bites is a wanted `Analysis` against `00 - Migration Analysis`: a single hit,\n * therefore silently relocated, therefore wrong, and uniqueness cannot save you\n * because there is nothing ambiguous about it.\n *\n * That is a failure mode this file's own safety rule would have HIDDEN rather than\n * caught. Only a leading run of digits with optional separator comes off.\n */\nfunction stripOrderingPrefix(name: string): string {\n return name.replace(/^\\d+\\s*[-._)]?\\s*/, \"\");\n}\n\n/**\n * The names under which a directory can be recognised.\n *\n * Both spellings are offered from both sides, so the prefix can have been added\n * OR removed since the path was registered.\n */\nfunction matchKeys(name: string): Set<string> {\n const keys = new Set<string>();\n const bare = norm(name);\n if (bare) keys.add(bare);\n const stripped = norm(stripOrderingPrefix(name));\n if (stripped) keys.add(stripped);\n return keys;\n}\n\nfunction shareAKey(a: Set<string>, b: Set<string>): boolean {\n for (const k of a) if (b.has(k)) return true;\n return false;\n}\n\n/**\n * Resolve symlinks so two spellings of one directory compare equal.\n *\n * Falls back to the input when it cannot resolve — a path that does not exist has\n * no real path, and for the duplicate check below \"unresolvable\" must not\n * accidentally equal some other unresolvable path.\n */\nfunction realOrSelf(path: string): string {\n try {\n return realpathSync(path);\n } catch {\n return path;\n }\n}\n\nfunction isDir(path: string): boolean {\n try {\n return statSync(path).isDirectory();\n } catch {\n return false;\n }\n}\n\n/**\n * Where a project went when a directory above it was renamed.\n *\n * Returns undefined rather than a guess whenever it cannot be certain — see the\n * uniqueness rule inside. undefined means \"still dead as far as this can tell\",\n * which leaves the entry exactly as it was.\n */\nexport function relocateRenamedAncestor(rootPath: string): string | undefined {\n // Nothing to relocate. Worth an explicit early return: a caller that asks\n // about a live path must not be told it \"moved\" to itself.\n if (existsSync(rootPath)) return undefined;\n\n const segments = rootPath.split(sep).filter((s) => s.length > 0);\n if (segments.length === 0) return undefined;\n\n // Walk DOWN while the path still exists. What survives is the deepest existing\n // ancestor, and the first segment that does not is the renamed one.\n const absolute = rootPath.startsWith(sep);\n let current = absolute ? sep : \"\";\n let i = 0;\n for (; i < segments.length; i++) {\n const next = join(current, segments[i]!);\n if (!existsSync(next)) break;\n current = next;\n }\n\n // Every segment resolved, yet the whole path does not exist. Should be\n // unreachable, and returning undefined is the safe reading of a contradiction.\n if (i === segments.length) return undefined;\n\n for (; i < segments.length; i++) {\n const wanted = matchKeys(segments[i]!);\n if (wanted.size === 0) return undefined; // punctuation only — nothing to match on\n\n let children: string[];\n try {\n children = readdirSync(current);\n } catch {\n return undefined; // unreadable directory — cannot claim to know\n }\n\n const hits = children.filter(\n (c) =>\n isDir(join(current, c)) &&\n shareAKey(matchKeys(c), wanted) &&\n // A leading dot is not decoration, so it must not normalise away.\n //\n // Caught on the first real run: `~/PAI` was \"relocated\" to `~/.pai`,\n // because norm() strips the dot and both reduce to \"pai\". `~/.pai` is\n // PAI's own registry and state directory — not a project — and the\n // uniqueness rule below could not save us, since there was exactly one\n // match. A hidden sibling is a category difference (`.git`, `.claude`,\n // `.pai` are tool state), not a rename of the visible one.\n isHidden(c) === isHidden(segments[i]!)\n );\n\n // THE SAFETY PROPERTY. Two directories that normalise the same means this\n // cannot tell which one the project was, and repointing a registered slug at\n // the wrong project would silently attach it to someone else's notes — worse\n // than leaving the entry dead, because it looks fixed. Ambiguity is not a\n // relocation.\n if (hits.length !== 1) return undefined;\n\n current = join(current, hits[0]!);\n }\n\n return current;\n}\n\n/**\n * The pre-existing guess: the project's leaf name in one of a few usual places.\n *\n * Kept, and kept SECOND, because it answers a different question — \"the project\n * itself was moved somewhere I know about\" rather than \"an ancestor was renamed\".\n * It is the weaker of the two: matching on basename alone can point at an\n * unrelated directory that happens to share a name, so it only gets to answer\n * when the ancestor walk has declined.\n */\nfunction suggestByBasename(rootPath: string): string | undefined {\n const name = basename(rootPath);\n const candidates = [\n join(homedir(), \"dev\", name),\n join(homedir(), \"dev\", \"ai\", name),\n join(homedir(), \"Desktop\", name),\n join(homedir(), \"Projects\", name),\n ];\n for (const candidate of candidates) {\n if (existsSync(candidate)) return candidate;\n }\n return undefined;\n}\n\n/**\n * Where a missing project probably is, or undefined.\n *\n * Feeds the health check's `suggestedPath ? \"stale\" : \"dead\"` classification, so\n * an answer here moves an entry out of the dead list and into the one `--fix`\n * repairs.\n *\n * `otherRoots` is every OTHER registered project's root path, and it is what\n * stops a repair from becoming a duplicate. Caught by the AIBroker session on the\n * first version of this: `~/PAI` was \"recovered\" to `~/dev/ai/PAI`, which is a\n * second spelling of `~/Daten/Cloud/Development/ai/PAI` — a path an ACTIVE project\n * already owns. Two registry entries for one directory is the exact mess that was\n * merged out of this registry earlier the same day.\n *\n * The comparison must be by realpath, not string. String equality is precisely\n * what missed it: the two paths share not one character after `/Users/owner/`.\n */\nexport function suggestMovedPath(\n rootPath: string,\n otherRoots: readonly string[] = []\n): string | undefined {\n const candidate = relocateRenamedAncestor(rootPath) ?? suggestByBasename(rootPath);\n if (!candidate) return undefined;\n\n const taken = new Set(otherRoots.map(realOrSelf));\n if (taken.has(realOrSelf(candidate))) return undefined;\n\n return candidate;\n}\n","/**\n * Project health check command — audits registered projects for missing paths,\n * moved directories, and orphaned note directories.\n */\n\nimport { existsSync, readdirSync, statSync, realpathSync } from \"node:fs\";\nimport { join } from \"node:path\";\nimport { homedir } from \"node:os\";\nimport chalk from \"chalk\";\nimport { ok, warn, err, dim, bold, header, shortenPath, now, renderTable, encodeDir } from \"../../utils.js\";\nimport type {\n HealthRow,\n HealthCategory,\n HealthReason,\n ProjectHealth,\n ProjectRow,\n} from \"./types.js\";\nimport { suggestMovedPath } from \"./relocate.js\";\nimport { unregistrableReason } from \"../../../registry/registrable.js\";\nimport { getRegistryBackend } from \"../../../storage/factory.js\";\n\nfunction findOrphanedNotesDirs(project: ProjectRow): string[] {\n const claudeProjects = join(homedir(), \".claude\", \"projects\");\n if (!existsSync(claudeProjects)) return [];\n\n const expected = encodeDir(project.root_path);\n const results: string[] = [];\n\n try {\n for (const entry of readdirSync(claudeProjects)) {\n const full = join(claudeProjects, entry);\n try {\n if (!statSync(full).isDirectory()) continue;\n } catch {\n continue;\n }\n if (entry === expected || entry === project.encoded_dir) {\n const notesDir = join(full, \"Notes\");\n if (existsSync(notesDir)) {\n results.push(notesDir);\n }\n }\n }\n } catch {\n // Unreadable — ignore\n }\n return results;\n}\n\n/*\n * suggestMovedPath now lives in relocate.ts, which walks to the deepest surviving\n * ancestor and re-matches the rest by normalised name.\n *\n * What was here looked for the project's BASENAME in four hardcoded directories,\n * so it could only recognise \"the leaf moved somewhere I already know about\". It\n * could not see the failure that actually happened: renaming `Ideaverse` to\n * `🧠 Ideaverse` left 32 registered projects exactly where they were and made\n * every one of them unreachable, and this function reported them dead and offered\n * to archive them.\n *\n * The basename guess is kept, second, since it answers the other question.\n */\n\n/**\n * Why a row is unhealthy, and what to do about it.\n *\n * `category` said only dead / stale / active, and \"archive\" was offered as the\n * remedy for everything. Four different situations were collapsed into that, and\n * archiving is right for exactly one of them:\n *\n * EPHEMERAL the path is a worktree or a temp dir — it should never have been\n * registered. Checked FIRST, deliberately: a temp path whose\n * directory has also vanished is both ephemeral and dead, and\n * \"should never have been registered\" is the stronger and more\n * actionable statement. Without a stated precedence the same row\n * gets different labels depending on evaluation order.\n * DUPLICATE the path is gone and another project owns where it went. Its\n * sessions are the only thing of value on it, so the remedy is\n * merge — archiving strands them.\n * MISNAMED the path EXISTS and another project owns a subtree of it, under a\n * slug that has nothing to do with it. `pferde` on `08 - Others/MDF`.\n * health never reported this at all, because existsSync says yes.\n * DEAD the path is gone and nothing claims it. Archive is correct here.\n *\n * The action must never name a command that destroys a row holding sessions —\n * a wrong command in an action field reads as vetted.\n */\nfunction diagnose(\n project: HealthRow,\n pathExists: boolean,\n others: HealthRow[]\n): { reason?: HealthReason; owner?: string; action?: string } {\n const ephemeral = unregistrableReason(project.root_path);\n if (ephemeral) {\n return {\n reason: \"ephemeral\",\n action:\n project.session_count > 0\n ? `holds ${project.session_count} session(s) — pai project merge ${project.slug} <durable-project> --execute, then unregister`\n : `pai project unregister ${project.slug} --execute (${ephemeral})`,\n };\n }\n\n if (pathExists) {\n // A live directory that another project owns a piece of, under an unrelated\n // slug. Only reported when the other project sits BENEATH this one, which is\n // the shape actually observed; two unrelated roots are not each other's problem.\n const nested = others.find(\n (o) =>\n o.status === \"active\" &&\n o.root_path.startsWith(project.root_path + \"/\") &&\n existsSync(o.root_path)\n );\n if (nested && project.status !== \"active\") {\n return {\n reason: \"misnamed\",\n owner: nested.slug,\n action: `live directory, but ${nested.slug} owns a subtree of it — rename, or pai project merge ${project.slug} ${nested.slug}`,\n };\n }\n return {};\n }\n\n // Path is gone. Does an active project already own where this one would go?\n const suggestion = suggestMovedPath(project.root_path, []);\n if (suggestion) {\n const owner = others.find(\n (o) => o.root_path === suggestion || realpathEq(o.root_path, suggestion)\n );\n if (owner) {\n return {\n reason: \"duplicate\",\n owner: owner.slug,\n action:\n project.session_count > 0\n ? `pai project merge ${project.slug} ${owner.slug} (moves ${project.session_count} session(s))`\n : `pai project merge ${project.slug} ${owner.slug} (no sessions — a plain drop)`,\n };\n }\n }\n\n return {\n reason: \"dead\",\n action:\n project.session_count > 0\n ? `holds ${project.session_count} session(s) — merge before archiving, or they become unreachable`\n : `pai project archive ${project.slug}`,\n };\n}\n\n/** Same directory under two spellings — symlinks, not string equality. */\nfunction realpathEq(a: string, b: string): boolean {\n try {\n return realpathSync(a) === realpathSync(b);\n } catch {\n return false;\n }\n}\n\nexport async function cmdHealth(\n opts: { fix?: boolean; json?: boolean; status?: string }\n): Promise<void> {\n const backend = await getRegistryBackend();\n const rows: HealthRow[] = await backend.listProjectsWithSessionStats();\n\n const results: ProjectHealth[] = rows.map((project) => {\n const pathExists = existsSync(project.root_path);\n const orphaned = findOrphanedNotesDirs(project);\n\n const others = rows.filter((r) => r.id !== project.id);\n\n let category: HealthCategory;\n let suggestedPath: string | undefined;\n\n if (pathExists) {\n category = \"active\";\n } else {\n // Every OTHER project's root, so a repair cannot point this entry at a\n // directory another project already owns.\n suggestedPath = suggestMovedPath(\n project.root_path,\n others.map((r) => r.root_path)\n );\n category = suggestedPath ? \"stale\" : \"dead\";\n }\n\n const { reason, owner, action } = diagnose(project, pathExists, others);\n\n return {\n project,\n category,\n suggestedPath,\n claudeNotesExists: orphaned.length > 0,\n orphanedNotesDirs: orphaned,\n reason,\n owner,\n action,\n };\n });\n\n const filtered = opts.status ? results.filter((r) => r.category === opts.status) : results;\n\n if (opts.json) {\n console.log(JSON.stringify(\n filtered.map((r) => ({\n slug: r.project.slug,\n root_path: r.project.root_path,\n status: r.project.status,\n health: r.category,\n session_count: r.project.session_count,\n suggested_path: r.suggestedPath ?? null,\n claude_notes_exists: r.claudeNotesExists,\n orphaned_notes_dirs: r.orphanedNotesDirs,\n // Added fields, not renamed ones: `health` keeps its old values so any\n // existing consumer keeps working.\n reason: r.reason ?? null,\n owner: r.owner ?? null,\n action: r.action ?? null,\n })),\n null,\n 2\n ));\n return;\n }\n\n const active = filtered.filter((r) => r.category === \"active\");\n const stale = filtered.filter((r) => r.category === \"stale\");\n const dead = filtered.filter((r) => r.category === \"dead\");\n\n console.log();\n console.log(header(\" PAI Project Health Report\"));\n console.log();\n console.log(\n ` ${chalk.green(\"Active:\")} ${active.length} ${chalk.yellow(\"Stale (moved?):\")} ${stale.length} ${chalk.red(\"Dead (missing):\")} ${dead.length}`\n );\n console.log();\n\n if (active.length) {\n console.log(bold(\" Active projects (path exists):\"));\n const tableRows = active.map((r) => [\n bold(r.project.slug),\n dim(shortenPath(r.project.root_path, 50)),\n String(r.project.session_count),\n r.claudeNotesExists ? chalk.green(\"yes\") : dim(\"no\"),\n ]);\n console.log(\n renderTable([\"Slug\", \"Path\", \"Sessions\", \"Claude Notes\"], tableRows)\n .split(\"\\n\").map((l) => \" \" + l).join(\"\\n\")\n );\n console.log();\n }\n\n if (stale.length) {\n console.log(warn(\" Stale projects (path missing, possible new location found):\"));\n for (const r of stale) {\n console.log(` ${bold(r.project.slug)}`);\n console.log(dim(` Old path: ${r.project.root_path}`));\n console.log(chalk.cyan(` Found at: ${r.suggestedPath}`));\n if (r.claudeNotesExists) {\n console.log(chalk.green(` Notes: ${r.orphanedNotesDirs.join(\", \")}`));\n }\n if (opts.fix && r.suggestedPath) {\n const ts = now();\n const newEncoded = encodeDir(r.suggestedPath);\n await backend.updateProjectPath(r.project.id, { rootPath: r.suggestedPath, encodedDir: newEncoded }, ts);\n console.log(ok(` Auto-fixed: updated path to ${r.suggestedPath}`));\n } else if (r.suggestedPath) {\n console.log(dim(` Fix: pai project move ${r.project.slug} ${r.suggestedPath}`));\n }\n }\n console.log();\n }\n\n if (dead.length) {\n console.log(err(\" Unreachable projects (path missing):\"));\n for (const r of dead) {\n const label = r.reason && r.reason !== \"dead\" ? warn(` [${r.reason}]`) : \"\";\n console.log(` ${bold(r.project.slug)} ${dim(r.project.root_path)}${label}`);\n if (r.claudeNotesExists) {\n console.log(chalk.yellow(` Notes: ${r.orphanedNotesDirs.join(\", \")}`));\n }\n // Only archive rows that are genuinely dead. A duplicate's sessions are the\n // only thing of value on it, and an ephemeral row wants unregistering, not\n // filing away — auto-archiving either was the bug behind this whole item.\n if (r.project.session_count === 0 && opts.fix && r.reason === \"dead\") {\n await backend.updateProjectStatus(r.project.id, \"archived\", { archivedAt: now(), updatedAt: now() });\n console.log(ok(\" Auto-fixed: archived (0 sessions, path gone, nothing claims it)\"));\n } else if (r.action) {\n console.log(dim(` Do: ${r.action}`));\n }\n }\n console.log();\n }\n\n // \"active\" is the one word in this output that means something else elsewhere,\n // so say which thing it is.\n //\n // This command's \"active\" is \"the path exists on disk\". The registry's `status`\n // column also says active, meaning \"not archived\". They are different\n // predicates over the same rows and they disagree by a lot: measured\n // 2026-08-04, 124 paths present against 99 registry-active, because 29 ARCHIVED\n // projects still have their directory. A careful reader took the 124 as a\n // registry figure today and drew a wrong conclusion from it, which is a fair\n // reading of a line that just said \"124 active\".\n const archivedButPresent = results.filter(\n (r) => r.category === \"active\" && r.project.status !== \"active\"\n ).length;\n\n console.log(\n dim(\n ` ${rows.length} total: ${active.length} with the path present, ` +\n `${stale.length} stale, ${dead.length} dead`\n )\n );\n if (archivedButPresent > 0) {\n console.log(\n dim(\n ` ${archivedButPresent} of those ${active.length} are archived in the registry — ` +\n `\"present\" here is about the directory, not the registry status.`\n )\n );\n }\n\n if (!opts.fix && (stale.length > 0 || dead.length > 0)) {\n console.log();\n console.log(warn(\" Run with --fix to auto-remediate where possible.\"));\n }\n}\n","/**\n * pai project merge <from> <into> — fold a duplicate registry row into the real one.\n *\n * Exists because `health` had one remedy for every unhealthy row — archive — and\n * archiving a duplicate strands its sessions on a row nobody will open again. The\n * sessions are the only thing of value on a duplicate.\n *\n * Preview by default. This deletes a registry row, and the five-table fan-out\n * behind it is not something a caller can eyeball afterwards, so the plan is\n * printed first and `--execute` is what touches the database.\n */\n\nimport { ok, warn, err, dim, bold, shortenPath } from \"../../utils.js\";\nimport { MergeError } from \"../../../registry/merge.js\";\nimport { getRegistryBackend } from \"../../../storage/factory.js\";\n\nexport async function cmdMerge(\n fromSlug: string,\n intoSlug: string,\n opts: { execute?: boolean } = {}\n): Promise<void> {\n const backend = await getRegistryBackend();\n\n let plan;\n try {\n plan = await backend.planProjectMerge(fromSlug, intoSlug);\n } catch (e) {\n if (e instanceof MergeError) {\n console.error(err(` ${e.message}`));\n process.exitCode = 1;\n return;\n }\n throw e;\n }\n\n const path = async (id: number): Promise<string> => {\n const project = await backend.getProjectById(id);\n return project ? shortenPath(project.root_path, 54) : \"(unknown)\";\n };\n\n console.log();\n console.log(bold(` Merge ${plan.fromSlug} into ${plan.intoSlug}`));\n console.log();\n console.log(dim(` from ${plan.fromSlug.padEnd(22)} ${await path(plan.fromId)}`));\n console.log(dim(` into ${plan.intoSlug.padEnd(22)} ${await path(plan.intoId)}`));\n console.log();\n\n if (plan.sessions.length > 0) {\n // The renumbering is the part a reader cannot predict — sessions are UNIQUE\n // on (project_id, number), so incoming ones cannot keep their numbers.\n console.log(` ${plan.sessions.length} session(s) move and are renumbered:`);\n for (const s of plan.sessions) {\n console.log(dim(` ${String(s.from).padStart(4)} -> ${String(s.to).padStart(4)}`));\n }\n } else {\n console.log(dim(` No sessions to move.`));\n }\n\n const extras: string[] = [];\n if (plan.tags) extras.push(`${plan.tags} tag(s)`);\n if (plan.aliases) extras.push(`${plan.aliases} existing alias(es)`);\n if (plan.compactions) extras.push(`${plan.compactions} compaction record(s)`);\n if (plan.links) extras.push(`${plan.links} inbound link(s)`);\n if (extras.length > 0) {\n console.log(dim(` Also repointed: ${extras.join(\", \")}.`));\n }\n\n if (plan.aliasToAdd) {\n console.log(\n dim(` \"${plan.aliasToAdd}\" is kept as an alias, so the old name still resolves.`)\n );\n } else {\n console.log(\n warn(` \"${plan.fromSlug}\" cannot be kept as an alias — the name is already taken.`)\n );\n }\n\n console.log(dim(` Then the ${plan.fromSlug} row is deleted.`));\n console.log();\n\n if (!opts.execute) {\n console.log(dim(\" Preview — nothing was changed. Re-run with --execute to merge.\"));\n console.log();\n return;\n }\n\n await backend.applyProjectMerge(plan);\n console.log(\n ok(\n ` Merged. ${plan.sessions.length} session(s) now belong to ${plan.intoSlug}.`\n )\n );\n console.log();\n}\n","/**\n * `pai project here <name>` — \"this directory is that project\".\n *\n * Directories move. They get renamed, reorganised into an archive tree, reached\n * through a symlinked parent. Every existing way to fix the registry afterwards\n * asks for something the user has to look up — `rebind` and `move` want a slug\n * AND a path, `add` wants a path — which means the moment you are standing in\n * the directory and know perfectly well what it is, you still cannot just say so.\n *\n * This command is that sentence. Run it in the directory, name the project, and\n * the registry points here. If no project by that name exists, it is created.\n *\n * Two details that matter more than they look:\n *\n * 1. The path is canonicalised with realpath. `resolve()` keeps whichever\n * spelling was typed, so a directory reachable through a symlinked parent\n * encodes two different ways and can be registered TWICE as two unrelated\n * projects — with sessions split across both. Storing the canonical path is\n * what makes \"this directory\" mean one row.\n *\n * 2. Matching accepts the name a human uses. The display name, the slug, and\n * all-query-words-present, in that order, so a project whose directory has\n * since gained or lost a word is still reachable by the name its owner\n * thinks of it by.\n */\n\nimport { realpathSync } from \"node:fs\";\nimport { ok, err, dim, bold, encodeDir, now } from \"../../utils.js\";\nimport { getRegistryBackend } from \"../../../storage/factory.js\";\n\ninterface ProjectRow {\n id: number;\n slug: string;\n display_name: string;\n root_path: string;\n status: string;\n}\n\n/** Normalise for name comparison: lowercase, collapse whitespace and hyphens. */\nfunction norm(s: string): string {\n return s.toLowerCase().replace(/[-_]+/g, \" \").replace(/\\s+/g, \" \").trim();\n}\n\n/**\n * Find the project a human means by `name`.\n *\n * Exact display-name or slug first. Only if that finds nothing do we fall back\n * to requiring every query word to appear, which is deliberately narrower than\n * a substring match: it reaches \"Jobs Search Beta\" from \"jobs beta\" without\n * also reaching a sibling project that merely shares the first word.\n */\nexport async function findProjectsByName(name: string): Promise<ProjectRow[]> {\n const backend = await getRegistryBackend();\n const rows: ProjectRow[] = await backend.listProjects();\n\n const q = norm(name);\n const exact = rows.filter((r) => norm(r.display_name) === q || norm(r.slug) === q);\n if (exact.length > 0) return exact;\n\n const words = q.split(\" \").filter(Boolean);\n if (words.length === 0) return [];\n return rows.filter((r) => {\n const hay = `${norm(r.display_name)} ${norm(r.slug)}`;\n return words.every((w) => hay.includes(w));\n });\n}\n\n/** Slug from a display name: lowercase, non-alphanumerics to hyphens. */\nexport function slugFromName(name: string): string {\n return (\n name\n .toLowerCase()\n .replace(/[^a-z0-9]+/g, \"-\")\n .replace(/^-+|-+$/g, \"\") || \"project\"\n );\n}\n\nexport async function cmdHere(\n name: string,\n opts: { cwd?: string; dryRun?: boolean } = {}\n): Promise<void> {\n // Canonicalise: see note 1 above. A symlinked parent must not create a second\n // identity for the same directory.\n let target: string;\n try {\n target = realpathSync(opts.cwd ?? process.cwd());\n } catch {\n console.error(err(`Cannot resolve the current directory.`));\n process.exitCode = 1;\n return;\n }\n\n const backend = await getRegistryBackend();\n const encoded = encodeDir(target);\n const matches = await findProjectsByName(name);\n\n if (matches.length > 1) {\n console.error(\n err(`\"${name}\" matches ${matches.length} projects — name one exactly:\\n`) +\n matches.map((m) => dim(` ${m.slug} (${m.display_name})\\n`)).join(\"\")\n );\n process.exitCode = 1;\n return;\n }\n\n // Another project already owns this directory: refuse rather than create a\n // duplicate row for a path that is already spoken for.\n const owner = (await backend.getProjectByEncodedDir(encoded)) ?? undefined;\n\n if (matches.length === 1) {\n const p = matches[0];\n\n if (owner && owner.id !== p.id) {\n console.error(\n err(`This directory is already registered to ${bold(owner.slug)}.\\n`) +\n dim(` ${target}\\n`) +\n dim(` Merge them: pai project merge ${owner.slug} ${p.slug} --execute`)\n );\n process.exitCode = 1;\n return;\n }\n\n // Already correct — say so instead of writing an identical row. Compared by\n // realpath, so a differently-spelled but equivalent path counts as correct.\n let sameAlready = false;\n try {\n sameAlready = realpathSync(p.root_path) === target;\n } catch {\n sameAlready = false;\n }\n if (sameAlready) {\n console.log(ok(`Already set: ${bold(p.display_name)}`));\n console.log(dim(` ${target}`));\n return;\n }\n\n if (opts.dryRun) {\n console.log(dim(`Would repoint ${p.slug}:`));\n console.log(dim(` from ${p.root_path}`));\n console.log(dim(` to ${target}`));\n return;\n }\n\n await backend.updateProjectPath(p.id, { rootPath: target, encodedDir: encoded }, now());\n\n console.log(ok(`${bold(p.display_name)} is now here`));\n console.log(dim(` was: ${p.root_path}`));\n console.log(dim(` now: ${target}`));\n return;\n }\n\n // No project by that name — create it pointing here.\n if (owner) {\n console.error(\n err(`This directory is already registered as ${bold(owner.slug)} (${owner.display_name}).\\n`) +\n dim(` Rename it instead: pai project edit ${owner.slug} --display-name \"${name}\"`)\n );\n process.exitCode = 1;\n return;\n }\n\n const slug = slugFromName(name);\n const taken = await backend.getProjectBySlug(slug);\n const finalSlug = taken ? `${slug}-${encoded.slice(-6)}` : slug;\n\n if (opts.dryRun) {\n console.log(dim(`Would create ${finalSlug} (${name}) at ${target}`));\n return;\n }\n\n const ts = now();\n await backend.createProject({\n slug: finalSlug,\n displayName: name,\n rootPath: target,\n encodedDir: encoded,\n type: \"local\",\n status: \"active\",\n createdAt: ts,\n updatedAt: ts,\n });\n\n console.log(ok(`Created ${bold(name)}`));\n console.log(dim(` slug: ${finalSlug}`));\n console.log(dim(` path: ${target}`));\n}\n","/**\n * pai project unregister <slug> — remove a row that should never have existed.\n *\n * The remedy for an EPHEMERAL project: one rooted in a git worktree or a temp\n * directory. Archiving is wrong for those — archiving means \"this was real and is\n * finished\", and these were never projects. Measured 2026-08-04 there were five:\n * two agent worktrees, `/private/tmp/ops-webui`, a scratchpad under\n * `/private/tmp/claude-501/…`, and `tmp` rooted at `/private/tmp` itself, active\n * since February.\n *\n * Written because `health` started printing \"unregister\" as the suggested action\n * and the command did not exist. An action field naming something unavailable is\n * worse than no action field: it reads as vetted.\n *\n * Refuses by default when the row holds sessions. Those sessions are the only\n * thing of value on it, and deleting the row would strand them — merge is the\n * right verb there, so this points at it rather than offering --force as the\n * obvious path.\n */\n\nimport { ok, warn, err, dim, bold } from \"../../utils.js\";\nimport { getRegistryBackend } from \"../../../storage/factory.js\";\n\nexport async function cmdUnregister(\n slug: string,\n opts: { execute?: boolean; force?: boolean } = {}\n): Promise<void> {\n const backend = await getRegistryBackend();\n const project = await backend.getProjectBySlug(slug);\n const row = project\n ? {\n id: project.id,\n slug: project.slug,\n root_path: project.root_path,\n status: project.status,\n session_count: await backend.countSessionsForProject(project.id),\n }\n : undefined;\n\n if (!row) {\n console.error(err(` No project with slug \"${slug}\".`));\n process.exitCode = 1;\n return;\n }\n\n console.log();\n console.log(bold(` Unregister ${row.slug}`));\n console.log(dim(` ${row.root_path}`));\n console.log(dim(` status ${row.status}, ${row.session_count} session(s)`));\n console.log();\n\n if (row.session_count > 0 && !opts.force) {\n console.log(warn(` Refusing: ${row.session_count} session(s) would be stranded.`));\n console.log(\n dim(` Those sessions are the only thing of value on this row. Move them first:`)\n );\n console.log(dim(` pai project merge ${row.slug} <into> --execute`));\n console.log(dim(` Or pass --force if the sessions are genuinely worthless.`));\n console.log();\n process.exitCode = 1;\n return;\n }\n\n if (!opts.execute) {\n console.log(\n dim(\" Preview — nothing was changed. Re-run with --execute to unregister.\")\n );\n if (row.session_count > 0) {\n console.log(warn(` --force is set: ${row.session_count} session(s) WILL be deleted.`));\n }\n console.log();\n return;\n }\n\n // Same five tables as merge. With foreign keys off, deleting only the project\n // row would silently orphan the rest.\n await backend.deleteProjectCascade(row.id);\n\n console.log(ok(` Unregistered ${row.slug}.`));\n console.log(dim(` The directory itself was not touched.`));\n console.log();\n}\n","/**\n * Commander registration for all `pai projects` sub-commands (plural namespace).\n *\n * This is the canonical namespace. `pai project <subcmd>` (singular) is a\n * deprecated alias that forwards here with a stderr notice.\n */\n\nimport type { Command } from \"commander\";\nimport { cmdPromote } from \"../../../session/promote.js\";\nimport {\n cmdAdd,\n cmdList,\n cmdInfo,\n cmdArchive,\n cmdUnarchive,\n cmdMove,\n cmdTag,\n cmdAlias,\n cmdEdit,\n cmdDetect,\n cmdConsolidate,\n cmdGo,\n cmdRebind,\n findMovedPath,\n} from \"./commands.js\";\nimport { existsSync } from \"node:fs\";\nimport { ok, warn, err, dim, bold, shortenPath, encodeDir, now } from \"../../utils.js\";\nimport { basename } from \"node:path\";\nimport { cmdName, cmdUnname, cmdNames, cmdConfig, cmdMcp, cmdTools } from \"./session-config.js\";\nimport { cmdHealth } from \"./health.js\";\nimport { cmdMerge } from \"./merge.js\";\nimport { cmdHere } from \"./here.js\";\nimport { cmdUnregister } from \"./unregister.js\";\nimport { resolveIdentifier } from \"./helpers.js\";\nimport { pinNotices, projectLaunchConfig } from \"../../../workers/project-config.js\";\nimport { getRegistryBackend } from \"../../../storage/factory.js\";\n\nexport { cmdGo };\n\nexport function registerProjectsCommands(projectsCmd: Command): void {\n // pai projects (bare) — show the unified deduped listing, same as `pai`.\n // The old project-only listing is still available as `pai projects list-raw`.\n projectsCmd\n .command(\"list\", { isDefault: true })\n .description(\n \"Show the unified deduped listing (same as `pai`).\\n\" +\n \"Use --all to include cold / zero-session / archived projects.\"\n )\n .option(\"--all\", \"Show all entries including cold / zero-session / archived projects\")\n .option(\"-n, --n <count>\", \"Max rows to show\", \"20\")\n .action(async (opts: { all?: boolean; n?: string }) => {\n const { cmdMain } = await import(\"../main-resolver.js\");\n await cmdMain(undefined, undefined, opts);\n });\n\n // Raw legacy project listing (registry rows only, with slug + path)\n projectsCmd\n .command(\"list-raw\")\n .description(\"Raw registry listing with slug, path, status, last_active columns\")\n .option(\"--all\", \"Include archived projects (default: active only)\")\n .option(\"--status <status>\", \"Filter by status: active | archived\")\n .option(\"--tag <tag>\", \"Filter by tag\")\n .option(\"--type <type>\", \"Filter by type\")\n .action(async (opts: { all?: boolean; status?: string; tag?: string; type?: string }) => {\n await cmdList(opts);\n });\n\n // pai projects cd <identifier>\n projectsCmd\n .command(\"cd <identifier>\")\n .description(\n \"cd to a project directory. Short form: pai cd <name>\\n\" +\n \"(The shell wrapper handles the actual cd; pure output here.)\\n\" +\n \"Auto-detects moved projects when the registered path no longer exists.\"\n )\n .action(async (identifier: string) => {\n const project = await resolveIdentifier(identifier);\n if (!project) {\n console.error(`Project not found: ${identifier}`);\n process.exitCode = 1;\n return;\n }\n\n if (existsSync(project.root_path)) {\n process.stdout.write(project.root_path + \"\\n\");\n return;\n }\n\n // Path missing — search for moved location\n process.stderr.write(\n warn(`Path not found: ${project.root_path}\\n`) +\n dim(\" Searching for moved location...\\n\")\n );\n\n const result = findMovedPath(project.root_path);\n\n if (result.found) {\n const newPath = result.found;\n const newEncoded = encodeDir(newPath);\n const ts = now();\n const backend = await getRegistryBackend();\n await backend.updateProjectPath(project.id, { rootPath: newPath, encodedDir: newEncoded }, ts);\n process.stderr.write(\n ok(`Project moved: ${shortenPath(project.root_path, 50)}\\n`) +\n dim(` → ${newPath}\\n`) +\n ok(\"Registry updated.\\n\")\n );\n process.stdout.write(newPath + \"\\n\");\n return;\n }\n\n if (result.ambiguous) {\n process.stderr.write(\n warn(`Multiple directories named \"${basename(project.root_path)}\" found:\\n`)\n );\n for (const candidate of result.ambiguous) {\n process.stderr.write(dim(` ${candidate}\\n`));\n }\n process.stderr.write(\n dim(`\\n Disambiguate with: pai projects rebind ${project.slug} <path>\\n`)\n );\n process.exitCode = 1;\n return;\n }\n\n process.stderr.write(\n err(\n `Project \"${project.slug}\" root_path \"${project.root_path}\" does not exist on disk\\n` +\n ` and no folder named \"${basename(project.root_path)}\" was found in scan dirs.\\n`\n ) +\n dim(` Fix with: pai projects rebind ${project.slug} <new-path>\\n`)\n );\n process.exitCode = 1;\n });\n\n // pai projects add <path>\n projectsCmd\n .command(\"add <path>\")\n .description(\"Register a project directory in the PAI registry\")\n .option(\"--slug <slug>\", \"Override auto-generated slug\")\n .option(\"--type <type>\", \"Project type: local | central | obsidian-linked | external\", \"local\")\n .option(\"--display-name <name>\", \"Human-readable display name\")\n .action(\n async (\n rawPath: string,\n opts: { slug?: string; type?: string; displayName?: string }\n ) => {\n await cmdAdd(rawPath, opts);\n }\n );\n\n // pai projects info <slug>\n projectsCmd\n .command(\"info <slug>\")\n .description(\"Show full details for a project\")\n .action(async (slug: string) => {\n await cmdInfo(slug);\n });\n\n // pai projects archive <slug>\n projectsCmd\n .command(\"archive <slug>\")\n .description(\"Archive a project\")\n .action(async (slug: string) => {\n await cmdArchive(slug);\n });\n\n // pai projects merge <from> <into> [--execute]\n //\n // The remedy for a DUPLICATE row, which archiving does not serve: archiving a\n // duplicate strands its sessions on a row nobody opens again. Preview by\n // default — this deletes a registry row and repoints five tables.\n projectsCmd\n .command(\"merge <from> <into>\")\n .description(\n \"Fold a duplicate project into another: move its sessions (renumbered), repoint its \" +\n \"tags, aliases, compaction records and links, keep the old slug as an alias, then \" +\n \"delete the row. Preview unless --execute is given.\"\n )\n .option(\"--execute\", \"Actually perform the merge\")\n .action(async (from: string, into: string, opts: { execute?: boolean }) => {\n await cmdMerge(from, into, opts);\n });\n\n // pai projects unregister <slug> [--execute] [--force]\n //\n // The remedy for an EPHEMERAL row — a worktree or temp path that should never\n // have been registered. Archiving claims \"this was real and is finished\", which\n // is the wrong thing to say about a directory designed to be deleted.\n projectsCmd\n .command(\"unregister <slug>\")\n .description(\n \"Remove a project row entirely, for paths that should never have been registered \" +\n \"(worktrees, temp dirs). Refuses when the row holds sessions — merge those first. \" +\n \"Preview unless --execute is given. The directory itself is never touched.\"\n )\n .option(\"--execute\", \"Actually remove the row\")\n .option(\"--force\", \"Remove even though sessions would be deleted with it\")\n .action(async (slug: string, opts: { execute?: boolean; force?: boolean }) => {\n await cmdUnregister(slug, opts);\n });\n\n // pai projects unarchive <slug>\n projectsCmd\n .command(\"unarchive <slug>\")\n .description(\"Restore an archived project to active status\")\n .action(async (slug: string) => {\n await cmdUnarchive(slug);\n });\n\n // pai projects move <slug> <new-path>\n projectsCmd\n .command(\"move <slug> <new-path>\")\n .description(\"Update the root path for a project\")\n .action(async (slug: string, newPath: string) => {\n await cmdMove(slug, newPath);\n });\n\n // pai projects rebind <slug> <new-path>\n projectsCmd\n .command(\"rebind <slug> <new-path>\")\n .description(\n \"Manually update the root_path for a project (for when auto-detect found multiple matches).\\n\" +\n \"Validates the new path exists and is a directory, then updates the registry.\"\n )\n .action(async (slug: string, newPath: string) => {\n await cmdRebind(slug, newPath);\n });\n\n // pai projects here <name>\n projectsCmd\n .command(\"here <name>\")\n .description(\n \"Declare that the CURRENT directory is this project — no slug, no path.\\n\" +\n \"Repoints an existing project of that name, or creates it. Stores the\\n\" +\n \"canonical (realpath) location, so a symlinked parent cannot register the\\n\" +\n \"same directory twice.\"\n )\n .option(\"--dry-run\", \"Show what would change without writing\")\n .action(async (name: string, opts: { dryRun?: boolean }) => {\n await cmdHere(name, { dryRun: opts.dryRun });\n });\n\n // pai projects tag <slug> <tags...>\n projectsCmd\n .command(\"tag <slug> <tags...>\")\n .description(\"Add one or more tags to a project\")\n .action(async (slug: string, tags: string[]) => {\n await cmdTag(slug, tags);\n });\n\n // pai projects alias <slug> <alias>\n projectsCmd\n .command(\"alias <slug> <alias>\")\n .description(\"Register an alternative slug for a project\")\n .action(async (slug: string, alias: string) => {\n await cmdAlias(slug, alias);\n });\n\n // pai projects edit <slug>\n projectsCmd\n .command(\"edit <slug>\")\n .description(\"Edit project metadata\")\n .option(\"--display-name <name>\", \"New display name\")\n .option(\"--type <type>\", \"New type\")\n .action(\n async (slug: string, opts: { displayName?: string; type?: string }) => {\n await cmdEdit(slug, opts);\n }\n );\n\n // pai projects detect [path]\n projectsCmd\n .command(\"detect [path]\")\n .description(\"Detect which registered project the given path (or CWD) belongs to\")\n .option(\"--json\", \"Output raw JSON instead of human-readable text\")\n .action(async (pathArg: string | undefined, opts: { json?: boolean }) => {\n await cmdDetect(pathArg, opts);\n });\n\n // pai projects health\n projectsCmd\n .command(\"health\")\n .description(\n \"Audit all registered projects: check which paths still exist, find moved/dead projects\"\n )\n .option(\"--fix\", \"Auto-remediate where possible\")\n .option(\"--json\", \"Output raw JSON report\")\n .option(\"--status <category>\", \"Filter output to: active | stale | dead\")\n .action(async (opts: { fix?: boolean; json?: boolean; status?: string }) => {\n await cmdHealth(opts);\n });\n\n // pai projects consolidate <identifier>\n projectsCmd\n .command(\"consolidate <identifier>\")\n .description(\n \"Consolidate scattered ~/.claude/projects/.../Notes/ directories for a project into its canonical Notes/ location\"\n )\n .option(\"--yes\", \"Perform consolidation without confirmation prompt\")\n .option(\"--dry-run\", \"Preview what would be moved without making changes\")\n .action(\n async (identifier: string, opts: { yes?: boolean; dryRun?: boolean }) => {\n await cmdConsolidate(identifier, opts);\n }\n );\n\n // pai projects promote\n projectsCmd\n .command(\"promote\")\n .description(\"Promote a session note into a new standalone project\")\n .requiredOption(\"--from-session <path>\", \"Path to the session note markdown file\")\n .requiredOption(\"--to <path>\", \"Directory path for the new project (must not exist)\")\n .option(\"--name <name>\", \"Display name for the new project (derived from filename if omitted)\")\n .action(async (opts: { fromSession: string; to: string; name?: string }) => {\n await cmdPromote(opts);\n });\n\n // pai projects go <query>\n projectsCmd\n .command(\"go <query>\")\n .description(\n \"Print the root path for a project by slug, partial name, or fuzzy match.\\n\" +\n \"Designed for shell integration: cd $(pai projects go <query>)\"\n )\n .action(async (query: string) => {\n await cmdGo(query);\n });\n\n // pai projects name <identifier> <shortname>\n projectsCmd\n .command(\"name <identifier> <shortname>\")\n .description(\"Give a project a short name for quick access\")\n .option(\"--permission <level>\", \"Permission level: full | trusted | default\")\n .action(\n async (\n identifier: string,\n shortname: string,\n opts: { permission?: string }\n ) => {\n await cmdName(identifier, shortname, opts);\n }\n );\n\n // pai projects unname <shortname>\n projectsCmd\n .command(\"unname <shortname>\")\n .description(\"Remove a project's short name\")\n .action(async (shortname: string) => {\n await cmdUnname(shortname);\n });\n\n // pai projects names\n projectsCmd\n .command(\"names\")\n .description(\"List named projects (your curated shortlist)\")\n .option(\"--json\", \"Output JSON for AIBroker consumption\")\n .option(\"--all\", \"Include ALL active registered projects, not just the named shortlist\")\n .action(async (opts: { json?: boolean; all?: boolean }) => {\n await cmdNames(opts);\n });\n\n // pai projects config [identifier]\n projectsCmd\n .command(\"config [identifier]\")\n .description(\n \"View or modify session launch config for a project.\\n\" +\n \"Use --options to discover available keys and presets.\"\n )\n .option(\n \"--set <key=value...>\",\n \"Set config values (repeatable)\",\n (v: string, prev: string[]) => [...prev, v],\n [] as string[]\n )\n .option(\n \"--unset <key...>\",\n \"Remove config keys (repeatable, use env.KEY for env vars)\",\n (v: string, prev: string[]) => [...prev, v],\n [] as string[]\n )\n .option(\"--preset <name>\", \"Apply a permission preset: full | trusted | default\")\n .option(\"--defaults\", \"Manage global session defaults instead of a project\")\n .option(\"--options\", \"List available config keys and presets\")\n .option(\"--json\", \"Output JSON\")\n .option(\"--reset\", \"Reset config to empty (inherit global defaults)\")\n .action(\n async (\n identifier: string | undefined,\n opts: {\n set?: string[];\n unset?: string[];\n preset?: string;\n defaults?: boolean;\n options?: boolean;\n json?: boolean;\n reset?: boolean;\n }\n ) => {\n await cmdConfig(identifier, opts);\n }\n );\n\n // pai projects mcp [names...]\n projectsCmd\n .command(\"mcp [names...]\")\n .description(\n \"View or set the MCP servers this project's interactive supervisor session\\n\" +\n \"loads (run from inside the project, like `pai projects here`).\\n\" +\n \"No args: print the current value. --set <names> (comma-separated): set it,\\n\" +\n \"validated against ~/.claude.json's mcpServers. --clear: unset (all\\n\" +\n \"servers load — today's default). Positional names are an error.\"\n )\n .option(\"--set <names>\", \"Set the pin (comma-separated server or set names)\")\n .option(\"--clear\", \"Unset — all MCP servers load again\")\n .action(async (names: string[], opts: { clear?: boolean; set?: string }) => {\n await cmdMcp(names, opts);\n });\n\n // pai projects pins — read by the SessionStart hook\n projectsCmd\n .command(\"pins\")\n .description(\"Print one line per active MCP/tools pin for the current directory's project (nothing when unpinned)\")\n .action(async () => {\n for (const line of pinNotices(await projectLaunchConfig(process.cwd()))) console.log(line);\n });\n\n // pai projects tools [names...]\n projectsCmd\n .command(\"tools [names...]\")\n .description(\n \"View or set the built-in tool schemas this project's interactive\\n\" +\n \"supervisor session loads (run from inside the project, like `pai projects\\n\" +\n \"here`). No args: print the current value. --set <names> (comma-separated):\\n\" +\n \"set it. --clear: unset (all tools load — today's default). Positional\\n\" +\n \"names are an error.\"\n )\n .option(\"--set <names>\", \"Set the pin (comma-separated tool names)\")\n .option(\"--clear\", \"Unset — all built-in tools load again\")\n .action(async (names: string[], opts: { clear?: boolean; set?: string }) => {\n await cmdTools(names, opts);\n });\n}\n","/** Registry migrate command: import data from ~/.claude/session-registry.json. */\n\nimport { existsSync, readFileSync } from \"node:fs\";\nimport { join } from \"node:path\";\nimport { homedir } from \"node:os\";\nimport { ok, warn, err, dim } from \"../../utils.js\";\nimport { slugify, parseSessionFilename } from \"../../../registry/migrate.js\";\nimport { upsertProject, upsertSession } from \"./utils.js\";\nimport { getRegistryBackend } from \"../../../storage/factory.js\";\n\n// ---------------------------------------------------------------------------\n// Legacy JSON registry types\n// ---------------------------------------------------------------------------\n\ninterface LegacyProjectEntry {\n encoded_dir: string;\n original_path: string;\n notes?: string[];\n session_count?: number;\n note_count?: number;\n todo_exists?: boolean;\n last_modified?: string;\n path_exists?: boolean;\n}\n\ninterface LegacyJsonRegistry {\n version?: number;\n projects?: LegacyProjectEntry[];\n [key: string]: unknown;\n}\n\nconst SESSION_REGISTRY_PATH = join(homedir(), \".claude\", \"session-registry.json\");\n\n// ---------------------------------------------------------------------------\n// cmdMigrate\n// ---------------------------------------------------------------------------\n\nexport async function cmdMigrate(): Promise<void> {\n if (!existsSync(SESSION_REGISTRY_PATH)) {\n console.error(err(`session-registry.json not found: ${SESSION_REGISTRY_PATH}`));\n process.exitCode = 1;\n return;\n }\n\n let registry: LegacyJsonRegistry;\n try {\n const raw = readFileSync(SESSION_REGISTRY_PATH, \"utf8\");\n registry = JSON.parse(raw) as LegacyJsonRegistry;\n } catch (e) {\n console.error(err(`Failed to parse session-registry.json: ${e}`));\n process.exitCode = 1;\n return;\n }\n\n const projects = registry.projects ?? [];\n if (!projects.length) {\n console.log(warn(\"No projects found in session-registry.json.\"));\n return;\n }\n\n console.log(dim(`Migrating ${projects.length} project(s) from session-registry.json ...`));\n\n const backend = await getRegistryBackend();\n let projectsNew = 0;\n let projectsSkipped = 0;\n let sessionsNew = 0;\n const errors: string[] = [];\n\n for (const entry of projects) {\n if (!entry.original_path || !entry.encoded_dir) {\n errors.push(`Skipping entry with missing path: ${JSON.stringify(entry)}`);\n continue;\n }\n\n const rootPath = entry.original_path;\n const encodedDir = entry.encoded_dir;\n const slug = slugify(rootPath);\n\n try {\n const { isNew, id } = await upsertProject(backend, slug, rootPath, encodedDir);\n if (isNew) projectsNew++;\n else projectsSkipped++;\n\n const notes = entry.notes ?? [];\n for (const filename of notes) {\n const parsed = parseSessionFilename(filename);\n if (!parsed) continue;\n\n const isNewSession = await upsertSession(backend, id, parsed.number, parsed.date, parsed.slug, parsed.title, parsed.filename);\n if (isNewSession) sessionsNew++;\n }\n } catch (e) {\n errors.push(`Error processing ${entry.encoded_dir}: ${String(e)}`);\n }\n }\n\n console.log(ok(\"Migration complete.\"));\n console.log(dim(` Projects: ${projectsNew} new, ${projectsSkipped} already existed`));\n console.log(dim(` Sessions: ${sessionsNew} new`));\n\n if (errors.length) {\n console.log();\n console.log(warn(` ${errors.length} error(s) during migration:`));\n for (const e of errors.slice(0, 5)) {\n console.log(dim(` ${e}`));\n }\n if (errors.length > 5) {\n console.log(dim(` ... and ${errors.length - 5} more`));\n }\n }\n}\n","/**\n * pai registry dedupe [--execute]\n *\n * Merges registry rows that describe the same project.\n *\n * WHY THIS HAPPENS\n * ----------------\n * `projects.root_path` is UNIQUE, so two spellings of the same directory\n * register as two projects. On a machine with symlinked path prefixes\n * (`~/dev -> Cloud/Development`, say) the same repo is reachable as\n * `~/dev/ai/PAI` and `~/Daten/Cloud/Development/ai/PAI`, and the scanner\n * registers both. Every session then lands under whichever spelling the shell\n * happened to be using, so history splits: one row accumulates 95 sessions and\n * the other has none, while both claim to be the same project.\n *\n * The symptom that surfaces it is a checkpoint labelled \"Unknown session\" —\n * the row matching the current directory genuinely has no session rows.\n *\n * WHY realpath IS THE RIGHT KEY\n * -----------------------------\n * It removes the judgement call. Two rows whose `root_path` resolves to the\n * same inode are the same project — not \"probably\", definitively — so the\n * merge is mechanical. Rows that merely look similar (same display name, one\n * path a prefix of another) are left alone: deciding those is the user's call,\n * not this command's.\n *\n * NO DATA LOSS\n * ------------\n * Everything referencing a merged row is repointed, never deleted:\n * sessions (renumbered only on collision), compaction_log, aliases, links,\n * project_tags, and any child project's parent_id. The DB is backed up before\n * anything is written, and the whole merge runs in one transaction.\n */\n\nimport { realpathSync, copyFileSync, existsSync } from \"node:fs\";\nimport { ok, warn, err, dim, bold } from \"../../utils.js\";\nimport { getRegistryBackend } from \"../../../storage/factory.js\";\nimport type { RegistryBackend } from \"../../../storage/registry-interface.js\";\n\ninterface ProjectRow {\n id: number;\n slug: string;\n display_name: string;\n root_path: string;\n encoded_dir: string;\n status: string;\n claude_notes_dir: string | null;\n session_config: string | null;\n created_at: number;\n}\n\ninterface MergeGroup {\n realPath: string;\n canonical: ProjectRow;\n duplicates: ProjectRow[];\n}\n\ninterface Counts {\n sessions: number;\n compaction: number;\n aliases: number;\n links: number;\n tags: number;\n children: number;\n}\n\n// ---------------------------------------------------------------------------\n// Analysis\n// ---------------------------------------------------------------------------\n\n/** Resolve a path to its canonical form, or null when it no longer exists. */\nfunction safeRealPath(p: string): string | null {\n try {\n return realpathSync(p);\n } catch {\n return null;\n }\n}\n\nasync function countRefs(backend: RegistryBackend, projectId: number): Promise<Counts> {\n return {\n sessions: await backend.countSessionsForProject(projectId),\n compaction: await backend.countCompactionLogsForProject(projectId),\n aliases: await backend.countAliasesForProject(projectId),\n links: await backend.countLinksForProject(projectId),\n tags: await backend.countProjectTagsForProject(projectId),\n children: await backend.countChildProjects(projectId),\n };\n}\n\n/**\n * Choose which row survives.\n *\n * The row whose stored path already IS the canonical filesystem path wins:\n * keeping it means `root_path` ends up correct without rewriting it, and any\n * external reference to that path keeps resolving. Failing that, the row with\n * the most sessions wins, then the oldest.\n */\nasync function pickCanonical(rows: ProjectRow[], realPath: string, backend: RegistryBackend): Promise<ProjectRow> {\n const exact = rows.filter((r) => r.root_path === realPath);\n const pool = exact.length > 0 ? exact : rows;\n\n const withSessions = await Promise.all(\n pool.map(async (r) => ({ row: r, sessions: (await countRefs(backend, r.id)).sessions }))\n );\n withSessions.sort((a, b) => {\n if (a.sessions !== b.sessions) return b.sessions - a.sessions;\n return a.row.created_at - b.row.created_at;\n });\n return withSessions[0].row;\n}\n\nexport async function analyzeDuplicates(backend: RegistryBackend): Promise<{\n groups: MergeGroup[];\n stalePaths: ProjectRow[];\n}> {\n const rows: ProjectRow[] = await backend.listProjects({ orderBy: \"id\" });\n\n const byRealPath = new Map<string, ProjectRow[]>();\n const stalePaths: ProjectRow[] = [];\n\n for (const row of rows) {\n const rp = safeRealPath(row.root_path);\n if (!rp) {\n // The directory is gone. It cannot be proven identical to anything, so\n // it is never merged automatically — only reported.\n stalePaths.push(row);\n continue;\n }\n const bucket = byRealPath.get(rp);\n if (bucket) bucket.push(row);\n else byRealPath.set(rp, [row]);\n }\n\n const groups: MergeGroup[] = [];\n for (const [realPath, members] of byRealPath) {\n if (members.length < 2) continue;\n const canonical = await pickCanonical(members, realPath, backend);\n groups.push({\n realPath,\n canonical,\n duplicates: members.filter((m) => m.id !== canonical.id),\n });\n }\n\n return { groups, stalePaths };\n}\n\n// ---------------------------------------------------------------------------\n// Merge\n// ---------------------------------------------------------------------------\n\ninterface MergeReport {\n movedSessions: number;\n foldedSessions: number;\n renumbered: Array<{ from: number; to: number; title: string }>;\n movedCompaction: number;\n movedAliases: number;\n movedLinks: number;\n movedTags: number;\n movedChildren: number;\n deletedRows: number;\n reclaimedSlugs: Array<{ from: string; to: string }>;\n}\n\n/**\n * Fold a duplicate session row into the one the canonical project already has\n * for the same note file.\n *\n * Both rows describe one file on disk — the double registration produced two\n * registry rows for it, often identical and created milliseconds apart. Moving\n * both would leave the merged project with two entries per note and force one\n * of them to a number that no longer matches its own filename.\n *\n * Nothing is discarded: tags and links are repointed at the surviving row, and\n * any field the survivor is missing is backfilled from the duplicate before it\n * is deleted.\n */\nasync function foldSession(backend: RegistryBackend, keepId: number, dropId: number): Promise<void> {\n // session_tags PK is (session_id, tag_id) — skip tags the survivor has.\n const tagIds = await backend.listSessionTagIds(dropId);\n for (const tagId of tagIds) {\n const clash = await backend.sessionHasTag(keepId, tagId);\n if (clash) {\n await backend.deleteSessionTag(dropId, tagId);\n } else {\n await backend.reassignSessionTag(dropId, keepId, tagId);\n }\n }\n\n // links UNIQUE(session_id, target_project_id)\n const links = await backend.listLinksForSession(dropId);\n for (const l of links) {\n const clash = await backend.linkExists(keepId, l.target_project_id);\n if (clash) await backend.deleteLink(l.id);\n else await backend.moveLinkToSession(l.id, keepId);\n }\n\n await backend.moveCompactionLogsBySession(dropId, keepId);\n\n // Backfill anything the survivor lacks, and prefer a finished status.\n await backend.backfillFoldedSession(keepId, dropId);\n\n await backend.deleteSession(dropId);\n}\n\nasync function mergeGroup(backend: RegistryBackend, group: MergeGroup): Promise<MergeReport> {\n const report: MergeReport = {\n movedSessions: 0,\n foldedSessions: 0,\n renumbered: [],\n movedCompaction: 0,\n movedAliases: 0,\n movedLinks: 0,\n movedTags: 0,\n movedChildren: 0,\n deletedRows: 0,\n reclaimedSlugs: [],\n };\n\n const canonicalId = group.canonical.id;\n\n // Highest session number already used on the canonical row — collisions are\n // appended above it so no existing number ever has to change.\n let nextNumber = (await backend.getMaxSessionNumber(canonicalId)) + 1;\n\n for (const dup of group.duplicates) {\n const sessions = await backend.listSessionsForProject(dup.id, { orderBy: \"number_asc\" });\n\n for (const s of sessions) {\n // Same file already registered on the canonical row? Then this is the\n // same session recorded twice, not a second session. Fold, do not move.\n const twin = await backend.findSessionByFilename(canonicalId, s.filename);\n\n if (twin) {\n await foldSession(backend, twin.id, s.id);\n report.foldedSessions++;\n continue;\n }\n\n const clash = await backend.sessionNumberTaken(canonicalId, s.number);\n\n if (clash) {\n const assigned = nextNumber++;\n await backend.moveSessionToProject(s.id, canonicalId, { number: assigned });\n report.renumbered.push({\n from: s.number,\n to: assigned,\n title: s.title,\n });\n } else {\n await backend.moveSessionToProject(s.id, canonicalId);\n if (s.number >= nextNumber) nextNumber = s.number + 1;\n }\n report.movedSessions++;\n }\n\n report.movedCompaction += await backend.moveCompactionLogsByProject(dup.id, canonicalId);\n\n // aliases.alias is the PK, so an alias already pointing at the canonical\n // row would collide. Repoint what can move, drop only exact duplicates.\n const dupAliases = await backend.listAliasesForProject(dup.id);\n for (const alias of dupAliases) {\n const existingProjectId = await backend.resolveAlias(alias);\n if (existingProjectId === canonicalId) continue;\n await backend.reassignAlias(alias, canonicalId);\n report.movedAliases++;\n }\n\n // links has UNIQUE(session_id, target_project_id).\n const dupLinks = await backend.listLinksForProject(dup.id);\n for (const l of dupLinks) {\n const clash = await backend.linkExists(l.session_id, canonicalId);\n if (clash) {\n await backend.deleteLink(l.id);\n continue;\n }\n await backend.retargetLink(l.id, canonicalId);\n report.movedLinks++;\n }\n\n // project_tags has PRIMARY KEY(project_id, tag_id).\n const dupTagIds = await backend.listProjectTagIds(dup.id);\n for (const tagId of dupTagIds) {\n const clash = await backend.projectHasTag(canonicalId, tagId);\n if (clash) {\n await backend.deleteProjectTag(dup.id, tagId);\n continue;\n }\n await backend.reassignProjectTag(dup.id, canonicalId, tagId);\n report.movedTags++;\n }\n\n report.movedChildren += await backend.reassignProjectParent(dup.id, canonicalId);\n\n // Carry over settings the canonical row is missing rather than losing them.\n if (!group.canonical.claude_notes_dir && dup.claude_notes_dir) {\n await backend.updateProjectClaudeNotesDir(canonicalId, dup.claude_notes_dir);\n }\n if (!group.canonical.session_config && dup.session_config) {\n await backend.updateProjectSessionConfig(canonicalId, dup.session_config);\n }\n\n await backend.deleteProject(dup.id);\n report.deletedRows++;\n }\n\n // An active duplicate means the project is active, whatever the canonical\n // row happened to say.\n const anyActive =\n group.canonical.status === \"active\" ||\n group.duplicates.some((d) => d.status === \"active\");\n if (anyActive && group.canonical.status !== \"active\") {\n await backend.updateProjectStatus(canonicalId, \"active\");\n }\n\n // Reclaim the good slug.\n //\n // The duplicate rows were created in path order, so the plain slug (\"pai\")\n // usually went to whichever spelling was seen first and the canonical row —\n // chosen by filesystem path, not by name — ended up with the suffixed one\n // (\"pai-2\"). Now that the duplicates are gone their names are free, so take\n // the best one back rather than leaving the merged project called \"pai-2\".\n const suffixed = /^(.*)-\\d+$/.exec(group.canonical.slug);\n if (suffixed) {\n const candidates = [\n suffixed[1],\n ...group.duplicates.map((d) => d.slug).filter((s) => !/-\\d+$/.test(s)),\n ];\n for (const candidate of candidates) {\n const taken = await backend.getProjectBySlug(candidate);\n if (taken) continue;\n await backend.updateProjectSlug(canonicalId, candidate, Date.now());\n report.reclaimedSlugs.push({\n from: group.canonical.slug,\n to: candidate,\n });\n break;\n }\n }\n\n // Point the surviving row at the canonical filesystem path.\n if (group.canonical.root_path !== group.realPath) {\n const taken = await backend.getProjectByRootPath(group.realPath, { excludeId: canonicalId });\n if (!taken) {\n await backend.updateProjectPath(canonicalId, { rootPath: group.realPath }, Date.now());\n }\n }\n\n return report;\n}\n\n/**\n * Rewrite any remaining root_path that is not already its canonical form.\n *\n * Merging fixes the rows that already split. This stops the rest from\n * splitting later: a project registered only as `~/dev/ai/PAILot` still has a\n * non-canonical path, so the first session started from the other spelling\n * would create a second row and the whole problem recurs.\n *\n * Only rows whose canonical path is free are touched — a collision would mean\n * a duplicate, and those have already been merged.\n */\nasync function canonicalizePaths(\n backend: RegistryBackend,\n dryRun: boolean\n): Promise<Array<{ slug: string; from: string; to: string }>> {\n const rows = await backend.listProjects();\n\n const changed: Array<{ slug: string; from: string; to: string }> = [];\n\n for (const row of rows) {\n const rp = safeRealPath(row.root_path);\n if (!rp || rp === row.root_path) continue;\n\n const taken = await backend.getProjectByRootPath(rp, { excludeId: row.id });\n if (taken) continue;\n\n changed.push({ slug: row.slug, from: row.root_path, to: rp });\n if (!dryRun) {\n await backend.updateProjectPath(row.id, { rootPath: rp }, Date.now());\n }\n }\n\n return changed;\n}\n\n// ---------------------------------------------------------------------------\n// Command\n// ---------------------------------------------------------------------------\n\nfunction backupDb(dbPath: string): string | null {\n if (!existsSync(dbPath)) return null;\n const stamp = new Date().toISOString().replace(/[:.]/g, \"-\");\n const dest = `${dbPath}.bak-${stamp}`;\n try {\n copyFileSync(dbPath, dest);\n // Copy the WAL too — without it the backup can miss committed pages.\n for (const suffix of [\"-wal\", \"-shm\"]) {\n if (existsSync(dbPath + suffix)) {\n copyFileSync(dbPath + suffix, dest + suffix);\n }\n }\n return dest;\n } catch {\n return null;\n }\n}\n\nexport async function cmdDedupe(\n opts: { execute?: boolean; dbPath?: string }\n): Promise<void> {\n const backend = await getRegistryBackend();\n const { groups, stalePaths } = await analyzeDuplicates(backend);\n\n console.log();\n console.log(\n bold(\n opts.execute\n ? \" pai registry dedupe\"\n : \" pai registry dedupe — DRY RUN (no changes will be made)\"\n )\n );\n console.log();\n\n if (groups.length === 0) {\n console.log(ok(\"No duplicate projects found — every root_path resolves uniquely.\"));\n }\n\n for (const g of groups) {\n console.log(` ${bold(g.realPath)}`);\n const cCounts = await countRefs(backend, g.canonical.id);\n console.log(\n ` ${ok(\"keep\")} #${g.canonical.id} ${bold(g.canonical.slug)} ` +\n dim(`(${g.canonical.root_path}) — ${cCounts.sessions} sessions, ${g.canonical.status}`)\n );\n for (const d of g.duplicates) {\n const dc = await countRefs(backend, d.id);\n const moving = [\n dc.sessions ? `${dc.sessions} sessions` : null,\n dc.compaction ? `${dc.compaction} compaction rows` : null,\n dc.aliases ? `${dc.aliases} aliases` : null,\n dc.links ? `${dc.links} links` : null,\n dc.tags ? `${dc.tags} tags` : null,\n dc.children ? `${dc.children} child projects` : null,\n ]\n .filter(Boolean)\n .join(\", \");\n console.log(\n ` ${warn(\"merge\")} #${d.id} ${bold(d.slug)} ` +\n dim(`(${d.root_path}) — ${moving || \"nothing to move\"}`)\n );\n }\n console.log();\n }\n\n if (stalePaths.length > 0) {\n console.log(\n dim(\n ` ${stalePaths.length} project(s) point at a path that no longer exists.`\n )\n );\n console.log(\n dim(\n \" These are never merged automatically — a missing directory cannot be\"\n )\n );\n console.log(dim(\" proven identical to anything else. Listed for review:\"));\n for (const s of stalePaths) {\n const sc = await countRefs(backend, s.id);\n if (sc.sessions === 0 && sc.compaction === 0) continue;\n console.log(\n dim(\n ` #${s.id} ${s.slug} (${s.root_path}) — ${sc.sessions} sessions, ${sc.compaction} compaction rows`\n )\n );\n }\n console.log();\n }\n\n const pathFixes = await canonicalizePaths(backend, true);\n if (pathFixes.length > 0) {\n console.log(\n dim(\n ` ${pathFixes.length} project(s) stored under a non-canonical path — these would`\n )\n );\n console.log(\n dim(\n \" split into a second row the first time a session runs from the other\"\n )\n );\n console.log(dim(\" spelling. They will be rewritten to their resolved path:\"));\n for (const p of pathFixes.slice(0, 8)) {\n console.log(dim(` ${p.slug}: ${p.from} -> ${p.to}`));\n }\n if (pathFixes.length > 8) {\n console.log(dim(` ... and ${pathFixes.length - 8} more`));\n }\n console.log();\n }\n\n if (!opts.execute) {\n if (groups.length > 0 || pathFixes.length > 0) {\n console.log(dim(\" Re-run with --execute to apply. The registry is backed up first.\"));\n console.log();\n }\n return;\n }\n\n if (groups.length === 0 && pathFixes.length === 0) return;\n\n // ---- Back up before touching anything ----\n const dbPath = opts.dbPath;\n if (dbPath) {\n const backup = backupDb(dbPath);\n if (backup) {\n console.log(ok(`Registry backed up to ${bold(backup)}`));\n } else {\n console.error(\n err(\"Could not back up the registry — refusing to merge without one.\")\n );\n process.exitCode = 1;\n return;\n }\n }\n\n // ---- Merge, all-or-nothing ----\n const totals: MergeReport = {\n movedSessions: 0,\n foldedSessions: 0,\n renumbered: [],\n movedCompaction: 0,\n movedAliases: 0,\n movedLinks: 0,\n movedTags: 0,\n movedChildren: 0,\n deletedRows: 0,\n reclaimedSlugs: [],\n };\n\n let canonicalized = 0;\n\n // Best-effort in sequence: RegistryBackend has no cross-row transaction\n // primitive, so a failure partway through no longer rolls back everything\n // the way the old raw-SQL transaction did — it leaves whatever merged\n // successfully in place and reports the point of failure.\n try {\n for (const g of groups) {\n const r = await mergeGroup(backend, g);\n totals.movedSessions += r.movedSessions;\n totals.foldedSessions += r.foldedSessions;\n totals.renumbered.push(...r.renumbered);\n totals.movedCompaction += r.movedCompaction;\n totals.movedAliases += r.movedAliases;\n totals.movedLinks += r.movedLinks;\n totals.movedTags += r.movedTags;\n totals.movedChildren += r.movedChildren;\n totals.deletedRows += r.deletedRows;\n totals.reclaimedSlugs.push(...r.reclaimedSlugs);\n }\n // After merging, the remaining collisions are gone, so the rest of the\n // registry can be moved onto canonical paths safely.\n canonicalized = (await canonicalizePaths(backend, false)).length;\n } catch (e) {\n console.error(err(`Merge failed partway through: ${String(e)}`));\n process.exitCode = 1;\n return;\n }\n\n console.log();\n console.log(ok(`Merged ${totals.deletedRows} duplicate row(s).`));\n console.log(` sessions moved: ${totals.movedSessions}`);\n console.log(\n ` sessions folded: ${totals.foldedSessions} ` +\n dim(\"(same note file registered twice)\")\n );\n console.log(` compaction rows moved: ${totals.movedCompaction}`);\n console.log(` aliases moved: ${totals.movedAliases}`);\n console.log(` links moved: ${totals.movedLinks}`);\n console.log(` tags moved: ${totals.movedTags}`);\n console.log(` child projects moved: ${totals.movedChildren}`);\n console.log(` paths canonicalized: ${canonicalized}`);\n\n if (totals.reclaimedSlugs.length > 0) {\n console.log();\n console.log(ok(\"Slugs reclaimed from the merged rows:\"));\n for (const s of totals.reclaimedSlugs) {\n console.log(dim(` ${s.from} -> ${s.to}`));\n }\n }\n\n if (totals.renumbered.length > 0) {\n console.log();\n console.log(\n warn(`${totals.renumbered.length} session(s) renumbered to avoid collisions:`)\n );\n for (const r of totals.renumbered) {\n console.log(dim(` ${r.from} -> ${r.to} ${r.title}`));\n }\n console.log(\n dim(\" Session note filenames on disk are unchanged — only the registry number moved.\")\n );\n console.log();\n console.log(\n warn(\n \" A renumbered session's registry number no longer matches the number in its\"\n )\n );\n console.log(\n dim(\n \" filename. The scanner keys sessions by (project, number), so a note whose\\n\" +\n \" number is now occupied cannot be re-registered until numbering is reconciled.\\n\" +\n \" Run `pai session cleanup <slug> --execute` on the affected projects to renumber\\n\" +\n \" notes and registry together.\"\n )\n );\n }\n console.log();\n}\n","/**\n * storage/paths.ts — the on-disk *.db path resolvers, re-exported from the\n * SQLite openers (src/storage/sqlite/federation-db.ts, registry-db.ts) so\n * that everything outside src/storage/ (config/pai-files.ts,\n * cli/commands/backup.ts, restore.ts, config.ts, workers/project-config.ts,\n * ...) can learn *where* the SQLite files live without importing\n * better-sqlite3 or openFederation/openRegistry directly — those imports are\n * what the boundary test (design doc §7) forbids outside src/storage/.\n */\n\nexport { federationDbPath, oldFederationPath } from \"./sqlite/federation-db.js\";\nexport { registryDbPath, oldRegistryPath } from \"./sqlite/registry-db.js\";\n\nimport { join } from \"node:path\";\nimport { homedir } from \"node:os\";\nimport { paiHomePath, migratePaiFile, type MigrateFileResult } from \"../config/pai-home.js\";\n\n/**\n * The orphaned ~/.config/pai/federation.db (a 0-byte leftover — the live\n * federation DB has lived at ~/.pai/federation.db since the Postgres\n * migration, nothing in src/ reads this copy). Lives here rather than in\n * config/pai-files.ts so that file stays free of *.db path literals.\n */\nexport function oldOrphanFederationDbPath(): string {\n return join(homedir(), \".config\", \"pai\", \"federation.db\");\n}\n\nexport function migrateOrphanFederationDb(opts: { dryRun?: boolean } = {}): MigrateFileResult {\n return migratePaiFile(paiHomePath(\"federation.db\"), [oldOrphanFederationDbPath()], opts);\n}\n","/**\n * PAI_HOME layout migration for the two legacy per-user SQLite files\n * (the orphaned ~/.config/pai/federation.db and the live ~/.pai/{federation,\n * registry}.db pair) — byte-relocation on disk, not application-level DB\n * access, but still routed through src/storage/ so `pai config migrate`\n * never has to name these files directly (boundary test, design doc §7).\n */\n\nimport { existsSync, renameSync, statSync } from \"node:fs\";\nimport { execFileSync } from \"node:child_process\";\nimport {\n paiHomePath,\n migratePaiFile,\n PaiFileMigrationError,\n type MigrateFileResult,\n} from \"../../config/pai-home.js\";\nimport {\n federationDbPath,\n oldFederationPath,\n registryDbPath,\n oldRegistryPath,\n migrateOrphanFederationDb,\n} from \"../paths.js\";\n\n/** Paths for `pai config path`/`migrate` display, without exposing the raw openers by name. */\nexport function legacyDbDisplayPaths(): { registryDb: string; federationDb: string } {\n return { registryDb: registryDbPath(), federationDb: federationDbPath() };\n}\n\n/**\n * The orphaned ~/.config/pai/federation.db (a 0-byte leftover — the live\n * federation DB has lived at ~/.pai/federation.db since the Postgres\n * migration, nothing in src/ reads this copy). Still checked for an open fd\n * before moving: if some other process ever does hold it, restart the\n * daemon (SIGTERM; launchd relaunches it) and give it a moment to let go\n * before renaming the file aside, per the migration runbook.\n */\nexport function migrateFederationDbOrphan(dryRun: boolean): MigrateFileResult {\n const oldPath = oldFederationPath();\n if (existsSync(oldPath) && !dryRun) {\n let heldOpen = false;\n try {\n execFileSync(\"lsof\", [oldPath], { stdio: \"pipe\" });\n heldOpen = true;\n } catch {\n // lsof exits non-zero when nothing has the file open — the expected case.\n }\n if (heldOpen) {\n console.log(\" federation database (orphan): held open by the daemon — restarting it first\");\n execFileSync(\"pai\", [\"daemon\", \"restart\"], { stdio: \"inherit\" });\n execFileSync(\"sleep\", [\"2\"]);\n }\n }\n return migrateOrphanFederationDb({ dryRun });\n}\n\nfunction checkpointAndVerify(oldPath: string, newPath: string, label: string): void {\n let hasSqlite3 = true;\n try {\n execFileSync(\"which\", [\"sqlite3\"], { stdio: \"pipe\" });\n } catch {\n hasSqlite3 = false;\n }\n if (hasSqlite3) {\n try {\n execFileSync(\"sqlite3\", [oldPath, \"PRAGMA wal_checkpoint(TRUNCATE);\"], { stdio: \"pipe\" });\n } catch {\n // Best-effort — the sidecar move below still catches an un-checkpointed WAL.\n }\n }\n\n const stamp = new Date().toISOString().slice(0, 10).replace(/-/g, \"\");\n for (const suffix of [\"-wal\", \"-shm\"]) {\n const sidecar = `${oldPath}${suffix}`;\n if (existsSync(sidecar)) {\n try {\n renameSync(sidecar, `${sidecar}.migrated-${stamp}`);\n } catch {\n // Non-fatal — surfaced by the integrity check below if it actually mattered.\n }\n }\n }\n\n if (hasSqlite3) {\n try {\n const check = execFileSync(\"sqlite3\", [newPath, \"PRAGMA integrity_check;\"], { stdio: \"pipe\" })\n .toString()\n .trim();\n if (check !== \"ok\") {\n throw new PaiFileMigrationError(\n `${newPath}: PRAGMA integrity_check reported \"${check}\" — investigate before trusting this copy`\n );\n }\n } catch (e) {\n if (e instanceof PaiFileMigrationError) throw e;\n // sqlite3 CLI failed for an unrelated reason (not installed, etc.) — the\n // byte-identical copy migratePaiFile already verified is still trustworthy.\n }\n }\n}\n\n/**\n * ~/.pai/registry.db — a third per-user location, alongside PAI_HOME and the\n * ~/.claude adapter. Same open-fd caution as the federation database below\n * (the daemon holds this connection while it runs), plus SQLite-specific\n * care: a WAL-mode DB can have unflushed writes sitting in a `-wal` sidecar\n * file, so a plain byte-copy of the main file alone can silently drop them.\n * This checkpoints the WAL into the main file before copying, moves any\n * sidecar left behind anyway, and runs `PRAGMA integrity_check` on the copy\n * when the `sqlite3` CLI is available — on top of migratePaiFile's own\n * byte-identical verification, not instead of it.\n */\nexport function migrateRegistryDbFile(dryRun: boolean): MigrateFileResult {\n const oldPath = oldRegistryPath();\n const newPath = paiHomePath(\"registry.db\");\n\n if (!existsSync(oldPath) || dryRun) {\n return migratePaiFile(newPath, [oldPath], { dryRun });\n }\n\n let heldOpen = false;\n try {\n execFileSync(\"lsof\", [oldPath], { stdio: \"pipe\" });\n heldOpen = true;\n } catch {\n // lsof exits non-zero when nothing has the file open — the expected case.\n }\n if (heldOpen) {\n console.log(\" registry database: held open by the daemon — restarting it first\");\n execFileSync(\"pai\", [\"daemon\", \"restart\"], { stdio: \"inherit\" });\n execFileSync(\"sleep\", [\"2\"]);\n }\n\n const result = migratePaiFile(newPath, [oldPath], { dryRun: false });\n if (result.fromPath) checkpointAndVerify(oldPath, newPath, \"registry database\");\n return result;\n}\n\n/**\n * ~/.pai/federation.db — the LIVE per-user SQLite federation DB. Despite the\n * comment on migrateFederationDbOrphan above, this one is not an orphan: the\n * sqlite storage backend opens it directly via the storage layer's default\n * path — independent of the storageBackend setting only for hosts still on\n * the sqlite backend. It shares its target filename with the harmless\n * 0-byte orphan migrated above, so that empty copy is renamed aside first\n * (never deleted) to free up the canonical name. Same open-fd,\n * WAL-checkpoint and integrity-check care as the registry database.\n */\nexport function migrateFederationDbFile(dryRun: boolean): MigrateFileResult {\n const oldPath = oldFederationPath();\n const newPath = paiHomePath(\"federation.db\");\n\n if (!dryRun && existsSync(newPath) && existsSync(oldPath) && statSync(newPath).size === 0) {\n const stamp = new Date().toISOString().slice(0, 10).replace(/-/g, \"\");\n renameSync(newPath, `${newPath}.empty-orphan-${stamp}`);\n }\n\n if (!existsSync(oldPath) || dryRun) {\n return migratePaiFile(newPath, [oldPath], { dryRun });\n }\n\n let heldOpen = false;\n try {\n execFileSync(\"lsof\", [oldPath], { stdio: \"pipe\" });\n heldOpen = true;\n } catch {\n // lsof exits non-zero when nothing has the file open — the expected case.\n }\n if (heldOpen) {\n console.log(\" federation database (live): held open by the daemon — restarting it first\");\n execFileSync(\"pai\", [\"daemon\", \"restart\"], { stdio: \"inherit\" });\n execFileSync(\"sleep\", [\"2\"]);\n }\n\n const result = migratePaiFile(newPath, [oldPath], { dryRun: false });\n if (result.fromPath) checkpointAndVerify(oldPath, newPath, \"federation database\");\n return result;\n}\n","/**\n * `pai registry reconnect` — point projects back at the transcripts they lost.\n *\n * A project's `encoded_dir` is written once and never updated when the project\n * moves, so every transcript lookup for a moved project quietly returns\n * nothing: no handover, no session digest, no checkpoint. The failure is\n * invisible because an empty result and an unasked question look identical.\n *\n * Repairs are derived from the transcripts themselves — each records the `cwd`\n * it ran in — rather than from a naming rule, because the naming rule is what\n * broke. Dry-run by default: this rewrites registry rows, and a repair that\n * guesses wrong reattaches a project to someone else's history.\n */\n\nimport { join } from \"node:path\";\nimport chalk from \"chalk\";\nimport {\n scanTranscriptFolders,\n findMovedProjects,\n transcriptFiles,\n claudeProjectsDir,\n type RegistryProjectRow,\n} from \"../../../registry/moved.js\";\nimport { legacyDbDisplayPaths } from \"../../../storage/sqlite/legacy-migration.js\";\nimport { getRegistryBackend } from \"../../../storage/factory.js\";\nimport { encodeDir } from \"../../utils.js\";\nimport { dim, ok, warn } from \"../../utils.js\";\n\nexport async function cmdReconnect(opts: { execute?: boolean }): Promise<void> {\n const backend = await getRegistryBackend();\n const active = await backend.listProjectsWithSessionStats({ status: \"active\" });\n const rows: RegistryProjectRow[] = active.map((p) => ({\n id: p.id,\n slug: p.slug,\n root_path: p.root_path,\n encoded_dir: p.encoded_dir,\n sessions: p.session_count,\n }));\n\n const base = claudeProjectsDir();\n const has = (name: string | null): boolean =>\n Boolean(name) && transcriptFiles(join(base, name!)).length > 0;\n\n // Mirrors resolveTranscriptDir: stored value first, derived from the current\n // root path second. A project either of those already handles is not broken,\n // and rewriting it would be churn dressed as a repair.\n const resolvesNow = (r: RegistryProjectRow): boolean =>\n has(r.encoded_dir) || has(encodeDir(r.root_path));\n\n const moved = findMovedProjects(rows, scanTranscriptFolders(base), resolvesNow);\n\n console.log();\n if (moved.length === 0) {\n console.log(ok(\" Nothing to reconnect. \"));\n console.log(dim(\" Every active project resolves to a folder holding transcripts.\"));\n console.log();\n return;\n }\n\n console.log(\n chalk.bold(` ${moved.length} project(s) point at the wrong transcript folder`)\n );\n console.log();\n for (const m of moved) {\n const cost = m.sessions > 0 ? chalk.yellow(`${m.sessions} sessions unreachable`) : dim(\"no sessions recorded\");\n console.log(` ${chalk.cyan(m.slug)} ${cost}`);\n console.log(dim(` stored: ${m.storedDir ?? \"(none)\"}`));\n console.log(dim(` correct: ${m.correctDir} (${m.transcripts} transcripts)`));\n }\n console.log();\n\n if (!opts.execute) {\n console.log(dim(\" Dry run. Re-run with --execute to write these.\"));\n console.log();\n return;\n }\n\n // Best-effort in sequence: a partial repair would leave the registry in a\n // state nobody chose, but RegistryBackend has no cross-row transaction\n // primitive to make this atomic the way the old raw-SQL transaction was.\n try {\n for (const m of moved) {\n await backend.updateProjectPath(m.id, { encodedDir: m.correctDir });\n }\n console.log(ok(` Reconnected ${moved.length} project(s).`));\n const recovered = moved.reduce((n, m) => n + m.sessions, 0);\n if (recovered > 0) {\n console.log(dim(` ${recovered} session(s) are reachable again.`));\n }\n } catch (e) {\n console.error(warn(\" Reconnect failed partway through: \") + (e instanceof Error ? e.message : String(e)));\n }\n console.log();\n}\n\n/** Path to the registry, for callers that want to back it up first. */\nexport const REGISTRY_PATH = legacyDbDisplayPaths().registryDb;\n","/** Registry command registration and simple sub-commands (stats, rebuild, lookup). */\n\nimport type { Command } from \"commander\";\nimport { ok, warn, err, dim, bold, fmtDate } from \"../../utils.js\";\nimport { homedir } from \"node:os\";\nimport { existsSync } from \"node:fs\";\nimport { resolve } from \"node:path\";\nimport { cmdScan, loadScanConfig, saveScanConfig, resolveHome } from \"./scan.js\";\nimport { cmdMigrate } from \"./migrate.js\";\nimport { cmdDedupe } from \"./dedupe.js\";\nimport { cmdReconnect } from \"./reconnect.js\";\nimport { legacyDbDisplayPaths } from \"../../../storage/sqlite/legacy-migration.js\";\nimport { getRegistryBackend } from \"../../../storage/factory.js\";\nimport type { RegistryBackend } from \"../../../storage/registry-interface.js\";\n\n// ---------------------------------------------------------------------------\n// stats\n// ---------------------------------------------------------------------------\n\nasync function cmdStats(): Promise<void> {\n const backend = await getRegistryBackend();\n const totalProjects = await backend.countProjects();\n const activeProjects = await backend.countProjects({ status: \"active\" });\n const archivedProjects = await backend.countProjects({ status: \"archived\" });\n const totalSessions = await backend.countSessions();\n const totalTags = (await backend.listAllTags()).length;\n\n const lastProjectUpdatedAt = await backend.getMostRecentProjectUpdatedAt();\n const lastSessionCreatedAt = await backend.getMostRecentSessionCreatedAt();\n\n console.log();\n console.log(bold(\" PAI Registry Stats\"));\n console.log();\n console.log(` ${bold(\"Projects:\")} ${totalProjects}`);\n console.log(` ${bold(\" Active:\")} ${activeProjects}`);\n console.log(` ${bold(\" Archived:\")} ${archivedProjects}`);\n console.log(` ${bold(\"Sessions:\")} ${totalSessions}`);\n console.log(` ${bold(\"Tags:\")} ${totalTags}`);\n if (lastProjectUpdatedAt) {\n console.log(` ${bold(\"Last updated:\")} ${fmtDate(lastProjectUpdatedAt)}`);\n }\n if (lastSessionCreatedAt) {\n console.log(` ${bold(\"Last session:\")} ${fmtDate(lastSessionCreatedAt)}`);\n }\n console.log();\n}\n\n// ---------------------------------------------------------------------------\n// rebuild\n// ---------------------------------------------------------------------------\n\nasync function cmdRebuild(backend: RegistryBackend): Promise<void> {\n console.log(warn(\"Rebuilding registry — all existing data will be erased.\"));\n console.log(dim(\"Clearing all tables ...\"));\n\n await backend.resetRegistry();\n\n console.log(dim(\"Registry cleared. Re-scanning ...\"));\n await cmdScan();\n}\n\n// ---------------------------------------------------------------------------\n// lookup\n// ---------------------------------------------------------------------------\n\nasync function cmdLookup(fsPath: string): Promise<void> {\n const backend = await getRegistryBackend();\n const resolved = resolve(fsPath);\n\n const row = await backend.getProjectByRootPath(resolved);\n\n if (!row) {\n process.exitCode = 1;\n return;\n }\n\n process.stdout.write(row.slug + \"\\n\");\n}\n\n// ---------------------------------------------------------------------------\n// Commander registration\n// ---------------------------------------------------------------------------\n\nexport function registerRegistryCommands(registryCmd: Command): void {\n // pai registry scan\n registryCmd\n .command(\"scan\")\n .description(\"Walk ~/.claude/projects/ and configured scan_dirs, upsert all projects\")\n .option(\"--add-dir <path>\", \"Add a directory to scan_dirs config\")\n .option(\"--remove-dir <path>\", \"Remove a directory from scan_dirs config\")\n .option(\"--show-dirs\", \"Show currently configured scan directories\")\n .option(\"--quick\", \"Minimal output mode (used by hooks and daemon)\")\n .action(async (opts: { addDir?: string; removeDir?: string; showDirs?: boolean; quick?: boolean }) => {\n if (opts.showDirs) {\n const config = loadScanConfig();\n if (!config.scan_dirs.length) {\n console.log(dim(\" No extra scan directories configured.\"));\n console.log(dim(\" Use --add-dir <path> to add one.\"));\n } else {\n console.log(bold(\" Configured scan directories:\"));\n for (const d of config.scan_dirs) {\n console.log(` ${d}`);\n }\n }\n return;\n }\n if (opts.addDir) {\n const config = loadScanConfig();\n const resolved = resolveHome(opts.addDir);\n if (!existsSync(resolved)) {\n console.error(err(`Directory not found: ${resolved}`));\n process.exitCode = 1;\n return;\n }\n const display = resolved.startsWith(homedir())\n ? \"~\" + resolved.slice(homedir().length)\n : resolved;\n if (config.scan_dirs.includes(display) || config.scan_dirs.includes(resolved)) {\n console.log(warn(`Already configured: ${display}`));\n } else {\n config.scan_dirs.push(display);\n saveScanConfig(config);\n console.log(ok(`Added scan directory: ${bold(display)}`));\n }\n }\n if (opts.removeDir) {\n const config = loadScanConfig();\n const resolved = resolveHome(opts.removeDir);\n const display = resolved.startsWith(homedir())\n ? \"~\" + resolved.slice(homedir().length)\n : resolved;\n const before = config.scan_dirs.length;\n config.scan_dirs = config.scan_dirs.filter((d) => resolveHome(d) !== resolved);\n if (config.scan_dirs.length < before) {\n saveScanConfig(config);\n console.log(ok(`Removed scan directory: ${bold(display)}`));\n } else {\n console.log(warn(`Not found in config: ${display}`));\n }\n }\n if (!opts.addDir && !opts.removeDir) {\n await cmdScan({ quick: opts.quick });\n }\n });\n\n // pai registry migrate\n registryCmd\n .command(\"migrate\")\n .description(\"Import data from ~/.claude/session-registry.json\")\n .action(async () => {\n await cmdMigrate();\n });\n\n // pai registry stats\n registryCmd\n .command(\"stats\")\n .description(\"Show summary statistics for the registry\")\n .action(async () => {\n await cmdStats();\n });\n\n // pai registry rebuild\n registryCmd\n .command(\"rebuild\")\n .description(\"Erase all registry data and rebuild from the filesystem (destructive)\")\n .action(async () => {\n await cmdRebuild(await getRegistryBackend());\n });\n\n // pai registry dedupe [--execute]\n registryCmd\n .command(\"dedupe\")\n .description(\n \"Merge registry rows that describe the same project.\\n\" +\n \"Two spellings of one directory (e.g. via a symlinked path prefix) register\\n\" +\n \"as separate projects and split session history between them. Rows are grouped\\n\" +\n \"by resolved path, so a merge only happens when the paths are provably identical.\\n\" +\n \"Dry-run by default; --execute backs up the registry first and merges in one transaction.\"\n )\n .option(\"--execute\", \"Actually perform the merge (default is dry-run)\")\n .action(async (opts: { execute?: boolean }) => {\n await cmdDedupe({\n execute: opts.execute,\n dbPath: legacyDbDisplayPaths().registryDb,\n });\n });\n\n // pai registry reconnect\n registryCmd\n .command(\"reconnect\")\n .description(\n \"Point projects back at the transcripts they lost.\\n\" +\n \"A project's encoded_dir is written once and never updated when the project\\n\" +\n \"moves, so handovers, session digests and checkpoints silently find nothing.\\n\" +\n \"Repairs are read from the transcripts themselves — each records the cwd it ran\\n\" +\n \"in — rather than re-derived from the naming rule that broke.\\n\" +\n \"Dry-run by default; --execute writes the corrected rows in one transaction.\"\n )\n .option(\"--execute\", \"Actually write the corrections (default is dry-run)\")\n .action(async (opts: { execute?: boolean }) => {\n await cmdReconnect({ execute: opts.execute });\n });\n\n // pai registry lookup --path <path>\n registryCmd\n .command(\"lookup\")\n .description(\"Find the project slug for a filesystem path (for use in scripts)\")\n .requiredOption(\"--path <path>\", \"Filesystem path to look up\")\n .action(async (opts: { path: string }) => {\n await cmdLookup(opts.path);\n });\n}\n","/** Memory embed command: generate embeddings for un-embedded chunks. */\n\nimport type { Command } from \"commander\";\nimport type { StorageBackend } from \"../../../storage/interface.js\";\nimport { getStorageBackend, getRegistryBackend } from \"../../../storage/factory.js\";\nimport { dim, bold, ok, err } from \"../../utils.js\";\n\n// ---------------------------------------------------------------------------\n// Shared embed runner (used by both index --embed and embed sub-command)\n// ---------------------------------------------------------------------------\n\n/** Rows fetched per DB round-trip. Keeps memory flat regardless of backlog size. */\nconst DEFAULT_PAGE_SIZE = 500;\n\nexport async function runEmbed(\n backend: StorageBackend,\n projectId?: number,\n projectSlug?: string,\n batchSize = 50,\n pageSize = DEFAULT_PAGE_SIZE,\n): Promise<void> {\n const label = projectSlug ? `project ${projectSlug}` : \"all projects\";\n console.log(dim(`Generating embeddings for ${label} (this may take a while on first run)...`));\n\n const { generateEmbeddings, serializeEmbedding } = await import(\"../../../memory/embeddings.js\");\n\n // Estimate, not an exact unembedded count: cheap (one row) vs. scanning the\n // whole backlog just to size a progress bar.\n const { chunks: totalEstimate } =\n projectId !== undefined ? await backend.getProjectStats(projectId) : await backend.getStats();\n\n let done = 0;\n let after: { projectId: number; id: string } | null = null;\n\n while (true) {\n const page = await backend.getUnembeddedChunkIds(projectId, pageSize, after);\n if (page.length === 0) break;\n\n for (let i = 0; i < page.length; i += batchSize) {\n const batch = page.slice(i, i + batchSize);\n const vecs = await generateEmbeddings(batch.map((r) => r.text));\n await Promise.all(batch.map((row, j) => backend.updateEmbedding(row.id, serializeEmbedding(vecs[j]))));\n done += batch.length;\n process.stdout.write(`\\r ${done} / ~${totalEstimate} chunks embedded...`);\n }\n\n const last = page[page.length - 1];\n after = { projectId: last.project_id, id: last.id };\n }\n\n process.stdout.write(\"\\r\");\n console.log(ok(`Done.`) + ` ${bold(String(done))} chunks embedded`);\n}\n\n// ---------------------------------------------------------------------------\n// Commander registration\n// ---------------------------------------------------------------------------\n\nexport function registerEmbedCommand(memoryCmd: Command): void {\n memoryCmd\n .command(\"embed [project-slug]\")\n .description(\"Generate embeddings for un-embedded chunks (Phase 2.5)\")\n .option(\"--batch-size <n>\", \"Chunks to embed per batch\", \"50\")\n .action(async (projectSlug: string | undefined, opts: { batchSize?: string }) => {\n let backend: StorageBackend;\n try {\n backend = await getStorageBackend();\n } catch (e) {\n console.error(err(`Failed to open storage backend: ${e}`));\n process.exitCode = 1;\n return;\n }\n\n if (projectSlug) {\n const registry = await getRegistryBackend();\n const project = await registry.getProjectBySlug(projectSlug);\n\n if (!project) {\n console.error(err(`Project not found: ${projectSlug}`));\n process.exitCode = 1;\n return;\n }\n\n await runEmbed(backend, project.id, project.slug, parseInt(opts.batchSize ?? \"50\", 10));\n } else {\n await runEmbed(backend, undefined, undefined, parseInt(opts.batchSize ?? \"50\", 10));\n }\n });\n}\n","/** Memory index command: index one or all projects into the memory store. */\n\nimport type { Command } from \"commander\";\nimport type { StorageBackend } from \"../../../storage/interface.js\";\nimport type { IndexResult } from \"../../../memory/indexer-backend.js\";\nimport { indexProjectWithBackend } from \"../../../memory/indexer-backend.js\";\nimport { getStorageBackend, getRegistryBackend } from \"../../../storage/factory.js\";\nimport { dim, bold, ok, err } from \"../../utils.js\";\nimport { PaiClient } from \"../../../daemon/ipc-client.js\";\nimport { loadConfig } from \"../../../daemon/config.js\";\nimport { runEmbed } from \"./embed.js\";\n\n/**\n * Index every active project through the StorageBackend. Loops\n * indexProjectWithBackend() per project rather than the sync indexer's\n * indexAll(), since that one still takes a raw registry Database handle.\n */\nasync function indexAllProjects(\n backend: StorageBackend,\n): Promise<{ projects: number; result: IndexResult }> {\n const registry = await getRegistryBackend();\n const projects = await registry.listProjects({ status: \"active\" });\n\n const totals: IndexResult = { filesProcessed: 0, chunksCreated: 0, filesSkipped: 0 };\n for (const project of projects) {\n const r = await indexProjectWithBackend(backend, project.id, project.root_path, project.claude_notes_dir);\n totals.filesProcessed += r.filesProcessed;\n totals.chunksCreated += r.chunksCreated;\n totals.filesSkipped += r.filesSkipped;\n }\n return { projects: projects.length, result: totals };\n}\n\nexport function registerIndexCommand(memoryCmd: Command): void {\n memoryCmd\n .command(\"index [project-slug]\")\n .description(\"Index memory files for one project or all projects\")\n .option(\"--all\", \"Index all active projects (default when no slug given)\")\n .option(\"--embed\", \"Also generate embeddings for newly indexed chunks (Phase 2.5)\")\n .option(\"--direct\", \"Skip daemon IPC and run index directly (for debugging)\")\n .action(async (projectSlug: string | undefined, opts: { all?: boolean; embed?: boolean; direct?: boolean }) => {\n // If daemon is running and no --direct flag, trigger via IPC (non-blocking)\n if (!opts.direct && !projectSlug) {\n try {\n const config = loadConfig();\n const client = new PaiClient(config.socketPath);\n await client.triggerIndex();\n console.log(ok(\"Index triggered in daemon.\") + dim(\" Check daemon logs for progress.\"));\n console.log(dim(\" Run `pai daemon logs` to watch progress.\"));\n return;\n } catch {\n console.log(dim(\"Daemon not running. Running direct index...\"));\n }\n }\n\n let backend: StorageBackend;\n try {\n backend = await getStorageBackend();\n } catch (e) {\n console.error(err(`Failed to open storage backend: ${e}`));\n process.exitCode = 1;\n return;\n }\n\n if (projectSlug) {\n const registry = await getRegistryBackend();\n const project = await registry.getProjectBySlug(projectSlug);\n\n if (!project || project.status !== \"active\") {\n console.error(err(`Project not found or not active: ${projectSlug}`));\n process.exitCode = 1;\n return;\n }\n\n console.log(dim(`Indexing ${project.display_name} (${project.slug})...`));\n const result = await indexProjectWithBackend(backend, project.id, project.root_path, project.claude_notes_dir);\n\n console.log(\n ok(`Done.`) +\n ` ${bold(String(result.filesProcessed))} files indexed` +\n `, ${bold(String(result.chunksCreated))} chunks created` +\n `, ${dim(String(result.filesSkipped) + \" skipped (unchanged)\")}`,\n );\n\n if (opts.embed) {\n await runEmbed(backend, project.id, project.slug);\n }\n\n } else if (opts.all || !projectSlug) {\n console.log(dim(\"Indexing all active projects...\"));\n\n const { projects, result } = await indexAllProjects(backend);\n\n console.log(\n ok(`Done.`) +\n ` ${bold(String(projects))} projects` +\n `, ${bold(String(result.filesProcessed))} files indexed` +\n `, ${bold(String(result.chunksCreated))} chunks created` +\n `, ${dim(String(result.filesSkipped) + \" skipped (unchanged)\")}`,\n );\n\n if (opts.embed) {\n await runEmbed(backend);\n }\n }\n });\n}\n","/** Memory search command: BM25/semantic/hybrid search across the memory store. */\n\nimport type { Command } from \"commander\";\nimport chalk from \"chalk\";\nimport { applyRecencyBoost, type SearchResult } from \"../../../memory/search.js\";\nimport { dim, bold, ok, warn, err } from \"../../utils.js\";\nimport { loadConfig } from \"../../../daemon/config.js\";\nimport { getStorageBackend, getRegistryBackend } from \"../../../storage/factory.js\";\n\n// ---------------------------------------------------------------------------\n// Helpers\n// ---------------------------------------------------------------------------\n\nfunction tierColor(tier: string): string {\n switch (tier) {\n case \"evergreen\": return chalk.green(tier);\n case \"daily\": return chalk.yellow(tier);\n case \"topic\": return chalk.blue(tier);\n case \"session\": return chalk.dim(tier);\n default: return tier;\n }\n}\n\n/** Populate projectSlug on results by looking up the small set of distinct project ids involved. */\nasync function populateSlugsFromRegistry(results: SearchResult[]): Promise<SearchResult[]> {\n if (results.length === 0) return results;\n const registry = await getRegistryBackend();\n const ids = [...new Set(results.map((r) => r.projectId))];\n const slugMap = new Map<number, string>();\n for (const id of ids) {\n const project = await registry.getProjectById(id);\n if (project) slugMap.set(id, project.slug);\n }\n return results.map((r) => ({ ...r, projectSlug: slugMap.get(r.projectId) }));\n}\n\n// ---------------------------------------------------------------------------\n// Commander registration\n// ---------------------------------------------------------------------------\n\nexport function registerSearchCommand(memoryCmd: Command): void {\n memoryCmd\n .command(\"search <query>\")\n .description(\"Search indexed memory (BM25 keyword, semantic, or hybrid)\")\n .option(\"--project <slug>\", \"Restrict search to a specific project\")\n .option(\"--source <source>\", \"Restrict to 'memory' or 'notes'\")\n .option(\"--limit <n>\", \"Maximum results to return\")\n .option(\"--mode <mode>\", \"Search mode: keyword (default), semantic, hybrid\")\n .option(\"--no-rerank\", \"Skip cross-encoder reranking (reranking is on by default)\")\n .option(\"--recency <days>\", \"Apply recency boost: score halves every N days. 0 = off\")\n .action(\n async (\n query: string,\n opts: { project?: string; source?: string; limit?: string; mode?: string; rerank: boolean; recency?: string },\n ) => {\n const config = loadConfig();\n const searchConfig = config.search;\n\n const maxResults = parseInt(opts.limit ?? String(searchConfig.defaultLimit), 10);\n const mode = (opts.mode ?? searchConfig.mode) as \"keyword\" | \"semantic\" | \"hybrid\";\n\n if (![\"keyword\", \"semantic\", \"hybrid\"].includes(mode)) {\n console.error(err(`Invalid mode: ${mode}. Use keyword, semantic, or hybrid.`));\n process.exitCode = 1;\n return;\n }\n\n let projectIds: number[] | undefined;\n if (opts.project) {\n const registry = await getRegistryBackend();\n const project = await registry.getProjectBySlug(opts.project);\n\n if (!project) {\n console.error(warn(`Project not found: ${opts.project} — searching all projects`));\n } else {\n projectIds = [project.id];\n }\n }\n\n const sources = opts.source ? [opts.source] : undefined;\n const searchOpts = { projectIds, sources, maxResults };\n\n // getStorageBackend() returns the process-wide shared backend, closed\n // once at process exit (src/storage/factory.ts's beforeExit hook) —\n // this command must not close it itself.\n const backend = await getStorageBackend();\n let results: SearchResult[];\n\n if (mode === \"keyword\") {\n results = await backend.searchKeyword(query, searchOpts);\n\n } else {\n const { generateEmbedding } = await import(\"../../../memory/embeddings.js\");\n\n console.log(dim(\"Generating query embedding...\"));\n const queryEmbedding = await generateEmbedding(query, true);\n\n if (mode === \"semantic\") {\n results = await backend.searchSemantic(queryEmbedding, searchOpts);\n } else {\n // Hybrid: combine keyword (BM25) and semantic results with min-max normalization\n const [keywordResults, semanticResults] = await Promise.all([\n backend.searchKeyword(query, { ...searchOpts, maxResults: 500 }),\n backend.searchSemantic(queryEmbedding, { ...searchOpts, maxResults: 500 }),\n ]);\n\n if (keywordResults.length === 0 && semanticResults.length === 0) {\n results = [];\n } else {\n const keyFor = (r: SearchResult) =>\n `${r.projectId}:${r.path}:${r.startLine}:${r.endLine}`;\n\n function minMaxNormalize(items: SearchResult[]): Map<string, number> {\n if (items.length === 0) return new Map();\n const min = Math.min(...items.map((r) => r.score));\n const max = Math.max(...items.map((r) => r.score));\n const range = max - min;\n const m = new Map<string, number>();\n for (const r of items) {\n m.set(keyFor(r), range === 0 ? 1 : (r.score - min) / range);\n }\n return m;\n }\n\n const kwNorm = minMaxNormalize(keywordResults);\n const semNorm = minMaxNormalize(semanticResults);\n const allKeys = new Set<string>([\n ...keywordResults.map(keyFor),\n ...semanticResults.map(keyFor),\n ]);\n const metaMap = new Map<string, SearchResult>();\n for (const r of [...keywordResults, ...semanticResults]) {\n metaMap.set(keyFor(r), r);\n }\n\n const combined: SearchResult[] = [];\n for (const key of allKeys) {\n const meta = metaMap.get(key)!;\n const combinedScore = 0.5 * (kwNorm.get(key) ?? 0) + 0.5 * (semNorm.get(key) ?? 0);\n combined.push({ ...meta, score: combinedScore });\n }\n\n results = combined.sort((a, b) => b.score - a.score).slice(0, maxResults);\n }\n }\n }\n\n if (!results || results.length === 0) {\n console.log(dim(`No results found for: \"${query}\" (mode: ${mode})`));\n return;\n }\n\n // Cross-encoder reranking (on by default, skip with --no-rerank)\n if (opts.rerank !== false) {\n const { rerankResults } = await import(\"../../../memory/reranker.js\");\n console.log(dim(\"Reranking with cross-encoder...\"));\n results = await rerankResults(query, results, { topK: maxResults });\n }\n\n // Recency boost (applied after reranking)\n const recencyDays = parseInt(opts.recency ?? String(searchConfig.recencyBoostDays), 10);\n if (recencyDays > 0) {\n console.log(dim(`Applying recency boost (half-life: ${recencyDays} days)...`));\n results = applyRecencyBoost(results, recencyDays);\n }\n\n const withSlugs = await populateSlugsFromRegistry(results);\n const rerankLabel = opts.rerank !== false ? \" +rerank\" : \"\";\n const modeLabel = mode !== \"keyword\" ? ` [${mode}${rerankLabel}]` : (opts.rerank !== false ? ` [rerank]` : \"\");\n\n console.log(\n `\\n ${bold(`Search results for: \"${query}\"`)}${modeLabel} ${dim(`(${withSlugs.length} found)`)}\\n`,\n );\n\n for (const result of withSlugs) {\n const projectLabel = result.projectSlug\n ? chalk.cyan(result.projectSlug)\n : chalk.cyan(String(result.projectId));\n\n const tierLabel = tierColor(result.tier);\n const scoreLabel = dim(`score: ${result.score.toFixed(4)}`);\n const locationLabel = dim(`${result.path}:${result.startLine}-${result.endLine}`);\n\n console.log(` ${projectLabel} ${tierLabel} ${locationLabel} ${scoreLabel}`);\n\n const snippetLines = result.snippet\n .split(\"\\n\")\n .slice(0, 3)\n .map((l) => ` ${l}`);\n console.log(snippetLines.join(\"\\n\"));\n console.log();\n }\n },\n );\n}\n","/** Memory status and settings commands for the PAI memory index. */\n\nimport type { Command } from \"commander\";\nimport { dim, bold, ok, err } from \"../../utils.js\";\nimport { loadConfig, paiConfigFilePath, readMainConfigRaw, writeMainConfigRaw } from \"../../../daemon/config.js\";\nimport { getStorageBackend, getRegistryBackend } from \"../../../storage/factory.js\";\n\nexport function registerStatsCommands(memoryCmd: Command): void {\n\n // -------------------------------------------------------------------------\n // pai memory status [project-slug]\n // -------------------------------------------------------------------------\n\n memoryCmd\n .command(\"status [project-slug]\")\n .description(\"Show memory index statistics\")\n .action(async (projectSlug: string | undefined) => {\n const backend = await getStorageBackend();\n const backendType = loadConfig().storageBackend;\n\n if (projectSlug) {\n const registry = await getRegistryBackend();\n const project = await registry.getProjectBySlug(projectSlug);\n\n if (!project) {\n console.error(err(`Project not found: ${projectSlug}`));\n process.exitCode = 1;\n return;\n }\n\n // StorageBackend has no per-project file/chunk/tier breakdown today\n // (only the global getStats() total) — see worker report for the\n // missing method (getMemoryStats(projectId?) with per-tier counts\n // and last-indexed timestamp).\n console.log(`\\n ${bold(project.display_name)} ${dim(`(${project.slug})`)}\\n`);\n console.log(dim(` Per-project breakdown is not available via the storage backend yet.`));\n console.log(dim(` Authoritative counts: ${bold(\"pai daemon status\")}`));\n console.log();\n\n } else {\n const stats = await backend.getStats();\n\n console.log(`\\n ${bold(\"PAI Memory Index — Global Status\")}\\n`);\n console.log(` ${bold(\"Storage backend:\")} ${backendType}`);\n console.log(` ${bold(\"Total files:\")} ${stats.files} ${bold(\"Total chunks:\")} ${stats.chunks}`);\n console.log();\n console.log(dim(` Per-project breakdown is not available via the storage backend yet.`));\n console.log(dim(` Authoritative counts: ${bold(\"pai daemon status\")}`));\n console.log();\n }\n });\n\n // -------------------------------------------------------------------------\n // pai memory settings [key] [value]\n // -------------------------------------------------------------------------\n\n memoryCmd\n .command(\"settings [key] [value]\")\n .description(\"View or modify search settings in the PAI config file (`pai config path`)\")\n .action((key: string | undefined, value: string | undefined) => {\n const config = loadConfig();\n const search = config.search;\n\n if (!key) {\n console.log(`\\n ${bold(\"PAI Memory — Search Settings\")}\\n`);\n console.log(` ${bold(\"mode:\")} ${search.mode}`);\n console.log(` ${bold(\"rerank:\")} ${search.rerank}`);\n console.log(` ${bold(\"recencyBoostDays:\")} ${search.recencyBoostDays}`);\n console.log(` ${bold(\"defaultLimit:\")} ${search.defaultLimit}`);\n console.log(` ${bold(\"snippetLength:\")} ${search.snippetLength}`);\n console.log();\n console.log(dim(` Config file: ${paiConfigFilePath()}`));\n console.log(dim(` Edit directly or use: pai memory settings <key> <value>`));\n console.log();\n return;\n }\n\n if (!value) {\n const val = (search as unknown as Record<string, unknown>)[key];\n if (val === undefined) {\n console.error(err(`Unknown setting: ${key}`));\n console.log(dim(` Valid keys: mode, rerank, recencyBoostDays, defaultLimit, snippetLength`));\n process.exitCode = 1;\n return;\n }\n console.log(String(val));\n return;\n }\n\n const validKeys = new Set([\"mode\", \"rerank\", \"recencyBoostDays\", \"defaultLimit\", \"snippetLength\"]);\n if (!validKeys.has(key)) {\n console.error(err(`Unknown setting: ${key}`));\n console.log(dim(` Valid keys: ${[...validKeys].join(\", \")}`));\n process.exitCode = 1;\n return;\n }\n\n let fileConfig: Record<string, unknown>;\n try {\n fileConfig = readMainConfigRaw();\n } catch (e) {\n console.error(err(`Could not read config: ${e instanceof Error ? e.message : String(e)}`));\n process.exitCode = 1;\n return;\n }\n\n if (!fileConfig.search || typeof fileConfig.search !== \"object\") {\n fileConfig.search = {};\n }\n\n let parsed: string | number | boolean;\n if (key === \"mode\") {\n if (![\"keyword\", \"semantic\", \"hybrid\"].includes(value)) {\n console.error(err(`Invalid mode: ${value}. Must be keyword, semantic, or hybrid.`));\n process.exitCode = 1;\n return;\n }\n parsed = value;\n } else if (key === \"rerank\") {\n parsed = value === \"true\" || value === \"1\" || value === \"on\";\n } else {\n parsed = parseInt(value, 10);\n if (isNaN(parsed)) {\n console.error(err(`Invalid number: ${value}`));\n process.exitCode = 1;\n return;\n }\n }\n\n (fileConfig.search as Record<string, unknown>)[key] = parsed;\n\n try {\n writeMainConfigRaw(fileConfig);\n console.log(ok(`Set search.${key} = ${parsed}`));\n console.log(dim(` Restart daemon to apply: pai daemon restart`));\n } catch (e) {\n console.error(err(`Could not write config: ${e}`));\n process.exitCode = 1;\n }\n });\n}\n","/** Registers `pai memory sources` — what the indexer has taken in. */\n\nimport type { Command } from \"commander\";\nimport { err, dim } from \"../../utils.js\";\n\nexport function registerSourcesCommand(memoryCmd: Command): void {\n memoryCmd\n .command(\"sources\")\n .description(\n \"Show what the indexer has taken in: composition by source, which roots\\n\" +\n \"content enters from, the heaviest single files, and how many chunks are\\n\" +\n \"rewritten per day (which distinguishes a finite backlog from a treadmill).\"\n )\n .option(\"--limit <n>\", \"Rows per section (default 8)\", \"8\")\n .action(async (opts: { limit?: string }) => {\n // Reads whichever backend is configured — unlike `memory status`, which\n // reads the SQLite federation file directly and therefore has nothing to\n // say when the live index lives in Postgres.\n const { createStorageBackend } = await import(\"../../../storage/factory.js\");\n const { loadConfig } = await import(\"../../../daemon/config.js\");\n const { cmdMemorySources } = await import(\"./sources.js\");\n\n let backend;\n try {\n backend = await createStorageBackend(loadConfig());\n } catch (e) {\n console.error(err(`Cannot reach the storage backend.`));\n console.error(dim(` ${e instanceof Error ? e.message : String(e)}`));\n process.exitCode = 1;\n return;\n }\n\n try {\n await cmdMemorySources(backend, {\n limit: Math.max(1, parseInt(opts.limit ?? \"8\", 10) || 8),\n });\n } finally {\n await backend.close?.().catch?.(() => {});\n }\n });\n}\n","/** Barrel: registers all memory sub-commands and re-exports registerMemoryCommands. */\n\nimport type { Command } from \"commander\";\nimport { registerIndexCommand } from \"./index-cmd.js\";\nimport { registerEmbedCommand } from \"./embed.js\";\nimport { registerSearchCommand } from \"./search.js\";\nimport { registerStatsCommands } from \"./stats.js\";\nimport { registerSourcesCommand } from \"./sources-cmd.js\";\n\nexport function registerMemoryCommands(memoryCmd: Command): void {\n registerIndexCommand(memoryCmd);\n registerEmbedCommand(memoryCmd);\n registerSearchCommand(memoryCmd);\n registerStatsCommands(memoryCmd);\n registerSourcesCommand(memoryCmd);\n}\n","/**\n * Who \"me\" is.\n *\n * One question is asked of this module: **is this address the user's own?**\n * It exists because the alternative is an assistant deciding that per message,\n * from context, at the moment it is about to act — and \"that looks like his\n * address\" is not a judgement worth re-making every time. Here it is a lookup\n * against a list the user maintains.\n *\n * The rule the list serves: a mail whose recipients are all the user's own may\n * be sent; anything else is a draft for review. So a false positive here means\n * mail leaving without review. Everything below is written to fail closed —\n * an empty list means nothing is self-addressed, an unparseable address is not\n * self-addressed, and no pattern is ever inferred.\n *\n * Aliases are NOT matched by pattern, deliberately. `matthias.nott+x@gmail.com`\n * and `owner.name@example.com` are the same mailbox, and Gmail also ignores\n * dots — but the general forms of those rules (\"same local part before a plus\",\n * \"same domain\") also match addresses belonging to other people. Gmail's own\n * normalisation is Gmail's; other providers treat `+` and `.` as ordinary\n * characters, and `first.last@company.com` is a different person from\n * `firstlast@company.com`. Anything that should count gets listed.\n */\n\n// ---------------------------------------------------------------------------\n// Normalisation\n// ---------------------------------------------------------------------------\n\n/**\n * Reduce an address to a comparable form: trimmed, unwrapped, lowercased.\n *\n * Handles `Display Name <addr@example.com>` because recipient fields carry that\n * form routinely. Returns null when there is nothing usable, which callers\n * treat as \"not the user's\" rather than as an error — an address that cannot be\n * parsed is one whose ownership cannot be established.\n */\nexport function normalizeEmail(raw: string | null | undefined): string | null {\n if (!raw) return null;\n\n let value = raw.trim();\n if (!value) return null;\n\n const angled = value.match(/<([^>]*)>/);\n if (angled) value = angled[1].trim();\n\n value = value.toLowerCase();\n\n // Must look like an address at all: exactly one @, with something either side\n // and a dot in the domain. This is a sanity check, not validation — its job\n // is to reject junk before it can be compared, not to police correctness.\n if (!/^[^@\\s]+@[^@\\s]+\\.[^@\\s]+$/.test(value)) return null;\n\n return value;\n}\n\n// ---------------------------------------------------------------------------\n// Ownership\n// ---------------------------------------------------------------------------\n\nexport interface IdentityLike {\n selfEmails?: string[];\n deliverTo?: string;\n sendingAccount?: string;\n}\n\n/** Is this address one the user has declared as their own? */\nexport function isSelfAddress(address: string, identity: IdentityLike | undefined): boolean {\n const candidate = normalizeEmail(address);\n if (!candidate) return false;\n\n const own = (identity?.selfEmails ?? [])\n .map(normalizeEmail)\n .filter((e): e is string => e !== null);\n\n return own.includes(candidate);\n}\n\n/**\n * May a mail to these recipients be sent without review?\n *\n * Every recipient must be the user's own — To, Cc and Bcc together, since a\n * single outsider on Bcc is still an outsider. An empty recipient list is not\n * permission: there is nothing to check, so it is refused.\n *\n * Returns the offending addresses rather than a bare false, so the caller can\n * say which recipient forced the draft instead of reporting a flat refusal.\n */\nexport interface SendDecision {\n allowed: boolean;\n /** Recipients that are not the user's own. Empty when allowed. */\n foreign: string[];\n reason: string;\n}\n\nexport function maySendWithoutReview(\n recipients: Array<string | null | undefined>,\n identity: IdentityLike | undefined\n): SendDecision {\n const listed = recipients.filter((r): r is string => Boolean(r && r.trim()));\n\n if (listed.length === 0) {\n return { allowed: false, foreign: [], reason: \"No recipients to check\" };\n }\n\n if (!identity?.selfEmails?.length) {\n return {\n allowed: false,\n foreign: listed,\n reason:\n \"No identity.selfEmails configured — nothing counts as your own address yet. \" +\n \"Run `pai identity add <email>`.\",\n };\n }\n\n const foreign = listed.filter((r) => !isSelfAddress(r, identity));\n\n if (foreign.length > 0) {\n return {\n allowed: false,\n foreign,\n reason: `Not your own address: ${foreign.join(\", \")} — draft for review instead`,\n };\n }\n\n return { allowed: true, foreign: [], reason: \"All recipients are your own address\" };\n}\n\n// ---------------------------------------------------------------------------\n// Delivery reachability\n// ---------------------------------------------------------------------------\n\n/**\n * Would a mail from `sendingAccount` to `deliverTo` actually reach an inbox?\n *\n * Gmail files a message sent from an account to itself, or to one of its own\n * domain aliases, under Sent only — it never appears in the inbox. The send\n * reports success, so the failure is invisible and indistinguishable from\n * delivery. This cost a real digest on 2026-08-01: owner@example.ch →\n * owner@example.de, sent cleanly, never seen.\n *\n * Detecting it in general is not possible from the addresses alone — whether\n * two addresses are the same Google account is a fact about the account, not\n * about their spelling. So this reports what it can see and says so plainly:\n * an exact match is certain, a shared domain is suspicious and worth warning\n * about, and anything else is unknown rather than fine.\n */\nexport type Reachability = \"unreachable\" | \"suspect\" | \"unknown\";\n\nexport interface ReachabilityVerdict {\n verdict: Reachability;\n reason: string;\n}\n\nexport function checkDeliveryReachability(\n deliverTo: string | undefined,\n sendingAccount: string | undefined,\n sendingAccountAliases: string[] = []\n): ReachabilityVerdict {\n const to = normalizeEmail(deliverTo);\n const from = normalizeEmail(sendingAccount);\n\n if (!to || !from) {\n return { verdict: \"unknown\", reason: \"Delivery address or sending account not configured\" };\n }\n\n if (to === from) {\n return {\n verdict: \"unreachable\",\n reason:\n `${to} is the sending account itself. Gmail files self-sends under Sent only — ` +\n `the send will report success and never reach the inbox.`,\n };\n }\n\n // The declared alias list is the only reliable signal, and it is why the list\n // exists. The case that actually cost a digest was owner@example.ch sending to\n // owner@example.de — one Google account behind two DIFFERENT domains, which no\n // comparison of the addresses themselves can reveal. Whether two addresses\n // are one mailbox is a fact about the account, not about their spelling.\n const aliases = sendingAccountAliases\n .map(normalizeEmail)\n .filter((e): e is string => e !== null);\n\n if (aliases.includes(to)) {\n return {\n verdict: \"unreachable\",\n reason:\n `${to} is a declared alias of the sending account (${from}). The send will report ` +\n `success and never reach the inbox. Deliver by adding the INBOX label to the ` +\n `written message instead.`,\n };\n }\n\n const toDomain = to.slice(to.indexOf(\"@\") + 1);\n const fromDomain = from.slice(from.indexOf(\"@\") + 1);\n\n if (toDomain === fromDomain) {\n return {\n verdict: \"suspect\",\n reason:\n `${to} shares a domain with the sending account (${from}). If it is an alias of the ` +\n `same account the send will silently not arrive. Deliver by adding the INBOX label ` +\n `to the written message rather than relying on the send path.`,\n };\n }\n\n // Deliberately not \"fine\". Cross-domain aliases of one account look exactly\n // like this and are undetectable here — which is why label-based delivery is\n // the safe default even when this returns unknown.\n return {\n verdict: \"unknown\",\n reason:\n \"Separate domains and not a declared alias — no suppression predictable from the \" +\n \"addresses alone, which is not the same as delivery being guaranteed.\",\n };\n}\n","/**\n * `pai identity` — declare who \"me\" is.\n *\n * The list this edits is an allowlist for actions that reach the outside world\n * without review, outbound mail being the case it was built for. Adding an\n * address widens what can be sent unreviewed, so every mutation here says what\n * it changed and the command warns rather than staying quiet when a setting\n * looks like it will not work.\n *\n * Edits the same PAI config file (`pai config path`) the daemon reads, preserving\n * everything else in the file — including the `_comment` keys a user may have\n * left for themselves.\n */\n\nimport type { Command } from \"commander\";\nimport { readMainConfigRaw, writeMainConfigRaw, paiConfigFilePath } from \"../../daemon/config.js\";\nimport {\n normalizeEmail,\n checkDeliveryReachability,\n type IdentityLike,\n} from \"../../identity/index.js\";\nimport { ok, err, warn, dim, bold } from \"../utils.js\";\n\ninterface ConfigShape {\n identity?: IdentityLike & { sendingAccountAliases?: string[] };\n [key: string]: unknown;\n}\n\nfunction readConfig(): ConfigShape {\n try {\n return readMainConfigRaw() as ConfigShape;\n } catch (e) {\n console.error(err(\"pai identity: \") + `Could not read ${paiConfigFilePath()}: ${String(e)}`);\n process.exit(1);\n }\n}\n\n/** Writes through writeMainConfigRaw — comment-preserving into config.yaml\n * when it exists, else atomic into config.json. */\nfunction writeConfig(config: ConfigShape): void {\n writeMainConfigRaw(config);\n}\n\nfunction identityOf(config: ConfigShape) {\n return config.identity ?? { selfEmails: [] };\n}\n\nfunction reportReachability(id: IdentityLike & { sendingAccountAliases?: string[] }): void {\n const v = checkDeliveryReachability(\n id.deliverTo,\n id.sendingAccount,\n id.sendingAccountAliases ?? []\n );\n if (v.verdict === \"unreachable\") {\n console.log(err(\" Unreachable: \") + v.reason);\n } else if (v.verdict === \"suspect\") {\n console.log(warn(\" Suspect: \") + v.reason);\n }\n}\n\nexport function registerIdentityCommands(identityCmd: Command): void {\n identityCmd\n .command(\"show\")\n .description(\"Show the configured identity\")\n .action(() => {\n const id = identityOf(readConfig());\n const emails = id.selfEmails ?? [];\n\n console.log();\n console.log(bold(\" Identity\"));\n console.log();\n console.log(` Deliver to: ${id.deliverTo ?? dim(\"not set\")}`);\n console.log(` Sending account: ${id.sendingAccount ?? dim(\"not set\")}`);\n console.log();\n\n if (emails.length === 0) {\n console.log(warn(\" No self addresses configured.\"));\n console.log(\n dim(\" Nothing counts as your own address, so every mail is drafts-only.\")\n );\n console.log(dim(\" Add one with: pai identity add <email>\"));\n } else {\n console.log(bold(` Your own addresses (${emails.length}):`));\n for (const e of emails) console.log(` ${e}`);\n console.log();\n console.log(\n dim(\" Mail addressed only to these may be sent; anything else is a draft.\")\n );\n }\n console.log();\n reportReachability(id);\n console.log();\n });\n\n identityCmd\n .command(\"add <email>\")\n .description(\"Declare an address as your own (widens what can be sent unreviewed)\")\n .action((email: string) => {\n const normalized = normalizeEmail(email);\n if (!normalized) {\n console.error(err(\"pai identity: \") + `Not a usable email address: ${email}`);\n process.exitCode = 1;\n return;\n }\n\n const config = readConfig();\n const id = identityOf(config);\n const emails = (id.selfEmails ?? []).slice();\n\n if (emails.map(normalizeEmail).includes(normalized)) {\n console.log(dim(` ${normalized} is already listed. Nothing changed.`));\n return;\n }\n\n emails.push(normalized);\n config.identity = { ...id, selfEmails: emails };\n writeConfig(config);\n\n console.log(ok(\"Added. \") + `${normalized} now counts as your own address.`);\n console.log(dim(` Mail addressed only to your own addresses may now be sent unreviewed.`));\n });\n\n identityCmd\n .command(\"remove <email>\")\n .description(\"Stop treating an address as your own\")\n .action((email: string) => {\n const normalized = normalizeEmail(email);\n const config = readConfig();\n const id = identityOf(config);\n const before = id.selfEmails ?? [];\n const after = before.filter((e) => normalizeEmail(e) !== normalized);\n\n if (after.length === before.length) {\n console.log(dim(` ${email} was not listed. Nothing changed.`));\n return;\n }\n\n config.identity = { ...id, selfEmails: after };\n writeConfig(config);\n console.log(ok(\"Removed. \") + `${email} is no longer treated as your own.`);\n });\n\n identityCmd\n .command(\"set-delivery <email>\")\n .description(\"Set where digests and 'mail me X' are delivered\")\n .action((email: string) => {\n const normalized = normalizeEmail(email);\n if (!normalized) {\n console.error(err(\"pai identity: \") + `Not a usable email address: ${email}`);\n process.exitCode = 1;\n return;\n }\n\n const config = readConfig();\n const id = identityOf(config);\n config.identity = { ...id, deliverTo: normalized };\n writeConfig(config);\n\n console.log(ok(\"Delivery address set. \") + normalized);\n\n // Warn immediately rather than at send time. A delivery address that\n // cannot arrive fails silently — the send reports success — so the only\n // useful moment to say so is when it is being configured.\n reportReachability(config.identity);\n\n if (!(id.selfEmails ?? []).map(normalizeEmail).includes(normalized)) {\n console.log(\n warn(\" Note: \") +\n `${normalized} is not in your self-address list, so mail to it would still be ` +\n `drafted rather than sent. Run: pai identity add ${normalized}`\n );\n }\n });\n}\n","/**\n * voices-config.ts — voices.json / voices.yaml, the same load-either /\n * migrate pattern as the main config (see main-config.ts) and workers.yaml.\n *\n * Nothing in src/ currently reads voices.json (see the note on\n * migrateVoicesJson in pai-files.ts) — this exists so the one place that\n * would ever load it has the YAML-preferring behaviour already in place,\n * and so `pai config yaml` has one JSON→YAML step to run for it, same as\n * every other PAI config file.\n */\n\nimport { paiHomePath } from \"./pai-home.js\";\nimport { readDualFormatConfigRaw, migrateMainConfigToYaml, type MainConfigMigrateResult } from \"./main-config.js\";\n\nexport function voicesJsonPath(): string {\n return paiHomePath(\"voices.json\");\n}\n\nexport function voicesYamlPath(): string {\n return paiHomePath(\"voices.yaml\");\n}\n\n/** voices.yaml if it exists, else voices.json, else `{}`. */\nexport function loadVoicesConfig(): Record<string, unknown> {\n return readDualFormatConfigRaw(voicesJsonPath(), voicesYamlPath());\n}\n\n/** `pai config yaml`'s voices.json → voices.yaml step. */\nexport function migrateVoicesToYaml(opts: { dryRun?: boolean; force?: boolean } = {}): MainConfigMigrateResult {\n return migrateMainConfigToYaml(voicesJsonPath(), { ...opts, yamlPath: voicesYamlPath(), sectionComments: {} });\n}\n","/**\n * dispatch.ts — Hand tasks to the PAI session that owns them\n *\n * Transport is AIBroker, and it is optional. With it, a task reaches a running\n * session or spawns one. Without it, dispatch degrades to reporting who owns\n * what and leaves acting to the user — PAI must stay useful on machines that\n * do not run iTerm2.\n *\n * See docs/task-bus.md.\n */\n\nimport type { DispatchResult, Task } from \"./types.js\";\n\n// ---------------------------------------------------------------------------\n// Transport\n// ---------------------------------------------------------------------------\n\n/** What a transport reports back. Mirrors DispatchOutcome minus the states\n * the bus decides for itself (`unrouted`, and `skipped` when disabled). */\nexport interface TransportResult {\n outcome: \"delivered\" | \"queued\" | \"spawned\" | \"unlaunchable\" | \"unreachable\" | \"skipped\";\n session?: string;\n reason?: string;\n}\n\n/**\n * The subset of AIBroker the bus needs.\n *\n * Deliberately one atomic call rather than liveSessions/launch/send. Deciding\n * \"is it running? no — launch it, then send\" is session-lifecycle logic, which\n * belongs to whoever owns sessions. Splitting it across a process boundary\n * would duplicate that logic here and race: a session can start or die between\n * the check and the send.\n *\n * Declared as an interface rather than imported so the module carries no hard\n * dependency on AIBroker being installed.\n */\nexport interface Transport {\n dispatch(\n project: string,\n message: string,\n opts: { spawnIfAbsent: boolean },\n ): Promise<TransportResult>;\n}\n\nexport interface DispatchOptions {\n transport?: Transport | null;\n /** When false, report ownership without contacting any session. */\n autoDispatch: boolean;\n /** Launch a session when the owner is not running. Default: true. */\n spawnIfAbsent?: boolean;\n /**\n * Reporting only — no session is contacted. Distinguishes \"no transport was\n * looked for\" from \"a transport was looked for and not found\", which read\n * identically without it and made a working setup look broken.\n */\n dryRun?: boolean;\n}\n\n// ---------------------------------------------------------------------------\n// Message\n// ---------------------------------------------------------------------------\n\n/**\n * Render a task as a session-to-session message.\n *\n * The body is included in full and deliberately not summarised: the whole point\n * of the description convention is that a task is actionable months later\n * without re-deriving anything, and truncating it here would undo that.\n */\nexport function renderTask(task: Task): string {\n const parts = [`Task from the bus: ${task.title}`];\n if (task.due) parts.push(`Due: ${task.due}`);\n if (task.priority !== \"p4\") parts.push(`Priority: ${task.priority}`);\n if (task.body.trim()) parts.push(\"\", task.body.trim());\n if (task.sourceUrl) parts.push(\"\", `Source: ${task.sourceUrl}`);\n parts.push(\n \"\",\n `Tracker id: ${task.id}`,\n `When done, run \\`pai task done ${task.id}\\` so it is not dispatched again.`,\n \"No reply is expected — the process that sent this has already exited.\",\n \"If you are blocked, file it back to the tracker with `pai task add` rather than replying.\",\n );\n return parts.join(\"\\n\");\n}\n\n// ---------------------------------------------------------------------------\n// Dispatch\n// ---------------------------------------------------------------------------\n\nexport async function dispatchTask(\n task: Task,\n opts: DispatchOptions,\n): Promise<DispatchResult> {\n if (task.owner.project === null) {\n return {\n task,\n outcome: \"unrouted\",\n reason: task.owner.rawHint\n ? `\"${task.owner.rawHint}\" matches no PAI project`\n : \"no owner label or container\",\n };\n }\n\n const project = task.owner.project;\n\n if (!opts.autoDispatch || !opts.transport) {\n // A dry run deliberately passes no transport, so the absence means nothing\n // here — reporting it as \"no transport available\" made a healthy system\n // look broken, and sent at least one investigation after a fault that did\n // not exist. Routing has already succeeded by this point; say so.\n const reason = opts.dryRun\n ? `would dispatch to ${project} (dry run — no session contacted)`\n : opts.transport\n ? \"autoDispatch is off\"\n : \"no transport available — run `pai <project>` to pick this up\";\n\n return { task, outcome: \"skipped\", session: project, reason };\n }\n\n let result: TransportResult;\n try {\n result = await opts.transport.dispatch(project, renderTask(task), {\n spawnIfAbsent: opts.spawnIfAbsent !== false,\n });\n } catch (e) {\n // A transport that throws is an infrastructure failure, not a routing\n // decision — surface it rather than reporting the task as skipped.\n return {\n task,\n outcome: \"unlaunchable\",\n session: project,\n reason: e instanceof Error ? e.message : String(e),\n };\n }\n\n // Only projects with a curated alias can be launched. That is a setup gap,\n // not a bug — name the fix so it is actionable without digging.\n if (result.outcome === \"unlaunchable\" && !result.reason?.includes(\"pai project name\")) {\n return {\n task,\n outcome: \"unlaunchable\",\n session: result.session ?? project,\n reason: `${result.reason ?? \"no session could be launched\"} — give it an alias with \\`pai project name <identifier> ${project}\\``,\n };\n }\n\n return { task, outcome: result.outcome, session: result.session ?? project, reason: result.reason };\n}\n\n/**\n * Dispatch a batch, sequentially.\n *\n * Deliberately not parallel: dispatching spawns terminal tabs and types into\n * them. Racing that would interleave input across sessions.\n */\nexport async function dispatchAll(\n tasks: Task[],\n opts: DispatchOptions,\n): Promise<DispatchResult[]> {\n const results: DispatchResult[] = [];\n for (const task of tasks) {\n results.push(await dispatchTask(task, opts));\n }\n return results;\n}\n","/**\n * scheduler.ts — Todoist-driven routine scheduler\n *\n * Todoist holds the schedule, the ordering, the dependencies and the run state.\n * This module is a dumb poller: it reads that state, decides what should happen\n * now, and reports. No LLM, so a tick costs nothing but an API call.\n *\n * Measured against the live Todoist API before this was written:\n *\n * - Completing a recurring task does NOT create a new one. The SAME task\n * rolls forward: same id, new due date, immediately unchecked. So \"is it\n * closed?\" never detects completion for a recurring task — it is closed for\n * an instant at most. Completion is \"the due date advanced\".\n *\n * - Labels survive that roll-forward. A running marker will therefore NOT\n * clear itself; the poller has to reconcile it, or the task looks\n * permanently in flight and silently never runs again.\n *\n * - Todoist never accumulates missed occurrences. Three days powered off\n * leaves ONE overdue task, not three, and completing it jumps to the next\n * future occurrence. There is no backlog to drain — but equally, Todoist\n * cannot tell you how many windows were missed, so overdue-by-N is computed\n * here.\n *\n * - Writing `due_date` DESTROYS a recurrence rule, silently turning a routine\n * into a one-off. This module must only ever write `due_string`.\n */\n\nimport type { Task } from \"./types.js\";\n\n// ---------------------------------------------------------------------------\n// Label vocabulary\n// ---------------------------------------------------------------------------\n\n/** Set when dispatched, cleared when the due date advances. */\nexport const RUNNING_LABEL = \"pai-running\";\n\n/** Run however late. The default when nothing is specified. */\nexport const CATCHUP_LABEL = \"pai-catchup\";\n\n/** `pai-skip-if-late:4h` — past that, the window is gone; wait for the next. */\nconst SKIP_IF_LATE_RE = /^pai-skip-if-late:(\\d+)([hm])$/i;\n\n/** Fallback when a task has no learned duration yet. */\nexport const DEFAULT_EXPECTED_MINUTES = 30;\n\n/** Probe once the run exceeds expected x this. */\nconst PROBE_FACTOR = 1.5;\n\n/** Consecutive unanswered probes before a run counts as stuck. */\nexport const STUCK_AFTER_FAILED_PROBES = 3;\n\n/**\n * Multiples of the expected duration after which a claim is released outright.\n *\n * The running label became an interlock once AIBroker's webhook started skipping\n * any task carrying it (0.17.1). That closed the duplicate-dispatch hole and\n * opened a worse one: nothing here ever took the label off except a completion,\n * so a session that died mid-turn left the task claimed forever — the webhook\n * skipping it, the poller probing it, and neither ever running it again. Silent\n * permanent non-execution is the exact failure this subsystem exists to prevent,\n * and it is worse than a duplicate run.\n *\n * Deliberately far beyond the probe threshold and NOT conditional on a probe\n * saying the session is gone. An empty session list is how the hub itself failed\n * on 2026-08-01, so \"absent\" is not trustworthy evidence; elapsed time is. A\n * sweep that has been in flight for five hours is not in flight.\n */\nexport const ABANDON_FACTOR = 10;\n\n/** Floor for the same, so a task with a short learned duration is not released early. */\nexport const ABANDON_FLOOR_MINUTES = 120;\n\n/** When a claim is old enough to be released regardless of what probes say. */\nexport function abandonAfterMinutes(observed: number[]): number {\n return Math.max(expectedMinutes(observed) * ABANDON_FACTOR, ABANDON_FLOOR_MINUTES);\n}\n\n// ---------------------------------------------------------------------------\n// Decisions\n// ---------------------------------------------------------------------------\n\nexport type Decision =\n /** Not due yet. */\n | { action: \"wait\"; task: Task }\n /**\n * Ticked off in the tracker by hand — run it now, and put the schedule back.\n *\n * `restoreTo` is the due_string to write afterwards, or null to leave the\n * advanced date alone (the occurrence that was just consumed is already past,\n * so restoring it would only produce an immediately-overdue task).\n */\n | { action: \"triggered\"; task: Task; restoreTo: string | null }\n /** Due (or overdue within policy) and not running — dispatch it. */\n | { action: \"dispatch\"; task: Task; overdueMinutes: number }\n /** Overdue past its skip-if-late window — leave for the next occurrence. */\n | { action: \"skip\"; task: Task; reason: string }\n /** Running and within its expected duration — leave alone. */\n | { action: \"running\"; task: Task; elapsedMinutes: number }\n /** Running and overrun — ask the session whether it is alive. */\n | { action: \"probe\"; task: Task; elapsedMinutes: number; expectedMinutes: number }\n /** Was running, due date has advanced — it finished. */\n | { action: \"complete\"; task: Task; durationMinutes: number | null }\n /**\n * Claimed so long ago that no run is plausibly still going — release it.\n *\n * Not a re-dispatch: the claim comes off and nothing else happens this tick.\n * The task is still overdue, so the next tick dispatches it through the normal\n * path, claim and all. Splitting it that way keeps one dispatcher.\n */\n | { action: \"abandoned\"; task: Task; elapsedMinutes: number; thresholdMinutes: number }\n /** Marked running but nothing knows about it — reconcile. */\n | { action: \"orphaned\"; task: Task; reason: string };\n\n/** In-flight run bookkeeping. Transient: losing it costs one extra probe. */\nexport interface RunState {\n /** Task id → epoch ms when dispatched. */\n startedAt: Record<string, number>;\n /** Task id → consecutive probes with no reply. */\n failedProbes: Record<string, number>;\n}\n\nexport const EMPTY_RUN_STATE: RunState = { startedAt: {}, failedProbes: {} };\n\n// ---------------------------------------------------------------------------\n// Policy helpers\n// ---------------------------------------------------------------------------\n\n/**\n * Did THIS poller start the run currently in flight?\n *\n * The distinction from {@link isClaimedByAnyone} has now caused three separate\n * defects in three functions across two days, so it is named rather than\n * re-derived from `startedAt` at each site.\n *\n * `startedAt` is set only when this poller dispatches. A run triggered through\n * the tracker's webhook is claimed by AIBroker before this poller ever sees the\n * task, so it has no `startedAt` here — and every piece of evidence that is\n * sound for our own runs is worthless for those. Twice that produced a\n * confident wrong answer: `overdue < 0` reported a webhook run finished on the\n * tick after it started, and an orphan sweep keyed on `startedAt` would have\n * discarded exactly the entries the webhook path depends on.\n *\n * Use this when the question is \"can I trust state I recorded myself\".\n */\nexport function isOurRun(state: RunState, taskId: string): boolean {\n return state.startedAt[taskId] !== undefined;\n}\n\n/**\n * Is anyone running this task — us, or a session claimed through the tracker?\n *\n * The RUNNING label is the shared interlock: whoever claims a task sets it,\n * whichever side they are. Use this when the question is \"is a run in flight at\n * all\", which is what liveness means for anything scoped to a run.\n */\nexport function isClaimedByAnyone(task: Task, state: RunState): boolean {\n return isRunning(task) || isOurRun(state, task.id);\n}\n\nexport function isRunning(task: Task): boolean {\n return task.labels.some((l) => l.toLowerCase() === RUNNING_LABEL);\n}\n\n/** Minutes after which a missed window is abandoned, or null for \"always run\". */\nexport function skipIfLateMinutes(task: Task): number | null {\n for (const label of task.labels) {\n const m = SKIP_IF_LATE_RE.exec(label.trim());\n if (m) {\n const n = Number.parseInt(m[1]!, 10);\n return m[2]!.toLowerCase() === \"h\" ? n * 60 : n;\n }\n }\n return null;\n}\n\n/**\n * How overdue a task is, in minutes. Negative means still in the future.\n *\n * A date-only due (\"2026-08-01\") is treated as due at the start of that day,\n * which is what Todoist shows the user.\n */\nexport function overdueMinutes(task: Task, now: number): number {\n if (!task.due) return Number.NEGATIVE_INFINITY;\n const due = task.due.length <= 10 ? `${task.due}T00:00:00` : task.due;\n const dueMs = new Date(due).getTime();\n if (Number.isNaN(dueMs)) return Number.NEGATIVE_INFINITY;\n return Math.floor((now - dueMs) / 60_000);\n}\n\n// ---------------------------------------------------------------------------\n// Manual trigger (the checkbox as a Run Now button)\n// ---------------------------------------------------------------------------\n\n/**\n * Why this is inferred from the due date rather than observed directly:\n *\n * Completing a recurring task leaves no completion event anywhere the API will\n * show us. Measured 2026-08-01 against the live account — a recurring task was\n * created, completed, and then queried through the completed-by-completion-date\n * endpoint: it is absent. The task itself comes straight back as open with the\n * due date advanced and the same id. So \"the due date jumped forward while we\n * were not running it\" is the ONLY evidence a human ticked the box.\n *\n * That evidence is ambiguous — dragging the date forward looks identical — so\n * the jump is checked against the recurrence period before it is believed. A\n * daily task that moves by exactly one day was ticked; one that moves by three\n * was rescheduled, and firing a sweep on that would both waste a run and undo\n * the user's edit.\n */\n\nconst MS_PER_DAY = 86_400_000;\n\n/**\n * Day-deltas consistent with one occurrence of `recurrence` elapsing.\n *\n * A set rather than a number because real rules are not fixed-length: a monthly\n * task advances by 28 to 31 days, and \"every weekday\" by one to three. Null\n * means the rule was not recognised, which the caller treats as permissive —\n * an unrecognised recurrence is still a recurrence, and refusing to fire is the\n * failure mode this whole mechanism exists to remove.\n */\nexport function expectedAdvanceDays(recurrence: string | null | undefined): number[] | null {\n if (!recurrence) return null;\n const r = recurrence.trim().toLowerCase();\n\n if (/\\bevery\\s+(work ?day|weekday)/.test(r)) return [1, 2, 3];\n if (/\\bevery\\s+other\\s+day\\b/.test(r)) return [2];\n if (/\\bevery\\s+(\\d+)\\s+days?\\b/.test(r)) {\n const n = Number.parseInt(/\\bevery\\s+(\\d+)\\s+days?\\b/.exec(r)![1]!, 10);\n return n > 0 ? [n] : null;\n }\n if (/\\bevery\\s+day\\b/.test(r) || /\\bdaily\\b/.test(r)) return [1];\n if (/\\bevery\\s+(\\d+)\\s+weeks?\\b/.test(r)) {\n const n = Number.parseInt(/\\bevery\\s+(\\d+)\\s+weeks?\\b/.exec(r)![1]!, 10);\n return n > 0 ? [n * 7] : null;\n }\n // Before the weekday rule: an unanchored \"mon\" alternative also matches the\n // \"mon\" in \"month\", which classified a monthly routine as weekly.\n if (/\\bevery\\s+(month|\\d+\\s+months?)\\b/.test(r) || /\\bmonthly\\b/.test(r)) return [28, 29, 30, 31];\n if (/\\bevery\\s+year\\b/.test(r) || /\\byearly\\b/.test(r) || /\\bannually\\b/.test(r)) return [365, 366];\n if (\n /\\bevery\\s+(?:week|mondays?|tuesdays?|wednesdays?|thursdays?|fridays?|saturdays?|sundays?|mon|tue|tues|wed|thu|thur|thurs|fri|sat|sun)\\b/.test(r) ||\n /\\bweekly\\b/.test(r)\n ) {\n return [7];\n }\n\n return null;\n}\n\n/** Whole days between two due values, rounded — times of day are preserved by\n * both a completion and a drag, so they carry no signal either way. */\nfunction advanceDays(prevDue: string, newDue: string): number {\n const a = new Date(prevDue.length <= 10 ? `${prevDue}T00:00:00` : prevDue).getTime();\n const b = new Date(newDue.length <= 10 ? `${newDue}T00:00:00` : newDue).getTime();\n if (Number.isNaN(a) || Number.isNaN(b)) return Number.NaN;\n return Math.round((b - a) / MS_PER_DAY);\n}\n\n/**\n * Did this task advance because someone ticked it off?\n *\n * A non-recurring task is never a trigger: completing one closes it, and it\n * drops out of the open list rather than reappearing with a later date. So a\n * forward jump there is unambiguously a reschedule.\n */\nexport function wasTicked(task: Task, prevDue: string | null | undefined): boolean {\n if (!prevDue || !task.due || !task.recurrence) return false;\n if (task.due <= prevDue) return false;\n\n const moved = advanceDays(prevDue, task.due);\n if (!Number.isFinite(moved) || moved <= 0) return false;\n\n const expected = expectedAdvanceDays(task.recurrence);\n return expected === null ? true : expected.includes(moved);\n}\n\n/**\n * The due_string that puts a ticked task back where it was.\n *\n * Rule and date in one string on purpose. Writing the date alone through the\n * tracker's date field drops the recurrence and quietly demotes a routine to a\n * one-off — the failure the provider's `setDue` comment warns about. Todoist\n * parses \"every day at 08:00 starting 2026-08-02\" and honours both halves;\n * verified against the live API on 2026-08-01.\n */\nexport function restoreDueString(recurrence: string, prevDue: string): string {\n const date = prevDue.slice(0, 10);\n const hasTime = prevDue.length > 10;\n const ruleCarriesTime = /\\bat\\s+\\d/.test(recurrence);\n\n // \"every day\" plus a datetime due would come back as a date-only occurrence,\n // losing the time of day. Carry it explicitly when the rule does not.\n const time = hasTime && !ruleCarriesTime ? ` at ${prevDue.slice(11, 16)}` : \"\";\n return `${recurrence}${time} starting ${date}`;\n}\n\n/**\n * When a restored slot actually falls due — the inverse of `restoreDueString`.\n *\n * Callers need this to decide whether putting the slot back is still useful.\n * Restoring one that has already passed writes a task that is overdue the\n * instant it lands, and the next tick dispatches it, which advances the date,\n * which restores it again: an unbounded loop that fires every tick for the rest\n * of the day. That is not hypothetical — it happened on 2026-08-04 with \"every\n * day at 9am\", which re-dispatched roughly every 30 minutes (two 900s ticks)\n * because the guard compared against the end of the day rather than 09:00.\n *\n * Both shapes `restoreDueString` can emit put the time after \"at\", so one match\n * covers \"every day at 9am starting …\" and \"every week at 08:00 starting …\".\n * A rule carrying no time at all is a genuine date-only occurrence, and for\n * those the slot really does run until the end of the day.\n */\nexport function triggeredSlotMs(keep: string, date: string): number {\n const at = /\\bat\\s+(\\d{1,2})(?::(\\d{2}))?\\s*([ap]m)?/i.exec(keep);\n if (!at) return new Date(`${date}T23:59:59`).getTime();\n\n let hour = Number(at[1]);\n const suffix = at[3]?.toLowerCase();\n if (suffix === \"pm\" && hour < 12) hour += 12;\n if (suffix === \"am\" && hour === 12) hour = 0;\n\n const hh = String(hour).padStart(2, \"0\");\n const mm = String(Number(at[2] ?? 0)).padStart(2, \"0\");\n return new Date(`${date}T${hh}:${mm}:00`).getTime();\n}\n\n// ---------------------------------------------------------------------------\n// Duration learning\n// ---------------------------------------------------------------------------\n\n/**\n * Expected runtime for a task.\n *\n * The mean rather than the max, deliberately: with a liveness probe available,\n * guessing low costs one extra probe, while guessing high leaves a dead session\n * undetected for far longer. The probe is what makes a tight estimate safe.\n *\n * Runs that ended in stuck or re-dispatch must never be fed in here — one hung\n * run would inflate the mean and every later run would inherit a too-generous\n * threshold.\n */\nexport function expectedMinutes(observed: number[], fallback = DEFAULT_EXPECTED_MINUTES): number {\n const clean = observed.filter((n) => Number.isFinite(n) && n > 0);\n if (clean.length === 0) return fallback;\n const recent = clean.slice(-5);\n return Math.max(1, Math.round(recent.reduce((a, b) => a + b, 0) / recent.length));\n}\n\n// ---------------------------------------------------------------------------\n// The decision function\n// ---------------------------------------------------------------------------\n\nexport interface DecideOptions {\n now: number;\n state: RunState;\n /** Task id → observed durations in minutes, most recent last. */\n history?: Record<string, number[]>;\n /**\n * Task id → the due date seen on the previous tick.\n *\n * The only way to notice a task was ticked off by hand. Absent on the first\n * tick after install, which is why a missing entry can never mean \"it moved\".\n */\n lastSeenDue?: Record<string, string>;\n /**\n * True when a tracker webhook is delivering completion events.\n *\n * When set, the due-date trigger inference is switched off: the webhook sees\n * the real event, so the guess can only add false positives. Defaults to\n * false so a machine with no webhook keeps the fallback — which is the case\n * the inference was written for.\n */\n webhookActive?: boolean;\n /**\n * Task id → when this poller FIRST saw the task carrying a running claim.\n *\n * Needed because the claim is no longer only ours to make: AIBroker's webhook\n * claims and dispatches on its own, so a claimed task frequently has no start\n * time here. This is the clock for those runs — both to age them out and to\n * tell a claim we have been watching from one we are seeing for the first time.\n */\n claimSeenAt?: Record<string, number>;\n}\n\n/**\n * Decide what should happen to one task on this tick.\n *\n * Pure: no I/O, no clock reads. Everything comes in via options so the state\n * machine can be tested against a fixed clock rather than by waiting.\n */\nexport function decide(task: Task, opts: DecideOptions): Decision {\n const { now, state } = opts;\n const running = isRunning(task);\n const overdue = overdueMinutes(task, now);\n const startedAt = state.startedAt[task.id];\n\n if (running) {\n const claimSeenAt = opts.claimSeenAt?.[task.id];\n const prevDue = opts.lastSeenDue?.[task.id];\n const dueAdvanced = prevDue !== undefined && task.due !== null && task.due > prevDue;\n const ourRun = isOurRun(state, task.id);\n\n // Completion is \"the due date moved into the future\" — not \"closed\". A\n // recurring task is closed for an instant at most, so testing for closed\n // would never fire.\n //\n // But `overdue < 0` alone is only evidence for a run WE started. A run\n // dispatched by AIBroker's webhook is triggered by the user ticking the box,\n // which advances the due date before the run even begins — so the due sits\n // in the future for the whole run, and this branch reported it finished on\n // the very next tick and stripped the interlock mid-flight. Measured against\n // a live probe on 2026-08-01: \"✓ would clear pai-running\" while the sweep\n // was still going.\n //\n // For someone else's run the completion signal is the due date advancing\n // AGAIN — and only once we have watched the claim across a tick, since the\n // first advance we see is the trigger itself, not an ending.\n const finishedElsewhere = dueAdvanced && claimSeenAt !== undefined && claimSeenAt < now;\n\n if ((ourRun && overdue < 0) || finishedElsewhere) {\n const duration = startedAt ? Math.max(1, Math.round((now - startedAt) / 60_000)) : null;\n return { action: \"complete\", task, durationMinutes: duration };\n }\n\n // Clock for everything below. A claim we did not make still has to age out,\n // or a webhook run whose session died would hold the interlock forever.\n const clock = startedAt ?? claimSeenAt;\n if (clock === undefined) {\n // First sighting of a claim with nothing to date it by. Either the state\n // file was lost or another dispatcher just claimed it. Probing is the safe\n // response — never assume it is dead and re-dispatch. The next tick has a\n // claim time and can age it properly.\n return {\n action: \"orphaned\",\n task,\n reason: \"marked running but no start time is known — probe before assuming anything\",\n };\n }\n\n const elapsed = Math.max(0, Math.round((now - clock) / 60_000));\n const expected = expectedMinutes(opts.history?.[task.id] ?? []);\n\n // Checked before probing: past this point the probe's answer no longer\n // changes what should happen. Even a session replying \"yes, still working\"\n // has been working ten times its usual run, and leaving the claim on is what\n // turns one dead session into a routine that never runs again.\n const abandonAfter = abandonAfterMinutes(opts.history?.[task.id] ?? []);\n if (elapsed >= abandonAfter) {\n return { action: \"abandoned\", task, elapsedMinutes: elapsed, thresholdMinutes: abandonAfter };\n }\n\n // Only interrogate our own runs. Asking after a session another dispatcher\n // started tells us nothing actionable — we would not re-dispatch it either\n // way — and the ageing above already bounds it.\n if (!ourRun) return { action: \"running\", task, elapsedMinutes: elapsed };\n\n if (elapsed >= expected * PROBE_FACTOR) {\n return { action: \"probe\", task, elapsedMinutes: elapsed, expectedMinutes: expected };\n }\n return { action: \"running\", task, elapsedMinutes: elapsed };\n }\n\n // Checked before the wait branch, because a task that was just ticked always\n // lands in the future — it would otherwise be indistinguishable from one that\n // is simply not due yet, which is exactly how the checkbox came to look like\n // a button that silently pushed the sweep out by a day.\n const prevDue = opts.lastSeenDue?.[task.id];\n\n // A claim we were watching until a moment ago means this advance is a run\n // ENDING, not a box being ticked. Since aibroker 0.17.4 the finishing session\n // drops the claim itself and then advances the recurrence with `pai task\n // done` — so by the next tick the task is unclaimed with its due date exactly\n // one period on, which is indistinguishable from a hand-tick and would have\n // dispatched the same work a second time.\n //\n // The entry is still here because tick() clears it AFTER deciding, which is\n // precisely the window this needs. When a whole run fits between two ticks we\n // never see the claim at all — but then the due has advanced twice, once for\n // the trigger and once for the completion, and the period check rejects it.\n const wasClaimed = opts.claimSeenAt?.[task.id] !== undefined;\n\n // Suppressed entirely where a webhook is live, and that is a deliberate\n // precedence decision rather than a tuning knob.\n //\n // The inference reads \"due advanced by exactly one period, unclaimed\" as a\n // tick. For a DAILY recurrence that is byte-identical to a human dragging the\n // task forward one day, and to this poller repairing a due date it wrote\n // wrongly itself. There is no local signal that separates them — it is an\n // irreducible false positive, not something a tighter check can remove.\n //\n // A webhook has the actual item:completed event and no ambiguity at all. So\n // where one is running, guessing is strictly worse than not guessing, and\n // leaving both active means the fallback keeps firing on reschedules the\n // webhook correctly ignored — which is how repairing one duplicate sweep\n // produced a second on 2026-08-02.\n //\n // The costs are asymmetric, which is what settles it. A false positive\n // dispatches real work nobody asked for — a sweep that scrapes, writes and\n // sends mail. A false negative merely delays a hand-triggered run until the\n // next tick, which then dispatches it on the ordinary overdue path. Guessing\n // wrong in the expensive direction to save minutes in the cheap one is a bad\n // trade wherever the unambiguous signal exists.\n if (!opts.webhookActive && !wasClaimed && wasTicked(task, prevDue)) {\n // Restore only a slot that has not passed. Putting back an occurrence that\n // is already overdue leaves a task that reads as late the instant it is\n // written, for a run that is starting right now.\n const prevMs = new Date(prevDue!.length <= 10 ? `${prevDue}T00:00:00` : prevDue!).getTime();\n const restoreTo =\n task.recurrence && prevMs > now ? restoreDueString(task.recurrence, prevDue!) : null;\n return { action: \"triggered\", task, restoreTo };\n }\n\n if (overdue < 0) return { action: \"wait\", task };\n\n const limit = skipIfLateMinutes(task);\n if (limit !== null && overdue > limit) {\n return {\n action: \"skip\",\n task,\n reason: `overdue by ${overdue}m, past its ${limit}m window — waiting for the next occurrence`,\n };\n }\n\n return { action: \"dispatch\", task, overdueMinutes: Math.max(0, overdue) };\n}\n\n/**\n * Order tasks for dispatch.\n *\n * Subtasks of the same parent run in Todoist's own order, so a routine is\n * sequenced by dragging its steps in the UI. Everything else falls back to\n * priority then due time, so the most overdue urgent thing goes first.\n */\nexport function dispatchOrder(tasks: Task[]): Task[] {\n const rank = { p1: 0, p2: 1, p3: 2, p4: 3 } as const;\n return [...tasks].sort((a, b) => {\n const p = rank[a.priority] - rank[b.priority];\n if (p !== 0) return p;\n return (a.due ?? \"\").localeCompare(b.due ?? \"\");\n });\n}\n","/**\n * poller.ts — one scheduler tick\n *\n * Reads Todoist, decides, acts, reports. Run by launchd on an interval; carries\n * no LLM, so a tick that finds nothing to do costs one API call and no tokens.\n *\n * Run state (start times, probe counts) is deliberately local and transient.\n * Losing it costs one extra probe, not a wrong decision — `decide` returns\n * \"orphaned\" rather than assuming a task is dead. The durable state — schedule,\n * order, running flag, learned durations — all lives in Todoist, so a new\n * machine picks up where this one left off.\n */\n\nimport { existsSync } from \"node:fs\";\nimport { homedir } from \"node:os\";\nimport { join } from \"node:path\";\nimport { execFileSync } from \"node:child_process\";\nimport { readJsonStrict, writeJsonAtomic } from \"../config/json-store.js\";\nimport { paiHomePath, resolvePaiFile, migratePaiFile, type MigrateFileResult } from \"../config/pai-home.js\";\nimport type { TodoistProvider } from \"./providers/todoist.js\";\nimport type { Transport } from \"./dispatch.js\";\nimport { dispatchTask } from \"./dispatch.js\";\nimport {\n decide,\n dispatchOrder,\n isRunning,\n isClaimedByAnyone,\n RUNNING_LABEL,\n STUCK_AFTER_FAILED_PROBES,\n type Decision,\n expectedMinutes,\n type RunState,\n EMPTY_RUN_STATE,\n wasTicked,\n restoreDueString,\n triggeredSlotMs,\n} from \"./scheduler.js\";\nimport type { Task } from \"./types.js\";\n\n/** Old scheduler-state.json path, inside ~/.pai/ (pre-2026-09-19). */\nfunction oldStateFilePath(): string {\n return join(homedir(), \".pai\", \"scheduler-state.json\");\n}\n\n/** Scheduler state path: PAI_HOME/scheduler-state.json if present, else the\n * old ~/.pai/scheduler-state.json (one-time stderr notice), else the new path. */\nexport const STATE_FILE = resolvePaiFile(\n paiHomePath(\"scheduler-state.json\"),\n [oldStateFilePath()],\n \"pai config migrate --scheduler-state\"\n);\n\nexport function migrateSchedulerState(opts: { dryRun?: boolean } = {}): MigrateFileResult {\n return migratePaiFile(paiHomePath(\"scheduler-state.json\"), [oldStateFilePath()], opts);\n}\n\n/** How many past durations to keep per task. */\nconst HISTORY_LIMIT = 5;\n\nexport interface PersistedState extends RunState {\n /** Task id → observed durations in minutes, most recent last. */\n history: Record<string, number[]>;\n /** Task id → epoch ms of the last completion we reported. */\n lastReported: Record<string, number>;\n /**\n * Task id → consecutive dispatch attempts that did not land.\n *\n * Without this a task whose session has died to a shell is retried every\n * tick, forever, reporting the same failure each time and escalating never —\n * the \"silently never runs\" failure this whole subsystem exists to avoid.\n */\n failedDispatches: Record<string, number>;\n /**\n * Task id → epoch ms of the last time an alarm about it reached the user.\n *\n * Every tick that raised an alarm before this existed raised it into\n * /tmp/pai-scheduler.log and nowhere else. Measured 2026-08-03: the daily\n * check had logged \"unrouted — cannot dispatch\" 151 times across two days,\n * on a task whose whole purpose was to notice things failing silently.\n *\n * Kept per task, and honoured for a day, because the two wrong answers are\n * symmetric: notify every tick and the alarm is muted as noise within the\n * hour, notify once ever and a fault fixed-then-reintroduced is silent the\n * second time.\n */\n alarmedAt: Record<string, number>;\n /**\n * Task id → the due date this tick saw.\n *\n * Durable rather than transient, unlike the rest of this file: it is the only\n * record that a recurring task's due date moved, and a lost entry means the\n * next tick cannot tell a hand-ticked task from one that was never due. That\n * costs a missed run, not a wrong one — the first tick after a reset simply\n * re-learns every date and triggers nothing.\n */\n lastSeenDue: Record<string, string>;\n /**\n * Task id → the due_string a hand-triggered run has to get back.\n *\n * Restoring once at trigger time is not enough. The session ends its run with\n * `pai task done`, which advances the recurrence a second time — so a sweep\n * run by hand on Saturday evening silently ate Sunday morning's scheduled\n * one. Held here until the completion is seen, then re-applied.\n */\n triggeredRestore: Record<string, string>;\n /**\n * Task id → when this poller first saw a running claim on it.\n *\n * The claim is no longer only ours to make — AIBroker's webhook claims and\n * dispatches on its own — so a claimed task often has no start time here.\n * This dates those runs, which is what lets them age out instead of holding\n * the interlock forever, and what separates a claim we have been watching\n * from one appearing for the first time.\n */\n claimSeenAt: Record<string, number>;\n /**\n * Task id → the failure that made this task stop being dispatched.\n *\n * `failedDispatches` counts attempts and gates the ALARM. It never gated the\n * RETRY, so a task whose dispatch could not possibly succeed was re-attempted\n * every tick for as long as it stayed due. On 2026-08-04 that meant a fresh\n * terminal window every fifteen minutes for nine hours, against a project\n * directory that had been renamed months earlier: each attempt opened a tab,\n * failed its `cd`, waited out the full 90s readiness timeout and gave up,\n * changing nothing before the next one.\n *\n * Parking is the missing state between \"failed\" and \"gave up\". A parked task\n * is still visible, still reported, and still escalates once — it simply\n * stops being launched at something that cannot accept it. It unparks when\n * the task's due date moves (the user rescheduled it) or when its parked\n * reason no longer applies, which is what keeps this from being a one-way\n * door that quietly retires a task nobody notices is gone.\n */\n parked: Record<string, { reason: string; at: number; due?: string }>;\n /**\n * Task id → consecutive re-dispatch attempts triggered by a probe reply\n * that starts with NOT-RECEIVED, for the claim currently in flight.\n *\n * Separate from `failedProbes`: that counter tracks silence, this one\n * tracks an explicit \"I was never told about this\" reply, which needs its\n * own cap so a dispatch that never lands stops being retried forever\n * rather than every tick.\n */\n notReceivedAttempts: Record<string, number>;\n /**\n * Task id → epoch ms before which another NOT-RECEIVED reply must not\n * trigger another re-dispatch.\n *\n * Deliberately not cleared on a \"delivered\"/\"queued\"/\"spawned\" transport\n * result — that is exactly the false signal this field exists to survive:\n * a transport reporting success does not mean the session received\n * anything, so the backoff clock only resets on a confirmed-alive reply or\n * on the claim being released.\n */\n notReceivedRetryAt: Record<string, number>;\n}\n\n/**\n * A fresh blank state, built per call rather than shared.\n *\n * This was a module-level constant spread into every load. A spread is shallow,\n * so any field the persisted file did not carry — a fresh install, or a file\n * written before that field existed — aliased the constant's own object, and\n * writes to it leaked into every later load in the process. Harmless for the\n * launchd tick, which loads once and exits, and quietly wrong everywhere else:\n * it made test runs contaminate each other and would do the same to any caller\n * that ticked twice.\n */\nfunction emptyState(): PersistedState {\n return {\n startedAt: {},\n failedProbes: {},\n history: {},\n lastReported: {},\n failedDispatches: {},\n alarmedAt: {},\n lastSeenDue: {},\n triggeredRestore: {},\n claimSeenAt: {},\n parked: {},\n notReceivedAttempts: {},\n notReceivedRetryAt: {},\n };\n}\n\n/** Consecutive failed dispatches before a task is reported as needing attention. */\nconst DISPATCH_FAILURES_BEFORE_ALARM = 3;\n\n/** How long an alarm about one task stays quiet before it is repeated. */\nconst ALARM_REPEAT_MS = 24 * 60 * 60 * 1000;\n\n/**\n * Tell the user about a task that is scheduled and not running.\n *\n * The counterpart of the storage-backend escalation: a subsystem whose entire\n * job is noticing silent failure has to be the last thing that fails silently.\n * `report.stuck` counted these accurately and showed the count in a CLI line\n * that only appears if somebody runs a tick by hand — which, for a job launchd\n * runs every 15 minutes, is nobody.\n *\n * Never allowed to throw: a notification that cannot be delivered must not take\n * down the tick, because the tick is what dispatches everything else.\n */\nasync function escalate(\n task: Task,\n headline: string,\n detail: string,\n state: PersistedState,\n now: number,\n opts?: TickOptions\n): Promise<void> {\n const last = state.alarmedAt[task.id];\n if (last !== undefined && now - last < ALARM_REPEAT_MS) return;\n state.alarmedAt[task.id] = now;\n\n let delivered = false;\n try {\n const { routeNotification } = await import(\"../notifications/router.js\");\n const { loadConfig } = await import(\"../daemon/config.js\");\n const result = await routeNotification(\n {\n event: \"error\",\n title: `PAI: ${headline}`,\n // `title`, not `content`. Todoist's own API calls this field `content`\n // and the provider renames it on the way in; writing the wire name here\n // compiled to `undefined` and shipped in v0.27.0, so every alarm this\n // path has ever sent read `\"undefined\" is due but has no owner`. The\n // test below asserts the title appears, because the one that existed\n // asserted only the tail of the sentence and passed all the way through.\n message: `\"${task.title}\" ${detail}`,\n },\n loadConfig().notifications\n );\n delivered = result.channelsSucceeded.length > 0;\n } catch {\n /* see above — the tick matters more than the notification */\n }\n\n // Last resort: say it on the task itself.\n //\n // routeNotification returns which channels worked, and this used to throw\n // that away. On 2026-08-04 the WhatsApp provider was dialling a socket that\n // had stopped existing when Whazaa became a thin adapter, so `error` events\n // silently failed — four escalations about a job that had not run in nine\n // hours reached /tmp/pai-scheduler.log and nowhere else, and the user found\n // out by looking.\n //\n // The tracker is the one channel that cannot be misconfigured out of\n // existence, because it is where the task already lives and where the user\n // already looks. A comment is durable, reaches the phone, and survives every\n // notification channel being down at once.\n if (delivered || !opts?.provider.comment) return;\n try {\n await opts.provider.comment(\n task.id,\n `${AGENT_MARK} **${headline}** — ${detail}\\n\\n` +\n `_Posted here because no notification channel accepted this alert. ` +\n `Check \\`pai notify status\\`._`\n );\n } catch {\n /* nothing left to try; the tick still matters more */\n }\n}\n\n/**\n * One best-effort PAILot alert at the moment a task transitions into parked.\n *\n * Parking is silent from the phone's point of view: the tracker note and the\n * scheduler log both say PARKED, but the only person who reads either is the\n * one who already knows. On 2026-09-27 both morning sweeps were parked on a\n * FALSE unreachable (the hub misread a statusline) and nothing surfaced it\n * until the day's run simply did not happen — the exact failure parking's own\n * docs promise to prevent.\n *\n * Deliberately NOT rate-limited beyond its call sites: it runs exactly once\n * per parking event (on the transition, not on every poll while parked), and\n * a re-park after a release is a new event that deserves a new alert. The\n * escalate() quiet window is untouched — this is the announcement, that is\n * the daily reminder.\n *\n * Never allowed to throw or linger: the hub gets a short deadline, and an\n * unreachable hub falls back to the poller log (launchd sends both streams to\n * /tmp/pai-scheduler.log, which already carries every PARKED line).\n */\nasync function alertParked(task: Task, reason: string): Promise<void> {\n const text =\n `PAI parked \"${task.title}\" (${task.id}) — the scheduler will not retry it.\\n` +\n `Reason: ${reason}\\n` +\n `To release it: fix the cause, then move the due date — due_string, natural ` +\n `language only (e.g. \"tomorrow 9am\").`;\n try {\n const { callAiBroker } = await import(\"../cli/lib/aibroker-client.js\");\n await callAiBroker(\"pailot_send\", { text }, 8_000);\n } catch (e) {\n console.error(\n `[pai-poller] parked-task alert not delivered to PAILot ` +\n `(${e instanceof Error ? e.message : String(e)}): ${text}`\n );\n }\n}\n\n/**\n * Run state is a rebuildable cache, so a damaged file must not block the\n * scheduler forever — starting fresh is the correct recovery here, which is\n * exactly the case json-store's guard is NOT for.\n */\nexport function loadState(file: string): PersistedState {\n try {\n const raw = readJsonStrict(file, \"~/.pai/scheduler-state.json\");\n return { ...emptyState(), ...(raw as unknown as PersistedState) };\n } catch {\n return emptyState();\n }\n}\n\nexport function saveState(state: PersistedState, file: string): void {\n writeJsonAtomic(file, state as unknown as Record<string, unknown>, { backup: false });\n}\n\n/**\n * Fold in any completion a concurrent `pai task done` wrote while this tick\n * was busy with the rest of the list.\n *\n * A tick holds one in-memory `state` for its whole run and saves it exactly\n * once, at the end. For a morning routine with several due tasks that run is\n * not instantaneous — each dispatch waits out AIBroker's spawn-readiness\n * window in turn, so the tick can still be working through task 4 minutes\n * after task 1 was launched. `pai task done` has no such window: it loads,\n * releases the claim, stamps `lastSeenDue` to the next occurrence, and saves,\n * all in well under a second. If the session for task 1 finishes and closes\n * it while the tick is still on task 4, the CLI's write lands on disk first —\n * and the tick's blind save at the end, built from the snapshot it took\n * before any of this happened, overwrites it: `lastSeenDue` reverts to the\n * occurrence that was just completed, and `startedAt`/`claimSeenAt` come back\n * because this tick never learned they had been released. The next tick then\n * reads that reverted date as \"due advanced by one period, unclaimed\" — which\n * is `wasTicked`'s exact definition of a hand-tick — and redispatches a task\n * that already finished. Reproduced against a live poller run; see the\n * negative control referenced in Notes/TODO.md.\n *\n * `lastSeenDue` only ever moves forward in normal operation, so a disk value\n * strictly ahead of what this tick computed can only mean a concurrent writer\n * — there is exactly one, `pai task done` — saw a later completion than this\n * tick's stale snapshot knew about. Whenever that happens, this tick's own\n * value is the stale one: defer to disk for that task's date, and carry the\n * claim release forward too, since a more-advanced date with no such release\n * is not a state `pai task done` ever produces.\n *\n * Scoped to exactly the fields `pai task done` touches (`releaseClaim` plus\n * the `lastSeenDue` stamp) — the two writers race over nothing else, so a\n * broader merge would only add risk without removing any.\n */\nfunction reconcileWithDisk(state: PersistedState, stateFile: string): void {\n const onDisk = loadState(stateFile);\n for (const [id, oursDue] of Object.entries(state.lastSeenDue)) {\n const diskDue = onDisk.lastSeenDue[id];\n if (diskDue !== undefined && diskDue > oursDue) {\n state.lastSeenDue[id] = diskDue;\n delete state.startedAt[id];\n delete state.claimSeenAt[id];\n delete state.failedProbes[id];\n }\n }\n}\n\n// ---------------------------------------------------------------------------\n// Liveness probe\n// ---------------------------------------------------------------------------\n\n/**\n * What a probe found.\n *\n * The distinction that matters is `busy` versus `silent`. Claude Code queues\n * typed input while mid-turn and does not read it until the turn ends, so a\n * session doing exactly the work we gave it stays silent for minutes. Since we\n * probe at expected x1.5 — precisely when a slow task is most likely still\n * working — treating silence as death would have declared healthy sessions\n * stuck on ordinary days and \"recovered\" them out from under themselves.\n *\n * AIBroker therefore decides liveness by sampling the screen before sending\n * anything: a working session animates, an idle one is static. `busy` is\n * positive evidence of life and must never count toward the stuck threshold.\n */\nexport type ProbeState = \"replied\" | \"busy\" | \"silent\" | \"absent\";\n\nexport interface ProbeAnswer {\n state: ProbeState;\n reply?: string;\n reason?: string;\n}\n\nexport interface Prober {\n /** Ask the owning session whether it is still working. */\n ask(project: string, question: string): Promise<ProbeAnswer>;\n}\n\n/**\n * Multiple of the expected duration past which a task is flagged even though it\n * keeps reporting `busy`. A session can be alive and still wrong — stuck in a\n * loop, waiting on a prompt that will never come. Liveness is not progress.\n */\nconst GROSS_OVERRUN_FACTOR = 5;\n\n// ---------------------------------------------------------------------------\n// Tick\n// ---------------------------------------------------------------------------\n\nexport interface TickOptions {\n provider: TodoistProvider;\n transport: Transport | null;\n prober: Prober | null;\n autoDispatch: boolean;\n dryRun: boolean;\n now?: number;\n /**\n * Where run state is persisted. Overridable so the dispatch path can be\n * tested at all: a real tick writes to disk, and the alternative — asserting\n * only through `dryRun` — skips the transport entirely, which is precisely\n * the code that misreported a delivered task as failed.\n */\n stateFile?: string;\n /**\n * Override webhook detection. Left unset, the tick asks AIBroker once.\n *\n * Present so tests can pin both sides of the precedence rule without a live\n * daemon — the whole point of the flag is which of two subsystems decides,\n * and that is not testable if it can only be discovered by shelling out.\n */\n webhookActive?: boolean;\n}\n\nexport interface TickReport {\n decisions: Array<{ decision: Decision; note: string }>;\n dispatched: number;\n completed: number;\n stuck: number;\n probed: number;\n}\n\n/**\n * Is a tracker webhook delivering completion events on this machine?\n *\n * Asked of AIBroker, which owns the receiver: `todoist status` reports whether\n * an OAuth grant is on file, and without a grant no completion events arrive.\n * Deliberately conservative — anything unexpected reads as \"no webhook\", which\n * leaves the due-date inference running. That is the pre-existing behaviour and\n * degrades to guessing rather than to silence.\n *\n * Cheap enough to run per tick (one short-lived process, once), and read live\n * rather than cached because a grant can lapse at any moment — that happened\n * mid-afternoon on 2026-08-01, and a cached \"yes\" would have suppressed the\n * fallback exactly when it was needed.\n */\nfunction detectWebhook(bin = \"aibroker\"): boolean {\n try {\n const out = execFileSync(bin, [\"todoist\", \"status\"], {\n timeout: 10_000,\n encoding: \"utf-8\",\n stdio: [\"ignore\", \"pipe\", \"pipe\"],\n });\n // A grant on file is the only thing that matters; without one the receiver\n // can verify webhooks but cannot resolve them, so no completion lands.\n return /authoris|authoriz|token on file|grant/i.test(out) && !/no (token|grant)/i.test(out);\n } catch {\n return false;\n }\n}\n\n/**\n * Discard restore entries whose run is no longer in flight.\n *\n * `triggeredRestore` belongs to one run: it records the occurrence that run\n * consumed, to be handed back when the run completes. It was only ever consumed\n * on the completion path, so a run that ended any other way — abandoned, or\n * released by hand — left the entry behind, and it then fired on that task's\n * NEXT completion, whenever that came.\n *\n * Observed 2026-08-02: runs ended by hand the previous night left entries that\n * fired on the first unattended 08:00 completion and wrote the due date from\n * 08-03 back to 08-02. That date was already past, so the task was instantly\n * overdue and the following tick would have dispatched a duplicate sweep.\n *\n * Clearing it at each release site would work, and would have to be remembered\n * at every site added later. Deriving it instead makes the class unreachable:\n * an entry with no claim behind it describes a run that is over, whatever ended\n * it. The claim is the only thing that says a run is still in flight.\n */\nfunction dropOrphanedRestores(tasks: Task[], state: PersistedState): void {\n const byId = new Map(tasks.map((t) => [t.id, t]));\n\n for (const id of Object.keys(state.triggeredRestore)) {\n const task = byId.get(id);\n\n // Gone from the open list entirely — nothing will complete it again.\n if (!task) {\n delete state.triggeredRestore[id];\n continue;\n }\n\n // Liveness means \"a run is in flight at all\", not \"a run we started\" —\n // a webhook-triggered run is claimed before this poller sees the task, so\n // keying on our own startedAt would discard exactly the entries the webhook\n // path depends on, one tick after recording them.\n if (!isClaimedByAnyone(task, state)) {\n delete state.triggeredRestore[id];\n }\n }\n}\n\nexport async function tick(opts: TickOptions): Promise<TickReport> {\n const now = opts.now ?? Date.now();\n const stateFile = opts.stateFile ?? STATE_FILE;\n const state = loadState(stateFile);\n const report: TickReport = { decisions: [], dispatched: 0, completed: 0, stuck: 0, probed: 0 };\n\n const tasks = await opts.provider.listOpen({ includeUnrouted: true });\n const ordered = dispatchOrder(tasks);\n\n if (!opts.dryRun) dropOrphanedRestores(tasks, state);\n\n // Resolved once per tick, not per task: it is a property of the machine.\n const webhookActive = opts.webhookActive ?? detectWebhook();\n\n for (const task of ordered) {\n // Captured before lastSeenDue is overwritten below — both the trigger\n // detection and the webhook-trigger signature need the PREVIOUS tick's value.\n const prevDue = state.lastSeenDue[task.id];\n\n const d = decide(task, {\n now,\n state,\n history: state.history,\n lastSeenDue: state.lastSeenDue,\n claimSeenAt: state.claimSeenAt,\n webhookActive,\n });\n let note = \"\";\n\n // Recorded for every task on every tick, whatever was decided — the next\n // tick's ability to spot a hand-ticked task depends on it, and skipping the\n // uninteresting cases is what would leave that blind.\n if (!opts.dryRun && task.due) state.lastSeenDue[task.id] = task.due;\n\n // Dated on first sight and dropped the moment the claim clears, so the next\n // claim on the same task is timed from when IT appeared rather than from a\n // stale entry belonging to a previous run.\n if (!opts.dryRun) {\n if (isRunning(task)) {\n // A claim appearing at the same moment the due date advanced by exactly\n // one period is the signature of a webhook-triggered run: the user\n // ticked the box, AIBroker claimed it and dispatched, all before this\n // tick. Recorded so the occurrence the tick consumed is given back after\n // the run, the same as for a trigger this poller handled itself.\n //\n // Recorded ONLY — the date is not written back now. AIBroker decides\n // whether the session may release its claim by checking that the due\n // date has advanced past the occurrence it noted when claiming, so\n // moving that date mid-run would make a finished run look unfinished\n // and strand the claim.\n if (state.claimSeenAt[task.id] === undefined && task.recurrence && prevDue !== undefined) {\n if (wasTicked(task, prevDue)) {\n state.triggeredRestore[task.id] = restoreDueString(task.recurrence, prevDue);\n }\n }\n state.claimSeenAt[task.id] ??= now;\n } else {\n delete state.claimSeenAt[task.id];\n }\n }\n\n switch (d.action) {\n case \"wait\":\n continue; // not worth reporting\n\n case \"triggered\": {\n const r = await handleDispatch(task, 0, opts, state, now);\n note = `ticked off by hand — ${r.note}`;\n if (!opts.dryRun) {\n if (!r.parked) report.dispatched++;\n if (r.alarm) report.stuck++;\n note += await restoreSchedule(task, d.restoreTo, opts, state);\n // Held for the completion, which advances the recurrence again.\n if (d.restoreTo) state.triggeredRestore[task.id] = d.restoreTo;\n }\n break;\n }\n\n case \"skip\":\n note = d.reason;\n break;\n\n case \"running\":\n note = `${d.elapsedMinutes}m elapsed`;\n break;\n\n case \"dispatch\": {\n const r = await handleDispatch(task, d.overdueMinutes, opts, state, now);\n note = r.note;\n if (!opts.dryRun) {\n // A parked task is not dispatched, so it must not be counted as one.\n // `dispatched: 1, stuck: 1` for a task nothing tried to launch reads\n // as a dispatch that then went wrong — the opposite of what happened.\n if (!r.parked) report.dispatched++;\n if (r.alarm) report.stuck++;\n }\n break;\n }\n\n case \"complete\":\n note = await handleComplete(task, d.durationMinutes, opts, state, now);\n if (!opts.dryRun) report.completed++;\n break;\n\n case \"abandoned\": {\n note = `claimed ${d.elapsedMinutes}m ago, past the ${d.thresholdMinutes}m limit — releasing the claim`;\n if (!opts.dryRun) {\n await releaseClaim(task, opts.provider, state);\n // Reported as needing attention: a run that had to be released this\n // way did not finish, and the next tick re-dispatching it is a repair,\n // not business as usual.\n report.stuck++;\n }\n break;\n }\n\n case \"probe\":\n case \"orphaned\": {\n const elapsed = d.action === \"probe\" ? d.elapsedMinutes : null;\n const result = await handleProbe(task, elapsed, opts, state, now);\n note = result.note;\n if (!opts.dryRun) {\n report.probed++;\n if (result.stuck) report.stuck++;\n if (result.dispatched) report.dispatched++;\n }\n break;\n }\n }\n\n report.decisions.push({ decision: d, note });\n }\n\n if (!opts.dryRun) {\n reconcileWithDisk(state, stateFile);\n saveState(state, stateFile);\n }\n return report;\n}\n\n// ---------------------------------------------------------------------------\n// Actions\n// ---------------------------------------------------------------------------\n\n/**\n * Is this failure a verdict rather than a setback?\n *\n * Retrying is the right default — a session that is briefly busy, an iTerm that\n * has not finished launching, a machine under load all resolve on their own.\n * A filesystem that does not have the directory is not one of those.\n *\n * A backstop, NOT the main guard. The missing-root case that motivated all of\n * this is caught before dispatch by asking the disk directly (see\n * `handleDispatch`), because AIBroker never phrases it this way: its report for\n * a vanished root is `unreachable — Launched a session in <path> but it did not\n * become ready within 90s`, which is indistinguishable in text from a machine\n * under load. This function exists for reasons that DO name the fault — a\n * transport throwing ENOENT, a future AIBroker that says so — and it is\n * deliberately narrow, since anything vaguer would park transient failures and\n * stop tasks that were only briefly unlucky.\n */\nfunction isPermanentDispatchFailure(result: { outcome: string; reason?: string }): boolean {\n const reason = result.reason ?? \"\";\n return (\n /project_root_missing/i.test(reason) ||\n /which does not exist/i.test(reason) ||\n /no such file or directory/i.test(reason)\n );\n}\n\nasync function handleDispatch(\n task: Task,\n overdue: number,\n opts: TickOptions,\n state: PersistedState,\n now: number\n): Promise<{ note: string; alarm: boolean; parked?: boolean }> {\n const late = overdue > 5 ? ` (${overdue}m late)` : \"\";\n if (opts.dryRun) {\n return { note: `would dispatch to ${task.owner.project ?? \"nobody\"}${late}`, alarm: false };\n }\n\n // A parked task is not dispatched, but it is still reported every tick — the\n // failure mode being avoided here is silence, not noise. Moving the due date\n // is the unpark gesture: it is what a user does after fixing the cause, and\n // it needs no command to remember.\n const parked = state.parked[task.id];\n if (parked) {\n if (parked.due === (task.due ?? undefined)) {\n // Re-escalated, not merely noted. `escalate` is idempotent inside its 24h\n // window, so this costs one notification a day and buys the thing the\n // whole v0.27.0 alarm existed for: a task that is not running cannot go\n // quiet. Alarming once at the moment of parking and never again would\n // rebuild the original failure in a new shape — a task scheduled every\n // day, executing never, and saying so only in a log nobody tails.\n await escalate(\n task,\n \"task is parked\",\n `is still parked (${parked.reason}) and is not being retried. ` +\n `Fix the cause and move its due date to release it.`,\n state,\n now,\n opts\n );\n return {\n note: `PARKED since ${new Date(parked.at).toISOString().slice(0, 16).replace(\"T\", \" \")} — ${parked.reason}`,\n alarm: true,\n parked: true,\n };\n }\n // Rescheduled — the user has acted. Try again.\n delete state.parked[task.id];\n delete state.failedDispatches[task.id];\n }\n\n // Reaching here at all means the task is DUE: an undated task scores\n // NEGATIVE_INFINITY overdue and is decided as `wait`, so it never arrives.\n // That is what separates this from the findings inbox, where UNROUTED is the\n // normal resting state and alarming would be pure noise — a finding carries\n // no date and makes no promise. A task with a due date does: it claims it\n // will run, and unrouted means it never can, by itself, ever.\n //\n // So this is the one dispatch failure that retrying cannot fix, and it used\n // to be the only one that did not alarm. The daily check sat here from\n // 2026-08-01 to 2026-08-03 reading \"recurring, every day at 9am\" while doing\n // nothing, and was found by a human reading the tracker, not by this code.\n if (!task.owner.project) {\n const hint = task.owner.rawHint ? ` (${task.owner.rawHint} matches no project)` : \"\";\n await escalate(\n task,\n \"scheduled task cannot run\",\n `is due${late} but has no owner${hint}, so nothing will ever pick it up. ` +\n `Move it into a session's sub-project, or give that container an alias.`,\n state,\n now,\n opts\n );\n return { note: `unrouted — cannot dispatch${hint}${late}`, alarm: true };\n }\n\n // A registered root that is not on disk is checked HERE, before anything is\n // spawned, because the transport cannot tell us about it.\n //\n // This is what the 2026-08-04 incident actually looked like from AIBroker's\n // side: it opened the tab, the shell's `cd` failed, Claude never started, and\n // it reported `unreachable — Launched a session in <path> but it did not\n // become ready within 90s`. That sentence names the path and says nothing\n // about it missing, so matching failure text for \"does not exist\" — the first\n // shape of this guard — would have parked nothing at all while reading, in\n // the diff, exactly like a fix. `/tmp/pai-scheduler.log` has the wording.\n //\n // Asked locally it is not a guess: the registry says where the project is,\n // and either the directory is there or it is not. Nine hours of fifteen-minute\n // retries, each costing a terminal window and a 90-second timeout, come down\n // to one `existsSync`.\n if (task.owner.rootPath && !existsSync(task.owner.rootPath)) {\n const reason = `project root missing: ${task.owner.rootPath}`;\n state.parked[task.id] = { reason, at: now, due: task.due ?? undefined };\n await alertParked(task, reason);\n await escalate(\n task,\n \"task is not running\",\n `cannot be dispatched: its project directory ${task.owner.rootPath} does not exist ` +\n `(renamed or moved?). It is now PARKED and will not be retried. Repoint the ` +\n `project with \\`pai project\\` and move the task's due date to release it.`,\n state,\n now,\n opts\n );\n return { note: `PARKED — ${reason}`, alarm: true, parked: true };\n }\n\n // Claim the task BEFORE dispatching, not after.\n //\n // This label is now the interlock between two independent dispatchers: this\n // poller, and AIBroker's Todoist webhook, which fires on item:completed and\n // skips any task already carrying it (aibroker 0.17.0). Setting it after the\n // dispatch returned would leave a window — the whole length of a spawn — in\n // which the webhook sees an unclaimed task and sends the same work order a\n // second time. It was previously written only for crash visibility, where\n // \"after\" was harmless; it is load-bearing now, so it has to come first.\n //\n // The cost is a label to undo when the dispatch does not land, which is the\n // cheaper failure: a task wrongly marked running is visible and self-clears\n // below, whereas a double-dispatched sweep reads Gmail twice and mails twice.\n // Already present when this is a re-dispatch of a claim that never landed\n // (handleNotReceived reaches here while the task is still marked running) —\n // append only if missing, or the label array grows a duplicate every retry.\n const claimed = task.labels.includes(RUNNING_LABEL) ? task.labels : [...task.labels, RUNNING_LABEL];\n await opts.provider.setLabels(task.id, claimed);\n\n const result = await dispatchTask(task, {\n transport: opts.transport,\n autoDispatch: opts.autoDispatch,\n spawnIfAbsent: true,\n });\n\n // `queued` counts as delivery. The message is sitting in a live session's\n // input box; Claude Code simply will not read it until the current turn ends.\n // Treating it as a failure marked a sweep NOT RUNNING while it was running,\n // and left the task without its running label so a later tick could send it\n // again — the same reasoning that makes `busy` positive evidence on the probe\n // path, where a working session is exactly the one that cannot answer.\n if (result.outcome === \"delivered\" || result.outcome === \"queued\" || result.outcome === \"spawned\") {\n state.startedAt[task.id] = now;\n delete state.failedProbes[task.id];\n delete state.failedDispatches[task.id];\n delete state.parked[task.id];\n // Dropped on success so the day-long quiet window never outlives the fault\n // it described: a task fixed this morning and broken again tonight alarms\n // tonight, rather than staying muted until tomorrow.\n delete state.alarmedAt[task.id];\n await markRunning(task, opts, now);\n return { note: `${result.outcome} to ${result.session}${late}`, alarm: false };\n }\n\n // Nothing is running, so release the claim — otherwise the task looks in\n // flight forever and the next tick reports it orphaned rather than retrying.\n // Not `releaseClaim`: the claim never made it into `state` on this path (it\n // is undone before `startedAt` would have been set), so there is nothing\n // there to clear, and `task.labels` is already the pre-claim array.\n await opts.provider.setLabels(task.id, task.labels);\n await clearRunningMark(task, opts.provider);\n\n const fails = (state.failedDispatches[task.id] ?? 0) + 1;\n state.failedDispatches[task.id] = fails;\n const detail = `${result.outcome}${result.reason ? \" — \" + result.reason : \"\"}`;\n\n // Some failures are verdicts, not setbacks. A project whose root directory\n // does not exist cannot be launched by trying harder, so it parks on the\n // first occurrence rather than burning three 90-second spawn timeouts and\n // then retrying anyway.\n const permanent = isPermanentDispatchFailure(result);\n\n if (permanent || fails >= DISPATCH_FAILURES_BEFORE_ALARM) {\n // `unreachable` against a session that has exited to a shell will never\n // resolve on its own: aibroker correctly refuses to type into it, and\n // nothing here can close the tab. Retrying quietly forever is the failure —\n // so this now stops retrying as well as reporting.\n state.parked[task.id] = {\n reason: detail,\n at: now,\n due: task.due ?? undefined,\n };\n await alertParked(task, detail);\n await escalate(\n task,\n \"task is not running\",\n `failed to dispatch ${permanent ? \"\" : `${fails} times `}(${detail}). ` +\n `It is scheduled and nothing is executing it. It is now PARKED and will not ` +\n `be retried until its due date moves — reschedule it once the cause is fixed.`,\n state,\n now,\n opts\n );\n return {\n note: `PARKED — ${permanent ? \"permanent failure\" : `${fails} failed dispatches`}: ${detail}`,\n alarm: true,\n };\n }\n return { note: `not dispatched (${fails}/${DISPATCH_FAILURES_BEFORE_ALARM}): ${detail}`, alarm: false };\n}\n\n/**\n * First line of the progress comment, and the only way it is ever found again.\n *\n * Deliberately matched by content rather than by a stored comment id. An id in\n * the state file is one more thing that goes stale — lose the file and the\n * comment is orphaned with nothing able to remove it. A sentinel is\n * self-healing: any later release finds and clears it, whatever happened in\n * between, and clearing is idempotent so doing it twice costs nothing.\n */\nconst RUNNING_COMMENT_MARK = \"**RUNNING**\";\n\n/**\n * The cross-repo \"this was written by a machine\" prefix.\n *\n * AIBroker mirrors Todoist comments to the session that owns a task — which is\n * wanted, and is how a human follow-up reaches the running work. Its echo guard\n * (`AGENT_MARK` in `daemon/todoist-webhook.ts`) is what stops that mirror from\n * feeding an agent its own writing, and it keys on this character.\n *\n * The progress marker below did not carry it. So: the poller dispatched the\n * sweep, posted \"RUNNING\" to Todoist, Todoist fired a `note:added` webhook, and\n * AIBroker mirrored the poller's own status line back as a work order — a\n * SECOND session for the same task, 19 seconds after the first. Observed\n * 2026-08-04 07:42:19 → 07:42:38 in `~/.aibroker/audit.jsonl`.\n *\n * Duplicated rather than imported because PAI must not depend on AIBroker. The\n * value is a protocol constant between them; if it ever changes, it changes in\n * both, and the mirror's own tests pin it on that side.\n */\nconst AGENT_MARK = \"🤖\";\n\n/**\n * Post the human-facing progress marker.\n *\n * This is NOT an interlock and nothing may ever decide from it. `pai-running`\n * is the machine-readable claim and both dispatchers read that alone. Two\n * mechanisms tracking one piece of state and disagreeing is the shape of every\n * defect found on 2026-08-01; this stays purely informational so it cannot\n * become the sixth.\n *\n * Failure is swallowed on purpose. A comment that could not be posted must not\n * cost the run — the sweep matters, the annotation does not.\n */\nasync function markRunning(task: Task, opts: TickOptions, startedAt: number): Promise<void> {\n if (!opts.provider.comment) return;\n const when = new Date(startedAt).toISOString().replace(\"T\", \" \").slice(0, 16);\n try {\n // Kept to one line on purpose. The first version explained the interlock\n // design here and read as noise to the person it was written for — a\n // progress marker's job is to say it is running, not to document why.\n await opts.provider.comment(\n task.id,\n `${AGENT_MARK} ${RUNNING_COMMENT_MARK} — started ${when} UTC, ${task.owner.project}. ` +\n `Disappears when it finishes.`\n );\n } catch {\n // Deliberately silent: see above.\n }\n}\n\n/**\n * Remove any progress marker left on a task.\n *\n * Called from every path that releases the claim, including the ones that\n * release it because something went wrong. A marker outliving its run is\n * exactly the stale-state problem this whole subsystem keeps producing, so the\n * cleanup is attached to the release rather than to the happy path.\n */\nasync function clearRunningMark(task: Task, provider: TodoistProvider): Promise<void> {\n const { listComments, deleteComment } = provider;\n if (!listComments || !deleteComment) return;\n try {\n const comments = await listComments.call(provider, task.id);\n for (const c of comments) {\n // `includes`, not `startsWith`: the marker gained an AGENT_MARK prefix,\n // and markers posted before that change must still be found and cleared.\n // The sentinel is meant to be self-healing — a matcher that only knows\n // the current spelling orphans every comment written by the last one.\n if (c.content.includes(RUNNING_COMMENT_MARK)) {\n await deleteComment.call(provider, c.id);\n }\n }\n } catch {\n // Same reasoning as posting it: never let the annotation break the run.\n }\n}\n\n/**\n * Release a claim on a task: strip the interlock label, remove the progress\n * marker, and forget the transient run state that made this poller believe\n * something was still working on it.\n *\n * This is every effect of ending a run except the tracker-side completion\n * itself — factored out so `handleComplete` and the \"abandoned\" release below\n * cannot drift apart, and so anything else that ends a claim (`pai task done`\n * included) gets the identical cleanup rather than a hand-rolled subset of it.\n * A partial version of this — closing the task but skipping this call — is\n * exactly what left `pai-running` and the transient state behind on every\n * `pai task done` before this existed: the next occurrence of a recurring task\n * inherited a live-looking claim it never made, and the poller spent the\n * morning asking a session whether it was still working on a task it had\n * already finished.\n *\n * Only ever removes `RUNNING_LABEL` from the label array — never replaces it —\n * so any other label the user put on the task survives. Only ever deletes this\n * task's own entries from `startedAt` / `claimSeenAt` / `failedProbes` —\n * `lastSeenDue` and `history` are untouched, exactly as `handleComplete` always\n * left them.\n */\nexport async function releaseClaim(\n task: Task,\n provider: TodoistProvider,\n state: Pick<\n PersistedState,\n \"startedAt\" | \"claimSeenAt\" | \"failedProbes\" | \"notReceivedAttempts\" | \"notReceivedRetryAt\"\n >\n): Promise<void> {\n await provider.setLabels(\n task.id,\n task.labels.filter((l) => l.toLowerCase() !== RUNNING_LABEL)\n );\n await clearRunningMark(task, provider);\n delete state.startedAt[task.id];\n delete state.failedProbes[task.id];\n delete state.claimSeenAt[task.id];\n delete state.notReceivedAttempts[task.id];\n delete state.notReceivedRetryAt[task.id];\n}\n\n/**\n * Put a hand-ticked task's schedule back.\n *\n * Deliberately after the dispatch and never in its place: the run is the point,\n * and a tracker that refuses the write must not cost the user the sweep they\n * asked for. A failure here is reported and nothing else — the task simply\n * keeps the advanced date, which is where it would have stayed anyway.\n */\nasync function restoreSchedule(\n task: Task,\n restoreTo: string | null,\n opts: TickOptions,\n state: PersistedState\n): Promise<string> {\n if (!restoreTo) return \", schedule left at the next occurrence\";\n if (!opts.provider.setDue) return \", schedule unchanged (provider cannot rewrite due dates)\";\n\n try {\n await opts.provider.setDue(task.id, restoreTo);\n // Record what we wrote, not what we read — the next tick must compare\n // against the restored date or it would read the restore as a fresh jump.\n const restoredDate = /starting (\\d{4}-\\d{2}-\\d{2})/.exec(restoreTo)?.[1];\n if (restoredDate) state.lastSeenDue[task.id] = restoredDate;\n return `, schedule restored (${restoreTo})`;\n } catch (e) {\n return `, could NOT restore the schedule: ${e instanceof Error ? e.message : String(e)}`;\n }\n}\n\nasync function handleComplete(\n task: Task,\n durationMinutes: number | null,\n opts: TickOptions,\n state: PersistedState,\n now: number\n): Promise<string> {\n if (opts.dryRun) return `would clear ${RUNNING_LABEL}, ${durationMinutes ?? \"?\"}m`;\n\n const wasStuck = (state.failedProbes[task.id] ?? 0) > 0;\n await releaseClaim(task, opts.provider, state);\n\n // A run that needed probing may have been stalled for most of its wall time.\n // Feeding that into the average would inflate every later threshold.\n if (durationMinutes !== null && !wasStuck) {\n const hist = state.history[task.id] ?? [];\n hist.push(durationMinutes);\n state.history[task.id] = hist.slice(-HISTORY_LIMIT);\n }\n\n const base =\n durationMinutes === null\n ? \"completed (duration unknown)\"\n : `completed in ${durationMinutes}m${wasStuck ? \" — not recorded, run was probed\" : \"\"}`;\n\n return base + (await keepTriggeredSchedule(task, opts, state, now));\n}\n\n/**\n * Give a hand-triggered run its schedule back, a second time.\n *\n * `pai task done` is the correct way to end a recurring run — the roll-forward\n * is how completion is detected at all — but it advances the date on top of the\n * advance the hand-tick already caused. Without this, using the checkbox as a\n * Run Now button quietly cancelled the next scheduled occurrence, which is the\n * exact behaviour the restore was added to prevent.\n *\n * Skipped when the slot has already passed: re-applying it would produce a task\n * that is overdue the moment it is written, and the next tick would dispatch it.\n */\nasync function keepTriggeredSchedule(\n task: Task,\n opts: TickOptions,\n state: PersistedState,\n now: number\n): Promise<string> {\n const keep = state.triggeredRestore[task.id];\n if (!keep) return \"\";\n delete state.triggeredRestore[task.id];\n\n const date = /starting (\\d{4}-\\d{2}-\\d{2})/.exec(keep)?.[1];\n if (!date || !opts.provider.setDue) return \"\";\n if (triggeredSlotMs(keep, date) <= now) {\n return \", triggered-run slot already past — schedule left as it is\";\n }\n\n try {\n await opts.provider.setDue(task.id, keep);\n state.lastSeenDue[task.id] = date;\n return `, schedule kept at ${date} (run was hand-triggered)`;\n } catch (e) {\n return `, could NOT keep the schedule: ${e instanceof Error ? e.message : String(e)}`;\n }\n}\n\n/** Re-dispatch attempts allowed for one claim before NOT-RECEIVED gives up. */\nexport const NOT_RECEIVED_MAX_ATTEMPTS = 3;\n\n/** Minutes of backoff per attempt: try 1 waits 2m, try 2 waits 4m, try 3 waits 6m. */\nexport const NOT_RECEIVED_BACKOFF_BASE_MIN = 2;\n\n/**\n * Does a probe reply say \"alive, but the dispatch never reached me\"?\n *\n * The session answers the liveness probe with NOT-RECEIVED (or the \"alive, not\n * received\" spelling) when it is demonstrably live yet has no knowledge of the\n * work order — the dispatch was reported delivered and was not. That is not\n * silence (the session is fine) and not death (nothing needs recovering): it is\n * an UNDELIVERED dispatch, and the honest response is to send it again.\n *\n * Anchored to the first line so a reply merely discussing the phrase —\n * \"we discussed not received earlier\" — does not trip it.\n */\nexport function isNotReceivedReply(reply: string | undefined | null): boolean {\n if (!reply) return false;\n const first = reply.split(\"\\n\", 1)[0]!.trim().toLowerCase();\n return first.startsWith(\"not-received\") || /^alive[,:]\\s*not[ -]received/.test(first);\n}\n\nasync function handleProbe(\n task: Task,\n elapsed: number | null,\n opts: TickOptions,\n state: PersistedState,\n now: number\n): Promise<{ note: string; stuck: boolean; dispatched?: boolean }> {\n const project = task.owner.project;\n const el = elapsed === null ? \"unknown\" : `${elapsed}m`;\n\n if (opts.dryRun) return { note: `would probe ${project ?? \"?\"} (${el} elapsed)`, stuck: false };\n\n if (!opts.prober || !project) {\n // No probe available. Do NOT guess — an unanswered question is not the same\n // as a dead session, and re-dispatching a live sweep would double-run it.\n return { note: `overrun (${el}) — no liveness probe available, leaving alone`, stuck: false };\n }\n\n const answer = await opts.prober.ask(\n project,\n `Are you still working on the task \"${task.title}\"? Reply in one short line.`\n );\n\n // Both of these are evidence of life. `busy` especially: it means AIBroker saw\n // the session actively producing output and deliberately sent nothing, so it\n // costs no tokens and must not count as a strike.\n if (answer.state === \"replied\" || answer.state === \"busy\") {\n state.failedProbes[task.id] = 0;\n\n // Alive but the dispatch never landed. Only `replied` can carry this — `busy`\n // means AIBroker sent nothing — and it must come before the overrun logic:\n // an elapsed-time verdict about a run that never started is noise.\n if (isNotReceivedReply(answer.reply)) {\n const attempts = state.notReceivedAttempts[task.id] ?? 0;\n const retryAt = state.notReceivedRetryAt[task.id] ?? 0;\n if (now < retryAt) {\n return {\n note: `alive but NOT-RECEIVED — re-dispatch backed off ${Math.round((retryAt - now) / 60_000)}m (try ${attempts})`,\n stuck: false,\n };\n }\n if (attempts >= NOT_RECEIVED_MAX_ATTEMPTS) {\n // A late-arriving original must not be double-run by an endless retry\n // loop: after three re-dispatches the claim is a human's problem.\n return {\n note: `alive but NOT-RECEIVED x${attempts} — dispatch never landed, claim left for a human`,\n stuck: true,\n };\n }\n const next = attempts + 1;\n state.notReceivedAttempts[task.id] = next;\n state.notReceivedRetryAt[task.id] = now + next * NOT_RECEIVED_BACKOFF_BASE_MIN * 60_000;\n const r = await handleDispatch(task, 0, opts, state, now);\n return {\n note: `alive but NOT-RECEIVED — re-dispatched (try ${next}): ${r.note}`,\n stuck: r.alarm === true,\n dispatched: true,\n };\n }\n\n // A confirmed-alive reply that does NOT say not-received is evidence the\n // dispatch DID land: the backoff clock resets (see notReceivedRetryAt docs).\n if (answer.state === \"replied\") {\n delete state.notReceivedAttempts[task.id];\n delete state.notReceivedRetryAt[task.id];\n }\n\n const expected = expectedMinutes(state.history[task.id] ?? []);\n if (elapsed !== null && elapsed > expected * GROSS_OVERRUN_FACTOR) {\n // Alive but far past any plausible runtime. Do not recover it — killing a\n // working session is worse — but do not stay quiet either.\n return {\n note: `alive but ${el} against an expected ${expected}m — ${GROSS_OVERRUN_FACTOR}x over, worth a look`,\n stuck: true,\n };\n }\n\n return answer.state === \"busy\"\n ? { note: `busy after ${el} (mid-turn, nothing sent)`, stuck: false }\n : { note: `alive after ${el}: ${answer.reply ?? \"(no detail)\"}`, stuck: false };\n }\n\n const fails = (state.failedProbes[task.id] ?? 0) + 1;\n state.failedProbes[task.id] = fails;\n\n // `absent` is stronger evidence than `silent`, but not acted on immediately:\n // an empty session list is exactly how the hub failed on 2026-08-01, and\n // re-dispatching on a lying hub would spawn a duplicate tab per task.\n const what = answer.state === \"absent\" ? \"session gone\" : \"no reply\";\n\n if (fails >= STUCK_AFTER_FAILED_PROBES) {\n return {\n note: `STUCK after ${el} — ${what} x${fails} (${answer.reason ?? \"no detail\"})`,\n stuck: true,\n };\n }\n return { note: `${what} ${fails}/${STUCK_AFTER_FAILED_PROBES} after ${el}`, stuck: false };\n}\n\nexport { isRunning };\n","/**\n * storage/backup.ts — the only place `pai backup`/`pai restore` touch the\n * *.db filenames. SQLite backend: byte-copy registry.db (+ federation.db,\n * if present) into/from the backup dir. Postgres backend: the file-copy\n * step is a no-op — pg_dump/pg_restore (still in cli/commands/backup.ts and\n * restore.ts, via docker exec) is the real backup for that backend.\n */\n\nimport { existsSync, copyFileSync, mkdirSync } from \"node:fs\";\nimport { dirname, join } from \"node:path\";\nimport type { PaiDaemonConfig } from \"../daemon/config.js\";\nimport { federationDbPath, registryDbPath } from \"./paths.js\";\n\nexport interface DbFileResult {\n label: string;\n path: string;\n status: \"ok\" | \"skipped\" | \"failed\";\n error?: string;\n}\n\n/** Copies registry.db (and federation.db, if present) into destDir. No-op on the postgres backend. */\nexport function backupStorageFiles(config: PaiDaemonConfig, destDir: string): DbFileResult[] {\n if (config.storageBackend === \"postgres\") return [];\n\n const results: DbFileResult[] = [];\n\n const registrySrc = registryDbPath();\n if (existsSync(registrySrc)) {\n const dest = join(destDir, \"registry.db\");\n try {\n copyFileSync(registrySrc, dest);\n results.push({ label: \"Registry DB\", path: dest, status: \"ok\" });\n } catch (e) {\n results.push({ label: \"Registry DB\", path: dest, status: \"failed\", error: String(e) });\n }\n } else {\n results.push({ label: \"Registry DB\", path: registrySrc, status: \"skipped\", error: \"not found\" });\n }\n\n const fedSrc = federationDbPath();\n if (existsSync(fedSrc)) {\n const dest = join(destDir, \"federation.db\");\n try {\n copyFileSync(fedSrc, dest);\n results.push({ label: \"Federation DB (legacy)\", path: dest, status: \"ok\" });\n } catch (e) {\n results.push({ label: \"Federation DB (legacy)\", path: dest, status: \"failed\", error: String(e) });\n }\n }\n\n return results;\n}\n\nexport interface StorageBackupInventory {\n hasRegistry: boolean;\n hasFederation: boolean;\n}\n\n/** What a backup dir holds, independent of which backend is configured now. */\nexport function inspectStorageBackup(srcDir: string): StorageBackupInventory {\n return {\n hasRegistry: existsSync(join(srcDir, \"registry.db\")),\n hasFederation: existsSync(join(srcDir, \"federation.db\")),\n };\n}\n\n/** Restores registry.db (and federation.db, if present) from srcDir. No-op on the postgres backend. */\nexport function restoreStorageFiles(config: PaiDaemonConfig, srcDir: string): DbFileResult[] {\n if (config.storageBackend === \"postgres\") return [];\n\n const results: DbFileResult[] = [];\n const inv = inspectStorageBackup(srcDir);\n\n if (inv.hasRegistry) {\n const dest = registryDbPath();\n try {\n mkdirSync(dirname(dest), { recursive: true });\n copyFileSync(join(srcDir, \"registry.db\"), dest);\n results.push({ label: \"Registry DB\", path: dest, status: \"ok\" });\n } catch (e) {\n results.push({ label: \"Registry DB\", path: dest, status: \"failed\", error: String(e) });\n }\n } else {\n results.push({ label: \"Registry DB\", path: registryDbPath(), status: \"skipped\", error: \"missing in backup\" });\n }\n\n if (inv.hasFederation) {\n const dest = federationDbPath();\n try {\n mkdirSync(dirname(dest), { recursive: true });\n copyFileSync(join(srcDir, \"federation.db\"), dest);\n results.push({ label: \"Federation DB (legacy)\", path: dest, status: \"ok\" });\n } catch (e) {\n results.push({ label: \"Federation DB (legacy)\", path: dest, status: \"failed\", error: String(e) });\n }\n }\n\n return results;\n}\n","/**\n * pai backup — snapshot registry, config, and Postgres database.\n *\n * Creates a timestamped backup directory at:\n * ~/.pai/backups/YYYY-MM-DD-HHmmss/\n *\n * Contents:\n * registry file — SQLite registry database (sqlite backend only)\n * config.json — PAI daemon config\n * postgres-pai.sql — pg_dump of the Postgres \"pai\" database (via docker exec)\n */\n\nimport type { Command } from \"commander\";\nimport {\n existsSync,\n mkdirSync,\n copyFileSync,\n statSync,\n chmodSync,\n openSync,\n closeSync,\n} from \"node:fs\";\nimport { join } from \"node:path\";\nimport { homedir } from \"node:os\";\nimport { execFileSync, spawnSync } from \"node:child_process\";\nimport { ok, warn, err, dim, bold } from \"../utils.js\";\nimport { loadConfig, paiConfigFilePath } from \"../../daemon/config.js\";\nimport { backupStorageFiles } from \"../../storage/backup.js\";\nimport { paiHomePath, resolvePaiFile } from \"../../config/pai-home.js\";\n\n// ---------------------------------------------------------------------------\n// Paths\n// ---------------------------------------------------------------------------\n\nconst HOME = homedir();\nconst CONFIG_FILE = paiConfigFilePath();\n\n/** Old backups dir, inside ~/.pai/ (pre-2026-09-19). */\nexport function oldBackupsDir(): string {\n return join(HOME, \".pai\", \"backups\");\n}\n\n/** Backups dir: PAI_HOME/backups if present, else the old ~/.pai/backups\n * (one-time stderr notice), else the new path. */\nexport function backupsDirPath(): string {\n return resolvePaiFile(paiHomePath(\"backups\"), [oldBackupsDir()], \"pai config migrate --backups\");\n}\n\nconst BACKUPS_DIR = backupsDirPath();\nconst DOCKER_CONTAINER = \"pai-pgvector\";\nconst PG_DATABASE = \"pai\";\nconst PG_USER = \"pai\";\n\n// ---------------------------------------------------------------------------\n// Helpers\n// ---------------------------------------------------------------------------\n\nfunction timestamp(): string {\n const now = new Date();\n const YYYY = now.getFullYear();\n const MM = String(now.getMonth() + 1).padStart(2, \"0\");\n const DD = String(now.getDate()).padStart(2, \"0\");\n const HH = String(now.getHours()).padStart(2, \"0\");\n const mm = String(now.getMinutes()).padStart(2, \"0\");\n const ss = String(now.getSeconds()).padStart(2, \"0\");\n return `${YYYY}-${MM}-${DD}-${HH}${mm}${ss}`;\n}\n\nfunction fmtBytes(bytes: number): string {\n if (bytes < 1024) return `${bytes} B`;\n if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`;\n return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;\n}\n\nfunction fileSize(path: string): string {\n try {\n return fmtBytes(statSync(path).size);\n } catch {\n return \"unknown\";\n }\n}\n\n// ---------------------------------------------------------------------------\n// Registration\n// ---------------------------------------------------------------------------\n\nexport function registerBackupCommands(program: Command): void {\n program\n .command(\"backup\")\n .description(\"Backup registry, config, and Postgres database to PAI_HOME/backups/\")\n .option(\"--no-postgres\", \"Skip the Postgres pg_dump (faster, registry+config only)\")\n .action(async (opts: { postgres: boolean }) => {\n const ts = timestamp();\n const backupDir = join(BACKUPS_DIR, ts);\n\n console.log(dim(`Creating backup: ${backupDir}`));\n mkdirSync(backupDir, { recursive: true });\n\n const results: { label: string; path: string; size: string; status: string }[] = [];\n const config = loadConfig();\n\n // ------------------------------------------------------------------\n // 1. Registry (+ legacy federation) SQLite DB, sqlite backend only\n // ------------------------------------------------------------------\n\n for (const r of backupStorageFiles(config, backupDir)) {\n results.push({\n label: r.label,\n path: r.path,\n size: r.status === \"ok\" ? fileSize(r.path) : \"-\",\n status:\n r.status === \"ok\" ? ok(\"ok\") : r.status === \"skipped\" ? warn(\"not found — skipped\") : err(`failed: ${r.error}`),\n });\n }\n\n // ------------------------------------------------------------------\n // 2. Config file\n // ------------------------------------------------------------------\n\n if (existsSync(CONFIG_FILE)) {\n const dest = join(backupDir, \"config.json\");\n try {\n copyFileSync(CONFIG_FILE, dest);\n chmodSync(dest, 0o600);\n results.push({ label: \"Config\", path: dest, size: fileSize(dest), status: ok(\"ok\") });\n } catch (e) {\n results.push({ label: \"Config\", path: dest, size: \"-\", status: err(`failed: ${e}`) });\n }\n } else {\n results.push({ label: \"Config\", path: CONFIG_FILE, size: \"-\", status: warn(\"not found — skipped\") });\n }\n\n // ------------------------------------------------------------------\n // 3. Postgres pg_dump via docker exec\n // ------------------------------------------------------------------\n\n if (opts.postgres) {\n const sqlDest = join(backupDir, \"postgres-pai.sql\");\n console.log(dim(` Running pg_dump on ${DOCKER_CONTAINER} (this may take a moment)...`));\n try {\n // Check Docker is running and container exists\n execFileSync(\"docker\", [\"inspect\", DOCKER_CONTAINER, \"--format={{.State.Status}}\"], {\n stdio: \"pipe\",\n });\n\n const sqlFd = openSync(sqlDest, \"w\");\n try {\n const dump = spawnSync(\n \"docker\",\n [\"exec\", DOCKER_CONTAINER, \"pg_dump\", \"-U\", PG_USER, PG_DATABASE],\n { stdio: [\"pipe\", sqlFd, \"pipe\"] }\n );\n if (dump.status !== 0) {\n throw new Error(dump.stderr?.toString().trim() || `pg_dump exited with code ${dump.status}`);\n }\n } finally {\n closeSync(sqlFd);\n }\n results.push({ label: \"Postgres DB\", path: sqlDest, size: fileSize(sqlDest), status: ok(\"ok\") });\n } catch (e) {\n const msg = e instanceof Error ? e.message.split(\"\\n\")[0] : String(e);\n results.push({ label: \"Postgres DB\", path: sqlDest, size: \"-\", status: err(`failed: ${msg}`) });\n console.log(warn(` Postgres backup failed. Is Docker running with container '${DOCKER_CONTAINER}'?`));\n }\n } else {\n results.push({ label: \"Postgres DB\", path: \"-\", size: \"-\", status: dim(\"skipped (--no-postgres)\") });\n }\n\n // ------------------------------------------------------------------\n // Summary\n // ------------------------------------------------------------------\n\n console.log(`\\n${bold(\"Backup complete:\")} ${backupDir}\\n`);\n\n const labelWidth = Math.max(...results.map((r) => r.label.length)) + 2;\n for (const r of results) {\n const label = r.label.padEnd(labelWidth);\n console.log(` ${bold(label)} ${r.status} ${dim(r.size)}`);\n }\n\n console.log(`\\n ${dim(\"Path:\")} ${backupDir}`);\n console.log(` ${dim(\"To restore:\")} pai restore ${backupDir}\\n`);\n });\n}\n","/** Symlink management: create, validate, migrate, and clean vault project symlinks. */\n\nimport {\n existsSync,\n mkdirSync,\n symlinkSync,\n readdirSync,\n lstatSync,\n unlinkSync,\n readlinkSync,\n writeFileSync,\n rmdirSync,\n} from \"node:fs\";\nimport { join } from \"node:path\";\nimport type { RegistryBackend } from \"../../storage/registry-interface.js\";\nimport type { SyncStats } from \"./types.js\";\n\n// ---------------------------------------------------------------------------\n// Internal helpers\n// ---------------------------------------------------------------------------\n\n/** Find the project-root Notes directory for a project. */\nexport function findNotesDir(rootPath: string): string | null {\n const canonical = join(rootPath, \"Notes\");\n if (existsSync(canonical)) return canonical;\n const alt = join(rootPath, \".claude\", \"Notes\");\n if (existsSync(alt)) return alt;\n return null;\n}\n\n/**\n * Find the Claude Code session notes directory from the registry-stored value.\n * Returns null if not set, missing on disk, or identical to notesDir.\n */\nexport function findClaudeNotesDir(\n claudeNotesDirFromRegistry: string | null,\n notesDir: string | null\n): string | null {\n if (!claudeNotesDirFromRegistry) return null;\n if (!existsSync(claudeNotesDirFromRegistry)) return null;\n if (notesDir && claudeNotesDirFromRegistry === notesDir) return null;\n return claudeNotesDirFromRegistry;\n}\n\n/** Check whether a path exists via lstat (does not follow symlinks). */\nfunction lstatExists(p: string): boolean {\n try {\n lstatSync(p);\n return true;\n } catch {\n return false;\n }\n}\n\n/** Resolve slug collisions by appending -2, -3, etc. */\nexport function uniqueSlug(base: string, taken: Set<string>): string {\n if (!taken.has(base)) return base;\n let n = 2;\n while (taken.has(`${base}-${n}`)) n++;\n return `${base}-${n}`;\n}\n\n/**\n * Remove broken symlinks from a directory (one level deep).\n * Returns count of entries removed.\n */\nexport function cleanBrokenSymlinks(dir: string): number {\n let removed = 0;\n if (!existsSync(dir)) return removed;\n for (const entry of readdirSync(dir)) {\n const full = join(dir, entry);\n try {\n const stat = lstatSync(full);\n if (stat.isSymbolicLink()) {\n const target = readlinkSync(full);\n if (!existsSync(target)) {\n unlinkSync(full);\n removed++;\n }\n }\n } catch {\n // Skip unreadable entries\n }\n }\n return removed;\n}\n\n/**\n * Ensure a sub-symlink inside a project directory is correct.\n * Returns true if a new symlink was created, false otherwise.\n */\nexport function ensureSubSymlink(\n linkPath: string,\n target: string,\n errors: string[],\n label: string\n): boolean {\n if (lstatExists(linkPath)) {\n try {\n const stat = lstatSync(linkPath);\n if (stat.isSymbolicLink()) {\n const current = readlinkSync(linkPath);\n if (current === target) {\n return false; // Already correct\n }\n unlinkSync(linkPath);\n } else {\n errors.push(`${label}: path exists but is not a symlink — skipped`);\n return false;\n }\n } catch (e) {\n errors.push(`${label}: error checking existing path — ${e}`);\n return false;\n }\n }\n\n try {\n symlinkSync(target, linkPath);\n return true;\n } catch (e) {\n errors.push(`${label}: symlink creation failed — ${e}`);\n return false;\n }\n}\n\n/**\n * Migrate a legacy flat symlink at `slugPath` to a real directory.\n * If `slugPath` is already a real directory, this is a no-op.\n * If it is a symlink (legacy), it is removed so mkdirSync can create the dir.\n */\nexport function migrateToProjectDir(\n slugPath: string,\n errors: string[],\n slug: string\n): boolean {\n if (!lstatExists(slugPath)) {\n return true; // Does not exist yet — mkdirSync will create it\n }\n\n try {\n const stat = lstatSync(slugPath);\n\n if (stat.isDirectory()) {\n return true; // Already a real directory — nothing to migrate\n }\n\n if (stat.isSymbolicLink()) {\n unlinkSync(slugPath);\n return true; // Removed old symlink — now ready for mkdirSync\n }\n\n errors.push(`${slug}: path exists as non-directory, non-symlink — skipped`);\n return false;\n } catch (e) {\n errors.push(`${slug}: error during migration — ${e}`);\n return false;\n }\n}\n\n/** Remove a project directory from the vault if it is empty. */\nfunction removeProjectDirIfEmpty(slugPath: string): void {\n try {\n const entries = readdirSync(slugPath);\n if (entries.length === 0) {\n rmdirSync(slugPath);\n }\n } catch {\n // Non-fatal\n }\n}\n\n// ---------------------------------------------------------------------------\n// Public: syncVault\n// ---------------------------------------------------------------------------\n\n/**\n * Sync all active project Notes directories into the Obsidian vault.\n *\n * For each active project with at least one Notes source, creates:\n * {vault}/{slug}/ — real directory\n * notes → {root}/Notes/\n * sessions → ~/.claude/projects/{enc}/Notes/ (if different)\n *\n * Archived projects get a stub markdown file in {vault}/_archive/.\n */\nexport async function syncVault(vaultPath: string, registry: RegistryBackend): Promise<SyncStats> {\n const stats: SyncStats = { created: 0, updated: 0, removed: 0, stubbed: 0, errors: [] };\n\n mkdirSync(vaultPath, { recursive: true });\n stats.removed += cleanBrokenSymlinks(vaultPath);\n\n // ORDER BY status ASC, slug ASC — RegistryBackend has no combined orderBy\n // for this pair, so sort client-side (plain string compare matches SQLite's\n // default ASCII collation for these columns).\n const projects = (await registry.listProjects()).sort((a, b) =>\n a.status !== b.status ? a.status.localeCompare(b.status) : a.slug.localeCompare(b.slug)\n );\n\n const takenSlugs = new Set<string>();\n\n for (const project of projects) {\n if (project.status === \"active\") {\n const notesDir = findNotesDir(project.root_path);\n const claudeNotesDir = findClaudeNotesDir(project.claude_notes_dir, notesDir);\n\n if (!notesDir && !claudeNotesDir) {\n continue;\n }\n\n const slug = uniqueSlug(project.slug, takenSlugs);\n takenSlugs.add(slug);\n const slugPath = join(vaultPath, slug);\n\n if (!migrateToProjectDir(slugPath, stats.errors, slug)) {\n continue;\n }\n\n try {\n mkdirSync(slugPath, { recursive: true });\n } catch (e) {\n stats.errors.push(`${slug}: failed to create project directory — ${e}`);\n continue;\n }\n\n if (notesDir) {\n const notesLink = join(slugPath, \"notes\");\n const created = ensureSubSymlink(notesLink, notesDir, stats.errors, `${slug}/notes`);\n if (created) stats.created++;\n else stats.updated++;\n }\n\n if (claudeNotesDir) {\n const sessionsLink = join(slugPath, \"sessions\");\n const created = ensureSubSymlink(sessionsLink, claudeNotesDir, stats.errors, `${slug}/sessions`);\n if (created) stats.created++;\n else stats.updated++;\n }\n\n try {\n await registry.updateProjectObsidianLink(project.id, slugPath, Date.now());\n } catch {\n // Non-fatal\n }\n } else if (project.status === \"archived\") {\n const archiveDir = join(vaultPath, \"_archive\");\n mkdirSync(archiveDir, { recursive: true });\n const stubPath = join(archiveDir, `${project.slug}.md`);\n if (!existsSync(stubPath)) {\n const content = [\n `# ${project.display_name}`,\n \"\",\n \"> Archived project — no live notes available.\",\n \"\",\n `- **Slug:** ${project.slug}`,\n `- **Root:** ${project.root_path}`,\n \"\",\n ].join(\"\\n\");\n try {\n writeFileSync(stubPath, content, \"utf-8\");\n stats.stubbed++;\n } catch (e) {\n stats.errors.push(`${project.slug} (archive stub): ${e}`);\n }\n }\n }\n }\n\n return stats;\n}\n","/** Index and topic page generation — writes _index.md and _topics/{tag}.md. */\n\nimport { mkdirSync, writeFileSync } from \"node:fs\";\nimport { join } from \"node:path\";\nimport { homedir } from \"node:os\";\nimport type { RegistryBackend } from \"../../storage/registry-interface.js\";\nimport { fmtDate } from \"../../cli/utils.js\";\nimport { findNotesDir, findClaudeNotesDir } from \"./symlinks.js\";\nimport { paiHomePath, resolvePaiFile, migratePaiDir, type MigrateDirResult } from \"../../config/pai-home.js\";\n\n/**\n * Generate _index.md listing all projects with session counts, tags, and\n * indicators for which note sources are available (notes, sessions, or both).\n */\nexport async function generateIndex(vaultPath: string, registry: RegistryBackend): Promise<void> {\n mkdirSync(vaultPath, { recursive: true });\n\n const rows = await registry.listProjectsWithSessionStats();\n\n const active = rows.filter((r) => r.status === \"active\");\n const archived = rows.filter((r) => r.status !== \"active\");\n\n const lines: string[] = [\n \"# PAI Project Index\",\n \"\",\n `> Auto-generated by PAI Knowledge OS — ${new Date().toISOString()}`,\n \"\",\n \"## Active Projects\",\n \"\",\n \"| Project | Sessions | Last Active | Sources | Tags |\",\n \"| ------- | -------- | ----------- | ------- | ---- |\",\n ];\n\n for (const row of active) {\n const tags = (await registry.listTagsForProject(row.id)).map((t) => `\\`${t}\\``).join(\" \");\n const lastActive = fmtDate(row.last_active);\n\n const notesDir = findNotesDir(row.root_path);\n const claudeNotesDir = findClaudeNotesDir(row.claude_notes_dir, notesDir);\n const sources: string[] = [];\n if (notesDir) sources.push(\"notes\");\n if (claudeNotesDir) sources.push(\"sessions\");\n const sourcesLabel = sources.length > 0 ? sources.join(\", \") : \"—\";\n\n lines.push(\n `| [[${row.slug}/|${row.display_name}]] | ${row.session_count} | ${lastActive} | ${sourcesLabel} | ${tags || \"—\"} |`\n );\n }\n\n if (archived.length > 0) {\n lines.push(\n \"\",\n \"## Archived Projects\",\n \"\",\n \"| Project | Sessions | Tags |\",\n \"| ------- | -------- | ---- |\"\n );\n for (const row of archived) {\n const tags = (await registry.listTagsForProject(row.id)).map((t) => `\\`${t}\\``).join(\" \");\n lines.push(\n `| [${row.display_name}](_archive/${row.slug}.md) | ${row.session_count} | ${tags || \"—\"} |`\n );\n }\n }\n\n lines.push(\n \"\",\n \"---\",\n `*${rows.length} projects total — ${active.length} active, ${archived.length} archived*`\n );\n\n writeFileSync(join(vaultPath, \"_index.md\"), lines.join(\"\\n\") + \"\\n\", \"utf-8\");\n}\n\n/**\n * Generate per-tag topic pages at _topics/{tag}.md.\n * Returns count of pages written.\n */\nexport async function generateTopicPages(vaultPath: string, registry: RegistryBackend): Promise<number> {\n const topicsDir = join(vaultPath, \"_topics\");\n mkdirSync(topicsDir, { recursive: true });\n\n const allTags = await registry.listAllTags();\n\n let written = 0;\n for (const tag of allTags) {\n const projects = await registry.listProjectsWithSessionStats({ tagId: tag.id });\n if (!projects.length) continue;\n\n const lines: string[] = [\n `# Topic: ${tag.name}`,\n \"\",\n `> Auto-generated by PAI Knowledge OS — ${new Date().toISOString()}`,\n \"\",\n \"## Projects\",\n \"\",\n \"| Project | Status | Sessions | Last Active |\",\n \"| ------- | ------ | -------- | ----------- |\",\n ];\n\n for (const p of projects) {\n const link =\n p.status === \"active\"\n ? `[[${p.slug}/|${p.display_name}]]`\n : `[${p.display_name}](../_archive/${p.slug}.md)`;\n lines.push(`| ${link} | ${p.status} | ${p.session_count} | ${fmtDate(p.last_active)} |`);\n }\n\n lines.push(\"\", \"---\", `*${projects.length} project(s) tagged \\`${tag.name}\\`*`);\n\n writeFileSync(join(topicsDir, `${tag.name}.md`), lines.join(\"\\n\") + \"\\n\", \"utf-8\");\n written++;\n }\n\n return written;\n}\n\n/** Old default vault path, inside ~/.pai/ (pre-2026-09-19). */\nexport function oldDefaultVaultPath(): string {\n return join(homedir(), \".pai\", \"obsidian-vault\");\n}\n\n/**\n * Default vault path: PAI_HOME/obsidian-vault if present, else the old\n * ~/.pai/obsidian-vault (one-time stderr notice), else the new path.\n */\nexport function defaultVaultPath(): string {\n return resolvePaiFile(paiHomePath(\"obsidian-vault\"), [oldDefaultVaultPath()], \"pai config migrate --obsidian-vault\");\n}\n\nexport function migrateObsidianVaultDir(opts: { dryRun?: boolean } = {}): MigrateDirResult {\n return migratePaiDir(paiHomePath(\"obsidian-vault\"), [oldDefaultVaultPath()], opts);\n}\n","/** Directory walking and session file discovery for vault note generation. */\n\nimport {\n existsSync,\n readdirSync,\n lstatSync,\n readlinkSync,\n} from \"node:fs\";\nimport { join, relative } from \"node:path\";\nimport type { SessionFile } from \"./types.js\";\n\nexport const SESSION_FILENAME_RE = /^(\\d{4}) - (\\d{4}-\\d{2})-\\d{2} - .+\\.md$/;\n\n/**\n * Walk a directory (non-recursive root level, then one level of YYYY/MM subdirs).\n * Returns absolute paths to all .md files found, skipping master note files.\n */\nexport function walkNotesDir(dir: string): string[] {\n const results: string[] = [];\n if (!existsSync(dir)) return results;\n\n let entries: string[];\n try {\n entries = readdirSync(dir);\n } catch {\n return results;\n }\n\n for (const entry of entries) {\n if (entry.startsWith(\".\")) continue;\n // Skip any master note file (pattern: _{slug}-master.md or legacy _master.md)\n if (entry === \"_master.md\" || /^_[^/]+-master\\.md$/.test(entry)) continue;\n const full = join(dir, entry);\n let stat: ReturnType<typeof lstatSync>;\n try {\n stat = lstatSync(full);\n } catch {\n continue;\n }\n if (stat.isDirectory()) {\n if (/^\\d{4}$/.test(entry)) {\n let monthEntries: string[];\n try {\n monthEntries = readdirSync(full);\n } catch {\n continue;\n }\n for (const month of monthEntries) {\n const monthPath = join(full, month);\n if (/^\\d{2}$/.test(month) && existsSync(monthPath)) {\n let monthFiles: string[];\n try {\n monthFiles = readdirSync(monthPath);\n } catch {\n continue;\n }\n for (const f of monthFiles) {\n if (f.endsWith(\".md\") && !f.startsWith(\".\")) {\n results.push(join(monthPath, f));\n }\n }\n }\n }\n }\n } else if (entry.endsWith(\".md\")) {\n results.push(full);\n }\n }\n return results;\n}\n\n/**\n * Extract YYYY/MM from a session file path.\n * Tries the path first (/YYYY/MM/ pattern), then falls back to filename date.\n */\nexport function extractYearMonth(filePath: string): string {\n const pathMatch = filePath.match(/\\/(\\d{4})\\/(\\d{2})\\//);\n if (pathMatch) return `${pathMatch[1]}/${pathMatch[2]}`;\n\n const basename = filePath.split(\"/\").pop() ?? \"\";\n const nameMatch = basename.match(/^\\d{4} - (\\d{4})-(\\d{2})-\\d{2}/);\n if (nameMatch) return `${nameMatch[1]}/${nameMatch[2]}`;\n\n return \"unknown\";\n}\n\n/**\n * Collect all session .md files from the notes and sessions symlinks\n * inside a vault project directory.\n */\nexport function collectSessionFiles(slugPath: string): SessionFile[] {\n const sessionFiles: SessionFile[] = [];\n const subLinks = [\"notes\", \"sessions\"];\n\n for (const subLink of subLinks) {\n const linkPath = join(slugPath, subLink);\n if (!existsSync(linkPath)) continue;\n\n let realDir: string;\n try {\n const stat = lstatSync(linkPath);\n if (stat.isSymbolicLink()) {\n realDir = readlinkSync(linkPath);\n } else if (stat.isDirectory()) {\n realDir = linkPath;\n } else {\n continue;\n }\n } catch {\n continue;\n }\n\n const files = walkNotesDir(realDir);\n for (const absPath of files) {\n const basename = absPath.split(\"/\").pop() ?? \"\";\n if (!SESSION_FILENAME_RE.test(basename)) continue;\n\n const relFromReal = relative(realDir, absPath);\n const vaultRelPath = `${subLink}/${relFromReal}`;\n const wikilinkTarget = vaultRelPath.replace(/\\.md$/, \"\");\n\n sessionFiles.push({\n absPath,\n vaultRelPath,\n wikilinkTarget,\n yearMonth: extractYearMonth(absPath),\n basename: basename.replace(/\\.md$/, \"\"),\n });\n }\n }\n\n return sessionFiles;\n}\n","/** Master note generation and session tag fixing for vault projects. */\n\nimport {\n existsSync,\n unlinkSync,\n writeFileSync,\n readFileSync,\n} from \"node:fs\";\nimport { join } from \"node:path\";\nimport type { RegistryBackend } from \"../../storage/registry-interface.js\";\nimport { collectSessionFiles, walkNotesDir } from \"./walk.js\";\n\n// ---------------------------------------------------------------------------\n// Helpers\n// ---------------------------------------------------------------------------\n\n/** Build the per-project master note filename. */\nexport function masterFilename(slug: string): string {\n return `_${slug}-master.md`;\n}\n\n/**\n * Remove any old/broken backlink footer from a session file.\n * Returns true if the file was modified.\n */\nfunction removeOldBacklink(filePath: string): boolean {\n let content: string;\n try {\n content = readFileSync(filePath, \"utf-8\");\n } catch {\n return false;\n }\n const oldPattern = /\\n---\\n\\[\\[\\.\\.\\/[^\\]]+\\]\\]\\n?$/;\n const newPattern = /\\n---\\n\\[\\[_[^\\]]*-master\\|[^\\]]*\\]\\]\\n?$/;\n const cleaned = content.replace(oldPattern, \"\").replace(newPattern, \"\");\n if (cleaned === content) return false;\n try {\n writeFileSync(filePath, cleaned, \"utf-8\");\n return true;\n } catch {\n return false;\n }\n}\n\n/**\n * Return true if the note has a meaningful body — i.e. content beyond blank\n * lines, horizontal rules, and an existing master-note footer.\n *\n * Guards against stamping a \"← Master\" backlink onto an empty note, which\n * produces a misleading dangling stub (empty body + footer only). Whatever\n * emptied the note, we must not decorate it as if it were a real session.\n */\nfunction hasMeaningfulBody(content: string): boolean {\n const body = content\n .replace(/\\n---\\n\\[\\[_[^\\]]*-master\\|[^\\]]*\\]\\]\\n?$/, \"\") // drop existing master footer\n .split(\"\\n\")\n .filter((line) => {\n const t = line.trim();\n return t !== \"\" && t !== \"---\";\n })\n .join(\"\");\n return body.length > 0;\n}\n\n/**\n * Append the master note backlink footer to a session file, idempotently.\n * Only writes if the sentinel string is not already present AND the note has\n * a meaningful body (never stamp a footer onto an empty note).\n */\nfunction appendBacklinkIfMissing(\n filePath: string,\n slug: string,\n displayName: string\n): boolean {\n let content: string;\n try {\n content = readFileSync(filePath, \"utf-8\");\n } catch {\n return false;\n }\n // Never stamp a footer onto an empty note — that is exactly the dangling-stub\n // signature (empty body + master backlink) we are trying to prevent.\n if (!hasMeaningfulBody(content)) return false;\n\n const masterName = masterFilename(slug).replace(/\\.md$/, \"\");\n const sentinel = `[[${masterName}|`;\n if (content.includes(sentinel)) return false;\n\n const footer = `\\n---\\n[[${masterName}|← ${displayName} Master]]\\n`;\n try {\n writeFileSync(filePath, content + footer, \"utf-8\");\n return true;\n } catch {\n return false;\n }\n}\n\n// ---------------------------------------------------------------------------\n// Public: generateMasterNotes\n// ---------------------------------------------------------------------------\n\n/**\n * Generate _master.md files for projects that have >= threshold session files.\n *\n * For each qualifying project, writes a {vaultPath}/{slug}/_master.md containing:\n * - Project title, session count + date range\n * - Sessions grouped by YYYY/MM with Obsidian [[wikilinks]]\n *\n * Also appends a backlink footer to each session file (idempotently).\n *\n * @param vaultPath Absolute path to the PAI Obsidian vault\n * @param registry Registry backend\n * @param threshold Minimum session count to generate a master note (default: 5)\n * @returns Number of master notes written\n */\nexport async function generateMasterNotes(\n vaultPath: string,\n registry: RegistryBackend,\n threshold = 5\n): Promise<number> {\n if (!existsSync(vaultPath)) return 0;\n\n const projects = await registry.listProjects({ status: \"active\", orderBy: \"slug\" });\n\n let written = 0;\n\n for (const project of projects) {\n const slugPath = join(vaultPath, project.slug);\n if (!existsSync(slugPath)) continue;\n\n // Remove legacy _master.md if present\n const legacyMaster = join(slugPath, \"_master.md\");\n if (existsSync(legacyMaster)) {\n try { unlinkSync(legacyMaster); } catch { /* Non-fatal */ }\n }\n\n const sessionFiles = collectSessionFiles(slugPath);\n if (sessionFiles.length < threshold) continue;\n\n // Sort by yearMonth then basename\n sessionFiles.sort((a, b) => {\n if (a.yearMonth !== b.yearMonth) return a.yearMonth.localeCompare(b.yearMonth);\n return a.basename.localeCompare(b.basename);\n });\n\n const byMonth = new Map<string, typeof sessionFiles>();\n for (const sf of sessionFiles) {\n if (!byMonth.has(sf.yearMonth)) byMonth.set(sf.yearMonth, []);\n byMonth.get(sf.yearMonth)!.push(sf);\n }\n\n const firstDate = sessionFiles[0]?.basename.match(/\\d{4}-\\d{2}-\\d{2}/)?.[0] ?? \"unknown\";\n const lastDate = sessionFiles[sessionFiles.length - 1]?.basename.match(/\\d{4}-\\d{2}-\\d{2}/)?.[0] ?? \"unknown\";\n\n const lines: string[] = [\n `# Project: ${project.display_name}`,\n \"\",\n `> Auto-generated by PAI Knowledge OS — ${new Date().toISOString()}`,\n \"\",\n \"## Overview\",\n \"\",\n `- **Sessions:** ${sessionFiles.length}`,\n `- **Date range:** ${firstDate} to ${lastDate}`,\n `- **Tags:** #${project.slug} #project`,\n \"\",\n \"## Sessions by Month\",\n \"\",\n ];\n\n for (const [ym, files] of byMonth) {\n lines.push(`### ${ym}`, \"\");\n for (const sf of files) {\n lines.push(`- [[${sf.wikilinkTarget}|${sf.basename}]]`);\n }\n lines.push(\"\");\n }\n\n lines.push(\"## Topics\", \"\", `#${project.slug} #project`, \"\");\n\n const masterPath = join(slugPath, masterFilename(project.slug));\n try {\n writeFileSync(masterPath, lines.join(\"\\n\"), \"utf-8\");\n written++;\n } catch {\n continue;\n }\n\n for (const sf of sessionFiles) {\n removeOldBacklink(sf.absPath);\n appendBacklinkIfMissing(sf.absPath, project.slug, project.display_name);\n }\n }\n\n return written;\n}\n\n// ---------------------------------------------------------------------------\n// Public: fixSessionTags\n// ---------------------------------------------------------------------------\n\n/**\n * Remove the generic #Session tag from session note files across all projects.\n *\n * Scans all session and notes directories for every active project and removes\n * #Session (with or without a trailing space) from any `**Tags:**` line.\n *\n * @param registry Registry backend\n * @returns Object with counts: { filesScanned, filesModified, errors }\n */\nexport async function fixSessionTags(\n registry: RegistryBackend\n): Promise<{ filesScanned: number; filesModified: number; errors: string[] }> {\n const results = { filesScanned: 0, filesModified: 0, errors: [] as string[] };\n\n const projects = await registry.listProjects({ status: \"active\", orderBy: \"slug\" });\n\n for (const project of projects) {\n const dirsToScan: string[] = [];\n\n // Inline findNotesDir to avoid a circular dependency with symlinks.ts\n const canonical = join(project.root_path, \"Notes\");\n const alt = join(project.root_path, \".claude\", \"Notes\");\n const notesDir = existsSync(canonical) ? canonical : existsSync(alt) ? alt : null;\n\n if (notesDir) dirsToScan.push(notesDir);\n\n if (\n project.claude_notes_dir &&\n existsSync(project.claude_notes_dir) &&\n project.claude_notes_dir !== notesDir\n ) {\n dirsToScan.push(project.claude_notes_dir);\n }\n\n for (const dir of dirsToScan) {\n const files = walkNotesDir(dir);\n for (const filePath of files) {\n results.filesScanned++;\n let content: string;\n try {\n content = readFileSync(filePath, \"utf-8\");\n } catch (e) {\n results.errors.push(`${filePath}: read error — ${e}`);\n continue;\n }\n\n if (!content.includes(\"#Session\")) continue;\n\n const updated = content.replace(\n /(\\*\\*Tags:\\*\\*[^\\n]*)#Session ?/g,\n \"$1\"\n );\n\n if (updated === content) continue;\n\n const cleaned = updated.replace(/(\\*\\*Tags:\\*\\*) *\\n/g, \"$1\\n\");\n\n try {\n writeFileSync(filePath, cleaned, \"utf-8\");\n results.filesModified++;\n } catch (e) {\n results.errors.push(`${filePath}: write error — ${e}`);\n }\n }\n }\n }\n\n return results;\n}\n","/**\n * obsidian/status.ts — Vault health checking for PAI Phase 4\n *\n * Vault structure (per project):\n *\n * {vault}/{slug}/ — real directory\n * notes → {root}/Notes/ (project root notes, optional)\n * sessions → ~/.claude/projects/{enc}/Notes/ (Claude Code session notes, optional)\n */\n\nimport { existsSync, readdirSync, lstatSync, readlinkSync } from \"node:fs\";\nimport { join } from \"node:path\";\nimport type { Project, RegistryBackend } from \"../storage/registry-interface.js\";\n\nexport interface SymlinkHealth {\n slug: string;\n linkPath: string;\n target: string | null;\n state: \"healthy\" | \"broken\" | \"orphaned\" | \"missing\";\n notes: string;\n}\n\nexport interface VaultHealthReport {\n vaultPath: string;\n healthy: SymlinkHealth[];\n broken: SymlinkHealth[];\n orphaned: SymlinkHealth[];\n missing: SymlinkHealth[];\n totalProjects: number;\n}\n\n// ---------------------------------------------------------------------------\n// Helpers\n// ---------------------------------------------------------------------------\n\n/**\n * Check the health of a sub-symlink inside a project directory.\n * Returns true if the symlink exists and its target exists on disk.\n */\nfunction subSymlinkHealthy(dir: string, name: string): boolean {\n const linkPath = join(dir, name);\n try {\n const stat = lstatSync(linkPath);\n if (!stat.isSymbolicLink()) return false;\n const target = readlinkSync(linkPath);\n return existsSync(target);\n } catch {\n return false;\n }\n}\n\n/**\n * Check if a sub-symlink exists (regardless of target validity).\n */\nfunction subSymlinkExists(dir: string, name: string): boolean {\n try {\n lstatSync(join(dir, name));\n return true;\n } catch {\n return false;\n }\n}\n\n/**\n * Find a project root Notes dir (checks canonical and .claude/Notes fallback).\n */\nfunction findNotesDir(rootPath: string): string | null {\n const canonical = join(rootPath, \"Notes\");\n if (existsSync(canonical)) return canonical;\n const alt = join(rootPath, \".claude\", \"Notes\");\n if (existsSync(alt)) return alt;\n return null;\n}\n\n// ---------------------------------------------------------------------------\n// Public API\n// ---------------------------------------------------------------------------\n\n/**\n * Check health of the Obsidian vault:\n *\n * Project entries are now real directories with sub-symlinks (`notes`, `sessions`).\n *\n * - healthy: project dir exists; at least one sub-symlink is present and resolves\n * - broken: project dir exists but all sub-symlinks have missing targets\n * - orphaned: directory entry in vault has no matching registry project\n * - missing: active project with at least one Notes source but no vault dir\n */\nexport async function checkHealth(vaultPath: string, registry: RegistryBackend): Promise<VaultHealthReport> {\n const report: VaultHealthReport = {\n vaultPath,\n healthy: [],\n broken: [],\n orphaned: [],\n missing: [],\n totalProjects: 0,\n };\n\n if (!existsSync(vaultPath)) {\n return report;\n }\n\n const projects = await registry.listProjects({ status: \"active\" });\n\n report.totalProjects = projects.length;\n\n // Build a map of slug → project for quick lookup\n const projectsBySlug = new Map<string, Project>();\n for (const p of projects) {\n projectsBySlug.set(p.slug, p);\n }\n\n // Scan vault directory\n const seenSlugs = new Set<string>();\n let entries: string[] = [];\n try {\n entries = readdirSync(vaultPath);\n } catch {\n return report;\n }\n\n for (const entry of entries) {\n // Skip internal PAI-generated dirs/files\n if (entry.startsWith(\"_\")) continue;\n\n const fullPath = join(vaultPath, entry);\n let stat;\n try {\n stat = lstatSync(fullPath);\n } catch {\n continue;\n }\n\n const project = projectsBySlug.get(entry);\n\n if (stat.isDirectory()) {\n // New-style project directory\n if (!project) {\n // Directory in vault with no matching registry project\n report.orphaned.push({\n slug: entry,\n linkPath: fullPath,\n target: null,\n state: \"orphaned\",\n notes: \"No matching active project in registry (directory)\",\n });\n continue;\n }\n\n seenSlugs.add(entry);\n\n const notesOk = subSymlinkHealthy(fullPath, \"notes\");\n const sessionsOk = subSymlinkHealthy(fullPath, \"sessions\");\n const notesExists = subSymlinkExists(fullPath, \"notes\");\n const sessionsExists = subSymlinkExists(fullPath, \"sessions\");\n\n if (notesOk || sessionsOk) {\n const parts: string[] = [];\n if (notesOk) parts.push(\"notes\");\n if (sessionsOk) parts.push(\"sessions\");\n report.healthy.push({\n slug: entry,\n linkPath: fullPath,\n target: null,\n state: \"healthy\",\n notes: `Sources: ${parts.join(\", \")}`,\n });\n } else {\n // At least one sub-link exists but none resolves\n const broken: string[] = [];\n if (notesExists) broken.push(\"notes\");\n if (sessionsExists) broken.push(\"sessions\");\n report.broken.push({\n slug: entry,\n linkPath: fullPath,\n target: null,\n state: \"broken\",\n notes: `Broken sub-symlinks: ${broken.join(\", \") || \"(empty directory)\"}`,\n });\n }\n } else if (stat.isSymbolicLink()) {\n // Legacy flat symlink — treat same as before\n let target: string | null = null;\n try {\n target = readlinkSync(fullPath);\n } catch {\n // Can't read symlink\n }\n\n const targetExists = target !== null && existsSync(target);\n\n if (!project) {\n report.orphaned.push({\n slug: entry,\n linkPath: fullPath,\n target,\n state: \"orphaned\",\n notes: \"No matching active project in registry (legacy symlink)\",\n });\n continue;\n }\n\n seenSlugs.add(entry);\n\n if (targetExists) {\n report.healthy.push({\n slug: entry,\n linkPath: fullPath,\n target,\n state: \"healthy\",\n notes: \"(legacy flat symlink — run sync to upgrade)\",\n });\n } else {\n report.broken.push({\n slug: entry,\n linkPath: fullPath,\n target,\n state: \"broken\",\n notes: `Target missing: ${target ?? \"(unknown)\"}`,\n });\n }\n }\n // Non-symlink, non-directory entries are ignored (e.g. stray files)\n }\n\n // Find active projects with Notes/ dirs that have no vault entry\n for (const project of projects) {\n if (seenSlugs.has(project.slug)) continue;\n\n const notesCanonical = join(project.root_path, \"Notes\");\n const notesAlt = join(project.root_path, \".claude\", \"Notes\");\n const hasProjectNotes = existsSync(notesCanonical) || existsSync(notesAlt);\n const hasClaudeNotes =\n project.claude_notes_dir !== null && existsSync(project.claude_notes_dir);\n\n if (hasProjectNotes || hasClaudeNotes) {\n const sources: string[] = [];\n if (hasProjectNotes) sources.push(\"notes\");\n if (hasClaudeNotes) sources.push(\"sessions\");\n report.missing.push({\n slug: project.slug,\n linkPath: join(vaultPath, project.slug),\n target: null,\n state: \"missing\",\n notes: `Has sources (${sources.join(\", \")}) but no vault entry — run 'pai obsidian sync'`,\n });\n }\n }\n\n return report;\n}\n","/**\n * pai obsidian <sub-command>\n *\n * sync — symlink project Notes/ dirs into vault, write _index.md + _topics/\n * status — health report: healthy, broken, orphaned, missing symlinks\n * open — open vault in Obsidian app (macOS)\n */\n\nimport type { Command } from \"commander\";\nimport { execFileSync } from \"node:child_process\";\nimport { ok, warn, err, dim, bold, header } from \"../utils.js\";\nimport {\n syncVault,\n generateIndex,\n generateTopicPages,\n generateMasterNotes,\n fixSessionTags,\n defaultVaultPath,\n} from \"../../obsidian/sync.js\";\nimport { checkHealth } from \"../../obsidian/status.js\";\nimport {\n loadConfig,\n expandHome,\n readMainConfigRaw,\n writeMainConfigRaw,\n} from \"../../daemon/config.js\";\nimport { getRegistryBackend } from \"../../storage/factory.js\";\nimport type { RegistryBackend } from \"../../storage/registry-interface.js\";\nimport { existsSync } from \"node:fs\";\nimport chalk from \"chalk\";\n\n// ---------------------------------------------------------------------------\n// Config helpers\n// ---------------------------------------------------------------------------\n\n/**\n * Read obsidianVaultPath from the main config (config.yaml if it exists,\n * else config.json). Falls back to defaultVaultPath() if not set.\n */\nfunction getVaultPath(override?: string): string {\n if (override) return expandHome(override);\n\n try {\n const cfg = readMainConfigRaw();\n if (typeof cfg.obsidianVaultPath === \"string\" && cfg.obsidianVaultPath) {\n return expandHome(cfg.obsidianVaultPath);\n }\n } catch {\n // Config missing or unreadable — use default\n }\n return defaultVaultPath();\n}\n\n/**\n * Read obsidianVaultPath from the main config without falling back to\n * defaultVaultPath() — undefined means \"not set\", distinct from getVaultPath's\n * \"give me something usable\". Used by `pai config migrate` to decide whether\n * the stored path still points at the pre-2026-09-19 default.\n */\nexport function getConfigObsidianVaultPathRaw(): string | undefined {\n try {\n const cfg = readMainConfigRaw();\n if (typeof cfg.obsidianVaultPath === \"string\" && cfg.obsidianVaultPath) {\n return expandHome(cfg.obsidianVaultPath);\n }\n } catch {\n // Config missing or unreadable — nothing set.\n }\n return undefined;\n}\n\n/** Persist obsidianVaultPath into the main config so future syncs use the\n * same path (config.yaml if it exists, else config.json). */\nexport function saveVaultPath(vaultPath: string): void {\n let cfg: Record<string, unknown> = {};\n try {\n cfg = readMainConfigRaw();\n } catch {\n // Start fresh if file is missing/corrupt\n }\n cfg.obsidianVaultPath = vaultPath;\n writeMainConfigRaw(cfg);\n}\n\n// ---------------------------------------------------------------------------\n// Command implementations\n// ---------------------------------------------------------------------------\n\nasync function cmdSync(\n registry: RegistryBackend,\n opts: { vault?: string; quiet?: boolean }\n): Promise<void> {\n const vaultPath = getVaultPath(opts.vault);\n const q = opts.quiet ?? false;\n\n if (!q) {\n console.log();\n console.log(header(\" PAI Obsidian Sync\"));\n console.log(dim(` Vault: ${vaultPath}`));\n console.log();\n }\n\n // --- Symlinks ---\n if (!q) process.stdout.write(\" Syncing symlinks...\");\n const stats = await syncVault(vaultPath, registry);\n if (!q) {\n process.stdout.write(\n `\\r ${ok(\"Symlinks:\")} created ${stats.created} updated ${stats.updated} removed ${stats.removed} stubs ${stats.stubbed}\\n`\n );\n if (stats.errors.length) {\n for (const e of stats.errors) {\n console.log(warn(` Warning: ${e}`));\n }\n }\n }\n\n // --- Index ---\n if (!q) process.stdout.write(\" Generating index...\");\n await generateIndex(vaultPath, registry);\n if (!q) console.log(`\\r ${ok(\"Index:\")} _index.md written `);\n\n // --- Topic pages ---\n if (!q) process.stdout.write(\" Generating topic pages...\");\n const topicCount = await generateTopicPages(vaultPath, registry);\n if (!q) console.log(`\\r ${ok(\"Topics:\")} ${topicCount} topic page(s) written `);\n\n // --- Fix session tags (remove generic #Session) ---\n if (!q) process.stdout.write(\" Fixing session tags...\");\n const tagStats = await fixSessionTags(registry);\n if (!q) {\n console.log(\n `\\r ${ok(\"Tags:\")} ${tagStats.filesModified} file(s) updated (scanned ${tagStats.filesScanned}) `\n );\n if (tagStats.errors.length) {\n for (const e of tagStats.errors) {\n console.log(warn(` Warning: ${e}`));\n }\n }\n }\n\n // --- Master notes ---\n if (!q) process.stdout.write(\" Generating master notes...\");\n const masterCount = await generateMasterNotes(vaultPath, registry);\n if (!q) console.log(`\\r ${ok(\"Masters:\")} ${masterCount} master note(s) written `);\n\n // Persist vault path so status/open can use it\n saveVaultPath(vaultPath);\n\n if (!q) {\n console.log();\n console.log(ok(` Done. Vault ready at: ${vaultPath}`));\n console.log();\n }\n}\n\nasync function cmdStatus(registry: RegistryBackend, opts: { vault?: string }): Promise<void> {\n const vaultPath = getVaultPath(opts.vault);\n const report = await checkHealth(vaultPath, registry);\n\n console.log();\n console.log(header(\" PAI Obsidian Vault Status\"));\n console.log(dim(` Vault: ${vaultPath}`));\n console.log();\n\n if (!existsSync(vaultPath)) {\n console.log(warn(\" Vault directory does not exist. Run: pai obsidian sync\"));\n console.log();\n return;\n }\n\n // Summary line\n const healthyCount = report.healthy.length;\n const brokenCount = report.broken.length;\n const orphanedCount = report.orphaned.length;\n const missingCount = report.missing.length;\n\n console.log(\n ` ${chalk.green(\"Healthy:\")} ${healthyCount} ` +\n `${chalk.red(\"Broken:\")} ${brokenCount} ` +\n `${chalk.yellow(\"Orphaned:\")} ${orphanedCount} ` +\n `${chalk.cyan(\"Missing:\")} ${missingCount}`\n );\n console.log();\n\n if (report.healthy.length && report.healthy.length <= 10) {\n console.log(bold(\" Healthy symlinks:\"));\n for (const h of report.healthy) {\n console.log(` ${chalk.green(\"✓\")} ${bold(h.slug)} ${dim(\"→ \" + (h.target ?? \"\"))}`);\n }\n console.log();\n } else if (report.healthy.length > 10) {\n console.log(bold(` ${report.healthy.length} healthy symlinks (all good)`));\n console.log();\n }\n\n if (report.broken.length) {\n console.log(err(\" Broken symlinks (target missing):\"));\n for (const b of report.broken) {\n console.log(` ${chalk.red(\"✗\")} ${bold(b.slug)} ${dim(b.notes)}`);\n }\n console.log(dim(\" Fix: pai obsidian sync\"));\n console.log();\n }\n\n if (report.orphaned.length) {\n console.log(warn(\" Orphaned symlinks (no registry project):\"));\n for (const o of report.orphaned) {\n console.log(` ${chalk.yellow(\"?\")} ${bold(o.slug)} ${dim(o.notes)}`);\n }\n console.log();\n }\n\n if (report.missing.length) {\n console.log(warn(\" Projects with Notes/ but no vault symlink:\"));\n for (const m of report.missing) {\n console.log(` ${chalk.cyan(\"→\")} ${bold(m.slug)} ${dim(m.notes)}`);\n }\n console.log();\n }\n\n if (brokenCount === 0 && orphanedCount === 0 && missingCount === 0) {\n console.log(ok(\" Vault is healthy.\"));\n console.log();\n }\n}\n\nfunction cmdOpen(opts: { vault?: string }): void {\n const vaultPath = getVaultPath(opts.vault);\n // Derive vault name from last path component (Obsidian uses folder name as vault name)\n const parts = vaultPath.split(\"/\").filter(Boolean);\n const vaultName = parts[parts.length - 1] ?? \"obsidian-vault\";\n\n if (!existsSync(vaultPath)) {\n console.error(err(`Vault not found at: ${vaultPath}`));\n console.error(dim(\" Run: pai obsidian sync\"));\n process.exitCode = 1;\n return;\n }\n\n const url = `obsidian://open?vault=${encodeURIComponent(vaultName)}`;\n console.log(dim(` Opening: ${url}`));\n try {\n execFileSync(\"open\", [url], { stdio: \"ignore\" });\n console.log(ok(` Opened vault \"${vaultName}\" in Obsidian.`));\n } catch (e) {\n console.error(err(` Failed to open Obsidian: ${e}`));\n console.error(dim(\" Ensure Obsidian is installed and the vault is registered in Obsidian.\"));\n process.exitCode = 1;\n return;\n }\n}\n\n// ---------------------------------------------------------------------------\n// Commander registration\n// ---------------------------------------------------------------------------\n\nexport function registerObsidianCommands(obsidianCmd: Command): void {\n // pai obsidian sync\n obsidianCmd\n .command(\"sync\")\n .description(\"Sync project Notes/ dirs into vault, generate _index.md and topic pages\")\n .option(\"--vault <path>\", \"Override vault path (default: ~/.pai/obsidian-vault)\")\n .option(\"--quiet\", \"Minimal output — suitable for cron/hook use\")\n .action(async (opts: { vault?: string; quiet?: boolean }) => {\n await cmdSync(await getRegistryBackend(), opts);\n });\n\n // pai obsidian status\n obsidianCmd\n .command(\"status\")\n .description(\"Show vault health: healthy, broken, orphaned, and missing symlinks\")\n .option(\"--vault <path>\", \"Override vault path\")\n .action(async (opts: { vault?: string }) => {\n await cmdStatus(await getRegistryBackend(), opts);\n });\n\n // pai obsidian open\n obsidianCmd\n .command(\"open\")\n .description(\"Open the vault in Obsidian (macOS)\")\n .option(\"--vault <path>\", \"Override vault path\")\n .action((opts: { vault?: string }) => {\n cmdOpen(opts);\n });\n}\n","/**\n * adapter-content.ts — PAI_HOME/agents and PAI_HOME/commands: the operator's\n * authored Agent/Command .md content, made PAI-owned with an adapter symlink\n * back at ~/.claude/agents and ~/.claude/commands so Claude Code still loads\n * them from its fixed harness paths (confirmed empirically: Claude Code\n * follows a directory symlink for .claude/agents/ — see docs/workers-config.md,\n * \"Where PAI lives\"). Unlike Hooks/skills (individual files symlinked by the\n * build's --sync step), these were real, un-owned files with no PAI copy to\n * symlink from — this module is what gives them one.\n */\n\nimport { existsSync, lstatSync, symlinkSync } from \"node:fs\";\nimport { homedir } from \"node:os\";\nimport { join } from \"node:path\";\nimport { paiHomePath, migratePaiDir, type MigrateDirResult } from \"./pai-home.js\";\n\nexport interface MigrateContentDirResult extends MigrateDirResult {\n symlinked: boolean;\n symlinkNote?: string;\n}\n\nfunction oldAgentsDir(): string {\n return join(homedir(), \".claude\", \"agents\");\n}\n\nfunction oldCommandsDir(): string {\n return join(homedir(), \".claude\", \"commands\");\n}\n\nfunction migrateContentDirWithSymlink(\n newDir: string,\n oldDir: string,\n opts: { dryRun?: boolean } = {}\n): MigrateContentDirResult {\n // Already a symlink (presumably to newDir, from an earlier run) — nothing to do.\n if (existsSync(oldDir) && lstatSync(oldDir).isSymbolicLink()) {\n return {\n fromDir: null,\n toDir: newDir,\n dryRun: !!opts.dryRun,\n note: `already symlinked (${oldDir} → ${newDir})`,\n symlinked: true,\n };\n }\n\n const result = migratePaiDir(newDir, [oldDir], opts);\n if (opts.dryRun || result.fromDir === null) {\n return { ...result, symlinked: false };\n }\n\n if (existsSync(oldDir)) {\n // migratePaiDir leaves the old dir in place (not renamed aside) when a\n // same-named entry already existed at newDir — never symlink over\n // whatever real content is left behind in that case.\n return {\n ...result,\n symlinked: false,\n symlinkNote: `${oldDir} still has entries after migration (${result.note ?? \"see above\"}) — not symlinked`,\n };\n }\n\n try {\n symlinkSync(newDir, oldDir);\n return { ...result, symlinked: true };\n } catch (e) {\n return { ...result, symlinked: false, symlinkNote: e instanceof Error ? e.message : String(e) };\n }\n}\n\n/** Move ~/.claude/agents/*.md into PAI_HOME/agents/, then symlink it back. */\nexport function migrateAgentsDir(opts: { dryRun?: boolean } = {}): MigrateContentDirResult {\n return migrateContentDirWithSymlink(paiHomePath(\"agents\"), oldAgentsDir(), opts);\n}\n\n/** Move ~/.claude/commands/*.md into PAI_HOME/commands/, then symlink it back. */\nexport function migrateCommandsDir(opts: { dryRun?: boolean } = {}): MigrateContentDirResult {\n return migrateContentDirWithSymlink(paiHomePath(\"commands\"), oldCommandsDir(), opts);\n}\n","/**\n * pai-files.ts — path resolution for the PAI per-user files that don't have\n * a dedicated module of their own: whisper-rules.md, advisor-mode.json, and\n * the stop-hook's session-state/ dir (kept here rather than in the hook\n * itself, since importing that hook module runs it — it calls main() at\n * import time).\n * config.json lives in daemon/config.ts, workers.yaml in\n * workers/workers-config.ts — this covers the rest of PAI_HOME.\n */\n\nimport { existsSync, statSync, renameSync } from \"node:fs\";\nimport { homedir } from \"node:os\";\nimport { join } from \"node:path\";\nimport {\n paiHomePath,\n resolvePaiFile,\n migratePaiFile,\n migratePaiDir,\n type MigrateFileResult,\n type MigrateDirResult,\n} from \"./pai-home.js\";\n\nexport { paiHomePath };\n\nfunction oldWhisperRulesPath(): string {\n return join(homedir(), \".claude\", \"whisper-rules.md\");\n}\n\nfunction oldAdvisorModePath(): string {\n return join(homedir(), \".claude\", \"advisor-mode.json\");\n}\n\nfunction oldSessionStateDir(): string {\n return join(homedir(), \".config\", \"pai\", \"session-state\");\n}\n\nexport function whisperRulesPath(): string {\n return resolvePaiFile(paiHomePath(\"whisper-rules.md\"), [oldWhisperRulesPath()], \"pai config migrate\");\n}\n\nexport function advisorModePath(): string {\n return resolvePaiFile(paiHomePath(\"advisor-mode.json\"), [oldAdvisorModePath()], \"pai config migrate\");\n}\n\nexport function migrateWhisperRules(opts: { dryRun?: boolean } = {}): MigrateFileResult {\n return migratePaiFile(paiHomePath(\"whisper-rules.md\"), [oldWhisperRulesPath()], opts);\n}\n\nexport function migrateAdvisorMode(opts: { dryRun?: boolean } = {}): MigrateFileResult {\n return migratePaiFile(paiHomePath(\"advisor-mode.json\"), [oldAdvisorModePath()], opts);\n}\n\n/** Read location: PAI_HOME's session-state/ if present, else the\n * pre-2026-09-19 ~/.config/pai/session-state (one-time stderr notice). */\nexport function sessionStateDir(): string {\n return resolvePaiFile(paiHomePath(\"session-state\"), [oldSessionStateDir()], \"pai config migrate\");\n}\n\nexport function migrateSessionStateDir(opts: { dryRun?: boolean } = {}): MigrateDirResult {\n return migratePaiDir(paiHomePath(\"session-state\"), [oldSessionStateDir()], opts);\n}\n\n// ---------------------------------------------------------------------------\n// Orphans: files under the old ~/.config/pai that no code currently reads\n// (voices.json — no `voice` reference anywhere in src). Still moved by\n// `pai config migrate` so ~/.config/pai ends up with nothing but\n// *.migrated-* markers. The orphaned legacy federation database is handled\n// by storage/paths.ts's migrateOrphanFederationDb — this file stays free of\n// database path literals so it is not a database opener.\n// ---------------------------------------------------------------------------\n\nfunction oldLastHousekeepingPath(): string {\n return join(homedir(), \".config\", \"pai\", \".last-housekeeping\");\n}\n\n/** session-stop.sh resolves this itself at runtime (it can't import TS) —\n * this is only what `pai config migrate` uses to relocate a leftover. */\nexport function migrateLastHousekeeping(opts: { dryRun?: boolean } = {}): MigrateFileResult {\n return migratePaiFile(paiHomePath(\".last-housekeeping\"), [oldLastHousekeepingPath()], opts);\n}\n\nfunction oldVoicesJsonPath(): string {\n return join(homedir(), \".config\", \"pai\", \"voices.json\");\n}\n\nexport function migrateVoicesJson(opts: { dryRun?: boolean } = {}): MigrateFileResult {\n return migratePaiFile(paiHomePath(\"voices.json\"), [oldVoicesJsonPath()], opts);\n}\n\n// ---------------------------------------------------------------------------\n// .session-stop.lock — a live mkdir-based mutex, not data. session-stop.sh\n// already creates new locks at PAI_HOME going forward (see src/hooks/\n// session-stop.sh); this only handles what's left at the old location.\n// ---------------------------------------------------------------------------\n\n// ---------------------------------------------------------------------------\n// History/, agent-sessions.json, session-routing.json, security-events.jsonl\n// — hook-written PAI state, read/written at runtime via the resolvers in\n// src/hooks/ts/lib/pai-paths.ts (ADAPTER_DIR fallback + notice, same as\n// everything here). Duplicated here rather than imported: pai-paths.ts\n// validates ADAPTER_DIR/HOOKS_DIR exist at import time (process.exit(1) if\n// not) since every hook that imports it genuinely needs that guarantee — the\n// CLI does not, and must not inherit a hook-only failure mode just to\n// migrate a file. Actively written by hooks in every live session, so unlike\n// most of this file the live move is NOT automatic — see `pai config migrate\n// --history` and its RUNNING-worker guard in src/cli/commands/config.ts.\n// ---------------------------------------------------------------------------\n\nfunction oldHistoryDir(): string {\n return join(homedir(), \".claude\", \"History\");\n}\n\nexport function migrateHistoryDir(opts: { dryRun?: boolean } = {}): MigrateDirResult {\n return migratePaiDir(paiHomePath(\"History\"), [oldHistoryDir()], opts);\n}\n\nfunction oldAgentSessionsPath(): string {\n return join(homedir(), \".claude\", \"agent-sessions.json\");\n}\n\nexport function migrateAgentSessions(opts: { dryRun?: boolean } = {}): MigrateFileResult {\n return migratePaiFile(paiHomePath(\"agent-sessions.json\"), [oldAgentSessionsPath()], opts);\n}\n\nfunction oldSessionRoutingPath(): string {\n return join(homedir(), \".claude\", \"session-routing.json\");\n}\n\nexport function migrateSessionRouting(opts: { dryRun?: boolean } = {}): MigrateFileResult {\n return migratePaiFile(paiHomePath(\"session-routing.json\"), [oldSessionRoutingPath()], opts);\n}\n\nfunction oldSecurityEventsPath(): string {\n return join(homedir(), \".claude\", \"history\", \"security\", \"security-events.jsonl\");\n}\n\nexport function migrateSecurityEvents(opts: { dryRun?: boolean } = {}): MigrateFileResult {\n return migratePaiFile(paiHomePath(\"History\", \"security\", \"security-events.jsonl\"), [oldSecurityEventsPath()], opts);\n}\n\nexport interface MigrateLockResult {\n status: \"nothing-to-migrate\" | \"skipped-in-use\" | \"moved\";\n note: string;\n}\n\n/** A lock this fresh means a session-stop.sh tail is genuinely running under\n * it right now — moving it out from under that process would break the\n * mutex it exists to provide. Matches the hook's own 600s abandonment\n * window with headroom, since this runs far less often than every Stop. */\nconst SESSION_STOP_LOCK_ABANDONED_AFTER_MS = 60 * 60 * 1000;\n\nexport function migrateSessionStopLock(opts: { dryRun?: boolean } = {}): MigrateLockResult {\n const oldPath = join(homedir(), \".config\", \"pai\", \".session-stop.lock\");\n const newPath = paiHomePath(\".session-stop.lock\");\n if (!existsSync(oldPath)) {\n return { status: \"nothing-to-migrate\", note: `nothing to migrate (${newPath})` };\n }\n\n const ageMs = Date.now() - statSync(oldPath).mtimeMs;\n if (ageMs <= SESSION_STOP_LOCK_ABANDONED_AFTER_MS) {\n return {\n status: \"skipped-in-use\",\n note: `in use (${Math.round(ageMs / 1000)}s old) — left in place; new locks go to ${newPath}`,\n };\n }\n\n const ageMin = Math.round(ageMs / 60000);\n if (opts.dryRun) {\n return { status: \"moved\", note: `would rename abandoned lock (${ageMin}m old) at ${oldPath}` };\n }\n\n const stamp = new Date().toISOString().slice(0, 10).replace(/-/g, \"\");\n renameSync(oldPath, `${oldPath}.migrated-${stamp}`);\n return { status: \"moved\", note: `abandoned lock (${ageMin}m old) renamed aside` };\n}\n","/**\n * kg-backfill.ts — Populate the temporal knowledge graph from existing\n * session notes.\n *\n * Walks `Notes/YYYY/MM/*.md` for each registered project, runs the same\n * extractor that the session-summary-worker uses on every NEW summary, and\n * stores triples in Postgres. Idempotent: a state file at\n * ~/.config/pai/kg-backfill-state.json records which note paths have been\n * processed so re-runs skip them.\n *\n * Even without the state file the operation is safe — extractAndStoreTriples\n * uses supersession logic so re-extracting a note never produces duplicates.\n */\n\nimport { existsSync, mkdirSync, readFileSync, readdirSync, statSync, writeFileSync } from \"node:fs\";\nimport { join, dirname } from \"node:path\";\nimport { homedir } from \"node:os\";\n\nimport { loadConfig, CONFIG_FILE } from \"../daemon/config.js\";\nimport { createStorageBackend, getRegistryBackend } from \"../storage/factory.js\";\nimport { extractAndStoreTriples } from \"./kg-extraction.js\";\nimport { paiHomePath, resolvePaiFile, migratePaiFile, type MigrateFileResult } from \"../config/pai-home.js\";\n\n// ---------------------------------------------------------------------------\n// State file\n// ---------------------------------------------------------------------------\n\nfunction oldStateFile(): string {\n return join(homedir(), \".config\", \"pai\", \"kg-backfill-state.json\");\n}\n\nfunction statePath(): string {\n return resolvePaiFile(paiHomePath(\"kg-backfill-state.json\"), [oldStateFile()], \"pai config migrate\");\n}\n\nexport function migrateKgBackfillState(opts: { dryRun?: boolean } = {}): MigrateFileResult {\n return migratePaiFile(paiHomePath(\"kg-backfill-state.json\"), [oldStateFile()], opts);\n}\n\ninterface BackfillState {\n /** Map of absolute note path -> ISO timestamp of when it was processed. */\n processed: Record<string, string>;\n}\n\nfunction loadState(): BackfillState {\n try {\n const file = statePath();\n if (existsSync(file)) {\n const raw = JSON.parse(readFileSync(file, \"utf-8\"));\n if (raw && typeof raw === \"object\" && raw.processed) return raw as BackfillState;\n }\n } catch { /* ignore */ }\n return { processed: {} };\n}\n\nfunction saveState(state: BackfillState): void {\n try {\n const file = paiHomePath(\"kg-backfill-state.json\");\n const dir = dirname(file);\n if (!existsSync(dir)) mkdirSync(dir, { recursive: true });\n writeFileSync(file, JSON.stringify(state, null, 2), \"utf-8\");\n } catch (e) {\n process.stderr.write(`[kg-backfill] failed to save state: ${e}\\n`);\n }\n}\n\n// ---------------------------------------------------------------------------\n// Note discovery\n// ---------------------------------------------------------------------------\n\ninterface ProjectRow {\n id: number;\n slug: string;\n root_path: string;\n}\n\ninterface ProjectNote {\n project: ProjectRow;\n notePath: string;\n}\n\n/**\n * Find all session notes under <project_root>/Notes/YYYY/MM/*.md.\n * Falls back to <project_root>/Notes/*.md (flat layout) when no year/month\n * subdirectories are present.\n */\nfunction findProjectNotes(project: ProjectRow): string[] {\n const notesRoot = join(project.root_path, \"Notes\");\n if (!existsSync(notesRoot)) return [];\n\n const found: Array<{ path: string; mtime: number }> = [];\n\n function walk(dir: string, depth: number): void {\n if (depth > 4) return;\n let entries: string[];\n try {\n entries = readdirSync(dir);\n } catch {\n return;\n }\n for (const name of entries) {\n const full = join(dir, name);\n let st;\n try {\n st = statSync(full);\n } catch {\n continue;\n }\n if (st.isDirectory()) {\n walk(full, depth + 1);\n } else if (st.isFile() && name.endsWith(\".md\") && /^\\d{3,4}/.test(name)) {\n found.push({ path: full, mtime: st.mtimeMs });\n }\n }\n }\n\n walk(notesRoot, 0);\n // Process oldest first so newer extractions can correctly supersede older facts\n found.sort((a, b) => a.mtime - b.mtime);\n return found.map((f) => f.path);\n}\n\n// ---------------------------------------------------------------------------\n// Public API\n// ---------------------------------------------------------------------------\n\nexport interface BackfillOptions {\n /** If set, only process this project's notes. */\n projectSlug?: string;\n /** Cap the total number of notes processed across all projects. */\n limit?: number;\n /** If true, do not call the extractor and do not write any state. */\n dryRun?: boolean;\n /** Progress callback fired before each note is processed. */\n onProgress?: (current: number, total: number, note: string) => void;\n}\n\nexport interface BackfillResult {\n notes_processed: number;\n triples_extracted: number;\n triples_added: number;\n triples_superseded: number;\n errors: number;\n}\n\n/**\n * Backfill the knowledge graph from existing session notes.\n *\n * Requires the Postgres backend (KG tables live there). Throws if Postgres\n * is unavailable, since this is an explicit user action.\n */\nexport async function backfillKgFromNotes(\n options: BackfillOptions = {}\n): Promise<BackfillResult> {\n const result: BackfillResult = {\n notes_processed: 0,\n triples_extracted: 0,\n triples_added: 0,\n triples_superseded: 0,\n errors: 0,\n };\n\n const config = loadConfig();\n if (config.storageBackend !== \"postgres\") {\n throw new Error(\n \"kg backfill requires the Postgres backend. \" +\n `Set \"storageBackend\": \"postgres\" in ${CONFIG_FILE}.`\n );\n }\n\n const backend = await createStorageBackend(config);\n if (!backend.supportsPostgresFeatures) {\n throw new Error(\n \"Postgres backend unavailable — fell back to SQLite. Cannot backfill KG.\"\n );\n }\n\n // -- Load projects -------------------------------------------------------\n const registry = await getRegistryBackend();\n let projects: ProjectRow[];\n if (options.projectSlug) {\n const row = await registry.getProjectBySlug(options.projectSlug);\n if (!row) {\n throw new Error(`Project not found: ${options.projectSlug}`);\n }\n projects = [row];\n } else {\n projects = await registry.listProjects({ status: \"active\", orderBy: \"slug\" });\n }\n\n // -- Build the work list -------------------------------------------------\n const state = loadState();\n const work: ProjectNote[] = [];\n for (const project of projects) {\n const notes = findProjectNotes(project);\n for (const notePath of notes) {\n if (state.processed[notePath]) continue;\n work.push({ project, notePath });\n }\n }\n\n const total = options.limit ? Math.min(options.limit, work.length) : work.length;\n\n // -- Process notes -------------------------------------------------------\n for (let i = 0; i < total; i++) {\n const { project, notePath } = work[i];\n options.onProgress?.(i + 1, total, notePath);\n\n if (options.dryRun) {\n result.notes_processed++;\n continue;\n }\n\n let noteContent: string;\n try {\n noteContent = readFileSync(notePath, \"utf-8\");\n } catch (e) {\n process.stderr.write(`[kg-backfill] read failed ${notePath}: ${e}\\n`);\n result.errors++;\n continue;\n }\n\n try {\n const stats = await extractAndStoreTriples(backend, {\n summaryText: noteContent,\n projectSlug: project.slug,\n projectId: project.id,\n sessionId: `backfill:${notePath}`,\n gitLog: \"\",\n model: \"sonnet\",\n });\n\n result.notes_processed++;\n result.triples_extracted += stats.extracted;\n result.triples_added += stats.added;\n result.triples_superseded += stats.superseded;\n\n // Mark as processed (state file is the idempotency hint; the supersession\n // logic in the extractor is the actual safety net)\n state.processed[notePath] = new Date().toISOString();\n // Save incrementally so a crash mid-run doesn't lose progress\n if ((i + 1) % 5 === 0) saveState(state);\n } catch (e) {\n process.stderr.write(`[kg-backfill] extract failed ${notePath}: ${e}\\n`);\n result.errors++;\n }\n }\n\n if (!options.dryRun) saveState(state);\n await backend.close();\n\n return result;\n}\n","/**\n * logs-migrate.ts — moving ~/.claude/logs/workers (status files, event\n * mirrors, pane registry, routing state — everything under one run's logDir)\n * into PAI_HOME. Directory-level counterpart to pai-home.ts's per-file\n * migratePaiFile, but simpler: an atomic renameSync of the whole directory\n * followed by a symlink left at the old path, so any process still holding an\n * fd open into the old directory (or writing to the old path by name) keeps\n * landing in the new one — every held fd follows the inode across a rename,\n * and the symlink covers new opens by path. Only falls back to copy + verify\n * + rename-aside (and only there does it need to refuse while a worker is\n * RUNNING against the old directory) when old and new are on different\n * volumes and the rename itself fails with EXDEV.\n */\n\nimport { existsSync, mkdirSync, cpSync, renameSync, symlinkSync, readdirSync } from \"node:fs\";\nimport { dirname, join } from \"node:path\";\nimport { loadStatuses, isChatPane, isLive } from \"./status.js\";\n\nexport class WorkerLogsMigrationError extends Error {}\n\n/** RUNNING-and-alive statuses in logDir, excluding the terminal's own interactive pane. */\nexport function activeSpawnedWorkerCount(logDir: string): number {\n return loadStatuses(logDir).filter((s) => isLive(s) && !isChatPane(s)).length;\n}\n\nfunction totalFileCount(dir: string): number {\n if (!existsSync(dir)) return 0;\n let count = 0;\n for (const entry of readdirSync(dir, { withFileTypes: true })) {\n const full = join(dir, entry.name);\n count += entry.isDirectory() ? totalFileCount(full) : 1;\n }\n return count;\n}\n\nexport interface MigrateLogsResult {\n fromPath: string | null;\n toPath: string;\n dryRun: boolean;\n filesMoved?: number;\n note?: string;\n}\n\n/**\n * Move the whole logDir tree into PAI_HOME. Normal case: renameSync the\n * directory (atomic, and every fd already open into it — including run.ts's\n * append-mode events file — follows the inode), then leave a symlink at the\n * old path so writes still addressed there land in the new directory too.\n * Falls back to recursive copy + verified file count + rename-aside (never\n * deleted) only on EXDEV, when old and new are on different volumes — that\n * path refuses while any spawned worker is RUNNING against the old\n * directory (check `pai worker ps` and retry once idle), since a copy can't\n * see writes still landing in the old tree.\n */\nexport function migrateWorkerLogs(\n oldPath: string,\n newPath: string,\n opts: { dryRun?: boolean } = {}\n): MigrateLogsResult {\n if (!existsSync(oldPath)) {\n return {\n fromPath: null,\n toPath: newPath,\n dryRun: !!opts.dryRun,\n note: \"nothing to migrate — old logDir does not exist\",\n };\n }\n if (existsSync(newPath)) {\n return {\n fromPath: oldPath,\n toPath: newPath,\n dryRun: !!opts.dryRun,\n note: \"already at new location — resolve manually, nothing changed\",\n };\n }\n\n if (opts.dryRun) return { fromPath: oldPath, toPath: newPath, dryRun: true };\n\n mkdirSync(dirname(newPath), { recursive: true });\n\n try {\n renameSync(oldPath, newPath);\n symlinkSync(newPath, oldPath);\n return { fromPath: oldPath, toPath: newPath, dryRun: false, filesMoved: totalFileCount(newPath) };\n } catch (e) {\n if ((e as NodeJS.ErrnoException).code !== \"EXDEV\") throw e;\n }\n\n const active = activeSpawnedWorkerCount(oldPath);\n if (active > 0) {\n throw new WorkerLogsMigrationError(\n `${active} worker(s) other than the interactive session are RUNNING and writing into ` +\n `${oldPath} — wait for them to finish (see \\`pai worker ps\\`), then retry`\n );\n }\n\n const stamp = new Date().toISOString().slice(0, 10).replace(/-/g, \"\");\n cpSync(oldPath, newPath, { recursive: true });\n\n const fromCount = totalFileCount(oldPath);\n const toCount = totalFileCount(newPath);\n if (fromCount !== toCount) {\n throw new WorkerLogsMigrationError(\n `${newPath}: copy produced ${toCount} files, expected ${fromCount} from ${oldPath} — ` +\n `aborting, old directory left in place`\n );\n }\n\n renameSync(oldPath, `${oldPath}.migrated-${stamp}`);\n return { fromPath: oldPath, toPath: newPath, dryRun: false, filesMoved: toCount };\n}\n","/**\n * `pai config` — the PAI_HOME namespace dir (~/.claude/pai by default) and\n * moving the per-user files that live under it: config.json, workers.yaml,\n * whisper-rules.md, advisor-mode.json, session-state/, and the remaining\n * per-subsystem state files/dirs migrated from ~/.config/pai on 2026-09-19\n * (session-scan-cache.json, queries/, summary-cooldowns.json,\n * work-queue.json, kg-backfill-state.json, voices.json, the orphaned legacy\n * federation database), plus the legacy registry database file and the two\n * content dirs that get an adapter symlink back to ~/.claude, `agents/` and\n * `commands/`. History/, agent-sessions.json, session-routing.json and\n * security-events.jsonl move only with `--history`; logs/workers only with\n * `--logs` — both are written by hooks on every live session and are\n * refused while a worker is RUNNING.\n */\n\nimport type { Command } from \"commander\";\nimport { existsSync, renameSync, statSync, readdirSync, readFileSync, cpSync } from \"node:fs\";\nimport { join } from \"node:path\";\nimport { homedir } from \"node:os\";\nimport { execSync } from \"node:child_process\";\nimport { createHash } from \"node:crypto\";\nimport {\n paiHomeDir,\n paiHomePath,\n migratePaiFile,\n PaiFileMigrationError,\n type MigrateFileResult,\n type MigrateDirResult,\n} from \"../../config/pai-home.js\";\nimport { paiConfigFilePath, paiConfigYamlFilePath, migrateConfigFile } from \"../../daemon/config.js\";\nimport { migrateMainConfigToYaml, MainConfigError } from \"../../config/main-config.js\";\nimport { voicesJsonPath, voicesYamlPath, migrateVoicesToYaml } from \"../../config/voices-config.js\";\nimport {\n listConfigOp,\n getConfigValueOp,\n setConfigValueOp,\n unsetConfigValueOp,\n formatConfigGetOutput,\n MainConfigOpsError,\n} from \"../../config/main-config-ops.js\";\nimport { workersYamlPath, relocateWorkersYaml } from \"../../workers/workers-config.js\";\nimport {\n legacyDbDisplayPaths,\n migrateFederationDbOrphan,\n migrateRegistryDbFile,\n migrateFederationDbFile,\n} from \"../../storage/sqlite/legacy-migration.js\";\nimport { STATE_FILE as schedulerStatePath, migrateSchedulerState } from \"../../tasks/poller.js\";\nimport { migrateIdentityFile } from \"../../memory/wakeup.js\";\nimport { migrateScanConfig } from \"./registry/scan.js\";\nimport { oldBackupsDir, backupsDirPath } from \"./backup.js\";\nimport { defaultVaultPath, oldDefaultVaultPath, migrateObsidianVaultDir } from \"../../obsidian/sync/generate.js\";\nimport { getConfigObsidianVaultPathRaw, saveVaultPath as saveObsidianVaultPath } from \"./obsidian.js\";\nimport { migrateAgentsDir, migrateCommandsDir, type MigrateContentDirResult } from \"../../config/adapter-content.js\";\nimport {\n whisperRulesPath,\n advisorModePath,\n sessionStateDir,\n migrateWhisperRules,\n migrateAdvisorMode,\n migrateSessionStateDir,\n migrateVoicesJson,\n migrateSessionStopLock,\n migrateLastHousekeeping,\n migrateHistoryDir,\n migrateAgentSessions,\n migrateSessionRouting,\n migrateSecurityEvents,\n} from \"../../config/pai-files.js\";\nimport { scanCacheFilePath, migrateSessionScanCache } from \"../../cli/lib/session-scan.js\";\nimport { queriesDirPath, migrateQueriesDir } from \"../../zettelkasten/query-feedback.js\";\nimport { migrateSummaryCooldowns } from \"../../daemon/session-summary-worker.js\";\nimport { migrateWorkQueue } from \"../../daemon/work-queue.js\";\nimport { migrateKgBackfillState } from \"../../memory/kg-backfill.js\";\nimport { readWorkersSection, expandHome } from \"../../workers/config.js\";\nimport { migrateWorkerLogs, WorkerLogsMigrationError, activeSpawnedWorkerCount } from \"../../workers/logs-migrate.js\";\nimport { ok, err, dim, bold } from \"../utils.js\";\n\nfunction sha256FileSync(path: string): string {\n return createHash(\"sha256\").update(readFileSync(path)).digest(\"hex\");\n}\n\nfunction walkFilesRelative(dir: string, base: string = dir): string[] {\n const out: string[] = [];\n for (const entry of readdirSync(dir)) {\n const full = join(dir, entry);\n if (statSync(full).isDirectory()) {\n out.push(...walkFilesRelative(full, base));\n } else {\n out.push(full.slice(base.length + 1));\n }\n }\n return out;\n}\n\nexport interface MigrateBackupsLegacyResult {\n fromDir: string | null;\n toDir: string;\n dryRun: boolean;\n filesVerified?: number;\n note?: string;\n}\n\n/**\n * ~/.pai/backups/ — pre-PAI_HOME `pai backup` snapshots (registry database,\n * config.json, federation database, postgres-pai.sql per timestamped run).\n * Moved into PAI_HOME/backups/legacy-pai-backups/ rather than merged into\n * PAI_HOME/backups/ directly, since that directory now holds newly created\n * backups going forward and the legacy runs are kept as a clearly-labelled\n * historical archive. Unlike migratePaiDir's plain rename-per-entry, this\n * copies the whole tree then verifies every file byte-identical (sha256)\n * before renaming the old dir aside — these snapshots include a 1.4GB\n * federation database and a 1GB pg_dump, worth the extra care.\n */\nfunction migrateBackupsLegacy(dryRun: boolean): MigrateBackupsLegacyResult {\n const oldDir = oldBackupsDir();\n const newDir = paiHomePath(\"backups\", \"legacy-pai-backups\");\n\n if (!existsSync(oldDir)) {\n return { fromDir: null, toDir: newDir, dryRun, note: \"nothing to migrate — directory does not exist yet\" };\n }\n if (existsSync(newDir)) {\n return { fromDir: oldDir, toDir: newDir, dryRun, note: \"already at new location\" };\n }\n if (dryRun) {\n return { fromDir: oldDir, toDir: newDir, dryRun: true };\n }\n\n cpSync(oldDir, newDir, { recursive: true });\n\n const relFiles = walkFilesRelative(oldDir);\n for (const rel of relFiles) {\n const a = sha256FileSync(join(oldDir, rel));\n const b = sha256FileSync(join(newDir, rel));\n if (a !== b) {\n throw new PaiFileMigrationError(\n `${join(newDir, rel)}: copy did not match ${join(oldDir, rel)} (sha256 mismatch) — aborting, old dir left in place`\n );\n }\n }\n\n const stamp = new Date().toISOString().slice(0, 10).replace(/-/g, \"\");\n renameSync(oldDir, `${oldDir}.migrated-${stamp}`);\n\n return { fromDir: oldDir, toDir: newDir, dryRun: false, filesVerified: relFiles.length };\n}\n\nexport interface MigrateObsidianVaultPathConfigResult {\n status: \"rewritten\" | \"would-rewrite\" | \"already-new\" | \"not-set\" | \"custom\";\n from?: string;\n to?: string;\n}\n\n/**\n * `pai obsidian sync --vault` used to save the then-default path\n * (~/.pai/obsidian-vault) into config.json's obsidianVaultPath explicitly.\n * migrateObsidianVaultDir above moves the directory but has no reason to\n * touch config.json, so without this every later sync (including the one\n * the session-stop hook runs) kept regenerating the vault at the old path.\n * Only rewrites when the stored value is EXACTLY the old default — a custom\n * vault path is left alone.\n */\nexport function migrateObsidianVaultPathConfig(dryRun: boolean): MigrateObsidianVaultPathConfigResult {\n const current = getConfigObsidianVaultPathRaw();\n const newPath = paiHomePath(\"obsidian-vault\");\n\n if (!current) return { status: \"not-set\" };\n if (current === newPath) return { status: \"already-new\", from: current, to: newPath };\n if (current !== oldDefaultVaultPath()) return { status: \"custom\", from: current };\n if (dryRun) return { status: \"would-rewrite\", from: current, to: newPath };\n\n saveObsidianVaultPath(newPath);\n return { status: \"rewritten\", from: current, to: newPath };\n}\n\nexport function registerConfigCommands(configCmd: Command): void {\n configCmd\n .command(\"path\")\n .description(\"Print the PAI_HOME namespace dir and each resolved per-user file\")\n .action(() => {\n console.log(bold(\"PAI_HOME: \") + paiHomeDir());\n console.log(` config.json / config.yaml: ${paiConfigFilePath()} / ${paiConfigYamlFilePath()}`);\n console.log(` workers.yaml: ${workersYamlPath()}`);\n console.log(` whisper-rules.md: ${whisperRulesPath()}`);\n console.log(` advisor-mode.json: ${advisorModePath()}`);\n console.log(` session-state/: ${sessionStateDir()}`);\n console.log(` session-scan-cache.json: ${scanCacheFilePath()}`);\n console.log(` queries/: ${queriesDirPath()}`);\n console.log(` summary-cooldowns.json, work-queue.json, kg-backfill-state.json:`);\n console.log(` all under PAI_HOME`);\n console.log(` voices.json / voices.yaml: ${voicesJsonPath()} / ${voicesYamlPath()}`);\n console.log(` federation database (orphan): under PAI_HOME`);\n console.log(` logs/workers/: ${paiHomePath(\"logs\", \"workers\")} (pass --logs to move)`);\n console.log(` History/: ${paiHomePath(\"History\")} (pass --history to move)`);\n console.log(` agent-sessions.json, session-routing.json, History/security/security-events.jsonl:`);\n console.log(` all under PAI_HOME (pass --history to move)`);\n console.log(` registry database: ${legacyDbDisplayPaths().registryDb}`);\n console.log(` federation database: ${legacyDbDisplayPaths().federationDb}`);\n console.log(` backups/: ${backupsDirPath()}`);\n console.log(` obsidian-vault/: ${defaultVaultPath()}`);\n console.log(` scheduler-state.json: ${schedulerStatePath}`);\n console.log(` identity.txt: ${paiHomePath(\"identity.txt\")} (L0 wake-up identity, optional)`);\n console.log(` registry-scan.json: ${paiHomePath(\"registry-scan.json\")}`);\n console.log(` agents/: ${paiHomePath(\"agents\")} (~/.claude/agents symlinks here after migrate)`);\n console.log(` commands/: ${paiHomePath(\"commands\")} (~/.claude/commands symlinks here after migrate)`);\n });\n\n configCmd\n .command(\"yaml\")\n .description(\n \"Convert config.json → config.yaml (and voices.json → voices.yaml), with a short\\n\" +\n \" # comment above every top-level section and any \\\"_comment\\\"/\\\"_...Note\\\" JSON-\\n\" +\n \" workaround key re-attached as a real comment. Verifies the generated YAML\\n\" +\n \" re-parses to the exact same data before writing anything; the JSON is then\\n\" +\n \" renamed to <name>.migrated-<YYYY-MM-DD> (never deleted). Every writer keeps\\n\" +\n \" working unmodified afterwards — see docs/config.md.\"\n )\n .option(\"--dry-run\", \"Print the plan without writing anything\")\n .option(\"--force\", \"Regenerate config.yaml (or voices.yaml) even if it already exists\")\n .action((opts: { dryRun?: boolean; force?: boolean }) => {\n let hadError = false;\n const run = (label: string, fn: () => ReturnType<typeof migrateMainConfigToYaml>) => {\n try {\n const r = fn();\n if (r.dryRun) {\n console.log(dim(` ${label}: would write ${r.yamlPath}`));\n } else {\n console.log(ok(` ${label}: `) + `${r.backupPath} → ${r.yamlPath}`);\n }\n } catch (e) {\n hadError = true;\n console.error(err(` ${label}: `) + (e instanceof MainConfigError ? e.message : String(e instanceof Error ? e.message : e)));\n }\n };\n run(\"config.yaml\", () => migrateMainConfigToYaml(paiConfigFilePath(), { dryRun: opts.dryRun, force: opts.force }));\n run(\"voices.yaml\", () => migrateVoicesToYaml({ dryRun: opts.dryRun, force: opts.force }));\n if (hadError) process.exitCode = 1;\n });\n\n configCmd\n .command(\"list\")\n .description(\"Print the main PAI config as YAML (secrets masked). Defaults to what the file explicitly sets.\")\n .option(\"--all\", \"Include every default value, not just what the file sets\")\n .option(\"--json\", \"Print JSON instead of YAML\")\n .action((opts: { all?: boolean; json?: boolean }) => {\n const r = listConfigOp({ all: opts.all });\n console.log(opts.json ? JSON.stringify(r.data, null, 2) : r.yaml.trimEnd());\n });\n\n configCmd\n .command(\"get <path>\")\n .description(\n \"Print one config value (dotted path, e.g. search.recencyBoostDays), masked if it looks like a secret. An object/array subtree prints as YAML; pass --json for JSON.\"\n )\n .option(\"--json\", \"Print an object/array value as JSON instead of YAML\")\n .action((path: string, opts: { json?: boolean }) => {\n try {\n const r = getConfigValueOp(path);\n if (!r.found) {\n console.error(err(`Not set: ${path}`));\n process.exitCode = 1;\n return;\n }\n console.log(formatConfigGetOutput(r.value, { json: opts.json }));\n } catch (e) {\n console.error(err((e instanceof MainConfigOpsError ? e.message : String(e instanceof Error ? e.message : e))));\n process.exitCode = 1;\n }\n });\n\n configCmd\n .command(\"set <path> <value>\")\n .description(\n \"Set one config value (dotted path). Value parsing: true/false, null, numbers,\\n\" +\n \" [...] / {...} as JSON, else a string. Creates config.yaml (from config.json,\\n\" +\n \" if any) on first use, then writes comment-preserving.\"\n )\n .option(\"--force\", \"Set an unknown top-level key, or a value of a different type than the default\")\n .action((path: string, value: string, opts: { force?: boolean }) => {\n try {\n const r = setConfigValueOp(path, value, { force: opts.force });\n if (r.yamlCreated) console.log(dim(` created ${r.yamlPath}`));\n console.log(ok(`Set ${path} = `) + (typeof r.value === \"object\" ? JSON.stringify(r.value) : String(r.value)));\n } catch (e) {\n console.error(err((e instanceof MainConfigOpsError || e instanceof MainConfigError ? e.message : String(e instanceof Error ? e.message : e))));\n process.exitCode = 1;\n }\n });\n\n configCmd\n .command(\"unset <path>\")\n .description(\"Remove one config value (dotted path), reverting it to the built-in default\")\n .action((path: string) => {\n try {\n const r = unsetConfigValueOp(path);\n console.log(r.existed ? ok(`Unset ${path}`) : dim(` ${path} was not set`));\n } catch (e) {\n console.error(err((e instanceof MainConfigOpsError ? e.message : String(e instanceof Error ? e.message : e))));\n process.exitCode = 1;\n }\n });\n\n configCmd\n .command(\"migrate\")\n .description(\n \"Move config.json, workers.yaml, whisper-rules.md, advisor-mode.json, session-state/,\\n\" +\n \"session-scan-cache.json, queries/, summary-cooldowns.json, work-queue.json,\\n\" +\n \"kg-backfill-state.json, voices.json, the orphaned legacy federation database,\\n\" +\n \"the legacy registry database, the legacy live federation database, ~/.pai/backups/,\\n\" +\n \"~/.pai/obsidian-vault/, ~/.pai/scheduler-state.json, and ~/.claude/agents/,\\n\" +\n \"~/.claude/commands/ into PAI_HOME.\\n\" +\n \"The registry and federation databases each get an extra WAL checkpoint and PRAGMA\\n\" +\n \"integrity_check (sqlite3 CLI, if present) on top of the byte-identical copy.\\n\" +\n \"backups/ is copied and sha256-verified file-by-file into\\n\" +\n \"backups/legacy-pai-backups/ rather than merged in place. agents/ and commands/ each\\n\" +\n \"get a symlink left at the old ~/.claude path once their content has moved, so\\n\" +\n \"Claude Code still finds them at its fixed harness paths.\\n\" +\n \"Each file is copied, verified byte-identical, then the old file is renamed to\\n\" +\n \"<name>.migrated-<YYYYMMDD> (never deleted); directories move their contents, then\\n\" +\n \"the emptied old directory is renamed the same way. Idempotent — entries already at\\n\" +\n \"the new location, or with nothing old to migrate, are skipped.\"\n )\n .option(\"--dry-run\", \"Print the plan without writing anything\")\n .option(\n \"--logs\",\n \"Also move ~/.claude/logs/workers into PAI_HOME: an atomic rename plus a\\n\" +\n \" symlink left at the old path, so any worker still writing there\\n\" +\n \" keeps landing in the new directory (falls back to copy+verify\\n\" +\n \" across volumes, refusing while a worker is RUNNING — see\\n\" +\n \" `pai worker ps` — only in that fallback case)\"\n )\n .option(\n \"--history\",\n \"Also move ~/.claude/History/, agent-sessions.json, session-routing.json\\n\" +\n \" and history/security/security-events.jsonl into PAI_HOME. These are\\n\" +\n \" written by hooks on EVERY session's every turn, not just spawned\\n\" +\n \" workers — refuses while any worker other than the interactive\\n\" +\n \" session is RUNNING (see `pai worker ps`), but that guard cannot see\\n\" +\n \" OTHER interactive `claude` sessions on this machine. Confirm none\\n\" +\n \" are active (`ps aux | grep claude`, `aibroker sessions list`) before\\n\" +\n \" passing this flag.\"\n )\n .action((opts: { dryRun?: boolean; logs?: boolean; history?: boolean }) => {\n const dryRun = !!opts.dryRun;\n let hadError = false;\n\n const report = (label: string, fn: () => MigrateFileResult) => {\n try {\n const r = fn();\n if (r.fromPath === null) {\n console.log(dim(` ${label}: ${r.note ?? \"nothing to migrate\"} (${r.toPath})`));\n } else if (r.dryRun) {\n console.log(dim(` ${label}: would move ${r.fromPath} → ${r.toPath}`));\n } else {\n console.log(ok(` ${label}: `) + `${r.fromPath} → ${r.toPath}` + (r.note ? dim(` (${r.note})`) : \"\"));\n }\n } catch (e) {\n hadError = true;\n console.error(err(` ${label}: `) + (e instanceof Error ? e.message : String(e)));\n }\n };\n\n const reportDir = (label: string, fn: () => MigrateDirResult) => {\n try {\n const r = fn();\n if (r.fromDir === null) {\n console.log(dim(` ${label}: ${r.note ?? \"nothing to migrate\"} (${r.toDir})`));\n } else if (r.dryRun) {\n console.log(dim(` ${label}: would move ${r.movedCount ?? 0} entrie(s) from ${r.fromDir} → ${r.toDir}`));\n } else {\n console.log(\n ok(` ${label}: `) +\n `${r.movedCount ?? 0} entrie(s) ${r.fromDir} → ${r.toDir}` +\n (r.note ? dim(` (${r.note})`) : \"\")\n );\n }\n } catch (e) {\n hadError = true;\n console.error(err(` ${label}: `) + (e instanceof Error ? e.message : String(e)));\n }\n };\n\n report(\"config.json\", () => migrateConfigFile({ dryRun }));\n report(\"workers.yaml\", () => relocateWorkersYaml({ dryRun }));\n report(\"whisper-rules.md\", () => migrateWhisperRules({ dryRun }));\n report(\"advisor-mode.json\", () => migrateAdvisorMode({ dryRun }));\n reportDir(\"session-state/\", () => migrateSessionStateDir({ dryRun }));\n report(\"session-scan-cache.json\", () => migrateSessionScanCache({ dryRun }));\n reportDir(\"queries/\", () => migrateQueriesDir({ dryRun }));\n report(\"summary-cooldowns.json\", () => migrateSummaryCooldowns({ dryRun }));\n report(\"work-queue.json\", () => migrateWorkQueue({ dryRun }));\n report(\"kg-backfill-state.json\", () => migrateKgBackfillState({ dryRun }));\n report(\"voices.json\", () => migrateVoicesJson({ dryRun }));\n report(\"federation database (orphan)\", () => migrateFederationDbOrphan(dryRun));\n report(\"registry database\", () => migrateRegistryDbFile(dryRun));\n report(\"federation database (live)\", () => migrateFederationDbFile(dryRun));\n reportDir(\"obsidian-vault/\", () => migrateObsidianVaultDir({ dryRun }));\n try {\n const r = migrateObsidianVaultPathConfig(dryRun);\n const label = \"obsidian-vault path in config.json\";\n switch (r.status) {\n case \"not-set\":\n console.log(dim(` ${label}: not set`));\n break;\n case \"already-new\":\n console.log(dim(` ${label}: already at new location`));\n break;\n case \"custom\":\n console.log(dim(` ${label}: custom, left alone`));\n break;\n case \"would-rewrite\":\n console.log(dim(` ${label}: would rewrite ${r.from} → ${r.to}`));\n break;\n case \"rewritten\":\n console.log(ok(` ${label}: `) + `${r.from} → ${r.to}`);\n break;\n }\n } catch (e) {\n hadError = true;\n console.error(err(\" obsidian-vault path in config.json: \") + (e instanceof Error ? e.message : String(e)));\n }\n report(\"scheduler-state.json\", () => migrateSchedulerState({ dryRun }));\n report(\"identity.txt\", () => migrateIdentityFile({ dryRun }));\n report(\"registry-scan.json\", () => migrateScanConfig({ dryRun }));\n\n try {\n const r = migrateBackupsLegacy(dryRun);\n if (r.fromDir === null) {\n console.log(dim(` backups/legacy-pai-backups/: ${r.note ?? \"nothing to migrate\"} (${r.toDir})`));\n } else if (r.dryRun) {\n console.log(dim(` backups/legacy-pai-backups/: would copy+verify ${r.fromDir} → ${r.toDir}`));\n } else {\n console.log(\n ok(` backups/legacy-pai-backups/: `) +\n `${r.filesVerified ?? 0} file(s) verified, ${r.fromDir} → ${r.toDir}` +\n (r.note ? dim(` (${r.note})`) : \"\")\n );\n }\n } catch (e) {\n hadError = true;\n console.error(err(` backups/legacy-pai-backups/: `) + (e instanceof Error ? e.message : String(e)));\n }\n\n const reportContentDir = (label: string, fn: () => MigrateContentDirResult) => {\n try {\n const r = fn();\n if (r.fromDir === null) {\n console.log(dim(` ${label}: ${r.note ?? \"nothing to migrate\"} (${r.toDir})`));\n } else if (r.dryRun) {\n console.log(dim(` ${label}: would move ${r.movedCount ?? 0} entrie(s) from ${r.fromDir} → ${r.toDir}, then symlink`));\n } else {\n const symlinkPart = r.symlinked\n ? \", adapter symlink created\"\n : ` — NOT symlinked (${r.symlinkNote ?? \"see note\"})`;\n console.log(\n ok(` ${label}: `) +\n `${r.movedCount ?? 0} entrie(s) ${r.fromDir} → ${r.toDir}${symlinkPart}` +\n (r.note ? dim(` (${r.note})`) : \"\")\n );\n }\n } catch (e) {\n hadError = true;\n console.error(err(` ${label}: `) + (e instanceof Error ? e.message : String(e)));\n }\n };\n reportContentDir(\"agents/\", () => migrateAgentsDir({ dryRun }));\n reportContentDir(\"commands/\", () => migrateCommandsDir({ dryRun }));\n\n if (opts.logs) {\n try {\n const { workers } = readWorkersSection();\n const oldLogDir = expandHome(workers.logDir);\n const newLogDir = paiHomePath(\"logs\", \"workers\");\n const r = migrateWorkerLogs(oldLogDir, newLogDir, { dryRun });\n if (r.fromPath === null) {\n console.log(dim(` logs/workers: ${r.note ?? \"nothing to migrate\"} (${r.toPath})`));\n } else if (r.dryRun) {\n console.log(dim(` logs/workers: would move ${r.fromPath} → ${r.toPath}`));\n } else {\n console.log(\n ok(` logs/workers: `) +\n `${r.filesMoved ?? 0} file(s) ${r.fromPath} → ${r.toPath}` +\n (r.note ? dim(` (${r.note})`) : \"\")\n );\n }\n } catch (e) {\n hadError = true;\n const msg = e instanceof WorkerLogsMigrationError ? e.message : String(e instanceof Error ? e.message : e);\n console.error(err(\" logs/workers: \") + msg);\n }\n } else {\n console.log(dim(` logs/workers: pass --logs to move it (skipped by default — actively written by running workers)`));\n }\n\n if (opts.history) {\n try {\n const { workers } = readWorkersSection();\n const active = activeSpawnedWorkerCount(expandHome(workers.logDir));\n if (active > 0 && !dryRun) {\n throw new Error(\n `${active} worker(s) other than the interactive session are RUNNING (see \\`pai worker ps\\`) — ` +\n `History/ etc. are written by every session's hooks, wait for them to finish and confirm no ` +\n `other interactive \\`claude\\` session is active, then retry`\n );\n }\n reportDir(\"History/\", () => migrateHistoryDir({ dryRun }));\n report(\"agent-sessions.json\", () => migrateAgentSessions({ dryRun }));\n report(\"session-routing.json\", () => migrateSessionRouting({ dryRun }));\n report(\"History/security/security-events.jsonl\", () => migrateSecurityEvents({ dryRun }));\n } catch (e) {\n hadError = true;\n console.error(err(\" History/ etc.: \") + (e instanceof Error ? e.message : String(e)));\n }\n } else {\n console.log(\n dim(` History/, agent-sessions.json, session-routing.json, security-events.jsonl: pass --history to move (skipped by default — actively written by hooks on every session)`)\n );\n }\n\n report(\".last-housekeeping\", () => migrateLastHousekeeping({ dryRun }));\n\n try {\n const r = migrateSessionStopLock({ dryRun });\n const line = ` .session-stop.lock: ${r.note}`;\n console.log(r.status === \"moved\" && !dryRun ? ok(line) : dim(line));\n } catch (e) {\n hadError = true;\n console.error(err(\" .session-stop.lock: \") + (e instanceof Error ? e.message : String(e)));\n }\n\n if (hadError) process.exitCode = 1;\n });\n}\n","/**\n * claude-json.ts — safe read/write for ~/.claude.json\n *\n * This file is not ours. It holds every MCP server registration plus Claude\n * Code's own per-project state — on a working machine, hundreds of kilobytes\n * that no one can reconstruct by hand. PAI only ever adds one key to it.\n *\n * The previous implementation (duplicated in mcp.ts and daemon.ts) did:\n *\n * try { return JSON.parse(read(path)); } catch { return {}; }\n *\n * followed by a full-file write. A malformed or transiently unreadable file\n * therefore became an empty object, and the next write replaced the user's\n * entire config with just PAI's entry. No error, no backup, exit code 0.\n *\n * The precondition is unlikely, which is exactly the problem: `pai setup` is\n * advertised as idempotent and safe to re-run, so it is what people run when\n * something is already broken — the state in which the file is most likely to\n * be malformed.\n */\n\nimport { existsSync, readFileSync, writeFileSync, copyFileSync, renameSync, unlinkSync } from \"node:fs\";\nimport { homedir } from \"node:os\";\nimport { join } from \"node:path\";\n\nexport const CLAUDE_JSON_PATH = join(homedir(), \".claude.json\");\nconst BACKUP_PATH = `${CLAUDE_JSON_PATH}.bak-pai`;\n\n/**\n * Read ~/.claude.json.\n *\n * A missing file is a legitimate first-run state and yields {}. Anything else\n * — malformed JSON, permissions, a half-written file — throws. Returning {}\n * for an unreadable file is what turns a read problem into data loss on the\n * next write.\n */\nexport function readClaudeJson(): Record<string, unknown> {\n if (!existsSync(CLAUDE_JSON_PATH)) return {};\n\n let raw: string;\n try {\n raw = readFileSync(CLAUDE_JSON_PATH, \"utf8\");\n } catch (e) {\n throw new Error(\n `Could not read ${CLAUDE_JSON_PATH}: ${e instanceof Error ? e.message : String(e)}\\n` +\n `Refusing to continue — writing now would replace your MCP registrations with PAI's alone.`\n );\n }\n\n try {\n return JSON.parse(raw) as Record<string, unknown>;\n } catch (e) {\n throw new Error(\n `${CLAUDE_JSON_PATH} is not valid JSON: ${e instanceof Error ? e.message : String(e)}\\n` +\n `Refusing to continue — overwriting it would destroy every MCP server registration it holds.\\n` +\n `Fix the file, or move it aside and let Claude Code recreate it, then re-run this command.`\n );\n }\n}\n\n/**\n * Write ~/.claude.json, keeping a backup and never leaving it half-written.\n *\n * Writes to a temp file and renames: rename is atomic within a filesystem, so\n * a crash mid-write leaves the original intact rather than truncated. The\n * previous direct writeFileSync could truncate the file and then fail.\n */\nexport function writeClaudeJson(data: Record<string, unknown>): void {\n const serialized = JSON.stringify(data, null, 2) + \"\\n\";\n\n if (existsSync(CLAUDE_JSON_PATH)) {\n try {\n copyFileSync(CLAUDE_JSON_PATH, BACKUP_PATH);\n } catch (e) {\n throw new Error(\n `Could not back up ${CLAUDE_JSON_PATH} to ${BACKUP_PATH}: ${e instanceof Error ? e.message : String(e)}\\n` +\n `Refusing to write without a backup.`\n );\n }\n }\n\n const tmp = `${CLAUDE_JSON_PATH}.tmp-pai-${process.pid}`;\n try {\n writeFileSync(tmp, serialized, \"utf8\");\n renameSync(tmp, CLAUDE_JSON_PATH);\n } catch (e) {\n try { if (existsSync(tmp)) unlinkSync(tmp); } catch { /* best effort */ }\n throw new Error(\n `Failed to write ${CLAUDE_JSON_PATH}: ${e instanceof Error ? e.message : String(e)}\\n` +\n `The original is unchanged${existsSync(BACKUP_PATH) ? `; a backup is at ${BACKUP_PATH}` : \"\"}.`\n );\n }\n}\n\n/** Where the pre-write backup is kept, for messages to the user. */\nexport function claudeJsonBackupPath(): string {\n return BACKUP_PATH;\n}\n","/**\n * pai mcp <sub-command>\n *\n * install — Register the PAI MCP servers in ~/.claude.json\n * status — Show whether the PAI MCP servers are registered and the binaries exist\n *\n * One server is managed: `pai` (memory/project tools, dist/daemon-mcp/index.mjs).\n * Registration is idempotent: an already-present entry is skipped\n * with a note, a missing one is added.\n */\n\nimport type { Command } from \"commander\";\nimport { existsSync } from \"node:fs\";\nimport { ok, warn, err, dim, bold } from \"../utils.js\";\nimport { readClaudeJson, writeClaudeJson } from \"../../config/claude-json.js\";\nimport { resolveFromModule } from \"../../module-paths.js\";\n\n// ---------------------------------------------------------------------------\n// Paths\n// ---------------------------------------------------------------------------\n\n/** Absolute path of a built entry under dist/ (chunk depth varies, so walk up — see module-paths). */\nfunction distEntry(entry: string): string {\n return resolveFromModule(import.meta.url, `dist/${entry}`);\n}\n\ninterface McpServerSpec {\n /** key under mcpServers in ~/.claude.json */\n name: string;\n /** dist/ relative entry path */\n entry: string;\n /** shown in install/status output */\n label: string;\n tools: string;\n}\n\nconst SERVERS: McpServerSpec[] = [\n {\n name: \"pai\",\n entry: \"daemon-mcp/index.mjs\",\n label: \"PAI MCP server\",\n tools: \"memory_search, memory_get, memory_outline, project_info, project_list, session_list, registry_search\",\n },\n];\n\n// ---------------------------------------------------------------------------\n// install\n// ---------------------------------------------------------------------------\n\nfunction cmdInstall(): void {\n const config = readClaudeJson();\n\n // Ensure mcpServers key exists\n if (typeof config.mcpServers !== \"object\" || config.mcpServers === null) {\n config.mcpServers = {};\n }\n\n const servers = config.mcpServers as Record<string, unknown>;\n let changed = false;\n\n for (const spec of SERVERS) {\n const bin = distEntry(spec.entry);\n\n if (spec.name in servers) {\n console.log(warn(`${spec.label} is already registered in ~/.claude.json as \"${spec.name}\".`));\n console.log(dim(` Entry: ${JSON.stringify(servers[spec.name])}`));\n continue;\n }\n\n servers[spec.name] = {\n command: \"node\",\n args: [bin],\n };\n changed = true;\n\n console.log(ok(`${spec.label} registered in ~/.claude.json as \"${spec.name}\".`));\n console.log(dim(` Binary: ${bin}`));\n console.log(dim(\"\"));\n console.log(dim(\" Restart Claude Code to activate the tools:\"));\n console.log(dim(` ${spec.tools}`));\n\n if (!existsSync(bin)) {\n console.log();\n console.log(warn(` Note: MCP binary not found at ${bin}`));\n console.log(dim(\" Run `bun run build` to compile it first.\"));\n }\n }\n\n if (!changed) {\n console.log(dim(\" Use `pai mcp status` to verify the configuration.\"));\n return;\n }\n\n try {\n writeClaudeJson(config);\n } catch (e) {\n console.error(err(`Failed to write ~/.claude.json: ${e}`));\n process.exitCode = 1;\n }\n}\n\n// ---------------------------------------------------------------------------\n// status\n// ---------------------------------------------------------------------------\n\nfunction cmdStatus(): void {\n const config = readClaudeJson();\n\n const servers =\n typeof config.mcpServers === \"object\" && config.mcpServers !== null\n ? (config.mcpServers as Record<string, unknown>)\n : {};\n\n console.log();\n console.log(bold(\" PAI MCP Server Status\"));\n console.log();\n\n let allReady = true;\n\n for (const spec of SERVERS) {\n const bin = distEntry(spec.entry);\n const registered = spec.name in servers;\n const binExists = existsSync(bin);\n\n if (registered) {\n console.log(ok(` ${spec.name}: registered in ~/.claude.json`));\n console.log(dim(` Config: ${JSON.stringify(servers[spec.name])}`));\n } else {\n console.log(warn(` ${spec.name}: NOT registered in ~/.claude.json`));\n console.log(dim(` Run: pai mcp install`));\n }\n\n if (binExists) {\n console.log(ok(` Binary found: ${bin}`));\n } else {\n console.log(warn(` Binary NOT found: ${bin}`));\n console.log(dim(` Run: bun run build`));\n }\n\n if (!registered || !binExists) allReady = false;\n console.log();\n }\n\n console.log(\n allReady\n ? dim(\" Status: READY — restart Claude Code to use the PAI tools\")\n : warn(\" Status: INCOMPLETE — run `pai mcp install` and/or `bun run build`\")\n );\n\n console.log();\n}\n\n// ---------------------------------------------------------------------------\n// Commander registration\n// ---------------------------------------------------------------------------\n\nexport function registerMcpCommands(mcpCmd: Command): void {\n mcpCmd\n .command(\"install\")\n .description(\n \"Register the PAI MCP server (pai) in ~/.claude.json (restart Claude Code to activate)\"\n )\n .action(() => {\n cmdInstall();\n });\n\n mcpCmd\n .command(\"status\")\n .description(\n \"Show whether the PAI MCP server (pai) is registered and the binary exists\"\n )\n .action(() => {\n cmdStatus();\n });\n}\n","/**\n * service-manager.ts — the one guard in front of every launchctl / systemctl call.\n *\n * Unit and plist files land under $HOME, but launchctl and systemctl --user act\n * on the real account's session whatever HOME says. Run under a foreign HOME\n * (a temp dir, a test, a sandbox) they rewire the live service to that HOME's\n * files. So when HOME is not the account home the files are still written and\n * the service manager is left alone.\n */\n\nimport { homedir, userInfo } from \"node:os\";\n\n/** True when it is safe to call launchctl/systemctl; otherwise prints why not and how to finish by hand. */\nexport function serviceManagerAllowed(manualCommand: string): boolean {\n if (homedir() === userInfo().homedir) return true;\n console.log(` service not (re)loaded because HOME is not the account home; as the real user run: ${manualCommand}`);\n return false;\n}\n","/**\n * daemon-status.ts — the health part of `pai daemon status`, as data.\n *\n * The daemon has always sent its work-queue depth in the status payload and the\n * command has never printed it. That is the remaining half of the July finding:\n * the queue backing up is what an outage actually looks like from outside —\n * `session-end`, `session-summary` and `registry-scan` enqueued and never\n * drained, session notes for two days never written — and it was the one number\n * that would have said so.\n *\n * Kept separate from the printing, and pure, because \"does status report the\n * outage\" is exactly the assertion that was missing when status reported\n * \"Index: idle\" through a two-day outage. A function that only writes to stdout\n * cannot be asked that question.\n */\n\nimport type { BackendOutage } from \"../../storage/outage.js\";\nimport { humanDuration } from \"../../storage/outage.js\";\n\nexport type HealthSeverity = \"error\" | \"warn\" | \"info\";\n\nexport interface HealthLine {\n severity: HealthSeverity;\n text: string;\n}\n\n/** The subset of the daemon status payload that describes storage health. */\nexport interface StorageHealthStatus {\n backendOutage?: BackendOutage | null;\n workQueue?: {\n pending?: number;\n processing?: number;\n completed?: number;\n failed?: number;\n total?: number;\n } | null;\n}\n\n/**\n * Health lines to print above the index/DB figures, worst first.\n *\n * Empty when there is nothing wrong: a healthy daemon should not grow a\n * paragraph of reassurance, or the one time it says something real gets read as\n * more of the same.\n */\nexport function formatStorageHealth(\n s: StorageHealthStatus,\n now = Date.now()\n): HealthLine[] {\n const lines: HealthLine[] = [];\n const outage = s.backendOutage ?? null;\n\n if (outage) {\n lines.push({\n severity: \"error\",\n text: `BACKEND DOWN: ${outage.backend} unreachable for ${humanDuration(now - outage.since)}`,\n });\n lines.push({\n severity: \"warn\",\n text: `${outage.attempts} attempts — ${outage.lastError}`,\n });\n }\n\n const queue = queueLine(s.workQueue, outage !== null);\n if (queue) lines.push(queue);\n\n if (outage) {\n lines.push({\n severity: \"info\",\n text: \"Indexing, session notes and the work queue are stalled until it returns.\",\n });\n }\n\n return lines;\n}\n\n/**\n * The queue depth, when it means something.\n *\n * During an outage it always means something — including a depth of zero, which\n * says the backlog is not the reason nothing is happening. Otherwise it is only\n * worth a line when work is actually sitting there: a queue that is empty\n * because everything drained is the normal case, and printing it every time\n * teaches the reader to skip the line.\n *\n * `failed` is reported whenever it is non-zero, outage or not. Items that\n * exhausted their retries are the silent-loss case this whole status block\n * exists for — they will never be retried and nothing else surfaces them.\n */\nfunction queueLine(\n q: StorageHealthStatus[\"workQueue\"],\n duringOutage: boolean\n): HealthLine | null {\n if (!q) return null;\n\n const pending = q.pending ?? 0;\n const processing = q.processing ?? 0;\n const failed = q.failed ?? 0;\n const waiting = pending + processing;\n\n if (!duringOutage && waiting === 0 && failed === 0) return null;\n\n const parts = [`${pending} pending`];\n if (processing > 0) parts.push(`${processing} processing`);\n if (failed > 0) parts.push(`${failed} failed`);\n\n return {\n // A backlog while the backend is down is a symptom, not a fault of its own;\n // failures that will never retry are a fault whether or not anything is down.\n severity: failed > 0 ? \"error\" : duringOutage || waiting > 0 ? \"warn\" : \"info\",\n text: `Work queue: ${parts.join(\", \")}`,\n };\n}\n","/**\n * pai daemon <sub-command>\n *\n * serve — Start the PAI daemon in the foreground\n * status — Query daemon status via IPC\n * restart — Restart the running daemon (launchd on macOS, systemd on Linux)\n * install — Install as a service (launchd plist / systemd user unit) + update ~/.claude.json to use the shim\n * uninstall — Remove the service + revert ~/.claude.json to direct MCP\n * logs — Tail the daemon log file\n */\n\nimport type { Command } from \"commander\";\nimport {\n existsSync,\n readFileSync,\n writeFileSync,\n unlinkSync,\n mkdirSync,\n} from \"node:fs\";\nimport { join } from \"node:path\";\nimport { homedir, userInfo } from \"node:os\";\nimport { execFileSync, spawnSync } from \"node:child_process\";\nimport { ok, warn, err, dim, bold } from \"../utils.js\";\nimport { loadConfig, paiConfigFilePath } from \"../../daemon/config.js\";\nimport { resolvedMainConfigPath } from \"../../config/main-config.js\";\nimport { PaiClient } from \"../../daemon/ipc-client.js\";\nimport { readClaudeJson, writeClaudeJson, CLAUDE_JSON_PATH } from \"../../config/claude-json.js\";\nimport { daemonLogPath } from \"../../runtime-paths.js\";\nimport { resolveFromModule } from \"../../module-paths.js\";\nimport { serviceManagerAllowed } from \"../../service-manager.js\";\nimport { formatStorageHealth, type StorageHealthStatus } from \"./daemon-status.js\";\nimport {\n loadSessionKeepaliveState,\n sessionKeepaliveLedgerSummary,\n} from \"../../daemon/session-keepalive.js\";\n\n// ---------------------------------------------------------------------------\n// Paths\n// ---------------------------------------------------------------------------\n\nconst HOME = homedir();\n// CLAUDE_JSON_PATH now imported from src/config/claude-json.ts\nconst PLIST_LABEL = \"com.pai.pai-daemon\";\nconst LAUNCH_AGENTS_DIR = join(HOME, \"Library\", \"LaunchAgents\");\nconst PLIST_PATH = join(LAUNCH_AGENTS_DIR, `${PLIST_LABEL}.plist`);\nconst DAEMON_LOG = daemonLogPath();\n\nconst SYSTEMD_UNIT_NAME = \"pai-daemon.service\";\nconst SYSTEMD_USER_DIR = join(HOME, \".config\", \"systemd\", \"user\");\nconst SYSTEMD_UNIT_PATH = join(SYSTEMD_USER_DIR, SYSTEMD_UNIT_NAME);\n\n/** Service name `daemon install` registers on this platform: systemd unit on Linux, launchd label elsewhere. */\nexport function daemonServiceName(plat: NodeJS.Platform = process.platform): string {\n return plat === \"linux\" ? SYSTEMD_UNIT_NAME : PLIST_LABEL;\n}\n\n/**\n * Resolve the absolute path to the built daemon entry point.\n * tsdown may emit this module's own chunk directly under dist/ rather than\n * under dist/cli/, so a fixed \"../daemon/index.mjs\" is not reliable — walk up\n * from this module until dist/daemon/index.mjs is found instead.\n */\nfunction getDaemonBinPath(): string {\n return resolveFromModule(import.meta.url, \"dist/daemon/index.mjs\");\n}\n\n/** Resolve the absolute path to the built MCP shim entry point. Same reasoning as getDaemonBinPath. */\nfunction getShimBinPath(): string {\n return resolveFromModule(import.meta.url, \"dist/daemon-mcp/index.mjs\");\n}\n\n// ---------------------------------------------------------------------------\n// claude.json helpers\n// ---------------------------------------------------------------------------\n\n// Moved to src/config/claude-json.ts — see the note there on why returning {}\n// from a failed read was a data-loss bug rather than a convenience.\n\n// ---------------------------------------------------------------------------\n// launchd plist generation\n// ---------------------------------------------------------------------------\n\nfunction generatePlist(daemonBin: string): string {\n return `<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/PropertyList-1.0.dtd\">\n<plist version=\"1.0\">\n<dict>\n <key>Label</key>\n <string>${PLIST_LABEL}</string>\n\n <key>ProgramArguments</key>\n <array>\n <string>/usr/local/bin/node</string>\n <string>${daemonBin}</string>\n <string>serve</string>\n </array>\n\n <key>KeepAlive</key>\n <true/>\n\n <key>ThrottleInterval</key>\n <integer>3</integer>\n\n <key>StandardOutPath</key>\n <string>${DAEMON_LOG}</string>\n\n <key>StandardErrorPath</key>\n <string>${DAEMON_LOG}</string>\n\n <key>RunAtLoad</key>\n <true/>\n\n <key>EnvironmentVariables</key>\n <dict>\n <key>PATH</key>\n <string>/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin</string>\n </dict>\n</dict>\n</plist>\n`;\n}\n\n// ---------------------------------------------------------------------------\n// systemd user unit generation (Linux)\n// ---------------------------------------------------------------------------\n\nexport function generateSystemdUnit(daemonBin: string, nodeBin: string): string {\n return `[Unit]\nDescription=PAI Daemon\nAfter=network.target\n\n[Service]\nExecStart=${nodeBin} ${daemonBin} serve\nRestart=on-failure\n\n[Install]\nWantedBy=default.target\n`;\n}\n\ntype LoginctlRunner = (args: string[]) => { status: number | null; stdout: string };\n\nconst runLoginctl: LoginctlRunner = (args) => {\n const r = spawnSync(\"loginctl\", args, { encoding: \"utf8\" });\n return { status: r.status, stdout: r.stdout ?? \"\" };\n};\n\n/**\n * Turns on systemd linger for the user (so the user unit survives logout) without sudo\n * or a prompt; prints the sudo command only when the system refuses. Linux only.\n */\nexport function ensureLinger(\n plat: NodeJS.Platform = process.platform,\n username: string = userInfo().username,\n loginctl: LoginctlRunner = runLoginctl,\n log: (s: string) => void = console.log,\n): void {\n if (plat !== \"linux\") return;\n const linger = () => {\n const r = loginctl([\"show-user\", username, \"-p\", \"Linger\"]);\n return r.status === 0 ? r.stdout.trim() : null;\n };\n const before = linger();\n if (before === null || before === \"Linger=yes\") return;\n loginctl([\"--no-ask-password\", \"enable-linger\", username]);\n if (linger() === \"Linger=yes\") {\n log(ok(\" linger: on (daemon survives logout)\"));\n return;\n }\n log(warn(` Linger is off for ${username} — the daemon stops when you log out.`));\n log(dim(` Enable it: sudo loginctl enable-linger ${username}`));\n}\n\n// ---------------------------------------------------------------------------\n// Command implementations\n// ---------------------------------------------------------------------------\n\nasync function cmdStatus(): Promise<void> {\n const config = loadConfig();\n const client = new PaiClient(config.socketPath);\n\n try {\n const status = await client.status();\n const s = status as Record<string, unknown>;\n\n console.log();\n console.log(bold(\" PAI Daemon Status\"));\n console.log();\n console.log(ok(` Daemon running`));\n console.log(dim(` Uptime: ${s[\"uptime\"]}s`));\n console.log(dim(` Socket: ${s[\"socketPath\"]}`));\n\n // Lead with the outage and the backlog. Everything below them — index\n // state, totals — describes a system that is not moving, and printing those\n // first is how \"Index: idle\" came to mean \"all is well\" during a two-day\n // outage.\n const health = formatStorageHealth(s as StorageHealthStatus);\n if (health.length > 0) {\n console.log();\n for (const line of health) {\n const paint = line.severity === \"error\" ? err : line.severity === \"warn\" ? warn : dim;\n console.log(paint(` ${line.text}`));\n }\n console.log();\n }\n console.log(\n dim(\n ` Index: ${s[\"indexInProgress\"] ? \"in progress\" : \"idle\"} (interval: ${s[\"indexIntervalSecs\"]}s)`\n )\n );\n if (s[\"lastIndexTime\"]) {\n console.log(dim(` Last index: ${s[\"lastIndexTime\"]}`));\n }\n if (s[\"db\"]) {\n const db = s[\"db\"] as Record<string, unknown>;\n console.log(\n dim(\n ` DB: ${db[\"projects\"]} projects, ${db[\"files\"]} files, ${db[\"chunks\"]} chunks`\n )\n );\n }\n console.log();\n } catch (e) {\n const msg = e instanceof Error ? e.message : String(e);\n console.log();\n console.log(warn(\" PAI Daemon Status\"));\n console.log();\n console.log(warn(` Daemon not running: ${msg}`));\n console.log(dim(\" Start with: pai daemon serve\"));\n console.log(dim(\" Or install as service: pai daemon install\"));\n console.log();\n process.exitCode = 1;\n }\n}\n\nfunction cmdRestart(): void {\n if (!serviceManagerAllowed(\"pai daemon restart\")) return;\n if (process.platform === \"linux\") {\n const result = spawnSync(\"systemctl\", [\"--user\", \"restart\", SYSTEMD_UNIT_NAME], {\n encoding: \"utf8\",\n });\n if (result.status === 0) {\n console.log(ok(\"Restarted via systemd.\"));\n } else {\n console.error(err(`systemctl restart failed: ${(result.stderr || \"\").trim()}`));\n process.exitCode = 1;\n }\n return;\n }\n\n if (process.platform !== \"darwin\") {\n console.error(\n err(`Unsupported platform: ${process.platform}. Daemon restart supports macOS (launchd) and Linux (systemd user) only.`)\n );\n process.exitCode = 1;\n return;\n }\n\n // Find and signal the running daemon\n try {\n const result = spawnSync(\"pgrep\", [\"-f\", \"pai-daemon.*serve\"], {\n encoding: \"utf8\",\n });\n\n if (result.status !== 0 || !result.stdout.trim()) {\n console.log(warn(\"No running pai-daemon process found.\"));\n\n // If launchd is managing it, kick it via launchctl\n const unloadResult = spawnSync(\n \"launchctl\",\n [\"kickstart\", \"-k\", `gui/${process.getuid?.() ?? 501}/${PLIST_LABEL}`],\n { encoding: \"utf8\" }\n );\n if (unloadResult.status === 0) {\n console.log(ok(\"Sent kickstart to launchd.\"));\n } else {\n console.log(dim(\"Not managed by launchd either. Run: pai daemon serve\"));\n }\n return;\n }\n\n const pids = result.stdout\n .trim()\n .split(\"\\n\")\n .map((p) => p.trim());\n for (const pid of pids) {\n try {\n process.kill(parseInt(pid, 10), \"SIGTERM\");\n console.log(ok(`Sent SIGTERM to pid ${pid}.`));\n } catch {\n console.log(warn(`Could not signal pid ${pid}.`));\n }\n }\n console.log(dim(\"launchd will restart the daemon automatically.\"));\n } catch (e) {\n const msg = e instanceof Error ? e.message : String(e);\n console.error(err(`restart error: ${msg}`));\n process.exitCode = 1;\n }\n}\n\nfunction installServiceDarwin(daemonBin: string): boolean {\n // 1. Write launchd plist\n if (!existsSync(LAUNCH_AGENTS_DIR)) {\n mkdirSync(LAUNCH_AGENTS_DIR, { recursive: true });\n }\n\n const plistContent = generatePlist(daemonBin);\n try {\n writeFileSync(PLIST_PATH, plistContent, \"utf8\");\n console.log(ok(` Wrote launchd plist: ${PLIST_PATH}`));\n } catch (e) {\n console.error(err(` Failed to write plist: ${e}`));\n process.exitCode = 1;\n return false;\n }\n\n // 2. Load the plist (unload first in case it was already there)\n if (!serviceManagerAllowed(`launchctl load ${PLIST_PATH}`)) return true;\n try {\n spawnSync(\"launchctl\", [\"unload\", PLIST_PATH], { encoding: \"utf8\" });\n const loadResult = spawnSync(\"launchctl\", [\"load\", PLIST_PATH], {\n encoding: \"utf8\",\n });\n if (loadResult.status === 0) {\n console.log(ok(\" Loaded plist with launchctl.\"));\n } else {\n console.log(warn(` launchctl load: ${loadResult.stderr?.trim() ?? \"unknown error\"}`));\n }\n } catch {\n console.log(warn(\" Could not run launchctl. Load manually:\"));\n console.log(dim(` launchctl load ${PLIST_PATH}`));\n }\n return true;\n}\n\nfunction installServiceLinux(daemonBin: string): boolean {\n // 1. Write systemd user unit\n if (!existsSync(SYSTEMD_USER_DIR)) {\n mkdirSync(SYSTEMD_USER_DIR, { recursive: true });\n }\n\n const unitContent = generateSystemdUnit(daemonBin, process.execPath);\n try {\n writeFileSync(SYSTEMD_UNIT_PATH, unitContent, \"utf8\");\n console.log(ok(` Wrote systemd unit: ${SYSTEMD_UNIT_PATH}`));\n } catch (e) {\n console.error(err(` Failed to write systemd unit: ${e}`));\n process.exitCode = 1;\n return false;\n }\n\n // 2. daemon-reload + enable --now\n if (!serviceManagerAllowed(`systemctl --user daemon-reload && systemctl --user enable --now ${SYSTEMD_UNIT_NAME}`)) return true;\n spawnSync(\"systemctl\", [\"--user\", \"daemon-reload\"], { encoding: \"utf8\" });\n const enableResult = spawnSync(\"systemctl\", [\"--user\", \"enable\", \"--now\", SYSTEMD_UNIT_NAME], {\n encoding: \"utf8\",\n });\n if (enableResult.status === 0) {\n console.log(ok(\" Enabled and started via systemd.\"));\n } else {\n console.log(warn(` systemctl enable --now: ${(enableResult.stderr || \"\").trim()}`));\n }\n\n ensureLinger();\n return true;\n}\n\nfunction cmdInstall(): void {\n const daemonBin = getDaemonBinPath();\n const shimBin = getShimBinPath();\n\n console.log();\n console.log(bold(\" PAI Daemon Install\"));\n console.log();\n\n if (process.platform === \"darwin\") {\n if (!installServiceDarwin(daemonBin)) return;\n } else if (process.platform === \"linux\") {\n if (!installServiceLinux(daemonBin)) return;\n } else {\n console.error(\n err(` Unsupported platform: ${process.platform}. Daemon install supports macOS (launchd) and Linux (systemd user) only.`)\n );\n process.exitCode = 1;\n return;\n }\n\n // 3. Update ~/.claude.json to use the shim\n const config = readClaudeJson();\n\n if (typeof config.mcpServers !== \"object\" || config.mcpServers === null) {\n config.mcpServers = {};\n }\n\n const servers = config.mcpServers as Record<string, unknown>;\n\n // Check if already pointing to the shim\n const existing = servers[\"pai\"] as Record<string, unknown> | undefined;\n const existingArgs = existing?.args as string[] | undefined;\n const alreadyShim = existingArgs?.includes(shimBin);\n\n if (alreadyShim) {\n console.log(ok(\" ~/.claude.json already points to the shim.\"));\n } else {\n // Back up the existing entry if any\n if (existing) {\n servers[\"pai-legacy\"] = existing;\n console.log(dim(\" Backed up existing 'pai' entry as 'pai-legacy'.\"));\n }\n\n servers[\"pai\"] = {\n command: \"node\",\n args: [shimBin],\n };\n\n try {\n writeClaudeJson(config);\n console.log(ok(\" Updated ~/.claude.json to use daemon shim.\"));\n } catch (e) {\n console.error(err(` Failed to write ~/.claude.json: ${e}`));\n process.exitCode = 1;\n return;\n }\n }\n\n // 4. Verify binaries exist\n console.log();\n if (!existsSync(daemonBin)) {\n console.log(warn(` Daemon binary not found: ${daemonBin}`));\n console.log(dim(\" Run: bun run build\"));\n } else {\n console.log(ok(` Daemon binary: ${daemonBin}`));\n }\n\n if (!existsSync(shimBin)) {\n console.log(warn(` Shim binary not found: ${shimBin}`));\n console.log(dim(\" Run: bun run build\"));\n } else {\n console.log(ok(` Shim binary: ${shimBin}`));\n }\n\n console.log();\n console.log(dim(\" Restart Claude Code to activate the daemon-backed PAI tools.\"));\n console.log();\n}\n\nfunction uninstallServiceDarwin(): void {\n if (existsSync(PLIST_PATH)) {\n if (serviceManagerAllowed(`launchctl unload ${PLIST_PATH}`)) {\n try {\n spawnSync(\"launchctl\", [\"unload\", PLIST_PATH], { encoding: \"utf8\" });\n console.log(ok(\" Unloaded launchd plist.\"));\n } catch {\n console.log(warn(\" Could not unload plist via launchctl.\"));\n }\n }\n try {\n unlinkSync(PLIST_PATH);\n console.log(ok(` Removed plist: ${PLIST_PATH}`));\n } catch (e) {\n console.log(warn(` Could not remove plist: ${e}`));\n }\n } else {\n console.log(dim(\" No launchd plist found.\"));\n }\n}\n\nfunction uninstallServiceLinux(): void {\n if (existsSync(SYSTEMD_UNIT_PATH)) {\n const managed = serviceManagerAllowed(`systemctl --user disable --now ${SYSTEMD_UNIT_NAME}`);\n if (managed) {\n spawnSync(\"systemctl\", [\"--user\", \"disable\", \"--now\", SYSTEMD_UNIT_NAME], {\n encoding: \"utf8\",\n });\n console.log(ok(\" Disabled and stopped via systemd.\"));\n }\n try {\n unlinkSync(SYSTEMD_UNIT_PATH);\n console.log(ok(` Removed unit: ${SYSTEMD_UNIT_PATH}`));\n } catch (e) {\n console.log(warn(` Could not remove unit: ${e}`));\n }\n if (managed) spawnSync(\"systemctl\", [\"--user\", \"daemon-reload\"], { encoding: \"utf8\" });\n } else {\n console.log(dim(\" No systemd unit found.\"));\n }\n}\n\nfunction cmdUninstall(): void {\n console.log();\n console.log(bold(\" PAI Daemon Uninstall\"));\n console.log();\n\n if (process.platform === \"darwin\") {\n uninstallServiceDarwin();\n } else if (process.platform === \"linux\") {\n uninstallServiceLinux();\n } else {\n console.error(\n err(` Unsupported platform: ${process.platform}. Daemon uninstall supports macOS (launchd) and Linux (systemd user) only.`)\n );\n process.exitCode = 1;\n return;\n }\n\n // Revert ~/.claude.json to legacy direct MCP\n const config = readClaudeJson();\n const servers =\n typeof config.mcpServers === \"object\" && config.mcpServers !== null\n ? (config.mcpServers as Record<string, unknown>)\n : {};\n\n const legacy = servers[\"pai-legacy\"];\n if (legacy) {\n servers[\"pai\"] = legacy;\n delete servers[\"pai-legacy\"];\n try {\n writeClaudeJson(config);\n console.log(ok(\" Reverted ~/.claude.json to legacy direct MCP.\"));\n } catch (e) {\n console.log(warn(` Could not update ~/.claude.json: ${e}`));\n }\n } else {\n console.log(dim(\" No legacy PAI entry found in ~/.claude.json.\"));\n }\n\n console.log();\n console.log(dim(\" Restart Claude Code to deactivate daemon-backed tools.\"));\n console.log();\n}\n\nasync function cmdMigrate(connectionString?: string): Promise<void> {\n console.log();\n console.log(bold(\" PAI Daemon Migrate\"));\n console.log();\n console.log(dim(\" Running SQLite → PostgreSQL migration...\"));\n console.log();\n\n // The migration script is at docker/migrate-sqlite.ts, a package-root\n // sibling of dist/ that is not bundled — walk up to find it regardless of\n // which dist/ chunk this module ended up in.\n const migrationScript = resolveFromModule(import.meta.url, \"docker/migrate-sqlite.ts\");\n const { spawnSync: spawn } = await import(\"node:child_process\");\n\n // Use npx tsx to run the TypeScript migration script (bun doesn't support better-sqlite3)\n const spawnArgs = [\"tsx\", migrationScript];\n if (connectionString) {\n spawnArgs.push(\"--connection-string\", connectionString);\n }\n\n const result = spawn(\"npx\", spawnArgs, { stdio: \"inherit\", encoding: \"utf8\" });\n\n if (result.status !== 0) {\n console.error(err(\" Migration failed. Check output above.\"));\n process.exitCode = result.status ?? 1;\n }\n}\n\nfunction cmdKeepalive(): void {\n const config = loadConfig().sessions.cacheKeepalive;\n\n console.log();\n console.log(bold(\" Session cache keepalive\"));\n console.log();\n console.log(config.enabled ? ok(` enabled`) : warn(` disabled`));\n console.log(\n dim(\n ` idleMinutes: ${config.idleMinutes} maxBeats: ${config.maxBeats} ` +\n `activeHours: ${config.activeHours} minContextTokens: ${config.minContextTokens} prompt: \"${config.prompt}\"`\n )\n );\n if (!config.enabled) {\n console.log();\n console.log(dim(` Enable it in ${resolvedMainConfigPath(paiConfigFilePath())}: sessions.cacheKeepalive.enabled: true`));\n console.log(dim(` pai config set sessions.cacheKeepalive.enabled true`));\n }\n\n const state = loadSessionKeepaliveState();\n const ids = Object.keys(state);\n console.log();\n if (ids.length === 0) {\n console.log(dim(\" No per-session beat counters recorded yet.\"));\n } else {\n console.log(bold(\" Per-session beat counters\"));\n for (const id of ids) {\n const e = state[id];\n console.log(dim(` ${id}: beats=${e.beats} lastBeatAt=${e.lastBeatAt ?? \"never\"}`));\n }\n }\n\n const summary = sessionKeepaliveLedgerSummary();\n console.log();\n console.log(bold(` Ledger: ${summary.sent} sent, ${summary.skipped} skipped`));\n for (const line of summary.lastLines) {\n console.log(dim(` ${line}`));\n }\n console.log();\n}\n\nfunction cmdLogs(opts: { lines?: string; follow?: boolean }): void {\n const lines = opts.lines ?? \"50\";\n\n if (!existsSync(DAEMON_LOG)) {\n console.log(warn(`No daemon log found at ${DAEMON_LOG}.`));\n console.log(dim(\"The daemon may not have run yet.\"));\n return;\n }\n\n if (opts.follow) {\n // exec stays running\n try {\n execFileSync(\"tail\", [\"-f\", \"-n\", lines, DAEMON_LOG], { stdio: \"inherit\" });\n } catch {\n // User pressed Ctrl+C — that's fine\n }\n } else {\n try {\n execFileSync(\"tail\", [\"-n\", lines, DAEMON_LOG], { stdio: \"inherit\" });\n } catch (e) {\n const msg = e instanceof Error ? e.message : String(e);\n console.error(err(`Could not read log: ${msg}`));\n process.exitCode = 1;\n }\n }\n}\n\n// ---------------------------------------------------------------------------\n// Commander registration\n// ---------------------------------------------------------------------------\n\nexport function registerDaemonCommands(daemonCmd: Command): void {\n daemonCmd\n .command(\"serve\")\n .description(\"Start the PAI daemon in the foreground\")\n .action(async () => {\n const { serve } = await import(\"../../daemon/daemon.js\");\n const { loadConfig: lc, ensureConfigDir } = await import(\n \"../../daemon/config.js\"\n );\n ensureConfigDir();\n const config = lc();\n await serve(config);\n });\n\n daemonCmd\n .command(\"migrate\")\n .description(\"Migrate federation data from SQLite to PostgreSQL\")\n .option(\n \"--connection-string <url>\",\n \"Postgres connection string (default: from config or postgresql://pai:pai@localhost:5432/pai)\"\n )\n .action(async (opts: { connectionString?: string }) => {\n await cmdMigrate(opts.connectionString);\n });\n\n daemonCmd\n .command(\"status\")\n .description(\"Query the running daemon status\")\n .action(async () => {\n await cmdStatus();\n });\n\n daemonCmd\n .command(\"restart\")\n .description(\"Restart the running daemon (launchd on macOS, systemd on Linux)\")\n .action(() => {\n cmdRestart();\n });\n\n daemonCmd\n .command(\"install\")\n .description(\n \"Install daemon as a service (launchd/systemd) and update ~/.claude.json to use the shim\"\n )\n .action(() => {\n cmdInstall();\n });\n\n daemonCmd\n .command(\"uninstall\")\n .description(\"Remove the daemon service (launchd/systemd) and revert to direct MCP\")\n .action(() => {\n cmdUninstall();\n });\n\n daemonCmd\n .command(\"keepalive\")\n .description(\"Show interactive-session cache keepalive config, per-session beat counters, and recent ledger lines\")\n .action(() => {\n cmdKeepalive();\n });\n\n daemonCmd\n .command(\"logs\")\n .description(`Tail the daemon log (${DAEMON_LOG})`)\n .option(\"-n, --lines <n>\", \"Number of lines to show\", \"50\")\n .option(\"-f, --follow\", \"Follow log output (like tail -f)\")\n .action((opts: { lines?: string; follow?: boolean }) => {\n cmdLogs(opts);\n });\n}\n","/**\n * pai hooks-db <sub-command>\n *\n * Backing implementation for the session-stop and pre-compact shell hooks\n * (src/hooks/session-stop.sh, src/hooks/pre-compact.sh). Each hook used to\n * shell out to the `sqlite3` CLI directly against the registry database; this\n * subcommand replaces those calls with one `pai` process that goes through\n * RegistryBackend, so hooks work against either SQLite or Postgres\n * (docs/design/postgres-only.md §6/§8, unit 8).\n *\n * Never throws to the caller's shell: exits 0 regardless of outcome — the\n * hooks already run detached/best-effort and must never block a session.\n *\n * pai hooks-db session-stop <projectSlug>\n * pai hooks-db pre-compact <projectSlug>\n */\n\nimport type { Command } from \"commander\";\nimport { getRegistryBackend } from \"../../storage/factory.js\";\nimport type { RegistryBackend, Session } from \"../../storage/registry-interface.js\";\n\n// ---------------------------------------------------------------------------\n// Shared lookup\n// ---------------------------------------------------------------------------\n\n/** Latest session for the project whose status is one of `statuses`, newest created_at first — mirrors the old `ORDER BY created_at DESC LIMIT 1` SQL. */\nasync function findLatestSessionByStatus(\n backend: RegistryBackend,\n projectId: number,\n statuses: Session[\"status\"][]\n): Promise<Session | null> {\n const sessions = await backend.listSessionsForProject(projectId, { orderBy: \"created_desc\" });\n return sessions.find((s) => statuses.includes(s.status)) ?? null;\n}\n\n// ---------------------------------------------------------------------------\n// session-stop: mark the latest open/compacted session completed\n// ---------------------------------------------------------------------------\n\nexport async function runSessionStopHook(backend: RegistryBackend, projectSlug: string): Promise<void> {\n const project = await backend.getProjectBySlug(projectSlug);\n if (!project) return;\n\n const session = await findLatestSessionByStatus(backend, project.id, [\"open\", \"compacted\"]);\n if (!session) return;\n\n await backend.updateSessionStatus(session.id, \"completed\", { closedAt: Date.now() });\n}\n\n// ---------------------------------------------------------------------------\n// pre-compact: mark the latest open session compacted, log the event\n// ---------------------------------------------------------------------------\n\nexport async function runPreCompactHook(backend: RegistryBackend, projectSlug: string): Promise<void> {\n const project = await backend.getProjectBySlug(projectSlug);\n if (!project) return;\n\n const session = await findLatestSessionByStatus(backend, project.id, [\"open\"]);\n if (session) {\n await backend.updateSessionStatus(session.id, \"compacted\");\n }\n\n await backend.appendCompactionLog({\n projectId: project.id,\n sessionId: session ? session.id : null,\n trigger: \"precompact\",\n filesWritten: \"\",\n tokenCount: null,\n createdAt: Date.now(),\n });\n}\n\n// ---------------------------------------------------------------------------\n// Commander registration\n// ---------------------------------------------------------------------------\n\nexport function registerHooksDbCommands(hooksDbCmd: Command): void {\n hooksDbCmd\n .command(\"session-stop <projectSlug>\")\n .description(\"Internal: session-stop hook registry update (called by src/hooks/session-stop.sh)\")\n .action(async (projectSlug: string) => {\n try {\n const backend = await getRegistryBackend();\n await runSessionStopHook(backend, projectSlug);\n } catch {\n // never surface a failure to the calling hook script\n }\n });\n\n hooksDbCmd\n .command(\"pre-compact <projectSlug>\")\n .description(\"Internal: pre-compact hook registry update (called by src/hooks/pre-compact.sh)\")\n .action(async (projectSlug: string) => {\n try {\n const backend = await getRegistryBackend();\n await runPreCompactHook(backend, projectSlug);\n } catch {\n // never surface a failure to the calling hook script\n }\n });\n}\n","/**\n * pai restore [path] — restore from a backup created by `pai backup`.\n *\n * If no path is given, lists available backups and restores the latest one\n * after prompting for confirmation.\n *\n * Restores:\n * registry database — SQLite registry database\n * config.json — PAI daemon config\n * postgres-pai.sql — Postgres dump (piped into psql via docker exec)\n * federation database — Legacy SQLite federation DB (if present)\n */\n\nimport type { Command } from \"commander\";\nimport {\n existsSync,\n readdirSync,\n statSync,\n copyFileSync,\n mkdirSync,\n readFileSync,\n chmodSync,\n} from \"node:fs\";\nimport { join, dirname } from \"node:path\";\nimport { homedir } from \"node:os\";\nimport { execFileSync, spawnSync } from \"node:child_process\";\nimport { createInterface } from \"node:readline\";\nimport { ok, warn, err, dim, bold } from \"../utils.js\";\nimport { loadConfig, paiConfigFilePath } from \"../../daemon/config.js\";\nimport { inspectStorageBackup, restoreStorageFiles } from \"../../storage/backup.js\";\nimport { backupsDirPath } from \"./backup.js\";\n\n// ---------------------------------------------------------------------------\n// Paths\n// ---------------------------------------------------------------------------\n\nconst HOME = homedir();\nconst CONFIG_FILE = paiConfigFilePath();\nconst BACKUPS_DIR = backupsDirPath();\nconst DOCKER_CONTAINER = \"pai-pgvector\";\nconst PG_DATABASE = \"pai\";\nconst PG_USER = \"pai\";\n\n// ---------------------------------------------------------------------------\n// Helpers\n// ---------------------------------------------------------------------------\n\nfunction confirm(question: string): Promise<boolean> {\n return new Promise((resolve) => {\n const rl = createInterface({ input: process.stdin, output: process.stdout });\n rl.question(`${question} [y/N] `, (answer) => {\n rl.close();\n resolve(answer.trim().toLowerCase() === \"y\");\n });\n });\n}\n\nfunction listBackups(): string[] {\n if (!existsSync(BACKUPS_DIR)) return [];\n return readdirSync(BACKUPS_DIR)\n .filter((name) => {\n const full = join(BACKUPS_DIR, name);\n return statSync(full).isDirectory() && /^\\d{4}-\\d{2}-\\d{2}-\\d{6}$/.test(name);\n })\n .sort()\n .reverse(); // newest first\n}\n\nfunction formatBackupDate(name: string): string {\n // YYYY-MM-DD-HHmmss → \"YYYY-MM-DD HH:mm:ss\"\n const m = name.match(/^(\\d{4}-\\d{2}-\\d{2})-(\\d{2})(\\d{2})(\\d{2})$/);\n if (!m) return name;\n return `${m[1]} ${m[2]}:${m[3]}:${m[4]}`;\n}\n\nfunction backupContents(backupDir: string): string[] {\n try {\n return readdirSync(backupDir);\n } catch {\n return [];\n }\n}\n\n// ---------------------------------------------------------------------------\n// Registration\n// ---------------------------------------------------------------------------\n\nexport function registerRestoreCommands(program: Command): void {\n program\n .command(\"restore [backup-path]\")\n .description(\"Restore from a backup directory (created by pai backup)\")\n .option(\"--no-postgres\", \"Skip restoring the Postgres database\")\n .option(\"--yes\", \"Skip confirmation prompt (non-interactive)\")\n .action(async (backupPath: string | undefined, opts: { postgres: boolean; yes: boolean }) => {\n\n // ------------------------------------------------------------------\n // 1. Determine backup directory\n // ------------------------------------------------------------------\n\n let resolvedDir: string;\n\n if (backupPath) {\n resolvedDir = backupPath.startsWith(\"~\")\n ? backupPath.replace(/^~/, HOME)\n : backupPath;\n\n if (!existsSync(resolvedDir)) {\n console.error(err(`Backup directory not found: ${resolvedDir}`));\n process.exitCode = 1;\n return;\n }\n } else {\n // No path given — pick from available backups\n const backups = listBackups();\n if (backups.length === 0) {\n console.error(err(`No backups found in ${BACKUPS_DIR}`));\n console.log(dim(\" Run 'pai backup' to create one.\"));\n process.exitCode = 1;\n return;\n }\n\n console.log(`\\n${bold(\"Available backups\")} (newest first):\\n`);\n backups.forEach((name, i) => {\n const dir = join(BACKUPS_DIR, name);\n const contents = backupContents(dir).join(\", \");\n const marker = i === 0 ? ok(\" ← latest\") : \"\";\n console.log(` ${bold(String(i + 1).padStart(2))}. ${formatBackupDate(name)}${marker}`);\n console.log(` ${dim(dir)}`);\n console.log(` ${dim(\"Contents:\")} ${contents}`);\n });\n\n console.log();\n resolvedDir = join(BACKUPS_DIR, backups[0]);\n console.log(dim(`Using latest backup: ${resolvedDir}\\n`));\n }\n\n // ------------------------------------------------------------------\n // 2. Inventory what's in the backup\n // ------------------------------------------------------------------\n\n const inventory = inspectStorageBackup(resolvedDir);\n const hasConfig = existsSync(join(resolvedDir, \"config.json\"));\n const hasSql = existsSync(join(resolvedDir, \"postgres-pai.sql\"));\n\n console.log(`${bold(\"Backup contents:\")}`);\n console.log(` Registry DB ${inventory.hasRegistry ? ok(\"present\") : warn(\"missing\")}`);\n console.log(` config.json ${hasConfig ? ok(\"present\") : warn(\"missing\")}`);\n console.log(` postgres-pai.sql ${hasSql && opts.postgres ? ok(\"present\") : hasSql ? warn(\"present (skipped via --no-postgres)\") : warn(\"missing\")}`);\n if (inventory.hasFederation) {\n console.log(` Federation DB ${ok(\"present\")} ${dim(\"(legacy)\")}`);\n }\n\n // ------------------------------------------------------------------\n // 3. Confirm\n // ------------------------------------------------------------------\n\n console.log(`\\n${warn(\"WARNING:\")} This will OVERWRITE your current PAI data.`);\n if (hasSql && opts.postgres) {\n console.log(warn(\" Postgres database will be dropped and recreated from the backup.\"));\n }\n\n const proceed = opts.yes || await confirm(\"Proceed with restore?\");\n if (!proceed) {\n console.log(dim(\"Restore cancelled.\"));\n return;\n }\n\n console.log();\n\n const results: { label: string; status: string }[] = [];\n\n // ------------------------------------------------------------------\n // 4. Restore registry (+ legacy federation) SQLite DB, sqlite backend only\n // ------------------------------------------------------------------\n\n for (const r of restoreStorageFiles(loadConfig(), resolvedDir)) {\n results.push({\n label: r.label,\n status: r.status === \"ok\" ? ok(\"restored\") : r.status === \"skipped\" ? warn(\"missing in backup — skipped\") : err(`failed: ${r.error}`),\n });\n }\n\n // ------------------------------------------------------------------\n // 5. Restore config.json\n // ------------------------------------------------------------------\n\n if (hasConfig) {\n try {\n mkdirSync(dirname(CONFIG_FILE), { recursive: true });\n copyFileSync(join(resolvedDir, \"config.json\"), CONFIG_FILE);\n chmodSync(CONFIG_FILE, 0o600);\n results.push({ label: \"Config\", status: ok(\"restored\") });\n } catch (e) {\n results.push({ label: \"Config\", status: err(`failed: ${e}`) });\n }\n } else {\n results.push({ label: \"Config\", status: warn(\"missing in backup — skipped\") });\n }\n\n // ------------------------------------------------------------------\n // 6. Restore Postgres via docker exec\n // ------------------------------------------------------------------\n\n if (hasSql && opts.postgres) {\n console.log(dim(\" Restoring Postgres database (this may take a while)...\"));\n try {\n // Verify container is running\n execFileSync(\"docker\", [\"inspect\", DOCKER_CONTAINER, \"--format={{.State.Status}}\"], {\n stdio: \"pipe\",\n });\n\n // Drop and recreate the database, then restore\n execFileSync(\n \"docker\",\n [\n \"exec\",\n DOCKER_CONTAINER,\n \"psql\",\n \"-U\",\n PG_USER,\n \"-c\",\n `DROP DATABASE IF EXISTS ${PG_DATABASE}; CREATE DATABASE ${PG_DATABASE} OWNER ${PG_USER};`,\n ],\n { stdio: \"pipe\" }\n );\n\n // Pipe the SQL dump into psql\n const sqlContent = readFileSync(join(resolvedDir, \"postgres-pai.sql\"), \"utf8\");\n const psqlResult = spawnSync(\n \"docker\",\n [\"exec\", \"-i\", DOCKER_CONTAINER, \"psql\", \"-U\", PG_USER, PG_DATABASE],\n { input: sqlContent, encoding: \"utf8\", stdio: [\"pipe\", \"pipe\", \"pipe\"] }\n );\n\n if (psqlResult.status !== 0) {\n const errMsg = psqlResult.stderr?.split(\"\\n\")[0] ?? \"unknown error\";\n results.push({ label: \"Postgres DB\", status: err(`failed: ${errMsg}`) });\n } else {\n results.push({ label: \"Postgres DB\", status: ok(\"restored\") });\n }\n } catch (e) {\n const msg = e instanceof Error ? e.message.split(\"\\n\")[0] : String(e);\n results.push({ label: \"Postgres DB\", status: err(`failed: ${msg}`) });\n console.log(warn(` Is Docker running with container '${DOCKER_CONTAINER}'?`));\n }\n } else if (!hasSql || !opts.postgres) {\n const reason = !hasSql ? \"no SQL dump in backup\" : \"--no-postgres\";\n results.push({ label: \"Postgres DB\", status: dim(`skipped (${reason})`) });\n }\n\n // ------------------------------------------------------------------\n // Summary\n // ------------------------------------------------------------------\n\n console.log(`\\n${bold(\"Restore complete:\")}\\n`);\n const labelWidth = Math.max(...results.map((r) => r.label.length)) + 2;\n for (const r of results) {\n console.log(` ${bold(r.label.padEnd(labelWidth))} ${r.status}`);\n }\n\n const hasErrors = results.some((r) => r.status.includes(\"\\u001b[31m\"));\n if (!hasErrors) {\n console.log(`\\n ${ok(\"All done.\")} You may need to restart the PAI daemon.\\n`);\n console.log(` ${dim(\"Restart:\")} pai daemon restart\\n`);\n } else {\n console.log(`\\n ${warn(\"Some items failed — check output above.\")}\\n`);\n process.exitCode = 1;\n return;\n }\n });\n}\n","/**\n * Safe process termination for the CLI.\n *\n * Node's stdout/stderr are ASYNCHRONOUS when they point at a pipe (and\n * synchronous when they point at a TTY or a regular file). `process.exit()`\n * tears the process down immediately, before any still-in-flight async\n * write completes — so a command whose output is piped (`pai ... | less`,\n * `| wc -c`, a subshell capture, etc.) can have its output silently\n * truncated or dropped entirely, while the same command run against a TTY\n * or redirected to a file prints correctly. Exit code is still 0, so the\n * failure is invisible.\n *\n * The robust fix, used everywhere it is possible, is to never call\n * `process.exit()` at all: set `process.exitCode` and let the command\n * return normally. Once the event loop has nothing left to do, Node exits\n * on its own — and a natural exit always flushes pending writes first.\n *\n * `exitAfterFlush` exists for the rare call site where control flow\n * genuinely cannot return (e.g. deep inside a callback with no path back\n * to the caller). It waits for both stdout and stderr to drain before\n * calling `process.exit()`.\n */\n\nfunction streamDrained(stream: NodeJS.WriteStream): Promise<void> {\n // `writableLength` is the number of bytes still buffered and not yet\n // handed to the OS. If it's already 0, there is nothing to wait for.\n if (!stream.writableLength) return Promise.resolve();\n return new Promise((resolve) => {\n stream.once(\"drain\", () => resolve());\n // Belt-and-braces: also resolve on the next tick after a short poll,\n // in case `drain` was already emitted before we attached the listener\n // (rare, but writableLength can hit 0 without a fresh `drain` event on\n // some platforms/versions).\n const check = () => {\n if (!stream.writableLength) resolve();\n };\n setImmediate(check);\n });\n}\n\n/**\n * Wait for stdout and stderr to finish flushing, then exit with `code`.\n * Never returns (the return type documents that; callers should not\n * expect code after this call to run).\n */\nexport async function exitAfterFlush(code: number): Promise<never> {\n await drainStdio();\n process.exit(code);\n}\n\n/**\n * Wait for stdout and stderr to finish flushing, without exiting.\n *\n * Belt-and-suspenders backstop for cli/index.ts: every command path avoids\n * process.exit() so a natural process exit — which always flushes pending\n * writes — can do the flushing. This makes that explicit and awaited at the\n * one place every command path funnels through, instead of trusting that\n * nothing between here and process shutdown (a dependency's keep-alive\n * socket being unref'd, a timer, anything else that can make the event loop\n * look \"empty\") lets Node begin tearing down before a write that's still\n * asynchronously in flight to a pipe has actually reached the OS.\n */\nexport async function drainStdio(): Promise<void> {\n await Promise.all([streamDrained(process.stdout), streamDrained(process.stderr)]);\n}\n","/**\n * Shared helpers for the PAI setup wizard: chalk colour shortcuts,\n * readline prompts, config read/write, and filesystem path finders.\n */\n\nimport { createInterface } from \"node:readline\";\nimport { existsSync } from \"node:fs\";\nimport { join } from \"node:path\";\nimport { spawnSync } from \"node:child_process\";\nimport chalk from \"chalk\";\nimport { readMainConfigRaw, writeMainConfigRaw } from \"../../../daemon/config.js\";\nimport { exitAfterFlush } from \"../../lib/exit.js\";\nimport { packageRoot } from \"../../../module-paths.js\";\n\n// ---------------------------------------------------------------------------\n// Chalk colour helpers\n// ---------------------------------------------------------------------------\n\nexport const c = {\n bold: (s: string) => chalk.bold(s),\n dim: (s: string) => chalk.dim(s),\n green: (s: string) => chalk.green(s),\n yellow: (s: string) => chalk.yellow(s),\n cyan: (s: string) => chalk.cyan(s),\n red: (s: string) => chalk.red(s),\n blue: (s: string) => chalk.blue(s),\n ok: (s: string) => chalk.green(\" \" + s),\n warn: (s: string) => chalk.yellow(\" \" + s),\n err: (s: string) => chalk.red(\" \" + s),\n};\n\nexport function line(text = \"\"): void {\n console.log(text);\n}\n\nexport function section(title: string): void {\n line();\n console.log(chalk.bold.cyan(\" \" + title));\n console.log(chalk.dim(\" \" + \"─\".repeat(title.length)));\n}\n\n// ---------------------------------------------------------------------------\n// Readline prompt helpers\n// ---------------------------------------------------------------------------\n\nexport function createRl() {\n const rl = createInterface({\n input: process.stdin,\n output: process.stdout,\n });\n\n rl.on(\"SIGINT\", () => {\n line();\n line(c.dim(\" Setup cancelled. Run `pai setup` again to restart.\"));\n line();\n // Control can't return here — readline is still blocking on input, so\n // there is no natural exit to fall through to. exitAfterFlush() waits\n // for the lines above to actually reach the OS before terminating.\n void exitAfterFlush(0);\n });\n\n return rl;\n}\n\nexport type Rl = ReturnType<typeof createRl>;\n\n/** Unattended-mode switches (`pai setup --yes --storage …`), set once by the command. */\nexport const setupOptions: { yes: boolean; storage?: \"sqlite\" | \"postgres\" } = { yes: false };\n\n/**\n * The one place every wizard question passes through. Under --yes it answers\n * `defaultValue` without touching stdin; a question with no default cannot be\n * answered unattended, so it fails naming the flag instead of returning \"\".\n */\nexport async function prompt(rl: Rl, question: string, defaultValue?: string): Promise<string> {\n if (setupOptions.yes) {\n if (defaultValue === undefined) {\n throw new Error(`\"${question.trim()}\" has no default; cannot answer it with --yes. Run without --yes, or skip the feature that asks it.`);\n }\n console.log(chalk.dim(` ${question.trim()} -> ${defaultValue || \"(default)\"}`));\n return defaultValue;\n }\n return new Promise((resolve) => {\n rl.question(question, (answer) => {\n resolve(answer.trim());\n });\n });\n}\n\n/** Prompt for a numbered menu selection. Returns 0-based index. */\nexport async function promptMenu(\n rl: Rl,\n options: Array<{ label: string; description?: string }>,\n defaultIdx = 0,\n): Promise<number> {\n for (let i = 0; i < options.length; i++) {\n const num = chalk.bold(` ${i + 1}.`);\n const label = i === defaultIdx ? chalk.cyan(options[i].label) : options[i].label;\n const marker = i === defaultIdx ? chalk.dim(\" (recommended)\") : \"\";\n console.log(`${num} ${label}${marker}`);\n if (options[i].description) {\n console.log(chalk.dim(` ${options[i].description}`));\n }\n }\n line();\n\n while (true) {\n const answer = await prompt(\n rl,\n chalk.bold(` Enter number [1-${options.length}] (default: ${defaultIdx + 1}): `),\n \"\",\n );\n\n if (answer === \"\") return defaultIdx;\n\n const n = parseInt(answer, 10);\n if (!isNaN(n) && n >= 1 && n <= options.length) {\n return n - 1;\n }\n\n console.log(c.warn(`Please enter a number between 1 and ${options.length}.`));\n }\n}\n\n/** Prompt for a yes/no answer. Returns true for yes. */\nexport async function promptYesNo(\n rl: Rl,\n question: string,\n defaultYes = true,\n): Promise<boolean> {\n const hint = defaultYes ? \"[Y/n]\" : \"[y/N]\";\n const answer = await prompt(rl, ` ${question} ${chalk.dim(hint)}: `, \"\");\n\n if (answer === \"\") return defaultYes;\n return answer.toLowerCase().startsWith(\"y\");\n}\n\n// ---------------------------------------------------------------------------\n// Config read/write helpers\n// ---------------------------------------------------------------------------\n\n/**\n * This file holds the Postgres connection string, the storage backend choice,\n * notification routing and any tracker API token — none of which the user can\n * reconstruct from memory. It previously returned {} on a parse failure and\n * then overwrote the file, so a damaged config was replaced by whatever the\n * current command happened to be setting.\n */\nexport function readConfigRaw(): Record<string, unknown> {\n return readMainConfigRaw();\n}\n\nexport function writeConfigRaw(data: Record<string, unknown>): void {\n writeMainConfigRaw(data);\n}\n\nexport function mergeConfig(updates: Record<string, unknown>): void {\n const current = readConfigRaw();\n const merged = { ...current, ...updates };\n if (updates.postgres && typeof current.postgres === \"object\" && current.postgres !== null) {\n merged.postgres = { ...(current.postgres as object), ...(updates.postgres as object) };\n }\n writeConfigRaw(merged);\n}\n\n// ---------------------------------------------------------------------------\n// Docker and connection helpers\n// ---------------------------------------------------------------------------\n\nexport function hasDocker(): boolean {\n try {\n const result = spawnSync(\"docker\", [\"--version\"], { stdio: \"pipe\" });\n return result.status === 0;\n } catch {\n return false;\n }\n}\n\nexport function getDockerDir(): string {\n return join(packageRoot(import.meta.url), \"docker\");\n}\n\nexport async function testPostgresConnection(connectionString: string): Promise<boolean> {\n try {\n const pgModule = await import(\"pg\");\n const pg = pgModule.default ?? pgModule;\n const client = new pg.Client({ connectionString });\n await client.connect();\n await client.end();\n return true;\n } catch {\n return false;\n }\n}\n\n// ---------------------------------------------------------------------------\n// Filesystem path finders\n// ---------------------------------------------------------------------------\n\nexport function getTemplatesDir(): string {\n return join(packageRoot(import.meta.url), \"templates\");\n}\n\nexport function getHooksDir(): string {\n return join(packageRoot(import.meta.url), \"src\", \"hooks\");\n}\n\nexport function getDistHooksDir(): string {\n return join(getDistDir(), \"hooks\");\n}\n\nexport function getDistDir(): string {\n return join(packageRoot(import.meta.url), \"dist\");\n}\n\nexport function getStatuslineScript(): string | null {\n const p = join(packageRoot(import.meta.url), \"statusline-command.sh\");\n return existsSync(p) ? p : null;\n}\n\nexport function getTabColorScript(): string | null {\n const p = join(packageRoot(import.meta.url), \"tab-color-command.sh\");\n return existsSync(p) ? p : null;\n}\n","/** Step 1: Welcome banner displayed at the start of the setup wizard. */\n\nimport chalk from \"chalk\";\nimport { c, line } from \"../utils.js\";\n\nexport function stepWelcome(): void {\n line();\n line(chalk.bold.cyan(\" ╔════════════════════════════════════════╗\"));\n line(chalk.bold.cyan(\" ║ PAI Knowledge OS — Setup Wizard ║\"));\n line(chalk.bold.cyan(\" ╚════════════════════════════════════════╝\"));\n line();\n line(\" PAI is a personal knowledge system that indexes your files, generates\");\n line(\" semantic embeddings for intelligent search, and stores everything in a\");\n line(\" local database so you can search your knowledge base with natural language.\");\n line();\n line(c.dim(\" This wizard will guide you through the initial configuration.\"));\n line(c.dim(\" Press Ctrl+C at any time to cancel.\"));\n}\n","/** Step 2: Storage backend selection (SQLite or PostgreSQL) and Docker helper. */\n\nimport { existsSync } from \"node:fs\";\nimport { join } from \"node:path\";\nimport { spawnSync } from \"node:child_process\";\nimport chalk from \"chalk\";\nimport { c, line, section, type Rl, prompt, promptMenu, promptYesNo, readConfigRaw, setupOptions, getDockerDir, hasDocker, testPostgresConnection } from \"../utils.js\";\n\nasync function startDocker(rl: Rl): Promise<boolean> {\n const dockerDir = getDockerDir();\n const composePath = join(dockerDir, \"docker-compose.yml\");\n\n if (!existsSync(composePath)) {\n console.log(c.warn(`docker-compose.yml not found at ${dockerDir}`));\n console.log(c.dim(\" You can start it manually later:\"));\n console.log(c.dim(\" docker compose up -d\"));\n return false;\n }\n\n console.log(c.dim(` Starting PostgreSQL container from ${dockerDir}...`));\n\n try {\n const result = spawnSync(\"docker\", [\"compose\", \"up\", \"-d\"], {\n cwd: dockerDir,\n stdio: \"inherit\",\n });\n\n if (result.status !== 0) {\n console.log(c.warn(\" Docker compose failed. You can start it manually:\"));\n console.log(c.dim(` cd ${dockerDir} && docker compose up -d`));\n return false;\n }\n\n console.log(c.ok(\"PostgreSQL container started.\"));\n return true;\n } catch (e) {\n console.log(c.warn(` Could not run docker compose: ${e}`));\n return false;\n }\n}\n\nexport async function stepStorage(rl: Rl): Promise<Record<string, unknown>> {\n section(\"Step 2: Storage Backend\");\n\n const existing = readConfigRaw();\n if (existing.storageBackend) {\n const backend = String(existing.storageBackend);\n if (backend === \"postgres\") {\n try {\n const result = spawnSync(\"docker\", [\"ps\", \"--filter\", \"name=pai-pgvector\", \"--format\", \"{{.Status}}\"], { stdio: \"pipe\" });\n const status = result.stdout?.toString().trim();\n if (status && status.includes(\"Up\")) {\n console.log(c.ok(`Storage backend: PostgreSQL (container running). Skipping.`));\n return existing;\n }\n console.log(c.dim(\" PostgreSQL container found but not running. Starting...\"));\n await startDocker(rl);\n await new Promise((r) => setTimeout(r, 3000));\n console.log(c.ok(\"PostgreSQL container started.\"));\n } catch {\n console.log(c.ok(`Storage backend: PostgreSQL (configured). Skipping.`));\n }\n return existing;\n } else {\n console.log(c.ok(`Storage backend: ${backend}. Skipping.`));\n return existing;\n }\n }\n\n line();\n line(\" Choose how PAI stores your indexed knowledge:\");\n line();\n\n // Unattended: --storage wins, else Postgres only when the default local one answers.\n const forced =\n setupOptions.storage ??\n (setupOptions.yes\n ? (await testPostgresConnection(\"postgresql://pai:pai@localhost:5432/pai\")) ? \"postgres\" : \"sqlite\"\n : undefined);\n\n const choice = forced ? (forced === \"sqlite\" ? 1 : 0) : await promptMenu(rl, [\n {\n label: \"PostgreSQL with pgvector\",\n description: \"Best for large collections, semantic search, and production use. Requires Docker or a Postgres server.\",\n },\n {\n label: \"SQLite\",\n description: \"Simple, no dependencies, zero configuration. Good for trying PAI out. Keyword search only.\",\n },\n ]);\n\n if (choice === 1) {\n line();\n console.log(c.ok(\"SQLite selected. No additional setup needed.\"));\n return { storageBackend: \"sqlite\" };\n }\n\n // PostgreSQL\n line();\n line(\" PostgreSQL requires a running Postgres server with the pgvector extension.\");\n line();\n\n if (hasDocker()) {\n console.log(c.ok(\"Docker is installed.\"));\n line();\n\n const useDocker = await promptYesNo(rl, \"Start the PAI PostgreSQL container with Docker? (recommended)\", true);\n\n if (useDocker) {\n line();\n await startDocker(rl);\n line();\n\n console.log(c.dim(\" Waiting 3 seconds for container to be ready...\"));\n await new Promise((r) => setTimeout(r, 3000));\n\n const connStr = \"postgresql://pai:pai@localhost:5432/pai\";\n console.log(c.dim(` Testing connection to ${connStr}...`));\n\n const ok2 = await testPostgresConnection(connStr);\n if (ok2) {\n console.log(c.ok(\"Connection successful!\"));\n } else {\n console.log(c.warn(\"Connection test failed. The container may still be starting.\"));\n console.log(c.dim(\" Using default connection string — you can verify with `pai daemon status`.\"));\n }\n return { storageBackend: \"postgres\", postgres: { connectionString: connStr } };\n }\n } else {\n console.log(c.dim(\" Docker not found. Using manual connection string entry.\"));\n }\n\n // Manual entry\n line();\n line(\" Enter your PostgreSQL connection details:\");\n line();\n\n const useConnStr = await promptYesNo(rl, \"Use a full connection string? (e.g. postgresql://user:pass@host:5432/dbname)\", true);\n\n if (useConnStr) {\n const connStr = await prompt(rl, chalk.bold(\" Connection string: \"), \"\");\n if (connStr) {\n console.log(c.dim(\" Testing connection...\"));\n const connected = await testPostgresConnection(connStr);\n if (connected) {\n console.log(c.ok(\"Connection successful!\"));\n } else {\n console.log(c.warn(\"Connection test failed — check credentials and try again later.\"));\n }\n return { storageBackend: \"postgres\", postgres: { connectionString: connStr } };\n }\n }\n\n const host = await prompt(rl, chalk.bold(\" Host [localhost]: \"), \"\") || \"localhost\";\n const portStr = await prompt(rl, chalk.bold(\" Port [5432]: \"), \"\") || \"5432\";\n const database = await prompt(rl, chalk.bold(\" Database [pai]: \"), \"\") || \"pai\";\n const user = await prompt(rl, chalk.bold(\" User [pai]: \"), \"\") || \"pai\";\n const password = await prompt(rl, chalk.bold(\" Password [pai]: \"), \"\") || \"pai\";\n\n const connStr = `postgresql://${user}:${password}@${host}:${portStr}/${database}`;\n console.log(c.dim(` Connection string: ${connStr}`));\n console.log(c.dim(\" Testing connection...\"));\n\n const connected = await testPostgresConnection(connStr);\n if (connected) {\n console.log(c.ok(\"Connection successful!\"));\n } else {\n console.log(c.warn(\"Connection test failed — check credentials and try again later.\"));\n }\n\n return { storageBackend: \"postgres\", postgres: { connectionString: connStr } };\n}\n","/** Step 3: Embedding model selection for semantic search. */\n\nimport { c, line, section, type Rl, promptMenu, readConfigRaw } from \"../utils.js\";\nimport { CONFIG_FILE } from \"../../../../daemon/config.js\";\n\nexport async function stepEmbedding(rl: Rl): Promise<Record<string, unknown>> {\n section(\"Step 3: Embedding Model\");\n\n const existing = readConfigRaw();\n if (existing.embeddingModel) {\n console.log(c.ok(`Embedding model: ${existing.embeddingModel}. Skipping.`));\n return { embeddingModel: existing.embeddingModel };\n }\n\n line();\n line(\" An embedding model converts your text into vectors for semantic search.\");\n line(\" Models are downloaded from HuggingFace on first use.\");\n line();\n\n const choice = await promptMenu(rl, [\n {\n label: \"Snowflake Arctic Embed m v1.5\",\n description: \"768 dims, ~118MB download. Best retrieval quality per MB (MTEB score 55.14). Asymmetric retrieval — different handling for queries vs documents. Best for most users.\",\n },\n {\n label: \"BGE Small EN v1.5\",\n description: \"384 dims, ~32MB download. Lightweight and fast. Good for limited disk space or when faster embedding is more important than maximum quality. English only.\",\n },\n {\n label: \"Nomic Embed Text v1.5\",\n description: \"768 dims, ~100MB download. 8K token context window — excellent for long documents. Matryoshka dimensions (can truncate for speed/size tradeoffs).\",\n },\n {\n label: \"None — skip embeddings\",\n description: \"BM25/keyword search only. No model download needed. You can add embeddings later by running `pai memory embed` after selecting a model.\",\n },\n ]);\n\n const models: Record<number, string | null> = {\n 0: \"Snowflake/snowflake-arctic-embed-m-v1.5\",\n 1: \"BAAI/bge-small-en-v1.5\",\n 2: \"nomic-ai/nomic-embed-text-v1.5\",\n 3: null,\n };\n\n const selectedModel = models[choice];\n\n line();\n if (selectedModel) {\n console.log(c.ok(`Model selected: ${selectedModel}`));\n console.log(c.dim(\" The model will be downloaded on first use of `pai memory embed`.\"));\n } else {\n console.log(c.ok(\"Skipping embeddings. Keyword search will still work.\"));\n console.log(c.dim(` Add later: update embeddingModel in ${CONFIG_FILE}`));\n }\n\n return { embeddingModel: selectedModel ?? \"none\" };\n}\n","/** Step 4: CLAUDE.md generation from PAI template. */\n\nimport { existsSync, readFileSync, writeFileSync, mkdirSync } from \"node:fs\";\nimport { homedir } from \"node:os\";\nimport { join } from \"node:path\";\nimport chalk from \"chalk\";\nimport { CONFIG_DIR } from \"../../../../daemon/config.js\";\nimport { c, line, section, type Rl, promptYesNo, getTemplatesDir } from \"../utils.js\";\n\nexport async function stepClaudeMd(rl: Rl): Promise<boolean> {\n section(\"Step 4: Agent Configuration (CLAUDE.md)\");\n line();\n line(\" PAI ships a CLAUDE.md template with agent orchestration patterns:\");\n line(\" swarm mode, model escalation, parallel execution, quality standards.\");\n line();\n\n const claudeDir = join(homedir(), \".claude\");\n const claudeMd = join(claudeDir, \"CLAUDE.md\");\n const agentPrefs = join(CONFIG_DIR, \"agent-prefs.md\");\n const templatesDir = getTemplatesDir();\n const templatePath = join(templatesDir, \"claude-md.template.md\");\n\n if (!existsSync(templatePath)) {\n console.log(c.warn(\"Template not found: \" + templatePath));\n console.log(c.dim(\" Skipping CLAUDE.md generation. You can copy it manually from templates/.\"));\n return false;\n }\n\n if (existsSync(claudeMd)) {\n const content = readFileSync(claudeMd, \"utf-8\");\n const isGenerated = content.includes(\"Generated by PAI Setup\");\n if (isGenerated) {\n console.log(c.dim(\" Found existing PAI-generated CLAUDE.md.\"));\n } else {\n console.log(c.yellow(\" Found existing CLAUDE.md (custom, not PAI-generated).\"));\n console.log(c.dim(\" A backup will be created before overwriting.\"));\n }\n line();\n\n const overwrite = await promptYesNo(rl, \"Update ~/.claude/CLAUDE.md with the latest PAI template?\", isGenerated);\n if (!overwrite) {\n console.log(c.dim(\" Keeping existing CLAUDE.md unchanged.\"));\n return false;\n }\n\n if (!isGenerated) {\n const backupPath = claudeMd + \".backup\";\n writeFileSync(backupPath, content, \"utf-8\");\n console.log(c.ok(`Backed up existing CLAUDE.md to ${backupPath}`));\n }\n }\n\n let template = readFileSync(templatePath, \"utf-8\");\n template = template.replace(/\\$\\{HOME\\}/g, homedir());\n\n if (!existsSync(claudeDir)) {\n mkdirSync(claudeDir, { recursive: true });\n }\n writeFileSync(claudeMd, template, \"utf-8\");\n\n line();\n console.log(c.ok(\"Generated ~/.claude/CLAUDE.md from PAI template.\"));\n\n if (!existsSync(agentPrefs)) {\n line();\n line(\" For personal settings (project mappings, notification preferences),\");\n line(\" copy the example and customize:\");\n line();\n console.log(chalk.cyan(` cp ${templatesDir}/agent-prefs.example.md ${agentPrefs}`));\n line();\n\n const createPrefs = await promptYesNo(rl, \"Copy the example agent-prefs.md now?\", true);\n if (createPrefs) {\n const examplePath = join(templatesDir, \"agent-prefs.example.md\");\n if (existsSync(examplePath)) {\n if (!existsSync(CONFIG_DIR)) {\n mkdirSync(CONFIG_DIR, { recursive: true });\n }\n writeFileSync(agentPrefs, readFileSync(examplePath, \"utf-8\"), \"utf-8\");\n console.log(c.ok(`Created ${agentPrefs}`));\n console.log(c.dim(\" Edit this file to add your personal preferences and project mappings.\"));\n } else {\n console.log(c.warn(\"Example file not found. Create agent-prefs.md manually.\"));\n }\n }\n } else {\n console.log(c.dim(\" Personal preferences: \" + agentPrefs));\n }\n\n return true;\n}\n","/** Step 5: PAI SKILL.md installation to ~/.claude/skills/PAI/. */\n\nimport { existsSync, readFileSync, writeFileSync, mkdirSync } from \"node:fs\";\nimport { homedir } from \"node:os\";\nimport { join } from \"node:path\";\nimport { c, line, section, type Rl, promptYesNo, getTemplatesDir } from \"../utils.js\";\n\nexport async function stepPaiSkill(rl: Rl): Promise<boolean> {\n section(\"Step 5: PAI Skill Installation\");\n line();\n line(\" PAI ships a SKILL.md that tells Claude Code how to invoke PAI commands.\");\n line();\n\n const templatesDir = getTemplatesDir();\n const templatePath = join(templatesDir, \"pai-skill.template.md\");\n\n if (!existsSync(templatePath)) {\n console.log(c.warn(\"Skill template not found: \" + templatePath));\n console.log(c.dim(\" Skipping PAI skill installation.\"));\n return false;\n }\n\n const skillDir = join(homedir(), \".claude\", \"skills\", \"PAI\");\n const skillFile = join(skillDir, \"SKILL.md\");\n\n if (existsSync(skillFile)) {\n const content = readFileSync(skillFile, \"utf-8\");\n const isGenerated = content.includes(\"Generated by PAI Setup\");\n if (isGenerated) {\n console.log(c.dim(\" Found existing PAI-generated SKILL.md.\"));\n } else {\n console.log(c.yellow(\" Found existing SKILL.md (not PAI-generated).\"));\n console.log(c.dim(\" A backup will be created before overwriting.\"));\n }\n line();\n\n const overwrite = await promptYesNo(rl, \"Update ~/.claude/skills/PAI/SKILL.md with the latest PAI skill?\", isGenerated);\n if (!overwrite) {\n console.log(c.dim(\" Keeping existing SKILL.md unchanged.\"));\n return false;\n }\n\n if (!isGenerated) {\n const backupPath = skillFile + \".backup\";\n writeFileSync(backupPath, content, \"utf-8\");\n console.log(c.ok(`Backed up existing SKILL.md to ${backupPath}`));\n }\n } else {\n const install = await promptYesNo(rl, \"Install PAI skill to ~/.claude/skills/PAI/SKILL.md?\", true);\n if (!install) {\n console.log(c.dim(\" Skipping PAI skill installation.\"));\n return false;\n }\n }\n\n let template = readFileSync(templatePath, \"utf-8\");\n template = template.replace(/\\$\\{HOME\\}/g, homedir());\n\n if (!existsSync(skillDir)) {\n mkdirSync(skillDir, { recursive: true });\n }\n writeFileSync(skillFile, template, \"utf-8\");\n\n line();\n console.log(c.ok(\"Installed ~/.claude/skills/PAI/SKILL.md\"));\n return true;\n}\n","/** Step 6: AI Steering Rules installation to ~/.claude/skills/PAI/. */\n\nimport { existsSync, readFileSync, writeFileSync, mkdirSync } from \"node:fs\";\nimport { homedir } from \"node:os\";\nimport { join } from \"node:path\";\nimport { c, line, section, type Rl, promptYesNo, getTemplatesDir } from \"../utils.js\";\n\nexport async function stepAiSteeringRules(rl: Rl): Promise<boolean> {\n section(\"Step 6: AI Steering Rules\");\n line();\n line(\" PAI ships a set of universal behavioral rules for AI assistants:\");\n line(\" surgical fixes, verification before assertion, root cause analysis,\");\n line(\" and honest failure modes. These rules load at startup as a skill.\");\n line();\n\n const templatesDir = getTemplatesDir();\n const templatePath = join(templatesDir, \"ai-steering-rules.template.md\");\n\n if (!existsSync(templatePath)) {\n console.log(c.warn(\"AI steering rules template not found: \" + templatePath));\n console.log(c.dim(\" Skipping AI steering rules installation.\"));\n return false;\n }\n\n const skillDir = join(homedir(), \".claude\", \"skills\", \"PAI\");\n const skillFile = join(skillDir, \"AI-STEERING-RULES.md\");\n\n if (existsSync(skillFile)) {\n const content = readFileSync(skillFile, \"utf-8\");\n const isGenerated = content.includes(\"Generated by PAI Setup\");\n if (isGenerated) {\n console.log(c.dim(\" Found existing PAI-generated AI-STEERING-RULES.md.\"));\n } else {\n console.log(c.yellow(\" Found existing AI-STEERING-RULES.md (not PAI-generated).\"));\n console.log(c.dim(\" A backup will be created before overwriting.\"));\n }\n line();\n\n const overwrite = await promptYesNo(rl, \"Update ~/.claude/skills/PAI/AI-STEERING-RULES.md with the latest rules?\", isGenerated);\n if (!overwrite) {\n console.log(c.dim(\" Keeping existing AI-STEERING-RULES.md unchanged.\"));\n return false;\n }\n\n if (!isGenerated) {\n const backupPath = skillFile + \".backup\";\n writeFileSync(backupPath, content, \"utf-8\");\n console.log(c.ok(`Backed up existing AI-STEERING-RULES.md to ${backupPath}`));\n }\n } else {\n const install = await promptYesNo(rl, \"Install AI steering rules to ~/.claude/skills/PAI/AI-STEERING-RULES.md?\", true);\n if (!install) {\n console.log(c.dim(\" Skipping AI steering rules installation.\"));\n return false;\n }\n }\n\n const template = readFileSync(templatePath, \"utf-8\");\n\n if (!existsSync(skillDir)) {\n mkdirSync(skillDir, { recursive: true });\n }\n writeFileSync(skillFile, template, \"utf-8\");\n\n line();\n console.log(c.ok(\"Installed ~/.claude/skills/PAI/AI-STEERING-RULES.md\"));\n return true;\n}\n","/**\n * Step 7: Install PAI MCP skill stubs to ~/.claude/skills/.\n *\n * Each PAI MCP prompt has a SKILL.md stub generated at build time\n * (dist/skills/<Name>/SKILL.md). This step symlinks (macOS/Linux) or\n * copies (Windows) them into Claude Code's skill discovery directory.\n *\n * Skills MUST live at ~/.claude/skills/<Name>/SKILL.md (top level),\n * NOT under a user/ subdirectory — Claude Code only scans one level deep.\n *\n * Symlinks keep the stubs in sync — rebuilding PAI updates them automatically.\n * Existing non-symlink directories with the same name are never overwritten.\n */\n\nimport {\n existsSync,\n readdirSync,\n symlinkSync,\n lstatSync,\n readlinkSync,\n copyFileSync,\n mkdirSync,\n unlinkSync,\n} from \"node:fs\";\nimport { homedir, platform } from \"node:os\";\nimport { join, resolve } from \"node:path\";\nimport { c, line, section, type Rl, promptYesNo, getDistDir } from \"../utils.js\";\n\nexport async function stepSkillStubs(rl: Rl): Promise<boolean> {\n section(\"Step 7: MCP Skill Stubs\");\n line();\n line(\" PAI MCP prompts need SKILL.md stubs so Claude Code can discover them.\");\n line(\" Stubs are symlinked from dist/skills/ — rebuilding PAI keeps them in sync.\");\n line();\n\n const distSkills = join(getDistDir(), \"skills\");\n\n if (!existsSync(distSkills)) {\n console.log(c.warn(\"dist/skills/ not found — run `bun run build` first.\"));\n return false;\n }\n\n const skillNames = readdirSync(distSkills).filter((name) => {\n const p = join(distSkills, name);\n return existsSync(join(p, \"SKILL.md\"));\n });\n\n if (skillNames.length === 0) {\n console.log(c.warn(\"No skill stubs found in dist/skills/.\"));\n return false;\n }\n\n console.log(c.dim(` Found ${skillNames.length} skill stubs to install.`));\n line();\n\n const install = await promptYesNo(\n rl,\n `Symlink ${skillNames.length} PAI skill stubs to ~/.claude/skills/?`,\n true,\n );\n if (!install) {\n console.log(c.dim(\" Skipping skill stub installation.\"));\n return false;\n }\n\n const targetBase = join(homedir(), \".claude\", \"skills\");\n mkdirSync(targetBase, { recursive: true });\n\n // Clean up stale symlinks from old user/ location\n const oldUserBase = join(targetBase, \"user\");\n if (existsSync(oldUserBase)) {\n for (const name of skillNames) {\n const oldTarget = join(oldUserBase, name);\n if (existsSync(oldTarget) && lstatSync(oldTarget).isSymbolicLink()) {\n unlinkSync(oldTarget);\n }\n }\n }\n\n const useSymlinks = platform() !== \"win32\";\n let installed = 0;\n let skipped = 0;\n let updated = 0;\n\n for (const name of skillNames) {\n const source = resolve(join(distSkills, name));\n const target = join(targetBase, name);\n\n // If target exists and is not a symlink, never overwrite (user's own skill)\n if (existsSync(target) && !lstatSync(target).isSymbolicLink()) {\n console.log(c.dim(` SKIP ${name} (existing user skill, not a symlink)`));\n skipped++;\n continue;\n }\n\n // If target is an existing symlink, check if it already points to the right place\n if (existsSync(target) && lstatSync(target).isSymbolicLink()) {\n const currentTarget = readlinkSync(target);\n if (resolve(currentTarget) === source) {\n // Already correct\n installed++;\n continue;\n }\n // Stale symlink — remove and recreate\n unlinkSync(target);\n updated++;\n }\n\n if (useSymlinks) {\n symlinkSync(source, target);\n } else {\n // Windows: copy the SKILL.md\n mkdirSync(target, { recursive: true });\n copyFileSync(join(source, \"SKILL.md\"), join(target, \"SKILL.md\"));\n }\n installed++;\n }\n\n line();\n const method = useSymlinks ? \"symlinked\" : \"copied\";\n if (updated > 0) {\n console.log(c.ok(`${installed} stubs ${method}, ${updated} updated, ${skipped} skipped (user skills).`));\n } else {\n console.log(c.ok(`${installed} skill stubs ${method} to ~/.claude/skills/.`));\n if (skipped > 0) {\n console.log(c.dim(` ${skipped} skipped (existing user skills not managed by PAI).`));\n }\n }\n return true;\n}\n","/** Step 7: Shell lifecycle hooks installation (pre-compact, session-stop, statusline). */\n\nimport {\n existsSync, lstatSync, readlinkSync, symlinkSync, unlinkSync,\n copyFileSync, chmodSync, mkdirSync,\n} from \"node:fs\";\nimport { homedir, platform } from \"node:os\";\nimport { join, resolve } from \"node:path\";\nimport { c, line, section, type Rl, promptYesNo, getHooksDir, getStatuslineScript, getTabColorScript } from \"../utils.js\";\n\nconst useSymlinks = platform() !== \"win32\";\n\nexport async function stepHooks(rl: Rl): Promise<boolean> {\n section(\"Step 7: Lifecycle Hooks\");\n line();\n line(\" PAI hooks fire on session stop and context compaction to save state,\");\n line(\" update notes, and display live statusline information.\");\n if (useSymlinks) {\n line(\" Files are symlinked so they auto-update when PAI is rebuilt.\");\n } else {\n line(\" Files are copied (Windows — re-run setup after PAI updates).\");\n }\n line();\n\n const install = await promptYesNo(rl, \"Install PAI lifecycle hooks (session stop, pre-compact, statusline)?\", true);\n if (!install) {\n console.log(c.dim(\" Skipping hook installation.\"));\n return false;\n }\n\n const hooksDir = getHooksDir();\n const statuslineSrc = getStatuslineScript();\n const tabColorSrc = getTabColorScript();\n\n const claudeDir = join(homedir(), \".claude\");\n const hooksTarget = join(claudeDir, \"Hooks\");\n\n if (!existsSync(hooksTarget)) {\n mkdirSync(hooksTarget, { recursive: true });\n }\n\n let anyInstalled = false;\n\n function installLink(src: string, dest: string, label: string): void {\n if (!existsSync(src)) {\n console.log(c.warn(` Source not found: ${src}`));\n return;\n }\n\n const absSrc = resolve(src);\n\n // Check existing target\n if (existsSync(dest) || (lstatSync(dest, { throwIfNoEntry: false })?.isSymbolicLink?.())) {\n try {\n const stat = lstatSync(dest);\n if (stat.isSymbolicLink()) {\n if (resolve(readlinkSync(dest)) === absSrc) {\n console.log(c.dim(` Current: ${label}`));\n return;\n }\n // Stale symlink — replace\n unlinkSync(dest);\n } else if (stat.isFile()) {\n // Old copy — replace with symlink\n unlinkSync(dest);\n } else {\n return; // Don't touch directories\n }\n } catch {\n // lstatSync failed — target doesn't exist, proceed\n }\n }\n\n if (useSymlinks) {\n symlinkSync(absSrc, dest);\n } else {\n copyFileSync(src, dest);\n chmodSync(dest, 0o755);\n }\n const verb = useSymlinks ? \"Linked\" : \"Installed\";\n console.log(c.ok(`${verb}: ${label}`));\n anyInstalled = true;\n }\n\n line();\n installLink(join(hooksDir, \"pre-compact.sh\"), join(hooksTarget, \"pai-pre-compact.sh\"), \"pai-pre-compact.sh\");\n installLink(join(hooksDir, \"session-stop.sh\"), join(hooksTarget, \"pai-session-stop.sh\"), \"pai-session-stop.sh\");\n installLink(join(hooksDir, \"session-autosave.sh\"), join(hooksTarget, \"pai-session-autosave.sh\"), \"pai-session-autosave.sh\");\n\n if (statuslineSrc) {\n installLink(statuslineSrc, join(claudeDir, \"statusline-command.sh\"), \"statusline-command.sh\");\n } else {\n console.log(c.warn(\" statusline-command.sh not found — skipping statusline.\"));\n }\n\n if (platform() !== \"darwin\") {\n console.log(c.dim(\" Skipping tab-color-command.sh (iTerm2 only).\"));\n } else if (tabColorSrc) {\n installLink(tabColorSrc, join(claudeDir, \"tab-color-command.sh\"), \"tab-color-command.sh\");\n } else {\n console.log(c.warn(\" tab-color-command.sh not found — skipping tab color.\"));\n }\n\n return anyInstalled;\n}\n","/** Step 7b: TypeScript (.mjs) hooks installation to ~/.claude/Hooks/. */\n\nimport {\n existsSync, readdirSync, lstatSync, readlinkSync,\n symlinkSync, unlinkSync, copyFileSync, chmodSync, mkdirSync,\n} from \"node:fs\";\nimport { homedir, platform } from \"node:os\";\nimport { join, resolve } from \"node:path\";\nimport { c, line, section, type Rl, promptYesNo, getDistHooksDir } from \"../utils.js\";\n\nconst useSymlinks = platform() !== \"win32\";\n\nexport async function stepTsHooks(rl: Rl): Promise<boolean> {\n section(\"Step 7b: TypeScript Hooks Installation\");\n line();\n line(\" PAI ships compiled TypeScript hooks (.mjs) that fire on session events,\");\n line(\" tool use, and context compaction to capture context and update notes.\");\n if (useSymlinks) {\n line(\" Files are symlinked so they auto-update when PAI is rebuilt.\");\n } else {\n line(\" Files are copied (Windows — re-run setup after PAI updates).\");\n }\n line();\n\n const install = await promptYesNo(rl, \"Install PAI TypeScript hooks to ~/.claude/Hooks/?\", true);\n if (!install) {\n console.log(c.dim(\" Skipping TypeScript hooks installation.\"));\n return false;\n }\n\n const distHooksDir = getDistHooksDir();\n\n if (!existsSync(distHooksDir)) {\n console.log(c.warn(` dist/hooks/ directory not found at: ${distHooksDir}`));\n console.log(c.dim(\" Build the package first: bun run build\"));\n return false;\n }\n\n const claudeDir = join(homedir(), \".claude\");\n const hooksTarget = join(claudeDir, \"Hooks\");\n\n if (!existsSync(hooksTarget)) {\n mkdirSync(hooksTarget, { recursive: true });\n }\n\n let allFiles: string[];\n try {\n allFiles = readdirSync(distHooksDir).filter((f) => f.endsWith(\".mjs\"));\n } catch (e) {\n console.log(c.warn(` Could not read dist/hooks/: ${e}`));\n return false;\n }\n\n if (allFiles.length === 0) {\n console.log(c.warn(\" No .mjs files found in dist/hooks/. Build first: bun run build\"));\n return false;\n }\n\n line();\n let linkedCount = 0;\n let skippedCount = 0;\n let cleanedCount = 0;\n\n for (const filename of allFiles) {\n const src = resolve(join(distHooksDir, filename));\n const dest = join(hooksTarget, filename);\n\n // Check existing target\n let needsLink = true;\n if (existsSync(dest) || lstatSync(dest, { throwIfNoEntry: false })?.isSymbolicLink?.()) {\n try {\n const stat = lstatSync(dest);\n if (stat.isSymbolicLink()) {\n if (resolve(readlinkSync(dest)) === src) {\n console.log(c.dim(` Current: ${filename}`));\n skippedCount++;\n needsLink = false;\n } else {\n unlinkSync(dest); // Stale symlink\n }\n } else if (stat.isFile()) {\n unlinkSync(dest); // Old copy — replace with symlink\n }\n } catch {\n // lstatSync failed — target doesn't exist\n }\n }\n\n if (needsLink) {\n if (useSymlinks) {\n symlinkSync(src, dest);\n } else {\n copyFileSync(src, dest);\n chmodSync(dest, 0o755);\n }\n const verb = useSymlinks ? \"Linked\" : \"Installed\";\n console.log(c.ok(`${verb}: ${filename}`));\n linkedCount++;\n }\n\n // Clean up stale .ts files from pre-build era\n const staleTsPath = join(hooksTarget, filename.replace(/\\.mjs$/, \".ts\"));\n if (existsSync(staleTsPath)) {\n unlinkSync(staleTsPath);\n console.log(c.ok(`Cleaned up stale: ${filename.replace(/\\.mjs$/, \".ts\")}`));\n cleanedCount++;\n }\n }\n\n line();\n if (linkedCount > 0 || cleanedCount > 0) {\n const parts = [];\n const verb = useSymlinks ? \"linked\" : \"installed\";\n if (linkedCount > 0) parts.push(`${linkedCount} hook(s) ${verb}`);\n if (skippedCount > 0) parts.push(`${skippedCount} current`);\n if (cleanedCount > 0) parts.push(`${cleanedCount} stale .ts file(s) cleaned up`);\n console.log(c.ok(parts.join(\", \") + \".\"));\n } else {\n console.log(c.dim(` All ${skippedCount} hook(s) already current.`));\n }\n\n return linkedCount > 0 || cleanedCount > 0;\n}\n","/**\n * settings-manager — merge-not-overwrite utility for ~/.claude/settings.json\n *\n * Provides safe, idempotent writes to Claude Code's settings.json:\n * - env vars: added only if the key is absent (never overwrites)\n * - hooks: appended per hookType, deduplicated by command string\n * - statusLine: written only if the key is not already present\n */\n\nimport { existsSync, readFileSync, writeFileSync, mkdirSync } from \"node:fs\";\nimport { homedir } from \"node:os\";\nimport { join } from \"node:path\";\nimport chalk from \"chalk\";\nimport { readJsonStrict, writeJsonAtomic } from \"../../config/json-store.js\";\n\n// ---------------------------------------------------------------------------\n// Paths\n// ---------------------------------------------------------------------------\n\nconst CLAUDE_DIR = join(homedir(), \".claude\");\nconst SETTINGS_FILE = join(CLAUDE_DIR, \"settings.json\");\n\n// ---------------------------------------------------------------------------\n// Types\n// ---------------------------------------------------------------------------\n\nexport type HookType =\n | \"PreCompact\"\n | \"Stop\"\n | \"SessionStart\"\n | \"SessionEnd\"\n | \"UserPromptSubmit\"\n | \"PreToolUse\"\n | \"PostToolUse\"\n | \"SubagentStop\";\n\nexport interface HookEntry {\n hookType: HookType;\n matcher?: string;\n command: string;\n}\n\nexport interface SettingsMergeOptions {\n env?: Record<string, string>;\n hooks?: HookEntry[];\n statusLine?: { type: string; command: string };\n permissions?: {\n allow?: string[];\n deny?: string[];\n };\n flags?: Record<string, unknown>;\n}\n\nexport interface MergeResult {\n changed: boolean;\n report: string[];\n}\n\n// ---------------------------------------------------------------------------\n// Internal shape of settings.json hooks\n// ---------------------------------------------------------------------------\n\ninterface HookCommand {\n type: string;\n command: string;\n}\n\ninterface HookRule {\n matcher?: string;\n hooks: HookCommand[];\n}\n\ntype HooksSection = Partial<Record<HookType, HookRule[]>>;\n\n// ---------------------------------------------------------------------------\n// Read / write helpers\n// ---------------------------------------------------------------------------\n\n/**\n * ~/.claude/settings.json is Claude Code's own file, not ours: hooks, env,\n * permissions, statusline, enabled plugins. PAI only ever adds to it. Returning\n * {} for a damaged file and then writing meant a stray parse error could strip\n * every hook registration the user had.\n */\nexport function readSettingsJson(): Record<string, unknown> {\n return readJsonStrict(SETTINGS_FILE, \"~/.claude/settings.json\");\n}\n\nexport function writeSettingsJson(data: Record<string, unknown>): void {\n writeJsonAtomic(SETTINGS_FILE, data, { label: \"~/.claude/settings.json\" });\n}\n\n// ---------------------------------------------------------------------------\n// Merge helpers\n// ---------------------------------------------------------------------------\n\n/**\n * Merge env vars — add keys that are absent, never overwrite existing ones.\n */\nfunction mergeEnv(\n settings: Record<string, unknown>,\n incoming: Record<string, string>,\n report: string[],\n): boolean {\n let changed = false;\n\n const existing = (\n typeof settings[\"env\"] === \"object\" && settings[\"env\"] !== null\n ? settings[\"env\"]\n : {}\n ) as Record<string, string>;\n\n for (const [key, value] of Object.entries(incoming)) {\n if (Object.prototype.hasOwnProperty.call(existing, key)) {\n report.push(chalk.dim(` Skipped: env.${key} already set`));\n } else {\n existing[key] = value;\n report.push(chalk.green(` Added env: ${key}`));\n changed = true;\n }\n }\n\n settings[\"env\"] = existing;\n return changed;\n}\n\n/**\n * Strip file extension from a command basename for extension-agnostic dedup.\n * This ensures that e.g. \"context-compression-hook.ts\" and\n * \"context-compression-hook.mjs\" are treated as the same hook.\n */\nfunction commandStem(cmd: string): string {\n const base = cmd.split(\"/\").pop() ?? cmd;\n return base.replace(/\\.(mjs|ts|js|sh)$/, \"\");\n}\n\n/**\n * Collect every command string already registered for a given hookType.\n * Stores full command, basename, AND extension-stripped stem for flexible\n * matching (handles ${ADAPTER_DIR}/Hooks/foo.sh vs /Users/.../Hooks/foo.sh,\n * and .ts → .mjs migrations).\n */\nfunction existingCommandsForHookType(rules: HookRule[]): Set<string> {\n const cmds = new Set<string>();\n for (const rule of rules) {\n for (const entry of rule.hooks) {\n cmds.add(entry.command);\n // Also add the basename so expanded paths match template paths\n const base = entry.command.split(\"/\").pop();\n if (base) cmds.add(base);\n // Also add extension-stripped stem for cross-extension dedup\n cmds.add(commandStem(entry.command));\n }\n }\n return cmds;\n}\n\n/**\n * Find and remove an existing rule whose command has the same stem\n * (extension-agnostic) as the incoming command. Returns true if a\n * replacement was made. This handles .ts → .mjs migrations cleanly.\n */\nfunction replaceStaleHook(\n existingRules: HookRule[],\n incomingStem: string,\n incomingCommand: string,\n incomingMatcher: string | undefined,\n): boolean {\n for (let i = 0; i < existingRules.length; i++) {\n const rule = existingRules[i];\n for (let j = 0; j < rule.hooks.length; j++) {\n const existingStem = commandStem(rule.hooks[j].command);\n if (existingStem === incomingStem && rule.hooks[j].command !== incomingCommand) {\n // Replace the old command with the new one\n rule.hooks[j].command = incomingCommand;\n // Update matcher if provided\n if (incomingMatcher !== undefined) {\n rule.matcher = incomingMatcher;\n }\n return true;\n }\n }\n }\n return false;\n}\n\n/**\n * Merge hooks — append entries, deduplicating by command string.\n * Extension-agnostic: a .mjs hook replaces an existing .ts hook with the\n * same stem, ensuring clean .ts → .mjs migrations without duplicates.\n */\nfunction mergeHooks(\n settings: Record<string, unknown>,\n incoming: HookEntry[],\n report: string[],\n): boolean {\n let changed = false;\n\n const hooksSection = (\n typeof settings[\"hooks\"] === \"object\" && settings[\"hooks\"] !== null\n ? settings[\"hooks\"]\n : {}\n ) as HooksSection;\n\n for (const entry of incoming) {\n const { hookType, matcher, command } = entry;\n\n const existingRules: HookRule[] = Array.isArray(hooksSection[hookType])\n ? (hooksSection[hookType] as HookRule[])\n : [];\n\n const basename = command.split(\"/\").pop() ?? command;\n const stem = commandStem(command);\n\n // Exact match — already registered, skip\n const existingCmds = existingCommandsForHookType(existingRules);\n if (existingCmds.has(command) || existingCmds.has(basename)) {\n report.push(chalk.dim(` Skipped: hook ${hookType} → ${basename} already registered`));\n continue;\n }\n\n // Stem match with different extension — replace old entry (.ts → .mjs migration)\n if (existingCmds.has(stem)) {\n if (replaceStaleHook(existingRules, stem, command, matcher)) {\n hooksSection[hookType] = existingRules;\n report.push(chalk.yellow(` Upgraded: hook ${hookType} → ${basename} (replaced stale extension)`));\n changed = true;\n continue;\n }\n }\n\n // No match at all — append new rule\n const newRule: HookRule = {\n hooks: [{ type: \"command\", command }],\n };\n if (matcher !== undefined) {\n newRule.matcher = matcher;\n }\n\n existingRules.push(newRule);\n hooksSection[hookType] = existingRules;\n\n report.push(chalk.green(` Added hook: ${hookType} → ${basename}`));\n changed = true;\n }\n\n settings[\"hooks\"] = hooksSection;\n return changed;\n}\n\n/**\n * Merge statusLine — write only if the key is not already present.\n */\nfunction mergeStatusLine(\n settings: Record<string, unknown>,\n incoming: { type: string; command: string },\n report: string[],\n): boolean {\n if (Object.prototype.hasOwnProperty.call(settings, \"statusLine\")) {\n report.push(chalk.dim(\" Skipped: statusLine already configured\"));\n return false;\n }\n\n settings[\"statusLine\"] = { ...incoming };\n report.push(chalk.green(\" Added statusLine\"));\n return true;\n}\n\n/**\n * Merge permissions — append allow/deny entries, deduplicating.\n */\nfunction mergePermissions(\n settings: Record<string, unknown>,\n incoming: { allow?: string[]; deny?: string[] },\n report: string[],\n): boolean {\n let changed = false;\n\n const perms = (\n typeof settings[\"permissions\"] === \"object\" && settings[\"permissions\"] !== null\n ? settings[\"permissions\"]\n : {}\n ) as Record<string, string[]>;\n\n for (const list of [\"allow\", \"deny\"] as const) {\n const entries = incoming[list];\n if (!entries || entries.length === 0) continue;\n\n const existing: string[] = Array.isArray(perms[list]) ? perms[list] : [];\n const existingSet = new Set(existing);\n\n for (const entry of entries) {\n if (existingSet.has(entry)) {\n report.push(chalk.dim(` Skipped: permissions.${list} \"${entry}\" already present`));\n } else {\n existing.push(entry);\n existingSet.add(entry);\n report.push(chalk.green(` Added permissions.${list}: ${entry}`));\n changed = true;\n }\n }\n\n perms[list] = existing;\n }\n\n settings[\"permissions\"] = perms;\n return changed;\n}\n\n/**\n * Merge flags — set keys only if not already present, never overwrite.\n */\nfunction mergeFlags(\n settings: Record<string, unknown>,\n incoming: Record<string, unknown>,\n report: string[],\n): boolean {\n let changed = false;\n\n for (const [key, value] of Object.entries(incoming)) {\n if (Object.prototype.hasOwnProperty.call(settings, key)) {\n report.push(chalk.dim(` Skipped: ${key} already set`));\n } else {\n settings[key] = value;\n report.push(chalk.green(` Added flag: ${key}`));\n changed = true;\n }\n }\n\n return changed;\n}\n\n// ---------------------------------------------------------------------------\n// Public orchestrator\n// ---------------------------------------------------------------------------\n\n/**\n * Merge env vars, hooks, and/or a statusLine entry into ~/.claude/settings.json.\n * Never overwrites existing values — only adds what is missing.\n *\n * Returns { changed, report } where report contains human-readable lines.\n */\nexport function mergeSettings(opts: SettingsMergeOptions): MergeResult {\n const settings = readSettingsJson();\n const report: string[] = [];\n let changed = false;\n\n if (opts.env !== undefined && Object.keys(opts.env).length > 0) {\n if (mergeEnv(settings, opts.env, report)) changed = true;\n }\n\n if (opts.hooks !== undefined && opts.hooks.length > 0) {\n if (mergeHooks(settings, opts.hooks, report)) changed = true;\n }\n\n if (opts.statusLine !== undefined) {\n if (mergeStatusLine(settings, opts.statusLine, report)) changed = true;\n }\n\n if (opts.permissions !== undefined) {\n if (mergePermissions(settings, opts.permissions, report)) changed = true;\n }\n\n if (opts.flags !== undefined && Object.keys(opts.flags).length > 0) {\n if (mergeFlags(settings, opts.flags, report)) changed = true;\n }\n\n if (changed) {\n writeSettingsJson(settings);\n }\n\n return { changed, report };\n}\n","/** Steps 8b and 8: Assistant name prompt and settings.json patch. */\n\nimport { homedir } from \"node:os\";\nimport { join } from \"node:path\";\nimport chalk from \"chalk\";\nimport { mergeSettings } from \"../../settings-manager.js\";\nimport { c, line, section, type Rl, prompt, promptYesNo } from \"../utils.js\";\n\nexport async function stepDaName(rl: Rl): Promise<string> {\n section(\"Step 8b: Assistant Name\");\n line();\n line(\" Choose a name for your AI assistant. This name appears in tab titles\");\n line(\" and session notes when hooks are active.\");\n line();\n\n const answer = await prompt(rl, chalk.bold(\" Assistant name [PAI]: \"), \"\");\n const daName = answer || \"PAI\";\n line();\n console.log(c.ok(`Assistant name set to: ${daName}`));\n return daName;\n}\n\nexport async function stepSettings(rl: Rl, daName: string): Promise<boolean> {\n section(\"Step 8: Settings Patch\");\n line();\n line(\" PAI will add env vars, all hook registrations, permissions, and flags\");\n line(\" to ~/.claude/settings.json. Existing values are never overwritten.\");\n line();\n\n const patch = await promptYesNo(rl, \"Patch ~/.claude/settings.json with PAI hooks, env vars, and settings?\", true);\n if (!patch) {\n console.log(c.dim(\" Skipping settings patch.\"));\n return false;\n }\n\n const adapterDir = join(homedir(), \".claude\");\n\n const result = mergeSettings({\n env: {\n ADAPTER_DIR: adapterDir,\n CLAUDE_AUTOCOMPACT_PCT_OVERRIDE: \"80\",\n CLAUDE_CODE_MAX_OUTPUT_TOKENS: \"64000\",\n DA: daName,\n },\n hooks: [\n { hookType: \"SessionStart\", command: \"${ADAPTER_DIR}/Hooks/load-core-context.mjs\" },\n { hookType: \"SessionStart\", command: \"${ADAPTER_DIR}/Hooks/load-project-context.mjs\" },\n { hookType: \"SessionStart\", command: \"${ADAPTER_DIR}/Hooks/initialize-session.mjs\" },\n { hookType: \"SessionStart\", command: \"${ADAPTER_DIR}/Hooks/capture-all-events.mjs --event-type SessionStart\" },\n { hookType: \"SessionStart\", matcher: \"compact\", command: \"${ADAPTER_DIR}/Hooks/post-compact-inject.mjs\" },\n { hookType: \"UserPromptSubmit\", command: \"${ADAPTER_DIR}/Hooks/cleanup-session-files.mjs\" },\n { hookType: \"UserPromptSubmit\", command: \"${ADAPTER_DIR}/Hooks/update-tab-titles.mjs\" },\n { hookType: \"UserPromptSubmit\", command: \"${ADAPTER_DIR}/Hooks/capture-all-events.mjs --event-type UserPromptSubmit\" },\n // Rolling checkpoint. The model is not invoked on /exit and never on\n // Ctrl+C, so a handover written at exit time is impossible — it has to\n // already exist. Rate-limited internally; safe on high-frequency events.\n { hookType: \"UserPromptSubmit\", command: \"${ADAPTER_DIR}/Hooks/pai-session-autosave.sh\" },\n { hookType: \"PreToolUse\", matcher: \"Bash\", command: \"${ADAPTER_DIR}/Hooks/security-validator.mjs\" },\n { hookType: \"PreToolUse\", matcher: \"Bash|Edit|Write|MultiEdit|NotebookEdit|mcp__.*\", command: \"${ADAPTER_DIR}/Hooks/worker-guard.mjs\" },\n { hookType: \"PreToolUse\", matcher: \"*\", command: \"${ADAPTER_DIR}/Hooks/capture-all-events.mjs --event-type PreToolUse\" },\n { hookType: \"PostToolUse\", matcher: \"TodoWrite\", command: \"${ADAPTER_DIR}/Hooks/sync-todo-to-md.mjs\" },\n { hookType: \"PostToolUse\", matcher: \"*\", command: \"${ADAPTER_DIR}/Hooks/capture-all-events.mjs --event-type PostToolUse\" },\n { hookType: \"PostToolUse\", matcher: \"*\", command: \"${ADAPTER_DIR}/Hooks/capture-tool-output.mjs\" },\n { hookType: \"PostToolUse\", matcher: \"*\", command: \"${ADAPTER_DIR}/Hooks/update-tab-on-action.mjs\" },\n { hookType: \"PostToolUse\", matcher: \"*\", command: \"${ADAPTER_DIR}/Hooks/pai-session-autosave.sh\" },\n { hookType: \"Stop\", command: \"${ADAPTER_DIR}/Hooks/stop-hook.mjs\" },\n { hookType: \"Stop\", command: \"${ADAPTER_DIR}/Hooks/capture-all-events.mjs --event-type Stop\" },\n { hookType: \"Stop\", command: \"${ADAPTER_DIR}/Hooks/pai-session-stop.sh\" },\n { hookType: \"SubagentStop\", command: \"${ADAPTER_DIR}/Hooks/subagent-stop-hook.mjs\" },\n { hookType: \"SubagentStop\", command: \"${ADAPTER_DIR}/Hooks/capture-all-events.mjs --event-type SubagentStop\" },\n { hookType: \"SessionEnd\", command: \"${ADAPTER_DIR}/Hooks/capture-session-summary.mjs\" },\n { hookType: \"SessionEnd\", command: \"${ADAPTER_DIR}/Hooks/capture-all-events.mjs --event-type SessionEnd\" },\n { hookType: \"PreCompact\", command: \"${ADAPTER_DIR}/Hooks/context-compression-hook.mjs\" },\n { hookType: \"PreCompact\", command: \"${ADAPTER_DIR}/Hooks/capture-all-events.mjs --event-type PreCompact\" },\n { hookType: \"PreCompact\", matcher: \"\", command: \"${ADAPTER_DIR}/Hooks/pai-pre-compact.sh\" },\n ],\n statusLine: {\n type: \"command\",\n command: \"bash ${ADAPTER_DIR}/statusline-command.sh\",\n },\n permissions: {\n allow: [\"Bash\", \"Read\", \"Write\", \"Edit\", \"Glob\", \"Grep\", \"WebFetch\", \"WebSearch\", \"NotebookEdit\", \"TodoWrite\", \"ExitPlanMode\", \"mcp__pai\"],\n deny: [\"Bash(rm -rf /)\", \"Bash(rm -rf /*)\", \"Bash(rm -rf ~)\", \"Bash(rm -rf $HOME)\", \"Bash(sudo rm -rf /)\", \"Bash(sudo rm -rf /*)\"],\n },\n flags: {\n enableAllProjectMcpServers: true,\n },\n });\n\n line();\n for (const r of result.report) {\n console.log(r);\n }\n\n if (!result.changed) {\n console.log(c.dim(\" Settings already up-to-date. No changes made.\"));\n }\n\n return result.changed;\n}\n","/** Step 9: PAI daemon installation (launchd plist on macOS, systemd user unit on Linux). */\n\nimport { existsSync } from \"node:fs\";\nimport { homedir, platform } from \"node:os\";\nimport { join } from \"node:path\";\nimport { spawnSync } from \"node:child_process\";\nimport { daemonServiceName } from \"../../daemon.js\";\nimport { c, line, section, type Rl, promptYesNo } from \"../utils.js\";\n\nexport async function stepDaemon(rl: Rl): Promise<boolean> {\n section(\"Step 9: Daemon Install\");\n line();\n line(\" The PAI daemon indexes your projects every 5 minutes in the background.\");\n line();\n\n const linux = platform() === \"linux\";\n if (linux && !existsSync(\"/run/systemd/system\")) {\n console.log(c.dim(\" systemd is not running here (container?). Skipping service install; start the daemon with: pai daemon serve\"));\n return false;\n }\n\n const plistPath = linux\n ? join(homedir(), \".config\", \"systemd\", \"user\", \"pai-daemon.service\")\n : join(homedir(), \"Library\", \"LaunchAgents\", \"com.pai.pai-daemon.plist\");\n const unit = linux ? \"systemd user unit\" : \"launchd plist\";\n const exists = existsSync(plistPath);\n\n if (exists) {\n console.log(c.dim(` PAI daemon ${unit} already installed.`));\n line();\n\n const reinstall = await promptYesNo(rl, `Reinstall the PAI daemon ${unit}?`, false);\n if (!reinstall) {\n console.log(c.dim(\" Keeping existing daemon installation.\"));\n return false;\n }\n } else {\n const install = await promptYesNo(rl, \"Install the PAI daemon to run automatically at login?\", true);\n if (!install) {\n console.log(c.dim(\" Skipping daemon install. Run manually: pai daemon install\"));\n return false;\n }\n }\n\n line();\n const result = spawnSync(\"pai\", [\"daemon\", \"install\"], { stdio: \"inherit\" });\n\n if (result.status !== 0) {\n console.log(c.warn(\" Daemon install failed. Run manually: pai daemon install\"));\n return false;\n }\n\n console.log(c.ok(`Daemon installed as ${daemonServiceName()}.`));\n return true;\n}\n","/** Step 10: PAI MCP server registration in ~/.claude.json. */\n\nimport { existsSync, readFileSync } from \"node:fs\";\nimport { homedir } from \"node:os\";\nimport { join } from \"node:path\";\nimport { spawnSync } from \"node:child_process\";\nimport { c, line, section, type Rl, promptYesNo } from \"../utils.js\";\n\nexport async function stepMcp(rl: Rl): Promise<boolean> {\n section(\"Step 10: MCP Registration\");\n line();\n line(\" Registering the PAI MCP server (pai) lets Claude Code\");\n line(\" call PAI tools directly.\");\n line();\n\n const claudeJsonPath = join(homedir(), \".claude.json\");\n if (existsSync(claudeJsonPath)) {\n try {\n const raw = readFileSync(claudeJsonPath, \"utf-8\");\n const parsed = JSON.parse(raw) as Record<string, unknown>;\n const mcpServers = parsed[\"mcpServers\"] as Record<string, unknown> | undefined;\n if (\n mcpServers &&\n Object.prototype.hasOwnProperty.call(mcpServers, \"pai\")\n ) {\n console.log(c.ok(\"PAI MCP server (pai) already registered in ~/.claude.json.\"));\n console.log(c.dim(\" Skipping MCP registration.\"));\n return false;\n }\n } catch {\n // continue\n }\n }\n\n const register = await promptYesNo(rl, \"Register the PAI MCP server (pai) in ~/.claude.json?\", true);\n if (!register) {\n console.log(c.dim(\" Skipping MCP registration. Run manually: pai mcp install\"));\n return false;\n }\n\n line();\n const result = spawnSync(\"pai\", [\"mcp\", \"install\"], { stdio: \"inherit\" });\n\n if (result.status !== 0) {\n console.log(c.warn(\" MCP registration failed. Run manually: pai mcp install\"));\n return false;\n }\n\n console.log(c.ok(\"PAI MCP server (pai) registered in ~/.claude.json.\"));\n return true;\n}\n","/** Step 11: Directory scanning configuration and registry scan prompt. */\n\nimport { existsSync } from \"node:fs\";\nimport { homedir } from \"node:os\";\nimport { join } from \"node:path\";\nimport chalk from \"chalk\";\nimport { line, section, type Rl, promptYesNo } from \"../utils.js\";\n\nexport async function stepDirectories(rl: Rl): Promise<void> {\n section(\"Step 11: Directories to Index\");\n line();\n line(\" PAI indexes files in your registered projects. You can register projects\");\n line(\" individually with `pai project add <path>`, or let the registry scanner\");\n line(\" discover them automatically with `pai registry scan`.\");\n line();\n\n const defaults = [\n join(homedir(), \"Projects\"),\n join(homedir(), \"Documents\"),\n join(homedir(), \"dev\"),\n ].filter(existsSync);\n\n if (defaults.length > 0) {\n line(\" These directories exist on your system:\");\n for (const d of defaults) {\n console.log(chalk.dim(` ${d}`));\n }\n line();\n }\n\n const runScan = await promptYesNo(rl, \"Run `pai registry scan` to auto-detect projects after setup?\", false);\n\n if (runScan) {\n line();\n console.log(chalk.dim(\" Registry scan will run after setup completes.\"));\n } else {\n console.log(chalk.dim(\" Add projects manually: pai project add <path>\"));\n console.log(chalk.dim(\" Or discover them later: pai registry scan\"));\n }\n\n (stepDirectories as { _runScan?: boolean })._runScan = runScan;\n}\n","/** Step 12: Initial index — optionally starts the daemon and runs registry scan. */\n\nimport { spawnSync } from \"node:child_process\";\nimport chalk from \"chalk\";\nimport { c, line, section, type Rl, promptYesNo } from \"../utils.js\";\nimport { stepDirectories } from \"./13-directories.js\";\n\nexport async function stepInitialIndex(rl: Rl): Promise<void> {\n section(\"Step 12: Initial Index\");\n line();\n line(\" Indexing scans your registered projects and builds the search index.\");\n line(\" The daemon runs indexing automatically every 5 minutes once started.\");\n line();\n\n const willScan = (stepDirectories as { _runScan?: boolean })._runScan;\n\n if (willScan) {\n const startDaemon = await promptYesNo(rl, \"Start the PAI daemon now? (enables background indexing)\", true);\n\n if (startDaemon) {\n line();\n console.log(c.dim(\" Starting daemon...\"));\n\n try {\n const result = spawnSync(\"pai\", [\"daemon\", \"serve\", \"--background\"], { stdio: \"pipe\", timeout: 10000 });\n if (result.status === 0) {\n console.log(c.ok(\"Daemon started in background.\"));\n } else {\n console.log(c.warn(\"Could not start daemon. Run manually: pai daemon serve\"));\n }\n } catch {\n console.log(c.warn(\"Could not start daemon. Run manually: pai daemon serve\"));\n }\n\n line();\n console.log(c.dim(\" Running registry scan to detect projects...\"));\n\n try {\n const result = spawnSync(\"pai\", [\"registry\", \"scan\"], { stdio: \"inherit\", timeout: 30000 });\n if (result.status !== 0) {\n console.log(c.warn(\"Registry scan encountered issues. Run `pai registry scan` manually.\"));\n }\n } catch {\n console.log(c.warn(\"Could not run registry scan. Run manually: pai registry scan\"));\n }\n } else {\n console.log(chalk.dim(\" Start the daemon later: pai daemon serve\"));\n console.log(chalk.dim(\" Scan projects later: pai registry scan\"));\n }\n } else {\n console.log(chalk.dim(\" Register projects with: pai project add <path>\"));\n console.log(chalk.dim(\" Then index them with: pai memory index --all\"));\n console.log(chalk.dim(\" Or start the daemon: pai daemon serve\"));\n }\n}\n","/** Step 13: Setup summary — displays all configuration choices made during setup. */\n\nimport chalk from \"chalk\";\nimport { CONFIG_FILE } from \"../../../../daemon/config.js\";\nimport { daemonServiceName } from \"../../daemon.js\";\nimport { line, section } from \"../utils.js\";\n\nexport function stepSummary(\n configUpdates: Record<string, unknown>,\n claudeMdGenerated: boolean,\n paiSkillInstalled: boolean,\n aiSteeringRulesInstalled: boolean,\n skillStubsInstalled: boolean,\n hooksInstalled: boolean,\n tsHooksInstalled: boolean,\n settingsPatched: boolean,\n daName: string,\n daemonInstalled: boolean,\n mcpRegistered: boolean,\n plat: NodeJS.Platform = process.platform,\n): void {\n section(\"Setup Complete\");\n line();\n console.log(chalk.green(\" PAI Knowledge OS is configured!\"));\n line();\n\n const backend = configUpdates.storageBackend as string;\n const model = configUpdates.embeddingModel as string;\n\n line(chalk.bold(\" Configuration saved to: \") + chalk.dim(CONFIG_FILE));\n line();\n console.log(chalk.dim(\" Storage backend: \") + chalk.cyan(backend ?? \"sqlite\"));\n console.log(chalk.dim(\" Embedding model: \") + chalk.cyan(model && model !== \"none\" ? model : \"(none — keyword search only)\"));\n console.log(chalk.dim(\" CLAUDE.md: \") + chalk.cyan(claudeMdGenerated ? \"~/.claude/CLAUDE.md (generated)\" : \"(unchanged)\"));\n console.log(chalk.dim(\" PAI skill: \") + chalk.cyan(paiSkillInstalled ? \"~/.claude/skills/PAI/SKILL.md (installed)\" : \"(unchanged)\"));\n console.log(chalk.dim(\" Steering rules: \") + chalk.cyan(aiSteeringRulesInstalled ? \"~/.claude/skills/PAI/AI-STEERING-RULES.md (installed)\" : \"(unchanged)\"));\n console.log(chalk.dim(\" Skill stubs: \") + chalk.cyan(skillStubsInstalled ? \"18 MCP prompt stubs symlinked to ~/.claude/skills/\" : \"(unchanged)\"));\n console.log(chalk.dim(\" Hooks (shell): \") + chalk.cyan(hooksInstalled ? \"pai-pre-compact.sh, pai-session-stop.sh (installed)\" : \"(unchanged)\"));\n console.log(chalk.dim(\" Hooks (TS): \") + chalk.cyan(tsHooksInstalled ? \"14 .mjs hooks installed to ~/.claude/Hooks/\" : \"(unchanged)\"));\n console.log(chalk.dim(\" Assistant name: \") + chalk.cyan(daName));\n console.log(chalk.dim(\" Settings: \") + chalk.cyan(settingsPatched ? \"env vars, hooks, permissions, flags (patched)\" : \"(unchanged)\"));\n console.log(chalk.dim(\" Daemon: \") + chalk.cyan(daemonInstalled ? `${daemonServiceName(plat)} (installed)` : \"(not installed by setup)\"));\n console.log(chalk.dim(\" MCP: \") + chalk.cyan(mcpRegistered ? \"registered in ~/.claude.json\" : \"(unchanged)\"));\n line();\n console.log(chalk.bold.yellow(\" → RESTART Claude Code to activate all changes.\"));\n line();\n\n line(chalk.bold(\" Next steps:\"));\n line();\n console.log(chalk.dim(\" # Register a project\"));\n console.log(chalk.cyan(\" pai project add ~/your/project\"));\n line();\n console.log(chalk.dim(\" # Index your files\"));\n console.log(chalk.cyan(\" pai memory index --all\"));\n line();\n console.log(chalk.dim(\" # Search your knowledge\"));\n console.log(chalk.cyan(\" pai memory search \\\"your query\\\"\"));\n line();\n if (model && model !== \"none\") {\n console.log(chalk.dim(\" # Generate embeddings for semantic search\"));\n console.log(chalk.cyan(\" pai memory embed\"));\n line();\n console.log(chalk.dim(\" # Semantic search\"));\n console.log(chalk.cyan(\" pai memory search --mode semantic \\\"your query\\\"\"));\n line();\n }\n console.log(chalk.dim(\" # Start the background daemon\"));\n console.log(chalk.cyan(\" pai daemon serve\"));\n line();\n console.log(chalk.dim(\" # Show all commands\"));\n console.log(chalk.cyan(\" pai --help\"));\n line();\n}\n","/**\n * resolver.ts — Ownership resolution for the task bus\n *\n * Maps a tracker task onto the PAI project that owns it.\n *\n * 1. an explicit `pai:<project>` label (authoritative)\n * 2. the enclosing container name (fallback, matches the sub-project mirror)\n * 3. otherwise UNROUTED (normal state — the findings inbox)\n *\n * Label wins because labels survive a task being moved between containers, and\n * because a task parked in the findings inbox has no meaningful container yet.\n *\n * See docs/task-bus.md.\n */\n\nimport { getRegistryBackend } from \"../storage/factory.js\";\nimport {\n OWNER_LABEL_PREFIX,\n UNROUTED,\n type TaskOwner,\n} from \"./types.js\";\n\n// ---------------------------------------------------------------------------\n// Alias map\n// ---------------------------------------------------------------------------\n\nexport interface AliasTarget {\n /** The alias itself — what `pai <name>` and aibroker_pai_launch accept. */\n alias: string;\n slug: string;\n rootPath: string;\n}\n\n/** Normalized alias → target. Keys are the output of `normalize()`. */\nexport type AliasMap = Map<string, AliasTarget>;\n\n/**\n * Fold a tracker-supplied string onto its comparable form.\n *\n * Container names are written for humans and carry decoration the registry\n * never sees: \"Reading List 📚\", \"AcmeAPI\", \"Client Work\". Strip the decoration\n * rather than demanding the user keep two naming schemes in sync.\n *\n * Emoji and pictographs are removed, separators collapse to a single hyphen,\n * and the result is lowercased — so \"Client Work\" and \"client-work\" agree.\n */\nexport function normalize(raw: string): string {\n return raw\n .normalize(\"NFKD\")\n // Drop emoji, pictographs, dingbats and variation selectors.\n .replace(/[\\u{1F000}-\\u{1FAFF}\\u{2600}-\\u{27BF}\\u{FE0F}\\u{200D}]/gu, \"\")\n .replace(/[^\\p{L}\\p{N}]+/gu, \"-\")\n .replace(/^-+|-+$/g, \"\")\n .toLowerCase();\n}\n\n/**\n * Load every alias registered in the PAI registry.\n *\n * Reads the curated shortlist only — the `aliases` table — deliberately. The\n * full registry commonly carries hundreds of projects with no aliases and\n * genuine ambiguity — separate entries routinely share a display name at\n * different paths — so widening this query resolves tasks to the wrong\n * directory. That failure is silent, which makes it worse than no match. Bus\n * participation is opt-in via `pai project name <identifier> <shortname>`.\n */\nexport async function loadAliasMap(): Promise<AliasMap> {\n const registry = await getRegistryBackend();\n const rows = await registry.listAliasMap();\n\n const map: AliasMap = new Map();\n for (const row of rows) {\n const target: AliasTarget = {\n alias: row.alias,\n slug: row.slug,\n rootPath: row.root_path,\n };\n map.set(normalize(row.alias), target);\n // The slug is also addressable, so a task may name either.\n // Aliases win: they are the curated, intentional handle.\n const slugKey = normalize(row.slug);\n if (!map.has(slugKey)) map.set(slugKey, target);\n }\n return map;\n}\n\n// ---------------------------------------------------------------------------\n// Resolution\n// ---------------------------------------------------------------------------\n\n/** Extract the project named by a `pai:<project>` label, if any. */\nexport function ownerLabel(labels: string[]): string | null {\n for (const label of labels) {\n const trimmed = label.trim();\n if (trimmed.toLowerCase().startsWith(OWNER_LABEL_PREFIX)) {\n const value = trimmed.slice(OWNER_LABEL_PREFIX.length).trim();\n if (value) return value;\n }\n }\n return null;\n}\n\nexport interface ResolveInput {\n labels: string[];\n /** Name of the enclosing tracker container (sub-project), when there is one. */\n container?: string | null;\n /**\n * Where a task marked only with the bare `pai` marker goes. Omit to leave\n * such a task unrouted, which sends it to the findings inbox for triage.\n */\n defaultOwner?: string | null;\n}\n\n/**\n * Does this task carry the bare `pai` marker?\n *\n * The marker means \"an AI should take this, and I do not know which one yet\" —\n * the single thing a task's location cannot express, and the reason to keep any\n * label mechanism at all. Everything else is answered by the project it sits\n * in, which is single-valued, visible, and changed by the act of moving it.\n *\n * Deliberately distinct from `pai:<name>`: that names a specific owner, this\n * names none. Previously a bare `pai` was a near miss — it looked like an\n * address and resolved to nothing — which meant the most natural thing to type\n * was the one thing that silently did nothing.\n */\nexport function hasDefaultMarker(labels: string[]): boolean {\n return labels.some((l) => l.trim().toLowerCase() === \"pai\");\n}\n\n/**\n * Resolve a task's owner.\n *\n * An unresolvable task is never an error: it returns UNROUTED with `rawHint`\n * set to whatever was tried, so a routine can say *why* it could not route it\n * rather than dropping it silently. A container like \"Reading List 📚\" maps to\n * no PAI project — that is expected, not a fault.\n */\nexport function resolveOwner(input: ResolveInput, aliases: AliasMap): TaskOwner {\n // CONTAINER FIRST. A task sits in exactly one project; it can carry many\n // labels. A multi-valued field cannot be the authoritative owner — with two\n // `pai:` labels there is no principled winner, only whichever the code\n // happens to see first.\n //\n // Labels also survive a move. On 2026-08-02 a task was moved from Clickr to\n // AIBroker in the tracker UI and a comment on it was delivered to Clickr,\n // because a label from its old home outranked the project it now sat in. The\n // user had not seen the label and had not used labels in months. A field\n // nobody is looking at should not outrank the one they just changed\n // deliberately.\n //\n // So: the container decides whenever it resolves. Moving a task is then the\n // whole act of re-assigning it, which is what someone dragging it between\n // projects already believes they are doing.\n if (input.container) {\n const hit = aliases.get(normalize(input.container));\n if (hit) {\n return {\n project: hit.alias,\n rootPath: hit.rootPath,\n source: \"container\",\n };\n }\n }\n\n // Labels remain the way to address a task whose container says nothing —\n // sitting in the Inbox, at the bus root, or in a project that mirrors no PAI\n // project. That is the case they are good at, and the only one where nothing\n // else can express the intent.\n const label = ownerLabel(input.labels);\n if (label) {\n const hit = aliases.get(normalize(label));\n if (hit) {\n return {\n project: hit.alias,\n rootPath: hit.rootPath,\n source: \"label\",\n };\n }\n // An explicit label that resolves to nothing is worth surfacing: someone\n // meant to route this somewhere and named a project that does not exist.\n return { ...UNROUTED, rawHint: label };\n }\n\n // The bare marker: an AI should take this, nobody said which. Last, because\n // anything more specific is a better answer — this is the Inbox case, where\n // there is no location to read and no owner named.\n if (input.defaultOwner && hasDefaultMarker(input.labels)) {\n const hit = aliases.get(normalize(input.defaultOwner));\n if (hit) {\n return { project: hit.alias, rootPath: hit.rootPath, source: \"label\" };\n }\n return { ...UNROUTED, rawHint: input.defaultOwner };\n }\n\n // Container present but unrecognised, and nothing else to go on. Report what\n // was tried — \"Reading List 📚\" maps to no PAI project, which is expected\n // rather than a fault.\n if (input.container) return { ...UNROUTED, rawHint: input.container };\n\n return { ...UNROUTED };\n}\n","/**\n * todoist.ts — Todoist provider for the task bus\n *\n * Talks to the Todoist REST API directly rather than going through the Todoist\n * MCP. The MCP is scoped to a Claude session; the daemon has no session, so an\n * MCP-only integration could never run the bus unattended.\n *\n * See docs/task-bus.md.\n */\n\nimport type {\n ListOptions,\n NewTask,\n Task,\n TaskPriority,\n TaskProvider,\n TodoistProviderConfig,\n} from \"../types.js\";\nimport { UNROUTED } from \"../types.js\";\nimport type { AliasMap } from \"../resolver.js\";\nimport { resolveOwner } from \"../resolver.js\";\n\n/**\n * Unified API v1. The older `/rest/v2` endpoints now return 410 Gone — they\n * were sunset, so anything still targeting them fails outright rather than\n * degrading.\n */\nconst API = \"https://api.todoist.com/api/v1\";\n\n// ---------------------------------------------------------------------------\n// Wire types (only the fields we rely on)\n// ---------------------------------------------------------------------------\n\n/** v1 wraps every collection and pages with an opaque cursor. */\ninterface WirePage<T> {\n results: T[];\n next_cursor?: string | null;\n}\n\ninterface WireProject {\n id: string;\n name: string;\n parent_id?: string | null;\n is_archived?: boolean;\n is_deleted?: boolean;\n /**\n * True when the project has collaborators — i.e. someone other than the user\n * can create tasks in it. See ownerContainers for why that matters here.\n */\n is_shared?: boolean;\n}\n\ninterface WireTask {\n id: string;\n content: string;\n description?: string;\n project_id: string;\n section_id?: string | null;\n labels?: string[];\n priority?: number; // 4 = p1 … 1 = p4\n due?: { date?: string; datetime?: string; string?: string; is_recurring?: boolean } | null;\n /** v1 returns completed and tombstoned tasks inline; both must be filtered. */\n checked?: boolean;\n is_deleted?: boolean;\n}\n\n// ---------------------------------------------------------------------------\n// HTTP\n// ---------------------------------------------------------------------------\n\nasync function call<T>(\n token: string,\n path: string,\n init?: { method?: string; body?: unknown },\n): Promise<T> {\n const res = await fetch(`${API}${path}`, {\n method: init?.method ?? \"GET\",\n headers: {\n Authorization: `Bearer ${token}`,\n ...(init?.body ? { \"Content-Type\": \"application/json\" } : {}),\n },\n body: init?.body ? JSON.stringify(init.body) : undefined,\n });\n\n if (!res.ok) {\n // 401 is by far the most common failure and the least self-explanatory,\n // so name the cause rather than surfacing a bare status code.\n const hint = res.status === 401 ? \" (token rejected — check it was copied in full)\" : \"\";\n throw new Error(`Todoist ${res.status} ${res.statusText}${hint}`);\n }\n\n // 204 on delete/close — nothing to parse.\n if (res.status === 204) return undefined as T;\n return (await res.json()) as T;\n}\n\n/**\n * Drain a paginated collection.\n *\n * Every v1 list endpoint pages. Stopping at the first page would silently\n * truncate — the account here already exceeds one page of tasks — so follow the\n * cursor to exhaustion. The guard is a safety stop against a server that keeps\n * handing back the same cursor, not an intentional cap.\n */\nasync function collect<T>(token: string, path: string): Promise<T[]> {\n const out: T[] = [];\n let cursor: string | null | undefined;\n let guard = 0;\n\n do {\n const sep = path.includes(\"?\") ? \"&\" : \"?\";\n const url = cursor ? `${path}${sep}cursor=${encodeURIComponent(cursor)}` : path;\n const page = await call<WirePage<T>>(token, url);\n out.push(...(page.results ?? []));\n cursor = page.next_cursor;\n } while (cursor && ++guard < 100);\n\n return out;\n}\n\n/**\n * List every live project on the account.\n *\n * Exported because setup uses it both to let the user pick the bus root and to\n * validate the token — a bad token fails here, at install time, rather than\n * silently returning nothing during a routine.\n */\n/** A bare calendar date — the only shape `due_date` accepts. */\nconst ISO_DATE = /^\\d{4}-\\d{2}-\\d{2}$/;\n\n/** A project inside the bus, and the sub-project whose name decides its owner. */\nexport interface OwnerContainer {\n project: WireProject;\n /** Name of the nearest ancestor directly under the root. Null at the root. */\n ownerName: string | null;\n}\n\n/**\n * Every project in the bus subtree, each mapped to the name that owns it.\n *\n * Ownership belongs to the sub-projects directly under the root — they are the\n * ones mirroring PAI projects. Anything deeper is a FOLDER: a way to group work\n * inside one owner's inbox, not a new owner. So `Claude / Jobs Alpha /\n * Executive Search` is eighteen tasks belonging to Jobs Alpha, filed under a\n * heading.\n *\n * This used to take the root and its direct children only. A grandchild was not\n * merely unrouted, it was never QUERIED — its tasks did not exist as far as the\n * bus was concerned. Which is the worst possible failure for this: the user\n * organises work into a sub-project, it looks tidy, and nothing is ever\n * dispatched. Observed 2026-08-02 with exactly the tree above.\n *\n * Descends from the root rather than walking up from each project, so a cycle\n * in the parent chain — which the API should never produce, but a bad write\n * could — cannot hang the poller.\n */\nexport function ownerContainers(\n projects: WireProject[],\n root: string\n): Map<string, OwnerContainer> {\n const live = projects.filter((p) => !p.is_archived && !p.is_deleted);\n const childrenOf = new Map<string, WireProject[]>();\n for (const p of live) {\n if (!p.parent_id) continue;\n const siblings = childrenOf.get(p.parent_id);\n if (siblings) siblings.push(p);\n else childrenOf.set(p.parent_id, [p]);\n }\n\n const out = new Map<string, OwnerContainer>();\n const rootProject = live.find((p) => p.id === root);\n if (!rootProject) return out;\n\n // The root carries no owner name: a task sitting there is unrouted, which is\n // how the findings inbox is meant to work.\n out.set(root, { project: rootProject, ownerName: null });\n\n const walk = (project: WireProject, ownerName: string): void => {\n if (out.has(project.id)) return; // cycle guard\n\n // A shared project is excluded, along with everything under it.\n //\n // Inheriting ownership down a subtree means a project only has to be MOVED\n // under the root to gain the right to dispatch work into a session. For a\n // project the user owns that is the entire point. For a shared one it is an\n // escalation: a collaborator writes the task, and the task is a work order\n // that spawns a terminal and types into it. Nobody reparents a project\n // thinking about that.\n //\n // Excluded rather than merely unrouted, and announced rather than dropped\n // quietly — a boundary that narrows in silence is the same defect as one\n // that widens in silence. AIBroker reached the same conclusion from the\n // other side on 2026-08-02 and made subtree grants opt-in; this is the\n // equivalent for a poller that has no grant to opt into.\n if (project.is_shared) {\n process.stderr.write(\n `pai: skipping shared project \"${project.name}\" (${project.id}) and anything under it — ` +\n `tasks there can be written by a collaborator, and a dispatched task runs as you.\\n`\n );\n return;\n }\n\n out.set(project.id, { project, ownerName });\n for (const child of childrenOf.get(project.id) ?? []) walk(child, ownerName);\n };\n\n for (const top of childrenOf.get(root) ?? []) walk(top, top.name);\n\n return out;\n}\n\n/**\n * Report a write that the server silently shortened.\n *\n * Todoist caps a task description at 16,383 characters and enforces it by\n * TRUNCATION, not by rejection: the request returns 200 and the response says\n * success. Measured 2026-08-02 — a 19,457-character runbook stored as 16,383,\n * losing 3,074 characters off the end with no error anywhere. What went missing\n * was the close-out section, so a session working from it would have done the\n * job correctly and then never marked the task done.\n *\n * The general form is worth more than the specific limit, which is why this\n * compares what came back rather than checking a number: an API that reports\n * success for an operation that did not fully happen is invisible until someone\n * inspects the artifact instead of the return value. Any sink with an unknown\n * limit deserves the same treatment.\n *\n * Warns rather than throws. The task exists and is usable; failing the call\n * would discard work that mostly succeeded. But it must not pass silently.\n *\n * THIS DOES NOT PREVENT THE LOSS. The truncation still happens and the tail is\n * still gone — all this does is turn a silent data-loss bug into a loud one.\n * That is a large improvement and it is not a resolution, and the difference\n * matters because the obvious wrong conclusion from reading this function is\n * \"handled, so long content is fine now\". It is not: whether anything is done\n * about the warning depends on someone reading stderr. Long content belongs in\n * a file, with the field pointing at it. Visible failure is not absence of\n * failure.\n */\nexport function warnIfTruncated(field: string, sent: string | undefined, stored: string | undefined): void {\n const before = sent?.length ?? 0;\n const after = stored?.length ?? 0;\n if (before === 0 || after >= before) return;\n\n process.stderr.write(\n `\\npai: WARNING — Todoist truncated the ${field}.\\n` +\n ` sent ${before} characters, stored ${after} — ${before - after} lost, silently.\\n` +\n ` The API reported success. Anything at the end of that text is gone.\\n` +\n ` Put long content in a file and reference it from the ${field} instead.\\n\\n`\n );\n}\n\n/**\n * Build the due portion of a task write.\n *\n * Exported for testing: the whole defect was a one-word field choice, so the\n * choice itself is what needs pinning.\n */\nexport function dueField(\n due: string | null | undefined\n): Record<string, string> {\n const value = due?.trim();\n if (!value) return {};\n return ISO_DATE.test(value) ? { due_date: value } : { due_string: value };\n}\n\nexport async function listProjects(token: string): Promise<Array<{ id: string; name: string }>> {\n const projects = await collect<WireProject>(token, \"/projects\");\n return projects\n .filter((p) => !p.is_archived && !p.is_deleted)\n .map((p) => ({ id: p.id, name: p.name }));\n}\n\n// ---------------------------------------------------------------------------\n// Mapping\n// ---------------------------------------------------------------------------\n\n/** Strip emoji and case so \"Whazaa 🐝\" and \"whazaa\" compare equal. */\nfunction normalizeName(raw: string): string {\n return raw\n .replace(/[\\u{1F000}-\\u{1FAFF}\\u{2600}-\\u{27BF}\\u{FE0F}\\u{200D}]/gu, \"\")\n .trim()\n .toLowerCase();\n}\n\n/** Todoist priority is inverted: 4 is urgent, 1 is lowest. */\nfunction toPriority(wire: number | undefined): TaskPriority {\n switch (wire) {\n case 4: return \"p1\";\n case 3: return \"p2\";\n case 2: return \"p3\";\n default: return \"p4\";\n }\n}\n\nfunction fromPriority(p: TaskPriority | undefined): number {\n switch (p) {\n case \"p1\": return 4;\n case \"p2\": return 3;\n case \"p3\": return 2;\n default: return 1;\n }\n}\n\n// ---------------------------------------------------------------------------\n// Provider\n// ---------------------------------------------------------------------------\n\nexport class TodoistProvider implements TaskProvider {\n readonly providerId = \"todoist\" as const;\n\n constructor(\n private readonly config: TodoistProviderConfig,\n private readonly aliases: AliasMap,\n /** Bus-level settings. Optional so existing callers keep working. */\n private readonly taskConfig?: { defaultOwner?: string },\n ) {}\n\n /**\n * Resolution order is config → env → unconfigured. A missing token disables\n * the provider rather than throwing: a user without a tracker still gets a\n * fully working PAI.\n */\n private token(): string | null {\n return this.config.apiKey?.trim() || process.env.TODOIST_API_KEY?.trim() || null;\n }\n\n isConfigured(): boolean {\n return Boolean(this.config.enabled && this.token() && this.config.rootProjectId);\n }\n\n /** Owner for a task carrying only the bare `pai` marker. See ResolveInput. */\n private get defaultOwner(): string | null {\n return this.taskConfig?.defaultOwner ?? null;\n }\n\n /**\n * Fetch one task by id, open or completed.\n *\n * `listOpen` cannot serve this: a completed task is gone from that list, and\n * the whole point of archiving is to run at or after completion. Ownership is\n * resolved the same way as in listOpen — label first, then the container\n * walked up to the sub-project directly under the bus root.\n */\n async getTask(id: string): Promise<Task | null> {\n const token = this.token();\n if (!token || !this.config.rootProjectId) return null;\n\n let w: WireTask;\n try {\n w = await call<WireTask>(token, `/tasks/${encodeURIComponent(id)}`);\n } catch {\n return null;\n }\n if (!w?.id) return null;\n\n const projects = await collect<WireProject>(token, \"/projects\");\n const owners = ownerContainers(projects, this.config.rootProjectId);\n const entry = owners.get(w.project_id ?? \"\");\n\n const owner = resolveOwner(\n { labels: w.labels ?? [], container: entry?.ownerName ?? null, defaultOwner: this.defaultOwner },\n this.aliases\n );\n\n return {\n id: w.id,\n title: w.content,\n body: w.description ?? \"\",\n owner,\n due: w.due?.datetime ?? w.due?.date ?? null,\n recurrence: w.due?.is_recurring ? (w.due.string ?? null) : null,\n priority: toPriority(w.priority),\n labels: w.labels ?? [],\n sourceUrl: `https://app.todoist.com/app/task/${w.id}`,\n };\n }\n\n async listOpen(opts: ListOptions = {}): Promise<Task[]> {\n const token = this.token();\n if (!token || !this.config.rootProjectId) return [];\n\n // Fetch the project tree once and keep only the bus subtree. Filtering\n // client-side is deliberate: Todoist's project search returns zero results\n // for names containing emoji, so a name query would silently find nothing.\n const projects = await collect<WireProject>(token, \"/projects\");\n const root = this.config.rootProjectId;\n const owners = ownerContainers(projects, root);\n if (owners.size === 0) return [];\n const inBus = new Map([...owners].map(([id, o]) => [id, o.project]));\n\n // Query per bus project rather than draining every task on the account.\n // The account may hold thousands; the bus holds a handful.\n const wire: WireTask[] = [];\n for (const id of inBus.keys()) {\n wire.push(...(await collect<WireTask>(token, `/tasks?project_id=${encodeURIComponent(id)}`)));\n }\n\n const out: Task[] = [];\n\n for (const w of wire) {\n if (w.checked || w.is_deleted) continue;\n\n const entry = owners.get(w.project_id);\n if (!entry) continue;\n\n // The owning name is the nearest ancestor directly under the root, NOT\n // the project the task literally sits in. The bus root itself is not an\n // owner — a task at the root has no container hint.\n const owner = resolveOwner(\n { labels: w.labels ?? [], container: entry.ownerName, defaultOwner: this.defaultOwner },\n this.aliases\n );\n\n if (opts.owner && owner.project !== opts.owner) continue;\n if (opts.includeUnrouted === false && owner.project === null) continue;\n\n const due = w.due?.datetime ?? w.due?.date ?? null;\n if (opts.dueBefore) {\n // No due date means no deadline to miss — such tasks are never overdue,\n // so they are excluded from a date-bounded sweep rather than always shown.\n if (!due || due.slice(0, 10) > opts.dueBefore) continue;\n }\n\n out.push({\n id: w.id,\n title: w.content,\n body: w.description ?? \"\",\n owner,\n due,\n recurrence: w.due?.is_recurring ? (w.due.string ?? null) : null,\n priority: toPriority(w.priority),\n labels: w.labels ?? [],\n // v1 does not return a task URL. Build the deep link from the ID so a\n // dispatched task is still one click from the tracker.\n sourceUrl: `https://app.todoist.com/app/task/${w.id}`,\n });\n\n if (opts.limit && out.length >= opts.limit) break;\n }\n\n return out;\n }\n\n /**\n * Choose the wire field for a due value.\n *\n * `due_date` takes an ISO date and nothing else — \"tomorrow\" or \"every day\"\n * come back as HTTP 400. Recurrence in particular is only expressible through\n * `due_string`, which is the field Todoist runs its natural-language parser\n * over. Sending everything as a date is what made `pai task add --repeat`\n * impossible to write, and a repeating task is the whole mechanism behind a\n * self-rescheduling routine on the bus.\n *\n * Strict ISO still goes to `due_date` rather than through the parser. The\n * parser is locale-sensitive and there is no reason to hand it a value that\n * is already unambiguous — this keeps existing ISO callers on exactly the\n * path they were on.\n */\n async add(task: NewTask): Promise<Task> {\n const token = this.token();\n if (!token || !this.config.rootProjectId) {\n throw new Error(\"Todoist provider is not configured — run `pai setup`.\");\n }\n\n // Prefer LOCATION over a label. Ownership resolves from the project a task\n // sits in, so filing it into the owner's sub-project says the same thing\n // the label would — visibly, in one place, and in the field someone changes\n // when they re-assign it. A label added here would only be a second, quieter\n // answer to the same question, and one that survives a move: on 2026-08-02 a\n // task moved between projects was still routed by its old label.\n //\n // Only when no sub-project mirrors this owner does the label do real work,\n // and then it is the sole way to express the intent.\n let ownerProjectId: string | null = null;\n if (task.owner && !task.into) {\n ownerProjectId = await this.subProjectForOwner(task.owner);\n }\n\n const labels = [...(task.labels ?? [])];\n const needsLabel = task.owner && !task.into && !ownerProjectId;\n if (\n needsLabel &&\n !labels.some((l) => l.toLowerCase() === `pai:${task.owner!.toLowerCase()}`)\n ) {\n labels.push(`pai:${task.owner}`);\n }\n\n // Unowned work lands in the findings inbox when one is configured, so it is\n // triaged later rather than lost at the root.\n const sectionId = task.owner || task.into ? undefined : this.config.findingsSectionId;\n\n // Filing into a per-project sub-project keeps findings from piling up flat\n // in the root, where they bury each other across projects.\n const projectId = task.into\n ? (await this.findOrCreateSubProject(task.into)).id\n : (ownerProjectId ?? this.config.rootProjectId);\n\n const created = await call<WireTask>(token, \"/tasks\", {\n method: \"POST\",\n body: {\n content: task.title,\n description: task.body,\n project_id: projectId,\n ...(sectionId ? { section_id: sectionId } : {}),\n labels,\n priority: fromPriority(task.priority),\n ...dueField(task.due),\n },\n });\n\n // The POST already returns what was stored, so this costs nothing.\n warnIfTruncated(\"task description\", task.body, created.description ?? \"\");\n\n return {\n id: created.id,\n title: created.content,\n body: created.description ?? \"\",\n owner: task.owner\n ? resolveOwner({ labels, container: null, defaultOwner: this.defaultOwner }, this.aliases)\n : { ...UNROUTED },\n due: created.due?.datetime ?? created.due?.date ?? null,\n priority: toPriority(created.priority),\n labels: created.labels ?? [],\n sourceUrl: `https://app.todoist.com/app/task/${created.id}`,\n };\n }\n\n /**\n * Replace a task's labels.\n *\n * Only ever sends `labels`. Never `due_date`: writing that field destroys a\n * recurrence rule, silently turning a routine into a one-off. Measured — it\n * cost me an invalid test before it cost anyone a schedule.\n */\n async setLabels(id: string, labels: string[]): Promise<void> {\n const token = this.token();\n if (!token) throw new Error(\"Todoist provider is not configured — run `pai task config`.\");\n await call<WireTask>(token, `/tasks/${id}`, { method: \"POST\", body: { labels } });\n }\n\n /**\n * Move a task's due date, expressed in Todoist's natural language.\n *\n * Always `due_string`, never `due_date`: writing a bare date to a recurring\n * task drops the recurrence and turns a routine into a one-off, silently. To\n * keep both, the caller passes the rule and the date together — Todoist\n * accepts \"every day at 08:00 starting 2026-08-02\" and honours each half.\n */\n async setDue(id: string, dueString: string): Promise<void> {\n const token = this.token();\n if (!token) throw new Error(\"Todoist provider is not configured — run `pai task config`.\");\n await call<WireTask>(token, `/tasks/${id}`, { method: \"POST\", body: { due_string: dueString } });\n }\n\n /**\n * Find the sub-project named `name` under the bus root, creating it if absent.\n *\n * Per-project sub-projects are the filing convention: a flat pile in the root\n * buries findings across projects. This exists so the convention can be\n * executed rather than re-derived — a session that has to infer structure\n * from the existing project list will sometimes infer cautiously and file\n * flat, which is the mess the convention prevents.\n *\n * Matching is case-insensitive and ignores decoration, so \"Whazaa\" finds an\n * existing \"Whazaa 🐝\" rather than creating a near-duplicate.\n */\n /**\n * The sub-projects under the bus root — i.e. every address a task can be\n * filed against. Archived and deleted ones are excluded: they cannot receive\n * work, so listing them would overstate what is reachable.\n */\n /**\n * The existing sub-project that resolves to this owner, if there is one.\n *\n * Matched by running the ordinary ownership resolution over each sub-project\n * name rather than comparing strings, because a display name is rarely its\n * PAI slug — \"AIBroker\" is `broker`, \"SL\" is `seriousletter`. Reusing the\n * resolver means filing and reading cannot disagree about where a task\n * belongs, which is a class of bug we have already paid for.\n *\n * Deliberately does NOT create anything. Filing a task should not bring a new\n * address into existence as a side effect; that is `--into`'s job, asked for\n * explicitly.\n */\n private async subProjectForOwner(owner: string): Promise<string | null> {\n try {\n for (const p of await this.listSubProjects()) {\n const resolved = resolveOwner({ labels: [], container: p.name }, this.aliases);\n if (resolved.project === owner) return p.id;\n }\n } catch {\n // Unreachable tracker — fall back to the label, which still routes.\n }\n return null;\n }\n\n async listSubProjects(): Promise<Array<{ id: string; name: string }>> {\n const token = this.token();\n if (!token || !this.config.rootProjectId) {\n throw new Error(\"Todoist provider is not configured — run `pai task config`.\");\n }\n\n const root = this.config.rootProjectId;\n const projects = await collect<WireProject>(token, \"/projects\");\n return projects\n .filter((p) => !p.is_archived && !p.is_deleted && p.parent_id === root)\n .map((p) => ({ id: p.id, name: p.name }));\n }\n\n async findOrCreateSubProject(name: string): Promise<{ id: string; created: boolean }> {\n const token = this.token();\n if (!token || !this.config.rootProjectId) {\n throw new Error(\"Todoist provider is not configured — run `pai task config`.\");\n }\n\n const root = this.config.rootProjectId;\n const projects = await collect<WireProject>(token, \"/projects\");\n const want = normalizeName(name);\n\n for (const p of projects) {\n if (p.is_archived || p.is_deleted) continue;\n if (p.parent_id === root && normalizeName(p.name) === want) {\n return { id: p.id, created: false };\n }\n }\n\n const created = await call<WireProject>(token, \"/projects\", {\n method: \"POST\",\n body: { name, parent_id: root },\n });\n return { id: created.id, created: true };\n }\n\n /** Append a comment — used to keep run history on the task itself. */\n async comment(id: string, content: string): Promise<void> {\n const token = this.token();\n if (!token) throw new Error(\"Todoist provider is not configured — run `pai task config`.\");\n const created = await call<{ content?: string }>(token, `/comments`, {\n method: \"POST\",\n body: { task_id: id, content },\n });\n // Comments have their own cap, and the same truncate-and-report-success\n // behaviour. A run history that quietly loses its tail is worse than none,\n // because it reads as complete.\n warnIfTruncated(\"comment\", content, created?.content ?? \"\");\n }\n\n /** Comments on one task, oldest first. */\n async listComments(\n taskId: string\n ): Promise<Array<{ id: string; content: string; postedAt?: string }>> {\n const token = this.token();\n if (!token) return [];\n const wire = await collect<{ id: string; content?: string; posted_at?: string }>(\n token,\n `/comments?task_id=${encodeURIComponent(taskId)}`\n );\n // posted_at is carried because the archive is a record: a discussion with\n // no dates cannot be reconciled against anything that happened around it.\n return wire.map((c) => ({ id: c.id, content: c.content ?? \"\", postedAt: c.posted_at }));\n }\n\n async deleteComment(commentId: string): Promise<void> {\n const token = this.token();\n if (!token) throw new Error(\"Todoist provider is not configured — run `pai task config`.\");\n await call<unknown>(token, `/comments/${commentId}`, { method: \"DELETE\" });\n }\n\n async complete(id: string): Promise<void> {\n const token = this.token();\n if (!token) throw new Error(\"Todoist provider is not configured — run `pai setup`.\");\n await call<void>(token, `/tasks/${id}/close`, { method: \"POST\" });\n }\n}\n","/** Step 16: Task bus — optional external tracker for cross-session work. */\n\nimport { c, line, section, type Rl, prompt, promptYesNo, readConfigRaw } from \"../utils.js\";\nimport { listProjects } from \"../../../../tasks/providers/todoist.js\";\nimport type { TaskConfig } from \"../../../../tasks/types.js\";\n\n/** Shape of the `tasks` block as it sits in the raw config file. */\ntype RawTaskConfig = Partial<TaskConfig> & {\n providers?: { todoist?: { apiKey?: string; rootProjectId?: string; enabled?: boolean } };\n};\n\nexport async function stepTaskBus(rl: Rl): Promise<Record<string, unknown>> {\n section(\"Step 16: Task Bus (Optional)\");\n\n const existing = readConfigRaw();\n const current = (existing.tasks ?? {}) as RawTaskConfig;\n const todoist = current.providers?.todoist;\n\n if (todoist?.apiKey && todoist?.rootProjectId) {\n console.log(c.ok(\"Task bus already configured. Skipping.\"));\n return { tasks: current };\n }\n\n line();\n line(\" The task bus lets any PAI session file work into a shared tracker, so a\");\n line(\" routine can pick it up later and hand it to the session that owns it.\");\n line();\n line(\" PAI works fully without this. Skip it unless you want cross-session tasks.\");\n line();\n\n const wanted = await promptYesNo(rl, \"Enable the task bus (requires a Todoist account)?\", false);\n if (!wanted) {\n line();\n console.log(c.ok(\"Skipping the task bus.\"));\n console.log(c.dim(\" Add later by re-running `pai setup`.\"));\n return { tasks: { enabled: false } };\n }\n\n // -- Token -----------------------------------------------------------------\n // Prefer an existing env token so the user need not paste a credential twice.\n // Never read it from ~/.claude.json: the Todoist MCP keeps one there, but that\n // belongs to a different tool and PAI must not scrape another program's secrets.\n line();\n line(\" Get your API token from Todoist:\");\n line(c.dim(\" Settings -> Integrations -> Developer\"));\n line(c.dim(\" https://app.todoist.com/app/settings/integrations/developer\"));\n line();\n line(\" It grants full read/write access to your Todoist. Treat it like a password.\");\n line();\n\n const envToken = process.env.TODOIST_API_KEY?.trim();\n let apiKey = todoist?.apiKey ?? \"\";\n\n if (!apiKey && envToken) {\n const useEnv = await promptYesNo(rl, \"Found TODOIST_API_KEY in the environment. Use it?\", true);\n if (useEnv) apiKey = envToken;\n }\n\n while (!apiKey) {\n apiKey = (await prompt(rl, \" Paste your Todoist API token here: \")).trim();\n if (!apiKey) console.log(c.warn(\" A token is required, or answer no to skip the task bus.\"));\n }\n\n // -- Root project ----------------------------------------------------------\n // Listing projects doubles as token validation: a bad token fails here, at\n // setup time, rather than silently returning nothing during a morning routine.\n line();\n line(\" Fetching your Todoist projects...\");\n\n let projects: Array<{ id: string; name: string }>;\n try {\n projects = await listProjects(apiKey);\n } catch (e) {\n line();\n console.log(c.warn(`Could not reach Todoist: ${e instanceof Error ? e.message : String(e)}`));\n console.log(c.dim(\" The token may be wrong, or the network is unavailable.\"));\n console.log(c.dim(\" Saved nothing. Re-run `pai setup` to try again.\"));\n return { tasks: { enabled: false } };\n }\n\n if (projects.length === 0) {\n console.log(c.warn(\" No projects found. Create one in Todoist first, then re-run setup.\"));\n return { tasks: { enabled: false } };\n }\n\n line();\n line(\" Which project should hold the bus? Create a dedicated one if unsure.\");\n line();\n projects.forEach((p, i) => line(` ${String(i + 1).padStart(2)}. ${p.name}`));\n line();\n\n let rootProjectId = \"\";\n while (!rootProjectId) {\n const answer = (await prompt(rl, ` Project number [1-${projects.length}]: `)).trim();\n const idx = Number.parseInt(answer, 10) - 1;\n if (Number.isInteger(idx) && idx >= 0 && idx < projects.length) {\n rootProjectId = projects[idx]!.id;\n } else {\n console.log(c.warn(` Enter a number between 1 and ${projects.length}.`));\n }\n }\n\n const chosen = projects.find((p) => p.id === rootProjectId)!;\n\n // The ID is stored, never the name. Todoist's project search silently returns\n // zero results for names containing emoji, so resolving by name later would\n // report \"no tasks\" instead of failing — the exact silent failure this\n // subsystem exists to surface.\n line();\n console.log(c.ok(`Task bus enabled on \"${chosen.name}\" (id ${rootProjectId}).`));\n console.log(c.dim(\" Label a task `pai:<project>` to assign it to a PAI project.\"));\n console.log(c.dim(\" Only projects with an alias can be dispatched to — see `pai project name`.\"));\n\n const autoDispatch = await promptYesNo(\n rl,\n \"Automatically hand tasks to the owning session (needs AIBroker)?\",\n false,\n );\n\n return {\n tasks: {\n enabled: true,\n autoDispatch,\n providers: {\n todoist: { enabled: true, apiKey, rootProjectId },\n },\n },\n };\n}\n","/**\n * install.ts — `pai worker install`: one command, whole migration.\n *\n * Three things:\n *\n * 1. ~/.claude/settings.json: point the PreToolUse \"Agent\" hook at\n * ${PAI_DIR}/Hooks/route-agents-to-worker.mjs, replacing the old\n * route-agents-to-glm.sh registration if present.\n * 2. ~/.local/bin compatibility shims (glm, glm-run, glm-ps, glm-log) that\n * exec the pai equivalents, so every existing habit, alias and script\n * keeps working. Anything already at those paths is moved aside to\n * <name>.pre-pai once — never deleted.\n * 3. Remove the old route-agents-to-glm.sh from ~/.claude/Hooks (the\n * settings.json entry is gone, so the file is dead).\n *\n * Idempotent: running it twice changes nothing the second time.\n */\n\nimport { execFileSync } from \"node:child_process\";\nimport { existsSync, mkdirSync, renameSync, unlinkSync, writeFileSync, chmodSync } from \"node:fs\";\nimport { homedir } from \"node:os\";\nimport { join } from \"node:path\";\nimport { readSettingsJson, writeSettingsJson } from \"../cli/commands/settings-manager.js\";\n\nconst CLAUDE_DIR = join(homedir(), \".claude\");\nconst HOOKS_DIR = join(CLAUDE_DIR, \"Hooks\");\nconst LOCAL_BIN = join(homedir(), \".local\", \"bin\");\n\nexport const NEW_AGENT_HOOK = \"${PAI_DIR}/Hooks/route-agents-to-worker.mjs\";\nconst OLD_AGENT_HOOK_STEM = \"route-agents-to-glm\";\n\n/**\n * The PreToolUse matcher for the subagent gate. It covers both names the\n * harness has used for the in-process subagent tool — the current \"Agent\" and\n * the older \"Task\" — because a matcher that names only one of them leaves the\n * gate registered but never invoked after a rename.\n */\nexport const AGENT_MATCHER = \"Agent|Task\";\n\n/** Matchers a previous PAI version wrote for this same hook. */\nconst LEGACY_AGENT_MATCHERS = [\"Agent\", \"Task\"];\n\nexport interface InstallResult {\n lines: string[];\n changed: boolean;\n}\n\n/** Absolute path of the pai executable to hard-wire into the shims. */\nfunction paiPath(): string {\n try {\n return execFileSync(\"/usr/bin/which\", [\"pai\"], { encoding: \"utf8\" }).trim() || \"pai\";\n } catch {\n return \"pai\";\n }\n}\n\n// ---------------------------------------------------------------------------\n// 1. settings.json: Agent hook migration\n// ---------------------------------------------------------------------------\n\n/**\n * Bring the subagent-gate registration up to date inside an already-read\n * settings object. Pure: it mutates `settings` and reports, the caller decides\n * whether to write. Exported so the registration can be tested against a copy\n * of a real settings.json instead of the live file.\n *\n * Three things must hold afterwards, and each one has been wrong at some point:\n * - the rule exists,\n * - its matcher covers every subagent tool name (AGENT_MATCHER), not just\n * the one the harness happened to emit when it was written,\n * - its command points at the current hook.\n */\nexport function patchAgentHookSettings(\n settings: Record<string, unknown>,\n lines: string[]\n): boolean {\n const hooks = (typeof settings[\"hooks\"] === \"object\" && settings[\"hooks\"] !== null\n ? settings[\"hooks\"]\n : {}) as Record<string, unknown>;\n\n const preToolUse = Array.isArray(hooks[\"PreToolUse\"])\n ? (hooks[\"PreToolUse\"] as Array<Record<string, unknown>>)\n : [];\n\n let changed = false;\n let found = false;\n\n for (const rule of preToolUse) {\n const matcher = typeof rule[\"matcher\"] === \"string\" ? rule[\"matcher\"] : \"\";\n if (matcher !== AGENT_MATCHER && !LEGACY_AGENT_MATCHERS.includes(matcher)) continue;\n\n const entries = Array.isArray(rule[\"hooks\"])\n ? (rule[\"hooks\"] as Array<Record<string, unknown>>)\n : [];\n let ours = false;\n for (const entry of entries) {\n const cmd = typeof entry[\"command\"] === \"string\" ? entry[\"command\"] : \"\";\n if (cmd === NEW_AGENT_HOOK) ours = true;\n if (cmd.includes(OLD_AGENT_HOOK_STEM)) {\n entry[\"command\"] = NEW_AGENT_HOOK;\n lines.push(`settings.json: subagent hook migrated to ${NEW_AGENT_HOOK}`);\n changed = true;\n ours = true;\n }\n }\n if (!ours) continue; // someone else's rule on the same matcher — leave it\n\n found = true;\n if (matcher !== AGENT_MATCHER) {\n rule[\"matcher\"] = AGENT_MATCHER;\n lines.push(`settings.json: subagent matcher widened \"${matcher}\" → \"${AGENT_MATCHER}\"`);\n changed = true;\n }\n }\n\n if (!found) {\n preToolUse.push({\n matcher: AGENT_MATCHER,\n hooks: [{ type: \"command\", command: NEW_AGENT_HOOK }],\n });\n lines.push(`settings.json: subagent hook added (${AGENT_MATCHER}) → ${NEW_AGENT_HOOK}`);\n changed = true;\n }\n\n if (changed) {\n hooks[\"PreToolUse\"] = preToolUse;\n settings[\"hooks\"] = hooks;\n } else {\n lines.push(`settings.json: subagent hook already current`);\n }\n return changed;\n}\n\nfunction patchAgentHook(lines: string[]): boolean {\n const settings = readSettingsJson();\n const changed = patchAgentHookSettings(settings, lines);\n if (changed) writeSettingsJson(settings);\n return changed;\n}\n\n// ---------------------------------------------------------------------------\n// 2. ~/.local/bin shims\n// ---------------------------------------------------------------------------\n\nconst GLM_PS_SHIM = (pai: string) => `# pai worker shim — replaces the python glm-ps of the same name\ncase \"\\${1-}\" in\n \"\"|\"ps\") exec ${pai} worker ps ;;\n follow|watch|pane|log) exec ${pai} worker \"$@\" ;;\n --status) shift; exec ${pai} worker status-line \"\\${ITERM_SESSION_ID:-}\" \"\\${PWD:-$HOME}\" ;;\n -*) exec ${pai} worker ps \"$@\" ;;\n *) exec ${pai} worker replay \"$@\" ;;\nesac\n`;\n\n/**\n * Names whose shim starts the harness itself on that provider (`pai launch`)\n * rather than a subagent worker — the historical meaning of typing \"glm\":\n * an interactive session pinned to the glm endpoint, not whatever provider\n * happens to be `active` in workers.yaml right now.\n */\nconst LAUNCH_SHIM_PROVIDERS = [\"glm\", \"kimi\"];\n\nexport function shim(name: string, pai: string): string {\n if (name === \"glm-ps\") return GLM_PS_SHIM(pai);\n if (name === \"glm-log\") return `#!/bin/sh\\n# pai worker shim — replaces the shell glm-log of the same name\\nexec ${pai} worker log \"$@\"\\n`;\n if (name === \"worker-say\") return `#!/bin/sh\\n# pai worker shim — say one line to a running worker\\nexec ${pai} worker say \"$@\"\\n`;\n if (LAUNCH_SHIM_PROVIDERS.includes(name)) {\n return `#!/bin/sh\\n# pai launch shim — starts an interactive Claude Code session pinned to the ${name} provider\\nexec ${pai} launch --provider ${name} \"$@\"\\n`;\n }\n // glm-run: pass everything through to a headless/worker run; `pai worker\n // run` forwards unknown options to claude and drops --output-format/\n // --verbose itself\n return `#!/bin/sh\\n# pai worker shim — replaces the previous ${name} of the same name\\nexec ${pai} worker run \"$@\"\\n`;\n}\n\nfunction installShims(lines: string[]): boolean {\n const pai = paiPath();\n let changed = false;\n for (const name of [\"glm\", \"glm-run\", \"glm-ps\", \"glm-log\", \"worker-say\", \"kimi\"]) {\n const path = join(LOCAL_BIN, name);\n const ours = `# pai worker shim`;\n let current: string | null = null;\n if (existsSync(path)) {\n try {\n current = execFileSync(\"head\", [\"-c\", \"64\", path], { encoding: \"utf8\" });\n } catch {\n current = null;\n }\n }\n if (current !== null && !current.includes(ours)) {\n // one-time move-aside; never clobber what a later run moved there\n const aside = join(LOCAL_BIN, `${name}.pre-pai`);\n if (!existsSync(aside)) {\n renameSync(path, aside);\n lines.push(`${name}: previous version kept as ${name}.pre-pai`);\n } else {\n unlinkSync(path);\n lines.push(`${name}: replaced (${name}.pre-pai already exists)`);\n }\n changed = true;\n } else if (current === null) {\n changed = true;\n } else {\n lines.push(`${name}: shim already current`);\n continue;\n }\n const body = shim(name, pai);\n mkdirSync(LOCAL_BIN, { recursive: true });\n writeFileSync(path, body, { encoding: \"utf8\", mode: 0o755 });\n try {\n chmodSync(path, 0o755);\n } catch {\n /* mode set on create where supported */\n }\n const target = LAUNCH_SHIM_PROVIDERS.includes(name) ? `${pai} launch --provider ${name}` : `${pai} worker …`;\n lines.push(`${name}: shim installed → ${target}`);\n }\n return changed;\n}\n\n// ---------------------------------------------------------------------------\n// 3. old hook file\n// ---------------------------------------------------------------------------\n\nfunction removeOldHook(lines: string[]): boolean {\n const old = join(HOOKS_DIR, \"route-agents-to-glm.sh\");\n if (!existsSync(old)) return false;\n unlinkSync(old);\n lines.push(`removed ${OLD_AGENT_HOOK_STEM}.sh (registration migrated above)`);\n return true;\n}\n\n// ---------------------------------------------------------------------------\n\nexport function installWorkers(): InstallResult {\n const lines: string[] = [];\n let changed = patchAgentHook(lines);\n if (installShims(lines)) changed = true;\n if (removeOldHook(lines)) changed = true;\n if (!existsSync(LOCAL_BIN)) {\n lines.push(`note: ${LOCAL_BIN} did not exist; make sure it is on PATH`);\n }\n return { lines, changed };\n}\n","/** Step 17: Worker providers — subagents on configured providers. */\n\nimport { c, line, section, type Rl, promptYesNo, readConfigRaw } from \"../utils.js\";\nimport { installWorkers } from \"../../../../workers/install.js\";\nimport { readWorkersSection } from \"../../../../workers/config.js\";\nimport { describeProviders, setWorkersEnabled } from \"../../../../workers/providers.js\";\n\nexport async function stepWorkers(rl: Rl): Promise<Record<string, unknown>> {\n section(\"Step 17: Worker Providers (Optional)\");\n\n const existing = readConfigRaw();\n const current = existing.workers as { enabled?: boolean; providers?: Record<string, unknown> } | undefined;\n\n if (current?.providers && Object.keys(current.providers).length > 0) {\n if (current.enabled !== false) {\n console.log(c.ok(\"Worker providers already configured. Running install to refresh shims…\"));\n for (const l of installWorkers().lines) line(` ${l}`);\n }\n return { workers: current };\n }\n\n line();\n line(\" PAI can run every subagent (worker) on a provider: the built-in\");\n line(\" `anthropic` one (your logged-in claude CLI, Max plan, no API key) or any\");\n line(\" Anthropic-compatible endpoint you add later.\");\n line();\n line(\" `pai worker run` is the runner; ps / follow / replay watch the runs;\");\n line(\" the Agent tool is denied and rewritten to it while workers are on.\");\n line();\n\n const wanted = await promptYesNo(rl, \"Turn workers on now with the built-in anthropic provider?\", true);\n if (!wanted) {\n line();\n console.log(c.ok(\"Skipping workers.\"));\n console.log(c.dim(\" Later: pai worker on (other providers: pai worker providers add <name> … — see docs/worker.md)\"));\n return { workers: { enabled: false, providers: {}, classes: {} } };\n }\n\n setWorkersEnabled(true);\n const r = installWorkers();\n for (const l of r.lines) line(` ${l}`);\n const { workers } = readWorkersSection();\n console.log(c.ok(\"Workers on — provider: anthropic (built-in, no API key written).\"));\n for (const l of describeProviders(workers)) line(c.dim(` ${l}`));\n return {};\n}\n","/**\n * PAI setup wizard — main entry point.\n * Orchestrates all setup steps in order and registers the Commander command.\n */\n\nimport type { Command } from \"commander\";\nimport chalk from \"chalk\";\nimport { existsSync } from \"node:fs\";\nimport { CONFIG_FILE, loadConfig } from \"../../../daemon/config.js\";\nimport { createRl, prompt, promptYesNo, line, mergeConfig, setupOptions } from \"./utils.js\";\nimport {\n stepWelcome,\n stepStorage,\n stepEmbedding,\n stepClaudeMd,\n stepPaiSkill,\n stepAiSteeringRules,\n stepSkillStubs,\n stepHooks,\n stepTsHooks,\n stepDaName,\n stepSettings,\n stepDaemon,\n stepMcp,\n stepDirectories,\n stepTaskBus,\n stepWorkers,\n stepInitialIndex,\n stepSummary,\n} from \"./steps/index.js\";\n\n/** Runs one wizard step; a throw is re-raised naming the step so unattended runs say where they died. */\nasync function step<T>(name: string, fn: () => T | Promise<T>): Promise<T> {\n try {\n return await fn();\n } catch (e) {\n throw new Error(`setup step \"${name}\" failed: ${e instanceof Error ? e.message : String(e)}`);\n }\n}\n\nasync function runSetup(): Promise<void> {\n const rl = createRl();\n\n try {\n if (existsSync(CONFIG_FILE)) {\n const current = loadConfig();\n line();\n console.log(\n chalk.yellow(\" Note: PAI is already configured.\") +\n chalk.dim(\" Proceeding will update your existing configuration.\"),\n );\n console.log(chalk.dim(` Config: ${CONFIG_FILE}`));\n console.log(chalk.dim(` Current backend: ${current.storageBackend}`));\n line();\n\n const proceed = await promptYesNo(rl, \"Continue and update configuration?\", true);\n if (!proceed) {\n rl.close();\n line(chalk.dim(\" Setup cancelled.\"));\n line();\n return;\n }\n }\n\n // Step 1: Welcome\n stepWelcome();\n line();\n await prompt(rl, chalk.dim(\" Press Enter to begin setup...\"), \"\");\n\n // Step 2: Storage\n const storageConfig = await step(\"stepStorage\", () => stepStorage(rl));\n\n // Step 3: Embeddings\n const embeddingConfig = await step(\"stepEmbedding\", () => stepEmbedding(rl));\n\n // Step 4: Agent configuration (CLAUDE.md)\n const claudeMdGenerated = await step(\"stepClaudeMd\", () => stepClaudeMd(rl));\n\n // Step 5: PAI Skill\n const paiSkillInstalled = await step(\"stepPaiSkill\", () => stepPaiSkill(rl));\n\n // Step 6: AI Steering Rules\n const aiSteeringRulesInstalled = await step(\"stepAiSteeringRules\", () => stepAiSteeringRules(rl));\n\n // Step 7: Skill Stubs\n const skillStubsInstalled = await step(\"stepSkillStubs\", () => stepSkillStubs(rl));\n\n // Step 8: Hooks (shell scripts)\n const hooksInstalled = await step(\"stepHooks\", () => stepHooks(rl));\n\n // Step 7b: TypeScript hooks (.mjs files)\n const tsHooksInstalled = await step(\"stepTsHooks\", () => stepTsHooks(rl));\n\n // Step 8b: DA name\n const daName = await step(\"stepDaName\", () => stepDaName(rl));\n\n // Step 8: Settings.json\n const settingsPatched = await step(\"stepSettings\", () => stepSettings(rl, daName));\n\n // Step 9: Daemon\n const daemonInstalled = await step(\"stepDaemon\", () => stepDaemon(rl));\n\n // Step 10: MCP\n const mcpRegistered = await step(\"stepMcp\", () => stepMcp(rl));\n\n // Step 11: Directories (informational — no config written)\n await step(\"stepDirectories\", () => stepDirectories(rl));\n\n // Step 11b: Task bus (optional external tracker)\n const taskConfig = await step(\"stepTaskBus\", () => stepTaskBus(rl));\n\n // Step 11c: Worker providers (optional subagent routing)\n const workersConfig = await step(\"stepWorkers\", () => stepWorkers(rl));\n\n // Write config after gathering all choices\n const allUpdates = { ...storageConfig, ...embeddingConfig, ...taskConfig, ...workersConfig };\n mergeConfig(allUpdates);\n\n line();\n console.log(chalk.green(\" Configuration saved.\"));\n\n // Step 12: Initial index\n await step(\"stepInitialIndex\", () => stepInitialIndex(rl));\n\n // Step 13: Summary\n stepSummary(\n allUpdates,\n claudeMdGenerated,\n paiSkillInstalled,\n aiSteeringRulesInstalled,\n skillStubsInstalled,\n hooksInstalled,\n tsHooksInstalled,\n settingsPatched,\n daName,\n daemonInstalled,\n mcpRegistered,\n );\n\n } finally {\n rl.close();\n }\n}\n\nexport function registerSetupCommand(program: Command): void {\n program\n .command(\"setup\")\n .alias(\"install\")\n .description(\n \"Setup wizard — configure storage, embeddings, agent config, and indexing (--yes for unattended)\",\n )\n .option(\"-y, --yes\", \"Unattended: take every default, read no input\")\n .option(\"--storage <backend>\", \"Storage backend: sqlite | postgres (default under --yes: sqlite unless local Postgres answers)\")\n .action(async (opts: { yes?: boolean; storage?: string }) => {\n if (opts.storage && opts.storage !== \"sqlite\" && opts.storage !== \"postgres\") {\n console.error(`pai setup: --storage must be sqlite or postgres, got \"${opts.storage}\"`);\n process.exitCode = 1;\n return;\n }\n setupOptions.yes = opts.yes === true;\n setupOptions.storage = opts.storage as \"sqlite\" | \"postgres\" | undefined;\n try {\n await runSetup();\n } catch (e) {\n console.error(chalk.red(`pai setup: ${e instanceof Error ? e.message : String(e)}`));\n process.exitCode = 1;\n }\n });\n}\n","/** Shared utilities for zettel CLI commands. */\n\nimport type { StorageBackend } from \"../../../storage/interface.js\";\nimport { getStorageBackend } from \"../../../storage/factory.js\";\nimport { err } from \"../../utils.js\";\n\n/** Shorten a vault path to just the last 2-3 components for display. */\nexport function shortPath(p: string, parts = 3): string {\n const segments = p.split(\"/\").filter(Boolean);\n return segments.slice(-parts).join(\"/\");\n}\n\n/** Get the process-wide StorageBackend (SQLite or Postgres, per config). */\nexport async function getFedBackend(): Promise<StorageBackend> {\n try {\n return await getStorageBackend();\n } catch (e) {\n console.error(err(`Failed to open PAI storage backend: ${e}`));\n process.exit(1);\n }\n}\n","/** zettel explore: follow link chains from a starting note. */\n\nimport type { Command } from \"commander\";\nimport chalk from \"chalk\";\nimport { ok, warn, err, dim, bold, header } from \"../../utils.js\";\nimport { getFedBackend, shortPath } from \"./utils.js\";\n\nasync function cmdExplore(\n note: string,\n opts: { depth?: string; direction?: string; mode?: string }\n): Promise<void> {\n const depth = parseInt(opts.depth ?? \"3\", 10);\n const direction = (opts.direction ?? \"both\") as \"forward\" | \"backward\" | \"both\";\n const mode = (opts.mode ?? \"all\") as \"sequential\" | \"associative\" | \"all\";\n\n const { zettelExplore } = await import(\"../../../zettelkasten/index.js\");\n const backend = await getFedBackend();\n\n const result = await zettelExplore(backend, { startNote: note, depth, direction, mode });\n\n console.log();\n console.log(header(\" PAI Zettel Explore\"));\n console.log(dim(` Starting note: ${note}`));\n console.log(dim(` Depth: ${depth} Direction: ${direction} Mode: ${mode}`));\n console.log();\n\n if (result.nodes.length === 0) {\n console.log(warn(\" No connected notes found. Check that the note path exists in the vault index.\"));\n console.log();\n return;\n }\n\n console.log(` ${chalk.cyan(\"●\")} ${bold(shortPath(result.root))} ${dim(\"(root)\")}`);\n\n const byDepth = new Map<number, typeof result.nodes>();\n for (const node of result.nodes) {\n const list = byDepth.get(node.depth) ?? [];\n list.push(node);\n byDepth.set(node.depth, list);\n }\n\n for (let d = 1; d <= depth; d++) {\n const nodes = byDepth.get(d) ?? [];\n if (nodes.length === 0) continue;\n\n console.log();\n console.log(dim(` ${\" \".repeat(d - 1)}Depth ${d}:`));\n for (const node of nodes) {\n const indent = \" \".repeat(d);\n const isBranching = result.branchingPoints.includes(node.path);\n const typeColor = node.linkType === \"sequential\" ? chalk.blue : chalk.magenta;\n const branchMark = isBranching ? chalk.yellow(\" ⑂ branching\") : \"\";\n const title = node.title ?? shortPath(node.path);\n const stats = dim(`in:${node.inbound} out:${node.outbound}`);\n console.log(\n ` ${indent}${typeColor(\"→\")} ${bold(title)}${branchMark} ${stats} ${dim(typeColor(node.linkType))}`\n );\n }\n }\n\n console.log();\n const edgeSummary = `${result.edges.length} edges (${result.edges.filter(e => e.type === \"sequential\").length} sequential, ${result.edges.filter(e => e.type === \"associative\").length} associative)`;\n console.log(dim(` ${edgeSummary}`));\n if (result.branchingPoints.length > 0) {\n console.log(ok(` ${result.branchingPoints.length} branching point(s) found`));\n }\n if (result.maxDepthReached) {\n console.log(warn(\" Max depth reached — use --depth to explore further\"));\n }\n console.log();\n}\n\nexport function registerExploreCommand(parent: Command): void {\n parent\n .command(\"explore <note>\")\n .description(\"Follow link chains from a starting note\")\n .option(\"--depth <n>\", \"Maximum traversal depth (1-10)\", \"3\")\n .option(\"--direction <d>\", \"Link direction: forward | backward | both\", \"both\")\n .option(\"--mode <m>\", \"Edge mode: sequential | associative | all\", \"all\")\n .action(async (note: string, opts: { depth?: string; direction?: string; mode?: string }) => {\n try {\n await cmdExplore(note, opts);\n } catch (e) {\n console.error(err(` Error: ${e}`));\n process.exitCode = 1;\n return;\n }\n });\n}\n","/** zettel health: vault structural health audit. */\n\nimport type { Command } from \"commander\";\nimport chalk from \"chalk\";\nimport { ok, warn, err, dim, bold, header } from \"../../utils.js\";\nimport { getFedBackend, shortPath } from \"./utils.js\";\n\nasync function cmdHealth(opts: {\n scope?: string;\n project?: string;\n days?: string;\n include?: string;\n}): Promise<void> {\n const scope = (opts.scope ?? \"full\") as \"full\" | \"recent\" | \"project\";\n const projectPath = opts.project;\n const recentDays = parseInt(opts.days ?? \"30\", 10);\n const includeTypes = opts.include\n ? (opts.include.split(\",\").map(s => s.trim()) as Array<\"dead_links\" | \"orphans\" | \"disconnected\" | \"low_connectivity\">)\n : undefined;\n\n const { zettelHealth } = await import(\"../../../zettelkasten/index.js\");\n const backend = await getFedBackend();\n\n const result = await zettelHealth(backend, { scope, projectPath, recentDays, include: includeTypes });\n\n console.log();\n console.log(header(\" PAI Zettel Health\"));\n console.log(dim(` Scope: ${scope}${scope === \"project\" ? ` Path: ${projectPath ?? \"(none)\"}` : \"\"}${scope === \"recent\" ? ` Days: ${recentDays}` : \"\"}`));\n console.log();\n\n const score = result.healthScore;\n const scoreColor = score >= 80 ? chalk.green : score >= 60 ? chalk.yellow : chalk.red;\n const barWidth = 30;\n const filled = Math.round((score / 100) * barWidth);\n const bar = scoreColor(\"█\".repeat(filled)) + dim(\"░\".repeat(barWidth - filled));\n console.log(` Health Score: ${scoreColor(bold(String(score)))}% [${bar}]`);\n console.log();\n console.log(dim(` Files: ${result.totalFiles} Links: ${result.totalLinks}`));\n console.log();\n\n if (result.deadLinks.length === 0) {\n console.log(ok(\" Dead links: none\"));\n } else {\n console.log(warn(` Dead links: ${result.deadLinks.length}`));\n const preview = result.deadLinks.slice(0, 10);\n for (const dl of preview) {\n console.log(` ${chalk.red(\"✗\")} ${dim(shortPath(dl.sourcePath))} → ${bold(dl.targetRaw)} ${dim(`(line ${dl.lineNumber})`)}`);\n }\n if (result.deadLinks.length > 10) {\n console.log(dim(` ... and ${result.deadLinks.length - 10} more`));\n }\n console.log();\n }\n\n if (result.orphans.length === 0) {\n console.log(ok(\" Orphan notes: none\"));\n } else {\n console.log(warn(` Orphan notes: ${result.orphans.length}`));\n const preview = result.orphans.slice(0, 10);\n for (const o of preview) {\n console.log(` ${chalk.yellow(\"○\")} ${dim(shortPath(o))}`);\n }\n if (result.orphans.length > 10) {\n console.log(dim(` ... and ${result.orphans.length - 10} more`));\n }\n console.log();\n }\n\n if (result.disconnectedClusters <= 1) {\n console.log(ok(\" Disconnected clusters: 1 (fully connected)\"));\n } else {\n console.log(warn(` Disconnected clusters: ${result.disconnectedClusters}`));\n }\n\n if (result.lowConnectivity.length === 0) {\n console.log(ok(\" Low-connectivity: none\"));\n } else {\n console.log(warn(` Low-connectivity: ${result.lowConnectivity.length} note(s) with ≤1 link`));\n const preview = result.lowConnectivity.slice(0, 5);\n for (const lc of preview) {\n console.log(` ${chalk.dim(\"—\")} ${dim(shortPath(lc))}`);\n }\n if (result.lowConnectivity.length > 5) {\n console.log(dim(` ... and ${result.lowConnectivity.length - 5} more`));\n }\n }\n\n console.log();\n}\n\nexport function registerHealthCommand(parent: Command): void {\n parent\n .command(\"health\")\n .description(\"Vault structural health audit: dead links, orphans, connectivity\")\n .option(\"--scope <s>\", \"Scope: full | recent | project\", \"full\")\n .option(\"--project <path>\", \"Project path prefix (requires --scope project)\")\n .option(\"--days <n>\", \"Look-back window in days (requires --scope recent)\", \"30\")\n .option(\"--include <types>\", \"Comma-separated subset: dead_links,orphans,disconnected,low_connectivity\")\n .action(async (opts: { scope?: string; project?: string; days?: string; include?: string }) => {\n try {\n await cmdHealth(opts);\n } catch (e) {\n console.error(err(` Error: ${e}`));\n process.exitCode = 1;\n return;\n }\n });\n}\n","/** zettel surprise: find semantically similar but graph-distant notes. */\n\nimport type { Command } from \"commander\";\nimport chalk from \"chalk\";\nimport { warn, err, dim, bold, header } from \"../../utils.js\";\nimport { getFedBackend, shortPath } from \"./utils.js\";\n\nasync function cmdSurprise(\n note: string,\n opts: { vaultProjectId?: string; limit?: string; minSimilarity?: string; minDistance?: string }\n): Promise<void> {\n if (!opts.vaultProjectId) {\n console.error(err(\" --vault-project-id is required\"));\n process.exitCode = 1;\n return;\n }\n\n const vaultProjectId = parseInt(opts.vaultProjectId, 10);\n const limit = parseInt(opts.limit ?? \"10\", 10);\n const minSimilarity = parseFloat(opts.minSimilarity ?? \"0.3\");\n const minGraphDistance = parseInt(opts.minDistance ?? \"3\", 10);\n\n const { zettelSurprise } = await import(\"../../../zettelkasten/index.js\");\n const backend = await getFedBackend();\n\n console.log();\n console.log(header(\" PAI Zettel Surprise\"));\n console.log(dim(` Reference note: ${note}`));\n process.stdout.write(dim(\" Searching for surprising connections...\\n\"));\n\n const results = await zettelSurprise(backend, {\n referencePath: note,\n vaultProjectId,\n limit,\n minSimilarity,\n minGraphDistance,\n });\n\n if (results.length === 0) {\n console.log(warn(\" No surprising connections found. Try lowering --min-similarity or --min-distance.\"));\n console.log();\n return;\n }\n\n console.log();\n console.log(bold(` Found ${results.length} surprising connection(s):`));\n console.log();\n\n for (let i = 0; i < results.length; i++) {\n const r = results[i];\n const title = r.title ?? shortPath(r.path);\n const surpriseBar = Math.round(r.surpriseScore * 10);\n console.log(` ${chalk.cyan(String(i + 1).padStart(2, \" \"))}. ${bold(title)}`);\n console.log(` ${dim(\"Surprise:\")} ${chalk.magenta(r.surpriseScore.toFixed(3))} ${\"■\".repeat(surpriseBar)}${\"□\".repeat(10 - surpriseBar)}`);\n console.log(` ${dim(\"Cosine:\")} ${r.cosineSimilarity.toFixed(3)} ${dim(\"Graph distance:\")} ${r.graphDistance}`);\n if (r.sharedSnippet) {\n console.log(` ${dim(\"Context:\")} ${r.sharedSnippet.slice(0, 120)}`);\n }\n console.log();\n }\n}\n\nexport function registerSurpriseCommand(parent: Command): void {\n parent\n .command(\"surprise <note>\")\n .description(\"Find semantically similar but graph-distant notes (surprising connections)\")\n .requiredOption(\"--vault-project-id <n>\", \"Project ID for the vault in the federation DB\")\n .option(\"--limit <n>\", \"Maximum results\", \"10\")\n .option(\"--min-similarity <f>\", \"Minimum cosine similarity (0–1)\", \"0.3\")\n .option(\"--min-distance <n>\", \"Minimum graph distance\", \"3\")\n .action(async (note: string, opts: { vaultProjectId?: string; limit?: string; minSimilarity?: string; minDistance?: string }) => {\n try {\n await cmdSurprise(note, opts);\n } catch (e) {\n console.error(err(` Error: ${e}`));\n process.exitCode = 1;\n return;\n }\n });\n}\n","/** zettel suggest: suggest new wikilink connections for a note. */\n\nimport type { Command } from \"commander\";\nimport chalk from \"chalk\";\nimport { warn, err, dim, bold, header } from \"../../utils.js\";\nimport { getFedBackend, shortPath } from \"./utils.js\";\n\nasync function cmdSuggest(\n note: string,\n opts: { vaultProjectId?: string; limit?: string; excludeLinked?: boolean }\n): Promise<void> {\n if (!opts.vaultProjectId) {\n console.error(err(\" --vault-project-id is required\"));\n process.exitCode = 1;\n return;\n }\n\n const vaultProjectId = parseInt(opts.vaultProjectId, 10);\n const limit = parseInt(opts.limit ?? \"5\", 10);\n const excludeLinked = opts.excludeLinked !== false;\n\n const { zettelSuggest } = await import(\"../../../zettelkasten/index.js\");\n const backend = await getFedBackend();\n\n console.log();\n console.log(header(\" PAI Zettel Suggest\"));\n console.log(dim(` Note: ${note}`));\n process.stdout.write(dim(\" Computing suggestions...\\n\"));\n\n const suggestions = await zettelSuggest(backend, {\n notePath: note,\n vaultProjectId,\n limit,\n excludeLinked,\n });\n\n if (suggestions.length === 0) {\n console.log(warn(\" No suggestions found. The note may be well-connected already.\"));\n console.log();\n return;\n }\n\n console.log();\n console.log(bold(` ${suggestions.length} suggested connection(s):`));\n console.log();\n\n for (let i = 0; i < suggestions.length; i++) {\n const s = suggestions[i];\n const title = s.title ?? shortPath(s.path);\n console.log(` ${chalk.green(String(i + 1).padStart(2, \" \"))}. ${bold(title)}`);\n console.log(` ${dim(\"Score:\")} ${chalk.green(s.score.toFixed(3))} ${dim(\"Semantic:\")} ${s.semanticScore.toFixed(2)} ${dim(\"Tag:\")} ${s.tagScore.toFixed(2)} ${dim(\"Neighbor:\")} ${s.neighborScore.toFixed(2)}`);\n console.log(` ${dim(\"Reason:\")} ${s.reason}`);\n console.log(` ${dim(\"Wikilink:\")} ${chalk.cyan(s.suggestedWikilink)}`);\n console.log();\n }\n}\n\nexport function registerSuggestCommand(parent: Command): void {\n parent\n .command(\"suggest <note>\")\n .description(\"Suggest new wikilink connections for a note\")\n .requiredOption(\"--vault-project-id <n>\", \"Project ID for the vault in the federation DB\")\n .option(\"--limit <n>\", \"Maximum suggestions\", \"5\")\n .option(\"--no-exclude-linked\", \"Include notes already linked from this one\")\n .action(async (note: string, opts: { vaultProjectId?: string; limit?: string; excludeLinked?: boolean }) => {\n try {\n await cmdSuggest(note, opts);\n } catch (e) {\n console.error(err(` Error: ${e}`));\n process.exitCode = 1;\n return;\n }\n });\n}\n","/** zettel converse: ask the vault a question and get a synthesis prompt. */\n\nimport type { Command } from \"commander\";\nimport chalk from \"chalk\";\nimport { warn, err, dim, bold, header } from \"../../utils.js\";\nimport { getFedBackend, shortPath } from \"./utils.js\";\n\nasync function cmdConverse(\n question: string,\n opts: { vaultProjectId?: string; depth?: string; limit?: string }\n): Promise<void> {\n if (!opts.vaultProjectId) {\n console.error(err(\" --vault-project-id is required\"));\n process.exitCode = 1;\n return;\n }\n\n const vaultProjectId = parseInt(opts.vaultProjectId, 10);\n const depth = parseInt(opts.depth ?? \"2\", 10);\n const limit = parseInt(opts.limit ?? \"15\", 10);\n\n const { zettelConverse } = await import(\"../../../zettelkasten/index.js\");\n const backend = await getFedBackend();\n\n console.log();\n console.log(header(\" PAI Zettel Converse\"));\n console.log(dim(` Question: \"${question}\"`));\n process.stdout.write(dim(\" Searching vault for relevant notes...\\n\"));\n\n const result = await zettelConverse(backend, { question, vaultProjectId, depth, limit });\n\n if (result.relevantNotes.length === 0) {\n console.log(warn(\" No relevant notes found. Try rephrasing your question.\"));\n console.log();\n return;\n }\n\n console.log();\n console.log(bold(` ${result.relevantNotes.length} relevant note(s) from ${result.domains.length} domain(s):`));\n console.log(dim(` Domains: ${result.domains.join(\", \")}`));\n console.log();\n\n for (const note of result.relevantNotes) {\n const title = note.title ?? shortPath(note.path);\n console.log(` ${chalk.cyan(\"◆\")} ${bold(title)} ${dim(`[${note.domain}] score: ${note.score.toFixed(3)}`)}`);\n if (note.snippet) {\n console.log(` ${dim(note.snippet.slice(0, 200))}`);\n }\n console.log();\n }\n\n if (result.connections.length > 0) {\n console.log(bold(\" Cross-domain connections:\"));\n for (const conn of result.connections.slice(0, 10)) {\n console.log(\n ` ${chalk.magenta(\"⟷\")} ${dim(conn.fromDomain)} ${chalk.dim(\"→\")} ${dim(conn.toDomain)} ` +\n `${dim(shortPath(conn.fromPath))} → ${dim(shortPath(conn.toPath))} ` +\n `${dim(`strength: ${conn.strength}`)}`\n );\n }\n console.log();\n }\n\n console.log(bold(\" Synthesis prompt (paste into your AI):\"));\n console.log();\n const promptLines = result.synthesisPrompt.split(\"\\n\");\n for (const line of promptLines) {\n console.log(` ${dim(line)}`);\n }\n console.log();\n}\n\nexport function registerConverseCommand(parent: Command): void {\n parent\n .command(\"converse <question>\")\n .description(\"Ask the vault a question and get a synthesis prompt with relevant notes\")\n .requiredOption(\"--vault-project-id <n>\", \"Project ID for the vault in the federation DB\")\n .option(\"--depth <n>\", \"Graph expansion depth around matched notes\", \"2\")\n .option(\"--limit <n>\", \"Maximum relevant notes to include\", \"15\")\n .action(async (question: string, opts: { vaultProjectId?: string; depth?: string; limit?: string }) => {\n try {\n await cmdConverse(question, opts);\n } catch (e) {\n console.error(err(` Error: ${e}`));\n process.exitCode = 1;\n return;\n }\n });\n}\n","/** zettel themes: detect emerging theme clusters in recently edited notes. */\n\nimport type { Command } from \"commander\";\nimport chalk from \"chalk\";\nimport { warn, err, dim, bold, header } from \"../../utils.js\";\nimport { getFedBackend, shortPath } from \"./utils.js\";\n\nasync function cmdThemes(opts: {\n vaultProjectId?: string;\n days?: string;\n minSize?: string;\n maxThemes?: string;\n threshold?: string;\n}): Promise<void> {\n if (!opts.vaultProjectId) {\n console.error(err(\" --vault-project-id is required\"));\n process.exitCode = 1;\n return;\n }\n\n const vaultProjectId = parseInt(opts.vaultProjectId, 10);\n const lookbackDays = parseInt(opts.days ?? \"30\", 10);\n const minClusterSize = parseInt(opts.minSize ?? \"3\", 10);\n const maxThemes = parseInt(opts.maxThemes ?? \"10\", 10);\n const similarityThreshold = parseFloat(opts.threshold ?? \"0.65\");\n\n const { zettelThemes } = await import(\"../../../zettelkasten/index.js\");\n const backend = await getFedBackend();\n\n console.log();\n console.log(header(\" PAI Zettel Themes\"));\n console.log(dim(` Lookback: ${lookbackDays}d Min cluster: ${minClusterSize} Threshold: ${similarityThreshold}`));\n process.stdout.write(dim(\" Detecting emerging themes...\\n\"));\n\n const result = await zettelThemes(backend, {\n vaultProjectId,\n lookbackDays,\n minClusterSize,\n maxThemes,\n similarityThreshold,\n });\n\n if (result.themes.length === 0) {\n console.log(warn(` No themes detected in the last ${lookbackDays} days. Try --days with a larger window.`));\n console.log();\n return;\n }\n\n const fromDate = new Date(result.timeWindow.from).toISOString().slice(0, 10);\n const toDate = new Date(result.timeWindow.to).toISOString().slice(0, 10);\n\n console.log();\n console.log(bold(` ${result.themes.length} theme(s) from ${result.totalNotesAnalyzed} notes [${fromDate} → ${toDate}]:`));\n console.log();\n\n for (let i = 0; i < result.themes.length; i++) {\n const cluster = result.themes[i];\n const diversityBar = Math.round(cluster.folderDiversity * 10);\n const indexSuggestion = cluster.suggestIndexNote ? chalk.yellow(\" ⚑ suggest index note\") : \"\";\n\n console.log(` ${chalk.cyan(String(i + 1).padStart(2, \" \"))}. ${bold(cluster.label)}${indexSuggestion}`);\n console.log(\n ` ${dim(\"Notes:\")} ${cluster.size} ` +\n `${dim(\"Diversity:\")} ${\"█\".repeat(diversityBar)}${\"░\".repeat(10 - diversityBar)} ${cluster.folderDiversity.toFixed(2)} ` +\n `${dim(\"Linked:\")} ${Math.round(cluster.linkedRatio * 100)}%`\n );\n\n const preview = cluster.notes.slice(0, 5);\n for (const note of preview) {\n const title = note.title ?? shortPath(note.path);\n console.log(` ${dim(\"•\")} ${title}`);\n }\n if (cluster.notes.length > 5) {\n console.log(dim(` ... and ${cluster.notes.length - 5} more`));\n }\n console.log();\n }\n}\n\nexport function registerThemesCommand(parent: Command): void {\n parent\n .command(\"themes\")\n .description(\"Detect emerging theme clusters in recently edited notes\")\n .requiredOption(\"--vault-project-id <n>\", \"Project ID for the vault in the federation DB\")\n .option(\"--days <n>\", \"Look-back window in days\", \"30\")\n .option(\"--min-size <n>\", \"Minimum notes per cluster\", \"3\")\n .option(\"--max-themes <n>\", \"Maximum themes to return\", \"10\")\n .option(\"--threshold <f>\", \"Similarity threshold for clustering (0–1)\", \"0.65\")\n .action(async (opts: { vaultProjectId?: string; days?: string; minSize?: string; maxThemes?: string; threshold?: string }) => {\n try {\n await cmdThemes(opts);\n } catch (e) {\n console.error(err(` Error: ${e}`));\n process.exitCode = 1;\n return;\n }\n });\n}\n","/** Barrel: registers all zettel sub-commands and exports registerZettelCommands. */\n\nimport type { Command } from \"commander\";\nimport { registerExploreCommand } from \"./explore.js\";\nimport { registerHealthCommand } from \"./health.js\";\nimport { registerSurpriseCommand } from \"./surprise.js\";\nimport { registerSuggestCommand } from \"./suggest.js\";\nimport { registerConverseCommand } from \"./converse.js\";\nimport { registerThemesCommand } from \"./themes.js\";\n\nexport function registerZettelCommands(parent: Command): void {\n registerExploreCommand(parent);\n registerHealthCommand(parent);\n registerSurpriseCommand(parent);\n registerSuggestCommand(parent);\n registerConverseCommand(parent);\n registerThemesCommand(parent);\n}\n","/**\n * pai observation <sub-command>\n *\n * list — List recent observations with filtering options\n * search — Search observations by title/narrative\n * stats — Show observation statistics\n */\n\nimport type { Command } from \"commander\";\nimport { createConnection } from \"net\";\nimport { ok, warn, err, dim, bold, header } from \"../utils.js\";\nimport chalk from \"chalk\";\nimport { paiSocketPath } from \"../../runtime-paths.js\";\n\n// ---------------------------------------------------------------------------\n// IPC helper — communicates with PAI daemon via Unix socket\n// ---------------------------------------------------------------------------\n\nfunction ipcCall(method: string, params: Record<string, unknown>): Promise<unknown> {\n return new Promise((resolve, reject) => {\n let settled = false;\n const settle = (fn: () => void) => { if (!settled) { settled = true; fn(); } };\n\n const client = createConnection(paiSocketPath(), () => {\n client.write(JSON.stringify({ id: 1, method, params }) + \"\\n\");\n });\n let data = \"\";\n client.on(\"data\", (chunk) => {\n data += chunk.toString();\n // Daemon sends a single JSON line then closes — try parsing eagerly\n try {\n const resp = JSON.parse(data) as { ok: boolean; result: unknown; error?: string };\n client.end();\n settle(() => resp.ok ? resolve(resp.result) : reject(new Error(resp.error ?? \"IPC call failed\")));\n } catch {\n // Incomplete — wait for more data or end\n }\n });\n client.on(\"end\", () => {\n settle(() => {\n try {\n const resp = JSON.parse(data) as { ok: boolean; result: unknown; error?: string };\n resp.ok ? resolve(resp.result) : reject(new Error(resp.error ?? \"IPC call failed\"));\n } catch (e) { reject(e); }\n });\n });\n client.on(\"error\", (e) => settle(() => reject(e)));\n setTimeout(() => { client.destroy(); settle(() => reject(new Error(\"IPC timeout\"))); }, 10000);\n });\n}\n\n// ---------------------------------------------------------------------------\n// Type colour mapping\n// ---------------------------------------------------------------------------\n\nfunction typeColor(type: string): string {\n switch (type) {\n case \"decision\": return chalk.cyan(type);\n case \"bugfix\": return chalk.red(type);\n case \"feature\": return chalk.green(type);\n case \"refactor\": return chalk.yellow(type);\n case \"discovery\": return chalk.blue(type);\n case \"change\": return chalk.magenta(type);\n default: return chalk.white(type);\n }\n}\n\n// ---------------------------------------------------------------------------\n// Date formatting helper\n// ---------------------------------------------------------------------------\n\nfunction fmtTs(ts: string | null | undefined): string {\n if (!ts) return dim(\"—\");\n try {\n const d = new Date(ts);\n const date = d.toISOString().slice(0, 10);\n const time = d.toISOString().slice(11, 16);\n return `${date} ${time}`;\n } catch {\n return dim(\"—\");\n }\n}\n\n// ---------------------------------------------------------------------------\n// Truncate a string to maxLen visible characters\n// ---------------------------------------------------------------------------\n\nfunction trunc(s: string, maxLen: number): string {\n if (!s) return \"\";\n if (s.length <= maxLen) return s;\n return s.slice(0, maxLen - 1) + \"…\";\n}\n\n// ---------------------------------------------------------------------------\n// Observation shape (from IPC responses)\n// ---------------------------------------------------------------------------\n\ninterface Observation {\n id: number;\n type: string;\n title: string;\n project_slug?: string | null;\n session_id?: string | null;\n created_at: string;\n narrative?: string | null;\n tool_input_summary?: string | null;\n}\n\ninterface ObservationStats {\n total: number;\n by_type: Array<{ type: string; count: number }>;\n by_project: Array<{ project_slug: string | null; count: number }>;\n most_recent: string | null;\n}\n\n// ---------------------------------------------------------------------------\n// Command: pai observation list\n// ---------------------------------------------------------------------------\n\nasync function cmdList(opts: {\n project?: string;\n type?: string;\n session?: string;\n limit?: string;\n}): Promise<void> {\n const limit = parseInt(opts.limit ?? \"20\", 10);\n\n const params: Record<string, unknown> = { limit };\n if (opts.project) params.project_slug = opts.project;\n if (opts.type) params.type = opts.type;\n if (opts.session) params.session_id = opts.session;\n\n let observations: Observation[];\n try {\n observations = (await ipcCall(\"observation_list\", params)) as Observation[];\n } catch (e) {\n console.error(err(` Failed to reach PAI daemon: ${e}`));\n console.error(dim(\" Is the daemon running? Try: pai daemon status\"));\n process.exitCode = 1;\n return;\n }\n\n console.log();\n console.log(header(\" PAI Observations\"));\n\n const filterParts: string[] = [];\n if (opts.project) filterParts.push(`project: ${opts.project}`);\n if (opts.type) filterParts.push(`type: ${opts.type}`);\n if (opts.session) filterParts.push(`session: ${opts.session}`);\n filterParts.push(`limit: ${limit}`);\n console.log(dim(` ${filterParts.join(\" | \")}`));\n console.log();\n\n if (!observations || observations.length === 0) {\n console.log(warn(\" No observations found.\"));\n console.log();\n return;\n }\n\n // Column widths\n const ID_W = 4;\n const TYPE_W = 10;\n const TITLE_W = 42;\n const PROJ_W = 14;\n const TS_W = 16;\n\n // Header row\n console.log(\n \" \" +\n bold(\"id\".padEnd(ID_W)) + \" \" +\n bold(\"type\".padEnd(TYPE_W)) + \" \" +\n bold(\"title\".padEnd(TITLE_W)) + \" \" +\n bold(\"project\".padEnd(PROJ_W)) + \" \" +\n bold(\"created_at\")\n );\n console.log(\n dim(\n \" \" +\n \"-\".repeat(ID_W) + \" \" +\n \"-\".repeat(TYPE_W) + \" \" +\n \"-\".repeat(TITLE_W) + \" \" +\n \"-\".repeat(PROJ_W) + \" \" +\n \"-\".repeat(TS_W)\n )\n );\n\n for (const obs of observations) {\n const idStr = String(obs.id).padStart(ID_W, \" \");\n const typeStr = typeColor(obs.type ?? \"\").padEnd(TYPE_W + (typeColor(obs.type ?? \"\").length - (obs.type ?? \"\").length));\n const titleStr = trunc(obs.title ?? \"\", TITLE_W).padEnd(TITLE_W);\n const projStr = trunc(obs.project_slug ?? \"—\", PROJ_W).padEnd(PROJ_W);\n const tsStr = fmtTs(obs.created_at);\n\n console.log(` ${idStr} ${typeStr} ${titleStr} ${projStr} ${dim(tsStr)}`);\n }\n\n console.log();\n console.log(dim(` ${observations.length} observation(s)`));\n console.log();\n}\n\n// ---------------------------------------------------------------------------\n// Command: pai observation search <query>\n// ---------------------------------------------------------------------------\n\nasync function cmdSearch(\n query: string,\n opts: { project?: string; type?: string; limit?: string }\n): Promise<void> {\n const limit = parseInt(opts.limit ?? \"20\", 10);\n\n const params: Record<string, unknown> = { query, limit };\n if (opts.project) params.project_slug = opts.project;\n if (opts.type) params.type = opts.type;\n\n let allObservations: Observation[];\n try {\n // Fetch more than requested to allow client-side text filtering\n allObservations = (await ipcCall(\"observation_query\", { ...params, limit: limit * 5, query: undefined })) as Observation[];\n } catch (e) {\n console.error(err(` Failed to reach PAI daemon: ${e}`));\n console.error(dim(\" Is the daemon running? Try: pai daemon status\"));\n process.exitCode = 1;\n return;\n }\n\n // Client-side text filter (observation_query doesn't support text search)\n const q = query.toLowerCase();\n const observations = allObservations\n .filter(o =>\n (o.title ?? \"\").toLowerCase().includes(q) ||\n (o.narrative ?? \"\").toLowerCase().includes(q) ||\n (o.tool_input_summary ?? \"\").toLowerCase().includes(q)\n )\n .slice(0, limit);\n\n console.log();\n console.log(header(\" PAI Observation Search\"));\n console.log(dim(` Query: \"${query}\"${opts.project ? ` project: ${opts.project}` : \"\"}${opts.type ? ` type: ${opts.type}` : \"\"} limit: ${limit}`));\n console.log();\n\n if (!observations || observations.length === 0) {\n console.log(warn(` No observations matching \"${query}\".`));\n console.log();\n return;\n }\n\n for (let i = 0; i < observations.length; i++) {\n const obs = observations[i];\n const idx = chalk.dim(String(i + 1).padStart(3, \" \"));\n const type = typeColor(obs.type ?? \"\");\n const title = bold(obs.title ?? \"(untitled)\");\n const proj = obs.project_slug ? dim(`[${obs.project_slug}]`) : dim(\"[—]\");\n const ts = dim(fmtTs(obs.created_at));\n const id = dim(`#${obs.id}`);\n\n console.log(` ${idx} ${type.padEnd(12)} ${title}`);\n console.log(` ${proj} ${ts} ${id}`);\n\n if (obs.narrative) {\n const snippet = trunc(obs.narrative.replace(/\\n/g, \" \"), 160);\n console.log(` ${dim(snippet)}`);\n }\n console.log();\n }\n\n console.log(dim(` ${observations.length} result(s)`));\n console.log();\n}\n\n// ---------------------------------------------------------------------------\n// Command: pai observation stats\n// ---------------------------------------------------------------------------\n\nasync function cmdStats(): Promise<void> {\n let stats: ObservationStats;\n try {\n stats = (await ipcCall(\"observation_stats\", {})) as ObservationStats;\n } catch (e) {\n console.error(err(` Failed to reach PAI daemon: ${e}`));\n console.error(dim(\" Is the daemon running? Try: pai daemon status\"));\n process.exitCode = 1;\n return;\n }\n\n console.log();\n console.log(header(\" PAI Observation Statistics\"));\n console.log();\n\n console.log(` ${bold(\"Total observations:\")} ${chalk.cyan(String(stats.total ?? 0))}`);\n if (stats.most_recent) {\n console.log(` ${bold(\"Most recent:\")} ${dim(fmtTs(stats.most_recent))}`);\n }\n console.log();\n\n // By type\n if (stats.by_type && stats.by_type.length > 0) {\n console.log(bold(\" By type:\"));\n const maxCount = Math.max(...stats.by_type.map((r) => r.count));\n for (const row of stats.by_type) {\n const barWidth = 20;\n const filled = Math.round((row.count / maxCount) * barWidth);\n const bar = chalk.cyan(\"█\".repeat(filled)) + dim(\"░\".repeat(barWidth - filled));\n const label = typeColor(row.type).padEnd(12 + (typeColor(row.type).length - row.type.length));\n console.log(` ${label} ${bar} ${String(row.count).padStart(5)}`);\n }\n console.log();\n }\n\n // By project\n if (stats.by_project && stats.by_project.length > 0) {\n console.log(bold(\" By project:\"));\n const maxCount = Math.max(...stats.by_project.map((r) => r.count));\n const show = stats.by_project.slice(0, 15);\n for (const row of show) {\n const barWidth = 20;\n const filled = Math.round((row.count / maxCount) * barWidth);\n const bar = chalk.green(\"█\".repeat(filled)) + dim(\"░\".repeat(barWidth - filled));\n const label = (row.project_slug ?? \"—\").padEnd(20);\n console.log(` ${dim(label)} ${bar} ${String(row.count).padStart(5)}`);\n }\n if (stats.by_project.length > 15) {\n console.log(dim(` ... and ${stats.by_project.length - 15} more project(s)`));\n }\n console.log();\n }\n\n if ((!stats.by_type || stats.by_type.length === 0) && (!stats.by_project || stats.by_project.length === 0)) {\n console.log(warn(\" No observation data yet.\"));\n console.log();\n }\n}\n\n// ---------------------------------------------------------------------------\n// Commander registration\n// ---------------------------------------------------------------------------\n\nexport function registerObservationCommands(parent: Command): void {\n // pai observation list\n parent\n .command(\"list\")\n .description(\"List recent observations\")\n .option(\"--project <slug>\", \"Filter by project slug\")\n .option(\"--type <type>\", \"Filter by type (decision, bugfix, feature, refactor, discovery, change)\")\n .option(\"--session <id>\", \"Filter by session ID\")\n .option(\"--limit <n>\", \"Maximum results\", \"20\")\n .action(async (opts: { project?: string; type?: string; session?: string; limit?: string }) => {\n try {\n await cmdList(opts);\n } catch (e) {\n console.error(err(` Error: ${e}`));\n process.exitCode = 1;\n return;\n }\n });\n\n // pai observation search <query>\n parent\n .command(\"search <query>\")\n .description(\"Search observations by title or narrative text\")\n .option(\"--project <slug>\", \"Filter by project slug\")\n .option(\"--type <type>\", \"Filter by type\")\n .option(\"--limit <n>\", \"Maximum results\", \"20\")\n .action(async (query: string, opts: { project?: string; type?: string; limit?: string }) => {\n try {\n await cmdSearch(query, opts);\n } catch (e) {\n console.error(err(` Error: ${e}`));\n process.exitCode = 1;\n return;\n }\n });\n\n // pai observation stats\n parent\n .command(\"stats\")\n .description(\"Show observation statistics: totals, by type, by project\")\n .action(async () => {\n try {\n await cmdStats();\n } catch (e) {\n console.error(err(` Error: ${e}`));\n process.exitCode = 1;\n return;\n }\n });\n}\n","/**\n * tokens.ts — the one tokenizer every audit section counts through.\n *\n * cl100k_base via js-tiktoken (pure JS, no native build). \"all\" + [] for the\n * allowed/disallowed-special-token arguments matches Python tiktoken's\n * `disallowed_special=()`: audited text (hook stdout, session transcripts)\n * routinely contains literal \"<|...|>\"-shaped substrings that are not actual\n * special tokens, and the strict default throws on sight of them.\n */\n\nimport { getEncoding, type Tiktoken } from \"js-tiktoken\";\n\nexport const TOKEN_ENCODING = \"cl100k_base\";\n\nlet cached: Tiktoken | null = null;\n\nfunction encoder(): Tiktoken {\n if (!cached) cached = getEncoding(TOKEN_ENCODING);\n return cached;\n}\n\nexport function countTokens(text: string): number {\n if (!text) return 0;\n return encoder().encode(text, \"all\", []).length;\n}\n","/**\n * `pai audit tokens files` — token cost of everything injected as static\n * memory: global/project CLAUDE.md files (and their @-imports), the CORE\n * skill, the whisper rules file, and this project's auto-memory index.\n */\n\nimport { existsSync, readFileSync } from \"node:fs\";\nimport { homedir } from \"node:os\";\nimport { dirname, join, resolve } from \"node:path\";\nimport { countTokens, TOKEN_ENCODING } from \"./tokens.js\";\nimport { whisperRulesPath } from \"../config/pai-files.js\";\nimport { encodeDir } from \"../cli/utils.js\";\n\nexport interface FileTokenReading {\n path: string;\n tokens: number;\n missing: boolean;\n}\n\nexport interface FilesReport {\n encoding: string;\n readings: FileTokenReading[];\n total: number;\n}\n\nconst SINGLE_FILE_LIMIT = 5000;\nconst TOTAL_LIMIT = 10000;\n\n/** Resolve one @-import token found in a CLAUDE.md line to an absolute path. */\nfunction resolveImport(token: string, fromDir: string): string {\n if (token.startsWith(\"~/\")) return join(homedir(), token.slice(2));\n if (token.startsWith(\"/\")) return token;\n return resolve(fromDir, token);\n}\n\n/**\n * Find every `@path` import in a CLAUDE.md's contents: a line starting with\n * `@`, or containing `@./` or `@~/` anywhere (Claude Code's memory-import\n * syntax allows either position).\n */\nfunction findImports(contents: string, fileDir: string): string[] {\n const found: string[] = [];\n const tokenPattern = /@(~\\/[^\\s]+|\\.\\/[^\\s]+|\\/[^\\s]+)/g;\n for (const line of contents.split(\"\\n\")) {\n if (!line.startsWith(\"@\") && !line.includes(\" @./\") && !line.includes(\"@~/\") && !line.includes(\" @/\")) continue;\n let match: RegExpExecArray | null;\n tokenPattern.lastIndex = 0;\n while ((match = tokenPattern.exec(line))) {\n found.push(resolveImport(match[1], fileDir));\n }\n }\n return found;\n}\n\n/** Every parent-directory CLAUDE.md from `startDir` up to (and including) `/`. */\nfunction parentClaudeMdFiles(startDir: string): string[] {\n const found: string[] = [];\n let dir = startDir;\n for (;;) {\n const candidate = join(dir, \"CLAUDE.md\");\n if (existsSync(candidate)) found.push(candidate);\n const parent = dirname(dir);\n if (parent === dir) break;\n dir = parent;\n }\n return found;\n}\n\n/** The default memory set the task describes, plus any @-imports they pull in. */\nexport function defaultFileSet(cwd: string): string[] {\n const seeds = [\n join(homedir(), \".claude\", \"CLAUDE.md\"),\n join(cwd, \"CLAUDE.md\"),\n join(cwd, \".claude\", \"CLAUDE.md\"),\n ...parentClaudeMdFiles(dirname(cwd)),\n join(homedir(), \".claude\", \"skills\", \"CORE\", \"SKILL.md\"),\n whisperRulesPath(),\n join(homedir(), \".claude\", \"projects\", encodeDir(cwd), \"memory\", \"MEMORY.md\"),\n ];\n\n const visited = new Set<string>();\n const ordered: string[] = [];\n const queue = [...seeds];\n while (queue.length) {\n const path = queue.shift()!;\n if (visited.has(path)) continue;\n visited.add(path);\n ordered.push(path);\n if (!existsSync(path)) continue;\n const contents = readFileSync(path, \"utf8\");\n for (const imported of findImports(contents, dirname(path))) {\n if (!visited.has(imported)) queue.push(imported);\n }\n }\n return ordered;\n}\n\nexport function readFileTokens(paths: string[]): FileTokenReading[] {\n return paths.map((path) => {\n if (!existsSync(path)) return { path, tokens: 0, missing: true };\n const text = readFileSync(path, \"utf8\");\n return { path, tokens: countTokens(text), missing: false };\n });\n}\n\nexport function auditFiles(paths: string[]): FilesReport {\n const readings = readFileTokens(paths);\n const total = readings.reduce((sum, r) => sum + r.tokens, 0);\n return { encoding: TOKEN_ENCODING, readings, total };\n}\n\nexport { SINGLE_FILE_LIMIT, TOTAL_LIMIT };\n\n// Referenced by the combined report's severity rules.\nexport function filesFindings(report: FilesReport): { path: string; tokens: number }[] {\n return report.readings.filter((r) => r.tokens > SINGLE_FILE_LIMIT).map((r) => ({ path: r.path, tokens: r.tokens }));\n}\n","/**\n * `pai audit tokens hooks` — token cost of the hooks that fire before the\n * model ever sees a token: SessionStart and UserPromptSubmit run on every\n * new session / every prompt respectively, and whatever they print to\n * stdout is injected into context.\n */\n\nimport { existsSync, readFileSync } from \"node:fs\";\nimport { homedir } from \"node:os\";\nimport { join } from \"node:path\";\nimport { spawnSync } from \"node:child_process\";\nimport { randomUUID } from \"node:crypto\";\nimport { countTokens, TOKEN_ENCODING } from \"./tokens.js\";\n\nconst HOOK_TIMEOUT_MS = 20_000;\n\ninterface HookEntry {\n type?: string;\n command?: string;\n timeout?: number;\n}\n\ninterface HookGroup {\n matcher?: string;\n hooks?: HookEntry[];\n}\n\ninterface SettingsJson {\n env?: Record<string, string>;\n hooks?: Record<string, HookGroup[]>;\n}\n\nexport interface HookReading {\n event: string;\n matcher?: string;\n command: string;\n tokens: number;\n error?: string;\n}\n\nexport interface HooksReport {\n encoding: string;\n settingsFiles: string[];\n readings: HookReading[];\n totalsByEvent: Record<string, number>;\n preToolUseHooks: { matcher?: string; command: string }[];\n preToolUseRewritesBash: boolean;\n}\n\nfunction settingsPaths(cwd: string): string[] {\n return [join(homedir(), \".claude\", \"settings.json\"), join(cwd, \".claude\", \"settings.json\")].filter(existsSync);\n}\n\nfunction readSettings(path: string): SettingsJson {\n try {\n return JSON.parse(readFileSync(path, \"utf8\")) as SettingsJson;\n } catch {\n return {};\n }\n}\n\n/** Expand ${PAI_DIR} and $HOME/${HOME} the way settings.json's own hooks do. */\nfunction expandCommand(command: string, env: Record<string, string>): string {\n const paiDir = env.PAI_DIR ?? process.env.PAI_DIR ?? join(homedir(), \".claude\");\n return command\n .replace(/\\$\\{PAI_DIR\\}/g, paiDir)\n .replace(/\\$\\{HOME\\}/g, homedir())\n .replace(/\\$HOME\\b/g, homedir());\n}\n\nfunction syntheticStdin(cwd: string, eventName: string): string {\n const sessionId = randomUUID();\n return JSON.stringify({\n session_id: sessionId,\n cwd,\n hook_event_name: eventName,\n prompt: \"audit\",\n transcript_path: join(homedir(), \".claude\", \"projects\", \"audit\", `${sessionId}.jsonl`),\n });\n}\n\nfunction runHook(command: string, cwd: string, eventName: string): { stdout: string; error?: string } {\n const result = spawnSync(command, {\n shell: true,\n cwd,\n input: syntheticStdin(cwd, eventName),\n encoding: \"utf8\",\n timeout: HOOK_TIMEOUT_MS,\n });\n if (result.error) return { stdout: \"\", error: result.error.message };\n if (result.signal) return { stdout: result.stdout ?? \"\", error: `killed by ${result.signal} (timeout?)` };\n return { stdout: result.stdout ?? \"\" };\n}\n\nconst AUDITED_EVENTS = [\"SessionStart\", \"UserPromptSubmit\"];\n\nexport function auditHooks(cwd: string): HooksReport {\n const settingsFiles = settingsPaths(cwd);\n const readings: HookReading[] = [];\n const totalsByEvent: Record<string, number> = {};\n const preToolUseHooks: { matcher?: string; command: string }[] = [];\n let preToolUseRewritesBash = false;\n\n for (const settingsPath of settingsFiles) {\n const settings = readSettings(settingsPath);\n const env = settings.env ?? {};\n\n for (const eventName of AUDITED_EVENTS) {\n const groups = settings.hooks?.[eventName] ?? [];\n totalsByEvent[eventName] = totalsByEvent[eventName] ?? 0;\n for (const group of groups) {\n for (const entry of group.hooks ?? []) {\n if (!entry.command) continue;\n const command = expandCommand(entry.command, env);\n const { stdout, error } = runHook(command, cwd, eventName);\n const tokens = countTokens(stdout);\n totalsByEvent[eventName] += tokens;\n readings.push({ event: eventName, matcher: group.matcher, command, tokens, error });\n }\n }\n }\n\n for (const group of settings.hooks?.PreToolUse ?? []) {\n for (const entry of group.hooks ?? []) {\n if (!entry.command) continue;\n preToolUseHooks.push({ matcher: group.matcher, command: expandCommand(entry.command, env) });\n if (group.matcher === \"Bash\") preToolUseRewritesBash = true;\n }\n }\n }\n\n return { encoding: TOKEN_ENCODING, settingsFiles, readings, totalsByEvent, preToolUseHooks, preToolUseRewritesBash };\n}\n","/**\n * first-turn.ts — where a turn's context actually comes from, attributed\n * line by line from the transcript.\n *\n * The API reports one number per assistant turn (cache_read + cache_creation\n * + input). The transcript holds the pieces Claude Code injected since the\n * previous turn: SessionStart / UserPromptSubmit hook output, the skill\n * listing, the deferred-tool name list, MCP server instructions, the human\n * prompt, any isMeta expansion (a skill body, a pasted slash-command result),\n * tool results, and the previous turn's own visible output (re-sent as\n * context on every later turn). Each of those is counted in cl100k here;\n * what is left over is the system prompt, the built-in tool schemas, agents\n * and memory files, which never appear in the transcript.\n *\n * Only the live lineage counts. Lines chain by parentUuid, and a prompt the\n * user abandoned before a reply (Escape, re-submit) leaves a dead branch\n * whose lines were never sent — run 9 measured a session whose first\n * /Name was abandoned and read 24k where the true baseline is 30k, because\n * the 3.5k-token skill listing sat on the dead branch.\n *\n * `type:\"system\"` lines with subtype local_command hold a slash command's\n * raw stdout, and the model receives it verbatim in addition to the isMeta\n * markdown copy — run 11 measured both present on the same turn, so both are\n * counted.\n */\n\nimport { readFileSync, existsSync } from \"node:fs\";\nimport { countTokens } from \"./tokens.js\";\n\nexport type FirstTurnKind =\n | \"hook:SessionStart\"\n | \"hook:UserPromptSubmit\"\n | \"hook:other\"\n | \"skill_listing\"\n | \"deferred_tools\"\n | \"mcp_instructions\"\n | \"attachment:other\"\n | \"prompt\"\n | \"prompt:meta\"\n | \"prompt:local_command\"\n | \"system:display-only\"\n | \"tool_result\"\n | \"assistant:prev\";\n\nexport interface FirstTurnItem {\n kind: FirstTurnKind;\n tokens: number;\n /** Hook name, attachment type, or the first 40 chars of a prompt. */\n label: string;\n}\n\nexport interface FirstTurnBreakdown {\n path: string;\n /** cache_read + cache_creation + input on the first assistant turn; null if none. */\n apiContext: number | null;\n items: FirstTurnItem[];\n /** Sum of every live-lineage item except system:display-only. */\n transcriptTokens: number;\n /** Per-kind sums over the live lineage (display-only excluded). */\n byKind: Partial<Record<FirstTurnKind, number>>;\n /** apiContext - transcriptTokens: system prompt, tool schemas, agents, memory (+ tokenizer delta). */\n remainder: number | null;\n /** Tokens on abandoned branches before the first turn — persisted but never sent. */\n deadBranchTokens: number;\n deadBranchLines: number;\n}\n\n/** Per-turn attribution of one API call's context growth (turn 2+; turn 1 reduces to the FirstTurnBreakdown shape). */\nexport interface TurnBreakdown {\n path: string;\n turn: number;\n /** cache_read + cache_creation + input on this turn's API call. */\n apiContext: number | null;\n /** Same, for the previous API call; 0 for turn 1. */\n prevApiContext: number;\n /** apiContext - prevApiContext; null if this turn has no usage. */\n billedDelta: number | null;\n /** Previous call's usage.output_tokens (includes thinking); 0 for turn 1. */\n prevOutputTokens: number;\n items: FirstTurnItem[];\n /** Sum of every item except system:display-only. */\n transcriptTokens: number;\n byKind: Partial<Record<FirstTurnKind, number>>;\n /** cl100k count of the previous call's visible text + tool_use input (the assistant:prev item); not part of transcriptTokens. */\n prevVisibleTokens: number;\n /**\n * billedDelta - transcriptTokens - prevOutputTokens. transcriptTokens\n * excludes assistant:prev (the previous call's visible output is only\n * present via prevOutputTokens, which also covers its redacted thinking),\n * so this is a single-count residual: positive means cl100k undercounts\n * the billing tokenizer for this turn's content; negative means the\n * previous call's thinking was not re-sent (thinking is only re-sent\n * inside a tool-use loop, not after a turn-ending text reply).\n */\n residual: number | null;\n}\n\ninterface ContentBlock {\n type?: string;\n text?: string;\n input?: unknown;\n}\n\ninterface Line {\n type?: string;\n subtype?: string;\n uuid?: string;\n parentUuid?: string | null;\n isMeta?: boolean;\n content?: string;\n attachment?: {\n type?: string;\n hookName?: string;\n stdout?: string;\n content?: string;\n addedNames?: string[];\n instructions?: unknown;\n addedInstructions?: unknown;\n };\n message?: {\n id?: string;\n content?: unknown;\n usage?: {\n cache_read_input_tokens?: number;\n cache_creation_input_tokens?: number;\n input_tokens?: number;\n output_tokens?: number;\n };\n };\n}\n\nfunction parseLines(path: string): Line[] {\n const lines: Line[] = [];\n for (const raw of readFileSync(path, \"utf8\").split(\"\\n\")) {\n if (!raw.trim()) continue;\n try {\n lines.push(JSON.parse(raw) as Line);\n } catch {\n /* skip */\n }\n }\n return lines;\n}\n\nfunction classify(line: Line): FirstTurnItem | null {\n if (line.type === \"attachment\") {\n const a = line.attachment ?? {};\n switch (a.type) {\n case \"hook_success\": {\n const name = a.hookName ?? \"\";\n const kind: FirstTurnKind = name.startsWith(\"SessionStart\")\n ? \"hook:SessionStart\"\n : name.startsWith(\"UserPromptSubmit\")\n ? \"hook:UserPromptSubmit\"\n : \"hook:other\";\n return { kind, tokens: countTokens(a.stdout ?? a.content ?? \"\"), label: name };\n }\n case \"skill_listing\":\n return { kind: \"skill_listing\", tokens: countTokens(a.content ?? \"\"), label: \"skill_listing\" };\n case \"deferred_tools_delta\":\n return { kind: \"deferred_tools\", tokens: countTokens((a.addedNames ?? []).join(\"\\n\")), label: \"deferred_tools_delta\" };\n case \"mcp_instructions_delta\":\n return {\n kind: \"mcp_instructions\",\n tokens: countTokens(JSON.stringify(a.instructions ?? a.addedInstructions ?? a)),\n label: \"mcp_instructions_delta\",\n };\n default:\n return { kind: \"attachment:other\", tokens: countTokens(JSON.stringify(a)), label: a.type ?? \"attachment\" };\n }\n }\n if (line.type === \"user\") {\n const c = line.message?.content;\n const isToolResult = Array.isArray(c) && (c as ContentBlock[]).some((b) => b?.type === \"tool_result\");\n const text = typeof c === \"string\" ? c : JSON.stringify(c ?? \"\");\n return {\n kind: isToolResult ? \"tool_result\" : line.isMeta ? \"prompt:meta\" : \"prompt\",\n tokens: countTokens(text),\n label: text.slice(0, 40).replace(/\\s+/g, \" \"),\n };\n }\n if (line.type === \"system\") {\n const kind: FirstTurnKind = line.subtype === \"local_command\" ? \"prompt:local_command\" : \"system:display-only\";\n return { kind, tokens: countTokens(line.content ?? \"\"), label: line.subtype ?? \"system\" };\n }\n return null;\n}\n\nfunction apiContextOf(usage: NonNullable<Line[\"message\"]>[\"usage\"]): number | null {\n if (!usage) return null;\n return (usage.cache_read_input_tokens ?? 0) + (usage.cache_creation_input_tokens ?? 0) + (usage.input_tokens ?? 0);\n}\n\n/**\n * Attribute API call `turn`'s context growth to the transcript lines\n * injected since the previous call. Turn numbering matches\n * session-usage.ts's `turnIndex`: several assistant lines sharing one\n * message.id (one line per streamed content block) are one call. Returns\n * null if the transcript has fewer than `turn` calls.\n */\nexport function turnBreakdown(path: string, turn: number): TurnBreakdown | null {\n if (!existsSync(path)) return null;\n const lines = parseLines(path);\n const byUuid = new Map<string, Line>();\n for (const l of lines) if (l.uuid) byUuid.set(l.uuid, l);\n\n const seen = new Set<string>();\n let n = 0;\n let target: Line | null = null;\n for (const l of lines) {\n if (l.type === \"assistant\" && l.message?.usage) {\n const id = l.message.id ?? l.uuid ?? \"\";\n if (!seen.has(id)) {\n seen.add(id);\n n++;\n if (n === turn) {\n target = l;\n break;\n }\n }\n }\n }\n if (!target) return null;\n\n // Walk parentUuid back, collecting the live lineage since the previous\n // call, until an assistant-with-usage line (a block of the previous call)\n // is hit.\n const rawChain: Line[] = [];\n let prevLine: Line | null = null;\n let cur = target.parentUuid ? byUuid.get(target.parentUuid) : undefined;\n while (cur) {\n if (cur.type === \"assistant\" && cur.message?.usage) {\n prevLine = cur;\n break;\n }\n rawChain.unshift(cur);\n cur = cur.parentUuid ? byUuid.get(cur.parentUuid) : undefined;\n }\n\n const items: FirstTurnItem[] = [];\n for (const l of rawChain) {\n const item = classify(l);\n if (item) items.push(item);\n }\n\n let prevApiContext = 0;\n let prevOutputTokens = 0;\n if (prevLine) {\n prevApiContext = apiContextOf(prevLine.message?.usage) ?? 0;\n prevOutputTokens = prevLine.message?.usage?.output_tokens ?? 0;\n\n // Gather every content-block line of the previous call (streaming can\n // split one call's thinking/text/tool_use blocks across several lines\n // chained by parentUuid, all sharing one message.id).\n const prevId = prevLine.message?.id ?? prevLine.uuid ?? \"\";\n const prevBlocks: Line[] = [prevLine];\n let cur2 = prevLine.parentUuid ? byUuid.get(prevLine.parentUuid) : undefined;\n while (cur2 && cur2.type === \"assistant\" && (cur2.message?.id ?? cur2.uuid ?? \"\") === prevId) {\n prevBlocks.unshift(cur2);\n cur2 = cur2.parentUuid ? byUuid.get(cur2.parentUuid) : undefined;\n }\n\n let prevText = \"\";\n let thinkingCount = 0;\n for (const b of prevBlocks) {\n const content = b.message?.content;\n if (!Array.isArray(content)) continue;\n for (const block of content as ContentBlock[]) {\n if (block?.type === \"text\") prevText += block.text ?? \"\";\n else if (block?.type === \"tool_use\") prevText += JSON.stringify(block.input ?? {});\n else if (block?.type === \"thinking\") thinkingCount++;\n }\n }\n const label = thinkingCount > 0 ? `previous call output (${thinkingCount} thinking block(s) redacted)` : \"previous call output\";\n items.push({ kind: \"assistant:prev\", tokens: countTokens(prevText), label });\n }\n\n const byKind: Partial<Record<FirstTurnKind, number>> = {};\n let transcriptTokens = 0;\n let prevVisibleTokens = 0;\n for (const item of items) {\n if (item.kind === \"system:display-only\") continue;\n if (item.kind === \"assistant:prev\") {\n prevVisibleTokens = item.tokens;\n continue;\n }\n transcriptTokens += item.tokens;\n byKind[item.kind] = (byKind[item.kind] ?? 0) + item.tokens;\n }\n\n const apiContext = apiContextOf(target.message?.usage);\n const billedDelta = apiContext === null ? null : apiContext - prevApiContext;\n const residual = billedDelta === null ? null : billedDelta - transcriptTokens - prevOutputTokens;\n\n return {\n path,\n turn,\n apiContext,\n prevApiContext,\n billedDelta,\n prevOutputTokens,\n items,\n transcriptTokens,\n byKind,\n prevVisibleTokens,\n residual,\n };\n}\n\n/** Attribute the first assistant turn's context to the transcript lines on its live lineage. */\nexport function firstTurnBreakdown(path: string): FirstTurnBreakdown {\n const empty: FirstTurnBreakdown = {\n path,\n apiContext: null,\n items: [],\n transcriptTokens: 0,\n byKind: {},\n remainder: null,\n deadBranchTokens: 0,\n deadBranchLines: 0,\n };\n if (!existsSync(path)) return empty;\n\n const tb = turnBreakdown(path, 1);\n if (!tb) return empty;\n\n const lines = parseLines(path);\n const byUuid = new Map<string, Line>();\n let firstIndex = -1;\n for (let i = 0; i < lines.length; i++) {\n const l = lines[i];\n if (l.uuid) byUuid.set(l.uuid, l);\n if (l.type === \"assistant\" && l.message?.usage && firstIndex < 0) firstIndex = i;\n }\n if (firstIndex < 0) return empty;\n const first = lines[firstIndex];\n\n const live = new Set<Line>();\n let cur = first.parentUuid ? byUuid.get(first.parentUuid) : undefined;\n while (cur && !live.has(cur)) {\n live.add(cur);\n cur = cur.parentUuid ? byUuid.get(cur.parentUuid) : undefined;\n }\n\n let deadBranchTokens = 0;\n let deadBranchLines = 0;\n for (let i = 0; i < firstIndex; i++) {\n const l = lines[i];\n if (!l.uuid || live.has(l)) continue;\n const item = classify(l);\n if (!item || item.kind === \"system:display-only\") continue;\n deadBranchLines++;\n deadBranchTokens += item.tokens;\n }\n\n return {\n path,\n apiContext: tb.apiContext,\n items: tb.items,\n transcriptTokens: tb.transcriptTokens,\n byKind: tb.byKind,\n remainder: tb.residual,\n deadBranchTokens,\n deadBranchLines,\n };\n}\n\n/** One-line evidence string for the combined report's first-turn finding. */\nexport function firstTurnEvidence(b: FirstTurnBreakdown): string {\n const parts: string[] = [];\n const order: FirstTurnKind[] = [\n \"skill_listing\",\n \"mcp_instructions\",\n \"deferred_tools\",\n \"hook:SessionStart\",\n \"hook:UserPromptSubmit\",\n \"hook:other\",\n \"prompt:meta\",\n \"prompt:local_command\",\n \"prompt\",\n \"attachment:other\",\n ];\n for (const k of order) {\n const v = b.byKind[k];\n if (v) parts.push(`${k} ${v}`);\n }\n const dead = b.deadBranchTokens > 0 ? `; dead-branch ${b.deadBranchTokens} not sent` : \"\";\n return `transcript-side ${b.transcriptTokens} (${parts.join(\", \")}), remainder ${b.remainder ?? \"n/a\"}${dead}`;\n}\n","/**\n * context-fill.ts — \"how full is this session's context window, right now?\"\n *\n * No hook payload carries this number directly (verified: PreCompact,\n * UserPromptSubmit and PostToolUse stdin never include `context_window`).\n * Two sources can reconstruct it, in order of preference:\n *\n * 1. STATUSLINE STATE FILE — statusline-command.sh receives the exact\n * figure from Claude Code (`.context_window.used_percentage` /\n * `.context_window.context_window_size`) on every render and persists\n * it to `${TMPDIR}/pai-context-<session_id>.json`. This is authoritative\n * but depends on the status line having rendered recently — a session\n * whose terminal isn't drawing a status line (headless, backgrounded)\n * leaves this file missing or stale.\n *\n * 2. TRANSCRIPT USAGE — every `message.usage` entry in the session's own\n * .jsonl transcript already reports the token accounting for that one\n * API call: `input_tokens + cache_read_input_tokens +\n * cache_creation_input_tokens` on the MOST RECENT such entry *is* the\n * current context fill, because each request resends the full context.\n * This is why it must be the last entry's fields summed, never a sum\n * across entries — summing across entries is a running token-spend\n * total, not a fill reading (see calculateSessionTokens, a different\n * metric answering a different question).\n *\n * When neither source is available, the answer is UNKNOWN, not zero. A zero\n * reads as \"plenty of room\" and would silently suppress every downstream\n * decision that depends on this number (the compaction handover chief among\n * them) for the entire session.\n */\n\nimport { existsSync, readdirSync, readFileSync } from \"node:fs\";\nimport { homedir, tmpdir } from \"node:os\";\nimport { join } from \"node:path\";\nimport {\n DEFAULT_CONTEXT_WINDOW,\n contextWindowFromModelId,\n stripModelVariant,\n} from \"../../../utils/model-window.js\";\nimport { readWorkersSection } from \"../../../workers/config.js\";\n\nexport { DEFAULT_CONTEXT_WINDOW };\n\n/** How many trailing lines of the transcript to scan for a usage entry.\n * A single turn is rarely more than a handful of JSONL lines (assistant\n * text + tool_use/tool_result pairs), so 40 comfortably covers one turn\n * even a busy one, without reading the whole file on every check. */\nconst TRANSCRIPT_TAIL_LINES = 40;\n\n/** A statusline reading older than this is treated as not there at all —\n * a stale number is worse than none, because it looks confident. */\nconst STATUSLINE_STALE_MS = 5 * 60 * 1000; // 5 minutes\n\nexport type ContextFillSource = \"statusline\" | \"transcript\" | \"unknown\";\n\nexport interface ContextFillReading {\n status: \"ok\" | \"unknown\";\n /** Tokens currently occupying the context window, or null when unknown. */\n usedTokens: number | null;\n windowSize: number;\n /** usedTokens / windowSize, or null when unknown. Not clamped to [0,1] —\n * callers that need a clamped display value do that themselves so the\n * raw reading (which can legitimately exceed 1 for a moment) is never\n * silently rewritten here. */\n fraction: number | null;\n source: ContextFillSource;\n}\n\nfunction unknownReading(windowSize: number): ContextFillReading {\n return { status: \"unknown\", usedTokens: null, windowSize, fraction: null, source: \"unknown\" };\n}\n\n// ---------------------------------------------------------------------------\n// Source 1 — statusline state file\n// ---------------------------------------------------------------------------\n\nexport function statuslineStateFilePath(sessionId: string): string {\n return join(tmpdir(), `pai-context-${sessionId}.json`);\n}\n\ninterface StatuslineState {\n used_percentage?: number;\n context_window_size?: number;\n session_id?: string;\n timestamp?: number;\n}\n\n/**\n * Read the state file statusline-command.sh writes on every render.\n * Returns null when the file is missing, unparsable, or older than\n * STATUSLINE_STALE_MS — all three mean \"not a source right now\".\n */\nexport function readStatuslineFill(sessionId: string, now = Date.now()): ContextFillReading | null {\n if (!sessionId) return null;\n const path = statuslineStateFilePath(sessionId);\n if (!existsSync(path)) return null;\n\n let raw: StatuslineState;\n try {\n raw = JSON.parse(readFileSync(path, \"utf-8\"));\n } catch {\n return null;\n }\n\n if (typeof raw.timestamp !== \"number\" || now - raw.timestamp > STATUSLINE_STALE_MS) {\n return null; // stale — fall through to the transcript source\n }\n if (typeof raw.used_percentage !== \"number\" || typeof raw.context_window_size !== \"number\") {\n return null;\n }\n\n const windowSize = raw.context_window_size;\n const usedTokens = Math.round((raw.used_percentage / 100) * windowSize);\n return {\n status: \"ok\",\n usedTokens,\n windowSize,\n fraction: raw.used_percentage / 100,\n source: \"statusline\",\n };\n}\n\n// ---------------------------------------------------------------------------\n// Source 2 — transcript usage (fallback)\n// ---------------------------------------------------------------------------\n\ninterface UsageEntry {\n input_tokens?: number;\n cache_read_input_tokens?: number;\n cache_creation_input_tokens?: number;\n}\n\n/**\n * Sum the three context-carrying fields of ONE usage object — never across\n * usage objects. A single `message.usage` already reports what that one API\n * call sent as context; adding another entry's numbers to it produces a\n * cumulative spend figure, not a fill reading, and can exceed the window\n * size many times over on a long session (the exact defect this helper\n * exists to not repeat — see the PreCompact header fix in the same change).\n */\nfunction usageTotal(usage: UsageEntry): number {\n return (\n (usage.input_tokens || 0) +\n (usage.cache_read_input_tokens || 0) +\n (usage.cache_creation_input_tokens || 0)\n );\n}\n\n/**\n * Derive context fill from the tail of a .jsonl transcript: the most recent\n * `message.usage` entry, read from the end backwards so a trailing line with\n * no usage field (a plain text turn, a tool_result) doesn't hide one just\n * before it.\n *\n * The window size, when the caller has none, is derived from the transcript's\n * own last assistant model id (\"glm-5.3[1m]\" → 1,000,000) before falling back\n * to the assumed default — a session whose model declares its window should\n * not be measured against a guess.\n */\nexport function contextFillFromTranscript(\n transcriptPath: string,\n windowSize?: number\n): ContextFillReading {\n if (!transcriptPath || !existsSync(transcriptPath)) {\n return unknownReading(windowSize ?? DEFAULT_CONTEXT_WINDOW);\n }\n\n let raw: string;\n try {\n raw = readFileSync(transcriptPath, \"utf-8\");\n } catch {\n return unknownReading(windowSize ?? DEFAULT_CONTEXT_WINDOW);\n }\n\n const lines = raw.trim().split(\"\\n\").filter((l) => l.trim());\n const tail = lines.slice(-TRANSCRIPT_TAIL_LINES);\n\n // one pass from the end: the first usage entry is the fill reading, and the\n // last assistant model seen (before or at that point) declares the window\n let lastModel: string | null = null;\n for (let i = tail.length - 1; i >= 0; i--) {\n let entry: { type?: string; message?: { usage?: UsageEntry; model?: unknown } };\n try {\n entry = JSON.parse(tail[i]);\n } catch {\n continue;\n }\n if (entry?.type === \"assistant\" && lastModel === null) {\n const model = entry.message?.model;\n if (typeof model === \"string\" && model !== \"\") lastModel = model;\n }\n const usage = entry?.message?.usage;\n if (usage && typeof usage === \"object\") {\n const window = windowSize ?? contextWindowFromModelId(lastModel) ?? DEFAULT_CONTEXT_WINDOW;\n const usedTokens = usageTotal(usage);\n return {\n status: \"ok\",\n usedTokens,\n windowSize: window,\n fraction: usedTokens / window,\n source: \"transcript\",\n };\n }\n }\n\n const window = windowSize ?? contextWindowFromModelId(lastModel) ?? DEFAULT_CONTEXT_WINDOW;\n return unknownReading(window);\n}\n\n// ---------------------------------------------------------------------------\n// Precedence — statusline (fresh) > transcript > unknown\n// ---------------------------------------------------------------------------\n\nexport function getContextFill(\n input: { sessionId?: string; transcriptPath?: string; windowSize?: number },\n now = Date.now()\n): ContextFillReading {\n if (input.sessionId) {\n const fromStatusline = readStatuslineFill(input.sessionId, now);\n if (fromStatusline) return fromStatusline;\n }\n\n if (input.transcriptPath) {\n // windowSize passes through only when the caller knows it; otherwise the\n // transcript's own model id is asked first (see contextFillFromTranscript)\n return contextFillFromTranscript(input.transcriptPath, input.windowSize);\n }\n\n return unknownReading(input.windowSize ?? DEFAULT_CONTEXT_WINDOW);\n}\n\n// ---------------------------------------------------------------------------\n// Display helper — clamp-and-flag rather than ever print an absurd number\n// ---------------------------------------------------------------------------\n\nexport interface FillDisplay {\n /** e.g. \"63k\" or \"unknown\". Never a number larger than the window. */\n text: string;\n /** True when the raw reading exceeded the window size and had to be\n * clamped — that reading was a bug, not a fill, and callers may want to\n * log it even though the displayed text is already safe. */\n flagged: boolean;\n}\n\nexport function formatContextFill(reading: ContextFillReading): FillDisplay {\n if (reading.status !== \"ok\" || reading.usedTokens === null) {\n return { text: \"unknown\", flagged: false };\n }\n\n let used = reading.usedTokens;\n let flagged = false;\n if (used > reading.windowSize) {\n flagged = true;\n used = reading.windowSize;\n }\n\n const text = used > 1000 ? `${Math.round(used / 1000)}k` : String(used);\n return { text: flagged ? `${text}+ (clamped — reading exceeded window)` : text, flagged };\n}\n\n// ---------------------------------------------------------------------------\n// Threshold-triggered handover — WHEN to warm up / refresh the model-written\n// summary ahead of a compaction.\n// ---------------------------------------------------------------------------\n//\n// THE CONFIGURED VALUE PREDICTS THE TRIGGER ONLY WHILE THE PLATFORM'S FORMULA\n// HOLDS. CLAUDE_AUTOCOMPACT_PCT_OVERRIDE was 80 throughout, on this machine,\n// across BOTH of the following regimes — the same configured number, two\n// different truths:\n//\n// 2026-08-16 .. 2026-09-10 preTokens ~ 993,096 – 1,002,500 (~100% of a 1M window; override ignored)\n// 2026-09-12 .. 2026-09-20 preTokens ~ 782,981 – 791,995 (1M window) and\n// preTokens ~ 143,280 – 154,832 (200k window)\n// = pct/100 × (window − 20,000), measured 2026-09-20\n//\n// A threshold derived only from the configured override was wrong for the\n// first regime and will be wrong again the next time the formula moves — the\n// config is not ground truth, it is what Claude Code claims it will honor.\n// Two more facts (2026-09-20): the settings.json `env` value beats the\n// process env, so there is no per-launch override; and headless `-p`\n// sessions do not compact at the derived trigger at all.\n//\n// GROUND TRUTH IS ON DISK ALREADY: every real compaction writes a\n// `compact_boundary` system event to the transcript with\n// `compactMetadata.preTokens` — the platform's own count of context tokens\n// immediately before IT compacted. `measureCompactionTrigger` scans a\n// project's own transcripts for these and takes the MINIMUM of the most\n// recent three (minimum, not mean: warm-up must fire before the earliest\n// plausible boundary, not the average one). A project that has compacted\n// before learns its own trigger and needs no code change when the regime\n// moves again — the same derivation produced 998,508 pre-09-12 and ~784k\n// after, from one unchanged formula.\n//\n// The configured chain — env override, then a default — is the fallback,\n// used ONLY when a project has no compaction history yet:\n//\n// effectiveTrigger = measured ?? configuredTrigger(windowSize, override ?? 80)\n// configuredTrigger(windowSize, pct) = pct/100 * (windowSize - 20,000)\n//\n// DEFAULT IS 80, NOT 100, WHEN NOTHING IS KNOWN. 100 was considered — it\n// matches the FIRST regime above — and rejected: the costs are asymmetric.\n// Warming up too early wastes one summary; cheap, invisible. Warming up too\n// late produces exactly the degraded successor session this feature exists\n// to prevent. 80 is the conservative default until a project has its own\n// measured history to correct it.\n//\n// Margins below the effective trigger are absolute tokens, sized against the\n// largest single-turn context jump measured: 62,383 tokens (517,952 →\n// 580,335, about 27 seconds). warmup sits 100k below the trigger —\n// comfortably more than that jump, so a session cannot leap clean over\n// warmup straight into a compaction in one turn; refresh at 40k below\n// catches a session that sat above warmup for a while; fireNow at 15k below\n// is \"no time left for a clean crossing\" and fires immediately rather than\n// waiting for one.\nexport const DEFAULT_AUTOCOMPACT_PCT = 80;\n\n/**\n * The platform's actual compaction trigger for a window/pct pair. NOT\n * windowSize * pct/100 — that formula gives 800,000 on 1M/80 and 160,000 on\n * 200k/80, but the platform fires at pct/100 * (windowSize - 20,000):\n * 784,000 and 144,000, measured on every compact_boundary on this machine,\n * 2026-09-20 (see the module comment above).\n */\nexport function configuredTrigger(windowSize: number, pct: number): number {\n return Math.round((pct / 100) * (windowSize - 20_000));\n}\n\n/** How many of the most recent compact_boundary events to consider, and to\n * take the minimum of. */\nconst MEASURED_TRIGGER_SAMPLE_SIZE = 3;\n\nexport const THRESHOLD_MARGIN_TOKENS = {\n warmup: 100_000,\n refresh: 40_000,\n immediate: 15_000,\n} as const;\n\n// ---------------------------------------------------------------------------\n// Measured trigger — scan a project's own transcript history for the\n// platform's own compact_boundary events, ground truth over configuration.\n// ---------------------------------------------------------------------------\n//\n// Deliberately re-implements the tiny bit of path encoding it needs (below)\n// rather than importing project-utils/paths.ts's encodePath: that module\n// chain ends in pai-paths.ts, which calls process.exit(1) if ADAPTER_DIR does\n// not resolve to an existing directory. context-fill.ts is specifically the\n// module a hook falls back on when its environment is unreliable — pulling\n// in a dependency that can kill the process on import would defeat that.\n// Claude Code's transcript directory is a fixed, home-relative convention\n// (~/.claude/projects/<encoded-cwd>/), not something ADAPTER_DIR governs, so\n// resolving it directly here is also just the more correct dependency, not\n// only the safer one.\n\n/** Real default — overridable per-call so tests never touch the user's\n * actual ~/.claude/projects/ (a live directory this very session writes to). */\nexport const CLAUDE_PROJECTS_DIR = join(homedir(), \".claude\", \"projects\");\n\nfunction encodeProjectPath(cwd: string): string {\n return cwd.replace(/[/\\s.-]/g, \"-\");\n}\n\nexport interface CompactBoundarySample {\n preTokens: number;\n timestampMs: number;\n /** ISO string, kept alongside timestampMs so callers can print it. */\n timestamp: string;\n /** The event's own uuid when present — the dedup key. Claude Code's\n * `sessions/` archive directory mirrors the live project transcript, so\n * the SAME compact_boundary event can legitimately appear in two files;\n * without deduping by identity, \"most recent three\" can silently become\n * three copies of one event, which is a stale reading wearing a\n * plausible-looking sample size. */\n uuid?: string;\n}\n\n/** Every `.jsonl` transcript belonging to a project — top-level (the live\n * file) and `sessions/` (Claude Code's archive, which mirrors it). No file\n * is excluded and no ordering is applied here: ordering by EVENT\n * timestamp, not by file mtime, is the whole point (see\n * readCompactBoundarySamples) — a file's mtime does not reliably track\n * which events inside it are recent, and pre-filtering by mtime is exactly\n * what caused this function to return a stale, pre-regime-change trigger\n * on real data. */\nfunction listProjectTranscripts(cwd: string, projectsDir: string): string[] {\n const projectDir = join(projectsDir, encodeProjectPath(cwd));\n if (!existsSync(projectDir)) return [];\n\n const paths: string[] = [];\n const collect = (dir: string): void => {\n if (!existsSync(dir)) return;\n let entries: string[];\n try {\n entries = readdirSync(dir);\n } catch {\n return;\n }\n for (const entry of entries) {\n if (entry.endsWith(\".jsonl\")) paths.push(join(dir, entry));\n }\n };\n collect(projectDir);\n collect(join(projectDir, \"sessions\"));\n return paths;\n}\n\n/** Placeholder model id the platform writes on synthetic assistant turns. */\nconst SYNTHETIC_MODEL = \"<synthetic>\";\n\n/**\n * Which family of model wrote a transcript: a stable key derived from the id\n * itself, never from an Anthropic allowlist. \"claude\" is the platform's own;\n * any other id reduces to its base form before any bracketed variant suffix\n * (\"glm-5.3[1m]\" → \"glm-5.3\"), so the same model family always produces the\n * same key; \"unknown\" is no usable model field at all. Whether a family is\n * foreign is decided by comparison (see isForeignModelFamily), not by the\n * key — keying \"foreign\" here is what made non-Anthropic primary models lose\n * their own compaction history.\n */\nexport type TranscriptModelFamily = \"claude\" | \"unknown\" | (string & {});\n\nexport function modelFamily(model: string | null | undefined): TranscriptModelFamily {\n if (typeof model !== \"string\" || model === \"\" || model === SYNTHETIC_MODEL) return \"unknown\";\n const base = stripModelVariant(model);\n return base.startsWith(\"claude-\") ? \"claude\" : base;\n}\n\n/**\n * Is a model id foreign to this machine's sessions? A model is native when it\n * is a Claude model or one the worker registry configures (any provider's\n * default or fast alias — the models this stack actually runs on). Anything\n * else, with a readable model id, is foreign. Doubt (no id, unreadable\n * registry) reads as native: the callers that skip work on \"foreign\" must not\n * skip it on doubt.\n */\nexport function isForeignModelFamily(\n family: TranscriptModelFamily,\n nativeFamilies?: Set<string>\n): boolean {\n if (family === \"unknown\" || family === \"claude\") return false;\n return !(nativeFamilies ?? nativeModelFamilies()).has(family);\n}\n\n/** Families of every model id the worker registry configures. Never throws:\n * an unreadable or missing registry means \"nothing configured\", which leaves\n * every non-Claude family foreign — the historical behaviour. */\nexport function nativeModelFamilies(configPath?: string): Set<string> {\n const families = new Set<string>();\n try {\n for (const p of Object.values(readWorkersSection(configPath).workers.providers)) {\n families.add(modelFamily(p.models.default));\n if (p.models.fast) families.add(modelFamily(p.models.fast));\n }\n } catch {\n return families;\n }\n return families;\n}\n\n/**\n * The family of the LAST assistant model in a transcript — scanned from the\n * end so a long transcript costs one read and a few lines of parsing.\n * Unreadable or model-less transcripts are \"unknown\", never foreign:\n * the callers that skip work on foreign models must not skip it on doubt.\n * Pair with isForeignModelFamily to judge the key.\n */\nexport function transcriptModelFamily(path: string): TranscriptModelFamily {\n let raw: string;\n try {\n raw = readFileSync(path, \"utf-8\");\n } catch {\n return \"unknown\";\n }\n const lines = raw.split(\"\\n\");\n for (let i = lines.length - 1; i >= 0; i--) {\n const line = lines[i];\n if (!line.includes('\"model\"')) continue;\n try {\n const entry = JSON.parse(line) as { type?: string; message?: { model?: unknown } };\n if (entry.type !== \"assistant\") continue;\n const model = entry.message?.model;\n if (typeof model === \"string\" && model !== SYNTHETIC_MODEL) return modelFamily(model);\n } catch {\n continue;\n }\n }\n return \"unknown\";\n}\n\n/**\n * Every DISTINCT compact_boundary sample found across ALL of a project's\n * transcripts (live + archived), newest first by the event's OWN timestamp\n * — never by which file it came from or that file's mtime. Deduplicated by\n * the event's uuid (falling back to a timestamp+preTokens key for the rare\n * line with no uuid) so an event mirrored into `sessions/` is counted once.\n */\nfunction readCompactBoundarySamples(\n cwd: string,\n projectsDir: string,\n nativeFamilies: Set<string>\n): CompactBoundarySample[] {\n const byKey = new Map<string, CompactBoundarySample>();\n\n for (const path of listProjectTranscripts(cwd, projectsDir)) {\n let raw: string;\n try {\n raw = readFileSync(path, \"utf-8\");\n } catch {\n continue;\n }\n\n // The model governing each sample: the most recent assistant\n // `message.model` seen in this file before the compact_boundary. A\n // transcript written by a model foreign to this machine (a headless\n // worker on another provider, with a different context window) compacts\n // at a different size and must not shape THIS project's trigger — two\n // such workers compacting at ~151k pulled a real project's trigger from\n // ~784k to ~151k. Native is Claude plus every model the registry\n // configures, so a non-Anthropic PRIMARY model keeps its own history.\n // Samples with no model seen yet are kept: older transcripts may lack\n // the field.\n let lastModel: string | null = null;\n let foreignDiscards = 0;\n\n for (const line of raw.split(\"\\n\")) {\n if (!line.trim()) continue;\n let entry: {\n type?: string;\n subtype?: string;\n timestamp?: string;\n uuid?: string;\n compactMetadata?: { preTokens?: number };\n message?: { model?: unknown };\n };\n try {\n entry = JSON.parse(line);\n } catch {\n continue;\n }\n if (entry.type === \"assistant\") {\n const model = entry.message?.model;\n if (typeof model === \"string\" && model !== SYNTHETIC_MODEL) lastModel = model;\n continue;\n }\n if (entry.type !== \"system\" || entry.subtype !== \"compact_boundary\") continue;\n const preTokens = entry.compactMetadata?.preTokens;\n if (typeof preTokens !== \"number\" || !Number.isFinite(preTokens)) continue;\n const timestampMs = entry.timestamp ? Date.parse(entry.timestamp) : NaN;\n if (!Number.isFinite(timestampMs)) continue;\n if (isForeignModelFamily(modelFamily(lastModel), nativeFamilies)) {\n foreignDiscards++;\n continue;\n }\n\n const key = entry.uuid ?? `${timestampMs}:${preTokens}`;\n if (!byKey.has(key)) {\n byKey.set(key, { preTokens, timestampMs, timestamp: entry.timestamp!, uuid: entry.uuid });\n }\n }\n\n if (foreignDiscards > 0) {\n console.error(\n `[context-fill] ignored ${foreignDiscards} compaction sample(s) from a ` +\n `foreign-model transcript (model=${lastModel}): ${path}`\n );\n }\n }\n\n return [...byKey.values()].sort((a, b) => b.timestampMs - a.timestampMs);\n}\n\n/**\n * The most recent MEASURED_TRIGGER_SAMPLE_SIZE distinct compact_boundary\n * events for a project, newest first — exposed on its own (not just the\n * derived minimum) so the number `measureCompactionTrigger` returns is\n * checkable: print these and the timestamps prove which three events\n * produced it, rather than asking for trust.\n */\nexport function selectedCompactionSamples(\n cwd: string,\n projectsDir: string = CLAUDE_PROJECTS_DIR,\n configPath?: string\n): CompactBoundarySample[] {\n if (!cwd) return [];\n return readCompactBoundarySamples(cwd, projectsDir, nativeModelFamilies(configPath))\n .slice(0, MEASURED_TRIGGER_SAMPLE_SIZE);\n}\n\n/**\n * The measured compaction trigger for a project, or null when it has no\n * compaction history yet (a brand-new project, one whose transcripts this\n * process cannot read, or one whose only recent samples belong to a\n * different override regime — see the admissibility filter below).\n * Minimum of the most recent MEASURED_TRIGGER_SAMPLE_SIZE DISTINCT\n * compact_boundary events, ordered by the events' own timestamps across\n * every transcript the project has (live and archived) — see the module\n * comment above for why minimum, not mean, and readCompactBoundarySamples\n * for why \"distinct\" and \"own timestamp\" both matter (a file-mtime-ordered,\n * non-deduplicated version of this returned a stale pre-regime-change\n * trigger on real project data).\n *\n * When `configuredTriggerTokens` is given, a sample is admissible only when\n * preTokens >= 0.5 * configuredTriggerTokens — a compaction that fired far\n * below the currently configured trigger came from a lower override or a\n * smaller window, a different regime than the one in force now, and must\n * not drag the trigger down with it. Real case, 2026-09-20:\n * CLAUDE_AUTOCOMPACT_PCT_OVERRIDE was 20 for seven hours, this project\n * compacted three times at ~195k-198k under it, then the override went\n * back to 80 (configured ~784k) — those three samples are inadmissible and\n * must be discarded, not averaged into the new regime's trigger, or the\n * handover would fire at ~155k for days until three fresh ~784k\n * compactions pushed them out on their own.\n */\nexport function measureCompactionTrigger(\n cwd: string,\n projectsDir: string = CLAUDE_PROJECTS_DIR,\n configPath?: string,\n configuredTriggerTokens?: number\n): number | null {\n const samples = selectedCompactionSamples(cwd, projectsDir, configPath);\n if (samples.length === 0) return null;\n\n let admissible = samples;\n if (configuredTriggerTokens !== undefined) {\n const floor = 0.5 * configuredTriggerTokens;\n admissible = samples.filter((s) => s.preTokens >= floor);\n const discarded = samples.length - admissible.length;\n if (discarded > 0) {\n console.error(\n `[context-fill] discarded ${discarded} compaction sample(s) as other-regime for ${cwd} ` +\n `(preTokens below ${Math.round(floor)}, half of configured=${configuredTriggerTokens}): ` +\n samples.filter((s) => s.preTokens < floor).map((s) => `${s.preTokens}@${s.timestamp}`).join(\", \")\n );\n }\n if (admissible.length === 0) return null;\n }\n\n const trigger = Math.min(...admissible.map((s) => s.preTokens));\n console.error(\n `[context-fill] measured trigger for ${cwd}: ${trigger} (minimum of ` +\n admissible.map((s) => `${s.preTokens}@${s.timestamp}`).join(\", \") + \")\"\n );\n return trigger;\n}\n\n/**\n * Read CLAUDE_AUTOCOMPACT_PCT_OVERRIDE from the environment. Absent →\n * DEFAULT_AUTOCOMPACT_PCT. Present but not a finite number in (0, 100] →\n * also DEFAULT_AUTOCOMPACT_PCT, logged, so a typo in the override degrades\n * to the documented default instead of silently producing nonsense\n * thresholds (0, negative, or a fraction so large no session ever reaches\n * it).\n */\nexport function resolveAutocompactPct(env: NodeJS.ProcessEnv = process.env): number {\n const raw = env.CLAUDE_AUTOCOMPACT_PCT_OVERRIDE;\n if (raw === undefined || raw === \"\") return DEFAULT_AUTOCOMPACT_PCT;\n\n const parsed = Number(raw);\n if (!Number.isFinite(parsed) || parsed <= 0 || parsed > 100) {\n console.error(\n `[context-fill] CLAUDE_AUTOCOMPACT_PCT_OVERRIDE=\"${raw}\" is not a usable percentage — ` +\n `falling back to the default ${DEFAULT_AUTOCOMPACT_PCT}.`\n );\n return DEFAULT_AUTOCOMPACT_PCT;\n }\n return parsed;\n}\n\n/**\n * The two thresholds that actually fire a handover job. \"immediate\" is not a\n * third job — see `isImmediate` below — it only changes how urgently a\n * crossing of these two is treated.\n */\nexport type ThresholdName = \"warmup\" | \"refresh\";\n\n/** Which basis actually produced effectiveTriggerTokens — reported so the\n * number is checkable rather than trusted. */\nexport type TriggerSource = \"measured\" | \"configured\" | \"measured-clamped\";\n\nexport interface ContextFillThresholds {\n warmupTokens: number;\n refreshTokens: number;\n immediateTokens: number;\n /** The derived compaction trigger these were measured back from —\n * min(measured, configured) when a measured value exists. */\n effectiveTriggerTokens: number;\n /** The raw measured value from this project's own compact_boundary\n * history, before any clamping — null when the project has no history.\n * Kept alongside effectiveTriggerTokens so a clamp is visible rather\n * than silent: a caller can see both what was measured and what was\n * actually used. */\n measuredTriggerTokens: number | null;\n /** The raw configured-chain value (env override or default, as a\n * fraction of the window) — always computed and reported, even when it\n * wasn't what ended up being used. */\n configuredTriggerTokens: number;\n /** The autocompact percentage from the configured chain. */\n autocompactPct: number;\n /** \"measured\": a measured value existed and was <= configured, so it was\n * used directly — the better estimate, since it reflects reality the\n * configured percentage cannot know.\n * \"measured-clamped\": a measured value existed but was HIGHER than\n * configured — it reflects a regime that may no longer apply (this\n * project's last compaction predates a since-changed trigger), so the\n * lower, safer configured value was used instead.\n * \"configured\": no measured value exists yet (no compaction history for\n * this project) — the configured chain is all there is. */\n triggerSource: TriggerSource;\n /** True when these were computed against a window size Claude Code itself\n * reported (the statusline source). False when the window is only an\n * assumed default (the transcript-fallback source never learns the real\n * window size) — callers should log this: a threshold silently computed\n * against the wrong basis is the same class of fault as printing a token\n * count larger than the window. */\n windowConfirmed: boolean;\n}\n\nexport interface ContextFillThresholdOpts {\n /** The session's project directory, used to look up its own compaction\n * history for the measured trigger. Omit when unknown — falls back to\n * the configured chain, same as a project with no history yet. */\n cwd?: string;\n /** Override the measured-trigger lookup instead of scanning transcripts —\n * primarily for tests. `null` forces the configured fallback even when\n * `cwd` is given; `undefined` (the default) does the real lookup. */\n measuredTrigger?: number | null;\n}\n\n/**\n * Derive warmup/refresh/immediate thresholds from a fill reading.\n *\n * effectiveTrigger = min(measured, configuredChainValue) when a measured\n * value exists — NOT the measured value outright. A project whose newest\n * compaction predates a regime change measures a stale-HIGH trigger: a real\n * case on this machine measured 998,267 for a project whose ACTUAL current\n * boundary (from a different project's fresher history, cross-checked\n * independently) is ~784,000 — using 998,267 directly would compute a\n * warm-up of 898,267, above the real boundary, so the handover would never\n * fire there. The measurement is honest; it is just old.\n *\n * The minimum is correct in both directions: the measured value is the\n * better estimate when it is LOWER than configured (it reflects reality the\n * configured percentage cannot know — see the module comment above, the\n * 100%-to-78% regime change this project itself lived through); it is\n * unsafe when it is HIGHER (it reflects a regime that no longer applies).\n * Taking the minimum costs nothing in the safe direction — one wasted\n * summary if the project's regime actually did move up — and prevents the\n * unsafe direction, where a stale-high measurement suppresses the handover\n * past the real boundary. That asymmetry is the same one the 80-not-100\n * default was chosen for.\n *\n * Margins below effectiveTrigger are clamped at 0 (and logged) for a window\n * small enough that a margin would otherwise go negative — a pathological\n * input should degrade to \"fire immediately\", never to a threshold below\n * zero.\n */\nexport function contextFillThresholds(\n reading: ContextFillReading,\n env: NodeJS.ProcessEnv = process.env,\n opts: ContextFillThresholdOpts = {}\n): ContextFillThresholds {\n const windowConfirmed = reading.source === \"statusline\";\n const autocompactPct = resolveAutocompactPct(env);\n const configuredTriggerTokens = configuredTrigger(reading.windowSize, autocompactPct);\n\n const measuredTriggerTokens = opts.measuredTrigger !== undefined\n ? opts.measuredTrigger\n : opts.cwd\n ? measureCompactionTrigger(opts.cwd, CLAUDE_PROJECTS_DIR, undefined, configuredTriggerTokens)\n : null;\n\n let effectiveTriggerTokens: number;\n let triggerSource: TriggerSource;\n\n if (measuredTriggerTokens === null) {\n effectiveTriggerTokens = configuredTriggerTokens;\n triggerSource = \"configured\";\n console.error(\n `[context-fill] trigger source: CONFIGURED — no compaction history for this project yet. ` +\n `measured=none, configured=${configuredTriggerTokens} (${autocompactPct}% of a ${reading.windowSize}-token ` +\n `window) -> using ${effectiveTriggerTokens}.`\n );\n } else if (measuredTriggerTokens <= configuredTriggerTokens) {\n effectiveTriggerTokens = measuredTriggerTokens;\n triggerSource = \"measured\";\n console.error(\n `[context-fill] trigger source: MEASURED — measured=${measuredTriggerTokens} ` +\n `(minimum of the most recent ${MEASURED_TRIGGER_SAMPLE_SIZE} compact_boundary events), ` +\n `configured=${configuredTriggerTokens} -> using ${effectiveTriggerTokens} (measured, ≤ configured).`\n );\n } else {\n effectiveTriggerTokens = configuredTriggerTokens;\n triggerSource = \"measured-clamped\";\n console.error(\n `[context-fill] trigger source: MEASURED-CLAMPED — measured=${measuredTriggerTokens} is HIGHER than ` +\n `configured=${configuredTriggerTokens} (a stale regime this project's history predates) -> ` +\n `using ${effectiveTriggerTokens} (configured, the safer bound).`\n );\n }\n\n const clamp = (name: string, value: number): number => {\n if (value >= 0) return value;\n console.error(\n `[context-fill] ${name} threshold went negative (${value}) for a ` +\n `${reading.windowSize}-token window — clamping to 0.`\n );\n return 0;\n };\n\n return {\n warmupTokens: clamp(\"warmup\", effectiveTriggerTokens - THRESHOLD_MARGIN_TOKENS.warmup),\n refreshTokens: clamp(\"refresh\", effectiveTriggerTokens - THRESHOLD_MARGIN_TOKENS.refresh),\n immediateTokens: clamp(\"immediate\", effectiveTriggerTokens - THRESHOLD_MARGIN_TOKENS.immediate),\n effectiveTriggerTokens,\n measuredTriggerTokens,\n configuredTriggerTokens,\n autocompactPct,\n triggerSource,\n windowConfirmed,\n };\n}\n\n/**\n * Which named thresholds `usedTokens` has newly crossed, given the ones that\n * have already fired this session. Ascending order (warmup before refresh).\n *\n * A single check can return more than one name — e.g. a session first\n * observed at 99% of its window crosses warmup and refresh in the same tick,\n * because there was no earlier clean crossing to catch it at. That is the\n * \"fire immediately\" case: the caller enqueues one handover and marks every\n * newly-crossed name fired, rather than waiting for a crossing that already\n * happened.\n */\nexport function crossedThresholds(\n usedTokens: number,\n thresholds: ContextFillThresholds,\n alreadyFired: ThresholdName[]\n): ThresholdName[] {\n const fired = new Set(alreadyFired);\n const ordered: Array<[ThresholdName, number]> = [\n [\"warmup\", thresholds.warmupTokens],\n [\"refresh\", thresholds.refreshTokens],\n ];\n return ordered.filter(([name, tokens]) => !fired.has(name) && usedTokens >= tokens).map(([name]) => name);\n}\n\n/**\n * True once a session is at or above the \"no time left for a clean crossing\"\n * floor (0.985 of the window). Purely informational for callers — it never\n * gates whether `crossedThresholds` fires (a poll-based check already fires\n * any unfired threshold the moment `usedTokens` reaches it, on whatever tick\n * observes it) — but it distinguishes \"this crossed on schedule\" from \"this\n * was first observed already almost out of room\", which is worth a different\n * log line and, for a caller that queues work, a higher priority.\n */\nexport function isImmediate(usedTokens: number, thresholds: ContextFillThresholds): boolean {\n return usedTokens >= thresholds.immediateTokens;\n}\n","/**\n * context-trigger.ts — the window size and compaction trigger a SPECIFIC\n * session transcript actually runs against, instead of a constant assumed\n * for every session.\n *\n * Run 4 of the token-waste audit (audits/token-waste/2026-09-20-run4.md)\n * rated a 1M-window session against a hardcoded 200k threshold, read the\n * resulting RED \"context growth\" finding at face value, and lowered\n * CLAUDE_AUTOCOMPACT_PCT_OVERRIDE to make the 1M session compact near 196k —\n * the opposite of what the 1M window exists for. See\n * audits/token-waste/reviewprompt.md, \"Rules the audit may not break\".\n *\n * Reuses, rather than re-implements, the same derivation the compaction-\n * handover hook uses (src/hooks/ts/lib/context-fill.ts) so the audit and the\n * hook can never disagree about what \"the trigger\" for a session is.\n */\n\nimport { readFileSync } from \"node:fs\";\nimport { basename, dirname } from \"node:path\";\nimport { DEFAULT_CONTEXT_WINDOW, contextWindowFromModelId } from \"../utils/model-window.js\";\nimport {\n contextFillFromTranscript,\n contextFillThresholds,\n readStatuslineFill,\n statuslineStateFilePath,\n type ContextFillReading,\n type TriggerSource,\n} from \"../hooks/ts/lib/context-fill.js\";\n\nconst TAIL_LINES = 40;\n\n/**\n * Where the session's context WINDOW SIZE came from, in the order they are\n * tried:\n *\n * 1. \"statusline-state\": the statusline's own persisted reading for this\n * session id, fresh (written within the last 5 minutes) — see\n * readStatuslineFill.\n * 2. \"statusline-state-stale\": the same file, older than 5 minutes —\n * accepted for an audit run well after the session ended, because a\n * session's window does not change after launch; a stale window number\n * is not the same defect a stale FILL number would be.\n * 3. \"transcript-field\": a `context_window` field on a recent transcript\n * line (checked before the model id, since it would be the platform's\n * own number — no transcript on this project carries it as of\n * 2026-09-20, but a future platform version might).\n * 4. \"model-id\": the last assistant model id in the transcript carries a\n * bracketed window variant (\"claude-fable-5-1[1m]\" -> 1,000,000).\n * 5. \"compaction-history\": this transcript's OWN compact_boundary events\n * recorded a preTokens reading above the 200k default — proof the\n * window is at least that large, rounded up to the nearest whole\n * million (in practice 1,000,000). Catches exactly the fault this\n * module exists to fix: a live 1M-window session whose transcript\n * stores the bare model id, with no fresh or stale statusline file to\n * fall back on.\n * 6. \"default\": DEFAULT_CONTEXT_WINDOW (200,000) — nothing above applied.\n */\nexport type WindowSource =\n | \"statusline-state\"\n | \"statusline-state-stale\"\n | \"transcript-field\"\n | \"model-id\"\n | \"compaction-history\"\n | \"default\";\n\n/** \"override\": `--ctx-threshold` was passed explicitly, bypassing derivation\n * entirely — added to context-fill.ts's own TriggerSource values, which\n * describe only the measured-vs-configured choice inside derivation. */\nexport type SessionTriggerSource = TriggerSource | \"override\";\n\nexport interface DerivedTrigger {\n window: number;\n windowSource: WindowSource;\n trigger: number;\n autocompactPct: number;\n triggerSource: TriggerSource;\n}\n\nfunction readTranscript(transcriptPath: string): string | null {\n try {\n return readFileSync(transcriptPath, \"utf-8\");\n } catch {\n return null;\n }\n}\n\n/**\n * A `context_window` field on a transcript line, when the platform ever\n * writes one — see WindowSource, step 3.\n */\nfunction contextWindowFromTranscriptField(transcriptPath: string): number | null {\n const raw = readTranscript(transcriptPath);\n if (raw === null) return null;\n const lines = raw.trim().split(\"\\n\").filter((l) => l.trim());\n const tail = lines.slice(-TAIL_LINES);\n for (let i = tail.length - 1; i >= 0; i--) {\n let entry: { context_window?: unknown; message?: { context_window?: unknown } };\n try {\n entry = JSON.parse(tail[i]);\n } catch {\n continue;\n }\n const value = entry.context_window ?? entry.message?.context_window;\n if (typeof value === \"number\" && Number.isFinite(value) && value > 0) return value;\n }\n return null;\n}\n\n/** The last assistant `message.model` id in the transcript, scanned from the\n * tail — see WindowSource, step 4. */\nfunction lastAssistantModelId(transcriptPath: string): string | null {\n const raw = readTranscript(transcriptPath);\n if (raw === null) return null;\n const lines = raw.trim().split(\"\\n\").filter((l) => l.trim());\n const tail = lines.slice(-TAIL_LINES);\n for (let i = tail.length - 1; i >= 0; i--) {\n let entry: { type?: string; message?: { model?: unknown } };\n try {\n entry = JSON.parse(tail[i]);\n } catch {\n continue;\n }\n if (entry?.type !== \"assistant\") continue;\n const model = entry.message?.model;\n if (typeof model === \"string\" && model !== \"\") return model;\n }\n return null;\n}\n\n/**\n * The largest `compactMetadata.preTokens` this transcript's own\n * compact_boundary events recorded, rounded up to the nearest whole million\n * — see WindowSource, step 5. Reads the WHOLE transcript (not just the tail):\n * a compaction event can sit anywhere in a long session's history, unlike\n * the model id / context_window checks above, which only ever need the most\n * recent turn.\n */\nfunction windowFromCompactionHistory(transcriptPath: string): number | null {\n const raw = readTranscript(transcriptPath);\n if (raw === null) return null;\n let maxPreTokens = 0;\n for (const line of raw.split(\"\\n\")) {\n if (!line.trim()) continue;\n let entry: { type?: string; subtype?: string; compactMetadata?: { preTokens?: unknown } };\n try {\n entry = JSON.parse(line);\n } catch {\n continue;\n }\n if (entry.type !== \"system\" || entry.subtype !== \"compact_boundary\") continue;\n const preTokens = entry.compactMetadata?.preTokens;\n if (typeof preTokens === \"number\" && Number.isFinite(preTokens) && preTokens > maxPreTokens) {\n maxPreTokens = preTokens;\n }\n }\n if (maxPreTokens <= DEFAULT_CONTEXT_WINDOW) return null;\n return Math.ceil(maxPreTokens / 1_000_000) * 1_000_000;\n}\n\n/**\n * The statusline state file for a session id, read WITHOUT the 5-minute\n * staleness gate readStatuslineFill applies — WindowSource step 2. Reuses\n * readStatuslineFill itself (rather than re-parsing the file) by handing it\n * back the file's own timestamp as \"now\", so the freshness check it performs\n * internally always computes a zero age; every other validation (shape,\n * numeric fields) stays exactly as readStatuslineFill enforces it.\n */\nfunction readStatuslineFillIgnoringStaleness(sessionId: string): ContextFillReading | null {\n const path = statuslineStateFilePath(sessionId);\n const raw = readTranscript(path);\n if (raw === null) return null;\n let parsed: { timestamp?: unknown };\n try {\n parsed = JSON.parse(raw);\n } catch {\n return null;\n }\n if (typeof parsed.timestamp !== \"number\") return null;\n return readStatuslineFill(sessionId, parsed.timestamp);\n}\n\n/**\n * The transcript's own project directory, as Claude Code encodes it under\n * `~/.claude/projects/<encoded-cwd>/` — recovered from the transcript's PATH\n * rather than accepted as a caller-supplied cwd, so the measured-trigger\n * history scan is always scoped to the transcript's OWN project, never the\n * auditing process's process.cwd() (a real fault: an audit of another\n * project's transcript logged \"measured trigger for <this process's cwd>\").\n *\n * An already-encoded directory NAME contains no `/`, whitespace or `.` — the\n * only characters encodeProjectPath (context-fill.ts) folds into `-` — so\n * re-encoding it is a no-op, and handing this name back as the `cwd` argument\n * to contextFillThresholds/measureCompactionTrigger reproduces the exact\n * directory via their own `join(projectsDir, encodeProjectPath(cwd))`, with\n * no changes needed there. A transcript inside Claude Code's `sessions/`\n * archive sits one level deeper than the live transcript, so its directory\n * name is one level further up.\n */\nfunction projectDirNameFromTranscript(transcriptPath: string): string {\n const dir = dirname(transcriptPath);\n return basename(dir) === \"sessions\" ? basename(dirname(dir)) : basename(dir);\n}\n\n/**\n * window: resolved via WindowSource's steps in order — never a window\n * assumed regardless of the session.\n *\n * trigger: contextFillThresholds's effectiveTriggerTokens for that window —\n * this transcript's own project's measured compact_boundary history when it\n * has any (the better estimate), else resolveAutocompactPct(env) applied to\n * the window.\n */\nexport function deriveSessionTrigger(\n transcriptPath: string,\n env: NodeJS.ProcessEnv = process.env,\n now: number = Date.now()\n): DerivedTrigger {\n const sessionId = basename(transcriptPath).replace(/\\.jsonl$/, \"\");\n\n let window: number;\n let windowSource: WindowSource;\n\n const fresh = readStatuslineFill(sessionId, now);\n const stale = fresh ? null : readStatuslineFillIgnoringStaleness(sessionId);\n const fieldWindow = fresh || stale ? null : contextWindowFromTranscriptField(transcriptPath);\n const modelWindow =\n fresh || stale || fieldWindow !== null\n ? null\n : contextWindowFromModelId(lastAssistantModelId(transcriptPath));\n const compactionWindow =\n fresh || stale || fieldWindow !== null || modelWindow !== null\n ? null\n : windowFromCompactionHistory(transcriptPath);\n\n if (fresh) {\n window = fresh.windowSize;\n windowSource = \"statusline-state\";\n } else if (stale) {\n window = stale.windowSize;\n windowSource = \"statusline-state-stale\";\n } else if (fieldWindow !== null) {\n window = fieldWindow;\n windowSource = \"transcript-field\";\n } else if (modelWindow !== null) {\n window = modelWindow;\n windowSource = \"model-id\";\n } else if (compactionWindow !== null) {\n window = compactionWindow;\n windowSource = \"compaction-history\";\n } else {\n window = DEFAULT_CONTEXT_WINDOW;\n windowSource = \"default\";\n }\n\n const reading = contextFillFromTranscript(transcriptPath, window);\n const thresholds = contextFillThresholds(reading, env, { cwd: projectDirNameFromTranscript(transcriptPath) });\n\n return {\n window,\n windowSource,\n trigger: thresholds.effectiveTriggerTokens,\n autocompactPct: thresholds.autocompactPct,\n triggerSource: thresholds.triggerSource,\n };\n}\n\nexport { DEFAULT_CONTEXT_WINDOW };\n","/**\n * `pai audit tokens session` — where a live session's context budget\n * actually goes: cache reads vs. cache writes vs. fresh input vs. output,\n * and the cache-TTL split (ephemeral 5m vs 1h) that says which cache the\n * session is riding.\n */\n\nimport { readdirSync, statSync } from \"node:fs\";\nimport { homedir } from \"node:os\";\nimport { join } from \"node:path\";\nimport { parseSessionUsage, totalUsageTokens, type SessionUsageReport } from \"./session-usage.js\";\nimport { encodeDir } from \"../cli/utils.js\";\nimport { firstTurnBreakdown, type FirstTurnBreakdown } from \"./first-turn.js\";\nimport { loadConfig } from \"../daemon/config.js\";\nimport { deriveSessionTrigger, type WindowSource, type SessionTriggerSource } from \"./context-trigger.js\";\n\n/** Newest *.jsonl under ~/.claude/projects, excluding subagents/. */\nexport function newestSessionLog(): string | null {\n const projectsDir = join(homedir(), \".claude\", \"projects\");\n let best: { path: string; mtime: number } | null = null;\n let projectDirs: string[];\n try {\n projectDirs = readdirSync(projectsDir);\n } catch {\n return null;\n }\n for (const projectDir of projectDirs) {\n const dirPath = join(projectsDir, projectDir);\n let entries: string[];\n try {\n entries = readdirSync(dirPath);\n } catch {\n continue;\n }\n for (const entry of entries) {\n if (!entry.endsWith(\".jsonl\")) continue;\n const filePath = join(dirPath, entry);\n let mtime: number;\n try {\n mtime = statSync(filePath).mtimeMs;\n } catch {\n continue;\n }\n if (!best || mtime > best.mtime) best = { path: filePath, mtime };\n }\n }\n return best?.path ?? null;\n}\n\nexport interface SessionReportOutput extends SessionUsageReport {\n totalTokens: number;\n percentages: Record<string, number>;\n firstTurn: FirstTurnBreakdown | null;\n /** The session's own context window (from its transcript, or the last\n * assistant model id — see context-trigger.ts), never a constant assumed\n * for every session. */\n window: number;\n windowSource: WindowSource;\n /** The compaction trigger this window derives to — what `threshold`\n * (turnsAboveThreshold, and the caller's ctxThreshold for severity) was\n * actually set from, unless `--ctx-threshold` overrode it. */\n trigger: number;\n autocompactPct: number;\n triggerSource: SessionTriggerSource;\n}\n\n/**\n * `threshold`, when given (`--ctx-threshold`), is an explicit override for\n * both the per-turn \"above threshold\" count and the trigger reported below.\n * Left undefined, the trigger — and the threshold used to parse the\n * transcript — are DERIVED from the session's own window and this project's\n * own measured/configured compaction trigger (deriveSessionTrigger), never\n * assumed to be 200k regardless of the session.\n */\nexport async function auditSession(path?: string, threshold?: number): Promise<SessionReportOutput | null> {\n const target = path ?? newestSessionLog();\n if (!target) return null;\n const keepaliveWord = loadConfig().sessions.cacheKeepalive.prompt;\n const derived = deriveSessionTrigger(target);\n const effectiveThreshold = threshold ?? derived.trigger;\n const report = await parseSessionUsage(target, effectiveThreshold, keepaliveWord);\n const total = totalUsageTokens(report.totals) || 1;\n const percentages: Record<string, number> = {};\n for (const [key, value] of Object.entries(report.totals)) {\n percentages[key] = (100 * value) / total;\n }\n return {\n ...report,\n totalTokens: total,\n percentages,\n firstTurn: firstTurnBreakdown(target),\n window: derived.window,\n windowSource: derived.windowSource,\n trigger: effectiveThreshold,\n autocompactPct: derived.autocompactPct,\n triggerSource: threshold !== undefined ? \"override\" : derived.triggerSource,\n };\n}\n\nexport interface SessionHistoryRow {\n path: string;\n sessionId: string;\n firstTurnAt: string | null;\n firstTurnContext: number | null;\n turns: number;\n models: Record<string, number>;\n switches: number;\n fallbacks: number;\n}\n\n/**\n * First-turn context of every session transcript in this project's\n * `~/.claude/projects/<encoded-cwd>` directory, newest first — the readings\n * needed to see when a step change (model switch, hook growth) happened\n * across sessions without an ad-hoc script.\n */\nexport async function sessionHistory(cwd: string, limit = 20): Promise<SessionHistoryRow[]> {\n const dir = join(homedir(), \".claude\", \"projects\", encodeDir(cwd));\n let entries: string[];\n try {\n entries = readdirSync(dir).filter((f) => f.endsWith(\".jsonl\"));\n } catch {\n return [];\n }\n\n const rows: SessionHistoryRow[] = [];\n for (const entry of entries) {\n const path = join(dir, entry);\n const report = await parseSessionUsage(path);\n if (report.turns === 0) continue;\n rows.push({\n path,\n sessionId: entry.replace(/\\.jsonl$/, \"\"),\n firstTurnAt: report.firstTurnAt,\n firstTurnContext: report.firstTurnContext,\n turns: report.turns,\n models: report.models,\n switches: report.modelSwitches.length,\n fallbacks: report.fallbacks.length,\n });\n }\n\n rows.sort((a, b) => {\n if (a.firstTurnAt === null && b.firstTurnAt === null) return 0;\n if (a.firstTurnAt === null) return 1;\n if (b.firstTurnAt === null) return -1;\n return b.firstTurnAt.localeCompare(a.firstTurnAt);\n });\n\n return rows.slice(0, limit);\n}\n","/**\n * `pai audit tokens spawn` — spawn-overhead comparison from existing logs,\n * no new LLM calls: Agent-tool subagents vs. pai workers (split further into\n * interactive/pane vs. headless -p), each read for first-turn context.\n */\n\nimport { createReadStream, existsSync, readFileSync, readdirSync, statSync } from \"node:fs\";\nimport { createInterface } from \"node:readline\";\nimport { homedir } from \"node:os\";\nimport { join } from \"node:path\";\nimport { readWorkersSection } from \"../workers/config.js\";\nimport { workersLogDir } from \"../workers/paths.js\";\nimport { parseSessionUsage } from \"./session-usage.js\";\nimport { countTokens } from \"./tokens.js\";\n\nexport type SpawnGroupName = \"agentSubagents\" | \"workersInteractive\" | \"workersHeadless\";\n\nexport interface SpawnLogReading {\n path: string;\n firstTurnContext: number | null;\n model: string | null;\n promptTokens: number | null;\n overhead: number | null;\n group: SpawnGroupName;\n}\n\nexport interface SpawnGroupStats {\n count: number;\n min: number | null;\n median: number | null;\n max: number | null;\n medianOverhead: number | null;\n models: string[];\n}\n\nexport interface SpawnReport {\n agentSubagents: SpawnGroupStats;\n workersInteractive: SpawnGroupStats;\n workersHeadless: SpawnGroupStats;\n readings: SpawnLogReading[];\n}\n\nfunction newestN(paths: string[], n: number): string[] {\n return paths\n .map((path) => ({ path, mtime: statSync(path).mtimeMs }))\n .sort((a, b) => b.mtime - a.mtime)\n .slice(0, n)\n .map((entry) => entry.path);\n}\n\n/** Newest N ~/.claude/projects/<project>/<session>/subagents/*.jsonl files. */\nexport function newestAgentSubagentLogs(n = 10): string[] {\n const projectsDir = join(homedir(), \".claude\", \"projects\");\n const found: string[] = [];\n let projectDirs: string[];\n try {\n projectDirs = readdirSync(projectsDir);\n } catch {\n return [];\n }\n for (const projectDir of projectDirs) {\n const sessionsRoot = join(projectsDir, projectDir);\n let sessionDirs: string[];\n try {\n sessionDirs = readdirSync(sessionsRoot);\n } catch {\n continue;\n }\n for (const sessionDir of sessionDirs) {\n const subagentsDir = join(sessionsRoot, sessionDir, \"subagents\");\n let files: string[];\n try {\n files = readdirSync(subagentsDir);\n } catch {\n continue;\n }\n for (const file of files) {\n if (file.endsWith(\".jsonl\")) found.push(join(subagentsDir, file));\n }\n }\n }\n return newestN(found, n);\n}\n\n/** Newest N pai-worker event-mirror *.jsonl files (excludes *.inbox.jsonl). */\nexport function newestWorkerLogs(logDir: string, n = 10): string[] {\n let files: string[];\n try {\n files = readdirSync(logDir);\n } catch {\n return [];\n }\n const eventLogs = files\n .filter((f) => f.endsWith(\".jsonl\") && !f.endsWith(\".inbox.jsonl\"))\n .map((f) => join(logDir, f));\n return newestN(eventLogs, n);\n}\n\nfunction workerIdFromLogPath(path: string): string {\n return path\n .split(\"/\")\n .pop()!\n .replace(/\\.jsonl$/, \"\");\n}\n\n/** Read `<id>.status` next to a worker event log; null if missing/unreadable. */\nfunction readWorkerStatus(logDir: string, id: string): { outputFormat?: string } | null {\n const statusFile = join(logDir, `${id}.status`);\n if (!existsSync(statusFile)) return null;\n try {\n return JSON.parse(readFileSync(statusFile, \"utf8\")) as { outputFormat?: string };\n } catch {\n return null;\n }\n}\n\ninterface UserLine {\n type?: string;\n session_id?: string;\n message?: { content?: string | Array<{ text?: string }> };\n}\n\n/** Token count of the first `type:\"user\"` message's text, or null if none found. */\nexport async function firstUserPromptTokens(path: string): Promise<number | null> {\n const rl = createInterface({ input: createReadStream(path, \"utf8\"), crlfDelay: Infinity });\n try {\n for await (const raw of rl) {\n if (!raw.trim()) continue;\n let obj: UserLine;\n try {\n obj = JSON.parse(raw) as UserLine;\n } catch {\n continue;\n }\n if (obj.type !== \"user\") continue;\n const content = obj.message?.content;\n const text =\n typeof content === \"string\"\n ? content\n : Array.isArray(content)\n ? content.map((part) => part?.text ?? \"\").join(\" \")\n : \"\";\n return countTokens(text);\n }\n } finally {\n rl.close();\n }\n return null;\n}\n\n/** `session_id` off the first record that carries one, or null if none do. */\nexport async function firstSessionId(path: string): Promise<string | null> {\n const rl = createInterface({ input: createReadStream(path, \"utf8\"), crlfDelay: Infinity });\n try {\n for await (const raw of rl) {\n if (!raw.trim()) continue;\n let obj: UserLine;\n try {\n obj = JSON.parse(raw) as UserLine;\n } catch {\n continue;\n }\n if (obj.session_id) return obj.session_id;\n }\n } finally {\n rl.close();\n }\n return null;\n}\n\n/** `~/.claude/projects/**\\/<sessionId>.jsonl`, searched only under the projects dir. */\nexport function findSessionTranscript(\n sessionId: string,\n projectsDir: string = join(homedir(), \".claude\", \"projects\")\n): string | null {\n const target = `${sessionId}.jsonl`;\n const stack = [projectsDir];\n while (stack.length > 0) {\n const dir = stack.pop()!;\n let entries: import(\"node:fs\").Dirent[];\n try {\n entries = readdirSync(dir, { withFileTypes: true });\n } catch {\n continue;\n }\n for (const entry of entries) {\n const full = join(dir, entry.name);\n if (entry.isDirectory()) {\n stack.push(full);\n } else if (entry.name === target) {\n return full;\n }\n }\n }\n return null;\n}\n\n/**\n * The mirror log has no `type:\"user\"` record (worker event mirrors carry\n * system/assistant/result records only, never the -p prompt itself), so\n * fall back to the session's own transcript under ~/.claude/projects,\n * located via whichever record in the mirror log carries a `session_id`.\n */\nasync function resolvePromptTokens(mirrorPath: string): Promise<number | null> {\n const direct = await firstUserPromptTokens(mirrorPath);\n if (direct !== null) return direct;\n const sessionId = await firstSessionId(mirrorPath);\n if (!sessionId) return null;\n const transcript = findSessionTranscript(sessionId);\n if (!transcript) return null;\n return firstUserPromptTokens(transcript);\n}\n\nasync function readOne(path: string, group: SpawnGroupName): Promise<SpawnLogReading> {\n const [report, promptTokens] = await Promise.all([parseSessionUsage(path), resolvePromptTokens(path)]);\n const model = Object.keys(report.models)[0] ?? null;\n const overhead =\n report.firstTurnContext !== null && promptTokens !== null ? report.firstTurnContext - promptTokens : null;\n return { path, firstTurnContext: report.firstTurnContext, model, promptTokens, overhead, group };\n}\n\nfunction median(values: number[]): number | null {\n if (values.length === 0) return null;\n const sorted = [...values].sort((a, b) => a - b);\n const mid = Math.floor(sorted.length / 2);\n return sorted.length % 2 ? sorted[mid] : (sorted[mid - 1] + sorted[mid]) / 2;\n}\n\nfunction summarize(readings: SpawnLogReading[]): SpawnGroupStats {\n const contexts = readings.map((r) => r.firstTurnContext).filter((v): v is number => v !== null).sort((a, b) => a - b);\n const overheads = readings.map((r) => r.overhead).filter((v): v is number => v !== null);\n const models = [...new Set(readings.map((r) => r.model).filter((m): m is string => !!m))];\n if (contexts.length === 0) {\n return { count: readings.length, min: null, median: null, max: null, medianOverhead: median(overheads), models };\n }\n return {\n count: readings.length,\n min: contexts[0],\n median: median(contexts),\n max: contexts[contexts.length - 1],\n medianOverhead: median(overheads),\n models,\n };\n}\n\nexport async function auditSpawn(n = 10): Promise<SpawnReport> {\n const { workers } = readWorkersSection();\n const logDir = workersLogDir(workers);\n\n const agentLogs = newestAgentSubagentLogs(n);\n const agentReadings = await Promise.all(agentLogs.map((path) => readOne(path, \"agentSubagents\")));\n\n const workerLogs = newestWorkerLogs(logDir, n);\n const workerReadings = await Promise.all(workerLogs.map((path) => readOne(path, \"workersInteractive\")));\n\n const interactive: SpawnLogReading[] = [];\n const headless: SpawnLogReading[] = [];\n for (const reading of workerReadings) {\n const status = readWorkerStatus(logDir, workerIdFromLogPath(reading.path));\n if (status?.outputFormat) headless.push({ ...reading, group: \"workersHeadless\" });\n else interactive.push(reading);\n }\n\n return {\n agentSubagents: summarize(agentReadings),\n workersInteractive: summarize(interactive),\n workersHeadless: summarize(headless),\n readings: [...agentReadings, ...interactive, ...headless],\n };\n}\n","/**\n * `pai audit tokens daemon` — how much LLM work the background daemon does\n * on its own, parsed from its plain-text log: model spawns, prompt sizes,\n * KG-extraction parse failures, and work-queue job throughput.\n */\n\nimport { existsSync, readFileSync, statSync } from \"node:fs\";\nimport { homedir } from \"node:os\";\nimport { join } from \"node:path\";\nimport { daemonLogPath } from \"../runtime-paths.js\";\n\nconst PLIST_PATH = join(homedir(), \"Library\", \"LaunchAgents\", \"com.pai.pai-daemon.plist\");\n\n/** StandardErrorPath from the launchd plist, falling back to the runtime default. */\nexport function resolveDaemonLogPath(): string {\n if (existsSync(PLIST_PATH)) {\n const plist = readFileSync(PLIST_PATH, \"utf8\");\n const match = plist.match(/<key>StandardErrorPath<\\/key>\\s*<string>([^<]+)<\\/string>/);\n if (match) return match[1];\n }\n return daemonLogPath();\n}\n\nexport interface ModelSpawnStats {\n count: number;\n avgPromptChars: number;\n}\n\nexport interface DaemonReport {\n logPath: string;\n windowStart: string | null;\n spawnsByModel: Record<string, ModelSpawnStats>;\n kgParseFailures: number;\n /** Every \"[kg-extraction] ...\" log line, of any outcome (diagnostic only). */\n kgExtractionLines: number;\n /**\n * kgParseFailures / total session-summary spawns. KG extraction runs once\n * per successful session-summary spawn (session-summary-worker.ts calls\n * extractAndStoreTriples() right after producing a summary) and only ever\n * logs on failure — there is no per-call success line — so the spawn count\n * is the only available denominator for a failure rate.\n */\n kgParseFailureRate: number;\n jobCounts: Record<string, { processing: number; completed: number }>;\n}\n\nconst SPAWN_LINE = /\\[session-summary\\] Sending (\\d+) char prompt to (\\S+?)\\.\\.\\./;\nconst KG_EXTRACTION_LINE = /^\\[kg-extraction\\]/;\nconst KG_PARSE_FAILED = /\\[kg-extraction\\] JSON parse failed/;\nconst PROCESSING_LINE = /\\[work-queue-worker\\] Processing (\\S+) \\(/;\nconst COMPLETED_LINE = /\\[work-queue-worker\\] Completed (\\S+) \\(/;\n\nexport function auditDaemon(logPath = resolveDaemonLogPath()): DaemonReport {\n const spawnsByModel: Record<string, { count: number; totalChars: number }> = {};\n const jobCounts: Record<string, { processing: number; completed: number }> = {};\n let kgParseFailures = 0;\n let kgExtractionLines = 0;\n let windowStart: string | null = null;\n\n if (existsSync(logPath)) {\n try {\n windowStart = statSync(logPath).birthtime.toISOString();\n } catch {\n windowStart = null;\n }\n const lines = readFileSync(logPath, \"utf8\").split(\"\\n\");\n for (const line of lines) {\n const spawnMatch = line.match(SPAWN_LINE);\n if (spawnMatch) {\n const chars = Number(spawnMatch[1]);\n const model = spawnMatch[2];\n const entry = (spawnsByModel[model] ??= { count: 0, totalChars: 0 });\n entry.count++;\n entry.totalChars += chars;\n continue;\n }\n if (KG_EXTRACTION_LINE.test(line)) {\n kgExtractionLines++;\n if (KG_PARSE_FAILED.test(line)) kgParseFailures++;\n continue;\n }\n const processingMatch = line.match(PROCESSING_LINE);\n if (processingMatch) {\n (jobCounts[processingMatch[1]] ??= { processing: 0, completed: 0 }).processing++;\n continue;\n }\n const completedMatch = line.match(COMPLETED_LINE);\n if (completedMatch) {\n (jobCounts[completedMatch[1]] ??= { processing: 0, completed: 0 }).completed++;\n }\n }\n }\n\n const spawns: Record<string, ModelSpawnStats> = {};\n for (const [model, { count, totalChars }] of Object.entries(spawnsByModel)) {\n spawns[model] = { count, avgPromptChars: count ? Math.round(totalChars / count) : 0 };\n }\n\n const totalSpawns = Object.values(spawns).reduce((sum, s) => sum + s.count, 0);\n const kgParseFailureRate = totalSpawns ? kgParseFailures / totalSpawns : 0;\n\n return {\n logPath,\n windowStart,\n spawnsByModel: spawns,\n kgParseFailures,\n kgExtractionLines,\n kgParseFailureRate,\n jobCounts,\n };\n}\n","/**\n * `pai audit tokens env` — is any live `claude` process pointed at a proxy?\n * A non-default ANTHROPIC_BASE_URL means every token that process sends and\n * receives passes through something other than Anthropic's own API.\n *\n * Uses `ps -axo pid=,etime=,comm=,args=` to enumerate processes (pgrep -x\n * fails to find processes when run from within a claude session). Filters for\n * comm=\"claude\" (also \"claude.exe\" or paths ending with \"/claude\"), then\n * excludes MCP child processes by checking args for patterns like \"npm exec\",\n * \"node\", or \"mcp-server\".\n */\n\nimport { existsSync, readFileSync } from \"node:fs\";\nimport { execFileSync } from \"node:child_process\";\nimport { homedir } from \"node:os\";\nimport { join } from \"node:path\";\n\nexport type Deferral = \"on\" | \"OFF\" | \"n/a\";\n\nexport interface ClaudeProcessEnv {\n pid: number;\n baseUrl: string | null;\n authTokenPresent: boolean;\n defaultModels: Record<string, string>;\n enableToolSearch: string | null;\n workerId: string | null;\n age: string | null;\n toolsArg: string | null;\n mcpServers: number | \"default\";\n deferral: Deferral;\n}\n\nexport interface EnvReport {\n processes: ClaudeProcessEnv[];\n mcpServerCount: number;\n settingsModel: string | null;\n settingsEffortLevel: string | null;\n}\n\nexport function isClaudeProcess(line: string): boolean {\n const parts = line.trim().split(/\\s+/);\n if (parts.length < 3) return false;\n\n const comm = parts[2];\n const isClaudeComm =\n comm === \"claude\" ||\n comm === \"claude.exe\" ||\n comm.endsWith(\"/claude\");\n\n if (!isClaudeComm) return false;\n\n const args = parts.slice(3).join(\" \");\n const isMcpChild =\n args.includes(\"npm exec\") ||\n args === \"node\" ||\n args.startsWith(\"node \") ||\n args.includes(\"mcp-server\");\n\n return !isMcpChild;\n}\n\nexport function findClaudePids(): number[] {\n try {\n const out = execFileSync(\"ps\", [\"-axo\", \"pid=,etime=,comm=,args=\"], {\n encoding: \"utf8\",\n });\n const pids: number[] = [];\n for (const line of out.split(\"\\n\")) {\n if (!line.trim()) continue;\n if (!isClaudeProcess(line)) continue;\n const parts = line.trim().split(/\\s+/);\n const pid = parseInt(parts[0], 10);\n if (!isNaN(pid)) pids.push(pid);\n }\n return pids;\n } catch {\n return [];\n }\n}\n\nfunction extract(pattern: RegExp, text: string): string | null {\n const match = text.match(pattern);\n return match ? match[1] : null;\n}\n\n/**\n * `--tools A,B` and `--tools=A,B` are both valid on the argv this process was\n * launched with; an explicit but empty value (`--tools \"\"` / `--tools=`)\n * means \"no tools at all\", distinct from the flag being absent entirely.\n */\nexport function extractToolsArg(line: string): string | null {\n const eq = line.match(/--tools=(?:\"([^\"]*)\"|(\\S*))/);\n if (eq) return eq[1] !== undefined ? eq[1] : eq[2];\n const sp = line.match(/--tools\\s+(?:\"([^\"]*)\"|(\\S+))/);\n if (sp) return sp[1] !== undefined ? sp[1] : sp[2];\n return null;\n}\n\nexport function extractMcpConfigArg(line: string): string | null {\n const eq = line.match(/--mcp-config=(?:\"([^\"]*)\"|(\\S*))/);\n if (eq) return eq[1] !== undefined ? eq[1] : eq[2];\n const sp = line.match(/--mcp-config\\s+(?:\"([^\"]*)\"|(\\S+))/);\n if (sp) return sp[1] !== undefined ? sp[1] : sp[2];\n return null;\n}\n\n/**\n * No `--tools` flag: the CLI default tool set applies, which includes\n * ToolSearch, so deferral is on. An explicit but empty list means no tools\n * at all, so deferral doesn't apply. Otherwise deferral is on only if the\n * explicit list still names ToolSearch.\n */\nexport function classifyDeferral(toolsArg: string | null): Deferral {\n if (toolsArg === null) return \"on\";\n if (toolsArg.trim() === \"\") return \"n/a\";\n const tools = toolsArg.split(\",\").map((t) => t.trim());\n return tools.includes(\"ToolSearch\") ? \"on\" : \"OFF\";\n}\n\nfunction countMcpServersInFile(path: string): number {\n try {\n const resolved = path.startsWith(\"~\") ? join(homedir(), path.slice(1)) : path;\n const parsed = JSON.parse(readFileSync(resolved, \"utf8\")) as { mcpServers?: Record<string, unknown> };\n return Object.keys(parsed.mcpServers ?? {}).length;\n } catch {\n return 0;\n }\n}\n\nexport function readProcessEnvLine(pid: number): string | null {\n try {\n return execFileSync(\"ps\", [\"-Eww\", \"-p\", String(pid), \"-o\", \"command=\"], { encoding: \"utf8\" });\n } catch {\n return null;\n }\n}\n\nfunction readProcessAge(pid: number): string | null {\n try {\n return execFileSync(\"ps\", [\"-p\", String(pid), \"-o\", \"etime=\"], { encoding: \"utf8\" }).trim() || null;\n } catch {\n return null;\n }\n}\n\nexport function auditEnv(): EnvReport {\n const processes: ClaudeProcessEnv[] = [];\n for (const pid of findClaudePids()) {\n const line = readProcessEnvLine(pid);\n if (!line) continue;\n const defaultModels: Record<string, string> = {};\n const modelPattern = /ANTHROPIC_DEFAULT_(\\w+?)_MODEL=(\\S*)/g;\n let modelMatch: RegExpExecArray | null;\n while ((modelMatch = modelPattern.exec(line))) {\n defaultModels[modelMatch[1]] = modelMatch[2];\n }\n const toolsArg = extractToolsArg(line);\n const mcpConfigPath = extractMcpConfigArg(line);\n processes.push({\n pid,\n baseUrl: extract(/ANTHROPIC_BASE_URL=(\\S*)/, line),\n authTokenPresent: /ANTHROPIC_AUTH_TOKEN=\\S/.test(line),\n defaultModels,\n enableToolSearch: extract(/ENABLE_TOOL_SEARCH=(\\S*)/, line),\n workerId: extract(/PAI_WORKER_ID=(\\S*)/, line),\n age: readProcessAge(pid),\n toolsArg,\n mcpServers: mcpConfigPath ? countMcpServersInFile(mcpConfigPath) : \"default\",\n deferral: classifyDeferral(toolsArg),\n });\n }\n\n let mcpServerCount = 0;\n const claudeJsonPath = join(homedir(), \".claude.json\");\n if (existsSync(claudeJsonPath)) {\n try {\n const parsed = JSON.parse(readFileSync(claudeJsonPath, \"utf8\")) as { mcpServers?: Record<string, unknown> };\n mcpServerCount = Object.keys(parsed.mcpServers ?? {}).length;\n } catch {\n mcpServerCount = 0;\n }\n }\n\n let settingsModel: string | null = null;\n let settingsEffortLevel: string | null = null;\n const settingsPath = join(homedir(), \".claude\", \"settings.json\");\n if (existsSync(settingsPath)) {\n try {\n const parsed = JSON.parse(readFileSync(settingsPath, \"utf8\")) as { model?: string; effortLevel?: string };\n settingsModel = parsed.model ?? null;\n settingsEffortLevel = parsed.effortLevel ?? null;\n } catch {\n /* leave nulls */\n }\n }\n\n return { processes, mcpServerCount, settingsModel, settingsEffortLevel };\n}\n","/**\n * `pai audit tokens schedule` — anything that wakes up more often than the\n * measured cache TTL burns a full cache-write every time, since the\n * previous cache entry has already expired.\n */\n\nimport { readdirSync } from \"node:fs\";\nimport { execFileSync } from \"node:child_process\";\nimport { homedir } from \"node:os\";\nimport { join } from \"node:path\";\nimport type { CacheCreationSplit } from \"./session-usage.js\";\n\nconst DEFAULT_TTL_SECONDS = 3600;\n\n/** Which ephemeral cache dominates a session's cache writes, as a TTL in seconds. */\nexport function cacheTtlSeconds(split: CacheCreationSplit | null | undefined): number {\n if (!split || split.ephemeral5m + split.ephemeral1h === 0) return DEFAULT_TTL_SECONDS;\n return split.ephemeral1h >= split.ephemeral5m ? 3600 : 300;\n}\n\nexport interface LaunchAgentEntry {\n path: string;\n label: string | null;\n startInterval: number | null;\n startCalendarInterval: unknown;\n keepAlive: unknown;\n programArgs: string[];\n exceedsTtl: boolean;\n}\n\ninterface PlistJson {\n Label?: string;\n StartInterval?: number;\n StartCalendarInterval?: unknown;\n KeepAlive?: unknown;\n ProgramArguments?: string[];\n}\n\nfunction readPlistJson(path: string): PlistJson | null {\n try {\n const out = execFileSync(\"plutil\", [\"-convert\", \"json\", \"-o\", \"-\", path], { encoding: \"utf8\" });\n return JSON.parse(out) as PlistJson;\n } catch {\n return null;\n }\n}\n\nexport function listLaunchAgents(ttlSeconds: number): LaunchAgentEntry[] {\n const dir = join(homedir(), \"Library\", \"LaunchAgents\");\n let files: string[];\n try {\n files = readdirSync(dir).filter((f) => f.endsWith(\".plist\"));\n } catch {\n return [];\n }\n const entries: LaunchAgentEntry[] = [];\n for (const file of files) {\n const path = join(dir, file);\n const plist = readPlistJson(path);\n const startInterval = plist?.StartInterval ?? null;\n entries.push({\n path,\n label: plist?.Label ?? null,\n startInterval,\n startCalendarInterval: plist?.StartCalendarInterval ?? null,\n keepAlive: plist?.KeepAlive ?? null,\n programArgs: (plist?.ProgramArguments ?? []).slice(0, 2),\n exceedsTtl: startInterval !== null && startInterval > ttlSeconds,\n });\n }\n return entries;\n}\n\nexport function readCrontab(): string[] {\n try {\n const out = execFileSync(\"crontab\", [\"-l\"], { encoding: \"utf8\" });\n return out.split(\"\\n\").filter((line) => line.trim() && !line.trim().startsWith(\"#\"));\n } catch {\n return [];\n }\n}\n\nexport interface ScheduleReport {\n ttlSeconds: number;\n agents: LaunchAgentEntry[];\n crontabLines: string[];\n}\n\nexport function auditSchedule(ttlSeconds = DEFAULT_TTL_SECONDS): ScheduleReport {\n return { ttlSeconds, agents: listLaunchAgents(ttlSeconds), crontabLines: readCrontab() };\n}\n\nexport { DEFAULT_TTL_SECONDS };\n","/**\n * `pai audit tokens skills` — token cost of the whole skill/command/plugin\n * catalogue: every `SKILL.md` under ~/.claude/skills and ~/.claude/plugins,\n * plus every command under ~/.claude/commands, rendered as the one-line\n * `- name: description` entry Claude Code loads into context for each.\n *\n * Two failure modes matter more than the raw total: the same real file\n * reachable through two paths (a plugin's marketplaces/ checkout and its\n * cache/ install both point at one file), and two different real files that\n * collide on name (a skill and a command both called \"Sessions\") — the first\n * wastes nothing but must not be double-counted, the second silently shadows\n * one of the two in whichever catalogue order Claude Code loads them.\n */\n\nimport { existsSync, readdirSync, readFileSync, realpathSync, statSync } from \"node:fs\";\nimport { homedir } from \"node:os\";\nimport { basename, dirname, join, relative, sep } from \"node:path\";\nimport { countTokens, TOKEN_ENCODING } from \"./tokens.js\";\n\nexport type CatalogueSource = \"skills\" | \"commands\" | \"plugins\";\n\nexport interface CatalogueEntry {\n name: string;\n description: string;\n source: CatalogueSource;\n path: string;\n tokens: number;\n /** Always true for skills/commands; for plugins, whether `enabledPlugins` turns it on. */\n enabled: boolean;\n /** Set on a plugin entry whose derived id has no matching `enabledPlugins` key. */\n status?: \"unknown\";\n}\n\nexport interface DuplicateGroup {\n name: string;\n paths: string[];\n}\n\nexport interface SkillsReport {\n encoding: string;\n entries: CatalogueEntry[];\n total: number;\n totalsBySource: Record<CatalogueSource, number>;\n countsBySource: Record<CatalogueSource, number>;\n /** Sum of `tokens` for entries that are actually loaded into a session (enabled plugins + all skills/commands). */\n enabledTotal: number;\n enabledTotalsBySource: Record<CatalogueSource, number>;\n duplicates: DuplicateGroup[];\n}\n\n/** Recursively collect every file under `dir` whose basename satisfies `matches`. */\nfunction findFilesRecursive(dir: string, matches: (name: string) => boolean): string[] {\n let entries: import(\"node:fs\").Dirent[];\n try {\n entries = readdirSync(dir, { withFileTypes: true });\n } catch {\n return [];\n }\n const found: string[] = [];\n for (const entry of entries) {\n const full = join(dir, entry.name);\n if (entry.isDirectory()) {\n found.push(...findFilesRecursive(full, matches));\n } else if (entry.isFile() && matches(entry.name)) {\n found.push(full);\n }\n }\n return found;\n}\n\ninterface RawFile {\n path: string;\n source: CatalogueSource;\n}\n\n/**\n * `<skillsDir>/*\\/SKILL.md`, one level deep only — this is what Claude Code\n * actually loads. `readdirSync(withFileTypes)` reports a symlinked skill\n * directory as neither file nor directory, so entries are resolved with\n * `statSync` (which follows symlinks) instead of trusting `Dirent`.\n */\nfunction findTopLevelSkillFiles(skillsDir: string): string[] {\n let entries: string[];\n try {\n entries = readdirSync(skillsDir);\n } catch {\n return [];\n }\n const found: string[] = [];\n for (const name of entries) {\n const dir = join(skillsDir, name);\n let isDir = false;\n try {\n isDir = statSync(dir).isDirectory();\n } catch {\n continue;\n }\n if (!isDir) continue;\n const skillFile = join(dir, \"SKILL.md\");\n if (existsSync(skillFile)) found.push(skillFile);\n }\n return found;\n}\n\n/** Every SKILL.md/*.md the three catalogue roots contribute, source-tagged. */\nexport function findCatalogueFiles(claudeDir: string): RawFile[] {\n const found: RawFile[] = [];\n for (const path of findTopLevelSkillFiles(join(claudeDir, \"skills\"))) {\n found.push({ path, source: \"skills\" });\n }\n for (const path of findFilesRecursive(join(claudeDir, \"commands\"), (n) => n.endsWith(\".md\"))) {\n found.push({ path, source: \"commands\" });\n }\n for (const path of findFilesRecursive(join(claudeDir, \"plugins\"), (n) => n === \"SKILL.md\")) {\n found.push({ path, source: \"plugins\" });\n }\n return found;\n}\n\n/** Strip a single layer of matching quotes (\"...\" or '...') from a trimmed value. */\nfunction unquote(value: string): string {\n if (value.length >= 2) {\n const first = value[0];\n const last = value[value.length - 1];\n if ((first === '\"' && last === '\"') || (first === \"'\" && last === \"'\")) {\n return value.slice(1, -1);\n }\n }\n return value;\n}\n\ninterface Frontmatter {\n name: string | null;\n description: string;\n}\n\n/** Parse the `name:`/`description:` frontmatter fields out of a skill/command file. */\nexport function parseFrontmatter(text: string): Frontmatter {\n const match = /^---\\n([\\s\\S]*?)\\n---/.exec(text);\n const fm = match ? match[1] : \"\";\n const nameMatch = /^name:\\s*(.+)$/m.exec(fm);\n const descMatch = /(?:^|\\n)description:\\s*(.+?)(?=\\n\\w[\\w-]*:|$)/s.exec(fm);\n return {\n name: nameMatch ? unquote(nameMatch[1].trim()) : null,\n description: descMatch ? unquote(descMatch[1].trim()) : \"\",\n };\n}\n\nfunction nameFallback(path: string, source: CatalogueSource): string {\n if (source === \"commands\") return basename(path).replace(/\\.md$/, \"\");\n return basename(dirname(path));\n}\n\n/** Build one catalogue entry from a file, deriving its name/description/tokens. */\nfunction readEntry(path: string, source: CatalogueSource): CatalogueEntry {\n let text = \"\";\n try {\n text = readFileSync(path, \"utf8\");\n } catch {\n /* unreadable file: treat as an empty entry rather than throwing */\n }\n const fm = parseFrontmatter(text);\n const name = fm.name ?? nameFallback(path, source);\n const line = `- ${name}: ${fm.description}`;\n return { name, description: fm.description, source, path, tokens: countTokens(line), enabled: true };\n}\n\ninterface EnabledPluginsSettings {\n enabledPlugins?: Record<string, boolean>;\n}\n\n/** `enabledPlugins` map from settings.json, or `{}` if the file is missing/unreadable. */\nfunction readEnabledPlugins(settingsPath: string): Record<string, boolean> {\n try {\n const parsed = JSON.parse(readFileSync(settingsPath, \"utf8\")) as EnabledPluginsSettings;\n return parsed.enabledPlugins ?? {};\n } catch {\n return {};\n }\n}\n\n/** `<relative-to-root>` split on the platform separator, or null if `path` is not under `root`. */\nfunction pathUnder(root: string, path: string): string[] | null {\n const rel = relative(root, path);\n if (rel.startsWith(\"..\")) return null;\n return rel.split(sep);\n}\n\ninterface PluginStatus {\n id: string;\n enabled: boolean;\n status?: \"unknown\";\n /** Identifies \"the same plugin file reached through cache/ or marketplaces/\" for enabled-token dedup. */\n dedupeKey: string;\n}\n\n/**\n * A cache install is `plugins/cache/<marketplace>/<plugin>/<version>/...`, so its id is exact.\n * A marketplace checkout is `plugins/marketplaces/<marketplace>/...` with no plugin segment, so\n * it can only be matched by marketplace name against whichever `enabledPlugins` key ends in\n * `@<marketplace>` — good enough for the single-plugin-per-marketplace installs this audits today.\n */\nfunction resolvePluginStatus(claudeDir: string, path: string, enabledPlugins: Record<string, boolean>): PluginStatus {\n const cacheParts = pathUnder(join(claudeDir, \"plugins\", \"cache\"), path);\n if (cacheParts && cacheParts.length >= 4) {\n const [marketplace, plugin, , ...suffix] = cacheParts;\n const id = `${plugin}@${marketplace}`;\n const known = id in enabledPlugins;\n return {\n id,\n enabled: known && enabledPlugins[id] === true,\n status: known ? undefined : \"unknown\",\n dedupeKey: `${id}::${suffix.join(\"/\")}`,\n };\n }\n\n const marketplaceParts = pathUnder(join(claudeDir, \"plugins\", \"marketplaces\"), path);\n if (marketplaceParts && marketplaceParts.length >= 2) {\n const [marketplace, ...suffix] = marketplaceParts;\n const matchKey = Object.keys(enabledPlugins).find((k) => k.endsWith(`@${marketplace}`));\n if (matchKey) {\n return {\n id: matchKey,\n enabled: enabledPlugins[matchKey] === true,\n dedupeKey: `${matchKey}::${suffix.join(\"/\")}`,\n };\n }\n return {\n id: `unknown@${marketplace}`,\n enabled: false,\n status: \"unknown\",\n dedupeKey: `unknown@${marketplace}::${suffix.join(\"/\")}`,\n };\n }\n\n return { id: path, enabled: false, status: \"unknown\", dedupeKey: path };\n}\n\n/** Group entries by case-insensitive name; only groups with >1 real path. */\nexport function findDuplicates(entries: CatalogueEntry[]): DuplicateGroup[] {\n const byName = new Map<string, Set<string>>();\n for (const entry of entries) {\n const key = entry.name.toLowerCase();\n if (!byName.has(key)) byName.set(key, new Set());\n byName.get(key)!.add(entry.path);\n }\n const duplicates: DuplicateGroup[] = [];\n for (const [name, paths] of byName) {\n if (paths.size > 1) duplicates.push({ name, paths: [...paths] });\n }\n return duplicates.sort((a, b) => a.name.localeCompare(b.name));\n}\n\nexport function auditSkills(\n claudeDir: string = join(homedir(), \".claude\"),\n settingsPath: string = join(claudeDir, \"settings.json\")\n): SkillsReport {\n const rawFiles = findCatalogueFiles(claudeDir);\n\n const seenReal = new Set<string>();\n const deduped: RawFile[] = [];\n for (const file of rawFiles) {\n let real = file.path;\n try {\n real = realpathSync(file.path);\n } catch {\n /* keep the original path if it vanished mid-scan */\n }\n if (seenReal.has(real)) continue;\n seenReal.add(real);\n deduped.push(file);\n }\n\n const enabledPlugins = readEnabledPlugins(settingsPath);\n const entries = deduped.map((f) => readEntry(f.path, f.source));\n const dedupeKeys = new Map<string, string>();\n for (const entry of entries) {\n if (entry.source !== \"plugins\") continue;\n const status = resolvePluginStatus(claudeDir, entry.path, enabledPlugins);\n entry.enabled = status.enabled;\n entry.status = status.status;\n dedupeKeys.set(entry.path, status.dedupeKey);\n }\n\n const totalsBySource: Record<CatalogueSource, number> = { skills: 0, commands: 0, plugins: 0 };\n const countsBySource: Record<CatalogueSource, number> = { skills: 0, commands: 0, plugins: 0 };\n const enabledTotalsBySource: Record<CatalogueSource, number> = { skills: 0, commands: 0, plugins: 0 };\n const seenEnabledPluginKeys = new Set<string>();\n for (const entry of entries) {\n totalsBySource[entry.source] += entry.tokens;\n countsBySource[entry.source] += 1;\n if (!entry.enabled) continue;\n if (entry.source !== \"plugins\") {\n enabledTotalsBySource[entry.source] += entry.tokens;\n continue;\n }\n const dedupeKey = dedupeKeys.get(entry.path)!;\n if (seenEnabledPluginKeys.has(dedupeKey)) continue;\n seenEnabledPluginKeys.add(dedupeKey);\n enabledTotalsBySource.plugins += entry.tokens;\n }\n\n return {\n encoding: TOKEN_ENCODING,\n entries,\n total: entries.reduce((sum, e) => sum + e.tokens, 0),\n totalsBySource,\n countsBySource,\n enabledTotal: enabledTotalsBySource.skills + enabledTotalsBySource.commands + enabledTotalsBySource.plugins,\n enabledTotalsBySource,\n duplicates: findDuplicates(entries),\n };\n}\n\nexport function topByTokens(report: SkillsReport, n: number): CatalogueEntry[] {\n return [...report.entries].sort((a, b) => b.tokens - a.tokens).slice(0, n);\n}\n\nexport const SKILL_CATALOGUE_AMBER = 3000;\nexport const SKILL_CATALOGUE_RED = 6000;\n","/**\n * severity.ts — the combined report's RED/AMBER/GREEN thresholds, kept in\n * one place so tightening or loosening a rule never means hunting through\n * each section's module.\n */\n\nimport type { FilesReport } from \"./files.js\";\nimport type { HooksReport } from \"./hooks.js\";\nimport type { SessionReportOutput } from \"./session.js\";\nimport type { DaemonReport } from \"./daemon.js\";\nimport type { EnvReport } from \"./env.js\";\nimport type { SkillsReport } from \"./skills.js\";\nimport type { SubagentsReport } from \"./subagents.js\";\nimport type { McpReport } from \"./mcp.js\";\nimport { firstTurnEvidence } from \"./first-turn.js\";\nimport { SINGLE_FILE_LIMIT, TOTAL_LIMIT } from \"./files.js\";\nimport { SKILL_CATALOGUE_AMBER, SKILL_CATALOGUE_RED } from \"./skills.js\";\n\nexport const HOOK_TOKEN_LIMIT = 1000;\nexport const PER_PROMPT_HOOK_COST_AMBER = 0.05;\nexport const PER_PROMPT_HOOK_COST_RED = 0.15;\nexport const FIRST_TURN_CONTEXT_LIMIT = 30_000;\nexport const DAEMON_FAILURE_RATE_LIMIT = 0.05;\n/** Ratios of the session's own derived trigger (SessionReportOutput.trigger\n * — see context-trigger.ts), NOT absolute token counts: a 1M-window session\n * and a 200k-window session have different triggers, and rating both\n * against one constant is the exact defect these were changed to fix (a\n * 200k-yardstick audit drove a wrong config change on 2026-09-20 — see\n * audits/token-waste/reviewprompt.md). */\nexport const CONTEXT_GROWTH_AVG_RED_RATIO = 0.5;\nexport const CONTEXT_GROWTH_MAX_AMBER_RATIO = 0.75;\nexport const MODEL_SWITCH_REBUILD_LIMIT = 20_000;\n\nexport type Severity = \"RED\" | \"AMBER\" | \"GREEN\";\n\nexport interface Finding {\n finding: string;\n severity: Severity;\n evidence: string;\n}\n\nconst SEVERITY_RANK: Record<Severity, number> = { RED: 0, AMBER: 1, GREEN: 2 };\n\nexport function sortFindings(findings: Finding[]): Finding[] {\n return [...findings].sort((a, b) => SEVERITY_RANK[a.severity] - SEVERITY_RANK[b.severity]);\n}\n\nexport function buildFindings(input: {\n files: FilesReport;\n hooks: HooksReport;\n session: SessionReportOutput | null;\n daemon: DaemonReport;\n env: EnvReport;\n skills: SkillsReport;\n subagents: SubagentsReport;\n mcp: McpReport;\n ctxThreshold: number;\n}): Finding[] {\n const findings: Finding[] = [];\n\n for (const proc of input.env.processes) {\n if (proc.baseUrl) {\n findings.push({\n finding: `claude process ${proc.pid} routed through a proxy`,\n severity: \"RED\",\n evidence: `ANTHROPIC_BASE_URL=${proc.baseUrl}`,\n });\n }\n }\n\n const bigFiles = input.files.readings.filter((r) => r.tokens > SINGLE_FILE_LIMIT);\n for (const f of bigFiles) {\n findings.push({\n finding: `memory file over ${SINGLE_FILE_LIMIT} tokens`,\n severity: \"AMBER\",\n evidence: `${f.path}: ${f.tokens} tokens`,\n });\n }\n findings.push({\n finding: \"total memory-file tokens\",\n severity: input.files.total > TOTAL_LIMIT ? \"AMBER\" : \"GREEN\",\n evidence: `${input.files.total} tokens across ${input.files.readings.length} files`,\n });\n\n const bigHooks = input.hooks.readings.filter((r) => r.tokens > HOOK_TOKEN_LIMIT);\n for (const h of bigHooks) {\n findings.push({\n finding: `${h.event} hook over ${HOOK_TOKEN_LIMIT} tokens`,\n severity: \"AMBER\",\n evidence: `${h.command}: ${h.tokens} tokens`,\n });\n }\n if (bigHooks.length === 0 && input.hooks.readings.length > 0) {\n findings.push({\n finding: \"SessionStart / UserPromptSubmit hook output\",\n severity: \"GREEN\",\n evidence: `${input.hooks.readings.length} hooks, all under ${HOOK_TOKEN_LIMIT} tokens`,\n });\n }\n\n if (input.session) {\n const first = input.session.firstTurnContext ?? 0;\n // A session whose first prompt was abandoned (Escape, re-submit) reads\n // low here because its skill listing sits on the dead branch; the\n // dead-branch figure in the evidence says so.\n const evidence =\n typeof input.session.firstTurn?.apiContext === \"number\"\n ? `${first} tokens: ${firstTurnEvidence(input.session.firstTurn)} (${input.session.path})`\n : `${first} tokens (${input.session.path})`;\n findings.push({\n finding: \"first-turn context size\",\n severity: first > FIRST_TURN_CONTEXT_LIMIT ? \"AMBER\" : \"GREEN\",\n evidence,\n });\n }\n\n const rate = input.daemon.kgParseFailureRate;\n findings.push({\n finding: \"daemon KG-extraction JSON parse failure rate\",\n severity: rate > DAEMON_FAILURE_RATE_LIMIT ? \"AMBER\" : \"GREEN\",\n evidence: `${(rate * 100).toFixed(1)}% (${input.daemon.kgParseFailures} failures over session-summary spawns)`,\n });\n\n if (!input.env.processes.some((p) => p.baseUrl)) {\n findings.push({\n finding: \"claude process base URL\",\n severity: \"GREEN\",\n evidence: `${input.env.processes.length} claude process(es), none proxied`,\n });\n }\n\n const deferralOff = input.env.processes.filter((p) => {\n const mcpCount = p.mcpServers === \"default\" ? input.env.mcpServerCount : p.mcpServers;\n return p.deferral === \"OFF\" && mcpCount > 0;\n });\n findings.push({\n finding: \"tool deferral on live processes\",\n severity: deferralOff.length > 0 ? \"RED\" : \"GREEN\",\n evidence:\n deferralOff.length > 0\n ? deferralOff\n .map((p) => `pid ${p.pid}: ${p.mcpServers === \"default\" ? input.env.mcpServerCount : p.mcpServers} mcp servers, tools=${p.toolsArg}`)\n .join(\"; \")\n : `${input.env.processes.length} claude process(es) checked, none with an explicit --tools list missing ToolSearch and MCP servers registered`,\n });\n\n const skillTokens = input.skills.enabledTotal;\n findings.push({\n finding: \"skill catalogue tokens\",\n severity: skillTokens > SKILL_CATALOGUE_RED ? \"RED\" : skillTokens > SKILL_CATALOGUE_AMBER ? \"AMBER\" : \"GREEN\",\n evidence: `${skillTokens} tokens loaded (${input.skills.total} on disk) across ${input.skills.entries.length} entries` +\n (input.skills.duplicates.length ? `, ${input.skills.duplicates.length} case-insensitive duplicate name(s)` : \"\"),\n });\n\n const inheriting = input.subagents.entries.filter((e) => e.model === \"inherits\");\n if (input.subagents.entries.length === 0) {\n findings.push({ finding: \"subagent model pinning\", severity: \"GREEN\", evidence: \"no agent files\" });\n } else {\n findings.push({\n finding: \"subagent model pinning\",\n severity: inheriting.length > 0 ? \"AMBER\" : \"GREEN\",\n evidence: `${inheriting.length} of ${input.subagents.entries.length} inherit`,\n });\n }\n\n const configuredCount = input.mcp.servers.filter((s) => s.configuredIn.length > 0).length;\n const plainLaunch = input.mcp.liveProcesses.find((p) => p.mcpConfigPath === null);\n if (!plainLaunch) {\n findings.push({\n finding: \"MCP servers loaded vs. used\",\n severity: \"GREEN\",\n evidence: `${configuredCount} configured, no plain launch found`,\n });\n } else {\n const zeroCallServers = input.mcp.servers.filter((s) => s.configuredIn.length > 0 && s.calls30d === 0);\n findings.push({\n finding: \"MCP servers loaded vs. used\",\n severity: zeroCallServers.length > 0 ? \"AMBER\" : \"GREEN\",\n evidence: `${configuredCount} configured, ${zeroCallServers.length} loaded by pid ${plainLaunch.pid}`,\n });\n }\n\n if (input.session) {\n const avg = input.session.avgContext ?? 0;\n const max = input.session.maxContext ?? 0;\n const trigger = input.ctxThreshold;\n const severity: Severity =\n avg > CONTEXT_GROWTH_AVG_RED_RATIO * trigger || input.session.turnsAboveThreshold > 0\n ? \"RED\"\n : max > CONTEXT_GROWTH_MAX_AMBER_RATIO * trigger\n ? \"AMBER\"\n : \"GREEN\";\n findings.push({\n finding: \"context growth\",\n severity,\n evidence:\n `avg ${avg.toLocaleString(\"en-US\")}, max ${max.toLocaleString(\"en-US\")} of trigger ${trigger.toLocaleString(\"en-US\")} ` +\n `(window ${input.session.window.toLocaleString(\"en-US\")}, override ${input.session.autocompactPct}), ` +\n `${input.session.turnsAboveThreshold} turns above`,\n });\n\n const perPromptHookCost = input.hooks.totalsByEvent.UserPromptSubmit;\n const inputSent =\n input.session.totals.cache_read_input_tokens +\n input.session.totals.cache_creation_input_tokens +\n input.session.totals.input_tokens;\n if (perPromptHookCost !== undefined && inputSent > 0) {\n const prompts = input.session.userPrompts;\n const turns = input.session.turns;\n const total = perPromptHookCost * input.session.promptExposure;\n const pct = (total / inputSent) * 100;\n findings.push({\n finding: \"per-prompt hook cost\",\n severity:\n pct > PER_PROMPT_HOOK_COST_RED * 100\n ? \"RED\"\n : pct > PER_PROMPT_HOOK_COST_AMBER * 100\n ? \"AMBER\"\n : \"GREEN\",\n evidence: `${perPromptHookCost} tokens/prompt x ${prompts} prompts, carried over ${turns} turns = ${total} tokens (${pct.toFixed(1)}% of ${inputSent} input tokens sent)`,\n });\n }\n\n const compactions = input.session.compactions;\n const autoCompactions = compactions.filter((c) => c.trigger === \"auto\");\n const overshoots = autoCompactions.filter((c) => c.preTokens > 1.25 * input.ctxThreshold);\n findings.push({\n finding: \"compaction trigger\",\n severity: overshoots.length > 0 ? \"RED\" : \"GREEN\",\n evidence:\n overshoots.length > 0\n ? `override not honoured: ${overshoots.map((c) => `preTokens=${c.preTokens}`).join(\", \")} vs configured ${input.ctxThreshold}`\n : compactions.length === 0\n ? `0 compactions in ${input.session.turns} turns`\n : `${compactions.length} compaction(s), all within 25% of configured ${input.ctxThreshold}`,\n });\n\n const switches = input.session.modelSwitches;\n const fallbacks = input.session.fallbacks;\n const rebuilds = switches.filter((s) => s.cacheCreation > MODEL_SWITCH_REBUILD_LIMIT);\n const short = (model: string) => model.replace(/^claude-/, \"\");\n let switchEvidence: string;\n if (switches.length === 0) {\n switchEvidence = `0 switches in ${input.session.turns} turns, models ${JSON.stringify(input.session.models)}`;\n } else {\n const list = switches\n .map(\n (s) =>\n `${short(s.from)}->${short(s.to)} at turn ${s.turnIndex} (cache_read ${s.cacheRead}, cache_creation ${s.cacheCreation})`\n )\n .join(\"; \");\n switchEvidence = `${switches.length} switch(es): ${list}`;\n if (fallbacks.length > 0) {\n switchEvidence += `; ${fallbacks.length} safeguard fallback(s): ${fallbacks.map((f) => f.category).join(\",\")}`;\n }\n }\n findings.push({\n finding: \"mid-session model switches\",\n severity: switches.length === 0 ? \"GREEN\" : rebuilds.length > 0 ? \"RED\" : \"AMBER\",\n evidence: switchEvidence,\n });\n }\n\n return sortFindings(findings);\n}\n","/**\n * `pai audit tokens ladder` — LIVE: what each layer of headless-worker\n * scaffolding actually costs in first-turn context, measured by spawning the\n * real `claude -p` binary at increasing configuration and reading the\n * `usage` block of its `--output-format json` result. No section above this\n * one makes a network call; this is the one exception, gated behind `--live`.\n *\n * Rungs (each a superset of the last, except L3 which drops every flag):\n * L0 empty MCP config, no tool schemas at all\n * L1 same, plus the core file/shell tool schemas (`--tools`)\n * L2 same as L1, but the empty MCP config is swapped for a real one\n * L3 plain: none of --strict-mcp-config / --mcp-config / --tools\n */\n\nimport { spawn } from \"node:child_process\";\nimport { readdirSync, statSync } from \"node:fs\";\nimport { join } from \"node:path\";\nimport { usageContextTokens, type UsageBlock } from \"../workers/run.js\";\nimport { ensureNoMcpConfig } from \"../workers/paths.js\";\n\nexport const PROBE_PROMPT = \"Reply with exactly the single word OK and nothing else. Do not use any tool.\";\nexport const LADDER_TOOLS = \"Bash,Read,Edit,Write,Grep,Glob,Skill,ToolSearch\";\nexport const LADDER_TIMEOUT_MS = 180_000;\nexport const DEFAULT_LADDER_MODEL = \"haiku\";\n\nexport interface LadderRung {\n id: string;\n description: string;\n /** Full argv (minus the `claude` binary itself). */\n args: string[];\n}\n\n/** Build the four rungs' argv. Pure — no spawning, so it's unit-testable. */\nexport function buildLadderRungs(model: string, emptyMcpPath: string, realMcpPath: string): LadderRung[] {\n const base = [\"-p\", PROBE_PROMPT, \"--model\", model, \"--output-format\", \"json\"];\n return [\n {\n id: \"L0\",\n description: \"empty MCP config, no tools\",\n args: [...base, \"--strict-mcp-config\", \"--mcp-config\", emptyMcpPath, \"--tools\", \"\"],\n },\n {\n id: \"L1\",\n description: `empty MCP config, --tools ${LADDER_TOOLS}`,\n args: [...base, \"--strict-mcp-config\", \"--mcp-config\", emptyMcpPath, \"--tools\", LADDER_TOOLS],\n },\n {\n id: \"L2\",\n description: `real MCP config, --tools ${LADDER_TOOLS}`,\n args: [...base, \"--strict-mcp-config\", \"--mcp-config\", realMcpPath, \"--tools\", LADDER_TOOLS],\n },\n {\n id: \"L3\",\n description: \"plain (no --strict-mcp-config, no --mcp-config, no --tools)\",\n args: [...base],\n },\n ];\n}\n\n/** Newest `*.mcp.json` in a worker log dir, or null if none exist. */\nexport function newestMcpConfig(logDir: string): string | null {\n let files: string[];\n try {\n files = readdirSync(logDir);\n } catch {\n return null;\n }\n const candidates = files\n .filter((f) => f.endsWith(\".mcp.json\"))\n .map((f) => join(logDir, f));\n if (candidates.length === 0) return null;\n return candidates\n .map((path) => ({ path, mtime: statSync(path).mtimeMs }))\n .sort((a, b) => b.mtime - a.mtime)[0].path;\n}\n\nexport interface LadderReading {\n id: string;\n description: string;\n firstTurnContext: number | null;\n timedOut: boolean;\n error?: string;\n /** Delta vs. the previous rung's firstTurnContext; null when either side is missing. */\n delta: number | null;\n}\n\nexport interface LadderReport {\n model: string;\n mcpConfigPath: string;\n readings: LadderReading[];\n}\n\n/** Spawn one rung of `claude -p ...` and read its usage-derived first-turn context. */\nfunction runRung(rung: LadderRung, cwd: string): Promise<{ firstTurnContext: number | null; timedOut: boolean; error?: string }> {\n return new Promise((resolvePromise) => {\n const env = { ...process.env };\n delete env.PAI_WORKER;\n\n const proc = spawn(\"claude\", rung.args, { cwd, env, stdio: [\"ignore\", \"pipe\", \"pipe\"] });\n let out = \"\";\n let timedOut = false;\n const timer = setTimeout(() => {\n timedOut = true;\n try {\n proc.kill(\"SIGKILL\");\n } catch {\n /* already gone */\n }\n }, LADDER_TIMEOUT_MS);\n\n proc.stdout.on(\"data\", (chunk: Buffer) => {\n out += chunk.toString(\"utf8\");\n });\n proc.on(\"error\", (e) => {\n clearTimeout(timer);\n resolvePromise({ firstTurnContext: null, timedOut: false, error: e.message });\n });\n proc.on(\"close\", () => {\n clearTimeout(timer);\n if (timedOut) {\n resolvePromise({ firstTurnContext: null, timedOut: true });\n return;\n }\n try {\n const parsed = JSON.parse(out.trim()) as { usage?: UsageBlock };\n resolvePromise({ firstTurnContext: usageContextTokens(parsed.usage), timedOut: false });\n } catch (e) {\n resolvePromise({ firstTurnContext: null, timedOut: false, error: (e as Error).message });\n }\n });\n });\n}\n\nexport async function auditLadder(opts: {\n model?: string;\n mcpConfigPath?: string;\n logDir: string;\n cwd?: string;\n}): Promise<LadderReport> {\n const model = opts.model ?? DEFAULT_LADDER_MODEL;\n const emptyMcpPath = ensureNoMcpConfig(opts.logDir);\n const realMcpPath = opts.mcpConfigPath ?? newestMcpConfig(opts.logDir) ?? emptyMcpPath;\n const cwd = opts.cwd ?? process.cwd();\n\n const rungs = buildLadderRungs(model, emptyMcpPath, realMcpPath);\n const readings: LadderReading[] = [];\n let previous: number | null = null;\n for (const rung of rungs) {\n const result = await runRung(rung, cwd);\n const delta = result.firstTurnContext !== null && previous !== null ? result.firstTurnContext - previous : null;\n readings.push({\n id: rung.id,\n description: rung.description,\n firstTurnContext: result.firstTurnContext,\n timedOut: result.timedOut,\n error: result.error,\n delta,\n });\n if (result.firstTurnContext !== null) previous = result.firstTurnContext;\n }\n\n return { model, mcpConfigPath: realMcpPath, readings };\n}\n","/**\n * `pai audit tokens subagents` — token cost of every Claude Code subagent\n * definition: `.claude/agents/*.md` under the user's home and (separately)\n * under the current project. A subagent with no `model:` frontmatter field\n * inherits the caller's model at runtime rather than costing a fixed model\n * choice, so it is reported as `\"inherits\"` rather than left blank.\n */\n\nimport { readdirSync, readFileSync } from \"node:fs\";\nimport { join } from \"node:path\";\nimport { countTokens, TOKEN_ENCODING } from \"./tokens.js\";\n\nexport interface SubagentEntry {\n path: string;\n tokens: number;\n model: string;\n}\n\nexport interface SubagentsReport {\n encoding: string;\n entries: SubagentEntry[];\n}\n\n/** Strip a single layer of matching quotes (\"...\" or '...') from a trimmed value. */\nfunction unquote(value: string): string {\n if (value.length >= 2) {\n const first = value[0];\n const last = value[value.length - 1];\n if ((first === '\"' && last === '\"') || (first === \"'\" && last === \"'\")) {\n return value.slice(1, -1);\n }\n }\n return value;\n}\n\n/** The top-level `model:` frontmatter field, or `\"inherits\"` if absent/empty. */\nfunction parseModel(text: string): string {\n const match = /^---\\n([\\s\\S]*?)\\n---/.exec(text);\n if (!match) return \"inherits\";\n const modelMatch = /^model:\\s*(.+)$/m.exec(match[1]);\n if (!modelMatch) return \"inherits\";\n const value = unquote(modelMatch[1].trim());\n return value === \"\" ? \"inherits\" : value;\n}\n\n/** Every top-level `.md` file directly under `agentsDir` (not recursive). */\nfunction findAgentFiles(agentsDir: string): string[] {\n let names: string[];\n try {\n names = readdirSync(agentsDir);\n } catch {\n return [];\n }\n return names.filter((n) => n.endsWith(\".md\")).map((n) => join(agentsDir, n));\n}\n\nfunction readEntry(path: string): SubagentEntry {\n let text = \"\";\n try {\n text = readFileSync(path, \"utf8\");\n } catch {\n /* unreadable file: treat as an empty entry rather than throwing */\n }\n return { path, tokens: countTokens(text), model: parseModel(text) };\n}\n\nexport function auditSubagents(homeDir: string, cwd: string): SubagentsReport {\n const paths = [\n ...findAgentFiles(join(homeDir, \".claude\", \"agents\")),\n ...findAgentFiles(join(cwd, \".claude\", \"agents\")),\n ];\n return { encoding: TOKEN_ENCODING, entries: paths.map(readEntry) };\n}\n","/**\n * `pai audit mcp` — cross-reference every MCP server name found anywhere\n * (global/project config, project `.mcp.json`, live process argv, 30-day\n * transcript usage) against where it's configured, whether it's pinned via\n * `pai project mcp`, whether a live process actually loaded it, and how much\n * it was used in the last 30 days.\n *\n * The `--connect` path is the only side-effecting one: it launches every\n * configured stdio MCP server to call `listTools()`. It never runs unless\n * the caller passes `connect: true` explicitly — there is no default-on path.\n */\n\nimport { existsSync, readFileSync, readdirSync, statSync } from \"node:fs\";\nimport { createReadStream } from \"node:fs\";\nimport { createInterface } from \"node:readline\";\nimport { homedir } from \"node:os\";\nimport { join, resolve } from \"node:path\";\nimport { findClaudePids, extractMcpConfigArg, readProcessEnvLine } from \"./env.js\";\nimport { projectLaunchConfig } from \"../workers/project-config.js\";\n\nexport interface McpLiveProcess {\n pid: number;\n mcpConfigPath: string | null;\n servers: string[] | null;\n}\n\nexport interface McpUsage {\n distinctTools: number;\n calls: number;\n}\n\nexport interface McpServerRow {\n server: string;\n configuredIn: string[];\n disabled: boolean;\n /** \"yes\" when the project pin lists this server, \"-\" when a pin exists without it, null when the project has no pin (every server loads). */\n pinned: \"yes\" | \"-\" | null;\n loadedLive: string;\n toolsExposed: number | \"UNKNOWN\";\n toolsUsed30d: number;\n calls30d: number;\n}\n\nexport interface McpReport {\n servers: McpServerRow[];\n liveProcesses: McpLiveProcess[];\n}\n\nconst THIRTY_DAYS_MS = 30 * 24 * 60 * 60 * 1000;\nconst MAX_TRANSCRIPT_BYTES = 50 * 1024 * 1024;\n\n/** Whether a transcript file is worth streaming: fresh enough and small enough. */\nexport function shouldScanTranscript(stat: { size: number; mtimeMs: number }, now: number): boolean {\n if (stat.size > MAX_TRANSCRIPT_BYTES) return false;\n if (now - stat.mtimeMs > THIRTY_DAYS_MS) return false;\n return true;\n}\n\n/**\n * Whether a live process's resolved server set includes `serverName`.\n * `servers === null` means \"no --mcp-config flag\", i.e. every configured\n * server loads; an array (possibly empty) means only those names load.\n */\nexport function liveProcessLoadsServer(\n proc: { servers: string[] | null },\n serverName: string,\n allConfiguredNames: string[]\n): boolean {\n if (proc.servers === null) return allConfiguredNames.includes(serverName);\n return proc.servers.includes(serverName);\n}\n\nfunction resolveHomePath(path: string, homeDir: string): string {\n return path.startsWith(\"~\") ? join(homeDir, path.slice(1)) : path;\n}\n\nfunction readJsonFile<T>(path: string): T | null {\n try {\n return JSON.parse(readFileSync(path, \"utf8\")) as T;\n } catch {\n return null;\n }\n}\n\ninterface ClaudeJsonShape {\n mcpServers?: Record<string, unknown>;\n projects?: Record<string, { mcpServers?: Record<string, unknown>; disabledMcpServers?: string[] }>;\n}\n\ninterface McpJsonShape {\n mcpServers?: Record<string, unknown>;\n}\n\ninterface ConfiguredSources {\n configuredIn: Map<string, Set<string>>;\n disabled: Set<string>;\n serverDefs: Map<string, unknown>;\n}\n\nfunction gatherConfiguredSources(cwd: string, homeDir: string): ConfiguredSources {\n const configuredIn = new Map<string, Set<string>>();\n const disabled = new Set<string>();\n const serverDefs = new Map<string, unknown>();\n\n const addTag = (name: string, tag: string) => {\n if (!configuredIn.has(name)) configuredIn.set(name, new Set());\n configuredIn.get(name)!.add(tag);\n };\n\n const claudeJsonPath = join(homeDir, \".claude.json\");\n const claudeJson = readJsonFile<ClaudeJsonShape>(claudeJsonPath);\n if (claudeJson) {\n for (const [name, def] of Object.entries(claudeJson.mcpServers ?? {})) {\n addTag(name, \"global\");\n if (!serverDefs.has(name)) serverDefs.set(name, def);\n }\n const projectEntry = claudeJson.projects?.[resolve(cwd)];\n if (projectEntry) {\n for (const [name, def] of Object.entries(projectEntry.mcpServers ?? {})) {\n addTag(name, \"project\");\n if (!serverDefs.has(name)) serverDefs.set(name, def);\n }\n for (const name of projectEntry.disabledMcpServers ?? []) {\n disabled.add(name);\n if (!configuredIn.has(name)) configuredIn.set(name, new Set());\n }\n }\n }\n\n const mcpJsonPath = join(cwd, \".mcp.json\");\n if (existsSync(mcpJsonPath)) {\n const mcpJson = readJsonFile<McpJsonShape>(mcpJsonPath);\n if (mcpJson) {\n for (const [name, def] of Object.entries(mcpJson.mcpServers ?? {})) {\n addTag(name, \"project .mcp.json\");\n if (!serverDefs.has(name)) serverDefs.set(name, def);\n }\n }\n }\n\n return { configuredIn, disabled, serverDefs };\n}\n\nfunction gatherLiveProcesses(homeDir: string): McpLiveProcess[] {\n const procs: McpLiveProcess[] = [];\n for (const pid of findClaudePids()) {\n const line = readProcessEnvLine(pid);\n if (!line) continue;\n const mcpConfigArg = extractMcpConfigArg(line);\n if (!mcpConfigArg) {\n procs.push({ pid, mcpConfigPath: null, servers: null });\n continue;\n }\n const resolved = resolveHomePath(mcpConfigArg, homeDir);\n const parsed = readJsonFile<McpJsonShape>(resolved);\n const servers = parsed ? Object.keys(parsed.mcpServers ?? {}) : [];\n procs.push({ pid, mcpConfigPath: resolved, servers });\n }\n return procs;\n}\n\n/** Recursively find every file under `dir` ending in `.jsonl`. */\nfunction findJsonlFiles(dir: string): string[] {\n let entries: import(\"node:fs\").Dirent[];\n try {\n entries = readdirSync(dir, { withFileTypes: true });\n } catch {\n return [];\n }\n const found: string[] = [];\n for (const entry of entries) {\n const full = join(dir, entry.name);\n if (entry.isDirectory()) {\n found.push(...findJsonlFiles(full));\n } else if (entry.isFile() && entry.name.endsWith(\".jsonl\")) {\n found.push(full);\n }\n }\n return found;\n}\n\ninterface ToolUseBlock {\n type?: string;\n name?: string;\n}\n\ninterface TranscriptLine {\n type?: string;\n name?: string;\n message?: { content?: ToolUseBlock[] };\n}\n\nfunction foldToolUseBlock(block: ToolUseBlock, usage: Map<string, { tools: Set<string>; calls: number }>): void {\n if (block.type !== \"tool_use\") return;\n const name = block.name;\n if (!name || !name.startsWith(\"mcp__\")) return;\n const parts = name.split(\"__\");\n const server = parts[1];\n const tool = parts.slice(2).join(\"__\");\n if (!server) return;\n if (!usage.has(server)) usage.set(server, { tools: new Set(), calls: 0 });\n const entry = usage.get(server)!;\n entry.tools.add(tool);\n entry.calls += 1;\n}\n\nasync function gatherUsage(transcriptsDir: string, now: number): Promise<Map<string, McpUsage>> {\n const usage = new Map<string, { tools: Set<string>; calls: number }>();\n for (const file of findJsonlFiles(transcriptsDir)) {\n let stat: import(\"node:fs\").Stats;\n try {\n stat = statSync(file);\n } catch {\n continue;\n }\n if (!shouldScanTranscript(stat, now)) continue;\n\n const rl = createInterface({ input: createReadStream(file, \"utf8\"), crlfDelay: Infinity });\n for await (const raw of rl) {\n if (!raw.includes('\"mcp__')) continue;\n let parsed: TranscriptLine;\n try {\n parsed = JSON.parse(raw) as TranscriptLine;\n } catch {\n continue;\n }\n if (parsed.type === \"tool_use\") {\n foldToolUseBlock(parsed as ToolUseBlock, usage);\n }\n if (Array.isArray(parsed.message?.content)) {\n for (const block of parsed.message!.content!) {\n foldToolUseBlock(block, usage);\n }\n }\n }\n }\n\n const result = new Map<string, McpUsage>();\n for (const [server, entry] of usage) {\n result.set(server, { distinctTools: entry.tools.size, calls: entry.calls });\n }\n return result;\n}\n\ninterface StdioLikeDef {\n command?: string;\n args?: string[];\n env?: Record<string, string>;\n}\n\nfunction isStdioDef(def: unknown): def is StdioLikeDef {\n return typeof def === \"object\" && def !== null && typeof (def as StdioLikeDef).command === \"string\";\n}\n\n/** Connect to every stdio MCP server row and record how many tools it exposes. Side-effecting: only called when `opts.connect === true`. */\nasync function connectAndCountTools(\n rows: McpServerRow[],\n serverDefs: Map<string, unknown>\n): Promise<void> {\n const { Client } = await import(\"@modelcontextprotocol/sdk/client/index.js\");\n const { StdioClientTransport } = await import(\"@modelcontextprotocol/sdk/client/stdio.js\");\n\n for (const row of rows) {\n const def = serverDefs.get(row.server);\n if (!isStdioDef(def)) continue;\n\n const transport = new StdioClientTransport({\n command: def.command!,\n args: def.args ?? [],\n env: def.env,\n });\n const client = new Client({ name: \"pai-audit-mcp\", version: \"1.0.0\" }, { capabilities: {} });\n\n let settled = false;\n const timeout = new Promise<\"UNKNOWN\">((res) => {\n setTimeout(() => {\n if (!settled) res(\"UNKNOWN\");\n }, 10_000);\n });\n\n try {\n const attempt = (async (): Promise<number | \"UNKNOWN\"> => {\n await client.connect(transport);\n const result = await client.listTools();\n return result.tools.length;\n })();\n const outcome = await Promise.race([attempt, timeout]);\n settled = true;\n row.toolsExposed = outcome;\n } catch {\n settled = true;\n row.toolsExposed = \"UNKNOWN\";\n } finally {\n try {\n await client.close();\n } catch {\n /* already closed or never connected */\n }\n }\n }\n}\n\nexport async function auditMcp(opts: {\n cwd: string;\n homeDir: string;\n connect?: boolean;\n projectsDbPath?: string;\n transcriptsDir?: string;\n}): Promise<McpReport> {\n const cwd = opts.cwd;\n const homeDir = opts.homeDir;\n const transcriptsDir = opts.transcriptsDir ?? join(homeDir, \".claude\", \"projects\");\n\n const { configuredIn, disabled, serverDefs } = gatherConfiguredSources(cwd, homeDir);\n const liveProcesses = gatherLiveProcesses(homeDir);\n const usage = await gatherUsage(transcriptsDir, Date.now());\n // `projectsDbPath` predates the storage-layer conversion, when this call opened\n // a sqlite file by path; `projectLaunchConfig` now takes an injectable\n // RegistryBackend instead, and no caller has passed one since, so it can only\n // fall through to the process-wide backend here too.\n const pinned = (await projectLaunchConfig(resolve(cwd)))?.mcp ?? null;\n\n const allNames = new Set<string>([...configuredIn.keys(), ...usage.keys()]);\n const allConfiguredNames = [...configuredIn.keys()];\n\n const rows: McpServerRow[] = [];\n for (const name of allNames) {\n const liveMatches = liveProcesses.filter((p) => liveProcessLoadsServer(p, name, allConfiguredNames));\n const serverUsage = usage.get(name);\n rows.push({\n server: name,\n configuredIn: [...(configuredIn.get(name) ?? new Set())],\n disabled: disabled.has(name),\n pinned: pinned === null ? null : pinned.includes(name) ? \"yes\" : \"-\",\n loadedLive: liveMatches.length ? liveMatches.map((p) => `pid ${p.pid}`).join(\", \") : \"-\",\n toolsExposed: \"UNKNOWN\",\n toolsUsed30d: serverUsage?.distinctTools ?? 0,\n calls30d: serverUsage?.calls ?? 0,\n });\n }\n\n rows.sort((a, b) => a.server.localeCompare(b.server));\n\n if (opts.connect === true) {\n await connectAndCountTools(rows, serverDefs);\n }\n\n return { servers: rows, liveProcesses };\n}\n","/**\n * pai audit tokens [section]\n *\n * files — token count per memory file (CLAUDE.md chain, CORE skill,\n * whisper rules, project auto-memory) and their total\n * hooks — token cost of every SessionStart / UserPromptSubmit hook,\n * plus which PreToolUse hooks rewrite Bash commands\n * session — cache/input/output token split for one session transcript,\n * plus a first-turn breakdown attributing turn-1 tokens to the\n * live-lineage transcript lines (hooks, skill listing, deferred\n * tools, MCP instructions, prompt) with dead-branch tokens\n * (abandoned prompts, persisted but never sent) reported\n * separately; `--history [n]` lists the first-turn context of\n * the newest n sessions in this project's transcript directory,\n * newest first; `--turn <n>` attributes API call n's context\n * growth to the lines injected since the previous call (hooks,\n * prompts, tool results, the previous call's re-sent output)\n * plus the cl100k residual against the billing tokenizer\n * spawn — first-turn context overhead: Agent-tool subagents vs. pai\n * workers (interactive/pane vs. headless); `--detail` adds\n * per-log prompt-token/overhead rows and a median-overhead column\n * daemon — LLM spawns, KG-extraction parse failures, work-queue counts\n * from the daemon log\n * env — ANTHROPIC_BASE_URL / model-override env on every live claude\n * process, MCP server count, configured model/effort\n * schedule — launchd agents / crontab entries that wake up more often than\n * the measured prompt-cache TTL\n * skills — token cost of the SKILL.md/command/plugin catalogue, top\n * entries by tokens, per-source subtotals, case-insensitive\n * duplicate names\n * ladder — LIVE (spawns `claude -p`, requires `--live`): first-turn context\n * at increasing headless-worker configuration (empty MCP/no\n * tools → tools → real MCP → plain)\n *\n * With no section, all eight non-live sections run and one combined\n * RED/AMBER/GREEN table is printed. `--json` prints the same data as JSON\n * instead of a table.\n */\n\nimport type { Command } from \"commander\";\nimport { homedir } from \"node:os\";\nimport { mkdirSync, readdirSync, appendFileSync, writeFileSync, existsSync } from \"node:fs\";\nimport { join } from \"node:path\";\nimport { header, ok, warn, err, dim, bold, renderTable } from \"../utils.js\";\nimport { TOKEN_ENCODING } from \"../../audit/tokens.js\";\nimport { auditFiles, defaultFileSet, type FilesReport } from \"../../audit/files.js\";\nimport { auditHooks, type HooksReport } from \"../../audit/hooks.js\";\nimport { auditSession, newestSessionLog, sessionHistory, type SessionReportOutput, type SessionHistoryRow } from \"../../audit/session.js\";\nimport { turnBreakdown, type TurnBreakdown } from \"../../audit/first-turn.js\";\nimport { auditSpawn, type SpawnGroupStats, type SpawnReport } from \"../../audit/spawn.js\";\nimport { auditDaemon, type DaemonReport } from \"../../audit/daemon.js\";\nimport { auditEnv, type EnvReport } from \"../../audit/env.js\";\nimport { auditSchedule, cacheTtlSeconds } from \"../../audit/schedule.js\";\nimport { buildFindings, type Finding, type Severity } from \"../../audit/severity.js\";\nimport { auditSkills, topByTokens, type SkillsReport } from \"../../audit/skills.js\";\nimport { auditLadder, type LadderReport } from \"../../audit/ladder.js\";\nimport { auditSubagents, type SubagentsReport } from \"../../audit/subagents.js\";\nimport { auditMcp, type McpReport } from \"../../audit/mcp.js\";\nimport { shortenPath } from \"../utils.js\";\nimport { readWorkersSection } from \"../../workers/config.js\";\nimport { workersLogDir } from \"../../workers/paths.js\";\n\nfunction severityColor(sev: Severity, text: string): string {\n if (sev === \"RED\") return err(text);\n if (sev === \"AMBER\") return warn(text);\n return ok(text);\n}\n\nfunction printJson(data: unknown): void {\n console.log(JSON.stringify(data, null, 2));\n}\n\n// ---------------------------------------------------------------------------\n// files\n// ---------------------------------------------------------------------------\n\nfunction printFiles(report: FilesReport): void {\n console.log(header(`Memory files (encoding: ${report.encoding})`));\n const rows = report.readings.map((r) => [\n r.missing ? dim(\"MISSING\") : String(r.tokens),\n r.path,\n ]);\n console.log(renderTable([\"tokens\", \"path\"], rows));\n console.log(bold(`total: ${report.total} tokens`));\n}\n\nasync function cmdFiles(paths: string[], opts: { json?: boolean }): Promise<void> {\n const targets = paths.length > 0 ? paths : defaultFileSet(process.cwd());\n const report = auditFiles(targets);\n if (opts.json) return printJson(report);\n printFiles(report);\n}\n\n// ---------------------------------------------------------------------------\n// hooks\n// ---------------------------------------------------------------------------\n\nfunction printHooks(report: HooksReport): void {\n console.log(header(`Hook output (encoding: ${report.encoding})`));\n console.log(dim(`settings.json: ${report.settingsFiles.join(\", \") || \"none found\"}`));\n const rows = report.readings.map((r) => [\n r.event,\n r.matcher ?? \"\",\n String(r.tokens),\n r.error ? err(r.error) : \"\",\n r.command,\n ]);\n console.log(renderTable([\"event\", \"matcher\", \"tokens\", \"error\", \"command\"], rows));\n for (const [event, total] of Object.entries(report.totalsByEvent)) {\n console.log(bold(`${event} total: ${total} tokens`));\n }\n console.log(\n dim(\n `PreToolUse hooks: ${report.preToolUseHooks.length}` +\n (report.preToolUseRewritesBash ? \", including a Bash-matcher rewrite hook\" : \"\")\n )\n );\n}\n\nfunction cmdHooks(opts: { json?: boolean }): void {\n const report = auditHooks(process.cwd());\n if (opts.json) return printJson(report);\n printHooks(report);\n}\n\n// ---------------------------------------------------------------------------\n// session\n// ---------------------------------------------------------------------------\n\nfunction printSession(report: SessionReportOutput): void {\n console.log(header(`Session usage: ${report.path}`));\n console.log(dim(`size: ${report.sizeBytes} bytes, assistant turns with usage: ${report.turns}`));\n console.log(\n dim(\n `window: ${report.window.toLocaleString(\"en-US\")} (${report.windowSource}), ` +\n `trigger: ${report.trigger.toLocaleString(\"en-US\")} (${report.triggerSource}, ` +\n `override ${report.autocompactPct}%)`\n )\n );\n const rows = Object.entries(report.totals).map(([key, value]) => [\n key,\n value.toLocaleString(\"en-US\"),\n `${report.percentages[key].toFixed(1)}%`,\n ]);\n rows.push([\"TOTAL\", report.totalTokens.toLocaleString(\"en-US\"), \"100.0%\"]);\n console.log(renderTable([\"metric\", \"tokens\", \"share\"], rows));\n console.log(`context on first turn: ${report.firstTurnContext ?? \"n/a\"}`);\n if (report.firstTurn && report.firstTurn.apiContext !== null) {\n const ft = report.firstTurn;\n console.log(header(\"first-turn breakdown (live lineage, cl100k)\"));\n const ftRows = ft.items.filter((i) => i.tokens > 0).map((i) => [i.kind, String(i.tokens), i.label]);\n console.log(renderTable([\"kind\", \"tokens\", \"label\"], ftRows));\n console.log(`transcript-side: ${ft.transcriptTokens}`);\n console.log(`remainder (system prompt + tool schemas + agents + memory): ${ft.remainder}`);\n console.log(`dead-branch: ${ft.deadBranchTokens} tokens on ${ft.deadBranchLines} lines, persisted but never sent`);\n }\n console.log(`context on last turn: ${report.lastTurnContext ?? \"n/a\"}`);\n console.log(\n `cache_creation split: ephemeral_5m=${report.cacheCreationSplit.ephemeral5m}, ` +\n `ephemeral_1h=${report.cacheCreationSplit.ephemeral1h}`\n );\n console.log(dim(`models: ${JSON.stringify(report.models)}`));\n console.log(\n `avg context: ${report.avgContext ?? \"n/a\"}, max context: ${report.maxContext ?? \"n/a\"}, ` +\n `turns above threshold: ${report.turnsAboveThreshold}, cache-rebuild turns: ${report.cacheRebuildTurns}, ` +\n `user prompts: ${report.userPrompts}, prompt exposure: ${report.promptExposure}`\n );\n console.log(`compactions: ${report.compactions.length}`);\n for (const c of report.compactions) {\n console.log(dim(` ${c.trigger} at turn ${c.turnIndex}: preTokens=${c.preTokens}`));\n }\n console.log(`model switches: ${report.modelSwitches.length}`);\n for (const s of report.modelSwitches) {\n console.log(dim(` ${s.from} -> ${s.to} at turn ${s.turnIndex}: cache_read=${s.cacheRead}, cache_creation=${s.cacheCreation}`));\n }\n if (report.fallbacks.length > 0) {\n console.log(`safeguard fallbacks: ${report.fallbacks.length}`);\n for (const f of report.fallbacks) {\n console.log(dim(` ${f.from} -> ${f.to} before turn ${f.turnIndex}: ${f.category} (${f.scope})`));\n }\n }\n console.log(\n dim(\n `idle gaps > 60min: ${report.idleGapsOver60min}, keepalive beats: ${report.keepaliveBeats ?? \"n/a\"}`\n )\n );\n}\n\nfunction printTurnBreakdown(tb: TurnBreakdown): void {\n console.log(header(`turn ${tb.turn} attribution (live lineage, cl100k)`));\n const rows = tb.items\n .filter((i) => i.tokens > 0)\n .map((i) => [String(i.tokens), i.kind, i.label.replace(/\\s+/g, \" \").slice(0, 60)]);\n console.log(renderTable([\"tokens\", \"kind\", \"label\"], rows));\n console.log(`transcript-side ${tb.transcriptTokens}`);\n console.log(`billed delta ${tb.billedDelta ?? \"n/a\"} (context ${tb.apiContext ?? \"n/a\"} after ${tb.prevApiContext})`);\n console.log(`previous call output ${tb.prevOutputTokens} (includes thinking, re-sent on tool turns)`);\n console.log(\n `previous call visible output ${tb.prevVisibleTokens} (cl100k, listed as assistant:prev, not in transcript-side)`\n );\n console.log(\n `residual ${tb.residual ?? \"n/a\"}: positive = cl100k undercount vs the billing tokenizer (1.2-1.45x measured on tool output and markdown tables); negative = the previous call's thinking was not re-sent (only re-sent inside a tool loop)`\n );\n}\n\nfunction printSessionHistory(rows: SessionHistoryRow[]): void {\n console.log(header(\"Session history (newest first)\"));\n const tableRows = rows.map((r) => [\n r.firstTurnAt ?? \"n/a\",\n r.sessionId,\n r.firstTurnContext === null ? \"n/a\" : String(r.firstTurnContext),\n String(r.turns),\n String(r.switches),\n Object.entries(r.models)\n .map(([name, count]) => `${name}:${count}`)\n .join(\",\"),\n ]);\n console.log(renderTable([\"first turn (UTC)\", \"session\", \"first-turn ctx\", \"turns\", \"switches\", \"models\"], tableRows));\n}\n\nasync function cmdSession(\n path: string | undefined,\n opts: { json?: boolean; ctxThreshold?: string; history?: string | boolean; turn?: string }\n): Promise<void> {\n if (opts.history !== undefined) {\n const limit = typeof opts.history === \"string\" ? parseInt(opts.history, 10) : 20;\n const rows = await sessionHistory(process.cwd(), limit);\n if (opts.json) return printJson(rows);\n printSessionHistory(rows);\n return;\n }\n if (opts.turn !== undefined) {\n const target = path ?? newestSessionLog();\n if (!target) {\n console.error(err(\" No session transcript found.\"));\n process.exitCode = 1;\n return;\n }\n const tb = turnBreakdown(target, parseInt(opts.turn, 10));\n if (!tb) {\n console.error(err(` Turn ${opts.turn} not found in ${target}.`));\n process.exitCode = 1;\n return;\n }\n if (opts.json) return printJson(tb);\n printTurnBreakdown(tb);\n return;\n }\n const threshold = opts.ctxThreshold ? parseInt(opts.ctxThreshold, 10) : undefined;\n const report = await auditSession(path, threshold);\n if (!report) {\n console.error(err(\" No session transcript found.\"));\n process.exitCode = 1;\n return;\n }\n if (opts.json) return printJson(report);\n printSession(report);\n}\n\n// ---------------------------------------------------------------------------\n// spawn\n// ---------------------------------------------------------------------------\n\nfunction printSpawnGroup(name: string, stats: SpawnGroupStats): string[] {\n return [\n name,\n String(stats.count),\n stats.min === null ? \"n/a\" : String(stats.min),\n stats.median === null ? \"n/a\" : String(stats.median),\n stats.max === null ? \"n/a\" : String(stats.max),\n stats.medianOverhead === null ? \"n/a\" : String(stats.medianOverhead),\n stats.models.join(\", \"),\n ];\n}\n\nconst SPAWN_GROUP_LABELS: Record<string, string> = {\n agentSubagents: \"Agent-tool subagents\",\n workersInteractive: \"pai workers (interactive/pane)\",\n workersHeadless: \"pai workers (headless -p)\",\n};\n\nfunction printSpawn(report: SpawnReport, detail?: boolean): void {\n if (detail) {\n console.log(header(\"Spawn overhead — per-log detail\"));\n const rows = report.readings.map((r) => [\n SPAWN_GROUP_LABELS[r.group] ?? r.group,\n r.firstTurnContext === null ? \"n/a\" : String(r.firstTurnContext),\n r.promptTokens === null ? \"n/a\" : String(r.promptTokens),\n r.overhead === null ? \"n/a\" : String(r.overhead),\n r.model ?? \"\",\n shortenPath(r.path, 70),\n ]);\n console.log(renderTable([\"group\", \"first-turn ctx\", \"prompt tok\", \"overhead\", \"model\", \"path\"], rows));\n }\n console.log(header(\"Spawn overhead (first-turn context tokens)\"));\n const rows = [\n printSpawnGroup(\"Agent-tool subagents\", report.agentSubagents),\n printSpawnGroup(\"pai workers (interactive/pane)\", report.workersInteractive),\n printSpawnGroup(\"pai workers (headless -p)\", report.workersHeadless),\n ];\n console.log(renderTable([\"group\", \"count\", \"min\", \"median\", \"max\", \"median overhead\", \"models\"], rows));\n}\n\nasync function cmdSpawn(opts: { json?: boolean; n?: string; detail?: boolean }): Promise<void> {\n const n = opts.n ? parseInt(opts.n, 10) : 10;\n const report = await auditSpawn(n);\n if (opts.json) return printJson(report);\n printSpawn(report, opts.detail);\n}\n\n// ---------------------------------------------------------------------------\n// daemon\n// ---------------------------------------------------------------------------\n\nfunction printDaemon(report: DaemonReport): void {\n console.log(header(`Daemon log: ${report.logPath}`));\n console.log(dim(`window start (log file birth time): ${report.windowStart ?? \"unknown\"}`));\n const spawnRows = Object.entries(report.spawnsByModel).map(([model, s]) => [\n model,\n String(s.count),\n String(s.avgPromptChars),\n ]);\n console.log(renderTable([\"model\", \"spawns\", \"avg prompt chars\"], spawnRows));\n console.log(\n `kg-extraction JSON parse failures: ${report.kgParseFailures} ` +\n `(${(report.kgParseFailureRate * 100).toFixed(1)}% of session-summary spawns; ` +\n `${report.kgExtractionLines} kg-extraction log lines total)`\n );\n const jobRows = Object.entries(report.jobCounts).map(([job, c]) => [job, String(c.processing), String(c.completed)]);\n console.log(renderTable([\"job type\", \"processing\", \"completed\"], jobRows));\n}\n\nfunction cmdDaemon(opts: { json?: boolean }): void {\n const report = auditDaemon();\n if (opts.json) return printJson(report);\n printDaemon(report);\n}\n\n// ---------------------------------------------------------------------------\n// env\n// ---------------------------------------------------------------------------\n\nfunction printEnv(report: EnvReport): void {\n console.log(header(\"Live claude processes\"));\n const rows = report.processes.map((p) => [\n String(p.pid),\n p.baseUrl ? err(p.baseUrl) : dim(\"(default)\"),\n p.authTokenPresent ? \"present\" : \"absent\",\n JSON.stringify(p.defaultModels),\n p.enableToolSearch ?? \"\",\n String(p.mcpServers),\n p.deferral === \"OFF\" ? err(p.deferral) : p.deferral === \"n/a\" ? dim(p.deferral) : p.deferral,\n p.workerId ?? \"\",\n p.age ?? \"\",\n ]);\n console.log(\n renderTable(\n [\"pid\", \"base url\", \"auth token\", \"default models\", \"tool search\", \"tools\", \"deferral\", \"worker id\", \"age\"],\n rows\n )\n );\n for (const p of report.processes) {\n if (p.baseUrl) console.log(err(` RED: claude process ${p.pid} is routed through ${p.baseUrl}`));\n const mcpCount = p.mcpServers === \"default\" ? report.mcpServerCount : p.mcpServers;\n if (p.deferral === \"OFF\" && mcpCount > 0) {\n console.log(err(` RED: claude process ${p.pid} has --tools without ToolSearch (${mcpCount} MCP servers registered)`));\n }\n }\n console.log(dim(`MCP servers registered: ${report.mcpServerCount}`));\n console.log(dim(`settings.json model: ${report.settingsModel ?? \"unset\"}, effortLevel: ${report.settingsEffortLevel ?? \"unset\"}`));\n}\n\nfunction cmdEnv(opts: { json?: boolean }): void {\n const report = auditEnv();\n if (opts.json) return printJson(report);\n printEnv(report);\n}\n\n// ---------------------------------------------------------------------------\n// schedule\n// ---------------------------------------------------------------------------\n\nasync function scheduleTtlSeconds(): Promise<number> {\n const session = await auditSession(newestSessionLog() ?? undefined).catch(() => null);\n return cacheTtlSeconds(session?.cacheCreationSplit);\n}\n\nfunction printSchedule(report: Awaited<ReturnType<typeof auditSchedule>>): void {\n console.log(header(`Launchd agents (cache TTL: ${report.ttlSeconds}s)`));\n const rows = report.agents.map((a) => [\n a.label ?? \"\",\n a.startInterval === null ? \"\" : String(a.startInterval),\n a.startCalendarInterval ? \"yes\" : \"\",\n a.keepAlive ? \"yes\" : \"\",\n a.programArgs.join(\" \"),\n a.exceedsTtl ? warn(\"exceeds TTL\") : \"\",\n ]);\n console.log(renderTable([\"label\", \"interval(s)\", \"calendar\", \"keepAlive\", \"program\", \"\"], rows));\n console.log(header(\"crontab -l\"));\n for (const line of report.crontabLines) console.log(dim(line));\n}\n\nasync function cmdSchedule(opts: { json?: boolean }): Promise<void> {\n const ttl = await scheduleTtlSeconds();\n const report = auditSchedule(ttl);\n if (opts.json) return printJson(report);\n printSchedule(report);\n}\n\n// ---------------------------------------------------------------------------\n// skills\n// ---------------------------------------------------------------------------\n\nfunction printSkills(report: SkillsReport, top: number): void {\n console.log(header(`Skill/command/plugin catalogue (encoding: ${report.encoding})`));\n console.log(dim(`${report.entries.length} entries, ${report.enabledTotal} tokens loaded (${report.total} on disk)`));\n const subtotalRows = ([\"skills\", \"commands\", \"plugins\"] as const).map((source) => [\n source,\n String(report.countsBySource[source]),\n String(report.totalsBySource[source]),\n String(report.enabledTotalsBySource[source]),\n ]);\n console.log(renderTable([\"source\", \"entries\", \"tokens\", \"enabled\"], subtotalRows));\n\n console.log(header(`Top ${top} by tokens`));\n const topRows = topByTokens(report, top).map((e) => [String(e.tokens), e.source, e.enabled ? \"yes\" : \"no\", e.name, e.path]);\n console.log(renderTable([\"tokens\", \"source\", \"on\", \"name\", \"path\"], topRows));\n\n console.log(header(\"Case-insensitive duplicate names\"));\n if (report.duplicates.length === 0) {\n console.log(dim(\"none\"));\n } else {\n for (const dup of report.duplicates) {\n console.log(`${bold(dup.name)}:`);\n for (const path of dup.paths) console.log(` ${path}`);\n }\n }\n}\n\nfunction cmdSkills(opts: { json?: boolean; top?: string }): void {\n const top = opts.top ? parseInt(opts.top, 10) : 15;\n const report = auditSkills();\n if (opts.json) return printJson({ ...report, top: topByTokens(report, top) });\n printSkills(report, top);\n}\n\n// ---------------------------------------------------------------------------\n// ladder\n// ---------------------------------------------------------------------------\n\nfunction printLadder(report: LadderReport): void {\n console.log(header(`Context ladder (model: ${report.model}, mcp config: ${report.mcpConfigPath})`));\n const rows = report.readings.map((r) => [\n r.id,\n r.description,\n r.timedOut ? \"TIMEOUT\" : r.firstTurnContext === null ? `n/a${r.error ? ` (${r.error})` : \"\"}` : String(r.firstTurnContext),\n r.delta === null ? \"n/a\" : String(r.delta),\n ]);\n console.log(renderTable([\"rung\", \"description\", \"first-turn tokens\", \"delta\"], rows));\n}\n\nasync function cmdLadder(opts: { json?: boolean; live?: boolean; model?: string; mcpConfig?: string }): Promise<void> {\n if (!opts.live) {\n console.log(\n warn(\n \"Refusing to run: `pai audit tokens ladder` spawns several real `claude -p` calls \" +\n \"(billed API traffic). Pass --live to run it.\"\n )\n );\n return;\n }\n const { workers } = readWorkersSection();\n const logDir = workersLogDir(workers);\n const report = await auditLadder({ model: opts.model, mcpConfigPath: opts.mcpConfig, logDir });\n if (opts.json) return printJson(report);\n printLadder(report);\n}\n\n// ---------------------------------------------------------------------------\n// subagents\n// ---------------------------------------------------------------------------\n\nfunction printSubagents(report: SubagentsReport): void {\n console.log(header(`Subagent definitions (encoding: ${report.encoding})`));\n if (report.entries.length === 0) {\n console.log(dim(\"no agent files\"));\n return;\n }\n const rows = report.entries.map((e) => [String(e.tokens), e.model === \"inherits\" ? dim(\"inherits\") : e.model, e.path]);\n console.log(renderTable([\"tokens\", \"model\", \"path\"], rows));\n const inheriting = report.entries.filter((e) => e.model === \"inherits\").length;\n console.log(dim(`${inheriting} of ${report.entries.length} inherit the caller's model`));\n}\n\nfunction cmdSubagents(opts: { json?: boolean }): void {\n const report = auditSubagents(homedir(), process.cwd());\n if (opts.json) return printJson(report);\n printSubagents(report);\n}\n\n// ---------------------------------------------------------------------------\n// mcp\n// ---------------------------------------------------------------------------\n\nfunction printMcp(report: McpReport): void {\n console.log(header(\"MCP servers: configured vs. loaded vs. used\"));\n const rows = report.servers.map((s) => [\n s.server + (s.disabled ? dim(\" (disabled)\") : \"\"),\n s.configuredIn.join(\", \") || dim(\"-\"),\n s.pinned ?? dim(\"all\"),\n s.loadedLive,\n String(s.toolsExposed),\n String(s.toolsUsed30d),\n String(s.calls30d),\n ]);\n console.log(\n renderTable(\n [\"server\", \"configured-in\", \"pinned\", \"loaded-live\", \"tools-exposed\", \"tools-used-30d\", \"calls-30d\"],\n rows\n )\n );\n console.log(dim(`live claude processes: ${report.liveProcesses.length}`));\n}\n\nasync function cmdMcp(opts: { json?: boolean; connect?: boolean }): Promise<void> {\n const report = await auditMcp({ cwd: process.cwd(), homeDir: homedir(), connect: opts.connect });\n if (opts.json) return printJson(report);\n printMcp(report);\n}\n\n// ---------------------------------------------------------------------------\n// combined\n// ---------------------------------------------------------------------------\n\nconst DEFAULT_CTX_THRESHOLD = 200_000;\n\nexport interface CombinedData {\n encoding: string;\n findings: Finding[];\n files: FilesReport;\n hooks: HooksReport;\n session: SessionReportOutput | null;\n spawn: SpawnReport;\n daemon: DaemonReport;\n env: EnvReport;\n schedule: Awaited<ReturnType<typeof auditSchedule>>;\n skills: SkillsReport;\n subagents: SubagentsReport;\n mcp: McpReport;\n}\n\nasync function gatherCombined(): Promise<CombinedData> {\n const cwd = process.cwd();\n const files = auditFiles(defaultFileSet(cwd));\n const hooks = auditHooks(cwd);\n // No threshold override here: auditSession derives window+trigger from the\n // session's own transcript (see context-trigger.ts) instead of rating\n // every session against a fixed 200k — DEFAULT_CTX_THRESHOLD is only the\n // last-resort fallback when a session has no transcript to derive from.\n const session = await auditSession(newestSessionLog() ?? undefined).catch(() => null);\n const daemon = auditDaemon();\n const env = auditEnv();\n const spawn = await auditSpawn();\n const ttl = cacheTtlSeconds(session?.cacheCreationSplit);\n const schedule = auditSchedule(ttl);\n const skills = auditSkills();\n const subagents = auditSubagents(homedir(), cwd);\n const mcp = await auditMcp({ cwd, homeDir: homedir() });\n\n const ctxThreshold = session?.trigger ?? DEFAULT_CTX_THRESHOLD;\n const findings = buildFindings({ files, hooks, session, daemon, env, skills, subagents, mcp, ctxThreshold });\n\n return { encoding: TOKEN_ENCODING, findings, files, hooks, session, spawn, daemon, env, schedule, skills, subagents, mcp };\n}\n\n/** Strip ANSI escape sequences (chalk colour codes) from captured console output. */\nfunction stripAnsi(text: string): string {\n // eslint-disable-next-line no-control-regex\n return text.replace(/\\x1b\\[[0-9;]*m/g, \"\");\n}\n\n/** Run `fn`, capturing everything it sends through `console.log` as plain text. */\nfunction captureOutput(fn: () => void): string {\n const original = console.log;\n const lines: string[] = [];\n console.log = (...args: unknown[]) => {\n lines.push(stripAnsi(args.map((a) => String(a)).join(\" \")));\n };\n try {\n fn();\n } finally {\n console.log = original;\n }\n return lines.join(\"\\n\");\n}\n\nfunction combinedSectionOutputs(data: CombinedData): { name: string; text: string }[] {\n const sections: { name: string; text: string }[] = [];\n sections.push({ name: \"files\", text: captureOutput(() => printFiles(data.files)) });\n sections.push({ name: \"hooks\", text: captureOutput(() => printHooks(data.hooks)) });\n if (data.session) sections.push({ name: \"session\", text: captureOutput(() => printSession(data.session!)) });\n sections.push({ name: \"spawn\", text: captureOutput(() => printSpawn(data.spawn)) });\n sections.push({ name: \"daemon\", text: captureOutput(() => printDaemon(data.daemon)) });\n sections.push({ name: \"env\", text: captureOutput(() => printEnv(data.env)) });\n sections.push({ name: \"schedule\", text: captureOutput(() => printSchedule(data.schedule)) });\n sections.push({ name: \"skills\", text: captureOutput(() => printSkills(data.skills, 15)) });\n sections.push({ name: \"subagents\", text: captureOutput(() => printSubagents(data.subagents)) });\n sections.push({ name: \"mcp\", text: captureOutput(() => printMcp(data.mcp)) });\n return sections;\n}\n\n/**\n * Replace the home directory with `~` in both its plain form and the encoded\n * form Claude Code uses for transcript directories (`/Users/name/x` →\n * `-Users-name-x`), so a recorded run carries no account name.\n */\nexport function redactHome(text: string, home = homedir()): string {\n const encoded = home.replace(/[\\/\\\\]/g, \"-\");\n return text.split(home).join(\"~\").split(encoded).join(\"~\");\n}\n\n/** `--record <dir>`: write a dated, numbered markdown+JSON snapshot of the combined report, plus one summary line in `runs.md`. */\nexport function recordCombinedReport(dir: string, data: CombinedData): void {\n mkdirSync(dir, { recursive: true });\n\n // Next number is max(existing)+1, not count+1: a directory holding only\n // run4 (earlier runs recorded elsewhere) must produce run5, not run2.\n const existingNumbers = existsSync(dir)\n ? readdirSync(dir).map((f) => /-run(\\d+)\\.md$/.exec(f)?.[1]).filter((n): n is string => n !== undefined).map(Number)\n : [];\n const runNumber = (existingNumbers.length ? Math.max(...existingNumbers) : 0) + 1;\n const date = new Date().toISOString().slice(0, 10);\n const baseName = `${date}-run${runNumber}`;\n\n const findingsTable = [\n \"| FINDING | SEVERITY | EVIDENCE |\",\n \"|---------|----------|----------|\",\n ...data.findings.map((f) => `| ${f.finding} | ${f.severity} | ${f.evidence} |`),\n ].join(\"\\n\");\n\n const sections = combinedSectionOutputs(data);\n const sectionBlocks = sections.map((s) => `### ${s.name}\\n\\n\\`\\`\\`\\n${s.text}\\n\\`\\`\\`\\n`).join(\"\\n\");\n\n const md = [\n \"# pai audit tokens — recorded run\",\n \"\",\n `date: ${date}`,\n `encoding: ${data.encoding}`,\n \"\",\n findingsTable,\n \"\",\n sectionBlocks,\n ].join(\"\\n\");\n\n const json = JSON.stringify(data, null, 2);\n\n const mdRedacted = redactHome(md);\n const jsonRedacted = redactHome(json);\n\n writeFileSync(join(dir, `${baseName}.md`), mdRedacted, \"utf8\");\n writeFileSync(join(dir, `${baseName}.json`), jsonRedacted, \"utf8\");\n\n const runsPath = join(dir, \"runs.md\");\n if (!existsSync(runsPath)) {\n writeFileSync(runsPath, \"# Audit token-waste runs\\n\\n\", \"utf8\");\n }\n const counts = { RED: 0, AMBER: 0, GREEN: 0 } as Record<Severity, number>;\n for (const f of data.findings) counts[f.severity]++;\n appendFileSync(\n runsPath,\n `- ${date} run${runNumber}: ${counts.RED} RED, ${counts.AMBER} AMBER, ${counts.GREEN} GREEN — ${baseName}.md\\n`,\n \"utf8\"\n );\n}\n\nasync function cmdCombined(opts: { json?: boolean; record?: string }): Promise<void> {\n const data = await gatherCombined();\n\n if (opts.json) {\n printJson(data);\n } else {\n console.log(header(\"pai audit tokens — combined report\"));\n console.log(dim(`token encoding: ${data.encoding}`));\n const rows = data.findings.map((f) => [severityColor(f.severity, f.finding), severityColor(f.severity, f.severity), f.evidence]);\n console.log(renderTable([\"FINDING\", \"SEVERITY\", \"EVIDENCE\"], rows));\n }\n\n if (opts.record) {\n recordCombinedReport(opts.record, data);\n }\n}\n\n// ---------------------------------------------------------------------------\n// wiring\n// ---------------------------------------------------------------------------\n\n/**\n * `--json` is declared once, on `tokensCmd` itself, and read back from there\n * in every subcommand action below — not redeclared per subcommand. Commander\n * resolves an option to whichever command in the chain first declares it, so\n * a child command that *also* declares `--json` never sees the flag: it gets\n * consumed by the parent before the child's own `opts()` is populated, and\n * `--json` on `pai audit tokens files` silently produced the table output.\n */\nexport function registerAuditCommands(auditCmd: Command): void {\n const tokensCmd = auditCmd\n .command(\"tokens\")\n .description(\n \"Token-waste audit: memory files, hooks, session usage, spawn overhead, daemon, env, schedule, skill catalogue, subagents, MCP\"\n )\n .option(\"--json\", \"Print JSON instead of a table\")\n .option(\"--record <dir>\", \"Write a dated, numbered markdown+JSON snapshot of the combined report to <dir>\")\n .action(async () => {\n await cmdCombined(jsonOpt() as { json?: boolean; record?: string });\n });\n\n const jsonOpt = (): { json?: boolean } => tokensCmd.opts() as { json?: boolean };\n\n tokensCmd\n .command(\"files\")\n .description(\"Token count per memory file (CLAUDE.md chain, CORE skill, whisper rules, auto-memory)\")\n .argument(\"[paths...]\", \"Files to count (default: the standard memory set)\")\n .action(async (paths: string[]) => {\n await cmdFiles(paths, jsonOpt());\n });\n\n tokensCmd\n .command(\"hooks\")\n .description(\"Token cost of every SessionStart / UserPromptSubmit hook\")\n .action(() => cmdHooks(jsonOpt()));\n\n tokensCmd\n .command(\"session\")\n .description(\"Cache/input/output token split for a session transcript (default: newest)\")\n .argument(\"[path]\", \"Session JSONL path (default: newest under ~/.claude/projects)\")\n .option(\"--ctx-threshold <n>\", \"Override the derived compaction trigger (default: derived from the session's own window and measured/configured trigger — see 'window'/'trigger' in the report header)\")\n .option(\"--history [n]\", \"List first-turn context of the newest n sessions in this project's transcript directory (default 20)\")\n .option(\"--turn <n>\", \"Attribute API call n's context growth to the transcript lines injected since the previous call\")\n .action(async (path: string | undefined, opts: { ctxThreshold?: string; history?: string | boolean; turn?: string }) => {\n await cmdSession(path, { ...jsonOpt(), ...opts });\n });\n\n tokensCmd\n .command(\"spawn\")\n .description(\"Spawn-overhead comparison: Agent-tool subagents vs. pai workers\")\n .option(\"-n, --n <count>\", \"How many of the newest logs per group to read (default 10)\")\n .option(\"--detail\", \"Print one row per log (first-turn ctx, prompt tokens, overhead, model, path)\")\n .action(async (opts: { n?: string; detail?: boolean }) => {\n await cmdSpawn({ ...jsonOpt(), ...opts });\n });\n\n tokensCmd\n .command(\"daemon\")\n .description(\"LLM spawns, KG-extraction parse failures, work-queue counts from the daemon log\")\n .action(() => cmdDaemon(jsonOpt()));\n\n tokensCmd\n .command(\"env\")\n .description(\"ANTHROPIC_BASE_URL / model-override env on every live claude process\")\n .action(() => cmdEnv(jsonOpt()));\n\n tokensCmd\n .command(\"schedule\")\n .description(\"Launchd agents / crontab entries that outpace the measured prompt-cache TTL\")\n .action(async () => {\n await cmdSchedule(jsonOpt());\n });\n\n tokensCmd\n .command(\"skills\")\n .description(\"Token cost of the SKILL.md / command / plugin catalogue, top entries, duplicate names\")\n .option(\"--top <n>\", \"How many top-by-tokens entries to show (default 15)\")\n .action((opts: { top?: string }) => {\n cmdSkills({ ...jsonOpt(), ...opts });\n });\n\n tokensCmd\n .command(\"subagents\")\n .description(\"Token cost of Claude Code subagent definitions (~/.claude/agents, <cwd>/.claude/agents) and their model pinning\")\n .action(() => cmdSubagents(jsonOpt()));\n\n tokensCmd\n .command(\"mcp\")\n .description(\"MCP servers: configured vs. pinned vs. loaded-live vs. used in the last 30 days\")\n .option(\"--connect\", \"Actually connect to every stdio MCP server to count exposed tools (side-effecting; never on by default)\")\n .action(async (opts: { connect?: boolean }) => {\n await cmdMcp({ ...jsonOpt(), ...opts });\n });\n\n tokensCmd\n .command(\"ladder\")\n .description(\"LIVE: first-turn context at increasing headless-worker configuration (spawns real claude -p calls)\")\n .option(\"--live\", \"Actually spawn claude -p (billed API traffic); refuses without this flag\")\n .option(\"--model <model>\", \"Model to probe with (default haiku)\")\n .option(\"--mcp-config <path>\", \"MCP config for the L2 rung (default: newest *.mcp.json in the workers log dir)\")\n .action(async (opts: { live?: boolean; model?: string; mcpConfig?: string }) => {\n await cmdLadder({ ...jsonOpt(), ...opts });\n });\n}\n","/**\n * pai skill <sub-command>\n *\n * telemetry — Show skill-invocation telemetry (self-educating skill system).\n *\n * The `skill` namespace is reserved for the wider self-educating loop\n * (search / install / audit / trial) — see Notes/swarm/skills-self-educating.md.\n */\n\nimport type { Command } from \"commander\";\nimport { createConnection } from \"net\";\nimport { warn, err, dim, bold, header } from \"../utils.js\";\nimport chalk from \"chalk\";\nimport { paiSocketPath } from \"../../runtime-paths.js\";\n\n// ---------------------------------------------------------------------------\n// IPC helper — communicates with PAI daemon via Unix socket\n// ---------------------------------------------------------------------------\n\nfunction ipcCall(method: string, params: Record<string, unknown>): Promise<unknown> {\n return new Promise((resolve, reject) => {\n let settled = false;\n const settle = (fn: () => void) => { if (!settled) { settled = true; fn(); } };\n\n const client = createConnection(paiSocketPath(), () => {\n client.write(JSON.stringify({ id: 1, method, params }) + \"\\n\");\n });\n let data = \"\";\n client.on(\"data\", (chunk) => {\n data += chunk.toString();\n try {\n const resp = JSON.parse(data) as { ok: boolean; result: unknown; error?: string };\n client.end();\n settle(() => resp.ok ? resolve(resp.result) : reject(new Error(resp.error ?? \"IPC call failed\")));\n } catch {\n // Incomplete — wait for more data or end\n }\n });\n client.on(\"end\", () => {\n settle(() => {\n try {\n const resp = JSON.parse(data) as { ok: boolean; result: unknown; error?: string };\n resp.ok ? resolve(resp.result) : reject(new Error(resp.error ?? \"IPC call failed\"));\n } catch (e) { reject(e); }\n });\n });\n client.on(\"error\", (e) => settle(() => reject(e)));\n setTimeout(() => { client.destroy(); settle(() => reject(new Error(\"IPC timeout\"))); }, 10000);\n });\n}\n\n// ---------------------------------------------------------------------------\n// Helpers\n// ---------------------------------------------------------------------------\n\nfunction fmtTs(ts: string | null | undefined): string {\n if (!ts) return dim(\"—\");\n try {\n const d = new Date(ts);\n return `${d.toISOString().slice(0, 10)} ${d.toISOString().slice(11, 16)}`;\n } catch {\n return dim(\"—\");\n }\n}\n\nfunction statusColor(status: string): string {\n switch (status) {\n case \"active\": return chalk.green(status);\n case \"trial\": return chalk.yellow(status);\n case \"archived\": return chalk.dim(status);\n default: return chalk.white(status);\n }\n}\n\nfunction trunc(s: string, maxLen: number): string {\n if (!s) return \"\";\n return s.length <= maxLen ? s : s.slice(0, maxLen - 1) + \"…\";\n}\n\ninterface SkillTelemetry {\n id: number;\n scope: string;\n skill_name: string;\n source: string;\n status: string;\n trigger_count: number;\n accept_count: number;\n first_triggered: string;\n last_triggered: string;\n context_projects: string[];\n hash: string | null;\n audit_status: string | null;\n last_audited: string | null;\n}\n\n// ---------------------------------------------------------------------------\n// Command: pai skill telemetry\n// ---------------------------------------------------------------------------\n\nasync function cmdTelemetry(opts: {\n scope?: string;\n status?: string;\n limit?: string;\n json?: boolean;\n}): Promise<void> {\n const params: Record<string, unknown> = {\n scope: opts.scope ?? \"default\",\n limit: parseInt(opts.limit ?? \"50\", 10),\n };\n if (opts.status) params.status = opts.status;\n\n let rows: SkillTelemetry[];\n try {\n rows = (await ipcCall(\"skill_telemetry_query\", params)) as SkillTelemetry[];\n } catch (e) {\n console.error(err(` Failed to reach PAI daemon: ${e}`));\n console.error(dim(\" Is the daemon running? Try: pai daemon status\"));\n process.exitCode = 1;\n return;\n }\n\n if (opts.json) {\n console.log(JSON.stringify(rows ?? [], null, 2));\n return;\n }\n\n console.log();\n console.log(header(\" Skill Telemetry\"));\n console.log(dim(` scope: ${params.scope}${opts.status ? ` | status: ${opts.status}` : \"\"}`));\n console.log();\n\n if (!rows || rows.length === 0) {\n console.log(warn(\" No skill telemetry yet.\"));\n console.log(dim(\" Invoke a skill (e.g. /name) and it will be recorded.\"));\n console.log();\n return;\n }\n\n const NAME_W = 24;\n const SRC_W = 10;\n const STAT_W = 9;\n const TRIG_W = 8;\n const SEEN_W = 16;\n\n console.log(\n \" \" +\n bold(\"skill\".padEnd(NAME_W)) + \" \" +\n bold(\"source\".padEnd(SRC_W)) + \" \" +\n bold(\"status\".padEnd(STAT_W)) + \" \" +\n bold(\"triggers\".padStart(TRIG_W)) + \" \" +\n bold(\"last used\".padEnd(SEEN_W)) + \" \" +\n bold(\"projects\")\n );\n console.log(dim(\" \" + \"-\".repeat(NAME_W + SRC_W + STAT_W + TRIG_W + SEEN_W + 18)));\n\n for (const r of rows) {\n const name = trunc(r.skill_name ?? \"\", NAME_W).padEnd(NAME_W);\n const src = trunc(r.source ?? \"\", SRC_W).padEnd(SRC_W);\n const stat = statusColor(r.status ?? \"\");\n const statPad = stat.padEnd(STAT_W + (stat.length - (r.status ?? \"\").length));\n const trig = String(r.trigger_count ?? 0).padStart(TRIG_W);\n const seen = fmtTs(r.last_triggered).padEnd(SEEN_W);\n const projs = Array.isArray(r.context_projects) ? r.context_projects.join(\", \") : \"\";\n console.log(` ${chalk.white(name)} ${dim(src)} ${statPad} ${chalk.cyan(trig)} ${dim(seen)} ${dim(trunc(projs, 30))}`);\n }\n\n console.log();\n console.log(dim(` ${rows.length} skill(s)`));\n console.log();\n}\n\n// ---------------------------------------------------------------------------\n// Commander registration\n// ---------------------------------------------------------------------------\n\nexport function registerSkillCommands(parent: Command): void {\n parent\n .command(\"telemetry\")\n .description(\"Show skill-invocation telemetry (triggers, last used, projects)\")\n .option(\"--scope <scope>\", \"Governance scope\", \"default\")\n .option(\"--status <status>\", \"Filter by status: trial | active | archived\")\n .option(\"--limit <n>\", \"Maximum rows\", \"50\")\n .option(\"--json\", \"Output raw JSON\")\n .action(async (opts: { scope?: string; status?: string; limit?: string; json?: boolean }) => {\n try {\n await cmdTelemetry(opts);\n } catch (e) {\n console.error(err(` Error: ${e}`));\n process.exitCode = 1;\n return;\n }\n });\n}\n","/**\n * pai update — Update PAI from the GitHub repository without losing customizations.\n *\n * Steps:\n * 1. Verify we're in a git repo with a remote (origin)\n * 2. Stash any local changes\n * 3. Pull latest from origin/main\n * 4. Pop the stash if there was one (handle conflicts gracefully)\n * 5. Rebuild: bun install && bun run build\n * 6. Restart the daemon (SIGHUP or pai daemon restart)\n * 7. Check if the CLAUDE.md template has changed and offer to refresh\n * 8. Run pai registry scan to update markers\n * 9. Report what changed\n */\n\nimport type { Command } from \"commander\";\nimport {\n existsSync,\n readFileSync,\n writeFileSync,\n} from \"node:fs\";\nimport { join } from \"node:path\";\nimport { homedir } from \"node:os\";\nimport { packageRoot } from \"../../module-paths.js\";\nimport { execSync, spawnSync } from \"node:child_process\";\nimport chalk from \"chalk\";\nimport { ok, warn, err, dim, bold } from \"../utils.js\";\nimport { daemonPidPath } from \"../../runtime-paths.js\";\n\n// ---------------------------------------------------------------------------\n// Helpers\n// ---------------------------------------------------------------------------\n\nfunction line(text = \"\") {\n console.log(text);\n}\n\nfunction step(msg: string) {\n console.log(chalk.bold.cyan(`\\n ${msg}`));\n}\n\nfunction info(msg: string) {\n console.log(dim(` ${msg}`));\n}\n\nfunction success(msg: string) {\n console.log(ok(` ${msg}`));\n}\n\nfunction warning(msg: string) {\n console.log(warn(` ${msg}`));\n}\n\nfunction error(msg: string) {\n console.log(err(` ${msg}`));\n}\n\n/**\n * Run a shell command, streaming output to stdout.\n * Returns true on success, false on failure.\n */\nfunction run(cmd: string, cwd?: string): boolean {\n try {\n execSync(cmd, {\n stdio: \"inherit\",\n cwd: cwd ?? process.cwd(),\n env: process.env,\n });\n return true;\n } catch {\n return false;\n }\n}\n\n/**\n * Run a shell command silently, returning stdout or null on failure.\n */\nfunction capture(cmd: string, cwd?: string): string | null {\n try {\n return execSync(cmd, {\n stdio: \"pipe\",\n cwd: cwd ?? process.cwd(),\n env: process.env,\n }).toString().trim();\n } catch {\n return null;\n }\n}\n\n/**\n * Get the PAI source directory: walk up from dist/cli/ or src/cli/ to the\n * package root where package.json lives.\n */\nfunction getPaiSrcDir(): string {\n return packageRoot(import.meta.url);\n}\n\n/**\n * Locate the templates directory relative to the installed package.\n */\nfunction getTemplatesDir(): string {\n const candidates = [\n join(getPaiSrcDir(), \"templates\"),\n join(homedir(), \"dev\", \"ai\", \"PAI\", \"templates\"),\n join(\"/\", \"usr\", \"local\", \"lib\", \"node_modules\", \"@mnott\", \"pai\", \"templates\"),\n ];\n for (const c of candidates) {\n if (existsSync(join(c, \"claude-md.template.md\"))) return c;\n }\n return join(getPaiSrcDir(), \"templates\");\n}\n\n// ---------------------------------------------------------------------------\n// Update steps\n// ---------------------------------------------------------------------------\n\n/**\n * Step 1: Verify git repo + remote.\n * Returns the PAI source directory (repo root) or null if not a git repo.\n */\nfunction stepVerifyRepo(): string | null {\n step(\"Checking repository...\");\n\n const paiDir = getPaiSrcDir();\n\n // Check git repo\n const gitDir = capture(\"git rev-parse --show-toplevel\", paiDir);\n if (!gitDir) {\n error(\"Not a git repository. Cannot update automatically.\");\n info(\"Install PAI via npm to get automatic updates: npm install -g @tekmidian/pai\");\n return null;\n }\n\n // Check remote\n const remote = capture(\"git remote get-url origin\", gitDir);\n if (!remote) {\n error(\"No 'origin' remote configured. Cannot pull updates.\");\n info(\"Add a remote: git remote add origin https://github.com/mnott/PAI.git\");\n return null;\n }\n\n info(`Repository: ${gitDir}`);\n info(`Remote: ${remote}`);\n return gitDir;\n}\n\n/**\n * Step 2: Stash local changes.\n * Returns true if something was stashed (so we need to pop later).\n */\nfunction stepStash(repoDir: string): boolean {\n step(\"Stashing local changes...\");\n\n // Check if there are any changes to stash\n const status = capture(\"git status --porcelain\", repoDir);\n if (!status) {\n info(\"Working tree is clean — nothing to stash.\");\n return false;\n }\n\n info(\"Uncommitted changes found:\");\n const statusLines = status.split(\"\\n\").slice(0, 5);\n for (const l of statusLines) {\n info(` ${l}`);\n }\n if (status.split(\"\\n\").length > 5) {\n info(` ... and ${status.split(\"\\n\").length - 5} more`);\n }\n\n const result = capture(\"git stash push -m 'pai update: auto-stash'\", repoDir);\n if (result === null) {\n warning(\"Could not stash changes. Proceeding anyway — merge conflicts may occur.\");\n return false;\n }\n\n if (result.includes(\"No local changes to save\")) {\n info(\"Nothing to stash.\");\n return false;\n }\n\n success(\"Changes stashed.\");\n return true;\n}\n\n/**\n * Step 3: Pull latest from origin/main.\n * Returns the git log summary of new commits or null if already up to date.\n */\nfunction stepPull(repoDir: string): string | null {\n step(\"Pulling latest from origin/main...\");\n\n // Get current HEAD before pull\n const headBefore = capture(\"git rev-parse HEAD\", repoDir);\n\n const pulled = run(\"git pull origin main\", repoDir);\n if (!pulled) {\n warning(\"git pull failed. There may be merge conflicts with your stash.\");\n return null;\n }\n\n // Get HEAD after pull\n const headAfter = capture(\"git rev-parse HEAD\", repoDir);\n\n if (headBefore === headAfter) {\n success(\"Already up to date — no new commits.\");\n return null;\n }\n\n // Show what changed\n const log = capture(\n `git log --oneline ${headBefore ?? \"\"}..${headAfter ?? \"HEAD\"}`,\n repoDir,\n );\n if (log) {\n success(\"New commits pulled:\");\n for (const l of log.split(\"\\n\")) {\n info(` ${l}`);\n }\n }\n\n return log;\n}\n\n/**\n * Step 4: Pop stash (if we stashed anything).\n */\nfunction stepPopStash(repoDir: string): void {\n step(\"Restoring local changes (git stash pop)...\");\n\n const result = spawnSync(\"git\", [\"stash\", \"pop\"], {\n cwd: repoDir,\n stdio: \"pipe\",\n encoding: \"utf8\",\n });\n\n if (result.status === 0) {\n success(\"Local changes restored.\");\n } else {\n const stderr = result.stderr ?? \"\";\n if (stderr.includes(\"CONFLICT\")) {\n warning(\"Stash pop caused merge conflicts. Resolve them manually:\");\n info(\" git status\");\n info(\" # Edit conflicting files\");\n info(\" git add <files>\");\n info(\" git stash drop\");\n } else {\n warning(`Stash pop encountered an issue: ${stderr.trim() || \"unknown error\"}`);\n info(\" Run: git stash pop\");\n info(\" Or: git stash list (to see stashed changes)\");\n }\n }\n}\n\n/**\n * Step 5: Rebuild.\n */\nfunction stepBuild(repoDir: string): boolean {\n step(\"Rebuilding PAI (bun install && bun run build)...\");\n\n info(\"Installing dependencies...\");\n const installed = run(\"bun install\", repoDir);\n if (!installed) {\n error(\"bun install failed.\");\n return false;\n }\n\n info(\"Building...\");\n const built = run(\"bun run build\", repoDir);\n if (!built) {\n error(\"bun run build failed.\");\n return false;\n }\n\n success(\"Build complete.\");\n return true;\n}\n\n/**\n * Step 6: Restart the daemon.\n * Tries SIGHUP first (graceful reload); falls back to pai daemon restart.\n */\nfunction stepRestartDaemon(repoDir: string): void {\n step(\"Restarting PAI daemon...\");\n\n // Try to find the daemon PID from the socket/pid file\n const pidFile = daemonPidPath();\n if (existsSync(pidFile)) {\n try {\n const pid = parseInt(readFileSync(pidFile, \"utf8\").trim(), 10);\n if (!isNaN(pid) && pid > 0) {\n const killed = capture(`kill -HUP ${pid}`);\n if (killed !== null) {\n success(`Sent SIGHUP to daemon (PID ${pid}).`);\n return;\n }\n }\n } catch {\n // fall through to pai daemon restart\n }\n }\n\n // Fall back to pai daemon restart subcommand\n const result = spawnSync(\"pai\", [\"daemon\", \"restart\"], {\n cwd: repoDir,\n stdio: \"pipe\",\n encoding: \"utf8\",\n timeout: 10_000,\n });\n\n if (result.status === 0) {\n success(\"Daemon restarted.\");\n } else {\n warning(\"Could not restart daemon automatically.\");\n info(\" Restart manually: pai daemon restart\");\n info(\" Or: pai daemon serve\");\n }\n}\n\n/**\n * Step 7: Check if CLAUDE.md template changed and offer to refresh.\n */\nfunction stepRefreshClaudeMd(repoDir: string, newCommitsLog: string | null): void {\n step(\"Checking CLAUDE.md template...\");\n\n const templatesDir = getTemplatesDir();\n const templatePath = join(templatesDir, \"claude-md.template.md\");\n const claudeMd = join(homedir(), \".claude\", \"CLAUDE.md\");\n\n if (!existsSync(templatePath)) {\n info(\"Template not found — skipping CLAUDE.md check.\");\n return;\n }\n\n if (!existsSync(claudeMd)) {\n info(\"No ~/.claude/CLAUDE.md found — skipping.\");\n return;\n }\n\n const userContent = readFileSync(claudeMd, \"utf8\");\n const isPaiGenerated = userContent.includes(\"Generated by PAI Setup\");\n\n if (!isPaiGenerated) {\n warning(\"~/.claude/CLAUDE.md appears to be custom (not PAI-generated).\");\n info(\" Skipping auto-update. Review the new template manually:\");\n info(` ${templatePath}`);\n return;\n }\n\n // Check if the template was actually modified in the new commits\n let templateChanged = false;\n if (newCommitsLog) {\n // Check git diff on the template file for newly pulled commits\n const diff = capture(\n \"git diff HEAD~1..HEAD -- templates/claude-md.template.md\",\n repoDir,\n );\n templateChanged = !!(diff && diff.trim().length > 0);\n }\n\n if (!templateChanged) {\n info(\"CLAUDE.md template unchanged in this update.\");\n return;\n }\n\n info(\"CLAUDE.md template was updated in this release.\");\n info(\"Refreshing ~/.claude/CLAUDE.md (PAI-generated — safe to overwrite)...\");\n\n let template = readFileSync(templatePath, \"utf8\");\n // Substitute ${HOME} with actual home directory\n template = template.replace(/\\$\\{HOME\\}/g, homedir());\n writeFileSync(claudeMd, template, \"utf8\");\n\n success(\"~/.claude/CLAUDE.md refreshed from updated template.\");\n}\n\n/**\n * Step 8: Run pai registry scan.\n */\nfunction stepRegistryScan(repoDir: string): void {\n step(\"Running pai registry scan...\");\n\n const result = spawnSync(\"pai\", [\"registry\", \"scan\"], {\n cwd: repoDir,\n stdio: \"inherit\",\n timeout: 60_000,\n });\n\n if (result.status === 0) {\n success(\"Registry scan complete.\");\n } else {\n warning(\"Registry scan encountered issues.\");\n info(\" Run manually: pai registry scan\");\n }\n}\n\n// ---------------------------------------------------------------------------\n// Main update action\n// ---------------------------------------------------------------------------\n\nasync function runUpdate(): Promise<void> {\n line();\n console.log(chalk.bold.cyan(\" ╔═══════════════════════════════════╗\"));\n console.log(chalk.bold.cyan(\" ║ PAI Knowledge OS — Update ║\"));\n console.log(chalk.bold.cyan(\" ╚═══════════════════════════════════╝\"));\n line();\n\n // Step 1: Verify repo\n const repoDir = stepVerifyRepo();\n if (!repoDir) {\n line();\n process.exitCode = 1;\n return;\n }\n\n // Step 2: Stash local changes\n const didStash = stepStash(repoDir);\n\n // Step 3: Pull\n const newCommitsLog = stepPull(repoDir);\n\n // Step 4: Pop stash (only if we stashed something)\n if (didStash) {\n stepPopStash(repoDir);\n }\n\n // If no new commits and no stash needed, we can short-circuit after\n // confirming things are current. But we still offer rebuild in case the\n // user's build is stale.\n\n // Step 5: Build\n const buildOk = stepBuild(repoDir);\n if (!buildOk) {\n line();\n error(\"Update failed at build step. Check errors above.\");\n line();\n process.exitCode = 1;\n return;\n }\n\n // Step 6: Restart daemon\n stepRestartDaemon(repoDir);\n\n // Step 7: CLAUDE.md refresh\n stepRefreshClaudeMd(repoDir, newCommitsLog);\n\n // Step 8: Registry scan\n stepRegistryScan(repoDir);\n\n // Summary\n line();\n console.log(chalk.bold.cyan(\" ─────────────────────────────────────\"));\n if (newCommitsLog) {\n success(\"PAI updated successfully!\");\n } else {\n success(\"PAI is up to date and rebuilt successfully!\");\n }\n console.log(dim(\" Version: \") + chalk.cyan(capture(\"pai --version\", repoDir) ?? \"unknown\"));\n line();\n}\n\n// ---------------------------------------------------------------------------\n// Commander registration\n// ---------------------------------------------------------------------------\n\nexport function registerUpdateCommand(program: Command): void {\n program\n .command(\"update\")\n .description(\n \"Update PAI from GitHub (git pull + rebuild + daemon restart). Preserves local customizations.\",\n )\n .action(async () => {\n await runUpdate();\n });\n}\n","/**\n * pai notify <sub-command>\n *\n * status — Show current notification mode and active channels\n * get — Alias for status\n * set — Set notification mode or channel/routing config\n * test — Send a test notification through configured channels\n * send — Send a notification (event + message)\n *\n * All sub-commands communicate with the running daemon via IPC.\n */\n\nimport type { Command } from \"commander\";\nimport { ok, warn, err, dim, bold, header } from \"../utils.js\";\nimport { loadConfig } from \"../../daemon/config.js\";\nimport { PaiClient } from \"../../daemon/ipc-client.js\";\nimport type {\n NotificationConfig,\n NotificationMode,\n NotificationEvent,\n} from \"../../notifications/types.js\";\n\n// ---------------------------------------------------------------------------\n// Helpers\n// ---------------------------------------------------------------------------\n\nfunction makeClient(): PaiClient {\n const config = loadConfig();\n return new PaiClient(config.socketPath);\n}\n\nfunction modeColor(mode: NotificationMode): string {\n switch (mode) {\n case \"off\":\n return warn(mode);\n case \"voice\":\n return bold(ok(mode));\n case \"auto\":\n return ok(mode);\n default:\n return ok(mode);\n }\n}\n\nfunction printConfig(config: NotificationConfig, activeChannels: string[]): void {\n console.log();\n console.log(header(\" PAI Notification Config\"));\n console.log();\n console.log(` ${bold(\"Mode:\")} ${modeColor(config.mode)}`);\n console.log();\n console.log(` ${bold(\"Channels:\")}`);\n for (const [ch, cfg] of Object.entries(config.channels)) {\n const c = cfg as { enabled: boolean; url?: string; voiceName?: string };\n const status = c.enabled ? ok(\"enabled\") : dim(\"disabled\");\n let extra = \"\";\n if (ch === \"ntfy\" && c.url) extra = dim(` ${c.url}`);\n if (ch === \"voice\" && c.voiceName) extra = dim(` voice: ${c.voiceName}`);\n console.log(` ${bold(ch.padEnd(12))}${status}${extra}`);\n }\n console.log();\n console.log(` ${bold(\"Active:\")} ${activeChannels.length > 0 ? activeChannels.join(\", \") : dim(\"(none)\")}`);\n console.log();\n console.log(` ${bold(\"Routing:\")} ${dim(\"(auto mode only)\")}`);\n for (const [event, channels] of Object.entries(config.routing)) {\n const ch = (channels as string[]).join(\", \") || dim(\"(none)\");\n console.log(` ${event.padEnd(12)}→ ${ch}`);\n }\n console.log();\n}\n\n// ---------------------------------------------------------------------------\n// Command implementations\n// ---------------------------------------------------------------------------\n\nasync function cmdStatus(): Promise<void> {\n const client = makeClient();\n try {\n const { config, activeChannels } = await client.getNotificationConfig();\n printConfig(config, activeChannels);\n } catch (e) {\n const msg = e instanceof Error ? e.message : String(e);\n console.log();\n console.log(warn(\" Cannot reach PAI daemon.\"));\n console.log(dim(` ${msg}`));\n console.log(dim(\" Start it with: pai daemon serve\"));\n console.log();\n process.exitCode = 1;\n return;\n }\n}\n\nasync function cmdSet(opts: {\n mode?: string;\n ntfyUrl?: string;\n ntfyPriority?: string;\n whatsappRecipient?: string;\n enableChannel?: string[];\n disableChannel?: string[];\n}): Promise<void> {\n const client = makeClient();\n\n // Build the patch object\n const patch: {\n mode?: NotificationMode;\n channels?: Record<string, unknown>;\n } = {};\n\n if (opts.mode) {\n const validModes: NotificationMode[] = [\n \"auto\", \"voice\", \"whatsapp\", \"ntfy\", \"macos\", \"cli\", \"off\",\n ];\n if (!validModes.includes(opts.mode as NotificationMode)) {\n console.error(err(`Invalid mode: ${opts.mode}. Valid: ${validModes.join(\", \")}`));\n process.exitCode = 1;\n return;\n }\n patch.mode = opts.mode as NotificationMode;\n }\n\n // Channel enable/disable\n const channels: Record<string, unknown> = {};\n\n if (opts.enableChannel) {\n for (const ch of opts.enableChannel) {\n channels[ch] = { enabled: true };\n }\n }\n\n if (opts.disableChannel) {\n for (const ch of opts.disableChannel) {\n channels[ch] = { enabled: false };\n }\n }\n\n if (opts.ntfyUrl) {\n channels[\"ntfy\"] = {\n ...(channels[\"ntfy\"] as object ?? {}),\n url: opts.ntfyUrl,\n };\n }\n\n if (opts.ntfyPriority) {\n channels[\"ntfy\"] = {\n ...(channels[\"ntfy\"] as object ?? {}),\n priority: opts.ntfyPriority,\n };\n }\n\n if (opts.whatsappRecipient) {\n channels[\"whatsapp\"] = {\n ...(channels[\"whatsapp\"] as object ?? {}),\n recipient: opts.whatsappRecipient,\n };\n }\n\n if (Object.keys(channels).length > 0) {\n patch.channels = channels;\n }\n\n if (!patch.mode && !patch.channels) {\n console.error(err(\"No changes specified. Use --mode, --enable, --disable, etc.\"));\n console.log(dim(\" Example: pai notify set --mode voice\"));\n console.log(dim(\" Example: pai notify set --mode auto --enable macos --disable ntfy\"));\n process.exitCode = 1;\n return;\n }\n\n try {\n const { config } = await client.setNotificationConfig(\n patch as Parameters<typeof client.setNotificationConfig>[0]\n );\n console.log();\n console.log(ok(\" Notification config updated.\"));\n console.log(` ${bold(\"Mode:\")} ${modeColor(config.mode)}`);\n console.log();\n } catch (e) {\n const msg = e instanceof Error ? e.message : String(e);\n console.error(err(` Failed: ${msg}`));\n process.exitCode = 1;\n return;\n }\n}\n\nasync function cmdTest(opts: { event?: string }): Promise<void> {\n const client = makeClient();\n const event = (opts.event ?? \"info\") as NotificationEvent;\n\n console.log();\n console.log(dim(` Sending test ${event} notification...`));\n\n try {\n const result = await client.sendNotification({\n event,\n message: `PAI test notification (${event})`,\n title: \"PAI Test\",\n });\n\n if (result.channelsSucceeded.length === 0 && result.channelsAttempted.length > 0) {\n console.log(warn(\" All channels failed.\"));\n } else if (result.channelsAttempted.length === 0) {\n console.log(warn(\" No channels active for this event. Check mode and channel config.\"));\n } else {\n console.log(ok(` Sent to: ${result.channelsSucceeded.join(\", \")}`));\n }\n\n if (result.channelsFailed.length > 0) {\n console.log(warn(` Failed: ${result.channelsFailed.join(\", \")}`));\n }\n\n console.log(dim(` Mode: ${result.mode}`));\n console.log();\n } catch (e) {\n const msg = e instanceof Error ? e.message : String(e);\n console.error(err(` Failed: ${msg}`));\n process.exitCode = 1;\n return;\n }\n}\n\nasync function cmdSend(\n event: string,\n message: string,\n opts: { title?: string }\n): Promise<void> {\n const client = makeClient();\n const validEvents: NotificationEvent[] = [\n \"error\", \"progress\", \"completion\", \"info\", \"debug\",\n ];\n\n if (!validEvents.includes(event as NotificationEvent)) {\n console.error(\n err(`Invalid event: ${event}. Valid: ${validEvents.join(\", \")}`)\n );\n process.exitCode = 1;\n return;\n }\n\n try {\n const result = await client.sendNotification({\n event: event as NotificationEvent,\n message,\n title: opts.title,\n });\n\n if (result.channelsSucceeded.length > 0) {\n console.log(ok(` Sent to: ${result.channelsSucceeded.join(\", \")}`));\n } else {\n console.log(warn(\" No channels received the notification.\"));\n }\n\n if (result.channelsFailed.length > 0) {\n console.log(warn(` Failed: ${result.channelsFailed.join(\", \")}`));\n }\n } catch (e) {\n const msg = e instanceof Error ? e.message : String(e);\n console.error(err(` Failed: ${msg}`));\n process.exitCode = 1;\n return;\n }\n}\n\n// ---------------------------------------------------------------------------\n// Commander registration\n// ---------------------------------------------------------------------------\n\nexport function registerNotifyCommands(notifyCmd: Command): void {\n // pai notify status (default / alias for get)\n notifyCmd\n .command(\"status\")\n .description(\"Show current notification mode and active channels\")\n .action(async () => {\n await cmdStatus();\n });\n\n // pai notify get (alias for status)\n notifyCmd\n .command(\"get\")\n .description(\"Show current notification config (alias for status)\")\n .action(async () => {\n await cmdStatus();\n });\n\n // pai notify set\n notifyCmd\n .command(\"set\")\n .description(\"Update notification mode or channel configuration\")\n .option(\n \"--mode <mode>\",\n \"Notification mode: auto | voice | whatsapp | ntfy | macos | cli | off\"\n )\n .option(\"--enable <channel...>\", \"Enable one or more channels\")\n .option(\"--disable <channel...>\", \"Disable one or more channels\")\n .option(\"--ntfy-url <url>\", \"Set the ntfy.sh topic URL\")\n .option(\n \"--ntfy-priority <level>\",\n \"Set ntfy priority: min | low | default | high | urgent\"\n )\n .option(\"--whatsapp-recipient <contact>\", \"Set WhatsApp recipient\")\n .action(\n async (opts: {\n mode?: string;\n enable?: string[];\n disable?: string[];\n ntfyUrl?: string;\n ntfyPriority?: string;\n whatsappRecipient?: string;\n }) => {\n await cmdSet({\n mode: opts.mode,\n enableChannel: opts.enable,\n disableChannel: opts.disable,\n ntfyUrl: opts.ntfyUrl,\n ntfyPriority: opts.ntfyPriority,\n whatsappRecipient: opts.whatsappRecipient,\n });\n }\n );\n\n // pai notify test\n notifyCmd\n .command(\"test\")\n .description(\"Send a test notification through configured channels\")\n .option(\n \"--event <event>\",\n \"Event type to test: error | progress | completion | info | debug\",\n \"info\"\n )\n .action(async (opts: { event?: string }) => {\n await cmdTest(opts);\n });\n\n // pai notify send <event> <message>\n notifyCmd\n .command(\"send <event> <message>\")\n .description(\"Send a notification with an explicit event type and message\")\n .option(\"--title <title>\", \"Optional notification title\")\n .action(async (event: string, message: string, opts: { title?: string }) => {\n await cmdSend(event, message, opts);\n });\n}\n","/**\n * aibroker.ts — AIBroker transport for the task bus\n *\n * Shells out to the `aibroker` CLI. PAI's CLI cannot call MCP tools, so the\n * MCP-only dispatch primitives are unreachable from a shell — this is the\n * bridge.\n *\n * Optional by design: when AIBroker is not installed, `detect()` returns null\n * and the bus reports ownership instead of delivering. PAI must stay useful on\n * machines that do not run iTerm2.\n *\n * See docs/task-bus.md.\n */\n\nimport { execFile } from \"node:child_process\";\nimport type { ProbeAnswer } from \"../poller.js\";\nimport type { Transport, TransportResult } from \"../dispatch.js\";\n\n/**\n * How long AIBroker may spend on one dispatch, in seconds.\n *\n * A cold spawn measured ~10s on an idle machine, but boot time is not bounded:\n * under load a session can take considerably longer to start accepting input.\n */\nconst DEFAULT_DISPATCH_TIMEOUT_SECS = 180;\n\n/**\n * Margin between AIBroker's own deadline and when we kill the process.\n *\n * These two timeouts must never disagree. If ours fired first we would kill a\n * dispatch mid-flight and report a transport failure that AIBroker cannot\n * reproduce from its own CLI — a phantom bug, in the other repo, with no trace\n * on either side. So we always pass our budget down via `--timeout` and give\n * it room to time out first and tell us why.\n */\nconst KILL_MARGIN_MS = 15_000;\n\ninterface WireResult {\n outcome?: string;\n project?: string;\n session?: string;\n reason?: string;\n}\n\nconst VALID_OUTCOMES = new Set([\n \"delivered\",\n \"queued\",\n \"spawned\",\n \"unlaunchable\",\n \"unreachable\",\n \"skipped\",\n]);\n\n/**\n * Run `aibroker dispatch`, feeding the message over stdin.\n *\n * stdin rather than argv is deliberate: task bodies carry the full procedure\n * and reasoning, so they are long, multi-line, and full of quotes and\n * backticks. argv would mangle them or hit length limits.\n */\nfunction run(bin: string, args: string[], stdin: string, killAfterMs: number): Promise<string> {\n return new Promise((resolve, reject) => {\n const child = execFile(\n bin,\n args,\n { timeout: killAfterMs, maxBuffer: 1024 * 1024 },\n (error, stdout, stderr) => {\n // A non-zero exit carrying parseable JSON is a reported outcome, not a\n // crash — let the caller interpret it rather than throwing here.\n if (error && !stdout.trim().startsWith(\"{\")) {\n reject(new Error(stderr.trim() || error.message));\n return;\n }\n resolve(stdout);\n },\n );\n child.stdin?.end(stdin);\n });\n}\n\nexport class AiBrokerTransport implements Transport {\n constructor(\n private readonly bin = \"aibroker\",\n private readonly timeoutSecs = DEFAULT_DISPATCH_TIMEOUT_SECS,\n ) {}\n\n async dispatch(\n project: string,\n message: string,\n opts: { spawnIfAbsent: boolean },\n ): Promise<TransportResult> {\n const args = [\"dispatch\", project, \"--stdin\", \"--json\", \"--timeout\", String(this.timeoutSecs)];\n if (!opts.spawnIfAbsent) args.push(\"--no-spawn\");\n\n const stdout = await run(\n this.bin,\n args,\n message,\n this.timeoutSecs * 1000 + KILL_MARGIN_MS,\n );\n\n // The CLI prints diagnostics before its JSON, so take the last JSON object\n // rather than assuming stdout is clean.\n const start = stdout.lastIndexOf(\"{\");\n if (start === -1) {\n throw new Error(`aibroker returned no JSON: ${stdout.trim().slice(0, 200)}`);\n }\n\n let wire: WireResult;\n try {\n wire = JSON.parse(stdout.slice(start)) as WireResult;\n } catch {\n throw new Error(`aibroker returned malformed JSON: ${stdout.trim().slice(0, 200)}`);\n }\n\n // Treat an unrecognised outcome as unlaunchable rather than trusting it.\n // Silently passing through an unknown string would report success for a\n // task that never arrived.\n if (!wire.outcome || !VALID_OUTCOMES.has(wire.outcome)) {\n return {\n outcome: \"unlaunchable\",\n session: wire.session ?? project,\n reason: `unexpected outcome from aibroker: ${String(wire.outcome)}`,\n };\n }\n\n return {\n outcome: wire.outcome as TransportResult[\"outcome\"],\n session: wire.session ?? wire.project ?? project,\n reason: wire.reason,\n };\n }\n}\n\n/**\n * Liveness prober backed by `aibroker ask`.\n *\n * Separate from Transport on purpose: asking must NEVER spawn. A probe that\n * creates the session it is probing for turns \"this session died\" into \"all is\n * well\", which is the failure the probe exists to catch.\n */\nexport class AiBrokerProber {\n constructor(\n private readonly bin = \"aibroker\",\n private readonly timeoutSecs = 60,\n ) {}\n\n async ask(project: string, question: string): Promise<ProbeAnswer> {\n let stdout: string;\n try {\n stdout = await run(\n this.bin,\n [\"ask\", project, \"--stdin\", \"--json\", \"--timeout\", String(this.timeoutSecs)],\n question,\n this.timeoutSecs * 1000 + KILL_MARGIN_MS,\n );\n } catch (e) {\n return { state: \"silent\", reason: e instanceof Error ? e.message : String(e) };\n }\n\n const start = stdout.lastIndexOf(\"{\");\n if (start === -1) return { state: \"silent\", reason: \"aibroker returned no JSON\" };\n\n try {\n const wire = JSON.parse(stdout.slice(start)) as {\n replied?: boolean;\n reply?: string;\n reason?: string;\n state?: string;\n };\n\n // Trust `state` when present. Fall back to `replied` for an older\n // aibroker that predates it — but never infer \"busy\" from silence, which\n // is the whole reason the fourth state exists.\n const state = wire.state ?? (wire.replied === true ? \"replied\" : \"silent\");\n if (state === \"replied\" || state === \"busy\" || state === \"silent\" || state === \"absent\") {\n return { state, reply: wire.reply, reason: wire.reason };\n }\n // An unrecognised state must not read as healthy.\n return { state: \"silent\", reason: `unexpected state from aibroker: ${String(wire.state)}` };\n } catch {\n return { state: \"silent\", reason: \"aibroker returned malformed JSON\" };\n }\n }\n}\n\n/**\n * Return a prober if the installed `aibroker` supports `ask`.\n *\n * Probes for the subcommand rather than the binary: an older AIBroker would\n * otherwise fail once per stuck task instead of the scheduler simply knowing it\n * has no liveness check and saying so.\n */\nexport async function detectProber(bin = \"aibroker\"): Promise<AiBrokerProber | null> {\n try {\n const help = await new Promise<string>((resolve, reject) => {\n execFile(bin, [\"help\"], { timeout: 10_000 }, (error, stdout, stderr) => {\n if (error && !stdout) reject(error);\n else resolve(stdout + stderr);\n });\n });\n return /\\bask\\b/.test(help) ? new AiBrokerProber(bin) : null;\n } catch {\n return null;\n }\n}\n\n/**\n * Return a transport if the `aibroker` CLI is present and supports dispatch.\n *\n * Probing for the subcommand rather than just the binary matters: AIBroker\n * shipped for a long time without `dispatch`, and an older install would\n * otherwise fail once per task instead of degrading cleanly up front.\n */\nexport async function detectAiBroker(\n bin = \"aibroker\",\n timeoutSecs = DEFAULT_DISPATCH_TIMEOUT_SECS,\n): Promise<Transport | null> {\n try {\n const help = await new Promise<string>((resolve, reject) => {\n execFile(bin, [\"help\"], { timeout: 10_000 }, (error, stdout, stderr) => {\n if (error && !stdout) reject(error);\n else resolve(stdout + stderr);\n });\n });\n return help.includes(\"dispatch\") ? new AiBrokerTransport(bin, timeoutSecs) : null;\n } catch {\n return null;\n }\n}\n","/**\n * schedule-install.ts — install the scheduler tick as a launchd agent\n *\n * Two speeds were considered and rejected in favour of one: a fixed interval.\n * A 15-minute tick is 96 runs a day, each one API call and zero tokens, and\n * daily routines do not need better than 15-minute granularity — a 09:00 sweep\n * starting at 09:12 is fine. Adaptive intervals would add a second thing that\n * can silently stop.\n *\n * StartInterval rather than StartCalendarInterval on purpose: the schedule\n * lives in Todoist, not here. This agent only decides how often to *look*.\n * That means one plist total, however many routines exist, and rescheduling a\n * routine never touches the machine.\n */\n\nimport { existsSync, mkdirSync, unlinkSync, writeFileSync } from \"node:fs\";\nimport { homedir } from \"node:os\";\nimport { dirname, join } from \"node:path\";\nimport { fileURLToPath } from \"node:url\";\nimport { spawnSync } from \"node:child_process\";\nimport { schedulerLogPath } from \"../runtime-paths.js\";\nimport { serviceManagerAllowed } from \"../service-manager.js\";\n\nexport const SCHEDULE_LABEL = \"com.pai.task-scheduler\";\nconst LAUNCH_AGENTS = join(homedir(), \"Library\", \"LaunchAgents\");\nexport const SCHEDULE_PLIST = join(LAUNCH_AGENTS, `${SCHEDULE_LABEL}.plist`);\nexport const SCHEDULE_LOG = schedulerLogPath();\n\n/** Default tick, in seconds. */\nexport const DEFAULT_INTERVAL_SECS = 900;\n\n/**\n * Absolute path to the CLI entrypoint the agent should run.\n *\n * Resolving \"index.mjs\" next to this module was wrong. The bundler emits shared\n * code into chunks at dist/ root, so import.meta.url pointed there and the plist\n * was written against dist/index.mjs — the library barrel, which parses no argv,\n * prints nothing and exits 0. The agent ticked every 15 minutes and did nothing\n * at all, which looks identical to \"nothing was due\". Anchor on the package root\n * and refuse to install rather than write a plist around a file that is not a CLI.\n */\nfunction cliPath(): string {\n let dir = dirname(fileURLToPath(import.meta.url));\n for (let i = 0; i < 6; i++) {\n for (const candidate of [join(dir, \"dist\", \"cli\", \"index.mjs\"), join(dir, \"cli\", \"index.mjs\")]) {\n if (existsSync(candidate)) return candidate;\n }\n const parent = dirname(dir);\n if (parent === dir) break;\n dir = parent;\n }\n throw new Error(\n \"cannot locate the PAI CLI entrypoint (dist/cli/index.mjs) — refusing to install a scheduler that would silently do nothing\"\n );\n}\n\nexport function generateSchedulePlist(intervalSecs: number, cli: string): string {\n return `<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/PropertyList-1.0.dtd\">\n<plist version=\"1.0\">\n<dict>\n <key>Label</key>\n <string>${SCHEDULE_LABEL}</string>\n\n <key>ProgramArguments</key>\n <array>\n <string>/usr/local/bin/node</string>\n <string>${cli}</string>\n <string>task</string>\n <string>poll</string>\n </array>\n\n <key>StartInterval</key>\n <integer>${intervalSecs}</integer>\n\n <key>RunAtLoad</key>\n <true/>\n\n <key>StandardOutPath</key>\n <string>${SCHEDULE_LOG}</string>\n\n <key>StandardErrorPath</key>\n <string>${SCHEDULE_LOG}</string>\n\n <key>EnvironmentVariables</key>\n <dict>\n <key>PATH</key>\n <string>/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin</string>\n </dict>\n</dict>\n</plist>\n`;\n}\n\nexport interface InstallResult {\n plistPath: string;\n intervalSecs: number;\n loaded: boolean;\n message: string;\n}\n\nexport function installSchedule(intervalSecs = DEFAULT_INTERVAL_SECS): InstallResult {\n if (process.platform !== \"darwin\") {\n return {\n plistPath: SCHEDULE_PLIST,\n intervalSecs,\n loaded: false,\n message: \"scheduler install supports launchd (macOS) only\",\n };\n }\n\n if (!existsSync(LAUNCH_AGENTS)) mkdirSync(LAUNCH_AGENTS, { recursive: true });\n\n const cli = cliPath();\n const plist = generateSchedulePlist(intervalSecs, cli);\n\n // Plain write: a plist is generated, not user data, so the json-store guard\n // would be the wrong tool — there is nothing here anyone could not regenerate.\n writeFileSync(SCHEDULE_PLIST, plist, \"utf8\");\n\n if (!serviceManagerAllowed(`launchctl load ${SCHEDULE_PLIST}`)) {\n return { plistPath: SCHEDULE_PLIST, intervalSecs, loaded: false, message: \"Plist written; not loaded (HOME is not the account home).\" };\n }\n spawnSync(\"launchctl\", [\"unload\", SCHEDULE_PLIST], { encoding: \"utf8\" });\n const load = spawnSync(\"launchctl\", [\"load\", SCHEDULE_PLIST], { encoding: \"utf8\" });\n\n return {\n plistPath: SCHEDULE_PLIST,\n intervalSecs,\n loaded: load.status === 0,\n message:\n load.status === 0\n ? `Scheduler installed — ticking every ${Math.round(intervalSecs / 60)} min.`\n : `Plist written but launchctl load failed: ${(load.stderr || \"\").trim()}`,\n };\n}\n\nexport function uninstallSchedule(): string {\n if (!existsSync(SCHEDULE_PLIST)) return \"Scheduler is not installed.\";\n if (serviceManagerAllowed(`launchctl unload ${SCHEDULE_PLIST}`)) {\n spawnSync(\"launchctl\", [\"unload\", SCHEDULE_PLIST], { encoding: \"utf8\" });\n }\n unlinkSync(SCHEDULE_PLIST);\n return \"Scheduler uninstalled.\";\n}\n\nexport function scheduleStatus(): { installed: boolean; running: boolean; detail: string } {\n const installed = existsSync(SCHEDULE_PLIST);\n if (!installed) return { installed: false, running: false, detail: \"Not installed.\" };\n\n const list = spawnSync(\"launchctl\", [\"list\", SCHEDULE_LABEL], { encoding: \"utf8\" });\n const running = list.status === 0;\n return {\n installed: true,\n running,\n detail: running\n ? `Loaded. Log: ${SCHEDULE_LOG}`\n : \"Plist present but not loaded — run `pai task schedule install` again.\",\n };\n}\n","/**\n * archive.ts — keep the conversation that happened on a task.\n *\n * A task's comment thread is where the actual thinking ends up: a question,\n * the answer, the correction, the reason a decision went the way it did. When\n * the task is completed that thread stops being visible — it is not deleted,\n * but it leaves every list anyone looks at, and it lives in a tracker rather\n * than in the project it was about. Six months later the decision is\n * unrecoverable from the place you would look for it.\n *\n * So on completion the thread is written into the owning project's notes, where\n * the rest of that project's knowledge already is and where the memory indexer\n * will pick it up.\n *\n * One file per task, rewritten in full each time rather than appended to. The\n * whole thread is fetched on every archive, so rewriting is naturally\n * idempotent: archiving twice produces the same file, and a recurring task\n * accumulates its history in one place instead of scattering a note per\n * occurrence. Appending would need duplicate detection, and duplicate detection\n * is where this would grow a bug.\n */\n\nimport { mkdirSync, writeFileSync, existsSync, readFileSync } from \"node:fs\";\nimport { join, dirname } from \"node:path\";\nimport type { Task } from \"./types.js\";\n\nexport interface ArchiveComment {\n id: string;\n content: string;\n postedAt?: string;\n}\n\nexport interface ArchiveResult {\n path: string;\n /** False when the file already held exactly this content. */\n written: boolean;\n commentCount: number;\n /** Set when nothing was written and the reason was not \"unchanged\". */\n skipped?: \"no-discussion\";\n}\n\n/**\n * Filesystem-safe stem for a task, stable across re-archives.\n *\n * The id is included because titles change and two tasks can share one — the\n * id is what makes the path stable, and stability is what makes rewriting\n * idempotent rather than accumulating near-duplicate files.\n */\nexport function archiveSlug(task: Pick<Task, \"id\" | \"title\">): string {\n const title = task.title\n .toLowerCase()\n .replace(/[^\\p{L}\\p{N}]+/gu, \"-\")\n .replace(/^-+|-+$/g, \"\")\n .slice(0, 60)\n .replace(/-+$/g, \"\");\n return title ? `${title} - ${task.id}` : task.id;\n}\n\n/** Where a task's thread belongs, given the project root that owns it. */\nexport function archivePath(projectRoot: string, task: Pick<Task, \"id\" | \"title\">): string {\n return join(projectRoot, \"Notes\", \"tasks\", `${archiveSlug(task)}.md`);\n}\n\n/**\n * Render the task and its thread as a note.\n *\n * The description is included in full. It is usually the runbook or the\n * reasoning, and it is the half of the record that explains why the comments\n * say what they say.\n */\nexport function renderArchive(\n task: Task,\n comments: ArchiveComment[],\n completedAt: string\n): string {\n const out: string[] = [];\n\n out.push(\"---\");\n out.push(`task_id: ${task.id}`);\n out.push(`title: ${JSON.stringify(task.title)}`);\n if (task.owner.project) out.push(`owner: ${task.owner.project}`);\n if (task.due) out.push(`due: ${task.due}`);\n if (task.recurrence) out.push(`recurrence: ${JSON.stringify(task.recurrence)}`);\n out.push(`completed: ${completedAt}`);\n if (task.sourceUrl) out.push(`source: ${task.sourceUrl}`);\n out.push(\"---\");\n out.push(\"\");\n out.push(`# ${task.title}`);\n out.push(\"\");\n\n if (task.body.trim()) {\n out.push(\"## Task\");\n out.push(\"\");\n out.push(task.body.trim());\n out.push(\"\");\n }\n\n out.push(`## Discussion (${comments.length})`);\n out.push(\"\");\n\n if (comments.length === 0) {\n // Stated rather than omitted: an empty section says the thread was checked\n // and was empty, where a missing section says nothing at all.\n out.push(\"_No comments were posted on this task._\");\n out.push(\"\");\n } else {\n for (const c of comments) {\n const when = c.postedAt ? c.postedAt.slice(0, 19).replace(\"T\", \" \") : \"date unknown\";\n out.push(`### ${when}`);\n out.push(\"\");\n out.push(c.content.trim());\n out.push(\"\");\n }\n }\n\n return out.join(\"\\n\");\n}\n\n/** Drop the `completed:` frontmatter line, so two archives are comparable. */\nfunction withoutStamp(body: string): string {\n return body\n .split(\"\\n\")\n .filter((l) => !l.startsWith(\"completed: \"))\n .join(\"\\n\");\n}\n\n/**\n * Write the archive, skipping the write when nothing changed.\n *\n * Returning `written: false` for an unchanged file matters for a recurring\n * task: it is archived on every completion, and rewriting an identical file\n * daily would churn mtimes and make the indexer re-read it for nothing.\n */\nexport function writeArchive(\n projectRoot: string,\n task: Task,\n comments: ArchiveComment[],\n completedAt: string\n): ArchiveResult {\n const path = archivePath(projectRoot, task);\n\n // Nothing was discussed, so there is nothing to keep. The point of this is to\n // preserve a conversation that completing the task would otherwise bury —\n // where no conversation happened, a file saying so is noise, and enough of it\n // buries the notes that DO carry something. The task itself is not at risk:\n // it stays in the tracker either way.\n if (comments.length === 0) {\n return { path, written: false, commentCount: 0, skipped: \"no-discussion\" };\n }\n\n const body = renderArchive(task, comments, completedAt);\n\n if (existsSync(path)) {\n try {\n // Compare without the completion stamp. It is `new Date()` at call time,\n // so including it makes every re-archive differ from the last by\n // construction — the unchanged case could never fire, the file would be\n // rewritten on every duplicate delivery, and the owning session would be\n // notified again each time about a discussion it has already seen. The\n // thread is what is being preserved; when to notice it is not part of it.\n if (withoutStamp(readFileSync(path, \"utf-8\")) === withoutStamp(body)) {\n return { path, written: false, commentCount: comments.length };\n }\n } catch {\n // Unreadable for any reason — fall through and rewrite rather than\n // treating a read failure as \"already up to date\".\n }\n }\n\n mkdirSync(dirname(path), { recursive: true });\n writeFileSync(path, body, \"utf-8\");\n return { path, written: true, commentCount: comments.length };\n}\n","/**\n * projects.ts — reconcile sessions against the tracker projects that address them.\n *\n * A session can only be given work from a phone if something in the tracker\n * names it. That mapping drifts constantly and invisibly: sessions get renamed,\n * new ones appear, old ones stop being used, and nothing anywhere says \"these\n * five can receive work and these thirteen cannot\". The failure is silent in the\n * worst direction — filing a task for a session with no project looks exactly\n * like filing one that will be picked up.\n *\n * So this reports the mapping in both directions rather than just listing what\n * exists: sessions with no project (cannot be addressed) and projects with no\n * session (will queue until one launches, which is legitimate and worth\n * distinguishing from a mistake).\n */\n\nimport { execFile } from \"node:child_process\";\nimport { promisify } from \"node:util\";\n\nconst run = promisify(execFile);\n\nexport interface SessionEntry {\n name: string;\n directory: string;\n}\n\nexport interface ProjectEntry {\n id: string;\n name: string;\n}\n\nexport type MappingState = \"mapped\" | \"session-only\" | \"project-only\";\n\nexport interface MappingRow {\n name: string;\n state: MappingState;\n directory?: string;\n projectId?: string;\n}\n\n/**\n * Read the session manifest from AIBroker.\n *\n * The manifest rather than a live enumeration, deliberately: it is the set of\n * sessions the user actually works with, which is the right unit for \"should\n * this have an inbox\". A live list would drop everything not open at this\n * instant and churn a permanent decision on a transient signal.\n *\n * Returns an empty list when AIBroker is absent — this command degrades to\n * \"here are your tracker projects\" rather than failing, in keeping with the\n * rest of the bus being optional.\n */\nexport async function readSessionManifest(bin = \"aibroker\"): Promise<SessionEntry[]> {\n let stdout: string;\n try {\n ({ stdout } = await run(bin, [\"sessions\", \"list\"], { timeout: 20_000 }));\n } catch {\n return [];\n }\n\n const entries: SessionEntry[] = [];\n for (const line of stdout.split(\"\\n\")) {\n // Manifest rows are indented \" Name<spaces>/absolute/path\". Anything\n // without a leading absolute path is a header or a log line.\n const m = line.match(/^\\s{2}(\\S.*?)\\s{2,}(\\/.*)$/);\n if (!m) continue;\n const name = m[1].trim();\n const directory = m[2].trim();\n if (!name || !directory) continue;\n entries.push({ name, directory });\n }\n return entries;\n}\n\n/** Compare names the way a person scanning two lists would. */\nexport function normalizeName(name: string): string {\n return name.trim().toLowerCase().replace(/\\s+/g, \" \");\n}\n\n/**\n * Join sessions and tracker projects into one ordered view.\n *\n * Sorted mapped-first then by name, so the actionable rows — sessions that\n * cannot yet be addressed — sit together rather than scattered through a list\n * of things that already work.\n */\nexport function reconcile(\n sessions: SessionEntry[],\n projects: ProjectEntry[]\n): MappingRow[] {\n const byName = new Map<string, ProjectEntry>();\n for (const p of projects) byName.set(normalizeName(p.name), p);\n\n const seen = new Set<string>();\n const rows: MappingRow[] = [];\n\n for (const s of sessions) {\n const key = normalizeName(s.name);\n const project = byName.get(key);\n if (project) seen.add(key);\n rows.push({\n name: s.name,\n state: project ? \"mapped\" : \"session-only\",\n directory: s.directory,\n projectId: project?.id,\n });\n }\n\n for (const p of projects) {\n const key = normalizeName(p.name);\n if (seen.has(key)) continue;\n rows.push({ name: p.name, state: \"project-only\", projectId: p.id });\n }\n\n const rank: Record<MappingState, number> = {\n mapped: 0,\n \"session-only\": 1,\n \"project-only\": 2,\n };\n return rows.sort(\n (a, b) => rank[a.state] - rank[b.state] || a.name.localeCompare(b.name)\n );\n}\n","/**\n * task.ts — `pai task` CLI\n *\n * Surface for the task bus: list what is open, file new work, dispatch tasks to\n * the sessions that own them, and close them out.\n *\n * See docs/task-bus.md.\n */\n\nimport type { Command } from \"commander\";\nimport chalk from \"chalk\";\nimport { chmodSync } from \"node:fs\";\nimport { createInterface } from \"node:readline\";\nimport { loadConfig, CONFIG_FILE } from \"../../daemon/config.js\";\nimport { readConfigRaw, writeConfigRaw } from \"./setup/utils.js\";\nimport { getRegistryBackend } from \"../../storage/factory.js\";\nimport { loadAliasMap } from \"../../tasks/resolver.js\";\nimport { TodoistProvider } from \"../../tasks/providers/todoist.js\";\nimport { dispatchAll } from \"../../tasks/dispatch.js\";\nimport { detectAiBroker, detectProber } from \"../../tasks/transport/aibroker.js\";\nimport { tick, releaseClaim, loadState, saveState, STATE_FILE } from \"../../tasks/poller.js\";\nimport { installSchedule, uninstallSchedule, scheduleStatus, DEFAULT_INTERVAL_SECS } from \"../../tasks/schedule-install.js\";\nimport type { DispatchResult, Task, TaskProvider } from \"../../tasks/types.js\";\nimport { writeArchive } from \"../../tasks/archive.js\";\nimport {\n readSessionManifest,\n reconcile,\n normalizeName,\n type MappingRow,\n} from \"../../tasks/projects.js\";\n\n/**\n * Make a tracker sub-project name resolve to the PAI project it belongs to.\n *\n * Ownership is decided by matching the container name against a curated alias.\n * A session's display name is almost never its PAI slug — \"SL\" is\n * `seriousletter`, \"Home\" is `owner` — so a freshly created sub-project\n * routes nowhere until someone connects the two. That gap bit twice on\n * 2026-08-02: the project existed, looked addressable in every listing, and\n * tasks filed into it came back unrouted.\n *\n * The session's working directory is what links them: the manifest gives the\n * directory, the registry gives the project at that path. Returns a line worth\n * printing, or null when nothing needed doing.\n */\nasync function ensureAlias(name: string, directory?: string): Promise<string | null> {\n if (!directory) return `no directory for \"${name}\" — set the alias by hand`;\n\n const registry = await getRegistryBackend();\n const project = await registry.getProjectByRootPath(directory);\n if (!project || project.status !== \"active\") {\n return `no PAI project at ${directory} — tasks here will not route`;\n }\n\n // resolveAlias() is case-sensitive; the original SQL compared lower(alias)\n // — a narrower check (see worker report: resolveAlias needs the same\n // opts.caseInsensitive that getProjectByAlias already has).\n const existingProjectId = await registry.resolveAlias(name);\n if (existingProjectId !== null) {\n // Repointing someone else's alias would silently steal their address.\n return existingProjectId === project.id\n ? null\n : `alias \"${name}\" already points elsewhere — left alone`;\n }\n\n await registry.addAlias(name, project.id);\n return `aliased → ${project.slug}`;\n}\n\n/**\n * Render the session/project mapping.\n *\n * The three states are printed with different weight on purpose. \"Cannot be\n * addressed\" is the only one that needs action, and it is the one that is\n * otherwise invisible — filing a task for a session with no project looks\n * exactly like filing one that works.\n */\nfunction printProjectMapping(rows: MappingRow[], noBroker: boolean): void {\n const mapped = rows.filter((r) => r.state === \"mapped\");\n const unmapped = rows.filter((r) => r.state === \"session-only\");\n const orphan = rows.filter((r) => r.state === \"project-only\");\n\n console.log();\n console.log(chalk.bold(` Can receive work (${mapped.length})`));\n for (const r of mapped) console.log(chalk.green(\" ✓ \") + r.name);\n if (mapped.length === 0) console.log(dim(\" none\"));\n\n if (unmapped.length > 0) {\n console.log();\n console.log(chalk.bold(` No project — cannot be addressed (${unmapped.length})`));\n for (const r of unmapped) console.log(chalk.yellow(\" · \") + r.name);\n console.log();\n console.log(dim(\" pai task projects --create <name>... or --create-all\"));\n }\n\n if (orphan.length > 0) {\n console.log();\n console.log(chalk.bold(` Project with no session (${orphan.length})`));\n for (const r of orphan) console.log(dim(\" · \" + r.name));\n console.log(dim(\" Tasks filed here queue until that session launches — not a fault.\"));\n }\n\n if (noBroker) {\n console.log();\n console.log(dim(\" AIBroker not reachable — showing tracker projects only.\"));\n }\n console.log();\n}\n\nconst dim = chalk.dim;\nconst bold = chalk.bold;\nconst warn = chalk.yellow;\nconst ok = chalk.green;\n\n// ---------------------------------------------------------------------------\n// Wiring\n// ---------------------------------------------------------------------------\n\nasync function buildProvider(): Promise<TodoistProvider | null> {\n const config = loadConfig();\n const tasks = config.tasks;\n if (!tasks?.enabled) return null;\n\n const aliases = await loadAliasMap();\n const provider = new TodoistProvider(tasks.providers.todoist, aliases, {\n defaultOwner: tasks.defaultOwner,\n });\n return provider.isConfigured() ? provider : null;\n}\n\n/** Print the one message that actually helps when nothing is configured. */\nfunction reportUnconfigured(): void {\n console.error(chalk.yellow(\" Task bus is not configured.\"));\n console.error(dim(\" Run `pai setup` and complete the Task Bus step.\"));\n}\n\nfunction todayIso(): string {\n return new Date().toISOString().slice(0, 10);\n}\n\n// ---------------------------------------------------------------------------\n// Credentials\n// ---------------------------------------------------------------------------\n\n/**\n * Prompt for a secret without echoing it.\n *\n * Never accept a token as a command-line argument: argv is visible in `ps` to\n * every user on the machine and lands in shell history verbatim.\n */\nfunction promptSecret(question: string): Promise<string> {\n return new Promise((resolve) => {\n const rl = createInterface({ input: process.stdin, output: process.stdout, terminal: true });\n const out = process.stdout as NodeJS.WriteStream & { muted?: boolean };\n // Swap in a writer that swallows echoed keystrokes but still lets the\n // prompt itself through.\n const write = out.write.bind(out);\n (rl as unknown as { _writeToOutput: (s: string) => void })._writeToOutput = (s: string) => {\n if (!out.muted) write(s);\n };\n write(question);\n out.muted = true;\n rl.question(\"\", (answer) => {\n out.muted = false;\n write(\"\\n\");\n rl.close();\n resolve(answer.trim());\n });\n });\n}\n\n/** Show enough of a token to recognise it, never enough to use it. */\nfunction redact(token: string | undefined): string {\n if (!token) return dim(\"(not set)\");\n return token.length <= 8 ? \"********\" : `${token.slice(0, 4)}…${token.slice(-4)}`;\n}\n\ntype RawTasks = {\n enabled?: boolean;\n autoDispatch?: boolean;\n dispatchTimeoutSecs?: number;\n providers?: { todoist?: { enabled?: boolean; apiKey?: string; rootProjectId?: string; findingsSectionId?: string } };\n};\n\n// ---------------------------------------------------------------------------\n// Rendering\n// ---------------------------------------------------------------------------\n\nfunction renderOwner(task: Task): string {\n if (task.owner.project) {\n const via = task.owner.source === \"label\" ? \"label\" : \"container\";\n return chalk.cyan(task.owner.project) + dim(` (${via})`);\n }\n return task.owner.rawHint\n ? chalk.yellow(\"unrouted\") + dim(` (${task.owner.rawHint} matches no project)`)\n : chalk.yellow(\"unrouted\");\n}\n\nfunction printTasks(tasks: Task[]): void {\n if (tasks.length === 0) {\n console.log(dim(\" Nothing open.\"));\n return;\n }\n console.log();\n for (const t of tasks) {\n const due = t.due ? dim(` due ${t.due.slice(0, 10)}`) : \"\";\n const prio = t.priority !== \"p4\" ? dim(` ${t.priority}`) : \"\";\n console.log(` ${bold(t.title)}${prio}${due}`);\n console.log(` ${renderOwner(t)} ${dim(\"· \" + t.id)}`);\n }\n console.log();\n}\n\nfunction printResults(results: DispatchResult[]): void {\n const symbol: Record<string, string> = {\n delivered: chalk.green(\"→\"),\n spawned: chalk.green(\"+\"),\n unrouted: chalk.yellow(\"?\"),\n unlaunchable: chalk.red(\"!\"),\n unreachable: chalk.red(\"✗\"),\n skipped: dim(\"·\"),\n };\n\n console.log();\n for (const r of results) {\n console.log(` ${symbol[r.outcome]} ${r.task.title}`);\n const detail = [r.session, r.reason].filter(Boolean).join(\" — \");\n if (detail) console.log(` ${dim(detail)}`);\n }\n console.log();\n\n const counts = results.reduce<Record<string, number>>((acc, r) => {\n acc[r.outcome] = (acc[r.outcome] ?? 0) + 1;\n return acc;\n }, {});\n console.log(dim(\" \" + Object.entries(counts).map(([k, v]) => `${v} ${k}`).join(\", \")));\n console.log();\n}\n\n// ---------------------------------------------------------------------------\n// Commands\n// ---------------------------------------------------------------------------\n\nexport function registerTaskCommands(taskCmd: Command): void {\n taskCmd\n .command(\"projects\")\n .description(\"Show which sessions can be given work from the tracker, and create the missing projects\")\n .option(\"--create <names...>\", \"Create tracker projects for these session names\")\n .option(\"--create-all\", \"Create a project for every session that has none\")\n .action(async (opts) => {\n const provider = await buildProvider();\n if (!provider) return reportUnconfigured();\n\n if (!provider.listSubProjects || !provider.findOrCreateSubProject) {\n console.log();\n console.log(\n chalk.yellow(` ${provider.providerId} does not address work by sub-project.`)\n );\n console.log(dim(\" Session-scoped inboxes need a tracker with nested projects.\"));\n console.log();\n return;\n }\n\n const [sessions, projects] = await Promise.all([\n readSessionManifest(),\n provider.listSubProjects(),\n ]);\n\n const rows = reconcile(sessions, projects);\n\n const wanted = opts.createAll\n ? rows.filter((r) => r.state === \"session-only\").map((r) => r.name)\n : ((opts.create as string[] | undefined) ?? []);\n\n if (wanted.length > 0) {\n const addressable = new Set(\n rows.filter((r) => r.state === \"session-only\").map((r) => normalizeName(r.name))\n );\n console.log();\n for (const name of wanted) {\n // Refuse to create a project for a session nobody has: it would look\n // addressable and never be picked up, which is the failure this\n // command exists to surface.\n if (!addressable.has(normalizeName(name))) {\n console.log(chalk.yellow(` skipped ${name}`) + dim(\" — no such session, or it already has a project\"));\n continue;\n }\n const { id, created } = await provider.findOrCreateSubProject(name);\n console.log(\n (created ? chalk.green(\" created \") : dim(\" exists \")) + name + dim(` ${id}`)\n );\n\n // A project alone does not route. Ownership resolves by matching the\n // container name against a curated alias, and a session's display\n // name is rarely its PAI slug — \"SL\" is `seriousletter`, \"Home\" is\n // `owner`. Creating the project and stopping produced something\n // that looked addressable and silently was not, twice, before this\n // was wired in. The alias is not a separate step a user should have\n // to know about; it is what makes the project mean anything.\n const row = rows.find((r) => normalizeName(r.name) === normalizeName(name));\n const note = await ensureAlias(name, row?.directory);\n if (note) console.log(dim(` ${note}`));\n }\n console.log();\n return;\n }\n\n printProjectMapping(rows, sessions.length === 0);\n });\n\n taskCmd\n .command(\"list\")\n .description(\"List open tasks on the bus\")\n .option(\"--owner <project>\", \"Only tasks owned by this PAI project\")\n .option(\"--due <date>\", \"Only tasks due on or before this ISO date\")\n .option(\"--today\", \"Shorthand for --due <today>\")\n .option(\"--routed\", \"Hide tasks with no resolved owner\")\n .option(\"--limit <n>\", \"Maximum tasks to show\", (v) => Number.parseInt(v, 10))\n .action(async (opts) => {\n const provider = await buildProvider();\n if (!provider) return reportUnconfigured();\n\n const tasks = await provider.listOpen({\n owner: opts.owner,\n dueBefore: opts.today ? todayIso() : opts.due,\n includeUnrouted: !opts.routed,\n limit: opts.limit,\n });\n printTasks(tasks);\n });\n\n taskCmd\n .command(\"add <title>\")\n .description(\"File a task onto the bus\")\n .option(\"--owner <project>\", \"PAI project that owns this (adds a pai: label)\")\n .option(\"--body <text>\", \"Full procedure and reasoning — not just a restatement of the title\")\n .option(\"--due <when>\", \"Due date: ISO, natural language, or a recurrence (\\\"every monday at 9\\\")\")\n .option(\"--priority <p>\", \"p1 (highest) … p4 (default)\")\n .option(\"--url <url>\", \"Reference — prefer a hook:// URL over a file path\")\n .option(\"--into <sub-project>\", \"File into this sub-project under the bus root, creating it if absent\")\n .action(async (title, opts) => {\n const provider = await buildProvider();\n if (!provider) return reportUnconfigured();\n\n // A title alone is not actionable months later. Warn rather than block:\n // refusing the write would just push people to file nothing at all.\n if (!opts.body) {\n console.log(dim(\" No --body given. Add the procedure and reasoning so this\"));\n console.log(dim(\" is still actionable months from now, by you alone.\"));\n }\n\n const created = await provider.add({\n title,\n body: opts.body ?? \"\",\n owner: opts.owner ?? null,\n due: opts.due,\n priority: opts.priority,\n sourceUrl: opts.url,\n into: opts.into,\n });\n\n console.log(chalk.green(` Filed: ${created.title}`));\n console.log(` ${renderOwner(created)} ${dim(\"· \" + created.id)}`);\n });\n\n taskCmd\n .command(\"dispatch\")\n .description(\"Hand open tasks to the PAI sessions that own them\")\n .option(\"--owner <project>\", \"Only dispatch tasks for this project\")\n .option(\"--today\", \"Only tasks due today or overdue\")\n .option(\"--dry-run\", \"Report what would happen without contacting any session\")\n .option(\"--no-spawn\", \"Never launch a session; skip owners that are not running\")\n .action(async (opts) => {\n const provider = await buildProvider();\n if (!provider) return reportUnconfigured();\n\n const config = loadConfig();\n const tasks = await provider.listOpen({\n owner: opts.owner,\n dueBefore: opts.today ? todayIso() : undefined,\n includeUnrouted: true,\n });\n\n if (tasks.length === 0) {\n console.log(dim(\" Nothing to dispatch.\"));\n return;\n }\n\n // A dry run must never contact a session, so skip detection entirely\n // rather than detecting and then declining to use it.\n const transport = opts.dryRun\n ? null\n : await detectAiBroker(undefined, config.tasks?.dispatchTimeoutSecs);\n\n if (!opts.dryRun && !transport) {\n console.log(dim(\" No aibroker CLI with `dispatch` found — reporting ownership only.\"));\n }\n\n const results = await dispatchAll(tasks, {\n transport,\n autoDispatch: opts.dryRun ? false : (config.tasks?.autoDispatch ?? false),\n spawnIfAbsent: opts.spawn !== false,\n dryRun: Boolean(opts.dryRun),\n });\n\n printResults(results);\n });\n\n taskCmd\n .command(\"poll\")\n .description(\"One scheduler tick: dispatch what is due, check what is running, report\")\n .option(\"--dry-run\", \"Show what would happen without touching anything\")\n .action(async (opts) => {\n const provider = await buildProvider();\n if (!provider) return reportUnconfigured();\n\n const config = loadConfig();\n const transport = opts.dryRun ? null : await detectAiBroker(undefined, config.tasks?.dispatchTimeoutSecs);\n const prober = opts.dryRun ? null : await detectProber();\n\n const report = await tick({\n provider: provider as TodoistProvider,\n transport,\n prober,\n autoDispatch: config.tasks?.autoDispatch ?? false,\n dryRun: Boolean(opts.dryRun),\n });\n\n if (report.decisions.length === 0) {\n console.log(dim(\" Nothing due, nothing running.\"));\n return;\n }\n\n const mark: Record<string, string> = {\n dispatch: chalk.green(\"→\"),\n triggered: chalk.green(\"▶\"),\n complete: chalk.green(\"✓\"),\n running: dim(\"·\"),\n probe: chalk.yellow(\"?\"),\n orphaned: chalk.yellow(\"!\"),\n abandoned: chalk.red(\"\\u2a2f\"),\n skip: dim(\"–\"),\n wait: dim(\" \"),\n };\n\n console.log();\n for (const { decision, note } of report.decisions) {\n console.log(` ${mark[decision.action] ?? \" \"} ${decision.task.title}`);\n if (note) console.log(` ${dim(note)}`);\n }\n console.log();\n console.log(\n dim(\n ` ${report.dispatched} dispatched, ${report.completed} completed, ` +\n `${report.probed} probed, ${report.stuck} stuck`\n )\n );\n console.log();\n });\n\n const scheduleCmd = taskCmd\n .command(\"schedule\")\n .description(\"Install, remove or inspect the launchd agent that ticks the scheduler\");\n\n scheduleCmd\n .command(\"install\")\n .description(\"Install the scheduler tick (default: every 15 minutes)\")\n .option(\"--interval <secs>\", \"Seconds between ticks\", (v) => Number.parseInt(v, 10))\n .action((opts) => {\n const r = installSchedule(opts.interval || DEFAULT_INTERVAL_SECS);\n console.log(r.loaded ? chalk.green(` ${r.message}`) : chalk.yellow(` ${r.message}`));\n console.log(dim(` ${r.plistPath}`));\n console.log(dim(` The schedule itself lives in Todoist — this only sets how often PAI looks.`));\n });\n\n scheduleCmd\n .command(\"uninstall\")\n .description(\"Remove the scheduler agent\")\n .action(() => console.log(chalk.green(` ${uninstallSchedule()}`)));\n\n scheduleCmd\n .command(\"status\")\n .description(\"Show whether the scheduler agent is installed and loaded\")\n .action(() => {\n const s = scheduleStatus();\n console.log();\n console.log(` ${s.installed ? chalk.green(\"installed\") : dim(\"not installed\")}` +\n ` ${s.installed ? (s.running ? chalk.green(\"· loaded\") : chalk.yellow(\"· not loaded\")) : \"\"}`);\n console.log(dim(` ${s.detail}`));\n console.log();\n });\n\n taskCmd\n .command(\"config\")\n .description(\"View or change task bus settings without running the full setup wizard\")\n .option(\"--token\", \"Prompt for the Todoist API token (input is hidden)\")\n .option(\"--from-env\", \"Adopt the token from TODOIST_API_KEY in the environment\")\n .option(\"--project <id>\", \"Tracker project ID that roots the bus (an ID, never a name)\")\n .option(\"--findings <id>\", \"Section ID for the findings inbox\")\n .option(\"--timeout <secs>\", \"Seconds a single dispatch may take\", (v) => Number.parseInt(v, 10))\n .option(\"--auto-dispatch <bool>\", \"Hand tasks to owning sessions automatically (true/false)\")\n .option(\"--disable\", \"Turn the task bus off without discarding its settings\")\n .action(async (opts) => {\n const raw = readConfigRaw();\n const tasks = (raw.tasks ?? {}) as RawTasks;\n tasks.providers ??= {};\n tasks.providers.todoist ??= {};\n const todoist = tasks.providers.todoist;\n\n const noChanges =\n !opts.token && !opts.fromEnv && !opts.project && !opts.findings &&\n opts.timeout === undefined && opts.autoDispatch === undefined && !opts.disable;\n\n if (noChanges) {\n console.log();\n console.log(` ${bold(\"Task bus\")} ${tasks.enabled ? chalk.green(\"enabled\") : dim(\"disabled\")}`);\n console.log(` ${bold(\"Token\")} ${redact(todoist.apiKey)}${\n !todoist.apiKey && process.env.TODOIST_API_KEY ? dim(\" (TODOIST_API_KEY is set in this shell)\") : \"\"\n }`);\n console.log(` ${bold(\"Root project\")} ${todoist.rootProjectId ?? dim(\"(not set)\")}`);\n console.log(` ${bold(\"Findings\")} ${todoist.findingsSectionId ?? dim(\"(not set)\")}`);\n console.log(` ${bold(\"Auto-dispatch\")} ${tasks.autoDispatch ? chalk.green(\"on\") : dim(\"off\")}`);\n console.log(` ${bold(\"Timeout\")} ${tasks.dispatchTimeoutSecs ?? dim(\"default (180s)\")}`);\n console.log();\n console.log(dim(` ${CONFIG_FILE}`));\n console.log();\n return;\n }\n\n if (opts.token && opts.fromEnv) {\n console.error(chalk.yellow(\" Use either --token or --from-env, not both.\"));\n process.exitCode = 1;\n return;\n }\n\n if (opts.fromEnv) {\n const envToken = process.env.TODOIST_API_KEY?.trim();\n if (!envToken) {\n console.error(chalk.yellow(\" TODOIST_API_KEY is not set in this environment.\"));\n process.exitCode = 1;\n return;\n }\n todoist.apiKey = envToken;\n todoist.enabled = true;\n tasks.enabled = true;\n }\n\n if (opts.token) {\n const entered = await promptSecret(\" Todoist API token (hidden): \");\n if (!entered) {\n console.error(chalk.yellow(\" No token entered. Nothing changed.\"));\n process.exitCode = 1;\n return;\n }\n todoist.apiKey = entered;\n todoist.enabled = true;\n tasks.enabled = true;\n }\n\n if (opts.project) { todoist.rootProjectId = opts.project; tasks.enabled = true; }\n if (opts.findings) todoist.findingsSectionId = opts.findings;\n if (opts.timeout !== undefined) tasks.dispatchTimeoutSecs = opts.timeout;\n if (opts.autoDispatch !== undefined) tasks.autoDispatch = opts.autoDispatch === \"true\";\n if (opts.disable) tasks.enabled = false;\n\n raw.tasks = tasks;\n writeConfigRaw(raw);\n\n // The file now holds a credential. Narrow it before saying anything else,\n // so a crash between write and chmod cannot leave it world-readable.\n try {\n chmodSync(CONFIG_FILE, 0o600);\n } catch {\n console.log(chalk.yellow(` Could not restrict permissions on ${CONFIG_FILE} — check them by hand.`));\n }\n\n console.log(chalk.green(\" Saved.\"));\n if (todoist.apiKey && !todoist.rootProjectId) {\n console.log(dim(\" No root project set yet — run `pai task config --project <id>`.\"));\n }\n });\n\n taskCmd\n .command(\"archive <id>\")\n .description(\"Save a task's discussion into the owning project's notes, without completing it\")\n .option(\"--quiet\", \"Print nothing (for hook and webhook callers)\")\n .option(\"--no-notify\", \"Do not tell the owning session where the file landed\")\n .action(async (id, opts: { quiet?: boolean; notify?: boolean }) => {\n const provider = await buildProvider();\n if (!provider) return reportUnconfigured();\n // Notifies by default: this command is the path something OUTSIDE the\n // session took — a checkbox ticked in the tracker — so the session has no\n // idea it happened. `task done` deliberately does not notify, because\n // there the session is the one that just did it.\n const saved = await cmdArchiveTask(provider, id, {\n quiet: opts.quiet,\n notify: opts.notify !== false,\n });\n // Non-zero on failure so a webhook caller can tell nothing was saved\n // rather than assuming success — the whole point is not losing content.\n if (!saved) process.exitCode = 1;\n });\n\n taskCmd\n .command(\"done <id>\")\n .description(\"Mark a task complete on the tracker, keeping its discussion\")\n .option(\"--no-archive\", \"Complete without saving the comment thread\")\n .action(async (id, opts) => {\n const provider = await buildProvider();\n if (!provider) return reportUnconfigured();\n\n // Archive BEFORE completing. A completed task is harder to read back on\n // some providers, and losing the thread is the failure this exists to\n // prevent — so the copy is taken while the task is unambiguously there.\n let archived: { path: string; written: boolean; commentCount: number } | null = null;\n if (opts.archive !== false) {\n archived = await archiveTaskThread(provider, id);\n }\n\n await provider.complete(id);\n console.log(chalk.green(` Completed ${id}.`));\n\n // Release the claim left behind by whichever run finished this task.\n // For a recurring task, `complete` above only advanced the due date to\n // the next occurrence — the `pai-running` label, the progress-marker\n // comment, and this poller's transient run state (`startedAt` /\n // `claimSeenAt` / `failedProbes`) all outlive it unless something\n // clears them. Left alone, the next occurrence looks already claimed,\n // and the scheduler classifies it as overrunning and starts probing a\n // session that finished hours ago.\n //\n // Best-effort throughout: closing the task above is the action that\n // has to succeed, and this cleanup must never turn a completed task\n // into a failed command.\n try {\n const task = await provider.getTask(id);\n if (task) {\n const state = loadState(STATE_FILE);\n await releaseClaim(task, provider, state);\n // Record the due date this advanced to, the same way `tick()`\n // records it every cycle. Without this, the next `pai task poll`\n // has no way to tell this advance apart from a human dragging the\n // task forward by hand — both look like \"due moved exactly one\n // recurrence period, unclaimed\" — and would dispatch the task a\n // second time for a run that just finished.\n if (task.due) state.lastSeenDue[id] = task.due;\n saveState(state, STATE_FILE);\n }\n } catch {\n // Cleanup is best-effort — see comment above.\n }\n\n if (archived) {\n const what = `${archived.commentCount} comment${archived.commentCount === 1 ? \"\" : \"s\"}`;\n console.log(\n archived.written\n ? dim(` Discussion saved (${what}) → ${archived.path}`)\n : dim(` Discussion already saved (${what}), unchanged.`)\n );\n }\n });\n}\n\n/**\n * `pai task archive <id>` — save a task's discussion without completing it.\n *\n * Split out from `done` so the webhook has something to call. Ticking the\n * checkbox in the tracker is the completion path people actually use, and it\n * never runs `pai task done` — so an archive wired only into that command\n * fires on the route nobody takes and not on the route everybody does.\n *\n * Safe to call repeatedly: the archive rewrites one file per task from the full\n * thread, so a duplicate webhook or a poller backstop covering the same\n * completion costs one no-op.\n */\nexport async function cmdArchiveTask(\n provider: TaskProvider,\n id: string,\n opts: { quiet?: boolean; notify?: boolean } = {}\n): Promise<boolean> {\n if (!provider.getTask || !provider.listComments) {\n if (!opts.quiet) console.log(warn(` ${provider.providerId} cannot read a task's discussion.`));\n return false;\n }\n\n const task = await provider.getTask(id);\n if (!task) {\n if (!opts.quiet) console.log(warn(` No task ${id}.`));\n return false;\n }\n\n const root = task.owner.rootPath;\n if (!task.owner.project || !root) {\n if (!opts.quiet) {\n console.log(\n warn(\" No owning project — nothing archived. \") +\n dim(\"Label it `pai:<project>` to give it a home.\")\n );\n }\n return false;\n }\n\n const comments = await provider.listComments(id);\n const r = writeArchive(root, task, comments, new Date().toISOString());\n\n if (r.written && opts.notify) {\n await tellOwningSession(task, r.path, r.commentCount, opts.quiet);\n }\n\n if (!opts.quiet) {\n const what = `${r.commentCount} comment${r.commentCount === 1 ? \"\" : \"s\"}`;\n if (r.skipped === \"no-discussion\") {\n console.log(dim(\" No discussion on this task — nothing to archive.\"));\n } else {\n console.log(\n r.written\n ? ok(` Saved (${what}) → `) + r.path\n : dim(` Already saved (${what}), unchanged.`)\n );\n }\n }\n return true;\n}\n\n/**\n * Tell the owning session that a discussion was filed, if it is running.\n *\n * `Notes/tasks/` is a default, not a decision. The session that owns a project\n * knows where things actually belong there — a dated triage folder, a ledger\n * row, an existing note the discussion continues. It can move or link the file;\n * this side cannot, because the convention lives in that project's head and not\n * in PAI.\n *\n * **Never spawns.** Launching a session to tell it a file exists inverts the\n * cost: the archive is already safely on disk, and a terminal opening by itself\n * because someone ticked a checkbox is worse than a note filed one directory\n * away from ideal. If nothing is running, the file simply waits.\n *\n * Best-effort throughout. The archive has already succeeded by the time this\n * runs, and a notification that cannot be delivered must never turn a saved\n * discussion into a reported failure.\n */\nasync function tellOwningSession(\n task: Task,\n path: string,\n comments: number,\n quiet?: boolean\n): Promise<void> {\n const project = task.owner.project;\n if (!project) return;\n\n try {\n const transport = await detectAiBroker();\n if (!transport) return;\n\n const message = [\n `A discussion was archived from the tracker — ${comments} comment${comments === 1 ? \"\" : \"s\"} on \"${task.title}\".`,\n \"\",\n `Filed provisionally at: ${path}`,\n `Tracker id: ${task.id}`,\n \"\",\n \"This is a notification, not a work order. Nothing is waiting on you and the\",\n \"content is already safe on disk.\",\n \"\",\n \"Notes/tasks/ is PAI's default, chosen without knowing this project's\",\n \"conventions. If it belongs somewhere else here — a dated folder, a ledger\",\n \"row, an existing note the discussion continues — move or link it, then say\",\n \"nothing. If the default is fine, leave it.\",\n ].join(\"\\n\");\n\n // spawnIfAbsent: false is the whole point — see the note above.\n const result = await transport.dispatch(project, message, { spawnIfAbsent: false });\n\n if (!quiet && result.outcome === \"delivered\") {\n console.log(dim(` Told the ${project} session where it landed.`));\n }\n } catch {\n // Deliberately silent: the discussion is saved, which is what mattered.\n }\n}\n\n/**\n * Copy a task's thread into the notes of the project that owns it.\n *\n * Best-effort by design: this runs as a side effect of completing a task, and\n * a failure to archive must never stop the task being completed. It reports\n * what went wrong rather than throwing, because a silent skip here would lose\n * exactly the content it exists to keep.\n */\nasync function archiveTaskThread(\n provider: TaskProvider,\n id: string\n): Promise<{ path: string; written: boolean; commentCount: number } | null> {\n try {\n if (!provider.listComments) return null;\n\n const tasks = await provider.listOpen({ includeUnrouted: true });\n const task = tasks.find((t) => t.id === id);\n if (!task) {\n console.log(dim(` Not on the open list — nothing archived.`));\n return null;\n }\n\n const project = task.owner.project;\n const root = task.owner.rootPath;\n if (!project || !root) {\n console.log(\n warn(` No owning project — discussion not archived. `) +\n dim(`Label it \\`pai:<project>\\` to give it a home.`)\n );\n return null;\n }\n\n const comments = await provider.listComments(id);\n return writeArchive(root, task, comments, new Date().toISOString());\n } catch (e) {\n console.log(\n warn(` Could not archive the discussion: `) +\n (e instanceof Error ? e.message : String(e))\n );\n return null;\n }\n}\n","/**\n * pai topic <sub-command>\n *\n * check <context> — Check whether context text has drifted to a different project\n *\n * Communicates with the running daemon via IPC.\n * The daemon uses BM25 keyword search to match context against indexed memory.\n *\n * Examples:\n * pai topic check \"working on authentication and JWT tokens\"\n * pai topic check \"fixing the React component\" --current myapp\n * pai topic check \"database schema migration\" --threshold 0.7\n */\n\nimport type { Command } from \"commander\";\nimport { ok, warn, err, dim, bold, header } from \"../utils.js\";\nimport { loadConfig } from \"../../daemon/config.js\";\nimport { PaiClient } from \"../../daemon/ipc-client.js\";\nimport type { TopicCheckResult } from \"../../topics/detector.js\";\n\n// ---------------------------------------------------------------------------\n// Helpers\n// ---------------------------------------------------------------------------\n\nfunction makeClient(): PaiClient {\n const config = loadConfig();\n return new PaiClient(config.socketPath);\n}\n\nfunction confidenceBar(confidence: number, width = 20): string {\n const filled = Math.round(confidence * width);\n const empty = width - filled;\n return \"[\" + \"=\".repeat(filled) + \" \".repeat(empty) + \"]\";\n}\n\nfunction printTopicResult(result: TopicCheckResult): void {\n console.log();\n console.log(header(\" PAI Topic Check\"));\n console.log();\n\n console.log(` ${bold(\"Current project:\")} ${result.currentProject ?? dim(\"(none)\")}`);\n console.log(` ${bold(\"Suggested project:\")} ${result.suggestedProject ?? dim(\"(none)\")}`);\n console.log(\n ` ${bold(\"Confidence:\")} ${confidenceBar(result.confidence)} ${(result.confidence * 100).toFixed(1)}%`\n );\n console.log(` ${bold(\"Chunks scored:\")} ${result.chunkCount}`);\n\n if (result.topProjects.length > 0) {\n console.log();\n console.log(` ${bold(\"Top matches:\")}`);\n for (const p of result.topProjects) {\n const bar = confidenceBar(p.score, 15);\n const marker = p.slug === result.currentProject ? dim(\" (current)\") : \"\";\n console.log(` ${p.slug.padEnd(30)} ${bar} ${(p.score * 100).toFixed(1)}%${marker}`);\n }\n }\n\n console.log();\n\n if (result.shifted) {\n console.log(\n warn(\" TOPIC SHIFT DETECTED\") +\n dim(` — conversation appears to be about \"${result.suggestedProject}\", not \"${result.currentProject}\"`)\n );\n console.log();\n } else if (result.suggestedProject && result.suggestedProject === result.currentProject) {\n console.log(ok(\" No shift detected\") + dim(\" — context matches current project\"));\n console.log();\n } else if (!result.currentProject) {\n if (result.suggestedProject) {\n console.log(\n ok(\" Best matching project: \") + bold(result.suggestedProject) +\n dim(` (confidence: ${(result.confidence * 100).toFixed(0)}%)`)\n );\n } else {\n console.log(dim(\" No matching project found in memory index.\"));\n }\n console.log();\n } else {\n console.log(\n dim(` No shift detected (top match \"${result.suggestedProject}\" has confidence ${(result.confidence * 100).toFixed(0)}% — below threshold)`)\n );\n console.log();\n }\n}\n\n// ---------------------------------------------------------------------------\n// Command implementations\n// ---------------------------------------------------------------------------\n\nasync function cmdCheck(\n context: string,\n opts: {\n current?: string;\n threshold?: string;\n json?: boolean;\n }\n): Promise<void> {\n const client = makeClient();\n\n const threshold = opts.threshold ? parseFloat(opts.threshold) : undefined;\n\n if (threshold !== undefined && (isNaN(threshold) || threshold < 0 || threshold > 1)) {\n console.error(err(\" --threshold must be a number between 0 and 1\"));\n process.exitCode = 1;\n return;\n }\n\n try {\n const result = await client.topicCheck({\n context,\n currentProject: opts.current,\n threshold,\n });\n\n if (opts.json) {\n console.log(JSON.stringify(result, null, 2));\n return;\n }\n\n printTopicResult(result);\n\n // Exit with code 1 if a shift was detected (useful for scripting / hooks)\n if (result.shifted) {\n process.exitCode = 1;\n return;\n }\n } catch (e) {\n const msg = e instanceof Error ? e.message : String(e);\n console.log();\n console.log(warn(\" Cannot reach PAI daemon.\"));\n console.log(dim(` ${msg}`));\n console.log(dim(\" Start it with: pai daemon serve\"));\n console.log();\n process.exitCode = 1;\n return;\n }\n}\n\n// ---------------------------------------------------------------------------\n// Commander registration\n// ---------------------------------------------------------------------------\n\nexport function registerTopicCommands(topicCmd: Command): void {\n // pai topic check <context>\n topicCmd\n .command(\"check <context>\")\n .description(\n \"Check whether context text has drifted to a different project.\\n\" +\n 'Example: pai topic check \"working on JWT authentication\"\\n' +\n \"Exit code 1 if a shift is detected (useful for hooks).\"\n )\n .option(\n \"--current <slug>\",\n \"The project this session is currently routed to\"\n )\n .option(\n \"--threshold <n>\",\n \"Confidence threshold [0-1] to declare a shift (default: 0.6)\"\n )\n .option(\"--json\", \"Output raw JSON result\")\n .action(\n async (\n context: string,\n opts: { current?: string; threshold?: string; json?: boolean }\n ) => {\n await cmdCheck(context, opts);\n }\n );\n}\n","/**\n * pai kg <sub-command>\n *\n * Temporal knowledge graph management:\n * pai kg backfill [--project SLUG] [--limit N] [--dry-run]\n * pai kg query [--subject S] [--predicate P] [--object O] [--as-of DATE] [--project SLUG]\n * pai kg list [--project SLUG] [--limit N]\n * pai kg stats\n *\n * All commands require the Postgres backend (KG tables live in Postgres).\n */\n\nimport type { Command } from \"commander\";\nimport type { StorageBackend } from \"../../storage/interface.js\";\n\nimport { ok, warn, err, dim, bold, header } from \"../utils.js\";\nimport { loadConfig, CONFIG_FILE } from \"../../daemon/config.js\";\nimport { createStorageBackend, getRegistryBackend } from \"../../storage/factory.js\";\nimport { backfillKgFromNotes } from \"../../memory/kg-backfill.js\";\n\n// ---------------------------------------------------------------------------\n// Helpers\n// ---------------------------------------------------------------------------\n\nasync function getBackend(): Promise<StorageBackend> {\n const config = loadConfig();\n if (config.storageBackend !== \"postgres\") {\n console.error(err(\" KG commands require Postgres backend.\"));\n console.error(dim(` Set \"storageBackend\": \"postgres\" in ${CONFIG_FILE}`));\n process.exit(1);\n }\n const backend = await createStorageBackend(config);\n if (!backend.supportsPostgresFeatures) {\n console.error(err(\" Postgres backend unavailable — fell back to SQLite.\"));\n process.exit(1);\n }\n return backend;\n}\n\nfunction shorten(s: string, n: number): string {\n return s.length <= n ? s : s.slice(0, n - 1) + \"…\";\n}\n\n// ---------------------------------------------------------------------------\n// pai kg backfill\n// ---------------------------------------------------------------------------\n\nasync function cmdBackfill(opts: {\n project?: string;\n limit?: string;\n dryRun?: boolean;\n}): Promise<void> {\n const limit = opts.limit ? parseInt(opts.limit, 10) : undefined;\n if (limit !== undefined && (isNaN(limit) || limit < 1)) {\n console.error(err(\" --limit must be a positive integer\"));\n process.exitCode = 1;\n return;\n }\n\n console.log();\n console.log(header(\" PAI KG Backfill\"));\n console.log();\n if (opts.project) console.log(` ${bold(\"Project:\")} ${opts.project}`);\n if (limit) console.log(` ${bold(\"Limit:\")} ${limit}`);\n if (opts.dryRun) console.log(` ${bold(\"Mode:\")} ${warn(\"dry-run\")}`);\n console.log();\n\n try {\n const result = await backfillKgFromNotes({\n projectSlug: opts.project,\n limit,\n dryRun: opts.dryRun,\n onProgress: (current, total, note) => {\n const short = shorten(note.replace(process.env.HOME ?? \"\", \"~\"), 70);\n process.stdout.write(` [${current}/${total}] ${dim(short)}\\n`);\n },\n });\n\n console.log();\n console.log(ok(\" Backfill complete\"));\n console.log(` Notes processed: ${bold(String(result.notes_processed))}`);\n console.log(` Triples extracted: ${bold(String(result.triples_extracted))}`);\n console.log(` Triples added: ${bold(String(result.triples_added))}`);\n console.log(` Triples superseded: ${bold(String(result.triples_superseded))}`);\n if (result.errors > 0) {\n console.log(` ${warn(\"Errors:\")} ${result.errors}`);\n }\n console.log();\n } catch (e) {\n const msg = e instanceof Error ? e.message : String(e);\n console.error(err(` ${msg}`));\n process.exitCode = 1;\n return;\n }\n}\n\n// ---------------------------------------------------------------------------\n// pai kg query\n// ---------------------------------------------------------------------------\n\nasync function cmdQuery(opts: {\n subject?: string;\n predicate?: string;\n object?: string;\n asOf?: string;\n project?: string;\n json?: boolean;\n}): Promise<void> {\n const backend = await getBackend();\n const registry = await getRegistryBackend();\n const close = () => backend.close();\n try {\n let projectId: number | undefined;\n if (opts.project) {\n const row = await registry.getProjectBySlug(opts.project);\n if (!row) {\n console.error(warn(` Project not found in Postgres: ${opts.project}`));\n } else {\n projectId = row.id;\n }\n }\n\n const asOf = opts.asOf ? new Date(opts.asOf) : undefined;\n if (asOf && isNaN(asOf.getTime())) {\n console.error(err(` Invalid --as-of date: ${opts.asOf}`));\n process.exitCode = 1;\n return;\n }\n\n const triples = await backend.queryKgTriples({\n subject: opts.subject,\n predicate: opts.predicate,\n object: opts.object,\n project_id: projectId,\n as_of: asOf,\n });\n\n if (opts.json) {\n console.log(JSON.stringify(triples, null, 2));\n return;\n }\n\n console.log();\n console.log(header(` ${triples.length} triple(s)`));\n console.log();\n for (const t of triples) {\n const validity = t.valid_to\n ? dim(`(invalidated ${t.valid_to.toISOString().slice(0, 10)})`)\n : dim(`(valid since ${t.valid_from.toISOString().slice(0, 10)})`);\n console.log(\n ` ${bold(t.subject)} ${dim(\"·\")} ${t.predicate} ${dim(\"·\")} ${t.object} ${validity}`\n );\n }\n console.log();\n } finally {\n await close();\n }\n}\n\n// ---------------------------------------------------------------------------\n// pai kg list\n// ---------------------------------------------------------------------------\n\nasync function cmdList(opts: { project?: string; limit?: string }): Promise<void> {\n const limit = opts.limit ? parseInt(opts.limit, 10) : 50;\n const backend = await getBackend();\n const registry = await getRegistryBackend();\n const close = () => backend.close();\n try {\n let projectId: number | undefined;\n if (opts.project) {\n const row = await registry.getProjectBySlug(opts.project);\n if (row) projectId = row.id;\n }\n\n const triples = await backend.queryKgTriples({ project_id: projectId });\n const slice = triples.slice(0, limit);\n\n console.log();\n console.log(\n header(` ${slice.length} of ${triples.length} currently-valid triple(s)`)\n );\n console.log();\n for (const t of slice) {\n console.log(\n ` ${bold(t.subject)} ${dim(\"·\")} ${t.predicate} ${dim(\"·\")} ${t.object}`\n );\n }\n if (triples.length > slice.length) {\n console.log();\n console.log(dim(` (${triples.length - slice.length} more — increase --limit)`));\n }\n console.log();\n } finally {\n await close();\n }\n}\n\n// ---------------------------------------------------------------------------\n// pai kg stats\n// ---------------------------------------------------------------------------\n\nasync function cmdStats(): Promise<void> {\n const backend = await getBackend();\n try {\n const stats = await backend.getKgStats();\n console.log();\n console.log(header(\" PAI KG Stats\"));\n console.log();\n console.log(` ${bold(\"Total triples:\")} ${stats.total}`);\n console.log(` ${bold(\"Currently valid:\")} ${stats.valid}`);\n console.log(` ${bold(\"Invalidated:\")} ${stats.invalidated}`);\n console.log(` ${bold(\"Distinct subjects:\")} ${stats.subjects}`);\n console.log(` ${bold(\"Distinct predicates:\")} ${stats.predicates}`);\n console.log(` ${bold(\"Contradictions:\")} ${stats.contradictions}`);\n console.log();\n } finally {\n await backend.close();\n }\n}\n\n// ---------------------------------------------------------------------------\n// Commander registration\n// ---------------------------------------------------------------------------\n\nexport function registerKgCommands(kgCmd: Command): void {\n kgCmd\n .command(\"backfill\")\n .description(\"Populate the KG from existing session notes (idempotent)\")\n .option(\"--project <slug>\", \"Restrict backfill to a single project\")\n .option(\"--limit <n>\", \"Maximum number of notes to process\")\n .option(\"--dry-run\", \"List notes that would be processed without extracting\")\n .action(async (opts: { project?: string; limit?: string; dryRun?: boolean }) => {\n await cmdBackfill(opts);\n });\n\n kgCmd\n .command(\"query\")\n .description(\"Query KG triples by subject, predicate, object, time, or project\")\n .option(\"--subject <s>\", \"Filter by subject\")\n .option(\"--predicate <p>\", \"Filter by predicate\")\n .option(\"--object <o>\", \"Filter by object\")\n .option(\"--as-of <date>\", \"Point-in-time query (YYYY-MM-DD or ISO 8601)\")\n .option(\"--project <slug>\", \"Restrict to a project slug\")\n .option(\"--json\", \"Output raw JSON\")\n .action(\n async (opts: {\n subject?: string;\n predicate?: string;\n object?: string;\n asOf?: string;\n project?: string;\n json?: boolean;\n }) => {\n await cmdQuery(opts);\n }\n );\n\n kgCmd\n .command(\"list\")\n .description(\"List currently-valid triples\")\n .option(\"--project <slug>\", \"Restrict to a project slug\")\n .option(\"--limit <n>\", \"Maximum triples to print\", \"50\")\n .action(async (opts: { project?: string; limit?: string }) => {\n await cmdList(opts);\n });\n\n kgCmd\n .command(\"stats\")\n .description(\"Show triple counts and contradiction count\")\n .action(async () => {\n await cmdStats();\n });\n}\n","/**\n * migrate-to-postgres.ts — one-shot SQLite -> Postgres migration\n * (`pai db migrate-to-postgres`, docs/design/postgres-only.md §4, §8).\n *\n * Copies kg_entities, every registry table, and the memory/vault rows that\n * are absent from Postgres by natural key. Idempotent: every insert is\n * `ON CONFLICT DO NOTHING`, so a re-run (or a run against a database that\n * already has some rows) just verifies zero rows missing and copies nothing\n * new. Never deletes or renames the SQLite files — the operator does that\n * after reviewing the verify output.\n *\n * The only module allowed to hold both a better-sqlite3 handle and a `pg`\n * pool at once (it lives in src/storage/, where that is the point).\n */\n\nimport { execFileSync, spawnSync } from \"node:child_process\";\nimport { existsSync, mkdirSync, statSync, openSync, closeSync } from \"node:fs\";\nimport { dirname } from \"node:path\";\nimport BetterSqlite3 from \"better-sqlite3\";\nimport type { Database } from \"better-sqlite3\";\nimport pg from \"pg\";\nimport type { Pool, PoolClient } from \"pg\";\nimport { loadConfig } from \"../daemon/config.js\";\nimport { federationDbPath } from \"./sqlite/federation-db.js\";\nimport { registryDbPath } from \"./sqlite/registry-db.js\";\nimport { paiHomePath } from \"../config/pai-home.js\";\nimport { PaiClient } from \"../daemon/ipc-client.js\";\nimport type { PostgresConfig } from \"./postgres/config.js\";\n\nconst { Pool: PgPool } = pg;\n\nconst DEFAULT_CONTAINER = \"pai-pgvector\";\nconst PG_DATA_DEST = \"/var/lib/postgresql/data\";\n\n// ---------------------------------------------------------------------------\n// Types\n// ---------------------------------------------------------------------------\n\nexport interface MigrateOptions {\n /** Preflight + counts only. No writes, no pg_dump. */\n dryRun?: boolean;\n /** Skip the pg_dump rollback artefact. Tests only. */\n skipDump?: boolean;\n /** Skip the \"daemon must not be running\" refusal. Tests only. */\n allowRunning?: boolean;\n /** docker container name. Default \"pai-pgvector\". */\n container?: string;\n /** Override the SQLite federation.db path (tests: a scratch file). */\n sqliteFederationPath?: string;\n /** Override the SQLite registry.db path (tests: a scratch file). */\n sqliteRegistryPath?: string;\n /** Override the Postgres connection (tests: a scratch database). */\n pgConfig?: PostgresConfig;\n /** Override the daemon IPC socket path (tests). */\n socketPath?: string;\n /** Skip the storageBackend:\"postgres\" config gate (tests only). */\n skipConfigGate?: boolean;\n}\n\nexport interface TableReport {\n table: string;\n sqliteRows: number;\n postgresRowsBefore: number;\n copied: number;\n missingAfter: number;\n}\n\nexport interface MigrationResult {\n ok: boolean;\n dryRun: boolean;\n refused?: string;\n dumpPath?: string;\n tables: TableReport[];\n embeddingsNote: string;\n}\n\n// ---------------------------------------------------------------------------\n// Preflight\n// ---------------------------------------------------------------------------\n\nfunction resolvePgConnectionString(config: PostgresConfig): string {\n return (\n config.connectionString ??\n `postgresql://${config.user ?? \"pai\"}:${config.password ?? \"pai\"}@${config.host ?? \"localhost\"}:${config.port ?? 5432}/${config.database ?? \"pai\"}`\n );\n}\n\nfunction resolvePgDatabaseName(config: PostgresConfig): string {\n if (config.database) return config.database;\n return new URL(resolvePgConnectionString(config)).pathname.slice(1) || \"pai\";\n}\n\ninterface DockerMount {\n Type: string;\n Source: string;\n Destination: string;\n}\n\nfunction checkBindMount(container: string): { ok: true } | { ok: false; reason: string } {\n let raw: string;\n try {\n raw = execFileSync(\"docker\", [\"inspect\", container, \"--format\", \"{{json .Mounts}}\"], {\n encoding: \"utf-8\",\n });\n } catch (e) {\n const msg = e instanceof Error ? e.message.split(\"\\n\")[0] : String(e);\n return { ok: false, reason: `docker inspect ${container} failed — is the container running? (${msg})` };\n }\n\n let mounts: DockerMount[];\n try {\n mounts = JSON.parse(raw);\n } catch {\n return { ok: false, reason: `docker inspect ${container}: could not parse mount list` };\n }\n\n const dataMount = mounts.find((m) => m.Destination === PG_DATA_DEST);\n if (!dataMount) {\n return { ok: false, reason: `${container}: no mount at ${PG_DATA_DEST} — refusing` };\n }\n if (dataMount.Type !== \"bind\") {\n return {\n ok: false,\n reason: `${container}: ${PG_DATA_DEST} is a \"${dataMount.Type}\" mount, not \"bind\" — refusing (the backup/rollback guarantee for the destination would not hold)`,\n };\n }\n return { ok: true };\n}\n\nasync function checkDaemonNotRunning(socketPath: string): Promise<{ ok: true } | { ok: false; reason: string }> {\n const client = new PaiClient(socketPath);\n try {\n await client.call(\"status\", {}, 1500);\n return {\n ok: false,\n reason: \"PAI daemon is running — stop it first (pai daemon restart / launchctl unload) or pass --allow-running (tests only)\",\n };\n } catch {\n // ENOENT/ECONNREFUSED/timeout all mean \"nothing answered\" — daemon not running.\n return { ok: true };\n }\n}\n\nfunction pgDump(\n container: string,\n pgConfig: PostgresConfig,\n destPath: string\n): { ok: true } | { ok: false; reason: string } {\n const database = resolvePgDatabaseName(pgConfig);\n const user = pgConfig.user ?? \"pai\";\n mkdirSync(dirname(destPath), { recursive: true });\n const fd = openSync(destPath, \"w\");\n try {\n const dump = spawnSync(\n \"docker\",\n [\"exec\", container, \"pg_dump\", \"-U\", user, \"-Fc\", database],\n { stdio: [\"pipe\", fd, \"pipe\"] }\n );\n if (dump.status !== 0) {\n const msg = dump.stderr?.toString().trim() || `pg_dump exited with code ${dump.status}`;\n return { ok: false, reason: `pg_dump failed: ${msg}` };\n }\n } catch (e) {\n const msg = e instanceof Error ? e.message.split(\"\\n\")[0] : String(e);\n return { ok: false, reason: `pg_dump failed: ${msg}` };\n } finally {\n closeSync(fd);\n }\n if (!existsSync(destPath) || statSync(destPath).size === 0) {\n return { ok: false, reason: `pg_dump produced an empty file at ${destPath}` };\n }\n return { ok: true };\n}\n\n// ---------------------------------------------------------------------------\n// Generic copy-by-natural-key helper\n// ---------------------------------------------------------------------------\n\ninterface CopyPlan<TRow> {\n table: string;\n sqliteRows: TRow[];\n /** Natural-key string for a row — used to diff SQLite against Postgres. */\n keyFn: (row: TRow) => string;\n /** SELECT that returns one column: the same key string, for every existing Postgres row. */\n pgKeySql: string;\n pgKeyParams?: unknown[];\n insertSql: string;\n paramsFn: (row: TRow) => unknown[];\n}\n\nasync function countRows(pool: Pool, sql: string, params: unknown[] = []): Promise<number> {\n const r = await pool.query<{ n: string }>(sql, params);\n return parseInt(r.rows[0]?.n ?? \"0\", 10);\n}\n\n/**\n * Insert every SQLite row whose natural key is absent from Postgres, in one\n * transaction, then report sqlite/postgres-before/copied/missing-after —\n * missing-after is recomputed from Postgres after the insert, so a partial\n * failure (caught, rolled back) is visible as a non-zero count rather than a\n * silent gap.\n */\nasync function copyByKey<TRow>(pool: Pool, dryRun: boolean, plan: CopyPlan<TRow>): Promise<TableReport> {\n const sqliteRows = plan.sqliteRows;\n const pgKeysBefore = await pool.query<{ k: string }>(plan.pgKeySql, plan.pgKeyParams ?? []);\n const beforeSet = new Set(pgKeysBefore.rows.map((r) => r.k));\n const postgresRowsBefore = beforeSet.size;\n\n const missing = sqliteRows.filter((row) => !beforeSet.has(plan.keyFn(row)));\n\n let copied = 0;\n if (!dryRun && missing.length > 0) {\n const client: PoolClient = await pool.connect();\n try {\n await client.query(\"BEGIN\");\n for (const row of missing) {\n const r = await client.query(plan.insertSql, plan.paramsFn(row));\n copied += r.rowCount ?? 0;\n }\n await client.query(\"COMMIT\");\n } catch (e) {\n await client.query(\"ROLLBACK\");\n throw e;\n } finally {\n client.release();\n }\n }\n\n const pgKeysAfter = dryRun\n ? pgKeysBefore\n : await pool.query<{ k: string }>(plan.pgKeySql, plan.pgKeyParams ?? []);\n const afterSet = dryRun ? beforeSet : new Set(pgKeysAfter.rows.map((r) => r.k));\n const missingAfter = dryRun ? missing.length : sqliteRows.filter((row) => !afterSet.has(plan.keyFn(row))).length;\n\n return {\n table: plan.table,\n sqliteRows: sqliteRows.length,\n postgresRowsBefore,\n copied: dryRun ? 0 : copied,\n missingAfter,\n };\n}\n\nasync function setSerialSequence(pool: Pool, table: string, column: string): Promise<void> {\n await pool.query(\n `SELECT setval(pg_get_serial_sequence($1, $2), GREATEST(COALESCE((SELECT MAX(${column}) FROM ${table}), 1), 1))`,\n [table, column]\n );\n}\n\n// ---------------------------------------------------------------------------\n// Registry tables (projects -> tags -> sessions -> project_tags ->\n// session_tags -> aliases -> links -> compaction_log)\n// ---------------------------------------------------------------------------\n\nasync function copyRegistryTables(\n sqlite: Database,\n pool: Pool,\n dryRun: boolean,\n tables: TableReport[]\n): Promise<void> {\n type Row = Record<string, unknown>;\n const all = (table: string): Row[] => sqlite.prepare(`SELECT * FROM ${table}`).all() as Row[];\n\n tables.push(\n await copyByKey<Row>(pool, dryRun, {\n table: \"projects\",\n sqliteRows: all(\"projects\"),\n keyFn: (r) => String(r.id),\n pgKeySql: \"SELECT id::text AS k FROM projects\",\n insertSql: `INSERT INTO projects\n (id, slug, display_name, root_path, encoded_dir, type, status, parent_id, obsidian_link, claude_notes_dir, session_config, created_at, updated_at, archived_at)\n VALUES ($1,$2,$3,$4,$5,$6,$7,$8,$9,$10,$11,$12,$13,$14)\n ON CONFLICT DO NOTHING`,\n paramsFn: (r) => [\n r.id, r.slug, r.display_name, r.root_path, r.encoded_dir, r.type, r.status,\n r.parent_id, r.obsidian_link, r.claude_notes_dir, r.session_config, r.created_at, r.updated_at, r.archived_at,\n ],\n })\n );\n if (!dryRun) await setSerialSequence(pool, \"projects\", \"id\");\n\n tables.push(\n await copyByKey<Row>(pool, dryRun, {\n table: \"tags\",\n sqliteRows: all(\"tags\"),\n keyFn: (r) => String(r.id),\n pgKeySql: \"SELECT id::text AS k FROM tags\",\n insertSql: `INSERT INTO tags (id, name) VALUES ($1,$2) ON CONFLICT DO NOTHING`,\n paramsFn: (r) => [r.id, r.name],\n })\n );\n if (!dryRun) await setSerialSequence(pool, \"tags\", \"id\");\n\n tables.push(\n await copyByKey<Row>(pool, dryRun, {\n table: \"sessions\",\n sqliteRows: all(\"sessions\"),\n keyFn: (r) => String(r.id),\n pgKeySql: \"SELECT id::text AS k FROM sessions\",\n insertSql: `INSERT INTO sessions\n (id, project_id, number, date, slug, title, filename, status, claude_session_id, token_count, created_at, closed_at)\n VALUES ($1,$2,$3,$4,$5,$6,$7,$8,$9,$10,$11,$12)\n ON CONFLICT DO NOTHING`,\n paramsFn: (r) => [\n r.id, r.project_id, r.number, r.date, r.slug, r.title, r.filename, r.status,\n r.claude_session_id, r.token_count, r.created_at, r.closed_at,\n ],\n })\n );\n if (!dryRun) await setSerialSequence(pool, \"sessions\", \"id\");\n\n tables.push(\n await copyByKey<Row>(pool, dryRun, {\n table: \"project_tags\",\n sqliteRows: all(\"project_tags\"),\n keyFn: (r) => `${r.project_id}:${r.tag_id}`,\n pgKeySql: \"SELECT project_id::text || ':' || tag_id::text AS k FROM project_tags\",\n insertSql: `INSERT INTO project_tags (project_id, tag_id) VALUES ($1,$2) ON CONFLICT DO NOTHING`,\n paramsFn: (r) => [r.project_id, r.tag_id],\n })\n );\n\n tables.push(\n await copyByKey<Row>(pool, dryRun, {\n table: \"session_tags\",\n sqliteRows: all(\"session_tags\"),\n keyFn: (r) => `${r.session_id}:${r.tag_id}`,\n pgKeySql: \"SELECT session_id::text || ':' || tag_id::text AS k FROM session_tags\",\n insertSql: `INSERT INTO session_tags (session_id, tag_id) VALUES ($1,$2) ON CONFLICT DO NOTHING`,\n paramsFn: (r) => [r.session_id, r.tag_id],\n })\n );\n\n tables.push(\n await copyByKey<Row>(pool, dryRun, {\n table: \"aliases\",\n sqliteRows: all(\"aliases\"),\n keyFn: (r) => String(r.alias),\n pgKeySql: \"SELECT alias AS k FROM aliases\",\n insertSql: `INSERT INTO aliases (alias, project_id) VALUES ($1,$2) ON CONFLICT DO NOTHING`,\n paramsFn: (r) => [r.alias, r.project_id],\n })\n );\n\n tables.push(\n await copyByKey<Row>(pool, dryRun, {\n table: \"links\",\n sqliteRows: all(\"links\"),\n keyFn: (r) => String(r.id),\n pgKeySql: \"SELECT id::text AS k FROM links\",\n insertSql: `INSERT INTO links (id, session_id, target_project_id, link_type, created_at)\n VALUES ($1,$2,$3,$4,$5) ON CONFLICT DO NOTHING`,\n paramsFn: (r) => [r.id, r.session_id, r.target_project_id, r.link_type, r.created_at],\n })\n );\n if (!dryRun) await setSerialSequence(pool, \"links\", \"id\");\n\n tables.push(\n await copyByKey<Row>(pool, dryRun, {\n table: \"compaction_log\",\n sqliteRows: all(\"compaction_log\"),\n keyFn: (r) => String(r.id),\n pgKeySql: \"SELECT id::text AS k FROM compaction_log\",\n insertSql: `INSERT INTO compaction_log (id, project_id, session_id, trigger, files_written, token_count, created_at)\n VALUES ($1,$2,$3,$4,$5,$6,$7) ON CONFLICT DO NOTHING`,\n paramsFn: (r) => [r.id, r.project_id, r.session_id, r.trigger, r.files_written, r.token_count, r.created_at],\n })\n );\n if (!dryRun) await setSerialSequence(pool, \"compaction_log\", \"id\");\n}\n\n// ---------------------------------------------------------------------------\n// kg_entities\n// ---------------------------------------------------------------------------\n\nasync function copyKgEntities(sqlite: Database, pool: Pool, dryRun: boolean, tables: TableReport[]): Promise<void> {\n type Row = Record<string, unknown>;\n const rows = sqlite.prepare(\"SELECT * FROM kg_entities\").all() as Row[];\n\n tables.push(\n await copyByKey<Row>(pool, dryRun, {\n table: \"kg_entities\",\n sqliteRows: rows,\n keyFn: (r) => String(r.entity_id),\n pgKeySql: \"SELECT entity_id AS k FROM kg_entities\",\n insertSql: `INSERT INTO kg_entities\n (entity_id, tenant_id, name, type, description, first_seen, last_seen, mention_count, feedback_weight)\n VALUES ($1,$2,$3,$4,$5,$6,$7,$8,$9)\n ON CONFLICT (entity_id) DO NOTHING`,\n paramsFn: (r) => [\n r.entity_id, r.tenant_id, r.name, r.type, r.description,\n r.first_seen, r.last_seen, r.mention_count, r.feedback_weight,\n ],\n })\n );\n}\n\n// ---------------------------------------------------------------------------\n// memory_files/memory_chunks -> pai_files/pai_chunks\n//\n// Missing is decided at the (project_id, path) file level, not per chunk: a\n// path already indexed into Postgres may have been re-chunked differently on\n// a later run, so only paths Postgres has never seen at all are copied —\n// file row plus every chunk under that path.\n// ---------------------------------------------------------------------------\n\n/**\n * SQLite chunks may carry embeddings from an older model with a different\n * dimension than pai_chunks.embedding (vector(N)) — inserting a mismatched\n * literal fails the whole batch. `dimension` is the live column width; a\n * blob whose float count doesn't match it is treated the same as \"no\n * embedding\" so the row still copies and the daemon re-embeds it.\n */\nfunction blobToVectorLiteral(buf: Buffer | null, dimension: number | null): string | null {\n if (!buf || buf.length === 0 || buf.length % 4 !== 0) return null;\n const floatCount = buf.length / 4;\n if (dimension !== null && floatCount !== dimension) return null;\n const floats: number[] = [];\n for (let i = 0; i < buf.length; i += 4) floats.push(buf.readFloatLE(i));\n return \"[\" + floats.join(\",\") + \"]\";\n}\n\n/** Live width of pai_chunks.embedding (vector(N)), read once per run. Null if the column type can't be parsed. */\nasync function resolvePaiChunksEmbeddingDimension(pool: Pool): Promise<number | null> {\n const r = await pool.query<{ type: string }>(\n `SELECT format_type(atttypid, atttypmod) AS type\n FROM pg_attribute\n WHERE attrelid = 'pai_chunks'::regclass AND attname = 'embedding' AND NOT attisdropped`\n );\n const m = /vector\\((\\d+)\\)/.exec(r.rows[0]?.type ?? \"\");\n return m ? parseInt(m[1], 10) : null;\n}\n\nasync function copyMemoryFilesAndChunks(\n sqlite: Database,\n pool: Pool,\n dryRun: boolean,\n tables: TableReport[],\n embeddingDimension: number | null\n): Promise<string> {\n type FileRow = { project_id: number; path: string; source: string; tier: string; hash: string; mtime: number; size: number };\n type ChunkRow = {\n id: string; project_id: number; source: string; tier: string; path: string;\n start_line: number; end_line: number; hash: string; text: string; updated_at: number;\n embedding: Buffer | null;\n };\n\n const fileRows = sqlite.prepare(\"SELECT * FROM memory_files\").all() as FileRow[];\n const fileKey = (r: { project_id: number; path: string }) => `${r.project_id}:${r.path}`;\n\n const pgFileKeys = await pool.query<{ k: string }>(\"SELECT project_id::text || ':' || path AS k FROM pai_files\");\n const pgFileKeySet = new Set(pgFileKeys.rows.map((r) => r.k));\n const missingFiles = fileRows.filter((r) => !pgFileKeySet.has(fileKey(r)));\n const missingFileKeySet = new Set(missingFiles.map(fileKey));\n\n const allChunkRows = sqlite.prepare(\"SELECT * FROM memory_chunks\").all() as ChunkRow[];\n const chunksToCopy = allChunkRows.filter((c) => missingFileKeySet.has(fileKey(c)));\n\n let filesCopied = 0;\n let chunksCopied = 0;\n let chunksWithEmbedding = 0;\n let chunksWithoutEmbedding = 0;\n\n if (!dryRun && missingFiles.length > 0) {\n const client: PoolClient = await pool.connect();\n try {\n await client.query(\"BEGIN\");\n for (const f of missingFiles) {\n const r = await client.query(\n `INSERT INTO pai_files (project_id, path, source, tier, hash, mtime, size)\n VALUES ($1,$2,$3,$4,$5,$6,$7) ON CONFLICT (project_id, path) DO NOTHING`,\n [f.project_id, f.path, f.source, f.tier, f.hash, f.mtime, f.size]\n );\n filesCopied += r.rowCount ?? 0;\n }\n for (const c of chunksToCopy) {\n const vec = blobToVectorLiteral(c.embedding, embeddingDimension);\n if (vec) chunksWithEmbedding++;\n else chunksWithoutEmbedding++;\n const r = await client.query(\n `INSERT INTO pai_chunks (id, project_id, source, tier, path, start_line, end_line, hash, text, updated_at, embedding)\n VALUES ($1,$2,$3,$4,$5,$6,$7,$8,$9,$10,$11::vector)\n ON CONFLICT (id) DO NOTHING`,\n [c.id, c.project_id, c.source, c.tier, c.path, c.start_line, c.end_line, c.hash, c.text.replace(/\\0/g, \"\"), c.updated_at, vec]\n );\n chunksCopied += r.rowCount ?? 0;\n }\n await client.query(\"COMMIT\");\n } catch (e) {\n await client.query(\"ROLLBACK\");\n throw e;\n } finally {\n client.release();\n }\n } else {\n for (const c of chunksToCopy) {\n if (blobToVectorLiteral(c.embedding, embeddingDimension)) chunksWithEmbedding++;\n else chunksWithoutEmbedding++;\n }\n }\n\n const pgFileKeysAfter = dryRun\n ? pgFileKeys\n : await pool.query<{ k: string }>(\"SELECT project_id::text || ':' || path AS k FROM pai_files\");\n const pgFileKeySetAfter = dryRun ? pgFileKeySet : new Set(pgFileKeysAfter.rows.map((r) => r.k));\n const missingFilesAfter = fileRows.filter((r) => !pgFileKeySetAfter.has(fileKey(r))).length;\n\n tables.push({\n table: \"memory_files -> pai_files\",\n sqliteRows: fileRows.length,\n postgresRowsBefore: pgFileKeySet.size,\n copied: dryRun ? 0 : filesCopied,\n missingAfter: dryRun ? missingFiles.length : missingFilesAfter,\n });\n\n const pgChunkCountBefore = await countRows(pool, \"SELECT COUNT(*)::text AS n FROM pai_chunks\");\n let missingChunksAfter = 0;\n if (!dryRun) {\n const pgChunkIds = await pool.query<{ id: string }>(\n \"SELECT id FROM pai_chunks WHERE id = ANY($1)\",\n [chunksToCopy.map((c) => c.id)]\n );\n const pgChunkIdSet = new Set(pgChunkIds.rows.map((r) => r.id));\n missingChunksAfter = chunksToCopy.filter((c) => !pgChunkIdSet.has(c.id)).length;\n } else {\n missingChunksAfter = chunksToCopy.length;\n }\n\n tables.push({\n table: \"memory_chunks -> pai_chunks\",\n sqliteRows: allChunkRows.length,\n postgresRowsBefore: pgChunkCountBefore,\n copied: dryRun ? 0 : chunksCopied,\n missingAfter: missingChunksAfter,\n });\n\n return `memory_chunks: ${chunksToCopy.length} chunk(s) eligible for copy under missing paths — ` +\n `${chunksWithEmbedding} copied with embedding, ${chunksWithoutEmbedding} copied without ` +\n `(dimension mismatch, re-embedded by the daemon).`;\n}\n\n// ---------------------------------------------------------------------------\n// Vault tables (vault_files, vault_aliases, vault_links, vault_name_index,\n// vault_health) — legacy SQLite tables from pre-Postgres-vault installs.\n// Migrated only if the SQLite db still physically has them.\n// ---------------------------------------------------------------------------\n\nfunction sqliteHasTable(sqlite: Database, table: string): boolean {\n return !!sqlite.prepare(\"SELECT 1 FROM sqlite_master WHERE type='table' AND name = ?\").get(table);\n}\n\nasync function copyVaultTables(sqlite: Database, pool: Pool, dryRun: boolean, tables: TableReport[]): Promise<void> {\n type Row = Record<string, unknown>;\n const all = (table: string): Row[] => sqlite.prepare(`SELECT * FROM ${table}`).all() as Row[];\n\n if (sqliteHasTable(sqlite, \"vault_files\")) {\n tables.push(\n await copyByKey<Row>(pool, dryRun, {\n table: \"vault_files\",\n sqliteRows: all(\"vault_files\"),\n keyFn: (r) => String(r.vault_path),\n pgKeySql: \"SELECT vault_path AS k FROM vault_files\",\n insertSql: `INSERT INTO vault_files (vault_path, inode, device, hash, title, indexed_at)\n VALUES ($1,$2,$3,$4,$5,$6) ON CONFLICT (vault_path) DO NOTHING`,\n paramsFn: (r) => [r.vault_path, r.inode, r.device, r.hash, r.title, r.indexed_at],\n })\n );\n }\n\n if (sqliteHasTable(sqlite, \"vault_aliases\")) {\n tables.push(\n await copyByKey<Row>(pool, dryRun, {\n table: \"vault_aliases\",\n sqliteRows: all(\"vault_aliases\"),\n keyFn: (r) => String(r.vault_path),\n pgKeySql: \"SELECT vault_path AS k FROM vault_aliases\",\n insertSql: `INSERT INTO vault_aliases (vault_path, canonical_path, inode, device)\n VALUES ($1,$2,$3,$4) ON CONFLICT (vault_path) DO NOTHING`,\n paramsFn: (r) => [r.vault_path, r.canonical_path, r.inode, r.device],\n })\n );\n }\n\n if (sqliteHasTable(sqlite, \"vault_links\")) {\n tables.push(\n await copyByKey<Row>(pool, dryRun, {\n table: \"vault_links\",\n sqliteRows: all(\"vault_links\"),\n keyFn: (r) => `${r.source_path}\\u0001${r.target_raw}\\u0001${r.line_number}`,\n pgKeySql: \"SELECT source_path || chr(1) || target_raw || chr(1) || line_number::text AS k FROM vault_links\",\n insertSql: `INSERT INTO vault_links (source_path, target_raw, target_path, link_type, line_number)\n VALUES ($1,$2,$3,$4,$5) ON CONFLICT (source_path, target_raw, line_number) DO NOTHING`,\n paramsFn: (r) => [r.source_path, r.target_raw, r.target_path, r.link_type, r.line_number],\n })\n );\n }\n\n if (sqliteHasTable(sqlite, \"vault_name_index\")) {\n tables.push(\n await copyByKey<Row>(pool, dryRun, {\n table: \"vault_name_index\",\n sqliteRows: all(\"vault_name_index\"),\n keyFn: (r) => `${r.name}\\u0001${r.vault_path}`,\n pgKeySql: \"SELECT name || chr(1) || vault_path AS k FROM vault_name_index\",\n insertSql: `INSERT INTO vault_name_index (name, vault_path) VALUES ($1,$2) ON CONFLICT (name, vault_path) DO NOTHING`,\n paramsFn: (r) => [r.name, r.vault_path],\n })\n );\n }\n\n if (sqliteHasTable(sqlite, \"vault_health\")) {\n tables.push(\n await copyByKey<Row>(pool, dryRun, {\n table: \"vault_health\",\n sqliteRows: all(\"vault_health\"),\n keyFn: (r) => String(r.vault_path),\n pgKeySql: \"SELECT vault_path AS k FROM vault_health\",\n insertSql: `INSERT INTO vault_health (vault_path, inbound_count, outbound_count, dead_link_count, is_orphan, computed_at)\n VALUES ($1,$2,$3,$4,$5,$6) ON CONFLICT (vault_path) DO NOTHING`,\n paramsFn: (r) => [r.vault_path, r.inbound_count, r.outbound_count, r.dead_link_count, r.is_orphan, r.computed_at],\n })\n );\n }\n}\n\n// ---------------------------------------------------------------------------\n// Spot check: 20 random rows per key-preserving table, field-by-field\n// ---------------------------------------------------------------------------\n\nexport interface SpotCheckMismatch {\n table: string;\n key: string;\n field: string;\n sqliteValue: unknown;\n postgresValue: unknown;\n}\n\n/**\n * For kg_entities and projects (the two tables §4 step 6 calls out by name):\n * pull up to 20 random rows from SQLite and confirm every field matches the\n * Postgres row exactly. Returns the mismatches found (empty = all match).\n */\nasync function spotCheck(sqlite: Database, pool: Pool, table: string, keyCol: string): Promise<SpotCheckMismatch[]> {\n const sample = sqlite\n .prepare(`SELECT * FROM ${table} ORDER BY RANDOM() LIMIT 20`)\n .all() as Array<Record<string, unknown>>;\n const mismatches: SpotCheckMismatch[] = [];\n\n for (const row of sample) {\n const key = row[keyCol];\n const r = await pool.query(`SELECT * FROM ${table} WHERE ${keyCol} = $1`, [key]);\n const pgRow = r.rows[0] as Record<string, unknown> | undefined;\n if (!pgRow) {\n mismatches.push({ table, key: String(key), field: keyCol, sqliteValue: key, postgresValue: undefined });\n continue;\n }\n for (const field of Object.keys(row)) {\n if (!(field in pgRow)) continue;\n const a = row[field];\n const b = pgRow[field];\n // BIGINT columns come back as strings from pg; normalise before compare.\n const normA = typeof a === \"number\" ? String(a) : a;\n const normB = typeof b === \"number\" ? String(b) : b;\n if (String(normA ?? \"\") !== String(normB ?? \"\")) {\n mismatches.push({ table, key: String(key), field, sqliteValue: a, postgresValue: b });\n }\n }\n }\n return mismatches;\n}\n\n// ---------------------------------------------------------------------------\n// Entry point\n// ---------------------------------------------------------------------------\n\nfunction refused(reason: string, dryRun: boolean): MigrationResult {\n return { ok: false, dryRun, refused: reason, tables: [], embeddingsNote: \"\" };\n}\n\nexport async function migrateToPostgres(opts: MigrateOptions = {}): Promise<MigrationResult> {\n const config = loadConfig();\n const container = opts.container ?? DEFAULT_CONTAINER;\n const dryRun = opts.dryRun ?? false;\n\n if (!opts.skipConfigGate && config.storageBackend !== \"postgres\") {\n return refused('config storageBackend is not \"postgres\" — set it first, then re-run', dryRun);\n }\n const pgConfig: PostgresConfig = opts.pgConfig ?? config.postgres ?? {};\n\n const mountCheck = checkBindMount(container);\n if (!mountCheck.ok) return refused(mountCheck.reason, dryRun);\n\n // Read-only counting cannot lose a concurrent write the way a real copy\n // can (see docs/design/postgres-only.md §6) — only the write path needs\n // the daemon stopped.\n if (!dryRun && !opts.allowRunning) {\n const daemonCheck = await checkDaemonNotRunning(opts.socketPath ?? config.socketPath);\n if (!daemonCheck.ok) return refused(daemonCheck.reason, dryRun);\n }\n\n const sqliteFederationPath = opts.sqliteFederationPath ?? federationDbPath();\n const sqliteRegistryPath = opts.sqliteRegistryPath ?? registryDbPath();\n if (!existsSync(sqliteFederationPath)) {\n return refused(`federation SQLite db not found at ${sqliteFederationPath}`, dryRun);\n }\n if (!existsSync(sqliteRegistryPath)) {\n return refused(`registry SQLite db not found at ${sqliteRegistryPath}`, dryRun);\n }\n\n let dumpPath: string | undefined;\n if (!dryRun && !opts.skipDump) {\n const stamp = new Date().toISOString().replace(/[:.]/g, \"-\");\n dumpPath = paiHomePath(\"backups\", `pg-pre-migration-${stamp}.dump`);\n const dump = pgDump(container, pgConfig, dumpPath);\n if (!dump.ok) return refused(dump.reason, dryRun);\n }\n\n // Idempotent DDL only (CREATE TABLE/DATABASE IF NOT EXISTS) — safe before a\n // dry-run count too, since it never touches existing rows. Every other\n // entry point (factory.ts) already calls this unconditionally.\n const { PostgresBackend } = await import(\"./postgres.js\");\n await PostgresBackend.ensureDatabase(pgConfig);\n\n const sqlite = new BetterSqlite3(sqliteFederationPath, { readonly: true });\n const registrySqlite =\n sqliteRegistryPath === sqliteFederationPath ? sqlite : new BetterSqlite3(sqliteRegistryPath, { readonly: true });\n const pool = new PgPool({\n connectionString: resolvePgConnectionString(pgConfig),\n max: 4,\n connectionTimeoutMillis: pgConfig.connectionTimeoutMs ?? 5000,\n });\n\n const tables: TableReport[] = [];\n let embeddingsNote = \"\";\n\n try {\n await copyRegistryTables(registrySqlite, pool, dryRun, tables);\n await copyKgEntities(sqlite, pool, dryRun, tables);\n const embeddingDimension = await resolvePaiChunksEmbeddingDimension(pool);\n embeddingsNote = await copyMemoryFilesAndChunks(sqlite, pool, dryRun, tables, embeddingDimension);\n await copyVaultTables(sqlite, pool, dryRun, tables);\n\n let spotCheckOk = true;\n if (!dryRun) {\n const kgMismatches = await spotCheck(sqlite, pool, \"kg_entities\", \"entity_id\");\n const projectMismatches = await spotCheck(registrySqlite, pool, \"projects\", \"id\");\n const mismatches = [...kgMismatches, ...projectMismatches];\n spotCheckOk = mismatches.length === 0;\n if (!spotCheckOk) {\n embeddingsNote +=\n ` SPOT CHECK FAILED: ${mismatches.length} field mismatch(es) — ` +\n mismatches.slice(0, 5).map((m) => `${m.table}.${m.key}.${m.field}`).join(\", \");\n }\n }\n\n // A dry run's \"missing\" count is the whole point of running it — it is\n // not a failure. Only a real copy is expected to reach zero missing.\n const missingTotal = tables.reduce((n, t) => n + t.missingAfter, 0);\n return {\n ok: dryRun ? true : missingTotal === 0 && spotCheckOk,\n dryRun,\n dumpPath,\n tables,\n embeddingsNote,\n };\n } finally {\n sqlite.close();\n if (registrySqlite !== sqlite) registrySqlite.close();\n await pool.end();\n }\n}\n\n// ---------------------------------------------------------------------------\n// Report rendering\n// ---------------------------------------------------------------------------\n\nexport function renderReport(result: MigrationResult): string {\n const lines: string[] = [];\n if (result.refused) {\n lines.push(`REFUSED: ${result.refused}`);\n return lines.join(\"\\n\");\n }\n\n lines.push(result.dryRun ? \"Dry run — no writes, no dump.\" : \"Migration complete.\");\n if (result.dumpPath) lines.push(`Rollback dump: ${result.dumpPath}`);\n lines.push(\"\");\n const header = [\"table\", \"sqlite_rows\", \"postgres_before\", \"copied\", \"missing_after\"];\n lines.push(header.join(\"\\t\"));\n for (const t of result.tables) {\n lines.push([t.table, t.sqliteRows, t.postgresRowsBefore, t.copied, t.missingAfter].join(\"\\t\"));\n }\n lines.push(\"\");\n lines.push(result.embeddingsNote);\n lines.push(\"\");\n if (result.dryRun) {\n lines.push(\"Dry run complete — \\\"missing_after\\\" is how many rows a real run would copy.\");\n } else {\n lines.push(result.ok ? \"OK: no missing rows.\" : \"FAILED: missing rows after copy — see table above.\");\n }\n return lines.join(\"\\n\");\n}\n","/**\n * Raw SQL introspection for `pai db query|tables|schema` — the one place\n * outside the backend classes themselves allowed to reach for the\n * underlying engine handle, so cli/commands/db.ts never has to import\n * better-sqlite3/pg or open its own connection (it goes through the\n * process-wide backend from storage/factory.ts instead).\n */\n\nimport type { StorageBackend } from \"./interface.js\";\nimport type { SQLiteBackend } from \"./sqlite.js\";\nimport type { PostgresBackend } from \"./postgres.js\";\n\nexport interface TableResult {\n columns: string[];\n rows: unknown[][];\n}\n\nfunction asSqlite(backend: StorageBackend): SQLiteBackend {\n return backend as unknown as SQLiteBackend;\n}\n\nfunction asPostgresPool(backend: StorageBackend) {\n return (backend as unknown as PostgresBackend).getPool();\n}\n\nexport async function runDbQuery(backend: StorageBackend, sql: string): Promise<TableResult> {\n if (backend.backendType === \"postgres\") {\n const result = await asPostgresPool(backend).query(sql);\n const columns = result.fields.map((f) => f.name);\n const rows = result.rows.map((r: Record<string, unknown>) => columns.map((c) => r[c]));\n return { columns, rows };\n }\n const db = asSqlite(backend).getRawDb();\n const stmt = db.prepare(sql);\n const raw = stmt.all() as Record<string, unknown>[];\n if (raw.length === 0) return { columns: [], rows: [] };\n const columns = Object.keys(raw[0]);\n const rows = raw.map((r) => columns.map((c) => r[c]));\n return { columns, rows };\n}\n\nexport async function listDbTables(backend: StorageBackend): Promise<string[]> {\n if (backend.backendType === \"postgres\") {\n const result = await asPostgresPool(backend).query<{ tablename: string }>(\n \"SELECT tablename FROM pg_tables WHERE schemaname = 'public' ORDER BY tablename\"\n );\n return result.rows.map((r) => r.tablename);\n }\n const db = asSqlite(backend).getRawDb();\n const rows = db\n .prepare(\"SELECT name FROM sqlite_master WHERE type='table' AND name NOT LIKE 'sqlite_%' ORDER BY name\")\n .all() as { name: string }[];\n return rows.map((r) => r.name);\n}\n\nexport async function getDbTableSchema(backend: StorageBackend, table: string): Promise<TableResult> {\n if (backend.backendType === \"postgres\") {\n const result = await asPostgresPool(backend).query<{\n column_name: string;\n data_type: string;\n is_nullable: string;\n column_default: string | null;\n }>(\n `SELECT column_name, data_type, is_nullable, column_default\n FROM information_schema.columns\n WHERE table_schema = 'public' AND table_name = $1\n ORDER BY ordinal_position`,\n [table]\n );\n if (result.rows.length === 0) {\n throw new Error(`Table not found in public schema: ${table}`);\n }\n const columns = [\"column\", \"type\", \"nullable\", \"default\"];\n const rows = result.rows.map((r) => [\n r.column_name,\n r.data_type,\n r.is_nullable === \"YES\" ? \"YES\" : \"NO\",\n r.column_default ?? \"\",\n ]);\n return { columns, rows };\n }\n\n const db = asSqlite(backend).getRawDb();\n // Validate table name — only allow alphanumeric / underscores\n if (!/^[a-zA-Z_][a-zA-Z0-9_]*$/.test(table)) {\n throw new Error(`Invalid table name: ${table}`);\n }\n const rows = db.prepare(`PRAGMA table_info(${table})`).all() as {\n cid: number;\n name: string;\n type: string;\n notnull: number;\n dflt_value: string | null;\n pk: number;\n }[];\n if (rows.length === 0) {\n throw new Error(`Table not found: ${table}`);\n }\n const columns = [\"cid\", \"name\", \"type\", \"notnull\", \"default\", \"pk\"];\n const outRows = rows.map((r) => [\n r.cid,\n r.name,\n r.type,\n r.notnull ? \"NOT NULL\" : \"\",\n r.dflt_value ?? \"\",\n r.pk ? \"PK\" : \"\",\n ]);\n return { columns, rows: outRows };\n}\n","/**\n * pai db <sub-command>\n *\n * Quick database inspection commands for both SQLite and Postgres backends.\n *\n * pai db query <sql> [--db sqlite|postgres] [--json]\n * pai db tables [--db sqlite|postgres]\n * pai db schema <table> [--db sqlite|postgres]\n */\n\nimport type { Command } from \"commander\";\nimport { ok, warn, err, dim, bold, header, renderTable } from \"../utils.js\";\nimport { loadConfig } from \"../../daemon/config.js\";\nimport { migrateToPostgres, renderReport } from \"../../storage/migrate-to-postgres.js\";\nimport { createStorageBackend } from \"../../storage/factory.js\";\nimport { runDbQuery, listDbTables, getDbTableSchema } from \"../../storage/db-admin.js\";\nimport type { StorageBackend } from \"../../storage/interface.js\";\n\n// ---------------------------------------------------------------------------\n// Types\n// ---------------------------------------------------------------------------\n\ntype DbTarget = \"sqlite\" | \"postgres\";\n\ninterface CommonOpts {\n db?: DbTarget;\n json?: boolean;\n}\n\n// ---------------------------------------------------------------------------\n// Backend-agnostic helpers — a one-off backend for the requested target\n// (independent of the process's configured backend, since this tool is for\n// inspecting either engine), created via storage/factory.ts's single entry\n// point so this file never opens its own better-sqlite3/pg connection.\n// ---------------------------------------------------------------------------\n\nasync function backendForTarget(target: DbTarget): Promise<StorageBackend> {\n const config = loadConfig();\n return createStorageBackend({ ...config, storageBackend: target });\n}\n\n// ---------------------------------------------------------------------------\n// Output helpers\n// ---------------------------------------------------------------------------\n\nfunction printResult(\n result: { columns: string[]; rows: unknown[][] },\n json: boolean\n): void {\n if (json) {\n const objects = result.rows.map((row) => {\n const obj: Record<string, unknown> = {};\n result.columns.forEach((col, i) => {\n obj[col] = row[i];\n });\n return obj;\n });\n console.log(JSON.stringify(objects, null, 2));\n return;\n }\n\n if (result.rows.length === 0) {\n console.log();\n console.log(dim(\" (no rows)\"));\n console.log();\n return;\n }\n\n const stringRows = result.rows.map((row) =>\n row.map((cell) => {\n if (cell === null || cell === undefined) return dim(\"NULL\");\n if (typeof cell === \"object\") return JSON.stringify(cell);\n return String(cell);\n })\n );\n\n console.log();\n console.log(renderTable(result.columns, stringRows));\n console.log();\n console.log(dim(` ${result.rows.length} row(s)`));\n console.log();\n}\n\n// ---------------------------------------------------------------------------\n// pai db query\n// ---------------------------------------------------------------------------\n\nasync function cmdQuery(\n sql: string,\n opts: CommonOpts\n): Promise<void> {\n const target: DbTarget = opts.db ?? \"sqlite\";\n console.log();\n console.log(\n header(` Query [${target}]`) + dim(` ${sql.slice(0, 80)}${sql.length > 80 ? \"…\" : \"\"}`)\n );\n\n try {\n let result: { columns: string[]; rows: unknown[][] };\n const backend = await backendForTarget(target);\n try {\n result = await runDbQuery(backend, sql);\n } finally {\n await backend.close();\n }\n printResult(result, opts.json ?? false);\n } catch (e) {\n const msg = e instanceof Error ? e.message : String(e);\n console.error(err(` ${msg}`));\n process.exitCode = 1;\n return;\n }\n}\n\n// ---------------------------------------------------------------------------\n// pai db tables\n// ---------------------------------------------------------------------------\n\nasync function cmdTables(opts: CommonOpts): Promise<void> {\n const target: DbTarget = opts.db ?? \"sqlite\";\n console.log();\n console.log(header(` Tables [${target}]`));\n console.log();\n\n try {\n let tables: string[];\n const backend = await backendForTarget(target);\n try {\n tables = await listDbTables(backend);\n } finally {\n await backend.close();\n }\n\n if (opts.json) {\n console.log(JSON.stringify(tables, null, 2));\n return;\n }\n\n if (tables.length === 0) {\n console.log(dim(\" (no tables found)\"));\n } else {\n for (const t of tables) {\n console.log(` ${bold(t)}`);\n }\n }\n console.log();\n console.log(dim(` ${tables.length} table(s)`));\n console.log();\n } catch (e) {\n const msg = e instanceof Error ? e.message : String(e);\n console.error(err(` ${msg}`));\n process.exitCode = 1;\n return;\n }\n}\n\n// ---------------------------------------------------------------------------\n// pai db schema\n// ---------------------------------------------------------------------------\n\nasync function cmdSchema(table: string, opts: CommonOpts): Promise<void> {\n const target: DbTarget = opts.db ?? \"sqlite\";\n console.log();\n console.log(header(` Schema: ${table} [${target}]`));\n\n try {\n let result: { columns: string[]; rows: unknown[][] };\n const backend = await backendForTarget(target);\n try {\n result = await getDbTableSchema(backend, table);\n } finally {\n await backend.close();\n }\n printResult(result, opts.json ?? false);\n } catch (e) {\n const msg = e instanceof Error ? e.message : String(e);\n console.error(err(` ${msg}`));\n process.exitCode = 1;\n return;\n }\n}\n\n// ---------------------------------------------------------------------------\n// Commander registration\n// ---------------------------------------------------------------------------\n\nexport function registerDbCommands(dbCmd: Command): void {\n dbCmd\n .command(\"query <sql>\")\n .description(\"Run a SQL query against the selected database\")\n .option(\"--db <target>\", \"Database target: sqlite (default) or postgres\", \"sqlite\")\n .option(\"--json\", \"Output results as JSON array\")\n .action(async (sql: string, opts: CommonOpts) => {\n await cmdQuery(sql, opts);\n });\n\n dbCmd\n .command(\"tables\")\n .description(\"List all tables in the selected database\")\n .option(\"--db <target>\", \"Database target: sqlite (default) or postgres\", \"sqlite\")\n .option(\"--json\", \"Output as JSON array\")\n .action(async (opts: CommonOpts) => {\n await cmdTables(opts);\n });\n\n dbCmd\n .command(\"schema <table>\")\n .description(\"Show column schema for a table\")\n .option(\"--db <target>\", \"Database target: sqlite (default) or postgres\", \"sqlite\")\n .option(\"--json\", \"Output as JSON array\")\n .action(async (table: string, opts: CommonOpts) => {\n await cmdSchema(table, opts);\n });\n\n dbCmd\n .command(\"migrate-to-postgres\")\n .description(\"One-shot migration of kg_entities, registry tables, and memory/vault rows from SQLite to Postgres\")\n .option(\"--dry-run\", \"Preflight + counts only — no writes, no pg_dump\")\n .option(\"--skip-dump\", \"Skip the pg_dump rollback artefact (tests only)\")\n .option(\"--allow-running\", \"Skip the daemon-not-running refusal (tests only)\")\n .action(async (opts: { dryRun?: boolean; skipDump?: boolean; allowRunning?: boolean }) => {\n const result = await migrateToPostgres({\n dryRun: opts.dryRun,\n skipDump: opts.skipDump,\n allowRunning: opts.allowRunning,\n });\n console.log(renderReport(result));\n if (!result.ok) process.exitCode = 1;\n });\n}\n","/**\n * pai help [area]\n *\n * Rich man-page viewer for the generated command docs under docs/commands/.\n * These pages are generated from the live Commander tree by\n * scripts/build-docs.mjs, so they never drift from the actual CLI.\n *\n * pai help → list all command areas (the index / apropos)\n * pai help <area> → print the man page for that area\n *\n * Falls back to `pai <area> --help` if a page is missing.\n */\n\nimport type { Command } from \"commander\";\nimport { readFileSync, existsSync, readdirSync } from \"node:fs\";\nimport { join, dirname } from \"node:path\";\nimport { fileURLToPath } from \"node:url\";\nimport chalk from \"chalk\";\nimport { err, dim } from \"../utils.js\";\n\n/**\n * Locate the docs/commands directory robustly, regardless of how the bundle is\n * chunked. Walks up from this module's location (and from argv[1]) looking for a\n * directory that contains docs/commands.\n */\nfunction findDocsDir(): string | null {\n const candidates: string[] = [];\n try {\n candidates.push(dirname(fileURLToPath(import.meta.url)));\n } catch {\n /* ignore */\n }\n if (process.argv[1]) candidates.push(dirname(process.argv[1]));\n\n for (const start of candidates) {\n let dir = start;\n for (let i = 0; i < 6; i++) {\n const probe = join(dir, \"docs\", \"commands\");\n if (existsSync(probe)) return probe;\n const parent = dirname(dir);\n if (parent === dir) break;\n dir = parent;\n }\n }\n return null;\n}\n\n/**\n * Light terminal styling for the generated markdown — enough to read like a man\n * page without a full markdown renderer. Headings bold, table rules + fences\n * dimmed, blockquotes dimmed.\n */\nfunction inlineBold(s: string): string {\n return s\n .replace(/\\\\\\|/g, \"|\")\n .replace(/\\*\\*([^*]+)\\*\\*/g, (_m, t) => chalk.bold(t));\n}\n\nfunction renderMarkdown(md: string): string {\n const out: string[] = [];\n let inFence = false;\n for (const line of md.split(\"\\n\")) {\n // Drop the generator's HTML comment header.\n if (/^\\s*<!--.*-->\\s*$/.test(line)) continue;\n // Code fences: suppress the ``` markers, colourise the body.\n if (/^```/.test(line)) {\n inFence = !inFence;\n continue;\n }\n if (inFence) {\n out.push(chalk.cyan(line));\n continue;\n }\n if (/^#{1,6}\\s/.test(line)) {\n out.push(chalk.bold(line.replace(/^#{1,6}\\s/, \"\")));\n continue;\n }\n if (/^>\\s?/.test(line)) {\n out.push(dim(line.replace(/^>\\s?/, \"\")));\n continue;\n }\n // Markdown table rule rows.\n if (/^\\|?\\s*[-:]+\\s*(\\|\\s*[-:]+\\s*)+\\|?$/.test(line)) {\n out.push(dim(line));\n continue;\n }\n out.push(inlineBold(line));\n }\n // Collapse leading blank lines.\n while (out.length > 0 && out[0].trim() === \"\") out.shift();\n return out.join(\"\\n\");\n}\n\nfunction listAreas(docsDir: string): string[] {\n return readdirSync(docsDir)\n .filter((f) => f.endsWith(\".md\") && f !== \"README.md\")\n .map((f) => f.replace(/\\.md$/, \"\"))\n .sort();\n}\n\nexport function registerHelpCommand(program: Command): void {\n program\n .command(\"help [area]\")\n .description(\"Show the man page for a command area (pai help <area>), or list all areas\")\n .option(\"--raw\", \"Print raw markdown without terminal styling\")\n .action((area: string | undefined, opts: { raw?: boolean }) => {\n const docsDir = findDocsDir();\n\n if (!docsDir) {\n console.error(err(\" Command docs not found.\"));\n console.error(dim(\" Run `bun run build` to generate them, or use `pai <area> --help`.\"));\n process.exitCode = 1;\n return;\n }\n\n // No area → print the index.\n if (!area) {\n const indexPath = join(docsDir, \"README.md\");\n if (existsSync(indexPath)) {\n const md = readFileSync(indexPath, \"utf8\");\n console.log(opts.raw ? md : renderMarkdown(md));\n } else {\n console.log(chalk.bold(\"\\nPAI command areas\\n\"));\n for (const a of listAreas(docsDir)) console.log(` ${a}`);\n console.log(dim(\"\\n pai help <area> show the man page for an area\"));\n }\n return;\n }\n\n // Area → print its page, or fall back.\n const page = join(docsDir, `${area}.md`);\n if (existsSync(page)) {\n const md = readFileSync(page, \"utf8\");\n console.log(opts.raw ? md : renderMarkdown(md));\n return;\n }\n\n console.error(err(` No man page for \"${area}\".`));\n const areas = listAreas(docsDir);\n const near = areas.filter((a) => a.includes(area) || area.includes(a));\n if (near.length > 0) {\n console.error(dim(` Did you mean: ${near.join(\", \")}?`));\n } else {\n console.error(dim(` Available: ${areas.join(\", \")}`));\n }\n console.error(dim(` Or try: pai ${area} --help`));\n process.exitCode = 1;\n });\n}\n","/**\n * Slug generator for PAI session notes.\n *\n * Extracts a 1-3 word descriptive slug from Claude Code JSONL transcripts\n * using keyword frequency analysis — no LLM required.\n *\n * JSONL format (one JSON object per line):\n * - type \"user\": { type: \"user\", message: { role: \"user\", content: string | [{ type, text }] } }\n * - type \"assistant\": { type: \"assistant\", message: { role: \"assistant\", content: [{ type, text }] } }\n */\n\nimport { readFileSync, existsSync, readdirSync, statSync } from \"node:fs\";\nimport { join } from \"node:path\";\nimport { homedir } from \"node:os\";\nimport { STOP_WORDS } from \"../utils/stop-words.js\";\n\n// ---------------------------------------------------------------------------\n// Public API types\n// ---------------------------------------------------------------------------\n\nexport interface SlugOptions {\n /** Maximum number of words in the generated slug (default: 3) */\n maxWords?: number;\n /** Maximum character length of the generated slug (default: 30) */\n maxLength?: number;\n}\n\n// STOP_WORDS imported from utils/stop-words.ts\n\n// ---------------------------------------------------------------------------\n// Internal helpers\n// ---------------------------------------------------------------------------\n\n/**\n * Extract plain text from a parsed JSONL message object.\n */\nfunction extractText(obj: Record<string, unknown>): string | null {\n const msg = obj.message as Record<string, unknown> | undefined;\n if (!msg) return null;\n\n const content = msg.content;\n\n // Array of content blocks (modern assistant messages)\n if (Array.isArray(content)) {\n const texts: string[] = [];\n for (const block of content) {\n const b = block as Record<string, unknown>;\n if (b.type === \"text\" && typeof b.text === \"string\") {\n texts.push(b.text);\n }\n }\n return texts.join(\" \") || null;\n }\n\n // Plain string content (user messages)\n if (typeof content === \"string\") {\n return content || null;\n }\n\n return null;\n}\n\n/**\n * Tokenize a string into lowercase words, filtering stop words and short tokens.\n */\nfunction tokenize(text: string): string[] {\n return text\n .toLowerCase()\n .replace(/[^a-z0-9]+/g, \" \")\n .split(/\\s+/)\n .filter((w) => {\n if (w.length < 3) return false;\n if (/^\\d+$/.test(w)) return false;\n if (STOP_WORDS.has(w)) return false;\n return true;\n });\n}\n\n// ---------------------------------------------------------------------------\n// Public functions\n// ---------------------------------------------------------------------------\n\n/**\n * Find the latest JSONL transcript for a project's Claude encoded directory.\n *\n * @param encodedDir Claude-encoded project directory name,\n * e.g. \"-Users-alice-dev-ai-PAI\"\n * @returns Absolute path to the most recently modified .jsonl file, or null.\n */\nexport function findLatestTranscript(encodedDir: string): string | null {\n const sessionsDir = join(\n homedir(),\n \".claude\",\n \"projects\",\n encodedDir,\n \"sessions\"\n );\n\n if (!existsSync(sessionsDir)) return null;\n\n let entries: string[];\n try {\n entries = readdirSync(sessionsDir).filter((f) => f.endsWith(\".jsonl\"));\n } catch {\n return null;\n }\n\n if (!entries.length) return null;\n\n // Sort by mtime descending, return the newest\n const sorted = entries\n .map((f) => {\n const fullPath = join(sessionsDir, f);\n try {\n return { path: fullPath, mtime: statSync(fullPath).mtimeMs };\n } catch {\n return { path: fullPath, mtime: 0 };\n }\n })\n .sort((a, b) => b.mtime - a.mtime);\n\n return sorted[0].path;\n}\n\n/**\n * Read the last `count` human/assistant message pairs from a JSONL file.\n *\n * Synchronous — reads the whole file in memory. JSONL transcripts are\n * bounded by context window size so this is safe even for large sessions.\n *\n * @param jsonlPath Absolute path to a Claude Code .jsonl transcript file.\n * @param count Number of conversation pairs to read (default: 15).\n * @returns Array of plain-text message strings (up to count*2 entries).\n */\nexport function readLastMessages(\n jsonlPath: string,\n count: number = 15\n): string[] {\n if (!existsSync(jsonlPath)) return [];\n\n let raw: string;\n try {\n raw = readFileSync(jsonlPath, \"utf8\");\n } catch {\n return [];\n }\n\n const lines = raw.split(\"\\n\").filter((l) => l.trim().length > 0);\n const messages: string[] = [];\n const limit = count * 2;\n\n // Walk from the end to collect the most recent messages first\n for (let i = lines.length - 1; i >= 0 && messages.length < limit; i--) {\n let obj: Record<string, unknown>;\n try {\n obj = JSON.parse(lines[i]) as Record<string, unknown>;\n } catch {\n continue;\n }\n\n const type = obj.type as string | undefined;\n if (type !== \"assistant\" && type !== \"user\") continue;\n\n const text = extractText(obj);\n if (text && text.trim().length > 0) {\n // Prepend to maintain chronological order\n messages.unshift(text);\n }\n }\n\n return messages;\n}\n\n/**\n * Generate a descriptive 1-3 word slug from conversation message strings.\n *\n * Algorithm:\n * 1. Tokenize all messages, remove stop words and short tokens\n * 2. Count word frequency\n * 3. Pick the top `maxWords` most frequent tokens\n * 4. Join with hyphen, lowercase\n *\n * @param messages Array of plain-text conversation messages.\n * @param opts Optional slug configuration.\n * @returns A lowercase hyphen-joined slug, e.g. \"memory-engine-refactor\",\n * or \"unnamed-session\" if no meaningful words are found.\n */\nexport function generateSlug(messages: string[], opts?: SlugOptions): string {\n const maxWords = opts?.maxWords ?? 3;\n const maxLength = opts?.maxLength ?? 30;\n\n if (!messages.length) return \"unnamed-session\";\n\n // Count word frequencies across all messages\n const freq = new Map<string, number>();\n for (const msg of messages) {\n for (const token of tokenize(msg)) {\n freq.set(token, (freq.get(token) ?? 0) + 1);\n }\n }\n\n if (!freq.size) return \"unnamed-session\";\n\n // Sort by frequency descending, take top maxWords\n const topWords = Array.from(freq.entries())\n .sort((a, b) => b[1] - a[1])\n .slice(0, maxWords)\n .map(([word]) => word);\n\n if (!topWords.length) return \"unnamed-session\";\n\n // Join and trim to maxLength (truncate at last hyphen boundary)\n let slug = topWords.join(\"-\");\n if (slug.length > maxLength) {\n slug = slug.slice(0, maxLength).replace(/-[^-]*$/, \"\");\n }\n\n return slug || \"unnamed-session\";\n}\n","/**\n * Shared DB helpers, formatting, and path utilities for session sub-commands.\n */\n\nimport { join } from \"node:path\";\nimport { homedir } from \"node:os\";\nimport chalk from \"chalk\";\nimport { err } from \"../../utils.js\";\nimport type { RegistryBackend } from \"../../../storage/registry-interface.js\";\nimport type { SessionRow, ProjectRow } from \"./types.js\";\n\nexport async function getProject(\n registryBackend: RegistryBackend,\n slug: string\n): Promise<ProjectRow | undefined> {\n const project = await registryBackend.getProjectBySlug(slug);\n return project ?? undefined;\n}\n\nexport function statusColor(status: string): string {\n switch (status) {\n case \"completed\":\n return chalk.green(status);\n case \"compacted\":\n return chalk.blue(status);\n default:\n return chalk.yellow(status);\n }\n}\n\n/** Convert a slug to title-cased display name: \"memory-engine\" → \"Memory Engine\" */\nexport function toTitleCase(slug: string): string {\n return slug\n .replace(/-/g, \" \")\n .replace(/\\b\\w/g, (c) => c.toUpperCase());\n}\n\n/** Notes directory for a project: ~/.claude/projects/<encoded_dir>/Notes/ */\nexport function getNotesDir(project: ProjectRow): string {\n return join(homedir(), \".claude\", \"projects\", project.encoded_dir, \"Notes\");\n}\n\n/** Format a session filename: number=27, date=\"2026-02-23\" → \"0027 - 2026-02-23 - Title.md\" */\nexport function formatFilename(\n number: number,\n date: string,\n titleSlug: string\n): string {\n const n = String(number).padStart(4, \"0\");\n return `${n} - ${date} - ${titleSlug}.md`;\n}\n\n/** Resolve a session by project + number or \"latest\". Exits on failure. */\nexport async function resolveSession(\n registryBackend: RegistryBackend,\n project: ProjectRow,\n numberOrLatest: string\n): Promise<SessionRow> {\n let session: SessionRow | undefined;\n\n if (numberOrLatest === \"latest\") {\n session = (await registryBackend.getLatestSessionForProject(project.id)) ?? undefined;\n } else {\n const num = parseInt(numberOrLatest, 10);\n if (isNaN(num)) {\n console.error(err(`Invalid session number: ${numberOrLatest}`));\n process.exit(1);\n }\n session = (await registryBackend.getSessionByNumber(project.id, num)) ?? undefined;\n }\n\n if (!session) {\n console.error(\n err(`Session ${numberOrLatest} not found in project ${project.slug}`)\n );\n process.exit(1);\n }\n\n return session;\n}\n\nexport async function upsertTag(registryBackend: RegistryBackend, tagName: string): Promise<number> {\n return registryBackend.upsertTag(tagName);\n}\n\nexport async function getSessionTags(registryBackend: RegistryBackend, sessionId: number): Promise<string[]> {\n return registryBackend.listTagsForSession(sessionId);\n}\n","/**\n * Session CRUD commands: list, info, rename, slug, tag, route, active, auto-route.\n */\n\nimport {\n existsSync,\n readdirSync,\n renameSync,\n readFileSync,\n writeFileSync,\n statSync,\n} from \"node:fs\";\nimport { join } from \"node:path\";\nimport { homedir } from \"node:os\";\nimport chalk from \"chalk\";\nimport {\n ok,\n warn,\n err,\n dim,\n bold,\n header,\n renderTable,\n fmtDate,\n} from \"../../utils.js\";\nimport {\n findLatestTranscript,\n readLastMessages,\n generateSlug,\n} from \"../../../session/slug-generator.js\";\nimport type { RegistryBackend } from \"../../../storage/registry-interface.js\";\nimport type { SessionRow, ProjectRow } from \"./types.js\";\nimport {\n getProject,\n statusColor,\n toTitleCase,\n getNotesDir,\n formatFilename,\n resolveSession,\n upsertTag,\n getSessionTags,\n} from \"./helpers.js\";\n\n// ---------------------------------------------------------------------------\n// list\n// ---------------------------------------------------------------------------\n\nexport async function cmdList(\n projectSlug: string | undefined,\n opts: { limit?: string; status?: string }\n): Promise<void> {\n const { getRegistryBackend } = await import(\"../../../storage/factory.js\");\n const registryBackend = await getRegistryBackend();\n\n const limit = parseInt(opts.limit ?? \"20\", 10);\n\n let project: ProjectRow | undefined;\n if (projectSlug) {\n project = await getProject(registryBackend, projectSlug);\n if (!project) {\n console.error(err(`Project not found: ${projectSlug}`));\n process.exitCode = 1;\n return;\n }\n }\n\n const rows = await registryBackend.listSessions({\n projectId: project?.id,\n status: opts.status,\n limit,\n });\n\n if (!rows.length) {\n console.log(warn(\"No sessions found.\"));\n return;\n }\n\n const showProject = !projectSlug;\n const headers = showProject\n ? [\"#\", \"Project\", \"Date\", \"Title\", \"Status\", \"Tokens\"]\n : [\"#\", \"Date\", \"Title\", \"Status\", \"Tokens\"];\n\n const tableRows = rows.map((r) => {\n const title = r.title.length > 45 ? r.title.slice(0, 42) + \"...\" : r.title;\n const tokens =\n r.token_count != null ? dim(r.token_count.toLocaleString()) : dim(\"—\");\n if (showProject) {\n return [\n dim(`#${r.number}`),\n r.project_slug,\n r.date,\n title,\n statusColor(r.status),\n tokens,\n ];\n }\n return [dim(`#${r.number}`), r.date, title, statusColor(r.status), tokens];\n });\n\n console.log();\n if (project) {\n console.log(` ${bold(project.display_name)} sessions:`);\n console.log();\n }\n console.log(renderTable(headers, tableRows));\n console.log();\n console.log(dim(` ${rows.length} session(s) shown (limit: ${limit})`));\n}\n\n// ---------------------------------------------------------------------------\n// info\n// ---------------------------------------------------------------------------\n\nexport async function cmdInfo(\n projectSlug: string,\n sessionNumber: string\n): Promise<void> {\n const { getRegistryBackend } = await import(\"../../../storage/factory.js\");\n const registryBackend = await getRegistryBackend();\n\n const project = await getProject(registryBackend, projectSlug);\n if (!project) {\n console.error(err(`Project not found: ${projectSlug}`));\n process.exitCode = 1;\n return;\n }\n\n const session = await resolveSession(registryBackend, project, sessionNumber);\n\n console.log();\n console.log(header(` Session #${session.number}: ${session.title}`));\n console.log();\n console.log(\n ` ${bold(\"Project:\")} ${project.display_name} (${project.slug})`\n );\n console.log(` ${bold(\"Date:\")} ${session.date}`);\n console.log(` ${bold(\"Status:\")} ${statusColor(session.status)}`);\n console.log(` ${bold(\"Filename:\")} ${session.filename}`);\n console.log(` ${bold(\"Slug:\")} ${session.slug}`);\n if (session.claude_session_id) {\n console.log(\n ` ${bold(\"Claude ID:\")} ${dim(session.claude_session_id)}`\n );\n }\n if (session.token_count != null) {\n console.log(\n ` ${bold(\"Tokens:\")} ${session.token_count.toLocaleString()}`\n );\n }\n console.log(` ${bold(\"Created:\")} ${fmtDate(session.created_at)}`);\n if (session.closed_at) {\n console.log(` ${bold(\"Closed:\")} ${fmtDate(session.closed_at)}`);\n }\n console.log();\n}\n\n// ---------------------------------------------------------------------------\n// rename\n// ---------------------------------------------------------------------------\n\nexport async function cmdRename(\n projectSlug: string,\n numberOrLatest: string,\n newSlug: string\n): Promise<void> {\n const { getRegistryBackend } = await import(\"../../../storage/factory.js\");\n const registryBackend = await getRegistryBackend();\n await cmdRenameWith(registryBackend, projectSlug, numberOrLatest, newSlug);\n}\n\nasync function cmdRenameWith(\n registryBackend: RegistryBackend,\n projectSlug: string,\n numberOrLatest: string,\n newSlug: string\n): Promise<void> {\n const project = await getProject(registryBackend, projectSlug);\n if (!project) {\n console.error(err(`Project not found: ${projectSlug}`));\n process.exitCode = 1;\n return;\n }\n\n const session = await resolveSession(registryBackend, project, numberOrLatest);\n const notesDir = getNotesDir(project);\n\n if (!existsSync(notesDir)) {\n console.error(err(`Notes directory not found: ${notesDir}`));\n process.exitCode = 1;\n return;\n }\n\n const titleSlug = toTitleCase(newSlug);\n const newFilename = formatFilename(session.number, session.date, titleSlug);\n const oldPath = join(notesDir, session.filename);\n const newPath = join(notesDir, newFilename);\n\n if (existsSync(oldPath)) {\n if (oldPath !== newPath) {\n try {\n renameSync(oldPath, newPath);\n } catch (e) {\n console.error(err(`Failed to rename file: ${e}`));\n process.exitCode = 1;\n return;\n }\n }\n } else {\n console.log(\n warn(` Note: file not found at expected path: ${session.filename}`)\n );\n console.log(warn(` Skipping disk rename. Database will still be updated.`));\n }\n\n if (existsSync(newPath)) {\n try {\n const content = readFileSync(newPath, \"utf8\");\n const lines = content.split(\"\\n\");\n let h1Updated = false;\n const updated = lines.map((line) => {\n if (!h1Updated && line.startsWith(\"# \")) {\n h1Updated = true;\n return `# ${titleSlug}`;\n }\n return line;\n });\n if (!h1Updated) updated.unshift(`# ${titleSlug}`, \"\");\n writeFileSync(newPath, updated.join(\"\\n\"), \"utf8\");\n } catch (e) {\n console.error(err(`Failed to update H1 in file: ${e}`));\n }\n }\n\n const normalizedSlug = newSlug\n .toLowerCase()\n .replace(/[^a-z0-9]+/g, \"-\")\n .replace(/^-+|-+$/g, \"\");\n\n await registryBackend.updateSessionMeta(session.id, {\n slug: normalizedSlug,\n title: titleSlug,\n filename: newFilename,\n });\n\n console.log();\n console.log(ok(` Session #${session.number} renamed.`));\n console.log(` ${bold(\"Old:\")} ${session.filename}`);\n console.log(` ${bold(\"New:\")} ${newFilename}`);\n console.log(` ${bold(\"Slug:\")} ${normalizedSlug}`);\n console.log(` ${bold(\"Title:\")} ${titleSlug}`);\n console.log();\n}\n\n// ---------------------------------------------------------------------------\n// slug\n// ---------------------------------------------------------------------------\n\nexport async function cmdSlug(\n projectSlug: string,\n numberOrLatest: string,\n opts: { apply?: boolean }\n): Promise<void> {\n const { getRegistryBackend } = await import(\"../../../storage/factory.js\");\n const registryBackend = await getRegistryBackend();\n\n const project = await getProject(registryBackend, projectSlug);\n if (!project) {\n console.error(err(`Project not found: ${projectSlug}`));\n process.exitCode = 1;\n return;\n }\n\n const session = await resolveSession(registryBackend, project, numberOrLatest);\n const transcriptPath = findLatestTranscript(project.encoded_dir);\n\n if (!transcriptPath) {\n console.log(warn(` No JSONL transcripts found for project ${projectSlug}`));\n console.log(\"unnamed-session\");\n return;\n }\n\n const messages = readLastMessages(transcriptPath);\n\n if (messages.length < 2) {\n console.log(\n warn(` Too few messages found (${messages.length}) in transcript`)\n );\n console.log(\"unnamed-session\");\n return;\n }\n\n const generatedSlug = generateSlug(messages);\n console.log(generatedSlug);\n\n if (opts.apply) {\n console.log();\n console.log(dim(` Applying slug to session #${session.number}...`));\n await cmdRenameWith(registryBackend, projectSlug, String(session.number), generatedSlug);\n }\n}\n\n// ---------------------------------------------------------------------------\n// tag\n// ---------------------------------------------------------------------------\n\nexport async function cmdTag(\n projectSlug: string,\n sessionNumber: string,\n rawTags: string[]\n): Promise<void> {\n const { getRegistryBackend } = await import(\"../../../storage/factory.js\");\n const registryBackend = await getRegistryBackend();\n\n const project = await getProject(registryBackend, projectSlug);\n if (!project) {\n console.error(err(`Project not found: ${projectSlug}`));\n process.exitCode = 1;\n return;\n }\n\n const session = await resolveSession(registryBackend, project, sessionNumber);\n\n if (rawTags.length === 0) {\n const current = await getSessionTags(registryBackend, session.id);\n console.log();\n if (current.length === 0) {\n console.log(dim(` Session #${session.number} has no tags.`));\n } else {\n console.log(\n ` ${bold(`Session #${session.number}`)} tags: ${current\n .map((t) => chalk.cyan(t))\n .join(\", \")}`\n );\n }\n console.log();\n return;\n }\n\n const tags = rawTags\n .flatMap((t) => t.split(\",\"))\n .map((t) => t.trim().toLowerCase())\n .filter((t) => t.length > 0);\n\n if (tags.length === 0) {\n console.log(warn(\"No valid tags provided.\"));\n return;\n }\n\n const added: string[] = [];\n const skipped: string[] = [];\n\n for (const tagName of tags) {\n const tagId = await upsertTag(registryBackend, tagName);\n const exists = await registryBackend.sessionHasTag(session.id, tagId);\n if (exists) {\n skipped.push(tagName);\n } else {\n await registryBackend.addSessionTag(session.id, tagId);\n added.push(tagName);\n }\n }\n\n console.log();\n if (added.length) {\n console.log(\n ok(\n ` Tagged session #${session.number}: ${added\n .map((t) => chalk.cyan(t))\n .join(\", \")}`\n )\n );\n }\n if (skipped.length) {\n console.log(dim(` Already present: ${skipped.join(\", \")}`));\n }\n\n const allTags = await getSessionTags(registryBackend, session.id);\n console.log(\n ` ${bold(\"All tags:\")} ${allTags.map((t) => chalk.cyan(t)).join(\", \")}`\n );\n console.log();\n}\n\n// ---------------------------------------------------------------------------\n// route\n// ---------------------------------------------------------------------------\n\nexport async function cmdRoute(\n projectSlug: string,\n sessionNumber: string,\n targetProjectSlug: string,\n opts: { type?: string }\n): Promise<void> {\n const { getRegistryBackend } = await import(\"../../../storage/factory.js\");\n const registryBackend = await getRegistryBackend();\n\n const project = await getProject(registryBackend, projectSlug);\n if (!project) {\n console.error(err(`Project not found: ${projectSlug}`));\n process.exitCode = 1;\n return;\n }\n\n const session = await resolveSession(registryBackend, project, sessionNumber);\n\n const targetProject = await getProject(registryBackend, targetProjectSlug);\n\n if (!targetProject) {\n console.error(err(`Target project not found: ${targetProjectSlug}`));\n process.exitCode = 1;\n return;\n }\n\n const validTypes = [\"related\", \"follow-up\", \"reference\"];\n const linkType = (opts.type ?? \"related\") as \"related\" | \"follow-up\" | \"reference\";\n if (!validTypes.includes(linkType)) {\n console.error(\n err(`Invalid link type \"${linkType}\". Valid: ${validTypes.join(\", \")}`)\n );\n process.exitCode = 1;\n return;\n }\n\n if (await registryBackend.linkExists(session.id, targetProject.id)) {\n console.log(\n warn(\n ` Link already exists: session #${session.number} → ${targetProjectSlug}`\n )\n );\n return;\n }\n\n await registryBackend.addLink({\n sessionId: session.id,\n targetProjectId: targetProject.id,\n linkType,\n createdAt: Date.now(),\n });\n\n console.log();\n console.log(\n ok(\n ` Linked session #${session.number} (${project.slug}) → ${targetProject.display_name} (${targetProjectSlug})`\n )\n );\n console.log(dim(` Link type: ${linkType}`));\n console.log();\n}\n\n// ---------------------------------------------------------------------------\n// active\n// ---------------------------------------------------------------------------\n\nexport async function cmdActive(\n opts: { minutes?: string; json?: boolean }\n): Promise<void> {\n const { getRegistryBackend } = await import(\"../../../storage/factory.js\");\n const registryBackend = await getRegistryBackend();\n\n const minutes = parseInt(opts.minutes ?? \"60\", 10);\n const cutoff = Date.now() - minutes * 60 * 1000;\n const claudeProjectsDir = join(homedir(), \".claude\", \"projects\");\n\n if (!existsSync(claudeProjectsDir)) {\n console.log(err(\"Claude projects directory not found.\"));\n return;\n }\n\n interface ActiveSession {\n slug: string;\n displayName: string;\n rootPath: string;\n encodedDir: string;\n lastModified: Date;\n jsonlFile: string;\n }\n\n const active: ActiveSession[] = [];\n const entries = readdirSync(claudeProjectsDir);\n\n for (const entry of entries) {\n const projectDir = join(claudeProjectsDir, entry);\n try {\n if (!statSync(projectDir).isDirectory()) continue;\n } catch {\n continue;\n }\n\n let latestJsonl: string | null = null;\n let latestMtime = 0;\n\n try {\n for (const file of readdirSync(projectDir)) {\n if (!file.endsWith(\".jsonl\")) continue;\n const filePath = join(projectDir, file);\n try {\n const mtime = statSync(filePath).mtimeMs;\n if (mtime > latestMtime) {\n latestMtime = mtime;\n latestJsonl = filePath;\n }\n } catch {\n continue;\n }\n }\n } catch {\n continue;\n }\n\n if (!latestJsonl || latestMtime < cutoff) continue;\n\n const project = await registryBackend.getProjectByEncodedDir(entry);\n\n active.push({\n slug: project?.slug ?? entry,\n displayName: project?.display_name ?? project?.slug ?? entry,\n rootPath: project?.root_path ?? \"\",\n encodedDir: entry,\n lastModified: new Date(latestMtime),\n jsonlFile: latestJsonl,\n });\n }\n\n active.sort((a, b) => b.lastModified.getTime() - a.lastModified.getTime());\n\n const seen = new Set<string>();\n const deduped = active.filter((a) => {\n const key = a.slug.replace(/-\\d+$/, \"\");\n if (seen.has(key)) return false;\n seen.add(key);\n return true;\n });\n\n if (opts.json) {\n console.log(\n JSON.stringify(\n deduped.map((a) => ({\n slug: a.slug,\n display_name: a.displayName,\n root_path: a.rootPath,\n last_modified: a.lastModified.toISOString(),\n })),\n null,\n 2\n )\n );\n return;\n }\n\n if (deduped.length === 0) {\n console.log(dim(`No active sessions in the last ${minutes} minutes.`));\n return;\n }\n\n console.log(\n header(`Currently Active Sessions`) +\n dim(` (modified in last ${minutes}min)`)\n );\n console.log();\n\n const rows = deduped.map((a) => {\n const time = a.lastModified.toTimeString().slice(0, 5);\n const dirName = a.rootPath\n ? a.rootPath.replace(homedir(), \"~\").split(\"/\").pop() ?? a.slug\n : a.slug;\n return [chalk.cyan(dirName), dim(a.slug), chalk.green(time)];\n });\n\n console.log(renderTable([\"Directory\", \"Project\", \"Last Active\"], rows));\n}\n\n// ---------------------------------------------------------------------------\n// auto-route\n// ---------------------------------------------------------------------------\n\nexport async function cmdAutoRoute(opts: {\n cwd?: string;\n context?: string;\n json?: boolean;\n}): Promise<void> {\n const { autoRoute, formatAutoRouteJson } = await import(\n \"../../../session/auto-route.js\"\n );\n const { getRegistryBackend, createStorageBackend } = await import(\"../../../storage/factory.js\");\n const { loadConfig } = await import(\"../../../daemon/config.js\");\n\n const config = loadConfig();\n const registryBackend = await getRegistryBackend();\n const federation = await createStorageBackend(config);\n\n const targetCwd = opts.cwd ?? process.cwd();\n const result = await autoRoute(\n registryBackend,\n federation,\n targetCwd,\n opts.context\n );\n\n if (!result) {\n console.log();\n console.log(warn(\" No project match found for: \" + targetCwd));\n console.log();\n console.log(\n dim(\" Tried: path match, PAI.md marker walk\") +\n (opts.context ? dim(\", topic detection\") : \"\")\n );\n console.log();\n console.log(dim(\" Run 'pai project add .' to register this directory.\"));\n console.log();\n return;\n }\n\n if (opts.json) {\n console.log(formatAutoRouteJson(result));\n return;\n }\n\n console.log();\n console.log(header(\" PAI Auto-Route\"));\n console.log();\n console.log(` ${bold(\"Project:\")} ${result.display_name}`);\n console.log(` ${bold(\"Slug:\")} ${result.slug}`);\n console.log(` ${bold(\"Root path:\")} ${result.root_path}`);\n console.log(` ${bold(\"Method:\")} ${result.method}`);\n console.log(\n ` ${bold(\"Confidence:\")} ${(result.confidence * 100).toFixed(0)}%`\n );\n console.log();\n console.log(ok(\" Routed to: \") + bold(result.slug));\n console.log();\n}\n","/**\n * Session checkpoint command — appends a timestamped block to the active\n * session note. Designed for use in hooks; fast, silent, rate-limited.\n */\n\nimport { existsSync, readdirSync, statSync, readFileSync, writeFileSync, renameSync } from \"node:fs\";\nimport { join } from \"node:path\";\nimport { homedir, tmpdir } from \"node:os\";\n\nfunction findNotesDirForCwd(): string | null {\n const cwd = process.cwd();\n const claudeProjectsDir = join(homedir(), \".claude\", \"projects\");\n\n if (!existsSync(claudeProjectsDir)) return null;\n\n const expectedEncoded = cwd.replace(/[/\\s.\\-]/g, \"-\");\n let encodedDir: string | null = null;\n\n try {\n const entries = readdirSync(claudeProjectsDir);\n\n if (entries.includes(expectedEncoded)) {\n encodedDir = expectedEncoded;\n } else {\n for (const entry of entries) {\n const full = join(claudeProjectsDir, entry);\n try {\n if (!statSync(full).isDirectory()) continue;\n } catch {\n continue;\n }\n const candidate = entry.replace(/-+$/, \"\");\n const expected = expectedEncoded.replace(/-+$/, \"\");\n if (candidate === expected) {\n encodedDir = entry;\n break;\n }\n }\n }\n } catch {\n return null;\n }\n\n if (!encodedDir) return null;\n const notesDir = join(claudeProjectsDir, encodedDir, \"Notes\");\n return existsSync(notesDir) ? notesDir : null;\n}\n\nfunction findLatestNoteFile(notesDir: string): string | null {\n let entries: string[];\n try {\n entries = readdirSync(notesDir);\n } catch {\n return null;\n }\n\n const mdFiles = entries.filter((e) => e.endsWith(\".md\"));\n if (mdFiles.length === 0) return null;\n\n let latestPath: string | null = null;\n let latestMtime = 0;\n\n for (const file of mdFiles) {\n const full = join(notesDir, file);\n try {\n const { mtimeMs } = statSync(full);\n if (mtimeMs > latestMtime) {\n latestMtime = mtimeMs;\n latestPath = full;\n }\n } catch {\n // skip unreadable files\n }\n }\n\n return latestPath;\n}\n\nfunction checkpointTooRecent(notesDir: string, minGapSeconds: number): boolean {\n const safeKey = notesDir.replace(/[^a-zA-Z0-9]/g, \"-\").slice(-80);\n const tmpFile = join(tmpdir(), `pai-checkpoint-${safeKey}`);\n if (!existsSync(tmpFile)) return false;\n try {\n const { mtimeMs } = statSync(tmpFile);\n return Date.now() - mtimeMs < minGapSeconds * 1000;\n } catch {\n return false;\n }\n}\n\nfunction touchCheckpointSentinel(notesDir: string): void {\n const safeKey = notesDir.replace(/[^a-zA-Z0-9]/g, \"-\").slice(-80);\n const tmpFile = join(tmpdir(), `pai-checkpoint-${safeKey}`);\n try {\n writeFileSync(tmpFile, String(Date.now()), \"utf8\");\n } catch {\n // Non-fatal — rate limiting is best-effort\n }\n}\n\nexport function cmdCheckpoint(\n message: string,\n opts: { minGap?: string }\n): void {\n const minGapSeconds = parseInt(opts.minGap ?? \"300\", 10);\n\n const notesDir = findNotesDirForCwd();\n if (!notesDir) return;\n\n if (checkpointTooRecent(notesDir, minGapSeconds)) return;\n\n const notePath = findLatestNoteFile(notesDir);\n if (!notePath) return;\n\n const timestamp = new Date().toISOString();\n const block = `\\n## Checkpoint — ${timestamp}\\n${message}\\n`;\n\n const tmpPath = `${notePath}.checkpoint.tmp`;\n try {\n const existing = readFileSync(notePath, \"utf8\");\n writeFileSync(tmpPath, existing + block, \"utf8\");\n renameSync(tmpPath, notePath);\n } catch {\n try {\n if (existsSync(tmpPath)) renameSync(tmpPath, tmpPath + \".dead\");\n } catch {\n /* ignore */\n }\n return;\n }\n\n touchCheckpointSentinel(notesDir);\n}\n","/**\n * pai pause [--body-file <path>] [--session-id <uuid>] [--no-body] [--dry-run]\n *\n * Writes a \"## Continue\" checkpoint to the project's TODO.md and appends the\n * same checkpoint to the current session note, then prints a prominent\n * reminder about HOW to exit Claude Code safely.\n *\n * THE BODY IS THE POINT\n * ---------------------\n * This command used to emit a fixed four-line block: session name, timestamp,\n * working directory. That is metadata, not a checkpoint — it tells the next\n * session nothing about what was decided, what is blocked, or what to do next.\n * The actual checkpoint was written by the model and printed to the terminal,\n * where it died. Recovering it required the user to copy it off the screen by\n * hand.\n *\n * So `--body-file` is now mandatory. Omitting it is an error rather than a\n * silent downgrade, because a silent downgrade is precisely the failure this\n * fixes: everything looked like it worked, and the state was gone.\n * `--no-body` remains available for deliberate metadata-only use.\n *\n * WHY Ctrl+C is dangerous:\n * Claude Code's stop-hook fires only on a clean exit (e.g. /exit inside the\n * Claude prompt, or claude process receiving SIGTERM). When you Ctrl+C from\n * a terminal that's running claude, the stop-hook is bypassed, which means:\n * - The top-level <project>/<uuid>.jsonl does NOT get its final system snapshot.\n * - Claude Code's --resume cannot find the session (0 system lines).\n * - The session becomes orphaned — transcript exists but is unresumable.\n */\n\nimport { basename } from \"node:path\";\nimport { realpathSync } from \"node:fs\";\nimport chalk from \"chalk\";\nimport type { RegistryBackend } from \"../../../storage/registry-interface.js\";\nimport type { ProjectRow, SessionRow } from \"./types.js\";\nimport {\n applyContinue,\n appendCheckpointToNote,\n findNotesDir,\n findLatestNote,\n readBodyFile,\n} from \"../../../session/checkpoint-block.js\";\n\nexport interface PauseOptions {\n dryRun?: boolean;\n /** Path to the model-authored checkpoint markdown, or \"-\" for stdin. */\n bodyFile?: string;\n /** Claude Code session UUID — becomes the `claude --resume` handle. */\n sessionId?: string;\n /** Deliberately write a metadata-only checkpoint. */\n noBody?: boolean;\n}\n\n/** Format a session row as \"0003 - 2026-08-01 - Title\". */\nexport function formatSessionLine(session: SessionRow | undefined): string {\n if (!session) return \"Unknown session\";\n const num = String(session.number).padStart(4, \"0\");\n const titlePart = session.title || session.slug || \"Session\";\n return `${num} - ${session.date} - ${titlePart}`;\n}\n\n/**\n * Resolve the string that identifies a session in a ## Continue block.\n *\n * THIS IS THE PRESERVATION KEY. It decides whether an authored checkpoint\n * belongs to the current session and must be left alone, so every writer must\n * derive it identically — `pai pause`, `pai session handover`, and the hooks'\n * updateTodoContinue. Deriving it in more than one place is how a checkpoint\n * gets clobbered: one writer says \"0006 - …\" and the other says\n * \"Unknown session\", the keys do not match, and the unattended writer replaces\n * work it should have preserved.\n *\n * The registry row is preferred. When the project has no session rows — which\n * happens whenever one codebase is registered under several paths — it falls\n * back to the session note's filename, which is what the hooks use.\n */\nexport function resolveSessionLine(\n project: ProjectRow,\n session: SessionRow | undefined,\n notePath: string | null\n): string {\n // The session note filename wins.\n //\n // It is what the unattended writers use, and agreement is the entire point —\n // a mismatch means the hooks replace a checkpoint they should have preserved.\n //\n // The registry row is the weaker source: it is resolved with\n // `ORDER BY number DESC`, which stops meaning \"current\" once a project has\n // been merged. Numbers from both merged rows then coexist, so the highest\n // number can be a session from months earlier while the note on disk is\n // today's. Observed live: the registry offered \"0184 - 2026-02-22\" while the\n // current note was \"0007 - 2026-08-01\".\n if (notePath) return basename(notePath).replace(/\\.md$/, \"\");\n if (session) return formatSessionLine(session);\n return \"Unknown session\";\n}\n\n/** Locate the latest session note for a project, or null. */\nexport function resolveNotePath(project: ProjectRow): string | null {\n const notesDir = findNotesDir(project.root_path, project.encoded_dir ?? \"\");\n return notesDir ? findLatestNote(notesDir) : null;\n}\n\n/**\n * Resolve the project containing cwd, or undefined.\n *\n * Tries the literal path first, then its resolved form. A symlinked path\n * prefix (`~/dev -> Cloud/Development`) makes one directory reachable under\n * two spellings; matching only the literal string means a session started from\n * the other spelling finds nothing, and the scanner then registers a second\n * project for the same directory — which splits session history in half.\n */\nexport async function resolveProjectByCwd(\n registryBackend: RegistryBackend,\n cwd: string\n): Promise<ProjectRow | undefined> {\n const direct = await registryBackend.findProjectByCwdPrefix(cwd);\n if (direct) return direct;\n\n let resolved: string;\n try {\n resolved = realpathSync(cwd);\n } catch {\n return undefined;\n }\n if (resolved === cwd) return undefined;\n\n const viaResolved = await registryBackend.findProjectByCwdPrefix(resolved);\n return viaResolved ?? undefined;\n}\n\nfunction printMissingBodyError(): void {\n console.error(\n \"\\n\" +\n chalk.bgRed.white.bold(\n \" CHECKPOINT BODY MISSING — nothing was written \"\n ) +\n \"\\n\\n\" +\n chalk.yellow(\n \" A checkpoint without a body is just a timestamp. The next session\\n\" +\n \" would learn nothing about what was decided or what is blocked.\\n\"\n ) +\n \"\\n\" +\n chalk.white(\" Write your checkpoint markdown to a file, then re-run:\\n\") +\n \"\\n\" +\n chalk.cyan(\" pai pause --body-file /path/to/checkpoint.md \\\\\\n\") +\n chalk.cyan(\" --session-id <claude-session-uuid>\\n\") +\n \"\\n\" +\n chalk.dim(\" Or pipe it: \") +\n chalk.cyan(\"cat checkpoint.md | pai pause --body-file -\\n\") +\n \"\\n\" +\n chalk.dim(\n \" If you genuinely want a metadata-only checkpoint, pass --no-body.\\n\"\n )\n );\n}\n\n// ---------------------------------------------------------------------------\n// Command\n// ---------------------------------------------------------------------------\n\nexport async function cmdPause(opts: PauseOptions): Promise<void> {\n // ---- 1. Body first — fail before touching anything ----\n let body = \"\";\n\n if (opts.bodyFile) {\n try {\n body = readBodyFile(opts.bodyFile).trim();\n } catch (err) {\n console.error(\n chalk.red(\"pai pause: \") +\n `Could not read checkpoint body from ${opts.bodyFile}: ${String(err)}`\n );\n process.exitCode = 1;\n return;\n }\n if (!body) {\n console.error(\n chalk.red(\"pai pause: \") +\n `Checkpoint body at ${opts.bodyFile} is empty.`\n );\n process.exitCode = 1;\n return;\n }\n } else if (!opts.noBody) {\n printMissingBodyError();\n process.exitCode = 1;\n return;\n }\n\n const { getRegistryBackend } = await import(\"../../../storage/factory.js\");\n const registryBackend = await getRegistryBackend();\n\n // ---- 2. Resolve project by cwd ----\n const cwd = process.cwd();\n const project = await resolveProjectByCwd(registryBackend, cwd);\n\n if (!project) {\n console.error(\n chalk.red(\"pai pause: \") +\n \"Current directory is not within a registered PAI project.\\n\" +\n ` cwd: ${cwd}`\n );\n process.exitCode = 1;\n return;\n }\n\n // ---- 3. Resolve latest session ----\n const session = (await registryBackend.getLatestSessionForProject(project.id)) ?? undefined;\n\n // ---- 3b. Agree with the hooks on how a session is identified ----\n //\n // The unattended writers (pre-compact hook, daemon work-queue worker) key a\n // session by its note filename. This command keys it by the registry row.\n // That string is the preservation key — it is what decides whether an\n // authored checkpoint belongs to the current session and must be left alone —\n // so the two must produce the same value or the hooks will clobber this\n // command's work.\n //\n // They diverge whenever the registry has no session row for the resolved\n // project, which happens when one codebase is registered under several paths.\n // Falling back to the note filename makes them agree, and incidentally\n // replaces a useless \"Unknown session\" label with the real one.\n const notePath = resolveNotePath(project);\n const sessionLine = resolveSessionLine(project, session, notePath);\n\n // A registered project with zero sessions almost always means split identity:\n // the same codebase registered under several paths (a dev copy and a synced\n // copy, say), with the session history filed under a different row. The\n // checkpoint still writes correctly, but \"Unknown session\" in TODO.md is a\n // symptom worth naming rather than shipping silently.\n if (!session) {\n const siblings = await registryBackend.findSiblingProjectsBySlugPattern(\n project.id,\n project.slug,\n 3\n );\n\n console.log(\n chalk.yellow(\n ` Note: project '${project.slug}' has no registered sessions; the ` +\n `checkpoint is labelled from the session note instead (\"${sessionLine}\").`\n )\n );\n for (const s of siblings) {\n console.log(\n chalk.dim(\n ` '${s.slug}' (${s.root_path}) has ${s.session_count} — likely the same project under another path.`\n )\n );\n }\n }\n\n // ---- 4. Write the ## Continue block ----\n const timestamp = new Date().toISOString();\n\n // `authored` records WHO initiated the checkpoint, not whether it turned out\n // to have anything in it. A pause is model-initiated by definition, so a\n // bodyless one used to mislabel itself \"auto\" and silently forfeit the\n // preservation rules that exist for exactly this kind of write. Substance is\n // a separate question and is now asked separately (`hasSubstance`); an empty\n // model block is still boilerplate-only, so it cannot freeze TODO.md — the\n // `isBoilerplateOnly` guards on cases 1b and 2b already see to that.\n const result = applyContinue({\n rootPath: project.root_path,\n authored: \"model\",\n sessionLine,\n sessionId: opts.sessionId,\n cwd,\n body,\n timestamp,\n dryRun: opts.dryRun,\n });\n\n if (result.action === \"failed\") {\n console.error(\n chalk.red(\"pai pause: \") + `Failed to write TODO.md: ${result.error}`\n );\n process.exitCode = 1;\n return;\n }\n\n if (opts.dryRun) {\n console.log(\n \"\\n\" + chalk.bold(\"Dry run — would write to:\") + \" \" + chalk.cyan(result.path ?? \"?\")\n );\n console.log(chalk.dim(\"─\".repeat(60)));\n console.log(chalk.dim(result.block));\n console.log(chalk.dim(\"─\".repeat(60)));\n } else {\n console.log(\n chalk.green(\" ## Continue written to: \") + chalk.cyan(result.path ?? \"?\")\n );\n }\n\n // ---- 5. Mirror the body into the session note ----\n //\n // TODO.md's ## Continue is a single slot that the next checkpoint overwrites.\n // The session note is the durable record, so the body goes to both.\n if (body) {\n if (!notePath) {\n console.log(\n chalk.yellow(\n \" (No session note found — checkpoint saved to TODO.md only.)\"\n )\n );\n } else if (opts.dryRun) {\n console.log(\n chalk.dim(` Would append checkpoint to: ${basename(notePath)}`)\n );\n } else {\n const { appended, error } = appendCheckpointToNote(\n notePath,\n body,\n timestamp\n );\n if (appended) {\n console.log(\n chalk.green(\" Checkpoint appended to note: \") +\n chalk.cyan(basename(notePath))\n );\n } else if (error) {\n console.log(\n chalk.yellow(` (Could not append to session note: ${error})`)\n );\n }\n }\n }\n\n // ---- 6. Print the safety warning ----\n const box = [\n \"\",\n chalk.bgRed.white.bold(\n \" IMPORTANT: How to exit safely \"\n ),\n \"\",\n chalk.yellow(\n \" Inside the Claude Code session, type: \" +\n chalk.white.bold(\"/exit\") +\n chalk.yellow(\" (then press Enter)\")\n ),\n \"\",\n chalk.red.bold(\" DO NOT press Ctrl+C.\"),\n \"\",\n chalk.dim(\" Ctrl+C bypasses PAI's stop-hook, which means:\"),\n chalk.dim(\" - No project snapshot is written to the top-level jsonl\"),\n chalk.dim(\" - The session becomes orphaned (cannot --resume next time)\"),\n chalk.dim(\n \" - pai session goto will launch a fresh session instead of resuming\"\n ),\n \"\",\n chalk.dim(\" The ## Continue checkpoint in TODO.md is already saved.\"),\n chalk.dim(\" Use /exit to preserve full session resumability.\"),\n \"\",\n ].join(\"\\n\");\n\n console.log(box);\n}\n","/**\n * Rolling auto-checkpoint — the floor under every session.\n *\n * WHY THIS EXISTS\n * ---------------\n * A model-authored checkpoint is the best handover there is: it knows what is\n * unfinished, what is load-bearing, and what to do next. It has one fatal\n * property — it only exists if the model was asked to write one.\n *\n * The model is not invoked on `/exit`, and certainly not on Ctrl+C. No hook can\n * make it write prose at exit time. So a good checkpoint at exit can only exist\n * if something wrote it *before* exit. Exit-time is the wrong moment by\n * construction, and building the guarantee there is why sessions kept ending\n * with nothing to hand over.\n *\n * This module builds a mechanical digest instead — recent prompts, the state of\n * the working tree — cheap enough to run on a timer during the session. It is\n * strictly worse than what the model would write, and that is the point: it is\n * the floor, not the ceiling. `applyContinue` writes it in \"auto\" mode, so it\n * can never overwrite a model-authored checkpoint for the same session.\n */\n\nimport { existsSync, readFileSync, readdirSync, statSync } from \"node:fs\";\nimport { join } from \"node:path\";\nimport { homedir } from \"node:os\";\nimport { execFileSync } from \"node:child_process\";\nimport { encodeDir } from \"../cli/utils.js\";\n\n/** How many recent user prompts to carry. */\nconst MAX_PROMPTS = 6;\n/** Longest a single quoted prompt may be before it is elided. */\nconst MAX_PROMPT_CHARS = 220;\n/** Cap on listed working-tree entries, so a big refactor cannot flood TODO.md. */\nconst MAX_FILES = 20;\n\n// ---------------------------------------------------------------------------\n// Transcript\n// ---------------------------------------------------------------------------\n\n/**\n * Text of a JSONL message, or null when the entry carries none.\n * Mirrors the shape handled by slug-generator: content is either a plain\n * string (user turns) or an array of typed blocks (assistant turns).\n */\nfunction messageText(obj: Record<string, unknown>): string | null {\n const msg = obj.message as Record<string, unknown> | undefined;\n if (!msg) return null;\n\n const content = msg.content;\n if (typeof content === \"string\") return content || null;\n\n if (Array.isArray(content)) {\n const texts: string[] = [];\n for (const block of content) {\n const b = block as Record<string, unknown>;\n if (b.type === \"text\" && typeof b.text === \"string\") texts.push(b.text);\n }\n return texts.join(\" \") || null;\n }\n\n return null;\n}\n\n/**\n * Strip injected machinery from a user turn, leaving what the user typed.\n *\n * Hooks do not get their own turn — `UserPromptSubmit` output is folded into\n * the same user message as the prompt itself. So this cannot be a predicate\n * that accepts or rejects whole messages: doing that discarded every prompt in\n * a session where a reminder happened to sort first, which is every session\n * here. The markup has to be cut out and the remainder kept.\n */\nfunction stripInjected(text: string): string {\n return text\n .replace(/<system-reminder>[\\s\\S]*?<\\/system-reminder>/g, \"\")\n .replace(/<local-command-[a-z]*>[\\s\\S]*?<\\/local-command-[a-z]*>/g, \"\")\n .replace(/<command-(name|message|args)>[\\s\\S]*?<\\/command-\\1>/g, \"\")\n .replace(/<persisted-output>[\\s\\S]*?<\\/persisted-output>/g, \"\")\n .trim();\n}\n\n/**\n * True for a turn that is entirely machinery — no user intent to recover.\n */\nfunction isMachinery(text: string): boolean {\n const t = text.trimStart();\n return (\n t.startsWith(\"Caveat:\") ||\n t.startsWith(\"[Request interrupted\") ||\n t.startsWith(\"This session is being continued from a previous\")\n );\n}\n\n/**\n * Every transcript file belonging to a session, oldest part first.\n *\n * A session's transcript is not one file. `load-project-context` archives all\n * but the newest `.jsonl` into `sessions/`, after which Claude Code opens a\n * fresh file under the same UUID — so the same session routinely has a large\n * archived half and a small live one. Reading only the newest file yields just\n * the current turn, which is how the first cut of this digest reported a\n * six-turn session as a single prompt.\n *\n * With no session id, falls back to the newest file in the project directory.\n */\n/**\n * Locate the Claude Code transcript directory for a project.\n *\n * The registry records an `encoded_dir`, but it records whatever was true when\n * the project was added and is not updated when the project moves. Audited on\n * 2026-08-01: of 114 active projects only 19 had an `encoded_dir` containing\n * any transcript at all — 20 named a directory that no longer exists and 75\n * named one holding no `.jsonl` files. AIBroker's still pointed at a path last\n * used in March, which silently disabled checkpoint capture for that project:\n * every lookup returned no transcript, so every digest came out empty and every\n * handover degraded to \"see the latest session note\".\n *\n * So the stored value is treated as a hint, not an answer. If it yields no\n * transcripts, the directory is re-derived from the project's current root\n * path — which is maintained — and that is used instead. This self-heals a\n * moved project on the next capture without a migration.\n */\nexport function resolveTranscriptDir(project: {\n encoded_dir?: string | null;\n root_path: string;\n}): string {\n const base = join(homedir(), \".claude\", \"projects\");\n\n const recorded = project.encoded_dir ? join(base, project.encoded_dir) : null;\n if (recorded && hasTranscripts(recorded)) return recorded;\n\n const derived = join(base, encodeDir(project.root_path));\n if (hasTranscripts(derived)) return derived;\n\n // Neither has anything. Return the recorded one so behaviour and any error\n // message still reference what the registry actually claims.\n return recorded ?? derived;\n}\n\nfunction hasTranscripts(dir: string): boolean {\n if (!existsSync(dir)) return false;\n try {\n if (readdirSync(dir).some((e) => e.endsWith(\".jsonl\"))) return true;\n const archived = join(dir, \"sessions\");\n return existsSync(archived) && readdirSync(archived).some((e) => e.endsWith(\".jsonl\"));\n } catch {\n return false;\n }\n}\n\nexport function findTranscripts(\n projectDir: string,\n sessionId?: string\n): string[] {\n if (!existsSync(projectDir)) return [];\n\n if (sessionId) {\n const archived = join(projectDir, \"sessions\", `${sessionId}.jsonl`);\n const live = join(projectDir, `${sessionId}.jsonl`);\n const parts = [archived, live].filter((p) => existsSync(p));\n if (parts.length > 0) return parts;\n }\n\n let newest: { path: string; mtime: number } | null = null;\n let entries: string[];\n try {\n entries = readdirSync(projectDir);\n } catch {\n return [];\n }\n\n for (const entry of entries) {\n if (!entry.endsWith(\".jsonl\")) continue;\n const full = join(projectDir, entry);\n try {\n const { mtimeMs } = statSync(full);\n if (!newest || mtimeMs > newest.mtime) newest = { path: full, mtime: mtimeMs };\n } catch {\n // Unreadable — skip.\n }\n }\n\n return newest ? [newest.path] : [];\n}\n\n/**\n * The last few things the user actually asked for, oldest first.\n *\n * Claude Code writes a dedicated `last-prompt` entry per turn, holding the\n * prompt already separated from the hook output that shares its user message.\n * That is used when present; parsing user messages is the fallback for\n * transcripts written before those entries existed.\n */\nexport function recentPrompts(\n transcriptPaths: string[],\n limit = MAX_PROMPTS\n): string[] {\n const fromEntries: string[] = [];\n const fromMessages: string[] = [];\n\n const take = (text: string, into: string[]): void => {\n const cleaned = stripInjected(text);\n if (!cleaned || isMachinery(cleaned)) return;\n into.push(\n cleaned.length > MAX_PROMPT_CHARS\n ? cleaned.slice(0, MAX_PROMPT_CHARS).trimEnd() + \" …\"\n : cleaned\n );\n };\n\n for (const path of transcriptPaths) {\n let raw: string;\n try {\n raw = readFileSync(path, \"utf8\");\n } catch {\n continue;\n }\n\n for (const line of raw.split(\"\\n\")) {\n if (!line.trim()) continue;\n let obj: Record<string, unknown>;\n try {\n obj = JSON.parse(line) as Record<string, unknown>;\n } catch {\n continue;\n }\n\n if (obj.type === \"last-prompt\") {\n if (typeof obj.lastPrompt === \"string\") take(obj.lastPrompt, fromEntries);\n continue;\n }\n\n if (obj.type === \"user\") {\n const text = messageText(obj);\n if (text) take(text, fromMessages);\n }\n }\n }\n\n const source = fromEntries.length > 0 ? fromEntries : fromMessages;\n\n // Consecutive duplicates are common: a prompt is recorded both when it is\n // submitted and again when the turn it belongs to is finalised.\n const deduped = source.filter((p, i) => i === 0 || p !== source[i - 1]);\n return deduped.slice(-limit);\n}\n\n// ---------------------------------------------------------------------------\n// Working tree\n// ---------------------------------------------------------------------------\n\nexport interface WorkingTree {\n branch: string | null;\n head: string | null;\n /** Porcelain entries, capped. */\n changes: string[];\n /** How many entries were dropped by the cap. */\n overflow: number;\n}\n\nexport function readWorkingTree(cwd: string): WorkingTree | null {\n const git = (args: string[]): string | null => {\n try {\n return execFileSync(\"git\", args, {\n cwd,\n encoding: \"utf8\",\n stdio: [\"ignore\", \"pipe\", \"ignore\"],\n }).trim();\n } catch {\n return null;\n }\n };\n\n const branch = git([\"rev-parse\", \"--abbrev-ref\", \"HEAD\"]);\n if (branch === null) return null; // Not a git repo — nothing to report.\n\n const status = git([\"status\", \"--porcelain\"]) ?? \"\";\n const all = status.split(\"\\n\").filter((l) => l.trim().length > 0);\n\n return {\n branch,\n head: git([\"log\", \"-1\", \"--pretty=%h %s\"]),\n changes: all.slice(0, MAX_FILES),\n overflow: Math.max(0, all.length - MAX_FILES),\n };\n}\n\n// ---------------------------------------------------------------------------\n// Digest\n// ---------------------------------------------------------------------------\n\nexport interface DigestInput {\n cwd: string;\n /** Every transcript file for this session, oldest part first. */\n transcriptPaths?: string[];\n timestamp?: string;\n}\n\n/**\n * Build the auto-checkpoint body, or \"\" when there is nothing worth recording.\n *\n * REQUIRING A PROMPT IS THE POINT\n * -------------------------------\n * A checkpoint that replaces an earlier one had better be worth more than what\n * it displaced. Until the user has said something, this session has produced no\n * state — and an uncommitted working tree is not this session's doing; it was\n * most likely already there when the session opened.\n *\n * Without this guard, opening a session in a dirty repo and touching one tool\n * is enough to overwrite the previous session's rich handover with \"clean tree,\n * nothing asked\". The handover survives in the session note either way, but\n * `## Continue` is what the next session is shown, so what sits there matters.\n */\n/**\n * Cap on `@n=` file-declaration lines. A handover reader gets no value from a\n * long file list — five paths are enough to identify what kind of change\n * happened, and the exact count and full list are always one `git status`\n * away via the `z=` trailer.\n */\nconst MAX_AT_LINES = 5;\n\n/** Flatten a prompt (or any field value) to a single trimmed line. */\nfunction flattenField(text: string): string {\n return text.replace(/\\s*\\n\\s*/g, \" \").trim();\n}\n\n/**\n * Strip a git-porcelain status prefix off a `changes` entry, leaving the path.\n *\n * Porcelain lines are a 1-2 char status field, a space, then the path (e.g.\n * `\"M src/foo.ts\"`, `\" M src/foo.ts\"`, `\"?? x.ts\"`). The stripped result falls\n * back to the trimmed original whenever the pattern does not match, so a\n * surprising entry still produces a usable `@n=` line instead of an empty one.\n */\nfunction pathOf(entry: string): string {\n const stripped = entry.replace(/^\\s*\\S{1,2}\\s+/, \"\").trim();\n return stripped || entry.trim();\n}\n\n/**\n * Build the auto-checkpoint body as a single AG2 (Agentish v2) message of kind\n * `T` (task/handover). Pure function of its inputs so it can be unit-tested\n * without touching the filesystem or git.\n *\n * Field layout — see the AG2 validator for the authoritative rules:\n * i = ckpt-<sanitised timestamp>\n * g = the most recent prompt (goal)\n * d = the earlier prompts, joined by \" | \" (\"?\" when there are none)\n * t = \"?\" always — a mechanical handover runs no tests\n * @n = one declaration per changed file, capped at MAX_AT_LINES so the\n * whole message stays within AG2's 25-line limit\n * z = a note summarising the working tree, capped at 200 chars\n */\nexport function ag2Checkpoint(\n prompts: string[],\n tree: WorkingTree | null,\n ts: string\n): string {\n const id = `ckpt-${ts.replace(/[^A-Za-z0-9._-]/g, \"-\")}`;\n const g = flattenField(prompts[0]);\n const d =\n prompts.length > 1 ? prompts.slice(1).map(flattenField).join(\" | \") : \"?\";\n\n const lines: string[] = [\"T\", `i=${id}`, `g=${g}`, `d=${d}`, \"t=?\"];\n\n let extra = 0;\n if (tree) {\n const shown = tree.changes.slice(0, MAX_AT_LINES);\n extra = Math.max(0, tree.changes.length - MAX_AT_LINES) + tree.overflow;\n shown.forEach((entry, i) => lines.push(`@${i + 1}=${pathOf(entry)}`));\n }\n\n let z: string;\n if (!tree) {\n z = \"auto ckpt (no model)\";\n } else {\n const dirty = tree.changes.length;\n const head = tree.head ?? \"nohead\";\n z = `auto ckpt (no model); ${tree.branch}@${head}; ${dirty} dirty`;\n if (extra > 0) z += `; +${extra} paths`;\n z += \"; git status for detail\";\n }\n lines.push(`z=${z.length > 200 ? z.slice(0, 200) : z}`);\n\n return lines.join(\"\\n\");\n}\n\nexport function buildAutosaveBody(input: DigestInput): string {\n const ts = input.timestamp ?? new Date().toISOString();\n const prompts = recentPrompts(input.transcriptPaths ?? []);\n if (prompts.length === 0) return \"\";\n\n const tree = readWorkingTree(input.cwd);\n return ag2Checkpoint(prompts, tree, ts);\n}\n","/**\n * Session handover command — writes a ## Continue section to the project's\n * TODO.md. Called from the session-stop and pre-compact hooks.\n *\n * PRESERVATION\n * ------------\n * This runs on every clean exit and before every compaction, unattended. It\n * used to regenerate a generic four-line block and strip whatever ## Continue\n * was already there — which meant a model-authored checkpoint written seconds\n * earlier by `pai pause` was destroyed by /exit.\n *\n * It now writes in \"auto\" mode: an authored checkpoint describing the *same*\n * session is left alone. One from an earlier session is stale and gets\n * replaced, so TODO.md never points at the wrong session.\n *\n * Still exits 0 on every path — this must never interrupt Claude Code.\n */\n\nimport type { RegistryBackend } from \"../../../storage/registry-interface.js\";\nimport type { SessionRow, ProjectRow } from \"./types.js\";\nimport { applyContinue } from \"../../../session/checkpoint-block.js\";\nimport {\n resolveSessionLine,\n resolveNotePath,\n resolveProjectByCwd,\n} from \"./pause.js\";\nimport {\n findTranscripts,\n buildAutosaveBody,\n resolveTranscriptDir,\n} from \"../../../session/autosave.js\";\n\n/**\n * Reconstruct a resumable digest for a session that is stopping.\n *\n * Best-effort by construction: this runs from a hook on every exit, so a failure\n * to read the transcript must degrade to an empty body — which the checkpoint\n * writer now treats as \"do not overwrite what is already there\" — rather than\n * throw and interrupt Claude Code.\n */\nfunction buildStopBody(project: ProjectRow, sessionId?: string): string {\n try {\n const transcriptPaths = findTranscripts(resolveTranscriptDir(project), sessionId);\n return buildAutosaveBody({ cwd: process.cwd(), transcriptPaths });\n } catch {\n return \"\";\n }\n}\n\nexport async function cmdHandover(\n projectSlug: string | undefined,\n numberOrLatest: string | undefined,\n sessionId?: string\n): Promise<void> {\n const { getRegistryBackend } = await import(\"../../../storage/factory.js\");\n const registryBackend: RegistryBackend = await getRegistryBackend();\n\n // ---- 1. Resolve project ----\n let project: ProjectRow | undefined;\n\n if (projectSlug) {\n project = (await registryBackend.getProjectBySlug(projectSlug)) ?? undefined;\n if (!project) return;\n } else {\n // Same resolver as `pai pause` — including the realpath fallback, so a\n // session started via a symlinked path prefix resolves to the same project\n // rather than silently finding nothing.\n const row = await resolveProjectByCwd(registryBackend, process.cwd());\n if (!row) return;\n project = row;\n }\n\n // ---- 2. Resolve session ----\n let session: SessionRow | undefined;\n const nol = numberOrLatest ?? \"latest\";\n\n if (nol === \"latest\") {\n session = (await registryBackend.getLatestSessionForProject(project!.id)) ?? undefined;\n } else {\n const num = parseInt(nol, 10);\n if (!isNaN(num)) {\n session = (await registryBackend.getSessionByNumber(project!.id, num)) ?? undefined;\n }\n }\n\n // ---- 3. Write, preserving any authored checkpoint for this session ----\n //\n // The session line must be derived exactly as `pai pause` derives it — it is\n // the fallback preservation key, and deriving it independently here is what\n // let this command clobber a checkpoint written seconds earlier.\n //\n // The line alone is not enough. By the time this runs, `session-stop.sh` has\n // already executed `session slug --apply` and `session cleanup --execute`,\n // both of which rewrite the session note filename the line is derived from.\n // The UUID is passed through precisely because it survives that.\n // A body, not just a pointer. This used to write metadata only, on the\n // assumption that the session note holds the real content — but the note is\n // written by a different path that can fail or never run at all, and when it\n // does the checkpoint names a file that does not exist. Reconstructing the\n // same digest the autosave writes (recent prompts + working tree) costs one\n // transcript read and means a stop-hook handover always carries something\n // resumable on its own.\n applyContinue({\n rootPath: project!.root_path,\n authored: \"auto\",\n sessionLine: resolveSessionLine(project!, session, resolveNotePath(project!)),\n sessionId,\n cwd: process.cwd(),\n body: buildStopBody(project!, sessionId),\n });\n}\n","/**\n * Worker-session detection.\n *\n * A disposable headless worker (a `claude -p` run started by an orchestrating\n * session, possibly against a different model provider with a different\n * context window) shares the project directory and the hook configuration\n * with the real session that spawned it. Left alone, the hooks treat it as a\n * session in its own right: they inject project context into it, create and\n * rename a numbered session note for it, autosave it, and enqueue a\n * model-written handover for it. Its compactions also land in the project's\n * transcript folder, where they are indistinguishable from a real session's\n * and drag the measured compaction trigger down (observed 2026-09-17: two\n * workers compacting at ~151k tokens pulled a project's trigger from ~784k\n * to ~151k, and the real session's handover fired at ~50k tokens).\n *\n * The launcher marks such sessions with `PAI_WORKER=1`. Every hook that does\n * per-session bookkeeping returns immediately when this predicate is true.\n * Deliberately NOT guarded: the security validator (a worker's shell\n * commands must still be checked) and observability capture.\n */\nexport function isWorkerSession(env: NodeJS.ProcessEnv = process.env): boolean {\n return env.PAI_WORKER === \"1\";\n}\n","/**\n * context-handover-trigger.ts — decide whether THIS check is the moment a\n * session should get its threshold-triggered pre-compaction handover, and\n * enqueue it with the daemon if so.\n *\n * Called from the same live hooks that already drive the AG2 rolling\n * autosave (UserPromptSubmit, PostToolUse) — see\n * cli/commands/session/autosave.ts. Reuses the context-fill reading and the\n * derived thresholds from hooks/ts/lib/context-fill.ts; this module only\n * adds the \"have we already fired this one\" bookkeeping and the enqueue.\n *\n * BUG FIXED (found in live use, session 77084e72-...): a threshold used to\n * be marked fired the moment the ENQUEUE call succeeded — recording intent,\n * not outcome. When the daemon worker then failed or was restarted mid-spawn\n * (observed: a daemon restart 90s after the enqueue), no handover cache was\n * ever written, but the marker already said \"done\" — so the session\n * compacted with the mechanical scrape only, and would never have retried,\n * ever, for that session. A threshold is now marked CONFIRMED only after a\n * handover cache file actually appears; an enqueue with no cache to show for\n * it within PENDING_TIMEOUT_MS is treated as failed and retried.\n *\n * Never throws. A daemon that isn't running, a stale reading, an unknown\n * fill — every one of those is a reason to do nothing this check and try\n * again next time, not a reason to interrupt the hook that called this.\n */\n\nimport { existsSync, readFileSync, unlinkSync, writeFileSync } from \"node:fs\";\nimport { tmpdir } from \"node:os\";\nimport { join } from \"node:path\";\n\nimport {\n getContextFill,\n contextFillThresholds,\n crossedThresholds,\n isForeignModelFamily,\n isImmediate,\n transcriptModelFamily,\n type ContextFillReading,\n type ThresholdName,\n} from \"../hooks/ts/lib/context-fill.js\";\nimport { isWorkerSession } from \"../hooks/ts/lib/worker-session.js\";\nimport { readContextHandoverCache, type ContextHandoverCache } from \"../hooks/ts/lib/context-handover-cache.js\";\nimport { PaiClient } from \"../daemon/ipc-client.js\";\n\n/** The enqueue only needs to hand the item to the daemon's queue file — the\n * actual LLM call happens later, asynchronously, in the worker loop. A\n * short bound keeps a rare (at most twice per session) daemon hiccup from\n * making the calling hook wait anywhere near as long as a normal IPC call\n * is allowed to. */\nconst ENQUEUE_TIMEOUT_MS = 2_000;\n\n/**\n * How long to wait for a handover cache to appear after an enqueue before\n * treating it as failed and retrying. Generous over the worker's own sonnet\n * timeout (120s, see session-summary-worker.ts's CLAUDE_TIMEOUT_MS) plus\n * queue latency, so a slow-but-working run isn't retried out from under\n * itself — and short enough that a genuinely dead attempt (a daemon restart\n * mid-spawn, exactly what happened in the live failure this fixes) is\n * retried well before the next compaction, not \"never\".\n */\nconst PENDING_TIMEOUT_MS = 5 * 60 * 1000;\n\n/** Both thresholds confirmed — nothing left to check for the rest of the session. */\nconst ALL_THRESHOLDS: ThresholdName[] = [\"warmup\", \"refresh\"];\n\n// ---------------------------------------------------------------------------\n// Persisted state — outcome-based, not intent-based\n// ---------------------------------------------------------------------------\n\n/**\n * `confirmed`: thresholds whose handover was actually verified written.\n * `pending`: an enqueue attempt awaiting outcome confirmation — cleared\n * either when a fresher cache appears (success) or when PENDING_TIMEOUT_MS\n * elapses with nothing to show for it (treated as failed; retried).\n *\n * A file in the OLD shape (`{fired: [...]}`, no `confirmed` key — exactly\n * what the live failure left behind) parses with `confirmed` defaulting to\n * `[]` and `pending` to `null`: it is treated as \"nothing confirmed yet\",\n * which self-heals the stuck session that bug left behind rather than\n * requiring a manual cleanup step.\n */\nexport interface HandoverTriggerState {\n confirmed: ThresholdName[];\n pending: { thresholds: ThresholdName[]; enqueuedAt: string } | null;\n}\n\nexport function triggerStatePath(sessionId: string): string {\n return join(tmpdir(), `pai-context-handover-fired-${sessionId}.json`);\n}\n\nexport function loadTriggerState(sessionId: string): HandoverTriggerState {\n const path = triggerStatePath(sessionId);\n if (!existsSync(path)) return { confirmed: [], pending: null };\n try {\n const raw = JSON.parse(readFileSync(path, \"utf-8\")) as Partial<HandoverTriggerState>;\n return {\n confirmed: Array.isArray(raw.confirmed) ? (raw.confirmed as ThresholdName[]) : [],\n pending: raw.pending && Array.isArray(raw.pending.thresholds) && typeof raw.pending.enqueuedAt === \"string\"\n ? raw.pending\n : null,\n };\n } catch {\n return { confirmed: [], pending: null };\n }\n}\n\nfunction saveTriggerState(sessionId: string, state: HandoverTriggerState): void {\n try {\n writeFileSync(triggerStatePath(sessionId), JSON.stringify(state), \"utf-8\");\n } catch {\n // Best-effort — a missing write only costs a possible re-attempt later.\n }\n}\n\n/**\n * Reset a session's fired/confirmed marker after a compaction, so the next\n * fill cycle gets its own handover instead of being judged against\n * thresholds this session already used up. Deliberately touches ONLY the\n * marker file (`triggerStatePath`) — never the handover cache\n * (`context-handover-cache.ts`), which is what actually gets injected at\n * the compaction boundary and must survive until that injection reads it.\n *\n * WHY THIS EXISTS (do not \"simplify\" it away): without this reset,\n * `confirmed` was permanent for the lifetime of a session id — nothing\n * anywhere ever cleared it. A session observed live on 2026-09-17\n * compacted once, both thresholds got confirmed, and every check for the\n * rest of that session's life then hit the `confirmed` short-circuit at\n * the top of `checkAndEnqueueContextHandover` and returned immediately —\n * even after a SECOND compaction, ~15 hours and a full context refill\n * later, whose injected \"handover\" was still the first one, generated\n * long before, and which said in its own text that nothing had changed\n * since it. A long-lived session compacts more than once; each compaction\n * starts a new fill cycle and deserves its own handover attempt.\n *\n * Call this exactly once per compaction, from the post-compact SessionStart\n * hook that already runs at that moment for other cleanup — see\n * post-compact-inject.ts.\n *\n * If a `pending` enqueue was outstanding at reset time, it is simply\n * dropped along with the rest of the marker — the in-flight daemon job\n * itself is NOT cancelled and will still write its cache entry when it\n * finishes (harmless: cache files are read by generation time, not by\n * whether a marker was watching for them). Because the marker is gone,\n * the next check has no pending entry to resolve, so it does not confirm\n * against that stray cache — it simply runs a fresh crossing check. So\n * this cannot double-enqueue: the only thing that ever calls `enqueue`\n * again is a genuinely fresh threshold crossing after the reset, of which\n * there is exactly one code path.\n *\n * Never throws: a session with no marker file (fresh session, or one\n * already reset) is a plain no-op, not an error.\n */\nexport function resetHandoverTriggerState(sessionId: string): void {\n const path = triggerStatePath(sessionId);\n try {\n if (existsSync(path)) unlinkSync(path);\n } catch {\n // Best-effort — see saveTriggerState above. Worst case on failure: the\n // next compaction re-serves one stale handover, exactly the bug this\n // function fixes, not a corrupted or crashed session.\n }\n}\n\n// ---------------------------------------------------------------------------\n// Enqueue\n// ---------------------------------------------------------------------------\n\nexport interface HandoverTriggerInput {\n sessionId: string;\n cwd: string;\n transcriptPath?: string;\n}\n\nasync function enqueueContextHandover(payload: {\n sessionId: string;\n cwd: string;\n transcriptPath?: string;\n threshold: ThresholdName;\n urgent: boolean;\n}): Promise<void> {\n const client = new PaiClient();\n await client.call(\n \"work_queue_enqueue\",\n {\n type: \"context-handover\",\n // Below session-end (2), above session-summary (4) — this is rarer\n // than session-summary (at most twice per session) and time-sensitive\n // (it exists specifically to beat the compaction it is racing).\n priority: 3,\n payload: {\n sessionId: payload.sessionId,\n cwd: payload.cwd,\n transcriptPath: payload.transcriptPath,\n threshold: payload.threshold,\n urgent: payload.urgent,\n },\n },\n ENQUEUE_TIMEOUT_MS\n );\n}\n\n// ---------------------------------------------------------------------------\n// Dependency injection — for testing without a real daemon or filesystem\n// ---------------------------------------------------------------------------\n\nexport interface HandoverTriggerDeps {\n getReading: (input: HandoverTriggerInput) => ContextFillReading;\n loadState: (sessionId: string) => HandoverTriggerState;\n saveState: (sessionId: string, state: HandoverTriggerState) => void;\n readCache: (sessionId: string) => ContextHandoverCache | null;\n enqueue: typeof enqueueContextHandover;\n now: () => number;\n}\n\nconst defaultDeps: HandoverTriggerDeps = {\n getReading: (input) =>\n getContextFill({ sessionId: input.sessionId, transcriptPath: input.transcriptPath }),\n loadState: loadTriggerState,\n saveState: saveTriggerState,\n readCache: readContextHandoverCache,\n enqueue: enqueueContextHandover,\n now: () => Date.now(),\n};\n\n// ---------------------------------------------------------------------------\n// Main entry point\n// ---------------------------------------------------------------------------\n\nexport interface HandoverTriggerResult {\n /** Newly enqueued this check — an attempt, not yet a confirmed outcome. */\n attempted: ThresholdName[];\n /** Newly CONFIRMED this check — a handover cache was actually verified. */\n confirmed: ThresholdName[];\n}\n\nconst NOTHING: HandoverTriggerResult = { attempted: [], confirmed: [] };\n\n/**\n * Check this session's current context fill against the derived thresholds,\n * confirm or retry any in-flight attempt, and enqueue a context-handover job\n * for any newly-crossed threshold. Never marks a threshold done on the\n * strength of the enqueue call alone — only a verified cache file does that.\n *\n * A single check that crosses both warmup and refresh in one jump (a\n * session first observed already close to compaction) enqueues ONE job,\n * tagged with the more urgent of the two, and — once confirmed — marks both\n * done together, since one model-written handover already covers what\n * either alone would have.\n */\nexport async function checkAndEnqueueContextHandover(\n input: HandoverTriggerInput,\n deps: HandoverTriggerDeps = defaultDeps\n): Promise<HandoverTriggerResult> {\n // A disposable worker session gets no handover: nobody resumes it, and its\n // model may not even be the platform's (a different window, a different\n // provider's bill). Two signals, either suffices: the launcher's env mark,\n // or a transcript whose last assistant turn came from a non-Claude model.\n if (isWorkerSession()) {\n console.error(`[context-handover-trigger] session ${input.sessionId}: worker session (PAI_WORKER=1) — no handover.`);\n return NOTHING;\n }\n // Native is Claude plus the registry's provider models: a primary session\n // on a non-Anthropic provider still gets handovers; only transcripts from\n // models this machine does not run are skipped.\n if (\n input.transcriptPath &&\n isForeignModelFamily(transcriptModelFamily(input.transcriptPath))\n ) {\n console.error(`[context-handover-trigger] session ${input.sessionId}: transcript written by a model this machine does not run — no handover.`);\n return NOTHING;\n }\n\n let state = deps.loadState(input.sessionId);\n if (ALL_THRESHOLDS.every((t) => state.confirmed.includes(t))) return NOTHING;\n\n // Thresholds confirmed DURING this check (from the pending-resolution step\n // below) — carried through every return path so a confirm that falls\n // through to a fresh crossing-check doesn't get silently dropped.\n let confirmedThisCheck: ThresholdName[] = [];\n\n // -------------------------------------------------------------------\n // Resolve any in-flight attempt first — confirm, keep waiting, or\n // time it out and clear it for retry. This is the outcome check: a\n // handover cache generated AFTER the enqueue is proof the worker ran\n // and actually wrote something, not just that the enqueue call itself\n // succeeded.\n // -------------------------------------------------------------------\n if (state.pending) {\n const cache = deps.readCache(input.sessionId);\n const cacheIsFromThisAttempt =\n cache !== null && Date.parse(cache.generatedAt) > Date.parse(state.pending.enqueuedAt);\n\n if (cacheIsFromThisAttempt) {\n const newlyConfirmed = state.pending.thresholds.filter((t) => !state.confirmed.includes(t));\n state = { confirmed: [...state.confirmed, ...newlyConfirmed], pending: null };\n deps.saveState(input.sessionId, state);\n confirmedThisCheck = newlyConfirmed;\n console.error(\n `[context-handover-trigger] session ${input.sessionId}: confirmed [${newlyConfirmed.join(\", \")}] ` +\n `— handover cache verified (generated ${cache!.generatedAt}).`\n );\n if (ALL_THRESHOLDS.every((t) => state.confirmed.includes(t))) {\n return { attempted: [], confirmed: confirmedThisCheck };\n }\n // Fall through — a session that jumped straight to refresh-confirmed\n // territory may already have crossed further; keep checking below.\n } else if (deps.now() - Date.parse(state.pending.enqueuedAt) < PENDING_TIMEOUT_MS) {\n // Still within the window — no news is not yet failure.\n return NOTHING;\n } else {\n console.error(\n `[context-handover-trigger] session ${input.sessionId}: pending [${state.pending.thresholds.join(\", \")}] ` +\n `timed out after ${PENDING_TIMEOUT_MS}ms with no handover cache written — ` +\n `treating as FAILED and retrying (daemon worker likely died or was restarted mid-run).`\n );\n state = { ...state, pending: null };\n deps.saveState(input.sessionId, state);\n }\n }\n\n const reading = deps.getReading(input);\n if (reading.status !== \"ok\" || reading.usedTokens === null) {\n return { attempted: [], confirmed: confirmedThisCheck };\n }\n\n const thresholds = contextFillThresholds(reading, process.env, { cwd: input.cwd });\n if (!thresholds.windowConfirmed) {\n console.error(\n `[context-handover-trigger] session ${input.sessionId}: window size not confirmed — ` +\n `using the assumed default (${reading.windowSize} tokens) rather than a reading Claude Code reported.`\n );\n }\n\n const newlyCrossed = crossedThresholds(reading.usedTokens, thresholds, state.confirmed);\n if (newlyCrossed.length === 0) return { attempted: [], confirmed: confirmedThisCheck };\n\n const urgent = isImmediate(reading.usedTokens, thresholds);\n const mostUrgent = newlyCrossed[newlyCrossed.length - 1];\n\n try {\n await deps.enqueue({\n sessionId: input.sessionId,\n cwd: input.cwd,\n transcriptPath: input.transcriptPath,\n threshold: mostUrgent,\n urgent,\n });\n } catch (err) {\n console.error(\n `[context-handover-trigger] session ${input.sessionId}: enqueue FAILED — will retry next check: ${err}`\n );\n // Nothing persisted — next check sees no pending and tries again.\n return { attempted: [], confirmed: confirmedThisCheck };\n }\n\n state = { ...state, pending: { thresholds: newlyCrossed, enqueuedAt: new Date(deps.now()).toISOString() } };\n deps.saveState(input.sessionId, state);\n console.error(\n `[context-handover-trigger] session ${input.sessionId}: enqueued [${newlyCrossed.join(\", \")}] ` +\n `at ${reading.usedTokens} tokens (trigger=${thresholds.effectiveTriggerTokens}, ` +\n `triggerSource=${thresholds.triggerSource}, autocompactPct=${thresholds.autocompactPct}, urgent=${urgent}) ` +\n `— awaiting outcome confirmation before marking done.`\n );\n\n return { attempted: newlyCrossed, confirmed: confirmedThisCheck };\n}\n","/**\n * pai session autosave [--session-id <uuid>] [--min-gap <seconds>] [--dry-run]\n *\n * The rolling floor. Runs unattended from live hooks during a session, so an\n * interrupted session still leaves a usable `## Continue` behind.\n *\n * It writes in \"auto\" mode, which is what makes it safe to run often: a\n * model-authored checkpoint for the same session is preserved untouched, so\n * this can never trade a good handover for a mechanical one. When there is no\n * authored checkpoint — the Ctrl+C case, the crash case, the \"never got round\n * to it\" case — this is what the next session finds instead of nothing.\n *\n * Silent and exit-0 on every path. It is wired to hooks that fire constantly;\n * anything it printed would be noise, and anything it threw would interrupt\n * Claude Code.\n */\n\nimport { join } from \"node:path\";\nimport { existsSync, statSync, writeFileSync } from \"node:fs\";\nimport { homedir, tmpdir } from \"node:os\";\nimport { applyContinue } from \"../../../session/checkpoint-block.js\";\nimport {\n buildAutosaveBody,\n findTranscripts,\n resolveTranscriptDir,\n} from \"../../../session/autosave.js\";\nimport { checkAndEnqueueContextHandover } from \"../../../session/context-handover-trigger.js\";\nimport { resolveSessionLine, resolveNotePath, resolveProjectByCwd } from \"./pause.js\";\nimport type { ProjectRow, SessionRow } from \"./types.js\";\n\nexport interface AutosaveOptions {\n sessionId?: string;\n minGap?: string;\n dryRun?: boolean;\n}\n\n/** Default seconds between autosaves. Long enough to be free, short enough to matter. */\nconst DEFAULT_MIN_GAP = 240;\n\n// ---------------------------------------------------------------------------\n// Rate limiting\n// ---------------------------------------------------------------------------\n\n/**\n * One sentinel per project, shared by every trigger.\n *\n * The command is wired to more than one hook event, and they fire at wildly\n * different rates. Keying the gap on the project rather than the event means\n * \"at most once per N seconds\" holds across all of them, instead of once per\n * N seconds *each*.\n */\nfunction sentinelPath(rootPath: string): string {\n const key = rootPath.replace(/[^a-zA-Z0-9]/g, \"-\").slice(-80);\n return join(tmpdir(), `pai-autosave-${key}`);\n}\n\nfunction tooRecent(rootPath: string, minGapSeconds: number): boolean {\n const p = sentinelPath(rootPath);\n if (!existsSync(p)) return false;\n try {\n return Date.now() - statSync(p).mtimeMs < minGapSeconds * 1000;\n } catch {\n return false;\n }\n}\n\nfunction touchSentinel(rootPath: string): void {\n try {\n writeFileSync(sentinelPath(rootPath), String(Date.now()), \"utf8\");\n } catch {\n // Best-effort — a missing sentinel only costs an extra write.\n }\n}\n\n// ---------------------------------------------------------------------------\n// Command\n// ---------------------------------------------------------------------------\n\nexport async function cmdAutosave(opts: AutosaveOptions): Promise<void> {\n const minGap = parseInt(opts.minGap ?? String(DEFAULT_MIN_GAP), 10);\n const cwd = process.cwd();\n\n const { getRegistryBackend } = await import(\"../../../storage/factory.js\");\n const registryBackend = await getRegistryBackend();\n\n const project = (await resolveProjectByCwd(registryBackend, cwd)) as ProjectRow | undefined;\n if (!project) return;\n\n if (!opts.dryRun && tooRecent(project.root_path, minGap)) return;\n\n // The transcript lives in the Claude Code project directory, which the\n // registry already records as encoded_dir. Passing the session id matters:\n // it selects this session's transcript — both halves of it — rather than\n // whichever file happens to be newest.\n // The registry's encoded_dir is a hint, not an answer — it goes stale when a\n // project moves and silently disables capture. See resolveTranscriptDir.\n const transcriptPaths = findTranscripts(\n resolveTranscriptDir(project),\n opts.sessionId\n );\n\n // Threshold-triggered pre-compaction handover — runs on the same cadence\n // as the rolling checkpoint below (same rate-limit gate above), but is\n // otherwise independent of it: it must still check even on a tick where\n // there is no new autosave body to write, because a session can sit idle\n // on the CLI side while its token count keeps climbing from tool output.\n // Best-effort and bounded (see context-handover-trigger.ts) — never lets a\n // daemon hiccup slow this hook down or throw out of it.\n if (opts.sessionId && !opts.dryRun) {\n const liveTranscript = transcriptPaths[transcriptPaths.length - 1];\n try {\n await checkAndEnqueueContextHandover({\n sessionId: opts.sessionId,\n cwd,\n transcriptPath: liveTranscript,\n });\n } catch {\n // checkAndEnqueueContextHandover already catches its own errors; this\n // is only a backstop against a bug in that catching.\n }\n }\n\n const body = buildAutosaveBody({ cwd, transcriptPaths });\n // Nothing worth recording — leave whatever is already there alone.\n if (!body) return;\n\n const session = ((await registryBackend.getLatestSessionForProject(project.id)) ??\n undefined) as SessionRow | undefined;\n\n const result = applyContinue({\n rootPath: project.root_path,\n authored: \"auto\",\n sessionLine: resolveSessionLine(project, session, resolveNotePath(project)),\n sessionId: opts.sessionId,\n cwd,\n body,\n dryRun: opts.dryRun,\n });\n\n if (opts.dryRun) {\n console.log(result.block);\n console.log(`\\n[dry-run] action would be: ${result.action}`);\n return;\n }\n\n touchSentinel(project.root_path);\n}\n","/**\n * pai session recent [-n N] [--all] [--json]\n *\n * List named sessions — resumable OR in the clc registry.\n * Default: \"named\" filter (resumable + stubs + transcript-only from clc registry).\n * --all: also include unnamed orphan sessions (only in sessions/ subdir, not in registry).\n *\n * Status column (always shown):\n * resumable — top-level jsonl with system snapshot (claude --resume works) — green\n * stub — in registry, top-level exists but no system lines (Ctrl+C exit) — yellow\n * transcript-only — in registry but no top-level jsonl — yellow\n * orphan — not in registry, only transcript exists — dim (--all only)\n *\n * When AIBroker is running, a \"Live Sessions\" section is shown first,\n * listing currently-active iTerm2 panes that have an active Claude session.\n */\n\nimport chalk from \"chalk\";\nimport { renderTable, err, dim, header } from \"../../utils.js\";\nimport { scanSessions, fmtAge, type SessionStatus } from \"../../lib/session-scan.js\";\nimport { fetchLiveSessions, type AiBrokerSessionMeta } from \"../../lib/aibroker-client.js\";\n\n// ---------------------------------------------------------------------------\n// Helpers\n// ---------------------------------------------------------------------------\n\nfunction fmtSize(bytes: number): string {\n if (bytes < 1024) return `${bytes}B`;\n if (bytes < 1_048_576) return `${Math.round(bytes / 1024)}K`;\n return `${(bytes / 1_048_576).toFixed(1)}M`;\n}\n\nfunction fmtStatus(status: SessionStatus): string {\n switch (status) {\n case \"resumable\":\n return chalk.green(\"resumable\");\n case \"stub\":\n return chalk.yellow(\"stub\");\n case \"transcript-only\":\n return chalk.yellow(\"transcript-only\");\n case \"orphan\":\n return chalk.dim(\"orphan\");\n }\n}\n\n// ---------------------------------------------------------------------------\n// Live-sessions rendering (AIBroker integration)\n// ---------------------------------------------------------------------------\n\nfunction renderLiveSessions(allLiveSessions: AiBrokerSessionMeta[], allTabs: boolean): void {\n // Default: Claude sessions only (kind === \"claude\"). --all-tabs includes shells.\n const displayed = allTabs\n ? allLiveSessions\n : allLiveSessions.filter((s) => s.kind !== \"shell\");\n const skippedCount = allLiveSessions.length - displayed.length;\n\n if (displayed.length === 0) return;\n\n console.log(\"\\n\" + header(\"Live Sessions\") + \"\\n\");\n\n const liveHeaders = [\"#\", \"id\", \"name\", \"at prompt\", \"kind\"];\n const liveRows = displayed.map((s, idx) => {\n const shortId = s.sessionId.slice(0, 8);\n // Prefer paiName for Claude sessions, fall back to iTerm2 tab name\n const rawName = s.paiName ?? s.name;\n const name = rawName.length > 36 ? rawName.slice(0, 35) + \"…\" : rawName;\n const atPrompt = s.atPrompt ? chalk.green(\"yes\") : chalk.yellow(\"busy\");\n const kind = s.kind === \"claude\"\n ? chalk.cyan(s.kind)\n : chalk.dim(s.kind);\n\n return [\n chalk.dim(String(idx + 1)),\n chalk.cyan(shortId),\n name,\n atPrompt,\n kind,\n ];\n });\n\n console.log(renderTable(liveHeaders, liveRows));\n\n if (skippedCount > 0) {\n console.log(\n dim(` (${skippedCount} shell tab${skippedCount === 1 ? \"\" : \"s\"} hidden — use --all-tabs to show)`)\n );\n }\n}\n\n// ---------------------------------------------------------------------------\n// Command\n// ---------------------------------------------------------------------------\n\nexport async function cmdRecent(\n opts: { n?: string; all?: boolean; allTabs?: boolean; json?: boolean }\n): Promise<void> {\n const limit = parseInt(opts.n ?? \"20\", 10);\n const includeAll = opts.all === true;\n const allTabs = opts.allTabs === true;\n\n // Fetch live sessions from AIBroker (silently no-ops if not running).\n const liveSessions = await fetchLiveSessions();\n\n const sessions = await scanSessions({\n limit,\n filter: includeAll ? \"all\" : \"named\",\n });\n\n if (opts.json) {\n const output = {\n live: liveSessions.map((s) => ({\n index: s.index,\n sessionId: s.sessionId,\n name: s.name,\n paiName: s.paiName,\n atPrompt: s.atPrompt,\n kind: s.kind,\n active: s.active,\n })),\n paused: sessions.map((s, idx) => ({\n idx: idx + 1,\n uuid: s.uuid,\n shortId: s.shortId,\n resumable: s.resumable,\n sessionStatus: s.sessionStatus,\n age: fmtAge(s.mtime),\n mtime: s.mtime,\n name: s.friendlyName,\n lastUserPrompt: s.lastUserPrompt,\n userLines: s.userLines,\n msgCount: s.msgCount,\n topLevelSize: s.topLevelSize,\n decodedPath: s.decodedPath,\n })),\n };\n console.log(JSON.stringify(output, null, 2));\n return;\n }\n\n // ── Live section ──────────────────────────────────────────────────────────\n const hasClaudeLive = liveSessions.some((s) => s.kind === \"claude\");\n if (liveSessions.length > 0) {\n renderLiveSessions(liveSessions, allTabs);\n }\n\n // ── Paused / disk-scan section ────────────────────────────────────────────\n if (sessions.length === 0) {\n if (!hasClaudeLive) {\n // Nothing at all\n if (includeAll) {\n console.log(err(\"No sessions found in ~/.claude/projects/.\"));\n } else {\n console.log(\n err(\n \"No named sessions found.\\n\\n\" +\n \" Named sessions appear when you have entries in ~/.claude/session.json\\n\" +\n \" (set via /Name inside Claude Code) or resumable top-level jsonl files.\\n\" +\n \" Run: pai session recent --all to list all sessions including unnamed orphans.\"\n )\n );\n }\n }\n // If we had live Claude sessions, nothing more to print.\n return;\n }\n\n const title = includeAll\n ? \"Paused / All Sessions (named + orphans)\"\n : \"Paused / Resumable Sessions\";\n console.log(\"\\n\" + header(title) + \"\\n\");\n\n const headers = [\"#\", \"id\", \"age\", \"name / project\", \"last prompt\", \"msgs\", \"size\", \"status\"];\n\n const rows = sessions.map((s, idx) => {\n const name = s.friendlyName ?? s.decodedPath;\n const prompt = s.lastUserPrompt\n ? chalk.dim(\n s.lastUserPrompt.length > 40\n ? s.lastUserPrompt.slice(0, 39) + \"…\"\n : s.lastUserPrompt\n )\n : dim(\"—\");\n\n return [\n chalk.dim(String(idx + 1)),\n chalk.cyan(s.shortId),\n dim(fmtAge(s.mtime)),\n name.length > 26 ? name.slice(0, 25) + \"…\" : name,\n prompt,\n s.userLines > 0 ? String(s.userLines) : dim(\"—\"),\n dim(fmtSize(s.topLevelSize)),\n fmtStatus(s.sessionStatus),\n ];\n });\n\n console.log(renderTable(headers, rows));\n\n console.log(\n \"\\n\" +\n dim(\"Go to a session: \") +\n chalk.white(\"pai resume <name>\") +\n \"\\n\" +\n (hasClaudeLive\n ? dim(\"Pause all live: \") + chalk.white(\"pai pause all\") + \"\\n\"\n : \"\") +\n (includeAll\n ? \"\"\n : dim(\"Show all sessions: \") +\n chalk.white(\"pai sessions --all\") +\n \"\\n\")\n );\n}\n","/**\n * pai resume <name> / pai sessions goto <name> [--dry-run]\n *\n * Smart session launcher with auto-fallback:\n *\n * 1. Resolve name → (uuid?, projectDir, friendlyName)\n * 2. chdir(projectDir)\n * 3. If uuid is set:\n * a. Probe: is a transcript for <uuid> on disk under this cwd?\n * (see probeResume in lib/launch.ts — the filesystem answers,\n * and only an unrecognised layout falls back to spawning claude)\n * b. If probe succeeds:\n * exec claude --resume <uuid> --name \"<friendlyName>\" \"/Name <friendlyName>\"\n * — through the provider the transcript ran on, when its model\n * matches one (providerResumePlan in lib/launch.ts)\n * c. Else (probe failed):\n * print clear stderr line: \"Resume failed. Starting fresh session in same dir.\"\n * exec claude --name \"<friendlyName>\" \"/Name <friendlyName>\"\n * 4. Else (no uuid known):\n * exec claude --name \"<friendlyName>\" \"/Name <friendlyName>\"\n *\n * Steps 3 and 4 are launchInDir's, shared — this file no longer carries its\n * own copy of the spawn dance.\n *\n * Why both --name AND /Name?\n * --name <friendlyName> → sets Claude Code's internal session label\n * \"/Name <friendlyName>\" → runs the /Name slash command via AIBroker, which updates\n * iTerm tab title, statusline, and AIBroker session registry.\n * No trailing `go`: the operator types it by hand when the\n * TODO.md handover is wanted\n *\n * The probe is two `existsSync` calls on the happy path. On the fallback path, total latency\n * is the same as a fresh start.\n */\n\nimport { realpathSync } from \"node:fs\";\nimport chalk from \"chalk\";\nimport { err } from \"../../utils.js\";\nimport {\n scanSessions,\n resolveSessionByNameOrId,\n fmtAge,\n type ScannedSession,\n} from \"../../lib/session-scan.js\";\nimport {\n launchInDir,\n launchPrompt,\n providerResumePlan,\n resumeArgvText,\n} from \"../../lib/launch.js\";\n\n// ---------------------------------------------------------------------------\n// Command\n// ---------------------------------------------------------------------------\n\nexport async function cmdGoto(\n query: string,\n opts: { dryRun?: boolean; noName?: boolean; noGo?: boolean }\n): Promise<void> {\n // ---- 1. Resolve name → session ----\n const allSessions = await scanSessions({ limit: 500, filter: \"named\" });\n\n let resolved;\n try {\n resolved = resolveSessionByNameOrId(allSessions, query);\n } catch (resolveErr) {\n let msg = String(resolveErr).replace(/^Error: /, \"\");\n // Append pai find suggestion for \"not found\" errors\n if (msg.includes(\"No session found matching\")) {\n msg += `\\n\\nTip: pai find \"${query}\" — search prompt history by keywords`;\n }\n console.error(err(msg));\n process.exitCode = 1;\n return;\n }\n\n const { session: matchedSession, friendlyName } = resolved;\n\n // ---- 2. Find best resumable UUID for this project ----\n let resumableUuid: string | undefined;\n let resumableSession: ScannedSession | undefined;\n\n if (matchedSession.resumable) {\n resumableUuid = matchedSession.uuid;\n resumableSession = matchedSession;\n } else if (matchedSession.encodedDir) {\n // Find the most-recently-modified resumable session in the same project dir\n const sameProject = allSessions.filter(\n (s) => s.encodedDir === matchedSession.encodedDir && s.resumable\n );\n sameProject.sort((a, b) => b.mtime - a.mtime);\n if (sameProject.length > 0) {\n resumableSession = sameProject[0];\n resumableUuid = resumableSession.uuid;\n }\n }\n\n // ---- 3. Determine project dir (realpathSync for --resume cwd correctness) ----\n const rawDir =\n matchedSession.clcDirectory ??\n matchedSession.registryRootPath ??\n matchedSession.decodedPath;\n\n let projectDir: string;\n try {\n projectDir = realpathSync(rawDir);\n } catch {\n console.error(\n err(\n `session \"${query}\": directory does not exist or cannot be resolved.\\n` +\n ` Registry says: ${rawDir}\\n` +\n ` The directory may have moved or been deleted.`\n )\n );\n process.exitCode = 1;\n return;\n }\n\n // ---- 4. Build argv components ----\n const name = friendlyName ?? query;\n\n // ---- 5. Dry-run mode ----\n if (opts.dryRun) {\n if (resumableUuid) {\n const plan = providerResumePlan(resumableUuid, projectDir);\n const argvResume = resumeArgvText(resumableUuid, name, plan);\n const argvFresh = `claude --name \"${name}\" \"${launchPrompt(name)}\"`;\n console.log(\n \"\\n\" + chalk.bold(\"Dry run — would probe then exec (RESUME path):\") + \"\\n\"\n );\n console.log(` cwd: ${chalk.cyan(projectDir)}`);\n console.log(` probe: claude --resume ${resumableUuid} --print --output-format=json \"_\"`);\n if (plan) {\n console.log(` route: ${plan.provider} (${plan.model})`);\n }\n console.log(` argv: ${chalk.white(argvResume)}`);\n console.log(` fallback: ${chalk.yellow(argvFresh)}`);\n if (resumableSession) {\n console.log(`\\n uuid: ${resumableSession.uuid}`);\n console.log(` age: ${fmtAge(resumableSession.mtime)}`);\n console.log(` status: ${resumableSession.sessionStatus}`);\n console.log(` sys: ${resumableSession.topLevelSystemLines} system lines`);\n }\n } else {\n const argvFresh = `claude --name \"${name}\" \"${launchPrompt(name)}\"`;\n console.log(\n \"\\n\" + chalk.bold(\"Dry run — would exec (FRESH path, no resumable UUID):\") + \"\\n\"\n );\n console.log(` cwd: ${chalk.cyan(projectDir)}`);\n console.log(` argv: ${chalk.white(argvFresh)}`);\n }\n console.log();\n return;\n }\n\n // ---- 6. Live execution ----\n // The whole probe → resume → fresh-fallback dance lives in launchInDir and\n // nowhere else. This file carried a near-verbatim copy of it, which is how\n // probeResume was once fixed in one copy while `pai resume <name>` kept the\n // bug. `engine: \"claude\"` keeps goto's fresh paths exactly what they were —\n // plain claude, no worker routing — while the resume inherits the provider\n // the transcript actually ran on.\n launchInDir(projectDir, name, { resumableUuid, engine: \"claude\" });\n}\n","/**\n * pai session restore — put displaced transcripts back where resume looks.\n *\n * PAI moved session transcripts out of `~/.claude/projects/<dir>/` into\n * `<dir>/sessions/`, and `claude --resume <uuid>` reads only the former. The\n * archiver no longer does that (it hardlinks), but the sessions already moved\n * stayed moved — and every checkpoint PAI ever wrote ends with\n * \"Resume with: claude --resume <uuid>\". For a displaced session that line is an\n * instruction that cannot work, and nothing says so.\n *\n * One project measured 1 transcript at the project root against 52 underneath.\n *\n * So this is a repair, and it is deliberately a COMMAND rather than another hook.\n * Silently relinking dozens of a user's files during an unrelated session start is\n * the same class of mistake as silently moving them was. Dry run is the default;\n * `--execute` is the only thing that touches disk.\n *\n * The link logic itself is imported, not rewritten. Today's other bug was a fix\n * that landed in one of three copies of `probeResume`, so `restoreTopLevel` stays\n * the single implementation.\n */\n\nimport { existsSync, readdirSync, statSync, readFileSync } from \"node:fs\";\nimport { join } from \"node:path\";\nimport { restoreTopLevel, hasConversation } from \"../../lib/launch.js\";\nimport { claudeProjectsDir } from \"../../../registry/moved.js\";\nimport { smartDecodeDir, ok, warn, err, dim, bold } from \"../../utils.js\";\n\nconst UUID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;\n\nexport interface DisplacedSession {\n uuid: string;\n /** The `~/.claude/projects/<encoded>` directory holding it. */\n projectDir: string;\n encodedDir: string;\n /** Decoded working directory, when it can be recovered. */\n cwd: string | null;\n bytes: number;\n mtime: number;\n /** Files that tell the user to resume this id — the lie being repaired. */\n promisedBy: string[];\n /**\n * Whether the transcript contains an actual exchange.\n *\n * False means restoring it changes nothing: `claude --resume` answers\n * \"No conversation found\" for a file with no conversation in it, wherever it\n * sits. Measured 2026-08-04 on 046bb712 — 537 bytes of `last-prompt`,\n * `custom-title`, `agent-name`, `mode`, `permission-mode` and nothing else. It\n * was restored, correctly hardlinked, and still would not resume.\n *\n * Worth reporting rather than hiding, because that id is named in a checkpoint\n * telling the user to resume it. The honest answer is that the session was\n * never there, not that the repair failed.\n */\n hasConversation: boolean;\n}\n\n/*\n * Stub detection lives in launch.ts, imported above, and this is the second time\n * today that duplicating a helper cost real work — so it stays imported.\n *\n * The version that was here read a bounded 256 KB head and looked only for an\n * assistant marker. AIBroker refuted it with measurements and both causes are\n * confirmed on this machine:\n *\n * b3462801, 867 KB, a session we had BOTH verified claude --resume accepts:\n * line 1 is 762,976 bytes — one hook-context attachment blob\n * first \"type\":\"user\" at byte 766,830\n * so any head shorter than 766 KB reports a working session as an empty stub.\n *\n * b8cd4a5d, 2,626 bytes, 3 user lines and ZERO assistant lines: restored and\n * probed, claude FINDS it. A user-only transcript is resumable, so requiring an\n * assistant marker is simply the wrong test.\n *\n * Measured over this project's 52 archived transcripts: head+assistant-only said\n * 20 real / 32 stubs, chunked-full-scan + user-OR-assistant says 33 real / 19\n * stubs. Thirteen disagreements, every one of them head=stub / full=real — a 41%\n * false-stub rate, all in the direction that talks the user out of a recovery.\n * The \"745 KB stub\" I reported was one of these.\n *\n * Cost is not the tradeoff it looked like: a full scan short-circuits at the\n * first marker, so a real session stops early, and a genuine stub is small\n * enough to read whole. The head read paid 256 KB on every file including the\n * ones it then got wrong.\n */\n\n/**\n * Transcripts that exist under `sessions/` with no twin at the project root.\n *\n * A twin means nothing needs doing: the archiver hardlinks now, so the normal\n * state is both names pointing at one inode. Only the ones missing upstairs were\n * displaced by the old rename.\n */\nexport function findDisplaced(projectsDir = claudeProjectsDir()): DisplacedSession[] {\n const out: DisplacedSession[] = [];\n\n let encodedDirs: string[];\n try {\n encodedDirs = readdirSync(projectsDir);\n } catch {\n return out; // no projects dir at all — nothing to repair, not an error\n }\n\n for (const encodedDir of encodedDirs) {\n const projectDir = join(projectsDir, encodedDir);\n const sessionsDir = join(projectDir, \"sessions\");\n if (!existsSync(sessionsDir)) continue;\n\n let files: string[];\n try {\n files = readdirSync(sessionsDir);\n } catch {\n continue;\n }\n\n for (const file of files) {\n if (!file.endsWith(\".jsonl\")) continue;\n const uuid = file.slice(0, -6);\n if (!UUID_RE.test(uuid)) continue;\n if (existsSync(join(projectDir, file))) continue; // already has its twin\n\n let bytes = 0;\n let mtime = 0;\n try {\n const st = statSync(join(sessionsDir, file));\n bytes = st.size;\n mtime = st.mtimeMs;\n } catch {\n continue; // vanished under us — nothing to restore\n }\n\n out.push({\n uuid,\n projectDir,\n encodedDir,\n cwd: smartDecodeDir(encodedDir),\n bytes,\n mtime,\n promisedBy: [],\n hasConversation: hasConversation(join(sessionsDir, file)),\n });\n }\n }\n\n // Biggest first: transcript size is the best available proxy for how much work\n // is currently unreachable, and it is what makes the report worth reading.\n return out.sort((a, b) => b.bytes - a.bytes);\n}\n\n/**\n * Notes that instruct the user to resume this id.\n *\n * Bounded on purpose — the project's own TODO.md and note files, never a search\n * from the home directory. The point is to name the specific promise each\n * restore makes true, so a count of files is not enough.\n */\nexport function findResumePromises(uuid: string, cwd: string | null): string[] {\n if (!cwd) return [];\n\n const candidates: string[] = [];\n for (const notesDir of [cwd, join(cwd, \"Notes\"), join(cwd, \"notes\")]) {\n try {\n for (const entry of readdirSync(notesDir)) {\n if (entry.endsWith(\".md\")) candidates.push(join(notesDir, entry));\n }\n } catch {\n /* not a directory here — try the next */\n }\n }\n\n // Deduplicate by INODE, not by path text and not by realpath.\n //\n // macOS is case-insensitive, so `Notes` and `notes` above are the same\n // directory and every note was reported twice, once per spelling. The obvious\n // fix — resolve with realpathSync and compare — works under Bun and does NOT\n // work under Node, whose non-native realpathSync leaves case alone; the tests\n // caught exactly that split. Identity by (device, inode) is filesystem truth\n // in either runtime, and it collapses symlinks for free, which matters because\n // PAI symlinks note directories into the Obsidian vault.\n const seen = new Set<string>();\n const hits: string[] = [];\n for (const file of candidates) {\n let key: string;\n try {\n const st = statSync(file);\n key = `${st.dev}:${st.ino}`;\n } catch {\n key = file.toLowerCase(); // cannot stat — path text is all that is left\n }\n if (seen.has(key)) continue;\n seen.add(key);\n\n try {\n if (readFileSync(file, \"utf8\").includes(uuid)) hits.push(file);\n } catch {\n /* unreadable — silence is right, this is a report not a gate */\n }\n }\n return hits;\n}\n\nfunction human(bytes: number): string {\n if (bytes >= 1024 * 1024) return `${(bytes / 1024 / 1024).toFixed(1)} MB`;\n if (bytes >= 1024) return `${Math.round(bytes / 1024)} KB`;\n return `${bytes} B`;\n}\n\nexport interface RestoreOptions {\n execute?: boolean;\n json?: boolean;\n /** Only sessions a checkpoint tells the user to resume. */\n promised?: boolean;\n /** Only sessions belonging to this working directory. */\n cwd?: string;\n /** List every displaced session instead of the largest few. */\n all?: boolean;\n /**\n * Also restore transcripts with no conversation in them.\n *\n * Off by default because it is pointless work, and because the raw count\n * misrepresents the damage: of 2869 displaced transcripts measured 2026-08-04,\n * 2240 were stubs and only 629 held an actual conversation. 1493 of those stubs\n * came from one probe tool spawning sessions that were never used. Skipping\n * them is what turns \"450 MB displaced\" into the honest \"417 MB recoverable\"\n * without a special case for anybody's tooling.\n */\n includeStubs?: boolean;\n}\n\n/** How many to list before the report stops being readable. */\nconst LIST_LIMIT = 20;\n\n/**\n * Which of the displaced sessions a run is about, and which of those to link.\n *\n * Pure, and separate from the printing, for two reasons. The obvious one is that\n * it can be tested without pointing a test at the developer's real\n * `~/.claude/projects` — an earlier draft of the test did exactly that and would\n * have relinked live files. The other is that the report and the action must\n * agree: `--json` originally restored everything in scope while the formatted\n * path skipped stubs, so the two modes did different things with the same flags.\n */\nexport function selectTargets(\n everything: DisplacedSession[],\n opts: RestoreOptions\n): { inScope: DisplacedSession[]; toRestore: DisplacedSession[] } {\n const inScope = everything.filter((d) => {\n if (opts.promised && d.promisedBy.length === 0) return false;\n if (opts.cwd && d.cwd !== opts.cwd) return false;\n return true;\n });\n\n const toRestore = opts.includeStubs\n ? inScope\n : inScope.filter((d) => d.hasConversation);\n\n return { inScope, toRestore };\n}\n\nexport function cmdRestore(opts: RestoreOptions = {}): void {\n const everything = findDisplaced();\n for (const d of everything) {\n d.promisedBy = findResumePromises(d.uuid, d.cwd);\n }\n\n // Scoping exists because the first real dry run reported 2874 transcripts and\n // 450 MB, machine-wide — not the ~52 of one project we had been reasoning\n // about. Restoring is non-destructive and costs no disk (hardlinks), but\n // relinking 2874 files across every project a user has ever opened is not\n // something to do because they typed a command once, and an unscoped list of\n // 2874 lines is not a report anyone can act on.\n const { inScope: displaced, toRestore } = selectTargets(everything, opts);\n\n if (opts.json) {\n console.log(\n JSON.stringify(\n {\n displaced,\n // Named so a script can see that stubs were held back, rather than\n // inferring it from a count that does not match.\n wouldRestore: toRestore.map((d) => d.uuid),\n executed: Boolean(opts.execute),\n },\n null,\n 2\n )\n );\n if (opts.execute) for (const d of toRestore) restoreTopLevel(d.uuid, d.projectDir);\n return;\n }\n\n console.log();\n console.log(bold(\" Displaced session transcripts\"));\n console.log();\n\n if (displaced.length === 0) {\n if (everything.length > 0) {\n console.log(ok(` Nothing displaced in this scope.`));\n console.log(dim(` ${everything.length} displaced elsewhere — drop the filters to see them.`));\n } else {\n console.log(ok(\" Nothing displaced — every archived transcript has its twin at the\"));\n console.log(dim(\" project root, which is where claude --resume looks.\"));\n }\n console.log();\n return;\n }\n\n const promised = displaced.filter((d) => d.promisedBy.length > 0);\n const totalBytes = displaced.reduce((n, d) => n + d.bytes, 0);\n\n console.log(\n warn(\n ` ${displaced.length} transcript(s), ${human(totalBytes)}, currently unresumable.`\n )\n );\n if (displaced.length !== everything.length) {\n console.log(dim(` (filtered from ${everything.length} machine-wide)`));\n }\n if (promised.length > 0) {\n console.log(\n err(\n ` ${promised.length} named in a checkpoint that tells you to resume them — those lines are lies today.`\n )\n );\n }\n const stubs = displaced.filter((d) => !d.hasConversation);\n if (stubs.length > 0) {\n // Restoring these is harmless and pointless. Saying so is the difference\n // between a report and a sales pitch: `claude --resume` answers\n // \"No conversation found\" for an empty transcript wherever it sits.\n console.log(\n dim(\n ` ${stubs.length} of them hold no conversation at all — restoring those changes nothing.`\n )\n );\n }\n console.log();\n\n // Promised ones first regardless of size: a checkpoint pointing at a dead id is\n // the concrete harm, and it is what --promised restores on its own.\n const ordered = [...promised, ...displaced.filter((d) => d.promisedBy.length === 0)];\n const shown = opts.all ? ordered : ordered.slice(0, LIST_LIMIT);\n\n for (const d of shown) {\n const where = d.cwd ?? dim(`(undecodable: ${d.encodedDir})`);\n const stub = d.hasConversation ? \"\" : warn(\" [stub — no conversation]\");\n console.log(\n ` ${bold(d.uuid.slice(0, 8))} ${human(d.bytes).padStart(8)} ${where}${stub}`\n );\n for (const file of d.promisedBy) {\n // This is the line the repair makes true again — unless there is nothing\n // in the file, in which case say so rather than implying a recovery.\n console.log(\n d.hasConversation\n ? err(` promised by ${file}`)\n : dim(` promised by ${file} (promise cannot be kept: empty transcript)`)\n );\n }\n }\n\n if (shown.length < ordered.length) {\n console.log();\n console.log(\n dim(` … and ${ordered.length - shown.length} more. --all lists every one.`)\n );\n }\n\n console.log();\n\n if (!opts.execute) {\n console.log(dim(\" Dry run — nothing was touched.\"));\n console.log(dim(\" Restore hardlinks each transcript back to the project root: same inode,\"));\n console.log(dim(\" no copy, and the sessions/ archive is left exactly where it is.\"));\n console.log();\n console.log(dim(\" --promised --execute just the ones a checkpoint promises (start here)\"));\n console.log(dim(\" --cwd <path> --execute one project\"));\n console.log(dim(\" --execute everything in scope\"));\n console.log();\n return;\n }\n\n // Report everything in scope, restore only what restoring can help. A stub is\n // listed above with its label so the picture stays complete, but linking it\n // would be work with a guaranteed null result.\n const target = toRestore;\n const skipped = displaced.length - target.length;\n\n let restored = 0;\n const failed: string[] = [];\n for (const d of target) {\n if (restoreTopLevel(d.uuid, d.projectDir)) restored++;\n else failed.push(d.uuid);\n }\n\n console.log(ok(` Restored ${restored} of ${target.length} recoverable.`));\n if (skipped > 0) {\n console.log(\n dim(` Skipped ${skipped} stub(s) — nothing in them to resume. --include-stubs overrides.`)\n );\n }\n if (failed.length > 0) {\n console.log(warn(` Could not restore ${failed.length}:`));\n for (const uuid of failed) console.log(dim(` ${uuid}`));\n }\n console.log();\n}\n","/**\n * Commander registration for all `pai session` sub-commands.\n */\n\nimport type { Command } from \"commander\";\nimport {\n cmdList,\n cmdInfo,\n cmdRename,\n cmdSlug,\n cmdTag,\n cmdRoute,\n cmdActive,\n cmdAutoRoute,\n} from \"./commands.js\";\nimport { cmdCheckpoint } from \"./checkpoint.js\";\nimport { cmdHandover } from \"./handover.js\";\nimport { cmdAutosave } from \"./autosave.js\";\nimport { cmdRecent } from \"./recent.js\";\nimport { cmdGoto } from \"./goto.js\";\nimport { cmdPause } from \"./pause.js\";\nimport { cmdRestore } from \"./restore.js\";\n\nexport function registerSessionCommands(sessionCmd: Command): void {\n // pai session list [-n N] [--all] [--json]\n // NEW: resumable sessions catalog (was \"recent\"). Short form: pai sessions.\n sessionCmd\n .command(\"list\")\n .description(\n \"Resumable sessions catalog — named sessions with resume status.\\n\" +\n \"Short form: pai sessions\\n\" +\n \"Default shows all sessions you've named or that are resumable.\\n\" +\n \"Use --all to also show unnamed orphan sessions.\"\n )\n .option(\"-n <count>\", \"Maximum sessions to show (default: 20)\", \"20\")\n .option(\"--all\", \"Include unnamed orphan sessions (not in clc registry)\")\n .option(\"--json\", \"Output raw JSON instead of formatted table\")\n .action(async (opts: { n?: string; all?: boolean; json?: boolean }) => {\n await cmdRecent(opts);\n });\n\n // pai session restore [--execute]\n //\n // Repair, not routine. PAI's old archiver moved transcripts out of the project\n // root, and claude --resume reads only that root, so those sessions became\n // unresumable while every checkpoint kept telling the user to resume them.\n // Dry run by default: relinking dozens of a user's files unasked is the same\n // class of mistake as moving them was.\n sessionCmd\n .command(\"restore\")\n // One line, no embedded newlines: the docs generator turns them into\n // paragraph breaks, which split this sentence mid-clause in\n // docs/commands/session.md.\n .description(\n \"Restore transcripts PAI displaced into sessions/ so claude --resume can find them again. \" +\n \"Reports what is unresumable, which checkpoints promise it, and which transcripts are empty. \" +\n \"Dry run unless --execute is given.\"\n )\n .option(\"--execute\", \"Actually restore (hardlink back to the project root)\")\n .option(\"--promised\", \"Only sessions a checkpoint tells you to resume\")\n .option(\"--cwd <path>\", \"Only sessions belonging to this working directory\")\n .option(\"--all\", \"List every displaced session, not just the largest few\")\n .option(\"--include-stubs\", \"Also restore transcripts that hold no conversation\")\n .option(\"--json\", \"Output raw JSON instead of a formatted report\")\n .action(\n (opts: {\n execute?: boolean;\n promised?: boolean;\n cwd?: string;\n all?: boolean;\n includeStubs?: boolean;\n json?: boolean;\n }) => {\n cmdRestore(opts);\n }\n );\n\n // pai session info <project-slug> <number>\n sessionCmd\n .command(\"info <project-slug> <number>\")\n .description(\"Show full details for a specific session\")\n .action(async (projectSlug: string, number: string) => {\n await cmdInfo(projectSlug, number);\n });\n\n // pai session rename <project-slug> <number> <new-slug>\n sessionCmd\n .command(\"rename <project-slug> <number> <new-slug>\")\n .description(\n \"Rename a session note — updates file on disk, H1 title, and registry\"\n )\n .action(async (projectSlug: string, number: string, newSlug: string) => {\n await cmdRename(projectSlug, number, newSlug);\n });\n\n // pai session slug <project-slug> <number|latest>\n sessionCmd\n .command(\"slug <project-slug> <number>\")\n .description(\n \"Generate a descriptive slug from the session JSONL transcript\"\n )\n .option(\"--apply\", \"Rename the session note using the generated slug\")\n .action(\n async (projectSlug: string, number: string, opts: { apply?: boolean }) => {\n await cmdSlug(projectSlug, number, opts);\n }\n );\n\n // pai session tag <project-slug> <number> [tags...]\n sessionCmd\n .command(\"tag <project-slug> <number> [tags...]\")\n .description(\n \"Set or show tags on a session. Tags can be space-separated or comma-separated.\"\n )\n .action(async (projectSlug: string, number: string, tags: string[]) => {\n await cmdTag(projectSlug, number, tags);\n });\n\n // pai session route <project-slug> <number> <target-project>\n sessionCmd\n .command(\"route <project-slug> <number> <target-project>\")\n .description(\n \"Create a cross-reference link from a session to a target project\"\n )\n .option(\"--type <type>\", \"Link type: related | follow-up | reference\", \"related\")\n .action(\n async (\n projectSlug: string,\n number: string,\n targetProject: string,\n opts: { type?: string }\n ) => {\n await cmdRoute(projectSlug, number, targetProject, opts);\n }\n );\n\n // pai session handover [project-slug] [number-or-latest]\n sessionCmd\n .command(\"handover [project-slug] [number-or-latest]\")\n .description(\n \"Write a ## Continue section to the project's TODO.md.\\n\" +\n \"Called automatically from session-stop and pre-compact hooks.\\n\" +\n \"Records the last session identifier, timestamp, and working directory\\n\" +\n \"so the next session can resume from the correct context.\"\n )\n .option(\n \"--session-id <uuid>\",\n \"Claude session UUID. Used to decide whether an existing authored\\n\" +\n \"checkpoint belongs to this session and must be preserved. Without it\\n\" +\n \"the comparison falls back to the session note filename, which the stop\\n\" +\n \"hook itself renames — so pass it whenever it is known.\"\n )\n .action(\n async (\n projectSlug: string | undefined,\n numberOrLatest: string | undefined,\n opts: { sessionId?: string }\n ) => {\n await cmdHandover(projectSlug, numberOrLatest, opts.sessionId);\n }\n );\n\n // pai session checkpoint <message>\n sessionCmd\n .command(\"checkpoint <message>\")\n .description(\n \"Append a timestamped checkpoint to the active session note.\\n\" +\n \"Designed for hooks (PostToolUse, UserPromptSubmit) — fast and silent.\\n\" +\n \"Rate-limited: skips silently if last checkpoint was < --min-gap seconds ago.\"\n )\n .option(\n \"--min-gap <seconds>\",\n \"Minimum seconds between checkpoints (default: 300 = 5 minutes)\",\n \"300\"\n )\n .action((message: string, opts: { minGap?: string }) => {\n cmdCheckpoint(message, opts);\n });\n\n // pai session autosave [--session-id <uuid>] [--min-gap <seconds>]\n sessionCmd\n .command(\"autosave\")\n .description(\n \"Refresh the ## Continue checkpoint from the transcript and working tree.\\n\" +\n \"Runs unattended from live hooks so an interrupted session still leaves\\n\" +\n \"a usable handover. Writes in auto mode, so a model-authored checkpoint\\n\" +\n \"for the same session is preserved untouched. Silent; always exits 0.\"\n )\n .option(\n \"--session-id <uuid>\",\n \"Claude Code session UUID — the key that decides whether an existing authored\\n\" +\n \"checkpoint belongs to this session and must be left alone.\"\n )\n .option(\n \"--min-gap <seconds>\",\n \"Minimum seconds between autosaves, shared across all triggers (default: 240)\",\n \"240\"\n )\n .option(\"--dry-run\", \"Print the block that would be written and exit\")\n .action(async (opts: { sessionId?: string; minGap?: string; dryRun?: boolean }) => {\n await cmdAutosave(opts);\n });\n\n // pai session active [--minutes N] [--json]\n sessionCmd\n .command(\"active\")\n .description(\n \"Show currently active Claude Code sessions.\\n\" +\n \"Detects live sessions by checking which JSONL transcript files\\n\" +\n \"were recently modified in ~/.claude/projects/.\"\n )\n .option(\n \"--minutes <n>\",\n \"Consider sessions active if modified within N minutes (default: 60)\",\n \"60\"\n )\n .option(\"--json\", \"Output raw JSON instead of formatted display\")\n .action(async (opts: { minutes?: string; json?: boolean }) => {\n await cmdActive(opts);\n });\n\n // pai session recent — DEPRECATED alias for `pai session list`\n sessionCmd\n .command(\"recent\")\n .description(\"[Deprecated] Use `pai session list` or `pai sessions` instead.\")\n .option(\"-n <count>\", \"Maximum sessions to show (default: 20)\", \"20\")\n .option(\"--all\", \"Include unnamed orphan sessions (not in clc registry)\")\n .option(\"--json\", \"Output raw JSON instead of formatted table\")\n .action(async (opts: { n?: string; all?: boolean; json?: boolean }) => {\n process.stderr.write(\n \"Deprecated: `pai session recent` — use `pai session list` or `pai sessions` instead.\\n\"\n );\n await cmdRecent(opts);\n });\n\n // pai session goto <name-or-id-or-prefix> [--skip-name] [--skip-go] [--dry-run]\n sessionCmd\n .command(\"goto <name-or-id>\")\n .description(\n \"Go to a session: resume if a resumable snapshot exists, start fresh otherwise.\\n\" +\n \"Recommended short form: pai resume <name>\\n\" +\n \"Resolves by clc/registry name (case-insensitive) or UUID prefix.\\n\" +\n \"Sends '/Name <name>' as the initial prompt to label the session.\"\n )\n .option(\"--skip-name\", \"Do not prepend /Name to set the session name in chrome\")\n .option(\"--skip-go\", \"Deprecated no-op: 'go' is no longer appended — type it by hand when wanted\")\n .option(\"--dry-run\", \"Print the exact argv and cwd, then exit without launching\")\n .action(\n async (nameOrId: string, opts: { skipName?: boolean; skipGo?: boolean; dryRun?: boolean }) => {\n process.stderr.write(\n \"Note: `pai session goto` works but `pai resume` is the new shorter form.\\n\"\n );\n await cmdGoto(nameOrId, { noName: opts.skipName, noGo: opts.skipGo, dryRun: opts.dryRun });\n }\n );\n\n // pai session pause [--dry-run]\n sessionCmd\n .command(\"pause\")\n .description(\n \"Write a ## Continue checkpoint to the project's TODO.md.\\n\" +\n \"Recommended short form: pai pause\\n\" +\n \"Ctrl+C bypasses the stop-hook, orphaning the session (cannot --resume).\\n\" +\n \"Use /exit inside Claude Code to preserve full session resumability.\"\n )\n .option(\"--dry-run\", \"Preview the ## Continue block without writing it\")\n .option(\n \"--body-file <path>\",\n \"File holding the checkpoint markdown to persist ('-' reads stdin). Required unless --no-body.\"\n )\n .option(\n \"--session-id <uuid>\",\n \"Claude Code session UUID — recorded as the `claude --resume` handle\"\n )\n .option(\n \"--no-body\",\n \"Deliberately write a metadata-only checkpoint (no content)\"\n )\n .action(async (opts: { dryRun?: boolean; bodyFile?: string; sessionId?: string; body?: boolean }) => {\n process.stderr.write(\n \"Note: `pai session pause` works but `pai pause` is the new shorter form.\\n\"\n );\n await cmdPause({\n dryRun: opts.dryRun,\n bodyFile: opts.bodyFile,\n sessionId: opts.sessionId,\n noBody: opts.body === false,\n });\n });\n\n // pai session auto-route [--cwd path] [--context \"text\"] [--json]\n sessionCmd\n .command(\"auto-route\")\n .description(\n \"Auto-detect which project this session belongs to.\\n\" +\n \"Tries: (1) path match in registry, (2) Notes/PAI.md marker walk, (3) topic detection.\\n\" +\n \"Designed for use in CLAUDE.md session-start hooks.\"\n )\n .option(\n \"--cwd <path>\",\n \"Working directory to detect from (default: process.cwd())\"\n )\n .option(\n \"--context <text>\",\n \"Conversation context for topic-based fallback routing\"\n )\n .option(\"--json\", \"Output raw JSON instead of formatted display\")\n .action(\n async (opts: { cwd?: string; context?: string; json?: boolean }) => {\n await cmdAutoRoute(opts);\n }\n );\n}\n","/**\n * agents.ts — run agent definitions (~/.claude/agents/<name>.md) as workers.\n *\n * The file format is the Claude Code agent one: YAML front matter (`model`,\n * `tools`, `description`) followed by a Markdown body that is the agent's\n * system prompt. `pai worker run --agent <name>` loads it and maps it onto\n * the worker runner:\n *\n * - body → --append-system-prompt (prepended, so an explicit caller\n * flag still wins);\n * - tools → --allowedTools;\n * - model → a task class via its tier (haiku→simple, sonnet→implement,\n * opus→complex). Non-Anthropic ids map through the provider\n * registry (models.fast → haiku tier, models.default → sonnet\n * tier, a provider's modelTiers override if set), unless\n * --class is given.\n */\n\nimport { readFileSync } from \"node:fs\";\nimport { join } from \"node:path\";\nimport { homedir } from \"node:os\";\nimport type { ModelTier, WorkerProvider } from \"./config.js\";\n\nexport type { ModelTier } from \"./config.js\";\n\nimport { UNLABELED } from \"./status.js\";\n\nexport interface AgentDef {\n name: string;\n /** Front-matter `model`, when present (any string; mapped via modelToClass). */\n model?: string;\n /** Front-matter `tools`, comma-separated. */\n tools?: string[];\n /** Front-matter `description`. */\n description?: string;\n /** Everything after the front matter — the agent's system prompt. */\n body: string;\n /** The file the definition was read from. */\n path: string;\n}\n\n/** Directory the agent library lives in. */\nexport function agentsDir(): string {\n return join(homedir(), \".claude\", \"agents\");\n}\n\nexport function agentPath(name: string): string {\n return join(agentsDir(), `${name}.md`);\n}\n\n/** `key: value` (bare) or `key: [a, b]` / `key:\\n - a\\n - b` (list). */\nfunction parseFrontMatter(text: string): Record<string, string | string[]> {\n const out: Record<string, string | string[]> = {};\n let key = \"\";\n for (const raw of text.split(\"\\n\")) {\n const listItem = /^\\s+-\\s+(.*)$/.exec(raw);\n if (listItem && key) {\n const cur = out[key];\n const item = listItem[1].trim().replace(/^[\"']|[\"']$/g, \"\");\n out[key] = Array.isArray(cur) ? [...cur, item] : [item];\n continue;\n }\n const kv = /^([A-Za-z0-9_-]+):\\s*(.*)$/.exec(raw);\n if (!kv) continue;\n key = kv[1];\n const val = kv[2].trim();\n if (!val) {\n out[key] = [];\n } else if (val.startsWith(\"[\") && val.endsWith(\"]\")) {\n out[key] = val\n .slice(1, -1)\n .split(\",\")\n .map((s) => s.trim().replace(/^[\"']|[\"']$/g, \"\"))\n .filter(Boolean);\n } else {\n out[key] = val.replace(/^[\"']|[\"']$/g, \"\");\n }\n }\n return out;\n}\n\n/** Parse an agent file's text (front matter + body). Throws on empty body. */\nexport function parseAgentFile(name: string, text: string, path: string): AgentDef {\n const m = /^---\\n([\\s\\S]*?)\\n---\\n?([\\s\\S]*)$/.exec(text);\n if (!m) {\n throw new Error(\n `${path}: expected YAML front matter (--- … ---) before the agent body`\n );\n }\n const fm = parseFrontMatter(m[1]);\n const body = m[2].trim();\n if (!body) throw new Error(`${path}: the agent body (after the front matter) is empty`);\n const def: AgentDef = { name, body, path };\n if (typeof fm.model === \"string\" && fm.model) def.model = fm.model;\n if (Array.isArray(fm.tools) && fm.tools.length) def.tools = fm.tools;\n if (typeof fm.description === \"string\" && fm.description) def.description = fm.description;\n return def;\n}\n\n/** Load ~/.claude/agents/<name>.md; the error names the library when missing. */\nexport function loadAgent(name: string): AgentDef {\n const path = agentPath(name);\n let text: string;\n try {\n text = readFileSync(path, \"utf8\");\n } catch {\n throw new Error(\n `no agent named \"${name}\" at ${path}. ` +\n `The agent library lives in ~/.claude/agents/<name>.md and runs on workers.`\n );\n }\n return parseAgentFile(name, text, path);\n}\n\n/** Tier → task class: the cheap tier runs as simple, the middle as\n * implement, the top as complex. */\nconst TIER_CLASS: Record<ModelTier, string> = {\n haiku: \"simple\",\n sonnet: \"implement\",\n opus: \"complex\",\n};\n\n/** Which tier a model id belongs to: one of the CLI's tier aliases, or an\n * exact match against a configured provider's models (a modelTiers override\n * first, then fast → haiku tier, default → sonnet tier). null = no match. */\nexport function modelTier(\n model: string,\n providers?: Record<string, WorkerProvider>\n): ModelTier | null {\n const m = model.toLowerCase();\n if (m.includes(\"haiku\")) return \"haiku\";\n if (m.includes(\"sonnet\")) return \"sonnet\";\n if (m.includes(\"opus\")) return \"opus\";\n if (providers) {\n for (const p of Object.values(providers)) {\n const override = p.modelTiers?.[model];\n if (override) return override;\n if (p.models.fast === model) return \"haiku\";\n if (p.models.default === model) return \"sonnet\";\n }\n }\n return null;\n}\n\n/** Model ids already warned about — one line per id, not one per run. */\nconst unmatchedTierLogged = new Set<string>();\n\n/** Map an agent's front-matter model to a worker class. An id that matches\n * no tier is logged once and falls back to the middle tier's class rather\n * than dropping the hint silently. */\nexport function modelToClass(\n model: string | undefined,\n providers?: Record<string, WorkerProvider>\n): string | undefined {\n if (!model) return undefined;\n const tier = modelTier(model, providers);\n if (tier) return TIER_CLASS[tier];\n if (!unmatchedTierLogged.has(model)) {\n unmatchedTierLogged.add(model);\n console.error(\n `[agents] model \"${model}\" matches no known tier — ` +\n `falling back to the ${TIER_CLASS.sonnet} class.`\n );\n }\n return TIER_CLASS.sonnet;\n}\n\n/** Extra claude args the definition contributes (before the caller's own). */\nexport function agentClaudeArgs(def: AgentDef): string[] {\n const args: string[] = [];\n if (def.tools?.length) args.push(\"--allowedTools\", def.tools.join(\",\"));\n args.push(\"--append-system-prompt\", def.body);\n return args;\n}\n\n/** `<agent>: <first 50 chars of prompt>` — the default label for a run. */\nexport function agentLabel(name: string, prompt: string | null | undefined): string {\n return `${name}: ${prompt ?? UNLABELED}`.slice(0, name.length + 2 + 50);\n}\n\n/**\n * Default `--label` for a bare `pai worker run` (no --chain/--agent, which\n * derive their own): the prompt's first non-blank line, whitespace-collapsed,\n * stripped of a leading markdown marker (#, *, -, >), truncated to 48 chars\n * with \"…\" appended if it was cut. An empty/whitespace-only prompt falls back\n * to \"worker <class>\" so the row is never blank.\n */\nexport function deriveLabel(prompt: string | null | undefined, klass: string): string {\n const firstLine = (prompt ?? \"\")\n .split(\"\\n\")\n .map((l) => l.trim())\n .find((l) => l.length > 0);\n if (!firstLine) return `worker ${klass}`;\n const stripped = firstLine.replace(/^[#*\\->]+\\s*/, \"\").replace(/\\s+/g, \" \").trim();\n if (!stripped) return `worker ${klass}`;\n return stripped.length > 48 ? `${stripped.slice(0, 48)}…` : stripped;\n}\n","/**\n * `pai worker providers` and `pai worker classes` — configuration commands.\n *\n * The actual mutations live in src/workers/providers.ts so the MCP tools run\n * the same code. The CLI only parses options. --key writes the token\n * verbatim as `key:` in workers.yaml (quoted, file kept 0600); --key-file\n * writes only a path. The MCP `add` tool takes a third route — a raw key it\n * parks in ~/.claude/pai/keys itself, storing only the path.\n *\n * `pai worker roles` stays as an alias of `classes` over the same data (the\n * config key was renamed; old configs migrate on first write).\n */\n\nimport type { Command } from \"commander\";\nimport {\n PROVIDER_TAGS,\n WorkersConfigError,\n readWorkersSection,\n type ClassTarget,\n} from \"../../../workers/config.js\";\nimport { workersLogDir } from \"../../../workers/paths.js\";\nimport { workersYamlLegacyNotice } from \"../../../workers/workers-config.js\";\nimport { testProvider } from \"../../../workers/run.js\";\nimport {\n addProvider,\n classTargetText,\n describeProviders,\n removeProvider,\n setClass,\n setProviderEnabled,\n unsetClass,\n updateProvider,\n useProvider,\n} from \"../../../workers/providers.js\";\nimport { ok, err, dim } from \"../../utils.js\";\n\nfunction fail(e: unknown): void {\n const msg = e instanceof Error ? e.message : String(e);\n console.error(err(\"pai worker: \") + msg);\n process.exitCode = 1;\n}\n\n/** `--env K=V` (repeatable) → record. Throws on a value without \"=\". */\nfunction collectEnv(pairs: string[] | undefined): Record<string, string> {\n const out: Record<string, string> = {};\n for (const p of pairs ?? []) {\n const eq = p.indexOf(\"=\");\n if (eq <= 0) {\n throw new WorkersConfigError(`--env expects NAME=VALUE, got \"${p}\"`);\n }\n out[p.slice(0, eq)] = p.slice(eq + 1);\n }\n return out;\n}\n\nfunction parseIntArg(v: string): number {\n const n = parseInt(v, 10);\n if (Number.isNaN(n) || n <= 0) {\n throw new WorkersConfigError(`expected a positive number, got \"${v}\"`);\n }\n return n;\n}\n\nfunction parseCostTier(v: string): number {\n const n = parseInt(v, 10);\n if (!Number.isInteger(n) || n < 1 || n > 5) {\n throw new WorkersConfigError(`cost tier must be an integer 1 (cheapest) … 5 (most expensive), got \"${v}\"`);\n }\n return n;\n}\n\n/** `--tags a,b` (repeatable) → validated tag list. */\nfunction collectTags(vals: string[] | undefined): string[] {\n const out: string[] = [];\n for (const v of vals ?? []) {\n for (const t of v.split(\",\").map((s) => s.trim()).filter(Boolean)) {\n if (!(PROVIDER_TAGS as readonly string[]).includes(t)) {\n throw new WorkersConfigError(`\"${t}\" is not a tag (from: ${PROVIDER_TAGS.join(\", \")})`);\n }\n if (!out.includes(t)) out.push(t);\n }\n }\n return out;\n}\n\ninterface ClassSetOpts {\n provider?: string;\n mcp?: string;\n maxCostTier?: number;\n requireTags?: string[];\n order?: string;\n}\n\n/** Positional target + constraint flags → a ClassTarget for setClass. */\nfunction buildClassTarget(target: string | undefined, opts: ClassSetOpts): ClassTarget {\n const hasConstraints =\n opts.provider !== undefined ||\n opts.mcp !== undefined ||\n opts.maxCostTier !== undefined ||\n opts.requireTags !== undefined ||\n opts.order !== undefined;\n if (target !== undefined && !hasConstraints) return target;\n const obj: Exclude<ClassTarget, string> = {};\n if (target !== undefined) obj.provider = target.split(\"/\")[0];\n if (opts.provider !== undefined) obj.provider = opts.provider;\n if (opts.mcp !== undefined) obj.mcp = opts.mcp.split(\",\").map((s) => s.trim()).filter(Boolean);\n if (opts.maxCostTier !== undefined) obj.maxCostTier = opts.maxCostTier;\n if (opts.requireTags !== undefined) obj.requireTags = opts.requireTags;\n if (opts.order !== undefined) obj.order = opts.order.split(\",\").map((s) => s.trim()).filter(Boolean);\n if (target !== undefined && target.includes(\"/\") && obj.provider) {\n // provider/alias in string form only; keep it out of the object\n const alias = target.split(\"/\")[1];\n if (alias && alias !== \"default\") {\n throw new WorkersConfigError(\n `a provider alias (\"${alias}\") cannot be combined with constraint flags — ` +\n `use the plain \"<target>\" form for pinning`\n );\n }\n }\n if (!Object.keys(obj).length) {\n throw new WorkersConfigError(\"give a target (provider, provider/fast) or a constraint (--max-cost-tier, --require-tags, …)\");\n }\n return obj;\n}\n\nexport function registerWorkerProviderCommands(providersCmd: Command): void {\n providersCmd\n .description(\"Providers: list (default), add, remove, use, enable, disable, test\")\n .action(() => {\n const { workers } = readWorkersSection();\n console.log();\n for (const line of describeProviders(workers)) console.log(` ${line}`);\n console.log();\n console.log(dim(` workers are ${workers.enabled ? \"on\" : \"off\"} — pai worker ${workers.enabled ? \"off\" : \"on\"}`));\n const legacyNotice = workersYamlLegacyNotice();\n if (legacyNotice) console.log(dim(` ${legacyNotice}`));\n console.log();\n });\n\n providersCmd\n .command(\"add <name>\")\n .description(\n \"Add a provider; the first one also turns workers on and seeds classes.\\n\" +\n \"Example: pai worker providers add glm --url https://…/anthropic \\\\\\n\" +\n \" --key sk-… --model glm-5.3 --fast-model glm-5.3-flash\\n\" +\n \"--key writes the token inline as `key:` in workers.yaml (quoted, file kept 0600);\\n\" +\n \"--key-file writes only a path to a 0600 file holding it — use one or the other.\\n\" +\n \"OpenAI-protocol: --protocol openai --upstream-url https://…/v1 (runs via the PAI proxy).\\n\" +\n \"Codex (ChatGPT plan): --engine codex — runs through the Codex CLI.\\n\" +\n \"Image generation: --engine image — `pai worker run --capability image` then POSTs\\n\" +\n \"straight to {url}/images/generations instead of spawning claude.\"\n )\n .option(\"--base-url <url>\", \"Anthropic-compatible API base URL (required unless --protocol openai)\")\n .option(\"--url <url>\", \"Alias of --base-url\")\n .requiredOption(\"--model <model>\", \"Default model id for this provider\")\n .option(\"--key <token>\", \"API token, written inline as `key:` (quoted); use instead of --key-file\")\n .option(\"--key-file <path>\", \"File holding the API token (0600); omit for token \\\"local\\\"\")\n .option(\"--fast-model <model>\", \"Cheaper model for spotchecks and routing\")\n .option(\"--env <name=value>\", \"Extra env for runs (repeatable)\", (v: string, acc: string[]) => [...acc, v], [] as string[])\n .option(\"--note <text>\", \"Human note shown in `providers list`\")\n .option(\"--protocol <proto>\", \"anthropic (default) or openai — openai runs through the local PAI proxy\")\n .option(\"--upstream-url <url>\", \"Chat Completions base URL (required for --protocol openai)\")\n .option(\"--engine <engine>\", \"claude (default), codex, or image — codex runs `codex exec --json`, image POSTs {url}/images/generations\")\n .option(\"--context-window <tokens>\", \"Context window for the meter (default 200000; init event overrides)\", parseIntArg)\n .option(\"--quota-probe <url>\", \"URL whose JSON first number is the quota percent (0-100)\")\n .option(\"--cost-tier <1-5>\", \"Cost tier 1 (cheapest) … 5 (most expensive; default 3)\", parseCostTier)\n .option(\"--tags <tags>\", \"Capability tags, comma-separated (from: \" + PROVIDER_TAGS.join(\", \") + \")\", (v: string, acc: string[]) => [...acc, v], [] as string[])\n .action(\n (\n name: string,\n opts: {\n baseUrl?: string;\n url?: string;\n model: string;\n key?: string;\n keyFile?: string;\n fastModel?: string;\n env?: string[];\n note?: string;\n protocol?: string;\n upstreamUrl?: string;\n engine?: string;\n contextWindow?: number;\n quotaProbe?: string;\n costTier?: number;\n tags?: string[];\n }\n ) => {\n try {\n const protocol = opts.protocol as \"anthropic\" | \"openai\" | undefined;\n const engine = opts.engine as \"claude\" | \"codex\" | \"image\" | undefined;\n if (protocol && protocol !== \"anthropic\" && protocol !== \"openai\") {\n throw new WorkersConfigError(`--protocol must be anthropic or openai, got \"${protocol}\"`);\n }\n if (engine && engine !== \"claude\" && engine !== \"codex\" && engine !== \"image\") {\n throw new WorkersConfigError(`--engine must be claude, codex, or image, got \"${engine}\"`);\n }\n const baseUrl = opts.baseUrl ?? opts.url;\n if (protocol !== \"openai\" && !baseUrl) {\n throw new WorkersConfigError(\"--base-url (or --url) is required (only --protocol openai goes without it)\");\n }\n if (opts.key && opts.keyFile) {\n throw new WorkersConfigError(\"--key and --key-file are alternatives — pass one, not both\");\n }\n const tags = collectTags(opts.tags);\n addProvider({\n name,\n baseUrl: baseUrl ?? \"\",\n keyFile: opts.keyFile ?? null,\n ...(opts.key ? { inlineKey: opts.key } : {}),\n model: opts.model,\n fastModel: opts.fastModel,\n env: collectEnv(opts.env),\n note: opts.note,\n ...(protocol ? { protocol } : {}),\n ...(opts.upstreamUrl ? { upstreamUrl: opts.upstreamUrl } : {}),\n ...(engine ? { engine } : {}),\n ...(opts.contextWindow ? { contextWindow: opts.contextWindow } : {}),\n quotaProbe: opts.quotaProbe,\n ...(opts.costTier !== undefined ? { costTier: opts.costTier } : {}),\n ...(tags.length ? { tags } : {}),\n });\n const { workers } = readWorkersSection();\n console.log(ok(`provider ${name} added`));\n if (workers.active === name) console.log(` active: ${name} — workers ${workers.enabled ? \"on\" : \"off\"}`);\n for (const line of describeProviders(workers)) console.log(dim(` ${line}`));\n } catch (e) {\n fail(e);\n }\n }\n );\n\n providersCmd\n .command(\"update <name>\")\n .description(\"Change cost tier and tags of a provider (routing constraints use these)\")\n .option(\"--cost-tier <1-5>\", \"Cost tier 1 (cheapest) … 5 (most expensive)\", parseCostTier)\n .option(\"--tags <tags>\", \"Capability tags, comma-separated (from: \" + PROVIDER_TAGS.join(\", \") + \"); --tags '' clears\", (v: string, acc: string[]) => [...acc, v], [] as string[])\n .action(\n (\n name: string,\n opts: { costTier?: number; tags?: string[] }\n ) => {\n try {\n if (opts.costTier === undefined && opts.tags === undefined) {\n throw new WorkersConfigError(\"nothing to update — pass --cost-tier and/or --tags\");\n }\n const tags = opts.tags === undefined ? undefined : collectTags(opts.tags);\n updateProvider(name, {\n ...(opts.costTier !== undefined ? { costTier: opts.costTier } : {}),\n ...(tags !== undefined ? { tags } : {}),\n });\n console.log(ok(`provider ${name} updated`));\n const { workers } = readWorkersSection();\n for (const line of describeProviders(workers)) console.log(dim(` ${line}`));\n } catch (e) {\n fail(e);\n }\n }\n );\n\n providersCmd\n .command(\"remove <name>\")\n .description(\"Remove a provider and any classes pointing at it\")\n .action((name: string) => {\n try {\n removeProvider(name);\n console.log(ok(`provider ${name} removed`));\n } catch (e) {\n fail(e);\n }\n });\n\n providersCmd\n .command(\"use <name>\")\n .description(\"Make this provider the active one, re-pointing every class pinned to a provider\")\n .action((name: string) => {\n try {\n const { repointed } = useProvider(name);\n console.log(\n ok(`active provider: ${name}${repointed ? `; ${repointed} class(es) re-pointed` : \"\"}`)\n );\n } catch (e) {\n fail(e);\n }\n });\n\n providersCmd\n .command(\"enable <name>\")\n .description(\"Enable a provider (also clears its cooldown)\")\n .action((name: string) => {\n try {\n setProviderEnabled(name, true);\n console.log(ok(`provider ${name} enabled`));\n } catch (e) {\n fail(e);\n }\n });\n\n providersCmd\n .command(\"disable <name>\")\n .description(\"Disable a provider (auto-routing skips it; --provider still works)\")\n .action((name: string) => {\n try {\n setProviderEnabled(name, false);\n console.log(ok(`provider ${name} disabled`));\n } catch (e) {\n fail(e);\n }\n });\n\n providersCmd\n .command(\"test [name]\")\n .description(\"One-word pong probe through a provider (default: the active one)\")\n .action(async (name: string | undefined) => {\n try {\n const { workers } = readWorkersSection();\n const pname = name ?? workers.active ?? \"\";\n if (!pname || !workers.providers[pname]) {\n throw new WorkersConfigError(\n name ? `no provider named \"${name}\"` : \"no active provider — name one explicitly\"\n );\n }\n console.log(dim(` probing ${pname} (${workers.providers[pname].models.default}) …`));\n const r = await testProvider(\n pname,\n workers.providers[pname],\n workersLogDir(workers),\n undefined,\n workers.caveman\n );\n const head = `${r.provider} ${r.model} ${(r.latencyMs / 1000).toFixed(1)}s`;\n if (r.skipped) {\n // e.g. engine codex without the CLI installed: report, do not fail\n console.log(dim(` ${head} ${r.skipped}`));\n console.log(` reply: ${r.result.slice(0, 200)}`);\n return;\n }\n console.log(r.ok ? ok(` ${head}`) : err(` ${head}`));\n console.log(` reply: ${r.result.slice(0, 200)}`);\n process.exitCode = r.ok ? 0 : 1;\n } catch (e) {\n fail(e);\n }\n });\n\n // no classes here — they hang directly off `pai worker classes`\n}\n\nfunction printClasses(): void {\n const { workers } = readWorkersSection();\n const entries = Object.entries(workers.classes);\n if (!entries.length) {\n console.log(dim(\" no classes set — runs use the active provider (or auto routing)\"));\n return;\n }\n for (const [cls, target] of entries) console.log(` ${cls.padEnd(12)} ${classTargetText(target)}`);\n}\n\nfunction registerClassSubcommands(classesCmd: Command): void {\n classesCmd\n .command(\"list\")\n .description(\"List classes and their targets (default action)\")\n .action(() => printClasses());\n\n classesCmd\n .command(\"set <class> [target]\")\n .description(\n \"Point a class at a provider (or provider/fast), or give only constraints:\\n\" +\n \"classes set research --max-cost-tier 2 --require-tags long-context,reasoning\"\n )\n .option(\"--provider <name>\", \"Pin the class to this provider (object form)\")\n .option(\"--mcp <names>\", \"MCP servers/sets for runs of this class (comma-separated)\")\n .option(\"--max-cost-tier <1-5>\", \"Auto-routing considers only providers up to this cost tier\", parseCostTier)\n .option(\"--require-tags <tags>\", \"Auto-routing needs these tags (comma-separated)\", (v: string) =>\n v.split(\",\").map((s) => s.trim()).filter(Boolean)\n )\n .option(\"--order <providers>\", \"Per-class routing order overriding workers.routing.order (comma-separated)\")\n .action((cls: string, target: string | undefined, opts: ClassSetOpts) => {\n try {\n const built = buildClassTarget(target, opts);\n setClass(cls, built);\n console.log(ok(`class ${cls} → ${classTargetText(built)}`));\n } catch (e) {\n fail(e);\n }\n });\n\n classesCmd\n .command(\"unset <class>\")\n .description(\"Remove a class (runs then use the active provider)\")\n .action((cls: string) => {\n try {\n unsetClass(cls);\n console.log(ok(`class ${cls} removed`));\n } catch (e) {\n fail(e);\n }\n });\n}\n\n/** `pai worker classes` — class → target assignment (roles is the old name). */\nexport function registerWorkerClassCommands(workerCmd: Command): void {\n const classesCmd = workerCmd\n .command(\"classes\")\n .description(\"Classes: which provider serves draft / implement / review / …\")\n .action(() => printClasses());\n registerClassSubcommands(classesCmd);\n\n // `roles` — the pre-classes name; same data, kept for muscle memory\n const rolesCmd = workerCmd\n .command(\"roles\", { hidden: true })\n .description(\"Alias of `classes` (roles was renamed to classes)\")\n .action(() => printClasses());\n registerClassSubcommands(rolesCmd);\n}\n","/**\n * `pai worker model` — the model ids a provider runs on, per capability.\n *\n * Reads and writes workers.providers.<name>.models.<capability> (default,\n * fast, image, …) through the same functions the MCP worker_model tool uses,\n * so CLI and chat agree on what a set means.\n */\n\nimport type { Command } from \"commander\";\nimport {\n WorkersConfigError,\n getProviderOrNative,\n isModelCapability,\n readWorkersSection,\n type ModelCapability,\n} from \"../../../workers/config.js\";\nimport {\n describeModels,\n modelPrefsText,\n resolveProviderName,\n setProviderModel,\n} from \"../../../workers/providers.js\";\nimport { ok, err, dim } from \"../../utils.js\";\n\nfunction fail(e: unknown): void {\n const msg = e instanceof Error ? e.message : String(e);\n console.error(err(\"pai worker: \") + msg);\n process.exitCode = 1;\n}\n\nexport function registerWorkerModelCommand(workerCmd: Command): void {\n workerCmd\n .command(\"model [what] [model]\")\n .description(\n \"Model ids per provider: no args lists them,\\n\" +\n \"`model <model-id>` sets the active provider's default model (back-compat),\\n\" +\n \"`model <capability>` shows one capability, `model <capability> <model-id>` sets it.\\n\" +\n \"Capability names are open (default, fast, image, … — any ^[a-z][a-z0-9-]*$ name);\\n\" +\n \"`pai worker capability` picks which provider serves one across the whole config.\\n\" +\n `--provider targets another provider.`\n )\n .option(\"--provider <name>\", \"Provider to read or change (default: the active one)\")\n .option(\"--capability <name>\", \"Capability to set/show, with a model id as the only argument (same as the MCP tool)\")\n .action((what: string | undefined, model: string | undefined, opts: { provider?: string; capability?: string }) => {\n try {\n const { workers } = readWorkersSection();\n if (opts.capability !== undefined) {\n if (!isModelCapability(opts.capability)) {\n throw new WorkersConfigError(`\"${opts.capability}\" is not a valid capability name`);\n }\n if (model !== undefined) throw new WorkersConfigError(\"with --capability pass only a model id\");\n // rewrite into the positional form below\n model = what;\n what = opts.capability;\n }\n if (what === undefined) {\n for (const line of describeModels(workers)) console.log(` ${line}`);\n return;\n }\n let capability: ModelCapability = \"default\";\n let id = what;\n if (isModelCapability(what)) {\n capability = what;\n if (model === undefined) {\n const name = resolveProviderName(workers, opts.provider);\n const p = getProviderOrNative(workers, name);\n if (p) console.log(` ${name} ${capability} ${p.models[capability] ?? \"(none)\"}`);\n return;\n }\n id = model;\n } else if (model !== undefined) {\n throw new WorkersConfigError(\n `unexpected second argument \"${model}\" — usage: model [<capability>] <model-id>`\n );\n }\n const name = resolveProviderName(workers, opts.provider);\n const fresh = setProviderModel(name, capability, id);\n const p = getProviderOrNative(fresh, name);\n if (p) {\n console.log(ok(`${name} ${capability} model: ${p.models[capability]}`));\n console.log(dim(` ${modelPrefsText(p)}`));\n }\n } catch (e) {\n fail(e);\n }\n });\n}\n","/**\n * `pai worker capability` — which provider(s) serve a capability across the\n * whole config (workers.yaml's `capabilities:` map), distinct from `pai\n * worker model` which sets a model id on one provider's own `models` table.\n *\n * No args: lists every configured preference and what it resolves to right\n * now. `capability <name> <provider>[,<provider>…]` sets a preference list\n * (first usable one wins, see `resolveCapability`). `capability <name>\n * --unset` removes it.\n */\n\nimport type { Command } from \"commander\";\nimport {\n describeCapabilities,\n setCapabilityPreference,\n unsetCapabilityPreference,\n} from \"../../../workers/providers.js\";\nimport { readWorkersSection } from \"../../../workers/config.js\";\nimport { ok, err } from \"../../utils.js\";\n\nfunction fail(e: unknown): void {\n const msg = e instanceof Error ? e.message : String(e);\n console.error(err(\"pai worker: \") + msg);\n process.exitCode = 1;\n}\n\nexport function registerWorkerCapabilityCommand(workerCmd: Command): void {\n workerCmd\n .command(\"capability [name] [providers]\")\n .description(\n \"Which provider(s) serve a capability (e.g. image), independent of any one\\n\" +\n \"provider's own model table: no args lists every preference and what it\\n\" +\n \"resolves to; `capability <name> <provider>[,<provider>…]` sets the\\n\" +\n \"preference list (first usable one wins); `capability <name> --unset`\\n\" +\n \"removes it. An `engine: image` provider preferred for \\\"image\\\" runs\\n\" +\n \"`pai worker run --capability image` directly against its images API.\"\n )\n .option(\"--unset\", \"Remove the preference for <name>\")\n .option(\"--json\", \"Machine-readable output\")\n .action(\n (name: string | undefined, providers: string | undefined, opts: { unset?: boolean; json?: boolean }) => {\n try {\n if (name === undefined) {\n const { workers } = readWorkersSection();\n const lines = describeCapabilities(workers);\n if (opts.json) {\n console.log(JSON.stringify(lines));\n } else {\n for (const line of lines) console.log(` ${line}`);\n }\n return;\n }\n if (opts.unset) {\n unsetCapabilityPreference(name);\n console.log(ok(`removed the capability preference for \"${name}\"`));\n return;\n }\n if (providers === undefined) {\n throw new Error(\n `usage: pai worker capability ${name} <provider>[,<provider>…] (or --unset to remove it)`\n );\n }\n const workers = setCapabilityPreference(name, providers.split(\",\").map((p) => p.trim()));\n const line = describeCapabilities(workers).find((l) => l.startsWith(`${name}:`));\n console.log(ok(`${name}: [${workers.capabilities[name]?.join(\", \")}]`));\n if (line) console.log(` -> ${line.split(\" -> \")[1]}`);\n } catch (e) {\n fail(e);\n }\n }\n );\n}\n","/**\n * `pai worker config` — manage workers.yaml itself (as opposed to\n * `pai worker providers`/`classes`, which manage what is inside it).\n */\n\nimport type { Command } from \"commander\";\nimport { existsSync, readFileSync, statSync } from \"node:fs\";\nimport { parseDocument } from \"yaml\";\nimport { CONFIG_FILE } from \"../../../daemon/config.js\";\nimport { WorkersConfigError, expandHome } from \"../../../workers/config.js\";\nimport {\n inlineWorkersYamlKeys,\n initWorkersYaml,\n migrateWorkersToYaml,\n needsWorkersYamlRelocation,\n parseWorkersYamlDocument,\n relocateWorkersYaml,\n workersYamlLegacyNotice,\n workersYamlPath,\n} from \"../../../workers/workers-config.js\";\nimport { describeProviders } from \"../../../workers/providers.js\";\nimport { readWorkersSection } from \"../../../workers/config.js\";\nimport { ok, err, warn, dim } from \"../../utils.js\";\n\nfunction fail(e: unknown): void {\n const msg = e instanceof Error ? e.message : String(e);\n console.error(err(\"pai worker: \") + msg);\n process.exitCode = 1;\n}\n\nexport function registerWorkerConfigCommands(workerCmd: Command): void {\n const configCmd = workerCmd\n .command(\"config\")\n .description(\"workers.yaml itself: path, init, migrate, check, inline-keys\");\n\n configCmd\n .command(\"path\")\n .description(\"Print the resolved workers.yaml path\")\n .action(() => {\n console.log(workersYamlPath());\n });\n\n configCmd\n .command(\"init\")\n .description(\"Write the commented starter workers.yaml (refuses if one already exists)\")\n .action(() => {\n try {\n const path = initWorkersYaml();\n console.log(ok(`wrote ${path}`));\n } catch (e) {\n fail(e);\n }\n });\n\n configCmd\n .command(\"migrate\")\n .description(\n \"Two things this can mean, chosen from what is on disk:\\n\" +\n \" - workers.yaml still at an old location (~/.claude/workers.yaml or\\n\" +\n \" ~/.config/pai/workers.yaml): moved byte-for-byte to ~/.claude/pai/workers.yaml\\n\" +\n \" (no JSON involved); the old file is renamed to workers.yaml.migrated-<date>.\\n\" +\n \" - no workers.yaml yet: built from the JSON `workers` section, which is\\n\" +\n \" backed up to workers.json.migrated-<date> next to config.json.\"\n )\n .option(\"--force\", \"Overwrite an existing workers.yaml\")\n .option(\"--dry-run\", \"Print the plan without writing anything\")\n .action((opts: { force?: boolean; dryRun?: boolean }) => {\n try {\n if (needsWorkersYamlRelocation()) {\n const r = relocateWorkersYaml({ force: opts.force, dryRun: opts.dryRun });\n if (r.dryRun) {\n console.log(dim(`# would move ${r.fromPath} → ${r.toPath}`));\n return;\n }\n console.log(ok(`moved ${r.fromPath} → ${r.toPath}`));\n return;\n }\n const r = migrateWorkersToYaml(CONFIG_FILE, { force: opts.force, dryRun: opts.dryRun });\n if (r.dryRun) {\n console.log(dim(`# would write ${r.yamlPath}:`));\n console.log(r.yamlText);\n return;\n }\n console.log(ok(`wrote ${r.yamlPath}`));\n console.log(` JSON workers section backed up to ${r.backupPath}`);\n console.log();\n const { workers } = readWorkersSection(CONFIG_FILE);\n for (const line of describeProviders(workers)) console.log(` ${line}`);\n } catch (e) {\n fail(e);\n }\n });\n\n configCmd\n .command(\"check [path]\")\n .description(\"Validate workers.yaml (or the file at [path]); exits non-zero with file:line on error\")\n .action((path: string | undefined) => {\n try {\n const target = path ?? workersYamlPath();\n if (!existsSync(target)) {\n console.log(dim(`no workers.yaml at ${target} — JSON config fallback is in effect`));\n return;\n }\n const text = readFileSync(target, \"utf8\");\n const doc = parseDocument(text);\n if (doc.errors.length) {\n throw new WorkersConfigError(`${target}: ${doc.errors[0]!.message}`);\n }\n const data = parseWorkersYamlDocument(doc, target);\n console.log(\n ok(\n `${target} OK — ${Object.keys(data.providers).length} provider(s), ` +\n `${Object.keys(data.classes).length} class(es)`\n )\n );\n\n const both = Object.entries(data.providers)\n .filter(([, p]) => p.key && p.keyFile)\n .map(([name]) => name);\n if (both.length) {\n console.log(dim(` note: ${both.join(\", \")} — both key and key_file set; key wins`));\n }\n\n const mode = statSync(target).mode & 0o777;\n const hasKey = Object.values(data.providers).some((p) => p.key);\n if (hasKey && mode !== 0o600) {\n console.log(\n warn(\n ` warning: ${target} contains a key: field but is mode ${mode.toString(8)} — chmod 600 ${target}`\n )\n );\n }\n\n const legacyNotice = workersYamlLegacyNotice();\n if (legacyNotice) console.log(dim(` ${legacyNotice}`));\n } catch (e) {\n fail(e);\n }\n });\n\n configCmd\n .command(\"inline-keys\")\n .description(\"Move each provider's key_file contents inline as `key:` (quoted); key files are left on disk\")\n .option(\"--dry-run\", \"Print the plan (provider names and key file paths, never key values); write nothing\")\n .action((opts: { dryRun?: boolean }) => {\n try {\n const r = inlineWorkersYamlKeys({ dryRun: opts.dryRun });\n if (!r.inlined.length) {\n console.log(dim(\"no providers have a key_file without an inline key — nothing to do\"));\n return;\n }\n for (const { provider, keyFilePath } of r.inlined) {\n console.log((r.dryRun ? dim(\" would inline \") : ok(\" inlined \")) + `${provider} (was ${keyFilePath})`);\n }\n if (r.dryRun) {\n console.log(dim(\" dry run — nothing written\"));\n return;\n }\n console.log(ok(`wrote ${r.yamlPath}`));\n console.log(dim(\" key files left on disk — remove them yourself if you no longer need them:\"));\n for (const { keyFilePath } of r.inlined) console.log(dim(` ${expandHome(keyFilePath)}`));\n } catch (e) {\n fail(e);\n }\n });\n}\n","/**\n * Pending worker results: finished workers whose worktree branch still holds\n * work that never reached the repo (commits not in HEAD, or uncommitted\n * files). Surfaced by `pai worker ps/pending`, the SessionStart hook, the\n * status line and the prepublish gate so an unmerged fix cannot be forgotten.\n */\n\nimport { existsSync, readFileSync, statSync, writeFileSync } from \"node:fs\";\nimport { join } from \"node:path\";\nimport { isLive, loadStatuses } from \"./status.js\";\nimport { statusPath } from \"./paths.js\";\nimport { git, uncommittedPaths } from \"./worktree.js\";\n\nexport interface PendingResult {\n id: string;\n label: string;\n repo: string;\n /** ms since the worker's status file last changed (its end). */\n ageMs: number;\n age: string;\n /** commits on the branch not in the repo's HEAD (patch-id aware). */\n commits: number;\n /** uncommitted paths in the worktree, node_modules excluded. */\n dirty: number;\n}\n\n/** Stale after this long: `pai worker gc` (archive) instead of merge. */\nexport const STALE_HOURS = 24;\n\nfunction toplevel(dir: string): string | null {\n try {\n return git(dir, [\"rev-parse\", \"--show-toplevel\"]);\n } catch {\n return null;\n }\n}\n\nfunction fmtAge(ms: number): string {\n const h = Math.floor(ms / 3_600_000);\n return h >= 48 ? `${Math.floor(h / 24)}d` : h >= 1 ? `${h}h` : `${Math.floor(ms / 60_000)}m`;\n}\n\n/** Finished workers with unmerged work; `repoRoot` limits to one repo, else every repo. */\nexport function pendingResults(logDir: string, repoRoot?: string, now = Date.now()): PendingResult[] {\n const want = repoRoot ? toplevel(repoRoot) : null;\n if (repoRoot && !want) return [];\n const out: PendingResult[] = [];\n for (const st of loadStatuses(logDir)) {\n if (st.archived || st.merged || isLive(st)) continue;\n if (!st.worktreeDir || !existsSync(st.worktreeDir)) continue;\n const repo = toplevel(st.cwd);\n if (!repo || (want && repo !== want)) continue;\n let commits = 0;\n let dirty = 0;\n try {\n if (st.branch) {\n commits = git(repo, [\"cherry\", \"HEAD\", `refs/heads/${st.branch}`])\n .split(\"\\n\")\n .filter((l) => l.startsWith(\"+\")).length;\n }\n dirty = uncommittedPaths(st.worktreeDir).filter((p) => !/(^|\\/)node_modules(\\/|$)/.test(p)).length;\n } catch {\n continue; // branch or worktree unreadable: nothing verifiable to report\n }\n if (!commits && !dirty) continue;\n let ageMs = 0;\n try {\n ageMs = Math.max(0, now - statSync(statusPath(logDir, st.id)).mtimeMs);\n } catch {\n // status vanished mid-scan: age unknown, keep the entry\n }\n out.push({ id: st.id, label: st.label, repo, ageMs, age: fmtAge(ageMs), commits, dirty });\n }\n return out;\n}\n\n/** One human line per result, with the exact next command. */\nexport function formatPending(results: PendingResult[], showRepo = false): string {\n return results\n .map((r) => {\n const next = r.ageMs > STALE_HOURS * 3_600_000 ? \"pai worker gc\" : `pai worker merge ${r.id}`;\n const repo = showRepo ? ` ${r.repo}` : \"\";\n return `${r.id} ${r.age} ${r.commits} commit(s), ${r.dirty} dirty ${r.label}${repo} -> ${next}`;\n })\n .join(\"\\n\");\n}\n\n/**\n * Release gate: exit code and message for `pai worker pending --gate`.\n * PAI_ALLOW_PENDING=1 overrides.\n */\nexport function pendingGate(\n results: PendingResult[],\n env: NodeJS.ProcessEnv = process.env\n): { code: number; message: string } {\n if (!results.length || env.PAI_ALLOW_PENDING === \"1\") return { code: 0, message: \"\" };\n return {\n code: 1,\n message:\n `${results.length} unmerged worker result(s) in this repo — merge or discard before publishing:\\n` +\n `${formatPending(results)}\\n` +\n \"Override deliberately with PAI_ALLOW_PENDING=1.\",\n };\n}\n\n/** Cached count of pending results for `cwd`'s repo (statusline/hook: cheap on repeat). */\nexport function pendingCountCached(logDir: string, cwd: string, ttlMs = 60_000, now = Date.now()): number {\n const file = join(logDir, \"pending-cache.json\");\n let cache: Record<string, { at: number; n: number }> = {};\n try {\n cache = JSON.parse(readFileSync(file, \"utf8\"));\n } catch {\n // no cache yet\n }\n const hit = cache[cwd];\n if (hit && now - hit.at < ttlMs) return hit.n;\n const n = pendingResults(logDir, cwd, now).length;\n try {\n writeFileSync(file, JSON.stringify({ ...cache, [cwd]: { at: now, n } }));\n } catch {\n // read-only log dir: recompute next time\n }\n return n;\n}\n","/**\n * wait.ts — `pai worker wait <id...>`: the deterministic poller.\n *\n * A script, not guidance: polls each worker's status file until every one is\n * terminal (done/failed/killed/lost), then returns their result texts. The\n * orchestrator's Bash call stays synchronous and gets the JSON on stdout,\n * with exit 1 naming whoever failed or timed out.\n */\n\nimport { readFileSync } from \"node:fs\";\nimport { eventsPath } from \"./paths.js\";\nimport { parseWorkerReport, type WorkerReport } from \"./report.js\";\nimport { loadStatus, type WorkerStatus } from \"./status.js\";\n\nexport interface WaitResult {\n id: string;\n label: string;\n state: WorkerStatus[\"state\"];\n rc: number | null;\n secs: number | null;\n ok: boolean;\n /** The `result` text of the last result event in the worker's transcript. */\n result: string | null;\n report: WorkerReport | null;\n}\n\nexport interface WaitOptions {\n /** Give up after this long; default 15 min. */\n timeoutMs?: number;\n /** Poll interval; default 2 s. */\n intervalMs?: number;\n}\n\nconst TERMINAL: readonly WorkerStatus[\"state\"][] = [\"done\", \"failed\", \"killed\", \"lost\"];\n\nfunction sleep(ms: number): Promise<void> {\n return new Promise((resolve) => setTimeout(resolve, ms));\n}\n\n/**\n * The `result` text of the last result event in the worker's `<id>.jsonl`,\n * scanning the file backwards; null when the file or the event is absent.\n */\nfunction lastResultText(logDir: string, id: string): string | null {\n let raw: string;\n try {\n raw = readFileSync(eventsPath(logDir, id), \"utf8\");\n } catch {\n return null;\n }\n const lines = raw.split(\"\\n\");\n for (let i = lines.length - 1; i >= 0; i--) {\n const line = lines[i].trim();\n if (!line.startsWith(\"{\")) continue;\n try {\n const e = JSON.parse(line) as { type?: string; result?: unknown };\n if (e.type === \"result\" && typeof e.result === \"string\") return e.result;\n } catch {\n // a torn line is skipped, not fatal\n }\n }\n return null;\n}\n\nfunction toWaitResult(logDir: string, st: WorkerStatus): WaitResult {\n const result = lastResultText(logDir, st.id);\n return {\n id: st.id,\n label: st.label,\n state: st.state,\n rc: st.rc,\n secs: st.secs,\n ok: st.state === \"done\" && (st.rc ?? 0) === 0,\n result,\n report: parseWorkerReport(result ?? \"\"),\n };\n}\n\n/**\n * Wait until every named worker reaches a terminal state. Unknown ids throw\n * immediately. On timeout nothing is thrown: the return carries what is known\n * and `timedOut` names the still-pending ids.\n */\nexport async function waitWorkers(\n logDir: string,\n ids: string[],\n opts: WaitOptions = {}\n): Promise<{ results: WaitResult[]; timedOut: string[] }> {\n const timeoutMs = opts.timeoutMs ?? 900_000;\n const intervalMs = opts.intervalMs ?? 2_000;\n const known = new Map<string, WorkerStatus>();\n const pending = new Set<string>();\n // the pre-sleep check: already-finished workers return without waiting\n for (const id of ids) {\n const st = loadStatus(logDir, id);\n if (!st) throw new Error(`no worker named \"${id}\"`);\n known.set(id, st);\n if (!TERMINAL.includes(st.state)) pending.add(id);\n }\n const deadline = Date.now() + timeoutMs;\n while (pending.size > 0 && Date.now() < deadline) {\n await sleep(Math.min(intervalMs, Math.max(1, deadline - Date.now())));\n for (const id of [...pending]) {\n const st = loadStatus(logDir, id);\n if (st) known.set(id, st); // a vanished file keeps the last known status\n if (!st || TERMINAL.includes(known.get(id)!.state)) pending.delete(id);\n }\n }\n return { results: ids.map((id) => toWaitResult(logDir, known.get(id)!)), timedOut: [...pending] };\n}\n","/**\n * `pai worker` — the CLI face of the worker system.\n *\n * Thin: every command here is a wrapper over src/workers/*, which is also\n * what the MCP tools and the Agent hook call. Nothing in this file decides\n * routing, providers or rendering on its own.\n *\n * Layout mirrors the old glm tooling so the habits transfer:\n *\n * pai worker run … (glm / glm-run)\n * pai worker ps (glm-ps)\n * pai worker follow (glm-ps follow / watch)\n * pai worker replay <id> (glm-ps <id>)\n * pai worker pane (glm-ps pane)\n * pai worker log (glm-log)\n *\n * Plus the operator surface: say/resume for talking to workers, proxy for the\n * local Anthropic↔OpenAI translation, mcp for the allowlist workers may load.\n */\n\nimport { InvalidArgumentError, type Command } from \"commander\";\nimport { spawn } from \"node:child_process\";\nimport { existsSync, readdirSync, readFileSync, statSync } from \"node:fs\";\nimport { readWorkersSection, type WorkerProvider } from \"../../../workers/config.js\";\nimport { workersLogDir, eventsPath, ledgerPath } from \"../../../workers/paths.js\";\nimport { longInlinePromptHint, parseRunnerArgs } from \"../../../workers/args.js\";\nimport { runWorker } from \"../../../workers/run.js\";\nimport { runChain } from \"../../../workers/chain.js\";\nimport { readSpecPrompt, resolveSpecPath } from \"../../../workers/specfile.js\";\nimport { agentClaudeArgs, agentLabel, deriveLabel, loadAgent, modelToClass } from \"../../../workers/agents.js\";\nimport { followWorkers, psOutput, replayOutput, statusLineOutput } from \"../../../workers/viewer.js\";\nimport { openFollowPane, openPaneForWorker, checkPaneForWorker } from \"../../../workers/pane.js\";\nimport { installWorkers } from \"../../../workers/install.js\";\nimport { setWorkersEnabled } from \"../../../workers/providers.js\";\nimport { fallbackOn, fallbackOff, fallbackStatus, fallbackStatusText } from \"../../../workers/fallback.js\";\nimport { registerWorkerProviderCommands, registerWorkerClassCommands } from \"./providers.js\";\nimport { registerWorkerModelCommand } from \"./model.js\";\nimport { registerWorkerCapabilityCommand } from \"./capability.js\";\nimport { registerWorkerConfigCommands } from \"./config.js\";\nimport { loadStatus, ownsPid, saveStatus, setWorkerLabel, waitForTerminalStatus } from \"../../../workers/status.js\";\nimport { sayToWorker } from \"../../../workers/operator.js\";\nimport { handoffFromInside } from \"../../../workers/handoff.js\";\nimport { discardWorker, gcWorktrees, mergeWorker, verifyWorker } from \"../../../workers/worktree.js\";\nimport { formatPending, pendingGate, pendingResults } from \"../../../workers/pending.js\";\nimport { waitWorkers } from \"../../../workers/wait.js\";\nimport { describeMcp } from \"../../../workers/mcp.js\";\nimport { clickrControlsArgv, markControlsHeld } from \"../../../workers/controls.js\";\nimport { DEFAULT_PROXY_PORT, ensureProxyRunning, stopProxy } from \"../../../workers/proxy/server.js\";\nimport { err, dim } from \"../../utils.js\";\n\nfunction currentLogDir(): string {\n const { workers } = readWorkersSection();\n return workersLogDir(workers);\n}\n\nfunction fail(e: unknown): void {\n const msg = e instanceof Error ? e.message : String(e);\n console.error(err(\"pai worker: \") + msg);\n process.exitCode = 1;\n}\n\nexport function registerWorkerCommands(workerCmd: Command): void {\n workerCmd\n .command(\"run\")\n .description(\n \"Run one claude-code worker through the configured provider.\\n\" +\n \"Unknown options are passed to claude verbatim (e.g. -p, --allowedTools);\\n\" +\n \"--output-format/--verbose are handled here.\\n\" +\n \"--label \\\"<goal>\\\" is optional — it is the row shown in ps / follow /\\n\" +\n \"the status line; when absent it is derived from the prompt's first line\\n\" +\n \"(--chain/--agent derive their own instead).\\n\" +\n \"--spec <file> (or --spec -) reads the prompt from a file/stdin instead of\\n\" +\n \"an inline -p '<prompt>', which breaks on shell quoting; mutually\\n\" +\n \"exclusive with -p.\\n\" +\n \"Grant MCP tools by naming mcp__server__tool in --allowedTools (the server loads automatically);\\n\" +\n \"--chain draft,implement[,review] runs a spec-first pipeline;\\n\" +\n \"--agent <name> runs an agent definition from ~/.claude/agents.\"\n )\n .allowUnknownOption(true)\n .option(\"--provider <name>\", \"Provider to run on (default: active, else routing order)\")\n .option(\"--class <name>\", \"Use the provider of this class (draft, implement, review, research, spotcheck, simple, complex, image)\")\n .option(\"--role <name>\", \"Alias of --class (roles were renamed to classes)\")\n .option(\n \"--capability <name>\",\n \"Run on whichever configured provider serves this capability (see `pai worker capability`), \" +\n \"not the active provider or --class's own one — e.g. --capability image runs an `engine: image` \" +\n \"provider directly, POSTing a prompt and writing the PNG instead of spawning claude\"\n )\n .option(\"--for <name>\", \"Alias of --capability\")\n .option(\"--out <file>\", \"Where an image-capability run writes its PNG (default: <logDir>/<worker-id>.png)\")\n .option(\"--size <WxH>\", \"Image size for an image-capability run, e.g. 1024x1024 (default: 1024x1024)\")\n .option(\"--timeout-ms <n>\", \"Timeout for an image-capability run's HTTP request (default: 120000)\", parseIntArg)\n .option(\"--chain <stages>\", \"Comma-separated stage classes, e.g. draft,implement or draft,implement,review\")\n .option(\"--agent <name>\", \"Run the agent definition ~/.claude/agents/<name>.md on a worker\")\n .option(\n \"--model <model>\",\n \"Override the model for this run. Headless (-p) workers default to the --class model; \" +\n \"an interactive launch (no -p) with no --model uses the harness default model from settings.json.\"\n )\n .option(\"--label <text>\", \"Short task label shown in ps / follow / status line (default: first line of the prompt)\")\n .option(\"--spec <path>\", \"Read the prompt from this file (or - for stdin) instead of -p; mutually exclusive with -p\")\n .option(\"--cwd <dir>\", \"Directory the worker runs in (default: this process's cwd)\")\n .option(\"--mcp <names>\", \"MCP servers/sets this worker may use (comma-separated; see `pai worker mcp`)\")\n .option(\"--no-pane\", \"Do not open a follow pane for this worker\")\n .option(\"--worktree\", \"Run in a git worktree on branch worker/<id> (default for implement/complex/plan in a git repo)\")\n .option(\"--no-worktree\", \"Run in place, no worktree\")\n .option(\"--max-minutes <n>\", \"Stop the run after n minutes, salvaging its work (0 = no limit; default workers.defaultMaxMinutes, 60)\", parseMinutesArg)\n .option(\"--deadline <HH:MM>\", \"Stop the run at this local time (tomorrow if already past); exclusive with --max-minutes\")\n .option(\"--browser\", \"Allow browser tools (re-enables them when workers.noBrowserByDefault is on)\")\n .option(\"--no-browser\", \"Strip --chrome and every browser MCP tool from this worker and deny them in its hooks\")\n .option(\"--print-cmd\", \"Print the assembled claude argv as JSON and exit, without spawning (audit tool)\")\n .option(\"--report <format>\", \"Final-report contract/parser: json or ag2 (default: ag2, or PAI_WORKER_REPORT)\")\n .option(\"--no-report-retry\", \"Skip the one bounded re-ask when the final AG2 message fails validation\")\n .argument(\"[args...]\", \"claude arguments, e.g. -p '<task>' --allowedTools 'Read,Edit,Bash'\")\n .action(\n async (\n args: string[],\n opts: {\n provider?: string;\n class?: string;\n role?: string;\n chain?: string;\n agent?: string;\n model?: string;\n label?: string;\n spec?: string;\n cwd?: string;\n mcp?: string;\n pane?: boolean;\n worktree?: boolean;\n maxMinutes?: number;\n deadline?: string;\n browser?: boolean;\n printCmd?: boolean;\n report?: string;\n reportRetry?: boolean;\n capability?: string;\n for?: string;\n out?: string;\n size?: string;\n timeoutMs?: number;\n }\n ) => {\n try {\n const className = opts.class ?? opts.role;\n if (opts.report !== undefined && opts.report !== \"json\" && opts.report !== \"ag2\") {\n throw new Error(`--report: expected \"json\" or \"ag2\", got \"${opts.report}\"`);\n }\n const reportFormatFlag = opts.report as \"json\" | \"ag2\" | undefined;\n // The picker passes the project dir; fail loudly rather than spawn elsewhere.\n if (opts.cwd !== undefined) {\n if (!existsSync(opts.cwd)) throw new Error(`--cwd: directory does not exist: ${opts.cwd}`);\n if (!statSync(opts.cwd).isDirectory()) throw new Error(`--cwd: not a directory: ${opts.cwd}`);\n }\n let claudeArgs = args;\n let specPath: string | undefined;\n if (opts.spec !== undefined) {\n if (parseRunnerArgs(args).headless) {\n throw new Error(\"--spec and -p/--print are mutually exclusive — pass the prompt one way, not both\");\n }\n const cwdForSpec = opts.cwd ?? process.cwd();\n specPath = resolveSpecPath(opts.spec, cwdForSpec);\n const promptText = readSpecPrompt(opts.spec, cwdForSpec);\n claudeArgs = [\"-p\", promptText, ...args];\n } else {\n const hint = longInlinePromptHint(parseRunnerArgs(args).prompt);\n if (hint) process.stderr.write(hint + \"\\n\");\n }\n\n let label = opts.label;\n let agentClass: string | undefined;\n if (opts.agent) {\n // agent definition: system prompt + tools come from the file, the\n // model maps to a class, the label defaults to \"<agent>: <prompt>\"\n const def = loadAgent(opts.agent);\n claudeArgs = [...agentClaudeArgs(def), ...claudeArgs];\n // provider models map into the tier table so routing survives\n // non-Anthropic ids; an unreadable config still resolves aliases\n let agentProviders: Record<string, WorkerProvider> | undefined;\n try {\n agentProviders = readWorkersSection().workers.providers;\n } catch {\n agentProviders = undefined;\n }\n agentClass = modelToClass(def.model, agentProviders);\n if (!label) label = agentLabel(opts.agent, parseRunnerArgs(claudeArgs).prompt);\n }\n if (opts.chain) {\n const brief = parseRunnerArgs(claudeArgs).prompt;\n if (!brief) {\n throw new Error(\"--chain needs the task as -p '<brief>'\");\n }\n const rc = await runChain({\n stages: opts.chain.split(\",\"),\n className: className ?? agentClass,\n providerFlag: opts.provider,\n modelFlag: opts.model,\n label,\n noPane: opts.pane === false,\n mcpFlag: opts.mcp,\n specPath,\n cwd: opts.cwd,\n brief,\n claudeArgs,\n });\n process.exitCode = rc;\n return;\n }\n if (!label) {\n label = deriveLabel(parseRunnerArgs(claudeArgs).prompt, className ?? agentClass ?? \"default\");\n }\n const rc = await runWorker({\n providerFlag: opts.provider,\n className: className ?? agentClass,\n modelFlag: opts.model,\n label,\n mcpFlag: opts.mcp,\n specPath,\n cwd: opts.cwd,\n noPane: opts.pane === false,\n worktreeFlag: opts.worktree,\n maxMinutes: opts.maxMinutes,\n deadline: opts.deadline,\n browser: opts.browser,\n printCmd: opts.printCmd,\n reportFormatFlag,\n noReportRetry: opts.reportRetry === false,\n capabilityFlag: opts.capability ?? opts.for,\n imageOut: opts.out,\n imageSize: opts.size,\n imageTimeoutMs: opts.timeoutMs,\n claudeArgs,\n });\n process.exitCode = rc;\n } catch (e) {\n fail(e);\n }\n }\n );\n\n workerCmd\n .command(\"ps\")\n .description(\"List workers of this session (RUNNING + FINISHED); --all for every worker\")\n .option(\"--all\", \"Show workers of all sessions, not just this terminal's\")\n .action((opts: { all?: boolean }) => {\n try {\n const logDir = currentLogDir();\n const pending = pendingResults(logDir, process.cwd()).length;\n if (pending) console.log(`${pending} unmerged result(s) — pai worker pending`);\n console.log(psOutput(logDir, opts.all === true));\n } catch (e) {\n fail(e);\n }\n });\n\n workerCmd\n .command(\"pending\")\n .description(\"Finished workers whose branch or worktree holds work not yet merged into this repo\")\n .option(\"--all\", \"Every repo in the log dir, not just the current one\")\n .option(\"--json\", \"Machine-readable output\")\n .option(\"--gate\", \"Release gate: exit 1 with the list unless PAI_ALLOW_PENDING=1\")\n .action((opts: { all?: boolean; json?: boolean; gate?: boolean }) => {\n try {\n const res = pendingResults(currentLogDir(), opts.all ? undefined : process.cwd());\n if (opts.gate) {\n const g = pendingGate(res);\n if (g.message) console.error(g.message);\n process.exit(g.code);\n }\n if (opts.json) console.log(JSON.stringify(res, null, 2));\n else console.log(res.length ? formatPending(res, opts.all === true) : \"no pending worker results\");\n } catch (e) {\n fail(e);\n }\n });\n\n workerCmd\n .command(\"follow [id]\")\n .description(\"Live transcript of one worker, or of this session's running workers\")\n .option(\"--all\", \"Follow workers of all sessions\")\n .option(\"--auto-exit [secs]\", \"Exit secs after the workers end (pane mode; default from config)\")\n .action(async (id: string | undefined, opts: { all?: boolean; autoExit?: string | boolean }) => {\n try {\n const { workers } = readWorkersSection();\n const autoExit =\n opts.autoExit === undefined || opts.autoExit === false\n ? 0\n : opts.autoExit === true\n ? workers.pane.autoExitSecs\n : Number(opts.autoExit);\n await followWorkers(currentLogDir(), id ?? null, opts.all === true, autoExit);\n } catch (e) {\n fail(e);\n }\n });\n\n workerCmd\n .command(\"replay <id>\")\n .description(\"Print the transcript of one finished or running worker\")\n .option(\"--tail <n>\", \"Only the last n rendered lines\", parseIntArg)\n .action((id: string, opts: { tail?: number }) => {\n try {\n console.log(replayOutput(currentLogDir(), id, process.stdout.isTTY === true, opts.tail));\n } catch (e) {\n fail(e);\n }\n });\n\n workerCmd\n .command(\"watch\")\n .description(\"ps refreshed every 2 seconds (plain `watch`, colors kept)\")\n .action(() => {\n const proc = spawn(\"watch\", [\"-n\", \"2\", \"pai worker ps --all\"], { stdio: \"inherit\" });\n proc.on(\"error\", (e) => fail(new Error(`cannot run watch: ${e.message}`)));\n proc.on(\"close\", (code) => {\n process.exitCode = code ?? 1;\n });\n });\n\n workerCmd\n .command(\"pane [id]\")\n .description(\"Open the follow pane for a worker (tmux split when $TMUX is set, else iTerm2 on macOS, else prints the follow command)\")\n .option(\"--check\", \"Only report whether the pane is open, plus the profile file's path, font, and the hosting window's bounds\")\n .action(async (id: string | undefined, opts: { check?: boolean }) => {\n try {\n const { workers } = readWorkersSection();\n const logDir = currentLogDir();\n const term = process.env.ITERM_SESSION_ID ?? \"\";\n if (id) {\n const msg = opts.check\n ? await checkPaneForWorker(id, workers.pane.fontSize, term)\n : (await openPaneForWorker(logDir, workers, id, term)).message;\n console.log(msg);\n } else {\n console.log(await openFollowPane(logDir, workers, term, opts.check === true));\n }\n } catch (e) {\n fail(e);\n }\n });\n\n workerCmd\n .command(\"log [what]\")\n .description(\"all = ledger, tail = last ledger lines, <id> = raw event stream, none = list\")\n .action((what: string | undefined) => {\n try {\n const logDir = currentLogDir();\n const ledger = ledgerPath(logDir);\n if (what === \"all\") {\n console.log(existsSync(ledger) ? readFileSync(ledger, \"utf8\") : dim(\"(no ledger yet)\"));\n return;\n }\n if (what === \"tail\") {\n if (!existsSync(ledger)) {\n console.log(dim(\"(no ledger yet)\"));\n return;\n }\n const lines = readFileSync(ledger, \"utf8\").trim().split(\"\\n\");\n console.log(lines.slice(-20).join(\"\\n\"));\n return;\n }\n if (what) {\n const path = eventsPath(logDir, what);\n if (!existsSync(path)) {\n fail(new Error(`no event log for ${what}`));\n return;\n }\n const lines = readFileSync(path, \"utf8\").trim().split(\"\\n\");\n console.log(lines.slice(-30).join(\"\\n\"));\n return;\n }\n if (!existsSync(logDir)) {\n console.log(dim(\"(no workers yet)\"));\n return;\n }\n for (const f of readdirSync(logDir).filter((x) => x.endsWith(\".jsonl\")).sort()) {\n const st = statSync(`${logDir}/${f}`);\n const mb = (st.size / 1024 / 1024).toFixed(1);\n console.log(` ${f.replace(\".jsonl\", \"\")} ${mb} MB ${st.mtime.toISOString().slice(0, 19).replace(\"T\", \" \")}`);\n }\n } catch (e) {\n fail(e);\n }\n });\n\n workerCmd\n .command(\"say <id> <text>\")\n .description(\"Send one message to a running worker (forwarded to its open stdin)\")\n .option(\"--goal <text>\", \"Relabel the worker (its ps / pane goal) before sending the message\")\n .action(async (id: string, text: string, opts: { goal?: string }) => {\n try {\n if (opts.goal !== undefined) setWorkerLabel(currentLogDir(), id, opts.goal);\n await sayToWorker(currentLogDir(), id, text);\n console.log(\"ok\");\n } catch (e) {\n fail(e);\n }\n });\n\n workerCmd\n .command(\"goal <id> <text>\")\n .description(\"Relabel a running worker (its ps / pane goal) without sending it a message\")\n .action((id: string, text: string) => {\n try {\n setWorkerLabel(currentLogDir(), id, text);\n console.log(\"ok\");\n } catch (e) {\n fail(e);\n }\n });\n\n workerCmd\n .command(\"handoff <json>\")\n .description(\n \"From inside a worker: append a handoff to the parent's inbox and (when it runs) say it to the parent.\\n\" +\n 'Payload: {\"kind\":\"proposal|question|blocker\",\"text\":\"…\",\"data\":{…}} — from/to come from the environment.'\n )\n .action(async (json: string) => {\n try {\n let payload: unknown;\n try {\n payload = JSON.parse(json);\n } catch {\n fail(new Error(`payload is not JSON: ${json}`));\n return;\n }\n const h = await handoffFromInside(currentLogDir(), process.env, payload);\n console.log(`ok → ${h.to} (${h.kind}); inbox ${h.to}.inbox.jsonl`);\n } catch (e) {\n fail(e);\n }\n });\n\n workerCmd\n .command(\"merge <id>\")\n .description(\"Merge a worker's worktree branch (worker/<id>) into the original checkout, then remove the worktree and delete the branch\")\n .option(\"--no-commit\", \"apply the changes as uncommitted working-tree changes; keep branch and worktree\")\n .option(\"--patch\", \"alias for --no-commit\")\n .action((id: string, opts: { commit?: boolean; patch?: boolean }) => {\n try {\n console.log(mergeWorker(currentLogDir(), id, { noCommit: opts.commit === false || opts.patch === true }));\n } catch (e) {\n fail(e);\n }\n });\n\n workerCmd\n .command(\"verify <id>\")\n .description(\n \"Compare a worker's committed changes with the working tree (or --against <ref>); exit 0 when every file is identical, so discarding is safe. Also reads an archived worker (refs/pai-archive/<id>)\"\n )\n .option(\"--against <ref>\", \"compare against this ref instead of the working tree\")\n .option(\"--json\", \"print the result as JSON\")\n .action((id: string, opts: { against?: string; json?: boolean }) => {\n try {\n const r = verifyWorker(currentLogDir(), id, { against: opts.against });\n if (opts.json) console.log(JSON.stringify(r, null, 2));\n else {\n for (const f of r.files) console.log(`${f.state.padEnd(9)} ${f.path}`);\n console.log(`${r.ok ? \"all identical\" : \"not applied\"} (${r.files.length} file(s), ${r.ref} vs ${r.against})`);\n }\n if (!r.ok) process.exitCode = 1;\n } catch (e) {\n fail(e);\n }\n });\n\n workerCmd\n .command(\"wait <ids...>\")\n .description(\n \"Poll workers until they finish; prints each result as one JSON line, exit 1 on failure or timeout\\n\" +\n \"Never busy-wait for a worker: no sleep loops, no sleep-then-`pai worker ps` polling, no manual retry loops. Two sanctioned waits: run workers as background Bash tasks (the harness notifies on completion), or call `pai worker wait`, which blocks until they finish and prints each result. If you catch yourself sleeping to re-check a worker, stop — you already get notified. Supervision events arrive automatically — no polling, no sleeps: the daemon watches your workers and tells you when one finishes, fails or stalls (docs/worker.md, Supervision).\"\n )\n .option(\"--timeout <secs>\", \"Give up after this many seconds (default 900)\", parseIntArg)\n .action(async (ids: string[], opts: { timeout?: number }) => {\n try {\n const { results, timedOut } = await waitWorkers(currentLogDir(), ids, {\n timeoutMs: (opts.timeout ?? 900) * 1000,\n });\n for (const r of results) console.log(JSON.stringify(r));\n if (timedOut.length) {\n console.error(err(`pai worker wait: timed out waiting for ${timedOut.join(\", \")}`));\n process.exitCode = 1;\n return;\n }\n const failed = results.filter((r) => !r.ok).map((r) => r.id);\n if (failed.length) {\n console.error(err(`pai worker wait: failed: ${failed.join(\", \")}`));\n process.exitCode = 1;\n }\n } catch (e) {\n fail(e);\n }\n });\n\n workerCmd\n .command(\"discard <id>\")\n .description(\"Drop a worker's worktree and branch, keeping nothing\")\n .action((id: string) => {\n try {\n console.log(discardWorker(currentLogDir(), id));\n } catch (e) {\n fail(e);\n }\n });\n\n workerCmd\n .command(\"gc\")\n .description(\"Archive leftover worker worktrees (state kept under refs/pai-archive/<id>; gitignored files are not kept)\")\n .option(\"--older-than <hours>\", \"minimum age of a finished worker\", \"24\")\n .option(\"--dry-run\", \"print what would be archived, change nothing\")\n .action((opts: { olderThan: string; dryRun?: boolean }) => {\n try {\n const hours = Number(opts.olderThan);\n if (!Number.isFinite(hours) || hours < 0) throw new Error(`bad --older-than \"${opts.olderThan}\"`);\n const res = gcWorktrees(currentLogDir(), hours, opts.dryRun === true);\n const verb = opts.dryRun ? \"would archive\" : \"archived\";\n for (const r of res) {\n console.log(r.skipped ? `skipped ${r.id}: ${r.skipped}` : `${verb} ${r.id} -> ${r.archive}`);\n }\n console.log(`${res.filter((r) => r.archive).length} ${opts.dryRun ? \"would be archived\" : \"archived\"}`);\n } catch (e) {\n fail(e);\n }\n });\n\n workerCmd\n .command(\"kill <id>\")\n .description(\"Send SIGTERM to a running worker process\")\n .action(async (id: string) => {\n try {\n const logDir = currentLogDir();\n const status = loadStatus(logDir, id);\n if (!status) {\n fail(new Error(`no worker named \"${id}\"`));\n return;\n }\n if (status.state !== \"running\") {\n fail(new Error(`worker ${id} is not running (state: ${status.state})`));\n return;\n }\n if (!status.pid || status.pid <= 0) {\n fail(new Error(`worker ${id} has no pid recorded`));\n return;\n }\n if (!ownsPid(status)) {\n status.state = \"lost\";\n status.last = `pid ${status.pid} no longer belongs to this worker (reused); marked lost`;\n status.rc = null;\n saveStatus(logDir, status);\n console.log(\n `worker ${id}: pid ${status.pid} is not this worker any more (started ${status.started}); marked lost, nothing signalled`\n );\n return;\n }\n try {\n process.kill(status.pid, \"SIGTERM\");\n // The run's own SIGTERM handler writes the terminal status (killed,\n // rc 143, secs) — it knows the numbers we do not. Give it a moment\n // and only write ourselves if it never got there: writing our own\n // pre-signal snapshot unconditionally raced that handler and put a\n // half-empty \"killed rc=null ?s\" row back on disk (2026-09-19).\n const selfReported = await waitForTerminalStatus(logDir, id);\n if (!selfReported) {\n const latest = loadStatus(logDir, id) ?? status;\n latest.state = \"killed\";\n latest.rc = latest.rc ?? 143;\n latest.last = latest.last || \"killed by signal SIGTERM\";\n saveStatus(logDir, latest);\n }\n console.log(`sent SIGTERM to worker ${id} (pid ${status.pid})`);\n } catch (e) {\n const msg = e instanceof Error ? e.message : String(e);\n if (msg.includes(\"ESRCH\")) {\n fail(new Error(`process ${status.pid} not found (already exited?)`));\n } else {\n fail(e);\n }\n }\n } catch (e) {\n fail(e);\n }\n });\n\n workerCmd\n .command(\"controls <id> <who>\")\n .description(\n \"Hand the desktop controls (clickr) to a worker or take them back.\\n\" +\n \"<who> is `you` (<id> becomes the exclusive controls holder; needs clickr with\\n\" +\n \"--agent support) or `me` (the operator takes them back);\\n\" +\n \"inside a worker's pane, typing \\\"your controls\\\" does the same.\"\n )\n .action((id: string, who: string) => {\n try {\n if (who !== \"you\" && who !== \"me\") {\n fail(new Error(`<who> must be \"you\" or \"me\"`));\n return;\n }\n const logDir = currentLogDir();\n const status = loadStatus(logDir, id);\n if (!status) {\n fail(new Error(`no worker named \"${id}\"`));\n return;\n }\n const proc = spawn(\"clickr\", clickrControlsArgv(id, who, status.pid), { stdio: \"inherit\" });\n proc.on(\"error\", (e) =>\n fail(new Error(`cannot run clickr controls: ${e.message} (is clickr installed?)`))\n );\n proc.on(\"close\", (code) => {\n if (code === 0) {\n if (who === \"you\") markControlsHeld(logDir, id);\n console.log(\n `controls → ${who === \"you\" ? `${id} (exclusive; needs clickr with --agent support)` : \"operator\"}`\n );\n }\n process.exitCode = code ?? 1;\n });\n } catch (e) {\n fail(e);\n }\n });\n\n workerCmd\n .command(\"resume <id> <text>\")\n .description(\"Continue a finished worker on the same provider: claude --resume <session>\")\n .option(\"--print-id\", \"Print the new worker id on its own line (pane follow handoff)\")\n .option(\"--no-pane\", \"Do not open a follow pane for the resumed worker\")\n .option(\"--output-format <fmt>\", \"text (default), json or stream-json — same as `run`\")\n .option(\"--label <text>\", \"Short task label (default: \\\"↩ <old label>\\\")\")\n .action(\n async (\n id: string,\n text: string,\n opts: { printId?: boolean; pane?: boolean; outputFormat?: string; label?: string }\n ) => {\n try {\n const logDir = currentLogDir();\n const old = loadStatus(logDir, id);\n if (!old) {\n fail(new Error(`no worker named \"${id}\"`));\n return;\n }\n if (!old.claudeSession) {\n fail(\n new Error(\n `worker ${id} recorded no Claude session id — it predates resume support ` +\n `or ran through an engine that does not expose one`\n )\n );\n return;\n }\n const claudeArgs = [\"--resume\", old.claudeSession, \"-p\", text];\n if (opts.outputFormat !== undefined) claudeArgs.push(\"--output-format\", opts.outputFormat);\n let newId = \"\";\n const rc = await runWorker({\n providerFlag: old.provider,\n modelFlag: old.model,\n label: opts.label || `↩ ${old.label}`,\n noPane: opts.pane === false,\n claudeArgs,\n onWorkerStart: (wid) => {\n newId = wid;\n },\n });\n if (opts.printId === true && newId) console.log(newId);\n process.exitCode = rc;\n } catch (e) {\n fail(e);\n }\n }\n );\n\n workerCmd\n .command(\"proxy [stop]\")\n .description(\n \"The local Anthropic↔OpenAI proxy (loopback only); started on demand by `run`,\\n\" +\n \"`proxy` starts/verifies it, `proxy stop` stops it again\"\n )\n .option(\"--port <n>\", `Port to listen on (default ${DEFAULT_PROXY_PORT})`, parseIntArg)\n .action(async (stop: string | undefined, opts: { port?: number }) => {\n try {\n const logDir = currentLogDir();\n if (stop === \"stop\") {\n console.log(stopProxy(logDir));\n return;\n }\n if (stop) {\n fail(new Error(`unknown proxy argument \"${stop}\" (expected: stop)`));\n return;\n }\n const url = await ensureProxyRunning(opts.port ?? DEFAULT_PROXY_PORT, logDir);\n console.log(`proxy listening on ${url} — point openai providers at ${url}/<provider>`);\n } catch (e) {\n fail(e);\n }\n });\n\n workerCmd\n .command(\"mcp [list]\")\n .description(\"MCP servers workers may load via --mcp / roles, and the configured sets\")\n .action((what: string | undefined) => {\n if (what && what !== \"list\") {\n fail(new Error(`unknown mcp argument \"${what}\" (expected: list)`));\n return;\n }\n try {\n const { workers } = readWorkersSection();\n console.log(describeMcp(workers).join(\"\\n\"));\n } catch (e) {\n fail(e);\n }\n });\n\n workerCmd\n .command(\"status-line [term] [cwd] [session]\")\n .description(\n \"One-line worker summary for a status bar (empty when none in scope).\\n\" +\n \"Called by statusline-command.sh with ITERM_SESSION_ID, the pane cwd and\\n\" +\n \"the claude session id (claims workers spawned by that session's Bash).\"\n )\n .action((term: string | undefined, cwd: string | undefined, session: string | undefined) => {\n try {\n const out = statusLineOutput(\n currentLogDir(),\n term ?? process.env.ITERM_SESSION_ID ?? \"\",\n cwd ?? process.cwd(),\n session ?? \"\",\n new Date(),\n readWorkersSection().workers.active\n );\n if (out) console.log(out);\n } catch (e) {\n fail(e);\n }\n });\n\n workerCmd\n .command(\"on\")\n .description(\"Route Agent-tool subagents to workers (default when a provider exists)\")\n .action(() => {\n try {\n setWorkersEnabled(true);\n console.log(\"workers on — the Agent hook now denies and suggests `pai worker run`\");\n } catch (e) {\n fail(e);\n }\n });\n\n workerCmd\n .command(\"off\")\n .description(\"Stop routing: Agent tool runs on Anthropic again\")\n .action(() => {\n try {\n setWorkersEnabled(false);\n console.log(\"workers off — Agent-tool subagents run on Anthropic\");\n } catch (e) {\n fail(e);\n }\n });\n\n workerCmd\n .command(\"fallback [action] [provider]\")\n .description(\n \"Machine-wide fallback: every NEW Claude Code process runs on a worker\\n\" +\n \"provider (settings.json env + model pin) until switched back.\\n\" +\n \"on [provider] switches (default: active), off restores settings.json\\n\" +\n \"exactly, status shows state and running sessions. CLAUDE_SETTINGS_PATH\\n\" +\n \"points at another settings.json for dry runs.\"\n )\n .action((action: string | undefined, provider: string | undefined) => {\n try {\n if (action === undefined || action === \"status\") {\n console.log(fallbackStatusText(fallbackStatus()).join(\"\\n\"));\n return;\n }\n if (action === \"on\") {\n const r = fallbackOn(provider);\n console.log(\n r.alreadyOn\n ? `fallback already on — provider ${r.provider}, env re-applied`\n : `fallback on — provider ${r.provider}; every new Claude Code process uses it`\n );\n console.log(` settings.json env: ${r.envKeys.join(\", \")}`);\n console.log(` model pin: ${r.model}`);\n console.log(\"running sessions keep their current provider until restarted (see: pai worker fallback status)\");\n return;\n }\n if (action === \"off\") {\n const r = fallbackOff();\n console.log(`fallback off — provider ${r.provider} released, settings.json restored`);\n console.log(` restored keys: ${r.envKeys.join(\", \")}`);\n return;\n }\n fail(new Error(`unknown fallback action \"${action}\" (expected: on, off, status)`));\n } catch (e) {\n fail(e);\n }\n });\n\n workerCmd\n .command(\"install\")\n .description(\"Migrate: Agent hook in settings.json, ~/.local/bin glm* shims, old script cleanup\")\n .action(() => {\n try {\n const r = installWorkers();\n for (const line of r.lines) console.log(` ${line}`);\n } catch (e) {\n fail(e);\n }\n });\n\n const providersCmd = workerCmd\n .command(\"providers\")\n .description(\"Providers: list, add, remove, use, enable, disable, test\");\n\n registerWorkerProviderCommands(providersCmd);\n registerWorkerClassCommands(workerCmd);\n registerWorkerModelCommand(workerCmd);\n registerWorkerCapabilityCommand(workerCmd);\n registerWorkerConfigCommands(workerCmd);\n}\n\nfunction parseIntArg(v: string): number {\n const n = parseInt(v, 10);\n return Number.isNaN(n) ? 0 : n;\n}\n\nfunction parseMinutesArg(v: string): number {\n const n = Number(v);\n if (!Number.isFinite(n) || n < 0) throw new InvalidArgumentError(`expected minutes >= 0, got \"${v}\"`);\n return n;\n}\n","/**\n * launch.ts — start the Claude Code harness directly on any provider/model\n * configured in workers.yaml, for an interactive session (`pai launch`).\n *\n * A running claude process cannot switch provider: base URL and auth are\n * fixed at start, so \"switch provider\" always means \"start a new session\".\n * Provider/model resolution and the spawn environment are NOT re-derived\n * here — this reuses getProviderOrNative (config.ts, the same lookup\n * routing.ts's resolveTarget uses) and buildRunEnv (run-env.ts, the exact\n * env every worker spawns with). Adding a provider to workers.yaml therefore\n * makes it appear here with no code change.\n */\n\nimport {\n ANTHROPIC_NATIVE,\n MODEL_CAPABILITIES,\n WorkersConfigError,\n getProviderOrNative,\n maskKey,\n nativeAnthropicProvider,\n type ModelCapability,\n type WorkerProvider,\n type WorkersConfig,\n} from \"./config.js\";\nimport { buildRunEnv } from \"./run-env.js\";\nimport { DEFAULT_PROXY_PORT, ensureProxyRunning } from \"./proxy/server.js\";\n\nexport class LaunchError extends WorkersConfigError {}\n\nexport interface LaunchRow {\n index: number;\n provider: string;\n model: string;\n capability: ModelCapability;\n native: boolean;\n /** This is workers.yaml's configured `active:` provider (not the running\n * session's actual provider — see detectCurrentProviderName for that). */\n configActive: boolean;\n}\n\n/** Providers `pai launch` will start a session on: the built-in anthropic\n * provider plus every ENABLED configured provider — same population\n * auto-routing considers (routing.ts), disabled providers excluded. */\nexport function launchableProviderNames(workers: Pick<WorkersConfig, \"providers\">): string[] {\n return [\n ANTHROPIC_NATIVE,\n ...Object.entries(workers.providers)\n .filter(([, p]) => p.enabled)\n .map(([name]) => name),\n ];\n}\n\nfunction configActiveName(active: string | null): string {\n return active ?? ANTHROPIC_NATIVE;\n}\n\n/**\n * One row per provider x configured model capability (default/fast/image),\n * built-in anthropic first, in workers.yaml provider order. Disabled\n * providers are skipped entirely — this is the sole source `--list` prints\n * and `--provider`/interactive selection accept.\n */\nexport function buildLaunchRows(\n workers: Pick<WorkersConfig, \"active\" | \"providers\" | \"nativeModels\">\n): LaunchRow[] {\n const rows: LaunchRow[] = [];\n let i = 1;\n const activeName = configActiveName(workers.active);\n const native = nativeAnthropicProvider(workers.nativeModels);\n for (const cap of MODEL_CAPABILITIES) {\n const id = native.models[cap];\n if (!id) continue;\n rows.push({\n index: i++,\n provider: ANTHROPIC_NATIVE,\n model: id,\n capability: cap,\n native: true,\n configActive: activeName === ANTHROPIC_NATIVE,\n });\n }\n for (const [name, p] of Object.entries(workers.providers)) {\n if (!p.enabled) continue;\n for (const cap of MODEL_CAPABILITIES) {\n const id = p.models[cap];\n if (!id) continue;\n rows.push({\n index: i++,\n provider: name,\n model: id,\n capability: cap,\n native: false,\n configActive: activeName === name,\n });\n }\n }\n return rows;\n}\n\n/**\n * Which configured provider a running claude process's ANTHROPIC_BASE_URL\n * belongs to. No base URL at all is the plain Anthropic login; a base URL\n * that matches no configured provider is treated the same way rather than\n * guessed at. Anthropic-protocol providers match by exact base URL;\n * openai-protocol providers run through the local PAI proxy, whose URL ends\n * in \"/<providerName>\" (see buildLaunchPlan).\n */\nexport function detectCurrentProviderName(\n workers: Pick<WorkersConfig, \"providers\">,\n env: NodeJS.ProcessEnv = process.env\n): string {\n const base = env.ANTHROPIC_BASE_URL;\n if (!base) return ANTHROPIC_NATIVE;\n for (const [name, p] of Object.entries(workers.providers)) {\n if (p.protocol === \"anthropic\" && p.baseUrl && p.baseUrl === base) return name;\n }\n for (const [name, p] of Object.entries(workers.providers)) {\n if (p.protocol === \"openai\" && base.endsWith(`/${name}`)) return name;\n }\n return ANTHROPIC_NATIVE;\n}\n\n/**\n * Best-effort current model id from the environment a `pai launch` session\n * carries. ANTHROPIC_DEFAULT_SONNET_MODEL is set (to the provider's default\n * model id) for every non-native provider by buildRunEnv, but NOT to\n * whichever capability (default/fast/image) was actually chosen — so this\n * is only ever the provider's default model id, never a positive\n * confirmation of the exact running model. Returns null (not a guess) when\n * nothing was set, e.g. the native anthropic provider, which sets no\n * DEFAULT_*_MODEL vars at all.\n */\nexport function detectCurrentModel(env: NodeJS.ProcessEnv = process.env): string | null {\n return env.ANTHROPIC_DEFAULT_SONNET_MODEL || null;\n}\n\n/** Provider by name, or a LaunchError naming the launchable ones. */\nexport function resolveLaunchProvider(\n workers: Pick<WorkersConfig, \"providers\" | \"nativeModels\">,\n name: string\n): WorkerProvider {\n const valid = launchableProviderNames(workers);\n const p = getProviderOrNative(workers, name);\n if (!p) {\n throw new LaunchError(`no provider named \"${name}\". Configured: ${valid.join(\", \")}`);\n }\n if (!p.enabled) {\n throw new LaunchError(`provider \"${name}\" is disabled. Configured: ${valid.join(\", \")}`);\n }\n return p;\n}\n\n/** The model for a launch: `modelName` if given (must be one of this\n * provider's configured model ids), else the provider's default. */\nexport function resolveLaunchModel(provider: WorkerProvider, modelName?: string): string {\n if (!modelName) return provider.models.default;\n const valid = Object.values(provider.models);\n if (!valid.includes(modelName)) {\n throw new LaunchError(`model \"${modelName}\" is not configured for this provider. Configured: ${valid.join(\", \")}`);\n }\n return modelName;\n}\n\nexport interface LaunchPlan {\n providerName: string;\n provider: WorkerProvider;\n model: string;\n /** Full claude argv (argv[0] \"claude\" excluded). */\n argv: string[];\n env: NodeJS.ProcessEnv;\n}\n\n/**\n * Resolve provider + model and build the exact argv/env `pai launch` execs\n * claude with. Starts the PAI proxy on demand for an openai-protocol\n * provider — the same on-demand start the worker runner uses\n * (run.ts's executeRun).\n */\nexport async function buildLaunchPlan(\n workers: Pick<WorkersConfig, \"providers\" | \"nativeModels\">,\n providerName: string,\n modelName: string | undefined,\n extraArgs: string[],\n logDir: string\n): Promise<LaunchPlan> {\n const provider = resolveLaunchProvider(workers, providerName);\n if (provider.engine === \"codex\") {\n throw new LaunchError(\n `provider \"${providerName}\" runs through the Codex CLI (engine: codex) — pai launch starts the Claude Code harness only`\n );\n }\n const model = resolveLaunchModel(provider, modelName);\n\n let proxyUrl: string | undefined;\n if (provider.protocol === \"openai\") {\n const base = await ensureProxyRunning(DEFAULT_PROXY_PORT, logDir);\n proxyUrl = `${base}/${providerName}`;\n }\n const env = buildRunEnv(provider, false, proxyUrl);\n // buildRunEnv only strips worker identity when headless — an interactive\n // launch is never headless, so a `pai launch` run FROM inside an existing\n // worker (PAI_WORKER=1 already in this process's own environment) would\n // otherwise hand its child that ancestor's marker. PAI_WORKER=1 gates every\n // per-session hook (autosave, stop bookkeeping, the edit-delegation guard —\n // isWorkerSession(), src/hooks/ts/lib/worker-session.ts) into treating a\n // brand-new interactive session as a disposable worker, and PAI_WORKER_ID\n // would misattribute it as a sub-worker of whatever spawned `pai launch`\n // (scope.ts's parent-chain lookup). This IS a new session, not a worker.\n delete env.PAI_WORKER;\n delete env.PAI_WORKER_ID;\n const argv = [\"--model\", model, ...extraArgs];\n return { providerName, provider, model, argv, env };\n}\n\n/** `****abcd`-style masking for `--dry-run` env printing — never the full\n * token, even the placeholder-free ones (\"local\" is left as-is: it is not\n * a secret). */\nexport function maskLaunchEnv(env: NodeJS.ProcessEnv): Record<string, string> {\n const masked: Record<string, string> = {};\n for (const [k, v] of Object.entries(env)) {\n if (v === undefined) continue;\n masked[k] = k === \"ANTHROPIC_AUTH_TOKEN\" && v !== \"local\" ? maskKey(v) : v;\n }\n return masked;\n}\n","/**\n * `pai launch` — start the Claude Code harness on any provider/model\n * configured in workers.yaml, in the current terminal.\n *\n * Claude Code's own `/model` picker only ever shows the models of whatever\n * endpoint the CURRENT process was started against (base URL + auth are\n * fixed for the life of the process) — started plainly it lists Anthropic\n * models, started through a provider's base URL it lists that provider's.\n * This command is the one place that turns \"start on <provider>[/<model>]\"\n * into a fresh `claude` process: pick from `pai launch` (interactive table)\n * or name one directly (`--provider`), then exec. Switching provider always\n * means starting a NEW session — a running process cannot do it.\n */\n\nimport type { Command } from \"commander\";\nimport { spawnSync } from \"node:child_process\";\nimport { createInterface } from \"node:readline\";\nimport {\n readWorkersSection,\n WorkersConfigError,\n type WorkersConfig,\n} from \"../../workers/config.js\";\nimport { workersLogDir, ledgerPath } from \"../../workers/paths.js\";\nimport { appendLedger } from \"../../workers/ledger.js\";\nimport {\n buildLaunchPlan,\n buildLaunchRows,\n detectCurrentModel,\n detectCurrentProviderName,\n launchableProviderNames,\n maskLaunchEnv,\n type LaunchPlan,\n type LaunchRow,\n} from \"../../workers/launch.js\";\nimport { renderTable, err, dim, bold, ok } from \"../utils.js\";\n\ninterface LaunchCliOpts {\n provider?: string;\n model?: string;\n list?: boolean;\n current?: boolean;\n dryRun?: boolean;\n}\n\n/** Table rows for `--list`, optionally marking the row matching a detected\n * current-session provider/model (the /providers skill's `--current`). */\nfunction tableLines(workers: WorkersConfig, opts: { current?: boolean }): string[] {\n const rows = buildLaunchRows(workers);\n const curProvider = opts.current ? detectCurrentProviderName(workers, process.env) : null;\n const curModel = opts.current ? detectCurrentModel(process.env) : null;\n\n const body = rows.map((r) => {\n const flags: string[] = [r.capability];\n if (r.configActive) flags.push(\"active\");\n if (curProvider && r.provider === curProvider && curModel && r.model === curModel) {\n flags.push(\"this session\");\n }\n return [String(r.index), r.provider, r.model, flags.join(\", \")];\n });\n const lines = renderTable([\"#\", \"provider\", \"model\", \"\"], body).split(\"\\n\");\n\n if (opts.current) {\n lines.push(\"\");\n lines.push(\n ` current session: ${bold(curProvider ?? \"?\")} model: ${curModel ?? \"(session default)\"}`\n );\n lines.push(\n dim(\" Switching provider starts a new session: pai launch --provider <name> --model <model>\")\n );\n }\n return lines;\n}\n\nfunction printTable(workers: WorkersConfig, opts: { current?: boolean }): void {\n console.log();\n for (const line of tableLines(workers, opts)) console.log(line);\n console.log();\n}\n\n/** Answer text (\"3\", \"glm\", \"glm/glm-5.3-flash\") -> provider + optional model. */\nfunction parseChoice(answer: string, rows: LaunchRow[]): { provider: string; model?: string } | null {\n const trimmed = answer.trim();\n if (!trimmed) return null;\n if (/^\\d+$/.test(trimmed)) {\n const row = rows.find((r) => r.index === Number(trimmed));\n return row ? { provider: row.provider, model: row.model } : null;\n }\n const slash = trimmed.indexOf(\"/\");\n if (slash < 0) return { provider: trimmed };\n return { provider: trimmed.slice(0, slash), model: trimmed.slice(slash + 1) };\n}\n\nfunction prompt(question: string): Promise<string> {\n const rl = createInterface({ input: process.stdin, output: process.stdout });\n return new Promise((resolve) => rl.question(question, (answer) => {\n rl.close();\n resolve(answer);\n }));\n}\n\nfunction printDryRun(plan: LaunchPlan): void {\n console.log();\n console.log(bold(\"Dry run — would exec:\"));\n console.log();\n console.log(` cwd: ${process.cwd()}`);\n console.log(` argv: claude ${plan.argv.map((a) => (/\\s/.test(a) ? JSON.stringify(a) : a)).join(\" \")}`);\n console.log(` env:`);\n const masked = maskLaunchEnv(plan.env);\n for (const k of Object.keys(masked).sort()) {\n if (!/^(ANTHROPIC_|CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC|ENABLE_TOOL_SEARCH|PAI_WORKER)/.test(k)) continue;\n console.log(` ${k}=${masked[k]}`);\n }\n console.log();\n}\n\nexport function registerLaunchCommand(program: Command): void {\n program\n .command(\"launch\")\n .description(\n \"Start the Claude Code harness on any provider/model from workers.yaml.\\n\" +\n \"No args, in a terminal: pick from a numbered table. --list (or piped/non-\\n\" +\n \"interactive output): print the table and exit.\\n\" +\n \"--provider <name> [--model <model>]: start directly, no prompt.\\n\" +\n \"A running claude process cannot switch provider — this always starts a NEW\\n\" +\n \"session in the current terminal. Claude Code's own /model command only shows\\n\" +\n \"the CURRENT endpoint's models; `pai launch --list` (or the /providers skill)\\n\" +\n \"shows every provider configured here.\\n\" +\n \"Extra arguments after the options are passed to claude verbatim, e.g.\\n\" +\n \"`pai launch --provider glm -- --resume <id>`.\\n\" +\n \"The `glm`/`kimi` shell shims are thin aliases of `pai launch --provider <name>`.\"\n )\n .option(\"--provider <name>\", \"Provider to start on (default: prompt from the table)\")\n .option(\"--model <model>\", \"Model id for that provider (default: the provider's default model)\")\n .option(\"--list\", \"Print the provider/model table and exit\")\n .option(\"--current\", \"With --list, also detect and mark this session's own provider/model\")\n .option(\"--dry-run\", \"Print the resolved command and environment (key masked); do not exec\")\n .allowUnknownOption(true)\n .argument(\"[args...]\", \"extra claude arguments, e.g. --resume <id>\")\n .action(async (args: string[], opts: LaunchCliOpts) => {\n let workers: WorkersConfig;\n try {\n ({ workers } = readWorkersSection());\n } catch (e) {\n console.error(err(`pai launch: ${e instanceof Error ? e.message : String(e)}`));\n process.exitCode = 1;\n return;\n }\n const logDir = workersLogDir(workers);\n const interactive = !!process.stdout.isTTY && !!process.stdin.isTTY;\n\n if (opts.list || (!opts.provider && !interactive)) {\n printTable(workers, { current: opts.current });\n return;\n }\n\n let providerName = opts.provider;\n let modelName = opts.model;\n\n if (!providerName) {\n printTable(workers, {});\n const rows = buildLaunchRows(workers);\n const answer = await prompt(dim(\" Pick a number or provider/model: \"));\n const picked = parseChoice(answer, rows);\n if (!picked) {\n console.error(\n err(\n `pai launch: \"${answer.trim()}\" is not a number from the table or a provider[/model] — ` +\n `valid providers: ${launchableProviderNames(workers).join(\", \")}`\n )\n );\n process.exitCode = 2;\n return;\n }\n providerName = picked.provider;\n modelName = picked.model;\n }\n\n let plan: LaunchPlan;\n try {\n plan = await buildLaunchPlan(workers, providerName, modelName, args, logDir);\n } catch (e) {\n if (e instanceof WorkersConfigError) {\n console.error(err(`pai launch: ${e.message}`));\n process.exitCode = 2;\n return;\n }\n throw e;\n }\n\n if (opts.dryRun) {\n printDryRun(plan);\n return;\n }\n\n appendLedger(ledgerPath(logDir), \"LAUNCH-START\", {\n kind: \"launch\",\n provider: plan.providerName,\n model: plan.model,\n cwd: process.cwd(),\n });\n\n console.log(dim(` starting claude on ${ok(plan.providerName)} (${plan.model}) …`));\n const result = spawnSync(\"claude\", plan.argv, {\n stdio: \"inherit\",\n env: plan.env,\n cwd: process.cwd(),\n });\n if (result.error) {\n console.error(err(`pai launch: failed to start claude: ${result.error.message}`));\n process.exitCode = 1;\n return;\n }\n process.exitCode = result.status ?? 0;\n });\n}\n","/**\n * Shared types and constants for the session-cleanup command.\n */\n\nexport interface ProjectRow {\n id: number;\n slug: string;\n display_name: string;\n root_path: string;\n encoded_dir: string;\n claude_notes_dir: string | null;\n}\n\nexport interface SessionRow {\n id: number;\n project_id: number;\n number: number;\n date: string;\n slug: string;\n title: string;\n filename: string;\n status: string;\n}\n\nexport type SessionClassification = \"EMPTY\" | \"UNNAMED\" | \"NAMED\" | \"LEGACY_FORMAT\";\n\nexport interface SessionCandidate {\n session: SessionRow | null; // null if file exists on disk but not in DB\n filename: string;\n filepath: string;\n sizeBytes: number;\n classification: SessionClassification;\n autoName?: string; // proposed name for UNNAMED sessions\n date: string;\n number: number;\n}\n\nexport interface NotesDirPlan {\n notesDir: string;\n toDelete: SessionCandidate[];\n toRename: SessionCandidate[];\n toMove: SessionCandidate[]; // survivors that need moving to YYYY/MM/ within this dir\n}\n\nexport interface CleanupPlan {\n project: ProjectRow;\n notesDirs: NotesDirPlan[]; // one entry per discovered Notes/ directory (up to 2)\n renumberMap: Map<number, number>; // old number → new number (global across both dirs)\n}\n\n// Template content indicators — if the file only contains these patterns, delete it.\nexport const TEMPLATE_INDICATORS = [\n \"<!-- PAI will add completed work here during session -->\",\n \"<!-- PAI will add completed work here -->\",\n \"Session completed.\",\n \"Session started and ready for your instructions\",\n];\n\n// Session filename patterns\nexport const MODERN_PATTERN = /^(\\d{4}) - (\\d{4}-\\d{2}-\\d{2}) - (.+)\\.md$/;\nexport const LEGACY_PATTERN = /^(\\d{4})_(\\d{4}-\\d{2}-\\d{2})_(.+)\\.md$/;\n","/**\n * Auto-name extraction and string helpers for session-cleanup.\n * Derives a meaningful session title from Markdown content.\n */\n\n// Meta-phrases indicating template/status text rather than real work.\nconst META_PHRASE_PATTERNS: RegExp[] = [\n /session initialized and ready for your instructions/i,\n /fresh session with no pending tasks/i,\n /starting new session.*checking for pending work/i,\n /fresh session with empty todo/i,\n /session started and ready/i,\n /^session\\b.*\\bready\\b/i,\n /^session\\b.*\\binitialized\\b/i,\n /^session\\b.*\\bno pending\\b/i,\n /^session\\b.*\\bno prior work\\b/i,\n /^no pending tasks/i,\n /^no prior work/i,\n];\n\nconst TITLE_CASE_MINOR_WORDS = new Set([\n \"a\", \"an\", \"the\", \"in\", \"on\", \"at\", \"for\", \"to\", \"of\", \"and\", \"or\",\n \"but\", \"via\", \"with\", \"from\", \"by\", \"as\", \"nor\",\n]);\n\nfunction cleanMarkdownLine(raw: string): string | null {\n let s = raw.trim();\n s = s.replace(/^[-*+]\\s+\\[[ xX]\\]\\s*/, \"\");\n s = s.replace(/^[-*+]\\s+/, \"\");\n s = s.replace(/^\\[[ xX]\\]\\s*/, \"\");\n s = s.replace(/\\*\\*\\s*([^*]+?)\\s*\\*\\*/g, \"$1\");\n s = s.replace(/\\*\\s*([^*]+?)\\s*\\*/g, \"$1\");\n s = s.replace(/`([^`]+)`/g, \"$1\");\n s = s.replace(/^\\*+\\s*/, \"\");\n s = s.replace(/^[.,;:]+/, \"\").replace(/[.,;:]+$/, \"\");\n s = s.replace(/\\s+/g, \" \").trim();\n return s.length >= 4 ? s : null;\n}\n\nfunction isMetaPhrase(text: string): boolean {\n return META_PHRASE_PATTERNS.some((re) => re.test(text));\n}\n\nfunction toTitleCase(text: string): string {\n const words = text.split(\" \");\n return words\n .map((word, i) => {\n const lower = word.toLowerCase();\n if (i !== 0 && TITLE_CASE_MINOR_WORDS.has(lower)) return lower;\n return word.charAt(0).toUpperCase() + word.slice(1);\n })\n .join(\" \");\n}\n\nexport function sanitizeName(raw: string): string {\n let s = raw.replace(/[\\/\\\\:*?\"<>|#`]/g, \"\");\n s = s.replace(/\\s+/g, \" \").trim();\n if (s.length > 60) {\n const truncated = s.slice(0, 60);\n const lastSpace = truncated.lastIndexOf(\" \");\n s = lastSpace > 20 ? truncated.slice(0, lastSpace) : truncated;\n }\n s = s.trim();\n return toTitleCase(s);\n}\n\nexport function extractAutoName(content: string): string {\n const lines = content.split(\"\\n\");\n\n const CONTENT_SECTION_HEADINGS = new Set([\n \"Work Done\", \"Summary\", \"Completed\", \"What Was Done\",\n \"Results\", \"Outcomes\", \"Changes\", \"Progress\",\n ]);\n const SKIP_SECTION_HEADINGS = new Set([\n \"Next Steps\", \"Tags\", \"TODO\", \"Blockers\", \"Notes\",\n \"Metadata\", \"Context\", \"Background\",\n ]);\n\n let pastH1 = false;\n let pastFirstHr = false;\n let currentSection: string | null = null;\n const contentSectionLines: string[] = [];\n const otherH2Headings: string[] = [];\n\n for (const line of lines) {\n const trimmed = line.trim();\n\n if (trimmed.startsWith(\"# \")) {\n pastH1 = true;\n continue;\n }\n if (!pastH1) continue;\n\n if (!pastFirstHr) {\n if (trimmed === \"---\") pastFirstHr = true;\n continue;\n }\n\n if (trimmed.startsWith(\"## \")) {\n const headingText = trimmed.slice(3).trim();\n if (CONTENT_SECTION_HEADINGS.has(headingText)) {\n currentSection = \"content\";\n } else if (SKIP_SECTION_HEADINGS.has(headingText)) {\n currentSection = \"skip\";\n } else {\n currentSection = null;\n otherH2Headings.push(headingText);\n }\n continue;\n }\n\n if (trimmed.startsWith(\"#\")) continue;\n if (trimmed === \"-->\") continue;\n if (trimmed.startsWith(\"<!--\") && /^<!--.*-->$/.test(trimmed)) continue;\n\n if (currentSection === \"content\" && trimmed.length > 0) {\n const withoutComment = trimmed.replace(/^<!--.*?-->\\s*/, \"\");\n const effective = withoutComment.length > 0 ? withoutComment : trimmed;\n if (effective.length === 0) continue;\n contentSectionLines.push(effective);\n }\n }\n\n for (const raw of contentSectionLines) {\n const cleaned = cleanMarkdownLine(raw);\n if (!cleaned) continue;\n if (isMetaPhrase(cleaned)) continue;\n if (cleaned.startsWith(\"<!--\") || cleaned.includes(\"PAI will add\")) continue;\n if (cleaned.length < 5) continue;\n return sanitizeName(cleaned);\n }\n\n for (const heading of otherH2Headings) {\n const cleaned = cleanMarkdownLine(heading);\n if (!cleaned) continue;\n if (isMetaPhrase(cleaned)) continue;\n if (cleaned.length > 3 && cleaned.length < 80) {\n return sanitizeName(cleaned);\n }\n }\n\n for (const line of lines) {\n const trimmed = line.trim();\n if (trimmed.startsWith(\"# \")) {\n const title = trimmed.slice(2).trim();\n if (/^Session \\d{4}/i.test(title)) continue;\n const cleaned = cleanMarkdownLine(title);\n if (cleaned && !isMetaPhrase(cleaned) && cleaned.length > 3) {\n return sanitizeName(cleaned);\n }\n }\n }\n\n return \"Unnamed Session\";\n}\n\n/** Format a 4-digit padded session number. */\nexport function padNum(n: number): string {\n return String(n).padStart(4, \"0\");\n}\n","/**\n * Analysis phase: scan Notes/ directories and build CleanupPlans.\n */\n\nimport {\n existsSync,\n readdirSync,\n readFileSync,\n statSync,\n} from \"node:fs\";\nimport { join } from \"node:path\";\nimport { homedir } from \"node:os\";\nimport type { RegistryBackend } from \"../../../storage/registry-interface.js\";\nimport type {\n ProjectRow,\n SessionRow,\n SessionCandidate,\n SessionClassification,\n NotesDirPlan,\n CleanupPlan,\n} from \"./types.js\";\nimport {\n TEMPLATE_INDICATORS,\n MODERN_PATTERN,\n LEGACY_PATTERN,\n} from \"./types.js\";\nimport { extractAutoName, padNum } from \"./rename.js\";\n\n// ---------------------------------------------------------------------------\n// DB helpers\n// ---------------------------------------------------------------------------\n\nexport async function getAllProjects(registryBackend: RegistryBackend): Promise<ProjectRow[]> {\n const projects = await registryBackend.listProjects({ status: \"active\", orderBy: \"slug\" });\n return projects.map((p) => ({\n id: p.id,\n slug: p.slug,\n display_name: p.display_name,\n root_path: p.root_path,\n encoded_dir: p.encoded_dir,\n claude_notes_dir: p.claude_notes_dir,\n }));\n}\n\nexport async function getProject(\n registryBackend: RegistryBackend,\n slug: string\n): Promise<ProjectRow | undefined> {\n const p = await registryBackend.getProjectBySlug(slug);\n if (!p) return undefined;\n return {\n id: p.id,\n slug: p.slug,\n display_name: p.display_name,\n root_path: p.root_path,\n encoded_dir: p.encoded_dir,\n claude_notes_dir: p.claude_notes_dir,\n };\n}\n\nasync function getProjectSessions(\n registryBackend: RegistryBackend,\n projectId: number\n): Promise<SessionRow[]> {\n const sessions = await registryBackend.listSessionsForProject(projectId, {\n orderBy: \"number_asc\",\n });\n return sessions as SessionRow[];\n}\n\n// ---------------------------------------------------------------------------\n// Notes directory discovery\n// ---------------------------------------------------------------------------\n\nfunction findRootNotesDir(rootPath: string): string | null {\n const canonical = join(rootPath, \"Notes\");\n if (existsSync(canonical)) return canonical;\n const alt = join(rootPath, \".claude\", \"Notes\");\n if (existsSync(alt)) return alt;\n return null;\n}\n\nfunction findClaudeNotesDir(\n project: ProjectRow,\n rootNotesDir: string | null\n): string | null {\n const candidate =\n project.claude_notes_dir ??\n join(homedir(), \".claude\", \"projects\", project.encoded_dir, \"Notes\");\n\n if (!existsSync(candidate)) return null;\n if (rootNotesDir && candidate === rootNotesDir) return null;\n return candidate;\n}\n\nexport function findAllNotesDirs(project: ProjectRow): string[] {\n const rootDir = findRootNotesDir(project.root_path);\n const claudeDir = findClaudeNotesDir(project, rootDir);\n const dirs: string[] = [];\n if (rootDir) dirs.push(rootDir);\n if (claudeDir) dirs.push(claudeDir);\n return dirs;\n}\n\n// ---------------------------------------------------------------------------\n// Content analysis\n// ---------------------------------------------------------------------------\n\nfunction isTemplateOnly(content: string): boolean {\n const hasTemplateMarker = TEMPLATE_INDICATORS.some((ind) =>\n content.includes(ind)\n );\n if (!hasTemplateMarker) return false;\n\n const lines = content.split(\"\\n\");\n let inWorkDone = false;\n\n for (const line of lines) {\n const trimmed = line.trim();\n if (trimmed === \"## Work Done\") {\n inWorkDone = true;\n continue;\n }\n if (trimmed.startsWith(\"## \") && inWorkDone) break;\n if (!inWorkDone) continue;\n if (!trimmed) continue;\n if (trimmed.startsWith(\"<!--\") && trimmed.endsWith(\"-->\")) continue;\n if (trimmed.startsWith(\"<!--\")) continue;\n if (trimmed === \"-->\") continue;\n if (trimmed === \"Session completed.\") continue;\n if (trimmed === \"#Session\" || trimmed === \"**Tags:** #Session\") continue;\n return false;\n }\n\n return true;\n}\n\n// ---------------------------------------------------------------------------\n// Scan + analysis\n// ---------------------------------------------------------------------------\n\nexport function scanNotesDir(\n notesDir: string,\n dbByFilename: Map<string, SessionRow>\n): SessionCandidate[] {\n const candidates: SessionCandidate[] = [];\n\n let flatFiles: string[] = [];\n try {\n flatFiles = readdirSync(notesDir, { withFileTypes: true })\n .filter((d) => d.isFile() && d.name.endsWith(\".md\"))\n .map((d) => d.name);\n } catch {\n // Directory unreadable\n }\n\n const subDirFiles: { filename: string; filepath: string }[] = [];\n try {\n const topEntries = readdirSync(notesDir, { withFileTypes: true });\n for (const entry of topEntries) {\n if (!entry.isDirectory()) continue;\n if (!/^\\d{4}$/.test(entry.name)) continue;\n const yearDir = join(notesDir, entry.name);\n const monthDirs = readdirSync(yearDir, { withFileTypes: true });\n for (const mEntry of monthDirs) {\n if (!mEntry.isDirectory()) continue;\n const monthDir = join(yearDir, mEntry.name);\n const files = readdirSync(monthDir).filter((f) => f.endsWith(\".md\"));\n for (const f of files) {\n subDirFiles.push({ filename: f, filepath: join(monthDir, f) });\n }\n }\n }\n } catch {\n // Ignore errors scanning sub-dirs\n }\n\n const allFiles: { filename: string; filepath: string }[] = [\n ...flatFiles.map((f) => ({ filename: f, filepath: join(notesDir, f) })),\n ...subDirFiles,\n ];\n\n for (const { filename, filepath } of allFiles) {\n let num: number;\n let date: string;\n let namepart: string;\n let classification: SessionClassification;\n\n const modernMatch = MODERN_PATTERN.exec(filename);\n const legacyMatch = LEGACY_PATTERN.exec(filename);\n\n if (modernMatch) {\n num = parseInt(modernMatch[1], 10);\n date = modernMatch[2];\n namepart = modernMatch[3];\n classification = \"NAMED\";\n } else if (legacyMatch) {\n num = parseInt(legacyMatch[1], 10);\n date = legacyMatch[2];\n namepart = legacyMatch[3];\n classification = \"LEGACY_FORMAT\";\n } else {\n continue; // not a session file\n }\n\n let sizeBytes = 0;\n let content = \"\";\n try {\n sizeBytes = statSync(filepath).size;\n content = readFileSync(filepath, \"utf8\");\n } catch {\n continue;\n }\n\n const dbSession =\n dbByFilename.get(filename) ??\n dbByFilename.get(filepath.split(`${notesDir}/`)[1] ?? \"\") ??\n null;\n\n if (classification !== \"LEGACY_FORMAT\") {\n if (sizeBytes < 400 || isTemplateOnly(content)) {\n classification = \"EMPTY\";\n } else if (\n namepart === \"New Session\" ||\n namepart === (process.env.USER ?? \"\") ||\n namepart === \"session-started-and-ready-for-your-instructions\"\n ) {\n classification = \"UNNAMED\";\n }\n }\n\n const candidate: SessionCandidate = {\n session: dbSession,\n filename,\n filepath,\n sizeBytes,\n classification,\n date,\n number: num,\n };\n\n if (\n classification === \"UNNAMED\" ||\n classification === \"LEGACY_FORMAT\"\n ) {\n candidate.autoName = extractAutoName(content);\n }\n\n candidates.push(candidate);\n }\n\n return candidates;\n}\n\n/**\n * Renumbering is disabled. This always returns an empty map.\n *\n * It used to reassign every surviving note to its position in the sorted list\n * (`newNum = idx + 1`), which treats the number as a position. The number is\n * also the note's identity — handovers and other notes cite notes by number —\n * and a value that is both a position and an identity parts company with\n * itself the moment the set changes.\n *\n * What that cost, measured on a real corpus: deleting or adding one note\n * shifted the whole series, and because the number is also written into the H1\n * (`# Session 0006: ...`), the rewrite changed file *contents* as well as\n * names. Git could not pair them as renames — 0 of 262 rewritten notes were\n * byte-identical to anything in HEAD — so a single shift produced 261 deletions\n * plus 262 additions. The operator changed one source file and their prompt\n * reported 262 changes, which hides any real uncommitted work in the noise.\n *\n * Numbers are now minted once, at creation, and never reassigned. Gaps are\n * expected and harmless: a gap is information (a note was removed) and costs\n * nothing, whereas a renumber invalidates every existing reference silently.\n *\n * Kept as a function rather than deleted so the call site and the CleanupPlan\n * shape stay intact; collisions should be reported by the caller, never\n * resolved by shifting.\n */\nfunction buildRenumberMap(\n _survivors: SessionCandidate[]\n): Map<number, number> {\n return new Map();\n}\n\nexport async function analyzeProject(\n registryBackend: RegistryBackend,\n project: ProjectRow\n): Promise<CleanupPlan | null> {\n const notesDirPaths = findAllNotesDirs(project);\n if (notesDirPaths.length === 0) return null;\n\n const dbSessions = await getProjectSessions(registryBackend, project.id);\n const dbByFilename = new Map<string, SessionRow>();\n for (const s of dbSessions) dbByFilename.set(s.filename, s);\n\n const notesDirPlans: NotesDirPlan[] = [];\n const allSurvivors: SessionCandidate[] = [];\n\n for (const notesDir of notesDirPaths) {\n const candidates = scanNotesDir(notesDir, dbByFilename);\n if (candidates.length === 0) continue;\n\n const toDelete = candidates.filter((c) => c.classification === \"EMPTY\");\n const toRename = candidates.filter(\n (c) =>\n c.classification === \"UNNAMED\" || c.classification === \"LEGACY_FORMAT\"\n );\n const survivors = candidates.filter((c) => c.classification !== \"EMPTY\");\n\n notesDirPlans.push({ notesDir, toDelete, toRename, toMove: survivors });\n allSurvivors.push(...survivors);\n }\n\n if (notesDirPlans.length === 0) return null;\n\n const renumberMap = buildRenumberMap(allSurvivors);\n\n return { project, notesDirs: notesDirPlans, renumberMap };\n}\n","/**\n * Dry-run display, Postgres vector DB path updates, and cleanup execution.\n */\n\nimport {\n existsSync,\n readdirSync,\n readFileSync,\n writeFileSync,\n mkdirSync,\n renameSync,\n statSync,\n} from \"node:fs\";\nimport { join, dirname } from \"node:path\";\nimport chalk from \"chalk\";\nimport { ok, warn, err, dim, bold, header } from \"../../utils.js\";\nimport type { CleanupPlan, SessionCandidate } from \"./types.js\";\nimport { padNum } from \"./rename.js\";\n\n// ---------------------------------------------------------------------------\n// Vector DB (StorageBackend) helpers\n// ---------------------------------------------------------------------------\n\nasync function countVectorDbPaths(oldPaths: string[]): Promise<number> {\n if (oldPaths.length === 0) return 0;\n try {\n const { loadConfig } = await import(\"../../../daemon/config.js\");\n const { createStorageBackend } = await import(\"../../../storage/factory.js\");\n const config = loadConfig();\n if (config.storageBackend !== \"postgres\") return 0;\n const backend = await createStorageBackend(config);\n try {\n if (!backend.supportsPostgresFeatures) return 0;\n return await backend.countFilesWithPaths(oldPaths);\n } finally {\n await backend.close();\n }\n } catch {\n return 0;\n }\n}\n\nasync function updateVectorDbPaths(\n moves: Array<{ oldPath: string; newPath: string }>\n): Promise<number> {\n if (moves.length === 0) return 0;\n try {\n const { loadConfig } = await import(\"../../../daemon/config.js\");\n const { createStorageBackend } = await import(\"../../../storage/factory.js\");\n const config = loadConfig();\n if (config.storageBackend !== \"postgres\") return 0;\n const backend = await createStorageBackend(config);\n try {\n if (!backend.supportsPostgresFeatures) {\n process.stderr.write(\"[session-cleanup] Postgres unavailable. Skipping vector DB path update.\\n\");\n return 0;\n }\n return await backend.renameFilePaths(moves);\n } finally {\n await backend.close();\n }\n } catch (e) {\n process.stderr.write(`[session-cleanup] Failed to update vector DB paths: ${e}\\n`);\n return -1;\n }\n}\n\n// ---------------------------------------------------------------------------\n// Dry-run display\n// ---------------------------------------------------------------------------\n\nexport async function displayDryRun(plans: CleanupPlan[]): Promise<void> {\n let totalDelete = 0;\n let totalRename = 0;\n let totalMove = 0;\n let totalRenumber = 0;\n\n for (const plan of plans) {\n const hasWork =\n plan.notesDirs.some(\n (d) => d.toDelete.length > 0 || d.toRename.length > 0 || d.toMove.length > 0\n ) || plan.renumberMap.size > 0;\n if (!hasWork) continue;\n\n console.log();\n console.log(\n header(` Project: ${plan.project.display_name} (${plan.project.slug})`)\n );\n\n for (const dirPlan of plan.notesDirs) {\n console.log(dim(` Notes: ${dirPlan.notesDir}`));\n\n if (dirPlan.toDelete.length > 0) {\n console.log(bold(\" ARCHIVE (empty/template-only sessions → .archive/):\"));\n for (const c of dirPlan.toDelete) {\n console.log(\n ` ${chalk.yellow(\"ARCH\")} ${dim(padNum(c.number))} - ${c.date} - ${\n c.filename.split(\" - \").slice(2).join(\" - \")\n } ${dim(`(${c.sizeBytes}b)`)}`\n );\n totalDelete++;\n }\n console.log();\n }\n\n if (dirPlan.toRename.length > 0) {\n console.log(bold(\" RENAME (unnamed or legacy-format sessions):\"));\n for (const c of dirPlan.toRename) {\n const autoName = c.autoName ?? \"Unnamed Session\";\n console.log(` ${chalk.yellow(\"REN\")} ${c.filename}`);\n console.log(` → ${padNum(c.number)} - ${c.date} - ${autoName}.md`);\n totalRename++;\n }\n console.log();\n }\n\n if (dirPlan.toMove.length > 0) {\n console.log(bold(\" MOVE TO YYYY/MM/ hierarchy:\"));\n for (const c of dirPlan.toMove) {\n const [year, month] = c.date.split(\"-\");\n console.log(` ${chalk.cyan(\"MOV\")} ${c.filename}`);\n console.log(` → ${year}/${month}/${c.filename}`);\n totalMove++;\n }\n console.log();\n }\n }\n\n if (plan.renumberMap.size > 0) {\n console.log(\n bold(\" RENUMBER (after deletions, global across all Notes/ dirs):\")\n );\n for (const [oldN, newN] of plan.renumberMap) {\n console.log(\n ` ${chalk.blue(\"NUM\")} #${padNum(oldN)} → #${padNum(newN)}`\n );\n totalRenumber++;\n }\n console.log();\n }\n }\n\n const wouldMovePaths: string[] = [];\n for (const plan of plans) {\n for (const dirPlan of plan.notesDirs) {\n for (const c of dirPlan.toMove) {\n const [year, month] = c.date.split(\"-\");\n const targetPath = join(dirPlan.notesDir, year, month, c.filename);\n if (c.filepath !== targetPath) wouldMovePaths.push(c.filepath);\n }\n }\n }\n\n const vectorDbCount = await countVectorDbPaths(wouldMovePaths);\n\n console.log();\n console.log(bold(\" Summary (dry-run):\"));\n console.log(` ${chalk.yellow(\"ARCH\")} ${totalDelete} empty sessions to archive (moved, not deleted)`);\n console.log(` ${chalk.yellow(\"REN\")} ${totalRename} unnamed sessions to rename`);\n console.log(` ${chalk.blue(\"NUM\")} ${totalRenumber} sessions to renumber`);\n console.log(` ${chalk.cyan(\"MOV\")} ${totalMove} sessions to move into YYYY/MM/ dirs`);\n if (vectorDbCount > 0) {\n console.log(\n ` ${chalk.magenta(\"VEC\")} ${vectorDbCount} file path(s) will be updated in the vector DB (embeddings preserved)`\n );\n } else if (wouldMovePaths.length > 0) {\n console.log(\n ` ${chalk.magenta(\"VEC\")} 0 file path(s) found in vector DB for moved files (no embeddings to preserve)`\n );\n }\n console.log();\n console.log(warn(\" This is a dry-run. Add --execute to apply changes.\"));\n console.log();\n}\n\n// ---------------------------------------------------------------------------\n// Execution\n// ---------------------------------------------------------------------------\n\nexport async function executeCleanup(\n plans: CleanupPlan[],\n skipReindex: boolean\n): Promise<void> {\n const { getRegistryBackend } = await import(\"../../../storage/factory.js\");\n const registryBackend = await getRegistryBackend();\n\n let deleted = 0;\n let renamed = 0;\n let moved = 0;\n let renumbered = 0;\n let dbUpdated = 0;\n const vectorDbMoves: Array<{ oldPath: string; newPath: string }> = [];\n\n for (const plan of plans) {\n console.log();\n console.log(\n header(` Project: ${plan.project.display_name} (${plan.project.slug})`)\n );\n\n for (const dirPlan of plan.notesDirs) {\n const { notesDir } = dirPlan;\n if (plan.notesDirs.length > 1) console.log(dim(` Directory: ${notesDir}`));\n\n // Step 1: Retire \"empty\" sessions — by moving them, never by unlinking.\n //\n // This deleted four titled notes from Youdrill on 2026-08-03 (\"Cocoapods\n // Bootstrap\", \"Youdrill Tools Pipeline Review\", and two more), and they\n // were only recoverable because that session inspected the diff before\n // committing. Anywhere this ran and was committed, the notes are gone.\n //\n // The classifier is the root problem: EMPTY is `sizeBytes < 400 ||\n // isTemplateOnly`. Size is a proxy for worthlessness and a bad one — a\n // real session that produced three lines and a good title is under 400\n // bytes. But no threshold is worth trusting with an irreversible\n // operation, so the fix is not a better number: it is that a cleanup tool\n // has no business destroying anything a human might want back.\n //\n // Moving to .archive/ still achieves the actual goal (the note leaves the\n // working listing and the numbering respreads) while keeping the file.\n for (const c of dirPlan.toDelete) {\n try {\n const archiveDir = join(dirname(c.filepath), \".archive\");\n mkdirSync(archiveDir, { recursive: true });\n let dest = join(archiveDir, c.filename);\n // Never clobber an earlier archived note with the same name.\n if (existsSync(dest)) {\n const stamp = statSync(c.filepath).mtimeMs.toString(36);\n dest = join(archiveDir, `${c.filename.replace(/\\.md$/, \"\")} (${stamp}).md`);\n }\n renameSync(c.filepath, dest);\n console.log(ok(` ARCH ${c.filename}`) + dim(\" → .archive/\"));\n deleted++;\n } catch (e) {\n console.log(err(` FAIL to archive ${c.filename}: ${e}`));\n }\n if (c.session) {\n try {\n await registryBackend.deleteSession(c.session.id);\n dbUpdated++;\n } catch (e) {\n console.log(err(` FAIL to remove session #${c.number} from DB: ${e}`));\n }\n }\n }\n\n // Step 2: Rename unnamed/legacy sessions\n for (const c of dirPlan.toRename) {\n const autoName = c.autoName ?? \"Unnamed Session\";\n const newFilename = `${padNum(c.number)} - ${c.date} - ${autoName}.md`;\n const newPath = join(notesDir, newFilename);\n\n if (c.filepath !== newPath) {\n try {\n renameSync(c.filepath, newPath);\n console.log(ok(` REN ${c.filename}`));\n console.log(dim(` → ${newFilename}`));\n renamed++;\n (c as { filename: string }).filename = newFilename;\n (c as { filepath: string }).filepath = newPath;\n } catch (e) {\n console.log(err(` FAIL rename ${c.filename}: ${e}`));\n continue;\n }\n }\n\n try {\n const content = readFileSync(newPath, \"utf8\");\n const lines = content.split(\"\\n\");\n let h1Updated = false;\n const updated = lines.map((line) => {\n if (!h1Updated && line.startsWith(\"# \")) {\n h1Updated = true;\n return `# ${autoName}`;\n }\n return line;\n });\n if (!h1Updated) updated.unshift(`# ${autoName}`, \"\");\n writeFileSync(newPath, updated.join(\"\\n\"), \"utf8\");\n } catch {\n // Non-fatal\n }\n\n if (c.session) {\n const normalizedSlug = autoName\n .toLowerCase()\n .replace(/[^a-z0-9]+/g, \"-\")\n .replace(/^-+|-+$/g, \"\");\n try {\n await registryBackend.updateSessionMeta(c.session.id, {\n slug: normalizedSlug,\n title: autoName,\n filename: newFilename,\n });\n dbUpdated++;\n } catch (e) {\n console.log(err(` FAIL DB update for session #${c.number}: ${e}`));\n }\n }\n }\n\n // Step 3a: Renumber survivors\n if (plan.renumberMap.size > 0) {\n const toRenumber = dirPlan.toMove.filter((c) =>\n plan.renumberMap.has(c.number)\n );\n\n const tempFiles: {\n candidate: SessionCandidate;\n tempPath: string;\n newNum: number;\n }[] = [];\n for (const c of toRenumber) {\n const newNum = plan.renumberMap.get(c.number)!;\n const tempFilename = `__tmp_${padNum(c.number)}_${c.filename}`;\n const tempPath = join(notesDir, tempFilename);\n try {\n if (existsSync(c.filepath)) {\n renameSync(c.filepath, tempPath);\n tempFiles.push({ candidate: c, tempPath, newNum });\n }\n } catch (e) {\n console.log(err(` FAIL temp-rename #${c.number}: ${e}`));\n }\n }\n\n for (const { candidate: c, tempPath, newNum } of tempFiles) {\n const newFilename = c.filename.replace(/^\\d{4}/, padNum(newNum));\n const newPath = join(notesDir, newFilename);\n try {\n renameSync(tempPath, newPath);\n console.log(\n ok(` NUM #${padNum(c.number)} → #${padNum(newNum)}: ${newFilename}`)\n );\n renumbered++;\n (c as { filename: string }).filename = newFilename;\n (c as { filepath: string }).filepath = newPath;\n (c as { number: number }).number = newNum;\n } catch (e) {\n console.log(err(` FAIL final-rename #${newNum}: ${e}`));\n }\n\n if (existsSync(newPath)) {\n try {\n const content = readFileSync(newPath, \"utf8\");\n const lines = content.split(\"\\n\");\n const updated = lines.map((line) => {\n if (line.match(/^# Session \\d{4}:/)) {\n return line.replace(\n /^# Session \\d{4}:/,\n `# Session ${padNum(newNum)}:`\n );\n }\n return line;\n });\n writeFileSync(newPath, updated.join(\"\\n\"), \"utf8\");\n } catch {\n // Non-fatal\n }\n }\n }\n\n const dbRenumbers = tempFiles\n .filter(({ candidate: c }) => c.session != null)\n .map(({ candidate: c, newNum }) => ({\n session: c.session!,\n newNum,\n newFilename: c.filename,\n }));\n\n if (dbRenumbers.length > 0) {\n try {\n // Negate first, then set final numbers — same two-pass order as the\n // old single SQL transaction, so mid-loop UNIQUE(project_id, number)\n // collisions between swapping sessions are still avoided.\n for (const { session, newNum } of dbRenumbers) {\n await registryBackend.updateSessionNumber(session.id, -newNum);\n }\n for (const { session, newNum, newFilename } of dbRenumbers) {\n await registryBackend.updateSessionNumber(session.id, newNum, {\n filename: newFilename,\n });\n }\n dbUpdated += dbRenumbers.length;\n } catch (e) {\n console.log(err(` FAIL DB renumber transaction: ${e}`));\n }\n }\n }\n\n // Step 3b: Move to YYYY/MM/\n for (const c of dirPlan.toMove) {\n const [year, month] = c.date.split(\"-\");\n const targetDir = join(notesDir, year, month);\n const targetPath = join(targetDir, c.filename);\n if (c.filepath === targetPath) continue;\n\n try {\n mkdirSync(targetDir, { recursive: true });\n } catch (e) {\n console.log(err(` FAIL mkdir ${targetDir}: ${e}`));\n continue;\n }\n\n const oldAbsPath = c.filepath;\n try {\n if (existsSync(c.filepath)) {\n renameSync(c.filepath, targetPath);\n console.log(ok(` MOV ${c.filename}`));\n console.log(dim(` → ${year}/${month}/${c.filename}`));\n moved++;\n vectorDbMoves.push({ oldPath: oldAbsPath, newPath: targetPath });\n }\n } catch (e) {\n console.log(err(` FAIL move ${c.filename}: ${e}`));\n continue;\n }\n\n const newFilenameInDb = `${year}/${month}/${c.filename}`;\n if (c.session) {\n try {\n await registryBackend.updateSessionFilename(c.session.id, newFilenameInDb);\n dbUpdated++;\n } catch (e) {\n console.log(err(` FAIL DB update path for ${c.filename}: ${e}`));\n }\n }\n }\n }\n }\n\n // Step 5: Update Postgres vector DB paths\n let vectorDbUpdated = 0;\n if (vectorDbMoves.length > 0) {\n console.log();\n console.log(\n dim(\n ` Updating ${vectorDbMoves.length} file path(s) in vector DB to preserve embeddings...`\n )\n );\n const result = await updateVectorDbPaths(vectorDbMoves);\n if (result >= 0) {\n vectorDbUpdated = result;\n console.log(\n ok(` Updated ${vectorDbUpdated} file path(s) in Postgres (embeddings preserved)`)\n );\n } else {\n console.log(\n warn(\n \" Vector DB path update failed — embeddings may be orphaned (check logs)\"\n )\n );\n }\n }\n\n console.log();\n console.log(bold(\" Cleanup complete:\"));\n console.log(ok(` ${deleted} session(s) deleted`));\n console.log(ok(` ${renamed} session(s) renamed`));\n console.log(ok(` ${renumbered} session(s) renumbered`));\n console.log(ok(` ${moved} session(s) moved to YYYY/MM/ hierarchy`));\n console.log(ok(` ${dbUpdated} registry DB record(s) updated`));\n if (vectorDbMoves.length > 0) {\n console.log(\n ok(\n ` ${vectorDbUpdated} vector DB file path(s) updated (embeddings preserved)`\n )\n );\n }\n\n if (!skipReindex) {\n console.log();\n console.log(\n dim(\" Memory re-index: the PAI daemon will pick up changes within 5 minutes.\")\n );\n console.log(dim(\" To force immediate re-index: pai memory index --all\"));\n }\n\n console.log();\n}\n","/**\n * Commander registration for `pai session cleanup`.\n */\n\nimport type { Command } from \"commander\";\nimport { err, dim, ok, header } from \"../../utils.js\";\nimport { getAllProjects, getProject, analyzeProject } from \"./scanner.js\";\nimport { displayDryRun, executeCleanup } from \"./executor.js\";\n\nexport function registerSessionCleanupCommand(sessionCmd: Command): void {\n sessionCmd\n .command(\"cleanup [project-slug]\")\n .description(\n \"Clean up session notes: delete empties, auto-name unnamed, move into YYYY/MM/ hierarchy, renumber\"\n )\n .option(\"--execute\", \"Actually perform the cleanup (default is dry-run)\")\n .option(\"--no-renumber\", \"Skip renumbering sessions after deletions\")\n .option(\"--no-reindex\", \"Skip triggering memory re-index after moves\")\n .action(\n async (\n projectSlug: string | undefined,\n opts: { execute?: boolean; renumber?: boolean; reindex?: boolean }\n ) => {\n const { getRegistryBackend } = await import(\"../../../storage/factory.js\");\n const registryBackend = await getRegistryBackend();\n\n const dryRun = !opts.execute;\n const skipReindex = opts.reindex === false;\n\n let projects;\n if (projectSlug) {\n const p = await getProject(registryBackend, projectSlug);\n if (!p) {\n console.error(err(`Project not found: ${projectSlug}`));\n process.exitCode = 1;\n return;\n }\n projects = [p];\n } else {\n projects = await getAllProjects(registryBackend);\n }\n\n console.log();\n console.log(\n header(\n dryRun\n ? \" pai session cleanup — DRY RUN (no changes will be made)\"\n : \" pai session cleanup — EXECUTING\"\n )\n );\n console.log(dim(` Analyzing ${projects.length} project(s)...`));\n\n const plans = [];\n for (const project of projects) {\n const plan = await analyzeProject(registryBackend, project);\n if (plan) plans.push(plan);\n }\n\n const activePlans = plans.filter(\n (p) =>\n p.notesDirs.some(\n (d) =>\n d.toDelete.length > 0 ||\n d.toRename.length > 0 ||\n d.toMove.length > 0\n ) || p.renumberMap.size > 0\n );\n\n if (activePlans.length === 0) {\n console.log();\n console.log(ok(\" Nothing to do — all session notes are clean!\"));\n console.log();\n return;\n }\n\n if (dryRun) {\n await displayDryRun(activePlans);\n } else {\n await executeCleanup(activePlans, skipReindex);\n }\n }\n );\n}\n","/**\n * pai end [--dry-run]\n *\n * Extends `pai pause` with two additional steps:\n * 1. Marks the current session note **Status: Completed** and adds a\n * Completed timestamp — mirrors what `finalizeSessionNote()` does in the\n * hooks lib but without importing the hooks-only module.\n * 2. Prints a slightly more final safe-exit reminder.\n *\n * The TODO.md ## Continue checkpoint is written first (same as `pai pause`),\n * so if the user exits without the session note step succeeding the content\n * checkpoint is still intact.\n */\n\nimport { readFileSync, writeFileSync, renameSync } from \"node:fs\";\nimport { basename } from \"node:path\";\nimport chalk from \"chalk\";\nimport { cmdPause, type PauseOptions } from \"./pause.js\";\nimport {\n findNotesDir,\n findLatestNote,\n} from \"../../../session/checkpoint-block.js\";\n\n// ---------------------------------------------------------------------------\n// Session-note finalization (mirrors finalizeSessionNote from session-notes.ts)\n// ---------------------------------------------------------------------------\n\nfunction finalizeNote(notePath: string): { finalized: boolean; path: string } {\n const content = readFileSync(notePath, \"utf-8\");\n\n if (content.includes(\"**Status:** Completed\")) {\n return { finalized: false, path: notePath }; // already done\n }\n\n let updated = content.replace(\"**Status:** In Progress\", \"**Status:** Completed\");\n\n if (!updated.includes(\"**Completed:**\")) {\n const completionTime = new Date().toISOString();\n updated = updated.replace(\n \"---\\n\\n## Work Done\",\n `**Completed:** ${completionTime}\\n\\n---\\n\\n## Work Done`\n );\n }\n\n // Write atomically\n const tmp = `${notePath}.end.tmp`;\n writeFileSync(tmp, updated, \"utf-8\");\n renameSync(tmp, notePath);\n\n return { finalized: true, path: notePath };\n}\n\n// ---------------------------------------------------------------------------\n// Command\n// ---------------------------------------------------------------------------\n\nexport async function cmdEnd(opts: PauseOptions): Promise<void> {\n // ---- Step 1: Run pause logic (writes ## Continue, prints warning) ----\n // We call cmdPause first; it prints the ## Continue block and the initial\n // safe-exit box. We'll print the end-specific reminder afterwards.\n await cmdPause(opts);\n\n // ---- Step 2: Locate the project ----\n const { getRegistryBackend } = await import(\"../../../storage/factory.js\");\n const registryBackend = await getRegistryBackend();\n const cwd = process.cwd();\n const project = await registryBackend.findProjectByCwdPrefix(cwd);\n\n if (!project) {\n // cmdPause already printed an error. Nothing more to do.\n return;\n }\n\n // ---- Step 3: Find notes directory ----\n const notesDir = findNotesDir(project.root_path, project.encoded_dir ?? \"\");\n\n if (!notesDir) {\n console.log(\n chalk.dim(\" (No session note found — notes directory does not exist yet.)\")\n );\n printEndBox(opts.dryRun);\n return;\n }\n\n // ---- Step 4: Find latest session note ----\n const notePath = findLatestNote(notesDir);\n\n if (!notePath) {\n console.log(chalk.dim(\" (No session note found in notes directory.)\"));\n printEndBox(opts.dryRun);\n return;\n }\n\n // ---- Step 5: Mark as completed ----\n if (opts.dryRun) {\n console.log(\n \"\\n\" +\n chalk.bold(\"Dry run — would finalize session note:\") +\n \" \" +\n chalk.cyan(notePath)\n );\n console.log(\n chalk.dim(\n \" Would replace: **Status:** In Progress\\n\" +\n \" with: **Status:** Completed\"\n )\n );\n console.log(\n chalk.dim(\" Would add: **Completed:** <timestamp>\")\n );\n } else {\n const { finalized, path } = finalizeNote(notePath);\n if (finalized) {\n console.log(\n chalk.green(\" Session note finalized: \") + chalk.cyan(basename(path))\n );\n } else {\n console.log(\n chalk.dim(\n ` Session note already marked Completed: ${basename(notePath)}`\n )\n );\n }\n }\n\n // ---- Step 6: Print the more-final exit reminder ----\n printEndBox(opts.dryRun);\n}\n\nfunction printEndBox(dryRun?: boolean): void {\n const label = dryRun ? \" (dry-run)\" : \"\";\n const box = [\n \"\",\n chalk.bgRed.white.bold(\n ` SESSION ENDING${label}: How to exit safely `\n ),\n \"\",\n chalk.yellow(\n \" Inside the Claude Code session, type: \" +\n chalk.white.bold(\"/exit\") +\n chalk.yellow(\" (then press Enter)\")\n ),\n \"\",\n chalk.red.bold(\" DO NOT press Ctrl+C.\"),\n \"\",\n chalk.dim(\" Ctrl+C bypasses PAI's stop-hook, which means:\"),\n chalk.dim(\" - The session note is NOT written to by the stop-hook\"),\n chalk.dim(\" - The session becomes orphaned (cannot --resume next time)\"),\n chalk.dim(\" - The final session summary is never generated\"),\n \"\",\n chalk.dim(\n \" ## Continue checkpoint and session note status are already saved.\"\n ),\n chalk.dim(\" Use /exit to let PAI's stop-hook finalize the session fully.\"),\n \"\",\n ].join(\"\\n\");\n\n console.log(box);\n}\n","/**\n * pai pause all [--exit] [--dry-run]\n *\n * Pause every live Claude Code session that AIBroker knows about.\n *\n * For each live session returned by AIBroker's session_content IPC method:\n * 1. Send \"pause session\" to the iTerm2 pane via send_to_session.\n * 2. Optionally send \"\\n/exit\\n\" after a short delay (--exit flag).\n * 3. Print a summary of what was sent and to which sessions.\n *\n * If AIBroker is not running, prints a clear error and exits.\n * If no live sessions are found, reports \"nothing to pause\".\n */\n\nimport chalk from \"chalk\";\nimport {\n fetchLiveSessions,\n sendToSession,\n type AiBrokerSessionMeta,\n} from \"../../lib/aibroker-client.js\";\nimport { header, dim, ok, err, warn } from \"../../utils.js\";\n\n// ---------------------------------------------------------------------------\n// Helpers\n// ---------------------------------------------------------------------------\n\nfunction sleep(ms: number): Promise<void> {\n return new Promise((resolve) => setTimeout(resolve, ms));\n}\n\nfunction sessionLabel(s: AiBrokerSessionMeta): string {\n const name = s.paiName ?? s.name;\n return `${chalk.cyan(s.sessionId.slice(0, 8))} ${chalk.bold(name)}`;\n}\n\n/** Give each session a moment to pick up the prompt before judging it idle. */\nconst SETTLE_MS = 4_000;\n/** How often to re-ask AIBroker which sessions are back at the prompt. */\nconst POLL_MS = 2_000;\n\n/**\n * Wait until each session is back at its prompt, i.e. has finished writing its\n * checkpoint.\n *\n * This used to be a flat 5-second sleep before sending /exit. Authoring a real\n * handover — reading state, composing it, writing the file — takes far longer\n * than five seconds, so the exit routinely arrived mid-write and killed the\n * session while it was saving the very thing the command exists to save.\n *\n * `atPrompt` is the signal that a session is idle again. Sessions still busy\n * when the deadline passes are returned as stragglers and deliberately NOT\n * exited: leaving a session open costs nothing, and killing one mid-write costs\n * the checkpoint.\n */\nasync function waitForSessionsIdle(\n sessionIds: string[],\n timeoutMs: number\n): Promise<{ idle: Set<string>; stragglers: Set<string> }> {\n const pending = new Set(sessionIds);\n const idle = new Set<string>();\n\n await sleep(Math.min(SETTLE_MS, timeoutMs));\n\n const deadline = Date.now() + timeoutMs;\n while (pending.size > 0 && Date.now() < deadline) {\n let current: AiBrokerSessionMeta[];\n try {\n current = await fetchLiveSessions();\n } catch {\n // AIBroker blipped — try again on the next tick rather than assuming\n // anything about sessions we can no longer see.\n await sleep(POLL_MS);\n continue;\n }\n\n for (const s of current) {\n if (pending.has(s.sessionId) && s.atPrompt) {\n pending.delete(s.sessionId);\n idle.add(s.sessionId);\n }\n }\n\n // A session that vanished from the list has already gone; stop waiting.\n const visible = new Set(current.map((s) => s.sessionId));\n for (const id of [...pending]) {\n if (!visible.has(id)) pending.delete(id);\n }\n\n if (pending.size > 0) await sleep(POLL_MS);\n }\n\n return { idle, stragglers: pending };\n}\n\n// ---------------------------------------------------------------------------\n// Command\n// ---------------------------------------------------------------------------\n\n/** Case-insensitive substring match on the session's name or id. */\nexport function matchesOnly(s: AiBrokerSessionMeta, only: string): boolean {\n const needle = only.trim().toLowerCase();\n if (!needle) return true;\n const name = (s.paiName ?? s.name ?? \"\").toLowerCase();\n return name.includes(needle) || s.sessionId.toLowerCase().includes(needle);\n}\n\nexport async function cmdPauseAll(opts: {\n exit?: boolean;\n dryRun?: boolean;\n only?: string;\n wait?: number;\n}): Promise<void> {\n // Upper bound on how long to wait for a session to finish its checkpoint —\n // not a fixed delay. Generous, because the cost of waiting is nothing and the\n // cost of exiting too early is the checkpoint.\n const waitMs = opts.wait ?? 180_000;\n\n // ── Fetch live sessions ────────────────────────────────────────────────────\n let liveSessions: AiBrokerSessionMeta[];\n try {\n liveSessions = await fetchLiveSessions();\n } catch (e) {\n console.error(err(\"AIBroker is not running. Cannot list live sessions.\"));\n console.error(dim(\" Start AIBroker or run `pai pause` from each session manually.\"));\n process.exitCode = 1;\n return;\n }\n\n // ── Filter: Claude sessions only (skip bare shells) ───────────────────────\n const allClaude = liveSessions.filter((s) => s.kind === \"claude\");\n const claudeSessions = opts.only\n ? allClaude.filter((s) => matchesOnly(s, opts.only!))\n : allClaude;\n const skipped = liveSessions.length - allClaude.length;\n\n if (opts.only) {\n // Said out loud, because a filter that silently matches nothing looks\n // identical to a filter that matched and did its job quietly.\n console.log(\n dim(\n ` --only ${opts.only}: ${claudeSessions.length} of ${allClaude.length} ` +\n `Claude session(s) matched.`\n )\n );\n if (claudeSessions.length === 0) {\n console.log(warn(`No live Claude session matches \"${opts.only}\". Nothing to pause.`));\n return;\n }\n }\n\n if (skipped > 0) {\n process.stderr.write(\n `Skipping ${skipped} non-Claude tab${skipped === 1 ? \"\" : \"s\"} (bare shells).\\n`\n );\n }\n\n if (claudeSessions.length === 0) {\n console.log(warn(\"No live Claude sessions found via AIBroker. Nothing to pause.\"));\n return;\n }\n\n // ── Dry-run ───────────────────────────────────────────────────────────────\n if (opts.dryRun) {\n console.log(\"\\n\" + header(\"Dry Run — Would Pause These Sessions\") + \"\\n\");\n for (const s of claudeSessions) {\n console.log(\" \" + sessionLabel(s));\n console.log(dim(' → send: \"pause session\"'));\n if (opts.exit) {\n console.log(dim(` → wait ${waitMs}ms then send: \"/exit\"`));\n }\n }\n console.log();\n return;\n }\n\n // ── Live run ──────────────────────────────────────────────────────────────\n console.log(\n \"\\n\" +\n header(\"Pausing All Live Sessions\") +\n \"\\n\" +\n dim(` ${claudeSessions.length} Claude session(s) via AIBroker`) +\n \"\\n\"\n );\n\n const results: Array<{\n session: AiBrokerSessionMeta;\n pauseOk: boolean;\n unconfirmed?: boolean;\n exitOk?: boolean;\n error?: string;\n }> = [];\n\n for (const s of claudeSessions) {\n process.stdout.write(\" \" + sessionLabel(s) + \" … \");\n\n // Send \"pause session\" command\n // No trailing newline: the transport appends Enter itself.\n const pauseResult = await sendToSession(s.sessionId, \"pause session\");\n\n // Three outcomes, not two. A timeout means the message reached the target's\n // mailbox — the handler deposits before it waits for the submit ack — so the\n // pause is under way and only the confirmation is missing.\n //\n // Printing that as FAILED is worse than useless: the obvious response is to\n // run the command again, and a second \"pause session\" into a session that\n // already paused nests a fresh \"carried forward\" block inside the one it\n // just wrote. Measured across 15 sessions: 9 reported failed, 8 verifiably\n // paused, and one refused the duplicate specifically to protect its own\n // checkpoint.\n if (pauseResult.timedOut) {\n console.log(warn(\"sent — checkpoint still running (not confirmed)\"));\n results.push({ session: s, pauseOk: true, unconfirmed: true });\n continue;\n }\n\n if (!pauseResult.ok) {\n console.log(err(\"FAILED: \" + (pauseResult.error ?? \"unknown error\")));\n results.push({ session: s, pauseOk: false, error: pauseResult.error });\n continue;\n }\n console.log(ok(\"paused\"));\n results.push({ session: s, pauseOk: true });\n }\n\n // ── Optional /exit after waiting ──────────────────────────────────────────\n if (opts.exit) {\n const pausedSessions = results.filter((r) => r.pauseOk).map((r) => r.session);\n if (pausedSessions.length > 0) {\n console.log(\n \"\\n\" +\n dim(\n ` Waiting for sessions to finish writing their checkpoints ` +\n `(up to ${Math.round(waitMs / 1000)}s)…`\n )\n );\n\n const { stragglers } = await waitForSessionsIdle(\n pausedSessions.map((s) => s.sessionId),\n waitMs\n );\n\n console.log();\n for (const s of pausedSessions) {\n // Never exit a session that is still working — it is most likely still\n // writing the checkpoint, and /exit would destroy it mid-write.\n if (stragglers.has(s.sessionId)) {\n console.log(\n \" \" +\n sessionLabel(s) +\n \" \" +\n warn(\"still busy — left open, not exited\")\n );\n continue;\n }\n\n process.stdout.write(\" \" + sessionLabel(s) + \" exiting … \");\n const exitResult = await sendToSession(s.sessionId, \"/exit\");\n const rec = results.find((r) => r.session.sessionId === s.sessionId)!;\n rec.exitOk = exitResult.ok;\n if (!exitResult.ok) {\n console.log(warn(\"exit failed: \" + (exitResult.error ?? \"unknown\")));\n } else {\n console.log(ok(\"exited\"));\n }\n }\n\n if (stragglers.size > 0) {\n console.log(\n \"\\n\" +\n dim(\n ` ${stragglers.size} session(s) were still working. Leaving a ` +\n `session open is harmless; exiting one mid-write is not.`\n )\n );\n }\n }\n }\n\n // ── Summary ───────────────────────────────────────────────────────────────\n const total = results.length;\n const succeeded = results.filter((r) => r.pauseOk).length;\n const unconfirmed = results.filter((r) => r.unconfirmed).length;\n const confirmed = succeeded - unconfirmed;\n const failed = total - succeeded;\n\n console.log();\n if (failed === 0 && unconfirmed === 0) {\n console.log(ok(`All ${total} session(s) paused successfully.`));\n } else {\n console.log(\n ok(`${confirmed} confirmed`) +\n (unconfirmed > 0 ? warn(`, ${unconfirmed} still writing`) : \"\") +\n (failed > 0 ? err(`, ${failed} not delivered`) : \"\") +\n dim(` — of ${total} session(s).`)\n );\n for (const r of results.filter((r) => !r.pauseOk)) {\n const label = r.session.paiName ?? r.session.name ?? r.session.sessionId.slice(0, 8);\n console.log(err(` ${label}: ${r.error ?? \"unknown error\"}`));\n }\n if (unconfirmed > 0) {\n // Said explicitly, because the whole cost of the old wording was that it\n // read as \"try again\" when trying again is the one harmful move.\n console.log(\n dim(\n `\\n \"Still writing\" means delivered — the session is composing its\\n` +\n ` checkpoint, which routinely outlasts the confirmation window.\\n` +\n ` Do NOT re-run to catch them: a second pause nests a new\\n` +\n ` \"carried forward\" block inside the one being written. Check with\\n` +\n ` \\`pai session list\\` in a minute instead.`\n )\n );\n }\n }\n\n if (opts.exit) {\n const exitOk = results.filter((r) => r.exitOk).length;\n console.log(dim(` /exit sent to ${exitOk} session(s).`));\n }\n\n console.log();\n}\n","/**\n * pai sessions clear-names\n *\n * Recovery command: wipes corrupted iTerm2 session name state.\n *\n * Two-step reset:\n * 1. Clear ~/.aibroker/session-names.json (the persistent name store).\n * 2. Clear user.paiName variable from all live iTerm2 sessions.\n *\n * After this runs the user can `/Name` each tab fresh. Nothing is\n * set automatically — the user re-asserts names via the /Name skill.\n */\n\nimport { callAiBroker } from \"../../lib/aibroker-client.js\";\nimport { ok, err, dim, warn } from \"../../utils.js\";\n\nexport async function cmdClearNames(opts: { dryRun?: boolean }): Promise<void> {\n if (opts.dryRun) {\n console.log(dim(\"Dry run — would:\"));\n console.log(dim(\" 1. Call AIBroker clear_session_names (wipe session-names.json)\"));\n console.log(dim(\" 2. Call AIBroker clear_pai_names (wipe user.paiName from all iTerm sessions)\"));\n return;\n }\n\n let anyFailed = false;\n\n // Step 1: clear the persistent name store\n process.stdout.write(\"Clearing session-names.json … \");\n try {\n const r1 = await callAiBroker(\"clear_session_names\", {}, 10_000) as {\n cleared?: number;\n };\n const n = r1.cleared ?? 0;\n console.log(ok(`cleared ${n} entr${n === 1 ? \"y\" : \"ies\"}`));\n } catch (e) {\n const msg = e instanceof Error ? e.message : String(e);\n if (msg.includes(\"ENOENT\") || msg.includes(\"ECONNREFUSED\")) {\n console.log(warn(\"AIBroker not running — session-names.json not cleared\"));\n anyFailed = true;\n } else {\n console.log(err(msg));\n anyFailed = true;\n }\n }\n\n // Step 2: clear user.paiName from all live iTerm2 sessions\n process.stdout.write(\"Clearing user.paiName from iTerm2 sessions … \");\n try {\n const r2 = await callAiBroker(\"clear_pai_names\", {}, 30_000) as {\n cleared?: number;\n };\n const n = r2.cleared ?? 0;\n console.log(ok(`cleared ${n} session${n === 1 ? \"\" : \"s\"}`));\n } catch (e) {\n const msg = e instanceof Error ? e.message : String(e);\n if (msg.includes(\"ENOENT\") || msg.includes(\"ECONNREFUSED\")) {\n console.log(warn(\"AIBroker not running — iTerm2 paiName variables not cleared\"));\n anyFailed = true;\n } else {\n console.log(err(msg));\n anyFailed = true;\n }\n }\n\n if (anyFailed) {\n console.log(warn(\"\\nSome steps could not complete. Restart AIBroker and try again.\"));\n process.exitCode = 1;\n } else {\n console.log(ok(\"\\nDone. Use /Name to re-label each tab.\"));\n }\n}\n","/**\n * pai find <query> [--n=20] [--json]\n *\n * Content-based search across ~/.claude/history.jsonl.\n * Finds user prompts matching the query (case-insensitive substring),\n * groups by sessionId, sorts by most-recent match, and shows a table\n * with short UUID, date, project path, and the matching prompt snippet.\n *\n * The sessionId from history.jsonl is the same UUID used by claude --resume,\n * so rows are directly resumable via: pai resume <id>\n *\n * Note: older history.jsonl entries (before Claude Code ~2.0) may lack a\n * sessionId field. These are grouped under a synthetic \"no-session\" bucket\n * and shown in a separate note at the bottom.\n */\n\nimport { existsSync, createReadStream } from \"node:fs\";\nimport { createInterface } from \"node:readline\";\nimport { join } from \"node:path\";\nimport { homedir } from \"node:os\";\nimport chalk from \"chalk\";\nimport { renderTable, header, dim, warn, err } from \"../utils.js\";\n\n// ---------------------------------------------------------------------------\n// Types\n// ---------------------------------------------------------------------------\n\ninterface HistoryEntry {\n display?: string;\n timestamp?: number;\n project?: string;\n sessionId?: string;\n pastedContents?: unknown;\n}\n\ninterface SessionMatch {\n sessionId: string | null;\n /** Most-recent timestamp among matching lines */\n lastMatchTs: number;\n /** Most-recent matching prompt text */\n lastMatchDisplay: string;\n /** Project path (from the most-recent match) */\n project: string;\n /** Number of matching lines in this session */\n matchCount: number;\n}\n\n// ---------------------------------------------------------------------------\n// History file location\n// ---------------------------------------------------------------------------\n\nconst HISTORY_FILE = join(homedir(), \".claude\", \"history.jsonl\");\n\n// ---------------------------------------------------------------------------\n// Shorten project path for display\n// ---------------------------------------------------------------------------\n\nfunction shortenProject(p: string, maxLen = 42): string {\n if (p.length <= maxLen) return p;\n // Keep tail (the meaningful part) and prefix with …\n return \"…\" + p.slice(-(maxLen - 1));\n}\n\n// ---------------------------------------------------------------------------\n// Format timestamp\n// ---------------------------------------------------------------------------\n\nfunction fmtTs(ts: number): string {\n const d = new Date(ts);\n const pad = (n: number) => String(n).padStart(2, \"0\");\n return (\n `${d.getFullYear()}-${pad(d.getMonth() + 1)}-${pad(d.getDate())} ` +\n `${pad(d.getHours())}:${pad(d.getMinutes())}`\n );\n}\n\n// ---------------------------------------------------------------------------\n// Core search function (streaming to avoid loading 26k lines into memory)\n// ---------------------------------------------------------------------------\n\nexport async function searchHistory(\n query: string,\n maxResults: number\n): Promise<SessionMatch[]> {\n if (!existsSync(HISTORY_FILE)) return [];\n\n const queryLower = query.toLowerCase();\n // Escape regex special chars from query for safety — but we're doing\n // string .includes() not regex, so no escaping needed.\n\n const bySession = new Map<string, SessionMatch>();\n const noSession: SessionMatch = {\n sessionId: null,\n lastMatchTs: 0,\n lastMatchDisplay: \"\",\n project: \"\",\n matchCount: 0,\n };\n\n const rl = createInterface({\n input: createReadStream(HISTORY_FILE, { encoding: \"utf8\" }),\n crlfDelay: Infinity,\n });\n\n for await (const line of rl) {\n const t = line.trim();\n if (!t) continue;\n\n let entry: HistoryEntry;\n try {\n entry = JSON.parse(t) as HistoryEntry;\n } catch {\n continue;\n }\n\n const display = entry.display ?? \"\";\n if (!display.toLowerCase().includes(queryLower)) continue;\n\n const ts = entry.timestamp ?? 0;\n const project = entry.project ?? \"\";\n const sessionId = entry.sessionId ?? null;\n\n if (!sessionId) {\n // Older entry without sessionId\n if (ts > noSession.lastMatchTs) {\n noSession.lastMatchTs = ts;\n noSession.lastMatchDisplay = display;\n noSession.project = project;\n }\n noSession.matchCount++;\n continue;\n }\n\n const existing = bySession.get(sessionId);\n if (!existing) {\n bySession.set(sessionId, {\n sessionId,\n lastMatchTs: ts,\n lastMatchDisplay: display,\n project,\n matchCount: 1,\n });\n } else {\n existing.matchCount++;\n if (ts > existing.lastMatchTs) {\n existing.lastMatchTs = ts;\n existing.lastMatchDisplay = display;\n existing.project = project;\n }\n }\n }\n\n // Sort by most-recent match descending\n const sorted = [...bySession.values()].sort(\n (a, b) => b.lastMatchTs - a.lastMatchTs\n );\n\n return sorted.slice(0, maxResults);\n}\n\n// ---------------------------------------------------------------------------\n// Command\n// ---------------------------------------------------------------------------\n\nexport async function cmdFind(\n query: string,\n opts: { n?: string; json?: boolean }\n): Promise<void> {\n const maxResults = parseInt(opts.n ?? \"20\", 10);\n\n if (!existsSync(HISTORY_FILE)) {\n console.error(err(`~/.claude/history.jsonl not found.`));\n process.exitCode = 1;\n return;\n }\n\n const matches = await searchHistory(query, maxResults);\n\n if (opts.json) {\n console.log(JSON.stringify(matches, null, 2));\n return;\n }\n\n if (matches.length === 0) {\n console.log(warn(`No sessions found matching \"${query}\" in ~/.claude/history.jsonl.`));\n console.log(dim(` Try a shorter or different search term.`));\n return;\n }\n\n console.log(\"\\n\" + header(\"Session Search Results\") + \"\\n\");\n console.log(dim(` Query: \"${query}\" (${matches.length} session(s) with matching prompts)\\n`));\n\n const headers = [\"#\", \"id\", \"when\", \"project\", \"last matching prompt\"];\n const rows = matches.map((m, idx) => {\n const shortId = (m.sessionId ?? \"no-session\").slice(0, 8);\n const when = m.lastMatchTs > 0 ? fmtTs(m.lastMatchTs) : dim(\"—\");\n const project = shortenProject(m.project || dim(\"—\"));\n // Truncate the prompt snippet — strip newlines, trim\n const snippet = m.lastMatchDisplay\n .replace(/\\n+/g, \" \")\n .trim()\n .slice(0, 48);\n const display = snippet.length < m.lastMatchDisplay.replace(/\\n+/g, \" \").trim().length\n ? `\"${snippet}…\"`\n : `\"${snippet}\"`;\n\n return [\n dim(String(idx + 1)),\n chalk.cyan(shortId),\n when,\n dim(project),\n chalk.dim(display),\n ];\n });\n\n console.log(renderTable(headers, rows));\n console.log();\n console.log(dim(\" Resume: \") + chalk.white(\"pai resume <id>\"));\n console.log();\n}\n","/**\n * pick.ts — Interactive project/session picker (`pai` with no args).\n *\n * One door for \"find where I worked on something and go there.\" A self-contained\n * modal TUI (no fzf) over a UNIFIED, deduped list of:\n * - PROJECTS (places where knowledge lives — every active project, even cold)\n * - SESSIONS (past conversations — live / resumable / transcript)\n * tagged so the two stay visibly distinct.\n *\n * Two modes (see runSelector):\n * - command (default): ↑↓/j/k move · g go-to-tab · n new · c cd · w worker · a claude · f finder · q quit\n * - search (after s or /): type to filter, Enter/esc back to command\n * Action keys fire IMMEDIATELY on the highlighted row — letters are commands in\n * command mode, filter text only in the explicit, visible search mode. This is\n * why a single fzf-style \"type to filter\" box couldn't work: there, every letter\n * is filter input and can never also be an action.\n *\n * Design decisions (all driven by real pain):\n * - Filtering matches names, paths, AND folded-in note file/folder names, so\n * typing \"samba\" surfaces a project whose *name* is \"Chenarlier\".\n * - The detail panel shows context for the HIGHLIGHTED row only (recent notes),\n * computed lazily — avoids the ~8s/all-sessions live-prompt fetch.\n * - Actions launch Claude in the CURRENT terminal (g switches the iTerm tab via\n * the screenshot reveal mechanism; n starts fresh; c hands the dir to the\n * pai() shell wrapper to cd; f opens the OS file manager).\n * - Every rendered line is truncated to the window width so nothing wraps\n * (wrapping desyncs the one-row-per-line layout).\n */\n\nimport { spawn } from \"node:child_process\";\nimport { readdirSync, statSync } from \"node:fs\";\nimport { join, basename } from \"node:path\";\nimport chalk from \"chalk\";\nimport { dim, encodeDir, now } from \"../utils.js\";\nimport { scanSessions, fmtAge, type ScannedSession } from \"../lib/session-scan.js\";\nimport {\n buildDeduped,\n normalizeName,\n type UnifiedSession,\n type RegisteredProject,\n} from \"../lib/dedup-sessions.js\";\nimport { writeFileSync } from \"node:fs\";\nimport { fetchLiveSessions, revealItermSession } from \"../lib/aibroker-client.js\";\nimport { renderDedupedSessions } from \"../lib/dedup-sessions.js\";\nimport { launchInDir } from \"../lib/launch.js\";\nimport { printExitDir } from \"../lib/exit-dir.js\";\n\n// ---------------------------------------------------------------------------\n// Dispatch record — one per selectable row.\n// ---------------------------------------------------------------------------\n\ninterface PickRecord {\n /** 'D' = has a directory to act on; 'L' = live-only, no dir. */\n t: \"D\" | \"L\";\n name: string;\n dir?: string;\n /** Best resumable session UUID for the dir, if any. */\n uuid?: string;\n /** Live AIBroker / iTerm2 session id (present whenever the row is live). */\n live?: string;\n /** Registry slug, when this row maps to a registered project (enables 'd'). */\n slug?: string;\n}\n\n// ---------------------------------------------------------------------------\n// Project registry query (ALL active projects — cold ones included on purpose)\n// ---------------------------------------------------------------------------\n\nasync function getProjects(includeArchived: boolean): Promise<RegisteredProject[]> {\n try {\n const { getRegistryBackend } = await import(\"../../storage/factory.js\");\n const registryBackend = await getRegistryBackend();\n const rows = await registryBackend.listProjectsWithSessionStats();\n return includeArchived ? rows : rows.filter((p) => p.status === \"active\");\n } catch {\n return [];\n }\n}\n\n// ---------------------------------------------------------------------------\n// Note-keyword folding — cheap topic hints from file/folder names\n// ---------------------------------------------------------------------------\n\nconst KW_SKIP = new Set([\n \"node_modules\", \".git\", \".obsidian\", \"dist\", \"build\", \".cache\", \"vendor\",\n \".next\", \"target\", \"__pycache__\", \".venv\", \"coverage\",\n]);\nconst KW_STOP = new Set([\n \"the\", \"and\", \"for\", \"with\", \"notes\", \"note\", \"readme\", \"index\", \"src\",\n \"untitled\", \"new\", \"draft\", \"md\", \"txt\",\n]);\n\n/** Collect topic tokens from markdown file/folder names, depth-limited. */\nfunction noteKeywords(root: string, maxTokens = 14): string {\n const tokens = new Set<string>();\n const walk = (dir: string, depth: number) => {\n if (depth > 2 || tokens.size >= maxTokens * 3) return;\n let entries: string[];\n try {\n entries = readdirSync(dir);\n } catch {\n return;\n }\n for (const name of entries) {\n if (name.startsWith(\".\") || KW_SKIP.has(name)) continue;\n const full = join(dir, name);\n let isDir = false;\n try {\n isDir = statSync(full).isDirectory();\n } catch {\n continue;\n }\n if (isDir) {\n for (const tok of tokenize(name)) tokens.add(tok);\n walk(full, depth + 1);\n } else if (name.toLowerCase().endsWith(\".md\")) {\n for (const tok of tokenize(name)) tokens.add(tok);\n }\n }\n };\n walk(root, 0);\n return Array.from(tokens).slice(0, maxTokens).join(\" \");\n}\n\nfunction tokenize(s: string): string[] {\n return s\n .replace(/\\.md$/i, \"\")\n .replace(/[0-9]+/g, \" \")\n .split(/[^a-zA-ZÀ-ſ]+/)\n .map((t) => t.toLowerCase())\n .filter((t) => t.length >= 3 && !KW_STOP.has(t));\n}\n\n// ---------------------------------------------------------------------------\n// Feed construction\n// ---------------------------------------------------------------------------\n\ninterface FeedLine {\n record: PickRecord;\n /** ANSI-colored display row. */\n display: string;\n /** Lowercased plain text (name · dir · keywords) used for filtering. */\n search: string;\n}\n\nfunction tagFor(status: UnifiedSession[\"status\"]): string {\n switch (status) {\n case \"live\":\n return chalk.green(\"live \");\n case \"resumable\":\n return chalk.cyan(\"resume \");\n case \"project\":\n return chalk.magenta(\"project\");\n default:\n return chalk.dim(\"old \");\n }\n}\n\nfunction pad(s: string, n: number): string {\n // pad based on visible length (strip ANSI)\n const visible = s.replace(/\\x1b\\[[0-9;]*m/g, \"\");\n if (visible.length >= n) return s;\n return s + \" \".repeat(n - visible.length);\n}\n\nfunction shorten(p: string, max = 40): string {\n if (!p) return \"\";\n return p.length <= max ? p : \"…\" + p.slice(-(max - 1));\n}\n\n/**\n * Build the candidate list. buildDeduped merges live+disk+registry into one\n * tagged, deduped catalog. showAll=true so cold/idle projects are NOT filtered\n * out (finding old work is the whole point); archived filtering already handled\n * by getProjects().\n */\nfunction buildFeedFrom(\n allSessions: ScannedSession[],\n projects: RegisteredProject[],\n liveSessions: Awaited<ReturnType<typeof fetchLiveSessions>>,\n allProjects: RegisteredProject[]\n): FeedLine[] {\n const deduped = buildDeduped(liveSessions, allSessions, projects, true);\n\n // Directory-resolution maps span ALL projects (archived included). A live\n // session's only source of a cwd is the registry — an archived project like\n // \"Chenarlier\" still has a real directory, so we must not filter it out here.\n const slugRoot = new Map<string, string>();\n const nameRoot = new Map<string, string>();\n for (const p of allProjects) {\n slugRoot.set(p.slug, p.root_path);\n nameRoot.set(normalizeName(p.display_name ?? p.slug).toLowerCase(), p.root_path);\n }\n\n const bestResumable = new Map<string, ScannedSession>();\n for (const s of allSessions) {\n if (!s.resumable || !s.encodedDir) continue;\n const prev = bestResumable.get(s.encodedDir);\n if (!prev || s.mtime > prev.mtime) bestResumable.set(s.encodedDir, s);\n }\n\n const lines: FeedLine[] = [];\n for (const e of deduped) {\n // Skip nameless rows (e.g. a fresh, unnamed live Claude tab whose name\n // normalizes to \"\"). They're not searchable and just add a blank line.\n if (!e.name.trim()) continue;\n\n // Resolve directory + resumable uuid\n let dir: string | undefined;\n let uuid: string | undefined;\n\n if (e.diskSession) {\n dir =\n e.diskSession.clcDirectory ??\n e.diskSession.registryRootPath ??\n e.diskSession.decodedPath;\n uuid = e.diskSession.resumable ? e.diskSession.uuid : undefined;\n } else if (e.status === \"project\") {\n dir = e.project;\n } else if (e.slug && slugRoot.has(e.slug)) {\n dir = slugRoot.get(e.slug);\n } else {\n // Live-only entry (e.g. a running tab) — recover its directory from the\n // registry by normalized name. This is what makes \"go there\" work for a\n // live session instead of falling back to a tab switch.\n dir = nameRoot.get(e.name.toLowerCase());\n }\n\n if (dir && !uuid) {\n const enc = encodeDir(dir);\n uuid = bestResumable.get(enc)?.uuid;\n }\n\n let record: PickRecord;\n if (dir) {\n // Carry the live id too (when present) so 'g' can switch to the running tab.\n record = { t: \"D\", name: e.name, dir, uuid, live: e.liveSessionId, slug: e.slug };\n } else if (e.liveSessionId) {\n record = { t: \"L\", name: e.name, live: e.liveSessionId, slug: e.slug };\n } else {\n continue; // not actionable\n }\n\n // Keywords for filtering (only when we have a real dir to scan)\n const kw = dir ? noteKeywords(dir) : \"\";\n\n const tag = tagFor(e.status);\n const name = chalk.white(pad(e.name, 22));\n const where = dir ? dim(shorten(dir, 40)) : chalk.green(\"(running in another tab)\");\n const age =\n e.status === \"live\"\n ? chalk.green(\"now\")\n : e.lastActivity > 0\n ? dim(fmtAge(e.lastActivity))\n : dim(\"—\");\n const kwDisp = kw ? dim(\" \" + kw) : \"\";\n const display = `${tag} ${name} ${pad(age, 5)} ${where}${kwDisp}`;\n const search = `${e.name} ${dir ?? \"\"} ${kw}`.toLowerCase();\n\n lines.push({ record, display, search });\n }\n return lines;\n}\n\n// ---------------------------------------------------------------------------\n// Modal selector — our own TUI (no fzf). Two modes:\n// command mode (default): ↑↓/j/k move · g go-to-tab · n new · c cd · w worker · a claude · s search · q quit\n// search mode (after s or /): type to filter · Enter/esc back to command\n// Action keys fire IMMEDIATELY on the highlighted row — no Enter dance, because\n// in command mode letters are commands, not filter input. Search is a distinct,\n// visible mode so plain letters can be both filter text and actions without\n// colliding.\n// ---------------------------------------------------------------------------\n\ntype PickAction = \"new\" | \"cd\" | \"switch\" | \"worker\" | \"anthropic\";\n\ninterface SelectorResult {\n record: PickRecord;\n action: PickAction;\n}\n\nconst stripAnsi = (s: string): string => s.replace(/\\x1b\\[[0-9;]*m/g, \"\");\n\n/** Open a path in the OS file manager (Finder / Explorer / xdg-open). */\nfunction openInFileManager(dir: string): void {\n const cmd =\n process.platform === \"darwin\" ? \"open\" : process.platform === \"win32\" ? \"explorer\" : \"xdg-open\";\n try {\n spawn(cmd, [dir], { stdio: \"ignore\", detached: true }).unref();\n } catch {\n /* best-effort */\n }\n}\n\n/**\n * Truncate to `max` visible columns, preserving ANSI color codes (which have\n * zero width) and appending an ellipsis when content is cut. This is what keeps\n * rows from wrapping on a narrow window — wrapping would desync the one-row-per-\n * line layout and make the cursor appear to jump.\n */\nfunction truncVisible(s: string, max: number): string {\n if (max <= 1) return \"\";\n let out = \"\";\n let vis = 0;\n let i = 0;\n while (i < s.length) {\n if (s[i] === \"\\x1b\") {\n const m = s.slice(i).match(/^\\x1b\\[[0-9;]*m/);\n if (m) {\n out += m[0];\n i += m[0].length;\n continue;\n }\n }\n if (vis >= max - 1) return out + \"…\\x1b[0m\";\n out += s[i];\n vis++;\n i++;\n }\n return out + \"\\x1b[0m\";\n}\n\nfunction filterLines(lines: FeedLine[], query: string): FeedLine[] {\n const q = query.trim().toLowerCase();\n if (!q) return lines;\n const tokens = q.split(/\\s+/);\n return lines.filter((l) => tokens.every((t) => l.search.includes(t)));\n}\n\n/** Cached per-directory context (recent notes) for the detail panel. */\nconst ctxCache = new Map<string, string[]>();\nfunction contextFor(rec: PickRecord): string[] {\n if (!rec.dir) {\n return rec.live ? [chalk.green(\"live — running in another tab\")] : [dim(\"no directory on record\")];\n }\n const cached = ctxCache.get(rec.dir);\n if (cached) return cached;\n const lines: string[] = [];\n const notes = recentMarkdown(rec.dir, 4);\n if (notes.length) {\n lines.push(dim(\"recent notes:\"));\n for (const n of notes) lines.push(\" \" + dim(n.rel.slice(0, 60)) + \" \" + dim(fmtAge(n.mtime)));\n } else {\n lines.push(dim(\"(no notes here yet)\"));\n }\n ctxCache.set(rec.dir, lines);\n return lines;\n}\n\nasync function runSelector(\n lines: FeedLine[],\n onRemove?: (rec: PickRecord) => Promise<boolean>\n): Promise<SelectorResult | null> {\n const stdin = process.stdin;\n const out = process.stdout;\n\n return new Promise<SelectorResult | null>((resolve) => {\n let pool = lines; // mutable: 'd' removes rows from here\n let query = \"\";\n let mode: \"command\" | \"search\" = \"command\";\n let filtered = pool;\n let cursor = 0;\n let confirm: FeedLine | null = null; // pending 'd' delete confirmation\n let flash = \"\"; // transient status line\n\n const wasRaw = stdin.isRaw ?? false;\n try {\n stdin.setRawMode?.(true);\n } catch {\n /* not a tty */\n }\n stdin.resume();\n out.write(\"\\x1b[?1049h\\x1b[?25l\"); // alt screen, hide cursor\n\n const teardown = () => {\n out.write(\"\\x1b[?25h\\x1b[?1049l\"); // show cursor, leave alt screen\n stdin.removeListener(\"data\", onData);\n try {\n stdin.setRawMode?.(wasRaw);\n } catch {\n /* ignore */\n }\n stdin.pause();\n };\n const finish = (val: SelectorResult | null) => {\n teardown();\n resolve(val);\n };\n\n const recompute = () => {\n filtered = filterLines(pool, query);\n if (cursor >= filtered.length) cursor = Math.max(0, filtered.length - 1);\n };\n\n const render = () => {\n const rows = out.rows && out.rows > 8 ? out.rows : 24;\n const cols = out.columns && out.columns > 20 ? out.columns : 100;\n const frame: string[] = [];\n\n // Header\n const title = chalk.bold(\" pai\") + dim(\" — find a project or session\");\n frame.push(title);\n if (mode === \"search\") {\n frame.push(\" \" + chalk.cyan(\"search > \") + query + chalk.inverse(\" \"));\n } else {\n frame.push(\n \" \" + dim(`${filtered.length}/${lines.length}`) +\n (query ? dim(` filter: ${query}`) : \"\")\n );\n }\n frame.push(dim(\" \" + \"─\".repeat(Math.min(cols - 4, 80))));\n\n // List window\n const ctxHeight = 6;\n const listHeight = Math.max(3, rows - 4 - ctxHeight - 2);\n let start = 0;\n if (cursor >= listHeight) start = cursor - listHeight + 1;\n const end = Math.min(filtered.length, start + listHeight);\n if (filtered.length === 0) {\n frame.push(\" \" + dim(\"no matches — press s to change the search, esc to quit\"));\n }\n for (let i = start; i < end; i++) {\n const row = filtered[i];\n if (i === cursor) {\n frame.push(chalk.inverse(\" \" + stripAnsi(row.display).slice(0, cols - 2).padEnd(cols - 2)));\n } else {\n frame.push(\" \" + row.display);\n }\n }\n\n // Detail panel for the highlighted row\n frame.push(dim(\" \" + \"─\".repeat(Math.min(cols - 4, 80))));\n const cur = filtered[cursor];\n if (cur) {\n frame.push(\" \" + chalk.bold.white(cur.record.name) + (cur.record.dir ? dim(\" \" + shorten(cur.record.dir, cols - 30)) : \"\"));\n for (const l of contextFor(cur.record).slice(0, ctxHeight - 2)) frame.push(l);\n }\n\n // Footer (keys / confirm / flash)\n frame.push(dim(\" \" + \"─\".repeat(Math.min(cols - 4, 80))));\n if (confirm) {\n frame.push(\n \" \" + chalk.yellow(`Remove \"${confirm.record.name}\" from PAI's list?`) +\n dim(\" files stay on disk \") + chalk.cyan(\"y\") + dim(\"/\") + chalk.cyan(\"N\")\n );\n } else if (mode === \"search\") {\n frame.push(\" \" + dim(\"type to filter · \") + chalk.cyan(\"Enter\") + dim(\" done · \") + chalk.cyan(\"esc\") + dim(\" cancel\"));\n } else {\n const parts: string[] = [];\n if (cur?.record.live) parts.push(chalk.cyan(\"g\") + dim(\" go to tab\"));\n if (cur?.record.dir) parts.push(chalk.cyan(\"n\") + dim(\" new\"));\n if (cur?.record.dir) parts.push(chalk.cyan(\"c\") + dim(\" cd\"));\n if (cur?.record.dir) parts.push(chalk.cyan(\"w\") + dim(\" worker\"));\n if (cur?.record.dir) parts.push(chalk.cyan(\"a\") + dim(\" claude\"));\n if (cur?.record.dir) parts.push(chalk.cyan(\"f\") + dim(\" finder\"));\n if (cur?.record.slug) parts.push(chalk.cyan(\"d\") + dim(\" remove\"));\n parts.push(chalk.cyan(\"s\") + dim(\" search\"));\n parts.push(chalk.cyan(\"↑↓\") + dim(\" move\"));\n parts.push(chalk.cyan(\"q\") + dim(\" quit\"));\n frame.push(\" \" + parts.join(dim(\" · \")));\n if (flash) frame.push(\" \" + dim(flash));\n }\n\n // Paint: home, each line truncated to width (no wrapping) + cleared to\n // EOL, then clear everything below.\n const painted = frame.map((l) => truncVisible(l, cols)).join(\"\\x1b[K\\n\");\n out.write(\"\\x1b[H\" + painted + \"\\x1b[K\\x1b[J\");\n };\n\n const move = (delta: number) => {\n if (filtered.length === 0) return;\n cursor = (cursor + delta + filtered.length) % filtered.length;\n };\n\n const onData = async (buf: Buffer) => {\n const s = buf.toString(\"utf8\");\n const cur = filtered[cursor];\n\n // Universal: Ctrl-C quits.\n if (s === \"\\x03\") return finish(null);\n\n // Pending delete confirmation captures the next key.\n if (confirm) {\n const target = confirm;\n confirm = null;\n if (s === \"y\" || s === \"Y\") {\n const okRemoved = onRemove ? await onRemove(target.record) : false;\n if (okRemoved) {\n pool = pool.filter((l) => l !== target);\n ctxCache.delete(target.record.dir ?? \"\");\n recompute();\n flash = `Removed \"${target.record.name}\" from PAI's list (files untouched).`;\n } else {\n flash = `Could not remove \"${target.record.name}\".`;\n }\n } else {\n flash = \"\";\n }\n return render();\n }\n\n // Arrows (work in both modes).\n if (s === \"\\x1b[A\") { move(-1); return render(); }\n if (s === \"\\x1b[B\") { move(1); return render(); }\n if (s === \"\\x1b[5~\") { move(-5); return render(); }\n if (s === \"\\x1b[6~\") { move(5); return render(); }\n\n if (mode === \"search\") {\n if (s === \"\\r\" || s === \"\\n\") { mode = \"command\"; return render(); }\n if (s === \"\\x1b\") { mode = \"command\"; return render(); } // esc → back to command, keep filter\n if (s === \"\\x7f\" || s === \"\\b\") { query = query.slice(0, -1); recompute(); return render(); }\n // Printable → append to query.\n if (s.length === 1 && s >= \" \") { query += s; cursor = 0; recompute(); return render(); }\n return; // ignore other escapes\n }\n\n // command mode\n if (s === \"q\" || s === \"\\x1b\") return finish(null);\n if (s === \"s\" || s === \"/\") { flash = \"\"; mode = \"search\"; return render(); }\n if (s === \"j\") { move(1); return render(); }\n if (s === \"k\") { move(-1); return render(); }\n if (!cur) return;\n if (s === \"g\" && cur.record.live) return finish({ record: cur.record, action: \"switch\" });\n if (s === \"n\" && cur.record.dir) return finish({ record: cur.record, action: \"new\" });\n if (s === \"c\" && cur.record.dir) return finish({ record: cur.record, action: \"cd\" });\n // w/a force one engine for the launch, overriding the workers config.\n if (s === \"w\" && cur.record.dir) return finish({ record: cur.record, action: \"worker\" });\n if (s === \"a\" && cur.record.dir) return finish({ record: cur.record, action: \"anthropic\" });\n // f → open in the file manager, but stay in the picker.\n if (s === \"f\" && cur.record.dir) { openInFileManager(cur.record.dir); return; }\n // d → ask to remove this row from PAI's list (only registered projects).\n if (s === \"d\") {\n if (cur.record.slug) { confirm = cur; flash = \"\"; }\n else flash = \"Only registered projects can be removed from the list.\";\n return render();\n }\n if (s === \"\\r\" || s === \"\\n\") {\n // Enter = smart default: live → go to tab, else → new session.\n if (cur.record.live) return finish({ record: cur.record, action: \"switch\" });\n if (cur.record.dir) return finish({ record: cur.record, action: \"new\" });\n }\n };\n\n stdin.on(\"data\", onData);\n render();\n });\n}\n\n// ---------------------------------------------------------------------------\n// Note discovery — recent markdown for the selector's detail panel.\n// ---------------------------------------------------------------------------\n\ninterface NoteRef {\n rel: string;\n full: string;\n mtime: number;\n}\n\nfunction recentMarkdown(root: string, limit: number): NoteRef[] {\n const found: NoteRef[] = [];\n const walk = (dir: string, depth: number) => {\n if (depth > 2 || found.length >= 200) return;\n let entries: string[];\n try {\n entries = readdirSync(dir);\n } catch {\n return;\n }\n for (const name of entries) {\n if (name.startsWith(\".\") || KW_SKIP.has(name)) continue;\n const full = join(dir, name);\n let st;\n try {\n st = statSync(full);\n } catch {\n continue;\n }\n if (st.isDirectory()) walk(full, depth + 1);\n else if (name.toLowerCase().endsWith(\".md\")) {\n found.push({ rel: full.slice(root.length + 1), full, mtime: st.mtimeMs });\n }\n }\n };\n walk(root, 0);\n found.sort((a, b) => b.mtime - a.mtime);\n return found.slice(0, limit);\n}\n\n// ---------------------------------------------------------------------------\n// Main command (`pai` with no args, interactive)\n// ---------------------------------------------------------------------------\n\nexport interface PickOpts {\n all?: boolean;\n dryRun?: boolean;\n}\n\nexport async function cmdPick(opts: PickOpts = {}): Promise<void> {\n const interactive = !!process.stdout.isTTY && !!process.stdin.isTTY;\n\n // Fallback to the static listing when we're not on an interactive terminal\n // (e.g. piped output). The modal selector needs a real tty.\n if (!interactive) {\n const allSessions = await scanSessions({ limit: 500, filter: \"named\" });\n const projects = await getProjects(opts.all ?? false);\n let live: Awaited<ReturnType<typeof fetchLiveSessions>> = [];\n try {\n live = await fetchLiveSessions();\n } catch {\n /* AIBroker not running */\n }\n const deduped = buildDeduped(live, allSessions, projects, opts.all ?? false);\n renderDedupedSessions(deduped, opts.all ? undefined : 20);\n return;\n }\n\n // Build the feed (with live sessions merged in)\n const allSessions = await scanSessions({ limit: 500, filter: \"named\" });\n const projects = await getProjects(opts.all ?? false);\n let live: Awaited<ReturnType<typeof fetchLiveSessions>> = [];\n try {\n live = await fetchLiveSessions();\n } catch {\n /* AIBroker not running — disk + registry still populate the picker */\n }\n\n const allProjects = await getProjects(true);\n const lines = buildFeedFrom(allSessions, projects, live, allProjects);\n if (lines.length === 0) {\n process.stderr.write(dim(\" No projects or sessions found.\\n\"));\n return;\n }\n\n // 'd' removes a row from PAI's list: archive the project (reversible, FK-safe,\n // files stay on disk). Returns false if the row isn't a registered project.\n const onRemove = async (rec: PickRecord): Promise<boolean> => {\n if (!rec.slug) return false;\n try {\n const { getRegistryBackend } = await import(\"../../storage/factory.js\");\n const registryBackend = await getRegistryBackend();\n const project = await registryBackend.getProjectBySlug(rec.slug);\n if (!project || project.status !== \"active\") return false;\n const ts = now();\n await registryBackend.updateProjectStatus(project.id, \"archived\", {\n archivedAt: ts,\n updatedAt: ts,\n requireCurrentStatus: \"active\",\n });\n return true;\n } catch {\n return false;\n }\n };\n\n // Modal selector — navigate, then g/n/c/w/a act immediately on the highlighted row.\n const sel = await runSelector(lines, onRemove);\n if (!sel) return; // cancelled\n const { record, action } = sel;\n\n // switch → reveal the running iTerm tab (the screenshot mechanism).\n if (action === \"switch\") {\n if (record.live) {\n const r = revealItermSession(record.live);\n if (!r.ok) {\n process.stderr.write(chalk.yellow(` Could not switch: ${r.error ?? \"unknown error\"}\\n`));\n }\n }\n return;\n }\n\n if (!record.dir) {\n process.stderr.write(dim(` No directory on record for \"${record.name}\".\\n`));\n return;\n }\n\n // cd → hand the directory to the `pai()` shell wrapper via PAI_PICK_OUT; a\n // child process can't change the parent shell's cwd itself. Without the\n // wrapper, just print the path so the user can cd manually.\n if (action === \"cd\") {\n const sink = process.env.PAI_PICK_OUT;\n if (sink) {\n try {\n writeFileSync(sink, record.dir, \"utf8\");\n return;\n } catch {\n /* fall through to printing */\n }\n }\n printExitDir(record.dir);\n return;\n }\n\n // new → start a fresh session here, in the current terminal. The engine\n // follows the workers config; w/a above forced one explicitly.\n const engine = action === \"worker\" ? \"worker\" : action === \"anthropic\" ? \"claude\" : undefined;\n launchInDir(record.dir, record.name, { forceFresh: true, engine, dryRun: opts.dryRun });\n}\n","/**\n * PAI CLI — Commander program builder.\n *\n * Constructs the full `pai` command tree WITHOUT parsing argv. This separation\n * lets the documentation generator (scripts/build-docs.mjs) import and introspect\n * the live program object — the single source of truth for `--help`, the\n * generated man pages, and `pai help <area>`.\n *\n * The thin entry point (cli/index.ts) imports buildProgram() and calls parse().\n */\n\nimport { Command } from \"commander\";\nimport { readFileSync } from \"node:fs\";\nimport { resolveFromModule } from \"../module-paths.js\";\nimport { getRegistryBackend } from \"../storage/factory.js\";\nimport { registerProjectsCommands } from \"./commands/project/projects-index.js\";\nimport { findMovedPath } from \"./commands/project/commands.js\";\nimport { existsSync } from \"node:fs\";\nimport { basename } from \"node:path\";\nimport { registerRegistryCommands } from \"./commands/registry.js\";\nimport { registerMemoryCommands } from \"./commands/memory.js\";\nimport { registerIdentityCommands } from \"./commands/identity.js\";\nimport { registerConfigCommands } from \"./commands/config.js\";\nimport { registerMcpCommands } from \"./commands/mcp.js\";\nimport { registerDaemonCommands } from \"./commands/daemon.js\";\nimport { registerHooksDbCommands } from \"./commands/hooks-db.js\";\nimport { registerBackupCommands } from \"./commands/backup.js\";\nimport { registerRestoreCommands } from \"./commands/restore.js\";\nimport { registerSetupCommand } from \"./commands/setup.js\";\nimport { registerObsidianCommands } from \"./commands/obsidian.js\";\nimport { registerZettelCommands } from \"./commands/zettel.js\";\nimport { registerObservationCommands } from \"./commands/observation.js\";\nimport { registerAuditCommands } from \"./commands/audit.js\";\nimport { registerSkillCommands } from \"./commands/skill.js\";\nimport { registerUpdateCommand } from \"./commands/update.js\";\nimport { registerNotifyCommands } from \"./commands/notify.js\";\nimport { registerTaskCommands } from \"./commands/task.js\";\nimport { registerTopicCommands } from \"./commands/topic.js\";\nimport { registerKgCommands } from \"./commands/kg.js\";\nimport { registerDbCommands } from \"./commands/db.js\";\nimport { registerHelpCommand } from \"./commands/help.js\";\nimport { registerSessionCommands } from \"./commands/session/index.js\";\nimport { registerWorkerCommands } from \"./commands/worker/index.js\";\nimport { registerLaunchCommand } from \"./commands/launch.js\";\nimport { registerSessionCleanupCommand } from \"./commands/session-cleanup/index.js\";\nimport { err, warn, ok, dim, shortenPath, encodeDir, now } from \"./utils.js\";\nimport { cmdPause } from \"./commands/session/pause.js\";\nimport { cmdEnd } from \"./commands/session/end.js\";\nimport { cmdGoto } from \"./commands/session/goto.js\";\nimport { cmdPauseAll } from \"./commands/session/pause-all.js\";\nimport { cmdClearNames } from \"./commands/session/clear-names.js\";\nimport { cmdList as cmdNotesList } from \"./commands/session/commands.js\";\nimport { resolveIdentifier } from \"./commands/project/helpers.js\";\nimport { cmdFind } from \"./commands/find.js\";\nimport { cmdMain } from \"./commands/main-resolver.js\";\nimport { cmdPick } from \"./commands/pick.js\";\n\n// ---------------------------------------------------------------------------\n// Version resolution\n// ---------------------------------------------------------------------------\n\nfunction getVersion(): string {\n try {\n const pkgPath = resolveFromModule(import.meta.url, \"package.json\");\n const pkg = JSON.parse(readFileSync(pkgPath, \"utf8\")) as { version?: string };\n return pkg.version ?? \"0.0.0\";\n } catch {\n return \"0.0.0\";\n }\n}\n\n// ---------------------------------------------------------------------------\n// Program builder\n// ---------------------------------------------------------------------------\n\n/**\n * Build and return the fully configured `pai` Commander program.\n * Does NOT call parse — the caller (cli/index.ts) does that.\n */\nexport function buildProgram(): Command {\n const program = new Command();\n\n program\n .name(\"pai\")\n .description(\"PAI Knowledge OS — Personal AI Infrastructure CLI\")\n .version(getVersion(), \"-V, --version\", \"Print version and exit\")\n .addHelpText(\n \"after\",\n `\nDaily commands:\n pai Show your work: live tabs + recent sessions + recent projects\n pai <name> Switch live tab, else fresh in its directory — universal\n pai pause [all] Pause current (or all live) sessions\n pai end Finalize current session\n\nSubcommands (rarely needed):\n pai projects ... Project registry management (cd, rebind, info, ...)\n pai registry ... Registry maintenance (scan, ...)\n pai memory ... Memory engine (index, search, ...)\n pai shell-init Shell integration (eval \"$(pai shell-init)\")\n\nRecovery:\n pai clear-names Wipe corrupt iTerm tab-name store\n\nExamples:\n pai aibroker Switch to the live AIBroker tab in iTerm\n pai mdf Find sessions where you worked on MDF\n pai 81c5c3dc Resume session by UUID prefix\n pai --all Show everything including cold and zero-session projects`\n );\n\n // -------------------------------------------------------------------------\n // pai projects (canonical plural namespace)\n // -------------------------------------------------------------------------\n\n const projectsCmd = program\n .command(\"projects\")\n .description(\"Manage registered projects (list, cd, add, info, ...)\");\n\n registerProjectsCommands(projectsCmd);\n\n // Singular alias: `pai project` → `pai projects`\n const projectCmd = program\n .command(\"project\")\n .description(\"Alias for `pai projects`\");\n\n registerProjectsCommands(projectCmd);\n\n // -------------------------------------------------------------------------\n // pai sessions (alias for the unified `pai` listing)\n // -------------------------------------------------------------------------\n\n // -------------------------------------------------------------------------\n // pai session <subcommand>\n //\n // These were written, exported and never wired: neither\n // registerSessionCommands nor registerSessionCleanupCommand was called from\n // anywhere, so every `pai session <x>` fell through to the default `query`\n // resolver and was interpreted as a prompt-history search.\n //\n // That is why session-stop.sh has been a near no-op — all four of its calls\n // (`session cleanup --execute`, `session checkpoint`, `session slug --apply`,\n // `session handover`) failed silently behind `2>/dev/null || true`.\n // -------------------------------------------------------------------------\n\n const sessionCmd = program\n .command(\"session\")\n .description(\n \"Session management: list, info, checkpoint, handover, cleanup, slug, tag, route.\\n\" +\n \"Short forms: `pai pause`, `pai end`, `pai resume <name>`.\"\n );\n\n registerSessionCommands(sessionCmd);\n registerSessionCleanupCommand(sessionCmd);\n\n // -------------------------------------------------------------------------\n // pai worker — subagent runs on configured providers (glm et al.)\n // -------------------------------------------------------------------------\n\n const workerCmd = program\n .command(\"worker\")\n .description(\n \"Run subagents on configured worker providers: run, ps, follow, replay,\\n\" +\n \"pane, log, status-line, providers, roles, goal, config, on, off, install.\\n\" +\n \"run takes --spec <file> (or --spec -) to read the prompt from a file/stdin\\n\" +\n \"instead of an inline -p '<prompt>', which breaks on shell quoting.\"\n );\n\n registerWorkerCommands(workerCmd);\n\n // -------------------------------------------------------------------------\n // pai launch — start the harness itself (not a subagent) on any provider\n // -------------------------------------------------------------------------\n\n registerLaunchCommand(program);\n\n program\n .command(\"sessions\")\n .description(\"Alias for `pai` — show the unified deduped listing.\")\n .option(\"--all\", \"Show all entries including cold / zero-session / archived projects\")\n .option(\"-n, --n <count>\", \"Max candidates for history search\", \"20\")\n .action(async (opts: { all?: boolean; n?: string }) => {\n await cmdMain(undefined, undefined, opts);\n });\n\n // -------------------------------------------------------------------------\n // pai registry\n // -------------------------------------------------------------------------\n\n const registryCmd = program\n .command(\"registry\")\n .description(\"Registry maintenance: scan, migrate, stats, rebuild\");\n\n registerRegistryCommands(registryCmd);\n\n // -------------------------------------------------------------------------\n // pai memory\n // -------------------------------------------------------------------------\n\n const memoryCmd = program\n .command(\"memory\")\n .description(\"Memory engine: index, search, and status\");\n\n registerMemoryCommands(memoryCmd);\n\n // -------------------------------------------------------------------------\n // pai identity\n // -------------------------------------------------------------------------\n\n const identityCmd = program\n .command(\"identity\")\n .description(\"Declare who you are: self addresses and where mail is delivered\");\n\n registerIdentityCommands(identityCmd);\n\n // -------------------------------------------------------------------------\n // pai config\n // -------------------------------------------------------------------------\n\n const configCmd = program\n .command(\"config\")\n .description(\"PAI_HOME namespace dir: path, migrate\");\n\n registerConfigCommands(configCmd);\n\n // -------------------------------------------------------------------------\n // pai mcp\n // -------------------------------------------------------------------------\n\n const mcpCmd = program\n .command(\"mcp\")\n .description(\"MCP server management: install and status\");\n\n registerMcpCommands(mcpCmd);\n\n // -------------------------------------------------------------------------\n // pai daemon\n // -------------------------------------------------------------------------\n\n const daemonCmd = program\n .command(\"daemon\")\n .description(\"PAI daemon management: serve, status, restart, install, uninstall, logs\");\n\n registerDaemonCommands(daemonCmd);\n\n // -------------------------------------------------------------------------\n // pai hooks-db — registry updates for the shell hooks\n // -------------------------------------------------------------------------\n\n const hooksDbCmd = program\n .command(\"hooks-db\")\n .description(\"Registry updates for the shell hooks (session-stop, pre-compact)\");\n\n registerHooksDbCommands(hooksDbCmd);\n\n // -------------------------------------------------------------------------\n // pai backup / pai restore\n // -------------------------------------------------------------------------\n\n registerBackupCommands(program);\n registerRestoreCommands(program);\n\n // -------------------------------------------------------------------------\n // pai setup / pai update\n // -------------------------------------------------------------------------\n\n registerSetupCommand(program);\n registerUpdateCommand(program);\n\n // -------------------------------------------------------------------------\n // pai notify\n // -------------------------------------------------------------------------\n\n const notifyCmd = program\n .command(\"notify\")\n .description(\"Notification config: status, get, set, test, send\");\n\n registerNotifyCommands(notifyCmd);\n\n // -------------------------------------------------------------------------\n // pai task\n // -------------------------------------------------------------------------\n\n const taskCmd = program\n .command(\"task\")\n .alias(\"tasks\")\n .description(\"Task bus: list, add, dispatch, and complete cross-session work\");\n\n registerTaskCommands(taskCmd);\n\n // -------------------------------------------------------------------------\n // pai topic\n // -------------------------------------------------------------------------\n\n const topicCmd = program\n .command(\"topic\")\n .description(\"Topic shift detection: check whether context has drifted to a different project\");\n\n registerTopicCommands(topicCmd);\n\n // -------------------------------------------------------------------------\n // pai kg\n // -------------------------------------------------------------------------\n\n const kgCmd = program\n .command(\"kg\")\n .description(\"Temporal knowledge graph: backfill, query, list, stats\");\n\n registerKgCommands(kgCmd);\n\n // -------------------------------------------------------------------------\n // pai db\n // -------------------------------------------------------------------------\n\n const dbCmd = program\n .command(\"db\")\n .description(\"Database inspection: query, tables, schema (sqlite or postgres)\");\n\n registerDbCommands(dbCmd);\n\n // -------------------------------------------------------------------------\n // pai obsidian\n // -------------------------------------------------------------------------\n\n const obsidianCmd = program\n .command(\"obsidian\")\n .description(\"Obsidian vault: sync project notes, view status, open in Obsidian\");\n\n registerObsidianCommands(obsidianCmd);\n\n // -------------------------------------------------------------------------\n // pai zettel\n // -------------------------------------------------------------------------\n\n const zettelCmd = program\n .command(\"zettel\")\n .description(\"Zettelkasten intelligence: explore, surprise, converse, themes, health, suggest\");\n\n registerZettelCommands(zettelCmd);\n\n // -------------------------------------------------------------------------\n // pai observation\n // -------------------------------------------------------------------------\n\n const observationCmd = program\n .command(\"observation\")\n .description(\"Observation capture: list, search, and stats\");\n\n registerObservationCommands(observationCmd);\n\n // -------------------------------------------------------------------------\n // pai audit\n // -------------------------------------------------------------------------\n\n const auditCmd = program.command(\"audit\").description(\"Diagnostic audits: token-waste report and friends\");\n\n registerAuditCommands(auditCmd);\n\n // -------------------------------------------------------------------------\n // pai skill\n // -------------------------------------------------------------------------\n\n const skillCmd = program\n .command(\"skill\")\n .description(\"Skill telemetry and (future) discovery for the self-educating skill system\");\n\n registerSkillCommands(skillCmd);\n\n // -------------------------------------------------------------------------\n // pai help [area] — rich man-page viewer for the generated docs\n // -------------------------------------------------------------------------\n\n registerHelpCommand(program);\n\n // -------------------------------------------------------------------------\n // DAILY VERBS — top-level\n // -------------------------------------------------------------------------\n\n // pai pause [all] [--dry-run] [--exit] [--wait <ms>]\n program\n .command(\"pause [target]\")\n .description(\n \"Save state and display safe-exit instructions for the current session.\\n\" +\n \"Writes a ## Continue checkpoint to the project's TODO.md.\\n\" +\n \"Use `pai pause all` to pause every live session via AIBroker.\"\n )\n .option(\"--dry-run\", \"Preview changes without writing them\")\n .option(\n \"--body-file <path>\",\n \"File holding the checkpoint markdown to persist ('-' reads stdin). Required unless --no-body.\"\n )\n .option(\n \"--session-id <uuid>\",\n \"Claude Code session UUID — recorded as the `claude --resume` handle\"\n )\n .option(\n \"--no-body\",\n \"Deliberately write a metadata-only checkpoint (no content)\"\n )\n .option(\"--exit\", \"(pause all only) Also send /exit to each session after pausing\")\n .option(\n \"--only <name>\",\n \"(pause all only) Pause just the sessions whose name or id contains <name>.\\n\" +\n \"For rehearsing the real thing against one session before trusting it with\\n\" +\n \"every open tab — a dry run exercises enumeration but never sends anything,\\n\" +\n \"which is exactly how a broken send survived until it was needed.\"\n )\n .option(\n \"--wait <ms>\",\n \"(pause all only) Max milliseconds to wait for a session to finish its\\n\" +\n \"checkpoint before /exit. Not a fixed delay — sessions are exited as soon\\n\" +\n \"as they return to the prompt, and any still busy at the deadline are left\\n\" +\n \"open rather than killed mid-write. (default: 180000)\",\n \"180000\"\n )\n .action(async (target: string | undefined, opts: { dryRun?: boolean; exit?: boolean; wait?: string; only?: string; bodyFile?: string; sessionId?: string; body?: boolean }) => {\n if (target === \"all\") {\n await cmdPauseAll({\n exit: opts.exit,\n dryRun: opts.dryRun,\n only: opts.only,\n wait: opts.wait !== undefined ? parseInt(opts.wait, 10) : undefined,\n });\n } else {\n if (target !== undefined) {\n console.error(err(`Unknown target: ${target}. Did you mean 'pai pause all'?`));\n process.exitCode = 1;\n return;\n }\n // Commander maps `--no-body` to opts.body === false.\n cmdPause({\n dryRun: opts.dryRun,\n bodyFile: opts.bodyFile,\n sessionId: opts.sessionId,\n noBody: opts.body === false,\n });\n }\n });\n\n // pai end [--body-file <path>] [--session-id <uuid>] [--no-body] [--dry-run]\n program\n .command(\"end\")\n .description(\n \"Finalize a session: save state, mark note Completed, display safe-exit instructions.\"\n )\n .option(\"--dry-run\", \"Preview all changes without writing them\")\n .option(\n \"--body-file <path>\",\n \"File holding the checkpoint markdown to persist ('-' reads stdin). Required unless --no-body.\"\n )\n .option(\n \"--session-id <uuid>\",\n \"Claude Code session UUID — recorded as the `claude --resume` handle\"\n )\n .option(\n \"--no-body\",\n \"Deliberately write a metadata-only checkpoint (no content)\"\n )\n .action((opts: { dryRun?: boolean; bodyFile?: string; sessionId?: string; body?: boolean }) => {\n cmdEnd({\n dryRun: opts.dryRun,\n bodyFile: opts.bodyFile,\n sessionId: opts.sessionId,\n noBody: opts.body === false,\n });\n });\n\n // pai clear-names [--dry-run] — recovery: wipe corrupt iTerm/JSON name store\n program\n .command(\"clear-names\")\n .description(\n \"Recovery: wipe corrupted iTerm2 session name state.\\n\" +\n \"Clears ~/.aibroker/session-names.json AND user.paiName from all live iTerm2 sessions.\\n\" +\n \"After running this, use /Name to re-label each tab.\"\n )\n .option(\"--dry-run\", \"Preview what would be cleared without making changes\")\n .action(async (opts: { dryRun?: boolean }) => {\n await cmdClearNames(opts);\n });\n\n // -------------------------------------------------------------------------\n // HIDDEN COMPAT ALIASES\n // -------------------------------------------------------------------------\n\n // pai resume <name> [--dry-run] — hidden; prefer `pai <name>`\n program\n .command(\"resume <name>\", { hidden: true })\n .description(\"Go to a session by name or UUID (prefer: pai <name>)\")\n .option(\"--dry-run\", \"Print the exact argv and cwd, then exit without launching\")\n .action(async (name: string, opts: { dryRun?: boolean }) => {\n await cmdGoto(name, { dryRun: opts.dryRun });\n });\n\n // pai cd <identifier> — hidden; shell wrapper uses this\n program\n .command(\"cd <identifier>\", { hidden: true })\n .description(\"cd to a project directory (shell wrapper handles the actual cd)\")\n .action(async (identifier: string) => {\n const project = await resolveIdentifier(identifier);\n if (!project) {\n console.error(`Project not found: ${identifier}`);\n process.exitCode = 1;\n return;\n }\n\n if (existsSync(project.root_path)) {\n process.stdout.write(project.root_path + \"\\n\");\n return;\n }\n\n process.stderr.write(\n warn(`Path not found: ${project.root_path}\\n`) +\n dim(\" Searching for moved location...\\n\")\n );\n\n const result = findMovedPath(project.root_path);\n\n if (result.found) {\n const newPath = result.found;\n const newEncoded = encodeDir(newPath);\n const ts = now();\n const registry = await getRegistryBackend();\n await registry.updateProjectPath(project.id, { rootPath: newPath, encodedDir: newEncoded }, ts);\n process.stderr.write(\n ok(`Project moved: ${shortenPath(project.root_path, 50)}\\n`) +\n dim(` → ${newPath}\\n`) +\n ok(\"Registry updated.\\n\")\n );\n process.stdout.write(newPath + \"\\n\");\n return;\n }\n\n if (result.ambiguous) {\n process.stderr.write(\n warn(`Multiple directories named \"${basename(project.root_path)}\" found:\\n`)\n );\n for (const candidate of result.ambiguous!) {\n process.stderr.write(dim(` ${candidate}\\n`));\n }\n process.stderr.write(\n dim(`\\n Disambiguate with: pai projects rebind ${project.slug} <path>\\n`)\n );\n process.exitCode = 1;\n return;\n }\n\n process.stderr.write(\n err(\n `Project \"${project.slug}\" root_path \"${project.root_path}\" does not exist on disk\\n` +\n ` and no folder named \"${basename(project.root_path)}\" was found in scan dirs.\\n`\n ) +\n dim(` Fix with: pai projects rebind ${project.slug} <new-path>\\n`)\n );\n process.exitCode = 1;\n });\n\n // -------------------------------------------------------------------------\n // pai notes — hidden power-user alias; accessible but not in main help\n // -------------------------------------------------------------------------\n\n const notesCmd = program\n .command(\"notes [project-slug]\", { hidden: true })\n .description(\"Markdown session notes — list notes, optionally filtered to a project.\")\n .option(\"--limit <n>\", \"Maximum number of notes to show\", \"20\")\n .option(\"--status <status>\", \"Filter by status: open | completed | compacted\")\n .action(\n async (projectSlug: string | undefined, opts: { limit?: string; status?: string }) => {\n await cmdNotesList(projectSlug, opts);\n }\n );\n\n notesCmd\n .command(\"list [project-slug]\")\n .description(\"List markdown session notes (same as: pai notes)\")\n .option(\"--limit <n>\", \"Maximum number of notes to show\", \"20\")\n .option(\"--status <status>\", \"Filter by status: open | completed | compacted\")\n .action(\n async (projectSlug: string | undefined, opts: { limit?: string; status?: string }) => {\n await cmdNotesList(projectSlug, opts);\n }\n );\n\n // -------------------------------------------------------------------------\n // pai find <query> — hidden power-user alias for history search\n // -------------------------------------------------------------------------\n\n program\n .command(\"find <query>\", { hidden: true })\n .description(\"Search prompt history for matching sessions.\")\n .option(\"-n, --n <count>\", \"Maximum number of sessions to show\", \"20\")\n .option(\"--json\", \"Output as JSON array\")\n .action(async (query: string, opts: { n?: string; json?: boolean }) => {\n await cmdFind(query, opts);\n });\n\n // -------------------------------------------------------------------------\n // pai shell-init — emit shell function for eval \"$(pai shell-init)\"\n // -------------------------------------------------------------------------\n\n program\n .command(\"shell-init\")\n .description('Emit shell integration code. Add to ~/.zshrc: eval \"$(pai shell-init)\"')\n .action(() => {\n process.stdout.write(\n `# PAI shell integration — generated by: pai shell-init\npai() {\n if [[ \"$1\" == \"cd\" ]]; then\n local dir=$(command pai cd \"$2\" 2>/dev/null)\n if [[ -n \"$dir\" && -d \"$dir\" ]]; then\n builtin cd \"$dir\"\n echo \"-> $dir\"\n else\n echo \"Project not found: $2\" >&2\n return 1\n fi\n elif [[ \"$1\" == \"projects\" && \"$2\" == \"cd\" ]]; then\n local dir=$(command pai projects cd \"$3\" 2>/dev/null)\n if [[ -n \"$dir\" && -d \"$dir\" ]]; then\n builtin cd \"$dir\"\n echo \"-> $dir\"\n else\n echo \"Project not found: $3\" >&2\n return 1\n fi\n elif [[ $# -eq 0 ]]; then\n # Bare \\`pai\\` → interactive picker. fzf draws on /dev/tty; the picker writes\n # a chosen directory to PAI_PICK_OUT when you press Ctrl-G (cd only), which we\n # then cd into here (a child process can't change this shell's cwd itself).\n local __pai_f\n __pai_f=$(mktemp -t pai-pick 2>/dev/null) || { command pai; return $?; }\n PAI_PICK_OUT=\"$__pai_f\" command pai\n local __pai_rc=$?\n local __pai_dir=\"\"\n [[ -s \"$__pai_f\" ]] && __pai_dir=$(cat \"$__pai_f\")\n command rm -f \"$__pai_f\"\n if [[ -n \"$__pai_dir\" && -d \"$__pai_dir\" ]]; then\n builtin cd \"$__pai_dir\"\n echo \"-> $__pai_dir\"\n fi\n return $__pai_rc\n else\n command pai \"$@\"\n fi\n}\n\n# Print the working directory after an interactive Claude Code session exits.\n# A SessionEnd hook can't do this reliably: Claude Code does not fire SessionEnd\n# on /exit (anthropics/claude-code#17885) and may reap the hook mid-run (#41577).\n# Running it from the shell, after \\`claude\\` returns, sidesteps both.\nclaude() {\n local __pai_skip=0 __pai_arg\n for __pai_arg in \"$@\"; do\n case \"$__pai_arg\" in\n -p|--print) __pai_skip=1 ;;\n esac\n done\n command claude \"$@\"\n local __pai_rc=$?\n if [[ -t 1 && $__pai_skip -eq 0 ]]; then\n printf '\\\\n\\\\033[2m📂 Working directory:\\\\033[0m %s\\\\n\\\\033[2m cd \"%s\"\\\\033[0m\\\\n' \"$PWD\" \"$PWD\"\n fi\n return $__pai_rc\n}\n`\n );\n });\n\n // -------------------------------------------------------------------------\n // pai [query] [pick] — DEFAULT COMMAND (must be registered LAST)\n // -------------------------------------------------------------------------\n\n program\n .command(\"query [query] [pick]\", { isDefault: true, hidden: true })\n .description(\n \"Find and launch a session by topic, name, or UUID.\\n\" +\n \"No arg → deduped session listing\\n\" +\n \"Name → switch live tab, or start fresh in its directory (--resume reopens the newest transcript)\\n\" +\n \"UUID → direct resume via filesystem scan\\n\" +\n \"Topic → history search → candidate list → pick #\"\n )\n .option(\"-y, --auto\", \"Auto-pick #1 without prompting\")\n .option(\"--dry-run\", \"Print what would happen without launching\")\n .option(\"-n, --n <count>\", \"Max candidates for history search\", \"20\")\n .option(\"--all\", \"Show all entries including cold / zero-session / archived projects\")\n .option(\"--list\", \"Skip the interactive picker; print the static session listing\")\n .option(\"--resume\", \"Reopen the name's newest transcript instead of starting fresh\")\n .action(async (query: string | undefined, pick: string | undefined, opts: { auto?: boolean; dryRun?: boolean; n?: string; all?: boolean; list?: boolean; resume?: boolean }) => {\n // No query → interactive fzf picker (projects + sessions, go where you pick).\n // `--list` (or a non-TTY pipe, handled inside cmdPick) falls back to the\n // static listing. Any query still goes through the name/UUID/topic resolver.\n if (query === undefined && !opts.list) {\n await cmdPick({ all: opts.all, dryRun: opts.dryRun });\n return;\n }\n const pickN = pick !== undefined ? parseInt(pick, 10) : undefined;\n await cmdMain(query, pickN, opts);\n });\n\n // -------------------------------------------------------------------------\n // Error handling\n // -------------------------------------------------------------------------\n\n program.configureOutput({\n writeErr: (str) => process.stderr.write(err(str)),\n });\n\n // Throw (the default exitOverride behaviour) instead of calling\n // process.exit() directly. Commander has already written the help text\n // or the formatted error message via configureOutput/writeErr above by\n // the time this runs; throwing lets that CommanderError propagate up\n // through parseAsync() to the top-level catch in cli/index.ts, which\n // sets process.exitCode and returns instead of exiting immediately.\n // A natural process exit always flushes stdout/stderr, even when either\n // is a pipe — process.exit() does not (see src/cli/lib/exit.ts).\n program.exitOverride();\n\n return program;\n}\n"],"mappings":"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"}
|