feat(cli): saga lint — spec v1.0 conformance validator (#19)#73
Merged
Conversation
New `saga lint` subcommand walks every topic in active layers and validates
against Saga Topic Spec v1.0. Closes the Level-2 conformance gate for
v1.0.0-rc.1 ("MUST implement lint covering: dangling relations, cycle
detection, type validity, sensitivity defaults" — spec §10).
Rules implemented (11 categories, all with stable JSON `category` strings
that tooling can filter on):
- `parse-error` — frontmatter doesn't parse (exit code 2)
- `missing-field` — required field absent (id, scope, type, title)
- `invalid-type` — type not in spec §4 vocabulary
- `invalid-enum` — confidence / lifecycle / provenance /
memory_family / operator_surface / sensitivity
outside the spec §5 allowed values
- `scope-mismatch` — frontmatter scope ≠ layer meta.yml scope (§2.1)
- `unknown-operator` — relation op not in spec §6.2 (accepted as opaque)
- `dangling-relation` — target resolves to no topic in active layers (§6.1)
- `cycle` — @supersedes or @derived_from cycle (§6.3)
- `slug-mismatch` — filename slug diverges from title-derived slug
AND filename slug isn't in synonyms (§1.1)
- `duplicate-id` — two topics share an `id` (§1.1: "never reused")
- `missing-recommended` — recommended frontmatter (currently `confidence`)
missing; `--fix` inserts spec §5.1 default
Flags:
- `--scope <name>` — restrict to a single layer scope
- `--fix` — apply safe insertions (currently only the
recommended `confidence: tentative` default).
Never touches body or relations.
- `--format human|json` — JSON for tooling; human for terminals
Exit codes: 0 clean / 1 findings / 2 parse errors.
15 unit tests covering every category + the --fix round-trip + --scope filter
(against synthetic layers in t.TempDir, no I/O outside the test dir).
Smoke-tested against the maintainer's real personal layer (48 notes): 0 parse
errors, 33 slug-mismatch warnings (titles evolved post-rename), 2 invalid-enum
errors (`confidence: "validated"` — drift caught, will be fixed at the data
layer, not in this PR).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Bundle F of the v1.0.0-rc.1 plan — closes the last Sprint-0 issue and the Level-2 conformance gate.
saga lintwalks every topic in active layers and validates against Saga Topic Spec v1.0. It's the missing piece for spec §10 Level 2 ("MUST implement lint covering: dangling relations, cycle detection, type validity, sensitivity defaults").Rule coverage
Eleven diagnostic categories, with stable JSON
categorystrings that tooling can filter on:parse-errormissing-fieldinvalid-typeinvalid-enumscope-mismatchunknown-operatordangling-relationcycleslug-mismatchduplicate-idmissing-recommendedFlags
--scope <name>— restrict to a single layer scope--fix— apply safe insertions only (currentlyconfidence: tentativeper spec §5.1 default). Never touches body or relations.--format human|json— JSON for tooling, human for terminalsExit codes: 0 clean / 1 findings / 2 parse errors.
Smoke run against the maintainer's real personal layer (48 notes)
That's the point of a linter: surface drift, let the operator decide.
Test plan
Closes #19