codealmanac 0.1.2 → 0.1.3

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.
package/guides/mini.md CHANGED
@@ -43,7 +43,9 @@ The output is page slugs. Pick 1-3 that look relevant, `almanac show <slug>`, fo
43
43
 
44
44
  ---
45
45
 
46
- ## The five commands you'll actually use
46
+ ## The commands you'll use
47
+
48
+ Other commands exist (`list`, `tag`, `untag`, `hook`, `uninstall`, `doctor`, etc.) — most are administrative. See `almanac --help` or `@~/.claude/codealmanac-reference.md` for the full surface. In normal sessions you'll live in the four commands below.
47
49
 
48
50
  ### 1. `almanac search` — the starting point
49
51
 
@@ -122,32 +124,40 @@ Almost always existing. Skim `almanac topics` before creating. New topic is just
122
124
  ### "Can I just `almanac tag`?"
123
125
  Yes — safe, idempotent, preserves body bytes. Use `almanac tag` / `untag` rather than hand-editing frontmatter.
124
126
 
127
+ ### "The wiki returned nothing. Now what?"
128
+ Trust the silence. Empty stdout with `# 0 results` on stderr means the query ran cleanly and matched nothing — the wiki doesn't have a page on that yet, or the query needs to be broader. That is the answer, not a bug. Don't fall back to guessing; fall back to the code, and trust that capture will surface the knowledge the next time a session naturally discovers it.
129
+
130
+ If stderr shows a real error (an `almanac:` prefix or a commander parse failure), the invocation is broken — re-read `almanac --help` for the right flags.
131
+
125
132
  ---
126
133
 
127
134
  ## A concrete example
128
135
 
129
- User: *"fix the checkout timeout bug."*
136
+ User: *"the indexer isn't picking up my new page, what's going on?"*
130
137
 
131
138
  ```bash
132
- # 1. Find relevant pages
133
- $ almanac search --mentions src/checkout/
134
- checkout-flow
135
- inventory-lock-gotcha
136
- stripe-async-migration
139
+ # 1. Find pages touching the indexer
140
+ $ almanac search --mentions src/indexer/
141
+ sqlite-indexer
142
+ wikilink-syntax
137
143
 
138
144
  # 2. Triage with --lead
139
- $ almanac show checkout-flow --lead
140
- $ almanac show inventory-lock-gotcha --lead
145
+ $ almanac show sqlite-indexer --lead
146
+ The indexer (`src/indexer/`) builds and maintains `.almanac/index.db` a
147
+ SQLite database that powers all query commands (`search`, `info`, `health`,
148
+ `topics show`). It runs silently before every query command, comparing page
149
+ file mtimes against the stored `content_hash`; only changed or new pages are
150
+ re-parsed.
141
151
 
142
152
  # 3. Read the most relevant
143
- $ almanac show inventory-lock-gotcha
144
- # ...points to [[stripe-deadlock]], show that too
153
+ $ almanac show sqlite-indexer
154
+ # ...covers the schema, freshness rules, and where new pages get picked up
145
155
 
146
- # 4. Before editing, check backlinks
147
- $ almanac show checkout-flow --backlinks
156
+ # 4. Before you touch anything, check backlinks
157
+ $ almanac show sqlite-indexer --backlinks
148
158
  ```
149
159
 
150
- You now know: there was a deadlock between webhooks and the inventory lock, the team moved to async Stripe in April, two other pages link to `checkout-flow` so your edits matter beyond this file.
160
+ You now know: the indexer only re-parses pages whose mtime is newer than the stored `content_hash`, runs on every query command, and backing it is a schema you can read at `src/indexer/schema.ts`. The lead alone ruled out two entire hypotheses ("maybe it only indexes on startup", "maybe I need to restart something") before you read any source code.
151
161
 
152
162
  You don't write anything. At session end the capture agent reads the transcript, sees your discovery, writes or updates pages. Next session, a different agent running a related task sees it surface in `--mentions`.
153
163
 
@@ -180,6 +190,32 @@ The reviewer subagent (run by capture at session end) enforces these. Stricter w
180
190
 
181
191
  ---
182
192
 
