Why
The review:* checklist trees and the preflight routers are executable infrastructure, not prose. tasks:review-changes triages checks by reading each one's Applies when … edge label back to the review parent; preflight routes symptom → finding via its outgoing edges. When one of those edges is malformed, the check or route is silently skipped. The node still exists, still looks maintained, and never fires again.
There is currently no way to detect that except by hand — which is how it went unnoticed. A manual audit of two memories this week found:
hadronmemory.com::hadron-portal — 7 of 24 checks had an edge label carrying no condition at all (bare applies-when, and one truncated Applies when with nothing after it). Every one was reachable only by opening the node, so a reviewer scanning the parent's Referenced-by list had nothing to match a diff against.
micromentor.org::mmdata — ~45 checks were fine, but 4 were broken in three distinct ways:
- 2 labelled
child-of instead of a trigger (posthog-backend-vs-app-event-routing, role-vs-group-ident-vocabulary)
- 1 whose label was a raw derived edge loc,
review:input-type-graphql-type:rel:review — with 4 more :rel:-shaped labels in the same branch, suggesting one bad batch
- 1 (
format-sources) triggered on "Applies when Dart sources change" while living in the TypeScript backend memory — it can never match a diff in the repo whose memory it's in
(Filed as micromentor-team/mmdata#1599.)
These are all mechanically detectable. That's the ask.
Proposed surface
A new top-level group alongside the existing task / node / edge / memory groups:
hadron coding review lint [-m <memory>] [--json] [--fix]
hadron coding preflight lint [-m <memory>] [--json]
coding as the parent because these lint the coding-workflow graph (review gates, preflight routing, coding-guidelines cross-links) rather than arbitrary nodes — leaving room for coding review ls, coding review add (the tasks:add-review-node procedure), coding preflight ls, etc.
coding review lint
For every node under the review parent in the target memory, check:
| Check |
Rationale |
Has an edge to the review parent at all |
Missing ⇒ the check is invisible to tasks:review-changes. The highest-severity finding. |
Edge label is a condition — starts Applies when, more than the bare stem |
Catches child-of, applies-when, related, and the truncated Applies when. |
Label isn't a loc — reject anything matching the <loc>:rel:<loc> shape |
Catches the leaked-identity batch above. |
| Label isn't a near-duplicate of another check's |
Two checks with the same trigger usually means one was cloned and not re-pointed. |
| Description carries the condition too |
hadron_find_nodes / list output surfaces the description, so a description without a trigger is a second blind spot (use-scoped-message-helpers had exactly this). |
| Trigger doesn't name a foreign toolchain |
Heuristic, warn-only: flag a Dart/Flutter trigger in a TS memory and vice-versa. Would have caught the misfiled format-sources. |
seq is unique among siblings |
Duplicate seq makes ordering non-deterministic. |
coding preflight lint
For the preflight node's outgoing routes:
- every target resolves (no route to a deleted/superseded node — CLAUDE.md's own words: "stale routing is worse than missing routing")
- labels are action-phrased (
to do X …), matching preflight's convention
- no route points at a node that has since been retired/tombstoned
- flag routes to nodes that moved memory (the portal findings migrating out of
::dev is exactly this churn)
Flags
--json for CI consumption — this is the shape that lets a repo gate its own memory hygiene in a workflow.
--fix on coding review lint for the mechanical subset only: promote the node's description sentence into the edge label when the description carries a trigger and the label doesn't. That covers most of the portal's 7. Everything else (a child-of on a node whose description has no condition either) needs a human and should just be reported.
- Non-zero exit when any error-severity finding is present; warnings alone exit 0.
⚠️ Implementation hazard: edges
Changing an edge label is currently dangerous. MCP exposes only hadron_create_edge — there is no update or delete — so the only route is hadron_update_node(edges:), which replaces the node's entire outgoing edge set. A naive --fix that rewrites one label will silently destroy the node's documented-by / relates-to edges. Four of the seven portal nodes had exactly those siblings.
The CLI talks to GraphQL directly, where updateEdge(edgeRef:) and deleteEdge(edgeRef:) do exist — so --fix should use updateEdge and must never go through a whole-node write. Tracked upstream as hadron-memory/hadron-server#835.
Prior art in this repo
hadron spec lint already exists for the spec corpus (per the add-spec skill). This is the same idea for the coding-workflow graph, and should match its output shape and exit-code conventions.
Why
The
review:*checklist trees and thepreflightrouters are executable infrastructure, not prose.tasks:review-changestriages checks by reading each one'sApplies when …edge label back to thereviewparent;preflightroutes symptom → finding via its outgoing edges. When one of those edges is malformed, the check or route is silently skipped. The node still exists, still looks maintained, and never fires again.There is currently no way to detect that except by hand — which is how it went unnoticed. A manual audit of two memories this week found:
hadronmemory.com::hadron-portal— 7 of 24 checks had an edge label carrying no condition at all (bareapplies-when, and one truncatedApplies whenwith nothing after it). Every one was reachable only by opening the node, so a reviewer scanning the parent's Referenced-by list had nothing to match a diff against.micromentor.org::mmdata— ~45 checks were fine, but 4 were broken in three distinct ways:child-ofinstead of a trigger (posthog-backend-vs-app-event-routing,role-vs-group-ident-vocabulary)review:input-type-graphql-type:rel:review— with 4 more:rel:-shaped labels in the same branch, suggesting one bad batchformat-sources) triggered on "Applies when Dart sources change" while living in the TypeScript backend memory — it can never match a diff in the repo whose memory it's in(Filed as micromentor-team/mmdata#1599.)
These are all mechanically detectable. That's the ask.
Proposed surface
A new top-level group alongside the existing
task/node/edge/memorygroups:codingas the parent because these lint the coding-workflow graph (review gates, preflight routing, coding-guidelines cross-links) rather than arbitrary nodes — leaving room forcoding review ls,coding review add(thetasks:add-review-nodeprocedure),coding preflight ls, etc.coding review lintFor every node under the
reviewparent in the target memory, check:reviewparent at alltasks:review-changes. The highest-severity finding.Applies when, more than the bare stemchild-of,applies-when,related, and the truncatedApplies when.<loc>:rel:<loc>shapehadron_find_nodes/ list output surfaces the description, so a description without a trigger is a second blind spot (use-scoped-message-helpershad exactly this).format-sources.seqis unique among siblingscoding preflight lintFor the
preflightnode's outgoing routes:to do X …), matching preflight's convention::devis exactly this churn)Flags
--jsonfor CI consumption — this is the shape that lets a repo gate its own memory hygiene in a workflow.--fixoncoding review lintfor the mechanical subset only: promote the node's description sentence into the edge label when the description carries a trigger and the label doesn't. That covers most of the portal's 7. Everything else (achild-ofon a node whose description has no condition either) needs a human and should just be reported.Changing an edge label is currently dangerous. MCP exposes only
hadron_create_edge— there is no update or delete — so the only route ishadron_update_node(edges:), which replaces the node's entire outgoing edge set. A naive--fixthat rewrites one label will silently destroy the node'sdocumented-by/relates-toedges. Four of the seven portal nodes had exactly those siblings.The CLI talks to GraphQL directly, where
updateEdge(edgeRef:)anddeleteEdge(edgeRef:)do exist — so--fixshould useupdateEdgeand must never go through a whole-node write. Tracked upstream as hadron-memory/hadron-server#835.Prior art in this repo
hadron spec lintalready exists for the spec corpus (per theadd-specskill). This is the same idea for the coding-workflow graph, and should match its output shape and exit-code conventions.