Repository navigation
feat: introduce deterministic canon linter for static canon validation #36
Description
Problem & Motivation
Canon Clerk is designed to enforce semantic, natural language architectural invariants via LLM evaluation cascades. However, foundational syntactic, structural, schema, and naming conventions do not require LLMs—they can and should be validated deterministically using standard static analysis (regex, YAML parsers, and file system checks).
Currently, authors writing canons can easily introduce silent defects without immediate feedback:
- Typos in YAML frontmatter keys or incorrect value types.
- Broken references to local documentation or specification files.
- Inconsistent naming conventions (e.g., camelCase filenames, duplicate numeric prefixes).
- Non-canon files inadvertently committed inside
.canons/directories.
Running a fast, offline, deterministic static linter in pre-commit hooks and CI prior to LLM evaluation saves tokens, reduces latency, and provides instantaneous feedback to canon authors.
Proposed Solution
Introduce a deterministic Canon Linter subproject / package that statically scans repository .canons/ directories without requiring LLM connections or external API credentials.
1. ESLint-Aligned Architecture & Sensitivity Controls
Following industry-standard linter conventions established by ESLint:
-
Severity Levels: Every rule supports three standard levels:
-
"off"(or0): Rule is disabled. -
"warn"(or1): Emits a warning; does not fail the process by default. -
"error"(or2): Emits an error; triggers non-zero exit code (1).
-
-
Default Exit Code Behavior:
- Clean or warnings only
$\rightarrow$ Exit0(Success). - One or more errors
$\rightarrow$ Exit1(Failure).
- Clean or warnings only
-
Sensitivity Control (
--max-warnings <n>):- Adopts ESLint's ratchet model: if emitted warnings exceed
<n>, the linter exits with code1. - Enables strict zero-tolerance CI gates via
canon-clerk lint --max-warnings 0.
- Adopts ESLint's ratchet model: if emitted warnings exceed
-
Quiet Mode (
--quiet):- Suppresses warning output entirely, reporting only errors.
2. Deep Monorepo Support: Cascading & "root" Breakout
To support complex monorepos, the linter's configuration discovery must mirror the hierarchical inheritance of tsconfig.json and ESLint:
- Hierarchical Cascade & Inheritance:
- The linter traverses upward from each discovered
.canons/directory to locate configuration files (e.g.,.canonlintrc.json). - Subdirectory configurations automatically inherit and merge rules from parent configurations.
- Specific rule severities can be overridden locally without redefining the entire rule catalog.
- Optional explicit
"extends": "<path>"support for sharing base configurations across monorepo packages.
- The linter traverses upward from each discovered
- Breakout
"root": trueBoundary:- Setting
"root": truein a package-level configuration stops the upward directory traversal. - Enables isolated monorepo packages, submodules, or microservices to enforce their own canon standards without inheriting rules from the monorepo root.
- Setting
3. Default Rule Catalog
| Scope | Rule | Default Severity | Description |
|---|---|---|---|
| Directory | no-non-canon-files |
warn |
Flags non-canon or non-markdown files placed inside .canons/ directories. |
| File Naming | kebab-case-filenames |
warn |
Requires canon filenames to adhere to lower kebab-case. |
| File Naming | unique-numeric-prefixes |
warn |
Detects duplicate ordering prefixes (e.g. two 0001-* files in the same directory). |
| Frontmatter | valid-yaml-frontmatter |
error |
Ensures opening/closing --- delimiters and valid YAML mapping syntax. |
| Frontmatter | no-unrecognized-keys |
warn |
Flags unrecognized frontmatter keys not defined in SPEC.md. |
| Frontmatter | valid-frontmatter-types |
error |
Verifies reserved keys match schema types (id: string, paths: string[], inspect: string[], etc.). |
| Frontmatter | id-matches-filename |
warn |
Flags when frontmatter id diverges from the file stem slug. |
| References | valid-reference-targets |
error |
Verifies files referenced in frontmatter references: exist on disk. |
| References | links-declared-in-references |
warn |
Flags relative Markdown body links that are not declared in references: frontmatter. |
4. CLI Interface
# Standard local run (warns on advisories, fails on errors)
canon-clerk lint
# Strict CI mode (ratchet zero warnings)
canon-clerk lint --max-warnings 0
# Quiet CI run (errors only)
canon-clerk lint --quietAcceptance Criteria
- Core deterministic scanning engine without LLM dependencies.
- ESLint-aligned severity matrix (
off,warn,error) and exit code handling. - Sensitivity controls implemented via
--max-warnings <n>and--quiet. - Hierarchical configuration resolution with inheritance (
extends) and breakout ("root": true) boundary support. - Initial rule catalog covering directory hygiene, file naming, frontmatter schema, and reference integrity.
- Comprehensive unit test suite with positive and negative canon fixtures across nested monorepo structures.
Activity