{"version":3,"file":"program-BQaDYGNC.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","warn","dim","ok","cmdMigrate","warn","dim","slugify","ok","bold","ok","dim","warn","ok","dim","warn","cmdStats","bold","warn","dim","ok","dim","ok","bold","ok","dim","bold","tierColor","warn","dim","bold","warn","dim","bold","ok","dim","warn","bold","dim","ok","loadState","saveState","HOME","REGISTRY_DB","CONFIG_FILE","BACKUPS_DIR","DOCKER_CONTAINER","PG_DATABASE","PG_USER","dim","ok","warn","bold","findClaudeNotesDir","findClaudeNotesDir","dim","ok","warn","cmdStatus","bold","CONFIG_FILE","dim","bold","schedulerStatePath","ok","expandHome","cmdInstall","warn","dim","ok","cmdStatus","bold","HOME","PLIST_PATH","cmdStatus","bold","ok","dim","warn","dim","bold","ok","warn","line","prompt","getTemplatesDir","prompt","CONFIG_FILE","getTemplatesDir","getTemplatesDir","getTemplatesDir","useSymlinks","prompt","CONFIG_FILE","normalize","normalizeName","prompt","line","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","getPool","dim","CONFIG_FILE","shorten","bold","warn","ok","cmdQuery","cmdList","dim","bold","dim","tokenize","getProject","toTitleCase","getProject","warn","dim","bold","toTitleCase","ok","findNotesDir","dim","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","warn","dim","ok"],"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/registry/merge.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/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/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/cli/commands/daemon-status.ts","../src/cli/commands/daemon.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/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/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 type { Database } from \"better-sqlite3\";\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 function cmdPromote(db: Database, opts: PromoteOptions): 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 encodedDir = encodeDir(targetPath);\n  const existing = db\n    .prepare(\"SELECT id FROM projects WHERE slug = ? OR root_path = ? OR encoded_dir = ?\")\n    .get(slug, targetPath, 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  db.prepare(\n    `INSERT INTO projects\n       (slug, display_name, root_path, encoded_dir, type, status, created_at, updated_at)\n     VALUES (?, ?, ?, ?, 'local', 'active', ?, ?)`\n  ).run(slug, displayName, targetPath, encodedDir, ts, ts);\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 database helper functions for project sub-commands.\n * All functions accept a Database instance and are pure query wrappers.\n */\n\nimport type { Database } from \"better-sqlite3\";\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 function getProject(db: Database, slug: string): ProjectRow | undefined {\n  const direct = db\n    .prepare(\"SELECT * FROM projects WHERE slug = ?\")\n    .get(slug) as ProjectRow | undefined;\n  if (direct) return direct;\n\n  const alias = db\n    .prepare(\n      `SELECT p.* FROM projects p\n       JOIN aliases a ON a.project_id = p.id\n       WHERE a.alias = ?`\n    )\n    .get(slug) as ProjectRow | undefined;\n  if (alias) return alias;\n\n  const insensitive = db\n    .prepare(\"SELECT * FROM projects WHERE lower(slug) = lower(?)\")\n    .get(slug) as ProjectRow | undefined;\n  if (insensitive) return insensitive;\n\n  return db\n    .prepare(\n      `SELECT p.* FROM projects p\n       JOIN aliases a ON a.project_id = p.id\n       WHERE lower(a.alias) = lower(?)`\n    )\n    .get(slug) as ProjectRow | undefined;\n}\n\nexport function requireProject(db: Database, slug: string): ProjectRow {\n  const project = getProject(db, 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 function resolveIdentifier(db: Database, identifier: string): ProjectRow | undefined {\n  const num = parseInt(identifier, 10);\n  if (!isNaN(num) && num > 0 && String(num) === identifier) {\n    const rows = db.prepare(\n      \"SELECT * FROM projects ORDER BY status ASC, updated_at DESC\"\n    ).all() as ProjectRow[];\n    if (num <= rows.length) return rows[num - 1];\n  }\n  return getProject(db, identifier);\n}\n\nexport function getProjectTags(db: Database, projectId: number): string[] {\n  const rows = db\n    .prepare(\n      `SELECT t.name FROM tags t\n       JOIN project_tags pt ON pt.tag_id = t.id\n       WHERE pt.project_id = ?\n       ORDER BY t.name`\n    )\n    .all(projectId) as { name: string }[];\n  return rows.map((r) => r.name);\n}\n\nexport function getProjectAliases(db: Database, projectId: number): string[] {\n  const rows = db\n    .prepare(\"SELECT alias FROM aliases WHERE project_id = ? ORDER BY alias\")\n    .all(projectId) as { alias: string }[];\n  return rows.map((r) => r.alias);\n}\n\nexport function getSessionCount(db: Database, projectId: number): number {\n  const row = db\n    .prepare(\"SELECT COUNT(*) AS cnt FROM sessions WHERE project_id = ?\")\n    .get(projectId) as { cnt: number };\n  return row.cnt;\n}\n\nexport function getLastSessionDate(db: Database, projectId: number): number | null {\n  const row = db\n    .prepare(\n      `SELECT created_at FROM sessions WHERE project_id = ?\n       ORDER BY created_at DESC LIMIT 1`\n    )\n    .get(projectId) as { created_at: number } | undefined;\n  return row ? row.created_at : null;\n}\n\nexport function upsertTag(db: Database, tagName: string): number {\n  db.prepare(\"INSERT OR IGNORE INTO tags (name) VALUES (?)\").run(tagName);\n  const row = db.prepare(\"SELECT id FROM tags WHERE name = ?\").get(tagName) as { id: number };\n  return row.id;\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 type { Database } from \"better-sqlite3\";\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 {\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 function cmdAdd(\n  db: Database,\n  rawPath: string,\n  opts: { slug?: string; type?: string; displayName?: string }\n): void {\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 = db\n    .prepare(\"SELECT id FROM projects WHERE slug = ? OR root_path = ?\")\n    .get(slug, 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 similar = db\n    .prepare(\n      `SELECT slug, root_path FROM projects WHERE status = 'active' AND slug != ?`\n    )\n    .all(slug) as { slug: string; root_path: string }[];\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  db.prepare(\n    `INSERT INTO projects\n       (slug, display_name, root_path, encoded_dir, type, status, created_at, updated_at)\n     VALUES (?, ?, ?, ?, ?, 'active', ?, ?)`\n  ).run(slug, displayName, rootPath, encodedDir, type, ts, ts);\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 function cmdList(\n  db: Database,\n  opts: { status?: string; tag?: string; type?: string; all?: boolean }\n): void {\n  let query = `\n    SELECT p.*,\n      (SELECT COUNT(*) FROM sessions s WHERE s.project_id = p.id) AS session_count,\n      (SELECT MAX(s.created_at) FROM sessions s WHERE s.project_id = p.id) AS last_active\n    FROM projects p\n  `;\n  const params: unknown[] = [];\n  const where: string[] = [];\n\n  // Default: active only, unless --all or explicit --status given\n  if (opts.status) {\n    where.push(\"p.status = ?\");\n    params.push(opts.status);\n  } else if (!opts.all) {\n    where.push(\"p.status = 'active'\");\n  }\n  if (opts.type) {\n    where.push(\"p.type = ?\");\n    params.push(opts.type);\n  }\n  if (opts.tag) {\n    where.push(`p.id IN (\n      SELECT pt.project_id FROM project_tags pt\n      JOIN tags t ON t.id = pt.tag_id WHERE t.name = ?\n    )`);\n    params.push(opts.tag);\n  }\n\n  if (where.length) query += \" WHERE \" + where.join(\" AND \");\n  query += \" ORDER BY p.status ASC, p.updated_at DESC\";\n\n  const rows = db.prepare(query).all(...params) as (ProjectRow & {\n    session_count: number;\n    last_active: number | null;\n  })[];\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 totalRow = db.prepare(\"SELECT COUNT(*) AS cnt FROM projects\").get() as { cnt: number };\n  const hiddenCount = totalRow.cnt - 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 function cmdInfo(db: Database, identifier: string): void {\n  const project =\n    resolveIdentifier(db, identifier) ?? requireProject(db, identifier);\n  const tags = getProjectTags(db, project.id);\n  const aliases = getProjectAliases(db, project.id);\n  const sessionCount = getSessionCount(db, project.id);\n  const lastSession = getLastSessionDate(db, project.id);\n\n  const recentSessions = db\n    .prepare(\n      `SELECT * FROM sessions WHERE project_id = ?\n       ORDER BY created_at DESC LIMIT 5`\n    )\n    .all(project.id) as SessionRow[];\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 function cmdArchive(db: Database, slug: string): void {\n  const project = requireProject(db, slug);\n  if (project.status === \"archived\") {\n    console.log(warn(`Project ${slug} is already archived.`));\n    return;\n  }\n  const ts = now();\n  db.prepare(\n    \"UPDATE projects SET status = 'archived', archived_at = ?, updated_at = ? WHERE id = ?\"\n  ).run(ts, ts, project.id);\n  console.log(ok(`Archived: ${bold(slug)}`));\n}\n\nexport function cmdUnarchive(db: Database, slug: string): void {\n  const project = requireProject(db, 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  db.prepare(\n    \"UPDATE projects SET status = 'active', archived_at = NULL, updated_at = ? WHERE id = ?\"\n  ).run(ts, project.id);\n  console.log(ok(`Unarchived: ${bold(slug)}`));\n}\n\nexport function cmdMove(db: Database, slug: string, newPath: string): void {\n  const project = requireProject(db, slug);\n  const resolvedNew = resolvePath(newPath);\n  const newEncoded = encodeDir(resolvedNew);\n  const ts = now();\n\n  db.prepare(\n    \"UPDATE projects SET root_path = ?, encoded_dir = ?, updated_at = ? WHERE id = ?\"\n  ).run(resolvedNew, newEncoded, ts, project.id);\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 function cmdTag(\n  db: Database,\n  slug: string,\n  tags: string[]\n): void {\n  const project = requireProject(db, slug);\n  const added: string[] = [];\n  const skipped: string[] = [];\n\n  for (const tagName of tags) {\n    const tagId = upsertTag(db, tagName);\n    const exists = db\n      .prepare(\n        \"SELECT 1 FROM project_tags WHERE project_id = ? AND tag_id = ?\"\n      )\n      .get(project.id, tagId);\n    if (exists) {\n      skipped.push(tagName);\n    } else {\n      db.prepare(\n        \"INSERT INTO project_tags (project_id, tag_id) VALUES (?, ?)\"\n      ).run(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 function cmdAlias(\n  db: Database,\n  slug: string,\n  alias: string\n): void {\n  requireProject(db, slug);\n\n  const conflict = db\n    .prepare(\"SELECT id FROM projects WHERE slug = ?\")\n    .get(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 = getProject(db, slug)!;\n  try {\n    db.prepare(\n      \"INSERT INTO aliases (alias, project_id) VALUES (?, ?)\"\n    ).run(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 function cmdEdit(\n  db: Database,\n  slug: string,\n  opts: { displayName?: string; type?: string }\n): void {\n  const project = requireProject(db, 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  if (opts.displayName) {\n    db.prepare(\n      \"UPDATE projects SET display_name = ?, updated_at = ? WHERE id = ?\"\n    ).run(opts.displayName, ts, project.id);\n    console.log(ok(`Display name updated: ${bold(opts.displayName)}`));\n  }\n  if (opts.type) {\n    db.prepare(\n      \"UPDATE projects SET type = ?, updated_at = ? WHERE id = ?\"\n    ).run(opts.type, ts, project.id);\n    console.log(ok(`Type updated: ${bold(opts.type)}`));\n  }\n}\n\nexport function cmdDetect(\n  db: Database,\n  pathArg: string | undefined,\n  opts: { json?: boolean }\n): void {\n  const cwd = pathArg ? resolvePath(pathArg) : process.cwd();\n  const detection = detectProject(db, 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 function cmdConsolidate(\n  db: Database,\n  identifier: string,\n  opts: { yes?: boolean; dryRun?: boolean }\n): void {\n  const project =\n    resolveIdentifier(db, identifier) ?? requireProject(db, 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 */\nfunction tryRecoverMovedProject(db: Database, project: ProjectRow): 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    db.prepare(\n      \"UPDATE projects SET root_path = ?, encoded_dir = ?, updated_at = ? WHERE id = ?\"\n    ).run(newPath, newEncoded, ts, project.id);\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 function cmdGo(db: Database, query: string): void {\n  const all = db\n    .prepare(\n      \"SELECT * FROM projects WHERE status = 'active' ORDER BY updated_at DESC\"\n    )\n    .all() as ProjectRow[];\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 = getProject(db, query);\n  if (exact) {\n    if (!existsSync(exact.root_path)) {\n      const recovered = tryRecoverMovedProject(db, 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 = tryRecoverMovedProject(db, 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 function cmdRebind(\n  db: Database,\n  slug: string,\n  newPath: string\n): void {\n  const project = requireProject(db, 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\n  // Check if another project already owns this path\n  const conflict = db\n    .prepare(\"SELECT slug FROM projects WHERE encoded_dir = ? AND id != ?\")\n    .get(newEncoded, project.id) as { slug: string } | undefined;\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  db.prepare(\n    \"UPDATE projects SET root_path = ?, encoded_dir = ?, updated_at = ? WHERE id = ?\"\n  ).run(resolved, newEncoded, ts, project.id);\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 type { Database } from \"better-sqlite3\";\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\";\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()). */\nfunction currentProject(db: Database, cwd?: string): ProjectRow | undefined {\n  const detected = detectProject(db, cwd);\n  if (!detected) return undefined;\n  return getProject(db, detected.slug);\n}\n\nfunction saveSessionConfig(db: Database, project: ProjectRow, config: SessionConfig): void {\n  db.prepare(\"UPDATE projects SET session_config = ?, updated_at = ? WHERE id = ?\")\n    .run(JSON.stringify(config), now(), project.id);\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 function cmdName(\n  db: Database,\n  identifier: string,\n  shortname: string,\n  opts: { permission?: string }\n): void {\n  const project = resolveIdentifier(db, identifier) ?? requireProject(db, identifier);\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 = db\n    .prepare(\"SELECT id FROM projects WHERE slug = ? AND id != ?\")\n    .get(shortname, project.id) as { id: number } | undefined;\n  if (conflictProject) {\n    console.error(err(`\"${shortname}\" is already a project slug.`));\n    process.exitCode = 1;\n    return;\n  }\n\n  const conflictAlias = db\n    .prepare(\"SELECT project_id FROM aliases WHERE alias = ?\")\n    .get(shortname) as { project_id: number } | undefined;\n  if (conflictAlias && conflictAlias.project_id !== project.id) {\n    console.error(err(`\"${shortname}\" is already used by another project.`));\n    process.exitCode = 1;\n    return;\n  }\n\n  if (!conflictAlias) {\n    db.prepare(\"INSERT INTO aliases (alias, project_id) VALUES (?, ?)\").run(shortname, project.id);\n  }\n\n  if (opts.permission) {\n    const existing = getSessionConfig(project);\n    const config = applyPreset(existing, opts.permission);\n    db.prepare(\"UPDATE projects SET session_config = ?, updated_at = ? WHERE id = ?\")\n      .run(JSON.stringify(config), now(), project.id);\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 function cmdUnname(db: Database, shortname: string): void {\n  const alias = db\n    .prepare(\"SELECT project_id FROM aliases WHERE alias = ?\")\n    .get(shortname) as { project_id: number } | undefined;\n\n  if (!alias) {\n    console.error(err(`No named project found: \"${shortname}\"`));\n    process.exitCode = 1;\n    return;\n  }\n\n  db.prepare(\"DELETE FROM aliases WHERE alias = ?\").run(shortname);\n\n  const remaining = db\n    .prepare(\"SELECT COUNT(*) AS cnt FROM aliases WHERE project_id = ?\")\n    .get(alias.project_id) as { cnt: number };\n\n  console.log(ok(`Removed name: ${bold(shortname)}`));\n  if (remaining.cnt === 0) {\n    console.log(dim(\"  Project has no remaining names.\"));\n  }\n}\n\nexport function cmdNames(db: Database, opts: { json?: boolean; all?: boolean }): void {\n  // --all: LEFT JOIN so projects WITHOUT a curated alias are included too (for\n  //         AIBroker's picker to search everything). Unnamed projects get their\n  //         slug as the launch key (pai project config resolves by slug).\n  const rows = db.prepare(`\n    SELECT p.*, a.alias AS name,\n      (SELECT COUNT(*) FROM sessions s WHERE s.project_id = p.id) AS session_count,\n      (SELECT MAX(s.created_at) FROM sessions s WHERE s.project_id = p.id) AS last_active\n    FROM projects p\n    ${opts.all ? \"LEFT JOIN\" : \"JOIN\"} aliases a ON a.project_id = p.id\n    WHERE p.status = 'active'\n    ORDER BY p.updated_at DESC\n  `).all() as (ProjectRow & { name: string | null; session_count: number; last_active: number | null; session_config: string | null })[];\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 function cmdConfig(\n  db: Database,\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): 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 = resolveIdentifier(db, identifier) ?? requireProject(db, identifier);\n  let config = getSessionConfig(project);\n\n  if (opts.reset) {\n    db.prepare(\"UPDATE projects SET session_config = NULL, updated_at = ? WHERE id = ?\").run(now(), project.id);\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    db.prepare(\"UPDATE projects SET session_config = ?, updated_at = ? WHERE id = ?\")\n      .run(JSON.stringify(config), now(), project.id);\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    db.prepare(\"UPDATE projects SET session_config = ?, updated_at = ? WHERE id = ?\")\n      .run(JSON.stringify(config), now(), project.id);\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    db.prepare(\"UPDATE projects SET session_config = ?, updated_at = ? WHERE id = ?\")\n      .run(JSON.stringify(config), now(), project.id);\n    console.log(ok(`Config updated for ${bold(project.slug)}`));\n  }\n\n  const effective = { ...getGlobalDefaults(), ...config };\n  const aliases = getProjectAliases(db, 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 [names...]` — 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 function cmdMcp(\n  db: Database,\n  names: string[],\n  opts: { clear?: boolean; cwd?: string } = {}\n): void {\n  const project = currentProject(db, 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    saveSessionConfig(db, project, config);\n    console.log(ok(`MCP servers unset for ${bold(project.slug)} — all servers load again.`));\n    return;\n  }\n\n  if (!names.length) {\n    console.log(config.mcp?.length ? config.mcp.join(\",\") : \"unset (all)\");\n    return;\n  }\n\n  const { workers } = readWorkersSection();\n  config.mcp = expandMcpNames(splitNames(names), workers); // unknown names fail fast\n  saveSessionConfig(db, project, config);\n  console.log(ok(`MCP servers set for ${bold(project.slug)}: ${config.mcp.join(\", \")}`));\n}\n\n/**\n * `pai project tools [names...]` — 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 function cmdTools(\n  db: Database,\n  names: string[],\n  opts: { clear?: boolean; cwd?: string } = {}\n): void {\n  const project = currentProject(db, 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    saveSessionConfig(db, project, config);\n    console.log(ok(`Tools unset for ${bold(project.slug)} — all built-in tools load again.`));\n    return;\n  }\n\n  if (!names.length) {\n    console.log(config.tools?.length ? config.tools.join(\",\") : \"unset (all)\");\n    return;\n  }\n\n  config.tools = splitNames(names);\n  saveSessionConfig(db, 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 type { Database } from \"better-sqlite3\";\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\";\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 function cmdHealth(\n  db: Database,\n  opts: { fix?: boolean; json?: boolean; status?: string }\n): void {\n  const rows = db\n    .prepare(\n      `SELECT p.*,\n         (SELECT COUNT(*) FROM sessions s WHERE s.project_id = p.id) AS session_count\n       FROM projects p\n       ORDER BY p.status ASC, p.updated_at DESC`\n    )\n    .all() as HealthRow[];\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        db.prepare(\"UPDATE projects SET root_path = ?, encoded_dir = ?, updated_at = ? WHERE id = ?\")\n          .run(r.suggestedPath, newEncoded, ts, r.project.id);\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        db.prepare(\"UPDATE projects SET status = 'archived', archived_at = ?, updated_at = ? WHERE id = ?\")\n          .run(now(), now(), r.project.id);\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 * merge.ts — fold one registered project into another without losing anything.\n *\n * The registry accumulates rows that describe the same work twice: a project\n * registered under two spellings of one directory, a vault-notes twin of a code\n * project, a slug that collided and got a `-1`. Measured 2026-08-04 there were\n * six such rows, and between them they carried ten sessions.\n *\n * Those sessions are the only thing of value on the losing row, which is why\n * `archive` is the wrong remedy and a plain DELETE is worse. `pai project health`\n * suggested archiving for every unhealthy row, and archiving a duplicate leaves\n * the sessions attached to a row nobody will look at again.\n *\n * FIVE tables reference a project, and `PRAGMA foreign_keys` is 0 on this\n * database — so deleting a project row does not fail, it silently orphans rows in\n * sessions, project_tags, aliases, compaction_log and links. Anything that merges\n * by hand will miss at least one of them. Hence one function.\n *\n * Two constraints shape the implementation:\n *\n *   sessions UNIQUE (project_id, number)  — incoming sessions must be renumbered,\n *                                           or the insert collides with the\n *                                           target's own numbering\n *   links    UNIQUE (session_id, target_project_id)\n *   project_tags PRIMARY KEY (project_id, tag_id)\n *                                         — both can already hold the row we are\n *                                           about to point at the target\n */\n\nimport type { Database } from \"better-sqlite3\";\n\nexport interface MergePlan {\n  fromId: number;\n  fromSlug: string;\n  intoId: number;\n  intoSlug: string;\n  /** Sessions to move, with the numbers they will be given. */\n  sessions: { id: number; from: number; to: number }[];\n  tags: number;\n  aliases: number;\n  compactions: number;\n  links: number;\n  /** The losing slug is kept as an alias, so `pai <old-name>` still resolves. */\n  aliasToAdd?: string;\n}\n\nexport class MergeError extends Error {}\n\n/**\n * What a merge would do, without doing it.\n *\n * Built as a plan first because the session renumbering is the part a reader will\n * not predict, and printing \"0001 -> 0016\" is the difference between a command\n * that can be trusted and one that has to be taken on faith.\n */\nexport function planMerge(db: Database, fromSlug: string, intoSlug: string): MergePlan {\n  if (fromSlug === intoSlug) {\n    throw new MergeError(`Cannot merge ${fromSlug} into itself.`);\n  }\n\n  const from = db\n    .prepare(\"SELECT id, slug FROM projects WHERE slug = ?\")\n    .get(fromSlug) as { id: number; slug: string } | undefined;\n  const into = db\n    .prepare(\"SELECT id, slug FROM projects WHERE slug = ?\")\n    .get(intoSlug) as { id: number; slug: string } | undefined;\n\n  if (!from) throw new MergeError(`No project with slug \"${fromSlug}\".`);\n  if (!into) throw new MergeError(`No project with slug \"${intoSlug}\".`);\n\n  // Where the target's numbering currently ends. Incoming sessions continue from\n  // there rather than keeping their own numbers, which would collide.\n  const maxRow = db\n    .prepare(\"SELECT COALESCE(MAX(number), 0) AS n FROM sessions WHERE project_id = ?\")\n    .get(into.id) as { n: number };\n  let next = maxRow.n;\n\n  const incoming = db\n    .prepare(\"SELECT id, number FROM sessions WHERE project_id = ? ORDER BY number ASC\")\n    .all(from.id) as { id: number; number: number }[];\n\n  const sessions = incoming.map((s) => ({ id: s.id, from: s.number, to: ++next }));\n\n  const count = (sql: string, ...args: unknown[]): number =>\n    (db.prepare(sql).get(...args) as { n: number }).n;\n\n  // The losing slug becomes an alias of the winner, so anything that resolved by\n  // that name keeps working — including a human who remembers the old name.\n  const aliasTaken =\n    count(\"SELECT COUNT(*) AS n FROM aliases WHERE alias = ?\", from.slug) > 0 ||\n    count(\"SELECT COUNT(*) AS n FROM projects WHERE slug = ?\", from.slug) > 1;\n\n  return {\n    fromId: from.id,\n    fromSlug: from.slug,\n    intoId: into.id,\n    intoSlug: into.slug,\n    sessions,\n    tags: count(\"SELECT COUNT(*) AS n FROM project_tags WHERE project_id = ?\", from.id),\n    aliases: count(\"SELECT COUNT(*) AS n FROM aliases WHERE project_id = ?\", from.id),\n    compactions: count(\n      \"SELECT COUNT(*) AS n FROM compaction_log WHERE project_id = ?\",\n      from.id\n    ),\n    links: count(\"SELECT COUNT(*) AS n FROM links WHERE target_project_id = ?\", from.id),\n    aliasToAdd: aliasTaken ? undefined : from.slug,\n  };\n}\n\n/**\n * Apply a plan. One transaction: either the whole row is folded in or nothing is.\n *\n * A half-merged project is the worst outcome available here — sessions moved but\n * the row still present, or the row gone and its tags orphaned — and with foreign\n * keys off nothing would complain.\n */\nexport function applyMerge(db: Database, plan: MergePlan): void {\n  const run = db.transaction(() => {\n    // Sessions, one at a time because each gets its own new number.\n    const move = db.prepare(\n      \"UPDATE sessions SET project_id = ?, number = ? WHERE id = ?\"\n    );\n    for (const s of plan.sessions) move.run(plan.intoId, s.to, s.id);\n\n    // Tags: OR IGNORE because (project_id, tag_id) is the primary key and the\n    // target may already carry the same tag.\n    db.prepare(\n      `INSERT OR IGNORE INTO project_tags (project_id, tag_id)\n       SELECT ?, tag_id FROM project_tags WHERE project_id = ?`\n    ).run(plan.intoId, plan.fromId);\n    db.prepare(\"DELETE FROM project_tags WHERE project_id = ?\").run(plan.fromId);\n\n    // Existing aliases of the loser now point at the winner.\n    db.prepare(\"UPDATE aliases SET project_id = ? WHERE project_id = ?\").run(\n      plan.intoId,\n      plan.fromId\n    );\n\n    db.prepare(\"UPDATE compaction_log SET project_id = ? WHERE project_id = ?\").run(\n      plan.intoId,\n      plan.fromId\n    );\n\n    // Links: same collision shape as tags, plus one extra case — a session that\n    // linked to the loser may now be linking to its own project, which is not a\n    // relationship worth keeping.\n    db.prepare(\n      `UPDATE OR IGNORE links SET target_project_id = ? WHERE target_project_id = ?`\n    ).run(plan.intoId, plan.fromId);\n    db.prepare(\"DELETE FROM links WHERE target_project_id = ?\").run(plan.fromId);\n    db.prepare(\n      `DELETE FROM links WHERE target_project_id = ?\n         AND session_id IN (SELECT id FROM sessions WHERE project_id = ?)`\n    ).run(plan.intoId, plan.intoId);\n\n    // And keep the old name resolvable.\n    if (plan.aliasToAdd) {\n      db.prepare(\"INSERT OR IGNORE INTO aliases (alias, project_id) VALUES (?, ?)\").run(\n        plan.aliasToAdd,\n        plan.intoId\n      );\n    }\n\n    db.prepare(\"DELETE FROM projects WHERE id = ?\").run(plan.fromId);\n  });\n\n  run();\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 type { Database } from \"better-sqlite3\";\nimport { ok, warn, err, dim, bold, shortenPath } from \"../../utils.js\";\nimport { planMerge, applyMerge, MergeError } from \"../../../registry/merge.js\";\n\nexport function cmdMerge(\n  db: Database,\n  fromSlug: string,\n  intoSlug: string,\n  opts: { execute?: boolean } = {}\n): void {\n  let plan;\n  try {\n    plan = planMerge(db, 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 = (id: number): string => {\n    const row = db.prepare(\"SELECT root_path FROM projects WHERE id = ?\").get(id) as\n      | { root_path: string }\n      | undefined;\n    return row ? shortenPath(row.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)} ${path(plan.fromId)}`));\n  console.log(dim(`    into  ${plan.intoSlug.padEnd(22)} ${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  applyMerge(db, 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 type { Database } from \"better-sqlite3\";\nimport { realpathSync } from \"node:fs\";\nimport { ok, err, dim, bold, encodeDir, now } from \"../../utils.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 function findProjectsByName(db: Database, name: string): ProjectRow[] {\n  const rows = db\n    .prepare(\"SELECT id, slug, display_name, root_path, status FROM projects\")\n    .all() as ProjectRow[];\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 function cmdHere(\n  db: Database,\n  name: string,\n  opts: { cwd?: string; dryRun?: boolean } = {}\n): 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 encoded = encodeDir(target);\n  const matches = findProjectsByName(db, 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 = db\n    .prepare(\"SELECT id, slug, display_name FROM projects WHERE encoded_dir = ?\")\n    .get(encoded) as Pick<ProjectRow, \"id\" | \"slug\" | \"display_name\"> | 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    db.prepare(\n      \"UPDATE projects SET root_path = ?, encoded_dir = ?, updated_at = ? WHERE id = ?\"\n    ).run(target, encoded, now(), p.id);\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 = db.prepare(\"SELECT 1 FROM projects WHERE slug = ?\").get(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  db.prepare(\n    `INSERT INTO projects (slug, display_name, root_path, encoded_dir, type, status, created_at, updated_at)\n     VALUES (?, ?, ?, ?, 'local', 'active', ?, ?)`\n  ).run(finalSlug, name, target, encoded, ts, ts);\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 type { Database } from \"better-sqlite3\";\nimport { ok, warn, err, dim, bold } from \"../../utils.js\";\n\nexport function cmdUnregister(\n  db: Database,\n  slug: string,\n  opts: { execute?: boolean; force?: boolean } = {}\n): void {\n  const row = db\n    .prepare(\n      `SELECT p.id, p.slug, p.root_path, p.status,\n              (SELECT COUNT(*) FROM sessions s WHERE s.project_id = p.id) AS session_count\n       FROM projects p WHERE p.slug = ?`\n    )\n    .get(slug) as\n    | { id: number; slug: string; root_path: string; status: string; session_count: number }\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  const run = db.transaction(() => {\n    db.prepare(\"DELETE FROM links WHERE target_project_id = ?\").run(row.id);\n    db.prepare(\n      \"DELETE FROM links WHERE session_id IN (SELECT id FROM sessions WHERE project_id = ?)\"\n    ).run(row.id);\n    db.prepare(\"DELETE FROM compaction_log WHERE project_id = ?\").run(row.id);\n    db.prepare(\"DELETE FROM project_tags WHERE project_id = ?\").run(row.id);\n    db.prepare(\"DELETE FROM aliases WHERE project_id = ?\").run(row.id);\n    db.prepare(\"DELETE FROM sessions WHERE project_id = ?\").run(row.id);\n    db.prepare(\"DELETE FROM projects WHERE id = ?\").run(row.id);\n  });\n  run();\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 type { Database } from \"better-sqlite3\";\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\";\n\nexport { cmdGo };\n\nexport function registerProjectsCommands(\n  projectsCmd: Command,\n  getDb: () => Database\n): 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(getDb(), 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((opts: { all?: boolean; status?: string; tag?: string; type?: string }) => {\n      cmdList(getDb(), 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((identifier: string) => {\n      const db = getDb();\n      const project = resolveIdentifier(db, 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        db.prepare(\n          \"UPDATE projects SET root_path = ?, encoded_dir = ?, updated_at = ? WHERE id = ?\"\n        ).run(newPath, newEncoded, ts, project.id);\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      (\n        rawPath: string,\n        opts: { slug?: string; type?: string; displayName?: string }\n      ) => {\n        cmdAdd(getDb(), 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((slug: string) => {\n      cmdInfo(getDb(), slug);\n    });\n\n  // pai projects archive <slug>\n  projectsCmd\n    .command(\"archive <slug>\")\n    .description(\"Archive a project\")\n    .action((slug: string) => {\n      cmdArchive(getDb(), 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((from: string, into: string, opts: { execute?: boolean }) => {\n      cmdMerge(getDb(), 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((slug: string, opts: { execute?: boolean; force?: boolean }) => {\n      cmdUnregister(getDb(), 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((slug: string) => {\n      cmdUnarchive(getDb(), 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((slug: string, newPath: string) => {\n      cmdMove(getDb(), 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((slug: string, newPath: string) => {\n      cmdRebind(getDb(), 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((name: string, opts: { dryRun?: boolean }) => {\n      cmdHere(getDb(), 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((slug: string, tags: string[]) => {\n      cmdTag(getDb(), 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((slug: string, alias: string) => {\n      cmdAlias(getDb(), 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      (slug: string, opts: { displayName?: string; type?: string }) => {\n        cmdEdit(getDb(), 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((pathArg: string | undefined, opts: { json?: boolean }) => {\n      cmdDetect(getDb(), 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((opts: { fix?: boolean; json?: boolean; status?: string }) => {\n      cmdHealth(getDb(), 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      (identifier: string, opts: { yes?: boolean; dryRun?: boolean }) => {\n        cmdConsolidate(getDb(), 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((opts: { fromSession: string; to: string; name?: string }) => {\n      cmdPromote(getDb(), 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((query: string) => {\n      cmdGo(getDb(), 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      (\n        identifier: string,\n        shortname: string,\n        opts: { permission?: string }\n      ) => {\n        cmdName(getDb(), 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((shortname: string) => {\n      cmdUnname(getDb(), 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((opts: { json?: boolean; all?: boolean }) => {\n      cmdNames(getDb(), 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      (\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        cmdConfig(getDb(), 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 names: print the current value. Names (comma-separated or repeated\\n\" +\n        \"args): set it, validated against ~/.claude.json's mcpServers.\\n\" +\n        \"--clear: unset (all servers load — today's default).\"\n    )\n    .option(\"--clear\", \"Unset — all MCP servers load again\")\n    .action((names: string[], opts: { clear?: boolean }) => {\n      cmdMcp(getDb(), names, opts);\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 names: print the current value. Names (comma-separated or\\n\" +\n        \"repeated args): set it. --clear: unset (all tools load — today's default).\"\n    )\n    .option(\"--clear\", \"Unset — all built-in tools load again\")\n    .action((names: string[], opts: { clear?: boolean }) => {\n      cmdTools(getDb(), 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 type { Database } from \"better-sqlite3\";\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 function cmdMigrate(db: Database): 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  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 } = upsertProject(db, 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 = upsertSession(db, 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 type { Database } from \"better-sqlite3\";\nimport { realpathSync, copyFileSync, existsSync } from \"node:fs\";\nimport { ok, warn, err, dim, bold } from \"../../utils.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\nfunction countRefs(db: Database, projectId: number): Counts {\n  const one = (sql: string): number =>\n    (db.prepare(sql).get(projectId) as { n: number }).n;\n\n  return {\n    sessions: one(\"SELECT COUNT(*) AS n FROM sessions WHERE project_id = ?\"),\n    compaction: one(\"SELECT COUNT(*) AS n FROM compaction_log WHERE project_id = ?\"),\n    aliases: one(\"SELECT COUNT(*) AS n FROM aliases WHERE project_id = ?\"),\n    links: one(\"SELECT COUNT(*) AS n FROM links WHERE target_project_id = ?\"),\n    tags: one(\"SELECT COUNT(*) AS n FROM project_tags WHERE project_id = ?\"),\n    children: one(\"SELECT COUNT(*) AS n FROM projects WHERE parent_id = ?\"),\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 */\nfunction pickCanonical(rows: ProjectRow[], realPath: string, db: Database): ProjectRow {\n  const exact = rows.filter((r) => r.root_path === realPath);\n  const pool = exact.length > 0 ? exact : rows;\n\n  return [...pool].sort((a, b) => {\n    const sa = countRefs(db, a.id).sessions;\n    const sb = countRefs(db, b.id).sessions;\n    if (sa !== sb) return sb - sa;\n    return a.created_at - b.created_at;\n  })[0];\n}\n\nexport function analyzeDuplicates(db: Database): {\n  groups: MergeGroup[];\n  stalePaths: ProjectRow[];\n} {\n  const rows = db\n    .prepare(\n      `SELECT id, slug, display_name, root_path, encoded_dir, status,\n              claude_notes_dir, session_config, created_at\n         FROM projects ORDER BY id`\n    )\n    .all() as ProjectRow[];\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 = pickCanonical(members, realPath, db);\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 */\nfunction foldSession(db: Database, keepId: number, dropId: number): void {\n  // session_tags PK is (session_id, tag_id) — skip tags the survivor has.\n  const tags = db\n    .prepare(\"SELECT tag_id FROM session_tags WHERE session_id = ?\")\n    .all(dropId) as Array<{ tag_id: number }>;\n  for (const t of tags) {\n    const clash = db\n      .prepare(\n        \"SELECT 1 FROM session_tags WHERE session_id = ? AND tag_id = ? LIMIT 1\"\n      )\n      .get(keepId, t.tag_id);\n    if (clash) {\n      db.prepare(\n        \"DELETE FROM session_tags WHERE session_id = ? AND tag_id = ?\"\n      ).run(dropId, t.tag_id);\n    } else {\n      db.prepare(\n        \"UPDATE session_tags SET session_id = ? WHERE session_id = ? AND tag_id = ?\"\n      ).run(keepId, dropId, t.tag_id);\n    }\n  }\n\n  // links UNIQUE(session_id, target_project_id)\n  const links = db\n    .prepare(\"SELECT id, target_project_id FROM links WHERE session_id = ?\")\n    .all(dropId) as Array<{ id: number; target_project_id: number }>;\n  for (const l of links) {\n    const clash = db\n      .prepare(\n        \"SELECT 1 FROM links WHERE session_id = ? AND target_project_id = ? LIMIT 1\"\n      )\n      .get(keepId, l.target_project_id);\n    if (clash) db.prepare(\"DELETE FROM links WHERE id = ?\").run(l.id);\n    else\n      db.prepare(\"UPDATE links SET session_id = ? WHERE id = ?\").run(\n        keepId,\n        l.id\n      );\n  }\n\n  db.prepare(\"UPDATE compaction_log SET session_id = ? WHERE session_id = ?\").run(\n    keepId,\n    dropId\n  );\n\n  // Backfill anything the survivor lacks, and prefer a finished status.\n  db.prepare(\n    `UPDATE sessions SET\n       claude_session_id = COALESCE(claude_session_id,\n                                    (SELECT claude_session_id FROM sessions WHERE id = ?)),\n       token_count       = COALESCE(token_count,\n                                    (SELECT token_count FROM sessions WHERE id = ?)),\n       closed_at         = COALESCE(closed_at,\n                                    (SELECT closed_at FROM sessions WHERE id = ?)),\n       status            = CASE WHEN status = 'open'\n                                 AND (SELECT status FROM sessions WHERE id = ?) != 'open'\n                                THEN (SELECT status FROM sessions WHERE id = ?)\n                                ELSE status END\n     WHERE id = ?`\n  ).run(dropId, dropId, dropId, dropId, dropId, keepId);\n\n  db.prepare(\"DELETE FROM sessions WHERE id = ?\").run(dropId);\n}\n\nfunction mergeGroup(db: Database, group: MergeGroup): 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 =\n    ((\n      db\n        .prepare(\"SELECT MAX(number) AS n FROM sessions WHERE project_id = ?\")\n        .get(canonicalId) as { n: number | null }\n    ).n ?? 0) + 1;\n\n  for (const dup of group.duplicates) {\n    const sessions = db\n      .prepare(\n        \"SELECT id, number, title, filename FROM sessions WHERE project_id = ? ORDER BY number\"\n      )\n      .all(dup.id) as Array<{\n      id: number;\n      number: number;\n      title: string;\n      filename: string;\n    }>;\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 = db\n        .prepare(\n          \"SELECT id FROM sessions WHERE project_id = ? AND filename = ? LIMIT 1\"\n        )\n        .get(canonicalId, s.filename) as { id: number } | undefined;\n\n      if (twin) {\n        foldSession(db, twin.id, s.id);\n        report.foldedSessions++;\n        continue;\n      }\n\n      const clash = db\n        .prepare(\n          \"SELECT 1 FROM sessions WHERE project_id = ? AND number = ? LIMIT 1\"\n        )\n        .get(canonicalId, s.number);\n\n      if (clash) {\n        const assigned = nextNumber++;\n        db.prepare(\n          \"UPDATE sessions SET project_id = ?, number = ? WHERE id = ?\"\n        ).run(canonicalId, assigned, s.id);\n        report.renumbered.push({\n          from: s.number,\n          to: assigned,\n          title: s.title,\n        });\n      } else {\n        db.prepare(\"UPDATE sessions SET project_id = ? WHERE id = ?\").run(\n          canonicalId,\n          s.id\n        );\n        if (s.number >= nextNumber) nextNumber = s.number + 1;\n      }\n      report.movedSessions++;\n    }\n\n    report.movedCompaction += db\n      .prepare(\"UPDATE compaction_log SET project_id = ? WHERE project_id = ?\")\n      .run(canonicalId, dup.id).changes;\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 = db\n      .prepare(\"SELECT alias FROM aliases WHERE project_id = ?\")\n      .all(dup.id) as Array<{ alias: string }>;\n    for (const a of dupAliases) {\n      const existing = db\n        .prepare(\"SELECT project_id FROM aliases WHERE alias = ?\")\n        .get(a.alias) as { project_id: number } | undefined;\n      if (existing && existing.project_id === canonicalId) continue;\n      db.prepare(\"UPDATE aliases SET project_id = ? WHERE alias = ?\").run(\n        canonicalId,\n        a.alias\n      );\n      report.movedAliases++;\n    }\n\n    // links has UNIQUE(session_id, target_project_id).\n    const dupLinks = db\n      .prepare(\"SELECT id, session_id FROM links WHERE target_project_id = ?\")\n      .all(dup.id) as Array<{ id: number; session_id: number }>;\n    for (const l of dupLinks) {\n      const clash = db\n        .prepare(\n          \"SELECT 1 FROM links WHERE session_id = ? AND target_project_id = ? LIMIT 1\"\n        )\n        .get(l.session_id, canonicalId);\n      if (clash) {\n        db.prepare(\"DELETE FROM links WHERE id = ?\").run(l.id);\n        continue;\n      }\n      db.prepare(\"UPDATE links SET target_project_id = ? WHERE id = ?\").run(\n        canonicalId,\n        l.id\n      );\n      report.movedLinks++;\n    }\n\n    // project_tags has PRIMARY KEY(project_id, tag_id).\n    const dupTags = db\n      .prepare(\"SELECT tag_id FROM project_tags WHERE project_id = ?\")\n      .all(dup.id) as Array<{ tag_id: number }>;\n    for (const t of dupTags) {\n      const clash = db\n        .prepare(\n          \"SELECT 1 FROM project_tags WHERE project_id = ? AND tag_id = ? LIMIT 1\"\n        )\n        .get(canonicalId, t.tag_id);\n      if (clash) {\n        db.prepare(\n          \"DELETE FROM project_tags WHERE project_id = ? AND tag_id = ?\"\n        ).run(dup.id, t.tag_id);\n        continue;\n      }\n      db.prepare(\n        \"UPDATE project_tags SET project_id = ? WHERE project_id = ? AND tag_id = ?\"\n      ).run(canonicalId, dup.id, t.tag_id);\n      report.movedTags++;\n    }\n\n    report.movedChildren += db\n      .prepare(\"UPDATE projects SET parent_id = ? WHERE parent_id = ?\")\n      .run(canonicalId, dup.id).changes;\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      db.prepare(\"UPDATE projects SET claude_notes_dir = ? WHERE id = ?\").run(\n        dup.claude_notes_dir,\n        canonicalId\n      );\n    }\n    if (!group.canonical.session_config && dup.session_config) {\n      db.prepare(\"UPDATE projects SET session_config = ? WHERE id = ?\").run(\n        dup.session_config,\n        canonicalId\n      );\n    }\n\n    db.prepare(\"DELETE FROM projects WHERE id = ?\").run(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    db.prepare(\"UPDATE projects SET status = 'active' WHERE id = ?\").run(\n      canonicalId\n    );\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 = db\n        .prepare(\"SELECT 1 FROM projects WHERE slug = ? LIMIT 1\")\n        .get(candidate);\n      if (taken) continue;\n      db.prepare(\"UPDATE projects SET slug = ?, updated_at = ? WHERE id = ?\").run(\n        candidate,\n        Date.now(),\n        canonicalId\n      );\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 = db\n      .prepare(\"SELECT id FROM projects WHERE root_path = ? AND id != ?\")\n      .get(group.realPath, canonicalId);\n    if (!taken) {\n      db.prepare(\n        \"UPDATE projects SET root_path = ?, updated_at = ? WHERE id = ?\"\n      ).run(group.realPath, Date.now(), canonicalId);\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 */\nfunction canonicalizePaths(\n  db: Database,\n  dryRun: boolean\n): Array<{ slug: string; from: string; to: string }> {\n  const rows = db\n    .prepare(\"SELECT id, slug, root_path FROM projects\")\n    .all() as Array<{ id: number; slug: string; root_path: string }>;\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 = db\n      .prepare(\"SELECT 1 FROM projects WHERE root_path = ? AND id != ? LIMIT 1\")\n      .get(rp, row.id);\n    if (taken) continue;\n\n    changed.push({ slug: row.slug, from: row.root_path, to: rp });\n    if (!dryRun) {\n      db.prepare(\n        \"UPDATE projects SET root_path = ?, updated_at = ? WHERE id = ?\"\n      ).run(rp, Date.now(), row.id);\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 function cmdDedupe(\n  db: Database,\n  opts: { execute?: boolean; dbPath?: string }\n): void {\n  const { groups, stalePaths } = analyzeDuplicates(db);\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 = countRefs(db, 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 = countRefs(db, 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 = countRefs(db, 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 = canonicalizePaths(db, 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  const run = db.transaction(() => {\n    for (const g of groups) {\n      const r = mergeGroup(db, 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 = canonicalizePaths(db, false).length;\n  });\n\n  try {\n    run();\n  } catch (e) {\n    console.error(err(`Merge failed and was rolled back: ${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 * `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 type { Database } from \"better-sqlite3\";\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 { registryDbPath } from \"../../../registry/db.js\";\nimport { encodeDir } from \"../../utils.js\";\nimport { dim, ok, warn } from \"../../utils.js\";\n\nexport function cmdReconnect(db: Database, opts: { execute?: boolean }): void {\n  const rows = db\n    .prepare(\n      `SELECT p.id, p.slug, p.root_path, p.encoded_dir,\n              (SELECT COUNT(*) FROM sessions s WHERE s.project_id = p.id) AS sessions\n         FROM projects p\n        WHERE p.status = 'active'`\n    )\n    .all() as RegistryProjectRow[];\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  // One transaction: a partial repair would leave the registry in a state\n  // nobody chose, and the whole point here is that stale rows are silent.\n  const update = db.prepare(\"UPDATE projects SET encoded_dir = ? WHERE id = ?\");\n  const apply = db.transaction((list: typeof moved) => {\n    for (const m of list) update.run(m.correctDir, m.id);\n  });\n\n  try {\n    apply(moved);\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(\"  Nothing was written: \") + (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 = registryDbPath();\n","/** Registry command registration and simple sub-commands (stats, rebuild, lookup). */\n\nimport type { Command } from \"commander\";\nimport type { Database } from \"better-sqlite3\";\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 { join } from \"node:path\";\nimport { registryDbPath } from \"../../../registry/db.js\";\n\n// ---------------------------------------------------------------------------\n// stats\n// ---------------------------------------------------------------------------\n\nfunction cmdStats(db: Database): void {\n  const totalProjects = (db.prepare(\"SELECT COUNT(*) AS n FROM projects\").get() as { n: number }).n;\n  const activeProjects = (db.prepare(\"SELECT COUNT(*) AS n FROM projects WHERE status = 'active'\").get() as { n: number }).n;\n  const archivedProjects = (db.prepare(\"SELECT COUNT(*) AS n FROM projects WHERE status = 'archived'\").get() as { n: number }).n;\n  const totalSessions = (db.prepare(\"SELECT COUNT(*) AS n FROM sessions\").get() as { n: number }).n;\n  const totalTags = (db.prepare(\"SELECT COUNT(*) AS n FROM tags\").get() as { n: number }).n;\n\n  const lastProject = db\n    .prepare(\"SELECT updated_at FROM projects ORDER BY updated_at DESC LIMIT 1\")\n    .get() as { updated_at: number } | undefined;\n\n  const lastSession = db\n    .prepare(\"SELECT created_at FROM sessions ORDER BY created_at DESC LIMIT 1\")\n    .get() as { created_at: number } | undefined;\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 (lastProject) {\n    console.log(`  ${bold(\"Last updated:\")} ${fmtDate(lastProject.updated_at)}`);\n  }\n  if (lastSession) {\n    console.log(`  ${bold(\"Last session:\")} ${fmtDate(lastSession.created_at)}`);\n  }\n  console.log();\n}\n\n// ---------------------------------------------------------------------------\n// rebuild\n// ---------------------------------------------------------------------------\n\nfunction cmdRebuild(db: Database): void {\n  console.log(warn(\"Rebuilding registry — all existing data will be erased.\"));\n  console.log(dim(\"Clearing all tables ...\"));\n\n  db.exec(`\n    DELETE FROM compaction_log;\n    DELETE FROM session_tags;\n    DELETE FROM project_tags;\n    DELETE FROM aliases;\n    DELETE FROM sessions;\n    DELETE FROM projects;\n    DELETE FROM tags;\n    DELETE FROM schema_version;\n  `);\n\n  console.log(dim(\"Registry cleared. Re-scanning ...\"));\n  cmdScan(db);\n}\n\n// ---------------------------------------------------------------------------\n// lookup\n// ---------------------------------------------------------------------------\n\nfunction cmdLookup(db: Database, fsPath: string): void {\n  const resolved = resolve(fsPath);\n\n  const row = db\n    .prepare(\"SELECT slug FROM projects WHERE root_path = ?\")\n    .get(resolved) as { slug: string } | undefined;\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(\n  registryCmd: Command,\n  getDb: () => Database\n): 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((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        cmdScan(getDb(), { 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(() => {\n      cmdMigrate(getDb());\n    });\n\n  // pai registry stats\n  registryCmd\n    .command(\"stats\")\n    .description(\"Show summary statistics for the registry\")\n    .action(() => {\n      cmdStats(getDb());\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(() => {\n      cmdRebuild(getDb());\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((opts: { execute?: boolean }) => {\n      cmdDedupe(getDb(), {\n        execute: opts.execute,\n        dbPath: registryDbPath(),\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((opts: { execute?: boolean }) => {\n      cmdReconnect(getDb(), { 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((opts: { path: string }) => {\n      cmdLookup(getDb(), opts.path);\n    });\n}\n","/** Memory embed command: generate embeddings for un-embedded chunks. */\n\nimport type { Command } from \"commander\";\nimport type { Database } from \"better-sqlite3\";\nimport { openFederation } from \"../../../memory/db.js\";\nimport { embedChunks } from \"../../../memory/indexer.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\nexport async function runEmbed(\n  federation: Database,\n  projectId?: number,\n  projectSlug?: string,\n  batchSize = 50,\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 { chunksEmbedded } = await embedChunks(\n    federation,\n    projectId,\n    batchSize,\n    (done, total) => {\n      process.stdout.write(`\\r  ${done} / ${total} chunks embedded...`);\n    },\n  );\n\n  process.stdout.write(\"\\r\");\n  console.log(ok(`Done.`) + `  ${bold(String(chunksEmbedded))} chunks embedded`);\n}\n\n// ---------------------------------------------------------------------------\n// Commander registration\n// ---------------------------------------------------------------------------\n\nexport function registerEmbedCommand(\n  memoryCmd: Command,\n  getDb: () => Database,\n): 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      const registryDb = getDb();\n\n      let federation: Database;\n      try {\n        federation = openFederation();\n      } catch (e) {\n        console.error(err(`Failed to open federation database: ${e}`));\n        process.exitCode = 1;\n        return;\n      }\n\n      if (projectSlug) {\n        const project = registryDb\n          .prepare(\"SELECT id, slug FROM projects WHERE slug = ?\")\n          .get(projectSlug) as { id: number; slug: string } | undefined;\n\n        if (!project) {\n          console.error(err(`Project not found: ${projectSlug}`));\n          process.exitCode = 1;\n          return;\n        }\n\n        await runEmbed(federation, project.id, project.slug, parseInt(opts.batchSize ?? \"50\", 10));\n      } else {\n        await runEmbed(federation, 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 { Database } from \"better-sqlite3\";\nimport { openFederation } from \"../../../memory/db.js\";\nimport { indexProject, indexAll } from \"../../../memory/indexer.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\nexport function registerIndexCommand(\n  memoryCmd: Command,\n  getDb: () => Database,\n): 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      const registryDb = getDb();\n\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 federation: Database;\n      try {\n        federation = openFederation();\n      } catch (e) {\n        console.error(err(`Failed to open federation database: ${e}`));\n        process.exitCode = 1;\n        return;\n      }\n\n      if (projectSlug) {\n        const project = registryDb\n          .prepare(\"SELECT id, slug, display_name, root_path FROM projects WHERE slug = ? AND status = 'active'\")\n          .get(projectSlug) as\n          | { id: number; slug: string; display_name: string; root_path: string }\n          | undefined;\n\n        if (!project) {\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 indexProject(federation, project.id, project.root_path);\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(federation, 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 indexAll(federation, registryDb);\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(federation);\n        }\n      }\n    });\n}\n","/** Memory search command: BM25/semantic/hybrid search across federation.db. */\n\nimport type { Command } from \"commander\";\nimport type { Database } from \"better-sqlite3\";\nimport chalk from \"chalk\";\nimport { openFederation } from \"../../../memory/db.js\";\nimport { searchMemory, populateSlugs, type SearchResult } from \"../../../memory/search.js\";\nimport { dim, bold, ok, warn, err } from \"../../utils.js\";\nimport { loadConfig } from \"../../../daemon/config.js\";\nimport { createStorageBackend } from \"../../../storage/factory.js\";\n\n// ---------------------------------------------------------------------------\n// Helper\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// ---------------------------------------------------------------------------\n// Commander registration\n// ---------------------------------------------------------------------------\n\nexport function registerSearchCommand(\n  memoryCmd: Command,\n  getDb: () => Database,\n): 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 registryDb = getDb();\n\n        let federation: Database;\n        try {\n          federation = openFederation();\n        } catch (e) {\n          console.error(err(`Failed to open federation database: ${e}`));\n          process.exitCode = 1;\n          return;\n        }\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 project = registryDb\n            .prepare(\"SELECT id FROM projects WHERE slug = ?\")\n            .get(opts.project) as { id: number } | undefined;\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        let results: SearchResult[];\n\n        if (mode === \"keyword\") {\n          results = searchMemory(federation, query, searchOpts);\n\n        } else if (mode === \"semantic\" || mode === \"hybrid\") {\n          const backend = await createStorageBackend(config);\n\n          try {\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          } finally {\n            await backend.close();\n          }\n        } else {\n          results = [];\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          const { applyRecencyBoost } = await import(\"../../../memory/search.js\");\n          console.log(dim(`Applying recency boost (half-life: ${recencyDays} days)...`));\n          results = applyRecencyBoost(results, recencyDays);\n        }\n\n        const withSlugs = populateSlugs(results, registryDb);\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 type { Database } from \"better-sqlite3\";\nimport chalk from \"chalk\";\nimport { openFederation } from \"../../../memory/db.js\";\nimport { renderTable, dim, bold, ok, err, warn, fmtDate } from \"../../utils.js\";\nimport { loadConfig, paiConfigFilePath, readMainConfigRaw, writeMainConfigRaw } from \"../../../daemon/config.js\";\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\nexport function registerStatsCommands(\n  memoryCmd: Command,\n  getDb: () => Database,\n): 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((projectSlug: string | undefined) => {\n      const registryDb = getDb();\n\n      let federation: Database;\n      try {\n        federation = openFederation();\n      } catch (e) {\n        console.error(err(`Failed to open federation database: ${e}`));\n        process.exitCode = 1;\n        return;\n      }\n\n      // Never wait indefinitely on a lock. These are unbounded COUNT(*) scans\n      // over a table the daemon writes continuously, so without a timeout this\n      // command does not fail — it hangs, with no output and no explanation,\n      // while `pai memory search` against the same data returns normally.\n      // Measured 2026-08-02: killed at 60s having produced zero bytes.\n      try {\n        federation.pragma(\"busy_timeout = 4000\");\n      } catch {\n        /* older driver — the queries below still run, just without the guard */\n      }\n\n      // Say which database these numbers came from. With storageBackend set to\n      // postgres the real index lives there, and this file holds whatever\n      // SQLite still has — often far less. Reporting a small number as if it\n      // were the whole index is worse than reporting nothing, because it looks\n      // like an answer.\n      // Stop here when the live index is not in this file.\n      //\n      // These queries read the SQLite federation database. With storageBackend\n      // set to postgres that file holds almost nothing, so the figures would be\n      // wrong — and wrong in the worst direction, because a small number reads\n      // as an answer rather than as an absence. Reporting where the data\n      // actually is beats reporting a confident zero.\n      const backend = loadConfig().storageBackend;\n      if (backend !== \"sqlite\") {\n        console.log();\n        console.log(warn(`  Nothing to report from here.`));\n        console.log(\n          dim(`  storageBackend is \"${backend}\", so the live index is not in the`)\n        );\n        console.log(dim(`  SQLite federation database this command reads.`));\n        console.log();\n        console.log(`  Authoritative counts:  ${bold(\"pai daemon status\")}`);\n        console.log();\n        try {\n          federation.close();\n        } catch {\n          /* already closed */\n        }\n        return;\n      }\n\n      if (projectSlug) {\n        const project = registryDb\n          .prepare(\"SELECT id, slug, display_name FROM projects WHERE slug = ?\")\n          .get(projectSlug) as\n          | { id: number; slug: string; display_name: string }\n          | undefined;\n\n        if (!project) {\n          console.error(err(`Project not found: ${projectSlug}`));\n          process.exitCode = 1;\n          return;\n        }\n\n        const fileStats = federation\n          .prepare(\"SELECT COUNT(*) as files FROM memory_files WHERE project_id = ?\")\n          .get(project.id) as { files: number };\n\n        const chunkStats = federation\n          .prepare(\"SELECT COUNT(*) as chunks FROM memory_chunks WHERE project_id = ?\")\n          .get(project.id) as { chunks: number };\n\n        const lastUpdate = federation\n          .prepare(\"SELECT MAX(updated_at) as last_at FROM memory_chunks WHERE project_id = ?\")\n          .get(project.id) as { last_at: number | null };\n\n        const tierBreakdown = federation\n          .prepare(\"SELECT tier, COUNT(*) as n FROM memory_chunks WHERE project_id = ? GROUP BY tier ORDER BY n DESC\")\n          .all(project.id) as Array<{ tier: string; n: number }>;\n\n        console.log(`\\n  ${bold(project.display_name)} ${dim(`(${project.slug})`)}\\n`);\n        console.log(`  ${bold(\"Files indexed:\")}  ${fileStats.files}`);\n        console.log(`  ${bold(\"Chunks:\")}         ${chunkStats.chunks}`);\n        console.log(`  ${bold(\"Last indexed:\")}   ${lastUpdate.last_at ? fmtDate(lastUpdate.last_at) : dim(\"never\")}`);\n\n        if (tierBreakdown.length > 0) {\n          console.log(`\\n  ${bold(\"By tier:\")}`);\n          for (const row of tierBreakdown) {\n            console.log(`    ${tierColor(row.tier).padEnd(20)}  ${row.n} chunks`);\n          }\n        }\n        console.log();\n\n      } else {\n        const globalFiles = federation.prepare(\"SELECT COUNT(*) as n FROM memory_files\").get() as { n: number };\n        const globalChunks = federation.prepare(\"SELECT COUNT(*) as n FROM memory_chunks\").get() as { n: number };\n        const lastUpdate = federation.prepare(\"SELECT MAX(updated_at) as last_at FROM memory_chunks\").get() as { last_at: number | null };\n\n        const projectStats = federation\n          .prepare(\n            `SELECT\n               mf.project_id,\n               COUNT(DISTINCT mf.path) as files,\n               COUNT(mc.id) as chunks,\n               MAX(mc.updated_at) as last_at\n             FROM memory_files mf\n             LEFT JOIN memory_chunks mc ON mc.project_id = mf.project_id AND mc.path = mf.path\n             GROUP BY mf.project_id\n             ORDER BY chunks DESC`,\n          )\n          .all() as Array<{ project_id: number; files: number; chunks: number; last_at: number | null }>;\n\n        const projectIds = projectStats.map((p) => p.project_id);\n        const slugMap = new Map<number, string>();\n        if (projectIds.length > 0) {\n          const placeholders = projectIds.map(() => \"?\").join(\", \");\n          const slugRows = registryDb\n            .prepare(`SELECT id, slug, display_name FROM projects WHERE id IN (${placeholders})`)\n            .all(...projectIds) as Array<{ id: number; slug: string; display_name: string }>;\n          for (const row of slugRows) {\n            slugMap.set(row.id, row.slug);\n          }\n        }\n\n        console.log(`\\n  ${bold(\"PAI Memory Index — Global Status\")}\\n`);\n        console.log(`  ${bold(\"Total files:\")}   ${globalFiles.n}   ${bold(\"Total chunks:\")}  ${globalChunks.n}`);\n        console.log(`  ${bold(\"Last indexed:\")}  ${lastUpdate.last_at ? fmtDate(lastUpdate.last_at) : dim(\"never\")}`);\n\n        if (projectStats.length > 0) {\n          console.log();\n          const rows = projectStats.map((p) => [\n            chalk.cyan(slugMap.get(p.project_id) ?? String(p.project_id)),\n            String(p.files),\n            String(p.chunks),\n            p.last_at ? fmtDate(p.last_at) : dim(\"never\"),\n          ]);\n          console.log(renderTable([\"Project\", \"Files\", \"Chunks\", \"Last Indexed\"], rows));\n        } else {\n          console.log(dim(\"\\n  No projects indexed yet. Run `pai memory index --all` to start.\"));\n        }\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 type { Database } from \"better-sqlite3\";\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(\n  memoryCmd: Command,\n  getDb: () => Database,\n): void {\n  registerIndexCommand(memoryCmd, getDb);\n  registerEmbedCommand(memoryCmd, getDb);\n  registerSearchCommand(memoryCmd, getDb);\n  registerStatsCommands(memoryCmd, getDb);\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 Notes/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\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  };\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 * 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);\n        note = result.note;\n        if (!opts.dryRun) {\n          report.probed++;\n          if (result.stuck) report.stuck++;\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 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  const claimed = [...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 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<PersistedState, \"startedAt\" | \"claimSeenAt\" | \"failedProbes\">\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}\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\nasync function handleProbe(\n  task: Task,\n  elapsed: number | null,\n  opts: TickOptions,\n  state: PersistedState\n): Promise<{ note: string; stuck: 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    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 * 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.db          — SQLite registry database\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} from \"node:fs\";\nimport { join } from \"node:path\";\nimport { homedir } from \"node:os\";\nimport { execSync } from \"node:child_process\";\nimport { ok, warn, err, dim, bold } from \"../utils.js\";\nimport { loadConfig, paiConfigFilePath } from \"../../daemon/config.js\";\nimport { registryDbPath } from \"../../registry/db.js\";\nimport { federationDbPath } from \"../../memory/db.js\";\nimport { paiHomePath, resolvePaiFile } from \"../../config/pai-home.js\";\n\n// ---------------------------------------------------------------------------\n// Paths\n// ---------------------------------------------------------------------------\n\nconst HOME = homedir();\nconst REGISTRY_DB = registryDbPath();\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\n      // ------------------------------------------------------------------\n      // 1. Registry SQLite DB\n      // ------------------------------------------------------------------\n\n      if (existsSync(REGISTRY_DB)) {\n        const dest = join(backupDir, \"registry.db\");\n        try {\n          copyFileSync(REGISTRY_DB, dest);\n          results.push({ label: \"Registry DB\", path: dest, size: fileSize(dest), status: ok(\"ok\") });\n        } catch (e) {\n          results.push({ label: \"Registry DB\", path: dest, size: \"-\", status: err(`failed: ${e}`) });\n        }\n      } else {\n        results.push({ label: \"Registry DB\", path: REGISTRY_DB, size: \"-\", status: warn(\"not found — skipped\") });\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          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. Optional: Federation SQLite (legacy)\n      // ------------------------------------------------------------------\n\n      const federationDb = federationDbPath();\n      if (existsSync(federationDb)) {\n        const dest = join(backupDir, \"federation.db\");\n        try {\n          copyFileSync(federationDb, dest);\n          results.push({ label: \"Federation DB (legacy)\", path: dest, size: fileSize(dest), status: ok(\"ok\") });\n        } catch (e) {\n          results.push({ label: \"Federation DB (legacy)\", path: dest, size: \"-\", status: warn(`skipped: ${e}`) });\n        }\n      }\n\n      // ------------------------------------------------------------------\n      // 4. 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          execSync(`docker inspect ${DOCKER_CONTAINER} --format='{{.State.Status}}'`, {\n            stdio: \"pipe\",\n          });\n\n          execSync(\n            `docker exec ${DOCKER_CONTAINER} pg_dump -U ${PG_USER} ${PG_DATABASE} > \"${sqlDest}\"`,\n            { stdio: [\"pipe\", \"pipe\", \"pipe\"], shell: true as unknown as string }\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 { Database } from \"better-sqlite3\";\nimport type { ProjectRow, 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 function syncVault(vaultPath: string, db: Database): 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  const projects = db\n    .prepare(\n      `SELECT id, slug, display_name, root_path, encoded_dir, status, obsidian_link, claude_notes_dir\n       FROM projects\n       ORDER BY status ASC, slug ASC`\n    )\n    .all() as ProjectRow[];\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        db.prepare(\"UPDATE projects SET obsidian_link = ?, updated_at = ? WHERE id = ?\").run(\n          slugPath,\n          Date.now(),\n          project.id\n        );\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 { Database } from \"better-sqlite3\";\nimport { fmtDate } from \"../../cli/utils.js\";\nimport type { ProjectRow, SessionStats, TagRow } from \"./types.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 function generateIndex(vaultPath: string, db: Database): void {\n  mkdirSync(vaultPath, { recursive: true });\n\n  const rows = db\n    .prepare(\n      `SELECT p.id, p.slug, p.display_name, p.status, p.root_path,\n         p.encoded_dir, p.claude_notes_dir,\n         (SELECT COUNT(*) FROM sessions s WHERE s.project_id = p.id) AS session_count,\n         (SELECT MAX(s.created_at) FROM sessions s WHERE s.project_id = p.id) AS last_active\n       FROM projects p\n       ORDER BY p.status ASC, p.updated_at DESC`\n    )\n    .all() as (ProjectRow & SessionStats)[];\n\n  const getTagsForProject = db.prepare(\n    `SELECT t.name FROM tags t\n     JOIN project_tags pt ON pt.tag_id = t.id\n     WHERE pt.project_id = ?\n     ORDER BY t.name`\n  );\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 = (getTagsForProject.all(row.id) as TagRow[]).map((t) => `\\`${t.name}\\``).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 = (getTagsForProject.all(row.id) as TagRow[]).map((t) => `\\`${t.name}\\``).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 function generateTopicPages(vaultPath: string, db: Database): number {\n  const topicsDir = join(vaultPath, \"_topics\");\n  mkdirSync(topicsDir, { recursive: true });\n\n  const allTags = db\n    .prepare(\"SELECT id, name FROM tags ORDER BY name\")\n    .all() as { id: number; name: string }[];\n\n  const getProjectsForTag = db.prepare(\n    `SELECT p.id, p.slug, p.display_name, p.status,\n       (SELECT COUNT(*) FROM sessions s WHERE s.project_id = p.id) AS session_count,\n       (SELECT MAX(s.created_at) FROM sessions s WHERE s.project_id = p.id) AS last_active\n     FROM projects p\n     JOIN project_tags pt ON pt.project_id = p.id\n     WHERE pt.tag_id = ?\n     ORDER BY p.status ASC, p.updated_at DESC`\n  );\n\n  let written = 0;\n  for (const tag of allTags) {\n    const projects = getProjectsForTag.all(tag.id) as (ProjectRow & SessionStats)[];\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 { Database } from \"better-sqlite3\";\nimport type { ProjectRow } from \"./types.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 db         Registry SQLite database\n * @param threshold  Minimum session count to generate a master note (default: 5)\n * @returns          Number of master notes written\n */\nexport function generateMasterNotes(\n  vaultPath: string,\n  db: Database,\n  threshold = 5\n): number {\n  if (!existsSync(vaultPath)) return 0;\n\n  const projects = db\n    .prepare(\n      `SELECT id, slug, display_name, root_path, encoded_dir, status, obsidian_link, claude_notes_dir\n       FROM projects\n       WHERE status = 'active'\n       ORDER BY slug ASC`\n    )\n    .all() as ProjectRow[];\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 db  Registry SQLite database\n * @returns   Object with counts: { filesScanned, filesModified, errors }\n */\nexport function fixSessionTags(\n  db: Database\n): { filesScanned: number; filesModified: number; errors: string[] } {\n  const results = { filesScanned: 0, filesModified: 0, errors: [] as string[] };\n\n  const projects = db\n    .prepare(\n      `SELECT id, slug, display_name, root_path, encoded_dir, status, obsidian_link, claude_notes_dir\n       FROM projects\n       WHERE status = 'active'\n       ORDER BY slug ASC`\n    )\n    .all() as ProjectRow[];\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 type { Database } from \"better-sqlite3\";\nimport { existsSync, readdirSync, lstatSync, readlinkSync } from \"node:fs\";\nimport { join } from \"node:path\";\n\n// ---------------------------------------------------------------------------\n// Types\n// ---------------------------------------------------------------------------\n\ninterface ProjectRow {\n  id: number;\n  slug: string;\n  display_name: string;\n  root_path: string;\n  status: string;\n  claude_notes_dir: string | null;\n  obsidian_link: string | null;\n}\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 function checkHealth(vaultPath: string, db: Database): 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 = db\n    .prepare(\n      `SELECT id, slug, display_name, root_path, status, claude_notes_dir, obsidian_link\n       FROM projects WHERE status = 'active'`\n    )\n    .all() as ProjectRow[];\n\n  report.totalProjects = projects.length;\n\n  // Build a map of slug → project for quick lookup\n  const projectsBySlug = new Map<string, ProjectRow>();\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 type { Database } from \"better-sqlite3\";\nimport { execSync } 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 { 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\nfunction cmdSync(\n  db: Database,\n  opts: { vault?: string; quiet?: boolean }\n): 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 = syncVault(vaultPath, db);\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  generateIndex(vaultPath, db);\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 = generateTopicPages(vaultPath, db);\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 = fixSessionTags(db);\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 = generateMasterNotes(vaultPath, db);\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\nfunction cmdStatus(db: Database, opts: { vault?: string }): void {\n  const vaultPath = getVaultPath(opts.vault);\n  const report = checkHealth(vaultPath, db);\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    execSync(`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(\n  obsidianCmd: Command,\n  getDb: () => Database\n): 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((opts: { vault?: string; quiet?: boolean }) => {\n      cmdSync(getDb(), 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((opts: { vault?: string }) => {\n      cmdStatus(getDb(), 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; federation.db — the\n// live federation DB has lived at ~/.pai/federation.db since the Postgres\n// migration, this copy is a 0-byte leftover). Still moved by `pai config\n// migrate` so ~/.config/pai ends up with nothing but *.migrated-* markers.\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\nfunction 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// ---------------------------------------------------------------------------\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\";\nimport type { Pool } from \"pg\";\n\nimport { openRegistry } from \"../registry/db.js\";\nimport { loadConfig, CONFIG_FILE } from \"../daemon/config.js\";\nimport { createStorageBackend } from \"../storage/factory.js\";\nimport { extractAndStoreTriples } from \"./kg-extraction.js\";\nimport { openFederation } from \"./db.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.backendType !== \"postgres\") {\n    throw new Error(\n      \"Postgres backend unavailable — fell back to SQLite. Cannot backfill KG.\"\n    );\n  }\n\n  const pool: Pool = (backend as unknown as { getPool(): Pool }).getPool();\n\n  // Open federation SQLite for entity upserts (kg_entities table)\n  const federationDb = openFederation();\n\n  // -- Load projects -------------------------------------------------------\n  const registry = openRegistry();\n  let projects: ProjectRow[];\n  try {\n    if (options.projectSlug) {\n      const row = registry\n        .prepare(\"SELECT id, slug, root_path FROM projects WHERE slug = ?\")\n        .get(options.projectSlug) as ProjectRow | undefined;\n      if (!row) {\n        throw new Error(`Project not found: ${options.projectSlug}`);\n      }\n      projects = [row];\n    } else {\n      projects = registry\n        .prepare(\n          \"SELECT id, slug, root_path FROM projects WHERE status = 'active' ORDER BY slug\"\n        )\n        .all() as ProjectRow[];\n    }\n  } finally {\n    registry.close();\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(pool, {\n        summaryText: noteContent,\n        projectSlug: project.slug,\n        projectId: project.id,\n        sessionId: `backfill:${notePath}`,\n        gitLog: \"\",\n        model: \"sonnet\",\n        federationDb,\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  federationDb.close();\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, federation.db),\n * plus ~/.pai/registry.db (`registry.db`) and the two content dirs that get\n * an adapter symlink back to ~/.claude, `agents/` and `commands/`. History/,\n * agent-sessions.json, session-routing.json and security-events.jsonl move\n * only with `--history`; logs/workers only with `--logs` — both are written\n * by hooks on every live session and are 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 { registryDbPath, oldRegistryPath } from \"../../registry/db.js\";\nimport { federationDbPath, oldFederationPath } from \"../../memory/db.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  migrateOrphanFederationDb,\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\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 */\nfunction migrateFederationDbOrphan(dryRun: boolean): MigrateFileResult {\n  const oldPath = join(homedir(), \".config\", \"pai\", \"federation.db\");\n  if (existsSync(oldPath) && !dryRun) {\n    let heldOpen = false;\n    try {\n      execSync(`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(dim(\"  federation.db: held open by the daemon — restarting it first\"));\n      execSync(\"pai daemon restart\", { stdio: \"inherit\" });\n      execSync(\"sleep 2\");\n    }\n  }\n  return migrateOrphanFederationDb({ dryRun });\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 federation.db above (the daemon\n * holds this connection while it runs), plus SQLite-specific care: a\n * WAL-mode DB can have unflushed writes sitting in a `-wal` sidecar file, so\n * a plain byte-copy of `registry.db` alone can silently drop them. This\n * checkpoints the WAL into the main file before copying, moves any sidecar\n * left behind anyway, and runs `PRAGMA integrity_check` on the copy when the\n * `sqlite3` CLI is available — on top of migratePaiFile's own byte-identical\n * verification, not instead of it.\n */\nfunction migrateRegistryDb(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    execSync(`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(dim(\"  registry.db: held open by the daemon — restarting it first\"));\n    execSync(\"pai daemon restart\", { stdio: \"inherit\" });\n    execSync(\"sleep 2\");\n  }\n\n  let hasSqlite3 = true;\n  try {\n    execSync(\"command -v sqlite3\", { stdio: \"pipe\" });\n  } catch {\n    hasSqlite3 = false;\n  }\n  if (hasSqlite3) {\n    try {\n      execSync(`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 result = migratePaiFile(newPath, [oldPath], { dryRun: false });\n\n  if (result.fromPath) {\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 = execSync(`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  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 * daemon's dispatcher, scheduler and session-summary-worker, plus kg-backfill\n * and several `pai memory`/`pai zettel`/`pai db` commands, open it directly\n * via openFederation()'s default path — independent of the storageBackend\n * setting, which only gates the search/stats StorageBackend abstraction.\n * It shares its target filename with the harmless 0-byte\n * ~/.config/pai/federation.db orphan migrated above, so that empty copy is\n * renamed aside first (never deleted) to free up the canonical name. Same\n * open-fd, WAL-checkpoint and integrity-check care as registry.db.\n */\nfunction migrateFederationDbLive(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    execSync(`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(dim(\"  federation.db: held open by the daemon — restarting it first\"));\n    execSync(\"pai daemon restart\", { stdio: \"inherit\" });\n    execSync(\"sleep 2\");\n  }\n\n  let hasSqlite3 = true;\n  try {\n    execSync(\"command -v sqlite3\", { stdio: \"pipe\" });\n  } catch {\n    hasSqlite3 = false;\n  }\n  if (hasSqlite3) {\n    try {\n      execSync(`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 result = migratePaiFile(newPath, [oldPath], { dryRun: false });\n\n  if (result.fromPath) {\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 = execSync(`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  return result;\n}\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.db,\n * config.json, federation.db, postgres-pai.sql per timestamped run). Moved\n * 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.db 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.db (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.db:              ${registryDbPath()}`);\n      console.log(`  federation.db:            ${federationDbPath()}`);\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 federation.db,\\n\" +\n        \"~/.pai/registry.db, ~/.pai/federation.db (live), ~/.pai/backups/,\\n\" +\n        \"~/.pai/obsidian-vault/, ~/.pai/scheduler-state.json, and ~/.claude/Agents/,\\n\" +\n        \"~/.claude/Commands/ into PAI_HOME.\\n\" +\n        \"registry.db and federation.db 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.db (orphan)\", () => migrateFederationDbOrphan(dryRun));\n      report(\"registry.db\", () => migrateRegistryDb(dryRun));\n      report(\"federation.db (live)\", () => migrateFederationDbLive(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/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 { join, dirname } from \"node:path\";\nimport { fileURLToPath } from \"node:url\";\nimport { ok, warn, err, dim, bold } from \"../utils.js\";\nimport { readClaudeJson, writeClaudeJson } from \"../../config/claude-json.js\";\n\n// ---------------------------------------------------------------------------\n// Paths\n// ---------------------------------------------------------------------------\n\n/**\n * Resolve the absolute path to a built MCP entry point.\n *\n * tsdown bundles all CLI commands into a single dist/cli/index.mjs file, so\n * import.meta.url always resolves to dist/cli/index.mjs at runtime.\n * From dist/cli/ we go up one level to dist/ and then into the entry.\n */\nfunction distDir(): string {\n  const __filename = fileURLToPath(import.meta.url);\n  // dist/cli/index.mjs  →  dist/\n  return join(dirname(__filename), \"..\");\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: \"mcp/index.mjs\",\n    label: \"PAI MCP server\",\n    tools: \"memory_search, memory_get, 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 = join(distDir(), 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 = join(distDir(), 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 * 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    — Send SIGTERM to running daemon (launchd will restart it)\n * install    — Write launchd plist + update ~/.claude.json to use the shim\n * uninstall  — Remove launchd plist + 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, dirname } from \"node:path\";\nimport { homedir } from \"node:os\";\nimport { fileURLToPath } from \"node:url\";\nimport { execSync, 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 { 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\n/**\n * Resolve the absolute path to the built daemon entry point.\n * tsdown bundles into dist/daemon/index.mjs (or similar).\n * From dist/cli/index.mjs → dist/ → dist/daemon/index.mjs\n */\nfunction getDaemonBinPath(): string {\n  const __filename = fileURLToPath(import.meta.url);\n  const __dirname = dirname(__filename);\n  return join(__dirname, \"../daemon/index.mjs\");\n}\n\n/**\n * Resolve the absolute path to the built MCP shim entry point.\n * dist/cli/index.mjs → dist/ → dist/daemon-mcp/index.mjs\n */\nfunction getShimBinPath(): string {\n  const __filename = fileURLToPath(import.meta.url);\n  const __dirname = dirname(__filename);\n  return join(__dirname, \"../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// 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  // 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 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  // 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;\n  }\n\n  // 2. Load the plist (unload first in case it was already there)\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\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 cmdUninstall(): void {\n  console.log();\n  console.log(bold(\"  PAI Daemon Uninstall\"));\n  console.log();\n\n  // 1. Unload and remove plist\n  if (existsSync(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    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  // 2. 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  // Resolve the migration script path relative to this built file\n  const { fileURLToPath } = await import(\"node:url\");\n  const { dirname: pathDirname, join: pathJoin } = await import(\"node:path\");\n  const __filename = fileURLToPath(import.meta.url);\n  const __dirname = pathDirname(__filename);\n\n  // The migration script is at docker/migrate-sqlite.ts in the source tree\n  // When built, we look for it relative to the package root\n  const migrationScript = pathJoin(__dirname, \"../..\", \"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      execSync(`tail -f -n ${lines} \"${DAEMON_LOG}\"`, { stdio: \"inherit\" });\n    } catch {\n      // User pressed Ctrl+C — that's fine\n    }\n  } else {\n    try {\n      execSync(`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(\"Send SIGTERM to the running daemon (launchd will restart it)\")\n    .action(() => {\n      cmdRestart();\n    });\n\n  daemonCmd\n    .command(\"install\")\n    .description(\n      \"Install daemon as a launchd service and update ~/.claude.json to use the shim\"\n    )\n    .action(() => {\n      cmdInstall();\n    });\n\n  daemonCmd\n    .command(\"uninstall\")\n    .description(\"Remove the launchd service 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 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.db          — SQLite registry database\n *   config.json          — PAI daemon config\n *   postgres-pai.sql     — Postgres dump (piped into psql via docker exec)\n *   federation.db        — 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} from \"node:fs\";\nimport { join, dirname } from \"node:path\";\nimport { homedir } from \"node:os\";\nimport { execSync, spawnSync } from \"node:child_process\";\nimport { createInterface } from \"node:readline\";\nimport { ok, warn, err, dim, bold } from \"../utils.js\";\nimport { paiConfigFilePath } from \"../../daemon/config.js\";\nimport { registryDbPath } from \"../../registry/db.js\";\nimport { federationDbPath } from \"../../memory/db.js\";\nimport { backupsDirPath } from \"./backup.js\";\n\n// ---------------------------------------------------------------------------\n// Paths\n// ---------------------------------------------------------------------------\n\nconst HOME = homedir();\nconst REGISTRY_DB = registryDbPath();\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 hasRegistry = existsSync(join(resolvedDir, \"registry.db\"));\n      const hasConfig   = existsSync(join(resolvedDir, \"config.json\"));\n      const hasSql      = existsSync(join(resolvedDir, \"postgres-pai.sql\"));\n      const hasFed      = existsSync(join(resolvedDir, \"federation.db\"));\n\n      console.log(`${bold(\"Backup contents:\")}`);\n      console.log(`  registry.db          ${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 (hasFed) {\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.db\n      // ------------------------------------------------------------------\n\n      if (hasRegistry) {\n        try {\n          mkdirSync(dirname(REGISTRY_DB), { recursive: true });\n          copyFileSync(join(resolvedDir, \"registry.db\"), REGISTRY_DB);\n          results.push({ label: \"Registry DB\", status: ok(\"restored\") });\n        } catch (e) {\n          results.push({ label: \"Registry DB\", status: err(`failed: ${e}`) });\n        }\n      } else {\n        results.push({ label: \"Registry DB\", status: warn(\"missing in backup — skipped\") });\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          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 federation.db (legacy)\n      // ------------------------------------------------------------------\n\n      if (hasFed) {\n        try {\n          copyFileSync(join(resolvedDir, \"federation.db\"), federationDbPath());\n          results.push({ label: \"Federation DB (legacy)\", status: ok(\"restored\") });\n        } catch (e) {\n          results.push({ label: \"Federation DB (legacy)\", status: warn(`skipped: ${e}`) });\n        }\n      }\n\n      // ------------------------------------------------------------------\n      // 7. 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          execSync(`docker inspect ${DOCKER_CONTAINER} --format='{{.State.Status}}'`, {\n            stdio: \"pipe\",\n          });\n\n          // Drop and recreate the database, then restore\n          const dropCreate = `docker exec ${DOCKER_CONTAINER} psql -U ${PG_USER} -c \"DROP DATABASE IF EXISTS ${PG_DATABASE}; CREATE DATABASE ${PG_DATABASE} OWNER ${PG_USER};\"`;\n          execSync(dropCreate, { stdio: \"pipe\", shell: true as unknown as string });\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 { homedir } from \"node:os\";\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\";\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\nexport async function prompt(rl: Rl, question: string): Promise<string> {\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    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  const candidates = [\n    join(process.cwd(), \"docker\"),\n    join(homedir(), \"dev\", \"ai\", \"PAI\", \"docker\"),\n    join(\"/\", \"usr\", \"local\", \"lib\", \"node_modules\", \"@tekmidian\", \"pai\", \"docker\"),\n  ];\n  for (const candidate of candidates) {\n    if (existsSync(join(candidate, \"docker-compose.yml\"))) return candidate;\n  }\n  return join(process.cwd(), \"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  const candidates = [\n    join(process.cwd(), \"templates\"),\n    join(homedir(), \"dev\", \"ai\", \"PAI\", \"templates\"),\n    join(\"/\", \"usr\", \"local\", \"lib\", \"node_modules\", \"@tekmidian\", \"pai\", \"templates\"),\n  ];\n  for (const candidate of candidates) {\n    if (existsSync(join(candidate, \"claude-md.template.md\"))) return candidate;\n  }\n  return join(process.cwd(), \"templates\");\n}\n\nexport function getHooksDir(): string {\n  const candidates = [\n    join(process.cwd(), \"src\", \"hooks\"),\n    join(homedir(), \"dev\", \"ai\", \"PAI\", \"src\", \"hooks\"),\n    join(\"/\", \"usr\", \"local\", \"lib\", \"node_modules\", \"@tekmidian\", \"pai\", \"src\", \"hooks\"),\n  ];\n  for (const candidate of candidates) {\n    if (existsSync(join(candidate, \"session-stop.sh\"))) return candidate;\n  }\n  return join(process.cwd(), \"src\", \"hooks\");\n}\n\nexport function getDistHooksDir(): string {\n  const moduleDir = new URL(\".\", import.meta.url).pathname;\n  const fromModule = join(moduleDir, \"..\", \"..\", \"hooks\");\n\n  const candidates = [\n    fromModule,\n    join(process.cwd(), \"dist\", \"hooks\"),\n    join(homedir(), \"dev\", \"ai\", \"PAI\", \"dist\", \"hooks\"),\n    join(\"/\", \"usr\", \"local\", \"lib\", \"node_modules\", \"@tekmidian\", \"pai\", \"dist\", \"hooks\"),\n  ];\n  for (const candidate of candidates) {\n    if (existsSync(join(candidate, \"stop-hook.mjs\"))) return candidate;\n  }\n  return fromModule;\n}\n\nexport function getDistDir(): string {\n  const moduleDir = new URL(\".\", import.meta.url).pathname;\n  const fromModule = join(moduleDir, \"..\", \"..\", \"..\");\n\n  const candidates = [\n    fromModule,\n    join(process.cwd(), \"dist\"),\n    join(homedir(), \"dev\", \"ai\", \"PAI\", \"dist\"),\n    join(\"/\", \"usr\", \"local\", \"lib\", \"node_modules\", \"@tekmidian\", \"pai\", \"dist\"),\n  ];\n  for (const candidate of candidates) {\n    if (existsSync(join(candidate, \"skills\"))) return candidate;\n  }\n  return fromModule;\n}\n\nexport function getStatuslineScript(): string | null {\n  const candidates = [\n    join(process.cwd(), \"statusline-command.sh\"),\n    join(homedir(), \"dev\", \"ai\", \"PAI\", \"statusline-command.sh\"),\n    join(\"/\", \"usr\", \"local\", \"lib\", \"node_modules\", \"@tekmidian\", \"pai\", \"statusline-command.sh\"),\n  ];\n  for (const candidate of candidates) {\n    if (existsSync(candidate)) return candidate;\n  }\n  return null;\n}\n\nexport function getTabColorScript(): string | null {\n  const candidates = [\n    join(process.cwd(), \"tab-color-command.sh\"),\n    join(homedir(), \"dev\", \"ai\", \"PAI\", \"tab-color-command.sh\"),\n    join(\"/\", \"usr\", \"local\", \"lib\", \"node_modules\", \"@tekmidian\", \"pai\", \"tab-color-command.sh\"),\n  ];\n  for (const candidate of candidates) {\n    if (existsSync(candidate)) return candidate;\n  }\n  return 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, 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  const choice = 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 (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: \"*\", 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 via launchd plist. */\n\nimport { existsSync } 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 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 plistPath = join(homedir(), \"Library\", \"LaunchAgents\", \"com.pai.pai-daemon.plist\");\n  const exists = existsSync(plistPath);\n\n  if (exists) {\n    console.log(c.dim(\"  PAI daemon plist already installed.\"));\n    line();\n\n    const reinstall = await promptYesNo(rl, \"Reinstall the PAI daemon launchd plist?\", 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 com.pai.pai-daemon.\"));\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 { 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): 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 ? \"com.pai.pai-daemon (installed)\" : \"(unchanged)\"));\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 Notes/docs/task-bus.md.\n */\n\nimport type { Database } from \"better-sqlite3\";\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 function loadAliasMap(db: Database): AliasMap {\n  const rows = db\n    .prepare(\n      `SELECT a.alias    AS alias,\n              p.slug     AS slug,\n              p.root_path AS rootPath\n         FROM aliases a\n         JOIN projects p ON p.id = a.project_id\n        WHERE p.status != 'archived'`\n    )\n    .all() as Array<{ alias: string; slug: string; rootPath: string }>;\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.rootPath,\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 Notes/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: \")).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, 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    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\";\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 you configure —\");\n  line(\"  a non-Anthropic endpoint via its Anthropic-compatible API.\");\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 once providers exist.\");\n  line();\n  line(\"  PAI works fully without this — Agent subagents then just run as before.\");\n  line();\n\n  const wanted = await promptYesNo(rl, \"Configure a worker provider now?\", false);\n  if (!wanted) {\n    line();\n    console.log(c.ok(\"Skipping worker providers.\"));\n    console.log(c.dim(\"  Add later: pai worker providers add <name> … — see docs/worker.md\"));\n    return { workers: { enabled: false, providers: {}, classes: {} } };\n  }\n\n  line();\n  line(\"  Add the provider after setup finishes (this step only prepares the\");\n  line(\"  wiring — shims, hook registration, log dir):\");\n  line();\n  line(c.dim(\"    pai worker providers add glm \\\\\"));\n  line(c.dim(\"      --base-url https://api.example.com/api/anthropic \\\\\"));\n  line(c.dim(\"      --key-file ~/.config/example/api_key \\\\\"));\n  line(c.dim(\"      --model example-4.7 --fast-model example-4.7-flash\"));\n  line();\n  line(\"  The first provider turns workers on and seeds the classes.\");\n  line();\n\n  const r = installWorkers();\n  for (const l of r.lines) line(`  ${l}`);\n  return { workers: { enabled: false, providers: {}, classes: {} } };\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 } 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\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 stepStorage(rl);\n\n    // Step 3: Embeddings\n    const embeddingConfig = await stepEmbedding(rl);\n\n    // Step 4: Agent configuration (CLAUDE.md)\n    const claudeMdGenerated = await stepClaudeMd(rl);\n\n    // Step 5: PAI Skill\n    const paiSkillInstalled = await stepPaiSkill(rl);\n\n    // Step 6: AI Steering Rules\n    const aiSteeringRulesInstalled = await stepAiSteeringRules(rl);\n\n    // Step 7: Skill Stubs\n    const skillStubsInstalled = await stepSkillStubs(rl);\n\n    // Step 8: Hooks (shell scripts)\n    const hooksInstalled = await stepHooks(rl);\n\n    // Step 7b: TypeScript hooks (.mjs files)\n    const tsHooksInstalled = await stepTsHooks(rl);\n\n    // Step 8b: DA name\n    const daName = await stepDaName(rl);\n\n    // Step 8: Settings.json\n    const settingsPatched = await stepSettings(rl, daName);\n\n    // Step 9: Daemon\n    const daemonInstalled = await stepDaemon(rl);\n\n    // Step 10: MCP\n    const mcpRegistered = await stepMcp(rl);\n\n    // Step 11: Directories (informational — no config written)\n    await stepDirectories(rl);\n\n    // Step 11b: Task bus (optional external tracker)\n    const taskConfig = await stepTaskBus(rl);\n\n    // Step 11c: Worker providers (optional subagent routing)\n    const workersConfig = await 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 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      \"Interactive setup wizard — configure storage, embeddings, agent config, and indexing\",\n    )\n    .action(async () => {\n      await runSetup();\n    });\n}\n","/** Shared utilities for zettel CLI commands. */\n\nimport type { Database } from \"better-sqlite3\";\nimport { openFederation } from \"../../../memory/db.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// Lazy-loaded federation DB singleton\nlet _fedDb: Database | null = null;\n\n/** Get (or lazily open) the PAI federation database. */\nexport function getFedDb(): Database {\n  if (!_fedDb) {\n    try {\n      _fedDb = openFederation();\n    } catch (e) {\n      console.error(err(`Failed to open PAI federation DB: ${e}`));\n      process.exit(1);\n    }\n  }\n  return _fedDb;\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 { getFedDb, 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 db = getFedDb();\n\n  const result = zettelExplore(db, { 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 { getFedDb, 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 db = getFedDb();\n\n  const result = zettelHealth(db, { 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 { getFedDb, 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 db = getFedDb();\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(db, {\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 { getFedDb, 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 db = getFedDb();\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(db, {\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 { getFedDb, 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 db = getFedDb();\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(db, { 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 { getFedDb, 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 db = getFedDb();\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(db, {\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 type { Database } from \"better-sqlite3\";\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(\n  parent: Command,\n  _getDb: () => Database   // registry DB (unused — federation DB is opened directly)\n): 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}\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  const pinned = opts.projectsDbPath\n    ? projectLaunchConfig(resolve(cwd), opts.projectsDbPath)?.mcp ?? null\n    : 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, dirname } from \"node:path\";\nimport { homedir } from \"node:os\";\nimport { fileURLToPath } from \"node:url\";\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  const __filename = fileURLToPath(import.meta.url);\n  const __dirname = dirname(__filename);\n  // dist/cli/index.mjs → dist/ → package root\n  return join(__dirname, \"../..\");\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 Notes/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\";\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 (!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  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  spawnSync(\"launchctl\", [\"unload\", SCHEDULE_PLIST], { encoding: \"utf8\" });\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 Notes/docs/task-bus.md.\n */\n\nimport type { Command } from \"commander\";\nimport type { Database } from \"better-sqlite3\";\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 { openRegistry } from \"../../registry/db.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 */\nfunction ensureAlias(db: Database, name: string, directory?: string): string | null {\n  if (!directory) return `no directory for \"${name}\" — set the alias by hand`;\n\n  const project = db\n    .prepare(\"SELECT id, slug FROM projects WHERE root_path = ? AND status = 'active'\")\n    .get(directory) as { id: number; slug: string } | undefined;\n\n  if (!project) return `no PAI project at ${directory} — tasks here will not route`;\n\n  const existing = db\n    .prepare(\"SELECT project_id FROM aliases WHERE lower(alias) = lower(?)\")\n    .get(name) as { project_id: number } | undefined;\n\n  if (existing) {\n    // Repointing someone else's alias would silently steal their address.\n    return existing.project_id === project.id\n      ? null\n      : `alias \"${name}\" already points elsewhere — left alone`;\n  }\n\n  db.prepare(\"INSERT INTO aliases (alias, project_id) VALUES (?, ?)\").run(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\nfunction buildProvider(): TodoistProvider | null {\n  const config = loadConfig();\n  const tasks = config.tasks;\n  if (!tasks?.enabled) return null;\n\n  const aliases = loadAliasMap(openRegistry());\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 = 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 = ensureAlias(openRegistry(), 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 = 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 = 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 = 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 = 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 = 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 = 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 { Pool } from \"pg\";\n\nimport { ok, warn, err, dim, bold, header } from \"../utils.js\";\nimport { loadConfig, CONFIG_FILE } from \"../../daemon/config.js\";\nimport { createStorageBackend } from \"../../storage/factory.js\";\nimport { kgQuery } from \"../../memory/kg.js\";\nimport { backfillKgFromNotes } from \"../../memory/kg-backfill.js\";\n\n// ---------------------------------------------------------------------------\n// Helpers\n// ---------------------------------------------------------------------------\n\nasync function getPool(): Promise<{ pool: Pool; close: () => Promise<void> }> {\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.backendType !== \"postgres\") {\n    console.error(err(\"  Postgres backend unavailable — fell back to SQLite.\"));\n    process.exit(1);\n  }\n  const pool = (backend as unknown as { getPool(): Pool }).getPool();\n  return { pool, close: () => backend.close() };\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 { pool, close } = await getPool();\n  try {\n    let projectId: number | undefined;\n    if (opts.project) {\n      const r = await pool.query<{ id: number }>(\n        \"SELECT id FROM projects WHERE slug = $1 LIMIT 1\",\n        [opts.project]\n      );\n      if (r.rows.length === 0) {\n        console.error(warn(`  Project not found in Postgres: ${opts.project}`));\n      } else {\n        projectId = r.rows[0].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 kgQuery(pool, {\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 { pool, close } = await getPool();\n  try {\n    let projectId: number | undefined;\n    if (opts.project) {\n      const r = await pool.query<{ id: number }>(\n        \"SELECT id FROM projects WHERE slug = $1 LIMIT 1\",\n        [opts.project]\n      );\n      if (r.rows.length > 0) projectId = r.rows[0].id;\n    }\n\n    const triples = await kgQuery(pool, { 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 { pool, close } = await getPool();\n  try {\n    const totals = await pool.query<{\n      total: string;\n      valid: string;\n      invalidated: string;\n      subjects: string;\n      predicates: string;\n    }>(\n      `SELECT\n         COUNT(*)::text                                            AS total,\n         COUNT(*) FILTER (WHERE valid_to IS NULL)::text            AS valid,\n         COUNT(*) FILTER (WHERE valid_to IS NOT NULL)::text        AS invalidated,\n         COUNT(DISTINCT subject)::text                             AS subjects,\n         COUNT(DISTINCT predicate)::text                           AS predicates\n       FROM kg_triples`\n    );\n\n    const contradictions = await pool.query<{ count: string }>(\n      `SELECT COUNT(*)::text AS count FROM (\n         SELECT subject, predicate\n         FROM kg_triples\n         WHERE valid_to IS NULL\n         GROUP BY subject, predicate\n         HAVING COUNT(*) > 1\n       ) c`\n    );\n\n    const row = totals.rows[0] ?? {};\n    console.log();\n    console.log(header(\"  PAI KG Stats\"));\n    console.log();\n    console.log(`  ${bold(\"Total triples:\")}        ${row.total ?? \"0\"}`);\n    console.log(`  ${bold(\"Currently valid:\")}      ${row.valid ?? \"0\"}`);\n    console.log(`  ${bold(\"Invalidated:\")}          ${row.invalidated ?? \"0\"}`);\n    console.log(`  ${bold(\"Distinct subjects:\")}    ${row.subjects ?? \"0\"}`);\n    console.log(`  ${bold(\"Distinct predicates:\")}  ${row.predicates ?? \"0\"}`);\n    console.log(`  ${bold(\"Contradictions:\")}       ${contradictions.rows[0]?.count ?? \"0\"}`);\n    console.log();\n  } finally {\n    await 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 * 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 { openFederation } from \"../../memory/db.js\";\n\n// ---------------------------------------------------------------------------\n// Types\n// ---------------------------------------------------------------------------\n\ntype DbTarget = \"sqlite\" | \"postgres\";\n\ninterface CommonOpts {\n  db?: DbTarget;\n  json?: boolean;\n}\n\n// ---------------------------------------------------------------------------\n// SQLite helpers (synchronous — better-sqlite3)\n// ---------------------------------------------------------------------------\n\nfunction getSqliteDb() {\n  return openFederation();\n}\n\nfunction sqliteQuery(sql: string): { columns: string[]; rows: unknown[][] } {\n  const db = getSqliteDb();\n  try {\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  } finally {\n    db.close();\n  }\n}\n\nfunction sqliteTables(): string[] {\n  const db = getSqliteDb();\n  try {\n    const rows = db\n      .prepare(\n        \"SELECT name FROM sqlite_master WHERE type='table' AND name NOT LIKE 'sqlite_%' ORDER BY name\"\n      )\n      .all() as { name: string }[];\n    return rows.map((r) => r.name);\n  } finally {\n    db.close();\n  }\n}\n\nfunction sqliteSchema(table: string): { columns: string[]; rows: unknown[][] } {\n  const db = getSqliteDb();\n  try {\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 data = 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, data: data } as unknown as { columns: string[]; rows: unknown[][] };\n  } finally {\n    db.close();\n  }\n}\n\n// ---------------------------------------------------------------------------\n// Postgres helpers (async — pg Pool)\n// ---------------------------------------------------------------------------\n\nasync function getPool() {\n  const config = loadConfig();\n  const pgConfig = config.postgres ?? {};\n\n  const { Pool } = await import(\"pg\");\n\n  // Build connection params from config\n  const poolConfig = pgConfig.connectionString\n    ? { connectionString: pgConfig.connectionString }\n    : {\n        host: pgConfig.host ?? \"localhost\",\n        port: pgConfig.port ?? 5432,\n        database: pgConfig.database ?? \"pai\",\n        user: pgConfig.user ?? \"pai\",\n        password: pgConfig.password ?? \"pai\",\n        connectionTimeoutMillis: pgConfig.connectionTimeoutMs ?? 5000,\n      };\n\n  const pool = new Pool(poolConfig);\n\n  // Quick connectivity test\n  try {\n    const client = await pool.connect();\n    client.release();\n  } catch (e) {\n    await pool.end();\n    const msg = e instanceof Error ? e.message : String(e);\n    throw new Error(`Cannot connect to Postgres: ${msg}`);\n  }\n\n  return pool;\n}\n\nasync function postgresQuery(\n  sql: string\n): Promise<{ columns: string[]; rows: unknown[][] }> {\n  const pool = await getPool();\n  try {\n    const result = await pool.query(sql);\n    const columns = result.fields.map((f) => f.name);\n    const rows = result.rows.map((r: Record<string, unknown>) =>\n      columns.map((c) => r[c])\n    );\n    return { columns, rows };\n  } finally {\n    await pool.end();\n  }\n}\n\nasync function postgresTables(): Promise<string[]> {\n  const pool = await getPool();\n  try {\n    const result = await pool.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  } finally {\n    await pool.end();\n  }\n}\n\nasync function postgresSchema(\n  table: string\n): Promise<{ columns: string[]; rows: unknown[][] }> {\n  const pool = await getPool();\n  try {\n    const result = await pool.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  } finally {\n    await pool.end();\n  }\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    if (target === \"postgres\") {\n      result = await postgresQuery(sql);\n    } else {\n      result = sqliteQuery(sql);\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    if (target === \"postgres\") {\n      tables = await postgresTables();\n    } else {\n      tables = sqliteTables();\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    if (target === \"postgres\") {\n      result = await postgresSchema(table);\n    } else {\n      const raw = sqliteSchema(table) as unknown as {\n        columns: string[];\n        data: unknown[][];\n      };\n      result = { columns: raw.columns, rows: raw.data };\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","/**\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 type { Database } from \"better-sqlite3\";\nimport { join } from \"node:path\";\nimport { homedir } from \"node:os\";\nimport chalk from \"chalk\";\nimport { err } from \"../../utils.js\";\nimport type { SessionRow, ProjectRow } from \"./types.js\";\n\nexport function getProject(db: Database, slug: string): ProjectRow | undefined {\n  return db\n    .prepare(\n      \"SELECT id, slug, display_name, root_path, encoded_dir FROM projects WHERE slug = ?\"\n    )\n    .get(slug) as ProjectRow | 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 function resolveSession(\n  db: Database,\n  project: ProjectRow,\n  numberOrLatest: string\n): SessionRow {\n  let session: SessionRow | undefined;\n\n  if (numberOrLatest === \"latest\") {\n    session = db\n      .prepare(\n        \"SELECT * FROM sessions WHERE project_id = ? ORDER BY number DESC LIMIT 1\"\n      )\n      .get(project.id) as SessionRow | 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 = db\n      .prepare(\"SELECT * FROM sessions WHERE project_id = ? AND number = ?\")\n      .get(project.id, num) as SessionRow | 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 function upsertTag(db: Database, tagName: string): number {\n  db.prepare(\"INSERT OR IGNORE INTO tags (name) VALUES (?)\").run(tagName);\n  const row = db\n    .prepare(\"SELECT id FROM tags WHERE name = ?\")\n    .get(tagName) as { id: number };\n  return row.id;\n}\n\nexport function getSessionTags(db: Database, sessionId: number): string[] {\n  const rows = db\n    .prepare(\n      `SELECT t.name FROM tags t\n       JOIN session_tags st ON st.tag_id = t.id\n       WHERE st.session_id = ?\n       ORDER BY t.name`\n    )\n    .all(sessionId) as { name: string }[];\n  return rows.map((r) => r.name);\n}\n","/**\n * Session CRUD commands: list, info, rename, slug, tag, route, active, auto-route.\n */\n\nimport type { Database } from \"better-sqlite3\";\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 { 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 function cmdList(\n  db: Database,\n  projectSlug: string | undefined,\n  opts: { limit?: string; status?: string }\n): void {\n  const limit = parseInt(opts.limit ?? \"20\", 10);\n  const params: unknown[] = [];\n\n  let query = `\n    SELECT s.*, p.slug AS project_slug, p.display_name AS project_name\n    FROM sessions s\n    JOIN projects p ON p.id = s.project_id\n  `;\n\n  const where: string[] = [];\n  if (projectSlug) {\n    const project = getProject(db, projectSlug);\n    if (!project) {\n      console.error(err(`Project not found: ${projectSlug}`));\n      process.exitCode = 1;\n      return;\n    }\n    where.push(\"s.project_id = ?\");\n    params.push(project.id);\n  }\n  if (opts.status) {\n    where.push(\"s.status = ?\");\n    params.push(opts.status);\n  }\n\n  if (where.length) query += \" WHERE \" + where.join(\" AND \");\n  query += \" ORDER BY s.date DESC, s.number DESC\";\n  query += ` LIMIT ${limit}`;\n\n  const rows = db.prepare(query).all(...params) as (SessionRow & {\n    project_slug: string;\n    project_name: string;\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 (projectSlug) {\n    const project = getProject(db, projectSlug)!;\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 function cmdInfo(\n  db: Database,\n  projectSlug: string,\n  sessionNumber: string\n): void {\n  const project = getProject(db, projectSlug);\n  if (!project) {\n    console.error(err(`Project not found: ${projectSlug}`));\n    process.exitCode = 1;\n    return;\n  }\n\n  const session = resolveSession(db, 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 function cmdRename(\n  db: Database,\n  projectSlug: string,\n  numberOrLatest: string,\n  newSlug: string\n): void {\n  const project = getProject(db, projectSlug);\n  if (!project) {\n    console.error(err(`Project not found: ${projectSlug}`));\n    process.exitCode = 1;\n    return;\n  }\n\n  const session = resolveSession(db, 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  db.prepare(\n    \"UPDATE sessions SET slug = ?, title = ?, filename = ? WHERE id = ?\"\n  ).run(normalizedSlug, titleSlug, newFilename, session.id);\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 function cmdSlug(\n  db: Database,\n  projectSlug: string,\n  numberOrLatest: string,\n  opts: { apply?: boolean }\n): void {\n  const project = getProject(db, projectSlug);\n  if (!project) {\n    console.error(err(`Project not found: ${projectSlug}`));\n    process.exitCode = 1;\n    return;\n  }\n\n  const session = resolveSession(db, 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    cmdRename(db, projectSlug, String(session.number), generatedSlug);\n  }\n}\n\n// ---------------------------------------------------------------------------\n// tag\n// ---------------------------------------------------------------------------\n\nexport function cmdTag(\n  db: Database,\n  projectSlug: string,\n  sessionNumber: string,\n  rawTags: string[]\n): void {\n  const project = getProject(db, projectSlug);\n  if (!project) {\n    console.error(err(`Project not found: ${projectSlug}`));\n    process.exitCode = 1;\n    return;\n  }\n\n  const session = resolveSession(db, project, sessionNumber);\n\n  if (rawTags.length === 0) {\n    const current = getSessionTags(db, 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 = upsertTag(db, tagName);\n    const exists = db\n      .prepare(\"SELECT 1 FROM session_tags WHERE session_id = ? AND tag_id = ?\")\n      .get(session.id, tagId);\n    if (exists) {\n      skipped.push(tagName);\n    } else {\n      db.prepare(\n        \"INSERT INTO session_tags (session_id, tag_id) VALUES (?, ?)\"\n      ).run(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 = getSessionTags(db, 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 function cmdRoute(\n  db: Database,\n  projectSlug: string,\n  sessionNumber: string,\n  targetProjectSlug: string,\n  opts: { type?: string }\n): void {\n  const project = getProject(db, projectSlug);\n  if (!project) {\n    console.error(err(`Project not found: ${projectSlug}`));\n    process.exitCode = 1;\n    return;\n  }\n\n  const session = resolveSession(db, project, sessionNumber);\n\n  const targetProject = db\n    .prepare(\"SELECT id, slug, display_name FROM projects WHERE slug = ?\")\n    .get(targetProjectSlug) as\n    | { id: number; slug: string; display_name: string }\n    | undefined;\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\";\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  try {\n    db.prepare(\n      `INSERT INTO links (session_id, target_project_id, link_type, created_at)\n       VALUES (?, ?, ?, ?)`\n    ).run(session.id, targetProject.id, linkType, Date.now());\n  } catch {\n    console.log(\n      warn(\n        `  Link already exists: session #${session.number} → ${targetProjectSlug}`\n      )\n    );\n    return;\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 function cmdActive(\n  db: Database,\n  opts: { minutes?: string; json?: boolean }\n): void {\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 = db\n      .prepare(\n        \"SELECT slug, display_name, root_path FROM projects WHERE encoded_dir = ?\"\n      )\n      .get(entry) as\n      | { slug: string; display_name: string; root_path: string }\n      | undefined;\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, formatAutoRoute, formatAutoRouteJson } = await import(\n    \"../../../session/auto-route.js\"\n  );\n  const { openRegistry } = await import(\"../../../registry/db.js\");\n  const { createStorageBackend } = await import(\"../../../storage/factory.js\");\n  const { loadConfig } = await import(\"../../../daemon/config.js\");\n\n  const config = loadConfig();\n  const registryDb = openRegistry();\n  const federation = await createStorageBackend(config);\n\n  const targetCwd = opts.cwd ?? process.cwd();\n  const result = await autoRoute(\n    registryDb,\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 type { Database } from \"better-sqlite3\";\nimport { basename } from \"node:path\";\nimport { realpathSync } from \"node:fs\";\nimport chalk from \"chalk\";\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 function resolveProjectByCwd(\n  db: Database,\n  cwd: string\n): ProjectRow | undefined {\n  const stmt = db.prepare(\n    `SELECT id, slug, display_name, root_path, encoded_dir\n       FROM projects\n      WHERE ? LIKE root_path || '%'\n      ORDER BY length(root_path) DESC\n      LIMIT 1`\n  );\n\n  const direct = stmt.get(cwd) as ProjectRow | undefined;\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  return stmt.get(resolved) as ProjectRow | 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 function cmdPause(db: Database, opts: PauseOptions): 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  // ---- 2. Resolve project by cwd ----\n  const cwd = process.cwd();\n  const project = resolveProjectByCwd(db, 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 = db\n    .prepare(\n      \"SELECT * FROM sessions WHERE project_id = ? ORDER BY number DESC LIMIT 1\"\n    )\n    .get(project.id) as SessionRow | 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 = db\n      .prepare(\n        `SELECT p.slug, p.root_path, COUNT(s.id) AS n\n           FROM projects p LEFT JOIN sessions s ON s.project_id = p.id\n          WHERE p.id != ? AND (p.slug = ? OR p.slug LIKE ? || '-%' OR ? LIKE p.slug || '-%')\n          GROUP BY p.id HAVING n > 0\n          ORDER BY n DESC LIMIT 3`\n      )\n      .all(project.id, project.slug, project.slug, project.slug) as Array<{\n      slug: string;\n      root_path: string;\n      n: number;\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.n} — 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 { Database } from \"better-sqlite3\";\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 function cmdHandover(\n  db: Database,\n  projectSlug: string | undefined,\n  numberOrLatest: string | undefined,\n  sessionId?: string\n): void {\n  // ---- 1. Resolve project ----\n  let project: ProjectRow | undefined;\n\n  if (projectSlug) {\n    project = db\n      .prepare(\n        \"SELECT id, slug, display_name, root_path, encoded_dir FROM projects WHERE slug = ?\"\n      )\n      .get(projectSlug) as ProjectRow | 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 = resolveProjectByCwd(db, 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 = db\n      .prepare(\n        \"SELECT * FROM sessions WHERE project_id = ? ORDER BY number DESC LIMIT 1\"\n      )\n      .get(project!.id) as SessionRow | undefined;\n  } else {\n    const num = parseInt(nol, 10);\n    if (!isNaN(num)) {\n      session = db\n        .prepare(\"SELECT * FROM sessions WHERE project_id = ? AND number = ?\")\n        .get(project!.id, num) as SessionRow | 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 type { Database } from \"better-sqlite3\";\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(db: Database, opts: AutosaveOptions): Promise<void> {\n  const minGap = parseInt(opts.minGap ?? String(DEFAULT_MIN_GAP), 10);\n  const cwd = process.cwd();\n\n  const project = resolveProjectByCwd(db, 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 = db\n    .prepare(\n      \"SELECT * FROM sessions WHERE project_id = ? ORDER BY number DESC LIMIT 1\"\n    )\n    .get(project.id) 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 type { Database } from \"better-sqlite3\";\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  db: Database,\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 = scanSessions(db, {\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>\\ngo\"\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>\\ngo\"\n *   4. Else (no uuid known):\n *        exec  claude --name \"<friendlyName>\" \"/Name <friendlyName>\\ngo\"\n *\n * Why both --name AND /Name?\n *   --name <friendlyName>       → sets Claude Code's internal session label\n *   \"/Name <friendlyName>\\ngo\"  → runs the /Name slash command via AIBroker, which updates\n *                                  iTerm tab title, statusline, and AIBroker session registry;\n *                                  the \\ngo on the next line triggers PAI's ## Continue resume\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 type { Database } from \"better-sqlite3\";\nimport { spawnSync } from \"node:child_process\";\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 { printExitDir } from \"../../lib/exit-dir.js\";\nimport { probeResume } from \"../../lib/launch.js\";\n\n// ---------------------------------------------------------------------------\n// Command\n// ---------------------------------------------------------------------------\n\nexport function cmdGoto(\n  db: Database,\n  query: string,\n  opts: { dryRun?: boolean }\n): void {\n  // ---- 1. Resolve name → session ----\n  const allSessions = scanSessions(db, { 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  // Initial prompt: /Name sets tab/statusline via AIBroker; \\ngo triggers ## Continue\n  const promptArg = `/Name ${name}\\ngo`;\n\n  // ---- 5. Dry-run mode ----\n  if (opts.dryRun) {\n    if (resumableUuid) {\n      const argvResume = `claude --resume ${resumableUuid} --name \"${name}\" \"/Name ${name}\\\\ngo\"`;\n      const argvFresh = `claude --name \"${name}\" \"/Name ${name}\\\\ngo\"`;\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      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}\" \"/Name ${name}\\\\ngo\"`;\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  if (resumableUuid) {\n    // Probe first\n    const probe = probeResume(resumableUuid, projectDir);\n\n    if (probe.ok) {\n      // Happy path: session is resumable\n      const result = spawnSync(\n        \"claude\",\n        [\"--resume\", resumableUuid, \"--name\", name, promptArg],\n        {\n          cwd: projectDir,\n          stdio: \"inherit\",\n          env: process.env,\n        }\n      );\n      if (result.error) {\n        console.error(err(`Failed to launch claude: ${result.error.message}`));\n        process.exitCode = 1;\n        return;\n      }\n      printExitDir(projectDir);\n      process.exitCode = result.status ?? 0;\n      return;\n    } else {\n      // Fallback path\n      process.stderr.write(\n        chalk.yellow(\n          `\\n  Resume failed for ${resumableUuid.slice(0, 8)}: ${probe.reason ?? \"unknown error\"}\\n` +\n            `  Starting fresh session in same directory.\\n\\n`\n        )\n      );\n      const result = spawnSync(\n        \"claude\",\n        [\"--name\", name, promptArg],\n        {\n          cwd: projectDir,\n          stdio: \"inherit\",\n          env: process.env,\n        }\n      );\n      if (result.error) {\n        console.error(err(`Failed to launch claude: ${result.error.message}`));\n        process.exitCode = 1;\n        return;\n      }\n      printExitDir(projectDir);\n      process.exitCode = result.status ?? 0;\n      return;\n    }\n  } else {\n    // No UUID at all — fresh start\n    const result = spawnSync(\n      \"claude\",\n      [\"--name\", name, promptArg],\n      {\n        cwd: projectDir,\n        stdio: \"inherit\",\n        env: process.env,\n      }\n    );\n    if (result.error) {\n      console.error(err(`Failed to launch claude: ${result.error.message}`));\n      process.exitCode = 1;\n      return;\n    }\n    printExitDir(projectDir);\n    process.exitCode = result.status ?? 0;\n  }\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 type { Database } from \"better-sqlite3\";\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(\n  sessionCmd: Command,\n  getDb: () => Database\n): 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(getDb(), 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((projectSlug: string, number: string) => {\n      cmdInfo(getDb(), 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((projectSlug: string, number: string, newSlug: string) => {\n      cmdRename(getDb(), 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      (projectSlug: string, number: string, opts: { apply?: boolean }) => {\n        cmdSlug(getDb(), 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((projectSlug: string, number: string, tags: string[]) => {\n      cmdTag(getDb(), 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      (\n        projectSlug: string,\n        number: string,\n        targetProject: string,\n        opts: { type?: string }\n      ) => {\n        cmdRoute(getDb(), 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      (\n        projectSlug: string | undefined,\n        numberOrLatest: string | undefined,\n        opts: { sessionId?: string }\n      ) => {\n        cmdHandover(getDb(), 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 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(getDb(), 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((opts: { minutes?: string; json?: boolean }) => {\n      cmdActive(getDb(), 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(getDb(), 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>\\\\ngo' as the initial prompt so the PAI ## Continue hook fires.\"\n    )\n    .option(\"--skip-name\", \"Do not prepend /Name to set the session name in chrome\")\n    .option(\"--skip-go\", \"Do not append \\\\ngo to trigger PAI auto-resume (## Continue)\")\n    .option(\"--dry-run\", \"Print the exact argv and cwd, then exit without launching\")\n    .action(\n      (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        cmdGoto(getDb(), 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((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      cmdPause(getDb(), {\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 for runs without --provider/--class\")\n    .action((name: string) => {\n      try {\n        useProvider(name);\n        console.log(ok(`active provider: ${name}`));\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(pname, workers.providers[pname], workersLogDir(workers));\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  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    .action((what: string | undefined, model: string | undefined, opts: { provider?: string }) => {\n      try {\n        const { workers } = readWorkersSection();\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 = workers.providers[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 = fresh.providers[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 * 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 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, mergeWorker } from \"../../../workers/worktree.js\";\nimport { waitWorkers } from \"../../../workers/wait.js\";\nimport { describeMcp } from \"../../../workers/mcp.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(\"--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          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            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        console.log(psOutput(currentLogDir(), opts.all === true));\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 (or one shared pane for this session)\")\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);\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    .action((id: string) => {\n      try {\n        console.log(mergeWorker(currentLogDir(), id));\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(\"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` (the worker may actuate) or `me` (the operator keeps them);\\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        if (!loadStatus(currentLogDir(), id)) {\n          fail(new Error(`no worker named \"${id}\"`));\n          return;\n        }\n        const proc = spawn(\"clickr\", [\"controls\", who], { 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) console.log(`controls → ${who === \"you\" ? id : \"operator\"}`);\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    .action(async (id: string, text: string, opts: { printId?: boolean; pane?: boolean }) => {\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        let newId = \"\";\n        const rc = await runWorker({\n          providerFlag: old.provider,\n          modelFlag: old.model,\n          label: `↩ ${old.label}`,\n          noPane: opts.pane === false,\n          claudeArgs: [\"--resume\", old.claudeSession, \"-p\", text],\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  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","/**\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 type { Database } from \"better-sqlite3\";\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 {\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 function getAllProjects(db: Database): ProjectRow[] {\n  return db\n    .prepare(\n      \"SELECT id, slug, display_name, root_path, encoded_dir, claude_notes_dir FROM projects WHERE status = 'active' ORDER BY slug\"\n    )\n    .all() as ProjectRow[];\n}\n\nexport function getProject(\n  db: Database,\n  slug: string\n): ProjectRow | undefined {\n  return db\n    .prepare(\n      \"SELECT id, slug, display_name, root_path, encoded_dir, claude_notes_dir FROM projects WHERE slug = ?\"\n    )\n    .get(slug) as ProjectRow | undefined;\n}\n\nfunction getProjectSessions(\n  db: Database,\n  projectId: number\n): SessionRow[] {\n  return db\n    .prepare(\"SELECT * FROM sessions WHERE project_id = ? ORDER BY number ASC\")\n    .all(projectId) 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 function analyzeProject(\n  db: Database,\n  project: ProjectRow\n): CleanupPlan | null {\n  const notesDirPaths = findAllNotesDirs(project);\n  if (notesDirPaths.length === 0) return null;\n\n  const dbSessions = getProjectSessions(db, 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 type { Database } from \"better-sqlite3\";\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// Postgres 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 { PostgresBackend } = await import(\"../../../storage/postgres.js\");\n    const config = loadConfig();\n    if (config.storageBackend !== \"postgres\") return 0;\n    const pgBackend = new PostgresBackend(config.postgres ?? {});\n    const connErr = await pgBackend.testConnection();\n    if (connErr) { await pgBackend.close(); return 0; }\n    const pool = (pgBackend as unknown as {\n      pool: { query: (sql: string, params: string[]) => Promise<{ rows: Array<{ n: string }> }> };\n    }).pool;\n    const placeholders = oldPaths.map((_, i) => `$${i + 1}`).join(\", \");\n    const result = await pool.query(\n      `SELECT COUNT(*)::text AS n FROM pai_files WHERE path IN (${placeholders})`,\n      oldPaths\n    );\n    await pgBackend.close();\n    return parseInt(result.rows[0]?.n ?? \"0\", 10);\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 { PostgresBackend } = await import(\"../../../storage/postgres.js\");\n    const config = loadConfig();\n    if (config.storageBackend !== \"postgres\") return 0;\n    const pgBackend = new PostgresBackend(config.postgres ?? {});\n    const connErr = await pgBackend.testConnection();\n    if (connErr) {\n      process.stderr.write(`[session-cleanup] Postgres unavailable (${connErr}). Skipping vector DB path update.\\n`);\n      await pgBackend.close();\n      return 0;\n    }\n    const pool = (pgBackend as unknown as {\n      pool: { connect: () => Promise<{\n        query: (sql: string, params: string[]) => Promise<{ rowCount: number | null }>;\n        release: () => void;\n      }> };\n    }).pool;\n    const client = await pool.connect();\n    let filesUpdated = 0;\n    try {\n      await client.query(\"BEGIN\", []);\n      for (const { oldPath, newPath } of moves) {\n        const r = await client.query(\"UPDATE pai_files SET path = $1 WHERE path = $2\", [newPath, oldPath]);\n        filesUpdated += r.rowCount ?? 0;\n        await client.query(\"UPDATE pai_chunks SET path = $1 WHERE path = $2\", [newPath, oldPath]);\n      }\n      await client.query(\"COMMIT\", []);\n    } catch (e) {\n      await client.query(\"ROLLBACK\", []);\n      throw e;\n    } finally {\n      client.release();\n    }\n    await pgBackend.close();\n    return filesUpdated;\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  db: Database,\n  plans: CleanupPlan[],\n  skipReindex: boolean\n): Promise<void> {\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            db.prepare(\"DELETE FROM sessions WHERE id = ?\").run(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            db.prepare(\n              \"UPDATE sessions SET slug = ?, title = ?, filename = ? WHERE id = ?\"\n            ).run(normalizedSlug, autoName, newFilename, c.session.id);\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          const renumberDb = db.transaction(() => {\n            for (const { session, newNum } of dbRenumbers) {\n              db.prepare(\"UPDATE sessions SET number = ? WHERE id = ?\").run(\n                -newNum,\n                session.id\n              );\n            }\n            for (const { session, newNum, newFilename } of dbRenumbers) {\n              db.prepare(\n                \"UPDATE sessions SET number = ?, filename = ? WHERE id = ?\"\n              ).run(newNum, newFilename, session.id);\n            }\n          });\n          try {\n            renumberDb();\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            db.prepare(\"UPDATE sessions SET filename = ? WHERE id = ?\").run(\n              newFilenameInDb,\n              c.session.id\n            );\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 type { Database } from \"better-sqlite3\";\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(\n  sessionCmd: Command,\n  getDb: () => Database\n): 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 db = getDb();\n        const dryRun = !opts.execute;\n        const skipReindex = opts.reindex === false;\n\n        let projects;\n        if (projectSlug) {\n          const p = getProject(db, 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 = getAllProjects(db);\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 = analyzeProject(db, 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(db, 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 type { Database } from \"better-sqlite3\";\nimport { readFileSync, writeFileSync, renameSync } from \"node:fs\";\nimport { basename } from \"node:path\";\nimport chalk from \"chalk\";\nimport type { ProjectRow } from \"./types.js\";\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 function cmdEnd(db: Database, opts: PauseOptions): 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  cmdPause(db, opts);\n\n  // ---- Step 2: Locate the project ----\n  const cwd = process.cwd();\n  const project = db\n    .prepare(\n      `SELECT id, slug, display_name, root_path, encoded_dir\n         FROM projects\n        WHERE ? LIKE root_path || '%'\n        ORDER BY length(root_path) DESC\n        LIMIT 1`\n    )\n    .get(cwd) as ProjectRow | undefined;\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 type { Database } from \"better-sqlite3\";\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\nfunction getProjects(db: Database, includeArchived: boolean): RegisteredProject[] {\n  try {\n    const rows = db\n      .prepare(`\n        SELECT p.slug, p.display_name, p.root_path, p.status,\n               COUNT(s.id) AS session_count,\n               MAX(s.created_at) AS last_active\n        FROM projects p\n        LEFT JOIN sessions s ON s.project_id = p.id\n        GROUP BY p.id\n        ORDER BY last_active DESC NULLS LAST, p.updated_at DESC\n      `)\n      .all() as RegisteredProject[];\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) => 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 = (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 ? 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(db: Database, 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 = scanSessions(db, { limit: 500, filter: \"named\" });\n    const projects = getProjects(db, 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 = scanSessions(db, { limit: 500, filter: \"named\" });\n  const projects = getProjects(db, 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 = getProjects(db, 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 = (rec: PickRecord): boolean => {\n    if (!rec.slug) return false;\n    try {\n      const ts = now();\n      const res = db\n        .prepare(\n          \"UPDATE projects SET status = 'archived', archived_at = ?, updated_at = ? WHERE slug = ? AND status = 'active'\"\n        )\n        .run(ts, ts, rec.slug);\n      return res.changes > 0;\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 { join, dirname } from \"node:path\";\nimport { fileURLToPath } from \"node:url\";\nimport { openRegistry } from \"../registry/db.js\";\nimport type { Database } from \"better-sqlite3\";\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 { 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 __dirname = dirname(fileURLToPath(import.meta.url));\n    const pkgPath = join(__dirname, \"../../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// Lazy database singleton\n// ---------------------------------------------------------------------------\n\nlet _db: Database | null = null;\n\nfunction getDb(): Database {\n  if (!_db) {\n    try {\n      _db = openRegistry();\n    } catch (e) {\n      // Throw instead of process.exit(1): the throw propagates up through\n      // the calling action and parseAsync() to the top-level catch in\n      // cli/index.ts, which prints the message and sets process.exitCode.\n      // That lets the process exit naturally, which flushes stdout/stderr\n      // even when either is a pipe (process.exit() does not — see\n      // src/cli/lib/exit.ts).\n      throw new Error(`Failed to open PAI registry: ${e}`);\n    }\n  }\n  return _db;\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 / resume / fresh — 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, getDb);\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, getDb);\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, getDb);\n  registerSessionCleanupCommand(sessionCmd, getDb);\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(getDb(), 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, getDb);\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, getDb);\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 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, getDb);\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, getDb);\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(getDb(), {\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(getDb(), {\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((name: string, opts: { dryRun?: boolean }) => {\n      cmdGoto(getDb(), 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((identifier: string) => {\n      const db = getDb();\n      const project = resolveIdentifier(db, 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        db.prepare(\n          \"UPDATE projects SET root_path = ?, encoded_dir = ?, updated_at = ? WHERE id = ?\"\n        ).run(newPath, newEncoded, ts, project.id);\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      (projectSlug: string | undefined, opts: { limit?: string; status?: string }) => {\n        cmdNotesList(getDb(), 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      (projectSlug: string | undefined, opts: { limit?: string; status?: string }) => {\n        cmdNotesList(getDb(), 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 resume, or start fresh\\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    .action(async (query: string | undefined, pick: string | undefined, opts: { auto?: boolean; dryRun?: boolean; n?: string; all?: boolean; list?: 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(getDb(), { all: opts.all, dryRun: opts.dryRun });\n        return;\n      }\n      const pickN = pick !== undefined ? parseInt(pick, 10) : undefined;\n      await cmdMain(getDb(), 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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"}