Metadata-Version: 2.4
Name: grounded-lint
Version: 0.12.1
Summary: Find dangling references in code comments. Deterministic, offline, zero dependencies.
License: MIT License
        
        Copyright (c) 2026 gonisulaimann
        
        Permission is hereby granted, free of charge, to any person obtaining a copy
        of this software and associated documentation files (the "Software"), to deal
        in the Software without restriction, including without limitation the rights
        to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
        copies of the Software, and to permit persons to whom the Software is
        furnished to do so, subject to the following conditions:
        
        The above copyright notice and this permission notice shall be included in all
        copies or substantial portions of the Software.
        
        THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
        IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
        FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
        AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
        LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
        OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
        SOFTWARE.
        
Project-URL: Homepage, https://github.com/gonisulaimann/Grounded
Project-URL: Changelog, https://github.com/gonisulaimann/Grounded/blob/main/CHANGELOG.md
Keywords: lint,comments,documentation,static-analysis,tech-debt
Classifier: Development Status :: 3 - Alpha
Classifier: Intended Audience :: Developers
Classifier: License :: OSI Approved :: MIT License
Classifier: Programming Language :: Python :: 3
Classifier: Topic :: Software Development :: Quality Assurance
Requires-Python: >=3.10
Description-Content-Type: text/markdown
License-File: LICENSE
Dynamic: license-file

<h1 align="center">
    <a href="https://grounded.readthedocs.io">
        <picture>
          <source media="(prefers-color-scheme: dark)" srcset="https://raw.githubusercontent.com/gonisulaimann/Grounded/main/docs/assets/logo_white.png">
          <img alt="Grounded Logo" src="https://raw.githubusercontent.com/gonisulaimann/Grounded/main/docs/assets/logo_black.png" width="260">
        </picture>
    </a>
    <br>
    Grounded
    <br>
    <small>The 0.6ms Reference &amp; Import Integrity Firewall for AI Coding Agents</small>
</h1>

<p align="center">
    <a href="https://github.com/gonisulaimann/Grounded/actions/workflows/ci.yml"><img src="https://github.com/gonisulaimann/Grounded/actions/workflows/ci.yml/badge.svg" alt="CI"></a>
    <a href="https://badge.fury.io/py/grounded-lint"><img src="https://badge.fury.io/py/grounded-lint.svg" alt="PyPI version"></a>
    <a href="https://grounded.readthedocs.io/en/latest/"><img src="https://readthedocs.org/projects/grounded/badge/?version=latest" alt="Documentation Status"></a>
    <a href="https://github.com/gonisulaimann/homebrew-tap"><img src="https://img.shields.io/badge/Homebrew-gonisulaimann%2Ftap-blue.svg?logo=homebrew" alt="Homebrew"></a>
    <a href="LICENSE"><img src="https://img.shields.io/badge/License-MIT-yellow.svg" alt="License: MIT"></a>
    <a href="https://www.ko-fi.com/gonisulaiman"><img src="https://srv-cdn.himpfen.io/badges/kofi/kofi-flat.svg" alt="Ko-Fi"></a>
    <br/>
    <a href="https://pypi.org/project/grounded-lint/"><img src="https://img.shields.io/pypi/pyversions/grounded-lint.svg" alt="Python Versions"></a>
    <img src="https://img.shields.io/badge/Dependencies-0%20(stdlib)-brightgreen" alt="Zero Dependencies">
    <img src="https://img.shields.io/badge/Scan%20Latency-0.6ms-blueviolet" alt="0.6ms Latency">
</p>

<p align="center">
    <a href="https://grounded.readthedocs.io/en/latest/"><strong>Documentation</strong></a>
    &middot;
    <a href="https://grounded.readthedocs.io/en/latest/installation/"><strong>Installation</strong></a>
    &middot;
    <a href="https://grounded.readthedocs.io/en/latest/rules/"><strong>Rules &amp; Checkers</strong></a>
    &middot;
    <a href="https://grounded.readthedocs.io/en/latest/agents-lsp/"><strong>Agent Setup (LSP / MCP)</strong></a>
    &middot;
    <a href="https://grounded.readthedocs.io/en/latest/benchmarks/"><strong>Benchmarks</strong></a>
</p>

