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, TML502 circular justification |
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.circular justification: a → b → a(TML502) — asupportsloop. The language refuses circular causation by default (TML303); this is circular justification, and it matters for the same reason plus one more: claims inside the loop derive confidence above 0.5 from no evidence outside it. Advisory rather than an error, because mutual support is not always a mistake — two readings of one body of evidence can genuinely reinforce each other. Either give one of them outside evidence, or merge them if they are one belief said twice.
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