{"version":3,"file":"pick-D7-XuJqF.mjs","names":["dim","ok","bold","getProject","upsertTag","dim","warn","bold","ok","cmdList","cmdInfo","cmdTag","upsertTag","getProject","ok","bold","dim","warn","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","CONFIG_FILE","ok","dim","CONFIG_FILE","warn","bold","dim","ok","cmdInstall","warn","dim","ok","cmdStatus","bold","HOME","cmdStatus","bold","ok","dim","warn","HOME","REGISTRY_DB","CONFIG_FILE","BACKUPS_DIR","DOCKER_CONTAINER","PG_DATABASE","PG_USER","dim","ok","warn","bold","dim","bold","ok","warn","line","CONFIG_FILE","getTemplatesDir","getTemplatesDir","getTemplatesDir","getTemplatesDir","useSymlinks","CONFIG_FILE","normalize","normalizeName","line","CONFIG_FILE","findClaudeNotesDir","findClaudeNotesDir","CONFIG_FILE","dim","ok","warn","cmdStatus","bold","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","fmtTs","dim","statusColor","warn","bold","dim","ok","warn","run","makeClient","warn","bold","ok","dim","run","STATE_FILE","loadState","saveState","CONFIG_FILE","bold","dim","warn","ok","getPool","dim","shorten","bold","warn","ok","cmdQuery","cmdList","dim","bold","dim","tokenize","getProject","toTitleCase","getProject","warn","dim","bold","toTitleCase","ok","findNotesDir","dim","bold","ok","dim","warn","dim","bold","warn","ok","dim","ok","findNotesDir","dim","warn","ok","dim","ok","warn","warn","dim","normalizeName","dim"],"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/claude-json.ts","../src/cli/commands/mcp.ts","../src/cli/commands/daemon-status.ts","../src/cli/commands/daemon.ts","../src/cli/commands/backup.ts","../src/cli/commands/restore.ts","../src/config/json-store.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/cli/commands/setup/index.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/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/cli/commands/skill.ts","../src/cli/commands/update.ts","../src/cli/commands/notify.ts","../src/tasks/dispatch.ts","../src/tasks/transport/aibroker.ts","../src/tasks/scheduler.ts","../src/tasks/poller.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/memory/kg-backfill.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/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/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"],"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\";\nimport { err } from \"../../utils.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    console.error(err(`Project not found: ${slug}`));\n    process.exit(1);\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.exit(1);\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.exit(1);\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.exit(1);\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.exit(1);\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.exit(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.exit(1);\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    process.exit(0);\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.exit(1);\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.exit(1);\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.exit(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.exit(1);\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.exit(1);\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.exit(1);\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 { join } from \"node:path\";\nimport { homedir } from \"node:os\";\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 } from \"./helpers.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 = join(homedir(), '.config', 'pai', 'config.json');\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 = join(homedir(), '.config', 'pai', 'config.json');\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(join(homedir(), '.config', 'pai'), { 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\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.exit(1);\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.exit(1);\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.exit(1);\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.exit(1);\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.exit(1);\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 * 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.exit(1);\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.exit(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.exit(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.exit(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.exit(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.exit(1);\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.exit(1);\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 } 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.exit(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","/** 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.exit(1);\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.exit(1);\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.exit(1);\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.exit(1);\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 { homedir } from \"node:os\";\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 { 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 = join(homedir(), \".pai\", \"registry.db\");\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\";\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.exit(1);\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.exit(1);\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: join(homedir(), \".pai\", \"registry.db\"),\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.exit(1);\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.exit(1);\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.exit(1);\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.exit(1);\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.exit(1);\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.exit(1);\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 { existsSync, readFileSync, writeFileSync } from \"node:fs\";\nimport chalk from \"chalk\";\nimport { openFederation } from \"../../../memory/db.js\";\nimport { renderTable, dim, bold, ok, err, warn, fmtDate } from \"../../utils.js\";\nimport { loadConfig, CONFIG_FILE, ensureConfigDir } 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.exit(1);\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.exit(1);\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 ~/.config/pai/config.json\")\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: ${CONFIG_FILE}`));\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.exit(1);\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.exit(1);\n      }\n\n      let fileConfig: Record<string, unknown> = {};\n      if (existsSync(CONFIG_FILE)) {\n        try {\n          fileConfig = JSON.parse(readFileSync(CONFIG_FILE, \"utf-8\")) as Record<string, unknown>;\n        } catch {\n          console.error(err(`Could not parse ${CONFIG_FILE}`));\n          process.exit(1);\n        }\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.exit(1);\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.exit(1);\n        }\n      }\n\n      (fileConfig.search as Record<string, unknown>)[key] = parsed;\n\n      try {\n        ensureConfigDir();\n        writeFileSync(CONFIG_FILE, JSON.stringify(fileConfig, null, 2) + \"\\n\", \"utf-8\");\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.exit(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 `~/.config/pai/config.json` 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 { readFileSync, writeFileSync, existsSync, renameSync } from \"node:fs\";\nimport { CONFIG_FILE } 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  if (!existsSync(CONFIG_FILE)) return {};\n  try {\n    return JSON.parse(readFileSync(CONFIG_FILE, \"utf-8\")) as ConfigShape;\n  } catch (e) {\n    console.error(err(\"pai identity: \") + `Could not parse ${CONFIG_FILE}: ${String(e)}`);\n    process.exit(1);\n  }\n}\n\n/**\n * Write via a temp file and rename, so an interrupted write cannot leave the\n * config truncated. The daemon reads this file; a half-written one takes the\n * whole system down rather than just this command.\n */\nfunction writeConfig(config: ConfigShape): void {\n  const tmp = `${CONFIG_FILE}.identity.tmp`;\n  writeFileSync(tmp, JSON.stringify(config, null, 2) + \"\\n\", \"utf-8\");\n  renameSync(tmp, CONFIG_FILE);\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.exit(1);\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.exit(1);\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 * 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 server in ~/.claude.json\n * status   — Show whether the PAI MCP server is registered and the binary exists\n */\n\nimport type { Command } from \"commander\";\nimport { existsSync, readFileSync, writeFileSync } from \"node:fs\";\nimport { join, dirname } from \"node:path\";\nimport { homedir } from \"node:os\";\nimport { fileURLToPath } from \"node:url\";\nimport { ok, warn, err, dim, bold } from \"../utils.js\";\nimport { readClaudeJson, writeClaudeJson, CLAUDE_JSON_PATH } from \"../../config/claude-json.js\";\n\n// ---------------------------------------------------------------------------\n// Paths\n// ---------------------------------------------------------------------------\n\n// CLAUDE_JSON_PATH now imported from src/config/claude-json.ts\n\n/**\n * Resolve the absolute path to the 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 mcp/index.mjs.\n */\nfunction getMcpBinPath(): string {\n  const __filename = fileURLToPath(import.meta.url);\n  const __dirname = dirname(__filename);\n  // dist/cli/index.mjs  →  dist/  →  dist/mcp/index.mjs\n  return join(__dirname, \"../mcp/index.mjs\");\n}\n\n// ---------------------------------------------------------------------------\n// Read / write ~/.claude.json safely\n// ---------------------------------------------------------------------------\n\n// Moved to src/config/claude-json.ts — these were duplicated here and in\n// daemon.ts, and both copies silently turned an unreadable file into {},\n// which the following write then made permanent.\n\n// ---------------------------------------------------------------------------\n// install\n// ---------------------------------------------------------------------------\n\nfunction cmdInstall(): void {\n  const mcpBin = getMcpBinPath();\n\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\n  if (\"pai\" in servers) {\n    console.log(warn(\"PAI MCP server is already registered in ~/.claude.json.\"));\n    console.log(dim(`  Entry: ${JSON.stringify(servers[\"pai\"])}`));\n    console.log(dim(\"  Use `pai mcp status` to verify the configuration.\"));\n    return;\n  }\n\n  servers[\"pai\"] = {\n    command: \"node\",\n    args: [mcpBin],\n  };\n\n  try {\n    writeClaudeJson(config);\n  } catch (e) {\n    console.error(err(`Failed to write ~/.claude.json: ${e}`));\n    process.exit(1);\n  }\n\n  console.log(ok(\"PAI MCP server registered in ~/.claude.json.\"));\n  console.log(dim(`  Binary: ${mcpBin}`));\n  console.log(dim(\"\"));\n  console.log(dim(\"  Restart Claude Code to activate the PAI MCP tools:\"));\n  console.log(dim(\"    memory_search, memory_get, project_info,\"));\n  console.log(dim(\"    project_list, session_list, registry_search\"));\n\n  if (!existsSync(mcpBin)) {\n    console.log();\n    console.log(\n      warn(`  Note: MCP binary not found at ${mcpBin}`)\n    );\n    console.log(dim(\"  Run `bun run build` to compile it first.\"));\n  }\n}\n\n// ---------------------------------------------------------------------------\n// status\n// ---------------------------------------------------------------------------\n\nfunction cmdStatus(): void {\n  const mcpBin = getMcpBinPath();\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  const registered = \"pai\" in servers;\n  const binExists = existsSync(mcpBin);\n\n  console.log();\n  console.log(bold(\"  PAI MCP Server Status\"));\n  console.log();\n\n  if (registered) {\n    const entry = servers[\"pai\"];\n    console.log(ok(`  Registered in ~/.claude.json`));\n    console.log(dim(`    Config: ${JSON.stringify(entry)}`));\n  } else {\n    console.log(warn(`  NOT registered in ~/.claude.json`));\n    console.log(dim(`    Run: pai mcp install`));\n  }\n\n  console.log();\n\n  if (binExists) {\n    console.log(ok(`  MCP binary found: ${mcpBin}`));\n  } else {\n    console.log(warn(`  MCP binary NOT found: ${mcpBin}`));\n    console.log(dim(\"    Run: bun run build\"));\n  }\n\n  console.log();\n\n  if (registered && binExists) {\n    console.log(dim(\"  Status: READY — restart Claude Code to use PAI tools\"));\n  } else if (registered && !binExists) {\n    console.log(warn(\"  Status: NEEDS BUILD — run `bun run build`\"));\n  } else {\n    console.log(dim(\"  Status: NOT INSTALLED — run `pai mcp install`\"));\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 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 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 } from \"../../daemon/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\";\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.exit(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.exit(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.exit(1);\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.exit(1);\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.exit(result.status ?? 1);\n  }\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.exit(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(\"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 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 } from \"../../daemon/config.js\";\n\n// ---------------------------------------------------------------------------\n// Paths\n// ---------------------------------------------------------------------------\n\nconst HOME = homedir();\nconst REGISTRY_DB = join(HOME, \".pai\", \"registry.db\");\nconst CONFIG_FILE = join(HOME, \".config\", \"pai\", \"config.json\");\nconst BACKUPS_DIR = join(HOME, \".pai\", \"backups\");\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/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 = join(HOME, \".pai\", \"federation.db\");\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","/**\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 } 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\";\n\n// ---------------------------------------------------------------------------\n// Paths\n// ---------------------------------------------------------------------------\n\nconst HOME = homedir();\nconst REGISTRY_DB = join(HOME, \".pai\", \"registry.db\");\nconst CONFIG_FILE = join(HOME, \".config\", \"pai\", \"config.json\");\nconst BACKUPS_DIR = join(HOME, \".pai\", \"backups\");\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.exit(1);\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.exit(1);\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        process.exit(0);\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(join(HOME, \".pai\"), { 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(join(HOME, \".config\", \"pai\"), { 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\"), join(HOME, \".pai\", \"federation.db\"));\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.exit(1);\n      }\n    });\n}\n","/**\n * json-store.ts — read/write JSON config files without destroying them\n *\n * The failure this exists to prevent:\n *\n *     try { return JSON.parse(read(path)); } catch { return {}; }\n *     ... later ...\n *     write(path, JSON.stringify(ourData));\n *\n * An unreadable file becomes an empty object, and the next write makes that\n * permanent. Silently, exit code 0. This shape appeared three times in this\n * repo against three different files, and twice in AIBroker.\n *\n * The distinction that matters is between *missing* and *unreadable*:\n *\n *   missing     — legitimate first run. Start fresh; writing is safe.\n *   unreadable  — the file exists and we could not parse it. Those bytes are\n *                 the only copy of something. Never overwrite them.\n *\n * Collapsing the second into the first is the bug.\n *\n * NOT everything deserves this guard. For a transient buffer — an undelivered\n * message queue, a cache — starting fresh IS the correct recovery, and\n * refusing to write would disable the feature permanently. Use `writeJsonAtomic`\n * alone there: it still prevents a crash from truncating a good file, without\n * blocking recovery. Reserve `readJsonStrict` for data a user cannot rebuild.\n */\n\nimport {\n  existsSync,\n  readFileSync,\n  writeFileSync,\n  copyFileSync,\n  renameSync,\n  unlinkSync,\n  mkdirSync,\n} from \"node:fs\";\nimport { dirname } from \"node:path\";\n\n/**\n * Read a JSON file, distinguishing \"absent\" from \"damaged\".\n *\n * @param path   file to read\n * @param label  how to name it to the user, e.g. \"~/.claude.json\"\n * @throws if the file exists but cannot be read or parsed\n */\nexport function readJsonStrict(path: string, label = path): Record<string, unknown> {\n  if (!existsSync(path)) return {};\n\n  let raw: string;\n  try {\n    raw = readFileSync(path, \"utf8\");\n  } catch (e) {\n    throw new Error(\n      `Could not read ${label}: ${e instanceof Error ? e.message : String(e)}\\n` +\n      `Refusing to continue — writing now would replace its contents with ours alone.`\n    );\n  }\n\n  try {\n    return JSON.parse(raw) as Record<string, unknown>;\n  } catch (e) {\n    throw new Error(\n      `${label} exists but is not valid JSON: ${e instanceof Error ? e.message : String(e)}\\n` +\n      `Refusing to continue — overwriting it would destroy whatever it holds.\\n` +\n      `Repair the file, or move it aside and re-run this command.`\n    );\n  }\n}\n\n/**\n * Write JSON without risking the existing file.\n *\n * Keeps a `.bak-pai` copy of the previous contents, then writes to a temp file\n * and renames. Rename is atomic within a filesystem, so a crash mid-write\n * leaves the original intact rather than truncated — which is how these files\n * become corrupt in the first place.\n */\nexport function writeJsonAtomic(\n  path: string,\n  data: Record<string, unknown>,\n  opts: { backup?: boolean; label?: string } = {}\n): void {\n  const { backup = true, label = path } = opts;\n  const serialized = JSON.stringify(data, null, 2) + \"\\n\";\n\n  const dir = dirname(path);\n  if (!existsSync(dir)) mkdirSync(dir, { recursive: true });\n\n  if (backup && existsSync(path)) {\n    try {\n      copyFileSync(path, `${path}.bak-pai`);\n    } catch (e) {\n      throw new Error(\n        `Could not back up ${label}: ${e instanceof Error ? e.message : String(e)}\\n` +\n        `Refusing to write without a backup.`\n      );\n    }\n  }\n\n  const tmp = `${path}.tmp-pai-${process.pid}`;\n  try {\n    writeFileSync(tmp, serialized, \"utf8\");\n    renameSync(tmp, path);\n  } catch (e) {\n    try { if (existsSync(tmp)) unlinkSync(tmp); } catch { /* best effort */ }\n    throw new Error(\n      `Failed to write ${label}: ${e instanceof Error ? e.message : String(e)}\\n` +\n      `The original is unchanged.`\n    );\n  }\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, readFileSync, writeFileSync, mkdirSync } from \"node:fs\";\nimport { homedir } from \"node:os\";\nimport { join } from \"node:path\";\nimport { readJsonStrict, writeJsonAtomic } from \"../../../config/json-store.js\";\nimport { spawnSync } from \"node:child_process\";\nimport chalk from \"chalk\";\nimport { CONFIG_DIR, CONFIG_FILE } from \"../../../daemon/config.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    process.exit(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 readJsonStrict(CONFIG_FILE, \"~/.config/pai/config.json\");\n}\n\nexport function writeConfigRaw(data: Record<string, unknown>): void {\n  writeJsonAtomic(CONFIG_FILE, data, { label: \"~/.config/pai/config.json\" });\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\";\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/pai/config.json\"));\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 ${PAI_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 paiDir = join(homedir(), \".claude\");\n\n  const result = mergeSettings({\n    env: {\n      PAI_DIR: paiDir,\n      CLAUDE_AUTOCOMPACT_PCT_OVERRIDE: \"80\",\n      CLAUDE_CODE_MAX_OUTPUT_TOKENS: \"64000\",\n      DA: daName,\n    },\n    hooks: [\n      { hookType: \"SessionStart\", command: \"${PAI_DIR}/Hooks/session-commands.mjs\" },\n      { hookType: \"SessionStart\", command: \"${PAI_DIR}/Hooks/load-core-context.mjs\" },\n      { hookType: \"SessionStart\", command: \"${PAI_DIR}/Hooks/load-project-context.mjs\" },\n      { hookType: \"SessionStart\", command: \"${PAI_DIR}/Hooks/initialize-session.mjs\" },\n      { hookType: \"SessionStart\", command: \"${PAI_DIR}/Hooks/capture-all-events.mjs --event-type SessionStart\" },\n      { hookType: \"SessionStart\", matcher: \"compact\", command: \"${PAI_DIR}/Hooks/post-compact-inject.mjs\" },\n      { hookType: \"UserPromptSubmit\", command: \"${PAI_DIR}/Hooks/cleanup-session-files.mjs\" },\n      { hookType: \"UserPromptSubmit\", command: \"${PAI_DIR}/Hooks/update-tab-titles.mjs\" },\n      { hookType: \"UserPromptSubmit\", command: \"${PAI_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: \"${PAI_DIR}/Hooks/pai-session-autosave.sh\" },\n      { hookType: \"PreToolUse\", matcher: \"Bash\", command: \"${PAI_DIR}/Hooks/security-validator.mjs\" },\n      { hookType: \"PreToolUse\", matcher: \"*\", command: \"${PAI_DIR}/Hooks/capture-all-events.mjs --event-type PreToolUse\" },\n      { hookType: \"PostToolUse\", matcher: \"TodoWrite\", command: \"${PAI_DIR}/Hooks/sync-todo-to-md.mjs\" },\n      { hookType: \"PostToolUse\", matcher: \"*\", command: \"${PAI_DIR}/Hooks/capture-all-events.mjs --event-type PostToolUse\" },\n      { hookType: \"PostToolUse\", matcher: \"*\", command: \"${PAI_DIR}/Hooks/capture-tool-output.mjs\" },\n      { hookType: \"PostToolUse\", matcher: \"*\", command: \"${PAI_DIR}/Hooks/update-tab-on-action.mjs\" },\n      { hookType: \"PostToolUse\", matcher: \"*\", command: \"${PAI_DIR}/Hooks/pai-session-autosave.sh\" },\n      { hookType: \"Stop\", command: \"${PAI_DIR}/Hooks/stop-hook.mjs\" },\n      { hookType: \"Stop\", command: \"${PAI_DIR}/Hooks/capture-all-events.mjs --event-type Stop\" },\n      { hookType: \"Stop\", command: \"${PAI_DIR}/Hooks/pai-session-stop.sh\" },\n      { hookType: \"SubagentStop\", command: \"${PAI_DIR}/Hooks/subagent-stop-hook.mjs\" },\n      { hookType: \"SubagentStop\", command: \"${PAI_DIR}/Hooks/capture-all-events.mjs --event-type SubagentStop\" },\n      { hookType: \"SessionEnd\", command: \"${PAI_DIR}/Hooks/capture-session-summary.mjs\" },\n      { hookType: \"SessionEnd\", command: \"${PAI_DIR}/Hooks/capture-all-events.mjs --event-type SessionEnd\" },\n      { hookType: \"PreCompact\", command: \"${PAI_DIR}/Hooks/context-compression-hook.mjs\" },\n      { hookType: \"PreCompact\", command: \"${PAI_DIR}/Hooks/capture-all-events.mjs --event-type PreCompact\" },\n      { hookType: \"PreCompact\", matcher: \"\", command: \"${PAI_DIR}/Hooks/pai-pre-compact.sh\" },\n    ],\n    statusLine: {\n      type: \"command\",\n      command: \"bash ${PAI_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 lets Claude Code 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 (mcpServers && Object.prototype.hasOwnProperty.call(mcpServers, \"pai\")) {\n        console.log(c.ok(\"PAI MCP server 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 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 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 * 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  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    // Write config after gathering all choices\n    const allUpdates = { ...storageConfig, ...embeddingConfig, ...taskConfig };\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","/** 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\";\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/**\n * Default vault path: ~/.pai/obsidian-vault\n */\nexport function defaultVaultPath(): string {\n  return join(homedir(), \".pai\", \"obsidian-vault\");\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  CONFIG_FILE,\n} from \"../../daemon/config.js\";\nimport { readFileSync, writeFileSync, existsSync, mkdirSync } from \"node:fs\";\nimport { dirname } from \"node:path\";\nimport chalk from \"chalk\";\n\n// ---------------------------------------------------------------------------\n// Config helpers\n// ---------------------------------------------------------------------------\n\n/**\n * Read obsidianVaultPath from ~/.config/pai/config.json.\n * Falls back to defaultVaultPath() if not set.\n */\nfunction getVaultPath(override?: string): string {\n  if (override) return expandHome(override);\n\n  try {\n    const raw = readFileSync(CONFIG_FILE, \"utf-8\");\n    const cfg = JSON.parse(raw) as Record<string, unknown>;\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 * Persist obsidianVaultPath into config.json so future syncs use the same path.\n */\nfunction saveVaultPath(vaultPath: string): void {\n  let cfg: Record<string, unknown> = {};\n  try {\n    cfg = JSON.parse(readFileSync(CONFIG_FILE, \"utf-8\")) as Record<string, unknown>;\n  } catch {\n    // Start fresh if file is missing/corrupt\n  }\n  cfg.obsidianVaultPath = vaultPath;\n  mkdirSync(dirname(CONFIG_FILE), { recursive: true });\n  writeFileSync(CONFIG_FILE, JSON.stringify(cfg, null, 2) + \"\\n\", \"utf-8\");\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.exit(1);\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.exit(1);\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","/** 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.exit(1);\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.exit(1);\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.exit(1);\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.exit(1);\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.exit(1);\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.exit(1);\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.exit(1);\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.exit(1);\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.exit(1);\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.exit(1);\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.exit(1);\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.exit(1);\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.exit(1);\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.exit(1);\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.exit(1);\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.exit(1);\n      }\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.exit(1);\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.exit(1);\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.exit(1);\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.exit(1);\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.exit(1);\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.exit(1);\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.exit(1);\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.exit(1);\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.exit(1);\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.exit(1);\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.exit(1);\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 * 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 * 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 * 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 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\nconst STATE_FILE = join(homedir(), \".pai\", \"scheduler-state.json\");\n\n/** How many past durations to keep per task. */\nconst HISTORY_LIMIT = 5;\n\ninterface 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 */\nfunction 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\nfunction saveState(state: PersistedState, file: string): void {\n  writeJsonAtomic(file, state as unknown as Record<string, unknown>, { backup: false });\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 opts.provider.setLabels(\n            task.id,\n            task.labels.filter((l) => l.toLowerCase() !== RUNNING_LABEL)\n          );\n          delete state.startedAt[task.id];\n          delete state.failedProbes[task.id];\n          delete state.claimSeenAt[task.id];\n          await clearRunningMark(task, opts);\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) saveState(state, stateFile);\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  await opts.provider.setLabels(task.id, task.labels);\n  await clearRunningMark(task, opts);\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, opts: TickOptions): Promise<void> {\n  const { listComments, deleteComment } = opts.provider;\n  if (!listComments || !deleteComment) return;\n  try {\n    const comments = await listComments.call(opts.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(opts.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 * 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  await opts.provider.setLabels(\n    task.id,\n    task.labels.filter((l) => l.toLowerCase() !== RUNNING_LABEL)\n  );\n\n  await clearRunningMark(task, opts);\n\n  const wasStuck = (state.failedProbes[task.id] ?? 0) > 0;\n  delete state.startedAt[task.id];\n  delete state.failedProbes[task.id];\n  delete state.claimSeenAt[task.id];\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 * 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 } 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(): TaskProvider | 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      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.exit(1);\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.exit(1);\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.exit(1);\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 * 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 } from \"node:path\";\nimport { homedir } from \"node:os\";\nimport type { Pool } from \"pg\";\n\nimport { openRegistry } from \"../registry/db.js\";\nimport { loadConfig } from \"../daemon/config.js\";\nimport { createStorageBackend } from \"../storage/factory.js\";\nimport { extractAndStoreTriples } from \"./kg-extraction.js\";\nimport { openFederation } from \"./db.js\";\n\n// ---------------------------------------------------------------------------\n// State file\n// ---------------------------------------------------------------------------\n\nconst STATE_FILE = join(homedir(), \".config\", \"pai\", \"kg-backfill-state.json\");\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    if (existsSync(STATE_FILE)) {\n      const raw = JSON.parse(readFileSync(STATE_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 dir = join(homedir(), \".config\", \"pai\");\n    if (!existsSync(dir)) mkdirSync(dir, { recursive: true });\n    writeFileSync(STATE_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/pai/config.json.'\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 * 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 } 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/pai/config.json'));\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.exit(1);\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.exit(1);\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.exit(1);\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.exit(1);\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.exit(1);\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.exit(1);\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.exit(1);\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.exit(1);\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.exit(1);\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.exit(1);\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.exit(1);\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.exit(1);\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.exit(1);\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.exit(1);\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.exit(1);\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.exit(1);\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) process.exit(0);\n\n  if (checkpointTooRecent(notesDir, minGapSeconds)) process.exit(0);\n\n  const notePath = findLatestNoteFile(notesDir);\n  if (!notePath) process.exit(0);\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    process.exit(0);\n  }\n\n  touchCheckpointSentinel(notesDir);\n  process.exit(0);\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.exit(1);\n    }\n    if (!body) {\n      console.error(\n        chalk.red(\"pai pause: \") +\n          `Checkpoint body at ${opts.bodyFile} is empty.`\n      );\n      process.exit(1);\n    }\n  } else if (!opts.noBody) {\n    printMissingBodyError();\n    process.exit(1);\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.exit(1);\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.exit(1);\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 */\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  const dirty = tree?.changes.length ?? 0;\n\n  const out: string[] = [\n    `_Automatic checkpoint — ${ts}. Written without the model, from the` +\n      ` transcript and the working tree. A model-authored checkpoint replaces` +\n      ` this; it is here so an interrupted session still leaves something._`,\n  ];\n\n  if (prompts.length > 0) {\n    out.push(\"\", \"### What was being asked\", \"\");\n    for (const p of prompts) {\n      // Quote as a list item, flattening newlines so the list stays a list.\n      out.push(`- ${p.replace(/\\s*\\n\\s*/g, \" \")}`);\n    }\n  }\n\n  if (tree) {\n    out.push(\"\", \"### Working tree\", \"\");\n    out.push(`- Branch: \\`${tree.branch}\\``);\n    if (tree.head) out.push(`- HEAD: ${tree.head}`);\n    if (dirty === 0) {\n      out.push(\"- Clean — nothing uncommitted.\");\n    } else {\n      out.push(`- ${dirty} uncommitted path(s)${tree.overflow > 0 ? ` (+${tree.overflow} more)` : \"\"}:`);\n      out.push(\"\");\n      out.push(\"```\");\n      out.push(...tree.changes);\n      out.push(\"```\");\n    }\n  }\n\n  return out.join(\"\\n\");\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) process.exit(0);\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) process.exit(0);\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  process.exit(0);\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 { 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 function cmdAutosave(db: Database, opts: AutosaveOptions): 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) process.exit(0);\n\n  if (!opts.dryRun && tooRecent(project.root_path, minGap)) process.exit(0);\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  const body = buildAutosaveBody({ cwd, transcriptPaths });\n  // Nothing worth recording — leave whatever is already there alone.\n  if (!body) process.exit(0);\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    process.exit(0);\n  }\n\n  touchSentinel(project.root_path);\n  process.exit(0);\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.exit(1);\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.exit(1);\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.exit(1);\n      }\n      printExitDir(projectDir);\n      process.exit(result.status ?? 0);\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.exit(1);\n      }\n      printExitDir(projectDir);\n      process.exit(result.status ?? 0);\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.exit(1);\n    }\n    printExitDir(projectDir);\n    process.exit(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((opts: { sessionId?: string; minGap?: string; dryRun?: boolean }) => {\n      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 * 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.exit(1);\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 · 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 · 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\";\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(\"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      // 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 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.\n  launchInDir(record.dir, record.name, { forceFresh: true, dryRun: opts.dryRun });\n}\n"],"mappings":"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"}