codealmanac 0.1.8 → 0.1.10

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.
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/commands/doctor-checks/wiki.ts"],"sourcesContent":["import { existsSync, readdirSync, statSync } from \"node:fs\";\nimport path from \"node:path\";\n\nimport type Database from \"better-sqlite3\";\n\nimport { ensureFreshIndex } from \"../../indexer/index.js\";\nimport { openIndex } from \"../../indexer/schema.js\";\nimport { findNearestAlmanacDir } from \"../../paths.js\";\nimport { findEntry } from \"../../registry/index.js\";\nimport { runHealth, type HealthReport } from \"../health.js\";\nimport { formatDuration } from \"./duration.js\";\nimport type { Check, DoctorOptions } from \"./types.js\";\n\nexport async function gatherWikiChecks(options: DoctorOptions): Promise<Check[]> {\n const checks: Check[] = [];\n const repoRoot = findNearestAlmanacDir(options.cwd);\n\n if (repoRoot === null) {\n checks.push({\n status: \"info\",\n key: \"wiki.none\",\n message: \"No wiki in current directory\",\n fix: \"run: almanac bootstrap (to create one in this repo)\",\n });\n return checks;\n }\n\n checks.push({\n status: \"info\",\n key: \"wiki.repo\",\n message: `repo: ${repoRoot}`,\n });\n\n try {\n await ensureFreshIndex({ repoRoot });\n } catch {\n // non-fatal: counts below and the health probe report any real issue.\n }\n\n checks.push(await describeRegistry(repoRoot));\n\n const almanacDir = path.join(repoRoot, \".almanac\");\n const dbPath = path.join(almanacDir, \"index.db\");\n checks.push(...describeCounts(dbPath));\n checks.push(describeIndexFreshness(dbPath));\n checks.push(describeLastCapture(almanacDir, options.now));\n checks.push(await describeHealth(repoRoot, options));\n\n return checks;\n}\n\nasync function describeRegistry(repoRoot: string): Promise<Check> {\n try {\n const entry = await findEntry({ path: repoRoot });\n if (entry !== null) {\n return {\n status: \"ok\",\n key: \"wiki.registered\",\n message: `registered as '${entry.name}'`,\n };\n }\n return {\n status: \"info\",\n key: \"wiki.registered\",\n message: \"not yet registered (will register on first command)\",\n };\n } catch (err: unknown) {\n const msg = err instanceof Error ? err.message : String(err);\n return {\n status: \"problem\",\n key: \"wiki.registered\",\n message: `could not read registry: ${msg}`,\n fix: \"inspect ~/.almanac/registry.json; remove or fix the malformed entry\",\n };\n }\n}\n\nfunction describeCounts(dbPath: string): Check[] {\n const checks: Check[] = [];\n let pageCount: number | null = null;\n let topicCount: number | null = null;\n\n if (existsSync(dbPath)) {\n try {\n const db = openIndex(dbPath);\n try {\n pageCount = countRows(db, \"pages\");\n topicCount = countRows(db, \"topics\");\n } finally {\n db.close();\n }\n } catch {\n pageCount = null;\n }\n }\n\n if (pageCount !== null) {\n checks.push({\n status: \"info\",\n key: \"wiki.pages\",\n message: `pages: ${pageCount}`,\n });\n }\n if (topicCount !== null) {\n checks.push({\n status: \"info\",\n key: \"wiki.topics\",\n message: `topics: ${topicCount}`,\n });\n }\n\n return checks;\n}\n\nfunction countRows(db: Database.Database, table: string): number {\n const row = db\n .prepare<[], { n: number }>(`SELECT COUNT(*) AS n FROM ${table}`)\n .get();\n return row?.n ?? 0;\n}\n\nfunction describeIndexFreshness(dbPath: string): Check {\n if (!existsSync(dbPath)) {\n return {\n status: \"info\",\n key: \"wiki.index\",\n message: \"index: not built yet (run any query command)\",\n };\n }\n try {\n const dbMtime = statSync(dbPath).mtimeMs;\n const age = Date.now() - dbMtime;\n return {\n status: \"info\",\n key: \"wiki.index\",\n message: `index: rebuilt ${formatDuration(age)} ago`,\n };\n } catch {\n return {\n status: \"info\",\n key: \"wiki.index\",\n message: \"index: present\",\n };\n }\n}\n\nfunction describeLastCapture(\n almanacDir: string,\n nowFn?: () => Date,\n): Check {\n if (!existsSync(almanacDir)) {\n return {\n status: \"info\",\n key: \"wiki.capture\",\n message: \"last capture: never\",\n };\n }\n let entries: string[];\n try {\n entries = readdirSync(almanacDir);\n } catch {\n return {\n status: \"info\",\n key: \"wiki.capture\",\n message: \"last capture: unknown\",\n };\n }\n const captures = entries\n .filter(\n (e) =>\n e.startsWith(\".capture-\") &&\n (e.endsWith(\".log\") || e.endsWith(\".jsonl\")),\n )\n .map((e) => {\n try {\n return {\n name: e,\n mtime: statSync(path.join(almanacDir, e)).mtimeMs,\n };\n } catch {\n return null;\n }\n })\n .filter((e): e is { name: string; mtime: number } => e !== null);\n if (captures.length === 0) {\n return {\n status: \"info\",\n key: \"wiki.capture\",\n message: \"last capture: never\",\n };\n }\n captures.sort((a, b) => b.mtime - a.mtime);\n const latest = captures[0]!;\n const now = (nowFn?.() ?? new Date()).getTime();\n const age = now - latest.mtime;\n return {\n status: \"info\",\n key: \"wiki.capture\",\n message: `last capture: ${formatDuration(age)} ago (${latest.name})`,\n };\n}\n\nasync function describeHealth(\n repoRoot: string,\n options: DoctorOptions,\n): Promise<Check> {\n const healthFn = options.runHealthFn ?? runHealth;\n try {\n const healthRes = await healthFn({\n cwd: repoRoot,\n json: true,\n });\n const problems = countHealthProblems(healthRes.stdout);\n if (problems === 0) {\n return {\n status: \"ok\",\n key: \"wiki.health\",\n message: \"almanac health reports 0 problems\",\n };\n }\n return {\n status: \"problem\",\n key: \"wiki.health\",\n message: `almanac health reports ${problems} problem${problems === 1 ? \"\" : \"s\"}`,\n fix: \"run: almanac health\",\n };\n } catch (err: unknown) {\n const msg = err instanceof Error ? err.message : String(err);\n return {\n status: \"info\",\n key: \"wiki.health\",\n message: `could not run almanac health: ${msg}`,\n };\n }\n}\n\nconst HEALTH_PROBLEM_KEYS: (keyof HealthReport)[] = [\n \"orphans\",\n \"stale\",\n \"dead_refs\",\n \"broken_links\",\n \"broken_xwiki\",\n \"empty_topics\",\n \"empty_pages\",\n \"slug_collisions\",\n];\n\nfunction countHealthProblems(jsonStdout: string): number {\n try {\n const report = JSON.parse(jsonStdout) as Partial<HealthReport>;\n let total = 0;\n for (const key of HEALTH_PROBLEM_KEYS) {\n const arr = report[key];\n if (Array.isArray(arr)) total += arr.length;\n }\n return total;\n } catch {\n return 0;\n }\n}\n"],"mappings":"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","names":[]}