{
  "version": 3,
  "sources": ["../../../src/hooks/ts/lib/worker-session.ts", "../../../src/hooks/ts/stop/stop-hook.ts", "../../../src/hooks/ts/lib/project-utils/paths.ts", "../../../src/hooks/ts/lib/pai-paths.ts", "../../../src/config/pai-home.ts", "../../../src/hooks/ts/lib/project-utils/notify.ts", "../../../src/hooks/ts/lib/project-utils/session-notes.ts", "../../../src/hooks/ts/lib/project-utils/todo.ts", "../../../src/session/checkpoint-block.ts", "../../../src/config/pai-files.ts"],
  "sourcesContent": ["/**\n * Worker-session detection.\n *\n * A disposable headless worker (a `claude -p` run started by an orchestrating\n * session, possibly against a different model provider with a different\n * context window) shares the project directory and the hook configuration\n * with the real session that spawned it. Left alone, the hooks treat it as a\n * session in its own right: they inject project context into it, create and\n * rename a numbered session note for it, autosave it, and enqueue a\n * model-written handover for it. Its compactions also land in the project's\n * transcript folder, where they are indistinguishable from a real session's\n * and drag the measured compaction trigger down (observed 2026-09-17: two\n * workers compacting at ~151k tokens pulled a project's trigger from ~784k\n * to ~151k, and the real session's handover fired at ~50k tokens).\n *\n * The launcher marks such sessions with `PAI_WORKER=1`. Every hook that does\n * per-session bookkeeping returns immediately when this predicate is true.\n * Deliberately NOT guarded: the security validator (a worker's shell\n * commands must still be checked) and observability capture.\n */\nexport function isWorkerSession(env: NodeJS.ProcessEnv = process.env): boolean {\n  return env.PAI_WORKER === \"1\";\n}\n", "#!/usr/bin/env node\n\nimport { isWorkerSession } from \"../lib/worker-session.js\";\nimport { readFileSync, writeFileSync, mkdirSync, existsSync, unlinkSync } from 'fs';\nimport { join, basename, dirname } from 'path';\nimport { connect } from 'net';\nimport { randomUUID } from 'crypto';\nimport {\n  sendNtfyNotification,\n  getCurrentNotePath,\n  finalizeSessionNote,\n  archiveSessionFilesToSessionsDir,\n  addWorkToSessionNote,\n  findNotesDir,\n  isProbeSession,\n  updateTodoContinue, sessionIdFromTranscript,\n  WorkItem\n} from '../lib/project-utils';\nimport { paiHomePath, sessionStateDir } from '../../../config/pai-files.js';\n\n// ---------------------------------------------------------------------------\n// Constants\n// ---------------------------------------------------------------------------\n\nconst DAEMON_SOCKET = process.env.PAI_SOCKET ?? '/tmp/pai.sock';\n\n/**\n * Per-call ceiling on daemon round-trips.\n *\n * This hook runs after EVERY assistant turn and makes six of these calls in\n * sequence, so the ceiling is multiplied by six before the user sees the next\n * prompt. At the previous 3000 ms that was an 18-second worst case, and it was\n * reached routinely: measured on this machine the same round-trip costs ~120 ms\n * with the daemon idle and ~3000 ms while it is mid embed-pass, and the daemon\n * was inside a pass 55% of wall-clock time.\n *\n * 800 ms is ~6x the idle round-trip, so a healthy daemon is never cut off, and\n * it caps the worst case at ~5 s instead of 18 s. Every call site already falls\n * back cleanly when the daemon does not answer in time, so the cost of timing\n * out under load is a skipped enrichment, not a failure.\n */\nconst DAEMON_TIMEOUT_MS = 800;\n\n/**\n * How many human messages must accumulate before triggering a mid-session\n * auto-save. Overrideable via the PAI_AUTO_SAVE_INTERVAL env var.\n */\nconst AUTO_SAVE_INTERVAL = (() => {\n  const raw = process.env.PAI_AUTO_SAVE_INTERVAL;\n  if (raw) {\n    const n = parseInt(raw, 10);\n    if (!isNaN(n) && n > 0) return n;\n  }\n  return 15;\n})();\n\n// ---------------------------------------------------------------------------\n// Session-state helpers (mid-session auto-save)\n// ---------------------------------------------------------------------------\n\ninterface SessionState {\n  humanMessageCount: number;\n}\n\nfunction readSessionState(sessionId: string): SessionState {\n  try {\n    const stateFile = join(sessionStateDir(), `${sessionId}.json`);\n    if (!existsSync(stateFile)) return { humanMessageCount: 0 };\n    const raw = readFileSync(stateFile, 'utf-8');\n    const parsed = JSON.parse(raw) as Partial<SessionState>;\n    return {\n      humanMessageCount: typeof parsed.humanMessageCount === 'number' ? parsed.humanMessageCount : 0,\n    };\n  } catch {\n    return { humanMessageCount: 0 };\n  }\n}\n\nfunction writeSessionState(sessionId: string, state: SessionState): void {\n  try {\n    const dir = paiHomePath('session-state');\n    mkdirSync(dir, { recursive: true });\n    const stateFile = join(dir, `${sessionId}.json`);\n    writeFileSync(stateFile, JSON.stringify(state, null, 2), 'utf-8');\n  } catch (e) {\n    console.error(`STOP-HOOK: Could not write session state: ${e}`);\n  }\n}\n\nfunction deleteSessionState(sessionId: string): void {\n  try {\n    const stateFile = join(sessionStateDir(), `${sessionId}.json`);\n    if (existsSync(stateFile)) {\n      unlinkSync(stateFile);\n    }\n  } catch {\n    // Non-fatal\n  }\n}\n\n/**\n * Count human (user-role) messages in the transcript lines.\n */\nfunction countHumanMessages(lines: string[]): number {\n  let count = 0;\n  for (const line of lines) {\n    try {\n      const entry = JSON.parse(line);\n      if (entry.type === 'user' && entry.message?.role === 'user') {\n        count++;\n      }\n    } catch {\n      // Skip invalid JSON\n    }\n  }\n  return count;\n}\n\n// ---------------------------------------------------------------------------\n// Helper: safely convert Claude content (string | Block[]) to plain text\n// ---------------------------------------------------------------------------\n\nfunction contentToText(content: any): string {\n  if (typeof content === 'string') return content;\n  if (Array.isArray(content)) {\n    return content\n      .map((c) => {\n        if (typeof c === 'string') return c;\n        if (c?.text) return c.text;\n        if (c?.content) return String(c.content);\n        return '';\n      })\n      .join(' ')\n      .trim();\n  }\n  return '';\n}\n\n// ---------------------------------------------------------------------------\n// Helper: extract COMPLETED: line from the last assistant response\n// ---------------------------------------------------------------------------\n\nfunction extractCompletedMessage(lines: string[]): string {\n  for (let i = lines.length - 1; i >= 0; i--) {\n    try {\n      const entry = JSON.parse(lines[i]);\n      if (entry.type === 'assistant' && entry.message?.content) {\n        const content = contentToText(entry.message.content);\n        const m = content.match(/COMPLETED:\\s*(.+?)(?:\\n|$)/i);\n        if (m) {\n          return m[1].trim().replace(/\\*+/g, '').replace(/\\[.*?\\]/g, '').trim();\n        }\n      }\n    } catch {\n      // Skip invalid JSON\n    }\n  }\n  return '';\n}\n\n// ---------------------------------------------------------------------------\n// AIBroker IPC helper \u2014 persistent name re-assertion\n// ---------------------------------------------------------------------------\n\nconst AIBROKER_SOCKET = '/tmp/aibroker.sock';\nconst AIBROKER_TIMEOUT_MS = 2_000;\n\n/**\n * Ask AIBroker for the persisted user-chosen name for the current iTerm2 session.\n * Returns the name string, or null if AIBroker is not running or no name is set.\n * Uses ITERM_SESSION_ID environment variable as the session identifier.\n */\nasync function getPersistentTabName(): Promise<string | null> {\n  const itermSessionId = process.env.ITERM_SESSION_ID;\n  if (!itermSessionId) return null;\n\n  return new Promise((resolve) => {\n    let done = false;\n    let buffer = '';\n    let timer: ReturnType<typeof setTimeout> | null = null;\n\n    function finish(result: string | null): void {\n      if (done) return;\n      done = true;\n      if (timer !== null) { clearTimeout(timer); timer = null; }\n      try { client.destroy(); } catch { /* ignore */ }\n      resolve(result);\n    }\n\n    const client = connect(AIBROKER_SOCKET, () => {\n      const msg = JSON.stringify({\n        id: randomUUID(),\n        method: 'get_persistent_name',\n        params: { itermSessionId },\n      }) + '\\n';\n      client.write(msg);\n    });\n\n    client.on('data', (chunk: Buffer) => {\n      buffer += chunk.toString();\n      const nl = buffer.indexOf('\\n');\n      if (nl === -1) return;\n      const line = buffer.slice(0, nl);\n      try {\n        const response = JSON.parse(line) as { ok: boolean; result?: { name: string | null } };\n        if (response.ok && response.result?.name) {\n          finish(response.result.name);\n        } else {\n          finish(null);\n        }\n      } catch {\n        finish(null);\n      }\n    });\n\n    client.on('error', () => finish(null));\n    client.on('end', () => { if (!done) finish(null); });\n    timer = setTimeout(() => finish(null), AIBROKER_TIMEOUT_MS);\n  });\n}\n\n// ---------------------------------------------------------------------------\n// Daemon IPC relay \u2014 fast path\n// ---------------------------------------------------------------------------\n\n/**\n * Try to enqueue work with the daemon over its Unix socket.\n * Returns true on success, false if the daemon is unreachable.\n * Times out after DAEMON_TIMEOUT_MS so the hook doesn't block.\n */\nfunction enqueueWithDaemon(payload: {\n  transcriptPath: string;\n  cwd: string;\n  message: string;\n}): Promise<boolean> {\n  return new Promise((resolve) => {\n    let done = false;\n    let buffer = '';\n    let timer: ReturnType<typeof setTimeout> | null = null;\n\n    function finish(ok: boolean): void {\n      if (done) return;\n      done = true;\n      if (timer !== null) { clearTimeout(timer); timer = null; }\n      try { client.destroy(); } catch { /* ignore */ }\n      resolve(ok);\n    }\n\n    const client = connect(DAEMON_SOCKET, () => {\n      const msg = JSON.stringify({\n        id: randomUUID(),\n        method: 'work_queue_enqueue',\n        params: {\n          type: 'session-end',\n          priority: 2,\n          payload: {\n            transcriptPath: payload.transcriptPath,\n            cwd: payload.cwd,\n            message: payload.message,\n          },\n        },\n      }) + '\\n';\n      client.write(msg);\n    });\n\n    client.on('data', (chunk: Buffer) => {\n      buffer += chunk.toString();\n      const nl = buffer.indexOf('\\n');\n      if (nl === -1) return;\n      const line = buffer.slice(0, nl);\n      try {\n        const response = JSON.parse(line) as { ok: boolean; error?: string };\n        if (response.ok) {\n          console.error(`STOP-HOOK: Work enqueued with daemon (id=${(response as any).result?.id}).`);\n          finish(true);\n        } else {\n          console.error(`STOP-HOOK: Daemon rejected enqueue: ${response.error}`);\n          finish(false);\n        }\n      } catch {\n        finish(false);\n      }\n    });\n\n    client.on('error', (e: NodeJS.ErrnoException) => {\n      if (e.code === 'ENOENT' || e.code === 'ECONNREFUSED') {\n        console.error('STOP-HOOK: Daemon not running \u2014 falling back to direct execution.');\n      } else {\n        console.error(`STOP-HOOK: Daemon socket error: ${e.message}`);\n      }\n      finish(false);\n    });\n\n    client.on('end', () => { if (!done) finish(false); });\n\n    timer = setTimeout(() => {\n      console.error(`STOP-HOOK: Daemon timeout after ${DAEMON_TIMEOUT_MS}ms \u2014 falling back.`);\n      finish(false);\n    }, DAEMON_TIMEOUT_MS);\n  });\n}\n\n/**\n * Enqueue a registry-scan work item with the daemon.\n * Fire-and-forget \u2014 never throws, never blocks the session stop.\n */\nfunction enqueueRegistryScanWithDaemon(): Promise<void> {\n  return new Promise((resolve) => {\n    let done = false;\n    let timer: ReturnType<typeof setTimeout> | null = null;\n\n    function finish(): void {\n      if (done) return;\n      done = true;\n      if (timer !== null) { clearTimeout(timer); timer = null; }\n      try { client.destroy(); } catch { /* ignore */ }\n      resolve();\n    }\n\n    const client = connect(DAEMON_SOCKET, () => {\n      const msg = JSON.stringify({\n        id: randomUUID(),\n        method: 'work_queue_enqueue',\n        params: {\n          type: 'registry-scan',\n          priority: 5,\n          payload: {},\n        },\n      }) + '\\n';\n      client.write(msg);\n    });\n\n    client.on('data', () => finish());\n    client.on('error', () => finish());\n    client.on('end', () => finish());\n    timer = setTimeout(() => finish(), DAEMON_TIMEOUT_MS);\n  });\n}\n\n/**\n * Enqueue a session-summary work item with `force: true` for mid-session auto-save.\n * Like the regular enqueueSessionSummaryWithDaemon but signals the daemon to\n * summarise even though the session is still ongoing.\n */\nfunction enqueueMidSessionSummaryWithDaemon(payload: {\n  cwd: string;\n}): Promise<boolean> {\n  return new Promise((resolve) => {\n    let done = false;\n    let buffer = '';\n    let timer: ReturnType<typeof setTimeout> | null = null;\n\n    function finish(ok: boolean): void {\n      if (done) return;\n      done = true;\n      if (timer !== null) { clearTimeout(timer); timer = null; }\n      try { client.destroy(); } catch { /* ignore */ }\n      resolve(ok);\n    }\n\n    const client = connect(DAEMON_SOCKET, () => {\n      const msg = JSON.stringify({\n        id: randomUUID(),\n        method: 'work_queue_enqueue',\n        params: {\n          type: 'session-summary',\n          priority: 3,\n          payload: {\n            cwd: payload.cwd,\n            force: true,\n          },\n        },\n      }) + '\\n';\n      client.write(msg);\n    });\n\n    client.on('data', (chunk: Buffer) => {\n      buffer += chunk.toString();\n      const nl = buffer.indexOf('\\n');\n      if (nl === -1) return;\n      const line = buffer.slice(0, nl);\n      try {\n        const response = JSON.parse(line) as { ok: boolean; result?: { id: string } };\n        if (response.ok) {\n          debug(`STOP-HOOK: Mid-session summary enqueued (id=${response.result?.id}).`);\n        }\n      } catch { /* ignore */ }\n      finish(true);\n    });\n\n    client.on('error', () => finish(false));\n    client.on('end', () => { if (!done) finish(false); });\n\n    timer = setTimeout(() => finish(false), DAEMON_TIMEOUT_MS);\n  });\n}\n\n/**\n * Enqueue a session-summary work item with the daemon for AI-powered note generation.\n * Non-blocking \u2014 if daemon is unavailable, silently skips (the mechanical note is sufficient).\n *\n * Note: we intentionally omit transcriptPath here to let the worker call findLatestJsonl()\n * itself. At session-end, Claude Code may still be moving the JSONL to sessions/, so a\n * stale path passed from the hook could point to a file that no longer exists.\n */\nfunction enqueueSessionSummaryWithDaemon(payload: {\n  cwd: string;\n}): Promise<boolean> {\n  return new Promise((resolve) => {\n    let done = false;\n    let buffer = '';\n    let timer: ReturnType<typeof setTimeout> | null = null;\n\n    function finish(ok: boolean): void {\n      if (done) return;\n      done = true;\n      if (timer !== null) { clearTimeout(timer); timer = null; }\n      try { client.destroy(); } catch { /* ignore */ }\n      resolve(ok);\n    }\n\n    const client = connect(DAEMON_SOCKET, () => {\n      const msg = JSON.stringify({\n        id: randomUUID(),\n        method: 'work_queue_enqueue',\n        params: {\n          type: 'session-summary',\n          priority: 4,\n          payload: {\n            cwd: payload.cwd,\n            force: true,\n          },\n        },\n      }) + '\\n';\n      client.write(msg);\n    });\n\n    client.on('data', (chunk: Buffer) => {\n      buffer += chunk.toString();\n      const nl = buffer.indexOf('\\n');\n      if (nl === -1) return;\n      const line = buffer.slice(0, nl);\n      try {\n        const response = JSON.parse(line) as { ok: boolean; result?: { id: string } };\n        if (response.ok) {\n          debug(`STOP-HOOK: Session summary enqueued (id=${response.result?.id}).`);\n        }\n      } catch { /* ignore */ }\n      finish(true);\n    });\n\n    client.on('error', () => finish(false));\n    client.on('end', () => { if (!done) finish(false); });\n\n    timer = setTimeout(() => finish(false), DAEMON_TIMEOUT_MS);\n  });\n}\n\n// ---------------------------------------------------------------------------\n// Direct execution \u2014 fallback path (original stop-hook logic)\n// ---------------------------------------------------------------------------\n\n/**\n * Extract work items from transcript for session note.\n * Looks for SUMMARY, ACTIONS, RESULTS sections in assistant responses.\n */\nfunction extractWorkFromTranscript(lines: string[]): WorkItem[] {\n  const workItems: WorkItem[] = [];\n  const seenSummaries = new Set<string>();\n\n  for (const line of lines) {\n    try {\n      const entry = JSON.parse(line);\n      if (entry.type === 'assistant' && entry.message?.content) {\n        const content = contentToText(entry.message.content);\n\n        // Look for SUMMARY: lines (our standard format)\n        const summaryMatch = content.match(/SUMMARY:\\s*(.+?)(?:\\n|$)/i);\n        if (summaryMatch) {\n          const summary = summaryMatch[1].trim();\n          if (summary && !seenSummaries.has(summary) && summary.length > 5) {\n            seenSummaries.add(summary);\n\n            // Try to extract details from ACTIONS section\n            const details: string[] = [];\n            const actionsMatch = content.match(/ACTIONS:\\s*(.+?)(?=\\n[A-Z]+:|$)/is);\n            if (actionsMatch) {\n              const actionLines = actionsMatch[1].split('\\n')\n                .map(l => l.replace(/^[-*\u2022]\\s*/, '').replace(/^\\d+\\.\\s*/, '').trim())\n                .filter(l => l.length > 3 && l.length < 100);\n              details.push(...actionLines.slice(0, 3));\n            }\n\n            workItems.push({\n              title: summary,\n              details: details.length > 0 ? details : undefined,\n              completed: true\n            });\n          }\n        }\n\n        // Also look for COMPLETED: lines as backup\n        const completedMatch = content.match(/COMPLETED:\\s*(.+?)(?:\\n|$)/i);\n        if (completedMatch && workItems.length === 0) {\n          const completed = completedMatch[1].trim().replace(/\\*+/g, '').replace(/\\[.*?\\]/g, '');\n          if (completed && !seenSummaries.has(completed) && completed.length > 5) {\n            seenSummaries.add(completed);\n            workItems.push({ title: completed, completed: true });\n          }\n        }\n      }\n    } catch {\n      // Skip invalid JSON lines\n    }\n  }\n\n  return workItems;\n}\n\n/**\n * Generate 4-word tab title summarizing what was done.\n */\nfunction generateTabTitle(prompt: string, completedLine?: string): string {\n  if (completedLine) {\n    const cleanCompleted = completedLine\n      .replace(/\\*+/g, '')\n      .replace(/\\[.*?\\]/g, '')\n      .replace(/COMPLETED:\\s*/gi, '')\n      .trim();\n\n    const completedWords = cleanCompleted.split(/\\s+/)\n      .filter(word => word.length > 2 &&\n        !['the', 'and', 'but', 'for', 'are', 'with', 'his', 'her', 'this', 'that', 'you', 'can', 'will', 'have', 'been', 'your', 'from', 'they', 'were', 'said', 'what', 'them', 'just', 'told', 'how', 'does', 'into', 'about', 'completed'].includes(word.toLowerCase()))\n      .map(word => word.charAt(0).toUpperCase() + word.slice(1).toLowerCase());\n\n    if (completedWords.length >= 2) {\n      const summary = completedWords.slice(0, 4);\n      while (summary.length < 4) summary.push('Done');\n      return summary.slice(0, 4).join(' ');\n    }\n  }\n\n  const cleanPrompt = prompt.replace(/[^\\w\\s]/g, ' ').trim();\n  const words = cleanPrompt.split(/\\s+/).filter(word =>\n    word.length > 2 &&\n    !['the', 'and', 'but', 'for', 'are', 'with', 'his', 'her', 'this', 'that', 'you', 'can', 'will', 'have', 'been', 'your', 'from', 'they', 'were', 'said', 'what', 'them', 'just', 'told', 'how', 'does', 'into', 'about'].includes(word.toLowerCase())\n  );\n\n  const lowerPrompt = prompt.toLowerCase();\n  const actionVerbs = ['test', 'rename', 'fix', 'debug', 'research', 'write', 'create', 'make', 'build', 'implement', 'analyze', 'review', 'update', 'modify', 'generate', 'develop', 'design', 'deploy', 'configure', 'setup', 'install', 'remove', 'delete', 'add', 'check', 'verify', 'validate', 'optimize', 'refactor', 'enhance', 'improve', 'send', 'email', 'help', 'updated', 'fixed', 'created', 'built', 'added'];\n  let titleWords: string[] = [];\n\n  for (const verb of actionVerbs) {\n    if (lowerPrompt.includes(verb)) {\n      let pastTense = verb;\n      if (verb === 'write') pastTense = 'Wrote';\n      else if (verb === 'make') pastTense = 'Made';\n      else if (verb === 'send') pastTense = 'Sent';\n      else if (verb.endsWith('e')) pastTense = verb.charAt(0).toUpperCase() + verb.slice(1, -1) + 'ed';\n      else pastTense = verb.charAt(0).toUpperCase() + verb.slice(1) + 'ed';\n      titleWords.push(pastTense);\n      break;\n    }\n  }\n\n  const remainingWords = words\n    .filter(word => !actionVerbs.includes(word.toLowerCase()))\n    .map(word => word.charAt(0).toUpperCase() + word.slice(1).toLowerCase());\n\n  for (const word of remainingWords) {\n    if (titleWords.length < 4) titleWords.push(word);\n    else break;\n  }\n\n  if (titleWords.length === 0) titleWords.push('Completed');\n  if (titleWords.length === 1) titleWords.push('Task');\n  if (titleWords.length === 2) titleWords.push('Successfully');\n  if (titleWords.length === 3) titleWords.push('Done');\n\n  return titleWords.slice(0, 4).join(' ');\n}\n\n/**\n * Do the heavy work directly in the hook process.\n * Used when the daemon is unreachable.\n */\nasync function executeDirectly(\n  lines: string[],\n  transcriptPath: string,\n  cwd: string,\n  message: string,\n  lastUserQuery: string\n): Promise<void> {\n  // Set terminal tab title\n  let tabTitle = message || '';\n  if (!tabTitle && lastUserQuery) {\n    tabTitle = generateTabTitle(lastUserQuery, '');\n  }\n\n  if (tabTitle) {\n    try {\n      const escapedTitle = tabTitle.replace(/'/g, \"'\\\\''\");\n      const { execSync } = await import('child_process');\n      execSync(`printf '\\\\033]0;${escapedTitle}\\\\007' >&2`);\n      execSync(`printf '\\\\033]2;${escapedTitle}\\\\007' >&2`);\n      execSync(`printf '\\\\033]30;${escapedTitle}\\\\007' >&2`);\n      console.error(`Tab title set to: \"${tabTitle}\"`);\n    } catch (e) {\n      console.error(`Failed to set tab title: ${e}`);\n    }\n  }\n\n  // Final tab title override\n  if (message) {\n    const finalTabTitle = message.slice(0, 50);\n    process.stderr.write(`\\x1b]2;${finalTabTitle}\\x07`);\n  }\n\n  // Finalize session note\n  try {\n    const notesInfo = findNotesDir(cwd);\n    const currentNotePath = getCurrentNotePath(notesInfo.path);\n\n    if (currentNotePath) {\n      const workItems = extractWorkFromTranscript(lines);\n      if (workItems.length > 0) {\n        addWorkToSessionNote(currentNotePath, workItems);\n        console.error(`Added ${workItems.length} work item(s) to session note`);\n      } else if (message) {\n        addWorkToSessionNote(currentNotePath, [{ title: message, completed: true }]);\n        console.error(`Added completion message to session note`);\n      }\n\n      const summary = message || 'Session completed.';\n      finalizeSessionNote(currentNotePath, summary);\n      console.error(`Session note finalized: ${basename(currentNotePath)}`);\n\n      // A session that produced neither work items nor a completion message has\n      // no handover to write. Claiming the slot anyway hands the next session a\n      // block naming a session that did nothing \u2014 this is exactly what happened\n      // on 2026-08-04, when a session opened and immediately exited took the\n      // \"last session\" line away from the previous day's real work across every\n      // project it touched. applyContinue now refuses such a write on its own;\n      // not building it here keeps the pointer on the last session that did\n      // something.\n      if (workItems.length === 0 && !message) {\n        console.error('Nothing to hand over \u2014 leaving TODO.md ## Continue intact');\n      } else {\n        try {\n          const stateLines: string[] = [];\n          stateLines.push(`Working directory: ${cwd}`);\n          if (workItems.length > 0) {\n            stateLines.push('', 'Work completed:');\n            for (const item of workItems.slice(0, 5)) {\n              stateLines.push(`- ${item.title}`);\n            }\n          }\n          if (message) {\n            stateLines.push('', `Last completed: ${message}`);\n          }\n          updateTodoContinue(\n            cwd,\n            basename(currentNotePath),\n            stateLines.join('\\n'),\n            'session-end',\n            // Identity, not the note title. The note is renamed as the session\n            // ends, so the title-based comparison stops recognising this very\n            // session moments after it writes.\n            sessionIdFromTranscript(transcriptPath)\n          );\n        } catch (todoError) {\n          console.error(`Could not update TODO.md: ${todoError}`);\n        }\n      }\n    }\n  } catch (noteError) {\n    console.error(`Could not finalize session note: ${noteError}`);\n  }\n\n  // Move session .jsonl files to sessions/\n  try {\n    const transcriptDir = dirname(transcriptPath);\n    const archivedCount = archiveSessionFilesToSessionsDir(transcriptDir);\n    if (archivedCount > 0) {\n      console.error(`Archived ${archivedCount} session file(s) to sessions/`);\n    }\n  } catch (moveError) {\n    console.error(`Could not archive session files: ${moveError}`);\n  }\n}\n\n// ---------------------------------------------------------------------------\n// Main\n// ---------------------------------------------------------------------------\n\n// Debug logging only when PAI_HOOK_DEBUG=1 \u2014 otherwise stop-hook is silent\nconst DEBUG = process.env.PAI_HOOK_DEBUG === '1';\nfunction debug(msg: string): void {\n  if (DEBUG) console.error(msg);\n}\n\nasync function main() {\n  if (isWorkerSession()) return; // disposable worker: no per-session bookkeeping\n  if (isProbeSession()) {\n    process.exit(0);\n  }\n\n  const timestamp = new Date().toISOString();\n  debug(`\\nSTOP-HOOK TRIGGERED AT ${timestamp}`);\n\n  // Read stdin\n  let input = '';\n  const decoder = new TextDecoder();\n  try {\n    for await (const chunk of process.stdin) {\n      input += decoder.decode(chunk, { stream: true });\n    }\n  } catch (e) {\n    console.error(`Error reading input: ${e}`);\n    process.exit(0);\n  }\n\n  if (!input) {\n    console.error('No input received');\n    process.exit(0);\n  }\n\n  let transcriptPath: string;\n  let cwd: string;\n  let stopHookActive: boolean = false;\n  let sessionId: string = '';\n  try {\n    const parsed = JSON.parse(input);\n    transcriptPath = parsed.transcript_path;\n    cwd = parsed.cwd || process.cwd();\n    stopHookActive = parsed.stop_hook_active === true;\n    // session_id may appear directly or be derivable from the transcript path\n    sessionId = parsed.session_id ?? basename(transcriptPath ?? '').replace(/\\.jsonl$/, '');\n    debug(`Transcript path: ${transcriptPath}`);\n    debug(`Working directory: ${cwd}`);\n    debug(`stop_hook_active: ${stopHookActive}`);\n    debug(`session_id: ${sessionId}`);\n  } catch (e) {\n    console.error(`Error parsing input JSON: ${e}`);\n    process.exit(0);\n  }\n\n  if (!transcriptPath) {\n    console.error('No transcript_path in input');\n    process.exit(0);\n  }\n\n  // Read transcript\n  let transcript: string;\n  try {\n    transcript = readFileSync(transcriptPath, 'utf-8');\n    debug(`Transcript loaded: ${transcript.split('\\n').length} lines`);\n  } catch (e) {\n    console.error(`Error reading transcript: ${e}`);\n    process.exit(0);\n  }\n\n  const lines = transcript.trim().split('\\n');\n\n  // ---------------------------------------------------------------------------\n  // Mid-session auto-save check\n  // ---------------------------------------------------------------------------\n  // When stop_hook_active is FALSE (normal Stop event, not a re-entry from our\n  // own exit-code-2 block), we check whether enough human messages have\n  // accumulated to warrant an interim session summary.\n  //\n  // When stop_hook_active is TRUE the hook is already in the blocked-loop mode\n  // we triggered on the previous fire, so we skip the check entirely and proceed\n  // with normal session-end logic.\n  //\n  // Failure of this entire block must never abort the normal flow \u2014 wrap it all.\n  if (!stopHookActive && sessionId) {\n    try {\n      const currentMsgCount = countHumanMessages(lines);\n      const state = readSessionState(sessionId);\n      const prevCount = state.humanMessageCount;\n      const newMessages = currentMsgCount - prevCount;\n\n      debug(\n        `STOP-HOOK: human messages \u2014 total=${currentMsgCount} prev=${prevCount} new=${newMessages} interval=${AUTO_SAVE_INTERVAL}`\n      );\n\n      // First-run safeguard: if the state file is missing and we're looking at\n      // a session that already has more than 2x the interval in messages, it's\n      // an existing long-running session that predates the auto-save feature.\n      // Initialize the counter to the current count instead of auto-saving\n      // immediately \u2014 otherwise every new message triggers a save.\n      if (prevCount === 0 && currentMsgCount > AUTO_SAVE_INTERVAL * 2) {\n        writeSessionState(sessionId, { humanMessageCount: currentMsgCount });\n        debug(\n          `STOP-HOOK: First-run safeguard \u2014 initializing counter to ${currentMsgCount} (session predates auto-save feature).`\n        );\n      } else if (newMessages >= AUTO_SAVE_INTERVAL) {\n        // Reset the counter so we don't re-trigger on the next fire.\n        writeSessionState(sessionId, { humanMessageCount: currentMsgCount });\n\n        debug(`STOP-HOOK: Auto-save threshold reached. Triggering mid-session summary.`);\n\n        // Fire-and-forget: push session-summary to daemon.\n        // We used to exit(2) to block the Stop, but Claude Code surfaces that\n        // as \"Stop hook error\" in the terminal \u2014 annoying cosmetic noise.\n        // The whisper rules already enforce \"never stop\" behavior, so blocking\n        // is redundant. Just fire the work item and exit 0 silently.\n        try {\n          await enqueueMidSessionSummaryWithDaemon({ cwd });\n        } catch { /* daemon may not be running \u2014 non-fatal */ }\n      } else {\n        // Update the stored count so we can measure delta on next fire.\n        writeSessionState(sessionId, { humanMessageCount: currentMsgCount });\n      }\n    } catch (autoSaveError) {\n      // Never let auto-save logic block the normal Stop flow.\n      console.error(`STOP-HOOK: Auto-save check failed (non-fatal): ${autoSaveError}`);\n    }\n  }\n\n  // Extract last user query for tab title / fallback\n  let lastUserQuery = '';\n  for (let i = lines.length - 1; i >= 0; i--) {\n    try {\n      const entry = JSON.parse(lines[i]);\n      if (entry.type === 'user' && entry.message?.content) {\n        const content = entry.message.content;\n        if (typeof content === 'string') {\n          lastUserQuery = content;\n        } else if (Array.isArray(content)) {\n          for (const item of content) {\n            if (item.type === 'text' && item.text) {\n              lastUserQuery = item.text;\n              break;\n            }\n          }\n        }\n        if (lastUserQuery) break;\n      }\n    } catch {\n      // Skip invalid JSON\n    }\n  }\n\n  // Extract completion message\n  const message = extractCompletedMessage(lines);\n\n  console.error(`User query: ${lastUserQuery || 'No query found'}`);\n  console.error(`Message: ${message || 'No completion message'}`);\n\n  // Always set terminal tab title immediately (fast, no daemon needed)\n  let tabTitle = message || '';\n  if (!tabTitle && lastUserQuery) {\n    tabTitle = generateTabTitle(lastUserQuery, '');\n  }\n  if (tabTitle) {\n    try {\n      const { execSync } = await import('child_process');\n      const escapedTitle = tabTitle.replace(/'/g, \"'\\\\''\");\n      execSync(`printf '\\\\033]0;${escapedTitle}\\\\007' >&2`);\n      execSync(`printf '\\\\033]2;${escapedTitle}\\\\007' >&2`);\n      execSync(`printf '\\\\033]30;${escapedTitle}\\\\007' >&2`);\n      console.error(`Tab title set to: \"${tabTitle}\"`);\n    } catch (e) {\n      console.error(`Failed to set tab title: ${e}`);\n    }\n  }\n  if (message) {\n    process.stderr.write(`\\x1b]2;${message.slice(0, 50)}\\x07`);\n  }\n\n  // Re-assert persistent name \u2014 overrides any auto-title Claude Code may have set.\n  // If the user ran /Name \"Solar\" earlier, that name sticks for the life of the session.\n  {\n    const persistentName = await getPersistentTabName();\n    if (persistentName) {\n      try {\n        const { execSync } = await import('child_process');\n        const escaped = persistentName.replace(/'/g, \"'\\\\''\");\n        execSync(`printf '\\\\033]0;${escaped}\\\\007' >&2`);\n        execSync(`printf '\\\\033]2;${escaped}\\\\007' >&2`);\n        execSync(`printf '\\\\033]30;${escaped}\\\\007' >&2`);\n        process.stderr.write(`\\x1b]2;${persistentName.slice(0, 50)}\\x07`);\n        console.error(`Tab title re-asserted to persistent name: \"${persistentName}\"`);\n      } catch (e) {\n        console.error(`Failed to re-assert persistent tab title: ${e}`);\n      }\n    }\n  }\n\n  // Send ntfy.sh notification (fast, fire-and-forget)\n  if (message) {\n    await sendNtfyNotification(message);\n  } else {\n    await sendNtfyNotification('Session ended');\n  }\n\n  // -----------------------------------------------------------------------\n  // Relay heavy work to daemon \u2014 fall back to direct execution if unavailable\n  // -----------------------------------------------------------------------\n  const relayed = await enqueueWithDaemon({\n    transcriptPath,\n    cwd,\n    message,\n  });\n\n  if (!relayed) {\n    console.error('STOP-HOOK: Using direct execution fallback.');\n    await executeDirectly(lines, transcriptPath, cwd, message, lastUserQuery);\n  }\n\n  // Also enqueue a session-summary for AI-powered note generation.\n  // We omit transcriptPath so the worker resolves it via findLatestJsonl(),\n  // avoiding a race where the session-end hook moves the JSONL before the worker reads it.\n  await enqueueSessionSummaryWithDaemon({ cwd });\n\n  // Enqueue a registry-scan so pai session recent stays fresh after this session ends.\n  await enqueueRegistryScanWithDaemon();\n\n  // Clean up the session-state file now that the session has truly ended.\n  if (sessionId) {\n    deleteSessionState(sessionId);\n    debug(`STOP-HOOK: Session state cleaned up for ${sessionId}.`);\n  }\n\n  debug(`STOP-HOOK COMPLETED SUCCESSFULLY at ${new Date().toISOString()}\\n`);\n}\n\nmain().catch(() => {});\n", "/**\n * Path utilities \u2014 encoding, Notes/Sessions directory discovery and creation.\n */\n\nimport { existsSync, mkdirSync, readdirSync, linkSync, copyFileSync } from 'fs';\nimport { join, basename } from 'path';\nimport { ADAPTER_DIR, PAI_DIR } from '../pai-paths.js';\n\n// Re-export for consumers. PROJECTS_DIR is a harness-adjacent data dir, not\n// PAI_HOME state \u2014 out of scope for the PAI_DIR\u2192PAI_HOME fold (see pai-paths.ts).\nexport { ADAPTER_DIR, PAI_DIR };\nexport const PROJECTS_DIR = join(ADAPTER_DIR, 'projects');\n\n/**\n * Directories known to be automated health-check / probe sessions.\n * Hooks should exit early for these to avoid registry clutter and wasted work.\n */\nconst PROBE_CWD_PATTERNS = [\n  '/CodexBar/ClaudeProbe',\n  '/ClaudeProbe',\n];\n\n/**\n * Check if the current working directory belongs to a probe/health-check session.\n * Returns true if hooks should skip this session entirely.\n */\nexport function isProbeSession(cwd?: string): boolean {\n  const dir = cwd || process.cwd();\n  return PROBE_CWD_PATTERNS.some(pattern => dir.includes(pattern));\n}\n\n/**\n * Encode a path the same way Claude Code does:\n * - Replace / with -\n * - Replace . with -\n * - Replace space with -\n */\nexport function encodePath(path: string): string {\n  return path\n    .replace(/\\//g, '-')\n    .replace(/\\./g, '-')\n    .replace(/ /g, '-');\n}\n\n/** Get the project directory for a given working directory. */\nexport function getProjectDir(cwd: string): string {\n  const encoded = encodePath(cwd);\n  return join(PROJECTS_DIR, encoded);\n}\n\n/** Get the Notes directory for a project (central location). */\nexport function getNotesDir(cwd: string): string {\n  return join(getProjectDir(cwd), 'Notes');\n}\n\n/**\n * Find Notes directory \u2014 checks local first, falls back to central.\n * Does NOT create the directory.\n */\nexport function findNotesDir(cwd: string): { path: string; isLocal: boolean } {\n  const cwdBasename = basename(cwd).toLowerCase();\n  if (cwdBasename === 'notes' && existsSync(cwd)) {\n    return { path: cwd, isLocal: true };\n  }\n\n  const localPaths = [\n    join(cwd, 'Notes'),\n    join(cwd, 'notes'),\n    join(cwd, '.claude', 'Notes'),\n  ];\n\n  for (const path of localPaths) {\n    if (existsSync(path)) {\n      return { path, isLocal: true };\n    }\n  }\n\n  return { path: getNotesDir(cwd), isLocal: false };\n}\n\n/** Get the sessions/ directory for a project (stores .jsonl transcripts). */\nexport function getSessionsDir(cwd: string): string {\n  return join(getProjectDir(cwd), 'sessions');\n}\n\n/** Get the sessions/ directory from a project directory path. */\nexport function getSessionsDirFromProjectDir(projectDir: string): string {\n  return join(projectDir, 'sessions');\n}\n\n// ---------------------------------------------------------------------------\n// Directory creation helpers\n// ---------------------------------------------------------------------------\n\n/** Ensure the Notes directory exists for a project. @deprecated Use ensureNotesDirSmart() */\nexport function ensureNotesDir(cwd: string): string {\n  const notesDir = getNotesDir(cwd);\n  if (!existsSync(notesDir)) {\n    mkdirSync(notesDir, { recursive: true });\n    console.error(`Created Notes directory: ${notesDir}`);\n  }\n  return notesDir;\n}\n\n/**\n * Smart Notes directory handling:\n * - If local Notes/ exists \u2192 use it (don't create anything new)\n * - If no local Notes/ \u2192 ensure central exists and use that\n */\nexport function ensureNotesDirSmart(cwd: string): { path: string; isLocal: boolean } {\n  const found = findNotesDir(cwd);\n  if (found.isLocal) return found;\n  if (!existsSync(found.path)) {\n    mkdirSync(found.path, { recursive: true });\n    console.error(`Created central Notes directory: ${found.path}`);\n  }\n  return found;\n}\n\n/** Ensure the sessions/ directory exists for a project. */\nexport function ensureSessionsDir(cwd: string): string {\n  const sessionsDir = getSessionsDir(cwd);\n  if (!existsSync(sessionsDir)) {\n    mkdirSync(sessionsDir, { recursive: true });\n    console.error(`Created sessions directory: ${sessionsDir}`);\n  }\n  return sessionsDir;\n}\n\n/** Ensure the sessions/ directory exists (from project dir path). */\nexport function ensureSessionsDirFromProjectDir(projectDir: string): string {\n  const sessionsDir = getSessionsDirFromProjectDir(projectDir);\n  if (!existsSync(sessionsDir)) {\n    mkdirSync(sessionsDir, { recursive: true });\n    console.error(`Created sessions directory: ${sessionsDir}`);\n  }\n  return sessionsDir;\n}\n\n/**\n * Publish every project-root .jsonl into sessions/ as well, WITHOUT removing it.\n *\n * This used to renameSync, and that is how PAI destroyed its users' sessions.\n *\n * `claude --resume <uuid>` finds a transcript only at the project root. Move it\n * into sessions/ and the session becomes permanently unresumable \u2014 measured\n * 2026-08-04: `claude --resume b3462801` (867 KB of real work, sessions/ only)\n * answers \"No conversation found with session ID\", while a top-level id is found\n * fine. Nothing warned; the id still looked valid everywhere PAI displayed it.\n *\n * The damage was not occasional. This ran from a UserPromptSubmit hook excluding\n * only the CURRENT session, so every prompt anyone typed unresumed every other\n * session in the project. One PAI project measured 1 transcript at top level\n * against 52 underneath. Among the casualties was 046bb712 \u2014 the exact id PAI's\n * own handover tells the user to resume.\n *\n * A hardlink satisfies both sides, which is why this is a two-line fix rather\n * than a redesign: the archive genuinely has consumers that read sessions/\n * (session-summary-worker, registry/moved, session/autosave), and `--resume`\n * needs the root path. One inode, two names, no copy, no window where the file\n * is missing from either place.\n *\n * Never unlink the source. Tidying up another tool's store was the whole\n * mistake; a stale duplicate is free, a lost session is not. Every caller of\n * `transcriptFiles()` was checked before choosing this \u2014 they all test emptiness\n * (`.length > 0`), never count, so the duplicate cannot skew a project's stats.\n *\n * `excludeFile` keeps a hook from archiving the transcript it is itself watching\n * being written. It is not a \"finished sessions only\" guard and must not be read\n * as one: it excludes exactly one file, the caller's own, so with two sessions\n * live in one project each still archives the other mid-turn. A consumer that\n * needs \"finished only\" has to enforce that itself \u2014 this function cannot know\n * what is live.\n *\n * Returns the number of files newly archived.\n */\nexport function archiveSessionFilesToSessionsDir(\n  projectDir: string,\n  excludeFile?: string,\n  silent = false\n): number {\n  const sessionsDir = ensureSessionsDirFromProjectDir(projectDir);\n\n  if (!existsSync(projectDir)) return 0;\n\n  const files = readdirSync(projectDir);\n  let archivedCount = 0;\n\n  for (const file of files) {\n    if (!file.endsWith('.jsonl') || file === excludeFile) continue;\n\n    const sourcePath = join(projectDir, file);\n    const destPath = join(sessionsDir, file);\n\n    // Already archived \u2014 including by an earlier rename, before this was a\n    // hardlink. Those are the sessions that need restoring to the root, which is\n    // a separate repair and not this function's job.\n    if (existsSync(destPath)) continue;\n\n    try {\n      linkSync(sourcePath, destPath);\n      if (!silent) console.error(`Archived ${file} \u2192 sessions/ (still resumable)`);\n      archivedCount++;\n    } catch (error) {\n      // Cross-device (EXDEV) is the realistic failure: sessions/ on another\n      // volume. Copy instead, and still leave the original alone.\n      try {\n        copyFileSync(sourcePath, destPath);\n        if (!silent) console.error(`Copied ${file} \u2192 sessions/ (hardlink unavailable)`);\n        archivedCount++;\n      } catch {\n        if (!silent) console.error(`Could not archive ${file}: ${error}`);\n      }\n    }\n  }\n\n  return archivedCount;\n}\n\n/**\n * @deprecated Renamed to `archiveSessionFilesToSessionsDir`, which is what it\n * now does. Kept so an out-of-tree caller fails loudly at the type level rather\n * than silently keeping the old destructive name for a non-destructive action.\n */\nexport const moveSessionFilesToSessionsDir = archiveSessionFilesToSessionsDir;\n\n// ---------------------------------------------------------------------------\n// CLAUDE.md / TODO.md discovery\n// ---------------------------------------------------------------------------\n\n/** Find TODO.md \u2014 check local first, fallback to central. */\nexport function findTodoPath(cwd: string): string {\n  const localPaths = [\n    join(cwd, 'TODO.md'),\n    join(cwd, 'notes', 'TODO.md'),\n    join(cwd, 'Notes', 'TODO.md'),\n    join(cwd, '.claude', 'TODO.md'),\n  ];\n\n  for (const path of localPaths) {\n    if (existsSync(path)) return path;\n  }\n\n  return join(getNotesDir(cwd), 'TODO.md');\n}\n\n/** Find CLAUDE.md \u2014 returns the FIRST found path. */\nexport function findClaudeMdPath(cwd: string): string | null {\n  const paths = findAllClaudeMdPaths(cwd);\n  return paths.length > 0 ? paths[0] : null;\n}\n\n/**\n * Find ALL CLAUDE.md files in local locations in priority order.\n */\nexport function findAllClaudeMdPaths(cwd: string): string[] {\n  const foundPaths: string[] = [];\n\n  const localPaths = [\n    join(cwd, '.claude', 'CLAUDE.md'),\n    join(cwd, 'CLAUDE.md'),\n    join(cwd, 'Notes', 'CLAUDE.md'),\n    join(cwd, 'notes', 'CLAUDE.md'),\n    join(cwd, 'Prompts', 'CLAUDE.md'),\n    join(cwd, 'prompts', 'CLAUDE.md'),\n  ];\n\n  for (const path of localPaths) {\n    if (existsSync(path)) foundPaths.push(path);\n  }\n\n  return foundPaths;\n}\n", "/**\n * PAI Path Resolution - Single Source of Truth\n *\n * This module provides consistent path resolution across all PAI hooks.\n *\n * Two different things live here, and they must not be confused:\n *\n * - ADAPTER_DIR (~/.claude by default) is the Claude Code harness adapter \u2014\n *   fixed, hardcoded paths the harness itself loads from (Hooks/, Skills/,\n *   Agents/, Commands/, settings.json, statusline-command.sh,\n *   tab-color-command.sh). It is harness-specific: a future harness would\n *   need its own adapter directory with its own conventions.\n * - PAI_HOME (~/.claude/pai by default \u2014 see ../../../config/pai-home.ts) is\n *   where PAI's own state lives: everything hooks WRITE (History/,\n *   agent-sessions.json, session-routing.json, ...) resolves there, with a\n *   fallback to the pre-2026-09-19 ADAPTER_DIR location and a one-time\n *   stderr notice, exactly like every other PAI_HOME file.\n *\n * ALSO loads .env file from ADAPTER_DIR so all hooks get environment\n * variables without relying on Claude Code's settings.json injection.\n *\n * Usage in hooks:\n *   import { ADAPTER_DIR, HOOKS_DIR, SKILLS_DIR, historyDir } from './lib/pai-paths';\n */\n\nimport { homedir } from 'os';\nimport { resolve, join } from 'path';\nimport { existsSync, readFileSync } from 'fs';\nimport {\n  paiHomePath,\n  resolvePaiFile,\n  migratePaiFile,\n  migratePaiDir,\n  type MigrateFileResult,\n  type MigrateDirResult,\n} from '../../../config/pai-home.js';\n\n/**\n * Load .env file and inject into process.env\n * Must run BEFORE ADAPTER_DIR resolution so .env can set ADAPTER_DIR/PAI_DIR if needed\n */\nfunction loadEnvFile(): void {\n  // Check common locations for .env\n  const possiblePaths = [\n    resolve(process.env.ADAPTER_DIR || process.env.PAI_DIR || '', '.env'),\n    resolve(homedir(), '.claude', '.env'),\n  ];\n\n  for (const envPath of possiblePaths) {\n    if (existsSync(envPath)) {\n      try {\n        const content = readFileSync(envPath, 'utf-8');\n        for (const line of content.split('\\n')) {\n          const trimmed = line.trim();\n          // Skip comments and empty lines\n          if (!trimmed || trimmed.startsWith('#')) continue;\n\n          const eqIndex = trimmed.indexOf('=');\n          if (eqIndex > 0) {\n            const key = trimmed.substring(0, eqIndex).trim();\n            let value = trimmed.substring(eqIndex + 1).trim();\n\n            // Remove surrounding quotes if present\n            if ((value.startsWith('\"') && value.endsWith('\"')) ||\n                (value.startsWith(\"'\") && value.endsWith(\"'\"))) {\n              value = value.slice(1, -1);\n            }\n\n            // Expand $HOME and ~ in values\n            value = value.replace(/\\$HOME/g, homedir());\n            value = value.replace(/^~(?=\\/|$)/, homedir());\n\n            // Only set if not already defined (env vars take precedence)\n            if (process.env[key] === undefined) {\n              process.env[key] = value;\n            }\n          }\n        }\n        // Found and loaded, don't check other paths\n        break;\n      } catch {\n        // Silently continue if .env can't be read\n      }\n    }\n  }\n}\n\n// Load .env FIRST, before any other initialization\nloadEnvFile();\n\nfunction defaultAdapterDir(): string {\n  return resolve(homedir(), '.claude');\n}\n\n/**\n * Never print when stdout/stderr is a data channel, not a human: hook\n * bundles and worker-status-line.mjs set PAI_QUIET_NOTICES=1 via their\n * esbuild banner (scripts/build-hooks.mjs), and a `pai worker run\n * --output-format json` invocation is detected directly off argv since its\n * env can't be set before this module's static imports resolve.\n */\nfunction suppressDeprecationNotices(): boolean {\n  if (process.env.PAI_QUIET_NOTICES === '1') return true;\n  const idx = process.argv.indexOf('--output-format');\n  return idx !== -1 && process.argv[idx + 1] === 'json';\n}\n\n/**\n * At most once per process \u2014 a `globalThis` flag rather than a module-level\n * `let` because hooks and the CLI can end up with more than one instance of\n * this module loaded into the same process (separate bundles), each with its\n * own module scope; only a property on the shared global survives that.\n */\nfunction warnPaiDirDeprecatedOnce(message: string): void {\n  const g = globalThis as typeof globalThis & { __paiDirNoticePrinted?: boolean };\n  if (g.__paiDirNoticePrinted || suppressDeprecationNotices()) return;\n  g.__paiDirNoticePrinted = true;\n  process.stderr.write(`pai: ${message}\\n`);\n}\n\n/**\n * Smart ADAPTER_DIR detection with fallback\n * Priority:\n * 1. ADAPTER_DIR environment variable (if set) \u2014 warns once if PAI_DIR is\n *    ALSO set and resolves to a different path (naming both).\n * 2. PAI_DIR environment variable (deprecated alias, one release) \u2014 warns\n *    once only when it differs from the default adapter root; PAI_DIR set to\n *    the same value as the default is the operator's current, correct\n *    setting and stays silent.\n * 3. ~/.claude (standard location)\n */\nfunction resolveAdapterDir(): string {\n  if (process.env.ADAPTER_DIR) {\n    const adapterDir = resolve(process.env.ADAPTER_DIR);\n    if (process.env.PAI_DIR) {\n      const paiDir = resolve(process.env.PAI_DIR);\n      if (paiDir !== adapterDir) {\n        warnPaiDirDeprecatedOnce(\n          `ADAPTER_DIR (${adapterDir}) and PAI_DIR (${paiDir}) are both set and differ \u2014 ADAPTER_DIR wins. PAI_DIR still works for one release.`\n        );\n      }\n    }\n    return adapterDir;\n  }\n  if (process.env.PAI_DIR) {\n    const paiDir = resolve(process.env.PAI_DIR);\n    if (paiDir !== defaultAdapterDir()) {\n      warnPaiDirDeprecatedOnce(\n        'PAI_DIR is deprecated \u2014 use ADAPTER_DIR for the harness adapter root (~/.claude). PAI_DIR still works for one release.'\n      );\n    }\n    return paiDir;\n  }\n  return defaultAdapterDir();\n}\n\n/** The Claude Code harness adapter root \u2014 NOT PAI's state home. See module doc above. */\nexport const ADAPTER_DIR = resolveAdapterDir();\n\n/** @deprecated alias for ADAPTER_DIR, kept for one release. Prefer ADAPTER_DIR. */\nexport const PAI_DIR = ADAPTER_DIR;\n\n/**\n * Adapter directories \u2014 fixed paths the Claude Code harness itself loads\n * from. These stay under ADAPTER_DIR; they are not PAI state.\n */\nexport const HOOKS_DIR = join(ADAPTER_DIR, 'Hooks');\nexport const SKILLS_DIR = join(ADAPTER_DIR, 'Skills');\nexport const AGENTS_DIR = join(ADAPTER_DIR, 'Agents');\nexport const COMMANDS_DIR = join(ADAPTER_DIR, 'Commands');\n\n/**\n * Validate PAI directory structure on first import\n * This fails fast with a clear error if PAI is misconfigured\n */\nfunction validatePAIStructure(): void {\n  if (!existsSync(ADAPTER_DIR)) {\n    console.error(`ADAPTER_DIR does not exist: ${ADAPTER_DIR}`);\n    console.error(`   Expected ~/.claude or set ADAPTER_DIR environment variable`);\n    process.exit(1);\n  }\n\n  if (!existsSync(HOOKS_DIR)) {\n    console.error(`PAI hooks directory not found: ${HOOKS_DIR}`);\n    console.error(`   Your ADAPTER_DIR may be misconfigured`);\n    console.error(`   Current ADAPTER_DIR: ${ADAPTER_DIR}`);\n    process.exit(1);\n  }\n}\n\n// Run validation on module import\n// This ensures any hook that imports this module will fail fast if paths are wrong\nvalidatePAIStructure();\n\n// ---------------------------------------------------------------------------\n// PAI state written by hooks \u2014 resolves under PAI_HOME, falling back to the\n// pre-2026-09-19 ADAPTER_DIR location (one-time stderr notice) until\n// `pai config migrate --history` moves it. Actively written on every hook\n// event across every session, so unlike most PAI_HOME files the live move is\n// deliberately NOT automatic \u2014 see `pai config migrate --history`.\n// ---------------------------------------------------------------------------\n\nfunction oldHistoryDir(): string {\n  return join(ADAPTER_DIR, 'History');\n}\n\n/** Read/write location for hook-captured history: PAI_HOME/History if\n *  present, else the old ADAPTER_DIR/History (one-time stderr notice). */\nexport function historyDir(): string {\n  return resolvePaiFile(paiHomePath('History'), [oldHistoryDir()], 'pai config migrate --history');\n}\n\nexport function migrateHistoryDir(opts: { dryRun?: boolean } = {}): MigrateDirResult {\n  return migratePaiDir(paiHomePath('History'), [oldHistoryDir()], opts);\n}\n\nfunction oldAgentSessionsPath(): string {\n  return join(ADAPTER_DIR, 'agent-sessions.json');\n}\n\nexport function agentSessionsPath(): string {\n  return resolvePaiFile(paiHomePath('agent-sessions.json'), [oldAgentSessionsPath()], 'pai config migrate --history');\n}\n\nexport function migrateAgentSessions(opts: { dryRun?: boolean } = {}): MigrateFileResult {\n  return migratePaiFile(paiHomePath('agent-sessions.json'), [oldAgentSessionsPath()], opts);\n}\n\nfunction oldSecurityEventsPath(): string {\n  return join(ADAPTER_DIR, 'history', 'security', 'security-events.jsonl');\n}\n\nexport function securityEventsPath(): string {\n  return resolvePaiFile(\n    paiHomePath('History', 'security', 'security-events.jsonl'),\n    [oldSecurityEventsPath()],\n    'pai config migrate --history'\n  );\n}\n\nexport function migrateSecurityEvents(opts: { dryRun?: boolean } = {}): MigrateFileResult {\n  return migratePaiFile(paiHomePath('History', 'security', 'security-events.jsonl'), [oldSecurityEventsPath()], opts);\n}\n\nfunction oldSessionRoutingPath(): string {\n  return join(ADAPTER_DIR, 'session-routing.json');\n}\n\nexport function sessionRoutingPath(): string {\n  return resolvePaiFile(paiHomePath('session-routing.json'), [oldSessionRoutingPath()], 'pai config migrate --history');\n}\n\nexport function migrateSessionRouting(opts: { dryRun?: boolean } = {}): MigrateFileResult {\n  return migratePaiFile(paiHomePath('session-routing.json'), [oldSessionRoutingPath()], opts);\n}\n\n/**\n * Helper to get history file path with date-based organization\n */\nexport function getHistoryFilePath(subdir: string, filename: string): string {\n  const now = new Date();\n  const tz = process.env.TIME_ZONE || Intl.DateTimeFormat().resolvedOptions().timeZone;\n  const localDate = new Date(now.toLocaleString('en-US', { timeZone: tz }));\n  const year = localDate.getFullYear();\n  const month = String(localDate.getMonth() + 1).padStart(2, '0');\n\n  return join(historyDir(), subdir, `${year}-${month}`, filename);\n}\n", "/**\n * pai-home.ts \u2014 the PAI_HOME namespace directory and generic per-user file\n * migration into it.\n *\n * Every PAI-owned per-user file (workers.yaml, config.json, whisper-rules.md,\n * advisor-mode.json, ...) lives under PAI_HOME (~/.claude/pai by default) so\n * nothing PAI writes can ever collide with a file Claude Code itself\n * introduces under ~/.claude. Decided 2026-09-19 \u2014 see docs/workers-config.md.\n */\n\nimport { existsSync, mkdirSync, readFileSync, copyFileSync, renameSync, readdirSync, statSync } from \"node:fs\";\nimport { homedir } from \"node:os\";\nimport { join, dirname } from \"node:path\";\nimport { execFileSync } from \"node:child_process\";\n\n/**\n * Byte-identical check that works for files of any size. `cmp` compares by\n * streaming rather than loading either file into memory, unlike a\n * readFileSync + Buffer.compare \u2014 which throws (\"File size is greater than\n * 2 GiB\") on anything past Node's 2GiB single-read ceiling, a real limit hit\n * migrating a multi-gigabyte federation.db.\n */\nfunction filesByteIdentical(a: string, b: string): boolean {\n  try {\n    execFileSync(\"cmp\", [\"-s\", a, b], { stdio: \"pipe\" });\n    return true;\n  } catch (e) {\n    if (e && typeof e === \"object\" && \"status\" in e && (e as { status: number }).status === 1) return false;\n    // cmp missing or errored for an unrelated reason \u2014 fall back to an\n    // in-memory compare (fine for the small files this path normally sees).\n    return Buffer.compare(readFileSync(a), readFileSync(b)) === 0;\n  }\n}\n\n/** PAI_HOME (test isolation, power users) overrides the default namespace dir. */\nexport function paiHomeDir(): string {\n  return process.env.PAI_HOME || join(homedir(), \".claude\", \"pai\");\n}\n\n/** A path under PAI_HOME, e.g. `paiHomePath(\"config.json\")`. */\nexport function paiHomePath(...segments: string[]): string {\n  return join(paiHomeDir(), ...segments);\n}\n\nconst noticesPrinted = new Set<string>();\n\n/**\n * Resolve a per-user PAI file: the new PAI_HOME path if it exists, else the\n * first existing entry in `oldCandidates` (checked in order \u2014 most recent\n * old location first), else the new path (the target a first write\n * creates). Prints one stderr notice per process per new-path when a\n * fallback location is actually used.\n */\nexport function resolvePaiFile(newPath: string, oldCandidates: string[], migrateHint: string): string {\n  if (existsSync(newPath)) return newPath;\n  for (const old of oldCandidates) {\n    if (existsSync(old)) {\n      if (!noticesPrinted.has(newPath)) {\n        noticesPrinted.add(newPath);\n        process.stderr.write(\n          `pai: ${old} is at an old location \u2014 run \\`${migrateHint}\\` to move it to ${newPath}\\n`\n        );\n      }\n      return old;\n    }\n  }\n  return newPath;\n}\n\nexport class PaiFileMigrationError extends Error {}\n\nexport interface MigrateFileResult {\n  fromPath: string | null;\n  toPath: string;\n  dryRun: boolean;\n  note?: string;\n}\n\n/**\n * Move a per-user PAI file to its new PAI_HOME location: copy, verify\n * byte-identical, then rename the source to `<name>.migrated-<YYYYMMDD>`\n * (never deleted). Idempotent: if the new path already holds bytes\n * identical to the found source, the source is just renamed aside; if it\n * differs, this refuses rather than overwrite silently.\n */\nexport function migratePaiFile(\n  newPath: string,\n  oldCandidates: string[],\n  opts: { dryRun?: boolean } = {}\n): MigrateFileResult {\n  const fromPath = oldCandidates.find((p) => existsSync(p)) ?? null;\n  if (!fromPath) {\n    const note = existsSync(newPath) ? \"already at new location\" : \"nothing to migrate \u2014 file does not exist yet\";\n    return { fromPath: null, toPath: newPath, dryRun: !!opts.dryRun, note };\n  }\n  if (opts.dryRun) return { fromPath, toPath: newPath, dryRun: true };\n\n  const stamp = new Date().toISOString().slice(0, 10).replace(/-/g, \"\");\n\n  if (existsSync(newPath)) {\n    if (filesByteIdentical(fromPath, newPath)) {\n      renameSync(fromPath, `${fromPath}.migrated-${stamp}`);\n      return { fromPath, toPath: newPath, dryRun: false, note: \"identical \u2014 old file renamed aside\" };\n    }\n    throw new PaiFileMigrationError(\n      `${newPath} already exists and differs from ${fromPath} \u2014 resolve manually, nothing changed`\n    );\n  }\n\n  const dir = dirname(newPath);\n  if (!existsSync(dir)) mkdirSync(dir, { recursive: true });\n  copyFileSync(fromPath, newPath);\n\n  if (!filesByteIdentical(fromPath, newPath)) {\n    throw new PaiFileMigrationError(\n      `${newPath}: copy did not match ${fromPath} byte-for-byte \u2014 aborting, old file left in place`\n    );\n  }\n\n  renameSync(fromPath, `${fromPath}.migrated-${stamp}`);\n  return { fromPath, toPath: newPath, dryRun: false };\n}\n\nexport interface MigrateDirResult {\n  fromDir: string | null;\n  toDir: string;\n  dryRun: boolean;\n  movedCount?: number;\n  note?: string;\n}\n\n/**\n * Move a per-user PAI directory (queries/, session-state/, ...) into\n * PAI_HOME: move every entry from the first found old candidate into the\n * new dir (never overwriting an existing entry there), then rename the now-\n * empty old dir aside to `<name>.migrated-<YYYYMMDD>` (never deleted).\n * Idempotent \u2014 an already-migrated dir has nothing left to find.\n */\nexport function migratePaiDir(\n  newDir: string,\n  oldCandidates: string[],\n  opts: { dryRun?: boolean } = {}\n): MigrateDirResult {\n  const fromDir = oldCandidates.find((p) => existsSync(p) && statSync(p).isDirectory()) ?? null;\n  if (!fromDir) {\n    const note = existsSync(newDir) ? \"already at new location\" : \"nothing to migrate \u2014 directory does not exist yet\";\n    return { fromDir: null, toDir: newDir, dryRun: !!opts.dryRun, note };\n  }\n\n  const entries = readdirSync(fromDir);\n  if (opts.dryRun) return { fromDir, toDir: newDir, dryRun: true, movedCount: entries.length };\n\n  if (!existsSync(newDir)) mkdirSync(newDir, { recursive: true });\n\n  let moved = 0;\n  const collided: string[] = [];\n  for (const entry of entries) {\n    const src = join(fromDir, entry);\n    const dst = join(newDir, entry);\n    if (existsSync(dst)) {\n      collided.push(entry);\n      continue;\n    }\n    renameSync(src, dst);\n    moved++;\n  }\n\n  const stamp = new Date().toISOString().slice(0, 10).replace(/-/g, \"\");\n  const remaining = readdirSync(fromDir);\n  const note = collided.length\n    ? `${collided.length} entrie(s) left in ${fromDir} \u2014 name already existed in ${newDir}`\n    : undefined;\n\n  if (remaining.length === 0) {\n    renameSync(fromDir, `${fromDir}.migrated-${stamp}`);\n  }\n\n  return { fromDir, toDir: newDir, dryRun: false, movedCount: moved, note };\n}\n", "/**\n * Push notification helpers \u2014 WhatsApp-aware with ntfy.sh fallback.\n */\n\nimport { existsSync, readFileSync } from 'fs';\nimport { join } from 'path';\nimport { homedir } from 'os';\n\n/**\n * Check if a messaging MCP server (AIBroker, Whazaa, or Telex) is configured.\n * When any messaging server is active, the AI handles notifications via MCP\n * and ntfy is skipped to avoid duplicates.\n */\nexport function isWhatsAppEnabled(): boolean {\n  try {\n    const settingsPath = join(homedir(), '.claude', 'settings.json');\n    if (!existsSync(settingsPath)) return false;\n\n    const settings = JSON.parse(readFileSync(settingsPath, 'utf-8'));\n    const enabled: string[] = settings.enabledMcpjsonServers || [];\n    return enabled.includes('aibroker') || enabled.includes('whazaa') || enabled.includes('telex');\n  } catch {\n    return false;\n  }\n}\n\n/**\n * Send push notification \u2014 WhatsApp-aware with ntfy fallback.\n *\n * When WhatsApp (Whazaa) is enabled in MCP config, ntfy is SKIPPED\n * because the AI sends WhatsApp messages directly via MCP.\n * When WhatsApp is NOT configured, ntfy fires as the fallback channel.\n */\nexport async function sendNtfyNotification(message: string, retries = 2): Promise<boolean> {\n  if (isWhatsAppEnabled()) {\n    console.error(`WhatsApp (Whazaa) enabled in MCP config \u2014 skipping ntfy`);\n    return true;\n  }\n\n  const topic = process.env.NTFY_TOPIC;\n\n  if (!topic) {\n    console.error('NTFY_TOPIC not set and WhatsApp not active \u2014 notifications disabled');\n    return false;\n  }\n\n  for (let attempt = 0; attempt <= retries; attempt++) {\n    try {\n      const response = await fetch(`https://ntfy.sh/${topic}`, {\n        method: 'POST',\n        body: message,\n        headers: {\n          'Title': 'Claude Code',\n          'Priority': 'default',\n        },\n      });\n\n      if (response.ok) {\n        console.error(`ntfy.sh notification sent (WhatsApp inactive): \"${message}\"`);\n        return true;\n      } else {\n        console.error(`ntfy.sh attempt ${attempt + 1} failed: ${response.status}`);\n      }\n    } catch (error) {\n      console.error(`ntfy.sh attempt ${attempt + 1} error: ${error}`);\n    }\n\n    if (attempt < retries) {\n      await new Promise(resolve => setTimeout(resolve, 1000));\n    }\n  }\n\n  console.error('ntfy.sh notification failed after all retries');\n  return false;\n}\n", "/**\n * Session note creation, editing, checkpointing, renaming, and finalization.\n */\n\nimport { existsSync, mkdirSync, readdirSync, readFileSync, writeFileSync, renameSync } from 'fs';\nimport { join, basename } from 'path';\n\n// ---------------------------------------------------------------------------\n// Internal helpers\n// ---------------------------------------------------------------------------\n\n/** Get or create the YYYY/MM subdirectory for the current month inside notesDir. */\nfunction getMonthDir(notesDir: string): string {\n  const now = new Date();\n  const year = String(now.getFullYear());\n  const month = String(now.getMonth() + 1).padStart(2, '0');\n  const monthDir = join(notesDir, year, month);\n  if (!existsSync(monthDir)) {\n    mkdirSync(monthDir, { recursive: true });\n  }\n  return monthDir;\n}\n\n// ---------------------------------------------------------------------------\n// Public API\n// ---------------------------------------------------------------------------\n\n/**\n * Get the next note number (4-digit format: 0001, 0002, etc.).\n * Numbers are scoped per YYYY/MM directory.\n */\nexport function getNextNoteNumber(notesDir: string): string {\n  const monthDir = getMonthDir(notesDir);\n\n  const files = readdirSync(monthDir)\n    .filter(f => f.match(/^\\d{3,4}[\\s_-]/))\n    .sort();\n\n  if (files.length === 0) return '0001';\n\n  let maxNumber = 0;\n  for (const file of files) {\n    const digitMatch = file.match(/^(\\d+)/);\n    if (digitMatch) {\n      const num = parseInt(digitMatch[1], 10);\n      if (num > maxNumber) maxNumber = num;\n    }\n  }\n\n  return String(maxNumber + 1).padStart(4, '0');\n}\n\n/**\n * Get the current (latest) note file path, or null if none exists.\n * Searches current month \u2192 previous month \u2192 flat notesDir (legacy).\n */\nexport function getCurrentNotePath(notesDir: string): string | null {\n  if (!existsSync(notesDir)) return null;\n\n  const findLatestIn = (dir: string): string | null => {\n    if (!existsSync(dir)) return null;\n    const files = readdirSync(dir)\n      .filter(f => f.match(/^\\d{3,4}[\\s_-].*\\.md$/))\n      .sort((a, b) => {\n        const numA = parseInt(a.match(/^(\\d+)/)?.[1] || '0', 10);\n        const numB = parseInt(b.match(/^(\\d+)/)?.[1] || '0', 10);\n        return numA - numB;\n      });\n    if (files.length === 0) return null;\n    return join(dir, files[files.length - 1]);\n  };\n\n  const now = new Date();\n  const year = String(now.getFullYear());\n  const month = String(now.getMonth() + 1).padStart(2, '0');\n  const currentMonthDir = join(notesDir, year, month);\n  const found = findLatestIn(currentMonthDir);\n  if (found) return found;\n\n  const prevDate = new Date(now.getFullYear(), now.getMonth() - 1, 1);\n  const prevYear = String(prevDate.getFullYear());\n  const prevMonth = String(prevDate.getMonth() + 1).padStart(2, '0');\n  const prevMonthDir = join(notesDir, prevYear, prevMonth);\n  const prevFound = findLatestIn(prevMonthDir);\n  if (prevFound) return prevFound;\n\n  return findLatestIn(notesDir);\n}\n\n/**\n * Create a new session note.\n * Format: \"NNNN - YYYY-MM-DD - New Session.md\" filed into YYYY/MM subdirectory.\n * Claude MUST rename at session end with a meaningful description.\n */\nexport function createSessionNote(notesDir: string, description: string): string {\n  const noteNumber = getNextNoteNumber(notesDir);\n  const date = new Date().toISOString().split('T')[0];\n  const monthDir = getMonthDir(notesDir);\n  const filename = `${noteNumber} - ${date} - New Session.md`;\n  const filepath = join(monthDir, filename);\n\n  const content = `# Session ${noteNumber}: ${description}\n\n**Date:** ${date}\n**Status:** In Progress\n\n---\n\n## Work Done\n\n<!-- PAI will add completed work here during session -->\n\n---\n\n## Next Steps\n\n<!-- To be filled at session end -->\n\n---\n\n**Tags:** #Session\n`;\n\n  writeFileSync(filepath, content);\n  console.error(`Created session note: ${filename}`);\n\n  return filepath;\n}\n\n/** Append a checkpoint to the current session note. */\nexport function appendCheckpoint(notePath: string, checkpoint: string): void {\n  if (!existsSync(notePath)) {\n    console.error(`Note file not found, recreating: ${notePath}`);\n    try {\n      const parentDir = join(notePath, '..');\n      if (!existsSync(parentDir)) mkdirSync(parentDir, { recursive: true });\n      const noteFilename = basename(notePath);\n      const numberMatch = noteFilename.match(/^(\\d+)/);\n      const noteNumber = numberMatch ? numberMatch[1] : '0000';\n      const date = new Date().toISOString().split('T')[0];\n      const content = `# Session ${noteNumber}: Recovered\\n\\n**Date:** ${date}\\n**Status:** In Progress\\n\\n---\\n\\n## Work Done\\n\\n<!-- PAI will add completed work here during session -->\\n\\n---\\n\\n## Next Steps\\n\\n<!-- To be filled at session end -->\\n\\n---\\n\\n**Tags:** #Session\\n`;\n      writeFileSync(notePath, content);\n      console.error(`Recreated session note: ${noteFilename}`);\n    } catch (err) {\n      console.error(`Failed to recreate note: ${err}`);\n      return;\n    }\n  }\n\n  const content = readFileSync(notePath, 'utf-8');\n  const timestamp = new Date().toISOString();\n  const checkpointText = `\\n### Checkpoint ${timestamp}\\n\\n${checkpoint}\\n`;\n\n  const nextStepsIndex = content.indexOf('## Next Steps');\n  const newContent = nextStepsIndex !== -1\n    ? content.substring(0, nextStepsIndex) + checkpointText + content.substring(nextStepsIndex)\n    : content + checkpointText;\n\n  writeFileSync(notePath, newContent);\n  console.error(`Checkpoint added to: ${basename(notePath)}`);\n}\n\n/** Work item for session notes. */\nexport interface WorkItem {\n  title: string;\n  details?: string[];\n  completed?: boolean;\n}\n\n/** Add work items to the \"Work Done\" section of a session note. */\nexport function addWorkToSessionNote(notePath: string, workItems: WorkItem[], sectionTitle?: string): void {\n  if (!existsSync(notePath)) {\n    console.error(`Note file not found: ${notePath}`);\n    return;\n  }\n\n  let content = readFileSync(notePath, 'utf-8');\n\n  let workText = '';\n  if (sectionTitle) workText += `\\n### ${sectionTitle}\\n\\n`;\n\n  for (const item of workItems) {\n    const checkbox = item.completed !== false ? '[x]' : '[ ]';\n    workText += `- ${checkbox} **${item.title}**\\n`;\n    if (item.details && item.details.length > 0) {\n      for (const detail of item.details) {\n        workText += `  - ${detail}\\n`;\n      }\n    }\n  }\n\n  const workDoneMatch = content.match(/## Work Done\\n\\n(<!-- .*? -->)?/);\n  if (workDoneMatch) {\n    const insertPoint = content.indexOf(workDoneMatch[0]) + workDoneMatch[0].length;\n    content = content.substring(0, insertPoint) + workText + content.substring(insertPoint);\n  } else {\n    const nextStepsIndex = content.indexOf('## Next Steps');\n    if (nextStepsIndex !== -1) {\n      content = content.substring(0, nextStepsIndex) + workText + '\\n' + content.substring(nextStepsIndex);\n    }\n  }\n\n  writeFileSync(notePath, content);\n  console.error(`Added ${workItems.length} work item(s) to: ${basename(notePath)}`);\n}\n\n/**\n * Check if a candidate title is meaningless / garbage.\n * Public wrapper around the internal filter for use by other hooks.\n */\nexport function isMeaningfulTitle(text: string): boolean {\n  return !isMeaninglessCandidate(text);\n}\n\n/** Sanitize a string for use in a filename. */\nexport function sanitizeForFilename(str: string): string {\n  return str\n    .toLowerCase()\n    .replace(/[^a-z0-9\\s-]/g, '')\n    .replace(/\\s+/g, '-')\n    .replace(/-+/g, '-')\n    .replace(/^-|-$/g, '')\n    .substring(0, 50);\n}\n\n/**\n * Return true if the candidate string should be rejected as a meaningful name.\n * Rejects file paths, shebangs, timestamps, system noise, XML tags, hashes, etc.\n */\nfunction isMeaninglessCandidate(text: string): boolean {\n  const t = text.trim();\n  if (!t) return true;\n  if (t.length < 5) return true;                              // too short to be meaningful\n  if (t.startsWith('/') || t.startsWith('~')) return true;    // file path\n  if (t.startsWith('#!')) return true;                         // shebang\n  if (t.includes('[object Object]')) return true;              // serialization artifact\n  if (/^\\d{4}-\\d{2}-\\d{2}(T[\\d:.Z+-]+)?$/.test(t)) return true; // ISO timestamp\n  if (/^\\d{1,2}:\\d{2}(:\\d{2})?(\\s*(AM|PM))?$/i.test(t)) return true; // time-only\n  if (/^<[a-z-]+[\\s/>]/i.test(t)) return true;               // XML/HTML tags (<task-notification>, etc.)\n  if (/^[0-9a-f]{10,}$/i.test(t)) return true;               // hex hash strings\n  if (/^Exit code \\d+/i.test(t)) return true;                 // exit code messages\n  if (/^Error:/i.test(t)) return true;                        // error messages\n  if (/^This session is being continued/i.test(t)) return true; // continuation boilerplate\n  if (/^\\(Bash completed/i.test(t)) return true;              // bash output noise\n  if (/^Task Notification$/i.test(t)) return true;            // literal \"Task Notification\"\n  if (/^New Session$/i.test(t)) return true;                  // placeholder title\n  if (/^Recovered Session$/i.test(t)) return true;            // placeholder title\n  if (/^Continued Session$/i.test(t)) return true;            // placeholder title\n  if (/^Untitled Session$/i.test(t)) return true;             // placeholder title\n  if (/^Context Compression$/i.test(t)) return true;          // compression artifact\n  if (/^[A-Fa-f0-9]{8,}\\s+Output$/i.test(t)) return true;   // hash + \"Output\" pattern\n  return false;\n}\n\n/**\n * Extract a meaningful name from session note content and summary.\n * Looks at Work Done section headers, bold text, and summary.\n */\nexport function extractMeaningfulName(noteContent: string, summary: string): string {\n  const workDoneMatch = noteContent.match(/## Work Done\\n\\n([\\s\\S]*?)(?=\\n---|\\n## Next)/);\n\n  if (workDoneMatch) {\n    const workDoneSection = workDoneMatch[1];\n\n    const subheadings = workDoneSection.match(/### ([^\\n]+)/g);\n    if (subheadings && subheadings.length > 0) {\n      const firstHeading = subheadings[0].replace('### ', '').trim();\n      if (!isMeaninglessCandidate(firstHeading) && firstHeading.length > 5 && firstHeading.length < 60) {\n        return sanitizeForFilename(firstHeading);\n      }\n    }\n\n    const boldMatches = workDoneSection.match(/\\*\\*([^*]+)\\*\\*/g);\n    if (boldMatches && boldMatches.length > 0) {\n      const firstBold = boldMatches[0].replace(/\\*\\*/g, '').trim();\n      if (!isMeaninglessCandidate(firstBold) && firstBold.length > 3 && firstBold.length < 50) {\n        return sanitizeForFilename(firstBold);\n      }\n    }\n\n    const numberedItems = workDoneSection.match(/^\\d+\\.\\s+\\*\\*([^*]+)\\*\\*/m);\n    if (numberedItems && !isMeaninglessCandidate(numberedItems[1])) {\n      return sanitizeForFilename(numberedItems[1]);\n    }\n  }\n\n  if (summary && summary.length > 5 && summary !== 'Session completed.' && !isMeaninglessCandidate(summary)) {\n    const cleanSummary = summary\n      .replace(/[^\\w\\s-]/g, ' ')\n      .trim()\n      .split(/\\s+/)\n      .slice(0, 5)\n      .join(' ');\n    if (cleanSummary.length > 3 && !isMeaninglessCandidate(cleanSummary)) {\n      return sanitizeForFilename(cleanSummary);\n    }\n  }\n\n  return '';\n}\n\n/**\n * Rename a session note with a meaningful name.\n * Always uses \"NNNN - YYYY-MM-DD - Description.md\" format.\n * Returns the new path, or original path if rename fails.\n */\nexport function renameSessionNote(notePath: string, meaningfulName: string): string {\n  if (!meaningfulName || !existsSync(notePath)) return notePath;\n\n  const dir = join(notePath, '..');\n  const oldFilename = basename(notePath);\n\n  const correctMatch = oldFilename.match(/^(\\d{3,4}) - (\\d{4}-\\d{2}-\\d{2}) - .*\\.md$/);\n  const legacyMatch = oldFilename.match(/^(\\d{3,4})_(\\d{4}-\\d{2}-\\d{2})_.*\\.md$/);\n  const match = correctMatch || legacyMatch;\n  if (!match) return notePath;\n\n  const [, noteNumber, date] = match;\n\n  const titleCaseName = meaningfulName\n    .split(/[\\s_-]+/)\n    .map(word => word.charAt(0).toUpperCase() + word.slice(1).toLowerCase())\n    .join(' ')\n    .trim();\n\n  const paddedNumber = noteNumber.padStart(4, '0');\n  const newFilename = `${paddedNumber} - ${date} - ${titleCaseName}.md`;\n  const newPath = join(dir, newFilename);\n\n  if (newFilename === oldFilename) return notePath;\n\n  try {\n    renameSync(notePath, newPath);\n    console.error(`Renamed note: ${oldFilename} \u2192 ${newFilename}`);\n    return newPath;\n  } catch (error) {\n    console.error(`Could not rename note: ${error}`);\n    return notePath;\n  }\n}\n\n/** Update the session note's H1 title and rename the file. */\nexport function updateSessionNoteTitle(notePath: string, newTitle: string): void {\n  if (!existsSync(notePath)) {\n    console.error(`Note file not found: ${notePath}`);\n    return;\n  }\n\n  let content = readFileSync(notePath, 'utf-8');\n  content = content.replace(/^# Session \\d+:.*$/m, (match) => {\n    const sessionNum = match.match(/Session (\\d+)/)?.[1] || '';\n    return `# Session ${sessionNum}: ${newTitle}`;\n  });\n  writeFileSync(notePath, content);\n  renameSessionNote(notePath, sanitizeForFilename(newTitle));\n}\n\n/**\n * Finalize session note \u2014 mark as complete, add summary, rename with meaningful name.\n * IDEMPOTENT: subsequent calls are no-ops if already finalized.\n * Returns the final path (may be renamed).\n */\nexport function finalizeSessionNote(notePath: string, summary: string): string {\n  if (!existsSync(notePath)) {\n    console.error(`Note file not found: ${notePath}`);\n    return notePath;\n  }\n\n  let content = readFileSync(notePath, 'utf-8');\n\n  if (content.includes('**Status:** Completed')) {\n    console.error(`Note already finalized: ${basename(notePath)}`);\n    return notePath;\n  }\n\n  content = content.replace('**Status:** In Progress', '**Status:** Completed');\n\n  if (!content.includes('**Completed:**')) {\n    const completionTime = new Date().toISOString();\n    content = content.replace(\n      '---\\n\\n## Work Done',\n      `**Completed:** ${completionTime}\\n\\n---\\n\\n## Work Done`\n    );\n  }\n\n  const nextStepsMatch = content.match(/## Next Steps\\n\\n(<!-- .*? -->)/);\n  if (nextStepsMatch) {\n    content = content.replace(\n      nextStepsMatch[0],\n      `## Next Steps\\n\\n${summary || 'Session completed.'}`\n    );\n  }\n\n  writeFileSync(notePath, content);\n  console.error(`Session note finalized: ${basename(notePath)}`);\n\n  const meaningfulName = extractMeaningfulName(content, summary);\n  if (meaningfulName) {\n    return renameSessionNote(notePath, meaningfulName);\n  }\n\n  return notePath;\n}\n", "/**\n * TODO.md management \u2014 creation, task updates, checkpoints, and Continue section.\n */\n\nimport { existsSync, mkdirSync, readFileSync, writeFileSync } from 'fs';\nimport { join, basename } from 'path';\nimport { findTodoPath } from './paths.js';\nimport { applyContinue } from '../../../../session/checkpoint-block.js';\n\n// ---------------------------------------------------------------------------\n// Types\n// ---------------------------------------------------------------------------\n\n/** Task item for TODO.md. */\nexport interface TodoItem {\n  content: string;\n  completed: boolean;\n}\n\n// ---------------------------------------------------------------------------\n// Helpers\n// ---------------------------------------------------------------------------\n\n/**\n * Ensure TODO.md exists. Creates it with default structure if missing.\n * Returns the path to the TODO.md file.\n */\nexport function ensureTodoMd(cwd: string): string {\n  const todoPath = findTodoPath(cwd);\n\n  if (!existsSync(todoPath)) {\n    const parentDir = join(todoPath, '..');\n    if (!existsSync(parentDir)) mkdirSync(parentDir, { recursive: true });\n\n    const content = `# TODO\n\n## Current Session\n\n- [ ] (Tasks will be tracked here)\n\n## Backlog\n\n- [ ] (Future tasks)\n\n---\n\n*Last updated: ${new Date().toISOString()}*\n`;\n\n    writeFileSync(todoPath, content);\n    console.error(`Created TODO.md: ${todoPath}`);\n  }\n\n  return todoPath;\n}\n\n// ---------------------------------------------------------------------------\n// Public API\n// ---------------------------------------------------------------------------\n\n/**\n * Update TODO.md with current session tasks.\n * Preserves the Backlog section and ensures exactly ONE timestamp at the end.\n */\nexport function updateTodoMd(cwd: string, tasks: TodoItem[], sessionSummary?: string): void {\n  const todoPath = ensureTodoMd(cwd);\n  const content = readFileSync(todoPath, 'utf-8');\n\n  const backlogMatch = content.match(/## Backlog[\\s\\S]*?(?=\\n---|\\n\\*Last updated|$)/);\n  const backlogSection = backlogMatch\n    ? backlogMatch[0].trim()\n    : '## Backlog\\n\\n- [ ] (Future tasks)';\n\n  const taskLines = tasks.length > 0\n    ? tasks.map(t => `- [${t.completed ? 'x' : ' '}] ${t.content}`).join('\\n')\n    : '- [ ] (No active tasks)';\n\n  const newContent = `# TODO\n\n## Current Session\n\n${taskLines}\n\n${sessionSummary ? `**Session Summary:** ${sessionSummary}\\n\\n` : ''}${backlogSection}\n\n---\n\n*Last updated: ${new Date().toISOString()}*\n`;\n\n  writeFileSync(todoPath, newContent);\n  console.error(`Updated TODO.md: ${todoPath}`);\n}\n\n/**\n * Add a checkpoint entry to TODO.md (without replacing tasks).\n * Ensures exactly ONE timestamp line at the end.\n */\nexport function addTodoCheckpoint(cwd: string, checkpoint: string): void {\n  const todoPath = ensureTodoMd(cwd);\n  let content = readFileSync(todoPath, 'utf-8');\n\n  // Remove ALL existing timestamp lines and trailing separators\n  content = content.replace(/(\\n---\\s*)*(\\n\\*Last updated:.*\\*\\s*)+$/g, '');\n\n  const checkpointText = `\\n**Checkpoint (${new Date().toISOString()}):** ${checkpoint}\\n\\n`;\n\n  const backlogIndex = content.indexOf('## Backlog');\n  if (backlogIndex !== -1) {\n    content = content.substring(0, backlogIndex) + checkpointText + content.substring(backlogIndex);\n  } else {\n    const continueIndex = content.indexOf('## Continue');\n    if (continueIndex !== -1) {\n      const afterContinue = content.indexOf('\\n---', continueIndex);\n      if (afterContinue !== -1) {\n        const insertAt = afterContinue + 4;\n        content = content.substring(0, insertAt) + '\\n' + checkpointText + content.substring(insertAt);\n      } else {\n        content = content.trimEnd() + '\\n' + checkpointText;\n      }\n    } else {\n      content = content.trimEnd() + '\\n' + checkpointText;\n    }\n  }\n\n  content = content.trimEnd() + `\\n\\n---\\n\\n*Last updated: ${new Date().toISOString()}*\\n`;\n\n  writeFileSync(todoPath, content);\n  console.error(`Checkpoint added to TODO.md`);\n}\n\n/**\n * The Claude Code session UUID, read off the transcript path.\n *\n * Claude Code names a transcript `<session-uuid>.jsonl`, so the hooks that\n * receive a transcript path are holding the session's identity without knowing\n * it. Both session-end writers had one available and neither used it.\n *\n * Shape-checked rather than trusted: only a UUID is returned, so a renamed,\n * archived or otherwise unexpected filename yields undefined and the caller\n * falls back to the session line rather than writing a `session-id` that\n * identifies nothing. A wrong id is worse than none \u2014 it would make two\n * different sessions compare equal.\n */\nexport function sessionIdFromTranscript(transcriptPath: string | undefined): string | undefined {\n  if (!transcriptPath) return undefined;\n  const base = basename(transcriptPath).replace(/\\.jsonl$/, '');\n  return /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test(base)\n    ? base\n    : undefined;\n}\n\n/**\n * Update the ## Continue section at the top of TODO.md.\n *\n * This is the unattended writer: the pre-compact hook and the daemon's\n * work-queue worker both come through here. It used to build its own block and\n * strip the existing section with `/## Continue\\n[\\s\\S]*?\\n---\\n+/` \u2014 a\n * non-greedy match to the first `---`, which cuts a rich checkpoint in half at\n * the first horizontal rule inside its body and leaves the remainder orphaned\n * in the document.\n *\n * More importantly it was the writer that actually destroyed model-authored\n * checkpoints: `pai pause` wrote one, then this ran seconds later on session\n * end and replaced it with `Working directory: \u2026 Check the latest session note\n * for details.`\n *\n * It now delegates to the shared checkpoint module in \"auto\" mode, which means\n * it inherits the preservation rules rather than reimplementing them. Guarding\n * here rather than at each of the three call sites is deliberate: any future\n * caller inherits the behaviour without knowing it exists.\n */\nexport function updateTodoContinue(\n  cwd: string,\n  noteFilename: string,\n  state: string | null,\n  tokenDisplay: string,\n  /**\n   * The Claude Code session UUID, when the caller knows it.\n   *\n   * Optional only because a caller may genuinely not have it; supply it\n   * whenever you can. Without it `isSameSession` falls back to comparing the\n   * note FILENAME, and pausing renames the note \u2014 so a session stops\n   * recognising its own checkpoint seconds after writing it, and an automated\n   * write is then free to replace it as though it belonged to a predecessor.\n   * That is how a live session lost a model-authored handover on 2026-08-04.\n   */\n  sessionId?: string\n): void {\n  // Ensure a TODO.md exists so applyContinue writes to the same file the rest\n  // of the hooks lib uses.\n  ensureTodoMd(cwd);\n\n  const result = applyContinue({\n    rootPath: cwd,\n    authored: 'auto',\n    // The hooks identify a session by its note filename; `pai pause` resolves\n    // the same string from the registry (and falls back to this filename when\n    // the registry has no session row). They must agree, because this string\n    // is the key that decides whether an authored checkpoint belongs to the\n    // current session and must be preserved.\n    sessionLine: noteFilename.replace(/\\.md$/, ''),\n    // The UUID is authoritative when present; the line above is the fallback\n    // for callers that cannot supply one, and it is mutable by design.\n    sessionId,\n    cwd,\n    body: state?.trim() || undefined,\n  });\n\n  if (result.action === 'preserved') {\n    console.error(\n      'TODO.md ## Continue left intact \u2014 authored checkpoint for this session'\n    );\n    return;\n  }\n\n  if (result.action === 'failed') {\n    console.error(`TODO.md ## Continue update failed: ${result.error}`);\n    return;\n  }\n\n  // Refresh the trailing \"Last updated\" stamp without disturbing the block.\n  try {\n    const todoPath = result.path!;\n    const now = new Date().toISOString();\n    let content = readFileSync(todoPath, 'utf-8');\n    content = content.replace(/(\\n---\\s*)*(\\n\\*Last updated:.*\\*\\s*)+$/g, '');\n    content = content.trimEnd() + `\\n\\n---\\n\\n*Last updated: ${now}*\\n`;\n    writeFileSync(todoPath, content);\n  } catch {\n    // Non-fatal \u2014 the checkpoint itself is already written.\n  }\n\n  console.error(\n    result.carriedForward\n      ? 'TODO.md ## Continue section updated (previous content carried forward)'\n      : 'TODO.md ## Continue section updated'\n  );\n}\n", "/**\n * Shared \"## Continue\" checkpoint logic for a project's TODO.md.\n *\n * WHY THIS MODULE EXISTS\n * ----------------------\n * Before this, `pause.ts` and `handover.ts` each carried their own copy of\n * findProjectTodo / stripContinueSection / block-builder. Both wrote the same\n * fixed four-line block, and both stripped any existing ## Continue section\n * unconditionally. The consequence was that a rich, model-authored checkpoint\n * could never survive:\n *\n *   1. `pai pause` had no way to accept a body, so the model printed its\n *      checkpoint to the terminal and it was lost.\n *   2. Even if a body had been written by hand, the session-stop hook runs\n *      `pai session handover` on every clean exit, which regenerated the\n *      generic block and erased it.\n *\n * So there are two jobs here:\n *\n *   - AUTHORED writes (`pai pause --body-file`) carry the model's markdown\n *     verbatim, wrapped in explicit start/end markers.\n *   - AUTO writes (hooks) must never destroy an authored checkpoint belonging\n *     to the *same* session. They may replace a stale one left by an earlier\n *     session, otherwise TODO.md would show a checkpoint that no longer\n *     describes where the work stands.\n *\n * PARSING\n * -------\n * A rich body can legitimately contain `---` rules and `##` headings, which the\n * old heuristic scanner treated as section terminators. Authored blocks are\n * therefore delimited by an explicit HTML-comment pair; the legacy heuristic is\n * kept only as a fallback for blocks written before this change.\n */\n\nimport {\n  existsSync,\n  readFileSync,\n  writeFileSync,\n  readdirSync,\n  renameSync,\n  mkdirSync,\n  realpathSync,\n} from \"node:fs\";\nimport { join } from \"node:path\";\nimport { homedir } from \"node:os\";\n\n// ---------------------------------------------------------------------------\n// Constants\n// ---------------------------------------------------------------------------\n\n/** Locations searched for a project TODO.md, in priority order. */\nexport const TODO_LOCATIONS = [\n  \"Notes/TODO.md\",\n  \".claude/Notes/TODO.md\",\n  \"tasks/todo.md\",\n  \"TODO.md\",\n];\n\nexport const MARKER_OPEN = \"<!-- pai:checkpoint\";\nexport const MARKER_CLOSE = \"<!-- /pai:checkpoint -->\";\n\n/**\n * Markers for a handover that has been superseded but not thrown away.\n *\n * Deliberately NOT `pai:checkpoint`: `locateContinue` scans for that marker and\n * for the `## Continue` heading, so an archived block written verbatim would be\n * found as the live section and rewritten in place of it. Archived entries are\n * inert by construction \u2014 different marker, no heading.\n */\nexport const ARCHIVE_OPEN = \"<!-- pai:archived-handover\";\nexport const ARCHIVE_CLOSE = \"<!-- /pai:archived-handover -->\";\nexport const ARCHIVE_HEADING = \"## Previous handovers\";\n\n/**\n * How many superseded handovers TODO.md keeps.\n *\n * Enough that a handover survives a run of sessions that each end without\n * pausing, which is the sequence that destroyed one on 2026-08-04. Not\n * unbounded: TODO.md is read by a human at the top of every session, and a file\n * that grows without limit stops being read, which is its own kind of loss.\n */\nexport const ARCHIVE_LIMIT = 5;\n\nexport const CONTINUE_HEADING = \"## Continue\";\n\n/** Who wrote the checkpoint currently in TODO.md. */\nexport type Authorship = \"model\" | \"auto\";\n\n/** Result of applying a checkpoint. */\nexport type ApplyAction = \"written\" | \"preserved\" | \"failed\";\n\n// ---------------------------------------------------------------------------\n// TODO.md discovery\n// ---------------------------------------------------------------------------\n\nexport function findProjectTodo(\n  rootPath: string\n): { path: string; content: string } | null {\n  for (const rel of TODO_LOCATIONS) {\n    const full = join(rootPath, rel);\n    if (existsSync(full)) {\n      try {\n        return { path: full, content: readFileSync(full, \"utf8\") };\n      } catch {\n        // unreadable \u2014 try next\n      }\n    }\n  }\n  return null;\n}\n\n/**\n * Resolve the TODO.md to write to, creating Notes/ if nothing exists yet.\n * Returns null only when the directory could not be created.\n */\nexport function resolveTodoTarget(\n  rootPath: string,\n  opts: { create?: boolean } = {}\n): { path: string; content: string } | null {\n  const found = findProjectTodo(rootPath);\n  if (found) return found;\n\n  const notesDir = join(rootPath, \"Notes\");\n  if (opts.create !== false) {\n    try {\n      if (!existsSync(notesDir)) mkdirSync(notesDir, { recursive: true });\n    } catch {\n      return null;\n    }\n  }\n  return { path: join(notesDir, \"TODO.md\"), content: \"\" };\n}\n\n// ---------------------------------------------------------------------------\n// Marker parsing\n// ---------------------------------------------------------------------------\n\nexport interface CheckpointMeta {\n  authored: Authorship;\n  /** Session line the checkpoint describes, e.g. \"0003 - 2026-08-01 - Title\". */\n  session?: string;\n  /** Claude Code session UUID, when known. */\n  sessionId?: string;\n  ts?: string;\n}\n\n/**\n * Parse a `<!-- pai:checkpoint key=\"value\" ... -->` marker line.\n * Returns null when the line is not a marker.\n */\nexport function parseMarker(line: string): CheckpointMeta | null {\n  const trimmed = line.trim();\n  if (!trimmed.startsWith(MARKER_OPEN)) return null;\n\n  const attrs: Record<string, string> = {};\n  for (const m of trimmed.matchAll(/([a-zA-Z][\\w-]*)=\"([^\"]*)\"/g)) {\n    attrs[m[1]] = m[2];\n  }\n\n  return {\n    authored: attrs.authored === \"model\" ? \"model\" : \"auto\",\n    session: attrs.session || undefined,\n    sessionId: attrs[\"session-id\"] || undefined,\n    ts: attrs.ts || undefined,\n  };\n}\n\nexport interface LocatedContinue {\n  /** Index of the \"## Continue\" heading line. */\n  startIdx: number;\n  /** Exclusive end index, past any trailing `---` separator. */\n  endIdx: number;\n  meta: CheckpointMeta | null;\n  /** Raw lines of the section, heading included. */\n  lines: string[];\n}\n\n/**\n * Lines an auto-generated block is made of. Anything else in a section is\n * content somebody put there deliberately.\n *\n * The blockquote marker is optional on every pattern. These lines appear\n * quoted when the block builder emits them as its header, and unquoted when a\n * caller passes the same text as a BODY \u2014 which the session-stop hook does: it\n * builds `Working directory: ${cwd}` and hands it over as state. Matching only\n * the quoted form made such a body read as content, which is how a session\n * that did no work at all overwrote a real handover on 2026-08-04. A line that\n * merely restates the generated header is boilerplate wherever it sits.\n */\nconst BOILERPLATE_PATTERNS = [\n  /^##\\s+Continue$/,\n  /^<!--\\s*\\/?pai:checkpoint/,\n  /^>?\\s*\\*\\*Last session:\\*\\*/,\n  /^>?\\s*\\*\\*Paused at:\\*\\*/,\n  /^>?\\s*Working directory:/,\n  /^>?\\s*Resume with:/,\n  /^>?\\s*_No checkpoint body was recorded/,\n  /^-{3,}$/,\n];\n\n/** A blank line, with or without a blockquote marker. */\nconst BLANK_LINE = /^>?\\s*$/;\n\n/**\n * True when a section contains nothing but generated header lines.\n *\n * This is the guard that stops an auto write from destroying content it did\n * not author. A session that hit the old clobbering bug may have worked around\n * it by hand \u2014 writing its state into a subsection underneath the generated\n * header lines, in an unmarked block. Those blocks predate the marker, so\n * authorship cannot be read off them; the only safe signal is whether anything\n * beyond boilerplate is present.\n */\nexport function isBoilerplateOnly(lines: string[]): boolean {\n  return lines.every((line) => {\n    const t = line.trim();\n    return BLANK_LINE.test(t) || BOILERPLATE_PATTERNS.some((re) => re.test(t));\n  });\n}\n\n/**\n * True when a checkpoint body says something the generated header does not.\n *\n * \"Has a body\" and \"has something to say\" are not the same question, and the\n * preservation rules care only about the second. An unattended writer that\n * emits a single line restating the working directory has produced a body by\n * any string test and a handover by none.\n */\nexport function hasSubstance(body: string | null | undefined): boolean {\n  const text = (body ?? \"\").trim();\n  if (!text) return false;\n  return !isBoilerplateOnly(text.split(\"\\n\"));\n}\n\n/**\n * Extract the non-boilerplate content of a section, or \"\" if there is none.\n *\n * Interior blank lines are kept. They are not decoration \u2014 in Markdown they\n * are what separates a paragraph from the table or list that follows, so\n * dropping them (as this did while it treated every blank line as boilerplate)\n * silently welds a checkpoint body into one unreadable run.\n */\nexport function extractSectionContent(lines: string[]): string {\n  const kept: string[] = [];\n  for (const line of lines) {\n    const t = line.trim();\n    if (BOILERPLATE_PATTERNS.some((re) => re.test(t))) continue;\n    kept.push(line);\n  }\n  return trimBlankEdges(collapseBlankRuns(kept)).join(\"\\n\");\n}\n\n/** Drop leading and trailing blank lines, leaving interior spacing alone. */\nfunction trimBlankEdges(lines: string[]): string[] {\n  let start = 0;\n  let end = lines.length;\n  while (start < end && BLANK_LINE.test(lines[start].trim())) start += 1;\n  while (end > start && BLANK_LINE.test(lines[end - 1].trim())) end -= 1;\n  return lines.slice(start, end);\n}\n\n/**\n * Collapse runs of blank lines to a single blank.\n *\n * Removing a boilerplate line leaves the blank that surrounded it behind, so\n * stripping the header can open a three-line gap in the middle of the body.\n * One blank line is all Markdown needs.\n */\nfunction collapseBlankRuns(lines: string[]): string[] {\n  const out: string[] = [];\n  let lastWasBlank = false;\n  for (const line of lines) {\n    const isBlank = BLANK_LINE.test(line.trim());\n    if (isBlank && lastWasBlank) continue;\n    out.push(line);\n    lastWasBlank = isBlank;\n  }\n  return out;\n}\n\n/**\n * Locate the existing ## Continue section.\n *\n * When the section carries an explicit marker pair, the close marker defines\n * the end \u2014 this is what lets a rich body contain `---` and `##` safely.\n * Otherwise the legacy heuristic applies: stop at the first `---` or the next\n * `##` heading.\n */\nexport function locateContinue(content: string): LocatedContinue | null {\n  const lines = content.split(\"\\n\");\n  const startIdx = lines.findIndex((l) => l.trim() === CONTINUE_HEADING);\n  if (startIdx === -1) return null;\n\n  // Look for an open marker in the first few lines of the section.\n  let meta: CheckpointMeta | null = null;\n  let markerIdx = -1;\n  for (let i = startIdx + 1; i < Math.min(startIdx + 6, lines.length); i++) {\n    const parsed = parseMarker(lines[i]);\n    if (parsed) {\n      meta = parsed;\n      markerIdx = i;\n      break;\n    }\n    // A non-blank, non-marker line means there is no marker for this section.\n    if (lines[i].trim() !== \"\") break;\n  }\n\n  let endIdx = lines.length;\n\n  if (markerIdx !== -1) {\n    const closeIdx = lines.findIndex(\n      (l, i) => i > markerIdx && l.trim() === MARKER_CLOSE\n    );\n    if (closeIdx !== -1) {\n      endIdx = closeIdx + 1;\n    } else {\n      // Malformed (open without close) \u2014 fall back to the heuristic so we do\n      // not swallow the rest of the file.\n      endIdx = heuristicEnd(lines, startIdx);\n    }\n  } else {\n    endIdx = heuristicEnd(lines, startIdx);\n  }\n\n  // Consume one trailing `---` separator and the blank lines around it.\n  let trailingEnd = endIdx;\n  while (trailingEnd < lines.length && lines[trailingEnd].trim() === \"\") {\n    trailingEnd += 1;\n  }\n  if (trailingEnd < lines.length && lines[trailingEnd].trim() === \"---\") {\n    trailingEnd += 1;\n  } else {\n    trailingEnd = endIdx;\n  }\n\n  return {\n    startIdx,\n    endIdx: trailingEnd,\n    meta,\n    lines: lines.slice(startIdx, trailingEnd),\n  };\n}\n\n/**\n * Legacy scanner for blocks written before checkpoint markers existed.\n *\n * Terminates on a horizontal rule or the next level-1 or level-2 heading. Note\n * the `(?!#)` \u2014 `###` is a *subsection* of `## Continue`, not a terminator. The\n * original scanner stopped at any run of `#`, which meant a `### Restored\n * state` subsection fell outside the section entirely and could not be seen,\n * let alone carried forward.\n *\n * `#{1,2}` rather than `##`, because matching only `##` did not stop at an H1\n * and therefore ATE IT. A TODO.md whose `## Continue` block precedes its\n * `# TODO` title had the title absorbed into the section and destroyed on the\n * next regenerate \u2014 reported independently by three sessions on 2026-08-03,\n * one of which lost it three times and recovered from git each time.\n *\n * An H1 is a document-level heading. It can never be part of a `## Continue`\n * section, so treating it as a terminator is not a heuristic improvement but a\n * structural fact.\n */\nfunction heuristicEnd(lines: string[], startIdx: number): number {\n  for (let i = startIdx + 1; i < lines.length; i++) {\n    const trimmed = lines[i].trim();\n    if (\n      trimmed === \"---\" ||\n      (/^#{1,2}(?!#)/.test(trimmed) && trimmed !== CONTINUE_HEADING)\n    ) {\n      return i;\n    }\n  }\n  return lines.length;\n}\n\n/** Remove the ## Continue section, returning the remainder of the document. */\nexport function stripContinue(content: string): string {\n  const found = locateContinue(content);\n  if (!found) return content;\n\n  const lines = content.split(\"\\n\");\n  const before = lines.slice(0, found.startIdx);\n  const after = lines.slice(found.endIdx);\n  while (after.length > 0 && after[0].trim() === \"\") after.shift();\n\n  return [...before, ...after].join(\"\\n\");\n}\n\n// ---------------------------------------------------------------------------\n// Reading a checkpoint back\n// ---------------------------------------------------------------------------\n\nexport interface ContinueCheckpoint {\n  meta: CheckpointMeta | null;\n  /** The authored body \u2014 the section with generated header lines removed. */\n  body: string;\n  /** The full section, heading included. */\n  raw: string;\n}\n\n/**\n * Read the `## Continue` checkpoint out of a TODO.md.\n *\n * Writing a checkpoint is only half of a handover; something has to deliver it\n * to the next session. This is the read side, used by the SessionStart hook.\n *\n * Returns null when there is no section, or when the section holds nothing but\n * generated header lines \u2014 a bodyless block carries no information a new\n * session does not already have, and injecting it would only add noise.\n */\nexport function readContinueCheckpoint(\n  content: string\n): ContinueCheckpoint | null {\n  const found = locateContinue(content);\n  if (!found) return null;\n\n  const body = found.meta\n    ? extractMarkedBody(found.lines)\n    : extractSectionContent(found.lines);\n  if (!body) return null;\n\n  return { meta: found.meta, body, raw: found.lines.join(\"\\n\") };\n}\n\n/**\n * Extract the body of a marker-delimited block by position rather than by\n * pattern.\n *\n * The layout is fixed: open marker, a contiguous run of `>` header lines, the\n * body, then the close marker. Because the boundaries are known exactly, the\n * body comes back byte-for-byte \u2014 including any `---` rules or `##` headings\n * of its own, which a pattern-based filter would mistake for boilerplate and\n * delete. Falls back to the pattern filter if the shape is not as expected.\n */\nfunction extractMarkedBody(lines: string[]): string {\n  const markerIdx = lines.findIndex((l) => parseMarker(l) !== null);\n  if (markerIdx === -1) return extractSectionContent(lines);\n\n  const closeIdx = lines.findIndex(\n    (l, i) => i > markerIdx && l.trim() === MARKER_CLOSE\n  );\n  if (closeIdx === -1) return extractSectionContent(lines);\n\n  // Skip the blank lines and the `>` header run that follow the open marker.\n  let bodyStart = markerIdx + 1;\n  while (bodyStart < closeIdx) {\n    const t = lines[bodyStart].trim();\n    if (BLANK_LINE.test(t) || t.startsWith(\">\")) {\n      bodyStart += 1;\n      continue;\n    }\n    break;\n  }\n\n  return trimBlankEdges(lines.slice(bodyStart, closeIdx)).join(\"\\n\");\n}\n\n// ---------------------------------------------------------------------------\n// Block construction\n// ---------------------------------------------------------------------------\n\nexport interface BuildOptions {\n  authored: Authorship;\n  /** Human-readable session line, or \"Unknown session\". */\n  sessionLine: string;\n  /** Claude Code session UUID \u2014 the `claude --resume` handle. */\n  sessionId?: string;\n  cwd: string;\n  /** Model-authored markdown. Omitted for auto blocks. */\n  body?: string;\n  /** Overridable for deterministic tests. */\n  timestamp?: string;\n}\n\nfunction escapeAttr(value: string): string {\n  return value.replace(/\"/g, \"'\");\n}\n\nexport function buildContinueBlock(opts: BuildOptions): string {\n  const ts = opts.timestamp ?? new Date().toISOString();\n\n  const attrs = [\n    `authored=\"${opts.authored}\"`,\n    `session=\"${escapeAttr(opts.sessionLine)}\"`,\n    opts.sessionId ? `session-id=\"${escapeAttr(opts.sessionId)}\"` : null,\n    `ts=\"${ts}\"`,\n  ]\n    .filter(Boolean)\n    .join(\" \");\n\n  const header = [\n    `> **Last session:** ${opts.sessionLine}`,\n    `> **Paused at:** ${ts}`,\n    \">\",\n    `> Working directory: ${opts.cwd}`,\n  ];\n\n  if (opts.sessionId) {\n    header.push(\">\", `> Resume with: \\`claude --resume ${opts.sessionId}\\``);\n  }\n\n  const body = (opts.body ?? \"\").trim();\n\n  const parts = [\n    CONTINUE_HEADING,\n    \"\",\n    `${MARKER_OPEN} ${attrs} -->`,\n    \"\",\n    ...header,\n  ];\n\n  if (body) {\n    parts.push(\"\", body);\n  } else {\n    parts.push(\n      \">\",\n      \"> _No checkpoint body was recorded \u2014 see the latest session note._\"\n    );\n  }\n\n  parts.push(\"\", MARKER_CLOSE, \"\", \"---\", \"\");\n\n  return parts.join(\"\\n\");\n}\n\n// ---------------------------------------------------------------------------\n// Apply\n// ---------------------------------------------------------------------------\n\nexport interface ApplyOptions extends BuildOptions {\n  /** Project root; TODO.md is resolved beneath it. */\n  rootPath: string;\n  /** Preview only \u2014 nothing is written. */\n  dryRun?: boolean;\n}\n\nexport interface ApplyResult {\n  action: ApplyAction;\n  path: string | null;\n  block: string;\n  /** Set when action === \"preserved\". */\n  preservedMeta?: CheckpointMeta;\n  /** Set when unattributed content was carried forward into the new block. */\n  carriedForward?: boolean;\n  /** Set when a superseded model-authored handover was moved to the archive. */\n  archived?: boolean;\n  error?: string;\n}\n\n/**\n * Write the ## Continue block, honouring the preservation rules.\n *\n * An AUTO write is unattended \u2014 it fires from the session-stop and pre-compact\n * hooks \u2014 so it operates under one governing rule: **never destroy content it\n * did not author.** Three cases follow from that:\n *\n *   1. An authored checkpoint for the SAME session is left untouched. The hooks\n *      fire after the model has already recorded the real state; overwriting it\n *      with metadata is the bug this module exists to fix.\n *\n *      \"Same session\" is decided by the Claude session UUID whenever both sides\n *      know it, and only falls back to the human-readable session line when one\n *      of them does not. The line is derived from the session note filename,\n *      and `session-stop.sh` *renames and renumbers that file* \u2014 via `session\n *      slug --apply` and `session cleanup --execute` \u2014 before it reaches the\n *      handover step. So the key the hook computes at exit is not the key the\n *      model wrote seconds earlier, and a filename-keyed comparison mismatches\n *      by construction. Observed live on 2026-08-01: notes renumbered twice\n *      within a single session. The UUID is the only identifier that holds\n *      still.\n *   2. An authored checkpoint from an EARLIER session is stale \u2014 TODO.md would\n *      otherwise keep pointing at the wrong session \u2014 so it is replaced. Its\n *      content is not lost: `pai pause` mirrors every authored body into the\n *      session note.\n *   3. An UNMARKED block predates the marker, so authorship cannot be read off\n *      it. If it is nothing but generated header lines it is replaced. If it\n *      carries anything else, that content was put there deliberately \u2014 quite\n *      possibly as a hand-rolled workaround for the very clobbering this fixes \u2014\n *      and is carried forward into the new block rather than dropped.\n *\n * A MODEL write always replaces: the model is authoring the checkpoint, and a\n * newer one supersedes an older one.\n */\n/**\n * Do an existing checkpoint and an incoming write describe the same session?\n *\n * The UUID is authoritative when both sides carry one: it is assigned by Claude\n * Code and never changes for the life of the session. The session line is a\n * derived, mutable label and is only consulted when there is no UUID to compare\n * \u2014 a checkpoint written before `--session-id` was threaded through, or an auto\n * write from a caller that was not given one.\n */\nfunction isSameSession(\n  meta: CheckpointMeta,\n  opts: Pick<ApplyOptions, \"sessionId\" | \"sessionLine\">\n): boolean {\n  if (meta.sessionId && opts.sessionId) {\n    return meta.sessionId === opts.sessionId;\n  }\n  return meta.session === opts.sessionLine;\n}\n\n/**\n * How long a model-authored checkpoint is protected from automated overwriting.\n *\n * Sized for the race, not for the session: the gap between a model writing its\n * checkpoint and a hook firing is seconds to minutes, and an hour covers even a\n * slow checkpoint on a loaded machine. Anything genuinely from an earlier\n * session is hours or days old and still falls through to case 2, which is what\n * keeps TODO.md from freezing on a checkpoint nobody will ever replace.\n */\nconst AUTHORED_GRACE_MS = 60 * 60 * 1000;\n\n/** Was this checkpoint written recently enough to still belong to the run in progress? */\nfunction isRecent(meta: CheckpointMeta, now?: string): boolean {\n  if (!meta.ts) return false; // no stamp, no claim \u2014 case 2 decides as before\n  const then = Date.parse(meta.ts);\n  if (Number.isNaN(then)) return false;\n  const nowMs = now ? Date.parse(now) : Date.now();\n  if (Number.isNaN(nowMs)) return false;\n  // Guard the future too: a clock skew that puts the stamp ahead of now must\n  // not read as \"very old\" and license an overwrite.\n  return nowMs - then < AUTHORED_GRACE_MS;\n}\n\n/**\n * Turn a live checkpoint block into an inert archive entry.\n *\n * Two things have to go: the `## Continue` heading and the `pai:checkpoint`\n * marker. Both are what `locateContinue` looks for, so leaving either in place\n * would let a later write treat the archive as the live section \u2014 and the next\n * one after that would then archive the archive. Everything else is kept\n * verbatim, because the body is the whole reason this exists.\n */\nfunction toArchiveEntry(lines: string[], meta: CheckpointMeta | null): string[] {\n  const out: string[] = [];\n  const label = meta?.session ?? \"Unknown session\";\n  const stamp = meta?.ts ? ` \u2014 checkpointed ${meta.ts}` : \"\";\n  out.push(`${ARCHIVE_OPEN} session=\"${label}\"${meta?.ts ? ` ts=\"${meta.ts}\"` : \"\"} -->`);\n  out.push(\"\");\n  out.push(`### ${label}${stamp}`);\n  out.push(\"\");\n  for (const line of lines) {\n    if (line.trim() === CONTINUE_HEADING) continue;\n    if (line.trim().startsWith(MARKER_OPEN)) continue;\n    if (line.trim() === MARKER_CLOSE) continue;\n    if (line.trim() === \"---\") continue;\n    out.push(line);\n  }\n  while (out.length > 0 && out[out.length - 1].trim() === \"\") out.pop();\n  out.push(\"\");\n  out.push(ARCHIVE_CLOSE);\n  return out;\n}\n\n/**\n * Put a superseded handover into the archive section, newest first.\n *\n * `rest` is the document with the Continue section already stripped. The\n * archive lives immediately below it so a reader meets the current handover\n * first and the previous ones directly after, rather than hunting for them at\n * the bottom of a file that also holds the project's open work.\n */\nfunction archiveInto(rest: string, entry: string[]): string {\n  const lines = rest.split(\"\\n\");\n  const headingIdx = lines.findIndex((l) => l.trim() === ARCHIVE_HEADING);\n\n  if (headingIdx === -1) {\n    return [ARCHIVE_HEADING, \"\", ...entry, \"\", \"---\", \"\", rest.trimStart()].join(\"\\n\");\n  }\n\n  // Insert directly under the heading, then drop whatever falls past the cap.\n  const before = lines.slice(0, headingIdx + 1);\n  const after = lines.slice(headingIdx + 1);\n  while (after.length > 0 && after[0].trim() === \"\") after.shift();\n\n  const merged = [...entry, \"\", ...after];\n  const kept: string[] = [];\n  let seen = 0;\n  for (let i = 0; i < merged.length; i++) {\n    if (merged[i].trim().startsWith(ARCHIVE_OPEN)) {\n      seen += 1;\n      if (seen > ARCHIVE_LIMIT) {\n        // Everything from here to the end of THIS entry goes; keep scanning so\n        // anything after the archive section (other headings, open work) stays.\n        while (i < merged.length && merged[i].trim() !== ARCHIVE_CLOSE) i++;\n        continue; // skips the close marker too\n      }\n    }\n    kept.push(merged[i]);\n  }\n  return [...before, \"\", ...kept].join(\"\\n\");\n}\n\nexport function applyContinue(opts: ApplyOptions): ApplyResult {\n  const target = resolveTodoTarget(opts.rootPath, { create: !opts.dryRun });\n  if (!target) {\n    return {\n      action: \"failed\",\n      path: null,\n      block: buildContinueBlock(opts),\n      error: \"Could not resolve or create a TODO.md target\",\n    };\n  }\n\n  const existing = locateContinue(target.content);\n  let carriedForward = false;\n  let effectiveBody = opts.body;\n  let archiveEntry: string[] | null = null;\n\n  if (opts.authored === \"auto\" && existing) {\n    // Case 1 \u2014 authored, same session: hands off.\n    if (existing.meta?.authored === \"model\" && isSameSession(existing.meta, opts)) {\n      return {\n        action: \"preserved\",\n        path: target.path,\n        block: buildContinueBlock(opts),\n        preservedMeta: existing.meta,\n      };\n    }\n\n    // Case 1b \u2014 the incoming write has nothing to say, and what is already\n    // there does. A metadata-only block says \"see the latest session note\", so\n    // replacing a real handover with one trades content for a pointer \u2014 and the\n    // pointer is not always good: observed live on 2026-08-01 in the AIBroker\n    // project, where the stop hook's bodyless handover overwrote the autosave's\n    // body and named a session note that had never been created, leaving the\n    // next session nothing to resume from.\n    //\n    // Deferring to the session note is only safe when the note demonstrably has\n    // the content. This code cannot see that, so it does the one thing that is\n    // never wrong: a write with nothing to say does not get to destroy\n    // something that does. A stale-but-real handover beats a fresh dead link.\n    // Scoped to blocks we marked. An unmarked block falls through to case 3,\n    // which salvages its content into the new block rather than freezing the\n    // old one \u2014 better, because an unmarked block has no session metadata worth\n    // preserving and may predate this scheme entirely.\n    //\n    // The test is SUBSTANCE, not emptiness. It was emptiness until 2026-08-04,\n    // when a session that was opened and immediately exited destroyed the\n    // previous day's handover for every project it touched. Its stop hook found\n    // no work items and no completion message, so it sent the one line it\n    // always builds unconditionally \u2014 `Working directory: \u2026`, a verbatim copy\n    // of a line the header already generates. Non-empty by a string test, so\n    // this guard stood down; worthless by any other reading. `hasSubstance`\n    // asks the question the comment above always claimed to be asking.\n    if (\n      existing.meta &&\n      !hasSubstance(opts.body) &&\n      !isBoilerplateOnly(existing.lines)\n    ) {\n      return {\n        action: \"preserved\",\n        path: target.path,\n        block: buildContinueBlock(opts),\n        preservedMeta: existing.meta ?? undefined,\n      };\n    }\n\n    // Case 2b \u2014 a model checkpoint written moments ago is THIS session's,\n    // whatever the identity comparison says.\n    //\n    // Case 2 deliberately lets an auto write replace an authored checkpoint\n    // from an EARLIER session, and that policy is right: TODO.md would freeze\n    // otherwise, and `pai pause` mirrors authored bodies into the session note.\n    // The bug is not the policy, it is that identity DEGRADES and healthy\n    // sessions fall into it.\n    //\n    // `sessionId` is optional on `pai pause`. Without it isSameSession compares\n    // a display line containing the note TITLE \u2014 and pausing renames the note.\n    // So a session writes a checkpoint, its note is renamed, the hook fires\n    // seconds later, no longer recognises its own work, and classifies it as a\n    // stale earlier session. Three sessions reported exactly this on\n    // 2026-08-03 from one `pai pause all`; one lost its checkpoint three times\n    // and recovered from git each time. Sessions without a clean repo would\n    // never have known it happened.\n    //\n    // Recency is the tiebreaker that identity cannot supply. A model checkpoint\n    // minutes old is not a stale predecessor by any reading, so an automated\n    // digest does not get to overwrite it. Older ones still fall through to\n    // case 2 and are replaced as before, which is what stops TODO.md freezing\n    // and keeps this from nesting carried-forward blocks without end.\n    if (\n      existing.meta?.authored === \"model\" &&\n      isRecent(existing.meta, opts.timestamp) &&\n      !isBoilerplateOnly(existing.lines)\n    ) {\n      return {\n        action: \"preserved\",\n        path: target.path,\n        block: buildContinueBlock(opts),\n        preservedMeta: existing.meta,\n      };\n    }\n\n    // Case 2c \u2014 an authored handover from an EARLIER session, past the grace\n    // window. Case 2 deleted it, on the reasoning that TODO.md must not freeze\n    // and that `pai pause` mirrors authored bodies into the session note.\n    //\n    // The first half is right. The second is an assumption, and on 2026-08-04\n    // it was false in the plainest way available: this project keeps no session\n    // notes at all (`Notes/*` is gitignored and none exist on disk), so the\n    // checkpoint WAS the only copy. It went at 08:36:29Z, to the autosave of a\n    // session that had been open for sixteen minutes and had typed one line \u2014\n    // destroying, as its first act, the handover it had been started to read.\n    //\n    // This is also the escape the v0.27.2 guard could not close: that one asks\n    // whether the block is RECENT, and a handover written the night before is\n    // not. Age was never the question. Nothing here needs the old block gone;\n    // it only needs the slot. So the block moves down the file instead, and the\n    // freeze argument and the loss argument stop being in tension.\n    //\n    // Only for `model` blocks with something in them. An auto block is a\n    // transcript scrape that regenerates on the next tick, and archiving those\n    // would bury the real handovers under mechanical noise.\n    if (\n      existing.meta?.authored === \"model\" &&\n      !isBoilerplateOnly(existing.lines) &&\n      !isSameSession(existing.meta, opts)\n    ) {\n      archiveEntry = toArchiveEntry(existing.lines, existing.meta);\n    }\n\n    // Case 3 \u2014 unmarked block holding content nobody can attribute to us.\n    if (!existing.meta && !isBoilerplateOnly(existing.lines)) {\n      const salvaged = extractSectionContent(existing.lines);\n      if (salvaged) {\n        effectiveBody = [\n          \"_Carried forward from the previous checkpoint (author unknown \u2014 this\",\n          \"block predates checkpoint authorship markers):_\",\n          \"\",\n          salvaged,\n        ].join(\"\\n\");\n        carriedForward = true;\n      }\n    }\n  }\n\n  const block = buildContinueBlock({ ...opts, body: effectiveBody });\n\n  if (opts.dryRun) {\n    return { action: \"written\", path: target.path, block, carriedForward, archived: !!archiveEntry };\n  }\n\n  const rest = stripContinue(target.content).trimStart();\n  const newContent = block + (archiveEntry ? archiveInto(rest, archiveEntry) : rest);\n  const tmpPath = `${target.path}.continue.tmp`;\n\n  try {\n    writeFileSync(tmpPath, newContent, \"utf8\");\n    renameSync(tmpPath, target.path);\n  } catch (err) {\n    try {\n      if (existsSync(tmpPath)) renameSync(tmpPath, `${tmpPath}.dead`);\n    } catch {\n      /* ignore */\n    }\n    return {\n      action: \"failed\",\n      path: target.path,\n      block,\n      error: String(err),\n    };\n  }\n\n  return { action: \"written\", path: target.path, block, carriedForward, archived: !!archiveEntry };\n}\n\n// ---------------------------------------------------------------------------\n// Session-note discovery\n//\n// Lifted verbatim from end.ts so pause, end and any future caller share one\n// implementation. end.ts now imports these rather than carrying its own copy.\n// ---------------------------------------------------------------------------\n\n/** ADAPTER_DIR (harness adapter root) \u2014 mirrors pai-paths.ts's resolveAdapterDir(),\n *  kept self-contained here to avoid importing pai-paths.ts's process.exit(1)\n *  validation side effect. PAI_DIR is the deprecated alias, same as there. */\nfunction getPaiDir(): string {\n  const envDir = process.env.ADAPTER_DIR || process.env.PAI_DIR;\n  if (envDir) {\n    try {\n      return realpathSync(envDir);\n    } catch {\n      return envDir;\n    }\n  }\n  return join(homedir(), \".claude\");\n}\n\n/**\n * Find the notes directory for a project \u2014 local first, then the central\n * ~/.claude/projects/<encoded>/Notes fallback. Never creates.\n */\nexport function findNotesDir(\n  rootPath: string,\n  encodedDir: string\n): string | null {\n  for (const rel of [\"Notes\", \"notes\", \".claude/Notes\"]) {\n    const p = join(rootPath, rel);\n    if (existsSync(p)) return p;\n  }\n  const central = join(getPaiDir(), \"projects\", encodedDir, \"Notes\");\n  if (existsSync(central)) return central;\n  return null;\n}\n\n/**\n * Find the current (highest-numbered) session note: current month, then the\n * previous month, then a flat notesDir as legacy fallback.\n */\nexport function findLatestNote(notesDir: string): string | null {\n  const findIn = (dir: string): string | null => {\n    if (!existsSync(dir)) return null;\n    let files: string[];\n    try {\n      files = readdirSync(dir);\n    } catch {\n      return null;\n    }\n    const notes = files\n      .filter((f) => /^\\d{3,4}[\\s_-].*\\.md$/.test(f))\n      .sort((a, b) => {\n        const na = parseInt(a.match(/^(\\d+)/)?.[1] ?? \"0\", 10);\n        const nb = parseInt(b.match(/^(\\d+)/)?.[1] ?? \"0\", 10);\n        return na - nb;\n      });\n    return notes.length > 0 ? join(dir, notes[notes.length - 1]) : null;\n  };\n\n  const now = new Date();\n  const year = String(now.getFullYear());\n  const month = String(now.getMonth() + 1).padStart(2, \"0\");\n\n  const current = findIn(join(notesDir, year, month));\n  if (current) return current;\n\n  const prev = new Date(now.getFullYear(), now.getMonth() - 1, 1);\n  const py = String(prev.getFullYear());\n  const pm = String(prev.getMonth() + 1).padStart(2, \"0\");\n  const prevFound = findIn(join(notesDir, py, pm));\n  if (prevFound) return prevFound;\n\n  return findIn(notesDir);\n}\n\n// ---------------------------------------------------------------------------\n// Session-note append\n// ---------------------------------------------------------------------------\n\n/**\n * Append the checkpoint body to a session note.\n *\n * TODO.md's ## Continue is a single slot that every later checkpoint\n * overwrites. The session note is the durable record, so the body goes to both.\n * Idempotent per timestamp: re-running with the same stamp will not duplicate.\n */\nexport function appendCheckpointToNote(\n  notePath: string,\n  body: string,\n  timestamp?: string\n): { appended: boolean; error?: string } {\n  const ts = timestamp ?? new Date().toISOString();\n  const heading = `## Pause Checkpoint \u2014 ${ts}`;\n\n  let existing: string;\n  try {\n    existing = readFileSync(notePath, \"utf8\");\n  } catch (err) {\n    return { appended: false, error: String(err) };\n  }\n\n  if (existing.includes(heading)) return { appended: false };\n\n  const block = `\\n\\n---\\n\\n${heading}\\n\\n${body.trim()}\\n`;\n  const tmpPath = `${notePath}.checkpoint.tmp`;\n\n  try {\n    writeFileSync(tmpPath, existing.trimEnd() + block, \"utf8\");\n    renameSync(tmpPath, notePath);\n  } catch (err) {\n    try {\n      if (existsSync(tmpPath)) renameSync(tmpPath, `${tmpPath}.dead`);\n    } catch {\n      /* ignore */\n    }\n    return { appended: false, error: String(err) };\n  }\n\n  return { appended: true };\n}\n\n// ---------------------------------------------------------------------------\n// Body loading\n// ---------------------------------------------------------------------------\n\n/** Read a checkpoint body from a file, or from stdin when path is \"-\". */\nexport function readBodyFile(path: string): string {\n  if (path === \"-\") {\n    return readFileSync(0, \"utf8\");\n  }\n  return readFileSync(path, \"utf8\");\n}\n", "/**\n * pai-files.ts \u2014 path resolution for the PAI per-user files that don't have\n * a dedicated module of their own: whisper-rules.md, advisor-mode.json, and\n * the stop-hook's session-state/ dir (kept here rather than in the hook\n * itself, since importing that hook module runs it \u2014 it calls main() at\n * import time).\n * config.json lives in daemon/config.ts, workers.yaml in\n * workers/workers-config.ts \u2014 this covers the rest of PAI_HOME.\n */\n\nimport { existsSync, statSync, renameSync } from \"node:fs\";\nimport { homedir } from \"node:os\";\nimport { join } from \"node:path\";\nimport {\n  paiHomePath,\n  resolvePaiFile,\n  migratePaiFile,\n  migratePaiDir,\n  type MigrateFileResult,\n  type MigrateDirResult,\n} from \"./pai-home.js\";\n\nexport { paiHomePath };\n\nfunction oldWhisperRulesPath(): string {\n  return join(homedir(), \".claude\", \"whisper-rules.md\");\n}\n\nfunction oldAdvisorModePath(): string {\n  return join(homedir(), \".claude\", \"advisor-mode.json\");\n}\n\nfunction oldSessionStateDir(): string {\n  return join(homedir(), \".config\", \"pai\", \"session-state\");\n}\n\nexport function whisperRulesPath(): string {\n  return resolvePaiFile(paiHomePath(\"whisper-rules.md\"), [oldWhisperRulesPath()], \"pai config migrate\");\n}\n\nexport function advisorModePath(): string {\n  return resolvePaiFile(paiHomePath(\"advisor-mode.json\"), [oldAdvisorModePath()], \"pai config migrate\");\n}\n\nexport function migrateWhisperRules(opts: { dryRun?: boolean } = {}): MigrateFileResult {\n  return migratePaiFile(paiHomePath(\"whisper-rules.md\"), [oldWhisperRulesPath()], opts);\n}\n\nexport function migrateAdvisorMode(opts: { dryRun?: boolean } = {}): MigrateFileResult {\n  return migratePaiFile(paiHomePath(\"advisor-mode.json\"), [oldAdvisorModePath()], opts);\n}\n\n/** Read location: PAI_HOME's session-state/ if present, else the\n *  pre-2026-09-19 ~/.config/pai/session-state (one-time stderr notice). */\nexport function sessionStateDir(): string {\n  return resolvePaiFile(paiHomePath(\"session-state\"), [oldSessionStateDir()], \"pai config migrate\");\n}\n\nexport function migrateSessionStateDir(opts: { dryRun?: boolean } = {}): MigrateDirResult {\n  return migratePaiDir(paiHomePath(\"session-state\"), [oldSessionStateDir()], opts);\n}\n\n// ---------------------------------------------------------------------------\n// Orphans: files under the old ~/.config/pai that no code currently reads\n// (voices.json \u2014 no `voice` reference anywhere in src; federation.db \u2014 the\n// live federation DB has lived at ~/.pai/federation.db since the Postgres\n// migration, this copy is a 0-byte leftover). Still moved by `pai config\n// migrate` so ~/.config/pai ends up with nothing but *.migrated-* markers.\n// ---------------------------------------------------------------------------\n\nfunction oldLastHousekeepingPath(): string {\n  return join(homedir(), \".config\", \"pai\", \".last-housekeeping\");\n}\n\n/** session-stop.sh resolves this itself at runtime (it can't import TS) \u2014\n *  this is only what `pai config migrate` uses to relocate a leftover. */\nexport function migrateLastHousekeeping(opts: { dryRun?: boolean } = {}): MigrateFileResult {\n  return migratePaiFile(paiHomePath(\".last-housekeeping\"), [oldLastHousekeepingPath()], opts);\n}\n\nfunction oldVoicesJsonPath(): string {\n  return join(homedir(), \".config\", \"pai\", \"voices.json\");\n}\n\nexport function migrateVoicesJson(opts: { dryRun?: boolean } = {}): MigrateFileResult {\n  return migratePaiFile(paiHomePath(\"voices.json\"), [oldVoicesJsonPath()], opts);\n}\n\nfunction oldOrphanFederationDbPath(): string {\n  return join(homedir(), \".config\", \"pai\", \"federation.db\");\n}\n\nexport function migrateOrphanFederationDb(opts: { dryRun?: boolean } = {}): MigrateFileResult {\n  return migratePaiFile(paiHomePath(\"federation.db\"), [oldOrphanFederationDbPath()], opts);\n}\n\n// ---------------------------------------------------------------------------\n// .session-stop.lock \u2014 a live mkdir-based mutex, not data. session-stop.sh\n// already creates new locks at PAI_HOME going forward (see src/hooks/\n// session-stop.sh); this only handles what's left at the old location.\n// ---------------------------------------------------------------------------\n\n// ---------------------------------------------------------------------------\n// History/, agent-sessions.json, session-routing.json, security-events.jsonl\n// \u2014 hook-written PAI state, read/written at runtime via the resolvers in\n// src/hooks/ts/lib/pai-paths.ts (ADAPTER_DIR fallback + notice, same as\n// everything here). Duplicated here rather than imported: pai-paths.ts\n// validates ADAPTER_DIR/HOOKS_DIR exist at import time (process.exit(1) if\n// not) since every hook that imports it genuinely needs that guarantee \u2014 the\n// CLI does not, and must not inherit a hook-only failure mode just to\n// migrate a file. Actively written by hooks in every live session, so unlike\n// most of this file the live move is NOT automatic \u2014 see `pai config migrate\n// --history` and its RUNNING-worker guard in src/cli/commands/config.ts.\n// ---------------------------------------------------------------------------\n\nfunction oldHistoryDir(): string {\n  return join(homedir(), \".claude\", \"History\");\n}\n\nexport function migrateHistoryDir(opts: { dryRun?: boolean } = {}): MigrateDirResult {\n  return migratePaiDir(paiHomePath(\"History\"), [oldHistoryDir()], opts);\n}\n\nfunction oldAgentSessionsPath(): string {\n  return join(homedir(), \".claude\", \"agent-sessions.json\");\n}\n\nexport function migrateAgentSessions(opts: { dryRun?: boolean } = {}): MigrateFileResult {\n  return migratePaiFile(paiHomePath(\"agent-sessions.json\"), [oldAgentSessionsPath()], opts);\n}\n\nfunction oldSessionRoutingPath(): string {\n  return join(homedir(), \".claude\", \"session-routing.json\");\n}\n\nexport function migrateSessionRouting(opts: { dryRun?: boolean } = {}): MigrateFileResult {\n  return migratePaiFile(paiHomePath(\"session-routing.json\"), [oldSessionRoutingPath()], opts);\n}\n\nfunction oldSecurityEventsPath(): string {\n  return join(homedir(), \".claude\", \"history\", \"security\", \"security-events.jsonl\");\n}\n\nexport function migrateSecurityEvents(opts: { dryRun?: boolean } = {}): MigrateFileResult {\n  return migratePaiFile(paiHomePath(\"History\", \"security\", \"security-events.jsonl\"), [oldSecurityEventsPath()], opts);\n}\n\nexport interface MigrateLockResult {\n  status: \"nothing-to-migrate\" | \"skipped-in-use\" | \"moved\";\n  note: string;\n}\n\n/** A lock this fresh means a session-stop.sh tail is genuinely running under\n *  it right now \u2014 moving it out from under that process would break the\n *  mutex it exists to provide. Matches the hook's own 600s abandonment\n *  window with headroom, since this runs far less often than every Stop. */\nconst SESSION_STOP_LOCK_ABANDONED_AFTER_MS = 60 * 60 * 1000;\n\nexport function migrateSessionStopLock(opts: { dryRun?: boolean } = {}): MigrateLockResult {\n  const oldPath = join(homedir(), \".config\", \"pai\", \".session-stop.lock\");\n  const newPath = paiHomePath(\".session-stop.lock\");\n  if (!existsSync(oldPath)) {\n    return { status: \"nothing-to-migrate\", note: `nothing to migrate (${newPath})` };\n  }\n\n  const ageMs = Date.now() - statSync(oldPath).mtimeMs;\n  if (ageMs <= SESSION_STOP_LOCK_ABANDONED_AFTER_MS) {\n    return {\n      status: \"skipped-in-use\",\n      note: `in use (${Math.round(ageMs / 1000)}s old) \u2014 left in place; new locks go to ${newPath}`,\n    };\n  }\n\n  const ageMin = Math.round(ageMs / 60000);\n  if (opts.dryRun) {\n    return { status: \"moved\", note: `would rename abandoned lock (${ageMin}m old) at ${oldPath}` };\n  }\n\n  const stamp = new Date().toISOString().slice(0, 10).replace(/-/g, \"\");\n  renameSync(oldPath, `${oldPath}.migrated-${stamp}`);\n  return { status: \"moved\", note: `abandoned lock (${ageMin}m old) renamed aside` };\n}\n"],
  "mappings": "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",
  "names": ["readFileSync", "writeFileSync", "mkdirSync", "existsSync", "join", "basename", "dirname", "existsSync", "mkdirSync", "readdirSync", "copyFileSync", "join", "homedir", "join", "existsSync", "readFileSync", "homedir", "existsSync", "readFileSync", "join", "existsSync", "join", "existsSync", "join", "existsSync", "mkdirSync", "readdirSync", "join", "copyFileSync", "join", "existsSync", "existsSync", "readFileSync", "join", "homedir", "resolve", "existsSync", "mkdirSync", "readdirSync", "readFileSync", "renameSync", "join", "basename", "existsSync", "readdirSync", "join", "existsSync", "readFileSync", "basename", "existsSync", "join", "basename", "renameSync", "existsSync", "readFileSync", "basename", "existsSync", "mkdirSync", "readFileSync", "writeFileSync", "join", "basename", "existsSync", "readFileSync", "writeFileSync", "readdirSync", "renameSync", "mkdirSync", "join", "join", "existsSync", "readFileSync", "mkdirSync", "writeFileSync", "renameSync", "existsSync", "existsSync", "join", "mkdirSync", "writeFileSync", "basename", "readFileSync", "writeFileSync", "homedir", "join", "join", "homedir", "join", "existsSync", "readFileSync", "mkdirSync", "writeFileSync", "resolve", "basename", "dirname"]
}
