Skip to content

JSON output ​

Each public JSON format has its own version. Plan JSON, check JSON, statistics JSON, and the monorepo envelope do not share a version merely because their current version numbers are all 1.

Dry-run plan JSON ​

Every current mutating command can expose the ordinary dry-run plan as stable JSON:

bash
syngraphe init --dry-run --json
syngraphe truth new domain-model --dry-run --json
syngraphe decision new use-postgres --dry-run --json
syngraphe state new migration --dry-run --json
syngraphe history new migration-outcome --dry-run --json
syngraphe state archive completed --dry-run --json

--json requires an explicit --dry-run; it never silently changes a real run into a dry run. Without --dry-run, the command exits 2, writes nothing, emits no partial JSON, and explains the usage error on standard error. List commands do not have JSON output.

json
{
  "version": 1,
  "ok": true,
  "scope": ".",
  "operations": [
    {
      "type": "create",
      "path": ".context/truth/domain-model.md"
    }
  ],
  "unchanged": [],
  "conflicts": []
}

Standard output contains only this payload and the final newline. The same planner builds human and JSON dry runs; only rendering differs, and neither path calls applyPlan.

Top level ​

FieldTypeMeaning
versionintegerPlan JSON shape version, currently 1 (PLAN_JSON_VERSION).
okbooleantrue exactly when the plan has no conflicts and the dry run can succeed.
scopestringSelected directory relative to Git root; . at repository root.
operationsarrayPlanned creates and patches, preserving internal plan order.
unchangedarrayPaths deliberately left alone and their reasons, preserving plan order.
conflictsarrayConditions that block apply, preserving plan order.

Operation paths are relative to the selected Syngraphe scope. Thus a nested scope reports "scope": "packages/api" while an operation inside it remains "path": ".context/truth/domain-model.md".

Operations, unchanged entries, and conflicts ​

A create contains type: "create" and path; it may contain a human summary. A patch contains type: "patch", path, and a human summary. Consumers should match type, not summary text.

json
{
  "type": "patch",
  "path": "AGENTS.md",
  "summary": "Syngraphe repository-context bootstrap"
}

An unchanged entry contains path and reason. A conflict always contains message and may contain path and details. Optional fields are omitted rather than emitted as null; a repository-level conflict can therefore be only { "message": "..." }.

Plan JSON is a deliberate projection, not serialized internal state. It never includes contents, before, after, complete file bodies, or user-authored repository text. The internal plan retains those values so applying that same plan can enforce stale-content and no-overwrite checks.

Stability and exit behavior ​

  • PLAN_JSON_VERSION is independent from check and statistics versions. Version 1 is stable.
  • Adding optional fields is additive. Removing or retyping an existing field requires a plan JSON version bump.
  • Operation type values are the machine contract. Summaries, conflict messages, details, and reasons remain human-readable and may be reworded.
  • A built plan with conflicts is still emitted with ok: false and exits 1; no human plan renderer is mixed into standard output.
  • Usage or operational failures that prevent a plan from being built preserve their existing standard-error and exit-code behavior instead of inventing a JSON payload.

Check JSON ​

bash
syngraphe check --json
json
{
  "version": 1,
  "ok": false,
  "findings": [
    {
      "code": "LINK001",
      "severity": "error",
      "category": "references",
      "file": ".context/index.md",
      "line": 12,
      "message": "Referenced path does not exist: truth/gone.md"
    },
    {
      "code": "STATE002",
      "severity": "warning",
      "category": "state",
      "file": ".context/state/current.md",
      "message": "Current state contains only headings.",
      "details": "Describe the current focus so the file is useful to humans and agents."
    }
  ]
}

The payload is printed to standard output and nothing else is, so it can be piped directly into jq without filtering.

Top level ​

FieldTypeMeaning
versionnumberThe shape of this payload. Currently 1.
okbooleanWhether the run succeeded — true exactly when the exit code is 0.
findingsarrayEvery finding, in the order the checks produced them.

ok respects --strict: the same repository with the same findings reports ok: true under a default run and ok: false under --strict, because ok describes the outcome of the run, not an abstract verdict on the repository. Read severity if you want the verdict independent of flags.

