codealmanac 0.1.5 → 0.1.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/cli.ts","../src/commands/setup.ts","../src/agent/auth.ts","../src/commands/hook.ts","../src/commands/hook/script.ts","../src/commands/setup/install-path.ts","../src/commands/setup/next-steps.ts","../src/ansi.ts","../src/cli/help.ts","../src/cli/helpers.ts","../src/indexer/index.ts","../src/slug.ts","../src/topics/yaml.ts","../src/indexer/frontmatter.ts","../src/indexer/freshness.ts","../src/indexer/paths.ts","../src/indexer/schema.ts","../src/indexer/topics-yaml.ts","../src/indexer/wikilinks.ts","../src/indexer/resolve-wiki.ts","../src/paths.ts","../src/registry/index.ts","../src/topics/frontmatter-rewrite.ts","../src/topics/paths.ts","../src/commands/tag.ts","../src/topics/dag.ts","../src/commands/topics/workspace.ts","../src/commands/topics/create.ts","../src/commands/topics/page-rewrite.ts","../src/commands/topics/delete.ts","../src/commands/topics/describe.ts","../src/commands/topics/link.ts","../src/commands/topics/list.ts","../src/commands/topics/rename.ts","../src/commands/topics/read.ts","../src/commands/topics/show.ts","../src/commands/topics/unlink.ts","../src/registry/autoregister.ts","../src/cli/register-edit-commands.ts","../src/commands/health.ts","../src/indexer/duration.ts","../src/commands/list.ts","../src/commands/search.ts","../src/commands/show.ts","../src/cli/register-query-commands.ts","../src/commands/doctor-checks/format.ts","../src/commands/doctor-checks/install.ts","../src/commands/doctor-checks/probes.ts","../src/update/config.ts","../src/update/schedule.ts","../src/update/check.ts","../src/update/state.ts","../src/update/semver.ts","../src/commands/doctor-checks/duration.ts","../src/commands/doctor-checks/updates.ts","../src/commands/doctor-checks/wiki.ts","../src/commands/doctor.ts","../src/commands/uninstall.ts","../src/commands/update.ts","../src/cli/register-setup-commands.ts","../src/commands/bootstrap.ts","../src/agent/prompts.ts","../src/agent/sdk.ts","../src/commands/init.ts","../src/commands/capture.ts","../src/commands/reindex.ts","../src/cli/register-wiki-lifecycle-commands.ts","../src/cli/register-commands.ts","../src/update/announce.ts"],"sourcesContent":["import { createRequire } from \"node:module\";\nimport { basename } from \"node:path\";\n\nimport { Command } from \"commander\";\n\nimport { runSetup } from \"./commands/setup.js\";\nimport { configureGroupedHelp } from \"./cli/help.js\";\nimport { emit } from \"./cli/helpers.js\";\nimport { registerCommands } from \"./cli/register-commands.js\";\nimport { announceUpdateIfAvailable } from \"./update/announce.js\";\nimport {\n runInternalUpdateCheck,\n scheduleBackgroundUpdateCheck,\n} from \"./update/schedule.js\";\n\n/**\n * Optional dependency overrides for `run`. Tests use these to avoid\n * spawning the real setup wizard, the real update background check,\n * and the real update banner. Production callers pass nothing.\n */\nexport interface RunDeps {\n /** Replace the setup wizard (bare `codealmanac` / `almanac setup`). */\n runSetup?: typeof runSetup;\n /** Replace the pre-command update-nag banner. */\n announceUpdate?: (stderr: NodeJS.WritableStream) => void;\n /** Replace the post-command background update check scheduler. */\n scheduleUpdateCheck?: (argv: string[]) => void;\n /** Replace the internal update-check worker (run on --internal-check-updates). */\n runInternalUpdateCheck?: () => Promise<void>;\n}\n\n/**\n * Process-level CLI entrypoint. This owns invocation-level behavior:\n * update checks, bare `codealmanac` setup routing, Commander creation,\n * grouped help, and parsing. Individual command wiring lives in\n * `src/cli/register-commands.ts`.\n */\nexport async function run(argv: string[], deps: RunDeps = {}): Promise<void> {\n const runSetupFn = deps.runSetup ?? runSetup;\n const announceUpdateFn = deps.announceUpdate ?? announceUpdateIfAvailable;\n const scheduleUpdateCheckFn =\n deps.scheduleUpdateCheck ?? scheduleBackgroundUpdateCheck;\n const runInternalUpdateCheckFn =\n deps.runInternalUpdateCheck ?? runInternalUpdateCheck;\n\n if (argv.slice(2).includes(\"--internal-check-updates\")) {\n await runInternalUpdateCheckFn();\n return;\n }\n\n const programName = getProgramName(argv);\n\n announceUpdateFn(process.stderr);\n scheduleUpdateCheckFn(argv);\n\n const program = new Command();\n program\n .name(programName)\n .description(\n \"codealmanac — a living wiki for codebases, maintained by AI agents\",\n )\n .version(readPackageVersion(), \"-v, --version\", \"print version\");\n\n if (programName === \"codealmanac\") {\n const setupInvocation = tryParseSetupShortcut(argv.slice(2));\n if (setupInvocation !== null) {\n emit(await runSetupFn(setupInvocation));\n return;\n }\n }\n\n registerCommands(program);\n configureGroupedHelp(program);\n\n await program.parseAsync(argv);\n}\n\nfunction getProgramName(argv: string[]): \"almanac\" | \"codealmanac\" {\n const invoked = argv[1] !== undefined ? basename(argv[1]) : \"almanac\";\n return invoked === \"codealmanac\" ? \"codealmanac\" : \"almanac\";\n}\n\nfunction readPackageVersion(): string {\n try {\n const require = createRequire(import.meta.url);\n const pkg = require(\"../package.json\") as { version?: unknown };\n if (typeof pkg.version === \"string\" && pkg.version.length > 0) {\n return pkg.version;\n }\n } catch {\n // Fall back to \"unknown\" rather than crashing the CLI on a broken install.\n }\n return \"unknown\";\n}\n\nexport interface SetupShortcutOptions {\n yes?: boolean;\n skipHook?: boolean;\n skipGuides?: boolean;\n}\n\n/**\n * Decide whether a bare `codealmanac [...args]` invocation should route\n * straight to `runSetup` (and if so, with which flags). Returns the\n * options object when it's a setup shortcut, or `null` when Commander\n * should parse the invocation normally.\n */\nexport function tryParseSetupShortcut(args: string[]): SetupShortcutOptions | null {\n if (args.length === 0) return {};\n\n const opts: SetupShortcutOptions = {};\n for (const arg of args) {\n if (arg === \"--yes\" || arg === \"-y\") {\n opts.yes = true;\n continue;\n }\n if (arg === \"--skip-hook\") {\n opts.skipHook = true;\n continue;\n }\n if (arg === \"--skip-guides\") {\n opts.skipGuides = true;\n continue;\n }\n return null;\n }\n return opts;\n}\n","import { existsSync } from \"node:fs\";\nimport {\n copyFile,\n mkdir,\n readFile,\n writeFile,\n} from \"node:fs/promises\";\nimport { createRequire } from \"node:module\";\nimport { homedir } from \"node:os\";\nimport path from \"node:path\";\nimport { fileURLToPath } from \"node:url\";\n\nimport {\n checkClaudeAuth,\n type ClaudeAuthStatus,\n type SpawnCliFn,\n UNAUTHENTICATED_MESSAGE,\n} from \"../agent/auth.js\";\nimport { runHookInstall } from \"./hook.js\";\nimport {\n detectCurrentInstallPath,\n detectEphemeral,\n spawnGlobalInstall,\n} from \"./setup/install-path.js\";\nimport {\n countExistingPages,\n printNextSteps,\n} from \"./setup/next-steps.js\";\n\n/**\n * `codealmanac setup` — the MCP-style branded TUI that runs when a user\n * invokes the bare `codealmanac` binary (or `almanac setup` / `codealmanac\n * setup` explicitly).\n *\n * Model: `mcp-ts/src/setup.ts` from openalmanac. Same ASCII banner + badge\n * + step-indicator style, same interactive + `--yes` + non-interactive\n * modes.\n *\n * Three things get installed:\n *\n * 1. The `SessionEnd` hook in `~/.claude/settings.json` (delegated to\n * `runHookInstall` from `./hook.ts`).\n * 2. The short \"how to use codealmanac\" guide at\n * `~/.claude/codealmanac.md`, sourced from `guides/mini.md` in the\n * package.\n * 3. The full reference at `~/.claude/codealmanac-reference.md`,\n * sourced from `guides/reference.md`.\n * 4. An `@~/.claude/codealmanac.md` import line in `~/.claude/CLAUDE.md`\n * so Claude Code picks up the short guide globally.\n *\n * Everything is idempotent — running setup again is safe. `--skip-hook`\n * and `--skip-guides` opt out of the individual installs. `--yes` or a\n * non-TTY stdin skips all prompts and installs everything.\n */\n\nexport interface SetupOptions {\n /** Install everything without prompting. */\n yes?: boolean;\n /** Don't install the SessionEnd hook. */\n skipHook?: boolean;\n /** Don't install the CLAUDE.md guides. */\n skipGuides?: boolean;\n\n // ─── Injection points (tests only) ────────────────────────────────\n /** Override the subprocess spawner for `claude auth status`. */\n spawnCli?: SpawnCliFn;\n /** Override `~/.claude/settings.json` path. */\n settingsPath?: string;\n /** Override the bundled hook script path. */\n hookScriptPath?: string;\n /** Override the stable hooks directory for the hook script copy. */\n stableHooksDir?: string;\n /** Override `~/.claude/` dir for guide install. */\n claudeDir?: string;\n /** Override the directory containing `mini.md` / `reference.md`. */\n guidesDir?: string;\n /** Override interactivity; defaults to `process.stdin.isTTY`. */\n isTTY?: boolean;\n /** Stdout sink; defaults to `process.stdout`. */\n stdout?: NodeJS.WritableStream;\n /**\n * Override the install-path probe result. When `null` the probe is\n * bypassed (tests that don't care about the ephemeral-path step).\n * When a string it's treated as the detected install path.\n */\n installPath?: string | null;\n /**\n * Override the npm global install spawner (tests inject a no-op to\n * avoid actually spawning npm during CI).\n */\n spawnGlobalInstall?: () => Promise<void>;\n}\n\nexport interface SetupResult {\n stdout: string;\n stderr: string;\n exitCode: number;\n}\n\n// ─── ANSI helpers ────────────────────────────────────────────────────\n\nconst RST = \"\\x1b[0m\";\nconst DIM = \"\\x1b[2m\";\nconst WHITE_BOLD = \"\\x1b[1;37m\";\nconst BLUE = \"\\x1b[38;5;75m\";\nconst ACCENT_BG = \"\\x1b[48;5;252m\\x1b[38;5;16m\";\n\nconst GRADIENT = [\n \"\\x1b[38;5;255m\",\n \"\\x1b[38;5;253m\",\n \"\\x1b[38;5;251m\",\n \"\\x1b[38;5;249m\",\n \"\\x1b[38;5;246m\",\n \"\\x1b[38;5;243m\",\n];\n\n// `codealmanac` 11-letter ASCII banner. Chosen for tasteful rendering —\n// same banner used in the MCP setup wizard design, retooled letters for\n// the word \"codealmanac\". Each glyph is 6 lines tall.\n//\n// If you tweak this, keep it to ≤80 visual columns wide so it fits in\n// narrow terminals (80 cols is the classic default).\nconst LOGO_LINES = [\n \" ___ ___ ___ ___ _ _ __ __ _ _ _ _ ___ \",\n \" / __/ _ \\\\| \\\\| __| /_\\\\ | | | \\\\/ | /_\\\\ | \\\\| | /_\\\\ / __|\",\n \"| (_| (_) | |) | _| / _ \\\\| |__| |\\\\/| |/ _ \\\\| .` |/ _ \\\\ (__ \",\n \" \\\\___\\\\___/|___/|___/_/ \\\\_\\\\____|_| |_/_/ \\\\_\\\\_|\\\\_/_/ \\\\_\\\\___|\",\n \" \",\n \" a living wiki for codebases, for your agent \",\n];\n\nconst BAR = ` ${DIM}\\u2502${RST}`;\n\nfunction printBanner(out: NodeJS.WritableStream): void {\n out.write(\"\\n\");\n for (let i = 0; i < LOGO_LINES.length; i++) {\n const color = GRADIENT[Math.min(i, GRADIENT.length - 1)] ?? \"\";\n out.write(`${color}${LOGO_LINES[i]}${RST}\\n`);\n }\n out.write(`\\n${WHITE_BOLD} Install the hook + agent guides${RST}\\n`);\n}\n\nfunction printBadge(out: NodeJS.WritableStream): void {\n out.write(`\\n ${ACCENT_BG} codealmanac ${RST}\\n\\n`);\n}\n\nfunction stepDone(out: NodeJS.WritableStream, msg: string): void {\n out.write(` ${BLUE}\\u25c7${RST} ${msg}\\n`);\n}\n\nfunction stepActive(out: NodeJS.WritableStream, msg: string): void {\n out.write(` ${BLUE}\\u25c6${RST} ${msg}\\n`);\n}\n\nfunction stepSkipped(out: NodeJS.WritableStream, msg: string): void {\n out.write(` ${DIM}\\u25cb ${msg}${RST}\\n`);\n}\n\n// ─── Entry point ─────────────────────────────────────────────────────\n\nexport async function runSetup(\n options: SetupOptions = {},\n): Promise<SetupResult> {\n const out = options.stdout ?? process.stdout;\n const isTTY =\n options.isTTY ?? (process.stdin.isTTY === true);\n const interactive = isTTY && options.yes !== true;\n\n // No-op fast path. When the caller explicitly skipped every install\n // step, rendering the full banner + step markers + \"Setup complete\"\n // box is actively misleading — nothing was actually set up. Emit a\n // single terse line and exit so the user gets honest feedback and\n // piped callers (CI, scripts) don't parse through nine lines of ANSI\n // to conclude nothing happened.\n if (options.skipHook === true && options.skipGuides === true) {\n out.write(\n \"codealmanac: nothing to install — use --help to see what setup does\\n\",\n );\n return { stdout: \"\", stderr: \"\", exitCode: 0 };\n }\n\n printBanner(out);\n printBadge(out);\n\n // Step 1: auth status. We report what we find; we don't block. The user\n // can still install the hook + guides without being logged in — they'll\n // hit the auth wall on first `capture`, not on setup.\n const auth = await safeCheckAuth(options.spawnCli);\n reportAuth(out, auth);\n out.write(BAR + \"\\n\");\n\n // Step 1b: ephemeral install detection. When codealmanac was invoked via\n // `npx codealmanac` (no prior `npm i -g`), the binary lives inside an\n // npx cache directory or pnpm store that can be evicted at any time.\n // `almanac` is also not on PATH, so the user can't use it after setup.\n //\n // When we detect an ephemeral location, we offer (or, on --yes, perform)\n // a `npm install -g codealmanac` to make the install permanent.\n //\n // This is Bug #2 from codealmanac-known-bugs.md.\n const ephem = options.installPath !== undefined\n ? (options.installPath !== null\n ? detectEphemeral(options.installPath)\n : false)\n : detectEphemeral(detectCurrentInstallPath());\n if (ephem) {\n let globalAction: InstallDecision = \"install\";\n if (interactive) {\n globalAction = await confirm(\n out,\n `Running from an ephemeral npx location. Install globally so 'almanac' stays on PATH?`,\n true,\n );\n }\n if (globalAction === \"install\") {\n stepActive(out, \"Installing codealmanac globally…\");\n try {\n await (options.spawnGlobalInstall ?? spawnGlobalInstall)();\n stepDone(out, \"codealmanac installed globally (almanac now on PATH)\");\n } catch (err: unknown) {\n const msg = err instanceof Error ? err.message : String(err);\n stepActive(out, `Global install failed: ${msg}`);\n out.write(\n ` ${DIM}You can retry manually: npm install -g codealmanac${RST}\\n`,\n );\n }\n } else {\n stepSkipped(\n out,\n `Global install ${DIM}skipped — almanac will not be on PATH after this session${RST}`,\n );\n }\n out.write(BAR + \"\\n\");\n }\n\n // Step 2: install the hook (default yes).\n let hookAction: InstallDecision = \"install\";\n if (options.skipHook === true) {\n hookAction = \"skip\";\n } else if (interactive) {\n hookAction = await confirm(\n out,\n \"Install the SessionEnd hook so capture runs at the end of every Claude Code session?\",\n true,\n );\n }\n\n let hookResultLine = \"\";\n if (hookAction === \"install\") {\n const res = await runHookInstall({\n settingsPath: options.settingsPath,\n hookScriptPath: options.hookScriptPath,\n stableHooksDir: options.stableHooksDir,\n });\n if (res.exitCode !== 0) {\n stepActive(out, `SessionEnd hook: ${res.stderr.trim()}`);\n return {\n stdout: \"\",\n stderr: res.stderr,\n exitCode: res.exitCode,\n };\n }\n hookResultLine = res.stdout.includes(\"already installed\")\n ? `SessionEnd hook ${DIM}already installed${RST}`\n : `SessionEnd hook installed`;\n stepDone(out, hookResultLine);\n } else {\n stepSkipped(out, `SessionEnd hook ${DIM}skipped${RST}`);\n }\n out.write(BAR + \"\\n\");\n\n // Step 3: install the guides.\n let guidesAction: InstallDecision = \"install\";\n if (options.skipGuides === true) {\n guidesAction = \"skip\";\n } else if (interactive) {\n guidesAction = await confirm(\n out,\n \"Install the codealmanac usage guides into ~/.claude/ and import them from CLAUDE.md?\",\n true,\n );\n }\n\n let guidesSummary: string;\n if (guidesAction === \"install\") {\n try {\n const summary = await installGuides({\n claudeDir: options.claudeDir ?? path.join(homedir(), \".claude\"),\n guidesDir: options.guidesDir ?? resolveGuidesDir(),\n });\n guidesSummary = summary.anyChanges\n ? `Guides installed (${summary.filesWritten.join(\", \")})`\n : `Guides ${DIM}already installed${RST}`;\n stepDone(out, guidesSummary);\n } catch (err: unknown) {\n const msg = err instanceof Error ? err.message : String(err);\n return {\n stdout: \"\",\n stderr: `almanac: guide install failed: ${msg}\\n`,\n exitCode: 1,\n };\n }\n } else {\n stepSkipped(out, `Guides ${DIM}skipped${RST}`);\n }\n out.write(BAR + \"\\n\");\n\n stepDone(out, `${BLUE}Setup complete${RST}`);\n out.write(\"\\n\");\n\n // Detect whether the current working directory is inside a repo that\n // already has a wiki with pages. This fixes Bug #6 from\n // codealmanac-known-bugs.md: Engineer B clones a repo that already has\n // `.almanac/pages/` (committed by Engineer A) and gets told to run\n // `almanac bootstrap`, which is wrong — the wiki already exists.\n const existingPageCount = countExistingPages(process.cwd());\n printNextSteps(out, existingPageCount);\n\n return { stdout: \"\", stderr: \"\", exitCode: 0 };\n}\n\n// ─── Auth reporting ──────────────────────────────────────────────────\n\nasync function safeCheckAuth(\n spawnCli?: SpawnCliFn,\n): Promise<ClaudeAuthStatus> {\n try {\n return await checkClaudeAuth(spawnCli);\n } catch {\n return { loggedIn: false };\n }\n}\n\nfunction reportAuth(\n out: NodeJS.WritableStream,\n auth: ClaudeAuthStatus,\n): void {\n if (auth.loggedIn) {\n const who = auth.email ?? \"Claude account\";\n const plan =\n auth.subscriptionType !== undefined\n ? ` ${DIM}(${auth.subscriptionType})${RST}`\n : \"\";\n stepDone(out, `Claude auth: ${WHITE_BOLD}${who}${RST}${plan}`);\n return;\n }\n if (\n process.env.ANTHROPIC_API_KEY !== undefined &&\n process.env.ANTHROPIC_API_KEY.length > 0\n ) {\n stepDone(out, `Claude auth: ${WHITE_BOLD}ANTHROPIC_API_KEY${RST} set`);\n return;\n }\n // Not blocking — just report and show the two paths.\n stepActive(out, `Claude auth: ${DIM}not signed in${RST}`);\n for (const line of UNAUTHENTICATED_MESSAGE.split(\"\\n\")) {\n out.write(` ${DIM}\\u2502 ${line}${RST}\\n`);\n }\n}\n\n// ─── Guide installation ──────────────────────────────────────────────\n\ninterface InstallGuidesOptions {\n claudeDir: string;\n guidesDir: string;\n}\n\ninterface InstallGuidesResult {\n anyChanges: boolean;\n filesWritten: string[];\n}\n\n/**\n * Copy the two guide files into `~/.claude/` and append an `@import`\n * line to `~/.claude/CLAUDE.md`. Every step is idempotent:\n *\n * - Guide files are compared by bytes before we write. If the content\n * matches the bundled version, we skip (so `setup` doesn't cause a\n * spurious mtime bump on every invocation).\n * - The import line is appended only if `CLAUDE.md` doesn't already\n * contain the exact `@~/.claude/codealmanac.md` token on a line by\n * itself. We don't try to parse the file — any mention of the token\n * on a non-comment line is treated as \"already present\".\n *\n * Returns a summary the caller uses to decide whether to say \"installed\"\n * or \"already installed\" in the TUI.\n */\nasync function installGuides(\n options: InstallGuidesOptions,\n): Promise<InstallGuidesResult> {\n await mkdir(options.claudeDir, { recursive: true });\n\n const srcMini = path.join(options.guidesDir, \"mini.md\");\n const srcRef = path.join(options.guidesDir, \"reference.md\");\n if (!existsSync(srcMini)) {\n throw new Error(`missing bundled guide: ${srcMini}`);\n }\n if (!existsSync(srcRef)) {\n throw new Error(`missing bundled guide: ${srcRef}`);\n }\n\n const destMini = path.join(options.claudeDir, \"codealmanac.md\");\n const destRef = path.join(options.claudeDir, \"codealmanac-reference.md\");\n\n const miniChanged = await copyIfChanged(srcMini, destMini);\n const refChanged = await copyIfChanged(srcRef, destRef);\n\n const claudeMd = path.join(options.claudeDir, \"CLAUDE.md\");\n const importChanged = await ensureImport(claudeMd);\n\n const filesWritten: string[] = [];\n if (miniChanged) filesWritten.push(\"codealmanac.md\");\n if (refChanged) filesWritten.push(\"codealmanac-reference.md\");\n if (importChanged) filesWritten.push(\"CLAUDE.md\");\n\n return { anyChanges: filesWritten.length > 0, filesWritten };\n}\n\nasync function copyIfChanged(src: string, dest: string): Promise<boolean> {\n const srcBytes = await readFile(src);\n if (existsSync(dest)) {\n try {\n const destBytes = await readFile(dest);\n if (srcBytes.equals(destBytes)) return false;\n } catch {\n // Fall through to write.\n }\n }\n await copyFile(src, dest);\n return true;\n}\n\n/** The exact import line we manage. Changing this requires updating\n * uninstall too. */\nexport const IMPORT_LINE = \"@~/.claude/codealmanac.md\";\n\n/**\n * Append the import line to `~/.claude/CLAUDE.md` if it isn't already\n * present. Creates the file if absent. Returns true when we wrote, false\n * when the line was already there.\n *\n * We match on `@~/.claude/codealmanac.md` appearing on any non-empty\n * line (trimmed). This catches both the bare line we write and any\n * user-edited variant (comments, trailing whitespace). We deliberately\n * do NOT try to repair a user who deleted the newline — that's their\n * file to shape.\n */\nasync function ensureImport(claudeMdPath: string): Promise<boolean> {\n let existing = \"\";\n if (existsSync(claudeMdPath)) {\n existing = await readFile(claudeMdPath, \"utf8\");\n }\n if (hasImportLine(existing)) return false;\n\n const sep =\n existing.length === 0 ? \"\" : existing.endsWith(\"\\n\") ? \"\\n\" : \"\\n\\n\";\n const body = `${existing}${sep}${IMPORT_LINE}\\n`;\n await writeFile(claudeMdPath, body, \"utf8\");\n return true;\n}\n\nexport function hasImportLine(contents: string): boolean {\n // Match line-starts-with-token rather than exact-line equality so a\n // user who annotated the import line (`@~/.claude/codealmanac.md #\n // codealmanac`) doesn't cause us to re-append a duplicate below.\n // The trailing-character check rules out accidental matches on a\n // longer line like `@~/.claude/codealmanac.md-extra`.\n const lines = contents.split(/\\r?\\n/).map((l) => l.trim());\n return lines.some((line) => {\n if (line === IMPORT_LINE) return true;\n if (!line.startsWith(IMPORT_LINE)) return false;\n const next = line[IMPORT_LINE.length];\n return next === \" \" || next === \"\\t\";\n });\n}\n\n// ─── Interactive prompt ──────────────────────────────────────────────\n\ntype InstallDecision = \"install\" | \"skip\";\n\n/**\n * Minimal `[Y/n]` prompt. No raw mode, no cursor — just readline. The\n * MCP setup uses a fancy arrow-key TUI for multi-choice; we only have\n * binary decisions here, so a line-reader prompt is clearer and doesn't\n * fight with the step-indicator rendering above it.\n */\nfunction confirm(\n out: NodeJS.WritableStream,\n question: string,\n defaultYes: boolean,\n): Promise<InstallDecision> {\n return new Promise((resolve) => {\n const hint = defaultYes ? \"[Y/n]\" : \"[y/N]\";\n out.write(` ${BLUE}\\u25c6${RST} ${question} ${DIM}${hint}${RST} `);\n\n let buf = \"\";\n const onData = (chunk: Buffer): void => {\n buf += chunk.toString(\"utf8\");\n const nl = buf.indexOf(\"\\n\");\n if (nl === -1) return;\n process.stdin.removeListener(\"data\", onData);\n process.stdin.pause();\n\n const answer = buf.slice(0, nl).trim().toLowerCase();\n const accepted =\n answer.length === 0\n ? defaultYes\n : answer === \"y\" || answer === \"yes\";\n resolve(accepted ? \"install\" : \"skip\");\n };\n\n process.stdin.resume();\n process.stdin.on(\"data\", onData);\n });\n}\n\n// ─── Guides path resolution ──────────────────────────────────────────\n\n/**\n * Locate `guides/` relative to the installed package. Mirrors\n * `resolvePromptsDir` from `src/agent/prompts.ts`.\n *\n * Two runtime layouts to handle:\n *\n * 1. **Bundled dist.** `dist/codealmanac.js` → walk one level up →\n * `guides/`.\n * 2. **Source (tests / tsx).** `src/commands/setup.ts` → walk two\n * levels up → `guides/`.\n *\n * We also try `createRequire` to resolve the package root from the\n * `codealmanac/package.json` manifest, as a belt-and-suspenders fallback\n * for unusual install layouts (monorepo hoisting, etc.). That path is\n * only exercised when the direct walk-up fails.\n */\nexport function resolveGuidesDir(): string {\n const here = path.dirname(fileURLToPath(import.meta.url));\n const candidates = [\n path.resolve(here, \"..\", \"guides\"), // dist layout\n path.resolve(here, \"..\", \"..\", \"guides\"), // src layout\n path.resolve(here, \"..\", \"..\", \"..\", \"guides\"),\n ];\n for (const dir of candidates) {\n if (looksLikeGuidesDir(dir)) return dir;\n }\n // Fallback: resolve via the package.json of the currently-running\n // codealmanac. createRequire lets us ask Node's resolver rather than\n // guessing at directory layouts.\n try {\n const require = createRequire(import.meta.url);\n const pkgJson = require.resolve(\"codealmanac/package.json\");\n const guides = path.join(path.dirname(pkgJson), \"guides\");\n if (looksLikeGuidesDir(guides)) return guides;\n } catch {\n // Ignore — we'll throw with the candidate list below.\n }\n throw new Error(\n \"could not locate bundled guides/ directory. Tried:\\n\" +\n candidates.map((c) => ` - ${c}`).join(\"\\n\"),\n );\n}\n\nfunction looksLikeGuidesDir(dir: string): boolean {\n return existsSync(path.join(dir, \"mini.md\"));\n}\n","import { spawn, type ChildProcess } from \"node:child_process\";\nimport { createRequire } from \"node:module\";\nimport { dirname, join } from \"node:path\";\n\n/**\n * Claude auth gate — accepts either an active Claude subscription login\n * OR an `ANTHROPIC_API_KEY` environment variable.\n *\n * The Claude Agent SDK delegates authentication to its bundled `cli.js`,\n * which reads OAuth credentials from `~/.claude/credentials/` (the same\n * store Claude Code uses). Users who are logged in via `claude auth login\n * --claudeai` should be able to run bootstrap/capture without ever\n * exporting an API key. Conversely, users on pay-per-token API keys\n * shouldn't be required to go through the OAuth flow.\n *\n * We spawn the bundled SDK's `cli.js auth status --json` to answer \"are\n * we logged in?\" rather than poking at the credentials file directly —\n * that's the SDK's contract, and it handles all the edge cases (token\n * expiry, org switching, consoleloginpath, …) for us.\n *\n * The CLI path is resolved via `require.resolve(\"@anthropic-ai/claude-\n * agent-sdk/package.json\")` + the `cli.js` sibling. Going through\n * `createRequire` keeps this compatible with both ESM dev mode (tsx) and\n * the bundled dist (tsup externalizes the SDK, so Node's own resolver\n * does the lookup at runtime). If the SDK isn't installed at all we fall\n * back to treating the user as unauthenticated — the assert will then\n * surface the familiar two-path error so they can at least fix it via\n * `ANTHROPIC_API_KEY`.\n */\n\nexport interface ClaudeAuthStatus {\n loggedIn: boolean;\n email?: string;\n subscriptionType?: string;\n authMethod?: string;\n}\n\nexport interface SpawnedProcess {\n stdout: { on: (event: \"data\", cb: (data: Buffer | string) => void) => void };\n stderr: { on: (event: \"data\", cb: (data: Buffer | string) => void) => void };\n on: (event: \"close\" | \"error\", cb: (arg: number | null | Error) => void) => void;\n kill: (signal?: string) => void;\n}\n\n/**\n * The subprocess spawner is injectable so tests can replace it with a\n * fake that emits canned JSON without touching the filesystem. Production\n * code uses `defaultSpawnCli` which invokes the bundled SDK CLI.\n */\nexport type SpawnCliFn = (args: string[]) => SpawnedProcess;\n\nconst AUTH_TIMEOUT_MS = 10_000;\n\n/**\n * Resolve `cli.js` from the bundled `@anthropic-ai/claude-agent-sdk`\n * install. Uses `createRequire` so the lookup works regardless of\n * whether we're running from `dist/` (where tsup externalized the SDK)\n * or directly from source.\n *\n * Throws if the SDK can't be located — `checkClaudeAuth` catches this\n * and treats the user as not-logged-in, which lets the env-var path\n * still work for users with a borked install.\n */\nfunction resolveCliJsPath(): string {\n // `import.meta.url` points at this module (dev or dist). `createRequire`\n // from that URL can then resolve sibling packages the same way Node's\n // own CJS resolver would.\n //\n // We resolve the main entry (not `./package.json`) because the SDK's\n // `exports` field locks subpath access — `require.resolve(\".../package.json\")`\n // fails with `ERR_PACKAGE_PATH_NOT_EXPORTED` on current SDK versions.\n // The main entry resolves fine; `cli.js` is its sibling in the install\n // layout the SDK has used since day one.\n const require = createRequire(import.meta.url);\n const entry = require.resolve(\"@anthropic-ai/claude-agent-sdk\");\n return join(dirname(entry), \"cli.js\");\n}\n\n/**\n * Default subprocess spawner for production use — invokes the bundled\n * SDK's `cli.js` via the same Node runtime that's running codealmanac.\n * Tests inject a fake via the `spawnCli` parameter.\n */\nexport const defaultSpawnCli: SpawnCliFn = (args: string[]) => {\n const cliPath = resolveCliJsPath();\n // Use `process.execPath` so we inherit the Node runtime codealmanac\n // itself is running under — avoids PATH weirdness on systems where\n // `node` isn't on PATH but codealmanac was installed via npm.\n const child = spawn(process.execPath, [cliPath, ...args], {\n stdio: [\"ignore\", \"pipe\", \"pipe\"],\n });\n return child as unknown as SpawnedProcess;\n};\n\n/**\n * Check whether the user is authenticated via Claude subscription OAuth.\n *\n * Spawns the bundled SDK CLI's `auth status --json`. On any failure\n * (spawn error, non-JSON stdout, non-zero exit, timeout) we return\n * `{ loggedIn: false }` rather than propagating the error — the caller\n * will fall back to the `ANTHROPIC_API_KEY` path and, if that's also\n * missing, produce a clean two-option error message.\n *\n * The 10s timeout guards against the CLI hanging on a broken network or\n * keychain prompt. In practice `auth status` is a cheap local read.\n */\nexport async function checkClaudeAuth(\n spawnCli: SpawnCliFn = defaultSpawnCli,\n): Promise<ClaudeAuthStatus> {\n let child: SpawnedProcess;\n try {\n child = spawnCli([\"auth\", \"status\", \"--json\"]);\n } catch {\n return { loggedIn: false };\n }\n\n return new Promise<ClaudeAuthStatus>((resolve) => {\n let stdout = \"\";\n let stderr = \"\";\n let settled = false;\n\n const settle = (value: ClaudeAuthStatus): void => {\n if (settled) return;\n settled = true;\n clearTimeout(timer);\n resolve(value);\n };\n\n const timer = setTimeout(() => {\n try {\n child.kill(\"SIGTERM\");\n } catch {\n // Kill can fail if the process already exited; nothing we can do.\n }\n settle({ loggedIn: false });\n }, AUTH_TIMEOUT_MS);\n\n child.stdout.on(\"data\", (data) => {\n stdout += data.toString();\n });\n child.stderr.on(\"data\", (data) => {\n stderr += data.toString();\n });\n\n child.on(\"error\", () => {\n settle({ loggedIn: false });\n });\n\n child.on(\"close\", (code) => {\n // The SDK writes `{\"loggedIn\": false, ...}` to stdout with a zero\n // exit code when the user isn't signed in, so we only reject on\n // non-zero + empty stdout. An empty stdout with zero exit (shouldn't\n // happen in practice) also fails safely to `loggedIn: false`.\n if (code !== 0 && stdout.trim().length === 0) {\n // `stderr` isn't surfaced to the user here — the caller's error\n // message covers both auth paths — but it would be captured by\n // `stderr` if we ever wanted to log it for debugging.\n void stderr;\n settle({ loggedIn: false });\n return;\n }\n try {\n const parsed = JSON.parse(stdout.trim()) as Record<string, unknown>;\n const loggedIn = parsed.loggedIn === true;\n const out: ClaudeAuthStatus = { loggedIn };\n if (typeof parsed.email === \"string\") out.email = parsed.email;\n if (typeof parsed.subscriptionType === \"string\") {\n out.subscriptionType = parsed.subscriptionType;\n }\n if (typeof parsed.authMethod === \"string\") {\n out.authMethod = parsed.authMethod;\n }\n settle(out);\n } catch {\n settle({ loggedIn: false });\n }\n });\n });\n}\n\n/**\n * Human-readable error when neither auth path is available. The text is\n * deliberately verbose — users hitting this wall for the first time\n * deserve both options in front of them, not a terse hint.\n */\nexport const UNAUTHENTICATED_MESSAGE =\n \"not authenticated to Claude.\\n\\n\" +\n \"Option 1 — use your Claude subscription (Pro/Max):\\n\" +\n \" claude auth login --claudeai\\n\\n\" +\n \"Option 2 — use a pay-per-token API key:\\n\" +\n \" Get one at https://console.anthropic.com\\n\" +\n \" export ANTHROPIC_API_KEY=sk-ant-...\\n\\n\" +\n \"Verify with: claude auth status\";\n\n/**\n * Assert that at least one auth path is satisfied. Prefers subscription\n * auth (fewer surprises for Claude Pro/Max users) but accepts\n * `ANTHROPIC_API_KEY` as a fallback. On failure throws with\n * `code = \"CLAUDE_AUTH_MISSING\"` so callers can distinguish this from\n * other errors if they ever want to.\n *\n * Returns the resolved auth status so callers that want to display the\n * logged-in email in a preamble can do so without a second subprocess.\n */\nexport async function assertClaudeAuth(\n spawnCli: SpawnCliFn = defaultSpawnCli,\n): Promise<ClaudeAuthStatus> {\n const status = await checkClaudeAuth(spawnCli);\n if (status.loggedIn) {\n return status;\n }\n const apiKey = process.env.ANTHROPIC_API_KEY;\n if (apiKey !== undefined && apiKey.length > 0) {\n // Signal to callers that we're on the API-key path. Not \"loggedIn\"\n // in the OAuth sense, but the SDK will pick up the env var and\n // succeed — so we return a status that tells bootstrap/capture the\n // gate is open.\n return { loggedIn: true, authMethod: \"apiKey\" };\n }\n const err = new Error(UNAUTHENTICATED_MESSAGE);\n (err as { code?: string }).code = \"CLAUDE_AUTH_MISSING\";\n throw err;\n}\n\n// Internal re-export — helps keep the public type surface minimal while\n// still letting tests import the `ChildProcess` shape when needed.\nexport type { ChildProcess };\n","import { existsSync } from \"node:fs\";\nimport { mkdir, readFile, rename, writeFile } from \"node:fs/promises\";\nimport path from \"node:path\";\n\nimport {\n copyToStableHooksDir,\n resolveHookScriptPath,\n resolveSettingsPath,\n type ScriptResolution,\n} from \"./hook/script.js\";\n\n/**\n * `almanac hook install|uninstall|status` — wires the bundled\n * `hooks/almanac-capture.sh` into `~/.claude/settings.json` as a\n * `SessionEnd` hook.\n *\n * Design notes:\n *\n * - **Schema.** Claude Code validates `settings.json` against a strict\n * schema: each entry in an event array (like `SessionEnd`) is a\n * `{matcher, hooks: [...]}` container, and the actual command objects\n * live in the nested `hooks` array. v0.1.0–v0.1.4 wrote command objects\n * directly at the event-array level; newer Claude Code versions now\n * reject that shape. We produce the wrapped form on install, and when\n * encountering a legacy unwrapped entry that we recognize as ours (by\n * `command` ending in `almanac-capture.sh`) we migrate it on next\n * install. `SessionEnd` never uses the `matcher` field to discriminate\n * anything — we always emit an empty `matcher: \"\"` (matches\n * everything, which is what session-end lifecycle hooks want).\n *\n * - **Idempotent.** `install` twice leaves one entry, not two. We match by\n * `command` string equality on the inner `hooks[]` entries. If the user\n * replaces our absolute path with a symlink pointing at the same\n * script, we'll treat it as foreign. That's acceptable; the `status`\n * output shows the path we'd use, so the user can reconcile manually.\n *\n * - **Refuse foreign entries.** If `SessionEnd` is already populated with\n * a command we don't recognize, we print the existing value and exit\n * non-zero. Claude Code lets users wire their own hooks (notifications,\n * git autocommit scripts, etc.) and silently replacing them would be\n * rude. Foreign wrapped containers that don't reference our script are\n * preserved byte-for-byte.\n *\n * - **Atomic write.** `settings.json` is small but heavily touched by\n * Claude Code. Writing via tmp-file + rename avoids corrupting the file\n * if we crash mid-write.\n *\n * - **Non-interactive.** No prompts, no confirmations. The caller is\n * already making an intentional choice by running `almanac hook\n * install`.\n */\n\nexport interface HookCommandOptions {\n /**\n * Override the hook script path. Production code leaves this undefined\n * and we resolve the bundled `hooks/almanac-capture.sh`. Tests pass a\n * fixture path to avoid depending on the runtime-install layout.\n */\n hookScriptPath?: string;\n /**\n * Override `~/.claude/settings.json`. Tests sandbox this to a tmpdir;\n * production code leaves it undefined.\n */\n settingsPath?: string;\n /**\n * Override the stable hooks directory where we copy the script.\n * Defaults to `~/.claude/hooks/`. Tests sandbox this to a tmpdir.\n *\n * Bug #1 fix: we always copy the bundled script to this stable path\n * before writing it into settings.json. This way the settings entry\n * points at a user-owned location that survives npm version bumps,\n * npx cache evictions, and nvm version switches — instead of an\n * ephemeral path inside ~/.npm/_npx/<sha>/... or the nvm-versioned\n * node_modules/.\n */\n stableHooksDir?: string;\n}\n\nexport interface HookCommandResult {\n stdout: string;\n stderr: string;\n exitCode: number;\n}\n\nconst HOOK_TIMEOUT_SECONDS = 10;\n\n/** A single command invocation inside a wrapper's `hooks[]` array. */\ninterface HookCommand {\n type: \"command\";\n command: string;\n timeout?: number;\n}\n\n/** A wrapped SessionEnd entry per Claude Code's schema. */\ninterface WrappedEntry {\n matcher: string;\n hooks: HookCommand[];\n}\n\n/**\n * What we read from `settings.hooks.SessionEnd`. During a read we may\n * encounter the legacy unwrapped shape (`HookCommand` directly) written\n * by v0.1.0–v0.1.4 — we recognize and migrate it. Unknown entries we\n * can't classify are preserved as-is via `unknown`.\n */\ntype RawEntry = WrappedEntry | HookCommand | unknown;\n\n/**\n * Claude Code's `settings.json` is a free-form JSON object; we only care\n * about the `hooks.SessionEnd` array. Preserve everything else verbatim\n * so we don't drop user settings when we write the file back.\n */\ntype SettingsJson = Record<string, unknown> & {\n hooks?: Record<string, RawEntry[] | undefined>;\n};\n\n/**\n * Heuristic: does this command path look like one we installed?\n *\n * We match on the filename `almanac-capture.sh` regardless of the parent\n * directory. This covers:\n * - the stable path: `~/.claude/hooks/almanac-capture.sh`\n * - legacy paths from v0.1.0–v0.1.5: inside the nvm node_modules or\n * npx cache\n * The stable path is what new installs produce; legacy paths are what\n * we migrate when the user runs `almanac hook install` again.\n */\nfunction isOurCommandPath(command: string): boolean {\n return command.endsWith(\"almanac-capture.sh\");\n}\n\n/**\n * Classify a raw SessionEnd entry. Wrapped entries are the canonical\n * shape; unwrapped-command entries are legacy output from v0.1.0–v0.1.4.\n * Anything else (random user JSON) is `unknown` and we leave it alone.\n */\ntype Classified =\n | { kind: \"wrapped\"; entry: WrappedEntry }\n | { kind: \"legacy\"; entry: HookCommand }\n | { kind: \"unknown\"; entry: unknown };\n\nfunction classifyEntry(raw: RawEntry): Classified {\n if (raw === null || typeof raw !== \"object\") {\n return { kind: \"unknown\", entry: raw };\n }\n const obj = raw as Record<string, unknown>;\n if (Array.isArray(obj.hooks)) {\n // Wrapped shape. `matcher` may be absent in hand-edited files; treat\n // absent as \"\" so we don't throw on slightly malformed input.\n const matcher = typeof obj.matcher === \"string\" ? obj.matcher : \"\";\n const hooks: HookCommand[] = [];\n for (const h of obj.hooks as unknown[]) {\n if (h !== null && typeof h === \"object\") {\n const ho = h as Record<string, unknown>;\n if (ho.type === \"command\" && typeof ho.command === \"string\") {\n const cmd: HookCommand = {\n type: \"command\",\n command: ho.command,\n };\n if (typeof ho.timeout === \"number\") cmd.timeout = ho.timeout;\n hooks.push(cmd);\n }\n }\n }\n return { kind: \"wrapped\", entry: { matcher, hooks } };\n }\n if (obj.type === \"command\" && typeof obj.command === \"string\") {\n // Legacy unwrapped shape — v0.1.0–v0.1.4 wrote this form.\n const cmd: HookCommand = {\n type: \"command\",\n command: obj.command as string,\n };\n if (typeof obj.timeout === \"number\") cmd.timeout = obj.timeout;\n return { kind: \"legacy\", entry: cmd };\n }\n return { kind: \"unknown\", entry: raw };\n}\n\n/** True when the entry references our script and is safely ours to manage. */\nfunction isOurWrapped(entry: WrappedEntry): boolean {\n return entry.hooks.some((h) => isOurCommandPath(h.command));\n}\n\nexport async function runHookInstall(\n options: HookCommandOptions = {},\n): Promise<HookCommandResult> {\n const bundled = resolveHookScriptPath(options);\n if (!bundled.ok) {\n return { stdout: \"\", stderr: `almanac: ${bundled.error}\\n`, exitCode: 1 };\n }\n\n // Copy the bundled hook script to a stable user-owned location before\n // writing that path into settings.json. This is the Bug #1 fix:\n //\n // OLD behavior: settings.json pointed at the bundled path (inside\n // ~/.nvm/versions/node/<ver>/lib/node_modules/codealmanac/hooks/... or\n // ~/.npm/_npx/<sha>/node_modules/codealmanac/hooks/...). When the user\n // switches Node versions or the npx cache is evicted, the path breaks\n // silently and captures stop firing.\n //\n // NEW behavior: we copy almanac-capture.sh to ~/.claude/hooks/ (same\n // directory Claude Code uses for its own built-in hooks, always present)\n // and point settings.json there. The stable path is independent of\n // Node version and npm cache state. When the user upgrades codealmanac,\n // `almanac hook install` copies a fresh script and updates settings.json\n // if the path changed.\n //\n // When `hookScriptPath` is explicitly provided (test injection), the\n // caller has already specified the destination path — skip the copy and\n // use that path directly. The stable-copy concern only applies to the\n // production flow where we resolved from the bundled package layout.\n const script: ScriptResolution = options.hookScriptPath !== undefined\n ? bundled // already the caller-provided path, no copy needed\n : await copyToStableHooksDir(bundled.path, options);\n if (!script.ok) {\n return { stdout: \"\", stderr: `almanac: ${script.error}\\n`, exitCode: 1 };\n }\n\n const settingsPath = resolveSettingsPath(options);\n const settings = await readSettings(settingsPath);\n const existing = (settings.hooks?.SessionEnd ?? []).slice();\n\n // Walk existing entries and split them into buckets:\n // - `preserved` — foreign wrapped/unknown entries we leave alone.\n // - `oursAlready` — a wrapped entry that already points at OUR exact\n // script path (makes install a no-op).\n // - `oursStale` — a wrapped or legacy entry that references our\n // capture script but at a different absolute path\n // (old install, `npm i` moved us) or in the legacy\n // unwrapped shape. We'll collapse these into a\n // single fresh entry at the new path.\n const preserved: RawEntry[] = [];\n let oursAlready: WrappedEntry | null = null;\n const staleCount = { n: 0 };\n\n for (const raw of existing) {\n const c = classifyEntry(raw);\n if (c.kind === \"wrapped\") {\n if (!isOurWrapped(c.entry)) {\n preserved.push(raw);\n continue;\n }\n // Entry belongs to us. Does it already point at the exact script\n // path? If every command in its `hooks[]` that looks like ours is\n // already at `script.path`, it's up to date.\n const exactMatch = c.entry.hooks.some(\n (h) => h.command === script.path,\n );\n if (exactMatch && oursAlready === null) {\n oursAlready = c.entry;\n } else {\n staleCount.n += 1;\n }\n } else if (c.kind === \"legacy\") {\n if (isOurCommandPath(c.entry.command)) {\n // Legacy unwrapped entry of ours — always migrate to wrapped.\n staleCount.n += 1;\n } else {\n // Foreign legacy entry (user had their own script before\n // settings.json required wrapping). Leave it alone.\n preserved.push(raw);\n }\n } else {\n // Unknown shape — we can't classify it. Preserve verbatim.\n preserved.push(raw);\n }\n }\n\n // If every non-ours entry is a foreign unwrapped command (not a\n // wrapped one) we refuse to touch the file — Claude Code's newer\n // schema will already reject such files, but surfacing it here lets\n // the user clean up before we stack our entry on top. Wrapped foreign\n // entries are fine to leave alongside ours.\n const foreignLegacy = preserved.filter((raw) => {\n const c = classifyEntry(raw);\n return c.kind === \"legacy\";\n });\n if (foreignLegacy.length > 0) {\n const lines = foreignLegacy\n .map((raw) => {\n const c = classifyEntry(raw);\n if (c.kind === \"legacy\") return ` - ${c.entry.command}`;\n return \" - <unrecognized>\";\n })\n .join(\"\\n\");\n return {\n stdout: \"\",\n stderr:\n `almanac: SessionEnd has a foreign legacy entry:\\n${lines}\\n` +\n `Remove or rewrap it manually in ${settingsPath} before installing.\\n`,\n exitCode: 1,\n };\n }\n\n if (oursAlready !== null && staleCount.n === 0) {\n return {\n stdout: `almanac: SessionEnd hook already installed at ${script.path}\\n`,\n stderr: \"\",\n exitCode: 0,\n };\n }\n\n // Build the fresh wrapped entry and append to preserved foreign\n // entries. Stale entries of ours are dropped (we only ever want a\n // single active entry; multiple copies of the capture hook would\n // double-fire on session end).\n const fresh: WrappedEntry = {\n matcher: \"\",\n hooks: [\n {\n type: \"command\",\n command: script.path,\n timeout: HOOK_TIMEOUT_SECONDS,\n },\n ],\n };\n\n const newEntries: RawEntry[] = [...preserved, fresh];\n\n settings.hooks = { ...(settings.hooks ?? {}), SessionEnd: newEntries };\n await writeSettings(settingsPath, settings);\n\n return {\n stdout:\n `almanac: SessionEnd hook installed\\n` +\n ` script: ${script.path}\\n` +\n ` settings: ${settingsPath}\\n`,\n stderr: \"\",\n exitCode: 0,\n };\n}\n\nexport async function runHookUninstall(\n options: HookCommandOptions = {},\n): Promise<HookCommandResult> {\n const settingsPath = resolveSettingsPath(options);\n\n if (!existsSync(settingsPath)) {\n return {\n stdout: `almanac: SessionEnd hook not installed (no settings file)\\n`,\n stderr: \"\",\n exitCode: 0,\n };\n }\n\n const settings = await readSettings(settingsPath);\n const existing = (settings.hooks?.SessionEnd ?? []).slice();\n\n const kept: RawEntry[] = [];\n let removed = 0;\n\n for (const raw of existing) {\n const c = classifyEntry(raw);\n if (c.kind === \"wrapped\") {\n // Filter out our command(s) from the inner hooks array. Keep\n // anything else in the array intact — a foreign wrapper that\n // happened to include our script alongside its own commands\n // (unusual, but survivable) loses our entry and keeps theirs.\n const innerKept = c.entry.hooks.filter(\n (h) => !isOurCommandPath(h.command),\n );\n const innerRemoved = c.entry.hooks.length - innerKept.length;\n removed += innerRemoved;\n if (innerKept.length === 0) {\n // Only drop the outer wrapper when it was entirely ours. A\n // foreign wrapper that never contained our script stays verbatim\n // below (handled by `innerRemoved === 0`, which leaves\n // `innerKept.length === c.entry.hooks.length`, hence we fall\n // through to the else-branch).\n if (innerRemoved === 0) kept.push(raw);\n // else: fully owned by us, drop the container.\n } else if (innerRemoved === 0) {\n // Untouched foreign wrapper — preserve the raw object to keep\n // any fields (like matcher) byte-for-byte.\n kept.push(raw);\n } else {\n // Partial: rebuild with just the kept inner entries, preserving\n // the original matcher string.\n kept.push({ matcher: c.entry.matcher, hooks: innerKept });\n }\n } else if (c.kind === \"legacy\") {\n if (isOurCommandPath(c.entry.command)) {\n removed += 1;\n } else {\n kept.push(raw);\n }\n } else {\n kept.push(raw);\n }\n }\n\n if (removed === 0) {\n return {\n stdout: `almanac: SessionEnd hook not installed\\n`,\n stderr: \"\",\n exitCode: 0,\n };\n }\n\n if (settings.hooks !== undefined) {\n if (kept.length === 0) {\n // Empty SessionEnd array confuses some linters; drop the key when\n // nothing's left.\n const { SessionEnd: _dropped, ...rest } = settings.hooks;\n void _dropped;\n settings.hooks = rest;\n } else {\n settings.hooks = { ...settings.hooks, SessionEnd: kept };\n }\n\n // If `hooks` itself is now empty (user had only our SessionEnd entry\n // and no other hook categories), drop the `hooks` key entirely so\n // uninstall leaves the settings file in the same shape it would be\n // in had we never run install. An empty `\"hooks\": {}` is an obvious\n // breadcrumb in commit diffs.\n if (Object.keys(settings.hooks).length === 0) {\n delete settings.hooks;\n }\n }\n\n await writeSettings(settingsPath, settings);\n\n return {\n stdout: `almanac: SessionEnd hook removed\\n`,\n stderr: \"\",\n exitCode: 0,\n };\n}\n\nexport async function runHookStatus(\n options: HookCommandOptions = {},\n): Promise<HookCommandResult> {\n const script = resolveHookScriptPath(options);\n const settingsPath = resolveSettingsPath(options);\n\n if (!existsSync(settingsPath)) {\n return {\n stdout:\n `SessionEnd hook: not installed\\n` +\n `settings: ${settingsPath} (does not exist)\\n` +\n (script.ok ? `script would be: ${script.path}\\n` : \"\"),\n stderr: \"\",\n exitCode: 0,\n };\n }\n\n const settings = await readSettings(settingsPath);\n const existing = settings.hooks?.SessionEnd ?? [];\n\n // Walk the array looking for any entry (wrapped or legacy) that\n // references our capture script. Gathering foreign entries separately\n // lets us show them to the user if nothing of ours was found.\n let ourCommand: string | null = null;\n const foreignSummary: string[] = [];\n for (const raw of existing) {\n const c = classifyEntry(raw);\n if (c.kind === \"wrapped\") {\n for (const h of c.entry.hooks) {\n if (isOurCommandPath(h.command)) {\n ourCommand ??= h.command;\n } else {\n foreignSummary.push(h.command);\n }\n }\n } else if (c.kind === \"legacy\") {\n if (isOurCommandPath(c.entry.command)) {\n ourCommand ??= c.entry.command;\n } else {\n foreignSummary.push(c.entry.command);\n }\n }\n }\n\n if (ourCommand === null) {\n const foreignLines = foreignSummary\n .map((c) => ` - ${c}`)\n .join(\"\\n\");\n return {\n stdout:\n `SessionEnd hook: not installed\\n` +\n `settings: ${settingsPath}\\n` +\n (foreignSummary.length > 0\n ? `(${foreignSummary.length} foreign entr${foreignSummary.length === 1 ? \"y\" : \"ies\"} present:\\n${foreignLines})\\n`\n : \"\") +\n (script.ok ? `script would be: ${script.path}\\n` : \"\"),\n stderr: \"\",\n exitCode: 0,\n };\n }\n\n return {\n stdout:\n `SessionEnd hook: installed\\n` +\n `script: ${ourCommand}\\n` +\n `settings: ${settingsPath}\\n`,\n stderr: \"\",\n exitCode: 0,\n };\n}\n\n// ─── Settings JSON helpers ───────────────────────────────────────────\n\nasync function readSettings(settingsPath: string): Promise<SettingsJson> {\n if (!existsSync(settingsPath)) return {};\n try {\n const raw = await readFile(settingsPath, \"utf8\");\n if (raw.trim().length === 0) return {};\n const parsed = JSON.parse(raw) as unknown;\n if (parsed === null || typeof parsed !== \"object\") return {};\n return parsed as SettingsJson;\n } catch (err: unknown) {\n const msg = err instanceof Error ? err.message : String(err);\n throw new Error(`failed to read ${settingsPath}: ${msg}`);\n }\n}\n\nasync function writeSettings(\n settingsPath: string,\n settings: SettingsJson,\n): Promise<void> {\n const dir = path.dirname(settingsPath);\n await mkdir(dir, { recursive: true });\n\n // Atomic write: JSON.stringify → tmp file → rename. `rename` within the\n // same filesystem is atomic on POSIX; Claude Code never sees a partial\n // file. Formatted with 2-space indent to match the existing settings.\n const tmp = `${settingsPath}.almanac-tmp-${process.pid}`;\n const body = `${JSON.stringify(settings, null, 2)}\\n`;\n await writeFile(tmp, body, \"utf8\");\n await rename(tmp, settingsPath);\n}\n","import { existsSync } from \"node:fs\";\nimport { copyFile, mkdir, readFile } from \"node:fs/promises\";\nimport { homedir } from \"node:os\";\nimport path from \"node:path\";\nimport { fileURLToPath } from \"node:url\";\n\nexport interface HookPathOptions {\n hookScriptPath?: string;\n settingsPath?: string;\n stableHooksDir?: string;\n}\n\nexport type ScriptResolution =\n | { ok: true; path: string }\n | { ok: false; error: string };\n\nexport function resolveSettingsPath(options: HookPathOptions): string {\n if (options.settingsPath !== undefined) return options.settingsPath;\n return path.join(homedir(), \".claude\", \"settings.json\");\n}\n\n/**\n * Copy the bundled hook script to `~/.claude/hooks/almanac-capture.sh`.\n *\n * This stable, user-owned destination survives Node version switches and\n * npm/npx cache evictions. The copy is idempotent: if bytes already match\n * we skip writing so repeated setup runs do not bump mtimes.\n */\nexport async function copyToStableHooksDir(\n bundledPath: string,\n options: HookPathOptions,\n): Promise<ScriptResolution> {\n const stableHooksDir =\n options.stableHooksDir ?? path.join(homedir(), \".claude\", \"hooks\");\n const dest = path.join(stableHooksDir, \"almanac-capture.sh\");\n\n try {\n await mkdir(stableHooksDir, { recursive: true });\n const srcBytes = await readFile(bundledPath);\n let needsCopy = true;\n if (existsSync(dest)) {\n try {\n const destBytes = await readFile(dest);\n if (srcBytes.equals(destBytes)) needsCopy = false;\n } catch {\n // Can't read dest — overwrite.\n }\n }\n if (needsCopy) {\n await copyFile(bundledPath, dest);\n }\n return { ok: true, path: dest };\n } catch (err: unknown) {\n const msg = err instanceof Error ? err.message : String(err);\n return {\n ok: false,\n error: `could not copy hook script to ${dest}: ${msg}`,\n };\n }\n}\n\n/**\n * Locate the bundled `hooks/almanac-capture.sh`. Mirrors\n * `resolvePromptsDir` from `src/agent/prompts.ts`: two plausible layouts\n * (installed dist vs. source dev), probe each.\n */\nexport function resolveHookScriptPath(\n options: HookPathOptions,\n): ScriptResolution {\n if (options.hookScriptPath !== undefined) {\n return { ok: true, path: options.hookScriptPath };\n }\n\n const here = path.dirname(fileURLToPath(import.meta.url));\n\n const candidates = [\n // Bundled: `.../codealmanac/dist/codealmanac.js` → `../hooks/…`\n path.resolve(here, \"..\", \"hooks\", \"almanac-capture.sh\"),\n // Source after ts-node-style module layout or nested dist helpers.\n path.resolve(here, \"..\", \"..\", \"hooks\", \"almanac-capture.sh\"),\n // Source: `.../codealmanac/src/commands/hook/script.ts` → `../../../hooks/…`\n path.resolve(here, \"..\", \"..\", \"..\", \"hooks\", \"almanac-capture.sh\"),\n // Defensive nested fallback.\n path.resolve(here, \"..\", \"..\", \"..\", \"..\", \"hooks\", \"almanac-capture.sh\"),\n ];\n\n for (const candidate of candidates) {\n if (existsSync(candidate)) {\n return { ok: true, path: candidate };\n }\n }\n\n return {\n ok: false,\n error:\n `could not locate hooks/almanac-capture.sh. Tried:\\n` +\n candidates.map((c) => ` - ${c}`).join(\"\\n\"),\n };\n}\n","import { execFile } from \"node:child_process\";\nimport { createRequire } from \"node:module\";\nimport { homedir } from \"node:os\";\nimport path from \"node:path\";\nimport { fileURLToPath } from \"node:url\";\n\n/**\n * Return the directory of the currently-running codealmanac install by\n * walking up from this module's file to the nearest `package.json` whose\n * `name` is `codealmanac`. Returns the empty string when the walk fails.\n */\nexport function detectCurrentInstallPath(): string {\n try {\n const req = createRequire(import.meta.url);\n const here = fileURLToPath(import.meta.url);\n let dir = path.dirname(here);\n for (let i = 0; i < 6; i++) {\n const pkgPath = path.join(dir, \"package.json\");\n try {\n const raw = req(\"fs\").readFileSync(pkgPath, \"utf-8\") as string;\n const pkg = JSON.parse(raw) as { name?: unknown };\n if (pkg.name === \"codealmanac\") return dir;\n } catch {\n // keep walking\n }\n const parent = path.dirname(dir);\n if (parent === dir) break;\n dir = parent;\n }\n } catch {\n // import.meta.url unavailable — not ephemeral for our purposes.\n }\n return \"\";\n}\n\n/**\n * Return true when the given install path looks ephemeral.\n *\n * Ephemeral locations we recognize:\n * - `~/.npm/_npx/` — npm's npx cache (GC'd on version bumps or\n * `npm cache clean`)\n * - `~/.local/share/pnpm/dlx/` — pnpm's dlx (like npx) cache\n * - `/tmp/` or `/var/folders/` — common CI / temp paths\n *\n * A global install (`~/.nvm/.../lib/node_modules/`, `/usr/local/lib/...`,\n * `~/.local/lib/node_modules/`) is NOT ephemeral.\n */\nexport function detectEphemeral(installPath: string): boolean {\n if (installPath.length === 0) return false;\n const home = homedir();\n if (installPath.startsWith(path.join(home, \".npm\", \"_npx\"))) return true;\n if (\n installPath.startsWith(path.join(home, \".local\", \"share\", \"pnpm\", \"dlx\"))\n ) return true;\n if (installPath.startsWith(\"/tmp/\")) return true;\n if (installPath.startsWith(\"/var/folders/\")) return true;\n return false;\n}\n\n/**\n * Spawn `npm install -g codealmanac@latest` in a child process and wait\n * for it to finish. Rejects on non-zero exit or spawn error.\n */\nexport function spawnGlobalInstall(): Promise<void> {\n return new Promise((resolve, reject) => {\n execFile(\n \"npm\",\n [\"install\", \"-g\", \"codealmanac@latest\"],\n { shell: false },\n (err, _stdout, stderr) => {\n if (err !== null) {\n reject(\n new Error(\n stderr.length > 0\n ? stderr.trim().split(\"\\n\")[0] ?? err.message\n : err.message,\n ),\n );\n } else {\n resolve();\n }\n },\n );\n });\n}\n","import { existsSync, readdirSync } from \"node:fs\";\nimport path from \"node:path\";\n\nconst RST = \"\\x1b[0m\";\nconst BOLD = \"\\x1b[1m\";\nconst DIM = \"\\x1b[2m\";\nconst WHITE_BOLD = \"\\x1b[1;37m\";\nconst BLUE = \"\\x1b[38;5;75m\";\nconst BLUE_DIM = \"\\x1b[38;5;69m\";\n\n/**\n * Print the \"Next steps\" box. When `existingPageCount` is greater than 0,\n * the current working directory already has a wiki with committed pages.\n * In that case we skip the `almanac bootstrap` step and tell the user to\n * start querying.\n */\nexport function printNextSteps(\n out: NodeJS.WritableStream,\n existingPageCount: number,\n): void {\n const innerW = 62;\n const vis = (s: string): number =>\n s.replace(/\\x1b\\[[0-9;]*m/g, \"\").length;\n const row = (content: string): string => {\n const padding = Math.max(0, innerW - vis(content));\n return ` ${BLUE_DIM}\\u2502${RST}${content}${\" \".repeat(padding)}${BLUE_DIM}\\u2502${RST}\\n`;\n };\n const empty = row(\"\");\n\n out.write(` ${BLUE_DIM}\\u256d${\"─\".repeat(innerW)}\\u256e${RST}\\n`);\n out.write(empty);\n out.write(row(` ${WHITE_BOLD}Next steps${RST}`));\n out.write(empty);\n\n if (existingPageCount > 0) {\n out.write(\n row(\n ` ${BLUE}\\u25c7${RST} This repo already has a wiki ${DIM}(${existingPageCount} page${existingPageCount === 1 ? \"\" : \"s\"})${RST}`,\n ),\n );\n out.write(empty);\n out.write(row(` ${BLUE}1.${RST} Start querying your wiki:`));\n out.write(row(` ${BOLD}almanac search --mentions <file>${RST}`));\n out.write(\n row(` ${BLUE}2.${RST} Work normally — capture runs on session end`),\n );\n } else {\n out.write(\n row(` ${BLUE}1.${RST} ${BOLD}cd${RST} into a repo you want to document`),\n );\n out.write(\n row(\n ` ${BLUE}2.${RST} ${BOLD}almanac bootstrap${RST} ${DIM}# scaffold the wiki${RST}`,\n ),\n );\n out.write(\n row(` ${BLUE}3.${RST} Work normally — capture runs on session end`),\n );\n }\n\n out.write(empty);\n out.write(` ${BLUE_DIM}\\u2570${\"─\".repeat(innerW)}\\u256f${RST}\\n\\n`);\n}\n\n/**\n * Count `.md` files in `.almanac/pages/` under the current working\n * directory or any parent. Returns 0 when no wiki is found or the pages\n * directory is empty.\n */\nexport function countExistingPages(cwd: string): number {\n try {\n let dir = cwd;\n for (let i = 0; i < 10; i++) {\n const pagesDir = path.join(dir, \".almanac\", \"pages\");\n if (existsSync(pagesDir)) {\n try {\n const entries = readdirSync(pagesDir);\n return entries.filter((e) => e.endsWith(\".md\")).length;\n } catch {\n return 0;\n }\n }\n const parent = path.dirname(dir);\n if (parent === dir) break;\n dir = parent;\n }\n } catch {\n // Swallow — never crash setup because of this.\n }\n return 0;\n}\n","/**\n * Shared ANSI escape-code constants. TTY-aware: when stdout is not a\n * terminal or `NO_COLOR` is set, every constant resolves to the empty\n * string so formatted output degrades to plain text without per-call\n * checks at every use site.\n *\n * See https://no-color.org/ for the `NO_COLOR` convention.\n */\n\nconst useColor =\n (process.stdout.isTTY ?? false) && !(\"NO_COLOR\" in process.env);\n\nexport const RST = useColor ? \"\\x1b[0m\" : \"\";\nexport const BOLD = useColor ? \"\\x1b[1m\" : \"\";\nexport const DIM = useColor ? \"\\x1b[2m\" : \"\";\nexport const GREEN = useColor ? \"\\x1b[38;5;35m\" : \"\";\nexport const RED = useColor ? \"\\x1b[38;5;167m\" : \"\";\nexport const BLUE = useColor ? \"\\x1b[38;5;75m\" : \"\";\nexport const YELLOW = useColor ? \"\\x1b[33m\" : \"\";\nexport const WHITE_BOLD = useColor ? \"\\x1b[1;37m\" : \"\";\nexport const BLUE_DIM = useColor ? \"\\x1b[38;5;69m\" : \"\";\nexport const ACCENT_BG = useColor\n ? \"\\x1b[48;5;252m\\x1b[38;5;16m\"\n : \"\";\n","import { Command, type Help } from \"commander\";\n\nimport { BLUE, BOLD, DIM, RST } from \"../ansi.js\";\n\nconst HELP_GROUPS: Array<{ title: string; commands: string[] }> = [\n {\n title: \"Query\",\n commands: [\"search\", \"show\", \"health\", \"list\"],\n },\n {\n title: \"Edit\",\n commands: [\"tag\", \"untag\", \"topics\"],\n },\n {\n title: \"Wiki lifecycle\",\n commands: [\"bootstrap\", \"capture\", \"hook\", \"reindex\"],\n },\n {\n title: \"Setup\",\n commands: [\"setup\", \"uninstall\", \"doctor\", \"update\"],\n },\n];\n\n/**\n * Install a custom `formatHelp` that replaces commander's flat\n * \"Commands:\" section with grouped headings. Keeps usage + options +\n * per-command short descriptions; only the commands section changes.\n */\nexport function configureGroupedHelp(program: Command): void {\n program.configureHelp({\n formatHelp(cmd, helper): string {\n if (cmd.parent !== null) {\n return renderDefault(cmd, helper);\n }\n\n const termWidth = helper.padWidth(cmd, helper);\n const helpWidth =\n helper.helpWidth ?? process.stdout.columns ?? 80;\n const itemSepWidth = 2;\n\n const out: string[] = [];\n out.push(`${BOLD}Usage:${RST} ${helper.commandUsage(cmd)}\\n`);\n\n const description = helper.commandDescription(cmd);\n if (description.length > 0) {\n out.push(\n helper.wrap(description, helpWidth, 0) + \"\\n\",\n );\n }\n\n const optionList = helper\n .visibleOptions(cmd)\n .map((o) => {\n const term = helper.optionTerm(o);\n const pad = \" \".repeat(Math.max(0, termWidth - term.length) + itemSepWidth);\n return `${BLUE}${term}${RST}${pad}${DIM}${helper.optionDescription(o)}${RST}`;\n });\n if (optionList.length > 0) {\n out.push(`${BOLD}Options:${RST}`);\n for (const l of optionList) out.push(` ${l}`);\n out.push(\"\");\n }\n\n const visible = helper.visibleCommands(cmd);\n const byName = new Map<string, (typeof visible)[number]>();\n for (const c of visible) byName.set(c.name(), c);\n\n for (const group of HELP_GROUPS) {\n const members = group.commands\n .map((n) => byName.get(n))\n .filter((c): c is (typeof visible)[number] => c !== undefined);\n if (members.length === 0) continue;\n out.push(`${BOLD}${group.title}:${RST}`);\n for (const c of members) {\n const term = helper.subcommandTerm(c);\n const desc = helper.subcommandDescription(c);\n const padding = Math.max(\n 0,\n termWidth - term.length + itemSepWidth,\n );\n out.push(` ${BLUE}${term}${RST}${\" \".repeat(padding)}${DIM}${desc}${RST}`);\n byName.delete(c.name());\n }\n out.push(\"\");\n }\n\n byName.delete(\"help\");\n if (byName.size > 0) {\n out.push(`${BOLD}Other:${RST}`);\n for (const c of byName.values()) {\n const term = helper.subcommandTerm(c);\n const desc = helper.subcommandDescription(c);\n const padding = Math.max(\n 0,\n termWidth - term.length + itemSepWidth,\n );\n out.push(` ${BLUE}${term}${RST}${\" \".repeat(padding)}${DIM}${desc}${RST}`);\n }\n out.push(\"\");\n }\n\n return out.join(\"\\n\");\n },\n });\n}\n\nfunction renderDefault(cmd: Command, helper: Help): string {\n const termWidth = helper.padWidth(cmd, helper);\n const helpWidth = helper.helpWidth ?? process.stdout.columns ?? 80;\n const itemSepWidth = 2;\n\n const lines: string[] = [`${BOLD}Usage:${RST} ${helper.commandUsage(cmd)}\\n`];\n const description = helper.commandDescription(cmd);\n if (description.length > 0) {\n lines.push(helper.wrap(description, helpWidth, 0) + \"\\n\");\n }\n\n const args = helper.visibleArguments(cmd).map((a) => {\n const term = helper.argumentTerm(a);\n const pad = \" \".repeat(Math.max(0, termWidth - term.length) + itemSepWidth);\n return `${BLUE}${term}${RST}${pad}${DIM}${helper.argumentDescription(a)}${RST}`;\n });\n if (args.length > 0) {\n lines.push(`${BOLD}Arguments:${RST}`);\n for (const a of args) lines.push(` ${a}`);\n lines.push(\"\");\n }\n\n const opts = helper.visibleOptions(cmd).map((o) => {\n const term = helper.optionTerm(o);\n const pad = \" \".repeat(Math.max(0, termWidth - term.length) + itemSepWidth);\n return `${BLUE}${term}${RST}${pad}${DIM}${helper.optionDescription(o)}${RST}`;\n });\n if (opts.length > 0) {\n lines.push(`${BOLD}Options:${RST}`);\n for (const o of opts) lines.push(` ${o}`);\n lines.push(\"\");\n }\n\n const subs = helper.visibleCommands(cmd).map((c) => {\n const term = helper.subcommandTerm(c);\n const pad = \" \".repeat(Math.max(0, termWidth - term.length) + itemSepWidth);\n return `${BLUE}${term}${RST}${pad}${DIM}${helper.subcommandDescription(c)}${RST}`;\n });\n if (subs.length > 0) {\n lines.push(`${BOLD}Commands:${RST}`);\n for (const s of subs) lines.push(` ${s}`);\n lines.push(\"\");\n }\n\n return lines.join(\"\\n\");\n}\n","export interface CommandResult {\n stdout: string;\n stderr: string;\n exitCode: number;\n}\n\nexport function emit(result: CommandResult): void {\n if (result.stderr.length > 0) process.stderr.write(result.stderr);\n if (result.stdout.length > 0) process.stdout.write(result.stdout);\n if (result.exitCode !== 0) process.exitCode = result.exitCode;\n}\n\nexport function collectOption(value: string, previous: string[]): string[] {\n return [...previous, value];\n}\n\nexport function parsePositiveInt(value: string): number {\n const n = Number.parseInt(value, 10);\n if (!Number.isFinite(n) || n < 0) {\n throw new Error(`invalid --limit \"${value}\" (expected a non-negative integer)`);\n }\n return n;\n}\n\nexport async function readStdin(): Promise<string> {\n if (process.stdin.isTTY === true) return \"\";\n const chunks: Buffer[] = [];\n for await (const chunk of process.stdin) {\n chunks.push(Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk));\n }\n return Buffer.concat(chunks).toString(\"utf8\");\n}\n","import { createHash } from \"node:crypto\";\nimport { existsSync, statSync } from \"node:fs\";\nimport { readFile, utimes } from \"node:fs/promises\";\nimport { basename, join } from \"node:path\";\n\nimport fg from \"fast-glob\";\nimport type Database from \"better-sqlite3\";\n\nimport { toKebabCase } from \"../slug.js\";\nimport { titleCase } from \"../topics/yaml.js\";\nimport { firstH1, parseFrontmatter } from \"./frontmatter.js\";\nimport {\n PAGES_GLOB,\n pagesNewerThan,\n topicsYamlNewerThan,\n} from \"./freshness.js\";\nimport {\n normalizePath,\n normalizePathPreservingCase,\n looksLikeDir,\n} from \"./paths.js\";\nimport { openIndex } from \"./schema.js\";\nimport { applyTopicsYaml, TOPICS_YAML_FILENAME } from \"./topics-yaml.js\";\nimport { extractWikilinks } from \"./wikilinks.js\";\n\nexport interface IndexContext {\n /** Absolute path to the repo root (the dir containing `.almanac/`). */\n repoRoot: string;\n}\n\nexport interface IndexResult {\n /** Pages parsed or re-parsed during this run. Zero when the DB was already up to date. */\n changed: number;\n /** Pages present in the DB before this run but missing from disk. */\n removed: number;\n /**\n * Pages on disk at the end of this run — i.e. files that made it all the\n * way through to the index. Skipped files (slug collisions, unreadable,\n * un-sluggable filenames) are NOT counted here. Use `filesSeen` for the\n * raw count of `.md` files encountered on disk.\n *\n * Alias retained for backwards-compat with existing tests/consumers; new\n * code should prefer `pagesIndexed` for clarity.\n */\n total: number;\n /** Pages that made it into the index. Same number as `total`. */\n pagesIndexed: number;\n /**\n * Count of `.md` files found under `pages/` before any filtering. Always\n * `>= pagesIndexed`; the difference is `filesSkipped`.\n */\n filesSeen: number;\n /**\n * Files dropped before making it into the index — slug collisions,\n * un-sluggable filenames, or filesystem races (deleted/unreadable mid-run).\n * Covered by stderr warnings when non-zero.\n */\n filesSkipped: number;\n}\n\n/**\n * The \"front door\" for query commands. Runs the indexer only if the DB is\n * missing or at least one page is newer than it. Meant to be cheap — the\n * common case is \"nothing changed, mtime check returns fast, we're done\".\n *\n * The spec is explicit: \"Reindex is implicit and invisible. If the user\n * didn't didn't explicitly run `reindex`, they shouldn't see reindex\n * output. Silent by default.\" So this function never writes to stdout;\n * warnings (slug collisions, bad frontmatter) still go to stderr.\n */\nexport async function ensureFreshIndex(ctx: IndexContext): Promise<IndexResult> {\n const almanacDir = join(ctx.repoRoot, \".almanac\");\n const dbPath = join(almanacDir, \"index.db\");\n const pagesDir = join(almanacDir, \"pages\");\n\n if (!existsSync(pagesDir)) {\n // No pages dir = nothing to index. Open/create the DB so downstream\n // queries can run against an empty schema rather than crashing on a\n // missing file.\n const db = openIndex(dbPath);\n db.close();\n return emptyResult();\n }\n\n if (\n !existsSync(dbPath) ||\n pagesNewerThan(pagesDir, dbPath) ||\n topicsYamlNewerThan(almanacDir, dbPath)\n ) {\n return runIndexer(ctx);\n }\n return emptyResult();\n}\n\nfunction emptyResult(): IndexResult {\n return {\n changed: 0,\n removed: 0,\n total: 0,\n pagesIndexed: 0,\n filesSeen: 0,\n filesSkipped: 0,\n };\n}\n\n/**\n * Force a full reindex. Identical to `ensureFreshIndex` except it runs\n * the indexer unconditionally. Exposed for `almanac reindex`.\n */\nexport async function runIndexer(ctx: IndexContext): Promise<IndexResult> {\n const almanacDir = join(ctx.repoRoot, \".almanac\");\n const dbPath = join(almanacDir, \"index.db\");\n const pagesDir = join(almanacDir, \"pages\");\n\n const db = openIndex(dbPath);\n let result: IndexResult;\n try {\n result = await indexPagesInto(db, pagesDir);\n // After pages are indexed, reconcile the topics table against\n // `.almanac/topics.yaml` (if present). `indexPagesInto` has already\n // lazily inserted rows for every topic slug mentioned in page\n // frontmatter with a title-cased title; `applyTopicsYaml` now\n // promotes the declared title/description and rewrites parent edges\n // for those topics that live in the file.\n await applyTopicsYaml(db, join(almanacDir, TOPICS_YAML_FILENAME));\n } finally {\n db.close();\n }\n\n // Bump the DB mtime to \"now\" after a successful reindex (even a no-op\n // one). Otherwise, a page file with a future mtime (clock skew,\n // `git checkout` preserving source mtimes) would trigger `ensureFreshIndex`\n // on every query: the freshness check sees `page.mtime > db.mtime`,\n // reindex runs, finds no content-hash changes, and the DB mtime stays\n // stale — locking us into a reindex-on-every-query loop. Touching the\n // DB mtime makes the comparison monotonic.\n try {\n const now = new Date();\n await utimes(dbPath, now, now);\n } catch {\n // Touching mtime is a freshness optimization; failures here are\n // non-fatal and the reindex result is still correct.\n }\n return result;\n}\n\ninterface ExistingRow {\n slug: string;\n content_hash: string;\n file_path: string;\n}\n\nasync function indexPagesInto(\n db: Database.Database,\n pagesDir: string,\n): Promise<IndexResult> {\n const files = await fg(PAGES_GLOB, {\n cwd: pagesDir,\n absolute: false,\n onlyFiles: true,\n caseSensitiveMatch: true,\n });\n\n // Load the current state of the index into memory so we can diff against\n // what's on disk. This is cheap even at 10k pages (one INTEGER + two\n // short strings per row).\n const existingRows = db\n .prepare<[], ExistingRow>(\"SELECT slug, content_hash, file_path FROM pages\")\n .all();\n const existingBySlug = new Map<string, ExistingRow>();\n for (const row of existingRows) existingBySlug.set(row.slug, row);\n\n // First pass: decide what to do with each file on disk. We record the\n // intent here so the transaction below can run synchronously — mixing\n // async file reads into a better-sqlite3 transaction doesn't work\n // (transactions are sync).\n const planned: Array<{\n slug: string;\n title: string;\n filePath: string;\n fullPath: string;\n contentHash: string;\n updatedAt: number;\n archivedAt: number | null;\n supersededBy: string | null;\n topics: string[];\n frontmatterFiles: string[];\n wikilinks: ReturnType<typeof extractWikilinks>;\n content: string;\n }> = [];\n const seenSlugs = new Set<string>();\n let filesSkipped = 0;\n\n for (const rel of files) {\n const fullPath = join(pagesDir, rel);\n const base = basename(rel, \".md\");\n const slug = toKebabCase(base);\n if (slug.length === 0) {\n process.stderr.write(\n `almanac: skipping \"${rel}\" — filename has no slug-able characters\\n`,\n );\n filesSkipped++;\n continue;\n }\n if (slug !== base) {\n // Filename isn't already canonical kebab-case. Warn, but still\n // index under the canonical slug. `almanac health` (slice 3) will\n // surface these as a proper report.\n process.stderr.write(\n `almanac: warning — \"${rel}\" is not canonical; indexed as slug \"${slug}\"\\n`,\n );\n }\n if (seenSlugs.has(slug)) {\n // Two files slugify to the same slug. Keep the first, skip the\n // rest — health will flag this properly in slice 3.\n process.stderr.write(\n `almanac: warning — slug \"${slug}\" collides with an earlier file; skipping \"${rel}\"\\n`,\n );\n filesSkipped++;\n continue;\n }\n\n // `fast-glob` gave us the list in one shot, but by the time we stat\n // and read each file it can have been deleted, renamed, or swapped\n // (editors that save via rename-swap expose this briefly). A single\n // such race shouldn't tank the whole reindex — matches the malformed-\n // YAML behavior (\"one bad file doesn't stop the others\"). We narrow\n // to ENOENT/EACCES so genuine I/O failures (EIO, EMFILE, etc.) still\n // surface.\n let st: ReturnType<typeof statSync>;\n let raw: string;\n try {\n st = statSync(fullPath);\n raw = await readFile(fullPath, \"utf8\");\n } catch (err: unknown) {\n if (\n err instanceof Error &&\n \"code\" in err &&\n (err.code === \"ENOENT\" || err.code === \"EACCES\")\n ) {\n process.stderr.write(\n `almanac: skipping \"${rel}\" — ${err.message}\\n`,\n );\n filesSkipped++;\n continue;\n }\n throw err;\n }\n\n seenSlugs.add(slug);\n const updatedAt = Math.floor(st.mtimeMs / 1000);\n\n // Content-hash skip: if the hash matches what's in the DB and the\n // file path hasn't moved, we can leave this page's rows alone. This\n // is the fast-path for \"user ran a query; one page was touched\".\n const contentHash = hashContent(raw);\n const existing = existingBySlug.get(slug);\n if (\n existing !== undefined &&\n existing.content_hash === contentHash &&\n existing.file_path === fullPath\n ) {\n continue;\n }\n\n const fm = parseFrontmatter(raw);\n const title = fm.title ?? firstH1(fm.body) ?? base;\n const links = extractWikilinks(fm.body);\n\n planned.push({\n slug,\n title,\n filePath: rel,\n fullPath,\n contentHash,\n updatedAt,\n archivedAt: fm.archived_at,\n supersededBy: fm.superseded_by,\n topics: fm.topics,\n frontmatterFiles: fm.files,\n wikilinks: links,\n content: fm.body,\n });\n }\n\n // Compute deletions: anything in the DB whose slug isn't on disk\n // anymore (or whose file slugifies to a different slug now).\n const toDelete: string[] = [];\n for (const slug of existingBySlug.keys()) {\n if (!seenSlugs.has(slug)) toDelete.push(slug);\n }\n\n const deleteByPage = db.prepare<[string]>(\"DELETE FROM pages WHERE slug = ?\");\n const deleteFtsByPage = db.prepare<[string]>(\n \"DELETE FROM fts_pages WHERE slug = ?\",\n );\n\n const replacePage = db.prepare<\n [string, string, string, string, number, number | null, string | null]\n >(\n `INSERT INTO pages (slug, title, file_path, content_hash, updated_at, archived_at, superseded_by)\n VALUES (?, ?, ?, ?, ?, ?, ?)\n ON CONFLICT(slug) DO UPDATE SET\n title = excluded.title,\n file_path = excluded.file_path,\n content_hash = excluded.content_hash,\n updated_at = excluded.updated_at,\n archived_at = excluded.archived_at,\n superseded_by = excluded.superseded_by`,\n );\n\n const deletePageTopics = db.prepare<[string]>(\n \"DELETE FROM page_topics WHERE page_slug = ?\",\n );\n const insertPageTopic = db.prepare<[string, string]>(\n \"INSERT OR IGNORE INTO page_topics (page_slug, topic_slug) VALUES (?, ?)\",\n );\n // Seed ad-hoc topics with a title-cased default. If the topic is\n // later declared in `.almanac/topics.yaml`, `applyTopicsYaml` will\n // promote the title/description to whatever the file says. We set the\n // title here (rather than leaving NULL) so `topics list` and\n // `health --topic` have a display name even before a user writes to\n // topics.yaml.\n const insertTopic = db.prepare<[string, string]>(\n \"INSERT OR IGNORE INTO topics (slug, title) VALUES (?, ?)\",\n );\n\n const deleteFileRefs = db.prepare<[string]>(\n \"DELETE FROM file_refs WHERE page_slug = ?\",\n );\n const insertFileRef = db.prepare<[string, string, string, number]>(\n \"INSERT OR IGNORE INTO file_refs (page_slug, path, original_path, is_dir) VALUES (?, ?, ?, ?)\",\n );\n\n const deleteWikilinks = db.prepare<[string]>(\n \"DELETE FROM wikilinks WHERE source_slug = ?\",\n );\n const insertWikilink = db.prepare<[string, string]>(\n \"INSERT OR IGNORE INTO wikilinks (source_slug, target_slug) VALUES (?, ?)\",\n );\n\n const deleteXwiki = db.prepare<[string]>(\n \"DELETE FROM cross_wiki_links WHERE source_slug = ?\",\n );\n const insertXwiki = db.prepare<[string, string, string]>(\n \"INSERT OR IGNORE INTO cross_wiki_links (source_slug, target_wiki, target_slug) VALUES (?, ?, ?)\",\n );\n\n const insertFts = db.prepare<[string, string, string]>(\n \"INSERT INTO fts_pages (slug, title, content) VALUES (?, ?, ?)\",\n );\n\n const apply = db.transaction(() => {\n for (const slug of toDelete) {\n // `fts_pages` is an FTS5 virtual table — FK cascades do NOT propagate\n // into it, so we must delete FTS rows explicitly before relying on\n // `DELETE FROM pages` to cascade-clean the four real tables\n // (page_topics, file_refs, wikilinks, cross_wiki_links). If this\n // explicit delete ever gets removed, orphaned FTS rows will show up\n // as phantom search hits pointing at non-existent slugs.\n deleteFtsByPage.run(slug);\n deleteByPage.run(slug); // CASCADE cleans page_topics, file_refs, wikilinks, cross_wiki_links\n }\n\n for (const p of planned) {\n // page_topics/file_refs/wikilinks/cross_wiki_links all cascade on\n // delete, so the cleanest \"replace\" story is: delete-then-insert\n // the per-page rows under the same transaction. Doing it this way\n // (rather than `ON CONFLICT DO UPDATE` per row) keeps the logic\n // uniform and makes \"remove a topic from frontmatter\" work.\n deletePageTopics.run(p.slug);\n deleteFileRefs.run(p.slug);\n deleteWikilinks.run(p.slug);\n deleteXwiki.run(p.slug);\n // Same virtual-table reason as the deletion branch above — FTS5\n // rows do not cascade, so clean them by hand before reinserting.\n deleteFtsByPage.run(p.slug);\n\n replacePage.run(\n p.slug,\n p.title,\n p.fullPath,\n p.contentHash,\n p.updatedAt,\n p.archivedAt,\n p.supersededBy,\n );\n\n for (const topic of p.topics) {\n const topicSlug = toKebabCase(topic);\n if (topicSlug.length === 0) continue;\n insertTopic.run(topicSlug, titleCase(topicSlug));\n insertPageTopic.run(p.slug, topicSlug);\n }\n\n // Frontmatter `files:` — normalize each entry, inferring directness\n // from its trailing slash. Authors who write `src/payments` (no\n // trailing slash) are asserting a file; this matches how `[[...]]`\n // classifies the same string. We store both the lowercased form\n // (for `--mentions` GLOB queries) and the casing-preserving form\n // (for dead-ref `existsSync` on case-sensitive filesystems).\n for (const raw of p.frontmatterFiles) {\n const isDir = looksLikeDir(raw);\n const path = normalizePath(raw, isDir);\n const originalPath = normalizePathPreservingCase(raw, isDir);\n if (path.length === 0) continue;\n insertFileRef.run(p.slug, path, originalPath, isDir ? 1 : 0);\n }\n\n // Inline `[[...]]` extracted from body.\n for (const ref of p.wikilinks) {\n switch (ref.kind) {\n case \"page\":\n insertWikilink.run(p.slug, ref.target);\n break;\n case \"file\":\n insertFileRef.run(p.slug, ref.path, ref.originalPath, 0);\n break;\n case \"folder\":\n insertFileRef.run(p.slug, ref.path, ref.originalPath, 1);\n break;\n case \"xwiki\":\n insertXwiki.run(p.slug, ref.wiki, ref.target);\n break;\n }\n }\n\n insertFts.run(p.slug, p.title, p.content);\n }\n });\n apply();\n\n const pagesIndexed = seenSlugs.size;\n return {\n changed: planned.length,\n removed: toDelete.length,\n total: pagesIndexed,\n pagesIndexed,\n filesSeen: files.length,\n filesSkipped,\n };\n}\n\nfunction hashContent(raw: string): string {\n return createHash(\"sha256\").update(raw).digest(\"hex\");\n}\n","/**\n * Canonical kebab-case slugifier used across the codebase.\n *\n * One function, three callers:\n * - `registry/index.ts` — wiki name slugs (both auto-derived and\n * user-supplied via `--name`)\n * - `indexer/index.ts` — page filename → slug and topic → slug\n * - `indexer/wikilinks.ts` — wikilink target → slug for resolution\n *\n * All three want the same behavior: lowercased, non-alphanumeric runs\n * collapse to a single hyphen, leading/trailing hyphens trimmed. Keeping\n * this in one place avoids a class of bug where an unusual input (e.g.\n * `Checkout_Flow`) produces different slugs depending on which layer\n * slugified it.\n *\n * Rules:\n * - Lowercase\n * - Non-alphanumeric runs collapse to a single hyphen\n * - Leading/trailing hyphens trimmed\n */\nexport function toKebabCase(input: string): string {\n return input\n .toLowerCase()\n .replace(/[^a-z0-9]+/g, \"-\")\n .replace(/^-+|-+$/g, \"\");\n}\n","import { existsSync } from \"node:fs\";\nimport { mkdir, readFile, rename, writeFile } from \"node:fs/promises\";\nimport { dirname } from \"node:path\";\n\nimport yaml from \"js-yaml\";\n\nimport { toKebabCase } from \"../slug.js\";\n\n/**\n * One entry in `.almanac/topics.yaml` — the source of truth for topic\n * metadata (title, description, DAG parents). Pages are still the source\n * of truth for which pages belong to which topics; this file only holds\n * the topic-level attributes.\n *\n * `slug` is the canonical kebab-case key used everywhere downstream\n * (SQLite `topics.slug`, page frontmatter `topics:` entries, wikilink\n * targets). `title` is the human-readable name the user typed at create\n * time. `description` is a free-form one-liner (or null when unset).\n * `parents` is the DAG edge list — kept as an array of slugs rather than\n * a nested structure so round-tripping stays boring and a user eyeballing\n * the file can see the full graph.\n */\nexport interface TopicEntry {\n slug: string;\n title: string;\n description: string | null;\n parents: string[];\n}\n\nexport interface TopicsFile {\n topics: TopicEntry[];\n}\n\n/**\n * Load `.almanac/topics.yaml` into a `TopicsFile`. A missing file is not\n * an error — it's the first-run state, which we treat as \"no topic\n * metadata, only whatever the pages declare in frontmatter\". Malformed\n * YAML IS an error; we surface it rather than silently clobbering the\n * user's committed source of truth.\n *\n * The return shape is always normalized — callers don't have to guard\n * for missing `topics` key, wrong types, or absent `parents` arrays.\n */\nexport async function loadTopicsFile(path: string): Promise<TopicsFile> {\n if (!existsSync(path)) {\n return { topics: [] };\n }\n let raw: string;\n try {\n raw = await readFile(path, \"utf8\");\n } catch (err: unknown) {\n if (isNodeError(err) && err.code === \"ENOENT\") {\n return { topics: [] };\n }\n throw err;\n }\n\n const trimmed = raw.trim();\n if (trimmed.length === 0) {\n return { topics: [] };\n }\n\n let parsed: unknown;\n try {\n parsed = yaml.load(raw);\n } catch (err: unknown) {\n const message = err instanceof Error ? err.message : String(err);\n throw new Error(`topics.yaml at ${path} is not valid YAML: ${message}`);\n }\n\n if (parsed === null || parsed === undefined) {\n return { topics: [] };\n }\n\n if (typeof parsed !== \"object\" || Array.isArray(parsed)) {\n throw new Error(`topics.yaml at ${path} must be a mapping`);\n }\n\n const obj = parsed as Record<string, unknown>;\n const rawTopics = obj.topics;\n if (rawTopics === undefined || rawTopics === null) {\n return { topics: [] };\n }\n if (!Array.isArray(rawTopics)) {\n throw new Error(`topics.yaml at ${path} — \"topics\" must be a list`);\n }\n\n const topics: TopicEntry[] = [];\n for (const item of rawTopics) {\n if (typeof item !== \"object\" || item === null || Array.isArray(item)) {\n continue;\n }\n const entry = item as Record<string, unknown>;\n const slugRaw = entry.slug;\n if (typeof slugRaw !== \"string\" || slugRaw.trim().length === 0) continue;\n const slug = toKebabCase(slugRaw);\n if (slug.length === 0) continue;\n const title =\n typeof entry.title === \"string\" && entry.title.trim().length > 0\n ? entry.title.trim()\n : titleCase(slug);\n const description =\n typeof entry.description === \"string\" &&\n entry.description.trim().length > 0\n ? entry.description.trim()\n : null;\n const parents: string[] = [];\n if (Array.isArray(entry.parents)) {\n for (const p of entry.parents) {\n if (typeof p === \"string\" && p.trim().length > 0) {\n const ps = toKebabCase(p);\n if (ps.length > 0 && ps !== slug && !parents.includes(ps)) {\n parents.push(ps);\n }\n }\n }\n }\n topics.push({ slug, title, description, parents });\n }\n\n return { topics };\n}\n\n/**\n * Write a `TopicsFile` atomically — tmp file + rename, same pattern as\n * the registry. A half-written topics.yaml would corrupt the user's\n * committed source of truth, so we never write in place.\n *\n * Ordering: topics are sorted by slug for stable diffs. Parents within\n * each entry stay in the order the caller passed them (semantically an\n * ordered list — topics.yaml is the place a user can visibly reason\n * about \"primary parent first\", even though SQLite treats them as a\n * set).\n *\n * We emit a leading comment so first-time readers know the file is\n * edited by the CLI and what its role is.\n */\nexport async function writeTopicsFile(\n path: string,\n file: TopicsFile,\n): Promise<void> {\n const sorted = [...file.topics].sort((a, b) => a.slug.localeCompare(b.slug));\n const doc = {\n topics: sorted.map((t) => {\n // Emit all four keys in a stable order: slug, title, description,\n // parents. description is emitted as `null` in YAML when unset so\n // the schema stays consistent across entries (js-yaml renders the\n // literal word `null`, not the `~` shorthand).\n return {\n slug: t.slug,\n title: t.title,\n description: t.description,\n parents: t.parents,\n };\n }),\n };\n\n const header =\n `# .almanac/topics.yaml — source of truth for topic metadata.\\n` +\n `# Managed by \\`almanac topics\\` commands. User-added comments\\n` +\n `# between entries will be stripped on the next write (js-yaml\\n` +\n `# doesn't round-trip comments). Edit at your own risk — or use the\\n` +\n `# CLI (\\`almanac topics create|link|describe|rename|delete\\`)\\n` +\n `# which preserves the structure correctly.\\n`;\n const body = yaml.dump(doc, {\n lineWidth: 100,\n noRefs: true,\n sortKeys: false,\n });\n const content = `${header}${body}`;\n const tmpPath = `${path}.tmp`;\n // mkdir parent in case `.almanac/` vanished (shouldn't, but cheap insurance)\n const parent = dirname(path);\n if (!existsSync(parent)) {\n await mkdir(parent, { recursive: true });\n }\n await writeFile(tmpPath, content, \"utf8\");\n await rename(tmpPath, path);\n}\n\n/**\n * Look up a topic by slug. Returns `null` when the slug is absent —\n * callers distinguish \"declared in topics.yaml\" from \"ad-hoc (only\n * appears in page frontmatter)\" based on this.\n */\nexport function findTopic(file: TopicsFile, slug: string): TopicEntry | null {\n for (const t of file.topics) {\n if (t.slug === slug) return t;\n }\n return null;\n}\n\n/**\n * Ensure a topic entry exists. If missing, inserts a minimal entry with\n * title-cased title and null description. Returns the (possibly new)\n * entry. Used by `tag`, `topics create` (with `--parent auto-creating`),\n * and `topics link` (auto-creating child/parent on demand).\n */\nexport function ensureTopic(file: TopicsFile, slug: string): TopicEntry {\n const existing = findTopic(file, slug);\n if (existing !== null) return existing;\n const entry: TopicEntry = {\n slug,\n title: titleCase(slug),\n description: null,\n parents: [],\n };\n file.topics.push(entry);\n return entry;\n}\n\n/**\n * Convert a slug back to a human-ish title: `auth-flow` → `Auth Flow`.\n * Used as the fallback title when the caller didn't provide one\n * (auto-creation paths, ad-hoc slugs coming from page frontmatter).\n */\nexport function titleCase(slug: string): string {\n if (slug.length === 0) return slug;\n return slug\n .split(\"-\")\n .filter((s) => s.length > 0)\n .map((s) => `${s[0]?.toUpperCase() ?? \"\"}${s.slice(1)}`)\n .join(\" \");\n}\n\nfunction isNodeError(err: unknown): err is NodeJS.ErrnoException {\n return err instanceof Error && \"code\" in err;\n}\n","import yaml from \"js-yaml\";\n\nexport interface Frontmatter {\n title?: string;\n topics: string[];\n files: string[];\n archived_at: number | null;\n superseded_by: string | null;\n supersedes: string | null;\n /**\n * The body of the file with frontmatter removed, for FTS5 and H1 fallback.\n * Always populated even when the file has no frontmatter.\n */\n body: string;\n}\n\n/**\n * Pull YAML frontmatter off the top of a markdown file and coerce the\n * relevant fields. Unknown fields are tolerated silently — the wiki should\n * accept fields we don't understand yet without spewing warnings at the\n * user (future slices might consume them).\n *\n * Failure modes:\n * - No frontmatter at all → `{ topics: [], files: [], ..., body: raw }`.\n * This is legal; a heading + prose is a valid page.\n * - Malformed YAML → warning to stderr, treated as \"no frontmatter\". We\n * choose not to throw so a single bad file doesn't tank a reindex.\n *\n * Note on `archived_at`: authors write this as a YAML date (`2026-04-15`),\n * which `js-yaml` parses to a JS `Date`. We also tolerate ISO-8601 strings\n * and raw numbers. Everything else gets dropped (treated as \"not\n * archived\"). Storing epoch seconds keeps `--since`/`--stale`/`archived`\n * arithmetic trivial at query time.\n */\nexport function parseFrontmatter(raw: string): Frontmatter {\n const empty: Frontmatter = {\n topics: [],\n files: [],\n archived_at: null,\n superseded_by: null,\n supersedes: null,\n body: raw,\n };\n\n // Frontmatter fence MUST start on line 1 — a `---` partway through the\n // document is just a horizontal rule. Be strict about the opening delim\n // so we don't accidentally strip section headers.\n if (!raw.startsWith(\"---\")) {\n return empty;\n }\n\n // Tolerate either Unix or Windows line endings. We read the first line\n // explicitly to confirm it's only `---` (no trailing content).\n const match = raw.match(/^---\\r?\\n([\\s\\S]*?)\\r?\\n---\\r?\\n?([\\s\\S]*)$/);\n if (match === null) {\n return empty;\n }\n\n const yamlBody = match[1] ?? \"\";\n const body = match[2] ?? \"\";\n\n let parsed: unknown;\n try {\n parsed = yaml.load(yamlBody);\n } catch (err: unknown) {\n const message = err instanceof Error ? err.message : String(err);\n process.stderr.write(`almanac: malformed frontmatter (${message})\\n`);\n return empty;\n }\n\n if (parsed === null || parsed === undefined) {\n return { ...empty, body };\n }\n if (typeof parsed !== \"object\" || Array.isArray(parsed)) {\n // Someone wrote a YAML scalar or list as the document root — not a\n // mapping, so no fields for us to extract. Treat as empty but keep the\n // post-fence body so FTS5 still gets content.\n return { ...empty, body };\n }\n\n const obj = parsed as Record<string, unknown>;\n\n return {\n title: coerceString(obj.title),\n topics: coerceStringArray(obj.topics),\n files: coerceStringArray(obj.files),\n archived_at: coerceEpochSeconds(obj.archived_at),\n superseded_by: coerceString(obj.superseded_by) ?? null,\n supersedes: coerceString(obj.supersedes) ?? null,\n body,\n };\n}\n\n/**\n * H1 fallback for title when frontmatter has none.\n *\n * Only considers the first 40 lines of the body — any real wiki page has\n * its H1 near the top. NOTE: `String.prototype.split(sep, limit)` still\n * splits the whole string internally and then truncates; it's not an\n * early-bail iteration. For the multi-megabyte files we might see in\n * practice this is still cheap (one regex pass, no allocation per-line\n * beyond the 40 we keep), so we favor the clearer code over hand-rolled\n * line iteration.\n */\nexport function firstH1(body: string): string | undefined {\n const lines = body.split(/\\r?\\n/, 40);\n for (const line of lines) {\n const m = line.match(/^#\\s+(.+?)\\s*#*\\s*$/);\n if (m !== null) {\n return m[1];\n }\n }\n return undefined;\n}\n\nfunction coerceString(v: unknown): string | undefined {\n if (typeof v === \"string\" && v.trim().length > 0) return v.trim();\n return undefined;\n}\n\nfunction coerceStringArray(v: unknown): string[] {\n if (!Array.isArray(v)) return [];\n const out: string[] = [];\n for (const item of v) {\n if (typeof item === \"string\" && item.trim().length > 0) {\n out.push(item.trim());\n }\n }\n return out;\n}\n\n/**\n * Coerce a frontmatter `archived_at` value (YAML Date, ISO string, or raw\n * epoch number) into epoch seconds. Returns `null` for anything we can't\n * make sense of — pages with an unrecognizable `archived_at` are treated as\n * active rather than silently marked archived, which is the safer default.\n */\nfunction coerceEpochSeconds(v: unknown): number | null {\n if (v instanceof Date) {\n return Math.floor(v.getTime() / 1000);\n }\n if (typeof v === \"number\" && Number.isFinite(v)) {\n return Math.floor(v);\n }\n if (typeof v === \"string\" && v.trim().length > 0) {\n const t = Date.parse(v.trim());\n if (!Number.isNaN(t)) {\n return Math.floor(t / 1000);\n }\n }\n return null;\n}\n","import { existsSync, statSync } from \"node:fs\";\nimport { join } from \"node:path\";\n\nimport fg from \"fast-glob\";\n\n// Glob is relative to `.almanac/pages/`, so this is every markdown page\n// beneath pages without repeating the `pages/` prefix.\nexport const PAGES_GLOB = \"**/*.md\";\n\n/**\n * Return true if any page file has an mtime strictly greater than the\n * index DB's mtime. This is intentionally cheap: `fast-glob` with\n * `stats: true` gives us mtimes without a second `stat` round-trip, and\n * the synchronous walk keeps the decision path simple for CLI entrypoints.\n */\nexport function pagesNewerThan(pagesDir: string, dbPath: string): boolean {\n let dbMtime: number;\n try {\n dbMtime = statSync(dbPath).mtimeMs;\n } catch {\n return true;\n }\n\n const entries = fg.sync(PAGES_GLOB, {\n cwd: pagesDir,\n absolute: true,\n onlyFiles: true,\n stats: true,\n }) as Array<{ path: string; stats?: { mtimeMs: number } }>;\n\n for (const entry of entries) {\n const mtime = entry.stats?.mtimeMs;\n if (mtime !== undefined && mtime > dbMtime) return true;\n }\n return false;\n}\n\n/**\n * Return true if `topics.yaml` has an mtime strictly greater than the\n * index DB's mtime. Missing `topics.yaml` is legal: it means \"no topic\n * metadata yet\", not \"the index is stale\".\n */\nexport function topicsYamlNewerThan(\n almanacDir: string,\n dbPath: string,\n): boolean {\n const path = join(almanacDir, \"topics.yaml\");\n if (!existsSync(path)) return false;\n let dbMtime: number;\n try {\n dbMtime = statSync(dbPath).mtimeMs;\n } catch {\n return true;\n }\n try {\n const st = statSync(path);\n return st.mtimeMs > dbMtime;\n } catch {\n return false;\n }\n}\n","/**\n * Path normalization for the file/folder references stored in `file_refs`\n * and for the query input passed to `--mentions`.\n *\n * The same function runs over both sides so a value written at index time\n * and a value looked up at query time compare byte-for-byte. If this ever\n * drifts between writers and readers, `--mentions` starts silently missing\n * matches — apply one canonicalization, not two.\n *\n * Rules (from the spec, Correctness):\n * - Lowercase (macOS filesystems are case-insensitive, so the wiki treats\n * `Src/Checkout/` and `src/checkout/` as the same path)\n * - Forward slashes only (never backslashes from Windows-authored content)\n * - No leading `./`\n * - Collapse redundant slashes (`src//checkout/` → `src/checkout/`)\n * - Trailing `/` iff the caller says it's a directory\n *\n * The `isDir` flag is a signal carried alongside the path — we don't infer\n * it from the raw string here, because frontmatter `files:` entries and the\n * inline `[[...]]` classifier both decide directness themselves and pass\n * the answer in. Having one place decide and one place normalize keeps the\n * directory inference rule testable in isolation.\n */\nexport function normalizePath(raw: string, isDir: boolean): string {\n const normalized = normalizeShape(raw, isDir);\n return normalized.toLowerCase();\n}\n\n/**\n * Normalize shape without lowercasing — preserves the author's casing.\n * Used to store `original_path` in `file_refs` so dead-ref checks on\n * case-sensitive filesystems (Linux, `git` checkouts with core.ignorecase\n * false) stat the actual path on disk rather than a lowercased alias.\n *\n * Everything else about the result is identical to `normalizePath`:\n * forward slashes, no `./`, no duplicate slashes, trailing `/` iff\n * `isDir`. The ONLY difference is the final `.toLowerCase()` is skipped.\n */\nexport function normalizePathPreservingCase(raw: string, isDir: boolean): string {\n return normalizeShape(raw, isDir);\n}\n\nfunction normalizeShape(raw: string, isDir: boolean): string {\n let s = raw.trim();\n\n // Windows-style backslashes → forward slashes. We never want to store\n // backslashes; a path authored on Windows and checked in should match\n // the same path authored on macOS.\n s = s.replace(/\\\\+/g, \"/\");\n\n // Drop a leading `./` — it's syntactic noise and authors inconsistently\n // include it. `./src/checkout/` and `src/checkout/` must hash equal.\n while (s.startsWith(\"./\")) s = s.slice(2);\n\n // Collapse any run of slashes to a single slash. This also normalizes\n // `src//checkout/` and accidental doubled slashes from string concat.\n s = s.replace(/\\/+/g, \"/\");\n\n // Strip any trailing slashes before re-applying the directory marker —\n // this way we don't care if the caller fed us `src/checkout` or\n // `src/checkout/` as a directory; we impose our own rule.\n s = s.replace(/\\/+$/, \"\");\n\n if (isDir) {\n // Directories ALWAYS end with a trailing slash. This is what lets the\n // GLOB queries distinguish `src/checkout/` (the directory) from\n // `src/checkout` (a file with no extension) without ambiguity.\n return `${s}/`;\n }\n return s;\n}\n\n/**\n * Infer `isDir` from the raw string the author wrote. Only used at the\n * point of parsing frontmatter `files:` entries and inline `[[...]]`\n * references — everywhere else, `isDir` is already known from context.\n *\n * Rule: trailing `/` (after backslash normalization) means directory.\n */\nexport function looksLikeDir(raw: string): boolean {\n const s = raw.trim().replace(/\\\\+/g, \"/\");\n return s.endsWith(\"/\");\n}\n","import Database from \"better-sqlite3\";\n\n/**\n * Schema DDL, applied on every open. All statements are `CREATE ... IF NOT\n * EXISTS` so this is idempotent — handy when the file already exists but\n * was written by an older version, and tolerable because the schema is\n * append-only (new tables don't collide).\n *\n * Departures from the raw spec, explained:\n * - `page_topics.topic_slug` has no FK to `topics(slug)`. Topics are\n * created lazily when a page declares them; a strict FK would force us\n * to upsert topic rows before the page rows, which doesn't buy us\n * anything in slice 2 and locks us out of slice 3's \"no explicit topic\n * registration needed\" behavior.\n * - `wikilinks.target_slug` / `cross_wiki_links.target_slug` also have\n * no FK — these can be intentionally broken (unwritten target page),\n * and `almanac health` will surface them in slice 3.\n *\n * `file_refs` carries TWO forms of each path:\n * - `path` — normalized + lowercased, used for GLOB/equality\n * queries (`--mentions`). Stable across casing\n * choices on macOS/Windows.\n * - `original_path` — as-written (normalized slashes, no `./`, trailing\n * `/` for dirs), preserving the author's casing.\n * Used for filesystem stats (dead-refs on\n * case-sensitive filesystems like Linux) and for\n * user-facing display (`almanac info`).\n *\n * See also: `SCHEMA_VERSION` below and the migration logic in `openIndex`.\n */\nconst SCHEMA_DDL = `\nCREATE TABLE IF NOT EXISTS pages (\n slug TEXT PRIMARY KEY,\n title TEXT,\n file_path TEXT NOT NULL,\n content_hash TEXT NOT NULL,\n updated_at INTEGER NOT NULL,\n archived_at INTEGER,\n superseded_by TEXT\n);\n\nCREATE TABLE IF NOT EXISTS topics (\n slug TEXT PRIMARY KEY,\n title TEXT,\n description TEXT\n);\n\nCREATE TABLE IF NOT EXISTS page_topics (\n page_slug TEXT NOT NULL REFERENCES pages(slug) ON DELETE CASCADE,\n topic_slug TEXT NOT NULL,\n PRIMARY KEY (page_slug, topic_slug)\n);\n\nCREATE TABLE IF NOT EXISTS topic_parents (\n child_slug TEXT NOT NULL,\n parent_slug TEXT NOT NULL,\n PRIMARY KEY (child_slug, parent_slug),\n CHECK (child_slug != parent_slug)\n);\n\nCREATE TABLE IF NOT EXISTS file_refs (\n page_slug TEXT NOT NULL REFERENCES pages(slug) ON DELETE CASCADE,\n path TEXT NOT NULL,\n original_path TEXT NOT NULL,\n is_dir INTEGER NOT NULL,\n PRIMARY KEY (page_slug, path)\n);\nCREATE INDEX IF NOT EXISTS idx_file_refs_path ON file_refs(path);\n\nCREATE TABLE IF NOT EXISTS wikilinks (\n source_slug TEXT NOT NULL REFERENCES pages(slug) ON DELETE CASCADE,\n target_slug TEXT NOT NULL,\n PRIMARY KEY (source_slug, target_slug)\n);\n\nCREATE TABLE IF NOT EXISTS cross_wiki_links (\n source_slug TEXT NOT NULL REFERENCES pages(slug) ON DELETE CASCADE,\n target_wiki TEXT NOT NULL,\n target_slug TEXT NOT NULL,\n PRIMARY KEY (source_slug, target_wiki, target_slug)\n);\n\n-- NOTE: virtual FTS5 table — ON DELETE CASCADE from pages does NOT apply.\n-- The indexer must explicitly DELETE FROM fts_pages whenever it removes\n-- or replaces a page row, or we leak orphaned FTS rows.\nCREATE VIRTUAL TABLE IF NOT EXISTS fts_pages USING fts5(slug, title, content);\n`;\n\n/**\n * Bump this whenever the schema changes in a backwards-incompatible way.\n * On open we compare the stored `user_version` against this constant; if\n * it's lower, we drop the affected tables so the next `runIndexer` can\n * rebuild them. Full reindex is cheap (everything lives on disk as\n * markdown), so \"drop + recreate\" is simpler than ALTER TABLE migrations.\n *\n * Version history:\n * 1 — initial slice-2 schema\n * 2 — slice-3-review: added `file_refs.original_path`\n */\nconst SCHEMA_VERSION = 2;\n\n/**\n * Open `index.db` and apply the schema. Foreign keys are off by default in\n * SQLite; we turn them on per-connection so the ON DELETE CASCADE on\n * `pages` actually fires when we delete stale rows during incremental\n * reindex.\n *\n * We don't wrap this open in a transaction — `CREATE ... IF NOT EXISTS` is\n * safe to run repeatedly and the FTS5 virtual-table creation is already\n * atomic.\n *\n * Migration: if the DB was created by an older schema (`user_version` <\n * `SCHEMA_VERSION`), we drop the tables whose shape changed and let the\n * CREATE IF NOT EXISTS below rebuild them. The next `runIndexer` repopulates\n * from the filesystem — cheap and avoids the ALTER TABLE dance.\n */\nexport function openIndex(dbPath: string): Database.Database {\n const db = new Database(dbPath);\n // WAL journal mode is persistent — once set, it's recorded in the DB\n // header and survives close/open cycles. Check first and only switch if\n // we're not already there; this avoids a redundant pragma write on every\n // query command.\n const mode = db.pragma(\"journal_mode\", { simple: true });\n if (typeof mode !== \"string\" || mode.toLowerCase() !== \"wal\") {\n db.pragma(\"journal_mode = WAL\");\n }\n db.pragma(\"foreign_keys = ON\");\n\n const rawVersion = db.pragma(\"user_version\", { simple: true });\n const currentVersion = typeof rawVersion === \"number\" ? rawVersion : 0;\n if (currentVersion < SCHEMA_VERSION) {\n // Drop tables whose shape changed. `file_refs` got `original_path`\n // as of v2; easiest to drop it entirely so CREATE IF NOT EXISTS\n // runs with the new definition. Pages/topics/links are untouched.\n db.exec(\"DROP TABLE IF EXISTS file_refs\");\n // The indexer's fast-path skips pages whose content_hash matches,\n // which means a migration-dropped `file_refs` wouldn't get\n // repopulated until a page changed. Clear the hash column so the\n // next reindex treats every page as changed and rebuilds its\n // file_refs/wikilinks/cross-wiki rows. Table may not exist yet on\n // a brand-new DB, so swallow errors.\n try {\n db.exec(\"UPDATE pages SET content_hash = ''\");\n } catch {\n // pages table didn't exist yet; the upcoming CREATE IF NOT EXISTS\n // takes care of a fresh install.\n }\n db.pragma(`user_version = ${SCHEMA_VERSION}`);\n }\n\n db.exec(SCHEMA_DDL);\n return db;\n}\n","import { existsSync } from \"node:fs\";\n\nimport type Database from \"better-sqlite3\";\n\nimport { loadTopicsFile } from \"../topics/yaml.js\";\n\nexport const TOPICS_YAML_FILENAME = \"topics.yaml\";\n\n/**\n * Apply the contents of `.almanac/topics.yaml` to SQLite.\n *\n * Called at the tail of every reindex. For each entry in the file we\n * upsert a row into `topics` and rewrite that topic's parent edges.\n *\n * Important invariants:\n * - Missing `topics.yaml` is a no-op. Absence is legal and means \"no\n * topic metadata yet\".\n * - Topics mentioned only in page frontmatter are legal ad-hoc topics.\n * Do not delete them just because they are absent from `topics.yaml`.\n * - Stale topic rows are pruned only after upserting declared topics and\n * collecting current `page_topics`, so `health` does not flag topics\n * that were removed from every page after a rename/delete.\n */\nexport async function applyTopicsYaml(\n db: Database.Database,\n topicsYamlPath: string,\n): Promise<void> {\n if (!existsSync(topicsYamlPath)) return;\n let file;\n try {\n file = await loadTopicsFile(topicsYamlPath);\n } catch (err: unknown) {\n const message = err instanceof Error ? err.message : String(err);\n process.stderr.write(`almanac: ${message}\\n`);\n return;\n }\n\n const upsertTopic = db.prepare<[string, string, string | null]>(\n `INSERT INTO topics (slug, title, description) VALUES (?, ?, ?)\n ON CONFLICT(slug) DO UPDATE SET\n title = excluded.title,\n description = excluded.description`,\n );\n const clearParents = db.prepare<[string]>(\n \"DELETE FROM topic_parents WHERE child_slug = ?\",\n );\n const insertParent = db.prepare<[string, string]>(\n \"INSERT OR IGNORE INTO topic_parents (child_slug, parent_slug) VALUES (?, ?)\",\n );\n\n // Declared means either explicitly present in topics.yaml or currently\n // referenced by page frontmatter. Anything outside this set is stale.\n const declared = new Set<string>();\n for (const t of file.topics) declared.add(t.slug);\n const adHoc = db\n .prepare<[], { topic_slug: string }>(\n \"SELECT DISTINCT topic_slug FROM page_topics\",\n )\n .all();\n for (const r of adHoc) declared.add(r.topic_slug);\n\n const apply = db.transaction(() => {\n for (const t of file.topics) {\n upsertTopic.run(t.slug, t.title, t.description);\n clearParents.run(t.slug);\n for (const parent of t.parents) {\n if (parent === t.slug) continue;\n insertParent.run(t.slug, parent);\n }\n }\n\n // Prune stale topic rows + any edges attached to them last, after\n // the upserts above have promoted declared slugs.\n const existing = db\n .prepare<[], { slug: string }>(\"SELECT slug FROM topics\")\n .all();\n const deleteTopic = db.prepare<[string]>(\"DELETE FROM topics WHERE slug = ?\");\n const deleteEdgesByChild = db.prepare<[string]>(\n \"DELETE FROM topic_parents WHERE child_slug = ?\",\n );\n const deleteEdgesByParent = db.prepare<[string]>(\n \"DELETE FROM topic_parents WHERE parent_slug = ?\",\n );\n for (const r of existing) {\n if (declared.has(r.slug)) continue;\n deleteEdgesByChild.run(r.slug);\n deleteEdgesByParent.run(r.slug);\n deleteTopic.run(r.slug);\n }\n });\n apply();\n}\n","import { toKebabCase } from \"../slug.js\";\nimport {\n looksLikeDir,\n normalizePath,\n normalizePathPreservingCase,\n} from \"./paths.js\";\n\n/**\n * One parsed `[[...]]` reference from a page body. Classification is\n * deterministic and content-based — see `classifyWikilink` below.\n *\n * Callers dispatch on `kind`:\n * - `page` → row in `wikilinks`\n * - `file` → row in `file_refs` with `is_dir = 0`\n * - `folder` → row in `file_refs` with `is_dir = 1`\n * - `xwiki` → row in `cross_wiki_links`\n *\n * File/folder refs carry TWO forms of the path:\n * - `path` — lowercased (for `--mentions` lookups)\n * - `originalPath` — as-written (for filesystem stats on case-sensitive\n * systems and for user-facing display)\n */\nexport type WikilinkRef =\n | { kind: \"page\"; target: string }\n | { kind: \"file\"; path: string; originalPath: string }\n | { kind: \"folder\"; path: string; originalPath: string }\n | { kind: \"xwiki\"; wiki: string; target: string };\n\n/**\n * Rules from the spec (\"Classification rules\"), applied in order:\n *\n * 1. Contains `:` before any `/` → cross-wiki reference (`wiki:slug`)\n * 2. Contains `/` → file or folder reference\n * - Trailing `/` = folder\n * - Otherwise = file\n * 3. Otherwise → page slug wikilink\n *\n * Edge cases the test suite pins down:\n * - `[[a:b/c]]` → xwiki (colon is before the slash, rule 1 wins)\n * - `[[src/a:b]]` → file (slash is before the colon, rule 2 wins)\n * - `[[./x]]` → the leading `./` is stripped by `normalizePath`,\n * so this lands in `file_refs` as `x`. A bare `./x`\n * with no inner slash would classify as a file.\n * - `[[foo|display]]`→ Obsidian-style display text is stripped; we key\n * on the target only. A future slice could surface\n * display text in `almanac info`.\n */\nexport function classifyWikilink(raw: string): WikilinkRef | null {\n // Strip Obsidian-style `|display` suffix — we don't index display text\n // in slice 2, but we want the classifier to see the real target.\n const pipe = raw.indexOf(\"|\");\n let body = pipe === -1 ? raw : raw.slice(0, pipe);\n body = body.trim();\n if (body.length === 0) return null;\n\n const firstColon = body.indexOf(\":\");\n const firstSlash = body.indexOf(\"/\");\n\n // Rule 1: cross-wiki, `wiki:slug`. Only if the colon comes before any\n // slash — otherwise `src/urls.ts:42` (hypothetical) would wrongly\n // classify as xwiki.\n if (firstColon !== -1 && (firstSlash === -1 || firstColon < firstSlash)) {\n const wiki = body.slice(0, firstColon).trim();\n const target = body.slice(firstColon + 1).trim();\n if (wiki.length === 0 || target.length === 0) return null;\n return { kind: \"xwiki\", wiki, target };\n }\n\n // Rule 2: file or folder. The `/` may be anywhere including trailing.\n if (firstSlash !== -1) {\n const isDir = looksLikeDir(body);\n const path = normalizePath(body, isDir);\n const originalPath = normalizePathPreservingCase(body, isDir);\n if (path.length === 0) return null;\n return isDir\n ? { kind: \"folder\", path, originalPath }\n : { kind: \"file\", path, originalPath };\n }\n\n // Rule 3: page slug wikilink. Authors might write `Checkout Flow` or\n // `Checkout_Flow` by accident — slugify defensively so backlinks still\n // resolve in those cases.\n const target = toKebabCase(body);\n if (target.length === 0) return null;\n return { kind: \"page\", target };\n}\n\n/**\n * Walk a markdown body and pull every `[[...]]` reference. We scan the\n * whole body rather than try to skip code blocks — the spec is explicit:\n * \"Prose outside `[[...]]` is just prose. No backtick-path heuristics, no\n * false positives from code blocks or log output.\" A `[[foo]]` inside a\n * fenced code block is still a wikilink. Authors who genuinely need a\n * literal `[[x]]` in code can escape one of the brackets.\n */\nexport function extractWikilinks(body: string): WikilinkRef[] {\n const out: WikilinkRef[] = [];\n const re = /\\[\\[([^\\]\\n]+)\\]\\]/g;\n let m: RegExpExecArray | null;\n while ((m = re.exec(body)) !== null) {\n const ref = classifyWikilink(m[1] ?? \"\");\n if (ref !== null) out.push(ref);\n }\n return out;\n}\n","import { existsSync } from \"node:fs\";\nimport { join } from \"node:path\";\n\nimport { findNearestAlmanacDir } from \"../paths.js\";\nimport { findEntry } from \"../registry/index.js\";\n\n/**\n * Figure out which repo root a query command should run against.\n *\n * Two modes, in order of precedence:\n * 1. `--wiki <name>` — look it up in the global registry. Fails\n * explicitly if the name isn't registered or its path has gone\n * missing (unmounted drive, deleted repo). No silent fallback, which\n * would hide the real problem from the user.\n * 2. default — walk up from `cwd` like git does. Fails if we're not\n * inside a `.almanac/` repo.\n *\n * Returns the absolute path to the repo root (the directory containing\n * `.almanac/`).\n *\n * NOTE (spec contract, not yet implemented): when `--all` lands in a\n * future slice, it must silently skip wikis whose paths have gone\n * unreachable — the asymmetry with `--wiki <name>` is deliberate.\n * Explicit lookup is loud about failures (user named a specific wiki);\n * bulk `--all` is quiet (user asked \"whatever's available\"). Don't\n * unify the error behavior when adding `--all`.\n */\nexport async function resolveWikiRoot(params: {\n cwd: string;\n wiki?: string;\n}): Promise<string> {\n if (params.wiki !== undefined) {\n const entry = await findEntry({ name: params.wiki });\n if (entry === null) {\n throw new Error(`no registered wiki named \"${params.wiki}\"`);\n }\n if (!existsSync(join(entry.path, \".almanac\"))) {\n throw new Error(\n `wiki \"${params.wiki}\" path is unreachable (${entry.path})`,\n );\n }\n return entry.path;\n }\n\n const nearest = findNearestAlmanacDir(params.cwd);\n if (nearest === null) {\n throw new Error(\n \"no .almanac/ found in this directory or any parent; run `almanac bootstrap` first\",\n );\n }\n return nearest;\n}\n","import { existsSync } from \"node:fs\";\nimport { homedir } from \"node:os\";\nimport { dirname, isAbsolute, join, resolve } from \"node:path\";\n\n/**\n * Absolute path to the user-level `~/.almanac/` directory.\n *\n * All global state (the registry, future global config) lives here, not in\n * the repo. We resolve this via `os.homedir()` rather than `$HOME` so the\n * CLI behaves the same on macOS, Linux, and Windows.\n */\nexport function getGlobalAlmanacDir(): string {\n return join(homedir(), \".almanac\");\n}\n\n/**\n * Absolute path to the global registry file.\n *\n * The registry is the single source of truth for \"which wikis exist on this\n * machine.\" It is intentionally stored outside any repo so it survives\n * branch switches, clones, and repo deletions.\n */\nexport function getRegistryPath(): string {\n return join(getGlobalAlmanacDir(), \"registry.json\");\n}\n\n/**\n * Repo-level `.almanac/` path for a given working directory (not resolved —\n * just `join(cwd, \".almanac\")`). Use `findNearestAlmanacDir` when you need\n * to walk upward like git does.\n */\nexport function getRepoAlmanacDir(cwd: string): string {\n return join(cwd, \".almanac\");\n}\n\n/**\n * Walk upward from `startDir` looking for a directory that contains\n * `.almanac/`. Returns the absolute path to the repo root (the directory\n * containing `.almanac/`), or `null` if none is found before hitting the\n * filesystem root.\n *\n * Mirrors how `git` locates the enclosing repository. This lets `almanac`\n * work from any subdirectory inside a repo, not just the root.\n *\n * We explicitly skip the global `~/.almanac/` directory. It shares the\n * `.almanac` name with the per-repo wiki dir, but it's not a wiki — it\n * only holds the registry and global state. If the user runs `almanac\n * init` anywhere inside their home directory (outside a real wiki), we\n * must NOT treat `~` as an enclosing wiki root. Otherwise init would try\n * to register the home dir itself as a wiki.\n */\nexport function findNearestAlmanacDir(startDir: string): string | null {\n const globalDir = getGlobalAlmanacDir();\n let current = isAbsolute(startDir) ? startDir : resolve(startDir);\n\n // Walk until we hit the filesystem root. `dirname(\"/\")` returns `\"/\"`,\n // so the loop terminates when we stop ascending.\n while (true) {\n const candidate = join(current, \".almanac\");\n if (candidate !== globalDir && existsSync(candidate)) {\n return current;\n }\n const parent = dirname(current);\n if (parent === current) {\n return null;\n }\n current = parent;\n }\n}\n","import { mkdir, readFile, rename, writeFile } from \"node:fs/promises\";\nimport { dirname } from \"node:path\";\n\nimport { getGlobalAlmanacDir, getRegistryPath } from \"../paths.js\";\nimport { toKebabCase } from \"../slug.js\";\n\n// Re-export so existing import sites (`from \"../registry/index.js\"`) keep\n// working without a mechanical fan-out. The canonical home is `../slug.js`.\nexport { toKebabCase };\n\n/**\n * One entry in `~/.almanac/registry.json`.\n *\n * `name` is the canonical kebab-case slug the user types. `path` is the\n * absolute repo root (the directory that contains `.almanac/`). We store\n * absolute paths so cross-wiki resolution works regardless of the caller's\n * cwd.\n */\nexport interface RegistryEntry {\n name: string;\n description: string;\n path: string;\n registered_at: string;\n}\n\n/**\n * Read the registry file into memory.\n *\n * A missing file is not an error — it's the first-run state, which we\n * treat as an empty registry. A malformed file IS an error; we surface it\n * rather than silently clobbering the user's data.\n */\nexport async function readRegistry(): Promise<RegistryEntry[]> {\n const path = getRegistryPath();\n let raw: string;\n try {\n raw = await readFile(path, \"utf8\");\n } catch (err: unknown) {\n if (isNodeError(err) && err.code === \"ENOENT\") {\n return [];\n }\n throw err;\n }\n\n const trimmed = raw.trim();\n if (trimmed.length === 0) {\n return [];\n }\n\n let parsed: unknown;\n try {\n parsed = JSON.parse(trimmed);\n } catch (err: unknown) {\n const message = err instanceof Error ? err.message : String(err);\n throw new Error(`registry at ${path} is not valid JSON: ${message}`);\n }\n\n if (!Array.isArray(parsed)) {\n throw new Error(`registry at ${path} must be a JSON array`);\n }\n\n // Validate every entry. We do NOT silently coerce missing `name` or\n // `path` — an entry with `name: \"\"` would be unremovable via `--drop`\n // and an empty `path` would match any `findEntry({ path: \"\" })` call.\n // If someone hand-edited the registry into a bad state, surfacing the\n // error is strictly better than limping along with corrupt data.\n return parsed.map((item, idx) => {\n if (typeof item !== \"object\" || item === null) {\n throw new Error(`registry entry ${idx} is not an object`);\n }\n const e = item as Record<string, unknown>;\n const name = typeof e.name === \"string\" ? e.name : \"\";\n const path = typeof e.path === \"string\" ? e.path : \"\";\n if (name.length === 0) {\n throw new Error(`registry entry ${idx} is missing a non-empty \"name\"`);\n }\n if (path.length === 0) {\n throw new Error(`registry entry ${idx} is missing a non-empty \"path\"`);\n }\n return {\n name,\n description: typeof e.description === \"string\" ? e.description : \"\",\n path,\n registered_at:\n typeof e.registered_at === \"string\" ? e.registered_at : \"\",\n };\n });\n}\n\n/**\n * Persist the registry to disk. Creates `~/.almanac/` if it doesn't exist.\n *\n * We write with a trailing newline and 2-space indentation so the file is\n * diff-friendly if someone ever commits or inspects it manually.\n *\n * The write is atomic: we write to `registry.json.tmp` and then rename,\n * which is an atomic operation on every mainstream filesystem. This\n * matters because two concurrent `almanac init` (or autoregister) calls\n * from different shells would otherwise race on a partial write and\n * corrupt the file — a single `rename` means one wins cleanly and the\n * other's contents are simply dropped.\n */\nexport async function writeRegistry(entries: RegistryEntry[]): Promise<void> {\n const path = getRegistryPath();\n await mkdir(dirname(path), { recursive: true });\n const body = `${JSON.stringify(entries, null, 2)}\\n`;\n const tmpPath = `${path}.tmp`;\n await writeFile(tmpPath, body, \"utf8\");\n await rename(tmpPath, path);\n}\n\n/**\n * macOS (HFS+/APFS default) and Windows (NTFS default) are case-insensitive\n * but case-preserving. `/Users/x/Project` and `/Users/x/project` are the\n * same directory. We must treat them as the same registry entry, or a\n * single `almanac init` from a differently-cased cwd would duplicate the\n * row. Linux is case-sensitive — do not normalize there.\n *\n * Callers still store the original casing; only comparisons are lowercased.\n */\nfunction pathsEqual(a: string, b: string): boolean {\n if (process.platform === \"darwin\" || process.platform === \"win32\") {\n return a.toLowerCase() === b.toLowerCase();\n }\n return a === b;\n}\n\n/**\n * Add (or replace) an entry in the registry.\n *\n * Uniqueness is enforced on BOTH `name` and `path`: a repo can only appear\n * once, and a name can only refer to one repo. If either matches, we\n * replace the existing entry rather than creating a duplicate. This is\n * what makes auto-registration idempotent.\n */\nexport async function addEntry(entry: RegistryEntry): Promise<RegistryEntry[]> {\n const existing = await readRegistry();\n const filtered = existing.filter(\n (e) => e.name !== entry.name && !pathsEqual(e.path, entry.path),\n );\n filtered.push(entry);\n await writeRegistry(filtered);\n return filtered;\n}\n\n/**\n * Remove an entry by name. Returns the removed entry (or `null` if none\n * matched). Only `almanac list --drop <name>` calls this — we never drop\n * automatically, even for unreachable paths.\n */\nexport async function dropEntry(name: string): Promise<RegistryEntry | null> {\n const existing = await readRegistry();\n const idx = existing.findIndex((e) => e.name === name);\n if (idx === -1) {\n return null;\n }\n const [removed] = existing.splice(idx, 1);\n await writeRegistry(existing);\n return removed ?? null;\n}\n\n/**\n * Find an entry by either name or absolute path. Used by auto-registration\n * to decide whether the current repo is already known.\n *\n * Path comparison is case-insensitive on macOS/Windows (see `pathsEqual`).\n */\nexport async function findEntry(params: {\n name?: string;\n path?: string;\n}): Promise<RegistryEntry | null> {\n const entries = await readRegistry();\n for (const entry of entries) {\n if (params.name !== undefined && entry.name === params.name) return entry;\n if (params.path !== undefined && pathsEqual(entry.path, params.path)) {\n return entry;\n }\n }\n return null;\n}\n\n/**\n * Ensure the global `.almanac/` directory exists. Safe to call repeatedly;\n * `mkdir recursive` is a no-op when the directory already exists.\n */\nexport async function ensureGlobalDir(): Promise<void> {\n await mkdir(getGlobalAlmanacDir(), { recursive: true });\n}\n\nfunction isNodeError(err: unknown): err is NodeJS.ErrnoException {\n return err instanceof Error && \"code\" in err;\n}\n","import { readFile, rename, writeFile } from \"node:fs/promises\";\n\nimport yaml from \"js-yaml\";\n\n/**\n * Rewrite the `topics:` field in a markdown file's YAML frontmatter.\n *\n * The absolute requirement is **body byte-preservation**. Tag/untag/\n * rename commands touch only the frontmatter; everything after the\n * closing `---` must be byte-identical to the input (same line endings,\n * same trailing whitespace, same final-newline-or-not). We lean on a\n * precise split rather than re-serializing the whole file so any\n * incidental bytes in the body (literal `\\r\\n`, unusual trailing\n * whitespace, triple-hyphen rulers) are left alone.\n *\n * For the frontmatter itself we do a **surgical rewrite of the `topics:`\n * field only** — not a full YAML roundtrip. Reasons:\n * - `js-yaml` normalizes quoting, re-orders keys alphabetically by\n * default, and can drop comments. Any of those would surprise a user\n * who hand-edited their own frontmatter and expects their formatting\n * preserved byte-for-byte.\n * - We only care about one field. Replacing just that field lets the\n * rest of the YAML survive verbatim.\n *\n * The strategy:\n * 1. Find the exact span of the `topics:` key (whether flow\n * `topics: [a, b]` or block `topics:\\n - a\\n - b\\n`) using a\n * small line scanner.\n * 2. Compute the new topics list from the caller's transform.\n * 3. Replace the span with a freshly-emitted `topics: [a, b, c]` line\n * (flow style — compact, readable, and what most authors write).\n * 4. If no `topics:` key exists and the new list is non-empty, append\n * a line to the end of the frontmatter block.\n * 5. If `topics:` exists but the new list is empty, drop the line\n * entirely rather than leaving `topics: []` around.\n *\n * The transform function gets the current deduplicated topic list and\n * returns the new list. Returning an empty array means \"remove the\n * `topics:` key entirely\".\n */\n\nexport interface RewriteResult {\n /** The page's topics before the rewrite (possibly empty). */\n before: string[];\n /** The page's topics after the rewrite (possibly empty). */\n after: string[];\n /** True iff the file content actually changed. */\n changed: boolean;\n}\n\n/**\n * Read `filePath`, compute the new topics via `transform`, and\n * atomically rewrite if the result differs.\n *\n * Atomic per file: write to `<path>.tmp` then rename, same pattern as\n * the registry and topics.yaml writers. A half-written page would\n * corrupt committed user content, so this is non-negotiable.\n */\nexport async function rewritePageTopics(\n filePath: string,\n transform: (current: string[]) => string[],\n): Promise<RewriteResult> {\n const raw = await readFile(filePath, \"utf8\");\n const { before, after, output, changed } = applyTopicsTransform(\n raw,\n transform,\n );\n if (changed) {\n const tmp = `${filePath}.tmp`;\n await writeFile(tmp, output, \"utf8\");\n await rename(tmp, filePath);\n }\n return { before, after, changed };\n}\n\ninterface TransformApplied {\n before: string[];\n after: string[];\n output: string;\n changed: boolean;\n}\n\n/**\n * Pure-string version of `rewritePageTopics`. Useful for tests and for\n * the few places (rename, delete) where we loop many files and want to\n * short-circuit no-op writes cheaply.\n */\nexport function applyTopicsTransform(\n raw: string,\n transform: (current: string[]) => string[],\n): TransformApplied {\n const parsed = splitFrontmatter(raw);\n if (parsed === null) {\n // No frontmatter at all. Tagging a topic on such a page means\n // creating a frontmatter block. We keep the body untouched and\n // prepend `---\\ntopics: [...]\\n---\\n\\n`. If the transform yields\n // an empty list, this is a no-op. Line endings: default to LF for a\n // brand-new frontmatter — we can't infer intent from a file that\n // doesn't have frontmatter yet, and LF is the committed default in\n // most modern repos.\n const next = dedupeSlugs(transform([]));\n if (next.length === 0) {\n return { before: [], after: [], output: raw, changed: false };\n }\n const fm = `---\\ntopics: ${flowList(next)}\\n---\\n\\n`;\n return {\n before: [],\n after: next,\n output: `${fm}${raw}`,\n changed: true,\n };\n }\n\n const { opener, fmLines, closer, body, eol } = parsed;\n const { before, existingRange } = readTopicsFromLines(fmLines);\n const beforeDeduped = dedupeSlugs(before);\n const after = dedupeSlugs(transform(beforeDeduped));\n\n if (arraysEqual(beforeDeduped, after)) {\n return { before: beforeDeduped, after, output: raw, changed: false };\n }\n\n let nextFmLines: string[];\n if (existingRange === null) {\n // No `topics:` key currently present. Add one (only if non-empty).\n if (after.length === 0) {\n return { before: beforeDeduped, after, output: raw, changed: false };\n }\n nextFmLines = [...fmLines, `topics: ${flowList(after)}`];\n } else {\n const replacement =\n after.length === 0 ? null : `topics: ${flowList(after)}`;\n // Interleaved comments/blank lines from a block-style list are\n // re-emitted BELOW the new flow-style `topics:` line so the\n // author's commentary sticks around. Flow/scalar inputs produce an\n // empty `preserved` array, so this collapses to the old behavior\n // for the common case. When we fully delete the key (empty after)\n // the preserved lines go too — without a `topics:` key to anchor\n // them to, trailing \"# below the topics list\" comments become\n // orphans that no longer mean what they said.\n const preservedTail =\n replacement === null ? [] : existingRange.preserved;\n nextFmLines = [\n ...fmLines.slice(0, existingRange.start),\n ...(replacement === null ? [] : [replacement]),\n ...preservedTail,\n ...fmLines.slice(existingRange.end),\n ];\n }\n\n // Rejoin with the same line ending the input frontmatter used so a\n // CRLF-authored file comes out CRLF end-to-end. `splitFrontmatter`\n // sniffed the dominant separator for us.\n const fmBlock =\n nextFmLines.length === 0 ? \"\" : `${nextFmLines.join(eol)}${eol}`;\n const output = `${opener}${fmBlock}${closer}${body}`;\n return {\n before: beforeDeduped,\n after,\n output,\n changed: true,\n };\n}\n\ninterface SplitFrontmatter {\n /** The opening `---\\n` or `---\\r\\n`. */\n opener: string;\n /** Frontmatter lines (no line-ending character included). */\n fmLines: string[];\n /** The closing `---\\n` (or `---\\r\\n`, possibly without trailing newline if EOF). */\n closer: string;\n /** Everything after the closing fence, byte-for-byte. */\n body: string;\n /**\n * Dominant line ending inside the frontmatter block. CRLF-authored\n * files stay CRLF on write; LF stays LF. We sniff once at split time\n * so rewriting doesn't have to re-inspect every line.\n */\n eol: \"\\n\" | \"\\r\\n\";\n}\n\n/**\n * Split a file into (opener, frontmatter lines, closer, body). The\n * regex mirrors `parseFrontmatter` in `indexer/frontmatter.ts` so the\n * indexer and the rewriter agree on what \"has frontmatter\" means.\n *\n * Returns `null` when the file doesn't start with a `---` fence or\n * lacks a matching closer — both cases are legal (a page with only\n * body content) and the caller treats them as \"no frontmatter to\n * rewrite\".\n */\nfunction splitFrontmatter(raw: string): SplitFrontmatter | null {\n if (!raw.startsWith(\"---\")) return null;\n // Match the exact opener (with its line ending) so we can preserve\n // it byte-for-byte.\n const openerMatch = raw.match(/^---(\\r?\\n)/);\n if (openerMatch === null) return null;\n const opener = `---${openerMatch[1] ?? \"\\n\"}`;\n const rest = raw.slice(opener.length);\n // Find the closing `---` that begins at the start of a line. We\n // also handle the edge case where the closer sits at position 0 of\n // `rest` (frontmatter was empty).\n let fenceIdx: number;\n if (rest.startsWith(\"---\")) {\n fenceIdx = 0;\n } else {\n const m = rest.match(/\\r?\\n---(\\r?\\n|$)/);\n if (m === null || m.index === undefined) return null;\n // `m.index` points at the `\\r?\\n` before `---`; skip that newline\n // so fenceIdx lands exactly on the `-`.\n const leadingNewlineLen = (m[0] ?? \"\").startsWith(\"\\r\\n\") ? 2 : 1;\n fenceIdx = m.index + leadingNewlineLen;\n }\n const fmBlock = rest.slice(0, fenceIdx);\n // Determine the closer's full span, including its trailing newline if any.\n const afterDashes = rest.slice(fenceIdx + 3);\n let closerTail = \"\";\n if (afterDashes.startsWith(\"\\r\\n\")) {\n closerTail = \"\\r\\n\";\n } else if (afterDashes.startsWith(\"\\n\")) {\n closerTail = \"\\n\";\n }\n const closer = `---${closerTail}`;\n const body = afterDashes.slice(closerTail.length);\n const fmLines =\n fmBlock.length === 0 ? [] : fmBlock.replace(/\\r?\\n$/, \"\").split(/\\r?\\n/);\n // Sniff the frontmatter's dominant line ending. We look at the\n // opener first (most reliable signal — it's always present and\n // always has an ending). Fall back to checking the fmBlock for any\n // `\\r\\n` runs so a frontmatter with a single-line opener and\n // multi-line body still gets classified right.\n const eol: \"\\n\" | \"\\r\\n\" =\n opener.endsWith(\"\\r\\n\") || /\\r\\n/.test(fmBlock) ? \"\\r\\n\" : \"\\n\";\n return { opener, fmLines, closer, body, eol };\n}\n\ninterface ExistingRange {\n /** Index in `fmLines` of the `topics:` key line (inclusive). */\n start: number;\n /** Index in `fmLines` one past the last line belonging to this key. */\n end: number;\n /**\n * Lines inside `[start+1, end)` that aren't `- entry` lines — i.e.\n * interleaved comments and blank lines a user wrote between entries.\n * We preserve these verbatim when rewriting block-style lists to\n * flow; otherwise a `tag` on a commented list would silently drop\n * the commentary. Empty for flow/scalar shapes.\n */\n preserved: string[];\n}\n\n/**\n * Find `topics:` in a frontmatter-lines array and read the values.\n *\n * Handles three YAML shapes authors commonly write:\n * - `topics: [a, b, c]` (flow sequence, one line)\n * - `topics:` followed by block entries like ` - a` (block sequence)\n * - `topics: a` (a single scalar — treated as one element)\n *\n * Also handles the empty case `topics:` with nothing after it, and the\n * \"no topics key\" case (returns `existingRange: null`).\n *\n * This is NOT a general YAML parser — it's intentionally scoped to the\n * one key we mutate, because using `js-yaml` for a round-trip would\n * lose comments and re-quote strings the user picked a specific way.\n */\nfunction readTopicsFromLines(fmLines: string[]): {\n before: string[];\n existingRange: ExistingRange | null;\n} {\n const keyLineIdx = findTopKey(fmLines, \"topics\");\n if (keyLineIdx === -1) {\n return { before: [], existingRange: null };\n }\n const keyLine = fmLines[keyLineIdx] ?? \"\";\n const colonIdx = keyLine.indexOf(\":\");\n // Everything to the right of the first colon, trimmed.\n const after = keyLine.slice(colonIdx + 1).trim();\n // Strip trailing `# ...` line-comment from a flow value so we don't\n // parse comments as list contents. (A block list's sub-items have\n // their own comments stripped in the block branch below.)\n const afterNoComment = stripTrailingComment(after);\n\n if (afterNoComment.length === 0) {\n // Block sequence style: collect subsequent `- item` lines. Between\n // entries a user may have written:\n // - interleaved `# comment` lines\n // - blank lines\n // We must NOT break the scan on those — doing so would drop every\n // entry after the first comment/blank when we rewrite (silent data\n // loss: the original bug that triggered this fix). We skip them in\n // the scan and stash them in `preserved` so the replacement step\n // can re-emit them verbatim BETWEEN the new flow-style line and\n // the rest of the frontmatter.\n //\n // Edge: comments/blanks that appear BEFORE the first `- entry` or\n // AFTER the last `- entry` count as part of the block too — pulling\n // them out keeps the author's commentary near the list it belongs\n // to. We cap the scan when we hit a real non-entry line (e.g. the\n // next top-level key), leaving everything from that line onward\n // outside the range.\n const values: string[] = [];\n const preserved: string[] = [];\n // Provisional scan cursor. `endIdx` only advances when we've seen\n // something we're sure belongs to this block (an entry line), so\n // trailing whitespace/comments that don't precede another entry\n // stay OUTSIDE the range and aren't shuffled on rewrite.\n let i = keyLineIdx + 1;\n let endIdx = i;\n // `pendingNonEntries` holds comments/blanks we've seen since the\n // last confirmed entry. They're committed to `preserved` only\n // when a subsequent `- entry` proves they live mid-list.\n let pendingNonEntries: string[] = [];\n while (i < fmLines.length) {\n const line = fmLines[i] ?? \"\";\n const trimmed = line.trim();\n if (trimmed.length === 0 || trimmed.startsWith(\"#\")) {\n pendingNonEntries.push(line);\n i += 1;\n continue;\n }\n const m = line.match(/^\\s*-\\s+(.*)$/);\n if (m === null) break;\n // Promote any pending comments/blanks — they're between entries\n // (or before the first entry within the block).\n if (pendingNonEntries.length > 0) {\n preserved.push(...pendingNonEntries);\n pendingNonEntries = [];\n }\n const raw = stripTrailingComment((m[1] ?? \"\").trim());\n const parsed = parseScalar(raw);\n if (parsed.length > 0) values.push(parsed);\n i += 1;\n endIdx = i;\n }\n return {\n before: values,\n existingRange: { start: keyLineIdx, end: endIdx, preserved },\n };\n }\n\n // Flow / scalar shape on one line. Let js-yaml handle the value-parsing\n // (quoting, escapes, etc.) for the RHS only.\n let parsed: unknown;\n try {\n parsed = yaml.load(afterNoComment);\n } catch {\n parsed = null;\n }\n const values: string[] = [];\n if (Array.isArray(parsed)) {\n for (const v of parsed) {\n if (typeof v === \"string\" && v.trim().length > 0) {\n values.push(v.trim());\n }\n }\n } else if (typeof parsed === \"string\" && parsed.trim().length > 0) {\n values.push(parsed.trim());\n }\n return {\n before: values,\n existingRange: { start: keyLineIdx, end: keyLineIdx + 1, preserved: [] },\n };\n}\n\n/**\n * Find a top-level key line. \"Top-level\" means no leading whitespace —\n * we don't walk into nested mappings. The indexer's frontmatter parser\n * only reads top-level keys too, so this matches.\n */\nfunction findTopKey(fmLines: string[], key: string): number {\n const re = new RegExp(`^${escapeRegex(key)}\\\\s*:`);\n for (let i = 0; i < fmLines.length; i += 1) {\n if (re.test(fmLines[i] ?? \"\")) return i;\n }\n return -1;\n}\n\nfunction escapeRegex(s: string): string {\n return s.replace(/[.*+?^${}()|[\\]\\\\]/g, \"\\\\$&\");\n}\n\nfunction stripTrailingComment(s: string): string {\n // Only strip `#` outside of quotes. For the shapes we handle —\n // slug-like kebab-case topics — quoted strings with `#` are rare, but\n // be defensive.\n let inSingle = false;\n let inDouble = false;\n for (let i = 0; i < s.length; i += 1) {\n const ch = s[i];\n if (ch === \"'\" && !inDouble) inSingle = !inSingle;\n else if (ch === '\"' && !inSingle) inDouble = !inDouble;\n else if (ch === \"#\" && !inSingle && !inDouble) {\n return s.slice(0, i).trimEnd();\n }\n }\n return s;\n}\n\n/**\n * Strip YAML quoting from a scalar. Block-sequence items might be\n * written as `- 'foo'` or `- \"foo\"` or bare `- foo`; we accept all\n * three and return the plain string.\n */\nfunction parseScalar(s: string): string {\n if (s.length === 0) return s;\n if (s.length >= 2 && s[0] === '\"' && s[s.length - 1] === '\"') {\n return s.slice(1, -1);\n }\n if (s.length >= 2 && s[0] === \"'\" && s[s.length - 1] === \"'\") {\n return s.slice(1, -1);\n }\n return s;\n}\n\n/**\n * Emit a flow-style YAML sequence like `[auth, jwt, security]`. We use\n * flow because it's the shape most authors write by hand and stays on\n * one line, which keeps diffs tight. Values are quoted only when\n * necessary — plain kebab-case slugs never need quoting.\n */\nfunction flowList(items: string[]): string {\n return `[${items.map((t) => formatScalar(t)).join(\", \")}]`;\n}\n\nfunction formatScalar(s: string): string {\n // If it's a bare kebab/alnum slug, no quotes. Otherwise fall back to\n // js-yaml for correct escaping. We check against a conservative\n // pattern — anything outside it gets YAML-quoted.\n if (/^[a-z0-9][a-z0-9-]*$/.test(s)) return s;\n return yaml\n .dump(s, { flowLevel: 0, lineWidth: Number.MAX_SAFE_INTEGER })\n .trimEnd();\n}\n\nfunction dedupeSlugs(list: string[]): string[] {\n const seen = new Set<string>();\n const out: string[] = [];\n for (const raw of list) {\n const s = raw.trim();\n if (s.length === 0) continue;\n if (seen.has(s)) continue;\n seen.add(s);\n out.push(s);\n }\n return out;\n}\n\nfunction arraysEqual(a: string[], b: string[]): boolean {\n if (a.length !== b.length) return false;\n for (let i = 0; i < a.length; i += 1) {\n if (a[i] !== b[i]) return false;\n }\n return true;\n}\n","import { join } from \"node:path\";\n\n/**\n * `.almanac/topics.yaml` inside a given repo root. Single helper so no\n * caller has to remember where the file lives.\n */\nexport function topicsYamlPath(repoRoot: string): string {\n return join(repoRoot, \".almanac\", \"topics.yaml\");\n}\n\n/**\n * `.almanac/index.db` inside a given repo root. Mirrors `topicsYamlPath`\n * so the topics commands don't have to import from scattered places.\n */\nexport function indexDbPath(repoRoot: string): string {\n return join(repoRoot, \".almanac\", \"index.db\");\n}\n","import { ensureFreshIndex, runIndexer } from \"../indexer/index.js\";\nimport { resolveWikiRoot } from \"../indexer/resolve-wiki.js\";\nimport { openIndex } from \"../indexer/schema.js\";\nimport { toKebabCase } from \"../slug.js\";\nimport { rewritePageTopics } from \"../topics/frontmatter-rewrite.js\";\nimport { indexDbPath, topicsYamlPath } from \"../topics/paths.js\";\nimport {\n ensureTopic,\n loadTopicsFile,\n writeTopicsFile,\n} from \"../topics/yaml.js\";\n\n/**\n * `almanac tag <page> <topic>...` and `almanac untag <page> <topic>`.\n *\n * These are the page-side of the topics system — `topics ...` manages\n * the DAG and metadata; `tag`/`untag` wires concrete pages into\n * topics. Both commands mutate page frontmatter atomically per file\n * and leave body bytes untouched.\n *\n * Auto-creation policy: if a topic passed to `tag` doesn't yet exist\n * in `topics.yaml`, we create a minimal entry for it (title-cased\n * title, no description, no parents). This matches the spec: \"Ensure\n * topic exists in topics.yaml; if not, create a minimal entry.\" We\n * don't silently create topics on `untag` — you can only untag\n * something that was already a topic.\n */\n\nexport interface TagCommandOutput {\n stdout: string;\n stderr: string;\n exitCode: number;\n}\n\nexport interface TagOptions {\n cwd: string;\n wiki?: string;\n page?: string;\n topics: string[];\n stdin?: boolean;\n stdinInput?: string;\n}\n\nexport interface UntagOptions {\n cwd: string;\n wiki?: string;\n page: string;\n topic: string;\n}\n\nexport async function runTag(options: TagOptions): Promise<TagCommandOutput> {\n const repoRoot = await resolveWikiRoot({ cwd: options.cwd, wiki: options.wiki });\n\n const topics = options.topics\n .map((t) => toKebabCase(t))\n .filter((t) => t.length > 0);\n if (topics.length === 0) {\n return {\n stdout: \"\",\n stderr: \"almanac: tag requires at least one topic\\n\",\n exitCode: 1,\n };\n }\n\n // Bulk mode reads slugs from stdin; single mode uses the positional.\n const pages: string[] = [];\n if (options.stdin === true) {\n if (options.stdinInput === undefined) {\n return {\n stdout: \"\",\n stderr: \"almanac: tag --stdin called without stdin input\\n\",\n exitCode: 1,\n };\n }\n for (const line of options.stdinInput.split(/\\r?\\n/)) {\n const s = line.trim();\n if (s.length > 0) pages.push(s);\n }\n } else if (options.page !== undefined && options.page.length > 0) {\n pages.push(options.page);\n } else {\n return {\n stdout: \"\",\n stderr: \"almanac: tag requires a page slug (or --stdin)\\n\",\n exitCode: 1,\n };\n }\n\n // Resolve slugs to file paths from the DB. A stale index is fine for\n // `tag` — we just need to find each page's file; `ensureFreshIndex`\n // runs first so the common path is consistent.\n await ensureFreshIndex({ repoRoot });\n const db = openIndex(indexDbPath(repoRoot));\n\n // Validate every requested page exists BEFORE touching topics.yaml.\n // Previously we auto-created topics first, which meant\n // `almanac tag does-not-exist brand-new` left `brand-new` in\n // topics.yaml as a state leak even though the tag itself errored. We\n // resolve rows up front, then short-circuit with an error (and no\n // mutations) if none of the pages are valid.\n const stmt = db.prepare<[string], { file_path: string }>(\n \"SELECT file_path FROM pages WHERE slug = ?\",\n );\n const resolved: { page: string; filePath: string }[] = [];\n const missing: string[] = [];\n try {\n for (const page of pages) {\n const row = stmt.get(toKebabCase(page));\n if (row === undefined) {\n missing.push(page);\n } else {\n resolved.push({ page, filePath: row.file_path });\n }\n }\n } finally {\n db.close();\n }\n\n // Hard-fail when NO page resolved. We deliberately don't mutate\n // topics.yaml on this path — the user's intent (tag page X) is\n // inarguably unsatisfiable, so we shouldn't leave breadcrumbs.\n //\n // In bulk mode (`--stdin`) some pages might resolve and others\n // won't; keeping the original partial-progress behavior for that case\n // (topics get created, resolved pages get tagged, `missing` are\n // reported on stderr with exitCode 1). The state leak only matters\n // when NOTHING succeeds, and that's the case we're fixing.\n if (resolved.length === 0) {\n const stderr = missing.map((p) => `almanac: no such page \"${p}\"\\n`).join(\"\");\n return {\n stdout: \"\",\n stderr,\n exitCode: 1,\n };\n }\n\n // Auto-create missing topics in topics.yaml. Safe to do now — we have\n // at least one page that will actually end up tagged with them.\n const yamlPath = topicsYamlPath(repoRoot);\n const file = await loadTopicsFile(yamlPath);\n let fileChanged = false;\n for (const t of topics) {\n // ensureTopic mutates the file; we check presence beforehand so\n // we only write when something actually changes (skip a redundant\n // atomic rewrite + mtime bump).\n const before = file.topics.length;\n ensureTopic(file, t);\n if (file.topics.length > before) fileChanged = true;\n }\n if (fileChanged) {\n await writeTopicsFile(yamlPath, file);\n }\n\n const summary: string[] = [];\n let taggedPages = 0;\n for (const { page, filePath } of resolved) {\n const result = await rewritePageTopics(filePath, (current) => {\n // Preserve existing order; append new topics in the order\n // the caller supplied them. `applyTopicsTransform` will\n // dedupe for us, but we skip redundant work here too.\n const out = [...current];\n for (const t of topics) if (!current.includes(t)) out.push(t);\n return out;\n });\n if (result.changed) {\n taggedPages += 1;\n // Only surface the NEWLY ADDED topics — not the full request.\n // Reporting every requested topic (including ones the page\n // already had) reads like false positives in commit diffs.\n const added = result.after.filter((t) => !result.before.includes(t));\n summary.push(`tagged ${page}: ${added.join(\", \")}`);\n } else {\n summary.push(\n `no change ${page} (already tagged with ${topics.join(\", \")})`,\n );\n }\n }\n\n if (taggedPages > 0 || fileChanged) {\n // Trigger a reindex so downstream queries see the new rows\n // immediately. Writes to page files bumped their mtimes; writes to\n // topics.yaml are caught by `topicsYamlNewerThan`.\n await runIndexer({ repoRoot });\n }\n\n const stderr = missing.map((p) => `almanac: no such page \"${p}\"\\n`).join(\"\");\n return {\n stdout: summary.length > 0 ? `${summary.join(\"\\n\")}\\n` : \"\",\n stderr,\n exitCode: missing.length > 0 ? 1 : 0,\n };\n}\n\nexport async function runUntag(\n options: UntagOptions,\n): Promise<TagCommandOutput> {\n const repoRoot = await resolveWikiRoot({ cwd: options.cwd, wiki: options.wiki });\n const page = toKebabCase(options.page);\n const topic = toKebabCase(options.topic);\n if (page.length === 0) {\n return {\n stdout: \"\",\n stderr: \"almanac: untag requires a page slug\\n\",\n exitCode: 1,\n };\n }\n if (topic.length === 0) {\n return {\n stdout: \"\",\n stderr: \"almanac: untag requires a topic\\n\",\n exitCode: 1,\n };\n }\n\n await ensureFreshIndex({ repoRoot });\n const db = openIndex(indexDbPath(repoRoot));\n let filePath: string;\n try {\n const row = db\n .prepare<[string], { file_path: string }>(\n \"SELECT file_path FROM pages WHERE slug = ?\",\n )\n .get(page);\n if (row === undefined) {\n return {\n stdout: \"\",\n stderr: `almanac: no such page \"${page}\"\\n`,\n exitCode: 1,\n };\n }\n filePath = row.file_path;\n } finally {\n db.close();\n }\n\n const result = await rewritePageTopics(filePath, (current) =>\n current.filter((t) => t !== topic),\n );\n if (result.changed) {\n await runIndexer({ repoRoot });\n }\n\n return {\n stdout: result.changed\n ? `untagged ${page}: ${topic}\\n`\n : `no change ${page} (not tagged with ${topic})\\n`,\n stderr: \"\",\n exitCode: 0,\n };\n}\n","import type Database from \"better-sqlite3\";\n\nimport type { TopicsFile } from \"./yaml.js\";\n\n/**\n * Depth cap for all recursive traversals of the topics DAG. Belt and\n * suspenders alongside the `CHECK (child_slug != parent_slug)` on the\n * `topic_parents` table — even if a cycle somehow slipped into the data\n * (hand-edited `topics.yaml`, past bug), the CTE can't runaway.\n *\n * 32 is chosen as \"deeper than any real human-authored taxonomy will\n * ever go\". A 32-level topic hierarchy is absurd; anything hitting this\n * cap is almost certainly a cycle.\n */\nexport const DAG_DEPTH_CAP = 32;\n\n/**\n * Given a `topics.yaml` in memory, compute the set of ancestors of a\n * given slug (not including the slug itself). Used by `topics link`\n * to check whether a proposed edge would create a cycle.\n *\n * Running off the in-memory file lets `link` validate BEFORE touching\n * either the DB or the YAML, so a refusal doesn't leave half the state\n * mutated. Depth-capped with the same constant as the SQLite CTE.\n */\nexport function ancestorsInFile(\n file: TopicsFile,\n slug: string,\n): Set<string> {\n // Build a child → parents map once.\n const parentsOf = new Map<string, string[]>();\n for (const t of file.topics) {\n parentsOf.set(t.slug, t.parents);\n }\n const ancestors = new Set<string>();\n // BFS, depth-capped. We stop descending when we've hit the cap or\n // revisit an already-seen node (self-loop defense).\n let frontier: string[] = parentsOf.get(slug) ?? [];\n let depth = 0;\n while (frontier.length > 0 && depth < DAG_DEPTH_CAP) {\n const next: string[] = [];\n for (const node of frontier) {\n if (ancestors.has(node)) continue;\n ancestors.add(node);\n const ps = parentsOf.get(node);\n if (ps !== undefined) next.push(...ps);\n }\n frontier = next;\n depth += 1;\n }\n return ancestors;\n}\n\n/**\n * Return all descendants of a given topic slug via the SQLite\n * `topic_parents` table. Depth-capped at `DAG_DEPTH_CAP`.\n *\n * Used by `topics show --descendants` to expand a topic's page list\n * through its subtopics. The query is a canonical recursive CTE; we\n * `UNION` (not `UNION ALL`) so cycles in the data don't spin forever.\n */\nexport function descendantsInDb(\n db: Database.Database,\n slug: string,\n): string[] {\n const rows = db\n .prepare<[string, number], { slug: string }>(\n `WITH RECURSIVE desc(slug, depth) AS (\n SELECT child_slug, 1 FROM topic_parents WHERE parent_slug = ?\n UNION\n SELECT tp.child_slug, d.depth + 1\n FROM topic_parents tp\n JOIN desc d ON tp.parent_slug = d.slug\n WHERE d.depth < ?\n )\n SELECT DISTINCT slug FROM desc ORDER BY slug`,\n )\n .all(slug, DAG_DEPTH_CAP)\n .map((r) => r.slug);\n return rows;\n}\n\n/**\n * Return the subtree rooted at `slug` (the slug itself + all\n * descendants). Convenience wrapper used by `health --topic` to scope\n * reports through the DAG.\n */\nexport function subtreeInDb(db: Database.Database, slug: string): string[] {\n return [slug, ...descendantsInDb(db, slug)];\n}\n","import type Database from \"better-sqlite3\";\n\nimport { ensureFreshIndex } from \"../../indexer/index.js\";\nimport { resolveWikiRoot } from \"../../indexer/resolve-wiki.js\";\nimport { openIndex } from \"../../indexer/schema.js\";\nimport { indexDbPath, topicsYamlPath } from \"../../topics/paths.js\";\nimport {\n findTopic,\n loadTopicsFile,\n type TopicsFile,\n} from \"../../topics/yaml.js\";\n\ninterface TopicsRepoOptions {\n cwd: string;\n wiki?: string;\n}\n\nexport interface TopicsWorkspace {\n repoRoot: string;\n yamlPath: string;\n file: TopicsFile;\n db: Database.Database;\n}\n\nexport function resolveTopicsRepo(options: TopicsRepoOptions): Promise<string> {\n return resolveWikiRoot({ cwd: options.cwd, wiki: options.wiki });\n}\n\n/**\n * Shared setup path for mutating topic commands. These commands all need\n * a fresh DB view so ad-hoc topics from page frontmatter can be promoted\n * into `topics.yaml` before mutation.\n */\nexport async function openFreshTopicsWorkspace(\n repoRoot: string,\n): Promise<TopicsWorkspace> {\n await ensureFreshIndex({ repoRoot });\n\n const yamlPath = topicsYamlPath(repoRoot);\n const file = await loadTopicsFile(yamlPath);\n const db = openIndex(indexDbPath(repoRoot));\n return { repoRoot, yamlPath, file, db };\n}\n\nexport function closeWorkspace(workspace: TopicsWorkspace): void {\n workspace.db.close();\n}\n\n/**\n * Is `slug` a known topic anywhere — in `topics.yaml`, or as an ad-hoc\n * slug that a page's frontmatter mentioned and the indexer seeded?\n */\nexport function topicExists(\n file: TopicsFile,\n db: Database.Database,\n slug: string,\n): boolean {\n if (findTopic(file, slug) !== null) return true;\n const row = db\n .prepare<[string], { slug: string }>(\n \"SELECT slug FROM topics WHERE slug = ?\",\n )\n .get(slug);\n return row !== undefined;\n}\n","import { runIndexer } from \"../../indexer/index.js\";\nimport { toKebabCase } from \"../../slug.js\";\nimport { ancestorsInFile } from \"../../topics/dag.js\";\nimport {\n ensureTopic,\n findTopic,\n titleCase,\n writeTopicsFile,\n type TopicEntry,\n} from \"../../topics/yaml.js\";\nimport type { TopicsCommandOutput, TopicsCreateOptions } from \"./types.js\";\nimport {\n closeWorkspace,\n openFreshTopicsWorkspace,\n resolveTopicsRepo,\n topicExists,\n} from \"./workspace.js\";\n\n/**\n * `almanac topics create <name> [--parent <slug>]...`.\n *\n * Policy: `--parent <slug>` MUST refer to an existing topic (created\n * earlier in topics.yaml or indexed from page frontmatter). Auto-\n * creating parents silently would let typos cascade — `create JWT\n * --parent secuirty` would quietly spawn a \"secuirty\" topic. Better to\n * refuse and point the user at `almanac topics create <parent>` first.\n *\n * Already-exists is not an error if no new parents are being added —\n * rerunning the same `create` is a no-op. If new parents are introduced\n * we add them (respecting cycle prevention, just like `link`).\n */\nexport async function runTopicsCreate(\n options: TopicsCreateOptions,\n): Promise<TopicsCommandOutput> {\n const repoRoot = await resolveTopicsRepo(options);\n const slug = toKebabCase(options.name);\n if (slug.length === 0) {\n return {\n stdout: \"\",\n stderr: `almanac: topic name \"${options.name}\" has no slug-able characters\\n`,\n exitCode: 1,\n };\n }\n const title = options.name.trim().length > 0 ? options.name.trim() : titleCase(slug);\n\n const workspace = await openFreshTopicsWorkspace(repoRoot);\n try {\n const { repoRoot, yamlPath, file, db } = workspace;\n // Resolve/validate parents BEFORE mutating the file. All-or-nothing.\n const requestedParents = (options.parents ?? [])\n .map((p) => toKebabCase(p))\n .filter((p) => p.length > 0);\n for (const p of requestedParents) {\n if (p === slug) {\n return {\n stdout: \"\",\n stderr: `almanac: topic cannot be its own parent\\n`,\n exitCode: 1,\n };\n }\n if (!topicExists(file, db, p)) {\n return {\n stdout: \"\",\n stderr: `almanac: parent topic \"${p}\" does not exist; create it first with \\`almanac topics create ${p}\\`\\n`,\n exitCode: 1,\n };\n }\n if (findTopic(file, p) === null) {\n // Topic exists only as an ad-hoc DB entry. Promote it into\n // topics.yaml so it has a proper record. `ensureTopic` is\n // idempotent so this is safe even if two loop iterations\n // reference the same ad-hoc parent.\n ensureTopic(file, p);\n }\n }\n\n const existing = findTopic(file, slug);\n if (existing === null) {\n const entry: TopicEntry = {\n slug,\n title,\n description: null,\n parents: requestedParents,\n };\n file.topics.push(entry);\n } else {\n // Add any new parents, skipping ones that already exist or would\n // create a cycle.\n for (const p of requestedParents) {\n if (existing.parents.includes(p)) continue;\n const ancestors = ancestorsInFile(file, p);\n if (ancestors.has(slug) || p === slug) {\n return {\n stdout: \"\",\n stderr: `almanac: adding \"${p}\" as a parent of \"${slug}\" would create a cycle\\n`,\n exitCode: 1,\n };\n }\n existing.parents.push(p);\n }\n // Promote the user-supplied title only if the existing one was a\n // title-cased default (i.e., they didn't describe it yet). Don't\n // clobber a deliberate title silently.\n if (\n existing.title === titleCase(existing.slug) &&\n title !== titleCase(slug) &&\n title !== existing.title\n ) {\n existing.title = title;\n }\n }\n\n await writeTopicsFile(yamlPath, file);\n await runIndexer({ repoRoot });\n return {\n stdout: existing === null\n ? `created topic \"${slug}\"\\n`\n : `updated topic \"${slug}\"\\n`,\n stderr: \"\",\n exitCode: 0,\n };\n } finally {\n closeWorkspace(workspace);\n }\n}\n","import { readFile } from \"node:fs/promises\";\nimport { join } from \"node:path\";\n\nimport fg from \"fast-glob\";\n\nimport {\n applyTopicsTransform,\n rewritePageTopics,\n} from \"../../topics/frontmatter-rewrite.js\";\n\n/**\n * Apply a `topic-list transform` to every `.almanac/pages/*.md` file\n * whose frontmatter contains a relevant topic. Returns the number of\n * files actually changed.\n *\n * We glob page files ourselves (not the DB) so this works even on a\n * stale index — `rename` and `delete` run the indexer AFTER mutation,\n * and we don't want the scan to miss a page that was just modified.\n */\nexport async function rewriteTopicOnPages(\n repoRoot: string,\n transform: (topics: string[]) => string[],\n): Promise<number> {\n const pagesDir = join(repoRoot, \".almanac\", \"pages\");\n const files = await fg(\"**/*.md\", {\n cwd: pagesDir,\n absolute: true,\n onlyFiles: true,\n });\n let changed = 0;\n for (const filePath of files) {\n // Cheap read → in-memory check. Skip files that wouldn't be\n // changed so we don't bump their mtime.\n const raw = await readFile(filePath, \"utf8\");\n const applied = applyTopicsTransform(raw, transform);\n if (!applied.changed) continue;\n await rewritePageTopics(filePath, transform);\n changed += 1;\n }\n return changed;\n}\n","import { runIndexer } from \"../../indexer/index.js\";\nimport { toKebabCase } from \"../../slug.js\";\nimport { writeTopicsFile } from \"../../topics/yaml.js\";\nimport { rewriteTopicOnPages } from \"./page-rewrite.js\";\nimport type { TopicsCommandOutput, TopicsDeleteOptions } from \"./types.js\";\nimport {\n closeWorkspace,\n openFreshTopicsWorkspace,\n resolveTopicsRepo,\n topicExists,\n} from \"./workspace.js\";\n\n/**\n * `almanac topics delete <slug>`. Removes the topic from `topics.yaml`\n * (if present), scrubs any parent edges pointing at it, and untags\n * every page that had it. Pages themselves are left alone — deleting a\n * topic doesn't delete pages, just the relationship.\n */\nexport async function runTopicsDelete(\n options: TopicsDeleteOptions,\n): Promise<TopicsCommandOutput> {\n const repoRoot = await resolveTopicsRepo(options);\n const slug = toKebabCase(options.slug);\n if (slug.length === 0) {\n return { stdout: \"\", stderr: `almanac: empty topic slug\\n`, exitCode: 1 };\n }\n\n const workspace = await openFreshTopicsWorkspace(repoRoot);\n let pagesUpdated: number;\n try {\n const { repoRoot, yamlPath, file, db } = workspace;\n if (!topicExists(file, db, slug)) {\n return {\n stdout: \"\",\n stderr: `almanac: no such topic \"${slug}\"\\n`,\n exitCode: 1,\n };\n }\n\n // Remove the entry and strip it from everyone else's `parents` list.\n file.topics = file.topics.filter((t) => t.slug !== slug);\n for (const t of file.topics) {\n t.parents = t.parents.filter((p) => p !== slug);\n }\n\n // Same write ordering as rename: topics.yaml first (atomic), then\n // pages. A crash between the two leaves topics.yaml already scrubbed\n // and any remaining in-page references become ad-hoc topics — which\n // the reindex will pick up as empty-topics on next health, and the\n // user can re-run to finish untagging.\n await writeTopicsFile(yamlPath, file);\n\n pagesUpdated = await rewriteTopicOnPages(repoRoot, (topics) =>\n topics.filter((t) => t !== slug),\n );\n } finally {\n closeWorkspace(workspace);\n }\n\n await runIndexer({ repoRoot: workspace.repoRoot });\n return {\n stdout: `deleted topic \"${slug}\" (${pagesUpdated} page${pagesUpdated === 1 ? \"\" : \"s\"} untagged)\\n`,\n stderr: \"\",\n exitCode: 0,\n };\n}\n","import { runIndexer } from \"../../indexer/index.js\";\nimport { toKebabCase } from \"../../slug.js\";\nimport { ensureTopic, writeTopicsFile } from \"../../topics/yaml.js\";\nimport type { TopicsCommandOutput, TopicsDescribeOptions } from \"./types.js\";\nimport {\n closeWorkspace,\n openFreshTopicsWorkspace,\n resolveTopicsRepo,\n topicExists,\n} from \"./workspace.js\";\n\n/**\n * `almanac topics describe <slug> \"<text>\"`. Sets or updates the\n * one-liner description. An empty string clears it.\n */\nexport async function runTopicsDescribe(\n options: TopicsDescribeOptions,\n): Promise<TopicsCommandOutput> {\n const repoRoot = await resolveTopicsRepo(options);\n const slug = toKebabCase(options.slug);\n if (slug.length === 0) {\n return { stdout: \"\", stderr: `almanac: empty topic slug\\n`, exitCode: 1 };\n }\n\n const workspace = await openFreshTopicsWorkspace(repoRoot);\n try {\n const { yamlPath, file, db } = workspace;\n if (!topicExists(file, db, slug)) {\n return {\n stdout: \"\",\n stderr: `almanac: no such topic \"${slug}\"\\n`,\n exitCode: 1,\n };\n }\n // `ensureTopic` is idempotent — if the topic was DB-only it\n // promotes into `file`; if already in `file` it returns the\n // existing entry. Either way we get a concrete entry to mutate.\n const entry = ensureTopic(file, slug);\n\n const text = options.description.trim();\n entry.description = text.length === 0 ? null : text;\n\n await writeTopicsFile(yamlPath, file);\n } finally {\n closeWorkspace(workspace);\n }\n\n await runIndexer({ repoRoot: workspace.repoRoot });\n return {\n stdout: `described ${slug}\\n`,\n stderr: \"\",\n exitCode: 0,\n };\n}\n","import { runIndexer } from \"../../indexer/index.js\";\nimport { toKebabCase } from \"../../slug.js\";\nimport { ancestorsInFile } from \"../../topics/dag.js\";\nimport {\n ensureTopic,\n findTopic,\n writeTopicsFile,\n} from \"../../topics/yaml.js\";\nimport type { TopicsCommandOutput, TopicsLinkOptions } from \"./types.js\";\nimport {\n closeWorkspace,\n openFreshTopicsWorkspace,\n resolveTopicsRepo,\n topicExists,\n} from \"./workspace.js\";\n\n/**\n * `almanac topics link <child> <parent>`. Adds a DAG edge after\n * checking that it wouldn't close a cycle. Both topics must exist.\n */\nexport async function runTopicsLink(\n options: TopicsLinkOptions,\n): Promise<TopicsCommandOutput> {\n const repoRoot = await resolveTopicsRepo(options);\n const child = toKebabCase(options.child);\n const parent = toKebabCase(options.parent);\n if (child.length === 0 || parent.length === 0) {\n return { stdout: \"\", stderr: `almanac: empty topic slug\\n`, exitCode: 1 };\n }\n if (child === parent) {\n return {\n stdout: \"\",\n stderr: `almanac: topic cannot be its own parent\\n`,\n exitCode: 1,\n };\n }\n\n const workspace = await openFreshTopicsWorkspace(repoRoot);\n try {\n const { repoRoot, yamlPath, file, db } = workspace;\n for (const slug of [child, parent]) {\n if (!topicExists(file, db, slug)) {\n return {\n stdout: \"\",\n stderr: `almanac: topic \"${slug}\" does not exist\\n`,\n exitCode: 1,\n };\n }\n if (findTopic(file, slug) === null) {\n // DB-only ad-hoc topic → promote it into topics.yaml so the\n // new DAG edge has a concrete home.\n ensureTopic(file, slug);\n }\n }\n\n const childEntry = findTopic(file, child);\n if (childEntry === null) {\n // Shouldn't happen after ensureTopic above — defensive.\n return {\n stdout: \"\",\n stderr: `almanac: topic \"${child}\" not found\\n`,\n exitCode: 1,\n };\n }\n\n if (childEntry.parents.includes(parent)) {\n return {\n stdout: `edge ${child} → ${parent} already exists\\n`,\n stderr: \"\",\n exitCode: 0,\n };\n }\n\n // Cycle check BEFORE mutation. Uses the in-memory file so the check\n // operates on the state we're about to write — no DB round-trip needed.\n const parentAncestors = ancestorsInFile(file, parent);\n if (parentAncestors.has(child) || parent === child) {\n return {\n stdout: \"\",\n stderr: `almanac: adding ${parent} as parent of ${child} would create a cycle\\n`,\n exitCode: 1,\n };\n }\n\n childEntry.parents.push(parent);\n await writeTopicsFile(yamlPath, file);\n await runIndexer({ repoRoot });\n return {\n stdout: `linked ${child} → ${parent}\\n`,\n stderr: \"\",\n exitCode: 0,\n };\n } finally {\n closeWorkspace(workspace);\n }\n}\n","import { BLUE, DIM, RST } from \"../../ansi.js\";\nimport { ensureFreshIndex } from \"../../indexer/index.js\";\nimport { resolveWikiRoot } from \"../../indexer/resolve-wiki.js\";\nimport { openIndex } from \"../../indexer/schema.js\";\nimport { indexDbPath } from \"../../topics/paths.js\";\nimport type { TopicsCommandOutput, TopicsListOptions } from \"./types.js\";\n\n/**\n * `almanac topics` (and `almanac topics list`). Prints one line per\n * known topic — from the DB, which already unions topics.yaml with any\n * ad-hoc slugs found in page frontmatter. Page counts come straight\n * from `page_topics`, which the indexer rebuilt on entry.\n */\nexport async function runTopicsList(\n options: TopicsListOptions,\n): Promise<TopicsCommandOutput> {\n const repoRoot = await resolveWikiRoot({ cwd: options.cwd, wiki: options.wiki });\n await ensureFreshIndex({ repoRoot });\n\n const db = openIndex(indexDbPath(repoRoot));\n try {\n const rows = db\n .prepare<\n [],\n { slug: string; title: string | null; description: string | null; page_count: number }\n >(\n // page_count excludes archived pages — matches the policy used\n // by `topics show` (see `pagesDirectlyTagged`) and by every\n // page-scoped check in `health`. Pick one rule and apply it\n // everywhere; a topic with \"5 pages\" in `topics list` and \"3\n // pages\" in `topics show` is a trust-eroding inconsistency.\n `SELECT t.slug, t.title, t.description,\n (SELECT COUNT(*)\n FROM page_topics pt\n JOIN pages p ON p.slug = pt.page_slug\n WHERE pt.topic_slug = t.slug AND p.archived_at IS NULL\n ) AS page_count\n FROM topics t\n ORDER BY t.slug`,\n )\n .all();\n\n if (options.json === true) {\n return {\n stdout: `${JSON.stringify(rows, null, 2)}\\n`,\n stderr: \"\",\n exitCode: 0,\n };\n }\n\n if (rows.length === 0) {\n return {\n stdout:\n \"no topics. create one with `almanac topics create <name>` or tag a page.\\n\",\n stderr: \"\",\n exitCode: 0,\n };\n }\n\n const slugWidth = rows.reduce((w, r) => Math.max(w, r.slug.length), 0);\n const lines = rows.map((r) => {\n const slug = r.slug.padEnd(slugWidth);\n const count = `(${r.page_count} page${r.page_count === 1 ? \"\" : \"s\"})`;\n return `${BLUE}${slug}${RST} ${DIM}${count}${RST}`;\n });\n return { stdout: `${lines.join(\"\\n\")}\\n`, stderr: \"\", exitCode: 0 };\n } finally {\n db.close();\n }\n}\n","import { runIndexer } from \"../../indexer/index.js\";\nimport { toKebabCase } from \"../../slug.js\";\nimport {\n findTopic,\n titleCase,\n writeTopicsFile,\n} from \"../../topics/yaml.js\";\nimport { rewriteTopicOnPages } from \"./page-rewrite.js\";\nimport type { TopicsCommandOutput, TopicsRenameOptions } from \"./types.js\";\nimport {\n closeWorkspace,\n openFreshTopicsWorkspace,\n resolveTopicsRepo,\n topicExists,\n} from \"./workspace.js\";\n\n/**\n * `almanac topics rename <old> <new>`. Rewrites the slug both in\n * `topics.yaml` (as an entry key and in anyone who declared it as a\n * parent) and in every affected page's frontmatter.\n *\n * Refuses if `<new>` is already a distinct topic — \"merging\" two topics\n * should be explicit, not a silent side effect of a rename.\n */\nexport async function runTopicsRename(\n options: TopicsRenameOptions,\n): Promise<TopicsCommandOutput> {\n const repoRoot = await resolveTopicsRepo(options);\n const oldSlug = toKebabCase(options.oldSlug);\n const newSlug = toKebabCase(options.newSlug);\n if (oldSlug.length === 0 || newSlug.length === 0) {\n return { stdout: \"\", stderr: `almanac: empty topic slug\\n`, exitCode: 1 };\n }\n if (oldSlug === newSlug) {\n return {\n stdout: `topic \"${oldSlug}\" unchanged\\n`,\n stderr: \"\",\n exitCode: 0,\n };\n }\n\n const workspace = await openFreshTopicsWorkspace(repoRoot);\n let pagesUpdated: number;\n try {\n const { repoRoot, yamlPath, file, db } = workspace;\n // Fetch existence info. `oldInYaml` is kept as a direct reference\n // because we mutate the entry; the DB check is only needed when\n // the slug isn't in the file (ad-hoc-only).\n const oldInYaml = findTopic(file, oldSlug);\n if (!topicExists(file, db, oldSlug)) {\n return {\n stdout: \"\",\n stderr: `almanac: no such topic \"${oldSlug}\"\\n`,\n exitCode: 1,\n };\n }\n\n if (topicExists(file, db, newSlug)) {\n return {\n stdout: \"\",\n stderr: `almanac: topic \"${newSlug}\" already exists; delete it first if you intend to merge\\n`,\n exitCode: 1,\n };\n }\n\n // Rewrite `topics.yaml`: the entry itself (if present) plus any\n // parent reference to `oldSlug`.\n if (oldInYaml !== null) {\n oldInYaml.slug = newSlug;\n if (oldInYaml.title === titleCase(oldSlug)) {\n // Title was the auto-generated default — refresh it to the new\n // slug's title-case. A custom title stays as-is.\n oldInYaml.title = titleCase(newSlug);\n }\n }\n for (const t of file.topics) {\n t.parents = t.parents.map((p) => (p === oldSlug ? newSlug : p));\n }\n\n // Write ordering matters: topics.yaml FIRST (atomic tmp+rename), THEN\n // the page rewrites. If topics.yaml write fails, no page was touched.\n // If a page rewrite fails midway, topics.yaml already reflects the\n // rename so the next reindex picks up the ad-hoc state and the user\n // can re-run to finish the remaining pages. The opposite ordering\n // would leave half-rewritten pages referencing a slug that\n // topics.yaml doesn't know about.\n await writeTopicsFile(yamlPath, file);\n\n // Rewrite every page that has `oldSlug` in `topics:` frontmatter.\n pagesUpdated = await rewriteTopicOnPages(repoRoot, (topics) =>\n topics.map((t) => (t === oldSlug ? newSlug : t)),\n );\n } finally {\n closeWorkspace(workspace);\n }\n\n await runIndexer({ repoRoot: workspace.repoRoot });\n return {\n stdout: `renamed ${oldSlug} → ${newSlug} (${pagesUpdated} page${pagesUpdated === 1 ? \"\" : \"s\"} updated)\\n`,\n stderr: \"\",\n exitCode: 0,\n };\n}\n","import type Database from \"better-sqlite3\";\n\nimport { BLUE, DIM, RST } from \"../../ansi.js\";\nimport { descendantsInDb } from \"../../topics/dag.js\";\nimport { titleCase } from \"../../topics/yaml.js\";\n\nexport interface TopicsShowRecord {\n slug: string;\n title: string | null;\n description: string | null;\n parents: string[];\n children: string[];\n pages: string[];\n descendants_used?: boolean;\n}\n\nexport function pagesDirectlyTagged(\n db: Database.Database,\n slug: string,\n): string[] {\n return db\n .prepare<[string], { page_slug: string }>(\n `SELECT pt.page_slug\n FROM page_topics pt\n JOIN pages p ON p.slug = pt.page_slug\n WHERE pt.topic_slug = ? AND p.archived_at IS NULL\n ORDER BY pt.page_slug`,\n )\n .all(slug)\n .map((r) => r.page_slug);\n}\n\nexport function pagesForSubtree(\n db: Database.Database,\n slug: string,\n): string[] {\n const slugs = [slug, ...descendantsInDb(db, slug)];\n // Deduplicate + preserve order via a Set — a page can belong to\n // multiple topics in the subtree and we only want one row per page.\n const placeholders = slugs.map(() => \"?\").join(\", \");\n const rows = db\n .prepare<unknown[], { page_slug: string }>(\n `SELECT DISTINCT pt.page_slug\n FROM page_topics pt\n JOIN pages p ON p.slug = pt.page_slug\n WHERE pt.topic_slug IN (${placeholders}) AND p.archived_at IS NULL\n ORDER BY pt.page_slug`,\n )\n .all(...slugs);\n return rows.map((r) => r.page_slug);\n}\n\nexport function formatShow(r: TopicsShowRecord): string {\n const lines: string[] = [];\n lines.push(`${DIM}slug:${RST} ${BLUE}${r.slug}${RST}`);\n lines.push(`${DIM}title:${RST} ${r.title ?? titleCase(r.slug)}`);\n lines.push(`${DIM}description:${RST} ${r.description ?? \"—\"}`);\n lines.push(\n `${DIM}parents:${RST} ${r.parents.length > 0 ? r.parents.join(\", \") : \"—\"}`,\n );\n lines.push(\n `${DIM}children:${RST} ${r.children.length > 0 ? r.children.join(\", \") : \"—\"}`,\n );\n const pagesLabel = r.descendants_used === true\n ? \"pages (incl. descendants)\"\n : \"pages\";\n lines.push(`${DIM}${pagesLabel}:${RST}`);\n if (r.pages.length === 0) {\n lines.push(\" —\");\n } else {\n for (const p of r.pages) lines.push(` ${BLUE}${p}${RST}`);\n }\n return `${lines.join(\"\\n\")}\\n`;\n}\n","import { ensureFreshIndex } from \"../../indexer/index.js\";\nimport { resolveWikiRoot } from \"../../indexer/resolve-wiki.js\";\nimport { openIndex } from \"../../indexer/schema.js\";\nimport { toKebabCase } from \"../../slug.js\";\nimport { indexDbPath } from \"../../topics/paths.js\";\nimport {\n formatShow,\n pagesDirectlyTagged,\n pagesForSubtree,\n type TopicsShowRecord,\n} from \"./read.js\";\nimport type { TopicsCommandOutput, TopicsShowOptions } from \"./types.js\";\n\n/**\n * `almanac topics show <slug>`. Prints metadata + parents, children,\n * and the page list. `--descendants` widens the page list to include\n * pages tagged with any descendant topic (via the DAG).\n */\nexport async function runTopicsShow(\n options: TopicsShowOptions,\n): Promise<TopicsCommandOutput> {\n const repoRoot = await resolveWikiRoot({ cwd: options.cwd, wiki: options.wiki });\n await ensureFreshIndex({ repoRoot });\n\n const slug = toKebabCase(options.slug);\n if (slug.length === 0) {\n return {\n stdout: \"\",\n stderr: `almanac: empty topic slug\\n`,\n exitCode: 1,\n };\n }\n\n const db = openIndex(indexDbPath(repoRoot));\n try {\n const row = db\n .prepare<\n [string],\n { slug: string; title: string | null; description: string | null }\n >(\"SELECT slug, title, description FROM topics WHERE slug = ?\")\n .get(slug);\n if (row === undefined) {\n return {\n stdout: \"\",\n stderr: `almanac: no such topic \"${slug}\"\\n`,\n exitCode: 1,\n };\n }\n\n const parents = db\n .prepare<[string], { parent_slug: string }>(\n \"SELECT parent_slug FROM topic_parents WHERE child_slug = ? ORDER BY parent_slug\",\n )\n .all(slug)\n .map((r) => r.parent_slug);\n\n const children = db\n .prepare<[string], { child_slug: string }>(\n \"SELECT child_slug FROM topic_parents WHERE parent_slug = ? ORDER BY child_slug\",\n )\n .all(slug)\n .map((r) => r.child_slug);\n\n const pageSlugs = options.descendants === true\n ? pagesForSubtree(db, slug)\n : pagesDirectlyTagged(db, slug);\n\n const record: TopicsShowRecord = {\n slug: row.slug,\n title: row.title,\n description: row.description,\n parents,\n children,\n pages: pageSlugs,\n descendants_used: options.descendants === true,\n };\n\n if (options.json === true) {\n return {\n stdout: `${JSON.stringify(record, null, 2)}\\n`,\n stderr: \"\",\n exitCode: 0,\n };\n }\n return { stdout: formatShow(record), stderr: \"\", exitCode: 0 };\n } finally {\n db.close();\n }\n}\n","import { runIndexer } from \"../../indexer/index.js\";\nimport { resolveWikiRoot } from \"../../indexer/resolve-wiki.js\";\nimport { toKebabCase } from \"../../slug.js\";\nimport { topicsYamlPath } from \"../../topics/paths.js\";\nimport {\n findTopic,\n loadTopicsFile,\n writeTopicsFile,\n} from \"../../topics/yaml.js\";\nimport type { TopicsCommandOutput, TopicsUnlinkOptions } from \"./types.js\";\n\n/**\n * `almanac topics unlink <child> <parent>`. Removes a DAG edge if it\n * exists. No-op (exit 0) if not. Never deletes topics.\n */\nexport async function runTopicsUnlink(\n options: TopicsUnlinkOptions,\n): Promise<TopicsCommandOutput> {\n const repoRoot = await resolveWikiRoot({ cwd: options.cwd, wiki: options.wiki });\n const child = toKebabCase(options.child);\n const parent = toKebabCase(options.parent);\n if (child.length === 0 || parent.length === 0) {\n return { stdout: \"\", stderr: `almanac: empty topic slug\\n`, exitCode: 1 };\n }\n const yamlPath = topicsYamlPath(repoRoot);\n const file = await loadTopicsFile(yamlPath);\n const childEntry = findTopic(file, child);\n if (childEntry === null || !childEntry.parents.includes(parent)) {\n return {\n stdout: `no edge ${child} → ${parent}\\n`,\n stderr: \"\",\n exitCode: 0,\n };\n }\n childEntry.parents = childEntry.parents.filter((p) => p !== parent);\n await writeTopicsFile(yamlPath, file);\n await runIndexer({ repoRoot });\n return {\n stdout: `unlinked ${child} → ${parent}\\n`,\n stderr: \"\",\n exitCode: 0,\n };\n}\n","import { existsSync } from \"node:fs\";\nimport { basename } from \"node:path\";\n\nimport { findNearestAlmanacDir } from \"../paths.js\";\nimport { toKebabCase } from \"../slug.js\";\nimport {\n addEntry,\n readRegistry,\n type RegistryEntry,\n} from \"./index.js\";\n\n/**\n * If the current working directory (or any parent) has a `.almanac/` that\n * isn't in the registry, silently add it. Runs as a side effect of every\n * command except `init` (which does its own registration) and `list --drop`\n * (which shouldn't resurrect the entry the user just removed).\n *\n * The contract is \"silent\" for environmental problems — missing home dir,\n * unreadable registry file, permission errors. Those shouldn't block the\n * real command from running. But a **malformed** registry IS surfaced: if\n * the JSON is corrupt, the user needs to know, not have auto-register\n * quietly pretend the registry was empty and start overwriting entries.\n */\nexport async function autoRegisterIfNeeded(\n cwd: string,\n): Promise<RegistryEntry | null> {\n try {\n const repoRoot = findNearestAlmanacDir(cwd);\n if (repoRoot === null) return null;\n\n // Double-check the directory still exists — `findNearestAlmanacDir`\n // already confirms this, but we're explicit about the precondition.\n if (!existsSync(repoRoot)) return null;\n\n // Read the registry ONCE. `resolveNameCollision` scans this snapshot\n // in memory; re-reading per iteration would be O(N²) in collision\n // count and needlessly hit the filesystem.\n const entries = await readRegistry();\n\n const existing = entries.find((e) => samePath(e.path, repoRoot));\n if (existing !== undefined) return existing;\n\n // Derive a kebab-case name from the directory. If the dir name is\n // somehow empty (e.g. repo is at filesystem root), skip — we don't\n // want to register a nameless entry.\n const name = toKebabCase(basename(repoRoot));\n if (name.length === 0) return null;\n\n // Resolve collisions on name by falling back to a disambiguated form.\n // Auto-registration should never overwrite an existing named entry\n // that points elsewhere.\n const finalName = resolveNameCollision(entries, name, repoRoot);\n if (finalName === null) return null;\n\n const entry: RegistryEntry = {\n name: finalName,\n description: \"\",\n path: repoRoot,\n registered_at: new Date().toISOString(),\n };\n await addEntry(entry);\n return entry;\n } catch (err: unknown) {\n // Only swallow errors that mean \"registry state isn't readable right\n // now\" — everything else (malformed JSON, programmer errors, bugs)\n // should propagate so the user can see it.\n if (\n err instanceof Error &&\n \"code\" in err &&\n (err.code === \"ENOENT\" ||\n err.code === \"EACCES\" ||\n err.code === \"EPERM\")\n ) {\n return null;\n }\n throw err;\n }\n}\n\n/**\n * If another repo already claims `name`, append `-2`, `-3`, ... until we\n * find an unused slug. Only relevant for auto-registration — `init` with\n * `--name` lets the user resolve collisions explicitly.\n *\n * Takes a snapshot of registry entries instead of re-reading the file per\n * iteration. Caps at 1000 attempts to prevent pathological loops if the\n * registry somehow contains every suffix (it can't, but we'd rather fail\n * explicitly than spin).\n */\nfunction resolveNameCollision(\n entries: RegistryEntry[],\n baseName: string,\n repoPath: string,\n): string | null {\n const owner = entries.find((e) => e.name === baseName);\n if (owner === undefined || samePath(owner.path, repoPath)) {\n return baseName;\n }\n const taken = new Set(entries.map((e) => e.name));\n const MAX_ATTEMPTS = 1000;\n for (let suffix = 2; suffix < MAX_ATTEMPTS + 2; suffix += 1) {\n const candidate = `${baseName}-${suffix}`;\n if (!taken.has(candidate)) return candidate;\n }\n return null;\n}\n\n/**\n * Mirror `pathsEqual` in `registry/index.ts` — case-insensitive on\n * macOS/Windows, case-sensitive on Linux. Duplicated here rather than\n * exported to keep the registry module's public surface small.\n */\nfunction samePath(a: string, b: string): boolean {\n if (process.platform === \"darwin\" || process.platform === \"win32\") {\n return a.toLowerCase() === b.toLowerCase();\n }\n return a === b;\n}\n","import { Command } from \"commander\";\n\nimport { runTag, runUntag } from \"../commands/tag.js\";\nimport {\n runTopicsCreate,\n runTopicsDelete,\n runTopicsDescribe,\n runTopicsLink,\n runTopicsList,\n runTopicsRename,\n runTopicsShow,\n runTopicsUnlink,\n} from \"../commands/topics.js\";\nimport { autoRegisterIfNeeded } from \"../registry/autoregister.js\";\nimport { collectOption, emit, readStdin } from \"./helpers.js\";\n\nexport function registerEditCommands(program: Command): void {\n program\n .command(\"tag [page] [topics...]\")\n .description(\"add topics to a page (auto-creates missing topics)\")\n .option(\"--stdin\", \"read page slugs from stdin (one per line)\")\n .option(\"--wiki <name>\", \"target a specific registered wiki\")\n .action(\n async (\n page: string | undefined,\n topicsArg: string[],\n opts: { stdin?: boolean; wiki?: string },\n ) => {\n await autoRegisterIfNeeded(process.cwd());\n const resolvedTopics = opts.stdin === true\n ? [page, ...topicsArg].filter(\n (t): t is string => typeof t === \"string\" && t.length > 0,\n )\n : topicsArg;\n const result = await runTag({\n cwd: process.cwd(),\n page: opts.stdin === true ? undefined : page,\n topics: resolvedTopics,\n stdin: opts.stdin,\n stdinInput: opts.stdin === true ? await readStdin() : undefined,\n wiki: opts.wiki,\n });\n emit(result);\n },\n );\n\n program\n .command(\"untag <page> <topic>\")\n .description(\"remove a topic from a page's frontmatter\")\n .option(\"--wiki <name>\", \"target a specific registered wiki\")\n .action(\n async (page: string, topic: string, opts: { wiki?: string }) => {\n await autoRegisterIfNeeded(process.cwd());\n const result = await runUntag({\n cwd: process.cwd(),\n page,\n topic,\n wiki: opts.wiki,\n });\n emit(result);\n },\n );\n\n const topics = program\n .command(\"topics\")\n .description(\"manage the topic DAG\");\n\n topics\n .command(\"list\", { isDefault: true })\n .description(\"list all topics with page counts\")\n .option(\"--wiki <name>\", \"target a specific registered wiki\")\n .option(\"--json\", \"emit structured JSON\")\n .action(async (opts: { wiki?: string; json?: boolean }) => {\n await autoRegisterIfNeeded(process.cwd());\n const result = await runTopicsList({\n cwd: process.cwd(),\n wiki: opts.wiki,\n json: opts.json,\n });\n emit(result);\n });\n\n topics\n .command(\"show <slug>\")\n .description(\"print a topic's metadata, parents, children, and pages\")\n .option(\"--descendants\", \"include pages tagged with descendant topics\")\n .option(\"--wiki <name>\", \"target a specific registered wiki\")\n .option(\"--json\", \"emit structured JSON\")\n .action(\n async (\n slug: string,\n opts: { descendants?: boolean; wiki?: string; json?: boolean },\n ) => {\n await autoRegisterIfNeeded(process.cwd());\n const result = await runTopicsShow({\n cwd: process.cwd(),\n slug,\n descendants: opts.descendants,\n wiki: opts.wiki,\n json: opts.json,\n });\n emit(result);\n },\n );\n\n topics\n .command(\"create <name>\")\n .description(\"create a topic (rejects if --parent slug does not exist)\")\n .option(\"--parent <slug>\", \"parent topic slug (repeat for multiple parents)\", collectOption, [] as string[])\n .option(\"--wiki <name>\", \"target a specific registered wiki\")\n .action(\n async (name: string, opts: { parent?: string[]; wiki?: string }) => {\n await autoRegisterIfNeeded(process.cwd());\n const result = await runTopicsCreate({\n cwd: process.cwd(),\n name,\n parents: opts.parent,\n wiki: opts.wiki,\n });\n emit(result);\n },\n );\n\n topics\n .command(\"link <child> <parent>\")\n .description(\"add a DAG edge (cycle-checked)\")\n .option(\"--wiki <name>\", \"target a specific registered wiki\")\n .action(async (child: string, parent: string, opts: { wiki?: string }) => {\n await autoRegisterIfNeeded(process.cwd());\n const result = await runTopicsLink({\n cwd: process.cwd(),\n child,\n parent,\n wiki: opts.wiki,\n });\n emit(result);\n });\n\n topics\n .command(\"unlink <child> <parent>\")\n .description(\"remove a DAG edge\")\n .option(\"--wiki <name>\", \"target a specific registered wiki\")\n .action(async (child: string, parent: string, opts: { wiki?: string }) => {\n await autoRegisterIfNeeded(process.cwd());\n const result = await runTopicsUnlink({\n cwd: process.cwd(),\n child,\n parent,\n wiki: opts.wiki,\n });\n emit(result);\n });\n\n topics\n .command(\"rename <old> <new>\")\n .description(\"rename a topic; rewrites every affected page's frontmatter\")\n .option(\"--wiki <name>\", \"target a specific registered wiki\")\n .action(async (oldSlug: string, newSlug: string, opts: { wiki?: string }) => {\n await autoRegisterIfNeeded(process.cwd());\n const result = await runTopicsRename({\n cwd: process.cwd(),\n oldSlug,\n newSlug,\n wiki: opts.wiki,\n });\n emit(result);\n });\n\n topics\n .command(\"delete <slug>\")\n .description(\"delete a topic; untags every affected page\")\n .option(\"--wiki <name>\", \"target a specific registered wiki\")\n .action(async (slug: string, opts: { wiki?: string }) => {\n await autoRegisterIfNeeded(process.cwd());\n const result = await runTopicsDelete({\n cwd: process.cwd(),\n slug,\n wiki: opts.wiki,\n });\n emit(result);\n });\n\n topics\n .command(\"describe <slug> <text>\")\n .description(\"set a topic's one-line description\")\n .option(\"--wiki <name>\", \"target a specific registered wiki\")\n .action(async (slug: string, text: string, opts: { wiki?: string }) => {\n await autoRegisterIfNeeded(process.cwd());\n const result = await runTopicsDescribe({\n cwd: process.cwd(),\n slug,\n description: text,\n wiki: opts.wiki,\n });\n emit(result);\n });\n}\n","import { existsSync } from \"node:fs\";\nimport { readFile } from \"node:fs/promises\";\nimport { basename, join } from \"node:path\";\n\nimport fg from \"fast-glob\";\nimport type Database from \"better-sqlite3\";\n\nimport { BLUE, BOLD, DIM, GREEN, RED, RST } from \"../ansi.js\";\nimport { parseDuration } from \"../indexer/duration.js\";\nimport { ensureFreshIndex } from \"../indexer/index.js\";\nimport { resolveWikiRoot } from \"../indexer/resolve-wiki.js\";\nimport { openIndex } from \"../indexer/schema.js\";\nimport { findEntry } from \"../registry/index.js\";\nimport { toKebabCase } from \"../slug.js\";\nimport { subtreeInDb } from \"../topics/dag.js\";\n\n/**\n * `almanac health` — flag problems in the wiki.\n *\n * Eight independent categories, each checked against the current index\n * and filesystem. Categories never throw each other off; one failing\n * is not a reason to skip the others.\n *\n * Scoping:\n * - `--topic <slug>` narrows every page-scoped category to pages\n * tagged with that topic OR any descendant topic (DAG traversal).\n * Topic-level categories (`empty_topics`) are narrowed to the\n * subtree itself.\n * - `--stdin` reads page slugs from stdin and limits page-scoped\n * categories to that set.\n *\n * Output:\n * - default: human-readable, grouped by category with counts.\n * - `--json`: one big object, shape = `HealthReport`.\n */\n\nexport interface HealthReport {\n orphans: { slug: string }[];\n stale: { slug: string; days_since_update: number }[];\n dead_refs: { slug: string; path: string }[];\n broken_links: { source_slug: string; target_slug: string }[];\n broken_xwiki: { source_slug: string; target_wiki: string; target_slug: string }[];\n empty_topics: { slug: string }[];\n empty_pages: { slug: string }[];\n slug_collisions: { slug: string; paths: string[] }[];\n}\n\nexport interface HealthOptions {\n cwd: string;\n wiki?: string;\n topic?: string;\n stale?: string;\n stdin?: boolean;\n stdinInput?: string;\n json?: boolean;\n}\n\nexport interface HealthCommandOutput {\n stdout: string;\n stderr: string;\n exitCode: number;\n}\n\n/**\n * Default `--stale` window. 90 days matches the spec. Users can tune\n * with `--stale <duration>` using the shared parser.\n */\nconst DEFAULT_STALE_SECONDS = 90 * 24 * 60 * 60;\n\nexport async function runHealth(\n options: HealthOptions,\n): Promise<HealthCommandOutput> {\n const repoRoot = await resolveWikiRoot({ cwd: options.cwd, wiki: options.wiki });\n await ensureFreshIndex({ repoRoot });\n\n const almanacDir = join(repoRoot, \".almanac\");\n const pagesDir = join(almanacDir, \"pages\");\n const db = openIndex(join(almanacDir, \"index.db\"));\n\n try {\n const staleSeconds = options.stale !== undefined\n ? parseDuration(options.stale)\n : DEFAULT_STALE_SECONDS;\n\n const scope = resolveScope(db, options);\n\n const report: HealthReport = {\n orphans: findOrphans(db, scope),\n stale: findStale(db, scope, staleSeconds),\n dead_refs: await findDeadRefs(db, scope, repoRoot),\n broken_links: findBrokenLinks(db, scope),\n broken_xwiki: await findBrokenXwiki(db, scope),\n empty_topics: findEmptyTopics(db, scope),\n empty_pages: await findEmptyPages(db, scope, pagesDir),\n slug_collisions: await findSlugCollisions(pagesDir),\n };\n\n if (options.json === true) {\n return {\n stdout: `${JSON.stringify(report, null, 2)}\\n`,\n stderr: \"\",\n exitCode: 0,\n };\n }\n\n return {\n stdout: formatReport(report),\n stderr: \"\",\n exitCode: 0,\n };\n } finally {\n db.close();\n }\n}\n\ninterface HealthScope {\n /** When non-null, restrict page-scoped checks to these slugs. */\n pages: Set<string> | null;\n /** When non-null, restrict topic-scoped checks to these slugs. */\n topics: Set<string> | null;\n}\n\n/**\n * Compute the active page/topic scope from `--topic` and `--stdin`\n * flags. Both null = no restriction (report everything).\n */\nfunction resolveScope(db: Database.Database, options: HealthOptions): HealthScope {\n let pages: Set<string> | null = null;\n let topics: Set<string> | null = null;\n\n if (options.topic !== undefined) {\n const rootSlug = toKebabCase(options.topic);\n if (rootSlug.length > 0) {\n const subtree = subtreeInDb(db, rootSlug);\n topics = new Set(subtree);\n const placeholders = subtree.map(() => \"?\").join(\", \");\n const rows = db\n .prepare<unknown[], { page_slug: string }>(\n `SELECT DISTINCT page_slug FROM page_topics\n WHERE topic_slug IN (${placeholders})`,\n )\n .all(...subtree);\n pages = new Set(rows.map((r) => r.page_slug));\n }\n }\n\n if (options.stdin === true && options.stdinInput !== undefined) {\n const stdinPages = new Set<string>();\n for (const line of options.stdinInput.split(/\\r?\\n/)) {\n const s = line.trim();\n if (s.length > 0) stdinPages.add(s);\n }\n // Intersect with any existing topic-scoped set.\n if (pages === null) pages = stdinPages;\n else {\n const out = new Set<string>();\n for (const s of stdinPages) if (pages.has(s)) out.add(s);\n pages = out;\n }\n }\n\n return { pages, topics };\n}\n\nfunction inPageScope(scope: HealthScope, slug: string): boolean {\n if (scope.pages === null) return true;\n return scope.pages.has(slug);\n}\n\n// ─────────────────────────────────────────────────────────────────────\n// individual checks\n// ─────────────────────────────────────────────────────────────────────\n\n/**\n * Pages with zero `topics:`. Archived pages are exempt — the spec\n * excludes them from search by default and they're inherently\n * \"retired\", not \"abandoned\".\n */\nfunction findOrphans(\n db: Database.Database,\n scope: HealthScope,\n): { slug: string }[] {\n const rows = db\n .prepare<[], { slug: string }>(\n `SELECT p.slug FROM pages p\n WHERE p.archived_at IS NULL\n AND NOT EXISTS (\n SELECT 1 FROM page_topics pt WHERE pt.page_slug = p.slug\n )\n ORDER BY p.slug`,\n )\n .all();\n return rows.filter((r) => inPageScope(scope, r.slug));\n}\n\n/**\n * Active pages whose `updated_at` is older than `staleSeconds`. We\n * report `days_since_update` rather than a raw timestamp because the\n * spec's example output (\"old-architecture (124 days)\") shows that.\n */\nfunction findStale(\n db: Database.Database,\n scope: HealthScope,\n staleSeconds: number,\n): { slug: string; days_since_update: number }[] {\n const now = Math.floor(Date.now() / 1000);\n const threshold = now - staleSeconds;\n const rows = db\n .prepare<[number], { slug: string; updated_at: number }>(\n `SELECT slug, updated_at FROM pages\n WHERE archived_at IS NULL AND updated_at < ?\n ORDER BY updated_at ASC`,\n )\n .all(threshold);\n return rows\n .filter((r) => inPageScope(scope, r.slug))\n .map((r) => ({\n slug: r.slug,\n days_since_update: Math.floor((now - r.updated_at) / (60 * 60 * 24)),\n }));\n}\n\n/**\n * `file_refs` whose target paths no longer exist on disk. We `stat`\n * each referenced path, relative to the repo root, and report misses.\n *\n * Only checks active pages — archived pages are allowed to reference\n * files that have since been deleted (that's often why they were\n * archived in the first place).\n *\n * We stat the `original_path` (author's casing) rather than the\n * lowercased `path` — on case-sensitive filesystems like Linux, stat\n * of a lowercased alias of `src/Dockerfile` returns ENOENT even\n * though the file exists. macOS and Windows are case-insensitive so\n * either form resolves there; using the original consistently means\n * the code behaves identically on every host.\n */\nasync function findDeadRefs(\n db: Database.Database,\n scope: HealthScope,\n repoRoot: string,\n): Promise<{ slug: string; path: string }[]> {\n const rows = db\n .prepare<\n [],\n { slug: string; path: string; original_path: string; is_dir: number }\n >(\n `SELECT p.slug, r.path, r.original_path, r.is_dir\n FROM file_refs r\n JOIN pages p ON p.slug = r.page_slug\n WHERE p.archived_at IS NULL\n ORDER BY p.slug, r.path`,\n )\n .all();\n const out: { slug: string; path: string }[] = [];\n for (const r of rows) {\n if (!inPageScope(scope, r.slug)) continue;\n const abs = join(repoRoot, r.original_path);\n if (!existsSync(abs)) {\n // Surface the author's casing in the report — matches what's in\n // the user's frontmatter/wikilink, which is what they'll search\n // for when fixing the miss.\n out.push({ slug: r.slug, path: r.original_path });\n }\n }\n return out;\n}\n\n/**\n * Wikilinks whose target slug has no row in `pages`. Every other\n * page-scoped check filters archived source pages out; this one and\n * `findBrokenXwiki` follow the same rule so the report doesn't flag\n * broken links from pages that have been retired.\n */\nfunction findBrokenLinks(\n db: Database.Database,\n scope: HealthScope,\n): { source_slug: string; target_slug: string }[] {\n const rows = db\n .prepare<[], { source_slug: string; target_slug: string }>(\n `SELECT w.source_slug, w.target_slug\n FROM wikilinks w\n JOIN pages src ON src.slug = w.source_slug\n LEFT JOIN pages tgt ON tgt.slug = w.target_slug\n WHERE tgt.slug IS NULL AND src.archived_at IS NULL\n ORDER BY w.source_slug, w.target_slug`,\n )\n .all();\n return rows.filter((r) => inPageScope(scope, r.source_slug));\n}\n\n/**\n * Cross-wiki links whose target wiki isn't registered OR whose path\n * is unreachable. Per the plan we stop at \"wiki unregistered or path\n * missing\" — walking into the other wiki's `index.db` to check the\n * slug exists is explicitly out of scope for slice 3 (documented in\n * the plan). A follow-up slice can deepen this.\n */\nasync function findBrokenXwiki(\n db: Database.Database,\n scope: HealthScope,\n): Promise<{ source_slug: string; target_wiki: string; target_slug: string }[]> {\n const rows = db\n .prepare<\n [],\n { source_slug: string; target_wiki: string; target_slug: string }\n >(\n // Same archived-source filter as `findBrokenLinks`. Retired pages\n // shouldn't spam the report with links to wikis that may have\n // been intentionally retired too.\n `SELECT x.source_slug, x.target_wiki, x.target_slug\n FROM cross_wiki_links x\n JOIN pages src ON src.slug = x.source_slug\n WHERE src.archived_at IS NULL\n ORDER BY x.source_slug, x.target_wiki, x.target_slug`,\n )\n .all();\n const out: { source_slug: string; target_wiki: string; target_slug: string }[] = [];\n // Cache the registry lookup so we only resolve each wiki once.\n const reachableCache = new Map<string, boolean>();\n for (const r of rows) {\n if (!inPageScope(scope, r.source_slug)) continue;\n let ok = reachableCache.get(r.target_wiki);\n if (ok === undefined) {\n const entry = await findEntry({ name: r.target_wiki });\n ok = entry !== null && existsSync(join(entry.path, \".almanac\"));\n reachableCache.set(r.target_wiki, ok);\n }\n if (!ok) {\n out.push({\n source_slug: r.source_slug,\n target_wiki: r.target_wiki,\n target_slug: r.target_slug,\n });\n }\n }\n return out;\n}\n\n/** Topics with zero pages. */\nfunction findEmptyTopics(\n db: Database.Database,\n scope: HealthScope,\n): { slug: string }[] {\n const rows = db\n .prepare<[], { slug: string }>(\n `SELECT t.slug FROM topics t\n WHERE NOT EXISTS (\n SELECT 1 FROM page_topics pt WHERE pt.topic_slug = t.slug\n )\n ORDER BY t.slug`,\n )\n .all();\n if (scope.topics === null) return rows;\n return rows.filter((r) => scope.topics!.has(r.slug));\n}\n\n/**\n * Pages whose body is effectively empty — only frontmatter, maybe a\n * heading, no prose. \"Empty\" = after dropping frontmatter and heading\n * lines, the remaining non-blank non-whitespace content is < 40\n * characters. This matches the test from the plan: \"a page with only\n * frontmatter + heading is empty; with a paragraph it's not.\"\n *\n * Archived pages are exempt — deliberately minimal archive stubs\n * shouldn't be flagged.\n */\nasync function findEmptyPages(\n db: Database.Database,\n scope: HealthScope,\n pagesDir: string,\n): Promise<{ slug: string }[]> {\n const rows = db\n .prepare<[], { slug: string; file_path: string }>(\n `SELECT slug, file_path FROM pages\n WHERE archived_at IS NULL\n ORDER BY slug`,\n )\n .all();\n const out: { slug: string }[] = [];\n for (const r of rows) {\n if (!inPageScope(scope, r.slug)) continue;\n let raw: string;\n try {\n raw = await readFile(r.file_path, \"utf8\");\n } catch {\n continue;\n }\n // Strip frontmatter if present.\n const m = raw.match(/^---\\r?\\n[\\s\\S]*?\\r?\\n---\\r?\\n?([\\s\\S]*)$/);\n const body = m !== null ? (m[1] ?? \"\") : raw;\n // \"Empty\" = after dropping frontmatter, heading lines, and blank\n // lines, nothing non-trivial remains. A single-line wikilink or\n // one-sentence paragraph counts as content; a page with only a\n // heading (or a heading + whitespace) does not.\n //\n // `pagesDir` is accepted for parity with future content-resolution\n // checks (e.g., resolving includes); referenced so lint doesn't\n // complain about an unused parameter.\n void pagesDir;\n const hasSubstance = body\n .split(/\\r?\\n/)\n .some((l) => {\n const t = l.trim();\n if (t.length === 0) return false;\n if (t.startsWith(\"#\")) return false;\n return true;\n });\n if (!hasSubstance) {\n out.push({ slug: r.slug });\n }\n }\n return out;\n}\n\n/**\n * Walk `.almanac/pages/` and group filenames by their kebab-cased\n * slug. Any slug with >1 filename is a collision. We rescan rather\n * than reading a persisted table — indexing surfaces collisions only\n * as warnings, so a dedicated rescan gives us a definitive answer\n * without adding a new table.\n */\nasync function findSlugCollisions(\n pagesDir: string,\n): Promise<{ slug: string; paths: string[] }[]> {\n if (!existsSync(pagesDir)) return [];\n const files = await fg(\"**/*.md\", {\n cwd: pagesDir,\n absolute: false,\n onlyFiles: true,\n caseSensitiveMatch: true,\n });\n const bySlug = new Map<string, string[]>();\n for (const rel of files) {\n const slug = toKebabCase(basename(rel, \".md\"));\n if (slug.length === 0) continue;\n const list = bySlug.get(slug) ?? [];\n list.push(rel);\n bySlug.set(slug, list);\n }\n const out: { slug: string; paths: string[] }[] = [];\n for (const [slug, paths] of bySlug.entries()) {\n if (paths.length > 1) {\n out.push({ slug, paths: paths.sort() });\n }\n }\n out.sort((a, b) => a.slug.localeCompare(b.slug));\n return out;\n}\n\n// ─────────────────────────────────────────────────────────────────────\n// pretty-print\n// ─────────────────────────────────────────────────────────────────────\n\nfunction formatReport(r: HealthReport): string {\n const sections: string[] = [];\n sections.push(\n section(\n \"orphans\",\n r.orphans.length,\n r.orphans.map((o) => ` ${BLUE}${o.slug}${RST}`),\n ),\n );\n sections.push(\n section(\n \"stale\",\n r.stale.length,\n r.stale.map((s) => ` ${BLUE}${s.slug}${RST} ${DIM}(${s.days_since_update} days)${RST}`),\n ),\n );\n sections.push(\n section(\n \"dead-refs\",\n r.dead_refs.length,\n r.dead_refs.map((d) => ` ${BLUE}${d.slug}${RST} references ${d.path} ${DIM}(missing)${RST}`),\n ),\n );\n sections.push(\n section(\n \"broken-links\",\n r.broken_links.length,\n r.broken_links.map(\n (b) => ` ${BLUE}${b.source_slug}${RST} → ${b.target_slug} ${DIM}(target does not exist)${RST}`,\n ),\n ),\n );\n sections.push(\n section(\n \"broken-xwiki\",\n r.broken_xwiki.length,\n r.broken_xwiki.map(\n (b) =>\n ` ${BLUE}${b.source_slug}${RST} → ${b.target_wiki}:${b.target_slug} ${DIM}(wiki unregistered or unreachable)${RST}`,\n ),\n ),\n );\n sections.push(\n section(\n \"empty-topics\",\n r.empty_topics.length,\n r.empty_topics.map((e) => ` ${BLUE}${e.slug}${RST}`),\n ),\n );\n sections.push(\n section(\n \"empty-pages\",\n r.empty_pages.length,\n r.empty_pages.map((e) => ` ${BLUE}${e.slug}${RST}`),\n ),\n );\n sections.push(\n section(\n \"slug-collisions\",\n r.slug_collisions.length,\n r.slug_collisions.map((c) => ` ${BLUE}${c.slug}${RST}: ${c.paths.join(\", \")}`),\n ),\n );\n return `${sections.join(\"\\n\\n\")}\\n`;\n}\n\nfunction section(label: string, count: number, lines: string[]): string {\n if (count === 0) return `${BOLD}${label}${RST} ${GREEN}(0): (ok)${RST}`;\n return `${BOLD}${label}${RST} ${RED}(${count})${RST}:\\n${lines.join(\"\\n\")}`;\n}\n","/**\n * Parse a compact duration string of the form `<N><unit>` into seconds.\n *\n * Accepted units (from the spec, `--since` / `--stale`):\n * - `m` — minutes\n * - `h` — hours\n * - `d` — days\n * - `w` — weeks\n *\n * Examples: `2w` → 1209600, `30d` → 2592000, `12h` → 43200.\n *\n * Anything else throws — the CLI surfaces the error with the usual\n * `almanac: <message>` prefix, which is clearer than silently treating\n * `2weeks` or `30 days` as zero.\n */\nexport function parseDuration(input: string): number {\n const trimmed = input.trim();\n const m = trimmed.match(/^(\\d+)([mhdw])$/);\n if (m === null) {\n throw new Error(\n `invalid duration \"${input}\" (expected Nw, Nd, Nh, or Nm — e.g. 2w, 30d)`,\n );\n }\n const n = Number.parseInt(m[1] ?? \"0\", 10);\n const unit = m[2];\n switch (unit) {\n case \"m\":\n return n * 60;\n case \"h\":\n return n * 60 * 60;\n case \"d\":\n return n * 60 * 60 * 24;\n case \"w\":\n return n * 60 * 60 * 24 * 7;\n default:\n // Unreachable — regex pins the unit — but satisfies exhaustiveness.\n throw new Error(`invalid duration unit \"${unit ?? \"\"}\"`);\n }\n}\n","import { existsSync } from \"node:fs\";\n\nimport { BLUE, BOLD, DIM, RST } from \"../ansi.js\";\nimport {\n dropEntry,\n readRegistry,\n type RegistryEntry,\n} from \"../registry/index.js\";\n\nexport interface ListOptions {\n json?: boolean;\n drop?: string;\n}\n\nexport interface ListCommandOutput {\n stdout: string;\n exitCode: number;\n}\n\n/**\n * `almanac list` — the global discovery surface. Three modes:\n *\n * - default: pretty table of reachable wikis\n * - `--json`: structured output (reachable wikis only, by default)\n * - `--drop <name>`: explicit removal, then exits\n *\n * **Unreachable paths are silently skipped in the default output**, but\n * never auto-dropped. This is the registry hygiene rule from the design —\n * branch switches, unmounted drives, and VM-offline repos should not cost\n * the user a registration. Only `--drop` removes entries.\n */\nexport async function listWikis(\n options: ListOptions,\n): Promise<ListCommandOutput> {\n if (options.drop !== undefined) {\n return handleDrop(options.drop);\n }\n\n const entries = await readRegistry();\n const reachable = entries.filter((e) => isReachable(e));\n\n if (options.json === true) {\n return { stdout: `${JSON.stringify(reachable, null, 2)}\\n`, exitCode: 0 };\n }\n\n return { stdout: formatPretty(reachable), exitCode: 0 };\n}\n\nasync function handleDrop(name: string): Promise<ListCommandOutput> {\n const removed = await dropEntry(name);\n if (removed === null) {\n return {\n stdout: `no registry entry named \"${name}\"\\n`,\n exitCode: 1,\n };\n }\n return {\n stdout: `removed \"${removed.name}\" (${removed.path})\\n`,\n exitCode: 0,\n };\n}\n\n/**\n * A registry path is \"reachable\" if something still exists at that path.\n * We use `existsSync` rather than `stat` — we don't care whether the path\n * is a directory or has a `.almanac/` inside; we only hide it from default\n * output when the path itself is gone (e.g., repo deleted, drive\n * unmounted).\n */\nfunction isReachable(entry: RegistryEntry): boolean {\n if (entry.path.length === 0) return false;\n return existsSync(entry.path);\n}\n\n/**\n * Human-readable listing. Empty state prints a gentle hint rather than a\n * blank screen, and entries render in registration order (chronological,\n * since `addEntry` appends).\n */\nfunction formatPretty(entries: RegistryEntry[]): string {\n if (entries.length === 0) {\n return `${DIM}no wikis registered. run \\`almanac bootstrap\\` in a repo to create one.${RST}\\n`;\n }\n\n // Column-width the name for alignment; cap at 30 so absurd names don't\n // stretch the whole table.\n const nameWidth = Math.min(\n 30,\n entries.reduce((w, e) => Math.max(w, e.name.length), 0),\n );\n\n const lines: string[] = [];\n for (const entry of entries) {\n const name = entry.name.padEnd(nameWidth);\n const desc = entry.description.length > 0 ? entry.description : \"—\";\n lines.push(`${BLUE}${BOLD}${name}${RST} ${desc}`);\n lines.push(`${\" \".repeat(nameWidth)} ${DIM}${entry.path}${RST}`);\n }\n return `${lines.join(\"\\n\")}\\n`;\n}\n","import { join } from \"node:path\";\n\nimport type Database from \"better-sqlite3\";\n\nimport { BLUE, RST } from \"../ansi.js\";\nimport { parseDuration } from \"../indexer/duration.js\";\nimport { ensureFreshIndex } from \"../indexer/index.js\";\nimport { looksLikeDir, normalizePath } from \"../indexer/paths.js\";\nimport { resolveWikiRoot } from \"../indexer/resolve-wiki.js\";\nimport { openIndex } from \"../indexer/schema.js\";\n\nexport interface SearchOptions {\n cwd: string;\n query?: string;\n topics: string[];\n mentions?: string;\n since?: string;\n stale?: string;\n orphan?: boolean;\n includeArchive?: boolean;\n archived?: boolean;\n wiki?: string;\n json?: boolean;\n limit?: number;\n}\n\nexport interface SearchResult {\n slug: string;\n title: string | null;\n updated_at: number;\n archived_at: number | null;\n superseded_by: string | null;\n topics: string[];\n}\n\nexport interface SearchCommandOutput {\n stdout: string;\n stderr: string;\n exitCode: number;\n}\n\n/**\n * `almanac search` — the core query surface.\n *\n * Filters compose with AND logic. The implementation is deliberately\n * pedestrian: build a list of clauses + params, join them with `AND`,\n * intersect topic filters by requiring one subquery per `--topic`. No\n * clever query-planner tricks needed for the sizes we handle (<10k pages).\n *\n * All output ordering is stable: `updated_at DESC, slug ASC`. FTS5 rank\n * is layered on top when the user passed a text query — we ORDER BY rank\n * first, then fall through to the default.\n */\nexport async function runSearch(\n options: SearchOptions,\n): Promise<SearchCommandOutput> {\n const repoRoot = await resolveWikiRoot({\n cwd: options.cwd,\n wiki: options.wiki,\n });\n await ensureFreshIndex({ repoRoot });\n\n const dbPath = join(repoRoot, \".almanac\", \"index.db\");\n const db = openIndex(dbPath);\n\n try {\n const rows = executeQuery(db, options);\n const limited =\n options.limit !== undefined && options.limit >= 0\n ? rows.slice(0, options.limit)\n : rows;\n\n const stdout = formatResults(limited, options);\n const stderr = buildStderr(limited, options);\n return { stdout, stderr, exitCode: 0 };\n } finally {\n db.close();\n }\n}\n\ninterface PageRow {\n slug: string;\n title: string | null;\n updated_at: number;\n archived_at: number | null;\n superseded_by: string | null;\n}\n\nfunction executeQuery(\n db: Database.Database,\n options: SearchOptions,\n): SearchResult[] {\n const whereClauses: string[] = [];\n const params: (string | number)[] = [];\n\n // Archive scope. Three modes, mutually exclusive in practice:\n // - default → active only\n // - --include-archive → active + archived\n // - --archived → archived only\n // `--archived` wins over `--include-archive` when both are passed —\n // being explicit about \"only archived\" is strictly narrower than\n // \"include archived\", so intersecting them yields \"only archived\".\n if (options.archived === true) {\n whereClauses.push(\"p.archived_at IS NOT NULL\");\n } else if (options.includeArchive !== true) {\n whereClauses.push(\"p.archived_at IS NULL\");\n }\n\n // --topic foo --topic bar → page must have BOTH. We add one EXISTS\n // subquery per topic rather than grouping, which keeps param order\n // simple and the plan readable.\n for (const rawTopic of options.topics) {\n const topicSlug = slugForTopic(rawTopic);\n if (topicSlug.length === 0) continue;\n whereClauses.push(\n \"EXISTS (SELECT 1 FROM page_topics pt WHERE pt.page_slug = p.slug AND pt.topic_slug = ?)\",\n );\n params.push(topicSlug);\n }\n\n // --mentions: look for a file_refs row on this page that either\n // matches exactly, OR is a containing folder (is_dir=1 and the query\n // path starts with the row's path), OR the row itself lives inside\n // the queried folder. See spec → \"Graph querying → Query examples\".\n //\n // We deliberately avoid GLOB on the RHS of the comparison with stored\n // `r.path`, because stored paths can legitimately contain GLOB\n // metacharacters — Next.js dynamic routes like `src/[id]/page.tsx`\n // store a literal `[id]`, and `[abc]` is a SQL GLOB character class.\n // Concatenating `r.path || '*'` into a GLOB pattern would make\n // `src/[id]/page.tsx*` match `src/i/page.tsx` (spurious hit on the\n // character class).\n //\n // Instead we enumerate the prefix folders in JS and use parameterized\n // equality. For `src/checkout/handler.ts` the prefixes are\n // `['src/', 'src/checkout/']`; any file_refs row with is_dir=1 and\n // a path in that list is a containing folder of the queried file.\n // This also lets SQLite use `idx_file_refs_path` as an equality\n // probe rather than a range scan.\n if (options.mentions !== undefined && options.mentions.length > 0) {\n const isDir = looksLikeDir(options.mentions);\n const norm = normalizePath(options.mentions, isDir);\n if (isDir) {\n // Query is a folder. Match: the exact folder, OR any file/sub-\n // folder whose path starts with the folder prefix. The prefix\n // match is the one place we still need GLOB — but we escape any\n // wildcard metacharacters in `norm` first so a user-supplied\n // `src/[id]/` query is treated as a literal. Note: the query\n // path comes from the caller, not from stored data, but a user\n // typing `--mentions src/[id]/` should get the literal folder,\n // not a character class.\n const escaped = escapeGlobMeta(norm);\n whereClauses.push(\n `EXISTS (\n SELECT 1 FROM file_refs r\n WHERE r.page_slug = p.slug\n AND (r.path = ? OR r.path GLOB ?)\n )`,\n );\n params.push(norm, `${escaped}*`);\n } else {\n // Query is a file. Match: the exact file, OR any folder whose\n // path is a prefix of this file. Build the prefix list in JS and\n // probe file_refs with equality — no GLOB on stored values.\n const prefixes = parentFolderPrefixes(norm);\n if (prefixes.length === 0) {\n whereClauses.push(\n `EXISTS (\n SELECT 1 FROM file_refs r\n WHERE r.page_slug = p.slug AND r.path = ?\n )`,\n );\n params.push(norm);\n } else {\n const placeholders = prefixes.map(() => \"?\").join(\", \");\n whereClauses.push(\n `EXISTS (\n SELECT 1 FROM file_refs r\n WHERE r.page_slug = p.slug\n AND (\n r.path = ?\n OR (r.is_dir = 1 AND r.path IN (${placeholders}))\n )\n )`,\n );\n params.push(norm, ...prefixes);\n }\n }\n }\n\n const now = Math.floor(Date.now() / 1000);\n\n if (options.since !== undefined) {\n const seconds = parseDuration(options.since);\n whereClauses.push(\"p.updated_at >= ?\");\n params.push(now - seconds);\n }\n\n if (options.stale !== undefined) {\n const seconds = parseDuration(options.stale);\n whereClauses.push(\"p.updated_at < ?\");\n params.push(now - seconds);\n }\n\n if (options.orphan === true) {\n whereClauses.push(\n \"NOT EXISTS (SELECT 1 FROM page_topics pt WHERE pt.page_slug = p.slug)\",\n );\n }\n\n // FTS5 text query. When a text query is supplied we JOIN against\n // `fts_pages` so we can ORDER BY its `rank` column — lower (more\n // negative) ranks are better matches. Without a query we skip the\n // join entirely.\n let sql: string;\n if (options.query !== undefined && options.query.trim().length > 0) {\n const ftsExpr = buildFtsQuery(options.query);\n sql = `\n SELECT p.slug, p.title, p.updated_at, p.archived_at, p.superseded_by\n FROM pages p\n JOIN fts_pages f ON f.slug = p.slug\n WHERE fts_pages MATCH ?\n ${whereClauses.length > 0 ? `AND ${whereClauses.join(\" AND \")}` : \"\"}\n ORDER BY f.rank ASC, p.updated_at DESC, p.slug ASC\n `;\n // MATCH param goes first (it's the first `?` in the compiled SQL).\n params.unshift(ftsExpr);\n } else {\n sql = buildSql(whereClauses);\n }\n\n const rows = db.prepare<unknown[], PageRow>(sql).all(...params);\n\n // Attach topics in a second pass — simpler than a correlated\n // `GROUP_CONCAT`, and the output rows are small enough that N+1 on a\n // single prepared statement is fine.\n const topicStmt = db.prepare<[string], { topic_slug: string }>(\n \"SELECT topic_slug FROM page_topics WHERE page_slug = ? ORDER BY topic_slug\",\n );\n const out: SearchResult[] = rows.map((row) => ({\n slug: row.slug,\n title: row.title,\n updated_at: row.updated_at,\n archived_at: row.archived_at,\n superseded_by: row.superseded_by,\n topics: topicStmt.all(row.slug).map((t) => t.topic_slug),\n }));\n\n return out;\n}\n\nfunction buildSql(whereClauses: string[]): string {\n const where =\n whereClauses.length > 0 ? `WHERE ${whereClauses.join(\" AND \")}` : \"\";\n return `\n SELECT p.slug, p.title, p.updated_at, p.archived_at, p.superseded_by\n FROM pages p\n ${where}\n ORDER BY p.updated_at DESC, p.slug ASC\n `;\n}\n\n/**\n * Turn a user query into an FTS5 MATCH expression. FTS5's default\n * grammar gets unhappy with punctuation (hyphens, colons, slashes), so\n * we tokenize into alphanumeric runs and emit a conjunction of prefixed\n * tokens. Each token is suffixed with `*` so \"stri\" matches \"stripe\".\n *\n * Quoted input (`\"stripe webhook\"`) is treated as an FTS5 phrase query\n * — tokens must appear contiguously in that order. This matches shell\n * conventions where quoting something means \"match it literally\". We\n * strip the surrounding quotes, collapse inner punctuation to spaces,\n * and re-wrap in quotes for FTS5's phrase syntax. Any embedded `\"` in\n * the user input is dropped (FTS5 phrase syntax has no escape).\n *\n * Anything that tokenizes to empty (e.g. pure punctuation) falls back\n * to an empty MATCH, which yields no rows — which is the right answer.\n */\nfunction buildFtsQuery(raw: string): string {\n const trimmed = raw.trim();\n if (\n trimmed.length >= 2 &&\n trimmed.startsWith(\"\\\"\") &&\n trimmed.endsWith(\"\\\"\")\n ) {\n // Phrase mode. Strip outer quotes, lowercase, collapse non-alnum\n // runs to a single space, trim. Any surviving inner `\"` are\n // removed since FTS5 phrase syntax has no escape mechanism.\n const inner = trimmed\n .slice(1, -1)\n .toLowerCase()\n .replace(/[^a-z0-9]+/g, \" \")\n .trim();\n if (inner.length === 0) return \"\\\"\\\"\";\n return `\"${inner}\"`;\n }\n const tokens = trimmed\n .toLowerCase()\n .split(/[^a-z0-9]+/)\n .filter((t) => t.length > 0);\n if (tokens.length === 0) return \"\\\"\\\"\";\n return tokens.map((t) => `${t}*`).join(\" AND \");\n}\n\nfunction slugForTopic(raw: string): string {\n return raw\n .toLowerCase()\n .replace(/[^a-z0-9]+/g, \"-\")\n .replace(/^-+|-+$/g, \"\");\n}\n\n/**\n * For a normalized file path like `src/checkout/handler.ts`, enumerate\n * every containing folder in the form those folders are stored (trailing\n * slash): `['src/', 'src/checkout/']`. Empty for paths with no folder\n * separator (e.g. `README.md`) — a top-level file has no parent folder\n * recorded in `file_refs`.\n *\n * Used by the `--mentions <file>` path to let us probe `file_refs` with\n * equality instead of GLOB, sidestepping wildcard-escape bugs entirely.\n */\nfunction parentFolderPrefixes(filePath: string): string[] {\n const out: string[] = [];\n let cursor = 0;\n while (true) {\n const next = filePath.indexOf(\"/\", cursor);\n if (next === -1) break;\n // Slice includes the slash — matches how directories are stored.\n out.push(filePath.slice(0, next + 1));\n cursor = next + 1;\n }\n return out;\n}\n\n/**\n * Escape SQLite GLOB metacharacters (`*`, `?`, `[`) by wrapping each in\n * a single-character class. SQLite GLOB has no backslash-escape, so the\n * idiomatic trick is `[*]` → literal `*`, `[?]` → literal `?`, `[[]` →\n * literal `[`. `]` doesn't need escaping outside a class.\n *\n * We only need this on the *query* side — stored paths aren't\n * concatenated into GLOB patterns anymore (see --mentions handling).\n * But a user-typed `--mentions src/[id]/` should still match a stored\n * `src/[id]/` literally, not as a character class over `i` or `d`.\n */\nfunction escapeGlobMeta(s: string): string {\n return s.replace(/[\\*\\?\\[]/g, (ch) => `[${ch}]`);\n}\n\nfunction formatResults(\n rows: SearchResult[],\n options: SearchOptions,\n): string {\n if (options.json === true) {\n return `${JSON.stringify(rows, null, 2)}\\n`;\n }\n // Default output: one slug per line. Empty result = empty output (not\n // \"no results found\") — makes piping into xargs / subsequent commands\n // degrade gracefully.\n if (rows.length === 0) return \"\";\n return `${rows.map((r) => `${BLUE}${r.slug}${RST}`).join(\"\\n\")}\\n`;\n}\n\nfunction buildStderr(rows: SearchResult[], options: SearchOptions): string {\n // Spec: \"print warns if >50 when not --json\". The warning goes to\n // stderr so it doesn't corrupt pipelines that filter stdout.\n if (options.json === true) return \"\";\n // Empty-result breadcrumb (v0.1.3). Interviews showed users saw blank\n // stdout and concluded the wiki was broken rather than the query\n // genuinely matched nothing. A single `# 0 results` line to stderr\n // makes the outcome legible without corrupting stdout pipelines (the\n // downstream command still sees zero lines). `--json` mode is silent\n // because `[]` is the unambiguous empty signal there.\n if (rows.length === 0) {\n return \"# 0 results\\n\";\n }\n if (options.limit !== undefined) return \"\";\n if (rows.length > 50) {\n return `almanac: ${rows.length} results — consider --limit or a narrower query\\n`;\n }\n return \"\";\n}\n","import { readFile } from \"node:fs/promises\";\nimport { join } from \"node:path\";\n\nimport type Database from \"better-sqlite3\";\n\nimport { BLUE, DIM, RST } from \"../ansi.js\";\nimport { ensureFreshIndex } from \"../indexer/index.js\";\nimport { resolveWikiRoot } from \"../indexer/resolve-wiki.js\";\nimport { openIndex } from \"../indexer/schema.js\";\n\n/**\n * `almanac show <slug>` — structured view of a page.\n *\n * This file absorbs what used to be split across `show` (body), `info`\n * (metadata), and `path` (file path resolution). One command, multiple\n * view flags.\n *\n * Three output \"shapes\":\n *\n * 1. **Default** — metadata header + `\\n---\\n` separator + body. Useful\n * to skim a page and still see its topics/files/links at a glance.\n * 2. **View flags** (mutually exclusive-ish):\n * --json structured JSON, overrides everything else\n * --raw body only (alias --body; the old `show` behavior)\n * --meta metadata only, no body\n * --lead first paragraph of body only (cheap preview)\n * 3. **Field flags** (composable). Each selects one \"field\" of the page:\n * --title / --topics / --files / --links / --backlinks / --xwiki\n * --lineage / --updated / --path\n * A single field → bare, pipe-friendly output (one item per line).\n * Multiple fields → labeled sections, one per flag.\n *\n * `--stdin` is always JSON Lines (one record per line). This avoids the\n * separator ambiguity with markdown `---` in bulk output: the old `info`\n * used a human-readable array; the old `show` used `\\n---\\n` which\n * collided with page frontmatter delimiters.\n */\nexport interface ShowOptions {\n cwd: string;\n slug?: string;\n stdin?: boolean;\n stdinInput?: string;\n wiki?: string;\n\n // View modes (mutually exclusive-ish — precedence: json > raw > meta > lead > default).\n json?: boolean;\n raw?: boolean; // alias: body\n meta?: boolean;\n lead?: boolean;\n\n // Composable field flags.\n title?: boolean;\n topics?: boolean;\n files?: boolean;\n links?: boolean;\n backlinks?: boolean;\n xwiki?: boolean;\n lineage?: boolean;\n updated?: boolean;\n path?: boolean;\n}\n\nexport interface ShowCommandOutput {\n stdout: string;\n stderr: string;\n exitCode: number;\n}\n\n/**\n * The structured record emitted by `--json`. Also the shape we read into\n * in order to compose any other output. Keeping this a plain flat object\n * means callers can JSON.parse downstream tooling without chasing nested\n * subschemas.\n */\nexport interface ShowRecord {\n slug: string;\n title: string | null;\n file_path: string;\n updated_at: number;\n archived_at: number | null;\n superseded_by: string | null;\n supersedes: string[];\n topics: string[];\n file_refs: Array<{ path: string; is_dir: boolean }>;\n wikilinks_out: string[];\n wikilinks_in: string[];\n cross_wiki_links: Array<{ wiki: string; target: string }>;\n body: string;\n}\n\nexport async function runShow(\n options: ShowOptions,\n): Promise<ShowCommandOutput> {\n const repoRoot = await resolveWikiRoot({\n cwd: options.cwd,\n wiki: options.wiki,\n });\n await ensureFreshIndex({ repoRoot });\n\n const dbPath = join(repoRoot, \".almanac\", \"index.db\");\n const db = openIndex(dbPath);\n\n try {\n const slugs = collectSlugs(options);\n if (slugs.length === 0) {\n return {\n stdout: \"\",\n stderr: \"almanac: show requires a slug (or --stdin)\\n\",\n exitCode: 1,\n };\n }\n\n const records: ShowRecord[] = [];\n const missing: string[] = [];\n for (const slug of slugs) {\n const rec = await fetchRecord(db, slug);\n if (rec === null) {\n missing.push(slug);\n continue;\n }\n records.push(rec);\n }\n\n const bulk = options.stdin === true;\n const stdout = bulk\n ? formatBulk(records)\n : formatSingle(records, options);\n\n const stderr = missing\n .map((s) => `almanac: no such page \"${s}\"\\n`)\n .join(\"\");\n\n return {\n stdout,\n stderr,\n exitCode: missing.length > 0 ? 1 : 0,\n };\n } finally {\n db.close();\n }\n}\n\n// ─── Record fetch ────────────────────────────────────────────────────\n\nasync function fetchRecord(\n db: Database.Database,\n slug: string,\n): Promise<ShowRecord | null> {\n const pageRow = db\n .prepare<\n [string],\n {\n slug: string;\n title: string | null;\n file_path: string;\n updated_at: number;\n archived_at: number | null;\n superseded_by: string | null;\n }\n >(\n \"SELECT slug, title, file_path, updated_at, archived_at, superseded_by FROM pages WHERE slug = ?\",\n )\n .get(slug);\n if (pageRow === undefined) return null;\n\n const topics = db\n .prepare<[string], { topic_slug: string }>(\n \"SELECT topic_slug FROM page_topics WHERE page_slug = ? ORDER BY topic_slug\",\n )\n .all(slug)\n .map((r) => r.topic_slug);\n\n const refs = db\n .prepare<[string], { original_path: string; is_dir: number }>(\n \"SELECT original_path, is_dir FROM file_refs WHERE page_slug = ? ORDER BY original_path\",\n )\n .all(slug)\n .map((r) => ({ path: r.original_path, is_dir: r.is_dir === 1 }));\n\n const linksOut = db\n .prepare<[string], { target_slug: string }>(\n \"SELECT target_slug FROM wikilinks WHERE source_slug = ? ORDER BY target_slug\",\n )\n .all(slug)\n .map((r) => r.target_slug);\n\n const linksIn = db\n .prepare<[string], { source_slug: string }>(\n \"SELECT source_slug FROM wikilinks WHERE target_slug = ? ORDER BY source_slug\",\n )\n .all(slug)\n .map((r) => r.source_slug);\n\n const xwiki = db\n .prepare<[string], { target_wiki: string; target_slug: string }>(\n \"SELECT target_wiki, target_slug FROM cross_wiki_links WHERE source_slug = ? ORDER BY target_wiki, target_slug\",\n )\n .all(slug)\n .map((r) => ({ wiki: r.target_wiki, target: r.target_slug }));\n\n const supersedesRows = db\n .prepare<[string], { slug: string }>(\n \"SELECT slug FROM pages WHERE superseded_by = ? ORDER BY slug\",\n )\n .all(slug)\n .map((r) => r.slug);\n\n // Read body (strip YAML frontmatter). `show` is the only command\n // permitted to read page content, per the spec.\n let body = \"\";\n try {\n body = stripFrontmatter(await readFile(pageRow.file_path, \"utf8\"));\n } catch {\n // If the page row exists but the file is unreadable (race with `git\n // mv`, permission change, …) we keep the record — everything else we\n // have is sourced from the index. Body renders as empty. The missing\n // view flags still work.\n }\n\n return {\n slug: pageRow.slug,\n title: pageRow.title,\n file_path: pageRow.file_path,\n updated_at: pageRow.updated_at,\n archived_at: pageRow.archived_at,\n superseded_by: pageRow.superseded_by,\n supersedes: supersedesRows,\n topics,\n file_refs: refs,\n wikilinks_out: linksOut,\n wikilinks_in: linksIn,\n cross_wiki_links: xwiki,\n body,\n };\n}\n\n// ─── Formatting ──────────────────────────────────────────────────────\n\nfunction formatBulk(records: ShowRecord[]): string {\n // JSON Lines. One record per line, trailing newline when non-empty.\n // Consumers split on `\\n` and `JSON.parse` each line.\n if (records.length === 0) return \"\";\n return records.map((r) => JSON.stringify(r)).join(\"\\n\") + \"\\n\";\n}\n\nfunction formatSingle(\n records: ShowRecord[],\n options: ShowOptions,\n): string {\n if (options.json === true) {\n // JSON always wins. Positional mode emits a single object (never an\n // array) for a found page, `null` for a missing one — matches the\n // shape contract from the old `info --json`.\n const only = records[0] ?? null;\n return `${JSON.stringify(only, null, 2)}\\n`;\n }\n return records.map((r) => formatRecord(r, options)).join(\"\");\n}\n\n/**\n * Figure out which fields the user asked for. Precedence:\n * 1. `--raw` / `--body` — body only. Everything else is ignored.\n * 2. `--meta` — metadata only, no body. Ignores `--lead`.\n * 3. `--lead` — first paragraph only.\n * 4. Any field flag (`--title`, `--topics`, …) set → those fields only.\n * 5. Nothing set → full view (metadata header + body).\n */\ntype FieldName =\n | \"title\"\n | \"topics\"\n | \"files\"\n | \"links\"\n | \"backlinks\"\n | \"xwiki\"\n | \"lineage\"\n | \"updated\"\n | \"path\";\n\nconst FIELD_ORDER: FieldName[] = [\n \"title\",\n \"topics\",\n \"files\",\n \"links\",\n \"backlinks\",\n \"xwiki\",\n \"lineage\",\n \"updated\",\n \"path\",\n];\n\nfunction selectedFields(options: ShowOptions): FieldName[] {\n const selected: FieldName[] = [];\n for (const f of FIELD_ORDER) {\n if (options[f] === true) selected.push(f);\n }\n return selected;\n}\n\nfunction formatRecord(rec: ShowRecord, options: ShowOptions): string {\n // 1. raw / body\n if (options.raw === true) {\n // Guarantee exactly one trailing newline. Without it, shell redirects\n // (`almanac show foo --raw > foo.md`) produce files missing a final\n // newline, which confuses concatenation and diff tools. We don't\n // collapse multiple trailing newlines — a page that ends with a\n // blank line is intentional.\n if (rec.body.length === 0) return \"\";\n return rec.body.endsWith(\"\\n\") ? rec.body : `${rec.body}\\n`;\n }\n\n // 4. Field flags (check before meta/lead so --meta + --title is unambiguous).\n const fields = selectedFields(options);\n if (fields.length > 0) {\n if (fields.length === 1) {\n return bareField(rec, fields[0]!);\n }\n return labeledFields(rec, fields);\n }\n\n // 2. meta only\n if (options.meta === true) {\n return metadataHeader(rec) + \"\\n\";\n }\n\n // 3. lead only\n if (options.lead === true) {\n return firstParagraph(rec.body) + \"\\n\";\n }\n\n // 5. Default — metadata header + separator + body.\n const header = metadataHeader(rec);\n const body = rec.body;\n const sep = body.length > 0 ? `\\n\\n${DIM}---${RST}\\n\\n` : \"\\n\";\n return header + sep + body;\n}\n\n/**\n * Single-field bare output. One item per line, no labels, no colons —\n * designed to be piped directly into another command. The exact format\n * per field tries to preserve \"scriptable by default\":\n *\n * --title → the title (or empty if null)\n * --topics → one topic slug per line\n * --files → one file ref per line; trailing slash for folders\n * --links → one outgoing slug per line\n * --backlinks → one incoming slug per line\n * --xwiki → one `wiki:slug` per line\n * --lineage → archived_at / supersedes / superseded_by, one per line\n * (only the ones that exist — silent when all absent)\n * --updated → ISO-8601 UTC timestamp\n * --path → absolute file path (replaces the old `almanac path`)\n */\nfunction bareField(rec: ShowRecord, field: FieldName): string {\n switch (field) {\n case \"title\":\n return (rec.title ?? \"\") + \"\\n\";\n case \"topics\":\n return rec.topics.map((t) => `${t}\\n`).join(\"\");\n case \"files\":\n return rec.file_refs\n .map((r) => `${r.path}\\n`)\n .join(\"\");\n case \"links\":\n return rec.wikilinks_out.map((t) => `${t}\\n`).join(\"\");\n case \"backlinks\":\n return rec.wikilinks_in.map((t) => `${t}\\n`).join(\"\");\n case \"xwiki\":\n return rec.cross_wiki_links\n .map((x) => `${x.wiki}:${x.target}\\n`)\n .join(\"\");\n case \"lineage\": {\n const lines: string[] = [];\n if (rec.archived_at !== null) {\n lines.push(\n `archived_at: ${new Date(rec.archived_at * 1000).toISOString()}`,\n );\n }\n if (rec.superseded_by !== null) {\n lines.push(`superseded_by: ${rec.superseded_by}`);\n }\n if (rec.supersedes.length > 0) {\n lines.push(`supersedes: ${rec.supersedes.join(\", \")}`);\n }\n return lines.length > 0 ? `${lines.join(\"\\n\")}\\n` : \"\";\n }\n case \"updated\":\n return `${new Date(rec.updated_at * 1000).toISOString()}\\n`;\n case \"path\":\n return `${rec.file_path}\\n`;\n }\n}\n\n/**\n * Multi-field labeled output. Each requested field renders as a labeled\n * section, in canonical order (the order of `FIELD_ORDER`, not the order\n * flags appeared on the command line). Sections are separated by blank\n * lines to make grep-by-label reliable.\n */\nfunction labeledFields(rec: ShowRecord, fields: FieldName[]): string {\n const parts: string[] = [];\n for (const f of fields) {\n parts.push(labeledSection(rec, f));\n }\n return parts.join(\"\\n\");\n}\n\nfunction labeledSection(rec: ShowRecord, field: FieldName): string {\n switch (field) {\n case \"title\":\n return `${DIM}title:${RST} ${rec.title ?? \"—\"}\\n`;\n case \"topics\":\n return rec.topics.length > 0\n ? `${DIM}topics:${RST} ${rec.topics.join(\", \")}\\n`\n : `${DIM}topics:${RST} —\\n`;\n case \"files\":\n return formatListSection(\n \"files\",\n rec.file_refs.map((r) => `${r.path}`),\n );\n case \"links\":\n return formatListSection(\"links\", rec.wikilinks_out);\n case \"backlinks\":\n return formatListSection(\"backlinks\", rec.wikilinks_in);\n case \"xwiki\":\n return formatListSection(\n \"xwiki\",\n rec.cross_wiki_links.map((x) => `${x.wiki}:${x.target}`),\n );\n case \"lineage\": {\n const lines: string[] = [`${DIM}lineage:${RST}`];\n if (rec.archived_at !== null) {\n lines.push(\n ` ${DIM}archived_at:${RST} ${new Date(rec.archived_at * 1000).toISOString()}`,\n );\n }\n if (rec.superseded_by !== null) {\n lines.push(` ${DIM}superseded_by:${RST} ${rec.superseded_by}`);\n }\n if (rec.supersedes.length > 0) {\n lines.push(` ${DIM}supersedes:${RST} ${rec.supersedes.join(\", \")}`);\n }\n if (lines.length === 1) lines.push(\" —\");\n return lines.join(\"\\n\") + \"\\n\";\n }\n case \"updated\":\n return `${DIM}updated:${RST} ${new Date(rec.updated_at * 1000).toISOString()}\\n`;\n case \"path\":\n return `${DIM}path:${RST} ${rec.file_path}\\n`;\n }\n}\n\nfunction formatListSection(label: string, items: string[]): string {\n if (items.length === 0) return `${DIM}${label}:${RST} —\\n`;\n if (items.length <= 3) return `${DIM}${label}:${RST} ${items.join(\", \")}\\n`;\n return `${DIM}${label}:${RST}\\n${items.map((i) => ` ${i}`).join(\"\\n\")}\\n`;\n}\n\n/**\n * Metadata header rendered for default and `--meta` views. Single-line\n * fields for short lists; indented block form for long lists. This is\n * the human-readable counterpart to the JSON dump — labeled, column-\n * aligned, skimmable.\n */\nfunction metadataHeader(rec: ShowRecord): string {\n const lines: string[] = [];\n lines.push(`${DIM}slug:${RST} ${BLUE}${rec.slug}${RST}`);\n lines.push(`${DIM}title:${RST} ${rec.title ?? \"—\"}`);\n lines.push(\n `${DIM}topics:${RST} ${rec.topics.length > 0 ? rec.topics.join(\", \") : \"—\"}`,\n );\n\n if (rec.file_refs.length > 0) {\n const parts = rec.file_refs.map(\n (r) => `${r.path}`,\n );\n lines.push(`${DIM}files:${RST} ${parts.join(\", \")}`);\n }\n\n lines.push(\n `${DIM}updated:${RST} ${new Date(rec.updated_at * 1000).toISOString()}`,\n );\n\n if (rec.wikilinks_out.length > 0) {\n lines.push(`${DIM}links:${RST} ${rec.wikilinks_out.join(\", \")}`);\n }\n if (rec.wikilinks_in.length > 0) {\n lines.push(`${DIM}backlinks:${RST} ${rec.wikilinks_in.join(\", \")}`);\n }\n if (rec.cross_wiki_links.length > 0) {\n lines.push(\n `${DIM}xwiki:${RST} ${rec.cross_wiki_links\n .map((x) => `${x.wiki}:${x.target}`)\n .join(\", \")}`,\n );\n }\n if (rec.archived_at !== null) {\n lines.push(\n `${DIM}archived:${RST} ${new Date(rec.archived_at * 1000).toISOString()}`,\n );\n }\n if (rec.superseded_by !== null) {\n lines.push(`${DIM}superseded_by:${RST} ${rec.superseded_by}`);\n }\n if (rec.supersedes.length > 0) {\n lines.push(`${DIM}supersedes:${RST} ${rec.supersedes.join(\", \")}`);\n }\n\n return lines.join(\"\\n\");\n}\n\n// ─── Helpers ─────────────────────────────────────────────────────────\n\n/**\n * Strip a leading YAML frontmatter block (fenced by `---` on its own\n * lines). Everything between the opening `---\\n` and the next `---\\n` is\n * dropped, along with the surrounding fence. If no opening fence is\n * present we return the source unchanged.\n *\n * Hand-rolled rather than pulling in gray-matter because the CLI's only\n * goal here is \"show the body without the YAML header\" — we don't need\n * the parsed fields, the indexer already has them.\n */\nfunction stripFrontmatter(src: string): string {\n if (!src.startsWith(\"---\\n\") && !src.startsWith(\"---\\r\\n\")) return src;\n // Strip the opening fence. A regex replace handles both LF and CRLF\n // line endings without the off-by-one that `src.indexOf(\"\\n\") + 1`\n // suffered on CRLF files — `indexOf(\"\\n\")` landed AFTER the `\\r`, so\n // the `\\r` survived as a prefix on the first body byte.\n const afterOpen = src.replace(/^---\\r?\\n/, \"\");\n const endMatch = afterOpen.match(/^---[ \\t]*\\r?\\n/m);\n if (endMatch === null || endMatch.index === undefined) return src;\n // Slice after the closing fence's line.\n return afterOpen.slice(endMatch.index + endMatch[0].length);\n}\n\n/**\n * First paragraph of the body, where \"paragraph\" is everything up to (but\n * not including) the first blank line. Skips a leading `# Title` line if\n * present so `--lead` previews the first real sentence, not the heading.\n */\nfunction firstParagraph(body: string): string {\n let src = body.trimStart();\n if (src.startsWith(\"# \")) {\n const nl = src.indexOf(\"\\n\");\n src = nl === -1 ? \"\" : src.slice(nl + 1).trimStart();\n }\n const blank = src.search(/\\n[ \\t]*\\n/);\n if (blank === -1) return src.trimEnd();\n return src.slice(0, blank).trimEnd();\n}\n\nfunction collectSlugs(options: ShowOptions): string[] {\n if (options.stdin === true && options.stdinInput !== undefined) {\n return options.stdinInput\n .split(/\\r?\\n/)\n .map((s) => s.trim())\n .filter((s) => s.length > 0);\n }\n if (options.slug !== undefined && options.slug.length > 0) {\n return [options.slug];\n }\n return [];\n}\n","import { Command } from \"commander\";\n\nimport { runHealth } from \"../commands/health.js\";\nimport { listWikis } from \"../commands/list.js\";\nimport { runSearch } from \"../commands/search.js\";\nimport { runShow } from \"../commands/show.js\";\nimport { autoRegisterIfNeeded } from \"../registry/autoregister.js\";\nimport {\n collectOption,\n emit,\n parsePositiveInt,\n readStdin,\n} from \"./helpers.js\";\n\nexport function registerQueryCommands(program: Command): void {\n program\n .command(\"search [query]\")\n .description(\"find pages by text, topic, file mentions, freshness\")\n .option(\n \"--topic <name...>\",\n \"filter by topic (repeat for intersection)\",\n collectOption,\n [] as string[],\n )\n .option(\n \"--mentions <path>\",\n \"pages referencing this path; matches exact file, trailing-slash folders, and any file under a folder prefix\",\n )\n .option(\"--since <duration>\", \"updated within duration, by file mtime (e.g. 2w, 30d)\")\n .option(\"--stale <duration>\", \"NOT updated within duration, by file mtime\")\n .option(\"--orphan\", \"pages with no topics\")\n .option(\"--include-archive\", \"include archived pages\")\n .option(\"--archived\", \"archived pages only\")\n .option(\"--wiki <name>\", \"target a specific registered wiki\")\n .option(\"--json\", \"emit structured JSON\")\n .option(\"--limit <n>\", \"cap results\", parsePositiveInt)\n .action(\n async (\n query: string | undefined,\n opts: {\n topic?: string[];\n mentions?: string;\n since?: string;\n stale?: string;\n orphan?: boolean;\n includeArchive?: boolean;\n archived?: boolean;\n wiki?: string;\n json?: boolean;\n limit?: number;\n },\n ) => {\n await autoRegisterIfNeeded(process.cwd());\n const result = await runSearch({\n cwd: process.cwd(),\n query,\n topics: opts.topic ?? [],\n mentions: opts.mentions,\n since: opts.since,\n stale: opts.stale,\n orphan: opts.orphan,\n includeArchive: opts.includeArchive,\n archived: opts.archived,\n wiki: opts.wiki,\n json: opts.json,\n limit: opts.limit,\n });\n emit(result);\n },\n );\n\n program\n .command(\"show [slug]\")\n .description(\"print a page (metadata + body; flags to narrow)\")\n .option(\"--stdin\", \"read slugs from stdin (one per line)\")\n .option(\"--wiki <name>\", \"target a specific registered wiki\")\n .option(\"--json\", \"structured JSON (overrides other view/field flags)\")\n .option(\"--raw\", \"body only (alias: --body)\")\n .option(\"--body\", \"body only (alias: --raw)\")\n .option(\"--meta\", \"metadata only, no body\")\n .option(\"--lead\", \"first paragraph of the body only\")\n .option(\"--title\", \"print title\")\n .option(\"--topics\", \"print topics\")\n .option(\"--files\", \"print file refs\")\n .option(\"--links\", \"print outgoing wikilinks\")\n .option(\"--backlinks\", \"print incoming wikilinks\")\n .option(\"--xwiki\", \"print cross-wiki links\")\n .option(\"--lineage\", \"print archived_at / supersedes / superseded_by\")\n .option(\"--updated\", \"print updated timestamp\")\n .option(\"--path\", \"print absolute file path\")\n .action(\n async (\n slug: string | undefined,\n opts: {\n stdin?: boolean;\n wiki?: string;\n json?: boolean;\n raw?: boolean;\n body?: boolean;\n meta?: boolean;\n lead?: boolean;\n title?: boolean;\n topics?: boolean;\n files?: boolean;\n links?: boolean;\n backlinks?: boolean;\n xwiki?: boolean;\n lineage?: boolean;\n updated?: boolean;\n path?: boolean;\n },\n ) => {\n await autoRegisterIfNeeded(process.cwd());\n const result = await runShow({\n cwd: process.cwd(),\n slug,\n stdin: opts.stdin,\n stdinInput: opts.stdin === true ? await readStdin() : undefined,\n wiki: opts.wiki,\n json: opts.json,\n raw: opts.raw === true || opts.body === true,\n meta: opts.meta,\n lead: opts.lead,\n title: opts.title,\n topics: opts.topics,\n files: opts.files,\n links: opts.links,\n backlinks: opts.backlinks,\n xwiki: opts.xwiki,\n lineage: opts.lineage,\n updated: opts.updated,\n path: opts.path,\n });\n emit(result);\n },\n );\n\n program\n .command(\"health\")\n .description(\"report graph integrity problems\")\n .option(\"--topic <name>\", \"scope to a topic + its descendants\")\n .option(\"--stale <duration>\", \"stale threshold (default 90d)\")\n .option(\"--stdin\", \"read page slugs from stdin (limit to these pages)\")\n .option(\"--json\", \"emit structured JSON\")\n .option(\"--wiki <name>\", \"target a specific registered wiki\")\n .action(\n async (opts: {\n topic?: string;\n stale?: string;\n stdin?: boolean;\n json?: boolean;\n wiki?: string;\n }) => {\n await autoRegisterIfNeeded(process.cwd());\n const result = await runHealth({\n cwd: process.cwd(),\n topic: opts.topic,\n stale: opts.stale,\n stdin: opts.stdin,\n stdinInput: opts.stdin === true ? await readStdin() : undefined,\n json: opts.json,\n wiki: opts.wiki,\n });\n emit(result);\n },\n );\n\n program\n .command(\"list\")\n .description(\"list registered wikis\")\n .option(\"--json\", \"emit structured JSON\")\n .option(\n \"--drop <name>\",\n \"remove a wiki from the registry (the only way entries are ever removed)\",\n )\n .action(async (opts: { json?: boolean; drop?: string }) => {\n if (opts.drop === undefined) {\n await autoRegisterIfNeeded(process.cwd());\n }\n const result = await listWikis(opts);\n process.stdout.write(result.stdout);\n if (result.exitCode !== 0) {\n process.exitCode = result.exitCode;\n }\n });\n}\n","import { BLUE, BOLD, DIM, GREEN, RED, RST } from \"../../ansi.js\";\nimport type { Check, CheckStatus, DoctorOptions, DoctorReport } from \"./types.js\";\n\nexport function formatReport(\n report: DoctorReport,\n options: DoctorOptions,\n): string {\n const color = options.stdout === undefined && process.stdout.isTTY === true;\n const lines: string[] = [];\n lines.push(`codealmanac v${report.version}`);\n lines.push(\"\");\n if (report.install.length > 0) {\n lines.push(color ? `${BOLD}## Install${RST}` : \"## Install\");\n for (const c of report.install) {\n lines.push(formatCheck(c, color));\n }\n lines.push(\"\");\n }\n if (report.updates.length > 0) {\n lines.push(color ? `${BOLD}## Updates${RST}` : \"## Updates\");\n for (const c of report.updates) {\n lines.push(formatCheck(c, color));\n }\n lines.push(\"\");\n }\n if (report.wiki.length > 0) {\n lines.push(color ? `${BOLD}## Current wiki${RST}` : \"## Current wiki\");\n for (const c of report.wiki) {\n lines.push(formatCheck(c, color));\n }\n lines.push(\"\");\n }\n return `${lines.join(\"\\n\")}\\n`;\n}\n\nfunction formatCheck(c: Check, color: boolean): string {\n const { icon, tint } = iconFor(c.status, color);\n const head = ` ${tint}${icon}${color ? RST : \"\"} ${c.message}`;\n if (c.fix === undefined) return head;\n const fixLine = color\n ? ` ${DIM}${c.fix}${RST}`\n : ` ${c.fix}`;\n return `${head}\\n${fixLine}`;\n}\n\nfunction iconFor(\n status: CheckStatus,\n color: boolean,\n): { icon: string; tint: string } {\n switch (status) {\n case \"ok\":\n return { icon: \"\\u2713\", tint: color ? GREEN : \"\" };\n case \"problem\":\n return { icon: \"\\u2717\", tint: color ? RED : \"\" };\n case \"info\":\n return { icon: \"\\u25c7\", tint: color ? BLUE : \"\" };\n }\n}\n","import { existsSync } from \"node:fs\";\nimport { readFile } from \"node:fs/promises\";\nimport { homedir } from \"node:os\";\nimport path from \"node:path\";\n\nimport type { ClaudeAuthStatus } from \"../../agent/auth.js\";\nimport { IMPORT_LINE } from \"../setup.js\";\nimport {\n classifyInstallPath,\n detectInstallPath,\n probeBetterSqlite3,\n safeCheckAuth,\n} from \"./probes.js\";\nimport type { Check, DoctorOptions } from \"./types.js\";\n\nexport async function gatherInstallChecks(\n options: DoctorOptions,\n): Promise<Check[]> {\n const checks: Check[] = [];\n\n const rawPath = options.installPath ?? detectInstallPath();\n const { installPath, isEphemeral } = classifyInstallPath(rawPath);\n checks.push(describeInstallPath(installPath, isEphemeral));\n\n const nodeVersion = options.nodeVersion ?? process.version;\n const sqlite = options.sqliteProbe ?? probeBetterSqlite3();\n checks.push({\n status: sqlite.ok ? \"ok\" : \"problem\",\n key: \"install.sqlite\",\n message: sqlite.ok\n ? `better-sqlite3 native binding OK (Node ${nodeVersion})`\n : `better-sqlite3 native binding failed: ${sqlite.summary}`,\n fix: sqlite.ok\n ? undefined\n : \"run: npm rebuild better-sqlite3 (in the install directory)\",\n });\n\n const auth = await safeCheckAuth(options.spawnCli);\n checks.push(describeAuth(auth));\n\n const settingsPath =\n options.settingsPath ?? path.join(homedir(), \".claude\", \"settings.json\");\n checks.push(await describeHook(settingsPath));\n\n const claudeDir = options.claudeDir ?? path.join(homedir(), \".claude\");\n checks.push(describeGuides(claudeDir));\n checks.push(await describeImportLine(claudeDir));\n\n return checks;\n}\n\nfunction describeInstallPath(\n installPath: string | null,\n isEphemeral: boolean,\n): Check {\n if (installPath === null) {\n return {\n status: \"problem\",\n key: \"install.path\",\n message: \"could not detect codealmanac install path\",\n fix: \"reinstall with: npm install -g codealmanac\",\n };\n }\n return {\n status: isEphemeral ? \"info\" : \"ok\",\n key: \"install.path\",\n message: isEphemeral\n ? `codealmanac running from ephemeral npx location: ${installPath}`\n : `codealmanac installed at ${installPath}`,\n fix: isEphemeral\n ? \"run: npm install -g codealmanac (to make the install permanent)\"\n : undefined,\n };\n}\n\nfunction describeAuth(auth: ClaudeAuthStatus): Check {\n if (auth.loggedIn) {\n if (auth.authMethod === \"apiKey\") {\n return {\n status: \"ok\",\n key: \"install.auth\",\n message: \"claude auth: ANTHROPIC_API_KEY set\",\n };\n }\n const who = auth.email ?? \"Claude account\";\n const plan =\n auth.subscriptionType !== undefined\n ? ` (${auth.subscriptionType} subscription)`\n : \"\";\n return {\n status: \"ok\",\n key: \"install.auth\",\n message: `claude auth: ${who}${plan}`,\n };\n }\n if (\n process.env.ANTHROPIC_API_KEY !== undefined &&\n process.env.ANTHROPIC_API_KEY.length > 0\n ) {\n return {\n status: \"ok\",\n key: \"install.auth\",\n message: \"claude auth: ANTHROPIC_API_KEY set\",\n };\n }\n return {\n status: \"problem\",\n key: \"install.auth\",\n message: \"claude auth: not signed in\",\n fix: \"run: claude auth login --claudeai (or export ANTHROPIC_API_KEY)\",\n };\n}\n\nasync function describeHook(settingsPath: string): Promise<Check> {\n if (!existsSync(settingsPath)) {\n return {\n status: \"problem\",\n key: \"install.hook\",\n message: \"SessionEnd hook not installed\",\n fix: \"run: almanac setup --yes\",\n };\n }\n try {\n const raw = await readFile(settingsPath, \"utf8\");\n const parsed = JSON.parse(raw) as {\n hooks?: {\n SessionEnd?: {\n command?: string;\n hooks?: { command?: string }[];\n }[];\n };\n };\n const entries = parsed.hooks?.SessionEnd ?? [];\n const found = entries.some((e) => {\n if (\n typeof e?.command === \"string\" &&\n e.command.endsWith(\"almanac-capture.sh\")\n ) {\n return true;\n }\n if (Array.isArray(e?.hooks)) {\n return e.hooks.some(\n (h) =>\n typeof h?.command === \"string\" &&\n h.command.endsWith(\"almanac-capture.sh\"),\n );\n }\n return false;\n });\n if (!found) {\n return {\n status: \"problem\",\n key: \"install.hook\",\n message: \"SessionEnd hook not installed\",\n fix: \"run: almanac setup --yes\",\n };\n }\n return {\n status: \"ok\",\n key: \"install.hook\",\n message: `SessionEnd hook installed at ${settingsPath}`,\n };\n } catch (err: unknown) {\n const msg = err instanceof Error ? err.message : String(err);\n return {\n status: \"problem\",\n key: \"install.hook\",\n message: `could not read ${settingsPath}: ${msg}`,\n fix: \"check the file for malformed JSON\",\n };\n }\n}\n\nfunction describeGuides(claudeDir: string): Check {\n const mini = path.join(claudeDir, \"codealmanac.md\");\n const ref = path.join(claudeDir, \"codealmanac-reference.md\");\n const haveMini = existsSync(mini);\n const haveRef = existsSync(ref);\n if (haveMini && haveRef) {\n return {\n status: \"ok\",\n key: \"install.guides\",\n message: `Agent guides installed (${path.basename(mini)}, ${path.basename(ref)})`,\n };\n }\n const missing = [\n haveMini ? null : \"codealmanac.md\",\n haveRef ? null : \"codealmanac-reference.md\",\n ].filter((s): s is string => s !== null);\n return {\n status: \"problem\",\n key: \"install.guides\",\n message: `Agent guides missing (${missing.join(\", \")})`,\n fix: \"run: almanac setup --yes\",\n };\n}\n\nasync function describeImportLine(claudeDir: string): Promise<Check> {\n const claudeMd = path.join(claudeDir, \"CLAUDE.md\");\n if (!existsSync(claudeMd)) {\n return {\n status: \"problem\",\n key: \"install.import\",\n message: \"CLAUDE.md import not present (no ~/.claude/CLAUDE.md)\",\n fix: \"run: almanac setup --yes\",\n };\n }\n try {\n const contents = await readFile(claudeMd, \"utf8\");\n const lines = contents.split(/\\r?\\n/).map((l) => l.trim());\n const present = lines.some((line) => {\n if (line === IMPORT_LINE) return true;\n if (!line.startsWith(IMPORT_LINE)) return false;\n const next = line[IMPORT_LINE.length];\n return next === \" \" || next === \"\\t\";\n });\n if (present) {\n return {\n status: \"ok\",\n key: \"install.import\",\n message: \"CLAUDE.md import present\",\n };\n }\n return {\n status: \"problem\",\n key: \"install.import\",\n message: \"CLAUDE.md import line missing\",\n fix: \"run: almanac setup --yes\",\n };\n } catch (err: unknown) {\n const msg = err instanceof Error ? err.message : String(err);\n return {\n status: \"problem\",\n key: \"install.import\",\n message: `could not read ${claudeMd}: ${msg}`,\n };\n }\n}\n","import { existsSync, readFileSync } from \"node:fs\";\nimport { createRequire } from \"node:module\";\nimport { homedir } from \"node:os\";\nimport path from \"node:path\";\nimport { fileURLToPath } from \"node:url\";\n\nimport { checkClaudeAuth, type ClaudeAuthStatus, type SpawnCliFn } from \"../../agent/auth.js\";\nimport type { SqliteProbeResult } from \"./types.js\";\n\n// Single `createRequire` instance — used by package/binding probes.\nconst req = createRequire(import.meta.url);\n\n/**\n * Detect where codealmanac is installed by walking up from the running\n * module until we find a `package.json` whose `name` is `codealmanac`.\n */\nexport function detectInstallPath(): string | null {\n try {\n const here = fileURLToPath(import.meta.url);\n let dir = path.dirname(here);\n for (let i = 0; i < 6; i++) {\n const pkgPath = path.join(dir, \"package.json\");\n if (existsSync(pkgPath)) {\n try {\n const raw = readFileSync(pkgPath, \"utf-8\");\n const pkg = JSON.parse(raw) as { name?: unknown };\n if (pkg.name === \"codealmanac\") return dir;\n } catch {\n // ignore — keep walking\n }\n }\n const parent = path.dirname(dir);\n if (parent === dir) break;\n dir = parent;\n }\n return null;\n } catch {\n return null;\n }\n}\n\n/**\n * Classify the detected install path as permanent or ephemeral.\n * Ephemeral locations (npm npx cache, pnpm dlx cache, /tmp/) are valid\n * installs but will disappear when the cache is evicted or the machine\n * reboots. Doctor reports them as `info` rather than `ok`.\n */\nexport function classifyInstallPath(\n raw: string | null,\n): { installPath: string | null; isEphemeral: boolean } {\n if (raw === null) return { installPath: null, isEphemeral: false };\n const home = homedir();\n const ephemeralPrefixes = [\n path.join(home, \".npm\", \"_npx\"),\n path.join(home, \".local\", \"share\", \"pnpm\", \"dlx\"),\n \"/tmp/\",\n \"/var/folders/\",\n ];\n const isEphemeral = ephemeralPrefixes.some((p) => raw.startsWith(p));\n return { installPath: raw, isEphemeral };\n}\n\n/**\n * Probe the better-sqlite3 native binding by opening an in-memory DB.\n */\nexport function probeBetterSqlite3(): SqliteProbeResult {\n try {\n // eslint-disable-next-line @typescript-eslint/no-var-requires\n const Database = req(\"better-sqlite3\") as typeof import(\"better-sqlite3\");\n const db = new Database(\":memory:\");\n db.close();\n return { ok: true, summary: \"native binding loads cleanly\" };\n } catch (err: unknown) {\n const msg = err instanceof Error ? err.message : String(err);\n const firstLine = msg.split(\"\\n\")[0] ?? msg;\n return { ok: false, summary: firstLine };\n }\n}\n\nexport async function safeCheckAuth(\n spawnCli?: SpawnCliFn,\n): Promise<ClaudeAuthStatus> {\n try {\n return await checkClaudeAuth(spawnCli);\n } catch {\n return { loggedIn: false };\n }\n}\n\nexport function readPackageVersion(): string | null {\n const candidates = [\n \"../../../package.json\",\n \"../../package.json\",\n \"../package.json\",\n ];\n for (const candidate of candidates) {\n try {\n const pkg = req(candidate) as { version?: unknown };\n if (typeof pkg.version === \"string\" && pkg.version.length > 0) {\n return pkg.version;\n }\n } catch {\n // Fall through to the next runtime layout candidate.\n }\n }\n return null;\n}\n","import { mkdir, readFile, rename, writeFile } from \"node:fs/promises\";\nimport { dirname, join } from \"node:path\";\n\nimport { getGlobalAlmanacDir } from \"../paths.js\";\n\n/**\n * `~/.almanac/config.json` — global, cross-wiki configuration. Today\n * the only field is `update_notifier` (on/off toggle for the pre-command\n * banner); designed as an object so we can add more knobs without\n * breaking users who already have the file on disk.\n *\n * Missing or malformed → defaults. Same tolerance as `UpdateState`:\n * the CLI must not be able to fail because this file drifted.\n */\nexport interface GlobalConfig {\n /** When `false`, suppress the pre-command update-nag banner. Default: true. */\n update_notifier: boolean;\n}\n\nexport function defaultConfig(): GlobalConfig {\n return { update_notifier: true };\n}\n\nexport function getConfigPath(): string {\n return join(getGlobalAlmanacDir(), \"config.json\");\n}\n\nexport async function readConfig(path?: string): Promise<GlobalConfig> {\n const file = path ?? getConfigPath();\n let raw: string;\n try {\n raw = await readFile(file, \"utf8\");\n } catch {\n return defaultConfig();\n }\n const trimmed = raw.trim();\n if (trimmed.length === 0) return defaultConfig();\n try {\n const parsed = JSON.parse(trimmed) as Partial<GlobalConfig>;\n return {\n update_notifier:\n typeof parsed.update_notifier === \"boolean\"\n ? parsed.update_notifier\n : true,\n };\n } catch {\n return defaultConfig();\n }\n}\n\nexport async function writeConfig(\n config: GlobalConfig,\n path?: string,\n): Promise<void> {\n const file = path ?? getConfigPath();\n await mkdir(dirname(file), { recursive: true });\n const body = `${JSON.stringify(config, null, 2)}\\n`;\n const tmp = `${file}.tmp`;\n await writeFile(tmp, body, \"utf8\");\n await rename(tmp, file);\n}\n","import { spawn } from \"node:child_process\";\nimport { readFileSync } from \"node:fs\";\n\nimport { checkForUpdate } from \"./check.js\";\nimport { getConfigPath } from \"./config.js\";\nimport { getStatePath, type UpdateState } from \"./state.js\";\n\n/**\n * Post-command scheduler for the background update check.\n *\n * After any normal `almanac <command>` exits, we want a fresh check to\n * have happened by the next invocation. We achieve that by spawning a\n * detached copy of ourselves with the hidden `--internal-check-updates`\n * flag; that child does nothing but hit the registry and write\n * `~/.almanac/update-state.json`, then exits.\n *\n * Why detach rather than check inline:\n * - 3s network timeout in the foreground would feel sluggish on every\n * command.\n * - `npm test` and CI scripts shouldn't pay for a registry round-trip\n * (gated below via env).\n * - A detached child with `stdio: \"ignore\"` cannot leak output into\n * the parent's stdout/stderr — critical for pipelines.\n *\n * Hazards we accept:\n * - A Claude Code subprocess whose parent shell exits right after the\n * `almanac` call may kill the child before it finishes. That's\n * fine: a failed check just means we try again next invocation.\n * - Detached child survival on Windows isn't as robust as on Unix.\n * Same fallback: next invocation retries.\n */\n\nexport function scheduleBackgroundUpdateCheck(argv: string[]): void {\n if (!shouldSchedule(argv)) return;\n\n const scriptPath = argv[1];\n const nodeBin = process.execPath;\n if (scriptPath === undefined || scriptPath.length === 0) return;\n\n // Spawn with the current Node and the same script path. `detached:\n // true` + `stdio: \"ignore\"` + `unref()` detaches the child from our\n // event loop so the parent can exit independently.\n try {\n const child = spawn(\n nodeBin,\n [scriptPath, \"--internal-check-updates\"],\n {\n detached: true,\n stdio: \"ignore\",\n // Windows: with `detached: true` and no `stdio`, Node opens a\n // console window — `\"ignore\"` prevents that.\n },\n );\n child.unref();\n // Swallow any synchronous spawn errors (e.g. ENOENT in strange\n // installs) — never propagate to the foreground command.\n child.on(\"error\", () => {});\n } catch {\n // Last-resort swallow: background checks are best-effort.\n }\n}\n\n/**\n * Should we spawn the worker at all?\n *\n * - Respect the `update_notifier` config — no banner means no need\n * for the data that feeds it.\n * - Skip in test environments so `npm test` doesn't fork 300 copies\n * of itself into the background and hammer the registry.\n * - Skip on the worker invocation itself (prevents a fork bomb).\n * - Skip when the user doesn't own the install path (permission\n * weirdness) — detected by `~/.almanac` mkdir failing; simplest\n * to just rely on the worker's own error handling, so we don't\n * gate here.\n * - Skip when the argv contains `--help`/`--version`/nothing — these\n * commands are often run from scripts that care about clean exit;\n * though the inline banner still shows, we don't kick off a check.\n */\nfunction shouldSchedule(argv: string[]): boolean {\n if (process.env.CODEALMANAC_SKIP_UPDATE_CHECK === \"1\") return false;\n if (process.env.NODE_ENV === \"test\") return false;\n if (process.env.VITEST !== undefined) return false;\n\n // Already the worker. argv[2..] contains the internal flag.\n if (argv.slice(2).includes(\"--internal-check-updates\")) return false;\n\n if (!notifierEnabled()) return false;\n\n return true;\n}\n\nfunction notifierEnabled(): boolean {\n try {\n const raw = readFileSync(getConfigPath(), \"utf8\");\n const parsed = JSON.parse(raw) as { update_notifier?: unknown };\n if (parsed.update_notifier === false) return false;\n return true;\n } catch {\n return true; // missing / malformed → default-on\n }\n}\n\n/**\n * The worker body. Invoked when `--internal-check-updates` appears on\n * the argv. Must be fast and must never print: the parent spawned us\n * with `stdio: \"ignore\"` but a stray write could still surprise a\n * downstream debugger.\n *\n * We take a simple file lock at `~/.almanac/.update-check.lock` to\n * prevent two workers running at the same time (which could happen if\n * the user fires several commands in parallel). The lock is just the\n * existence of the file with our PID inside; if an existing lock is\n * stale (older than the 3s + cache-write budget), we steal it.\n */\nexport async function runInternalUpdateCheck(): Promise<void> {\n // The worker is intentionally minimal. Any error (network, fs,\n // JSON) is handled inside `checkForUpdate` and surfaces as a\n // swallowed return; we just need to await it and exit.\n try {\n await checkForUpdate({});\n } catch {\n // Defense-in-depth: nothing must escape the worker.\n }\n}\n\n/**\n * Read the current state snapshot for diagnostic surfaces (doctor, the\n * `update --check` command). Wraps the sync read so callers can grab\n * state without the `async readState` ceremony.\n */\nexport function readStateForDoctor(path?: string): UpdateState | null {\n const file = path ?? getStatePath();\n try {\n const raw = readFileSync(file, \"utf8\");\n const trimmed = raw.trim();\n if (trimmed.length === 0) return null;\n const parsed = JSON.parse(trimmed) as Partial<UpdateState>;\n return {\n last_check_at:\n typeof parsed.last_check_at === \"number\" ? parsed.last_check_at : 0,\n installed_version:\n typeof parsed.installed_version === \"string\"\n ? parsed.installed_version\n : \"\",\n latest_version:\n typeof parsed.latest_version === \"string\" ? parsed.latest_version : \"\",\n dismissed_versions: Array.isArray(parsed.dismissed_versions)\n ? parsed.dismissed_versions.filter((v): v is string => typeof v === \"string\")\n : [],\n last_fetch_failed_at:\n typeof parsed.last_fetch_failed_at === \"number\"\n ? parsed.last_fetch_failed_at\n : undefined,\n };\n } catch {\n return null;\n }\n}\n","import { createRequire } from \"node:module\";\n\nimport { readState, writeState, type UpdateState } from \"./state.js\";\n\n/**\n * Background update check. Called by the detached worker (`--internal-\n * check-updates`) after any normal command exits; also reachable via\n * `almanac update --check` for a synchronous \"am I current?\" readout.\n *\n * Contract:\n * - Reads `~/.almanac/update-state.json` if present.\n * - If the last check is older than `cacheSeconds` (default 24h), queries\n * the npm registry for `codealmanac`'s `dist-tags.latest` and writes a\n * new state file.\n * - Network timeout is 3s: registry flakes must not prevent a check\n * cycle on the next invocation.\n * - All errors are swallowed; the returned state is always a usable\n * snapshot (possibly the old one when the fetch failed).\n *\n * The fetch function is injectable. Tests pass a stub; production uses\n * the native `globalThis.fetch`. No dependency on `node-fetch`.\n */\n\nexport interface CheckOptions {\n /** Override the installed version (prod: read from package.json). */\n installedVersion?: string;\n /** Cache window; no registry call if last check is newer than this. */\n cacheSeconds?: number;\n /** Network timeout in ms (default 3000). */\n timeoutMs?: number;\n /** Clock. Tests inject to make \"24h ago\" deterministic. */\n now?: () => number;\n /** Fetch function (default `globalThis.fetch`). */\n fetchFn?: typeof fetch;\n /** Override the state file path (tests point it at a tmpdir). */\n statePath?: string;\n /** Force a registry call regardless of the cache. Used by `update --check`. */\n force?: boolean;\n}\n\nexport interface CheckResult {\n /** The state after the check (either refreshed or unchanged). */\n state: UpdateState;\n /** True when a registry call actually happened this run. */\n fetched: boolean;\n /** True when the registry call failed (network / timeout / parse). */\n fetchFailed: boolean;\n}\n\nconst DEFAULT_CACHE_SECONDS = 24 * 60 * 60;\nconst DEFAULT_TIMEOUT_MS = 3000;\nconst REGISTRY_URL = \"https://registry.npmjs.org/codealmanac\";\n\nexport async function checkForUpdate(\n opts: CheckOptions = {},\n): Promise<CheckResult> {\n const now = opts.now ?? (() => Math.floor(Date.now() / 1000));\n const cacheSeconds = opts.cacheSeconds ?? DEFAULT_CACHE_SECONDS;\n const timeoutMs = opts.timeoutMs ?? DEFAULT_TIMEOUT_MS;\n const fetchFn = opts.fetchFn ?? globalThis.fetch;\n const installed = opts.installedVersion ?? readInstalledVersion();\n\n const state = await readState(opts.statePath);\n\n // Cache gate. Skip the registry call when the previous check is\n // fresh enough, unless `force: true` (used by `update --check` so\n // the user can see real-time status without waiting out the window).\n if (\n !opts.force &&\n state.last_check_at > 0 &&\n now() - state.last_check_at < cacheSeconds\n ) {\n return { state, fetched: false, fetchFailed: false };\n }\n\n // Query the registry with a hard timeout. `AbortController` is the\n // idiomatic Node 20+ way to bound a fetch; `setTimeout` fires abort,\n // `clearTimeout` cancels if fetch resolves first.\n let latest: string | null = null;\n let failed = false;\n try {\n const ac = new AbortController();\n const timer = setTimeout(() => ac.abort(), timeoutMs);\n try {\n const res = await fetchFn(REGISTRY_URL, {\n signal: ac.signal,\n headers: { accept: \"application/json\" },\n });\n if (!res.ok) {\n failed = true;\n } else {\n const body = (await res.json()) as {\n [\"dist-tags\"]?: { latest?: unknown };\n };\n const tag = body[\"dist-tags\"]?.latest;\n if (typeof tag === \"string\" && tag.length > 0) {\n latest = tag;\n } else {\n failed = true;\n }\n }\n } finally {\n clearTimeout(timer);\n }\n } catch {\n failed = true;\n }\n\n if (failed || latest === null) {\n // Record the failure but DON'T clobber the previous latest_version —\n // an offline check shouldn't make us forget that 0.1.6 is out.\n const next: UpdateState = {\n ...state,\n // We still bump last_check_at on a failed attempt; without this,\n // every subsequent command would re-try the registry. A one-shot\n // retry on the next invocation is enough; sustained failure gets\n // retried on the 24h cadence like a success.\n last_check_at: now(),\n installed_version: installed,\n last_fetch_failed_at: now(),\n };\n try {\n await writeState(next, opts.statePath);\n } catch {\n // Even the state write failed (permissions, disk full). Return\n // whatever we have — the CLI doesn't care.\n }\n return { state: next, fetched: true, fetchFailed: true };\n }\n\n const next: UpdateState = {\n last_check_at: now(),\n installed_version: installed,\n latest_version: latest,\n dismissed_versions: state.dismissed_versions,\n // Clear the failure marker on success.\n last_fetch_failed_at: undefined,\n };\n try {\n await writeState(next, opts.statePath);\n } catch {\n // Silent — same rationale as above.\n }\n return { state: next, fetched: true, fetchFailed: false };\n}\n\n/**\n * Read the `version` field from `package.json`. Matches the same\n * lookup strategy as `readPackageVersion` in `cli.ts` and `doctor.ts`;\n * duplicated here to keep the update module self-contained and to\n * avoid a circular import at CLI startup.\n */\nfunction readInstalledVersion(): string {\n try {\n const require = createRequire(import.meta.url);\n const pkg = require(\"../../package.json\") as { version?: unknown };\n if (typeof pkg.version === \"string\" && pkg.version.length > 0) {\n return pkg.version;\n }\n } catch {\n // Fall through.\n }\n try {\n const require = createRequire(import.meta.url);\n const pkg = require(\"../package.json\") as { version?: unknown };\n if (typeof pkg.version === \"string\" && pkg.version.length > 0) {\n return pkg.version;\n }\n } catch {\n // Fall through.\n }\n return \"unknown\";\n}\n","import { mkdir, readFile, rename, writeFile } from \"node:fs/promises\";\nimport { dirname, join } from \"node:path\";\n\nimport { getGlobalAlmanacDir } from \"../paths.js\";\n\n/**\n * `~/.almanac/update-state.json` — the only piece of persistent state\n * the update system owns. Written by the background check worker (once\n * per 24h) and by `almanac update` / `almanac update --dismiss`; read\n * by the pre-command banner, `almanac doctor`, and the check path to\n * decide whether 24h have elapsed since the last check.\n *\n * Format is a single flat object with no schema version: fields are\n * only ever added (and read with defaults), never removed or reshaped.\n * If we ever need a breaking migration, the file is trivially\n * regenerable — the worst case is a single extra registry round-trip.\n *\n * Corruption handling: every read path tolerates missing or malformed\n * JSON as \"no state\" (empty defaults). We never propagate a parse\n * error up to the CLI banner, because a corrupt state file must not\n * be able to break every invocation.\n */\nexport interface UpdateState {\n /** Unix epoch seconds of the last registry query. */\n last_check_at: number;\n /** The codealmanac version running when we last wrote state. */\n installed_version: string;\n /** The newest version the registry has published at last check. */\n latest_version: string;\n /** Versions the user dismissed via `almanac update --dismiss`. */\n dismissed_versions: string[];\n /**\n * Epoch seconds of the last registry fetch attempt that FAILED. Used\n * by the check scheduler to back off (one failure shouldn't hammer\n * the registry on every command) — reads tolerate a missing field.\n */\n last_fetch_failed_at?: number;\n}\n\nexport function emptyState(): UpdateState {\n return {\n last_check_at: 0,\n installed_version: \"\",\n latest_version: \"\",\n dismissed_versions: [],\n };\n}\n\nexport function getStatePath(): string {\n return join(getGlobalAlmanacDir(), \"update-state.json\");\n}\n\n/**\n * Read the state file. Missing, empty, or malformed → empty state.\n * We deliberately swallow all errors here: the update system is\n * best-effort, and a read failure must not break any command.\n */\nexport async function readState(path?: string): Promise<UpdateState> {\n const file = path ?? getStatePath();\n let raw: string;\n try {\n raw = await readFile(file, \"utf8\");\n } catch {\n return emptyState();\n }\n const trimmed = raw.trim();\n if (trimmed.length === 0) return emptyState();\n try {\n const parsed = JSON.parse(trimmed) as Partial<UpdateState>;\n return {\n last_check_at:\n typeof parsed.last_check_at === \"number\" ? parsed.last_check_at : 0,\n installed_version:\n typeof parsed.installed_version === \"string\"\n ? parsed.installed_version\n : \"\",\n latest_version:\n typeof parsed.latest_version === \"string\" ? parsed.latest_version : \"\",\n dismissed_versions: Array.isArray(parsed.dismissed_versions)\n ? parsed.dismissed_versions.filter(\n (v): v is string => typeof v === \"string\" && v.length > 0,\n )\n : [],\n last_fetch_failed_at:\n typeof parsed.last_fetch_failed_at === \"number\"\n ? parsed.last_fetch_failed_at\n : undefined,\n };\n } catch {\n return emptyState();\n }\n}\n\n/**\n * Write the state file atomically (tmp + rename). Makes the concurrent\n * \"two commands ran their update checks at once\" race safe — one rename\n * wins, the other is dropped. Creates `~/.almanac/` if missing.\n */\nexport async function writeState(\n state: UpdateState,\n path?: string,\n): Promise<void> {\n const file = path ?? getStatePath();\n await mkdir(dirname(file), { recursive: true });\n const body = `${JSON.stringify(state, null, 2)}\\n`;\n const tmp = `${file}.tmp`;\n await writeFile(tmp, body, \"utf8\");\n await rename(tmp, file);\n}\n","/**\n * Tiny semver comparator for update-version checks. We do NOT take a\n * dependency on `semver` — we only need a subset (compare two\n * well-formed `x.y.z` strings, optionally with a pre-release tag), and\n * adding a 400KB install for six lines of logic isn't a good trade.\n *\n * What we handle:\n * - Numeric `major.minor.patch` with or without a `-pre` tag.\n * - Missing parts default to 0 (`1.2` → `1.2.0`).\n * - Leading `v` is stripped.\n *\n * What we don't handle:\n * - Build metadata (`+sha.abcd`) — ignored.\n * - Pre-release precedence rules beyond \"tagged < untagged at same\n * numeric triple\". Good enough for codealmanac's linear release\n * cadence; if we ever publish `-rc.1` vs `-rc.2` and care which\n * comes first, revisit.\n */\n\ninterface Parsed {\n major: number;\n minor: number;\n patch: number;\n /** Empty string = no pre-release (counts as \"higher\" than a pre-release at the same triple). */\n pre: string;\n}\n\nfunction parse(v: string): Parsed | null {\n const trimmed = v.trim().replace(/^v/i, \"\");\n // Strip build metadata (everything from the first `+`).\n const noBuild = trimmed.split(\"+\")[0] ?? \"\";\n // Split off pre-release tag.\n const dashAt = noBuild.indexOf(\"-\");\n const core = dashAt === -1 ? noBuild : noBuild.slice(0, dashAt);\n const pre = dashAt === -1 ? \"\" : noBuild.slice(dashAt + 1);\n\n const parts = core.split(\".\").map((p) => Number.parseInt(p, 10));\n if (parts.length === 0 || parts.some((n) => !Number.isFinite(n) || n < 0)) {\n return null;\n }\n return {\n major: parts[0] ?? 0,\n minor: parts[1] ?? 0,\n patch: parts[2] ?? 0,\n pre,\n };\n}\n\n/**\n * Return `true` iff `latest` > `installed`. Returns `false` on unparseable\n * input rather than throwing — a bad version string must not be able to\n * crash the CLI's every-command banner path.\n */\nexport function isNewer(latest: string, installed: string): boolean {\n const a = parse(latest);\n const b = parse(installed);\n if (a === null || b === null) return false;\n\n if (a.major !== b.major) return a.major > b.major;\n if (a.minor !== b.minor) return a.minor > b.minor;\n if (a.patch !== b.patch) return a.patch > b.patch;\n\n // Same numeric triple. Empty pre-release beats a tagged pre-release\n // (1.2.3 > 1.2.3-rc.1). Two tagged pre-releases compare lexically.\n if (a.pre === b.pre) return false;\n if (a.pre === \"\" && b.pre !== \"\") return true;\n if (a.pre !== \"\" && b.pre === \"\") return false;\n return a.pre > b.pre;\n}\n","export function formatDuration(ms: number): string {\n if (ms < 0) return \"just now\";\n const seconds = Math.floor(ms / 1000);\n if (seconds < 60) return `${seconds}s`;\n const minutes = Math.floor(seconds / 60);\n if (minutes < 60) return `${minutes}m`;\n const hours = Math.floor(minutes / 60);\n if (hours < 48) return `${hours}h`;\n const days = Math.floor(hours / 24);\n return `${days}d`;\n}\n","import { readConfig } from \"../../update/config.js\";\nimport { readStateForDoctor } from \"../../update/schedule.js\";\nimport { isNewer } from \"../../update/semver.js\";\nimport { formatDuration } from \"./duration.js\";\nimport type { Check, DoctorOptions } from \"./types.js\";\n\nexport async function gatherUpdateChecks(\n options: DoctorOptions,\n installedVersion: string,\n): Promise<Check[]> {\n const checks: Check[] = [];\n const state = readStateForDoctor(options.updateStatePath);\n const config = await readConfig(options.updateConfigPath);\n\n if (state === null || state.latest_version.length === 0) {\n checks.push({\n status: \"info\",\n key: \"update.status\",\n message: `on ${installedVersion}; no update check has run yet`,\n fix: \"run: almanac update --check\",\n });\n } else if (isNewer(state.latest_version, installedVersion)) {\n const dismissed = state.dismissed_versions.includes(state.latest_version)\n ? \" (dismissed — run `almanac update` to install anyway)\"\n : \"\";\n checks.push({\n status: \"problem\",\n key: \"update.status\",\n message:\n `${state.latest_version} available (you're on ${installedVersion})${dismissed}`,\n fix: \"run: almanac update\",\n });\n } else {\n checks.push({\n status: \"ok\",\n key: \"update.status\",\n message: `on latest (${installedVersion})`,\n });\n }\n\n if (state !== null && state.last_check_at > 0) {\n const now = (options.now?.() ?? new Date()).getTime();\n const ageMs = now - state.last_check_at * 1000;\n const failedSuffix =\n state.last_fetch_failed_at !== undefined &&\n state.last_fetch_failed_at === state.last_check_at\n ? \" (last attempt failed — will retry next invocation)\"\n : \"\";\n checks.push({\n status: \"info\",\n key: \"update.last_check\",\n message: `last checked: ${formatDuration(ageMs)} ago${failedSuffix}`,\n });\n } else {\n checks.push({\n status: \"info\",\n key: \"update.last_check\",\n message: \"last checked: never\",\n });\n }\n\n checks.push({\n status: \"info\",\n key: \"update.notifier\",\n message: `update notifier: ${config.update_notifier ? \"enabled\" : \"disabled\"}`,\n fix: config.update_notifier\n ? undefined\n : \"run: almanac update --enable-notifier\",\n });\n\n if (state !== null && state.dismissed_versions.length > 0) {\n checks.push({\n status: \"info\",\n key: \"update.dismissed\",\n message: `dismissed versions: ${state.dismissed_versions.join(\", \")}`,\n });\n }\n\n return checks;\n}\n","import { existsSync, readdirSync, statSync } from \"node:fs\";\nimport path from \"node:path\";\n\nimport type Database from \"better-sqlite3\";\n\nimport { ensureFreshIndex } from \"../../indexer/index.js\";\nimport { openIndex } from \"../../indexer/schema.js\";\nimport { findNearestAlmanacDir } from \"../../paths.js\";\nimport { findEntry } from \"../../registry/index.js\";\nimport { runHealth, type HealthReport } from \"../health.js\";\nimport { formatDuration } from \"./duration.js\";\nimport type { Check, DoctorOptions } from \"./types.js\";\n\nexport async function gatherWikiChecks(options: DoctorOptions): Promise<Check[]> {\n const checks: Check[] = [];\n const repoRoot = findNearestAlmanacDir(options.cwd);\n\n if (repoRoot === null) {\n checks.push({\n status: \"info\",\n key: \"wiki.none\",\n message: \"No wiki in current directory\",\n fix: \"run: almanac bootstrap (to create one in this repo)\",\n });\n return checks;\n }\n\n checks.push({\n status: \"info\",\n key: \"wiki.repo\",\n message: `repo: ${repoRoot}`,\n });\n\n try {\n await ensureFreshIndex({ repoRoot });\n } catch {\n // non-fatal: counts below and the health probe report any real issue.\n }\n\n checks.push(await describeRegistry(repoRoot));\n\n const almanacDir = path.join(repoRoot, \".almanac\");\n const dbPath = path.join(almanacDir, \"index.db\");\n checks.push(...describeCounts(dbPath));\n checks.push(describeIndexFreshness(dbPath));\n checks.push(describeLastCapture(almanacDir, options.now));\n checks.push(await describeHealth(repoRoot, options));\n\n return checks;\n}\n\nasync function describeRegistry(repoRoot: string): Promise<Check> {\n try {\n const entry = await findEntry({ path: repoRoot });\n if (entry !== null) {\n return {\n status: \"ok\",\n key: \"wiki.registered\",\n message: `registered as '${entry.name}'`,\n };\n }\n return {\n status: \"info\",\n key: \"wiki.registered\",\n message: \"not yet registered (will register on first command)\",\n };\n } catch (err: unknown) {\n const msg = err instanceof Error ? err.message : String(err);\n return {\n status: \"problem\",\n key: \"wiki.registered\",\n message: `could not read registry: ${msg}`,\n fix: \"inspect ~/.almanac/registry.json; remove or fix the malformed entry\",\n };\n }\n}\n\nfunction describeCounts(dbPath: string): Check[] {\n const checks: Check[] = [];\n let pageCount: number | null = null;\n let topicCount: number | null = null;\n\n if (existsSync(dbPath)) {\n try {\n const db = openIndex(dbPath);\n try {\n pageCount = countRows(db, \"pages\");\n topicCount = countRows(db, \"topics\");\n } finally {\n db.close();\n }\n } catch {\n pageCount = null;\n }\n }\n\n if (pageCount !== null) {\n checks.push({\n status: \"info\",\n key: \"wiki.pages\",\n message: `pages: ${pageCount}`,\n });\n }\n if (topicCount !== null) {\n checks.push({\n status: \"info\",\n key: \"wiki.topics\",\n message: `topics: ${topicCount}`,\n });\n }\n\n return checks;\n}\n\nfunction countRows(db: Database.Database, table: string): number {\n const row = db\n .prepare<[], { n: number }>(`SELECT COUNT(*) AS n FROM ${table}`)\n .get();\n return row?.n ?? 0;\n}\n\nfunction describeIndexFreshness(dbPath: string): Check {\n if (!existsSync(dbPath)) {\n return {\n status: \"info\",\n key: \"wiki.index\",\n message: \"index: not built yet (run any query command)\",\n };\n }\n try {\n const dbMtime = statSync(dbPath).mtimeMs;\n const age = Date.now() - dbMtime;\n return {\n status: \"info\",\n key: \"wiki.index\",\n message: `index: rebuilt ${formatDuration(age)} ago`,\n };\n } catch {\n return {\n status: \"info\",\n key: \"wiki.index\",\n message: \"index: present\",\n };\n }\n}\n\nfunction describeLastCapture(\n almanacDir: string,\n nowFn?: () => Date,\n): Check {\n if (!existsSync(almanacDir)) {\n return {\n status: \"info\",\n key: \"wiki.capture\",\n message: \"last capture: never\",\n };\n }\n let entries: string[];\n try {\n entries = readdirSync(almanacDir);\n } catch {\n return {\n status: \"info\",\n key: \"wiki.capture\",\n message: \"last capture: unknown\",\n };\n }\n const captures = entries\n .filter(\n (e) =>\n e.startsWith(\".capture-\") &&\n (e.endsWith(\".log\") || e.endsWith(\".jsonl\")),\n )\n .map((e) => {\n try {\n return {\n name: e,\n mtime: statSync(path.join(almanacDir, e)).mtimeMs,\n };\n } catch {\n return null;\n }\n })\n .filter((e): e is { name: string; mtime: number } => e !== null);\n if (captures.length === 0) {\n return {\n status: \"info\",\n key: \"wiki.capture\",\n message: \"last capture: never\",\n };\n }\n captures.sort((a, b) => b.mtime - a.mtime);\n const latest = captures[0]!;\n const now = (nowFn?.() ?? new Date()).getTime();\n const age = now - latest.mtime;\n return {\n status: \"info\",\n key: \"wiki.capture\",\n message: `last capture: ${formatDuration(age)} ago (${latest.name})`,\n };\n}\n\nasync function describeHealth(\n repoRoot: string,\n options: DoctorOptions,\n): Promise<Check> {\n const healthFn = options.runHealthFn ?? runHealth;\n try {\n const healthRes = await healthFn({\n cwd: repoRoot,\n json: true,\n });\n const problems = countHealthProblems(healthRes.stdout);\n if (problems === 0) {\n return {\n status: \"ok\",\n key: \"wiki.health\",\n message: \"almanac health reports 0 problems\",\n };\n }\n return {\n status: \"problem\",\n key: \"wiki.health\",\n message: `almanac health reports ${problems} problem${problems === 1 ? \"\" : \"s\"}`,\n fix: \"run: almanac health\",\n };\n } catch (err: unknown) {\n const msg = err instanceof Error ? err.message : String(err);\n return {\n status: \"info\",\n key: \"wiki.health\",\n message: `could not run almanac health: ${msg}`,\n };\n }\n}\n\nconst HEALTH_PROBLEM_KEYS: (keyof HealthReport)[] = [\n \"orphans\",\n \"stale\",\n \"dead_refs\",\n \"broken_links\",\n \"broken_xwiki\",\n \"empty_topics\",\n \"empty_pages\",\n \"slug_collisions\",\n];\n\nfunction countHealthProblems(jsonStdout: string): number {\n try {\n const report = JSON.parse(jsonStdout) as Partial<HealthReport>;\n let total = 0;\n for (const key of HEALTH_PROBLEM_KEYS) {\n const arr = report[key];\n if (Array.isArray(arr)) total += arr.length;\n }\n return total;\n } catch {\n return 0;\n }\n}\n","import { formatReport } from \"./doctor-checks/format.js\";\nimport { gatherInstallChecks } from \"./doctor-checks/install.js\";\nimport { readPackageVersion } from \"./doctor-checks/probes.js\";\nimport type {\n Check,\n CheckStatus,\n DoctorOptions,\n DoctorReport,\n DoctorResult,\n SqliteProbeResult,\n} from \"./doctor-checks/types.js\";\nimport { gatherUpdateChecks } from \"./doctor-checks/updates.js\";\nimport { gatherWikiChecks } from \"./doctor-checks/wiki.js\";\n\nexport type {\n Check,\n CheckStatus,\n DoctorOptions,\n DoctorReport,\n DoctorResult,\n SqliteProbeResult,\n};\n\n/**\n * `almanac doctor` — install + wiki health report.\n *\n * Separate from `almanac health` (which checks graph integrity of a\n * specific wiki). `doctor` answers the \"is this install even set up\n * correctly?\" question that users hit when first trying the tool or when\n * sessions silently stop getting captured.\n *\n * This file is the command composition root. The section-specific probes\n * and formatting live in `doctor-checks/` so each durable fact has one\n * obvious owner.\n */\nexport async function runDoctor(\n options: DoctorOptions,\n): Promise<DoctorResult> {\n const version =\n options.versionOverride ?? readPackageVersion() ?? \"unknown\";\n\n const install: Check[] = options.wikiOnly === true\n ? []\n : await gatherInstallChecks(options);\n\n const updates: Check[] = options.wikiOnly === true\n ? []\n : await gatherUpdateChecks(options, version);\n\n const wiki: Check[] = options.installOnly === true\n ? []\n : await gatherWikiChecks(options);\n\n const report: DoctorReport = { version, install, updates, wiki };\n\n if (options.json === true) {\n return {\n stdout: `${JSON.stringify(report, null, 2)}\\n`,\n stderr: \"\",\n exitCode: 0,\n };\n }\n\n return {\n stdout: formatReport(report, options),\n stderr: \"\",\n exitCode: 0,\n };\n}\n","import { existsSync } from \"node:fs\";\nimport { readFile, rm, writeFile } from \"node:fs/promises\";\nimport { homedir } from \"node:os\";\nimport path from \"node:path\";\n\nimport { runHookUninstall } from \"./hook.js\";\nimport { IMPORT_LINE } from \"./setup.js\";\n\n/**\n * `almanac uninstall` — the reverse of `setup`.\n *\n * Idempotent and order-insensitive: each step is a no-op if that\n * artifact was never installed. We remove exactly the things setup added,\n * nothing else:\n *\n * 1. The `@~/.claude/codealmanac.md` line from `~/.claude/CLAUDE.md`.\n * Other content stays untouched. If removing our line leaves the\n * file empty, we delete the file so our fingerprint doesn't persist\n * as zero bytes.\n * 2. The guide files `~/.claude/codealmanac.md` and\n * `~/.claude/codealmanac-reference.md`.\n * 3. The SessionEnd hook entry (delegated to `runHookUninstall`, which\n * already knows how to leave foreign entries alone).\n *\n * Flags:\n * --yes skip confirmations; remove everything\n * --keep-hook leave the hook alone\n * --keep-guides leave the guides + CLAUDE.md import alone\n *\n * Non-interactive (no TTY) → behaves as if `--yes` was passed. Same\n * contract as `setup`.\n */\n\nexport interface UninstallOptions {\n yes?: boolean;\n keepHook?: boolean;\n keepGuides?: boolean;\n\n // ─── Injection points ────────────────────────────────────────────\n settingsPath?: string;\n hookScriptPath?: string;\n claudeDir?: string;\n isTTY?: boolean;\n stdout?: NodeJS.WritableStream;\n}\n\nexport interface UninstallResult {\n stdout: string;\n stderr: string;\n exitCode: number;\n}\n\nconst BLUE = \"\\x1b[38;5;75m\";\nconst DIM = \"\\x1b[2m\";\nconst RST = \"\\x1b[0m\";\n\nexport async function runUninstall(\n options: UninstallOptions = {},\n): Promise<UninstallResult> {\n const out = options.stdout ?? process.stdout;\n const isTTY =\n options.isTTY ?? (process.stdin.isTTY === true);\n const interactive = isTTY && options.yes !== true;\n const claudeDir = options.claudeDir ?? path.join(homedir(), \".claude\");\n\n out.write(\"\\n\");\n\n // Hook removal.\n let removeHook = true;\n if (options.keepHook === true) {\n removeHook = false;\n } else if (interactive) {\n removeHook = await confirm(\n out,\n \"Remove the SessionEnd hook from ~/.claude/settings.json?\",\n true,\n );\n }\n if (removeHook) {\n const res = await runHookUninstall({\n settingsPath: options.settingsPath,\n hookScriptPath: options.hookScriptPath,\n });\n if (res.exitCode !== 0) {\n return { stdout: \"\", stderr: res.stderr, exitCode: res.exitCode };\n }\n out.write(` ${BLUE}\\u25c7${RST} ${res.stdout.trim()}\\n`);\n } else {\n out.write(` ${DIM}\\u25cb Hook kept${RST}\\n`);\n }\n\n // Guide + import removal.\n let removeGuides = true;\n if (options.keepGuides === true) {\n removeGuides = false;\n } else if (interactive) {\n removeGuides = await confirm(\n out,\n \"Remove the guides + CLAUDE.md import line?\",\n true,\n );\n }\n if (removeGuides) {\n const summary = await removeGuideFiles(claudeDir);\n if (summary.anyChanges) {\n out.write(\n ` ${BLUE}\\u25c7${RST} Guides removed (${summary.filesTouched.join(\", \")})\\n`,\n );\n } else {\n out.write(` ${DIM}\\u25cb Guides not installed${RST}\\n`);\n }\n } else {\n out.write(` ${DIM}\\u25cb Guides kept${RST}\\n`);\n }\n\n out.write(`\\n ${BLUE}\\u25c7${RST} ${BLUE}Uninstall complete${RST}\\n\\n`);\n\n return { stdout: \"\", stderr: \"\", exitCode: 0 };\n}\n\ninterface RemoveGuidesResult {\n anyChanges: boolean;\n filesTouched: string[];\n}\n\nasync function removeGuideFiles(\n claudeDir: string,\n): Promise<RemoveGuidesResult> {\n const touched: string[] = [];\n\n const mini = path.join(claudeDir, \"codealmanac.md\");\n const ref = path.join(claudeDir, \"codealmanac-reference.md\");\n const claudeMd = path.join(claudeDir, \"CLAUDE.md\");\n\n if (existsSync(mini)) {\n await rm(mini, { force: true });\n touched.push(\"codealmanac.md\");\n }\n if (existsSync(ref)) {\n await rm(ref, { force: true });\n touched.push(\"codealmanac-reference.md\");\n }\n\n if (existsSync(claudeMd)) {\n const existing = await readFile(claudeMd, \"utf8\");\n const { changed, body } = removeImportLine(existing);\n if (changed) {\n // If the file is now content-free, delete it outright so our\n // installation leaves no trace. A user who was using CLAUDE.md\n // before we touched it still has their content; only the case\n // where CLAUDE.md contained nothing but our line gets cleaned up.\n if (body.trim().length === 0) {\n await rm(claudeMd, { force: true });\n touched.push(\"CLAUDE.md (deleted)\");\n } else {\n await writeFile(claudeMd, body, \"utf8\");\n touched.push(\"CLAUDE.md\");\n }\n }\n }\n\n return { anyChanges: touched.length > 0, filesTouched: touched };\n}\n\n/**\n * Remove the import line from a CLAUDE.md body. Match is line-anchored\n * (trimmed equality) so we don't munge a line that happens to include\n * the token as part of a longer string. Returns the unchanged body (and\n * `changed: false`) if the line isn't present — this is what makes the\n * command safe to run repeatedly.\n */\nexport function removeImportLine(contents: string): {\n changed: boolean;\n body: string;\n} {\n const eol = contents.includes(\"\\r\\n\") ? \"\\r\\n\" : \"\\n\";\n const lines = contents.split(/\\r?\\n/);\n\n const indices: number[] = [];\n for (let i = 0; i < lines.length; i++) {\n if (lines[i]!.trim() === IMPORT_LINE) indices.push(i);\n }\n if (indices.length === 0) return { changed: false, body: contents };\n\n // Remove the line(s). Iterate from the end so earlier indices stay\n // valid as we splice.\n for (let i = indices.length - 1; i >= 0; i--) {\n lines.splice(indices[i]!, 1);\n }\n\n let body = lines.join(eol);\n\n // Cleanup: collapse any double-blank that our removal created at the\n // spot the line used to live. A best-effort tidy — we don't try to\n // normalize the whole file.\n body = body.replace(/\\n\\n\\n+/g, \"\\n\\n\");\n\n return { changed: true, body };\n}\n\nfunction confirm(\n out: NodeJS.WritableStream,\n question: string,\n defaultYes: boolean,\n): Promise<boolean> {\n return new Promise((resolve) => {\n const hint = defaultYes ? \"[Y/n]\" : \"[y/N]\";\n out.write(` ${BLUE}\\u25c6${RST} ${question} ${DIM}${hint}${RST} `);\n\n let buf = \"\";\n const onData = (chunk: Buffer): void => {\n buf += chunk.toString(\"utf8\");\n const nl = buf.indexOf(\"\\n\");\n if (nl === -1) return;\n process.stdin.removeListener(\"data\", onData);\n process.stdin.pause();\n\n const answer = buf.slice(0, nl).trim().toLowerCase();\n const accepted =\n answer.length === 0\n ? defaultYes\n : answer === \"y\" || answer === \"yes\";\n resolve(accepted);\n };\n\n process.stdin.resume();\n process.stdin.on(\"data\", onData);\n });\n}\n","import { spawn, type SpawnOptions } from \"node:child_process\";\nimport { createRequire } from \"node:module\";\n\nimport { checkForUpdate } from \"../update/check.js\";\nimport {\n readConfig,\n writeConfig,\n type GlobalConfig,\n} from \"../update/config.js\";\nimport { isNewer } from \"../update/semver.js\";\nimport { readState, writeState } from \"../update/state.js\";\n\n/**\n * `almanac update` — manual upgrade command, the counterpart to the\n * persistent nag banner.\n *\n * Default action: shell out to `npm i -g codealmanac@latest` with\n * inherited stdio so the user sees real-time download/install/permission\n * output. Synchronous in the user's terminal — no background install,\n * no mid-invocation swap (see the pair review's Tier-B design for\n * rationale).\n *\n * Flags:\n * --dismiss — mark the current `latest_version` as \"don't nag about\n * this one again\". No install. Writes state and exits.\n * --check — force a registry query regardless of the 24h cache.\n * Shows the result and exits. No install.\n * --enable-notifier / --disable-notifier — flip the global\n * `update_notifier` config. Default is enabled; after\n * `--disable-notifier` the banner won't show even when a new\n * version is available.\n */\n\nexport interface UpdateOptions {\n dismiss?: boolean;\n check?: boolean;\n enableNotifier?: boolean;\n disableNotifier?: boolean;\n\n // ─── Test injection points ──────────────────────────────────────\n /** Override state file path (tests point at a tmpdir). */\n statePath?: string;\n /** Override config file path (tests point at a tmpdir). */\n configPath?: string;\n /** Override the installed version report. */\n installedVersion?: string;\n /**\n * Replace `checkForUpdate` — tests inject a stub that returns a\n * canned state without hitting the registry.\n */\n checkFn?: typeof checkForUpdate;\n /** Replace `spawn` for tests (install path shouldn't run npm). */\n spawnFn?: typeof spawn;\n /** Clock for deterministic `last_check_at` assertions. */\n now?: () => number;\n}\n\nexport interface UpdateResult {\n stdout: string;\n stderr: string;\n exitCode: number;\n}\n\nexport async function runUpdate(\n opts: UpdateOptions = {},\n): Promise<UpdateResult> {\n // Precedence: config toggles > --dismiss > --check > install.\n // Config toggles are disjoint from the other flags (you'd never\n // `update --dismiss --disable-notifier`), but if someone does we\n // apply them in order and take the last action as the \"command\"\n // that sets the exit code.\n if (opts.enableNotifier === true) {\n return await toggleNotifier(true, opts);\n }\n if (opts.disableNotifier === true) {\n return await toggleNotifier(false, opts);\n }\n if (opts.dismiss === true) {\n return await dismissLatest(opts);\n }\n if (opts.check === true) {\n return await forceCheck(opts);\n }\n return await installLatest(opts);\n}\n\n// ─── --dismiss ────────────────────────────────────────────────────\n\nasync function dismissLatest(opts: UpdateOptions): Promise<UpdateResult> {\n const state = await readState(opts.statePath);\n // Nothing to dismiss when we don't know of a newer version. Silently\n // no-op with a message — more helpful than pretending to write state\n // that no future banner would consult.\n if (state.latest_version.length === 0) {\n return {\n stdout:\n \"codealmanac: no pending update to dismiss. \" +\n \"Run `almanac update --check` to query the registry.\\n\",\n stderr: \"\",\n exitCode: 0,\n };\n }\n const installed = opts.installedVersion ?? readInstalledVersion();\n if (!isNewer(state.latest_version, installed)) {\n return {\n stdout: `codealmanac: already on latest (${installed}); nothing to dismiss.\\n`,\n stderr: \"\",\n exitCode: 0,\n };\n }\n if (state.dismissed_versions.includes(state.latest_version)) {\n return {\n stdout: `codealmanac: ${state.latest_version} already dismissed.\\n`,\n stderr: \"\",\n exitCode: 0,\n };\n }\n const next = {\n ...state,\n dismissed_versions: [...state.dismissed_versions, state.latest_version],\n };\n await writeState(next, opts.statePath);\n return {\n stdout:\n `codealmanac: dismissed ${state.latest_version}. The nag banner ` +\n `will not show for this version.\\n` +\n `Run \\`almanac update\\` to upgrade, or \\`almanac update --enable-notifier\\` to re-enable nags.\\n`,\n stderr: \"\",\n exitCode: 0,\n };\n}\n\n// ─── --check ───────────────────────────────────────────────────────\n\nasync function forceCheck(opts: UpdateOptions): Promise<UpdateResult> {\n const installed = opts.installedVersion ?? readInstalledVersion();\n const checkFn = opts.checkFn ?? checkForUpdate;\n const result = await checkFn({\n installedVersion: installed,\n force: true,\n statePath: opts.statePath,\n now: opts.now,\n });\n if (result.fetchFailed) {\n return {\n stdout: \"\",\n stderr:\n `codealmanac: could not reach registry.npmjs.org (timeout or network error).\\n` +\n `Installed: ${installed}. No cached latest available.\\n`,\n exitCode: 1,\n };\n }\n const latest = result.state.latest_version;\n if (latest.length === 0) {\n return {\n stdout: `codealmanac: installed ${installed}; registry did not report a latest tag.\\n`,\n stderr: \"\",\n exitCode: 0,\n };\n }\n if (isNewer(latest, installed)) {\n const dismissed = result.state.dismissed_versions.includes(latest)\n ? \" (dismissed — banner suppressed; `almanac update` still installs)\"\n : \"\";\n return {\n stdout:\n `codealmanac ${latest} available (you're on ${installed})${dismissed}.\\n` +\n `Run: almanac update\\n`,\n stderr: \"\",\n exitCode: 0,\n };\n }\n return {\n stdout: `codealmanac: up to date (${installed}).\\n`,\n stderr: \"\",\n exitCode: 0,\n };\n}\n\n// ─── --enable/--disable-notifier ──────────────────────────────────\n\nasync function toggleNotifier(\n enable: boolean,\n opts: UpdateOptions,\n): Promise<UpdateResult> {\n const config = await readConfig(opts.configPath);\n const next: GlobalConfig = { ...config, update_notifier: enable };\n await writeConfig(next, opts.configPath);\n return {\n stdout:\n enable\n ? \"codealmanac: update notifier enabled. \" +\n \"The pre-command banner will show when a new version is available.\\n\"\n : \"codealmanac: update notifier disabled. \" +\n \"No more pre-command banners. Run `almanac update --check` to see status.\\n\",\n stderr: \"\",\n exitCode: 0,\n };\n}\n\n// ─── default: install ─────────────────────────────────────────────\n\nasync function installLatest(opts: UpdateOptions): Promise<UpdateResult> {\n const spawnFn = opts.spawnFn ?? spawn;\n const installed = opts.installedVersion ?? readInstalledVersion();\n\n // Inherit stdio so npm's progress bar, permission prompts, and\n // peer-dep warnings land in the user's terminal verbatim. No\n // wrapping, no capture — npm output is its own contract.\n const spawnOpts: SpawnOptions = { stdio: \"inherit\" };\n\n return await new Promise<UpdateResult>((resolve) => {\n const child = spawnFn(\n \"npm\",\n [\"i\", \"-g\", \"codealmanac@latest\"],\n spawnOpts,\n );\n\n // Two failure modes need distinct messaging:\n // - ENOENT: npm isn't on PATH. Rare on dev laptops, common in\n // stripped-down CI containers. Tell the user what we tried to\n // run so they can diagnose.\n // - EACCES / exit code 243 / etc.: npm ran but couldn't write\n // to the global prefix. Suggest sudo; don't try it ourselves\n // (silently escalating privileges would be a trust violation,\n // and the pair review explicitly rejected it).\n child.on(\"error\", (err: NodeJS.ErrnoException) => {\n if (err.code === \"ENOENT\") {\n resolve({\n stdout: \"\",\n stderr:\n \"codealmanac: `npm` not found on PATH. \" +\n \"Install Node.js + npm, or install codealmanac via your package manager.\\n\",\n exitCode: 1,\n });\n return;\n }\n resolve({\n stdout: \"\",\n stderr: `codealmanac: failed to run npm: ${err.message}\\n`,\n exitCode: 1,\n });\n });\n\n child.on(\"exit\", async (code, _signal) => {\n const exitCode = code ?? 1;\n if (exitCode !== 0) {\n // Check for the common EACCES cause. npm prints \"EACCES\" to\n // stderr, which we don't have (inherited stdio), so we rely\n // on exit code heuristics + a generic hint.\n const hint =\n `codealmanac: npm install failed (exit ${exitCode}).\\n` +\n `If you see \"EACCES\" above, try: sudo npm i -g codealmanac@latest\\n` +\n `Or install with a version manager (nvm, volta, fnm) to avoid sudo.\\n`;\n resolve({ stdout: \"\", stderr: hint, exitCode });\n return;\n }\n // On success, refresh the state file so the next command's\n // banner reflects that we're current. We can't read the new\n // version out of our own process (we're still running the old\n // build); we record what the state file's latest_version was,\n // on the assumption that npm installed that version.\n try {\n const state = await readState(opts.statePath);\n const now =\n opts.now ?? (() => Math.floor(Date.now() / 1000));\n await writeState(\n {\n last_check_at: now(),\n installed_version: state.latest_version || installed,\n latest_version: state.latest_version || installed,\n dismissed_versions: state.dismissed_versions,\n },\n opts.statePath,\n );\n } catch {\n // Non-fatal: the next `almanac` invocation will re-run the\n // background check and refresh state properly.\n }\n resolve({\n stdout: \"codealmanac: updated.\\n\",\n stderr: \"\",\n exitCode: 0,\n });\n });\n });\n}\n\nfunction readInstalledVersion(): string {\n // Dev layout: `src/commands/update.ts` → `../../package.json`.\n // Bundled layout: `dist/codealmanac.js` → `../package.json`. We try\n // both so the version lookup works from both. (Same approach as\n // `cli.ts` and `doctor.ts`, which hit the same ambiguity.)\n try {\n const require = createRequire(import.meta.url);\n const pkg = require(\"../../package.json\") as { version?: unknown };\n if (typeof pkg.version === \"string\" && pkg.version.length > 0) {\n return pkg.version;\n }\n } catch {\n // Fall through.\n }\n try {\n const require = createRequire(import.meta.url);\n const pkg = require(\"../package.json\") as { version?: unknown };\n if (typeof pkg.version === \"string\" && pkg.version.length > 0) {\n return pkg.version;\n }\n } catch {\n // Fall through.\n }\n return \"unknown\";\n}\n","import { Command } from \"commander\";\n\nimport { runDoctor } from \"../commands/doctor.js\";\nimport { runSetup } from \"../commands/setup.js\";\nimport { runUninstall } from \"../commands/uninstall.js\";\nimport { runUpdate } from \"../commands/update.js\";\nimport { emit } from \"./helpers.js\";\n\nexport function registerSetupCommands(program: Command): void {\n program\n .command(\"setup\")\n .description(\"install the hook + CLAUDE.md guides (bare codealmanac alias)\")\n .option(\"-y, --yes\", \"skip prompts; install everything\")\n .option(\"--skip-hook\", \"opt out of the SessionEnd hook\")\n .option(\"--skip-guides\", \"opt out of the CLAUDE.md guides\")\n .action(\n async (opts: {\n yes?: boolean;\n skipHook?: boolean;\n skipGuides?: boolean;\n }) => {\n const result = await runSetup({\n yes: opts.yes,\n skipHook: opts.skipHook,\n skipGuides: opts.skipGuides,\n });\n emit(result);\n },\n );\n\n program\n .command(\"doctor\")\n .description(\"report on the codealmanac install + current wiki health\")\n .option(\"--json\", \"emit structured JSON\")\n .option(\"--install-only\", \"report only on the install (skip wiki checks)\")\n .option(\"--wiki-only\", \"report only on the current wiki (skip install checks)\")\n .action(\n async (opts: {\n json?: boolean;\n installOnly?: boolean;\n wikiOnly?: boolean;\n }) => {\n const result = await runDoctor({\n cwd: process.cwd(),\n json: opts.json,\n installOnly: opts.installOnly,\n wikiOnly: opts.wikiOnly,\n });\n emit(result);\n },\n );\n\n program\n .command(\"update\")\n .description(\"install the latest codealmanac (synchronous foreground `npm i -g`)\")\n .option(\n \"--dismiss\",\n \"silence the update banner for the current `latest_version` without installing\",\n )\n .option(\"--check\", \"force a registry check now (bypasses the 24h cache); no install\")\n .option(\n \"--enable-notifier\",\n \"re-enable the pre-command update banner (writes ~/.almanac/config.json)\",\n )\n .option(\n \"--disable-notifier\",\n \"silence the pre-command update banner (writes ~/.almanac/config.json)\",\n )\n .action(\n async (opts: {\n dismiss?: boolean;\n check?: boolean;\n enableNotifier?: boolean;\n disableNotifier?: boolean;\n }) => {\n const result = await runUpdate({\n dismiss: opts.dismiss,\n check: opts.check,\n enableNotifier: opts.enableNotifier,\n disableNotifier: opts.disableNotifier,\n });\n emit(result);\n },\n );\n\n program\n .command(\"uninstall\")\n .description(\"remove the hook + guides + import line\")\n .option(\"-y, --yes\", \"skip confirmations; remove everything\")\n .option(\"--keep-hook\", \"don't remove the SessionEnd hook (guides still prompted unless --yes)\")\n .option(\n \"--keep-guides\",\n \"don't remove the guides or CLAUDE.md import (hook still prompted unless --yes)\",\n )\n .action(\n async (opts: {\n yes?: boolean;\n keepHook?: boolean;\n keepGuides?: boolean;\n }) => {\n const result = await runUninstall({\n yes: opts.yes,\n keepHook: opts.keepHook,\n keepGuides: opts.keepGuides,\n });\n emit(result);\n },\n );\n}\n","import { createWriteStream, existsSync, type WriteStream } from \"node:fs\";\nimport { readdir } from \"node:fs/promises\";\nimport { join, relative } from \"node:path\";\n\nimport type { SDKMessage } from \"@anthropic-ai/claude-agent-sdk\";\n\nimport { assertClaudeAuth, type SpawnCliFn } from \"../agent/auth.js\";\nimport { loadPrompt } from \"../agent/prompts.js\";\nimport {\n runAgent,\n type AgentResult,\n type RunAgentOptions,\n} from \"../agent/sdk.js\";\nimport { findNearestAlmanacDir, getRepoAlmanacDir } from \"../paths.js\";\nimport { initWiki } from \"./init.js\";\n\nexport interface BootstrapOptions {\n cwd: string;\n /** Suppress per-tool-use streaming output; print only errors + final line. */\n quiet?: boolean;\n /** Override the agent model. Defaults to the SDK default (sonnet-4-6). */\n model?: string;\n /** Overwrite a populated wiki. Default refuses with a pointer at `capture`. */\n force?: boolean;\n /** Injectable agent runner — tests replace this with a fake. */\n runAgent?: (opts: RunAgentOptions) => Promise<AgentResult>;\n /**\n * Injectable subprocess spawner for the Claude auth-status check.\n * Tests substitute a stub that emits canned JSON without running the\n * bundled CLI. Production leaves this undefined and `assertClaudeAuth`\n * falls through to `defaultSpawnCli`.\n */\n spawnCli?: SpawnCliFn;\n /**\n * Clock injection, for tests. Otherwise `Date.now()` timestamps the\n * transcript log filename and defaults to the SDK's session_id once\n * we receive one (the filename is chosen BEFORE the agent starts so\n * we can stream to it).\n */\n now?: () => Date;\n}\n\nexport interface BootstrapResult {\n stdout: string;\n stderr: string;\n exitCode: number;\n}\n\n/**\n * Tools the bootstrap agent is permitted to use. Bootstrap reads the repo\n * (Read/Glob/Grep), runs quick inspection commands (Bash — scoped by the\n * prompt, not by the SDK — the agent isn't expected to do anything\n * destructive), and writes scaffolding (Write/Edit). Notably absent:\n * - `Agent` — bootstrap has no subagents (reviewer is slice 5).\n * - `WebFetch` / `WebSearch` — prompt is explicit that we work from the\n * repo, not the internet. Adding these would invite drift.\n * - MCP servers — none needed for a local filesystem scan.\n */\nconst BOOTSTRAP_TOOLS = [\"Read\", \"Write\", \"Edit\", \"Glob\", \"Grep\", \"Bash\"];\n\n/**\n * `almanac bootstrap` — first Claude Agent SDK integration.\n *\n * Flow:\n * 1. Auth gate (ANTHROPIC_API_KEY). Fail fast with a clean error.\n * 2. Resolve repo root (existing `.almanac/` or cwd).\n * 3. Refuse-if-populated unless --force.\n * 4. Auto-init silently if `.almanac/` doesn't exist yet.\n * 5. Load `prompts/bootstrap.md`.\n * 6. Run the agent with BOOTSTRAP_TOOLS, cwd = repo root.\n * 7. Stream tool-uses to stdout (unless --quiet); write the full raw\n * transcript to `.almanac/.bootstrap-<session>.log`.\n * 8. Print a final `[done]` / `[failed]` line with cost + turns.\n *\n * Non-zero exit on failure so shell users can pipe into `&&`.\n */\nexport async function runBootstrap(\n options: BootstrapOptions,\n): Promise<BootstrapResult> {\n // Fail before loading prompts so we don't do filesystem work on a request\n // that can't succeed. `assertClaudeAuth` accepts either subscription\n // OAuth (via the bundled SDK CLI) or `ANTHROPIC_API_KEY`; missing both\n // surfaces a two-option error and MUST exit non-zero so the SessionEnd\n // hook (which backgrounds the process and ignores stderr) doesn't\n // treat silent auth failure as success.\n try {\n await assertClaudeAuth(options.spawnCli);\n } catch (err: unknown) {\n const msg = err instanceof Error ? err.message : String(err);\n return {\n stdout: \"\",\n stderr: `almanac: ${msg}\\n`,\n exitCode: 1,\n };\n }\n\n // Repo root: honor an already-initialized wiki anywhere above us.\n // Otherwise treat `cwd` as the root for a fresh wiki.\n const repoRoot = findNearestAlmanacDir(options.cwd) ?? options.cwd;\n const almanacDir = getRepoAlmanacDir(repoRoot);\n const pagesDir = join(almanacDir, \"pages\");\n\n // Refuse to clobber a populated wiki. `almanac capture` is the tool for\n // maintaining wikis after bootstrap.\n if (options.force !== true && existsSync(pagesDir)) {\n const existing = await countMarkdownPages(pagesDir);\n if (existing > 0) {\n return {\n stdout: \"\",\n stderr:\n `almanac: .almanac/ already initialized with ${existing} page${existing === 1 ? \"\" : \"s\"}. ` +\n \"Use 'almanac capture' instead, or --force to overwrite.\\n\",\n exitCode: 1,\n };\n }\n }\n\n // Auto-init silently if missing. `initWiki` is idempotent — re-running\n // on an existing wiki is a no-op for the README / pages dir.\n if (!existsSync(almanacDir)) {\n try {\n await initWiki({ cwd: repoRoot });\n } catch (err: unknown) {\n // Per the slice spec: auto-init failures should be loud. The user\n // needs to know init is broken, not see a cascading agent error.\n const msg = err instanceof Error ? err.message : String(err);\n return {\n stdout: \"\",\n stderr: `almanac: init failed during bootstrap: ${msg}\\n`,\n exitCode: 1,\n };\n }\n }\n\n const systemPrompt = await loadPrompt(\"bootstrap\");\n\n // Transcript log filename: timestamp-based so it's sortable. The session\n // ID from the SDK isn't known until the first message arrives — by then\n // we'd already want somewhere to stream to. Use a clock-derived prefix\n // that's still meaningful even on a run that fails before producing a\n // session_id.\n const now = options.now?.() ?? new Date();\n const logName = `.bootstrap-${formatTimestamp(now)}.log`;\n const logPath = join(almanacDir, logName);\n const logStream = createWriteStream(logPath, { flags: \"w\" });\n\n // The streaming formatter is what the user sees on stdout unless\n // --quiet is set. The raw log captures EVERYTHING (including\n // `stream_event` partials) for postmortem.\n const out = process.stdout;\n const formatter = new StreamingFormatter({\n write: (line: string) => {\n if (options.quiet !== true) out.write(line);\n },\n });\n\n const onMessage = (msg: SDKMessage): void => {\n // Write the raw message to the transcript. Keep one JSON per line so\n // the log is grep-able and can be re-parsed if needed.\n try {\n logStream.write(`${JSON.stringify(msg)}\\n`);\n } catch {\n // Serialization failures are non-fatal — we'd rather keep streaming\n // to stdout than crash because one message had a circular ref.\n }\n formatter.handle(msg);\n };\n\n const runner = options.runAgent ?? runAgent;\n\n const userPrompt = `Begin the bootstrap now. Working directory: ${repoRoot}.`;\n\n let result: AgentResult;\n try {\n result = await runner({\n systemPrompt,\n prompt: userPrompt,\n allowedTools: BOOTSTRAP_TOOLS,\n cwd: repoRoot,\n model: options.model,\n onMessage,\n });\n } finally {\n await closeStream(logStream);\n }\n\n const finalLine = formatFinalLine(result, logPath, repoRoot);\n\n if (result.success) {\n return {\n stdout: `${finalLine}\\n`,\n stderr: \"\",\n exitCode: 0,\n };\n }\n\n return {\n stdout: options.quiet === true ? \"\" : `${finalLine}\\n`,\n stderr: `almanac: bootstrap failed: ${result.error ?? \"unknown error\"}\\n`,\n exitCode: 1,\n };\n}\n\n/**\n * Format the final line the user sees. On success it's a one-liner with\n * cost + turns; on failure we still print the cost/turns so the user\n * knows what the partial run used. The log path is shown relative to\n * `repoRoot` to keep the line short.\n */\nfunction formatFinalLine(\n result: AgentResult,\n logPath: string,\n repoRoot: string,\n): string {\n const status = result.success ? \"done\" : \"failed\";\n const rel = relative(repoRoot, logPath);\n const cost = `$${result.cost.toFixed(3)}`;\n return `[${status}] cost: ${cost}, turns: ${result.turns} (transcript: ${rel})`;\n}\n\nasync function countMarkdownPages(pagesDir: string): Promise<number> {\n try {\n const entries = await readdir(pagesDir, { withFileTypes: true });\n return entries.filter((e) => e.isFile() && e.name.endsWith(\".md\")).length;\n } catch {\n return 0;\n }\n}\n\nfunction closeStream(stream: WriteStream): Promise<void> {\n return new Promise((resolve) => {\n stream.end(() => resolve());\n });\n}\n\nfunction formatTimestamp(d: Date): string {\n // YYYYMMDD-HHMMSS, local time. Collision-proof enough for human use\n // (one bootstrap per second, per repo is an acceptable ceiling).\n const pad = (n: number): string => n.toString().padStart(2, \"0\");\n const y = d.getFullYear();\n const mo = pad(d.getMonth() + 1);\n const da = pad(d.getDate());\n const h = pad(d.getHours());\n const mi = pad(d.getMinutes());\n const s = pad(d.getSeconds());\n return `${y}${mo}${da}-${h}${mi}${s}`;\n}\n\n/**\n * Translates SDK messages into one-line-per-tool-use output.\n *\n * Design rules:\n * - One line per `tool_use` block, not per token. Users scanning the\n * output want to see \"what the agent did\", not a tail of the\n * assistant's prose.\n * - `Bash` gets special formatting because the input is most usefully\n * rendered as the command being run.\n * - On final `result`, emit a summary line. Callers can suppress all\n * intermediate output via `--quiet` and still get the summary.\n * - Tool paths are shown relative to the cwd (not implemented here\n * because the SDK doesn't give us cwd on every message; users get\n * the raw input). The cwd-relative rendering is a nice-to-have that\n * we can layer on later without changing the API.\n *\n * Exported for testing — `StreamingFormatter` is easier to unit-test in\n * isolation than the whole command.\n */\nexport class StreamingFormatter {\n private readonly sink: { write: (line: string) => void };\n /**\n * Current agent label. Starts as \"bootstrap\"; switches when we see an\n * `Agent` tool-use (slice 5 will exercise this). We still track it here\n * so the formatter can stay shared between bootstrap and capture.\n */\n private currentAgent = \"bootstrap\";\n\n constructor(sink: { write: (line: string) => void }) {\n this.sink = sink;\n }\n\n /**\n * Swap the top-level agent label. `capture` uses this to relabel from\n * the default \"bootstrap\" to \"writer\" — otherwise the writer's tool-use\n * output would render as `[bootstrap] …`, which is confusing when you're\n * reading capture logs.\n */\n setAgent(name: string): void {\n this.currentAgent = name;\n }\n\n handle(msg: SDKMessage): void {\n if (msg.type === \"assistant\") {\n for (const block of msg.message.content) {\n if (block.type !== \"tool_use\") continue;\n this.handleToolUse(block.name, block.input);\n }\n return;\n }\n\n if (msg.type === \"result\") {\n // The command-level finalLine is what the user sees at the end of\n // stdout; the formatter also emits one here so live-tailing a\n // transcript log shows the full run. Kept terse.\n const status =\n msg.subtype === \"success\" ? \"done\" : `failed (${msg.subtype})`;\n this.sink.write(\n `[${status}] cost: $${msg.total_cost_usd.toFixed(3)}, turns: ${msg.num_turns}\\n`,\n );\n return;\n }\n }\n\n private handleToolUse(name: string, rawInput: unknown): void {\n const input = normalizeToolInput(rawInput);\n\n if (name === \"Agent\") {\n // Subagent dispatch. Track the label so subsequent tool-uses show\n // up under the right agent bracket. The label is whatever the\n // parent passed as `subagent_type`, falling back to \"subagent\" if\n // the input was malformed (shouldn't happen, but defensively).\n const sub =\n typeof input.subagent_type === \"string\" ? input.subagent_type : \"subagent\";\n this.currentAgent = sub;\n this.sink.write(`[${sub}] starting\\n`);\n return;\n }\n\n const summary = formatToolSummary(name, input);\n this.sink.write(`[${this.currentAgent}] ${summary}\\n`);\n }\n}\n\n/**\n * SDK quirk: `tool_use.input` arrives as either an object OR a\n * JSON-encoded string. Always normalize before touching fields.\n */\nfunction normalizeToolInput(raw: unknown): Record<string, unknown> {\n if (typeof raw === \"string\") {\n try {\n const parsed = JSON.parse(raw);\n if (parsed !== null && typeof parsed === \"object\") {\n return parsed as Record<string, unknown>;\n }\n } catch {\n // Fall through to empty object.\n }\n return {};\n }\n if (raw !== null && typeof raw === \"object\") {\n return raw as Record<string, unknown>;\n }\n return {};\n}\n\n/**\n * Render a tool call as a single human-readable line. Not exhaustive —\n * we cover the tools bootstrap actually invokes and fall back to the\n * tool name + a terse input summary for anything else.\n */\nfunction formatToolSummary(\n name: string,\n input: Record<string, unknown>,\n): string {\n switch (name) {\n case \"Read\": {\n const target = stringField(input, \"file_path\") ?? \"?\";\n return `reading ${target}`;\n }\n case \"Write\": {\n const target = stringField(input, \"file_path\") ?? \"?\";\n return `writing ${target}`;\n }\n case \"Edit\": {\n const target = stringField(input, \"file_path\") ?? \"?\";\n return `editing ${target}`;\n }\n case \"Glob\": {\n const pattern = stringField(input, \"pattern\") ?? \"?\";\n return `glob ${pattern}`;\n }\n case \"Grep\": {\n const pattern = stringField(input, \"pattern\") ?? \"?\";\n return `grep ${pattern}`;\n }\n case \"Bash\": {\n const command = stringField(input, \"command\") ?? \"?\";\n // Truncate long commands so one tool-use stays one line.\n const trimmed =\n command.length > 80 ? `${command.slice(0, 77)}...` : command;\n return `bash ${trimmed}`;\n }\n default: {\n // Unknown or MCP tool. Show the name; omit the input to avoid\n // spamming the terminal with arbitrary JSON.\n return name;\n }\n }\n}\n\nfunction stringField(\n input: Record<string, unknown>,\n key: string,\n): string | undefined {\n const value = input[key];\n return typeof value === \"string\" ? value : undefined;\n}\n","import { existsSync } from \"node:fs\";\nimport { readFile } from \"node:fs/promises\";\nimport path from \"node:path\";\nimport { fileURLToPath } from \"node:url\";\n\n/**\n * Loads bundled prompt text from the `prompts/` directory that ships with\n * the npm package. Used by `almanac bootstrap` (slice 4) and `almanac\n * capture` (slice 5).\n *\n * ## Why not embed the prompts as TS string literals?\n *\n * The non-negotiable from the spec (see CLAUDE.md → \"Non-negotiables\"):\n * \"Prompts are shipped from the npm package. They live in `prompts/` at\n * repo root, are bundled into `files` in `package.json`, and the agent\n * harness reads them from the package install path at runtime.\"\n *\n * Keeping them as separate files means they can be reviewed as prose,\n * diffed meaningfully, and (in the future) edited by users without\n * rebuilding the package.\n *\n * ## Path resolution\n *\n * Two runtime layouts need to work:\n *\n * 1. **Installed (`npm i -g codealmanac`).** The entry point lives at\n * `dist/codealmanac.js`; prompts at `prompts/*.md`. Walking up from\n * `import.meta.url` (`.../<pkg>/dist/codealmanac.js`) one level and\n * into `prompts/` hits the right directory.\n *\n * 2. **Source dev.** During `npm run dev`, tsup emits to `dist/` just\n * like in production, so case 1 applies. Tests import\n * `src/agent/prompts.ts` directly via tsx/vitest; `import.meta.url`\n * points at `src/agent/prompts.ts`. Walking up two levels from there\n * lands at the repo root, where `prompts/` also lives.\n *\n * We probe a small list of candidates in order and use the first that\n * contains all three expected prompt files. This keeps a single source of\n * truth — the `prompts/` directory on disk — without baking in whether\n * we're running from `dist/` or `src/`.\n */\n\nexport type PromptName = \"bootstrap\" | \"writer\" | \"reviewer\";\n\nconst PROMPT_NAMES: readonly PromptName[] = [\n \"bootstrap\",\n \"writer\",\n \"reviewer\",\n];\n\n/**\n * Override the prompts directory, for tests. Production code should never\n * call this — the auto-resolution handles both installed + source layouts.\n */\nlet overrideDir: string | null = null;\n\nexport function setPromptsDirForTesting(dir: string | null): void {\n overrideDir = dir;\n}\n\n/**\n * Resolve the prompts directory by probing candidate locations. Cached\n * after the first call so repeated `loadPrompt()` calls don't stat the\n * filesystem more than once per process.\n */\nlet resolvedDir: string | null = null;\n\nexport function resolvePromptsDir(): string {\n if (overrideDir !== null) return overrideDir;\n if (resolvedDir !== null) return resolvedDir;\n\n const here = path.dirname(fileURLToPath(import.meta.url));\n\n // Candidates, most-specific first. Each path is where `prompts/` MIGHT\n // live given some plausible bundle layout. The first one that exists\n // and contains our three expected files wins.\n const candidates = [\n // Bundled dist layout: `.../<pkg>/dist/codealmanac.js` → `../prompts`\n path.resolve(here, \"..\", \"prompts\"),\n // Source layout: `.../<pkg>/src/agent/prompts.ts` → `../../prompts`\n path.resolve(here, \"..\", \"..\", \"prompts\"),\n // Defensive fallback: if tsup someday emits a nested `dist/src/agent`,\n // walk up three levels.\n path.resolve(here, \"..\", \"..\", \"..\", \"prompts\"),\n ];\n\n for (const dir of candidates) {\n if (isPromptsDir(dir)) {\n resolvedDir = dir;\n return dir;\n }\n }\n\n // If none matched, give a helpful error with the candidates we tried.\n // This typically means the package was installed without the `prompts/`\n // dir included — shouldn't happen unless someone broke `files` in\n // package.json.\n throw new Error(\n \"could not locate bundled prompts/ directory. Tried:\\n\" +\n candidates.map((c) => ` - ${c}`).join(\"\\n\"),\n );\n}\n\nfunction isPromptsDir(dir: string): boolean {\n if (!existsSync(dir)) return false;\n // Require all three prompts to be present. A half-populated directory\n // is worse than not finding one — we'd rather error early.\n return PROMPT_NAMES.every((name) =>\n existsSync(path.join(dir, `${name}.md`)),\n );\n}\n\nexport async function loadPrompt(name: PromptName): Promise<string> {\n const dir = resolvePromptsDir();\n return readFile(path.join(dir, `${name}.md`), \"utf8\");\n}\n","import { query } from \"@anthropic-ai/claude-agent-sdk\";\nimport type {\n AgentDefinition,\n SDKMessage,\n} from \"@anthropic-ai/claude-agent-sdk\";\n\n/**\n * Thin wrapper around `@anthropic-ai/claude-agent-sdk`'s `query()`. This is\n * the ONLY module that imports from the SDK — every other command imports\n * from here. Slice 5 (capture) reuses this wrapper unchanged.\n *\n * Why a wrapper at all:\n * 1. Sets defaults (`maxTurns`, `includePartialMessages`, model) once so\n * commands stay small.\n * 2. Translates the SDK's rich message types into a {cost, turns, result}\n * summary the commands actually care about.\n *\n * The auth gate lives in `src/agent/auth.ts`. Commands call\n * `assertClaudeAuth()` BEFORE `runAgent` so we fail with a clean two-path\n * error before the SDK generator spins up. `runAgent` itself doesn't\n * re-check — the SDK reads whichever of (subscription OAuth,\n * `ANTHROPIC_API_KEY`) is present.\n *\n * Keep this module SMALL. If a feature can live in the caller, it should.\n */\n\nexport interface RunAgentOptions {\n /** Full system prompt text — usually loaded from `prompts/*.md`. */\n systemPrompt: string;\n /** User prompt / kick-off message. */\n prompt: string;\n /** Tool allowlist, e.g. `[\"Read\", \"Write\", \"Edit\", \"Glob\", \"Grep\", \"Bash\"]`. */\n allowedTools: string[];\n /**\n * Subagent definitions (slice 5 passes `{ reviewer: ... }`). Defaults to\n * `{}` — bootstrap has no subagents.\n */\n agents?: Record<string, AgentDefinition>;\n /** Working directory the agent's tools operate in (repo root). */\n cwd: string;\n /**\n * Model override. Defaults to `claude-sonnet-4-6`. Note the FULL form —\n * `options.model` requires `claude-sonnet-4-6`, not `sonnet`.\n */\n model?: string;\n /**\n * Hard cap on turns. Defaults to 100. SDK enforces this as a strict stop\n * (no graceful wrap-up turn), so set generously.\n */\n maxTurns?: number;\n /**\n * Observer called for every SDK message. The formatter (streaming\n * output, transcript log) runs here.\n */\n onMessage?: (msg: SDKMessage) => void;\n}\n\nexport interface AgentResult {\n /** `true` when the SDK emitted a `result` with `subtype: \"success\"`. */\n success: boolean;\n /** Total USD cost reported by the final `result` message. */\n cost: number;\n /** Number of turns the agent used. */\n turns: number;\n /** The assistant's final textual result, if any. */\n result: string;\n /** Session ID captured from the first assistant/result message. */\n sessionId?: string;\n /** Populated when `success === false`. */\n error?: string;\n}\n\n/**\n * Run an agent to completion. Iterates the SDK's `AsyncGenerator` and\n * returns a summary. Any thrown error in the `for await` becomes a\n * `success: false` result with the error message attached — we don't\n * propagate because the caller wants to write transcripts + print\n * formatted output regardless of outcome.\n *\n * The caller is responsible for running `assertClaudeAuth()` BEFORE\n * loading prompts or printing progress — see `bootstrap.ts`/`capture.ts`.\n * `runAgent` itself no longer re-checks; the SDK will happily pick up\n * whichever of (subscription OAuth, `ANTHROPIC_API_KEY`) the environment\n * provides.\n */\nexport async function runAgent(opts: RunAgentOptions): Promise<AgentResult> {\n\n const q = query({\n prompt: opts.prompt,\n options: {\n systemPrompt: opts.systemPrompt,\n allowedTools: opts.allowedTools,\n agents: opts.agents ?? {},\n cwd: opts.cwd,\n model: opts.model ?? \"claude-sonnet-4-6\",\n maxTurns: opts.maxTurns ?? 100,\n // REQUIRED for streaming text deltas. Without it, `stream_event`\n // messages never fire and the CLI has no progress visibility during\n // long turns. See docs/research/agent-sdk.md §12 pitfall #1.\n includePartialMessages: true,\n },\n });\n\n let cost = 0;\n let turns = 0;\n let result = \"\";\n let sessionId: string | undefined;\n let success = false;\n let errorMsg: string | undefined;\n\n try {\n for await (const msg of q) {\n opts.onMessage?.(msg);\n\n // Capture session_id from the first message that carries it.\n // Per the research doc, it appears on the first `assistant` or on\n // the `result` — whichever arrives first.\n if (\n sessionId === undefined &&\n typeof (msg as { session_id?: unknown }).session_id === \"string\"\n ) {\n sessionId = (msg as { session_id: string }).session_id;\n }\n\n if (msg.type === \"result\") {\n // `SDKResultMessage = SDKResultSuccess | SDKResultError`. Both\n // carry `total_cost_usd` and `num_turns`; only success has\n // `result` (the final assistant text).\n cost = msg.total_cost_usd;\n turns = msg.num_turns;\n if (msg.subtype === \"success\") {\n success = true;\n result = msg.result;\n } else {\n success = false;\n errorMsg =\n // `SDKResultError` variants don't carry a `result` string; the\n // useful detail lives in `errors` (array of strings) or the\n // subtype itself (e.g. \"error_max_turns\").\n (msg.errors?.join(\"; \") ?? \"\") || `agent error: ${msg.subtype}`;\n }\n }\n }\n } catch (err: unknown) {\n errorMsg = err instanceof Error ? err.message : String(err);\n success = false;\n }\n\n return { success, cost, turns, result, sessionId, error: errorMsg };\n}\n","import { existsSync } from \"node:fs\";\nimport { mkdir, readFile, writeFile } from \"node:fs/promises\";\nimport { basename, join } from \"node:path\";\n\nimport { findNearestAlmanacDir, getRepoAlmanacDir } from \"../paths.js\";\nimport { toKebabCase } from \"../slug.js\";\nimport {\n addEntry,\n ensureGlobalDir,\n type RegistryEntry,\n} from \"../registry/index.js\";\n\nexport interface InitOptions {\n cwd: string;\n name?: string;\n description?: string;\n}\n\nexport interface InitResult {\n entry: RegistryEntry;\n almanacDir: string;\n created: boolean; // false if .almanac/ already existed (idempotent re-init)\n}\n\n/**\n * Scaffold `.almanac/` in the repo and register it globally.\n *\n * Idempotent: running `init` on a repo that already has `.almanac/` is\n * fine — we re-register (refreshing the name/description) and skip\n * anything that already exists. We never overwrite a user-authored\n * `README.md` or touch existing pages.\n *\n * If `cwd` lives inside a subdirectory of an existing wiki, we walk up to\n * the wiki root and operate there. `almanac init` from `src/nested/`\n * should update the enclosing wiki, not create a nested one at\n * `src/nested/.almanac/` (which would fragment the registry and leave a\n * confusing orphan `.almanac/` on disk).\n */\nexport async function initWiki(options: InitOptions): Promise<InitResult> {\n // If cwd is already inside a wiki, prefer that root. Otherwise treat\n // cwd as the new wiki root.\n const repoRoot = findNearestAlmanacDir(options.cwd) ?? options.cwd;\n\n const almanacDir = getRepoAlmanacDir(repoRoot);\n const pagesDir = join(almanacDir, \"pages\");\n const readmePath = join(almanacDir, \"README.md\");\n\n const alreadyExisted = existsSync(almanacDir);\n\n await mkdir(pagesDir, { recursive: true });\n\n if (!existsSync(readmePath)) {\n await writeFile(readmePath, starterReadme(), \"utf8\");\n }\n\n await ensureGitignoreHasIndexDb(repoRoot);\n\n const name = toKebabCase(options.name ?? basename(repoRoot));\n if (name.length === 0) {\n throw new Error(\n \"could not derive a wiki name from the current directory; pass --name\",\n );\n }\n\n const description = (options.description ?? \"\").trim();\n\n await ensureGlobalDir();\n const entry: RegistryEntry = {\n name,\n description,\n path: repoRoot,\n registered_at: new Date().toISOString(),\n };\n await addEntry(entry);\n\n return { entry, almanacDir, created: !alreadyExisted };\n}\n\n/**\n * Ensure `.gitignore` in the repo root contains the codealmanac-derived\n * files that should never be committed.\n *\n * We ignore three paths:\n * - `.almanac/index.db` — the SQLite index (derived)\n * - `.almanac/index.db-wal` — WAL mode sidecar (present during writes)\n * - `.almanac/index.db-shm` — shared-memory sidecar (WAL mode)\n *\n * All three are derived from the markdown pages. The sidecars only show\n * up while better-sqlite3 has the DB open and can vanish between `git\n * status` calls, but explicitly ignoring them prevents \"dirty worktree\"\n * noise during active reindexing.\n *\n * We add the block regardless of whether the file exists (creating\n * `.gitignore` if needed), and we add any target lines that aren't\n * already present. Existing targets are left alone. If none of the\n * target lines need adding, the file is not touched at all.\n *\n * Formatting: when we do append, we guarantee exactly one blank line\n * between the prior content and our appended block. If the `# codealmanac`\n * header is already present but new targets need adding, we just append\n * the missing lines (no duplicate header).\n */\nasync function ensureGitignoreHasIndexDb(cwd: string): Promise<void> {\n const path = join(cwd, \".gitignore\");\n const targets = [\n \".almanac/index.db\",\n \".almanac/index.db-wal\",\n \".almanac/index.db-shm\",\n // Capture/bootstrap/ingest log files written by the AI pipeline.\n // These can be multi-megabyte JSONL files and should never be\n // committed. Bug #4 from codealmanac-known-bugs.md: a 1.8MB bootstrap\n // log was accidentally committed before this line was added.\n \".almanac/.capture-*\",\n \".almanac/.bootstrap-*\",\n \".almanac/.ingest-*\",\n ];\n\n let existing = \"\";\n if (existsSync(path)) {\n existing = await readFile(path, \"utf8\");\n }\n\n // Normalize to line comparison to avoid false negatives on trailing\n // whitespace or CRLF line endings.\n const lines = existing.split(/\\r?\\n/).map((l) => l.trim());\n const missing = targets.filter((t) => !lines.includes(t));\n if (missing.length === 0) return;\n\n const hasHeader = lines.includes(\"# codealmanac\");\n const block = hasHeader\n ? missing.join(\"\\n\") + \"\\n\"\n : `# codealmanac\\n${missing.join(\"\\n\")}\\n`;\n\n // Three cases for the separator before the appended block:\n // - empty file: no separator needed\n // - ends with newline: one more newline produces a single blank line\n // - no trailing newline: two newlines (one to terminate the last line,\n // one for the blank separator)\n const sep =\n existing.length === 0 ? \"\" : existing.endsWith(\"\\n\") ? \"\\n\" : \"\\n\\n\";\n await writeFile(path, `${existing}${sep}${block}`, \"utf8\");\n}\n\n/**\n * The starter `.almanac/README.md` content. Based on the \"Wiki README\" and\n * \"Notability bar\" sections of the design spec. Kept opinionated but short\n * (~70 lines) — the user is expected to edit it to fit the repo.\n */\nfunction starterReadme(): string {\n return `# Wiki\n\nThis is the codealmanac wiki for this repository. It captures the knowledge\nthe code itself can't say — decisions, flows, invariants, gotchas, incidents.\n\nThe primary reader is an AI coding agent. The secondary reader is a human\nskimming to understand the shape of the codebase. Write accordingly: dense,\nfactual, linked.\n\n## Notability bar\n\nWrite a page when there is **non-obvious knowledge that will help a future\nagent**. Specifically:\n\n- A decision that took discussion, research, or trial-and-error\n- A gotcha discovered through failure\n- A cross-cutting flow that spans multiple files and isn't obvious from any\n one of them\n- A constraint or invariant not visible from the code\n- An entity (technology, service, system) referenced by multiple pages\n\nDo not write pages that restate what the code does. Do not write pages of\ninference — only of observation. Silence is an acceptable outcome.\n\n## Topic taxonomy\n\nTopics form a DAG; pages can belong to multiple topics. Start with these and\ngrow as the wiki does:\n\n- \\`stack\\` — technologies and services we use (frameworks, databases, APIs)\n- \\`systems\\` — custom systems we built (auth, billing, search)\n- \\`flows\\` — multi-file processes end-to-end (checkout-flow, publish-flow)\n- \\`decisions\\` — \"why X over Y\"\n- \\`incidents\\` — recorded failures and their fixes\n- \\`concepts\\` — shared vocabulary specific to this codebase\n\nDomain topics (\\`auth\\`, \\`payments\\`, \\`frontend\\`, \\`backend\\`) live alongside\nthese. A page about JWT rotation belongs to both \\`auth\\` and \\`decisions\\`.\n\n## Page shapes\n\nFour shapes cover most of what gets written. They are suggestions, not a\nschema — a page that fits none of them is fine.\n\n- **Entity** — a stable named thing (Supabase, Stripe, the search service)\n- **Decision** — why we chose X over Y\n- **Flow** — how a multi-file process works end-to-end\n- **Gotcha** — a specific surprise, failure, or constraint\n\n## Writing conventions\n\n- Every sentence contains a specific fact. If it doesn't, cut it.\n- Neutral tone. \"is\", not \"serves as\". No \"plays a pivotal role\", no\n interpretive \"-ing\" clauses, no vague attribution (\"experts argue\").\n- No hedging or knowledge-gap disclaimers. If you don't know, don't write\n the sentence.\n- Prose first. Bullets for genuine lists. Tables only for structured\n comparison.\n- No formulaic conclusions. End with the last substantive fact.\n\n## Linking\n\nOne \\`[[...]]\\` syntax for everything, disambiguated by content:\n\n- \\`[[checkout-flow]]\\` — page slug\n- \\`[[src/checkout/handler.ts]]\\` — file reference\n- \\`[[src/checkout/]]\\` — folder reference (trailing slash)\n- \\`[[other-wiki:slug]]\\` — cross-wiki reference\n\nEvery page should link to at least one entity when possible. A page with no\nentity link is suspect.\n\n## Pages live in \\`.almanac/pages/\\`\n\nOne markdown file per page, kebab-case slug. Frontmatter carries \\`topics:\\`\nand optional \\`files:\\`. The rest is prose.\n`;\n}\n","import { createHash } from \"node:crypto\";\nimport {\n createWriteStream,\n existsSync,\n statSync,\n type WriteStream,\n} from \"node:fs\";\nimport { readFile, readdir, stat } from \"node:fs/promises\";\nimport { homedir } from \"node:os\";\nimport { basename, join, relative } from \"node:path\";\n\nimport type { AgentDefinition, SDKMessage } from \"@anthropic-ai/claude-agent-sdk\";\n\nimport { assertClaudeAuth, type SpawnCliFn } from \"../agent/auth.js\";\nimport { loadPrompt } from \"../agent/prompts.js\";\nimport {\n runAgent,\n type AgentResult,\n type RunAgentOptions,\n} from \"../agent/sdk.js\";\nimport { parseFrontmatter } from \"../indexer/frontmatter.js\";\nimport { findNearestAlmanacDir, getRepoAlmanacDir } from \"../paths.js\";\nimport { StreamingFormatter } from \"./bootstrap.js\";\n\nexport interface CaptureOptions {\n cwd: string;\n /** Explicit transcript path. Skips auto-resolution. */\n transcriptPath?: string;\n /** Target a specific session ID. */\n sessionId?: string;\n /** Suppress per-tool-use streaming; print only the final summary line. */\n quiet?: boolean;\n /** Model override. Defaults to the SDK default (sonnet-4-6). */\n model?: string;\n /** Injectable agent runner — tests replace this with a fake. */\n runAgent?: (opts: RunAgentOptions) => Promise<AgentResult>;\n /**\n * Injectable spawner for the Claude auth-status subprocess. Tests pass\n * a stub; production uses `defaultSpawnCli` which shells out to the\n * bundled SDK's `cli.js`.\n */\n spawnCli?: SpawnCliFn;\n /** Clock injection for deterministic log filenames in tests. */\n now?: () => Date;\n /**\n * Override the Claude Code projects directory when auto-resolving a\n * transcript. Production code leaves this undefined and we fall back to\n * `~/.claude/projects`; tests point it at a fixture dir.\n */\n claudeProjectsDir?: string;\n}\n\nexport interface CaptureResult {\n stdout: string;\n stderr: string;\n exitCode: number;\n}\n\n/**\n * Tools the writer agent is permitted to use.\n *\n * - `Read` — read the session transcript, existing wiki pages, source files\n * - `Write` / `Edit` — create and update pages under `.almanac/pages/`\n * - `Glob` / `Grep` — navigate the wiki and source code\n * - `Bash` — interrogate the wiki via `almanac search/show/info/list`\n * - `Agent` — invoke the reviewer subagent\n *\n * `WebFetch`/`WebSearch` are intentionally absent: the writer should work\n * from the transcript + repo, not the open internet.\n */\nconst WRITER_TOOLS = [\"Read\", \"Write\", \"Edit\", \"Glob\", \"Grep\", \"Bash\", \"Agent\"];\n\n/**\n * Tools the reviewer subagent is permitted to use. The absence of\n * `Write`/`Edit`/`Agent` is the *only* thing preventing the reviewer from\n * editing files or chaining to further subagents — the SDK enforces this\n * based on the `tools` field in the `AgentDefinition`.\n */\nconst REVIEWER_TOOLS = [\"Read\", \"Grep\", \"Glob\", \"Bash\"];\n\nconst REVIEWER_DESCRIPTION =\n \"Reviews proposed wiki changes against the full knowledge base for \" +\n \"cohesion, duplication, missing links, notability, and writing conventions.\";\n\n/**\n * `almanac capture` — writer agent + reviewer subagent on a session transcript.\n *\n * Flow:\n * 1. Auth gate (ANTHROPIC_API_KEY).\n * 2. Resolve repo root (walk up for `.almanac/`). Refuse if none.\n * 3. Resolve transcript path (arg, --session, or auto-resolve from\n * Claude Code's session storage).\n * 4. Snapshot `.almanac/pages/` BEFORE the agent runs so we can compute\n * a created/updated/archived summary when it finishes.\n * 5. Load `prompts/writer.md` + `prompts/reviewer.md`. Build a reviewer\n * `AgentDefinition` with read-only tools.\n * 6. Run the writer agent with the reviewer registered under `agents`.\n * 7. Stream tool-uses via the shared `StreamingFormatter` (unless --quiet).\n * 8. Diff the snapshot → emit `[done] N updated, M created, K archived …`.\n *\n * Empty outcomes (writer wrote nothing) exit 0 with a clear \"notability bar\"\n * message — per the writer prompt, silence is a valid output.\n */\nexport async function runCapture(\n options: CaptureOptions,\n): Promise<CaptureResult> {\n // Fail before any filesystem work. `assertClaudeAuth` accepts either\n // subscription OAuth (via the bundled SDK CLI) or `ANTHROPIC_API_KEY`;\n // missing both surfaces a two-option error with exit 1 so the\n // SessionEnd hook (which backgrounds + redirects to a sidecar log)\n // doesn't silently treat auth failure as a successful capture.\n try {\n await assertClaudeAuth(options.spawnCli);\n } catch (err: unknown) {\n const msg = err instanceof Error ? err.message : String(err);\n return {\n stdout: \"\",\n stderr: `almanac: ${msg}\\n`,\n exitCode: 1,\n };\n }\n\n // Resolve the repo root by walking up for `.almanac/`. Unlike bootstrap,\n // capture refuses to run when no wiki exists — the writer needs existing\n // pages to read against, and auto-initing here would hide the fact that\n // the user skipped `almanac bootstrap`.\n const repoRoot = findNearestAlmanacDir(options.cwd);\n if (repoRoot === null) {\n return {\n stdout: \"\",\n stderr:\n \"almanac: no .almanac/ found in this directory or any parent. \" +\n \"Run 'almanac bootstrap' first.\\n\",\n exitCode: 1,\n };\n }\n\n const almanacDir = getRepoAlmanacDir(repoRoot);\n const pagesDir = join(almanacDir, \"pages\");\n\n // Resolve the transcript path up front. Doing this before we open a log\n // stream keeps bad-arg errors uncluttered by side effects.\n const transcriptResolution = await resolveTranscript({\n repoRoot,\n explicit: options.transcriptPath,\n sessionId: options.sessionId,\n claudeProjectsDir: options.claudeProjectsDir,\n });\n if (!transcriptResolution.ok) {\n return {\n stdout: \"\",\n stderr: `almanac: ${transcriptResolution.error}\\n`,\n exitCode: 1,\n };\n }\n const transcriptPath = transcriptResolution.path;\n\n // Snapshot the pages dir BEFORE the writer runs. We compare against it\n // after the agent exits to compute a created/updated/archived tally.\n // Doing this in TS (not via the agent's self-reporting) means the summary\n // stays trustworthy even if the writer gets confused about what it did.\n const snapshotBefore = await snapshotPages(pagesDir);\n\n // Load the two prompts. Kept sequential rather than parallel — both files\n // are tiny and the second read is cache-warm.\n const systemPrompt = await loadPrompt(\"writer\");\n const reviewerPrompt = await loadPrompt(\"reviewer\");\n\n const agents: Record<string, AgentDefinition> = {\n reviewer: {\n description: REVIEWER_DESCRIPTION,\n prompt: reviewerPrompt,\n tools: REVIEWER_TOOLS,\n },\n };\n\n // Transcript log filename. Prefer the Claude Code session-id when we\n // have one (`--session` was passed — which is what the SessionEnd\n // hook does) so the SDK transcript and the hook's human-readable\n // sidecar share a stem: `.capture-<sid>.jsonl` (this file) alongside\n // `.capture-<sid>.log` (the hook's stdout redirect). `ls -lah\n // .capture-<sid>.*` tells one coherent story per session. Fall back\n // to a timestamp for manual `almanac capture` invocations where no\n // session-id is available — we don't have the SDK session_id either\n // at this point; it arrives on the first SDK message, too late to\n // use as a filename.\n //\n // The file extension is `.jsonl` because that's what's actually\n // written (one JSON SDK message per line). Using `.log` here would\n // collide with the hook's stdout-redirect sidecar, which happens to\n // share the session-id stem; distinct extensions keep them from\n // clobbering each other.\n const now = options.now?.() ?? new Date();\n const logStem =\n options.sessionId !== undefined && options.sessionId.length > 0\n ? options.sessionId\n : formatTimestamp(now);\n const logName = `.capture-${logStem}.jsonl`;\n const logPath = join(almanacDir, logName);\n const logStream = createWriteStream(logPath, { flags: \"w\" });\n\n const out = process.stdout;\n const formatter = new StreamingFormatter({\n write: (line: string) => {\n if (options.quiet !== true) out.write(line);\n },\n });\n // The shared StreamingFormatter defaults its currentAgent to \"bootstrap\"\n // because bootstrap was the first command to use it. For capture the\n // writer owns the top-level turn, so relabel.\n formatter.setAgent(\"writer\");\n\n const onMessage = (msg: SDKMessage): void => {\n try {\n logStream.write(`${JSON.stringify(msg)}\\n`);\n } catch {\n // Best-effort: one unserializable message shouldn't kill the whole\n // stream. Humans read the log; if a line is missing they can re-run.\n }\n formatter.handle(msg);\n };\n\n // Pass an ABSOLUTE path for the transcript so the writer doesn't have to\n // guess at cwd semantics. Everything else (`.almanac/pages/`) is already\n // relative to the cwd the SDK gives its tools.\n const userPrompt =\n `Capture this coding session.\\n` +\n `Transcript: ${transcriptPath}.\\n` +\n `Working directory: ${repoRoot}.`;\n\n const runner = options.runAgent ?? runAgent;\n\n let result: AgentResult;\n try {\n result = await runner({\n systemPrompt,\n prompt: userPrompt,\n allowedTools: WRITER_TOOLS,\n agents,\n cwd: repoRoot,\n model: options.model,\n // Capture sessions can touch many pages; give it more headroom than\n // bootstrap. The SDK treats `maxTurns` as a hard stop — better to\n // overshoot than to cut off mid-review.\n maxTurns: 150,\n onMessage,\n });\n } finally {\n await closeStream(logStream);\n }\n\n const snapshotAfter = await snapshotPages(pagesDir);\n const delta = diffSnapshots(snapshotBefore, snapshotAfter);\n\n if (!result.success) {\n return {\n stdout: \"\",\n stderr:\n `almanac: capture failed: ${result.error ?? \"unknown error\"}\\n` +\n `(transcript: ${relative(repoRoot, logPath)})\\n`,\n exitCode: 1,\n };\n }\n\n const summary = formatSummary(result, delta, logPath, repoRoot);\n\n return {\n stdout: `${summary}\\n`,\n stderr: \"\",\n exitCode: 0,\n };\n}\n\n// ─── Transcript resolution ────────────────────────────────────────────────\n\ninterface ResolvedTranscript {\n ok: true;\n path: string;\n}\ninterface FailedTranscript {\n ok: false;\n error: string;\n}\n\n/**\n * Resolve the transcript path from the three possible sources, in priority:\n * 1. Explicit positional arg (`almanac capture <path>`).\n * 2. `--session <id>`: find the single `.jsonl` matching that ID.\n * 3. Auto-resolve: most recent `.jsonl` under Claude Code's projects dir\n * whose parent directory hashes to `repoRoot`. If multiple candidates\n * or none match, return an error directing the user to pass a path.\n *\n * Claude Code names the per-project directory with a path-hash that we\n * can't deterministically reproduce without reading Claude Code's source.\n * Rather than guess at the hashing scheme, we scan all project dirs, pick\n * the one whose most recent transcript mentions the `repoRoot` in its\n * `cwd` field, and take the newest `.jsonl` from there.\n */\nasync function resolveTranscript(args: {\n repoRoot: string;\n explicit?: string;\n sessionId?: string;\n claudeProjectsDir?: string;\n}): Promise<ResolvedTranscript | FailedTranscript> {\n if (args.explicit !== undefined && args.explicit.length > 0) {\n if (!existsSync(args.explicit)) {\n return {\n ok: false,\n error: `transcript not found: ${args.explicit}`,\n };\n }\n return { ok: true, path: args.explicit };\n }\n\n const projectsDir =\n args.claudeProjectsDir ?? join(homedir(), \".claude\", \"projects\");\n if (!existsSync(projectsDir)) {\n return {\n ok: false,\n error:\n `could not auto-resolve transcript; ${projectsDir} does not exist. ` +\n `Pass --session <id> or <transcript-path>.`,\n };\n }\n\n const allTranscripts = await collectTranscripts(projectsDir);\n\n if (args.sessionId !== undefined && args.sessionId.length > 0) {\n const expected = `${args.sessionId}.jsonl`;\n const match = allTranscripts.find((t) => basename(t.path) === expected);\n if (match === undefined) {\n return {\n ok: false,\n error:\n `no transcript found for session ${args.sessionId} under ${projectsDir}`,\n };\n }\n return { ok: true, path: match.path };\n }\n\n // Auto-resolve: prefer transcripts whose `cwd` field matches `repoRoot`,\n // then fall back to the most recently modified if no cwd match is found.\n // We read a peek of each transcript (not the whole file) to check the\n // cwd — JSONL's first line typically carries it.\n const matches = await filterTranscriptsByCwd(allTranscripts, args.repoRoot);\n\n if (matches.length === 0) {\n return {\n ok: false,\n error:\n `could not auto-resolve transcript under ${projectsDir}; ` +\n `no session matches cwd ${args.repoRoot}. ` +\n `Pass --session <id> or <transcript-path>.`,\n };\n }\n\n // Sort by mtime desc and pick the newest.\n matches.sort((a, b) => b.mtime - a.mtime);\n return { ok: true, path: matches[0]!.path };\n}\n\ninterface TranscriptEntry {\n path: string;\n mtime: number;\n}\n\nasync function collectTranscripts(\n projectsDir: string,\n): Promise<TranscriptEntry[]> {\n const out: TranscriptEntry[] = [];\n let topLevel: string[];\n try {\n topLevel = await readdir(projectsDir);\n } catch {\n return out;\n }\n for (const name of topLevel) {\n const projectDir = join(projectsDir, name);\n let entries: string[];\n try {\n entries = await readdir(projectDir);\n } catch {\n continue;\n }\n for (const entry of entries) {\n if (!entry.endsWith(\".jsonl\")) continue;\n const full = join(projectDir, entry);\n try {\n const st = await stat(full);\n if (st.isFile()) {\n out.push({ path: full, mtime: st.mtimeMs });\n }\n } catch {\n // Transient read error, skip.\n }\n }\n }\n return out;\n}\n\n/**\n * Keep only transcripts whose first session record mentions a `cwd` that\n * matches `repoRoot` (exact string match). We also match on the Claude\n * Code project-hash heuristic: the per-project dir name is usually the\n * absolute repo path with `/` replaced by `-`. Falling back to the hash\n * heuristic means we still resolve sanely when the JSONL format changes.\n */\nasync function filterTranscriptsByCwd(\n transcripts: TranscriptEntry[],\n repoRoot: string,\n): Promise<TranscriptEntry[]> {\n const dirHash = `-${repoRoot.replace(/^\\/+/, \"\").replace(/\\//g, \"-\")}`;\n\n const byDirName = transcripts.filter((t) => {\n const parent = basename(join(t.path, \"..\"));\n return parent === dirHash || parent.endsWith(dirHash);\n });\n if (byDirName.length > 0) return byDirName;\n\n // Fallback: peek into each JSONL for a `\"cwd\":\"<repoRoot>\"` needle.\n const needle = `\"cwd\":\"${repoRoot}\"`;\n const hits: TranscriptEntry[] = [];\n for (const t of transcripts) {\n try {\n const head = await readHead(t.path, 4096);\n if (head.includes(needle)) hits.push(t);\n } catch {\n continue;\n }\n }\n return hits;\n}\n\nasync function readHead(path: string, bytes: number): Promise<string> {\n // Small files — just read the whole thing. We only call this on .jsonl\n // files, which can be large, so cap at `bytes` via slicing.\n const content = await readFile(path, \"utf8\");\n return content.length > bytes ? content.slice(0, bytes) : content;\n}\n\n// ─── Snapshot / delta ─────────────────────────────────────────────────────\n\ninterface PageSnapshotEntry {\n slug: string;\n /** SHA-256 of file bytes — cheap, stable, avoids relying on mtime. */\n hash: string;\n /** `true` when the frontmatter has `archived_at` set. */\n archived: boolean;\n}\n\ntype PageSnapshot = Map<string, PageSnapshotEntry>;\n\nasync function snapshotPages(pagesDir: string): Promise<PageSnapshot> {\n const out: PageSnapshot = new Map();\n if (!existsSync(pagesDir)) return out;\n\n let entries: string[];\n try {\n entries = await readdir(pagesDir);\n } catch {\n return out;\n }\n for (const entry of entries) {\n if (!entry.endsWith(\".md\")) continue;\n const slug = entry.slice(0, -3);\n const full = join(pagesDir, entry);\n try {\n const st = statSync(full);\n if (!st.isFile()) continue;\n const content = await readFile(full, \"utf8\");\n const hash = createHash(\"sha256\").update(content).digest(\"hex\");\n const fm = parseFrontmatter(content);\n out.set(slug, {\n slug,\n hash,\n archived: fm.archived_at !== null,\n });\n } catch {\n continue;\n }\n }\n return out;\n}\n\ninterface SnapshotDelta {\n created: number;\n updated: number;\n archived: number;\n}\n\nfunction diffSnapshots(\n before: PageSnapshot,\n after: PageSnapshot,\n): SnapshotDelta {\n let created = 0;\n let updated = 0;\n let archived = 0;\n\n for (const [slug, entry] of after) {\n const prev = before.get(slug);\n if (prev === undefined) {\n created += 1;\n continue;\n }\n if (prev.hash !== entry.hash) {\n // An edit that flips a page from active → archived counts as\n // \"archived\", not \"updated\" — the archive is the semantically\n // interesting thing.\n if (!prev.archived && entry.archived) {\n archived += 1;\n } else {\n updated += 1;\n }\n }\n }\n // Note: we deliberately don't track deleted pages. The writer prompt\n // tells agents to archive (via frontmatter), not delete — a page that\n // disappears entirely is a protocol violation worth surfacing, but not\n // by silently counting it in the summary.\n\n return { created, updated, archived };\n}\n\n// ─── Formatting ───────────────────────────────────────────────────────────\n\nfunction formatSummary(\n result: AgentResult,\n delta: SnapshotDelta,\n logPath: string,\n repoRoot: string,\n): string {\n const rel = relative(repoRoot, logPath);\n const cost = `$${result.cost.toFixed(3)}`;\n const { created, updated, archived } = delta;\n\n if (created === 0 && updated === 0 && archived === 0) {\n return (\n `[capture] no new knowledge met the notability bar (0 pages written), ` +\n `cost: ${cost}, turns: ${result.turns} (transcript: ${rel})`\n );\n }\n\n return (\n `[done] ${updated} page${updated === 1 ? \"\" : \"s\"} updated, ` +\n `${created} created, ` +\n `${archived} archived, ` +\n `cost: ${cost}, turns: ${result.turns} (transcript: ${rel})`\n );\n}\n\nfunction formatTimestamp(d: Date): string {\n const pad = (n: number): string => n.toString().padStart(2, \"0\");\n return (\n `${d.getFullYear()}${pad(d.getMonth() + 1)}${pad(d.getDate())}-` +\n `${pad(d.getHours())}${pad(d.getMinutes())}${pad(d.getSeconds())}`\n );\n}\n\nfunction closeStream(stream: WriteStream): Promise<void> {\n return new Promise((resolve) => {\n stream.end(() => resolve());\n });\n}\n","import { runIndexer, type IndexResult } from \"../indexer/index.js\";\nimport { resolveWikiRoot } from \"../indexer/resolve-wiki.js\";\n\nexport interface ReindexOptions {\n cwd: string;\n wiki?: string;\n}\n\nexport interface ReindexCommandOutput {\n result: IndexResult;\n stdout: string;\n exitCode: number;\n}\n\n/**\n * `almanac reindex` — force a full rebuild.\n *\n * Unlike the implicit reindex every query command triggers, this one\n * prints a summary line so the user gets feedback for an explicitly\n * requested action. The summary is terse on purpose (one line, three\n * numbers) — verbose progress reporting would fight the design rule that\n * the CLI stays quiet by default.\n */\nexport async function runReindex(\n options: ReindexOptions,\n): Promise<ReindexCommandOutput> {\n const repoRoot = await resolveWikiRoot({\n cwd: options.cwd,\n wiki: options.wiki,\n });\n const result = await runIndexer({ repoRoot });\n // Summary wording: \"reindexed: N pages (K updated, R removed)\". When\n // some files were on disk but never made it into the index\n // (slug collisions, ENOENT races, un-sluggable filenames), tack on a\n // `; S skipped` suffix so the user notices. The per-file reason was\n // already written to stderr at indexing time.\n const skipSuffix =\n result.filesSkipped > 0 ? `; ${result.filesSkipped} skipped` : \"\";\n const stdout = `reindexed: ${result.pagesIndexed} page${result.pagesIndexed === 1 ? \"\" : \"s\"} (${result.changed} updated, ${result.removed} removed${skipSuffix})\\n`;\n return { result, stdout, exitCode: 0 };\n}\n","import { Command } from \"commander\";\n\nimport { runBootstrap } from \"../commands/bootstrap.js\";\nimport { runCapture } from \"../commands/capture.js\";\nimport {\n runHookInstall,\n runHookStatus,\n runHookUninstall,\n} from \"../commands/hook.js\";\nimport { runReindex } from \"../commands/reindex.js\";\nimport { autoRegisterIfNeeded } from \"../registry/autoregister.js\";\nimport { emit } from \"./helpers.js\";\n\nexport function registerWikiLifecycleCommands(program: Command): void {\n program\n .command(\"bootstrap\")\n .description(\n \"scaffold a wiki in this repo via an AI agent (requires ANTHROPIC_API_KEY or Claude subscription)\",\n )\n .option(\"--quiet\", \"suppress per-tool streaming; print only the final line\")\n .option(\"--model <model>\", \"override the agent model\")\n .option(\"--force\", \"overwrite an existing populated wiki (default: refuse)\")\n .action(\n async (opts: { quiet?: boolean; model?: string; force?: boolean }) => {\n const result = await runBootstrap({\n cwd: process.cwd(),\n quiet: opts.quiet,\n model: opts.model,\n force: opts.force,\n });\n emit(result);\n },\n );\n\n program\n .command(\"capture [transcript]\")\n .description(\"run the writer/reviewer pipeline on a session (usually automatic)\")\n .option(\"--session <id>\", \"target a specific session by ID\")\n .option(\"--quiet\", \"suppress per-tool streaming; print only the final summary\")\n .option(\"--model <model>\", \"override the agent model\")\n .action(\n async (\n transcript: string | undefined,\n opts: { session?: string; quiet?: boolean; model?: string },\n ) => {\n await autoRegisterIfNeeded(process.cwd());\n const result = await runCapture({\n cwd: process.cwd(),\n transcriptPath: transcript,\n sessionId: opts.session,\n quiet: opts.quiet,\n model: opts.model,\n });\n emit(result);\n },\n );\n\n const hook = program\n .command(\"hook\")\n .description(\"manage the SessionEnd auto-capture hook\");\n\n hook\n .command(\"install\")\n .description(\"add a SessionEnd entry that runs 'almanac capture' on session end\")\n .action(async () => {\n const result = await runHookInstall();\n emit(result);\n });\n\n hook\n .command(\"uninstall\")\n .description(\"remove codealmanac's SessionEnd entry; leaves foreign entries alone\")\n .action(async () => {\n const result = await runHookUninstall();\n emit(result);\n });\n\n hook\n .command(\"status\")\n .description(\"report whether the SessionEnd hook is installed\")\n .action(async () => {\n const result = await runHookStatus();\n emit(result);\n });\n\n program\n .command(\"reindex\")\n .description(\"force a full rebuild of .almanac/index.db\")\n .option(\"--wiki <name>\", \"target a specific registered wiki\")\n .action(async (opts: { wiki?: string }) => {\n await autoRegisterIfNeeded(process.cwd());\n const result = await runReindex({\n cwd: process.cwd(),\n wiki: opts.wiki,\n });\n process.stdout.write(result.stdout);\n if (result.exitCode !== 0) process.exitCode = result.exitCode;\n });\n}\n","import { Command } from \"commander\";\n\nimport { registerEditCommands } from \"./register-edit-commands.js\";\nimport { registerQueryCommands } from \"./register-query-commands.js\";\nimport { registerSetupCommands } from \"./register-setup-commands.js\";\nimport { registerWikiLifecycleCommands } from \"./register-wiki-lifecycle-commands.js\";\n\nexport function registerCommands(program: Command): void {\n registerQueryCommands(program);\n registerEditCommands(program);\n registerWikiLifecycleCommands(program);\n registerSetupCommands(program);\n}\n","import { readFileSync } from \"node:fs\";\nimport { createRequire } from \"node:module\";\n\nimport { getConfigPath } from \"./config.js\";\nimport { isNewer } from \"./semver.js\";\nimport { getStatePath, type UpdateState } from \"./state.js\";\n\n/**\n * Pre-command update-nag banner. Runs synchronously at the very top of\n * every `run()` invocation, before commander even touches argv. Prints\n * one line to stderr if:\n *\n * 1. `~/.almanac/update-state.json` exists and parses.\n * 2. `latest_version` is strictly newer than `installed_version`.\n * 3. `latest_version` is NOT in `dismissed_versions`.\n * 4. `~/.almanac/config.json`.`update_notifier` is not `false`.\n *\n * Otherwise silent. Deliberately synchronous and filesystem-blocking:\n * this runs in the CLI critical path and waiting on a Promise would\n * force every command to become async upfront. The files are tiny\n * (<1KB each); a sync read is cheap.\n *\n * Why stderr: agents and scripts parse stdout; stderr is the\n * conventional channel for diagnostics and nags. A tool piping\n * `almanac search` into `xargs` shouldn't see the banner mixed into\n * its slug list. Users running interactively see stderr anyway, so\n * the nag is visible in practice.\n */\n\nexport interface AnnounceOptions {\n statePath?: string;\n configPath?: string;\n /** Override for tests — normally reads from package.json at import time. */\n installedVersion?: string;\n /** Enable ANSI coloring regardless of isTTY. Tests pass `false`. */\n color?: boolean;\n}\n\nconst RST = \"\\x1b[0m\";\nconst BOLD = \"\\x1b[1m\";\nconst YELLOW = \"\\x1b[33m\";\n\nexport function announceUpdateIfAvailable(\n stderr: NodeJS.WritableStream,\n opts: AnnounceOptions = {},\n): void {\n const statePath = opts.statePath ?? getStatePath();\n const configPath = opts.configPath ?? getConfigPath();\n const installed = opts.installedVersion ?? readInstalledVersion();\n\n // Config gate. Must be checked before state: a user who disabled the\n // notifier shouldn't pay even a state-file read.\n if (!shouldNotify(configPath)) return;\n\n const state = readStateSync(statePath);\n if (state === null) return;\n if (state.latest_version.length === 0) return;\n if (!isNewer(state.latest_version, installed)) return;\n if (state.dismissed_versions.includes(state.latest_version)) return;\n\n const useColor =\n opts.color ?? (process.stderr.isTTY === true && !(\"NO_COLOR\" in process.env));\n const warn = useColor ? `${YELLOW}${BOLD}\\u26a0${RST}` : \"!\";\n const cmd = useColor ? `${BOLD}almanac update${RST}` : \"almanac update\";\n stderr.write(\n `${warn} codealmanac ${state.latest_version} available ` +\n `(you're on ${installed}) — run: ${cmd}\\n`,\n );\n}\n\n/**\n * Sync-read the state file. Returns `null` when missing, empty, or\n * malformed — the announce path MUST NOT throw into the CLI critical\n * path. Avoids the `async readState` used by the worker because\n * `run()` would otherwise need `await announceUpdateIfAvailable(...)`\n * on every invocation, which turns into a multi-millisecond penalty\n * on commands that don't care.\n */\nfunction readStateSync(path: string): UpdateState | null {\n let raw: string;\n try {\n raw = readFileSync(path, \"utf8\");\n } catch {\n return null;\n }\n const trimmed = raw.trim();\n if (trimmed.length === 0) return null;\n try {\n const parsed = JSON.parse(trimmed) as Partial<UpdateState>;\n return {\n last_check_at:\n typeof parsed.last_check_at === \"number\" ? parsed.last_check_at : 0,\n installed_version:\n typeof parsed.installed_version === \"string\"\n ? parsed.installed_version\n : \"\",\n latest_version:\n typeof parsed.latest_version === \"string\" ? parsed.latest_version : \"\",\n dismissed_versions: Array.isArray(parsed.dismissed_versions)\n ? parsed.dismissed_versions.filter(\n (v): v is string => typeof v === \"string\",\n )\n : [],\n };\n } catch {\n return null;\n }\n}\n\nfunction shouldNotify(configPath: string): boolean {\n let raw: string;\n try {\n raw = readFileSync(configPath, \"utf8\");\n } catch {\n return true; // no config file → default notify on\n }\n const trimmed = raw.trim();\n if (trimmed.length === 0) return true;\n try {\n const parsed = JSON.parse(trimmed) as { update_notifier?: unknown };\n if (parsed.update_notifier === false) return false;\n return true;\n } catch {\n return true;\n }\n}\n\nfunction readInstalledVersion(): string {\n // Dev: `src/update/announce.ts` → `../../package.json`. Bundled:\n // `dist/codealmanac.js` → `../package.json`. Try both.\n try {\n const require = createRequire(import.meta.url);\n const pkg = require(\"../../package.json\") as { version?: unknown };\n if (typeof pkg.version === \"string\" && pkg.version.length > 0) {\n return pkg.version;\n }\n } catch {\n // Fall through.\n }\n try {\n const require = createRequire(import.meta.url);\n const pkg = require(\"../package.json\") as { version?: unknown };\n if (typeof pkg.version === \"string\" && pkg.version.length > 0) {\n return pkg.version;\n }\n } catch {\n // Fall through.\n }\n return \"unknown\";\n}\n"],"mappings":"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","names":["createRequire","basename","existsSync","copyFile","mkdir","readFile","writeFile","createRequire","homedir","path","fileURLToPath","require","resolve","existsSync","mkdir","readFile","path","existsSync","readFile","path","mkdir","createRequire","homedir","path","fileURLToPath","req","resolve","existsSync","path","RST","DIM","WHITE_BOLD","BLUE","path","homedir","mkdir","existsSync","readFile","copyFile","writeFile","resolve","fileURLToPath","require","createRequire","RST","BOLD","DIM","BLUE","BOLD","RST","BLUE","DIM","existsSync","statSync","readFile","join","fg","existsSync","mkdir","readFile","rename","writeFile","dirname","path","existsSync","readFile","dirname","mkdir","writeFile","rename","yaml","existsSync","join","path","existsSync","topicsYamlPath","existsSync","target","path","join","existsSync","fg","statSync","readFile","path","existsSync","join","existsSync","homedir","dirname","join","mkdir","readFile","rename","writeFile","dirname","path","readFile","isNodeError","mkdir","dirname","writeFile","rename","existsSync","join","readFile","rename","writeFile","yaml","values","parsed","join","stderr","repoRoot","readFile","join","fg","join","fg","readFile","repoRoot","repoRoot","BLUE","RST","DIM","repoRoot","DIM","RST","BLUE","existsSync","basename","existsSync","basename","existsSync","readFile","basename","join","fg","join","existsSync","readFile","fg","basename","BLUE","RST","DIM","BOLD","existsSync","existsSync","DIM","RST","BLUE","BOLD","join","join","BLUE","RST","readFile","join","join","readFile","DIM","RST","BLUE","query","formatReport","BOLD","RST","DIM","BLUE","existsSync","readFile","homedir","path","existsSync","createRequire","homedir","path","fileURLToPath","createRequire","fileURLToPath","path","existsSync","homedir","Database","safeCheckAuth","safeCheckAuth","path","homedir","existsSync","readFile","mkdir","readFile","rename","writeFile","dirname","join","join","path","readFile","mkdir","dirname","writeFile","rename","spawn","readFileSync","createRequire","mkdir","readFile","rename","writeFile","dirname","join","join","path","readFile","mkdir","dirname","writeFile","rename","next","require","createRequire","spawn","readFileSync","path","existsSync","readdirSync","statSync","path","path","existsSync","statSync","readdirSync","formatReport","existsSync","readFile","writeFile","homedir","path","BLUE","DIM","RST","path","homedir","confirm","existsSync","readFile","writeFile","resolve","spawn","createRequire","readInstalledVersion","spawn","resolve","require","createRequire","existsSync","join","existsSync","readFile","path","fileURLToPath","path","fileURLToPath","existsSync","readFile","existsSync","mkdir","readFile","writeFile","basename","join","join","existsSync","mkdir","writeFile","basename","path","readFile","join","existsSync","resolve","createHash","createWriteStream","existsSync","statSync","readFile","readdir","homedir","basename","join","relative","join","formatTimestamp","createWriteStream","closeStream","relative","existsSync","homedir","basename","readdir","path","readFile","statSync","createHash","resolve","readFileSync","createRequire","RST","BOLD","readInstalledVersion","useColor","path","readFileSync","require","createRequire","readPackageVersion","basename","require","createRequire"]}