<p align="center">
    <a href="docs/README_AR.md">العربية</a>
    &middot;
    <a href="docs/README_ES.md">Español</a>
    &middot;
    <a href="docs/README_PT-BR.md">Português (BR)</a>
    &middot;
    <a href="docs/README_FR.md">Français</a>
    &middot;
    <a href="docs/README_DE.md">Deutsch</a>
    &middot;
    <a href="docs/README_CN.md">中文</a>
    &middot;
    <a href="docs/README_JP.md">日本語</a>
    &middot;
    <a href="docs/README_RU.md">Русский</a>
    &middot;
    <a href="docs/README_KR.md">한국어</a>
</p>

---

Catch hallucinated APIs, broken imports, and stale references in **0.6 milliseconds** before your test runner even boots. Zero dependencies. Pure standard library. Works across Python, JavaScript/TypeScript, Go, and C.

```console
$ grounded scan ./src
LIE src/app.py:9 [stale-symbol-ref] Comment references `ghost_service` which is not defined here
    claim: `ghost_service()`
    evidence: `ghost_service` is not defined, imported, or used in this file,
              and no definition was found in 84 indexed source files.
    fix: Update the comment to the current name, or remove the reference.
```

Zero dependencies. No network access. Works on Python, JavaScript/TypeScript, Go, and C.

## Install

### macOS & Linux (Homebrew)

```console
brew install gonisulaimann/tap/grounded
```

### Python Package (pip / uv)

```console
pip install grounded-lint
# or with uv
uv tool install grounded-lint
```

From source:

```console
git clone https://github.com/gonisulaimann/Grounded.git
cd Grounded
pip install -e .
```

## Usage

```console
grounded scan [PATH] [--format terminal|json|sarif|html] [--output FILE]
              [--fail-on lie|drift|smell|never]
              [--enable CHECKER,...] [--disable CHECKER,...]
              [--baseline FILE] [--show-baselined]
              [--changed [BASE]] [--cache [FILE]]
              [--config grounded.toml] [--no-color] [--quiet]
              [--jobs N]
grounded baseline [PATH] [--output FILE]  # record findings for delta gating
grounded fix [PATH] [--dry-run]  # rewrite unambiguous stale refs
grounded impact SYMBOL [PATH] [--format terminal|json]
                     # show everything touching a symbol: definers,
                     # importers, comment claims
grounded list [PATH]       # show files that would be scanned
grounded explain CHECKER   # describe a checker (including removed ones)
grounded init [--force]    # write a starter grounded.toml
grounded init-agent [--claude|--cursor|--aider] [--force] [--dry-run]
grounded mcp [--root .]    # MCP server over stdio for coding agents
grounded lsp               # LSP 3.17 server over stdio for editors
```

`grounded scan` exits with status `1` when any finding meets `--fail-on`
(default: `lie`), `0` otherwise. Point it at CI and gate on the default.

Example output formats for tooling: `--format json` for scripts,
`--format sarif` for GitHub code scanning, `--format html` for a
self-contained report page (no external assets, works opened from disk).

Large trees scan in parallel automatically (512+ files); `--jobs N`
overrides, `--jobs 1` forces serial. Output is identical either way.

In VS Code, wire the bundled problem matcher through a task
(`.vscode/tasks.json`, paths relative to the workspace):

```json
{
  "version": "2.0.0",
  "tasks": [
    {
      "label": "grounded",
      "type": "shell",
      "command": "grounded scan . --no-color",
      "problemMatcher": {
        "owner": "grounded",
        "pattern": [
          {
            "regexp": "^(LIE|DRIFT|SMELL)\\s+(.+?):(\\d+)\\s+\\[(.+?)\\]\\s+(.*)$",
            "file": 2,
            "line": 3,
            "code": 4,
            "message": 5
          }
        ]
      }
    }
  ]
}
```

## Adopting on an existing codebase

Two mechanisms, composable. Both keep the full-tree scan and filter
reporting only.

Record a baseline once, commit it, gate on the delta:

```console
grounded baseline . --output .grounded-baseline.json   # record today
git add .grounded-baseline.json
grounded scan . --baseline .grounded-baseline.json     # new findings only
```

Fingerprints cover checker, path, and claim text, not line numbers, so
unrelated edits do not churn the file. Editing the offending line itself
re-triggers the gate. `--show-baselined` lists suppressed findings.

Gate pull requests on changed lines only:

```console
grounded scan . --changed                # uncommitted work vs HEAD
grounded scan . --changed origin/main    # branch vs base (CI)
```