A finding ​

FieldTypeAlways presentMeaning
codestringyesThe stable identifier, e.g. LINK001. See checks.
severitystringyeserror, warning or info.
categorystringyesmanifest, structure, references, agents or state.
filestringnoScope-relative path (repository-relative by default).
linenumberno1-based line, when the finding points at one.
messagestringyesOne sentence stating the problem.
detailsstringnoAdditional context or the suggested fix.

Optional fields are omitted, never null. Key order is stable in the emitted JSON, though no consumer should depend on it.

Stability ​

  • version changes only when the payload shape changes. New optional fields are additive and do not bump it; a removed or re-typed field would.
  • Codes are never renumbered or reused for a different meaning. A code may stop being emitted; it will never come back meaning something else.
  • message and details are human-readable text and may be reworded. Match on code, never on message text.

Recipes ​

bash
# Errors only.
syngraphe check --json | jq '[.findings[] | select(.severity == "error")]'

# Fail on a specific code, ignore the rest.
syngraphe check --json | jq -e '[.findings[] | select(.code == "AGENT002")] | length == 0'

# Group by category.
syngraphe check --json | jq 'group_by(.category) | map({(.[0].category): length}) | add'

# GitHub Actions annotations.
syngraphe check --json | jq -r '
  .findings[]
  | "::\(if .severity == "error" then "error" else "warning" end) file=\(.file // ""),line=\(.line // 1)::\(.code) \(.message)"
'

Because the codes are stable, excluding a check that is not yet actionable in your repository is a filter on one code rather than a reason to stop running the command.

Exit code and payload together ​

The payload never replaces the exit code; it explains it.

ExitPayload
0ok: true. Findings may still be present as warnings.
1ok: false, with at least one error — or a warning under --strict.
3ok: false, including a MANIFEST003 finding.
2No payload: the command never ran. The message goes to stderr.

Programmatic alternative ​

If you are already in Node, skip the parsing:

ts
import { Repository, createCheckContext, runChecks } from "syngraphe";

const repository = await Repository.open(process.cwd());
const run = await runChecks(await createCheckContext(repository));

console.log(run.errors, run.warnings, run.findings);

runChecks returns the same findings, plus the per-check results the human renderer uses.

Statistics JSON ​

syngraphe stats --json has an independent version 1 payload:

FieldMeaning
versionStatistics payload version, currently 1.
scopeSelected directory relative to Git root, or ..
tokenEstimateceil(UTF-8 bytes / 4) per Markdown file.
budgetPositive integer total Markdown token budget.
overBudgetWhether total estimated tokens strictly exceed the budget; advisory.
total, active, historyObjects with files, markdownFiles, bytes, words, estimatedTokens. Active excludes history/; total is their sum.
documentsEvery regular .md file: path, bytes, words, estimatedTokens, sorted by decreasing bytes and then path.
largeDocumentsPaths of Markdown documents above 2,000 estimated tokens.
duplicatesArrays of paths with identical nonempty Markdown contents.
skippedSymlinks and other non-regular paths skipped during traversal.

All paths are relative to the selected scope. Arrays other than documents follow deterministic path traversal order. Counts include files regardless of whether an agent would read them; tokens are estimates, not billing or model-specific measurements. Single-scope argument/context failures use stderr and a nonzero exit code instead of emitting a statistics payload.

Monorepo JSON envelope ​

check --all --json and stats --all --json wrap the corresponding per-scope reports:

FieldMeaning
versionEnvelope version, currently 1.
okTrue exactly when the aggregate command exits 0.
scopesOrdered array of { scope, result } objects.

scope is Git-root-relative (. for the root). result is the ordinary check or stats payload. A scope-level operational failure instead produces { error, exitCode } in result, and other scopes are still processed. Failure to select or discover scopes prevents the report altogether. Unsupported schemas take precedence over integrity failures in the aggregate exit code.

Finding file paths inside each result are relative to that scope. Prefix them with scope (unless it is .) to locate them from the Git root. Single-scope check --json, including with --scope, retains exactly the existing version, ok, findings contract.

Syngraphe v0.4.1 · context schema v1 · GitHub · Donate