Foam vault
Overview
Foam is a VS Code extension that adds wikilink autocomplete and backlinks on
top of a plain directory of markdown files. There's also an official foam mcp server (foam-cli, docs at docs.foam.md/tools/cli/mcp/) exposing the
same graph — links, backlinks, tags, orphan/dead-end detection — as MCP
tools. It's registered project-scoped for this vault (.mcp.json, read-only,
no --allow-writes) — prefer its graph tools (mcp__foam__*, once
approved/connected) over hand-rolled regex over [[...]] for anything
graph-shaped (orphans, backlinks, dead links). For plain content reads and
edits, Read/Write/Edit/Grep on the files directly is still the normal path.
This workstation's setup
- Vault:
~/workdir/notes, a git repo with a public GitHub remote
(origin, sandravwc/notes, branch master). Every write to the vault
ends with git add -A && git commit && git push, no separate ask needed
— the user expects the repo in sync. Because it is public, grep the diff
for secrets, tokens, passwords, public IPs and real account names before
the push (the no-company-specifics rule below applies doubly).
- This vault replaced a Logseq DB-version graph (migrated 2026-09-19,
because Logseq's DB version broke git-diffable sync and namespace display).
MIGRATION-REVIEW.md in the vault root documents what did and didn't carry
over from that one-time conversion.
- Layout: what used to be Logseq's
category⁄subtopic namespace convention
is now real directories, e.g. cheat sheet/mysql-major-upgrade.md,
android/adb-debloat.md. When creating a new page that conceptually
belongs under an existing category, put it in that category's directory
rather than flattening it at the vault root.
- Journal: one file per day at
journal/YYYY-MM-DD.md, frontmatter
date: YYYY-MM-DD. Journal entries are reference material (often TODO-style
progress notes), not scratch — don't discard existing entries.
- Page frontmatter:
title: is optional — Foam falls back to the first # H1, then the filename, if absent. Keep it where the migration set it
(the page name, with a real / for what used to be a namespace) rather
than stripping it.
- Tags:
tags: [...] frontmatter and inline #tag are real, live Foam
features — indexed and browsable via the Tag Explorer sidebar panel, not
inert metadata. Foam renders each tag as its own graph node, so never
give a page a tag that's the same string as a category/directory it's
already filed under (e.g. don't tag cheat sheet/jq.md with cheat sheet) — that produces a tag-node and a note-node with an identical
label, which look like duplicate/broken graph nodes. The migration did
exactly this at first and it had to be undone (see MIGRATION-REVIEW.md).
Directory placement already is the category; only add a tag for something
that genuinely cuts across categories.
- No namespace feature: unlike OG Logseq, Foam does not auto-generate an
index/hierarchy page per category. The directory tree (VS Code's file
explorer) is the only built-in substitute. The flat
[[category/x]] link
lists on hub pages like cheat sheet.md are manual holdovers from
Logseq's namespace view — they will not stay in sync automatically, so
add a link there by hand when adding a new file under that category.
- Wikilinks:
[[category/page]], same syntax Logseq used. Foam resolves by
path or unique basename and autocompletes on typing [[. A link to a page
that doesn't exist yet renders as an unresolved placeholder — that's fine;
only create the target file once there's real content for it.
- Every category directory needs a top-level
<category>.md hub linking its
children ([[category/x]] list, same shape as cheat sheet.md), even one
with no body content of its own otherwise — without it, every file in that
directory is an orphan in Foam's graph (no auto namespace edges, see
above). Add the new page's link to the hub when creating one.
.markdownlint.json at the vault root turns off rules that fight this
vault's actual conventions (long real command lines, <placeholder>
genericization, Logseq's inline ### label bullets read as headings) —
don't silence a new markdownlint warning by wrapping/reformatting real
content; check whether it's one of these known-intentional patterns first
and extend the config instead.
- Never write company-, client-, or employer-identifying specifics into
this vault: no real customer/client names, no internal hostnames/domains,
no credentials/API keys/tokens, no employee names beyond the user
themself. Generalize hostnames (
db01.example.internal), redact secrets
(<redacted>), keep content technique-focused and reusable. This was a
deliberate cleanup the user already did once (see journal/2026-09-17.md)
— don't reintroduce that kind of content on their behalf.
- Prefer fenced code blocks for anything code/command/config-shaped, matching
the style already used throughout the vault.
- No zero-width-space escaping needed for
[[ ... ]] shell test syntax
inside code blocks — that was a workaround for Logseq's CLI eagerly
parsing every [[...]] as a wikilink even inside fenced code. Foam doesn't
do that; write shell code exactly as it should run.
Page style
The user writes these pages as personal shorthand/memory-jogs, not
documentation for a general reader. Match that register — do not write like
a tutorial or a wiki. Compiled from the pages the user actually authored (not
pages this assistant wrote):
- Label = 2-6 word noun/imperative fragment, lowercase, no terminal
punctuation. Not a sentence.
"resize disk", "create con", "mod con"
— not "How to resize a disk".
- The code block is the content. A label followed immediately by a fenced
command is the default shape of a bullet; most bullets need nothing else.
- No motivation, background, or "why this matters" prose. State the action
or fact, not the reasoning behind wanting it, unless a single short clause
is truly required to disambiguate the label.
- Never explain what a tool/flag/concept generically is or why it's useful —
assume the reader already knows or will look it up.
- Reader = senior linux admin. Record only what they can't reproduce from
memory: the non-obvious flag, the platform quirk, the order, the decision.
A generic command (
rsync -av, dmesg | grep, ls, a -c config check,
a pipe glued from standard tools) is never content, on any page.
- Category pages (
android/, kubernetes/, …) hold only what is specific
to that platform/project. Generic tool knowledge never lives there: if it
is worth keeping at all, it goes to cheat sheet/<tool> and the category
page links it.
- Cheat sheet bar: a command earns a line by its flags or its sequence, not
by existing.
- Install/setup recipes are command-only, that's fine. But when a bullet
records a choice between approaches (nfs mount vs ssh for bulk moves,
auth at the proxy vs in the app), the content is the one-clause why, not
a generic command demonstrating the chosen side.
- One label = one subject. A tangent ("getting data onto the ssd" under
"stale handles") gets its own bullet or gets cut.
- Caveats, gotchas, sequencing ("on every node:", "then on master:"), and
"won't work like this:" warnings go inside the code block as
comments/lines, not as separate explanatory bullets or prose.
- Don't restate in prose what the code already shows, and don't pad the code
with comments explaining what a command obviously does.
- No headers/sections/"Overview" framing unless the source material
genuinely has distinct phases the user themself would separate — default
is a flat bullet list.
- Nesting mirrors real execution order or real variants ("quick and dirty"
vs. "somewhat fancy" as sibling sub-bullets), never conceptual/topical
grouping added for tidiness.
- Leave typos, shorthand, mixed German/English, and raw pasted terminal
history (prompts, timestamps) as-is when reusing the user's own words —
don't proofread or formalize their voice. Do not introduce new
spelling/grammar looseness on this assistant's own writing to fake the
voice, either.
- Incomplete stubs are fine and expected: a bare label with no code,
"tbd",
or a dangling - placeholder. Don't feel pressure to complete or pad them.
- Omit anything not load-bearing for reproducing the action: no "Goal:"
framing, no scope statements, no citations/sources, no incidental
command that only mattered for one specific occasion, unless it's actually
core to the technique being recorded.
- When genericizing a real command (per the no-company-specifics rule
above), swap the real value for a placeholder and move on — don't add a
sentence about why it was swapped.
End of every session: rundown, not just the journal
After any session with real work, do a pass over what was learned and split
it into the right places, then tell the user in a short list what was
written where:
journal/<today>.md: what happened, outcomes, todos, lessons — the
narrative of the day.
- Own page per reusable technique: anything that would help on a different
project (a build recipe, an API quirk, a debugging path, a platform
limitation) goes on its own page in the right category directory, linked
from the category hub and from the project page that produced it. The
project page then links there instead of repeating the recipe.
- Existing pages: if today corrected or extended something already noted,
edit that page rather than adding a parallel one.
Then commit and push (see vault setup). Print the rundown as a bullet list:
page path, one clause what went there.
Typical asks and how to serve them
- "Log/note that I did X" / "add today's progress" / "add a TODO" → open or
create
journal/<today, YYYY-MM-DD>.md (frontmatter date: <today> if
the file is new) and append a top-level bullet.
- "Update my
<topic> cheat sheet" / "add this to my notes" → Grep the
vault for the topic to find the right file (check the likely category
directory first, e.g. cheat sheet/); Edit it to append/update a bullet,
or Write a new file with title: frontmatter if it doesn't exist yet.
- Prefer nested markdown bullets (sibling/child list items, each idea its
own bullet) over flattening structured content — multiple steps, code
blocks, variants — into one paragraph or one giant block. This mirrors
the block-per-idea shape Logseq enforced; nothing enforces it here, so
keep doing it deliberately.
- Link related pages with
[[category/page]] rather than restating content
that already lives on another page.
Stop hook: session-log prompt
This workstation has a Claude Code Stop hook wired in
~/.claude/settings.json that fires when a session ends. It does not
write to the vault itself — an LLM can't reliably tell trivial edits from
things worth journaling, so it defers to the user.
- Script:
hooks/foam-stop-log.sh in this repo, installed at
~/.claude/hooks/foam-stop-log.sh.
- Behavior: once per session, if any tool was used, it blocks the Stop
(
{"decision":"block","reason":...}) so the assistant asks the user (via
AskUserQuestion) whether to log a note to today's journal file, and what
to write. Declining or "skip" ends it there — nothing is written without
explicit go-ahead.
- Dedup: touches a marker file at
~/.cache/claude-code-foam-stop/<session_id> so it only blocks once per
session (also checks the hook's own stop_hook_active flag to avoid
looping).
Install on a new machine:
mkdir -p ~/.claude/hooks
cp hooks/foam-stop-log.sh ~/.claude/hooks/foam-stop-log.sh
chmod +x ~/.claude/hooks/foam-stop-log.sh
Then merge this into ~/.claude/settings.json (merge into existing
hooks.Stop, don't overwrite):
{
"hooks": {
"Stop": [
{
"hooks": [
{ "type": "command", "command": "~/.claude/hooks/foam-stop-log.sh", "timeout": 15 }
]
}
]
}
}
Needs jq on PATH. Requires opening /hooks once (or restarting Claude
Code) after first install so the settings watcher picks up the new hooks
directory.
Tips
- The vault is a plain git repo — commit like any other repo when the user
asks; there's no separate sync/export step the way Logseq needed one.
- If a wikilink's target category doesn't have a directory yet, create it by
just writing the first file into it (
mkdir -p semantics via Write).
1---2name: foam3description: Operate this workstation's Foam notes vault (plain markdown files + wikilinks, git-backed) at ~/workdir/notes — log work progress, create/update pages and cheat sheets, capture journal entries, link related notes. Use whenever the user asks to note something down, log/journal work progress, update their notes, or otherwise mentions Foam/their notes vault.4---56# Foam vault78## Overview910Foam is a VS Code extension that adds wikilink autocomplete and backlinks on11top of a plain directory of markdown files. There's also an official `foam12mcp` server (`foam-cli`, docs at docs.foam.md/tools/cli/mcp/) exposing the13same graph — links, backlinks, tags, orphan/dead-end detection — as MCP14tools. It's registered project-scoped for this vault (`.mcp.json`, read-only,15no `--allow-writes`) — prefer its graph tools (`mcp__foam__*`, once16approved/connected) over hand-rolled regex over `[[...]]` for anything17graph-shaped (orphans, backlinks, dead links). For plain content reads and18edits, Read/Write/Edit/Grep on the files directly is still the normal path.1920## This workstation's setup2122- Vault: `~/workdir/notes`, a git repo with a **public** GitHub remote23 (`origin`, `sandravwc/notes`, branch `master`). Every write to the vault24 ends with `git add -A && git commit && git push`, no separate ask needed25 — the user expects the repo in sync. Because it is public, grep the diff26 for secrets, tokens, passwords, public IPs and real account names before27 the push (the no-company-specifics rule below applies doubly).28- This vault replaced a Logseq **DB-version** graph (migrated 2026-09-19,29 because Logseq's DB version broke git-diffable sync and namespace display).30 `MIGRATION-REVIEW.md` in the vault root documents what did and didn't carry31 over from that one-time conversion.32- Layout: what used to be Logseq's `category⁄subtopic` namespace convention33 is now real directories, e.g. `cheat sheet/mysql-major-upgrade.md`,34 `android/adb-debloat.md`. When creating a new page that conceptually35 belongs under an existing category, put it in that category's directory36 rather than flattening it at the vault root.37- Journal: one file per day at `journal/YYYY-MM-DD.md`, frontmatter38 `date: YYYY-MM-DD`. Journal entries are reference material (often TODO-style39 progress notes), not scratch — don't discard existing entries.40- Page frontmatter: `title:` is optional — Foam falls back to the first `#41 H1`, then the filename, if absent. Keep it where the migration set it42 (the page name, with a real `/` for what used to be a namespace) rather43 than stripping it.44- Tags: `tags: [...]` frontmatter and inline `#tag` are real, live Foam45 features — indexed and browsable via the Tag Explorer sidebar panel, not46 inert metadata. Foam renders each tag as its own graph node, so **never47 give a page a tag that's the same string as a category/directory it's48 already filed under** (e.g. don't tag `cheat sheet/jq.md` with `cheat49 sheet`) — that produces a tag-node and a note-node with an identical50 label, which look like duplicate/broken graph nodes. The migration did51 exactly this at first and it had to be undone (see `MIGRATION-REVIEW.md`).52 Directory placement already is the category; only add a tag for something53 that genuinely cuts across categories.54- No namespace feature: unlike OG Logseq, Foam does not auto-generate an55 index/hierarchy page per category. The directory tree (VS Code's file56 explorer) is the only built-in substitute. The flat `[[category/x]]` link57 lists on hub pages like `cheat sheet.md` are manual holdovers from58 Logseq's namespace view — they will not stay in sync automatically, so59 add a link there by hand when adding a new file under that category.60- Wikilinks: `[[category/page]]`, same syntax Logseq used. Foam resolves by61 path or unique basename and autocompletes on typing `[[`. A link to a page62 that doesn't exist yet renders as an unresolved placeholder — that's fine;63 only create the target file once there's real content for it.64- Every category directory needs a top-level `<category>.md` hub linking its65 children (`[[category/x]]` list, same shape as `cheat sheet.md`), even one66 with no body content of its own otherwise — without it, every file in that67 directory is an orphan in Foam's graph (no auto namespace edges, see68 above). Add the new page's link to the hub when creating one.69- `.markdownlint.json` at the vault root turns off rules that fight this70 vault's actual conventions (long real command lines, `<placeholder>`71 genericization, Logseq's inline `### label` bullets read as headings) —72 don't silence a *new* markdownlint warning by wrapping/reformatting real73 content; check whether it's one of these known-intentional patterns first74 and extend the config instead.75- **Never write company-, client-, or employer-identifying specifics into76 this vault**: no real customer/client names, no internal hostnames/domains,77 no credentials/API keys/tokens, no employee names beyond the user78 themself. Generalize hostnames (`db01.example.internal`), redact secrets79 (`<redacted>`), keep content technique-focused and reusable. This was a80 deliberate cleanup the user already did once (see `journal/2026-09-17.md`)81 — don't reintroduce that kind of content on their behalf.82- Prefer fenced code blocks for anything code/command/config-shaped, matching83 the style already used throughout the vault.84- No zero-width-space escaping needed for `[[ ... ]]` shell test syntax85 inside code blocks — that was a workaround for Logseq's CLI eagerly86 parsing every `[[...]]` as a wikilink even inside fenced code. Foam doesn't87 do that; write shell code exactly as it should run.8889## Page style9091The user writes these pages as personal shorthand/memory-jogs, not92documentation for a general reader. Match that register — do not write like93a tutorial or a wiki. Compiled from the pages the user actually authored (not94pages this assistant wrote):9596- Label = 2-6 word noun/imperative fragment, lowercase, no terminal97 punctuation. Not a sentence. `"resize disk"`, `"create con"`, `"mod con"`98 — not `"How to resize a disk"`.99- The code block *is* the content. A label followed immediately by a fenced100 command is the default shape of a bullet; most bullets need nothing else.101- No motivation, background, or "why this matters" prose. State the action102 or fact, not the reasoning behind wanting it, unless a single short clause103 is truly required to disambiguate the label.104- Never explain what a tool/flag/concept generically is or why it's useful —105 assume the reader already knows or will look it up.106- Reader = senior linux admin. Record only what they can't reproduce from107 memory: the non-obvious flag, the platform quirk, the order, the decision.108 A generic command (`rsync -av`, `dmesg | grep`, `ls`, a `-c` config check,109 a pipe glued from standard tools) is never content, on any page.110- Category pages (`android/`, `kubernetes/`, …) hold only what is specific111 to that platform/project. Generic tool knowledge never lives there: if it112 is worth keeping at all, it goes to `cheat sheet/<tool>` and the category113 page links it.114- Cheat sheet bar: a command earns a line by its flags or its sequence, not115 by existing.116- Install/setup recipes are command-only, that's fine. But when a bullet117 records a **choice between approaches** (nfs mount vs ssh for bulk moves,118 auth at the proxy vs in the app), the content is the one-clause why, not119 a generic command demonstrating the chosen side.120- One label = one subject. A tangent ("getting data onto the ssd" under121 "stale handles") gets its own bullet or gets cut.122- Caveats, gotchas, sequencing ("on every node:", "then on master:"), and123 "won't work like this:" warnings go **inside the code block as124 comments/lines**, not as separate explanatory bullets or prose.125- Don't restate in prose what the code already shows, and don't pad the code126 with comments explaining what a command obviously does.127- No headers/sections/"Overview" framing unless the source material128 genuinely has distinct phases the user themself would separate — default129 is a flat bullet list.130- Nesting mirrors real execution order or real variants ("quick and dirty"131 vs. "somewhat fancy" as sibling sub-bullets), never conceptual/topical132 grouping added for tidiness.133- Leave typos, shorthand, mixed German/English, and raw pasted terminal134 history (prompts, timestamps) as-is when reusing the user's own words —135 don't proofread or formalize their voice. Do not introduce new136 spelling/grammar looseness on this assistant's own writing to fake the137 voice, either.138- Incomplete stubs are fine and expected: a bare label with no code, `"tbd"`,139 or a dangling `-` placeholder. Don't feel pressure to complete or pad them.140- Omit anything not load-bearing for reproducing the action: no "Goal:"141 framing, no scope statements, no citations/sources, no incidental142 command that only mattered for one specific occasion, unless it's actually143 core to the technique being recorded.144- When genericizing a real command (per the no-company-specifics rule145 above), swap the real value for a placeholder and move on — don't add a146 sentence about why it was swapped.147148## End of every session: rundown, not just the journal149150After any session with real work, do a pass over what was learned and split151it into the right places, then tell the user in a short list what was152written where:153154- `journal/<today>.md`: what happened, outcomes, todos, lessons — the155 narrative of the day.156- Own page per reusable technique: anything that would help on a different157 project (a build recipe, an API quirk, a debugging path, a platform158 limitation) goes on its own page in the right category directory, linked159 from the category hub and from the project page that produced it. The160 project page then links there instead of repeating the recipe.161- Existing pages: if today corrected or extended something already noted,162 edit that page rather than adding a parallel one.163164Then commit and push (see vault setup). Print the rundown as a bullet list:165page path, one clause what went there.166167## Typical asks and how to serve them168169- "Log/note that I did X" / "add today's progress" / "add a TODO" → open or170 create `journal/<today, YYYY-MM-DD>.md` (frontmatter `date: <today>` if171 the file is new) and append a top-level bullet.172- "Update my `<topic>` cheat sheet" / "add this to my notes" → Grep the173 vault for the topic to find the right file (check the likely category174 directory first, e.g. `cheat sheet/`); Edit it to append/update a bullet,175 or Write a new file with `title:` frontmatter if it doesn't exist yet.176- Prefer nested markdown bullets (sibling/child list items, each idea its177 own bullet) over flattening structured content — multiple steps, code178 blocks, variants — into one paragraph or one giant block. This mirrors179 the block-per-idea shape Logseq enforced; nothing enforces it here, so180 keep doing it deliberately.181- Link related pages with `[[category/page]]` rather than restating content182 that already lives on another page.183184## Stop hook: session-log prompt185186This workstation has a Claude Code **Stop hook** wired in187`~/.claude/settings.json` that fires when a session ends. It does *not*188write to the vault itself — an LLM can't reliably tell trivial edits from189things worth journaling, so it defers to the user.190191- Script: `hooks/foam-stop-log.sh` in this repo, installed at192 `~/.claude/hooks/foam-stop-log.sh`.193- Behavior: once per session, if any tool was used, it blocks the Stop194 (`{"decision":"block","reason":...}`) so the assistant asks the user (via195 `AskUserQuestion`) whether to log a note to today's journal file, and what196 to write. Declining or "skip" ends it there — nothing is written without197 explicit go-ahead.198- Dedup: touches a marker file at199 `~/.cache/claude-code-foam-stop/<session_id>` so it only blocks once per200 session (also checks the hook's own `stop_hook_active` flag to avoid201 looping).202203Install on a new machine:204205```sh206mkdir -p ~/.claude/hooks207cp hooks/foam-stop-log.sh ~/.claude/hooks/foam-stop-log.sh208chmod +x ~/.claude/hooks/foam-stop-log.sh209```210211Then merge this into `~/.claude/settings.json` (merge into existing212`hooks.Stop`, don't overwrite):213214```json215{216 "hooks": {217 "Stop": [218 {219 "hooks": [220 { "type": "command", "command": "~/.claude/hooks/foam-stop-log.sh", "timeout": 15 }221 ]222 }223 ]224 }225}226```227228Needs `jq` on PATH. Requires opening `/hooks` once (or restarting Claude229Code) after first install so the settings watcher picks up the new hooks230directory.231232## Tips233234- The vault is a plain git repo — commit like any other repo when the user235 asks; there's no separate sync/export step the way Logseq needed one.236- If a wikilink's target category doesn't have a directory yet, create it by237 just writing the first file into it (`mkdir -p` semantics via Write).