schemas/v1/report.schema.json
|
{ "$schema": "http://json-schema.org/draft-07/schema#", "$id": "https://raw.githubusercontent.com/Fortigi/PSComplexity/main/schemas/v1/report.schema.json", "title": "PSComplexity report", "description": "The machine-readable form of one PSComplexity run. Two shapes: a measurement report, written by Measure-PSComplexity, and a gate report, written by Test-PSComplexity, which additionally carries the thresholds that applied and the verdict they produced. The version lives in this file's PATH rather than in the $id's git ref, so a v2 goes in schemas/v2/ beside this one and a document naming this URL keeps resolving to the format it was written against.", "type": "object", "additionalProperties": true, "required": [ "generatedFrom", "schemaVersion", "producedBy", "generatedAt", "mode", "metricVersion", "scope", "skipped", "summary", "units" ], "properties": { "generatedFrom": { "const": "PSComplexity", "description": "What wrote this. A consumer pointed at a directory of reports can tell ours from anything else before it reads a number." }, "schemaVersion": { "type": "integer", "minimum": 1, "description": "Changes when a field changes meaning or disappears, NEVER when one is added. That is why additionalProperties is true here: a consumer validating against this survives a release that records more." }, "producedBy": { "type": "object", "required": ["module", "version"], "properties": { "module": { "type": "string" }, "version": { "type": "string", "description": "The ModuleVersion that produced the report." } } }, "generatedAt": { "type": "string", "pattern": "^[0-9]{4}-[0-9]{2}-[0-9]{2}T[0-9]{2}:[0-9]{2}:[0-9]{2}Z$", "description": "UTC, ISO-8601, second precision. A STRING in the file: PowerShell's ConvertFrom-Json recognises the format and hands a caller a [datetime] instead, so a PowerShell reader never sees this text and a validator handed a round-tripped object is checking something that no longer exists. Validate the FILE." }, "mode": { "type": "string", "enum": ["Measure", "Gate"], "description": "Which command wrote it. A measurement applied no thresholds and reached no verdict; see the conditional at the end of this schema, which makes a verdict unrepresentable in a measurement report." }, "metricVersion": { "type": "integer", "minimum": 1, "description": "Which metric produced these numbers. REQUIRED, and that is the point: the metric has already moved twice for source that did not change, so a stored report whose numbers cannot be checked for comparability is a trend chart waiting to mislead. Anything comparing two reports must refuse to compare across two different values rather than mix them." }, "scope": { "type": "object", "required": ["path", "recurse", "root"], "description": "What was asked for. REQUIRED alongside the summary: an aggregate that cannot say what it covered is the failure this project exists to find in other people's code.", "properties": { "path": { "type": "array", "items": { "type": "string" } }, "recurse": { "type": "boolean" }, "root": { "type": "string", "description": "The working directory the File fields are relative to." }, "changedFile": { "type": ["array", "null"], "items": { "type": "string" }, "description": "The files a diff-scoped run was restricted to, or null for a whole-tree run. Absent-versus-empty matters: only null may be read as a measurement of everything under `path`. A type union rather than oneOf, because oneOf reports a failure in every branch and drags a bogus complaint about this key along with any unrelated mistake elsewhere in the file." } } }, "skipped": { "type": "array", "description": "Files that were asked for and not measured, with the reason. REQUIRED, and an empty array is the normal case -- absent and empty are different answers, and a consumer must not have to tell them apart to know whether everything was read.", "items": { "type": "object", "required": ["file", "reason"], "properties": { "file": { "type": "string" }, "reason": { "type": "string" } } } }, "baselined": { "type": "array", "description": "Units the baseline excused, each with the score it was held to. A baseline routinely excuses far more units than an acceptance ever will, so a report carrying one and not the other would report a pass over a set it never named. The score, not just the name: 'excused' without a number does not say how much room was left.", "items": { "type": "object", "required": ["file", "unit", "cyclomatic", "cognitive"], "properties": { "file": { "type": "string" }, "unit": { "type": "string" }, "cyclomatic": { "type": "integer", "minimum": 0 }, "cognitive": { "type": "integer", "minimum": 0 } } } }, "summary": { "type": "object", "required": ["fileCount", "unitCount", "maxCyclomatic", "maxCognitive", "averageCyclomatic", "averageCognitive"], "description": "Aggregates over `units`. Every one of them is reconcilable against that array; none of them is the only place a fact lives.", "properties": { "fileCount": { "type": "integer", "minimum": 0 }, "unitCount": { "type": "integer", "minimum": 0 }, "maxCyclomatic": { "type": "integer", "minimum": 0 }, "maxCognitive": { "type": "integer", "minimum": 0 }, "averageCyclomatic": { "type": "number", "minimum": 0 }, "averageCognitive": { "type": "number", "minimum": 0 } } }, "units": { "type": "array", "description": "Every unit measured. PascalCase field names, unlike this envelope, because these ARE the records Measure-PSComplexity emits -- one vocabulary for the object and its serialisation, so a consumer reading the JSON sees the field names the README documents.", "items": { "$ref": "#/definitions/unit" } }, "thresholds": { "type": "object", "required": ["cyclomatic", "cognitive"], "description": "The ceilings that applied. Present only in a gate report.", "properties": { "cyclomatic": { "type": "integer", "minimum": 1 }, "cognitive": { "type": "integer", "minimum": 0 } } }, "passed": { "type": "boolean", "description": "The verdict. Present only in a gate report, and forbidden elsewhere -- see the conditional below." }, "violations": { "type": "array", "description": "Units over a ceiling that no acceptance covered. A subset of `units`, repeated rather than referenced so a consumer needs one pass.", "items": { "$ref": "#/definitions/unit" } }, "accepted": { "type": "array", "description": "Units allowed over a ceiling, each with the argument for it. A report that hides these would report a pass without saying what it excused.", "items": { "type": "object", "required": ["file", "unit", "reason"], "properties": { "file": { "type": "string" }, "unit": { "type": "string" }, "reason": { "type": "string" } } } } }, "definitions": { "unit": { "type": "object", "additionalProperties": true, "required": ["File", "Unit", "Line", "Cyclomatic", "Cognitive", "MetricVersion"], "properties": { "File": { "type": "string", "description": "Relative to scope.root with forward slashes; a full path for a file outside it." }, "Unit": { "type": "string", "description": "Class.Member, Outer/Inner, <script-body>, or a name with an ordinal when several share one scope." }, "Line": { "type": "integer", "minimum": 1, "description": "Where the unit starts. NOT an identity: it moves whenever anything above the unit is edited." }, "Cyclomatic": { "type": "integer", "minimum": 1 }, "Cognitive": { "type": "integer", "minimum": 0 }, "MetricVersion": { "type": "integer", "minimum": 1 }, "Contributions": { "type": "array", "description": "Present only when the run used -Detailed. The increments that produced Cognitive, in line order; the amounts sum to it.", "items": { "type": "object", "required": ["Line", "Construct", "Amount"], "properties": { "Line": { "type": "integer", "minimum": 1 }, "Construct": { "type": "string" }, "Amount": { "type": "integer", "minimum": 1 } } } } } } }, "allOf": [ { "description": "A measurement report reaches no verdict, and the format makes one impossible rather than merely undocumented. Measure-PSComplexity applies no thresholds, so a 'passed' beside its numbers is an answer nobody computed -- the same shape of lie as a partial score quoted as a real one. Written as boolean-false property schemas rather than as 'not': PowerShell's Test-Json silently IGNORES the 'not' keyword, so the obvious spelling of this rule is a clause that can never fail, which looks exactly like one that passes.", "if": { "required": ["thresholds"] }, "then": { "required": ["thresholds", "passed", "violations", "accepted", "baselined"] }, "else": { "properties": { "passed": false, "violations": false, "accepted": false, "baselined": false } } } ] } |