193
+ ## Troubleshooting
194
+
195
+ ### `almanac doctor` is the catchall
196
+ When anything feels off and you don't know where to start, run `almanac doctor`. It reports the install (binary, native SQLite binding, Claude auth, hook, guides, CLAUDE.md import) and the current wiki (registered, page/topic counts, index freshness, last capture age, health problems). Every ✗ comes with a one-line `run: …` fix. Add `--json` for scripting.
197
+
198
+ ### "better-sqlite3 bindings failed"
199
+ Node version or arch mismatch with the prebuilt native binding. `almanac doctor` reports this under `install.sqlite` with the raw error. Fix by rebuilding the native binding:
200
+ ```bash
201
+ npm rebuild better-sqlite3 # in the install directory
202
+ ```
203
+
204
+ ### "search returned nothing"
205
+ Empty stdout plus `# 0 results` on stderr means the query ran and genuinely matched nothing. Don't retry with random flag permutations — either broaden the query, or accept the wiki hasn't covered that area yet. A real error would have come with an `almanac:`-prefixed stderr line.
206
+
207
+ `--json` is silent on stderr — the `[]` array is the empty signal there.
208
+
209
+ ### "capture didn't fire after my last session"
210
+ ```bash
211
+ almanac doctor # install.hook: ok/problem, wiki.capture: last capture age
212
+ almanac hook status # just the hook entry
213
+ ls -lah .almanac/.capture-*.log
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.
216
+
217
+ ---
218
+
183
219
  ## When in doubt
184
220
 
185
221
  - `.almanac/README.md` — repo-specific conventions + notability bar
@@ -4,12 +4,14 @@ Long-form manual for the `almanac` / `codealmanac` CLI. The mini guide at `~/.cl
4
4
 
5
5
  Groupings match `almanac --help`:
6
6
 
7
- 1. **Query** — `search`, `show`, `path`, `info`, `topics`, `health`
8
- 2. **Edit** — `tag`, `untag`, `topics create|link|unlink|rename|delete|describe`, `reindex`
9
- 3. **Wiki lifecycle** — `init`, `list`, `bootstrap`, `capture`
10
- 4. **Install** — `hook install|uninstall|status`
7
+ 1. **Query** — `search`, `show`, `health`, `list`
8
+ 2. **Edit** — `tag`, `untag`, `topics ...`
9
+ 3. **Wiki lifecycle** — `bootstrap`, `capture`, `hook ...`, `reindex`
10
+ 4. **Setup** — `setup`, `uninstall`, `doctor`
11
11
 
