@descent-vtt/spec-graph 0.5.0 → 0.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +190 -3
- package/README.md +62 -12
- package/dist/cli.d.ts +5 -4
- package/dist/cli.js +127 -21
- package/dist/cli.js.map +1 -1
- package/dist/config.d.ts +3 -3
- package/dist/config.js +4 -4
- package/dist/config.js.map +1 -1
- package/dist/diff.d.ts +120 -0
- package/dist/diff.js +404 -0
- package/dist/diff.js.map +1 -0
- package/dist/directives.d.ts +18 -1
- package/dist/directives.js +79 -0
- package/dist/directives.js.map +1 -1
- package/dist/extract.js +4 -2
- package/dist/extract.js.map +1 -1
- package/dist/glob.d.ts +41 -1
- package/dist/glob.js +61 -6
- package/dist/glob.js.map +1 -1
- package/dist/index.d.ts +5 -5
- package/dist/index.js +3 -3
- package/dist/index.js.map +1 -1
- package/dist/paths.d.ts +10 -0
- package/dist/paths.js +13 -0
- package/dist/paths.js.map +1 -1
- package/dist/regex.d.ts +18 -6
- package/dist/regex.js +185 -71
- package/dist/regex.js.map +1 -1
- package/dist/report.d.ts +10 -0
- package/dist/report.js +2 -0
- package/dist/report.js.map +1 -1
- package/dist/resolve.js +11 -0
- package/dist/resolve.js.map +1 -1
- package/dist/types.d.ts +8 -0
- package/dist/types.js.map +1 -1
- package/package.json +1 -1
package/dist/types.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;GAmBG;AA4FH,MAAM,CAAC,MAAM,YAAY,GAA2B,MAAM,CAAC,MAAM,CAAC;IAChE,YAAY;IACZ,UAAU;IACV,WAAW;IACX,WAAW;IACX,eAAe;IACf,UAAU;IACV,UAAU;CACX,CAAC,CAAC;AAEH,gDAAgD;AAChD,MAAM,CAAC,MAAM,WAAW,GAA4C,MAAM,CAAC,MAAM,CAAC;IAChF,UAAU,EAAE,MAAM;IAClB,QAAQ,EAAE,SAAS;IACnB,SAAS,EAAE,MAAM;IACjB,SAAS,EAAE,QAAQ;IACnB,eAAe,EAAE,QAAQ;IACzB,QAAQ,EAAE,QAAQ;IAClB,QAAQ,EAAE,QAAQ;CACnB,CAAC,CAAC;AAkDH,MAAM,CAAC,MAAM,WAAW,GAA2C,MAAM,CAAC,MAAM,CAAC;IAC/E,QAAQ,EAAE,EAAE,WAAW,EAAE,KAAK,EAAE,mBAAmB,EAAE,KAAK,EAAE,SAAS,EAAE,KAAK,EAAE,MAAM,EAAE,UAAU,EAAE;IAClG,UAAU,EAAE,EAAE,WAAW,EAAE,KAAK,EAAE,mBAAmB,EAAE,KAAK,EAAE,SAAS,EAAE,KAAK,EAAE,MAAM,EAAE,YAAY,EAAE;IACtG,YAAY,EAAE,EAAE,WAAW,EAAE,KAAK,EAAE,mBAAmB,EAAE,KAAK,EAAE,SAAS,EAAE,KAAK,EAAE,MAAM,EAAE,YAAY,EAAE;IACxG,YAAY,EAAE,EAAE,WAAW,EAAE,IAAI,EAAE,mBAAmB,EAAE,KAAK,EAAE,SAAS,EAAE,KAAK,EAAE,MAAM,EAAE,YAAY,EAAE;IACvG,OAAO,EAAE,EAAE,WAAW,EAAE,IAAI,EAAE,mBAAmB,EAAE,KAAK,EAAE,SAAS,EAAE,KAAK,EAAE,MAAM,EAAE,SAAS,EAAE;IAC/F,cAAc,EAAE,EAAE,WAAW,EAAE,KAAK,EAAE,mBAAmB,EAAE,IAAI,EAAE,SAAS,EAAE,KAAK,EAAE,MAAM,EAAE,cAAc,EAAE;IAC3G,YAAY,EAAE,EAAE,WAAW,EAAE,IAAI,EAAE,mBAAmB,EAAE,IAAI,EAAE,SAAS,EAAE,KAAK,EAAE,MAAM,EAAE,eAAe,EAAE;IACzG,UAAU,EAAE,EAAE,WAAW,EAAE,KAAK,EAAE,mBAAmB,EAAE,KAAK,EAAE,SAAS,EAAE,IAAI,EAAE,MAAM,EAAE,YAAY,EAAE;IACrG,MAAM,EAAE,EAAE,WAAW,EAAE,IAAI,EAAE,mBAAmB,EAAE,KAAK,EAAE,SAAS,EAAE,IAAI,EAAE,MAAM,EAAE,QAAQ,EAAE;CAC7F,CAAC,CAAC;AAEH,MAAM,CAAC,MAAM,UAAU,GAAG,MAAM,CAAC,IAAI,CAAC,WAAW,CAAwB,CAAC;AA6M1E,yEAAyE;AACzE,MAAM,UAAU,aAAa,CAAC,IAAe;IAC3C,OAAO,IAAI,CAAC,UAAU,CAAC,mBAAmB,CAAC,CAAC;AAC9C,CAAC;AAED,MAAM,CAAC,MAAM,mBAAmB,GAAG,UAAU,CAAC","sourcesContent":["/**\n * The spec-graph data model.\n *\n * Three ideas carry the whole design:\n *\n * 1. **Documents have a lifecycle phase**, normalised from whatever vocabulary\n * the repository already uses (MADR `accepted`, KEP `implementable`, RFC\n * `final`, plain English `archived`). The phase answers one question that\n * matters more than any other: *can this document still absorb new work?*\n *\n * 2. **Items are obligations with nuanced state.** A checkbox is not a boolean.\n * An item can be open, narrowed to a smaller scope, satisfied, consciously\n * accepted as debt, or obviated because the premise it rested on evaporated.\n * Collapsing those five into `[ ]` / `[x]` is what makes ad-hoc greps lie.\n *\n * 3. **Edges are typed and carry provenance.** Every relation records where it\n * was *declared*, which is frequently not the node it points at. That is what\n * lets a diagnostic say \"ADR-0009 line 12 claims to supersede ADR-0003\"\n * instead of \"something, somewhere, is inconsistent\".\n */\n\n/* -------------------------------------------------------------------------- */\n/* Source positions */\n/* -------------------------------------------------------------------------- */\n\n/** A point in a source file. `offset` is a UTF-16 code-unit index. */\nexport interface Position {\n readonly offset: number;\n /** 1-based. */\n readonly line: number;\n /** 1-based, counted in UTF-16 code units. */\n readonly column: number;\n}\n\n/** A half-open `[start, end)` range of a source file. */\nexport interface Span {\n readonly start: Position;\n readonly end: Position;\n}\n\n/** A span plus the file it belongs to. Everything user-visible carries one. */\nexport interface SourceRef {\n /** Repository-relative POSIX path. */\n readonly file: string;\n readonly span: Span;\n}\n\n/* -------------------------------------------------------------------------- */\n/* Lifecycle */\n/* -------------------------------------------------------------------------- */\n\n/**\n * The normalised document lifecycle, as a four-state lattice.\n *\n * Ecosystem vocabularies are large and mutually incompatible; the *consequences*\n * of a status are not. These four phases are chosen so that every question\n * spec-graph asks can be answered from the phase alone.\n *\n * - `draft` - not yet binding. Mutable. Absorbs new work.\n * - `active` - binding and mutable. Absorbs new work.\n * - `frozen` - binding and immutable. Changes require a new document.\n * - `retired` - no longer binding: superseded, rejected, withdrawn, archived.\n *\n * `unknown` is a fifth, deliberately non-lattice value for documents that never\n * declared a status. Rules treat it permissively rather than guessing.\n */\nexport type Phase = 'draft' | 'active' | 'frozen' | 'retired' | 'record' | 'unknown';\n\n/**\n * Can a document in this phase take on a *new* open obligation?\n *\n * This is the single predicate behind ghost-handover detection. Delegating an\n * open question into a `frozen` or `retired` document is a write to a closed\n * ledger: nobody will ever read it again.\n */\nexport type Receptivity = 'receptive' | 'sealed' | 'unknown';\n\n/* -------------------------------------------------------------------------- */\n/* Item state */\n/* -------------------------------------------------------------------------- */\n\n/**\n * How much of an obligation is left.\n *\n * Kept separate from {@link Disposition} because the two answer different\n * questions: `openness` is \"does someone still owe work?\", `disposition` is\n * \"what happened to it?\". Rules almost always want the former; humans reading a\n * report always want the latter.\n */\nexport type Openness = 'open' | 'partial' | 'closed';\n\n/** Why an item is in the state it is in. */\nexport type Disposition =\n /** Nothing has happened yet. */\n | 'unresolved'\n /** Scope was cut down, but a remainder is still owed. */\n | 'narrowed'\n /** Ownership moved elsewhere. Still owed - by somebody else. */\n | 'delegated'\n /** Done, answered, decided. */\n | 'satisfied'\n /** Knowingly not done. A closed item with a scar. */\n | 'accepted-debt'\n /** Decided against. */\n | 'rejected'\n /**\n * The premise evaporated, so the question stopped being a question.\n * Load-bearing citations to an obviated item are stale premises.\n */\n | 'obviated';\n\nexport const DISPOSITIONS: readonly Disposition[] = Object.freeze([\n 'unresolved',\n 'narrowed',\n 'delegated',\n 'satisfied',\n 'accepted-debt',\n 'rejected',\n 'obviated',\n]);\n\n/** The openness implied by each disposition. */\nexport const OPENNESS_OF: Readonly<Record<Disposition, Openness>> = Object.freeze({\n unresolved: 'open',\n narrowed: 'partial',\n delegated: 'open',\n satisfied: 'closed',\n 'accepted-debt': 'closed',\n rejected: 'closed',\n obviated: 'closed',\n});\n\n/* -------------------------------------------------------------------------- */\n/* Edges */\n/* -------------------------------------------------------------------------- */\n\n/**\n * Relation kinds.\n *\n * The set is small on purpose. Two orthogonal properties - declared once in\n * {@link EDGE_TRAITS} - do the actual work, so adding a kind never means\n * touching a rule.\n */\nexport type EdgeKind =\n /** Structural: a document contains an item. Never inferred from prose. */\n | 'contains'\n /** A neutral citation. */\n | 'references'\n /** Explicitly *not* load-bearing: \"See also\", \"History\", \"Prior art\". */\n | 'relates-to'\n /** The source design depends on the target content. */\n | 'depends-on'\n /** The source rests on a claim made by the target. */\n | 'assumes'\n /** The source hands an obligation to the target. */\n | 'delegates-to'\n /** The source cannot proceed until the target does. */\n | 'blocked-by'\n /** The source replaces the target. */\n | 'supersedes'\n /** The source modifies the target without replacing it. */\n | 'amends';\n\nexport interface EdgeTraits {\n /**\n * Does the source continued validity depend on what the target says?\n *\n * Load-bearing edges into retired documents are stale premises. Non\n * load-bearing ones (\"see also\") are just history, and reporting them would\n * bury the real findings - which is exactly how ad-hoc linters lose trust.\n */\n readonly loadBearing: boolean;\n /** Does the edge move an obligation from source to target? */\n readonly transfersObligation: boolean;\n /** Does the edge assert something about the target lifecycle? */\n readonly lifecycle: boolean;\n /** Human-readable phrase used in diagnostics: \"ADR-1 <phrase> ADR-2\". */\n readonly phrase: string;\n}\n\nexport const EDGE_TRAITS: Readonly<Record<EdgeKind, EdgeTraits>> = Object.freeze({\n contains: { loadBearing: false, transfersObligation: false, lifecycle: false, phrase: 'contains' },\n references: { loadBearing: false, transfersObligation: false, lifecycle: false, phrase: 'references' },\n 'relates-to': { loadBearing: false, transfersObligation: false, lifecycle: false, phrase: 'relates to' },\n 'depends-on': { loadBearing: true, transfersObligation: false, lifecycle: false, phrase: 'depends on' },\n assumes: { loadBearing: true, transfersObligation: false, lifecycle: false, phrase: 'assumes' },\n 'delegates-to': { loadBearing: false, transfersObligation: true, lifecycle: false, phrase: 'delegates to' },\n 'blocked-by': { loadBearing: true, transfersObligation: true, lifecycle: false, phrase: 'is blocked by' },\n supersedes: { loadBearing: false, transfersObligation: false, lifecycle: true, phrase: 'supersedes' },\n amends: { loadBearing: true, transfersObligation: false, lifecycle: true, phrase: 'amends' },\n});\n\nexport const EDGE_KINDS = Object.keys(EDGE_TRAITS) as readonly EdgeKind[];\n\n/** How an edge came to exist. Reported verbatim so findings are auditable. */\nexport type EdgeOrigin =\n /** A `<!-- @spec-edge -->` directive. */\n | 'directive'\n /** A front-matter field such as `supersedes:` or `superseded-by:`. */\n | 'front-matter'\n /** A Markdown link, classified by the section it sits in. */\n | 'link'\n /** A bare textual identifier such as `ADR-0007` in prose. */\n | 'text'\n /** Synthesised by the extractor (document -> item). */\n | 'structural';\n\nexport interface Edge {\n readonly kind: EdgeKind;\n /** Node id. */\n readonly from: string;\n /** Node id. */\n readonly to: string;\n readonly origin: EdgeOrigin;\n /**\n * Where the relation was written down. Not necessarily inside `from`: a\n * `superseded-by: ADR-9` field in ADR-3 declares `ADR-9 supersedes ADR-3`,\n * and the only place a human can go fix it is ADR-3.\n */\n readonly declaredAt: SourceRef;\n /** The raw text that produced the edge, for diagnostics. */\n readonly raw: string;\n /**\n * Every file that declares this relation.\n *\n * A supersession stated only in the superseding document leaves the\n * superseded one silently presenting itself as current, which is a finding in\n * its own right - so the set of declaration sites has to survive de-duplication.\n */\n readonly declaredIn: readonly string[];\n /** True when `from` and `to` are the same node. Kept, never traversed. */\n readonly reflexive: boolean;\n}\n\n/* -------------------------------------------------------------------------- */\n/* Nodes */\n/* -------------------------------------------------------------------------- */\n\nexport type NodeKind = 'document' | 'item';\n\ninterface NodeBase {\n /** Canonical, graph-unique. Documents: `ADR-0007`. Items: `ADR-0007#q.1`. */\n readonly id: string;\n readonly kind: NodeKind;\n /** Short human label used in reports. */\n readonly title: string;\n readonly at: SourceRef;\n}\n\nexport interface DocumentNode extends NodeBase {\n readonly kind: 'document';\n /** Repository-relative POSIX path. */\n readonly path: string;\n /** Every spelling that resolves to this document, lowercased. */\n readonly aliases: readonly string[];\n readonly phase: Phase;\n /** The status string as written, before normalisation. `null` if absent. */\n readonly rawStatus: string | null;\n /** Where the status was declared, when it was declared anywhere. */\n readonly statusAt: SourceRef | null;\n /** Front-matter fields, flattened to strings and string arrays. */\n readonly frontMatter: Readonly<Record<string, string | readonly string[]>>;\n}\n\nexport interface ItemNode extends NodeBase {\n readonly kind: 'item';\n /** Owning document id. */\n readonly document: string;\n /** Heading path the item sits under, outermost first. */\n readonly section: readonly string[];\n /** First line of the item, normalised to one line for reports. */\n readonly text: string;\n /** The full multi-line body, continuations included. */\n readonly body: string;\n readonly disposition: Disposition;\n readonly openness: Openness;\n /** Which signal decided the disposition. */\n readonly evidence: StateSignal;\n /** Conflicting signals found while resolving the state. */\n readonly conflicts: readonly StateSignal[];\n}\n\nexport type SpecNode = DocumentNode | ItemNode;\n\n/** One observation about an item state. */\nexport interface StateSignal {\n /** Higher wins. See `SIGNAL_PRIORITY` in `state.ts`. */\n readonly source: 'directive' | 'marker' | 'strikethrough' | 'checkbox' | 'section' | 'default';\n readonly disposition: Disposition;\n /** The literal text that produced the signal. */\n readonly raw: string;\n readonly at: SourceRef;\n}\n\n/* -------------------------------------------------------------------------- */\n/* Unresolved references */\n/* -------------------------------------------------------------------------- */\n\nexport type DanglingReason =\n /** Nothing in the corpus answers to this identifier. */\n | 'unknown-target'\n /** More than one document answers to it. */\n | 'ambiguous'\n /** The path exists on disk but is not a parsed specification. */\n | 'not-a-spec'\n /** The document resolved, but the `#anchor` did not. */\n | 'unknown-anchor';\n\n/** A citation that failed foreign-key validation. */\nexport interface DanglingRef {\n readonly kind: EdgeKind;\n readonly from: string;\n /** The identifier as written. */\n readonly target: string;\n readonly reason: DanglingReason;\n readonly origin: EdgeOrigin;\n readonly declaredAt: SourceRef;\n readonly raw: string;\n /** Candidate ids when `reason` is `ambiguous`, or near-misses otherwise. */\n readonly candidates: readonly string[];\n}\n\n/**\n * A citation that failed to resolve and that configuration told us to ignore.\n *\n * Kept so a repository can audit its own silence. `ignoreReferences` and\n * `ignoreFamilies` are the two places a team can tell spec-graph to stop\n * reporting something, and a single over-broad glob can quietly turn the whole\n * check off - which looks exactly like a clean repository. The opportunistic\n * rule from [ADR-0004] is deliberately not counted here: reading `SHA-256` as\n * prose is spec-graph's own judgement, not a decision anybody wrote down, and\n * listing it would bury the handful of entries that are. See ADR-0008.\n */\nexport interface SuppressedRef {\n /** The identifier as written. */\n readonly target: string;\n /** The document that wrote it. */\n readonly from: string;\n readonly at: SourceRef;\n /** Which declaration silenced it. */\n readonly by: 'reference' | 'family';\n}\n\n/**\n * A front-matter key that reads as a relation and is not one.\n *\n * Front matter is an open vocabulary - `title`, `tags`, `sidebar_position` and\n * whatever else the site generator wants all live there - so an unrecognised key\n * is normally none of spec-graph's business. This is the exception: a key one\n * character away from a relation, carrying something shaped like a citation, is\n * a relation the author believes they declared. The edge does not exist, and\n * nothing else would ever say so.\n */\nexport interface MisreadKey {\n /** The key as written. */\n readonly key: string;\n /** The relation key it is a hair away from. */\n readonly suggestion: string;\n /** The document the key was written in. */\n readonly from: string;\n readonly at: SourceRef;\n}\n\n/* -------------------------------------------------------------------------- */\n/* Diagnostics */\n/* -------------------------------------------------------------------------- */\n\nexport type Severity = 'error' | 'warn' | 'info' | 'off';\n\nexport type RuleId =\n | 'ghost-handover'\n | 'stale-premise'\n | 'broken-reference'\n | 'reference-outside-corpus'\n | 'ambiguous-reference'\n | 'circular-delegation'\n | 'orphaned-obligation'\n | 'unreciprocated-supersession'\n | 'live-supersession'\n | 'state-conflict'\n | 'self-reference'\n | 'unknown-relation-key';\n\n/**\n * A rule a repository wrote for itself, as a selector plus a sentence.\n *\n * Namespaced, and the namespace is load-bearing rather than decorative. A\n * built-in id can never contain a colon, so a project rule can never collide\n * with one however it is named - which means `--rule`, the severity table, a\n * baseline entry and a SARIF `ruleId` all keep working without anyone having to\n * reserve a vocabulary. See ADR-0016.\n */\nexport type ProjectRuleId = `project:${string}`;\n\n/** Any rule that can produce a finding: built in, or written by a repository. */\nexport type AnyRuleId = RuleId | ProjectRuleId;\n\n/** True for an id in the project namespace, as opposed to a built-in. */\nexport function isProjectRule(rule: AnyRuleId): rule is ProjectRuleId {\n return rule.startsWith(PROJECT_RULE_PREFIX);\n}\n\nexport const PROJECT_RULE_PREFIX = 'project:';\n\nexport interface RelatedLocation {\n readonly at: SourceRef;\n readonly note: string;\n}\n\nexport interface Diagnostic {\n readonly rule: AnyRuleId;\n readonly severity: Exclude<Severity, 'off'>;\n /** One line, no trailing period. The headline of the finding. */\n readonly message: string;\n /** Where a human should go to fix it. */\n readonly at: SourceRef;\n /** Node ids involved, most relevant first. */\n readonly nodes: readonly string[];\n /**\n * What within the document this finding is about, where that is not a node.\n *\n * `null` everywhere else, where `nodes` already names the subject. It exists\n * because a broken reference names something that is not a node - that is\n * what makes it broken - and a baseline still has to be able to tell one\n * broken reference from another. A misread front-matter key is the same\n * shape: a spelling, not a node. See ADR-0012.\n */\n readonly target: string | null;\n /** Supporting locations: the other end of an edge, a cycle members. */\n readonly related: readonly RelatedLocation[];\n /** Concrete next action. */\n readonly hint: string;\n}\n\n/* -------------------------------------------------------------------------- */\n/* Analysis output */\n/* -------------------------------------------------------------------------- */\n\n/** A problem with the input itself, as opposed to a finding about the graph. */\nexport interface ParseProblem {\n readonly message: string;\n readonly at: SourceRef;\n}\n\nexport interface AnalysisSummary {\n readonly documents: number;\n readonly items: number;\n readonly edges: number;\n readonly openObligations: number;\n readonly errors: number;\n readonly warnings: number;\n readonly infos: number;\n /** Milliseconds, wall clock. */\n readonly durationMs: number;\n}\n"]}
|
|
1
|
+
{"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;GAmBG;AA4FH,MAAM,CAAC,MAAM,YAAY,GAA2B,MAAM,CAAC,MAAM,CAAC;IAChE,YAAY;IACZ,UAAU;IACV,WAAW;IACX,WAAW;IACX,eAAe;IACf,UAAU;IACV,UAAU;CACX,CAAC,CAAC;AAEH,gDAAgD;AAChD,MAAM,CAAC,MAAM,WAAW,GAA4C,MAAM,CAAC,MAAM,CAAC;IAChF,UAAU,EAAE,MAAM;IAClB,QAAQ,EAAE,SAAS;IACnB,SAAS,EAAE,MAAM;IACjB,SAAS,EAAE,QAAQ;IACnB,eAAe,EAAE,QAAQ;IACzB,QAAQ,EAAE,QAAQ;IAClB,QAAQ,EAAE,QAAQ;CACnB,CAAC,CAAC;AAkDH,MAAM,CAAC,MAAM,WAAW,GAA2C,MAAM,CAAC,MAAM,CAAC;IAC/E,QAAQ,EAAE,EAAE,WAAW,EAAE,KAAK,EAAE,mBAAmB,EAAE,KAAK,EAAE,SAAS,EAAE,KAAK,EAAE,MAAM,EAAE,UAAU,EAAE;IAClG,UAAU,EAAE,EAAE,WAAW,EAAE,KAAK,EAAE,mBAAmB,EAAE,KAAK,EAAE,SAAS,EAAE,KAAK,EAAE,MAAM,EAAE,YAAY,EAAE;IACtG,YAAY,EAAE,EAAE,WAAW,EAAE,KAAK,EAAE,mBAAmB,EAAE,KAAK,EAAE,SAAS,EAAE,KAAK,EAAE,MAAM,EAAE,YAAY,EAAE;IACxG,YAAY,EAAE,EAAE,WAAW,EAAE,IAAI,EAAE,mBAAmB,EAAE,KAAK,EAAE,SAAS,EAAE,KAAK,EAAE,MAAM,EAAE,YAAY,EAAE;IACvG,OAAO,EAAE,EAAE,WAAW,EAAE,IAAI,EAAE,mBAAmB,EAAE,KAAK,EAAE,SAAS,EAAE,KAAK,EAAE,MAAM,EAAE,SAAS,EAAE;IAC/F,cAAc,EAAE,EAAE,WAAW,EAAE,KAAK,EAAE,mBAAmB,EAAE,IAAI,EAAE,SAAS,EAAE,KAAK,EAAE,MAAM,EAAE,cAAc,EAAE;IAC3G,YAAY,EAAE,EAAE,WAAW,EAAE,IAAI,EAAE,mBAAmB,EAAE,IAAI,EAAE,SAAS,EAAE,KAAK,EAAE,MAAM,EAAE,eAAe,EAAE;IACzG,UAAU,EAAE,EAAE,WAAW,EAAE,KAAK,EAAE,mBAAmB,EAAE,KAAK,EAAE,SAAS,EAAE,IAAI,EAAE,MAAM,EAAE,YAAY,EAAE;IACrG,MAAM,EAAE,EAAE,WAAW,EAAE,IAAI,EAAE,mBAAmB,EAAE,KAAK,EAAE,SAAS,EAAE,IAAI,EAAE,MAAM,EAAE,QAAQ,EAAE;CAC7F,CAAC,CAAC;AAEH,MAAM,CAAC,MAAM,UAAU,GAAG,MAAM,CAAC,IAAI,CAAC,WAAW,CAAwB,CAAC;AAqN1E,yEAAyE;AACzE,MAAM,UAAU,aAAa,CAAC,IAAe;IAC3C,OAAO,IAAI,CAAC,UAAU,CAAC,mBAAmB,CAAC,CAAC;AAC9C,CAAC;AAED,MAAM,CAAC,MAAM,mBAAmB,GAAG,UAAU,CAAC","sourcesContent":["/**\n * The spec-graph data model.\n *\n * Three ideas carry the whole design:\n *\n * 1. **Documents have a lifecycle phase**, normalised from whatever vocabulary\n * the repository already uses (MADR `accepted`, KEP `implementable`, RFC\n * `final`, plain English `archived`). The phase answers one question that\n * matters more than any other: *can this document still absorb new work?*\n *\n * 2. **Items are obligations with nuanced state.** A checkbox is not a boolean.\n * An item can be open, narrowed to a smaller scope, satisfied, consciously\n * accepted as debt, or obviated because the premise it rested on evaporated.\n * Collapsing those five into `[ ]` / `[x]` is what makes ad-hoc greps lie.\n *\n * 3. **Edges are typed and carry provenance.** Every relation records where it\n * was *declared*, which is frequently not the node it points at. That is what\n * lets a diagnostic say \"ADR-0009 line 12 claims to supersede ADR-0003\"\n * instead of \"something, somewhere, is inconsistent\".\n */\n\n/* -------------------------------------------------------------------------- */\n/* Source positions */\n/* -------------------------------------------------------------------------- */\n\n/** A point in a source file. `offset` is a UTF-16 code-unit index. */\nexport interface Position {\n readonly offset: number;\n /** 1-based. */\n readonly line: number;\n /** 1-based, counted in UTF-16 code units. */\n readonly column: number;\n}\n\n/** A half-open `[start, end)` range of a source file. */\nexport interface Span {\n readonly start: Position;\n readonly end: Position;\n}\n\n/** A span plus the file it belongs to. Everything user-visible carries one. */\nexport interface SourceRef {\n /** Repository-relative POSIX path. */\n readonly file: string;\n readonly span: Span;\n}\n\n/* -------------------------------------------------------------------------- */\n/* Lifecycle */\n/* -------------------------------------------------------------------------- */\n\n/**\n * The normalised document lifecycle, as a four-state lattice.\n *\n * Ecosystem vocabularies are large and mutually incompatible; the *consequences*\n * of a status are not. These four phases are chosen so that every question\n * spec-graph asks can be answered from the phase alone.\n *\n * - `draft` - not yet binding. Mutable. Absorbs new work.\n * - `active` - binding and mutable. Absorbs new work.\n * - `frozen` - binding and immutable. Changes require a new document.\n * - `retired` - no longer binding: superseded, rejected, withdrawn, archived.\n *\n * `unknown` is a fifth, deliberately non-lattice value for documents that never\n * declared a status. Rules treat it permissively rather than guessing.\n */\nexport type Phase = 'draft' | 'active' | 'frozen' | 'retired' | 'record' | 'unknown';\n\n/**\n * Can a document in this phase take on a *new* open obligation?\n *\n * This is the single predicate behind ghost-handover detection. Delegating an\n * open question into a `frozen` or `retired` document is a write to a closed\n * ledger: nobody will ever read it again.\n */\nexport type Receptivity = 'receptive' | 'sealed' | 'unknown';\n\n/* -------------------------------------------------------------------------- */\n/* Item state */\n/* -------------------------------------------------------------------------- */\n\n/**\n * How much of an obligation is left.\n *\n * Kept separate from {@link Disposition} because the two answer different\n * questions: `openness` is \"does someone still owe work?\", `disposition` is\n * \"what happened to it?\". Rules almost always want the former; humans reading a\n * report always want the latter.\n */\nexport type Openness = 'open' | 'partial' | 'closed';\n\n/** Why an item is in the state it is in. */\nexport type Disposition =\n /** Nothing has happened yet. */\n | 'unresolved'\n /** Scope was cut down, but a remainder is still owed. */\n | 'narrowed'\n /** Ownership moved elsewhere. Still owed - by somebody else. */\n | 'delegated'\n /** Done, answered, decided. */\n | 'satisfied'\n /** Knowingly not done. A closed item with a scar. */\n | 'accepted-debt'\n /** Decided against. */\n | 'rejected'\n /**\n * The premise evaporated, so the question stopped being a question.\n * Load-bearing citations to an obviated item are stale premises.\n */\n | 'obviated';\n\nexport const DISPOSITIONS: readonly Disposition[] = Object.freeze([\n 'unresolved',\n 'narrowed',\n 'delegated',\n 'satisfied',\n 'accepted-debt',\n 'rejected',\n 'obviated',\n]);\n\n/** The openness implied by each disposition. */\nexport const OPENNESS_OF: Readonly<Record<Disposition, Openness>> = Object.freeze({\n unresolved: 'open',\n narrowed: 'partial',\n delegated: 'open',\n satisfied: 'closed',\n 'accepted-debt': 'closed',\n rejected: 'closed',\n obviated: 'closed',\n});\n\n/* -------------------------------------------------------------------------- */\n/* Edges */\n/* -------------------------------------------------------------------------- */\n\n/**\n * Relation kinds.\n *\n * The set is small on purpose. Two orthogonal properties - declared once in\n * {@link EDGE_TRAITS} - do the actual work, so adding a kind never means\n * touching a rule.\n */\nexport type EdgeKind =\n /** Structural: a document contains an item. Never inferred from prose. */\n | 'contains'\n /** A neutral citation. */\n | 'references'\n /** Explicitly *not* load-bearing: \"See also\", \"History\", \"Prior art\". */\n | 'relates-to'\n /** The source design depends on the target content. */\n | 'depends-on'\n /** The source rests on a claim made by the target. */\n | 'assumes'\n /** The source hands an obligation to the target. */\n | 'delegates-to'\n /** The source cannot proceed until the target does. */\n | 'blocked-by'\n /** The source replaces the target. */\n | 'supersedes'\n /** The source modifies the target without replacing it. */\n | 'amends';\n\nexport interface EdgeTraits {\n /**\n * Does the source continued validity depend on what the target says?\n *\n * Load-bearing edges into retired documents are stale premises. Non\n * load-bearing ones (\"see also\") are just history, and reporting them would\n * bury the real findings - which is exactly how ad-hoc linters lose trust.\n */\n readonly loadBearing: boolean;\n /** Does the edge move an obligation from source to target? */\n readonly transfersObligation: boolean;\n /** Does the edge assert something about the target lifecycle? */\n readonly lifecycle: boolean;\n /** Human-readable phrase used in diagnostics: \"ADR-1 <phrase> ADR-2\". */\n readonly phrase: string;\n}\n\nexport const EDGE_TRAITS: Readonly<Record<EdgeKind, EdgeTraits>> = Object.freeze({\n contains: { loadBearing: false, transfersObligation: false, lifecycle: false, phrase: 'contains' },\n references: { loadBearing: false, transfersObligation: false, lifecycle: false, phrase: 'references' },\n 'relates-to': { loadBearing: false, transfersObligation: false, lifecycle: false, phrase: 'relates to' },\n 'depends-on': { loadBearing: true, transfersObligation: false, lifecycle: false, phrase: 'depends on' },\n assumes: { loadBearing: true, transfersObligation: false, lifecycle: false, phrase: 'assumes' },\n 'delegates-to': { loadBearing: false, transfersObligation: true, lifecycle: false, phrase: 'delegates to' },\n 'blocked-by': { loadBearing: true, transfersObligation: true, lifecycle: false, phrase: 'is blocked by' },\n supersedes: { loadBearing: false, transfersObligation: false, lifecycle: true, phrase: 'supersedes' },\n amends: { loadBearing: true, transfersObligation: false, lifecycle: true, phrase: 'amends' },\n});\n\nexport const EDGE_KINDS = Object.keys(EDGE_TRAITS) as readonly EdgeKind[];\n\n/** How an edge came to exist. Reported verbatim so findings are auditable. */\nexport type EdgeOrigin =\n /** A `<!-- @spec-edge -->` directive. */\n | 'directive'\n /** A front-matter field such as `supersedes:` or `superseded-by:`. */\n | 'front-matter'\n /** A Markdown link, classified by the section it sits in. */\n | 'link'\n /** A bare textual identifier such as `ADR-0007` in prose. */\n | 'text'\n /** Synthesised by the extractor (document -> item). */\n | 'structural';\n\nexport interface Edge {\n readonly kind: EdgeKind;\n /** Node id. */\n readonly from: string;\n /** Node id. */\n readonly to: string;\n readonly origin: EdgeOrigin;\n /**\n * Where the relation was written down. Not necessarily inside `from`: a\n * `superseded-by: ADR-9` field in ADR-3 declares `ADR-9 supersedes ADR-3`,\n * and the only place a human can go fix it is ADR-3.\n */\n readonly declaredAt: SourceRef;\n /** The raw text that produced the edge, for diagnostics. */\n readonly raw: string;\n /**\n * Every file that declares this relation.\n *\n * A supersession stated only in the superseding document leaves the\n * superseded one silently presenting itself as current, which is a finding in\n * its own right - so the set of declaration sites has to survive de-duplication.\n */\n readonly declaredIn: readonly string[];\n /** True when `from` and `to` are the same node. Kept, never traversed. */\n readonly reflexive: boolean;\n}\n\n/* -------------------------------------------------------------------------- */\n/* Nodes */\n/* -------------------------------------------------------------------------- */\n\nexport type NodeKind = 'document' | 'item';\n\ninterface NodeBase {\n /** Canonical, graph-unique. Documents: `ADR-0007`. Items: `ADR-0007#q.1`. */\n readonly id: string;\n readonly kind: NodeKind;\n /** Short human label used in reports. */\n readonly title: string;\n readonly at: SourceRef;\n}\n\nexport interface DocumentNode extends NodeBase {\n readonly kind: 'document';\n /** Repository-relative POSIX path. */\n readonly path: string;\n /** Every spelling that resolves to this document, lowercased. */\n readonly aliases: readonly string[];\n readonly phase: Phase;\n /** The status string as written, before normalisation. `null` if absent. */\n readonly rawStatus: string | null;\n /** Where the status was declared, when it was declared anywhere. */\n readonly statusAt: SourceRef | null;\n /** Front-matter fields, flattened to strings and string arrays. */\n readonly frontMatter: Readonly<Record<string, string | readonly string[]>>;\n}\n\nexport interface ItemNode extends NodeBase {\n readonly kind: 'item';\n /**\n * Whether the id was written by an author rather than numbered by position.\n *\n * A numbered id is the item's ordinal within its section, so inserting one\n * item renumbers every item after it. Only a declared id names the same item\n * in two states of a repository (ADR-0020).\n */\n readonly declared: boolean;\n /** Owning document id. */\n readonly document: string;\n /** Heading path the item sits under, outermost first. */\n readonly section: readonly string[];\n /** First line of the item, normalised to one line for reports. */\n readonly text: string;\n /** The full multi-line body, continuations included. */\n readonly body: string;\n readonly disposition: Disposition;\n readonly openness: Openness;\n /** Which signal decided the disposition. */\n readonly evidence: StateSignal;\n /** Conflicting signals found while resolving the state. */\n readonly conflicts: readonly StateSignal[];\n}\n\nexport type SpecNode = DocumentNode | ItemNode;\n\n/** One observation about an item state. */\nexport interface StateSignal {\n /** Higher wins. See `SIGNAL_PRIORITY` in `state.ts`. */\n readonly source: 'directive' | 'marker' | 'strikethrough' | 'checkbox' | 'section' | 'default';\n readonly disposition: Disposition;\n /** The literal text that produced the signal. */\n readonly raw: string;\n readonly at: SourceRef;\n}\n\n/* -------------------------------------------------------------------------- */\n/* Unresolved references */\n/* -------------------------------------------------------------------------- */\n\nexport type DanglingReason =\n /** Nothing in the corpus answers to this identifier. */\n | 'unknown-target'\n /** More than one document answers to it. */\n | 'ambiguous'\n /** The path exists on disk but is not a parsed specification. */\n | 'not-a-spec'\n /** The document resolved, but the `#anchor` did not. */\n | 'unknown-anchor';\n\n/** A citation that failed foreign-key validation. */\nexport interface DanglingRef {\n readonly kind: EdgeKind;\n readonly from: string;\n /** The identifier as written. */\n readonly target: string;\n readonly reason: DanglingReason;\n readonly origin: EdgeOrigin;\n readonly declaredAt: SourceRef;\n readonly raw: string;\n /** Candidate ids when `reason` is `ambiguous`, or near-misses otherwise. */\n readonly candidates: readonly string[];\n}\n\n/**\n * A citation that failed to resolve and that configuration told us to ignore.\n *\n * Kept so a repository can audit its own silence. `ignoreReferences` and\n * `ignoreFamilies` are the two places a team can tell spec-graph to stop\n * reporting something, and a single over-broad glob can quietly turn the whole\n * check off - which looks exactly like a clean repository. The opportunistic\n * rule from [ADR-0004] is deliberately not counted here: reading `SHA-256` as\n * prose is spec-graph's own judgement, not a decision anybody wrote down, and\n * listing it would bury the handful of entries that are. See ADR-0008.\n */\nexport interface SuppressedRef {\n /** The identifier as written. */\n readonly target: string;\n /** The document that wrote it. */\n readonly from: string;\n readonly at: SourceRef;\n /** Which declaration silenced it. */\n readonly by: 'reference' | 'family';\n}\n\n/**\n * A front-matter key that reads as a relation and is not one.\n *\n * Front matter is an open vocabulary - `title`, `tags`, `sidebar_position` and\n * whatever else the site generator wants all live there - so an unrecognised key\n * is normally none of spec-graph's business. This is the exception: a key one\n * character away from a relation, carrying something shaped like a citation, is\n * a relation the author believes they declared. The edge does not exist, and\n * nothing else would ever say so.\n */\nexport interface MisreadKey {\n /** The key as written. */\n readonly key: string;\n /** The relation key it is a hair away from. */\n readonly suggestion: string;\n /** The document the key was written in. */\n readonly from: string;\n readonly at: SourceRef;\n}\n\n/* -------------------------------------------------------------------------- */\n/* Diagnostics */\n/* -------------------------------------------------------------------------- */\n\nexport type Severity = 'error' | 'warn' | 'info' | 'off';\n\nexport type RuleId =\n | 'ghost-handover'\n | 'stale-premise'\n | 'broken-reference'\n | 'reference-outside-corpus'\n | 'ambiguous-reference'\n | 'circular-delegation'\n | 'orphaned-obligation'\n | 'unreciprocated-supersession'\n | 'live-supersession'\n | 'state-conflict'\n | 'self-reference'\n | 'unknown-relation-key';\n\n/**\n * A rule a repository wrote for itself, as a selector plus a sentence.\n *\n * Namespaced, and the namespace is load-bearing rather than decorative. A\n * built-in id can never contain a colon, so a project rule can never collide\n * with one however it is named - which means `--rule`, the severity table, a\n * baseline entry and a SARIF `ruleId` all keep working without anyone having to\n * reserve a vocabulary. See ADR-0016.\n */\nexport type ProjectRuleId = `project:${string}`;\n\n/** Any rule that can produce a finding: built in, or written by a repository. */\nexport type AnyRuleId = RuleId | ProjectRuleId;\n\n/** True for an id in the project namespace, as opposed to a built-in. */\nexport function isProjectRule(rule: AnyRuleId): rule is ProjectRuleId {\n return rule.startsWith(PROJECT_RULE_PREFIX);\n}\n\nexport const PROJECT_RULE_PREFIX = 'project:';\n\nexport interface RelatedLocation {\n readonly at: SourceRef;\n readonly note: string;\n}\n\nexport interface Diagnostic {\n readonly rule: AnyRuleId;\n readonly severity: Exclude<Severity, 'off'>;\n /** One line, no trailing period. The headline of the finding. */\n readonly message: string;\n /** Where a human should go to fix it. */\n readonly at: SourceRef;\n /** Node ids involved, most relevant first. */\n readonly nodes: readonly string[];\n /**\n * What within the document this finding is about, where that is not a node.\n *\n * `null` everywhere else, where `nodes` already names the subject. It exists\n * because a broken reference names something that is not a node - that is\n * what makes it broken - and a baseline still has to be able to tell one\n * broken reference from another. A misread front-matter key is the same\n * shape: a spelling, not a node. See ADR-0012.\n */\n readonly target: string | null;\n /** Supporting locations: the other end of an edge, a cycle members. */\n readonly related: readonly RelatedLocation[];\n /** Concrete next action. */\n readonly hint: string;\n}\n\n/* -------------------------------------------------------------------------- */\n/* Analysis output */\n/* -------------------------------------------------------------------------- */\n\n/** A problem with the input itself, as opposed to a finding about the graph. */\nexport interface ParseProblem {\n readonly message: string;\n readonly at: SourceRef;\n}\n\nexport interface AnalysisSummary {\n readonly documents: number;\n readonly items: number;\n readonly edges: number;\n readonly openObligations: number;\n readonly errors: number;\n readonly warnings: number;\n readonly infos: number;\n /** Milliseconds, wall clock. */\n readonly durationMs: number;\n}\n"]}
|
package/package.json
CHANGED