| name | verify |
| description | Check that Founder OS is healthy. Say "verify the OS", "health check", "is the OS working", "check my setup" (or run /founder-os:verify). Returns a structured report across 9 substrate checks, each marked PASS / WARN / FAIL with a one-line reason. Read-only. Never auto-fixes.
|
| why | Checks that the OS substrate is actually wired up correctly rather than just declaring setup done - hooks, scripts, and counts that disagree silently break skills. |
| enhance | Run after every setup or update to catch wiring issues early - a FAIL on scripts or hooks means several skills will behave incorrectly on every subsequent run. |
| allowed-tools | ["Read","Glob","Grep","Bash"] |
| mcp_requirements | [] |
Verify
Runs on: local-exec - runs a stdlib syntax check (ast.parse, writes nothing to disk) on the shipped scripts as part of the check; on a cloud or read-only surface I report the checks I can read and mark the exec ones as not run.
Read-only health check across 9 substrate checks. Returns one screen. Never auto-fixes.
Resolving the engine root
Several checks read files that ship with the engine, and the engine is not always in the folder you are standing in. Work ENGINE out ONCE, before Check 1, and use it everywhere below that names it:
- If the working directory contains
skills/, the engine is here. ENGINE is the working directory. This is the git-clone, curl, and ZIP case.
- Otherwise this is a data-folder install: the founder's folder holds only their data, and the engine is installed as a plugin. Take the first that resolves:
${CLAUDE_PLUGIN_ROOT} - Claude Code sets it for a plugin's own components. Read it with echo "$CLAUDE_PLUGIN_ROOT" if it has not already been substituted into this file.
- The newest match for
~/.claude/plugins/**/.claude-plugin/plugin.json whose name is founder-os. ENGINE is the folder holding that .claude-plugin/. Search rather than assume a fixed path: plugin managers move their layout between versions, and a hardcoded path reports a healthy install as broken.
- If neither resolves,
ENGINE is unknown. Check 1 reports that as a FAIL. Do not guess a path and do not silently skip the checks that need one.
A data folder is a correct install shape, not a defect. A data folder with no reachable engine is a broken OS, and Check 1 is what tells the two apart.
Run the deterministic verifier first
python scripts/verify.py --json
Run it once, before Check 1, and keep the JSON. Checks 2 and 3 are translations of its output, not your own enumeration. The division of labour is deliberate and is the fix for a real blind spot: a model that derives its expectations from whatever is on disk cannot notice that something is missing from disk, so "enumerate dynamically" made deletion undetectable. The script carries the contract (derived from templates/scripts/, the set setup copies - never a hand-maintained list) and performs the comparison; your job is to render its findings in plain language, not to re-derive them.
If scripts/verify.py itself is missing, that IS a finding: report Check 3 as [FAIL] Scripts present (the install verifier scripts/verify.py is missing - run an update to restore the OS's own files) and fall back to reading what you can for the other checks. Never silently substitute your own glob-and-count for the script's comparison.
The nine checks
Run all nine checks. Each produces one of: [PASS], [WARN], or [FAIL].
Check 1 - Plugin surface integrity
Resolve ENGINE as above, then:
ENGINE unknown -> [FAIL] Plugin surface (this folder holds your data, but the engine that runs it could not be found - restart Claude Code first; if it persists, install the plugin again) and skip the counting below.
ENGINE resolved to a plugin (case 2) -> read <ENGINE>/.claude-plugin/plugin.json.
- Parses with a
version -> [PASS] Plugin surface (engine v<version> runs from the plugin; no local engine copy to count) and skip the counting below.
- Unparseable ->
[FAIL] Plugin surface (the engine is installed but its manifest could not be read - install the plugin again) and skip the counting below.
Otherwise (ENGINE is the working directory - git-clone, curl, or ZIP), verify that the skill and command counts are internally consistent:
- Count
skills/<name>/SKILL.md files on disk.
- Count
.claude/commands/*.md files on disk.
- Read
skills/index.md and extract the declared skill count from its header line.
- Read
README.md and extract the skill and command counts from the "Skills" and
"Slash commands" sections.
- Read
.claude-plugin/plugin.json and .claude-plugin/marketplace.json for their
version fields and any count claims in the description strings.
Outcome:
- All counts agree ->
[PASS] Plugin surface (<N> skills / <M> commands, counts agree)
- Any count disagrees ->
[WARN] Plugin surface (disk has <N> skills / <M> commands, README claims <X>/<Y>)
Check 2 - Hooks installed (dispatcher wiring)
Translate the verifier's hooks-wired and hook-dispatcher checks. The script
already read .claude/settings.json, confirmed scripts/hooks/dispatch.py
exists, and checked that all six events (SessionStart, PreToolUse,
UserPromptSubmit, PreCompact, Stop, PostToolUse) each call the
dispatcher with the matching event name.
Outcome, from the JSON:
hooks-wired pass and hook-dispatcher pass ->
[PASS] Hooks installed (6/6 events wired to dispatch.py)
hook-dispatcher fail ->
[FAIL] Hooks installed (settings wires the dispatcher but scripts/hooks/dispatch.py is missing)
hooks-wired warn with some events missing ->
[WARN] Hooks installed (<N>/6 events wired to dispatch.py - missing: <missing_events>)
hooks-wired warn because settings.json is absent or unreadable ->
[WARN] Hooks installed (no hooks registered) - hooks are opt-in by design,
so this is never a FAIL.
Check 3 - Scripts present and compile
Translate the verifier's scripts-complete and scripts-parse checks. The
script derived the required set from the engine's templates/scripts/ (what
setup copies into every install) and compared it against scripts/ on disk,
then parsed every present script including scripts/hooks/dispatch.py. That
comparison is the whole point: a missing file cannot be noticed by globbing
what exists, only by diffing against a contract that exists independently.
One thing stays yours: if python / python3 / py -3 all fail to run at
all, report [FAIL] Scripts present (Python not found - it is a hard prerequisite; install python.org 3.11+) - the verifier cannot report its own
interpreter being absent.
Outcome, from the JSON:
- Both pass ->
[PASS] Scripts present (<N>/<N> present and parse cleanly, incl. hooks/dispatch.py)
scripts-complete fail -> [FAIL] Scripts present (missing from scripts/: <missing list> - run an update to restore them)
scripts-parse fail -> [FAIL] Scripts present (<file>: <error> - the file is damaged; run an update to restore it)
scripts-complete warn (no engine reachable to derive the contract) ->
[WARN] Scripts present (present scripts parse, but the shipped list could not be found to prove completeness)
Check 4 - MCP availability
Read CLAUDE.md and AGENTS.md for referenced MCP server names (look for server
names listed under "Tools Available" or similar sections).
For each named MCP, check whether it appears under an mcpServers key in any of
the places Claude Code actually reads them: .mcp.json at the OS root, .mcp.json
inside any company or project folder the wizard created, or the user's
.claude/settings.json. Project MCPs live in .mcp.json files - checking only
settings.json reports 0 on installs whose MCPs are wired per-project.
Outcome:
- Configured MCPs found ->
[PASS] MCP availability (<N> configured: <names>)
- No MCPs configured ->
[WARN] MCP availability (0 configured - optional but adds Gmail, Calendar, Notion integrations)
- MCPs referenced but none configured ->
[WARN] MCP availability (referenced in docs but none configured)
Do NOT fail if MCPs are unconfigured. MCP setup is optional.
Check 5 - Free-tier floor preserved
Grep the full shipped script set from Check 3 - every <ENGINE>/scripts/*.py
and <ENGINE>/scripts/hooks/*.py, dispatch.py included - for environment
variable references that imply an API key:
ANTHROPIC_API_KEY
OPENAI_API_KEY
GEMINI_API_KEY
Scope and judgment both matter. Grep the shipped scripts, NOT the whole tree:
skills, docs, and reference files legitimately MENTION key names when documenting
optional opt-in upgrades. And within the scripts, a mention is not a requirement.
scripts/connect.py is the connector catalog - it names keys (ELEVENLABS_API_KEY,
GEMINI_API_KEY) as metadata describing optional paid/free upgrades, but reads none
of them and runs fine with zero keys. That is a documented mention, not a
requirement, and does not fail the floor. The floor this check guards is precise:
every shipped script must RUN without any key. Flag a script only when it reads a
key as mandatory config to do its job.
Outcome:
- No shipped script requires a key to run ->
[PASS] Free-tier floor preserved (no shipped script requires an API key)
- A shipped script requires a key to run ->
[FAIL] Free-tier floor (a shipped script requires an API key - breaks free-tier users)
Check 6 - Wiki integrity
Run the lint check logic inline (do not invoke the lint skill recursively):
- Load
wiki_layer_files from scripts/_common.py and use that exact result as the
file scope. This is the canonical wiki layer: core/, context/, cadence/,
brain/, network/, companies/, roles/, and rules/, with the helper's
exclusions applied.
- Do not scan
skills/, docs/, templates/, raw/, or other product surfaces.
- For each
[[target]] found, verify that a file matching the target exists somewhere
in the tree.
- Ignore
[[...]] that appears inside backticks or a fenced code block - those are
syntax examples in shipped prose (rules/entry-conventions.md, brain/knowledge/README.md),
not links. Counting them makes a pristine install report WARN forever.
Approximate issue count:
- 0 unresolvable links ->
[PASS] Wiki integrity (0 issues)
- 1-3 ->
[WARN] Wiki integrity (<N> unresolvable links)
- 4+ ->
[FAIL] Wiki integrity (<N> unresolvable links - run /founder-os:lint for detail)
Check 7 - Cadence staleness
Compare today's date against the date headers in the cadence files:
cadence/daily-anchors.md - looks for ## Today: YYYY-MM-DD:
- Same day or 1 day stale ->
[PASS]
- 2-3 days stale ->
[WARN]
- 4+ days stale or missing ->
[FAIL]
cadence/weekly-commitments.md - looks for ## Week of YYYY-MM-DD:
- Within 7 days ->
[PASS]
- 7-10 days ->
[WARN]
- 10+ days or missing ->
[FAIL]
Report the worse of the two:
- Both PASS ->
[PASS] Cadence staleness (daily and weekly current)
- One WARN ->
[WARN] Cadence staleness (daily-anchors <N> days stale) or similar
- Either FAIL ->
[FAIL] Cadence staleness (<detail> - refresh before planning)
Check 8 - Auto-memory presence
Check whether MEMORY.md exists in the expected auto-memory location for this project.
The auto-memory path is ~/.claude/projects/<slug>/memory/MEMORY.md where <slug>
is the repo path with separators replaced by hyphens.
- MEMORY.md exists -> count the entries (lines starting with
- [) and report
[PASS] Auto-memory presence (MEMORY.md, <N> entries)
- MEMORY.md missing ->
[WARN] Auto-memory presence (MEMORY.md not found - run setup wizard to create it)
Check 9 - Every skill is reachable from something
python scripts/skills_sync.py --reachability --json
A skill only ever runs if something can find it. A well-written skill that no slash command names, no registry row lists, no docs entry mentions, and no other skill points at is reachable from nothing, and the OS behaves as though it were never written. Nothing about that failure looks broken from the outside, which is why it needs its own check.
The script counts a skill as reachable if any one of these names it: a slash command, skills/index.md, CLAUDE.md, docs/skills.md, another skill's body, or skills/discoverable.yaml.
Outcome:
- Every skill reachable ->
[PASS] Skill reachability (<N>/<N> reachable, <M> natively discoverable)
- Any skill reachable from nothing ->
[FAIL] Skill reachability (<K> skills reachable from nothing: <names, capped at 5>)
- Script missing or no script access ->
[WARN] Skill reachability (not run - no script access)
A FAIL here is fixed by naming the skill somewhere: add its registry row, add a slash command, or promote it in skills/discoverable.yaml. Never by deleting the check.
Check the generated capability page in the same pass, since it reads the same skill set:
python scripts/skills_sync.py --capabilities --check
docs/what-this-can-do.md is generated from the skills on disk and is the answer to "what can this actually do". A stale page is worse than no page, because it teaches the founder the OS cannot do something it can.
- Page current -> nothing extra, the Check 9 line stands as it is.
- Page stale or missing -> append to the same line:
[WARN] Skill reachability (<N>/<N> reachable; docs/what-this-can-do.md is stale - rebuild with python scripts/skills_sync.py --capabilities)
And run the native-discovery drift check in the same pass, because it is the failure this check exists for in its sharpest form:
python scripts/skills_sync.py --check
The handful of skills in skills/discoverable.yaml are the ones that fire when you describe a SITUATION ("log this", "what's on my plate") rather than name a command. They only fire natively while their copy under .claude/skills/ matches the source. Any edit to a source skill silently breaks that - the skill still exists, still passes reachability, and quietly stops firing on the situations it was built for. Before v1.47 this check ran only once, inside setup, so the drift was invisible to every later health check.
- Exit 0 -> append
; native copies current to the Check 9 line.
- Any drift ->
[FAIL] Skill reachability (native-discovery drift: <K> skills - run python scripts/skills_sync.py --apply). The fix is the one command; never delete a skill from discoverable.yaml to silence the check.
WARN rather than FAIL: a stale page is a wrong description of a working OS, not a broken one.
Output format
Plain text. Maximum 30 lines including the header. Exactly this shape:
FounderOS v<version> - health check
<YYYY-MM-DD HH:MM>
[PASS] Plugin surface (<N> skills / <M> commands, counts agree)
[PASS] Hooks installed (6/6 events wired to dispatch.py)
[PASS] Scripts present (23/23 parse cleanly, incl. hooks/dispatch.py)
[WARN] MCP availability (0 configured - optional but adds Gmail, Calendar, Notion integrations)
[PASS] Free-tier floor preserved (no shipped script requires an API key)
[PASS] Wiki integrity (0 issues)
[FAIL] Cadence staleness (daily-anchors 4 days stale - refresh before planning)
[PASS] Auto-memory presence (MEMORY.md, 12 entries)
[PASS] Skill reachability (89/89 reachable, 5 natively discoverable)
7 PASS - 1 WARN - 1 FAIL - next: refresh cadence/daily-anchors.md
Rules for the output:
- No emoji.
- No color codes.
- Brackets + state word + parenthetical for every check line. Exactly this format.
- Summary footer: count each state, name the single highest-priority next action (first
FAIL, or first WARN if no FAILs, or "all green" if all PASS).
- Read the version from the
VERSION file in the repo root. On a data-folder install with no VERSION file, read the version from the plugin's .claude-plugin/plugin.json (the same manifest Check 1 reads); if neither exists, print the header without a version rather than failing.
- Do not exceed 30 lines total.
Skill reliability
When a user asks how the OS guarantees behavior (or runs /founder-os:verify and wants to know which skills are bulletproof vs which depend on the model following markdown), surface this table at the end of the report.
| Skill | Gate type |
|---|
| linkedin-post | Python-enforced (check-voice-ready) |
| email-drafter | Python-enforced (check-voice-ready) |
| client-update | Python-enforced (check-voice-ready) |
| proposal-writer | Python-enforced (check-voice-ready) |
| content-repurposer | Python-enforced (check-voice-ready) |
| weekly-review | Python-enforced (check-identity-ready) |
| decision-framework | Python-enforced (check-identity-ready) |
| meeting-prep | Python-enforced (check-identity-ready) |
| strategic-analysis | Python-enforced (check-identity-ready) |
| brain-pass | Python-enforced (check-log-has-history) |
| linkedin-post brain context | Python-enforced (check-log-has-history) |
| brain-log | Instruction-only |
| knowledge-capture | Instruction-only |
| queue | Instruction-only |
| founder-coaching | Instruction-only |
| unit-economics | Instruction-only |
| priority-triage | Instruction-only |
Python-enforced gates exit non-zero and stop the skill in code. Instruction-only skills rely on the model following the SKILL.md body. The distinction is what behavior the OS can guarantee versus what depends on the model. See docs/calibrating-your-os.md for how to test instruction-only skills yourself.
Rules
- This skill never auto-fixes anything. It only reports.
- Read-only. Never write to any file, never run any command that modifies state.
- Python is a hard prerequisite for the OS, not an optional extra. Missing Python
is a
[FAIL] on Check 3 (Scripts present), not a [WARN] - without it the
scripts every skill relies on cannot run at all.
- If a non-prerequisite check cannot run (an optional file is absent, MCPs are
unconfigured, settings.json is unreadable), report
[WARN] not [FAIL] for that
check, with a reason in the parenthetical.
- Distinguish
[WARN] (degraded but functional) from [FAIL] (broken or missing).
The distinction matters so founders can prioritize. A [FAIL] means something that
would produce an error or wrong output in normal use. A [WARN] means something
suboptimal that degrades gracefully.