12
- Every command auto-registers the current repo in `~/.almanac/registry.json` on first run. Exceptions: `init` (registers explicitly) and `list --drop` (skips auto-register so the removal intent isn't undone).
12
+ Every query/edit command auto-registers the current repo in `~/.almanac/registry.json` on first run. Exceptions: `list --drop` (skips auto-register so the removal intent isn't undone) and the setup group (installers, not wiki commands — they never touch the registry).
13
+
14
+ There is no `almanac init` command. The two ways a wiki gets scaffolded are `almanac bootstrap` (agent reads the repo and seeds stub pages) and committing a `.almanac/` that someone else authored and cloning into it (auto-registered on first query command).
13
15
 
14
16
  ---
15
17
 
@@ -23,7 +25,7 @@ Every command auto-registers the current repo in `~/.almanac/registry.json` on f
23
25
  |---|---|---|---|
24
26
  | `[query]` | string | — | FTS5 MATCH against titles + bodies. Omit for pure-filter queries. |
25
27
  | `--topic <name...>` | repeatable | `[]` | AND-intersect filter. Walks the DAG subtree — `--topic auth` matches `auth` or any descendant. |
26
- | `--mentions <path>` | string | — | Pages referencing this file (no trailing `/`) or folder (trailing `/`). Matches both `files:` frontmatter and `[[...]]` file refs, case-insensitive. |
28
+ | `--mentions <path>` | string | — | Pages referencing this path. Matches exact file, trailing-slash folders, and any file under a folder prefix. Case-insensitive. |
27
29
  | `--since <duration>` | duration | — | Updated within window. Format: `<int>[smhdw]` (`2w`, `30d`, `48h`). By file mtime. |
28
30
  | `--stale <duration>` | duration | — | Inverse of `--since`. |
29
31
  | `--orphan` | bool | false | Pages with zero topics. |
@@ -33,52 +35,36 @@ Every command auto-registers the current repo in `~/.almanac/registry.json` on f
33
35
  | `--json` | bool | false | Structured JSON. |
34
36
  | `--limit <n>` | int ≥0 | unbounded | Cap results. |
35
37
 
36
- **Default output:** one slug per line to stdout.
37
- **`--json` schema:** `{wiki, results: [{slug, title, updated_at, topics, path}]}`.
38
- **Exit:** `0` always (empty result isn't an error). `2` on flag validation failure.
38
+ **Default output:** one slug per line to stdout. When zero pages match, stdout is empty and stderr emits `# 0 results` (a breadcrumb so users can tell "matched nothing" apart from "command broken"). `--json` is silent on stderr — `[]` is the unambiguous empty signal there.
39
+ **`--json` schema:** JSON array of `{slug, title, updated_at, topics, path}`.
40
+ **Exit:** `0` always (empty result isn't an error). Arg-parse failures exit `1` with an `almanac:` error.
39
41
 
40
42
  #### `almanac show [slug]`
41
43
 
42
- | Flag | Semantics |
43
- |---|---|
44
- | `[slug]` | Required unless `--stdin`. Slugs are kebab-canonicalized before lookup. |
45
- | `--stdin` | Read slugs from stdin. Pages separated by form-feed (`\f`) in output. |
46
- | `--wiki <name>` | Target a specific registered wiki. |
47
-
48
- Projections (if available in the installed build — check `--help`): `--raw` (body only), `--meta` (metadata only), `--lead` (first paragraph), `--backlinks` (pages linking in), `--links` (pages this links out to). Falls back to `almanac info` + `path` if missing.
49
-
50
- **Exit:** `0` on success, `1` if slug not found, `2` on flag errors.
51
-
52
- #### `almanac path [slug]`
44
+ Unified reader. Absorbs the old `info` and `path` commands — pick fields with flags.
53
45
 
54
- Resolve slug absolute file path. `--stdin` writes one path per input line, preserving order; missing slugs emit a blank line so output is 1:1.
55
-
56
- #### `almanac info [slug]`
57
-
58
- Metadata only (topics, refs, links, lineage). No body.
59
-
60
- **`--json` schema:**
61
- ```json
62
- {
63
- "slug": "checkout-flow",
64
- "title": "...",
65
- "updated_at": 1713000000,
66
- "archived_at": null,
67
- "superseded_by": null, "supersedes": null,
68
- "topics": [...],
69
- "files": [...],
70
- "wikilinks_out": [...],
71
- "wikilinks_in": [...],
72
- "file_refs": [{"path": "...", "is_dir": false}],
73
- "cross_wiki_refs": [{"wiki": "...", "target": "..."}]
74
- }
75
- ```
76
-
77
- #### `almanac topics` (and subcommands)
78
-
79
- `almanac topics` — list all with page counts. `--json` emits `[{slug, description, parents[], children[], page_count}]`.
80
-
81
- `almanac topics show <slug>` — description, parents, children, pages. `--descendants` includes pages tagged with descendant topics (walks the DAG subtree).
46
+ | Flag | Default | Semantics |
47
+ |---|---|---|
48
+ | `[slug]` | — | Required unless `--stdin`. Slugs are kebab-canonicalized before lookup. |
49
+ | `--stdin` | false | Read slugs from stdin, one per line. JSON Lines output for `--json` mode. |
50
+ | `--wiki <name>` | current repo | Target a specific registered wiki. |
51
+ | `--json` | false | Structured JSON. Overrides every view/field flag. |
52
+ | `--raw` / `--body` | false | Body only (alias pair). Guarantees exactly one trailing newline — shell redirect produces a well-formed file. |
53
+ | `--meta` | false | Metadata header only, no body. |
54
+ | `--lead` | false | First paragraph of the body only (cheap preview). |
55
+ | `--title` | false | Print title. |
56
+ | `--topics` | false | Print topics. |
57
+ | `--files` | false | Print file refs. |
58
+ | `--links` | false | Print outgoing wikilinks. |
59
+ | `--backlinks` | false | Print incoming wikilinks. |
60
+ | `--xwiki` | false | Print cross-wiki links. |
61
+ | `--lineage` | false | Print `archived_at` / `supersedes` / `superseded_by`. |
62
+ | `--updated` | false | Print updated timestamp. |
63
+ | `--path` | false | Print absolute file path (`info` + `path` replacement). |
64
+
65
+ Combining field flags emits labeled sections in canonical order. `--meta` is the full labeled header; individual flags like `--title --topics` give you just those two sections.
66
+
67
+ **Exit:** `0` on success, `1` if slug not found, non-zero on flag/input errors.
82
68
 
83
69
  #### `almanac health`
84
70
 
@@ -94,75 +80,137 @@ Eight independent categories. One failing doesn't skip the others.
94
80
 
95
81
  **Categories:** `orphans`, `stale`, `dead-refs`, `broken-links`, `broken-xwiki`, `empty-topics`, `empty-pages`, `slug-collisions`. Archived pages are exempt from most (see §4). Exit `0` always — the report IS the output.
96
82
 
83
+ #### `almanac list`
84
+
85
+ | Flag | Semantics |
86
+ |---|---|
87
+ | `--json` | Structured JSON. |
88
+ | `--drop <name>` | Remove a wiki from the registry. The **only** way entries are ever removed. Skips auto-register. |
89
+
97
90
  ### 1.2 Edit
98
91
 
99
92
  #### `almanac tag [page] [topics...]`
100
93
 
101
94
  Add topics to a page. Auto-creates missing topics. Idempotent. Rewrites only the frontmatter block; body bytes preserved. `--stdin` tags every page-slug from stdin with the same topic set — in that mode all positionals are topics.
102
95
 
96
+ Flags: `--stdin`, `--wiki <name>`.
97
+
103
98
  #### `almanac untag <page> <topic>`
104
99
 
105
- Remove one topic. Idempotent (silent 0 if page wasn't tagged).
100
+ Remove one topic. Idempotent (silent `0` if page wasn't tagged).
101
+
102
+ Flags: `--wiki <name>`.
106
103
 
107
- #### `almanac topics create <name>`
104
+ #### `almanac topics` (DAG management)
108
105
 
109
- `--parent <slug>` repeatable. Rejects if any parent slug doesn't exist.
106
+ - `almanac topics list` — list all topics with page counts. `--json` emits an array of `{slug, description, parents[], children[], page_count}`.
107
+ - `almanac topics show <slug>` — description, parents, children, pages. `--descendants` includes pages tagged with descendant topics (walks the DAG subtree).
108
+ - `almanac topics create <name>` — `--parent <slug>` repeatable. Rejects if any parent slug doesn't exist.
109
+ - `almanac topics link <child> <parent>` / `almanac topics unlink <child> <parent>` — add/remove a DAG edge. `link` is cycle-checked (§5). `unlink` is idempotent.
110
+ - `almanac topics rename <old> <new>` — rewrites `topics.yaml` first (atomic tmp+rename), then every affected page's `topics:` frontmatter. YAML-first so a mid-pass crash leaves the graph, not the pages, as the source of truth.
111
+ - `almanac topics delete <slug>` — removes from `topics.yaml`, untags every affected page. Does **not** cascade to children — orphaned children become top-level. Run `almanac health` to surface stragglers.
112
+ - `almanac topics describe <slug> <text>` — set the topic's one-line description.
113
+
114
+ All topic subcommands accept `--wiki <name>`. `list` / `show` accept `--json`.
115
+
116
+ ### 1.3 Wiki lifecycle
117
+
118
+ #### `almanac bootstrap`
110
119
 
111
- #### `almanac topics link <child> <parent>` / `topics unlink <child> <parent>`
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`.
112
121
 
113
- Add/remove a DAG edge. `link` is cycle-checked (§5). `unlink` is idempotent.
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.
114
123
 
115
- #### `almanac topics rename <old> <new>`
124
+ #### `almanac capture [transcript]`
116
125
 
117
- Rewrites `topics.yaml` first (atomic tmp+rename), then every affected page's `topics:` frontmatter. YAML-first so a mid-pass crash leaves the graph, not the pages, as the source of truth.
126
+ Run the writer/reviewer pipeline on a Claude Code session transcript. Usually automatic — the `SessionEnd` hook invokes this. Refuses if no `.almanac/` exists in cwd or any parent (capture maintains wikis, doesn't create them; run `almanac bootstrap` first).
118
127
 
119
- #### `almanac topics delete <slug>`
128
+ | Flag | Semantics |
129
+ |---|---|
130
+ | `[transcript]` | Explicit path. Falls back to `--session` match or most-recent-by-cwd. |
131
+ | `--session <id>` | Target a specific session by ID. Matches filename under `~/.claude/projects/`. |
132
+ | `--quiet` | Suppress per-tool streaming; print only the final summary. |
133
+ | `--model <model>` | Override the agent model. |
120
134
 
121
- Removes from `topics.yaml`, untags every affected page. Does **not** cascade to children orphaned children become top-level. Run `almanac health` to surface stragglers.
135
+ Writes SDK transcript to `.almanac/.capture-<session-id>.log`. A writer subagent drafts pages; a reviewer subagent enforces notability + writing conventions (§9) before drafts land.
122
136
 
123
- #### `almanac topics describe <slug> <text>`
137
+ #### `almanac hook install | uninstall | status`
124
138
 
125
- Set the topic's one-line description in `topics.yaml`.
139
+ See §7 — the hook is complex enough to warrant its own section.
126
140
 
127
141
  #### `almanac reindex`
128
142
 
129
143
  Forces a full rebuild of `.almanac/index.db`. Rarely needed — every query calls `ensureFreshIndex` first. Use after manual `topics.yaml` edits or when clock skew defeats mtime checks.
130
144
 
131
- ### 1.3 Wiki lifecycle
145
+ Flag: `--wiki <name>`.
132
146
 
133
- #### `almanac init`
147
+ ### 1.4 Setup
134
148
 
135
- Scaffolds `.almanac/` in cwd. `--name <name>` sets the registry entry (default: basename of cwd). `--description <text>` stored in the registry. Creates `pages/`, `topics.yaml`, `README.md`, `index.db`, and adds `.gitignore` entries.
149
+ #### `almanac setup` (alias: bare `codealmanac`)
136
150
 
137
- #### `almanac list`
151
+ Install the SessionEnd hook + the two CLAUDE.md guides (`codealmanac.md`, `codealmanac-reference.md`) + the `@~/.claude/codealmanac.md` import line. Idempotent.
138
152
 
139
- `--json` for structured output. `--drop <name>` is the **only** way to remove a registry entry.
153
+ | Flag | Semantics |
154
+ |---|---|
155
+ | `-y, --yes` | Skip prompts; install everything. |
156
+ | `--skip-hook` | Opt out of the SessionEnd hook. |
157
+ | `--skip-guides` | Opt out of the CLAUDE.md guides. |
140
158
 
141
- #### `almanac bootstrap`
159
+ Both `almanac setup` and bare `codealmanac` route here. `codealmanac --yes`, `codealmanac --skip-hook`, and `codealmanac --skip-guides` are the typical first-run invocations. Passing `--skip-hook --skip-guides` together short-circuits with a terse line — nothing was installed, no banner drawn.
142
160
 
143
- Spawn an agent to create initial wiki stubs. Requires `ANTHROPIC_API_KEY`. `--quiet` suppresses per-tool streaming. `--model <model>` overrides the model. `--force` overwrites a populated wiki. Writes `.almanac/.bootstrap-<timestamp>.log`.
161
+ #### `almanac uninstall`
144
162
 
145
- #### `almanac capture [transcript]`
163
+ Remove the hook + guides + import line.
146
164
 
147
- Capture knowledge from a Claude Code session transcript. Requires `ANTHROPIC_API_KEY` or a logged-in Claude Code session. Refuses if no `.almanac/` exists — capture maintains wikis, doesn't create them.
165
+ | Flag | Semantics |
166
+ |---|---|
167
+ | `-y, --yes` | Skip confirmations; remove everything. |
168
+ | `--keep-hook` | Don't remove the SessionEnd hook (guides still prompted unless `--yes`). |
169
+ | `--keep-guides` | Don't remove the guides or CLAUDE.md import (hook still prompted unless `--yes`). |
148
170
 
149
- **Transcript resolution order:** explicit positional → `--session <id>` matches filename under `~/.claude/projects/` → most recent transcript whose recorded `cwd` matches this repo.
171
+ #### `almanac doctor`
150
172
 
151
- Writes SDK transcript to `.almanac/.capture-<session-id>.log`. A writer subagent drafts pages; a reviewer subagent enforces notability + writing conventions (§9) before drafts land.
173
+ Read-only install + current-wiki health report. Every check reports a state; none of them mutate. Exit always `0` doctor is a report, not a test.
152
174
 
153
- ### 1.4 Install
175
+ | Flag | Semantics |
176
+ |---|---|
177
+ | `--json` | Structured JSON. |
178
+ | `--install-only` | Report only on the install (skip the wiki section). |
179
+ | `--wiki-only` | Report only on the current wiki (skip the install section). |
154
180
 
155
- #### `almanac hook install | uninstall | status`
181
+ **JSON shape:**
182
+ ```json
183
+ {
184
+ "version": "0.1.3",
185
+ "install": [
186
+ { "key": "install.path", "status": "ok", "message": "..." },
187
+ { "key": "install.sqlite", "status": "ok", "message": "..." },
188
+ { "key": "install.auth", "status": "problem", "message": "...", "fix": "run: claude auth login --claudeai" },
189
+ { "key": "install.hook", "status": "ok", "message": "..." },
190
+ { "key": "install.guides", "status": "ok", "message": "..." },
191
+ { "key": "install.import", "status": "ok", "message": "..." }
192
+ ],
193
+ "wiki": [
194
+ { "key": "wiki.repo", "status": "info", "message": "repo: /abs/path" },
195
+ { "key": "wiki.registered", "status": "ok", "message": "registered as '...'" },
196
+ { "key": "wiki.pages", "status": "info", "message": "pages: 42" },
197
+ { "key": "wiki.topics", "status": "info", "message": "topics: 7" },
198
+ { "key": "wiki.index", "status": "info", "message": "index: rebuilt 2m ago" },
199
+ { "key": "wiki.capture", "status": "info", "message": "last capture: 1h ago (.capture-<id>.log)" },
200
+ { "key": "wiki.health", "status": "ok", "message": "almanac health reports 0 problems" }
201
+ ]
202
+ }
203
+ ```
156
204
 
157
- See §7 the hook is complex enough to warrant its own section.
205
+ Each check has a stable `key` safe for scripting. entries include a `fix` field with a one-line "run: …" hint. Parse `--json` and count `status === "problem"` for a pass/fail gate.
158
206
 
159
207
  ### 1.5 `--stdin` pipe semantics
160
208
 
161
- Commands that accept `--stdin`: `show`, `path`, `info`, `tag`, `health`.
209
+ Commands that accept `--stdin`: `show`, `tag`, `health`.
162
210
 
163
211
  - One slug per line; blank lines ignored; whitespace trimmed.
164
- - Output order mirrors input order (matters for `path`, `info`).
165
- - Missing slugs don't abort — logged to stderr, pipeline continues. `show --stdin` writes a "not found" marker and keeps exit `0` for pipeline resilience; `path` / `info` exit `1` overall if any slug was missing.
212
+ - Output order mirrors input order.
213
+ - Missing slugs don't abort — logged to stderr, pipeline continues. `show --stdin` writes a "not found" marker per slug and keeps exit `0` for pipeline resilience.
166
214
  - `--stdin` must be explicit. No `isTTY` auto-detection (confusing under script redirection).
167
215
 
168
216
  ---
@@ -230,6 +278,8 @@ inventory lock row to Supabase, returns the PI client secret. See
230
278
  [[inventory-lock-gotcha]] for the deadlock we hit in March.
231
279
  ```
232
280
 
281
+ CRLF-terminated files are handled transparently — `show --raw` strips frontmatter without leaving a stray `\r` at the body head.
282
+
233
283
  ---
234
284
 
235
285
  ## 4. Archive / lineage
@@ -348,7 +398,7 @@ almanac topics delete old-payments
348
398
  **List pages that lack `files:` frontmatter for files they mention in prose:**
349
399
  ```bash
350
400
  almanac search | while read slug; do
351
- info=$(almanac info "$slug" --json)
401
+ info=$(almanac show "$slug" --json)
352
402
  prose=$(echo "$info" | jq -r '.file_refs[].path' | sort -u)
353
403
  fm=$(echo "$info" | jq -r '.files[]' | sort -u)
354
404
  missing=$(comm -23 <(echo "$prose") <(echo "$fm"))
@@ -358,7 +408,17 @@ done
358
408
 
359
409
  **Open every orphan page in `$EDITOR`:**
360
410
  ```bash
361
- almanac search --orphan | almanac path --stdin | xargs -n 1 "$EDITOR"
411
+ almanac search --orphan | almanac show --stdin --path | xargs -n 1 "$EDITOR"
412
+ ```
413
+
414
+ **Export a page's body to a standalone markdown file:**
415
+ ```bash
416
+ almanac show checkout-flow --raw > checkout-flow.md # exactly one trailing \n
417
+ ```
418
+
419
+ **Doctor a flaky install in CI:**
420
+ ```bash
421
+ almanac doctor --json | jq '.install[] | select(.status == "problem")'
362
422
  ```
363
423
 
364
424
  ---
@@ -396,11 +456,14 @@ Falls back to `npx --no-install codealmanac` if `almanac` isn't on `PATH`.
396
456
  **`status`:**
397
457
  - Reports installed / not installed, the script path, the settings path. Non-interactive.
398
458
 
459
+ `almanac setup` wraps `hook install` alongside the guides. `almanac uninstall` wraps `hook uninstall` alongside guide removal. You rarely invoke `hook *` directly.
460
+
399
461
  ### Diagnosing "capture didn't run"
400
462
 
401
463
  ```bash
402
- almanac hook status # installed?
403
- ls -lah .almanac/.capture-*.log # any logs at all?
464
+ almanac doctor # catch-all — reports hook state + last capture age
465
+ almanac hook status # just the hook entry
466
+ ls -lah .almanac/.capture-*.log
404
467
  ```
405
468
 
406
469
  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).
@@ -434,13 +497,13 @@ Common causes:
434
497
 
435
498
  ### Registration paths
436
499
 
437
- - **`almanac init`**explicit. Sets `name` and `description`.
438
- - **Silent auto-register**every other command (except `list --drop`) calls `autoRegisterIfNeeded` on cwd. Repo with `.almanac/` but no registry entry → added with `name = basename(cwd)`, no description. Makes "cloned a repo with `.almanac/` committed" just work.
500
+ - **Silent auto-register**every query/edit command (except `list --drop`) calls `autoRegisterIfNeeded` on cwd. A repo with `.almanac/` but no registry entry → added with `name = basename(cwd)`, no description. Makes "cloned a repo with `.almanac/` committed" just work.
501
+ - **`almanac bootstrap`**auto-registers as a side effect of scaffolding. `name` defaults to the repo basename; edit `~/.almanac/registry.json` or re-bootstrap to rename.
439
502
  - **`almanac list --drop <name>`** — the only removal path. Skips auto-register so the removal isn't immediately undone.
440
503
 
441
504
  ### `--wiki <name>`
442
505
 
443
- Route the command at a specific registered wiki. Used when you're in one repo but querying another. Without `--wiki`, commands resolve to the wiki whose `path` is an ancestor of cwd. If none, commands error: `almanac: no wiki registered for this cwd. run 'almanac init' or pass --wiki.`
506
+ Route the command at a specific registered wiki. Used when you're in one repo but querying another. Without `--wiki`, commands resolve to the wiki whose `path` is an ancestor of cwd. If none, commands error: `almanac: no .almanac/ found in this directory or any parent; run 'almanac bootstrap' first`.
444
507
 
445
508
  ### Cross-wiki link resolution
446
509
 
@@ -563,31 +626,54 @@ one, usually the webhook, leaving orders silently stuck in pending.
563
626
 
564
627
  ## 10. Troubleshooting
565
628
 
566
- ### "dead-refs reports files that exist"
567
- Case sensitivity on Linux. Schema v2 stores `original_path` for case-preserving stat; upgrade from pre-v2 requires `almanac reindex`. Dangling symlinks fail `existsSync` too.
629
+ ### Catch-all: `almanac doctor`
630
+
631
+ When something feels off and you don't know where to start, run `almanac doctor`. It reports install state (binary, native binding, Claude auth, hook, guides, import line) and current-wiki state (registered, page/topic counts, index freshness, last capture age, health problems). Every ✗ comes with a one-line `run: …` fix. `--json` for scripting.
632
+
633
+ ### "better-sqlite3 bindings missing"
634
+ Node version / arch mismatch with the prebuilt binary. `almanac doctor` reports it as `install.sqlite: problem` with the underlying error's first line. Fix:
635
+ ```bash
636
+ npm rebuild better-sqlite3 # in the install directory
637
+ ```
638
+ On M-series Macs with x64+arm64 Node installs, bindings are arch-specific — rebuild in the arch you'll run from. Node ≥20 required (`engines.node`).
639
+
640
+ ### "search returns nothing"
641
+
642
+ Two different outcomes to distinguish:
643
+ - **Silent stdout, stderr says `# 0 results`.** The query ran and genuinely matched nothing — this is an answer, not a failure. Either the wiki doesn't cover that area yet, or the query needs broadening.
644
+ - **An actual error on stderr.** Commander or `almanac:` prefix. That's a broken invocation; re-read the `--help`.
645
+
646
+ `--json` is silent on stderr — the `[]` array is the unambiguous empty signal.
568
647
 
569
648
  ### "pages don't show up in `--mentions`"
649
+
570
650
  Missing `files:` frontmatter, OR path referenced only in inline prose (not via `[[...]]`). Inline prose isn't indexed. If neither: `almanac reindex`.
571
651
 
572
652
  ### "topics missing after rename"
653
+
573
654
  `topics rename` bumps `topics.yaml` mtime → next query's `ensureFreshIndex` catches up. Hand-edited `topics.yaml` without page rewrites leaves frontmatter out of sync — `almanac reindex` then audit with `almanac health --orphans --empty-topics`.
574
655
 
575
656
  ### "capture didn't fire"
657
+
576
658
  ```bash
577
- almanac hook status
578
- claude auth status # OAuth token present?
579
- echo "${ANTHROPIC_API_KEY:0:10}" # API key fallback?
659
+ almanac doctor # reports hook state + last capture age + auth
660
+ claude auth status # OAuth token present?
661
+ echo "${ANTHROPIC_API_KEY:0:10}" # API key fallback?
580
662
  ls -lah .almanac/.capture-*.log
581
663
  ```
582
- No logs at all → script bailed pre-background. Add `set -x` to `hooks/almanac-capture.sh` to trace.
664
+
665
+ 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`.
583
666
 
584
667
  ### "slug collision warnings"
668
+
585
669
  Two files kebab-case to the same slug (`Checkout Flow.md` and `checkout-flow.md`). `health --slug-collisions` lists them. Rename one, grep `.almanac/pages/` for any `[[...]]` references, update them.
586
670
 
587
- ### "better-sqlite3 bindings missing"
588
- Node version / arch mismatch with the prebuilt binary. `npm rebuild better-sqlite3`. On M-series Macs with x64+arm64 Node installs, bindings are arch-specific — rebuild in the arch you'll run from. Node ≥20 required (`engines.node`).
671
+ ### "dead-refs reports files that exist"
672
+
673
+ Case sensitivity on Linux. Schema v2 stores `original_path` for case-preserving stat; upgrade from pre-v2 requires `almanac reindex`. Dangling symlinks also fail `existsSync`.
589
674
 
590
675
  ### Forensics files
676
+
591
677
  - `.almanac/.capture-<session-id>.log` — per-session SDK transcript from capture. Writer + reviewer interleaved.
592
678
  - `.almanac/.bootstrap-<timestamp>.log` — one per bootstrap. Gitignored by default.
593
679
 
@@ -596,4 +682,5 @@ Node version / arch mismatch with the prebuilt binary. `npm rebuild better-sqlit
596
682
  ## When in doubt
597
683
 
598
684
  - `almanac --help` / `almanac <command> --help` — flags are always current for the installed build.
685
+ - `almanac doctor` — one command that reports everything relevant about install + current wiki.
599
686
  - `.almanac/README.md` in the repo — the notability bar and topic taxonomy for *this* repo override anything here.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "codealmanac",
3
- "version": "0.1.2",
3
+ "version": "0.1.3",
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",