Diagnostics
Diagnostics are how ThoughtML tells you something’s wrong with a document’s form. They come in two severities:
- Error — the document is malformed. The CLI exits non-zero.
- Warning — suspicious but parseable. The CLI still exits zero, unless you
pass
--strict(which makes warnings fail too).
Diagnostics go to stderr; the JSON model goes to stdout. They are distinct from the mirror’s conflict report, which judges a document’s coherence, not its form, and never fails parsing.
The strict-clean invariant. Every bundled example parses with zero errors and zero warnings under default options. A test (
bundled_examples_are_strict _clean) enforces it, so the corpus can’t silently rot.
Codes and machine-readable output
thoughtml check --json emits each diagnostic with a stable code, its severity,
line, message, and — where one can be computed — a suggested help fix:
{ "code": "TML102", "severity": "warning", "line": 5,
"message": "unknown relation `supprts`",
"help": "did you mean `supports`? (relations are a closed set)" }
Codes are grouped so an agent or editor can route on the family:
| Range | Family | Examples |
|---|---|---|
TML1xx | vocabulary | TML101 unknown kind, TML102 unknown relation, TML103 unknown posture, TML104 unknown field |
TML2xx | references | TML201 unresolved reference, TML202 illegal link endpoint |
TML3xx | graph coherence | TML301 orphan, TML302 contradictory stances, TML303 cycle, TML304 revision before its target, TML305 decision-graph |
TML4xx | numbers | TML401 missing basis, TML402 out-of-range clamp |
TML5xx | lints (opt-in) | TML501 supports used as a list |
For the “unknown <thing>” family the help is a nearest-spelling suggestion
from the relevant closed vocabulary — it catches typos like supprts → supports.
Codes are part of the tool’s contract and are not renumbered once assigned.
Errors
| Message (abbreviated) | Cause |
|---|---|
tab indentation is invalid; v0 requires spaces | a tab in leading whitespace |
indented block line before any record header | a block line with no open record |
<kw> header expects exactly one identifier | malformed focus/scope/question/profile header |
link header expects [alias:] from relation to | wrong link arity |
stance header expects [alias:] agent posture target | wrong stance arity |
import header expects import <name> as <namespace> | malformed import |
unknown record kind or header … | first token isn’t a keyword, second isn’t a posture |
<posture> action expects a single target identifier | wrong arity for a simple action |
suspects expects from relation to [as alias] | malformed suspects |
infers expects target from id-list / requires at least one source | malformed infers |
invalid identifier/symbol … (expected lowercase kebab-case) | bad token |
confidence must be a number, range, or ? | non-numeric confidence |
confidence range must be ordered low..high | reversed range |
weight/probability must be a number in 0..1 | non-numeric weight/probability |
link.from/to … targets a <kind>; links may only connect foci, questions, or links | a link pointing at a stance or scope |
Warnings
Indentation & structure
block lines should be indented by two or more spacesonly a scope may contain nested objects; desugaring them at the top level
Ids & kinds
duplicate id/id is reused across recordsfocus … was declared as kind X but redeclared as Y; keeping Xduplicate <field> field; using the last(kind, confidence, weight, probability, expects, status, formula)unknown focus kind/unknown posture/unknown relation/unknown field(unless a profile declares it)
Values
quantity should be <number> <unit>weight/probability should be in 0..1; clampingprobability on a <rel> link is ignored/a weight on a leads-to link is ignoredkind requires a value/until requires a referencequestion should include body text or an expects field/about expects one or more ids
Reference resolution
… is an unresolved reference(link endpoints, stance targets,about,because/answers/blocked-by/undercut-by, formula refs)
Semantic lints (need the whole graph)
agent … takes contradictory stances on …— incompatible posture pairs:accepts/rejects,accepts/doubts,chooses/rejects,holds/rejects.cyclic dependency: a → b → a— a cycle amongcauses/depends-onedges.focus … is not connected to anything— an orphan: nothing links it, no stance targets it, no field references it.
Opinionated lints (only with thoughtml check --lint)
focus … gathers N supports links and no counter-evidence(TML501) — a claim used as an enumeration. Evidence relations inflatederived_confidence; if these are list items, usepart-ofinstead. Off by default so strict-clean documents are unaffected.
Decision graph
leads-to edge … points outcome … at itselfoption … has no leads-to outcomes, but its sibling options do— it’d be silently missing from the EV ranking.
Temporal
… revises … but is asserted earliervalid-during … ends before it starts
Compute (only with the relevant opt-in flags)
- formula: parse error, unresolved/quantity-less reference, dimension mismatch, dependency cycle.
- decision EV: missing probability/payoff, mixed dimensions, probability mass > 1.
Provenance (only with --strict-provenance)
… declares no basis (add measured/estimated/assumed)
Imports
unknown import …/import cycle through …; skipped