{"id":50493668,"url":"https://github.com/asmuelle/spec-drift","last_synced_at":"2026-06-02T05:02:37.116Z","repository":{"id":353353859,"uuid":"1217877906","full_name":"asmuelle/spec-drift","owner":"asmuelle","description":null,"archived":false,"fork":false,"pushed_at":"2026-05-21T21:46:11.000Z","size":266,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-05-22T06:57:48.662Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"Rust","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/asmuelle.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2026-04-22T10:01:06.000Z","updated_at":"2026-05-21T21:46:15.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/asmuelle/spec-drift","commit_stats":null,"previous_names":["asmuelle/spec-drift"],"tags_count":2,"template":false,"template_full_name":null,"purl":"pkg:github/asmuelle/spec-drift","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/asmuelle%2Fspec-drift","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/asmuelle%2Fspec-drift/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/asmuelle%2Fspec-drift/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/asmuelle%2Fspec-drift/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/asmuelle","download_url":"https://codeload.github.com/asmuelle/spec-drift/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/asmuelle%2Fspec-drift/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":33806987,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-26T15:22:16.424Z","status":"online","status_checked_at":"2026-06-02T02:00:07.132Z","response_time":109,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2026-06-02T05:02:36.461Z","updated_at":"2026-06-02T05:02:37.110Z","avatar_url":"https://github.com/asmuelle.png","language":"Rust","funding_links":[],"categories":[],"sub_categories":[],"readme":"# spec-drift\n*Semantic Coherence Analysis between Specification and Implementation*\n\n[![Rust CI](https://github.com/asmuelle/spec-drift/actions/workflows/rust.yml/badge.svg?branch=main)](https://github.com/asmuelle/spec-drift/actions/workflows/rust.yml)\n[![License: MIT OR Apache-2.0](https://img.shields.io/badge/license-MIT%20OR%20Apache--2.0-blue.svg)](#license)\n[![Rust edition](https://img.shields.io/badge/rust-2024%20%7C%201.95%2B-orange.svg)](https://doc.rust-lang.org/edition-guide/rust-2024/index.html)\n\n## 1. Core Philosophy\n`spec-drift` operates on the principle of **Single Source of Truth (SSOT) Verification**. It treats documentation, examples, and CI configs as \"executable specifications.\" When the code changes, the specification must evolve, or it becomes a \"lie.\"\n\n`spec-drift` detects these \"lies\" by cross-referencing the semantic claims made in natural language (Markdown) against the structural reality of the Rust codebase.\n\n---\n\n## 2. The Coherence Pillars\n\n`spec-drift` analyzes four primary surfaces to detect divergence:\n\n### A. Documentation Drift (`README.md`, `AGENTS.md`, `docs/`)\n*   **The Check:** Extracts mentioned function names, types, and architectural constraints from Markdown.\n*   **Drift Detection:**\n    *   **Symbol Absence:** The `README` mentions `fn connect_to_db()`, but that function was renamed to `fn init_connection()`.\n    *   **Constraint Violation:** `AGENTS.md` states *\"All API handlers must return a `Result\u003cT, ApiError\u003e`\"*, but a new handler is found returning `Option\u003cT\u003e`.\n    *   **Outdated Logic:** The docs describe a 3-step auth flow, but the code now implements a 2-step flow.\n\n### B. Example Drift (`examples/*.rs`)\n*   **The Check:** This is the \"Hard Truth\" check. It attempts to compile examples against the current library version.\n*   **Drift Detection:**\n    *   **Compilation Failure:** An example fails to compile because a public API changed.\n    *   **Deprecated Usage:** The example uses a function marked with `#[deprecated]`.\n    *   **Logic Gap:** The example demonstrates a feature that has been removed or fundamentally altered in the core logic.\n\n### C. Test-Spec Drift (`tests/*.rs`, `#[test]`)\n*   **The Check:** Compares the *intent* described in test names/doc-comments with the *assertion* logic.\n*   **Drift Detection:**\n    *   **The \"Lying Test\":** A test named `test_user_cannot_access_admin_panel` exists, but the assertion inside is commented out or merely checks for a `200 OK` instead of a `403 Forbidden`.\n    *   **Missing Coverage:** The `README` claims the tool \"supports concurrent writes,\" but no tests in the `tests/` directory exercise concurrency.\n\n### D. CI/Infrastructure Drift (`.github/workflows/`, `Makefile`, `justfile`)\n*   **The Check:** Matches the build/test commands in CI against the actual project structure.\n*   **Drift Detection:**\n    *   **Ghost Commands:** CI runs `cargo test --package legacy_crate`, but `legacy_crate` was merged into the main crate.\n    *   **Environment Mismatch:** The `README` says the project requires `libssl-dev`, but the CI workflow is using a container that provides `openssl-devel`.\n\n---\n\n## 3. How It Works\n\n`spec-drift` follows a ports-and-adapters (hexagonal) architecture. The domain model knows nothing about filesystems, parsers, or output formats — every I/O concern lives behind a trait at the edge.\n\n```\n┌──────────────────────────────────────────────────────────────────┐\n│                            spec-drift                            │\n│                                                                  │\n│   Sources ──▶ Parsers ──▶ Domain Model ──▶ Analyzers             │\n│   (adapters) (adapters)  (core, pure)      (use cases)           │\n│                                │                                 │\n│                                ▼                                 │\n│                            Reporters                             │\n│                            (adapters)                            │\n└──────────────────────────────────────────────────────────────────┘\n```\n\n### Layers\n\n| Layer | Responsibility | Key types / crates |\n|---|---|---|\n| **Sources**   | Enumerate project files; optionally diff against git `HEAD`.              | `FsWalker`, `GitHistory` (`ignore`, `git2`)                                     |\n| **Parsers**   | Translate raw bytes into structured facts. Pure, cached per file.         | `syn` (`full`), `pulldown-cmark`, `serde_yaml`, `regex`                         |\n| **Domain**    | Pure types. No I/O, no globals.                                           | `SpecClaim`, `CodeFact`, `Divergence`, `Severity`, `RuleId`, `Location`         |\n| **Analyzers** | Implement `trait DriftAnalyzer`. Independent, parallel-safe under `rayon`.| `DocsAnalyzer`, `ExamplesAnalyzer`, `TestsAnalyzer`, `CiAnalyzer`               |\n| **Reporters** | Serialize `Vec\u003cDivergence\u003e` to an output format.                          | `HumanReporter`, `JsonReporter`, `SarifReporter`, `FixPromptReporter`           |\n\n### The core contract\n\n```rust\npub trait DriftAnalyzer {\n    fn id(\u0026self) -\u003e \u0026'static str;\n    fn analyze(\u0026self, ctx: \u0026ProjectContext) -\u003e Vec\u003cDivergence\u003e;\n}\n\npub struct Divergence {\n    pub rule:     RuleId,\n    pub severity: Severity,\n    pub location: Location,\n    pub stated:   String,\n    pub reality:  String,\n    pub risk:     String,\n}\n```\n\nAnalyzers are independent by construction and run in parallel. Parsed ASTs are cached on `ProjectContext` so each source file is parsed at most once per run. Errors use a single `SpecDriftError` enum (`thiserror`) at library boundaries; the CLI crate wraps with `anyhow`.\n\n---\n\n## 4. Detection Confidence Matrix\n\nNot every drift check is deterministic. The matrix below states the mechanism and confidence level of each rule, so users and CI gates can calibrate trust.\n\n| Pillar    | Rule                   | Mechanism                                                                                  | Confidence              |\n|-----------|------------------------|--------------------------------------------------------------------------------------------|-------------------------|\n| Docs      | `symbol_absence`       | `syn` AST lookup for symbols mentioned in Markdown code spans.                             | **Deterministic**       |\n| Docs      | `constraint_violation` | User-authored rule DSL (e.g. *\"all handlers return `Result\u003c_, ApiError\u003e`\"*) checked on AST.| **Heuristic**           |\n| Docs      | `outdated_logic`       | LLM summarization + structural comparison.                                                 | **Experimental (LLM)**  |\n| Examples  | `compile_failure`      | Thin wrapper over `cargo check --examples --message-format=json`.                          | **Deterministic**       |\n| Examples  | `deprecated_usage`     | `cargo clippy` `deprecated` lint re-framed as drift.                                       | **Deterministic**       |\n| Examples  | `logic_gap`            | LLM comparison of example narrative vs. current API surface.                               | **Experimental (LLM)**  |\n| Tests     | `lying_test`           | Parse test-name intent (negative / positive / status class) vs. `assert!`/`assert_eq!` bodies. | **Heuristic**       |\n| Tests     | `missing_coverage`     | README capability claims vs. test-corpus symbol coverage.                                  | **Heuristic**           |\n| CI        | `ghost_command`        | Parse workflows / `Makefile` / `justfile`; cross-reference `cargo metadata`.               | **Deterministic**       |\n| CI        | `env_mismatch`         | Normalize named deps (e.g. `libssl-dev` ≡ `openssl-devel`) and match CI image manifest.    | **Heuristic**           |\n\nConfidence levels map to defaults:\n\n- **Deterministic** — enabled by default, failures are actionable verdicts.\n- **Heuristic** — enabled by default, reported at `warning` or lower. False positives are expected; inline ignores exist for a reason.\n- **Experimental (LLM)** — **opt-in only**. Requires `[llm] enabled = true` and network/credential access. `--no-llm` disables globally.\n\n`--strict` promotes every heuristic rule one severity level.\n\n---\n\n## 5. CLI Interface (UX)\n\n```bash\n# Analyze the entire project for coherence\nspec-drift\n\n# Focus a single pillar\nspec-drift --docs\nspec-drift --examples\n\n# Generate a \"Correction Prompt\" for the AI to fix the drift\nspec-drift --fix-prompt\n\n# CI integration\nspec-drift --format sarif --deny warning --baseline .spec-drift.baseline.json\n```\n\n### Flags\n\n| Flag                                      | Purpose                                                                 |\n|-------------------------------------------|-------------------------------------------------------------------------|\n| `--docs`, `--examples`, `--tests`, `--ci` | Run a single pillar.                                                    |\n| `--format {human,json,sarif}`             | Output format. Default `human`.                                         |\n| `--deny \u003cseverity\u003e`                       | Exit non-zero when divergences at or above `\u003cseverity\u003e` exist.          |\n| `--baseline \u003cfile\u003e`                       | Accept existing divergences; fail only on *new* drift.                  |\n| `--config \u003cpath\u003e`                         | Path to `spec-drift.toml`. Default: walk up from CWD.                   |\n| `--fix-prompt`                            | Emit a structured correction prompt instead of a report.                |\n| `--strict`                                | Promote heuristic rules one severity level.                             |\n| `--diff \u003cref\u003e`                            | Focus on files changed since the given git ref and drift induced by changed code. |\n| `--blame`                                 | Attribute each divergence to the commit/author/date that wrote the line.|\n| `--package \u003cname\u003e`                        | Restrict analysis to one member of a cargo workspace.                   |\n| `--no-llm`                                | Disable all LLM-backed checks regardless of config.                     |\n\n### Exit codes\n\n- `0` — no divergences at or above `--deny` threshold.\n- `1` — divergences found.\n- `2` — tool error (bad config, parse failure, I/O).\n\n### Example Output\n```text\n📉 SPEC DRIFT REPORT: [3 Divergences Found]\n\n❌ CRITICAL: symbol_absence\n- File: README.md (Line 42)\n- Stated: `Client::new` exists in the codebase\n- Reality: no symbol named `new` found in the parsed Rust sources\n- Risk: New developers and AI agents will reach for a non-existent API.\n- Blame: abc1234 Ada Lovelace (2024-01-02): Initial README\n\n⚠️  WARNING: ghost_command\n- File: .github/workflows/ci.yml (Line 14)\n- Stated: CI runs `cargo` against package `legacy_crate`\n- Reality: `legacy_crate` is not a member of the workspace\n- Risk: CI exercises a target that no longer exists; the step is a no-op at best.\n\n🟡 NOTICE: missing_coverage\n- File: README.md (Line 8)\n- Stated: `place_order` is a capability the project exposes\n- Reality: no test references `place_order` by name\n- Risk: Capability claimed in the docs has no guard-rail in tests.\n```\n\n---\n\n## GitHub Action\n\nRun `spec-drift` in CI and publish results to GitHub code scanning:\n\n```yaml\nname: spec-drift\non: [push, pull_request]\njobs:\n  drift:\n    runs-on: ubuntu-latest\n    permissions:\n      contents: read\n      security-events: write\n    steps:\n      - uses: actions/checkout@v4\n        with: { fetch-depth: 0 }  # --blame needs full history\n      - uses: asmuelle/spec-drift@main\n        with:\n          format: sarif\n          output: spec-drift.sarif\n          args: --blame --deny warning\n      - uses: github/codeql-action/upload-sarif@v4\n        if: always()\n        with:\n          sarif_file: spec-drift.sarif\n          category: spec-drift\n```\n\n### Action inputs\n\n| Input                | Default  | Purpose                                                                |\n|----------------------|----------|------------------------------------------------------------------------|\n| `version`            | `v0.2.0` | git ref (branch, tag, or SHA) to install.                              |\n| `format`             | `human`  | Output format: `human`, `json`, or `sarif`.                            |\n| `output`             | *(stdout)* | File path to write output to.                                         |\n| `deny`               | `notice` | Fail the step when divergences at or above this severity exist.        |\n| `args`               | *(empty)* | Extra arguments forwarded to `spec-drift` (e.g. `--blame --strict`). |\n| `working-directory`  | `.`      | Directory the scan runs in.                                            |\n| `anthropic-api-key`  | *(empty)* | Enables LLM-backed rules when combined with `[llm] enabled = true`. |\n\n---\n\n## 6. Configuration\n\nProject-level config lives in `spec-drift.toml` at the project root. Every rule can be silenced in config, or inline at the source.\n\n### `spec-drift.toml`\n\n```toml\n[severity]\n# Deterministic\nsymbol_absence       = \"critical\"\ncompile_failure      = \"critical\"\nghost_command        = \"warning\"\ndeprecated_usage     = \"warning\"\n# Heuristic\nlying_test           = \"critical\"\nconstraint_violation = \"warning\"\nenv_mismatch         = \"notice\"\nmissing_coverage     = \"notice\"\n# Experimental (LLM)\noutdated_logic       = \"notice\"\nlogic_gap            = \"notice\"\n\n[ignore]\nrules   = [\"outdated_logic\"]\npaths   = [\"docs/legacy/**\", \"examples/archived/**\"]\nsymbols = [\"internal_*\", \"*_deprecated\"]\n\n[llm]\nenabled   = false              # opt-in; --no-llm always wins\nprovider  = \"anthropic\"        # anthropic | openai | local\nmodel     = \"claude-sonnet-4-6\"\nmax_calls = 50                 # per run; fail closed when exceeded\ntimeout_s = 30\n\n# User-authored structural rules parsed from AGENTS.md or declared here.\n[[rules.constraint_violation]]\nname        = \"handlers_return_result\"\nglob        = \"src/handlers/**\"\nreturn_type = \"Result\u003c_, ApiError\u003e\"\n```\n\n### Inline ignores\n\nInline ignores must name the rule. A blanket ignore with no rule ID is rejected — silence must be specific and auditable.\n\n```rust\n// Rust: accept a specific divergence on the next item.\n#[allow(spec_drift::symbol_absence)]\npub fn legacy_shim() {}\n```\n\n```markdown\n\u003c!-- spec-drift: ignore-rule symbol_absence --\u003e\nUse `Client::new()` to connect.\n```\n\n```yaml\n# .github/workflows/ci.yml\n# spec-drift: ignore-rule ghost_command\n- run: cargo test --package legacy_crate\n```\n\n### Baselines\n\n`--baseline \u003cfile\u003e` snapshots the current set of divergences. Subsequent runs only fail on *new* drift. Use this to adopt `spec-drift` on a legacy repo without a flag-day cleanup:\n\n```bash\nspec-drift --format json \u003e .spec-drift.baseline.json\nspec-drift --baseline .spec-drift.baseline.json --deny warning\n```\n\n---\n\n## 7. The \"Vibe Coding\" Grand Architecture\n\n`spec-drift` is the final stage of a complete **AI-Native Development Lifecycle (AIDL)**. Each tool owns one failure mode of AI-assisted coding.\n\n| Stage         | Tool             | Responsibility          | Purpose                                                          |\n| :------------ | :--------------- | :---------------------- | :--------------------------------------------------------------- |\n| **1. Input**  | `cargo-context`  | Context Engineering     | Ensure the AI is \"smart\" enough to start.                        |\n| **2. Filter** | `diff-risk`      | Semantic Guardrails     | Prevent the AI from introducing \"silent\" disasters.              |\n| **3. Verify** | `cargo-impact`   | Blast Radius Analysis   | Prove the change works and name everything it touches.           |\n| **4. Align**  | `spec-drift`     | Coherence Verification  | Keep docs, tests, examples, and CI honest with the code.         |\n\n### How the stages compose\n\n```\n            ┌──────────────┐   ┌────────────┐   ┌──────────────┐   ┌──────────────┐\n  intent ─▶ │ cargo-context│─▶ │ diff-risk  │─▶ │ cargo-impact │─▶ │  spec-drift  │ ─▶ merge\n            └──────────────┘   └────────────┘   └──────────────┘   └──────────────┘\n              Load smarts        Block bad         Prove correct       Keep honest\n```\n\n- `cargo-context` gives the agent the right **information**.\n- `diff-risk` vetoes the wrong **change**.\n- `cargo-impact` confirms the right **result**.\n- `spec-drift` keeps the **story** the repo tells about itself true.\n\nSkip any stage and drift creeps back in somewhere else. `spec-drift` is the last line of defense: once the code is right, it makes sure the rest of the repo agrees.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fasmuelle%2Fspec-drift","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fasmuelle%2Fspec-drift","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fasmuelle%2Fspec-drift/lists"}