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.
@@ -140,23 +140,22 @@ function describeLastCapture(almanacDir, nowFn) {
140
140
  message: "last capture: never"
141
141
  };
142
142
  }
143
- let entries;
144
- try {
145
- entries = readdirSync(almanacDir);
146
- } catch {
147
- return {
148
- status: "info",
149
- key: "wiki.capture",
150
- message: "last capture: unknown"
151
- };
152
- }
153
- const captures = entries.filter(
154
- (e) => e.startsWith(".capture-") && (e.endsWith(".log") || e.endsWith(".jsonl"))
155
- ).map((e) => {
143
+ const logDirs = [path.join(almanacDir, "logs"), almanacDir];
144
+ const captures = logDirs.flatMap((dir) => {
145
+ let entries;
146
+ try {
147
+ entries = readdirSync(dir);
148
+ } catch {
149
+ return [];
150
+ }
151
+ return entries.filter(
152
+ (e) => e.startsWith(".capture-") && (e.endsWith(".log") || e.endsWith(".jsonl"))
153
+ ).map((e) => ({ dir, name: e }));
154
+ }).map((e) => {
156
155
  try {
157
156
  return {
158
- name: e,
159
- mtime: statSync(path.join(almanacDir, e)).mtimeMs
157
+ name: e.name,
158
+ mtime: statSync(path.join(e.dir, e.name)).mtimeMs
160
159
  };
161
160
  } catch {
162
161
  return null;
@@ -235,4 +234,4 @@ function countHealthProblems(jsonStdout) {
235
234
  export {
236
235
  gatherWikiChecks
237
236
  };
238
- //# sourceMappingURL=wiki-EHZ7LG7R.js.map
237
+ //# sourceMappingURL=wiki-IPSRRGOT.js.map
@@ -0,0 +1 @@
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 const logDirs = [path.join(almanacDir, \"logs\"), almanacDir];\n const captures = logDirs\n .flatMap((dir) => {\n let entries: string[];\n try {\n entries = readdirSync(dir);\n } catch {\n return [];\n }\n return entries\n .filter(\n (e) =>\n e.startsWith(\".capture-\") &&\n (e.endsWith(\".log\") || e.endsWith(\".jsonl\")),\n )\n .map((e) => ({ dir, name: e }));\n })\n .map((e) => {\n try {\n return {\n name: e.name,\n mtime: statSync(path.join(e.dir, e.name)).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":";;;;;;;;;;;;;;;;AAAA,SAAS,YAAY,aAAa,gBAAgB;AAClD,OAAO,UAAU;AAYjB,eAAsB,iBAAiB,SAA0C;AAC/E,QAAM,SAAkB,CAAC;AACzB,QAAM,WAAW,sBAAsB,QAAQ,GAAG;AAElD,MAAI,aAAa,MAAM;AACrB,WAAO,KAAK;AAAA,MACV,QAAQ;AAAA,MACR,KAAK;AAAA,MACL,SAAS;AAAA,MACT,KAAK;AAAA,IACP,CAAC;AACD,WAAO;AAAA,EACT;AAEA,SAAO,KAAK;AAAA,IACV,QAAQ;AAAA,IACR,KAAK;AAAA,IACL,SAAS,SAAS,QAAQ;AAAA,EAC5B,CAAC;AAED,MAAI;AACF,UAAM,iBAAiB,EAAE,SAAS,CAAC;AAAA,EACrC,QAAQ;AAAA,EAER;AAEA,SAAO,KAAK,MAAM,iBAAiB,QAAQ,CAAC;AAE5C,QAAM,aAAa,KAAK,KAAK,UAAU,UAAU;AACjD,QAAM,SAAS,KAAK,KAAK,YAAY,UAAU;AAC/C,SAAO,KAAK,GAAG,eAAe,MAAM,CAAC;AACrC,SAAO,KAAK,uBAAuB,MAAM,CAAC;AAC1C,SAAO,KAAK,oBAAoB,YAAY,QAAQ,GAAG,CAAC;AACxD,SAAO,KAAK,MAAM,eAAe,UAAU,OAAO,CAAC;AAEnD,SAAO;AACT;AAEA,eAAe,iBAAiB,UAAkC;AAChE,MAAI;AACF,UAAM,QAAQ,MAAM,UAAU,EAAE,MAAM,SAAS,CAAC;AAChD,QAAI,UAAU,MAAM;AAClB,aAAO;AAAA,QACL,QAAQ;AAAA,QACR,KAAK;AAAA,QACL,SAAS,kBAAkB,MAAM,IAAI;AAAA,MACvC;AAAA,IACF;AACA,WAAO;AAAA,MACL,QAAQ;AAAA,MACR,KAAK;AAAA,MACL,SAAS;AAAA,IACX;AAAA,EACF,SAAS,KAAc;AACrB,UAAM,MAAM,eAAe,QAAQ,IAAI,UAAU,OAAO,GAAG;AAC3D,WAAO;AAAA,MACL,QAAQ;AAAA,MACR,KAAK;AAAA,MACL,SAAS,4BAA4B,GAAG;AAAA,MACxC,KAAK;AAAA,IACP;AAAA,EACF;AACF;AAEA,SAAS,eAAe,QAAyB;AAC/C,QAAM,SAAkB,CAAC;AACzB,MAAI,YAA2B;AAC/B,MAAI,aAA4B;AAEhC,MAAI,WAAW,MAAM,GAAG;AACtB,QAAI;AACF,YAAM,KAAK,UAAU,MAAM;AAC3B,UAAI;AACF,oBAAY,UAAU,IAAI,OAAO;AACjC,qBAAa,UAAU,IAAI,QAAQ;AAAA,MACrC,UAAE;AACA,WAAG,MAAM;AAAA,MACX;AAAA,IACF,QAAQ;AACN,kBAAY;AAAA,IACd;AAAA,EACF;AAEA,MAAI,cAAc,MAAM;AACtB,WAAO,KAAK;AAAA,MACV,QAAQ;AAAA,MACR,KAAK;AAAA,MACL,SAAS,UAAU,SAAS;AAAA,IAC9B,CAAC;AAAA,EACH;AACA,MAAI,eAAe,MAAM;AACvB,WAAO,KAAK;AAAA,MACV,QAAQ;AAAA,MACR,KAAK;AAAA,MACL,SAAS,WAAW,UAAU;AAAA,IAChC,CAAC;AAAA,EACH;AAEA,SAAO;AACT;AAEA,SAAS,UAAU,IAAuB,OAAuB;AAC/D,QAAM,MAAM,GACT,QAA2B,6BAA6B,KAAK,EAAE,EAC/D,IAAI;AACP,SAAO,KAAK,KAAK;AACnB;AAEA,SAAS,uBAAuB,QAAuB;AACrD,MAAI,CAAC,WAAW,MAAM,GAAG;AACvB,WAAO;AAAA,MACL,QAAQ;AAAA,MACR,KAAK;AAAA,MACL,SAAS;AAAA,IACX;AAAA,EACF;AACA,MAAI;AACF,UAAM,UAAU,SAAS,MAAM,EAAE;AACjC,UAAM,MAAM,KAAK,IAAI,IAAI;AACzB,WAAO;AAAA,MACL,QAAQ;AAAA,MACR,KAAK;AAAA,MACL,SAAS,kBAAkB,eAAe,GAAG,CAAC;AAAA,IAChD;AAAA,EACF,QAAQ;AACN,WAAO;AAAA,MACL,QAAQ;AAAA,MACR,KAAK;AAAA,MACL,SAAS;AAAA,IACX;AAAA,EACF;AACF;AAEA,SAAS,oBACP,YACA,OACO;AACP,MAAI,CAAC,WAAW,UAAU,GAAG;AAC3B,WAAO;AAAA,MACL,QAAQ;AAAA,MACR,KAAK;AAAA,MACL,SAAS;AAAA,IACX;AAAA,EACF;AACA,QAAM,UAAU,CAAC,KAAK,KAAK,YAAY,MAAM,GAAG,UAAU;AAC1D,QAAM,WAAW,QACd,QAAQ,CAAC,QAAQ;AAChB,QAAI;AACJ,QAAI;AACF,gBAAU,YAAY,GAAG;AAAA,IAC3B,QAAQ;AACN,aAAO,CAAC;AAAA,IACV;AACA,WAAO,QACJ;AAAA,MACC,CAAC,MACC,EAAE,WAAW,WAAW,MACvB,EAAE,SAAS,MAAM,KAAK,EAAE,SAAS,QAAQ;AAAA,IAC9C,EACC,IAAI,CAAC,OAAO,EAAE,KAAK,MAAM,EAAE,EAAE;AAAA,EAClC,CAAC,EACA,IAAI,CAAC,MAAM;AACV,QAAI;AACF,aAAO;AAAA,QACL,MAAM,EAAE;AAAA,QACR,OAAO,SAAS,KAAK,KAAK,EAAE,KAAK,EAAE,IAAI,CAAC,EAAE;AAAA,MAC5C;AAAA,IACF,QAAQ;AACN,aAAO;AAAA,IACT;AAAA,EACF,CAAC,EACA,OAAO,CAAC,MAA4C,MAAM,IAAI;AACjE,MAAI,SAAS,WAAW,GAAG;AACzB,WAAO;AAAA,MACL,QAAQ;AAAA,MACR,KAAK;AAAA,MACL,SAAS;AAAA,IACX;AAAA,EACF;AACA,WAAS,KAAK,CAAC,GAAG,MAAM,EAAE,QAAQ,EAAE,KAAK;AACzC,QAAM,SAAS,SAAS,CAAC;AACzB,QAAM,OAAO,QAAQ,KAAK,oBAAI,KAAK,GAAG,QAAQ;AAC9C,QAAM,MAAM,MAAM,OAAO;AACzB,SAAO;AAAA,IACL,QAAQ;AAAA,IACR,KAAK;AAAA,IACL,SAAS,iBAAiB,eAAe,GAAG,CAAC,SAAS,OAAO,IAAI;AAAA,EACnE;AACF;AAEA,eAAe,eACb,UACA,SACgB;AAChB,QAAM,WAAW,QAAQ,eAAe;AACxC,MAAI;AACF,UAAM,YAAY,MAAM,SAAS;AAAA,MAC/B,KAAK;AAAA,MACL,MAAM;AAAA,IACR,CAAC;AACD,UAAM,WAAW,oBAAoB,UAAU,MAAM;AACrD,QAAI,aAAa,GAAG;AAClB,aAAO;AAAA,QACL,QAAQ;AAAA,QACR,KAAK;AAAA,QACL,SAAS;AAAA,MACX;AAAA,IACF;AACA,WAAO;AAAA,MACL,QAAQ;AAAA,MACR,KAAK;AAAA,MACL,SAAS,0BAA0B,QAAQ,WAAW,aAAa,IAAI,KAAK,GAAG;AAAA,MAC/E,KAAK;AAAA,IACP;AAAA,EACF,SAAS,KAAc;AACrB,UAAM,MAAM,eAAe,QAAQ,IAAI,UAAU,OAAO,GAAG;AAC3D,WAAO;AAAA,MACL,QAAQ;AAAA,MACR,KAAK;AAAA,MACL,SAAS,iCAAiC,GAAG;AAAA,IAC/C;AAAA,EACF;AACF;AAEA,IAAM,sBAA8C;AAAA,EAClD;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF;AAEA,SAAS,oBAAoB,YAA4B;AACvD,MAAI;AACF,UAAM,SAAS,KAAK,MAAM,UAAU;AACpC,QAAI,QAAQ;AACZ,eAAW,OAAO,qBAAqB;AACrC,YAAM,MAAM,OAAO,GAAG;AACtB,UAAI,MAAM,QAAQ,GAAG,EAAG,UAAS,IAAI;AAAA,IACvC;AACA,WAAO;AAAA,EACT,QAAQ;AACN,WAAO;AAAA,EACT;AACF;","names":[]}
package/guides/mini.md CHANGED
@@ -210,9 +210,9 @@ Empty stdout plus `# 0 results` on stderr means the query ran and genuinely matc
210
210
  ```bash
211
211
  almanac doctor # install.hook: ok/problem, wiki.capture: last capture age
212
212
  almanac hook status # just the hook entry
213
- ls -lah .almanac/.capture-*.log
213
+ ls -lah .almanac/logs/.capture-*.log
214
214
  ```
215
- No logs at all → the hook isn't installed, or bailed before backgrounding, or `cwd` was outside any wiki (silent correct no-op). Capture ran but wrote nothing → the reviewer rejected the draft for notability, or the session was pure-read. Check the `.capture-<id>.log` for the writer/reviewer transcript.
215
+ No logs at all → the hook isn't installed, or bailed before backgrounding, or `cwd` was outside any wiki (silent correct no-op). Capture ran but wrote nothing → the reviewer rejected the draft for notability, or the session was pure-read. Check `.almanac/logs/.capture-<id>.log` for the writer/reviewer transcript.
216
216
 
217
217
  ---
218
218
 
@@ -117,7 +117,7 @@ All topic subcommands accept `--wiki <name>`. `list` / `show` accept `--json`.
117
117
 
118
118
  #### `almanac bootstrap`
119
119
 
120
- Spawns an agent to create initial wiki stubs. Requires `ANTHROPIC_API_KEY` or a logged-in Claude subscription. `--quiet` suppresses per-tool streaming. `--model <model>` overrides the model. `--force` overwrites an existing populated wiki. Writes `.almanac/.bootstrap-<timestamp>.log`.
120
+ Spawns an agent to create initial wiki stubs. Requires `ANTHROPIC_API_KEY` or a logged-in Claude subscription. `--quiet` suppresses per-tool streaming. `--model <model>` overrides the model. `--force` overwrites an existing populated wiki. Writes `.almanac/logs/.bootstrap-<timestamp>.log`.
121
121
 
122
122
  Bootstrap is the scaffolding path — it creates `.almanac/pages/`, `.almanac/topics.yaml`, `.almanac/README.md`, and stub entity pages based on what the agent reads in the repo.
123
123
 
@@ -132,7 +132,7 @@ Run the writer/reviewer pipeline on a Claude Code session transcript. Usually au
132
132
  | `--quiet` | Suppress per-tool streaming; print only the final summary. |
133
133
  | `--model <model>` | Override the agent model. |
134
134
 
135
- Writes SDK transcript to `.almanac/.capture-<session-id>.jsonl` (one JSON message per line). When invoked manually without `--session`, falls back to `.capture-<timestamp>.jsonl` so repeated runs don't clobber each other. A writer subagent drafts pages; a reviewer subagent enforces notability + writing conventions (§9) before drafts land.
135
+ Writes SDK transcript to `.almanac/logs/.capture-<session-id>.jsonl` (one JSON message per line). When invoked manually without `--session`, falls back to `.capture-<timestamp>.jsonl` so repeated runs don't clobber each other. A writer subagent drafts pages; a reviewer subagent enforces notability + writing conventions (§9) before drafts land.
136
136
 
137
137
  #### `almanac hook install | uninstall | status`
138
138
 
@@ -455,7 +455,7 @@ Claude Code invokes `SessionEnd` hooks after each session. Payload on stdin:
455
455
 
456
456
  1. Parse payload with `jq`. Missing `jq` → exit 0 silently.
457
457
  2. Walk upward from `cwd` for a `.almanac/`. Bounded at filesystem root.
458
- 3. Background `almanac capture "$TRANSCRIPT" --session "$SESSION_ID" --quiet`, redirect to `.almanac/.capture-$SESSION_ID.log`, `disown`.
458
+ 3. Background `almanac capture "$TRANSCRIPT" --session "$SESSION_ID" --quiet`, redirect to `.almanac/logs/.capture-$SESSION_ID.log`, `disown`.
459
459
  4. Exit always `0`. Capture failures must never break Claude Code's session-end path.
460
460
 
461
461
  Falls back to `npx --no-install codealmanac` if `almanac` isn't on `PATH`.
@@ -482,7 +482,7 @@ Falls back to `npx --no-install codealmanac` if `almanac` isn't on `PATH`.
482
482
  ```bash
483
483
  almanac doctor # catch-all — reports hook state + last capture age
484
484
  almanac hook status # just the hook entry
485
- ls -lah .almanac/.capture-*.log
485
+ ls -lah .almanac/logs/.capture-*.log
486
486
  ```
487
487
 
488
488
  Installed but no log: `SessionEnd` didn't fire (rare, hard crash), or script bailed before backgrounding (add `set -x` to trace), or no `.almanac/` upward from `cwd` (silent correct no-op).
@@ -490,7 +490,7 @@ Installed but no log: `SessionEnd` didn't fire (rare, hard crash), or script bai
490
490
  ### Diagnosing "capture ran but wrote nothing"
491
491
 
492
492
  ```bash
493
- tail -200 .almanac/.capture-<id>.log
493
+ tail -200 .almanac/logs/.capture-<id>.log
494
494
  ```
495
495
 
496
496
  Common causes:
@@ -678,7 +678,7 @@ Missing `files:` frontmatter, OR path referenced only in inline prose (not via `
678
678
  almanac doctor # reports hook state + last capture age + auth
679
679
  claude auth status # OAuth token present?
680
680
  echo "${ANTHROPIC_API_KEY:0:10}" # API key fallback?
681
- ls -lah .almanac/.capture-*.log
681
+ ls -lah .almanac/logs/.capture-*.log
682
682
  ```
683
683
 
684
684
  No logs at all → script bailed pre-background. Add `set -x` to `hooks/almanac-capture.sh` to trace. If the hook itself isn't installed, `almanac doctor` reports `install.hook: problem` with `run: almanac setup --yes`.
@@ -693,9 +693,9 @@ Case sensitivity on Linux. Schema v2 stores `original_path` for case-preserving
693
693
 
694
694
  ### Forensics files
695
695
 
696
- - `.almanac/.capture-<session-id>.jsonl` — SDK message stream from `almanac capture` (one JSON object per line). Writer + reviewer interleaved.
697
- - `.almanac/.capture-<session-id>.log` — companion sidecar written by the SessionEnd hook: stdout+stderr of `almanac capture`, human-readable. Present only for hook-invoked captures; manual invocations emit only the `.jsonl`.
698
- - `.almanac/.bootstrap-<timestamp>.log` — one per bootstrap. Gitignored by default.
696
+ - `.almanac/logs/.capture-<session-id>.jsonl` — SDK message stream from `almanac capture` (one JSON object per line). Writer + reviewer interleaved.
697
+ - `.almanac/logs/.capture-<session-id>.log` — companion sidecar written by the SessionEnd hook: stdout+stderr of `almanac capture`, human-readable. Present only for hook-invoked captures; manual invocations emit only the `.jsonl`.
698
+ - `.almanac/logs/.bootstrap-<timestamp>.log` — one per bootstrap. Gitignored by default.
699
699
 
700
700
  ---
701
701
 
@@ -13,6 +13,13 @@
13
13
 
14
14
  set -u
15
15
 
16
+ # CodeAlmanac's own bootstrap/capture agents run Claude Code internally.
17
+ # Their SessionEnd events must not trigger another capture, or one capture
18
+ # can become an unbounded capture chain.
19
+ if [ "${CODEALMANAC_INTERNAL_SESSION:-}" = "1" ]; then
20
+ exit 0
21
+ fi
22
+
16
23
  # Be forgiving: if jq is missing, we can't parse the payload, so no-op.
17
24
  if ! command -v jq >/dev/null 2>&1; then
18
25
  exit 0
@@ -33,7 +40,8 @@ CWD=$(echo "$INPUT" | jq -r '.cwd // empty')
33
40
  DIR="$CWD"
34
41
  while [ "$DIR" != "/" ] && [ -n "$DIR" ]; do
35
42
  if [ -d "$DIR/.almanac" ]; then
36
- LOG_DIR="$DIR/.almanac"
43
+ LOG_DIR="$DIR/.almanac/logs"
44
+ mkdir -p "$LOG_DIR" || exit 0
37
45
  # Prefer `almanac` on PATH; fall back to `npx codealmanac` if the
38
46
  # binary isn't linked (happens with non-global installs).
39
47
  if command -v almanac >/dev/null 2>&1; then
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "codealmanac",
3
- "version": "0.1.8",
3
+ "version": "0.1.10",
4
4
  "description": "A living wiki for codebases, maintained by AI agents. Documents what the code can't say: decisions, flows, invariants, incidents, gotchas.",
5
5
  "keywords": [
6
6
  "wiki",
@@ -2,7 +2,9 @@
2
2
 
3
3
  You are the bootstrap agent for codealmanac. Your job is to create the initial `.almanac/` wiki for a codebase — the stubs and scaffolding that future coding sessions will build on.
4
4
 
5
- This runs once per repo. You're not writing a complete encyclopedia. You're setting up the anchors so the writer has something to attach knowledge to when real sessions happen.
5
+ This runs once per repo. You're not writing a complete encyclopedia. You're setting up the anchors and empty containers so the writer has something to attach knowledge to when real sessions happen.
6
+
7
+ Bootstrap optimizes for future usefulness, not completeness. The highest-value knowledge in an Almanac wiki is usually not "what files exist"; it is the context a future agent would otherwise have to rediscover: gotchas, why-we-do-this decisions, design philosophy, cross-file flows, upstream quirks, and constraints that are not obvious from code. During bootstrap, capture only what is observable from the repo. When the reason is not visible, create a well-labeled stub section that invites future capture instead of inventing rationale.
6
8
 
7
9
  ## Before you start
8
10
 
@@ -24,10 +26,14 @@ An anchor is a stable named thing that other pages will link to. Good anchors:
24
26
  - **Major third-party dependencies** we clearly use throughout the codebase: a framework (Next.js, FastAPI), a database client (Supabase, Prisma), a payment/search/auth service
25
27
  - **External services** referenced in config: Stripe, Claude API, Meilisearch, Redis
26
28
  - **Custom systems** visible in the directory structure: `src/auth/` suggests an auth system, `src/checkout/` suggests a checkout system, `backend/src/services/` suggests service modules
29
+ - **Cross-file flows** that are visible from code structure: polling-and-rendering, checkout, publishing, ingest, sync, auth callback handling
30
+ - **Design systems / visual language** when the repo has UI code: typography, color tokens, spacing conventions, component composition, animation rules, visual constraints
27
31
  - **Runtimes / deployment targets** when they matter: specific Python/Node versions, Docker orchestration
28
32
 
29
33
  **Group related dependencies into single anchors.** `@supabase/supabase-js` + `@supabase/auth-helpers-nextjs` + `postgres` with a `src/lib/supabase.ts` utility = one page called "Supabase." Not four pages.
30
34
 
35
+ Prefer project-specific anchors over generic stack anchors when both are plausible. A "Results API proxy" page is usually more useful than a "Next.js" page; a "Design system" page is usually more useful than a "Tailwind CSS" page. Keep stack pages short unless the repo does something unusual or version-specific with that technology.
36
+
31
37
  ### What's NOT an anchor
32
38
 
33
39
  Skip these. Creating pages for them bloats the wiki without value:
@@ -52,6 +58,8 @@ Good examples of topics that often emerge:
52
58
 
53
59
  Anchor pages usually carry the `stack` or `systems` topic plus a domain topic. Example: Supabase page → `[stack, database]`. Checkout flow page → `[flows, payments]`.
54
60
 
61
+ For UI repos, include a topic that can hold design knowledge (`ui`, `design`, or the repo's own term). For repos with external dependencies or live upstream data, include a place for operational knowledge in the README and in future-capture sections. Only create an `incidents`, `gotchas`, or similar topic if at least one page will use that topic during this bootstrap run; `almanac health` treats unused topics as empty.
62
+
55
63
  ## What to produce
56
64
 
57
65
  ### `.almanac/README.md`
@@ -120,9 +128,11 @@ Stubs are fine. They should have:
120
128
  - **One-paragraph intro** describing what it is in this repo (not generic docs)
121
129
  - **A "Where we use it" or similar section** pointing to specific files
122
130
  - **A stub marker comment** so the writer knows this page is incomplete
131
+ - **Future-capture sections when useful** — short empty headings such as "Known gotchas", "Design intent", "Rejected alternatives", "Operational constraints", or "Invariants" if that page is likely to accumulate that kind of knowledge. Leave the section empty with a stub comment unless the repo already proves the fact.
123
132
 
124
133
  Do NOT:
125
134
  - Write speculative content ("chosen for scalability" when you don't know why we chose it)
135
+ - Infer design rationale from aesthetics alone ("chosen to feel premium", "optimized for trust") unless a repo document says so
126
136
  - Paste generic docs for the dependency
127
137
  - Create one page per sub-package of a grouped dep
128
138