Untracked files are fully reported. Outside a git repo, or with an
unresolvable base, `--changed` exits `2` with the git error instead of
silently scanning everything.

Repeat scans go faster with `--cache` (per-file results keyed by
mtime and size, opt-in, never required):

```console
grounded scan . --cache                # writes .grounded-cache.json
```

A repeat full-tree scan reuses unchanged files; the index still rebuilds
from disk, so expect roughly a 2x speedup on large trees, not magic.
Corrupt or mismatched caches fall back to a full scan silently.

## Rules

| ID | Default severity | What it reports |
|---|---|---|
| `stale-symbol-ref` | lie (error) | A comment names a call that resolves nowhere: not defined in the repo, not imported in the file, not used in the file, not a builtin or keyword. Prints rename suggestions when a close match exists. |
| `stale-import` | lie (error) | A resolvable import whose module is missing, or whose name is not defined, re-exported, or a submodule there. Python `from`/`import`, JS/TS relative imports (tsconfig aliases resolved). Guarded, stdlib, and external imports never report. |
| `stale-file-ref` | lie (error) | A comment claims a path inside the repo tree that does not exist. References to other projects, frameworks, template namespaces, and placeholder paths are ignored. |
| `number-drift` | drift (warning) | A comment states a magic number (timeout, port, limit, threshold) that disagrees with adjacent code. |
| `fragile-anchor` | smell (note) | `line 42` anchors, `see above` / `see below` without a symbol, and workaround markers (`HACK`, `XXX`, `workaround`) with no ticket or expiry condition. |

A rule stays silent unless the contradiction is mechanical. Imported names,
standard library names, parameters, locals, attributes, docstring field
lists (`:param:`, `@param`), and illustrative examples ("For example …")
never produce findings.

## Configuration

`grounded init` writes a starter file. Settings also load from
`pyproject.toml` under `[tool.grounded]`.

```toml
# grounded.toml
disable = ["fragile-anchor"]
fail_on = "lie"
ignore_dirs = ["docs", "sandbox"]
ignore_files = ["generated.py"]

# JS/TS path aliases, repo-root-relative (tsconfig `paths` are picked
# up automatically per directory; these are the fallback).
# path_aliases = { "@/" = "src/", "~/" = "app/" }
```

Path aliases (`@/`, `~/`, and any tsconfig `paths` entries) resolve
against the nearest `tsconfig.json` (which may use comments and
`extends`). Unresolvable alias targets report as drift, never as lies:
they are often build-generated. Mappings into `node_modules` are
always skipped.

Suppress a single accepted finding where it sits (reviewable, local):

```python
# Calls `legacy_parse()` for old dumps.  # grounded-disable: stale-symbol-ref
```

```js
// Calls `legacyParse()` for old dumps.  // grounded-disable: stale-symbol-ref
```

## CI, pre-commit, and GitHub Action

Gate pull requests with the first-party Action (inline PR annotations
included via problem matchers):

```yaml
- uses: gonisulaimann/Grounded@v0.12.1
  with:
    changed-base: origin/main   # new findings on edited lines only
    fail-on: lie
```

Or with a baseline file for whole-tree delta gating:

```yaml
- uses: gonisulaimann/Grounded@v0.12.1
  with:
    baseline: .grounded-baseline.json
```

As a pre-commit hook (runs on uncommitted changes):

```yaml
repos:
  - repo: https://github.com/gonisulaimann/Grounded
    rev: v0.12.1
    hooks:
      - id: grounded
```

SARIF upload for code scanning: run with `--format sarif --output
results.sarif`, then upload with `github/codeql-action/upload-sarif`.

## Coding agents

`grounded scan <file>` checks one file (exit 1 on findings, 0 when clean),
which is the contract agent lint loops expect. Verified recipes:

Aider (`--lint-cmd` accepts filenames, expects non-zero on failure):

```console
aider --lint-cmd "sh -c 'for f; do grounded scan \"$f\" --quiet || exit 1; done' sh"
```

Claude Code (`.claude/settings.json`, runs after every file edit):

```json
{
  "hooks": {
    "PostToolUse": [
      {
        "matcher": "Edit|Write",
        "hooks": [{ "type": "command", "command": "grounded scan . --changed --quiet" }]
      }
    ]
  }
}
```

