codealmanac 0.2.5 → 0.2.7

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.
Files changed (79) hide show
  1. package/README.md +25 -20
  2. package/dist/{agents-RVTQYE6A.js → agents-V2ZOIACP.js} +6 -5
  3. package/dist/{chunk-P5WGG4FJ.js → chunk-5BWUMAOX.js} +2 -2
  4. package/dist/chunk-5BWUMAOX.js.map +1 -0
  5. package/dist/{chunk-KQUVMF27.js → chunk-BFIG2CXM.js} +2 -516
  6. package/dist/chunk-BFIG2CXM.js.map +1 -0
  7. package/dist/{chunk-DL5BXZCX.js → chunk-BQY5L3DL.js} +3 -53
  8. package/dist/chunk-BQY5L3DL.js.map +1 -0
  9. package/dist/{chunk-F53U6JQG.js → chunk-CQJVM34R.js} +2 -2
  10. package/dist/chunk-FUBE6KCO.js +124 -0
  11. package/dist/chunk-FUBE6KCO.js.map +1 -0
  12. package/dist/chunk-IZBXXAVL.js +524 -0
  13. package/dist/chunk-IZBXXAVL.js.map +1 -0
  14. package/dist/{chunk-7JUX4ADQ.js → chunk-IZT6RBHS.js} +1 -1
  15. package/dist/{chunk-SMIK2YLU.js → chunk-JLQZELHQ.js} +82 -88
  16. package/dist/chunk-JLQZELHQ.js.map +1 -0
  17. package/dist/{chunk-TT6ZP4GS.js → chunk-KZXWPG4P.js} +2 -2
  18. package/dist/{chunk-6BJUYZ43.js → chunk-QIA22IAM.js} +8 -16
  19. package/dist/chunk-QIA22IAM.js.map +1 -0
  20. package/dist/{chunk-BGUID5BS.js → chunk-RALBM6HZ.js} +20 -139
  21. package/dist/chunk-RALBM6HZ.js.map +1 -0
  22. package/dist/{chunk-TILAKDN6.js → chunk-U5DLLWIC.js} +3 -3
  23. package/dist/chunk-WL4UE7Q6.js +1386 -0
  24. package/dist/chunk-WL4UE7Q6.js.map +1 -0
  25. package/dist/{chunk-GFUB57IT.js → chunk-ZUQN5Y3K.js} +48 -124
  26. package/dist/chunk-ZUQN5Y3K.js.map +1 -0
  27. package/dist/{chunk-MRRX4UQB.js → chunk-ZZLLOAI6.js} +3 -3
  28. package/dist/{cli-CL4ID7EO.js → cli-XWPNARA6.js} +35 -18
  29. package/dist/cli-XWPNARA6.js.map +1 -0
  30. package/dist/codealmanac.js +1 -1
  31. package/dist/{config-ML2RCR7J.js → config-KH3JUMG6.js} +4 -4
  32. package/dist/doctor-ENJT665Z.js +18 -0
  33. package/dist/paths-O5CZADP2.js +14 -0
  34. package/dist/process-KFSLENL3.js +61 -0
  35. package/dist/{register-commands-FBJ6XQ3L.js → register-commands-LULZUSPO.js} +993 -1015
  36. package/dist/register-commands-LULZUSPO.js.map +1 -0
  37. package/dist/uninstall-BD4MMQ7M.js +16 -0
  38. package/dist/uninstall-BD4MMQ7M.js.map +1 -0
  39. package/dist/update-XSKPDFMJ.js +11 -0
  40. package/dist/update-XSKPDFMJ.js.map +1 -0
  41. package/dist/{wiki-IGNRNLUZ.js → wiki-O4RWMAE6.js} +8 -6
  42. package/dist/wiki-O4RWMAE6.js.map +1 -0
  43. package/guides/mini.md +11 -9
  44. package/guides/reference.md +96 -39
  45. package/hooks/almanac-capture.sh +7 -8
  46. package/package.json +1 -1
  47. package/prompts/agents/.gitkeep +1 -0
  48. package/prompts/base/notability.md +139 -0
  49. package/prompts/base/purpose.md +85 -0
  50. package/prompts/base/syntax.md +114 -0
  51. package/prompts/operations/absorb.md +43 -0
  52. package/prompts/operations/build.md +49 -0
  53. package/prompts/operations/garden.md +51 -0
  54. package/dist/chunk-6BJUYZ43.js.map +0 -1
  55. package/dist/chunk-BGUID5BS.js.map +0 -1
  56. package/dist/chunk-DL5BXZCX.js.map +0 -1
  57. package/dist/chunk-GFUB57IT.js.map +0 -1
  58. package/dist/chunk-KQUVMF27.js.map +0 -1
  59. package/dist/chunk-P5WGG4FJ.js.map +0 -1
  60. package/dist/chunk-SMIK2YLU.js.map +0 -1
  61. package/dist/cli-CL4ID7EO.js.map +0 -1
  62. package/dist/doctor-DOLJRGS4.js +0 -17
  63. package/dist/register-commands-FBJ6XQ3L.js.map +0 -1
  64. package/dist/uninstall-DX6LFKMX.js +0 -15
  65. package/dist/update-P2IPG7RO.js +0 -11
  66. package/dist/wiki-IGNRNLUZ.js.map +0 -1
  67. package/prompts/bootstrap.md +0 -176
  68. package/prompts/reviewer.md +0 -129
  69. package/prompts/writer.md +0 -134
  70. /package/dist/{agents-RVTQYE6A.js.map → agents-V2ZOIACP.js.map} +0 -0
  71. /package/dist/{chunk-F53U6JQG.js.map → chunk-CQJVM34R.js.map} +0 -0
  72. /package/dist/{chunk-7JUX4ADQ.js.map → chunk-IZT6RBHS.js.map} +0 -0
  73. /package/dist/{chunk-TT6ZP4GS.js.map → chunk-KZXWPG4P.js.map} +0 -0
  74. /package/dist/{chunk-TILAKDN6.js.map → chunk-U5DLLWIC.js.map} +0 -0
  75. /package/dist/{chunk-MRRX4UQB.js.map → chunk-ZZLLOAI6.js.map} +0 -0
  76. /package/dist/{config-ML2RCR7J.js.map → config-KH3JUMG6.js.map} +0 -0
  77. /package/dist/{doctor-DOLJRGS4.js.map → doctor-ENJT665Z.js.map} +0 -0
  78. /package/dist/{uninstall-DX6LFKMX.js.map → paths-O5CZADP2.js.map} +0 -0
  79. /package/dist/{update-P2IPG7RO.js.map → process-KFSLENL3.js.map} +0 -0
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/commands/setup.ts","../src/commands/setup/install-path.ts","../src/commands/setup/next-steps.ts"],"sourcesContent":["import { existsSync } from \"node:fs\";\nimport { spawn } from \"node:child_process\";\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 type { SpawnCliFn } from \"../agent/providers/claude/index.js\";\nimport {\n buildProviderModelChoices,\n buildProviderSetupView,\n getProviderLabel,\n parseAgentSelection,\n} from \"../agent/provider-view.js\";\nimport type {\n ProviderModelChoice,\n ProviderSetupView,\n} from \"../agent/provider-view.js\";\nimport {\n isAgentProviderId,\n readConfig,\n writeConfig,\n type AgentProviderId,\n} from \"../update/config.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. Provider-specific agent guidance for the chosen default provider.\n * Claude uses `~/.claude/CLAUDE.md`; Codex uses `~/.codex/AGENTS.md`;\n * Cursor uses a project rule in `.cursor/rules/`.\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 auto-capture hooks. */\n skipHook?: boolean;\n /** Don't install provider-specific guides. */\n skipGuides?: boolean;\n /** Set the default provider during setup. */\n agent?: string;\n /** Set the default model for the selected provider during setup. */\n model?: string;\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 provider integrations${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 const agentChoice = await chooseDefaultAgent({\n out,\n interactive,\n requested: options.agent,\n requestedModel: options.model,\n spawnCli: options.spawnCli,\n });\n if (!agentChoice.ok) {\n return {\n stdout: \"\",\n stderr: `almanac: ${agentChoice.error}\\n`,\n exitCode: 1,\n };\n }\n stepDone(\n out,\n `Default provider: ${WHITE_BOLD}${agentChoice.provider}${RST}` +\n ` (${agentChoice.model ?? \"provider default\"})`,\n );\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 auto-capture hooks for supported agents?\",\n true,\n );\n }\n\n let hookResultLine = \"\";\n if (hookAction === \"install\") {\n const res = await runHookInstall({\n source: \"all\",\n settingsPath: options.settingsPath,\n hookScriptPath: options.hookScriptPath,\n stableHooksDir: options.stableHooksDir,\n });\n if (res.exitCode !== 0) {\n stepActive(out, `Auto-capture 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 ? `Auto-capture hooks ${DIM}already installed${RST}`\n : `Auto-capture hooks installed`;\n stepDone(out, hookResultLine);\n } else {\n stepSkipped(out, `Auto-capture hooks ${DIM}skipped${RST}`);\n }\n out.write(BAR + \"\\n\");\n\n // Step 3: install the provider-specific guide.\n let guidesAction: InstallDecision = \"install\";\n const providerLabel = getProviderLabel(agentChoice.provider);\n if (options.skipGuides === true) {\n guidesAction = \"skip\";\n } else if (interactive) {\n guidesAction = await confirm(\n out,\n `Install the codealmanac guide for ${providerLabel}?`,\n true,\n );\n }\n\n let guidesSummary: string;\n if (guidesAction === \"install\") {\n try {\n const summary = await installGuides({\n provider: agentChoice.provider,\n claudeDir: options.claudeDir ?? path.join(homedir(), \".claude\"),\n guidesDir: options.guidesDir ?? resolveGuidesDir(),\n cwd: process.cwd(),\n });\n guidesSummary = summary.anyChanges\n ? `Guide installed for ${summary.providerLabel} (${summary.filesWritten.join(\", \")})`\n : `Guide for ${summary.providerLabel} ${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 printProviderNextSteps(out);\n\n return { stdout: \"\", stderr: \"\", exitCode: 0 };\n}\n\ntype AgentChoice =\n | { ok: true; provider: AgentProviderId; model: string | null }\n | { ok: false; error: string };\n\nasync function chooseDefaultAgent(args: {\n out: NodeJS.WritableStream;\n interactive: boolean;\n requested?: string;\n requestedModel?: string;\n spawnCli?: SpawnCliFn;\n}): Promise<AgentChoice> {\n const config = await readConfig();\n let view: ProviderSetupView | null = null;\n let selected = args.requested ?? config.agent.default;\n if (args.interactive || args.requested !== undefined) {\n view = await buildProviderSetupView({ config, spawnCli: args.spawnCli });\n }\n if (args.interactive && args.requested === undefined && view !== null) {\n args.out.write(\" Choose default provider for bootstrap/capture:\\n\");\n view.choices.forEach((choice, index) => {\n const tag = choice.recommended ? \" recommended\" : \"\";\n const status = choice.ready ? \"ready\" : \"not ready\";\n const detail = choice.account ?? choice.fixCommand ?? choice.detail;\n args.out.write(\n ` ${index + 1}. ${choice.label.padEnd(6)} ${status.padEnd(9)}${tag} ${detail}\\n`,\n );\n });\n selected = (await promptText(\n args.out,\n \"Default provider\",\n view.recommendedProvider,\n )).toLowerCase();\n const number = Number.parseInt(selected, 10);\n if (\n Number.isInteger(number) &&\n number >= 1 &&\n number <= view.choices.length\n ) {\n selected = view.choices[number - 1]?.id ?? selected;\n }\n }\n const parsed = parseAgentSelection(selected);\n if (parsed.provider === null || !isAgentProviderId(parsed.provider)) {\n return {\n ok: false,\n error:\n `unknown agent '${selected}'. Expected one of: claude, codex, cursor.`,\n };\n }\n const provider = parsed.provider;\n let selectedChoice = view?.choices.find((choice) => choice.id === provider);\n if (\n args.interactive &&\n selectedChoice !== undefined &&\n !selectedChoice.ready &&\n selectedChoice.fixCommand?.startsWith(\"run: \") === true\n ) {\n const command = selectedChoice.fixCommand.slice(\"run: \".length);\n const runLogin = await confirm(\n args.out,\n `${selectedChoice.label} is not ready. Run '${command}' now?`,\n true,\n );\n if (runLogin === \"install\") {\n const login = await runLoginCommand(command);\n if (login.ok) {\n view = await buildProviderSetupView({ config, spawnCli: args.spawnCli });\n selectedChoice = view.choices.find((choice) => choice.id === provider);\n } else {\n stepActive(args.out, `${selectedChoice.label} login failed: ${login.error}`);\n }\n }\n }\n const requestedModel = args.requestedModel ?? parsed.model;\n const model = requestedModel ?? await chooseProviderModel({\n out: args.out,\n interactive: args.interactive,\n provider,\n choice: selectedChoice,\n configuredModel: config.agent.models[provider] ?? null,\n });\n await writeConfig({\n ...config,\n agent: {\n ...config.agent,\n default: provider,\n models: {\n ...config.agent.models,\n [provider]: model,\n },\n },\n });\n if (!args.interactive || args.requested !== undefined) {\n const detail = selectedChoice?.ready === true\n ? \"ready\"\n : selectedChoice?.fixCommand ?? selectedChoice?.detail ?? \"status unknown\";\n stepDone(args.out, `Agent readiness: ${detail}`);\n }\n return { ok: true, provider, model };\n}\n\nasync function chooseProviderModel(args: {\n out: NodeJS.WritableStream;\n interactive: boolean;\n provider: AgentProviderId;\n choice?: ProviderSetupView[\"choices\"][number];\n configuredModel: string | null;\n}): Promise<string | null> {\n const choices =\n args.choice?.modelChoices ??\n buildProviderModelChoices(args.provider, args.configuredModel);\n const recommended =\n choices.find((choice) => choice.recommended) ??\n choices.find((choice) => choice.source === \"provider-default\");\n if (!args.interactive) {\n return args.configuredModel ?? recommended?.value ?? null;\n }\n\n args.out.write(` Choose model for ${getProviderLabel(args.provider)}:\\n`);\n choices.forEach((choice, index) => {\n const marker = choice.recommended ? \" recommended\" : \"\";\n const current = choice.value === args.configuredModel ? \" current\" : \"\";\n args.out.write(\n ` ${index + 1}. ${choice.label}${marker}${current}\\n`,\n );\n });\n const currentIndex = choices.findIndex((choice) =>\n choice.value === args.configuredModel\n );\n const recommendedIndex = choices.findIndex((choice) => choice.recommended);\n const defaultIndex =\n currentIndex >= 0\n ? currentIndex + 1\n : recommendedIndex >= 0\n ? recommendedIndex + 1\n : 1;\n const selected = await promptText(args.out, \"Model\", String(defaultIndex));\n const number = Number.parseInt(selected, 10);\n let modelChoice: ProviderModelChoice | undefined;\n if (\n Number.isInteger(number) &&\n number >= 1 &&\n number <= choices.length\n ) {\n modelChoice = choices[number - 1];\n } else {\n modelChoice = choices.find((choice) => choice.value === selected);\n }\n if (modelChoice?.source === \"custom\") {\n const custom = await promptText(args.out, \"Custom model id\", \"\");\n return custom.length > 0 ? custom : recommended?.value ?? null;\n }\n return modelChoice?.value ?? recommended?.value ?? null;\n}\n\nasync function runLoginCommand(command: string): Promise<\n | { ok: true }\n | { ok: false; error: string }\n> {\n return new Promise((resolve) => {\n const child = spawn(command, {\n shell: true,\n stdio: \"inherit\",\n });\n child.on(\"error\", (err) => {\n resolve({ ok: false, error: err.message });\n });\n child.on(\"close\", (code) => {\n if (code === 0) {\n resolve({ ok: true });\n return;\n }\n resolve({ ok: false, error: `exited ${code ?? 1}` });\n });\n });\n}\n\n// ─── Guide installation ──────────────────────────────────────────────\n\ninterface InstallGuidesOptions {\n provider: AgentProviderId;\n claudeDir: string;\n guidesDir: string;\n cwd: string;\n}\n\ninterface InstallGuidesResult {\n providerLabel: string;\n anyChanges: boolean;\n filesWritten: string[];\n}\n\nconst CODEX_BLOCK_START = \"<!-- codealmanac:start -->\";\nconst CODEX_BLOCK_END = \"<!-- codealmanac:end -->\";\n\n/**\n * Install provider-specific usage guidance. Each provider gets the same\n * core instructions, but in the place that provider actually reads.\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 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 switch (options.provider) {\n case \"claude\":\n return await installClaudeGuides(options, srcMini, srcRef);\n case \"codex\":\n return await installCodexGuides(srcMini, srcRef);\n case \"cursor\":\n return await installCursorGuides(options.cwd, srcMini);\n }\n}\n\nasync function installClaudeGuides(\n options: InstallGuidesOptions,\n srcMini: string,\n srcRef: string,\n): Promise<InstallGuidesResult> {\n await mkdir(options.claudeDir, { recursive: true });\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 {\n providerLabel: \"Claude\",\n anyChanges: filesWritten.length > 0,\n filesWritten,\n };\n}\n\nasync function installCodexGuides(\n srcMini: string,\n srcRef: string,\n): Promise<InstallGuidesResult> {\n const codexDir = path.join(homedir(), \".codex\");\n await mkdir(codexDir, { recursive: true });\n const destMini = path.join(codexDir, \"codealmanac.md\");\n const destRef = path.join(codexDir, \"codealmanac-reference.md\");\n const miniChanged = await copyIfChanged(srcMini, destMini);\n const refChanged = await copyIfChanged(srcRef, destRef);\n const agentsChanged = await ensureManagedBlock(\n path.join(codexDir, \"AGENTS.md\"),\n codexGuideBlock(),\n );\n\n const filesWritten: string[] = [];\n if (miniChanged) filesWritten.push(\"codealmanac.md\");\n if (refChanged) filesWritten.push(\"codealmanac-reference.md\");\n if (agentsChanged) filesWritten.push(\"AGENTS.md\");\n\n return {\n providerLabel: \"Codex\",\n anyChanges: filesWritten.length > 0,\n filesWritten,\n };\n}\n\nasync function installCursorGuides(\n cwd: string,\n srcMini: string,\n): Promise<InstallGuidesResult> {\n const rulesDir = path.join(cwd, \".cursor\", \"rules\");\n await mkdir(rulesDir, { recursive: true });\n const mini = await readFile(srcMini, \"utf8\");\n const dest = path.join(rulesDir, \"codealmanac.mdc\");\n const body =\n \"---\\n\" +\n \"description: Use codealmanac wiki context during coding work\\n\" +\n \"alwaysApply: true\\n\" +\n \"---\\n\\n\" +\n mini;\n const changed = await writeIfChanged(dest, body);\n\n return {\n providerLabel: \"Cursor\",\n anyChanges: changed,\n filesWritten: changed ? [\".cursor/rules/codealmanac.mdc\"] : [],\n };\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\nasync function writeIfChanged(dest: string, body: string): Promise<boolean> {\n if (existsSync(dest)) {\n try {\n if (await readFile(dest, \"utf8\") === body) return false;\n } catch {\n // Fall through to write.\n }\n }\n await writeFile(dest, body, \"utf8\");\n return true;\n}\n\nasync function ensureManagedBlock(\n file: string,\n block: string,\n): Promise<boolean> {\n let existing = \"\";\n if (existsSync(file)) existing = await readFile(file, \"utf8\");\n const pattern = new RegExp(\n `${escapeRegex(CODEX_BLOCK_START)}[\\\\s\\\\S]*?${escapeRegex(CODEX_BLOCK_END)}`,\n );\n const next = pattern.test(existing)\n ? existing.replace(pattern, block.trimEnd())\n : `${existing.trimEnd()}${existing.trim().length > 0 ? \"\\n\\n\" : \"\"}${block.trimEnd()}\\n`;\n const normalized = next.endsWith(\"\\n\") ? next : `${next}\\n`;\n if (normalized === existing) return false;\n await writeFile(file, normalized, \"utf8\");\n return true;\n}\n\nfunction codexGuideBlock(): string {\n return [\n CODEX_BLOCK_START,\n \"# codealmanac\",\n \"\",\n \"This machine has codealmanac installed: a local wiki for codebases.\",\n \"Before codealmanac/wiki-related work, read `~/.codex/codealmanac.md`.\",\n \"For the full command reference, read `~/.codex/codealmanac-reference.md` on demand.\",\n CODEX_BLOCK_END,\n \"\",\n ].join(\"\\n\");\n}\n\nfunction escapeRegex(value: string): string {\n return value.replace(/[.*+?^${}()|[\\]\\\\]/g, \"\\\\$&\");\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\nfunction promptText(\n out: NodeJS.WritableStream,\n question: string,\n defaultValue: string,\n): Promise<string> {\n return new Promise((resolve) => {\n out.write(\n ` ${BLUE}\\u25c6${RST} ${question} ${DIM}[${defaultValue}]${RST} `,\n );\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();\n resolve(answer.length === 0 ? defaultValue : answer);\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\nfunction printProviderNextSteps(out: NodeJS.WritableStream): void {\n out.write(` ${DIM}Change provider/model later:${RST}\\n`);\n out.write(` ${DIM} almanac agents list${RST}\\n`);\n out.write(` ${DIM} almanac agents use <claude|codex|cursor>${RST}\\n`);\n out.write(\n ` ${DIM} almanac agents model <provider> <model|--default>${RST}\\n\\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"],"mappings":"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","names":["existsSync","createRequire","homedir","path","fileURLToPath","path","RST","DIM","WHITE_BOLD","BLUE","path","homedir","existsSync","fileURLToPath","require","createRequire"]}
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/indexer/schema.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/topics-yaml.ts","../src/indexer/wikilinks.ts","../src/registry/index.ts","../src/commands/health.ts","../src/indexer/duration.ts","../src/indexer/resolve-wiki.ts","../src/topics/dag.ts"],"sourcesContent":["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 { 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 // Keep read-side freshness even when CLI/agent write paths eagerly\n // reindex: users can still change `.almanac/pages/` directly via\n // manual edits, git pulls, merges, or branch switches.\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 { 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 { 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 { 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\";\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 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"],"mappings":"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","names":["existsSync","statSync","readFile","join","fg","yaml","existsSync","existsSync","existsSync","target","join","existsSync","fg","statSync","readFile","mkdir","readFile","rename","writeFile","dirname","readFile","isNodeError","path","mkdir","dirname","writeFile","rename","existsSync","readFile","basename","join","fg","existsSync","join","existsSync","join","join","existsSync","readFile","fg","basename"]}
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/update/config.ts"],"sourcesContent":["import { existsSync } from \"node:fs\";\nimport { mkdir, readFile, rename, writeFile } from \"node:fs/promises\";\nimport { dirname, join } from \"node:path\";\n\nimport {\n findNearestAlmanacDir,\n getGlobalAlmanacDir,\n getRepoAlmanacDir,\n} from \"../paths.js\";\n\nexport const AGENT_PROVIDER_IDS = [\"claude\", \"codex\", \"cursor\"] as const;\nexport type AgentProviderId = (typeof AGENT_PROVIDER_IDS)[number];\n\nexport function isAgentProviderId(value: string): value is AgentProviderId {\n return (AGENT_PROVIDER_IDS as readonly string[]).includes(value);\n}\n\nexport interface AgentConfig {\n /** Default provider for bootstrap/capture. Default: \"claude\". */\n default: AgentProviderId;\n /** Optional per-provider model override. `null` means provider default. */\n models: Partial<Record<AgentProviderId, string | null>>;\n}\n\n/**\n * `~/.almanac/config.toml` — global, cross-wiki configuration. Legacy\n * `config.json` is read and migrated forward on first normal access.\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 /** Agent-provider settings for bootstrap/capture. */\n agent: AgentConfig;\n}\n\nexport function defaultConfig(): GlobalConfig {\n return {\n update_notifier: true,\n agent: {\n default: \"claude\",\n models: {\n claude: null,\n codex: null,\n cursor: null,\n },\n },\n };\n}\n\nexport function getConfigPath(): string {\n return join(getGlobalAlmanacDir(), \"config.toml\");\n}\n\nexport function getLegacyConfigPath(): string {\n return join(getGlobalAlmanacDir(), \"config.json\");\n}\n\nexport function getProjectConfigPath(cwd: string): string | null {\n const repoRoot = findNearestAlmanacDir(cwd);\n return repoRoot === null ? null : join(getRepoAlmanacDir(repoRoot), \"config.toml\");\n}\n\nexport type ConfigOrigin = \"default\" | \"user\" | \"project\";\n\nexport interface ConfigReadOptions {\n path?: string;\n cwd?: string;\n}\n\nexport interface ConfigReadResult {\n config: GlobalConfig;\n origins: Record<string, ConfigOrigin>;\n raw: Record<string, unknown>;\n}\n\nexport async function readConfig(\n input?: string | ConfigReadOptions,\n): Promise<GlobalConfig> {\n return (await readConfigWithOrigins(input)).config;\n}\n\nexport async function readConfigWithOrigins(\n input?: string | ConfigReadOptions,\n): Promise<ConfigReadResult> {\n const opts = normalizeReadOptions(input);\n if (opts.path !== undefined) {\n const raw = await readRawConfigObject(opts.path);\n return {\n config: normalizeRawConfig(raw),\n origins: originsFromRaw(raw, \"user\"),\n raw,\n };\n }\n\n const file = getConfigPath();\n await migrateLegacyConfigIfNeeded(file);\n const userRaw = await readRawConfigObject(file);\n const mergedRaw = cloneJsonObject(userRaw);\n const origins = originsFromRaw(userRaw, \"user\");\n const projectPath = opts.cwd !== undefined ? getProjectConfigPath(opts.cwd) : null;\n if (projectPath !== null) {\n const projectRaw = await readRawConfigObject(projectPath);\n applyProjectConfig(mergedRaw, projectRaw);\n Object.assign(origins, originsFromRaw(projectRaw, \"project\", true));\n }\n return {\n config: normalizeRawConfig(mergedRaw),\n origins,\n raw: mergedRaw,\n };\n}\n\nfunction normalizeReadOptions(\n input?: string | ConfigReadOptions,\n): ConfigReadOptions {\n return typeof input === \"string\" ? { path: input } : input ?? {};\n}\n\nasync function migrateLegacyConfigIfNeeded(file: string): Promise<void> {\n if (existsSync(file)) return;\n const legacy = getLegacyConfigPath();\n if (!existsSync(legacy)) return;\n const raw = await readRawConfigObject(legacy);\n if (Object.keys(raw).length === 0) return;\n await writeConfig(normalizeRawConfig(raw), file);\n}\n\nfunction normalizeRawConfig(raw: Record<string, unknown>): GlobalConfig {\n const defaults = defaultConfig();\n const rawAgent =\n raw.agent !== undefined &&\n raw.agent !== null &&\n typeof raw.agent === \"object\" &&\n !Array.isArray(raw.agent)\n ? (raw.agent as Partial<AgentConfig>)\n : {};\n const rawDefault =\n typeof rawAgent.default === \"string\" &&\n isAgentProviderId(rawAgent.default)\n ? rawAgent.default\n : defaults.agent.default;\n const rawModels =\n rawAgent.models !== undefined &&\n rawAgent.models !== null &&\n typeof rawAgent.models === \"object\" &&\n !Array.isArray(rawAgent.models)\n ? (rawAgent.models as Record<string, unknown>)\n : {};\n const models: Partial<Record<AgentProviderId, string | null>> = {\n ...defaults.agent.models,\n };\n for (const id of AGENT_PROVIDER_IDS) {\n const value = rawModels[id];\n if (typeof value === \"string\" && value.length > 0) {\n models[id] = value === \"default\" || value === \"null\" ? null : value;\n } else if (value === null) {\n models[id] = null;\n }\n }\n return {\n update_notifier:\n typeof raw.update_notifier === \"boolean\"\n ? raw.update_notifier\n : defaults.update_notifier,\n agent: {\n default: rawDefault,\n models,\n },\n };\n}\n\nfunction applyProjectConfig(\n target: Record<string, unknown>,\n projectRaw: Record<string, unknown>,\n): void {\n const projectAgent =\n projectRaw.agent !== null &&\n typeof projectRaw.agent === \"object\" &&\n !Array.isArray(projectRaw.agent)\n ? projectRaw.agent as Record<string, unknown>\n : {};\n if (Object.keys(projectAgent).length === 0) return;\n const targetAgent =\n target.agent !== null &&\n typeof target.agent === \"object\" &&\n !Array.isArray(target.agent)\n ? target.agent as Record<string, unknown>\n : {};\n target.agent = targetAgent;\n if (typeof projectAgent.default === \"string\") {\n targetAgent.default = projectAgent.default;\n }\n const projectModels =\n projectAgent.models !== null &&\n typeof projectAgent.models === \"object\" &&\n !Array.isArray(projectAgent.models)\n ? projectAgent.models as Record<string, unknown>\n : {};\n if (Object.keys(projectModels).length === 0) return;\n const targetModels =\n targetAgent.models !== null &&\n typeof targetAgent.models === \"object\" &&\n !Array.isArray(targetAgent.models)\n ? targetAgent.models as Record<string, unknown>\n : {};\n targetAgent.models = targetModels;\n for (const id of AGENT_PROVIDER_IDS) {\n if (Object.prototype.hasOwnProperty.call(projectModels, id)) {\n targetModels[id] = projectModels[id];\n }\n }\n}\n\nfunction originsFromRaw(\n raw: Record<string, unknown>,\n origin: ConfigOrigin,\n agentOnly = false,\n): Record<string, ConfigOrigin> {\n const origins: Record<string, ConfigOrigin> = {};\n if (!agentOnly && Object.prototype.hasOwnProperty.call(raw, \"update_notifier\")) {\n origins.update_notifier = origin;\n }\n const agent =\n raw.agent !== null &&\n typeof raw.agent === \"object\" &&\n !Array.isArray(raw.agent)\n ? raw.agent as Record<string, unknown>\n : {};\n if (Object.prototype.hasOwnProperty.call(agent, \"default\")) {\n origins[\"agent.default\"] = origin;\n }\n const models =\n agent.models !== null &&\n typeof agent.models === \"object\" &&\n !Array.isArray(agent.models)\n ? agent.models as Record<string, unknown>\n : {};\n for (const id of AGENT_PROVIDER_IDS) {\n if (Object.prototype.hasOwnProperty.call(models, id)) {\n origins[`agent.models.${id}`] = origin;\n }\n }\n return origins;\n}\n\nasync function readSingleConfig(file: string): Promise<GlobalConfig> {\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 return normalizeRawConfig(parseConfigText(trimmed, file));\n } catch {\n return defaultConfig();\n }\n}\n\nexport async function writeConfig(\n config: GlobalConfig | Partial<GlobalConfig>,\n path?: string,\n): Promise<void> {\n const file = path ?? getConfigPath();\n await mkdir(dirname(file), { recursive: true });\n const current = await readSingleConfig(file);\n const existingRaw = await readRawConfigObject(file);\n const stored = toStoredConfigPatch(config, current, existingRaw);\n const body = serializeConfig(stored, file);\n const tmp = `${file}.tmp`;\n await writeFile(tmp, body, \"utf8\");\n await rename(tmp, file);\n}\n\nfunction normalizeConfig(config: GlobalConfig | Partial<GlobalConfig>): GlobalConfig {\n const defaults = defaultConfig();\n return {\n update_notifier:\n typeof config.update_notifier === \"boolean\"\n ? config.update_notifier\n : defaults.update_notifier,\n agent: {\n default:\n config.agent !== undefined && isAgentProviderId(config.agent.default)\n ? config.agent.default\n : defaults.agent.default,\n models: {\n ...defaults.agent.models,\n ...(config.agent?.models ?? {}),\n },\n },\n };\n}\n\nasync function readRawConfigObject(\n path: string,\n): Promise<Record<string, unknown>> {\n try {\n return parseConfigText(await readFile(path, \"utf8\"), path);\n } catch {\n // Fall through to empty.\n }\n return {};\n}\n\nexport function parseConfigText(\n raw: string,\n path = \"config.toml\",\n): Record<string, unknown> {\n const trimmed = raw.trim();\n if (trimmed.length === 0) return {};\n if (path.endsWith(\".json\") || trimmed.startsWith(\"{\")) {\n const parsed = JSON.parse(trimmed) as unknown;\n return parsed !== null && typeof parsed === \"object\" && !Array.isArray(parsed)\n ? parsed as Record<string, unknown>\n : {};\n }\n return parseTomlConfig(trimmed);\n}\n\nexport function serializeConfig(\n raw: Record<string, unknown>,\n path = \"config.toml\",\n): string {\n return path.endsWith(\".json\")\n ? `${JSON.stringify(raw, null, 2)}\\n`\n : serializeTomlConfig(raw);\n}\n\nfunction parseTomlConfig(raw: string): Record<string, unknown> {\n const result: Record<string, unknown> = {};\n let section: string[] = [];\n for (const original of raw.split(/\\r?\\n/)) {\n const line = stripTomlComment(original).trim();\n if (line.length === 0) continue;\n const sectionMatch = line.match(/^\\[([A-Za-z0-9_.-]+)\\]$/);\n if (sectionMatch !== null) {\n section = sectionMatch[1]!.split(\".\");\n continue;\n }\n const eq = line.indexOf(\"=\");\n if (eq === -1) continue;\n const key = line.slice(0, eq).trim();\n const value = parseTomlValue(line.slice(eq + 1).trim());\n setObjectPath(result, [...section, key], value);\n }\n return result;\n}\n\nfunction serializeTomlConfig(raw: Record<string, unknown>): string {\n const lines: string[] = [];\n if (typeof raw.update_notifier === \"boolean\") {\n lines.push(`update_notifier = ${raw.update_notifier ? \"true\" : \"false\"}`);\n }\n const agent =\n raw.agent !== null &&\n typeof raw.agent === \"object\" &&\n !Array.isArray(raw.agent)\n ? raw.agent as Record<string, unknown>\n : {};\n if (typeof agent.default === \"string\") {\n if (lines.length > 0) lines.push(\"\");\n lines.push(\"[agent]\");\n lines.push(`default = ${tomlString(agent.default)}`);\n }\n const models =\n agent.models !== null &&\n typeof agent.models === \"object\" &&\n !Array.isArray(agent.models)\n ? agent.models as Record<string, unknown>\n : {};\n const modelLines: string[] = [];\n for (const id of AGENT_PROVIDER_IDS) {\n if (!Object.prototype.hasOwnProperty.call(models, id)) continue;\n const value = models[id] === null ? \"default\" : models[id];\n if (typeof value === \"string\" && value.length > 0) {\n modelLines.push(`${id} = ${tomlString(value)}`);\n }\n }\n if (modelLines.length > 0) {\n if (lines.length > 0) lines.push(\"\");\n lines.push(\"[agent.models]\", ...modelLines);\n }\n return `${lines.join(\"\\n\")}\\n`;\n}\n\nfunction stripTomlComment(line: string): string {\n let inString = false;\n let escaped = false;\n for (let i = 0; i < line.length; i++) {\n const ch = line[i];\n if (escaped) {\n escaped = false;\n continue;\n }\n if (ch === \"\\\\\") {\n escaped = true;\n continue;\n }\n if (ch === \"\\\"\") inString = !inString;\n if (ch === \"#\" && !inString) return line.slice(0, i);\n }\n return line;\n}\n\nfunction parseTomlValue(raw: string): string | boolean {\n if (raw === \"true\") return true;\n if (raw === \"false\") return false;\n if (raw.startsWith(\"\\\"\") && raw.endsWith(\"\\\"\")) {\n return JSON.parse(raw) as string;\n }\n return raw;\n}\n\nfunction tomlString(value: string): string {\n return JSON.stringify(value);\n}\n\nfunction setObjectPath(\n raw: Record<string, unknown>,\n path: string[],\n value: string | boolean,\n): void {\n let cursor = raw;\n for (const part of path.slice(0, -1)) {\n const next = cursor[part];\n if (next === null || typeof next !== \"object\" || Array.isArray(next)) {\n cursor[part] = {};\n }\n cursor = cursor[part] as Record<string, unknown>;\n }\n const leaf = path[path.length - 1];\n if (leaf !== undefined) cursor[leaf] = value;\n}\n\nfunction toStoredConfigPatch(\n config: GlobalConfig | Partial<GlobalConfig>,\n current: GlobalConfig,\n raw: Record<string, unknown>,\n): Record<string, unknown> {\n const normalized = normalizeConfig(config);\n const defaults = defaultConfig();\n const stored = cloneJsonObject(raw);\n\n if (\n config.update_notifier !== undefined &&\n normalized.update_notifier !== current.update_notifier\n ) {\n setStoredValue(\n stored,\n [\"update_notifier\"],\n normalized.update_notifier,\n defaults.update_notifier,\n );\n }\n\n if (config.agent !== undefined) {\n if (\n config.agent.default !== undefined &&\n normalized.agent.default !== current.agent.default\n ) {\n setStoredValue(\n stored,\n [\"agent\", \"default\"],\n normalized.agent.default,\n defaults.agent.default,\n );\n }\n\n const inputModels = config.agent.models ?? {};\n for (const id of AGENT_PROVIDER_IDS) {\n if (!Object.prototype.hasOwnProperty.call(inputModels, id)) continue;\n const value = normalized.agent.models[id] ?? null;\n const currentValue = current.agent.models[id] ?? null;\n const defaultValue = defaults.agent.models[id] ?? null;\n if (value !== currentValue) {\n setStoredValue(stored, [\"agent\", \"models\", id], value, defaultValue);\n }\n }\n }\n pruneEmptyObjects(stored);\n return stored;\n}\n\nfunction setStoredValue(\n raw: Record<string, unknown>,\n path: string[],\n value: string | boolean | null,\n defaultValue: string | boolean | null,\n): void {\n let cursor = raw;\n for (const part of path.slice(0, -1)) {\n const next = cursor[part];\n if (next === null || typeof next !== \"object\" || Array.isArray(next)) {\n cursor[part] = {};\n }\n cursor = cursor[part] as Record<string, unknown>;\n }\n const leaf = path[path.length - 1];\n if (leaf === undefined) return;\n cursor[leaf] = value;\n if (value !== defaultValue) return;\n // Keep explicit defaults only when the caller changed the value to the\n // default. Unchanged explicit defaults are preserved by cloning `raw`.\n}\n\nfunction cloneJsonObject(raw: Record<string, unknown>): Record<string, unknown> {\n return JSON.parse(JSON.stringify(raw)) as Record<string, unknown>;\n}\n\nfunction pruneEmptyObjects(raw: Record<string, unknown>): void {\n for (const [key, value] of Object.entries(raw)) {\n if (value === null || typeof value !== \"object\" || Array.isArray(value)) {\n continue;\n }\n pruneEmptyObjects(value as Record<string, unknown>);\n if (Object.keys(value).length === 0) delete raw[key];\n }\n}\n"],"mappings":"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","names":[]}
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/commands/doctor-checks/format.ts","../src/commands/doctor-checks/install.ts","../src/commands/doctor-checks/probes.ts","../src/commands/doctor-checks/agents.ts","../src/commands/doctor-checks/updates.ts","../src/commands/doctor.ts"],"sourcesContent":["import { BLUE, BOLD, DIM, GREEN, RED, RST } from \"../../ansi.js\";\nimport type {\n AgentDoctorCheck,\n Check,\n CheckStatus,\n DoctorOptions,\n DoctorReport,\n} 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.agents.length > 0) {\n lines.push(color ? `${BOLD}## Agents${RST}` : \"## Agents\");\n for (const c of report.agents.map(agentToCheck)) {\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 agentToCheck(agent: AgentDoctorCheck): Check {\n const message = [\n agent.label,\n agent.selected ? \"(default)\" : null,\n agent.recommended ? \"(recommended)\" : null,\n agent.status === \"ok\" ? \"ready\" : readinessMessage(agent),\n `model: ${agent.model ?? \"provider default\"}`,\n agent.account,\n ].filter((part): part is string => part !== null).join(\" \");\n return {\n status: agent.status,\n key: `agents.${agent.id}`,\n message,\n fix: agent.fix,\n };\n}\n\nfunction readinessMessage(agent: AgentDoctorCheck): string {\n if (!agent.installed) return \"missing\";\n if (!agent.authenticated) return `not ready: ${agent.detail}`;\n return agent.detail;\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 { IMPORT_LINE } from \"../setup.js\";\nimport {\n classifyInstallPath,\n detectInstallPath,\n probeBetterSqlite3,\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 checks.push(describeProviderAuthSurface());\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, options.cwd));\n checks.push(await describeGuideActivation(claudeDir, options.cwd));\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 describeProviderAuthSurface(): Check {\n return {\n status: \"info\",\n key: \"install.auth\",\n message: \"provider auth is reported under Agents\",\n fix: \"run: almanac agents list\",\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, cwd: 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 for Claude (${path.basename(mini)}, ${path.basename(ref)})`,\n };\n }\n const codexMini = path.join(homedir(), \".codex\", \"codealmanac.md\");\n const codexRef = path.join(homedir(), \".codex\", \"codealmanac-reference.md\");\n if (existsSync(codexMini) && existsSync(codexRef)) {\n return {\n status: \"ok\",\n key: \"install.guides\",\n message: \"Agent guides installed for Codex (codealmanac.md, codealmanac-reference.md)\",\n };\n }\n const cursorRule = path.join(cwd, \".cursor\", \"rules\", \"codealmanac.mdc\");\n if (existsSync(cursorRule)) {\n return {\n status: \"ok\",\n key: \"install.guides\",\n message: \"Agent guide installed for Cursor (.cursor/rules/codealmanac.mdc)\",\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 describeGuideActivation(\n claudeDir: string,\n cwd: string,\n): Promise<Check> {\n const claudeMd = path.join(claudeDir, \"CLAUDE.md\");\n if (existsSync(claudeMd)) {\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 guide import present\",\n };\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\n const codexAgents = path.join(homedir(), \".codex\", \"AGENTS.md\");\n if (existsSync(codexAgents)) {\n try {\n const contents = await readFile(codexAgents, \"utf8\");\n if (\n contents.includes(\"<!-- codealmanac:start -->\") &&\n contents.includes(\"<!-- codealmanac:end -->\")\n ) {\n return {\n status: \"ok\",\n key: \"install.import\",\n message: \"Codex guide block present\",\n };\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 ${codexAgents}: ${msg}`,\n };\n }\n }\n\n const cursorRule = path.join(cwd, \".cursor\", \"rules\", \"codealmanac.mdc\");\n if (existsSync(cursorRule)) {\n return {\n status: \"ok\",\n key: \"install.import\",\n message: \"Cursor guide rule present\",\n };\n }\n\n return {\n status: \"problem\",\n key: \"install.import\",\n message: \"provider guide activation missing\",\n fix: \"run: almanac setup --yes\",\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 {\n checkClaudeAuth,\n type ClaudeAuthStatus,\n type SpawnCliFn,\n} from \"../../agent/providers/claude/index.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 { buildProviderSetupView } from \"../../agent/provider-view.js\";\nimport { checkClaudeAuth } from \"../../agent/providers/claude/index.js\";\nimport type { ProviderStatus } from \"../../agent/types.js\";\nimport type { AgentDoctorCheck, DoctorOptions } from \"./types.js\";\n\nexport async function gatherAgentChecks(\n options: DoctorOptions,\n): Promise<AgentDoctorCheck[]> {\n const view = await buildProviderSetupView({\n spawnCli: options.spawnCli,\n statuses:\n options.providerStatuses ??\n (options.spawnCli === undefined\n ? undefined\n : await injectedProviderStatuses(options)),\n });\n return view.choices.map((choice) => {\n return {\n status: choice.ready ? \"ok\" : \"problem\",\n id: choice.id,\n label: choice.label,\n readiness: choice.readiness,\n selected: choice.selected,\n recommended: choice.recommended,\n installed: choice.installed,\n authenticated: choice.authenticated,\n model: choice.effectiveModel,\n providerDefaultModel: choice.providerDefaultModel,\n configuredModel: choice.configuredModel,\n account: choice.account,\n detail: choice.detail,\n fix: choice.fixCommand ?? undefined,\n };\n });\n}\n\nasync function injectedProviderStatuses(\n options: DoctorOptions,\n): Promise<ProviderStatus[]> {\n const auth = await checkClaudeAuth(options.spawnCli);\n const hasApiKey =\n process.env.ANTHROPIC_API_KEY !== undefined &&\n process.env.ANTHROPIC_API_KEY.length > 0;\n const claudeReady = auth.loggedIn || hasApiKey;\n return [\n {\n id: \"claude\",\n installed: true,\n authenticated: claudeReady,\n detail: claudeReady\n ? auth.email ?? (hasApiKey ? \"ANTHROPIC_API_KEY set\" : \"logged in\")\n : \"not logged in\",\n },\n {\n id: \"codex\",\n installed: false,\n authenticated: false,\n detail: \"codex status not injected\",\n },\n {\n id: \"cursor\",\n installed: false,\n authenticated: false,\n detail: \"cursor-agent status not injected\",\n },\n ];\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 config set update_notifier true\",\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 { 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 AgentDoctorCheck,\n DoctorOptions,\n DoctorReport,\n DoctorResult,\n SqliteProbeResult,\n} from \"./doctor-checks/types.js\";\nimport { gatherAgentChecks } from \"./doctor-checks/agents.js\";\nimport { gatherUpdateChecks } from \"./doctor-checks/updates.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 agents: AgentDoctorCheck[] = options.wikiOnly === true\n ? []\n : await gatherAgentChecks(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 safeGatherWikiChecks(options);\n\n const report: DoctorReport = { version, install, agents, 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\nasync function safeGatherWikiChecks(\n options: DoctorOptions,\n): Promise<Check[]> {\n try {\n const { gatherWikiChecks } = await import(\"./doctor-checks/wiki.js\");\n return await gatherWikiChecks(options);\n } catch (err: unknown) {\n const msg = err instanceof Error ? err.message : String(err);\n return [\n {\n status: \"problem\",\n key: \"wiki.checks\",\n message: `could not run wiki checks: ${msg.split(\"\\n\")[0] ?? msg}`,\n fix: \"run: npm rebuild better-sqlite3 (in the install directory)\",\n },\n ];\n }\n}\n"],"mappings":"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","names":["existsSync","homedir","path","path","homedir","existsSync"]}