{
"$schema": "http://json-schema.org/draft-07/schema#",
"$id": "https://raw.githubusercontent.com/ShortArrow/pathlint/main/schemas/check.schema.json",
"title": "CheckOutcomeView",
"description": "JSON shape of one element in the top-level array emitted by `pathlint check --json`. The discriminator is the `kind` field. See PRD §17 for the wire-shape history.",
"type": "object",
"required": [
"command",
"kind",
"matched_sources",
"severity"
],
"properties": {
"avoid": {
"description": "`avoid` set copied from the source `[[expect]]` rule. Same optional-when-empty story as `prefer`.",
"type": "array",
"items": {
"type": "string"
}
},
"command": {
"type": "string"
},
"diagnosis": {
"anyOf": [
{
"$ref": "#/definitions/Diagnosis"
},
{
"type": "null"
}
]
},
"kind": {
"description": "Outcome kind discriminator: `\"ok\"`, `\"ng_wrong_source\"`, `\"ng_unknown_source\"`, `\"ng_not_found\"`, `\"ng_not_executable\"`, `\"skip\"`, `\"not_applicable\"`, or `\"config_error\"`. Always a flat string (no nested object) since 0.0.17.",
"allOf": [
{
"$ref": "#/definitions/Status"
}
]
},
"matched_sources": {
"description": "Sources matched against the resolved path. Always emitted — even an empty array — so consumers do not have to special-case \"field present\" vs \"field absent\". 0.0.17 hoisted this from schema-required-with-skip-serializing into a stable always-emit shape so the schema and the wire form match.",
"type": "array",
"items": {
"type": "string"
}
},
"prefer": {
"description": "`prefer` set copied from the source `[[expect]]` rule. Skipped when empty so consumers reading typical Ok / Skip outcomes don't have to dig past the empty array. Schema marks the field optional via `#[serde(default)]`.",
"type": "array",
"items": {
"type": "string"
}
},
"reason": {
"description": "Human-readable reason. Populated for `kind = \"ng_not_executable\"` (R2 shape check rejection) and `kind = \"config_error\"` (e.g. undefined source name). Absent otherwise. 0.0.17 split this off from the kind discriminator so consumers can branch on `kind` as a string and read `reason` separately.",
"type": [
"string",
"null"
]
},
"resolved": {
"type": [
"string",
"null"
]
},
"severity": {
"description": "Per-rule severity copied from the Outcome. Always emitted (even for `error`, the default) so a downstream consumer gating on severity does not need a fallback for the absent case.",
"allOf": [
{
"$ref": "#/definitions/Severity"
}
]
}
},
"definitions": {
"Diagnosis": {
"description": "Pure-data view of *why* an outcome failed. Derived from `Outcome` by `diagnose`; kept separate so the presentation layer renders strings from a structured value rather than from raw `Outcome` fields. `serde::Serialize` so the same value can drive `check --json`.\n\nVariants name the failure mode; the fields are the load-bearing facts callers need: which sources were missed (`prefer_missed`), which `avoid` names were hit (`avoid_hits`), the reason the shape check rejected the file, etc. The struct does *not* carry `command` / `resolved` — those live on `Outcome` and the caller pairs them up.",
"oneOf": [
{
"description": "Resolved path matched some sources, but none of the `prefer` set — or it matched a source listed under `avoid`. `avoid_hits` is the intersection of `matched ∩ avoid`; non-empty means the rule explicitly forbids this source. `prefer_missed` is `prefer` itself (every name the user hoped for); rendering decides whether to show it.",
"type": "object",
"required": [
"avoid_hits",
"kind",
"matched",
"prefer_missed"
],
"properties": {
"avoid_hits": {
"type": "array",
"items": {
"type": "string"
}
},
"kind": {
"type": "string",
"enum": [
"wrong_source"
]
},
"matched": {
"type": "array",
"items": {
"type": "string"
}
},
"prefer_missed": {
"type": "array",
"items": {
"type": "string"
}
}
}
},
{
"description": "Path lies outside every defined `[source.<name>]`. No source name matched at all.",
"type": "object",
"required": [
"kind",
"prefer"
],
"properties": {
"kind": {
"type": "string",
"enum": [
"unknown_source"
]
},
"prefer": {
"type": "array",
"items": {
"type": "string"
}
}
}
},
{
"description": "Command was not on PATH, and the rule was not optional.",
"type": "object",
"required": [
"kind",
"prefer"
],
"properties": {
"kind": {
"type": "string",
"enum": [
"not_found"
]
},
"prefer": {
"type": "array",
"items": {
"type": "string"
}
}
}
},
{
"description": "`kind = \"executable\"` shape check rejected the resolved file. `reason` is the short human-readable cause from `lint` (`\"is a directory\"`, `\"broken symlink\"`, …).",
"type": "object",
"required": [
"kind",
"matched",
"reason"
],
"properties": {
"kind": {
"type": "string",
"enum": [
"not_executable"
]
},
"matched": {
"type": "array",
"items": {
"type": "string"
}
},
"reason": {
"type": "string"
}
}
},
{
"description": "`[[expect]]` referenced a source name that is not defined.",
"type": "object",
"required": [
"kind",
"message"
],
"properties": {
"kind": {
"type": "string",
"enum": [
"config"
]
},
"message": {
"type": "string"
}
}
}
]
},
"Severity": {
"description": "Per-rule severity for `[[expect]]`. Defaults to `Error` so 0.0.x rules behave exactly as before.",
"oneOf": [
{
"description": "NG escalates to exit 1. Default.",
"type": "string",
"enum": [
"error"
]
},
{
"description": "NG is reported but does not change the exit code.",
"type": "string",
"enum": [
"warn"
]
}
]
},
"Status": {
"description": "Outcome status discriminator. Unit-only enum, serialised as a snake_case string under the `kind` field of `pathlint check --json`. Human-readable detail (when present) rides on `Outcome::reason`. See PRD §17 for the wire-shape history.",
"oneOf": [
{
"type": "string",
"enum": [
"ok",
"ng_wrong_source",
"ng_unknown_source",
"ng_not_found",
"skip",
"not_applicable",
"config_error"
]
},
{
"description": "R2 — resolved path failed `kind` shape check (directory, broken symlink, missing exec bit, etc.). The human-readable reason rides on `Outcome::reason`.",
"type": "string",
"enum": [
"ng_not_executable"
]
}
]
}
}
}