Cursor rules are generated, not hand-written (`.md` files in
`.cursor/rules/` are ignored by Cursor; only `.mdc` with frontmatter
loads):

```console
grounded init-agent            # Claude hook + Cursor rule + Aider config
grounded init-agent --cursor   # just .cursor/rules/grounded.mdc
```

Or write the rule by hand (agent-requested mode: description, no globs):

```markdown
---
description: Verify code references with grounded before building on edited code
alwaysApply: false
---

After editing source files, run `grounded scan . --changed` and fix
reported lies (dangling function names, missing files) before running
tests or committing.
```

For agents that speak MCP, use `grounded mcp` (see below) instead of
shelling out.

Measured cost (best of 7, wall clock, `examples/bench/bench.py`):
in-process single-file check 0.6 ms, cold CLI single-file check 58 ms
(Python startup dominates). No pytest comparison is claimed here: tests
catch everything, grounded is the millisecond pre-filter before you pay
for them.

`grounded` serves itself over stdio as a Model Context Protocol server,
so agents can verify references instead of trusting them:

```json
{
  "mcpServers": {
    "grounded": { "command": "grounded", "args": ["mcp", "--root", "."] }
  }
}
```

Two tools: `check_path` (scan a path under the server root; paths cannot
escape it), `explain_checker`, and `blast_radius` (definers, importers,
and comment claims for a symbol: ask before renaming). Protocol versions `2025-03-26` through
`2025-06-18` are negotiated per the spec; logs go to stderr, stdout
carries only MCP messages.

## Editors (LSP)

`grounded lsp` speaks Language Server Protocol 3.17 over stdio: instant
diagnostics (lie as error, drift as warning, smell as information) plus
quickfix actions for unambiguous renames and path moves. Neovim:

```lua
vim.api.nvim_create_autocmd("FileType", {
  pattern = { "python", "javascript", "typescript", "go", "c" },
  callback = function()
    vim.lsp.start({ name = "grounded", cmd = { "grounded", "lsp" } })
  end,
})
```

Any editor with a generic LSP client (VS Code, Cursor, Zed, Emacs
eglot) can point at the same command.

## Non-goals

Docstring contracts (parameter lists, return sections, raised exceptions)
are covered precisely by [darglint](https://github.com/terrencepreilly/darglint)
and [pydoclint](https://github.com/jsh9/pydoclint) for Python and
[eslint-plugin-jsdoc](https://github.com/gajus/eslint-plugin-jsdoc) for
JavaScript/TypeScript. Commented-out code is covered by
[Ruff ERA001](https://docs.astral.sh/ruff/rules/commented-out-code/) and
equivalent ESLint rules. `grounded` intentionally does not duplicate them;
`grounded explain <id>` points at the right tool for each removed check.

## Limitations

- Unformatted, unverbed name mentions are skipped. A rename noted without
  backticks or a reference verb ("calls", "see", "uses") will be missed.
  This trades recall for precision.
- Names imported from anywhere are treated as known elsewhere, including
  cross-module renames.
- Framework namespaces (template paths, URL names) are out of scope; such
  references stay silent instead of guessed.
- JavaScript/TypeScript, Go, and C analysis is syntactic (imports plus
  identifiers), not a full type graph.
- External references stay silent only when recognized: stdlib and POSIX
  names, imports, and same-file identifiers. References to vendored code,
  kernel idioms, platform APIs, paper algorithms, and prose verbs in
  parentheses (`forks()`) can still report; judge those on sight.
- Rename suggestions use string similarity only; the first guess can miss.
  `grounded fix` applies a symbol rename only with exactly one similar,
  same-directory candidate.
- `grounded fix` rewrites stale file paths only on unambiguous
  same-basename matches in comments (never docstrings, never ties).

## Development

```console
python -m unittest discover -s tests   # 136 tests, stdlib only, no extras
grounded scan src                      # self-scan gate, must report clean
grounded scan examples/v2demo          # fixture tree, expect 10 findings
```

## Contributing

Issues and pull requests are welcome. Please include:

- a minimal fixture (a few lines showing the comment and the code),
- current output vs expected output,
- the checker id in the issue title.

New checkers are accepted only with a fixture, tests, and no new runtime
dependencies (stdlib only is a project rule).

## License

MIT. See [LICENSE](https://github.com/gonisulaimann/Grounded/blob/main/LICENSE).
