Checks and findings
Every check is deterministic, offline and read-only. Nothing here writes a file, calls a model, or touches the network.
The checks
They run in this order, and each prints one line in human output:
| Check | Category | Verifies |
|---|---|---|
manifest | manifest | The manifest exists, parses, and declares a supported schema. |
context structure | structure | .context/ exists, belongs to Syngraphe, and has the expected files. |
internal references | references | Local paths referenced from context documents resolve. |
AGENTS.md | agents | The AGENTS.md managed block is present, unique and unmodified. |
Claude integration | agents | CLAUDE.md points at AGENTS.md, when Claude is used here. |
context state | state | state/current.md says something, and has not silently fallen behind. |
Checks do not cascade. When .context/ is absent, the structure check reports it once and the others stay quiet rather than adding six symptoms of the same cause.
Integrations that write nothing — Cursor, Codex — contribute no check: there is nothing about them that can be wrong.
Finding codes
Codes are stable. They are never renumbered, never reused for a different meaning, and never named after internal implementation details.
Structure — CTX
| Code | Severity | Message | Meaning |
|---|---|---|---|
CTX001 | error | Repository context is not initialized. | No .context/. Run syngraphe init. |
CTX002 | error | Expected context file is missing. | One of the seven schema files is absent. init restores it. |
CTX003 | error | .context/ is not a Syngraphe repository context. | The directory exists but belongs to something else. |
Manifest — MANIFEST
| Code | Severity | Meaning |
|---|---|---|
MANIFEST001 | error | .context/manifest.json is missing while the rest of the context exists. |
MANIFEST002 | error | The manifest is not valid JSON, is not an object, or has no numeric schemaVersion. The parser's message is included as details. |
MANIFEST003 | error | The manifest declares a schema version this build does not support. Forces exit code 3. |
MANIFEST004 | warning | The manifest declares a layout this build does not know. |
MANIFEST005 | warning | The manifest declares no protocol, so the directory can only be recognised by its shape. |
AGENTS.md — AGENT
| Code | Severity | Meaning |
|---|---|---|
AGENT001 | error | AGENTS.md has no Syngraphe block. Run syngraphe init. |
AGENT002 | error | The block was modified manually. Syngraphe will not overwrite it. |
AGENT003 | error | The file contains more than one Syngraphe block. |
AGENT004 | error | The markers are unbalanced. The finding carries the line number. |
AGENT005 | error | AGENTS.md cannot be managed safely — it is a directory, a symlink, not valid UTF-8, or declares an unknown block version. |
AGENT006 | warning | The block still holds a body an earlier Syngraphe version wrote. Run syngraphe init to update it. |
These are errors rather than warnings because .context/ without a bootstrap is context nothing will read. AGENT006 is the exception: the file says what an older Syngraphe meant it to say, and init updates it without losing anything, so it is work the repository owes rather than a failure. It is a warning, which --strict still fails on.
Claude — CLAUDE
| Code | Severity | Meaning |
|---|---|---|
CLAUDE001 | warning | Claude is used in this repository (a CLAUDE.md or .claude/ exists) but nothing imports AGENTS.md. |
CLAUDE002 | error | The CLAUDE.md managed block was modified manually. |
CLAUDE003 | error | CLAUDE.md contains duplicate Syngraphe blocks. |
CLAUDE004 | error | The markers in CLAUDE.md are unbalanced. |
CLAUDE005 | warning | A setup Syngraphe deliberately leaves alone, such as a symlink pointing elsewhere. |
CLAUDE006 | warning | The CLAUDE.md block still holds a body an earlier Syngraphe version wrote. Reserved: this body has had no superseded version yet. |
A missing integration is a warning, not an error: an agent nobody uses here needs no file. Nothing at all is reported for a repository that shows no sign of using Claude.
References — LINK
| Code | Severity | Meaning |
|---|---|---|
LINK001 | error | A context document references a local path that does not exist. The finding carries the file and the line. |
Markdown links resolve relative to the document and are checked whatever they point at. Destinations may contain balanced parentheses, escaped parentheses or angle brackets, with an optional link title. Every path component must match the entry's letter case, even on a case-insensitive filesystem, so a reference accepted on macOS or Windows does not break on Linux because of its spelling. A reference through a symlink — the file itself or a directory on the way — resolves only when the link's target exists inside the Git repository; a broken link, or one leading outside, is reported. Inline code is prose, so only .md references are checked — `truth/architecture.md` — and they may resolve relative to the document, from the repository root, or from .context/. A directory named in inline code is not treated as a reference. Fenced code blocks, anchors and external URLs are ignored. See context schema for the reasoning.
State — STATE
| Code | Severity | Meaning |
|---|---|---|
STATE001 | warning | state/current.md has not changed in N days while the repository has. |
STATE002 | warning | state/current.md contains only headings. |
How freshness is judged
STATE001 is raised only when both conditions hold:
- the last commit touching
.context/state/current.mdis at least 45 days old, and - the repository has commits newer than that one.
If Git cannot answer — no history, an untracked file, a shallow clone, no git on the PATH — the check stays silent. An unanswerable question produces no finding.
The message states the age and asks for a review:
WARN STATE001 .context/state/current.md
.context/state/current.md has not changed in 47 days.
This may be intentional. Review whether it still reflects the current repository state.What it deliberately does not say is ERROR: context is stale. Age is a signal, not a verdict: a document can be old and perfectly accurate, and a tool that cries wolf about it teaches people to ignore the one time it matters.
Severity and exit codes
| Severity | Default run | --strict |
|---|---|---|
error | fails (exit 1) | fails (exit 1) |
warning | reported only (exit 0) | fails (exit 1) |
info | reported only | reported only |
MANIFEST003 is the one special case: it forces exit code 3 regardless, because an unsupported schema makes every other answer unreliable.
Adding a check
The registry is a list, and each check is a small object:
interface Check {
id: string;
label: string; // the line printed in human output
category: CheckCategory;
run(context: CheckContext): Promise<Finding[]>;
}CheckContext is a snapshot gathered once per run — the repository, the context inspection, the AGENTS.md state, every agent's state, and the reference instant used by age-based checks. Checks read from it; they never print, never write, and never decide the exit code.
Adding one means: a module in src/checks/, a line in the registry, a new stable code, tests, and a row in this table.