NoEllipsis
A fast, local CLI that detects incomplete or dangerously truncated LLM-generated code before you paste, commit, or deploy it.
NoEllipsis is a deterministic static checker. It does not call a model, does not need an API key, and never uploads your source. It never executes the files it scans and never changes Git state.
It is not an AI-content detector, a full compiler, a vulnerability scanner, a rewriter, or a hosted service.
2-minute landing
pipx install noellipsis
# or: python -m pip install noellipsis
noellipsis --version
noellipsis check examples/ok_examples.py --format text
noellipsis check examples/incomplete.py --format text
noellipsis compare examples/generated_snippet.py --against examples/original_module.py --format text
A clean file prints No issues found. An unfinished function reports NE002 and a one-line count such as 1 finding: 1 error. Compare reports shrink / removed-symbol rules (NE101–NE104) plus any placeholders in the candidate.
The image above is a static terminal snapshot generated from real CLI output (see docs/demo.tape). It is not an animated GIF.
Install
Requires Python 3.11+.
pipx install noellipsis
python -m pip install noellipsis
From the git tag (same version):
python -m pip install "noellipsis @ git+https://github.com/hoodbroskillson/NoEllipsis.git@v1.1.2"
Then:
noellipsis --help
python -m noellipsis --help
Local development:
python -m pip install -e ".[dev]"
ruff check .
pytest -q
The dev extra includes pytest, pytest-cov, ruff, build, twine, Hypothesis, and pre-commit. Runtime dependencies remain empty.
Commands
Shared options may appear before or after the subcommand:
noellipsis --format json check src/
noellipsis check src/ --format json
noellipsis --fail-on warning compare candidate.py --against original.py
noellipsis compare candidate.py --against original.py --fail-on warning
noellipsis --format github git-diff --staged
noellipsis git-diff --staged --format github
noellipsis check FILE_OR_DIRECTORY
Scan a file or a tree. Default excludes include .git, virtualenvs, node_modules, vendor, dist, build, coverage output, __pycache__, minified JS, and common binaries.
noellipsis check src/
noellipsis check examples/incomplete.py --format text
noellipsis compare GENERATED --against ORIGINAL
Compare a model’s candidate against the file it was supposed to edit. Reports percent size reduction, removed top-level functions/classes/methods, removed imports, snippet-as-replacement, and any placeholders the candidate introduced.
noellipsis compare examples/generated_snippet.py --against examples/original_module.py
noellipsis git-diff / noellipsis git-diff --staged
Inspect added lines in the working-tree (or staged) diff. Requires a Git repository. Uses subprocess.run with an argument list and shell=False. Does not stage, commit, reset, restore, or checkout anything.
noellipsis git-diff
noellipsis git-diff --staged --format github
Outside a repository the command exits 2 with a clear error.
noellipsis rules
Print the built-in catalog (deterministic order). --format json or text.
noellipsis rules
noellipsis rules --format json
Options
| Option | Meaning |
|---|---|
--format text|json|github|sarif |
Human text, stable JSON, GitHub Actions annotations, or SARIF 2.1.0 |
--fail-on error|warning |
Minimum severity that yields exit code 1 |
--exclude PATTERN |
Extra glob (repeatable) |
--disable RULE_ID |
Turn off a rule (repeatable) |
--shrink-threshold 40 |
Percent shrinkage that triggers NE101 (integer 0–100) |
--verbose |
Progress on stderr |
--config PATH |
TOML file with [tool.noellipsis] (overrides auto-discovered pyproject.toml) |
Invalid CLI flags or invalid [tool.noellipsis] values print a clear error and exit 2. An explicit --config that is missing, unreadable, malformed, or invalid also exits 2 (no silent fallback).
Exit codes
| Code | Meaning |
|---|---|
0 |
Nothing at or above --fail-on |
1 |
One or more findings at or above the threshold |
2 |
Invalid arguments, invalid config, unreadable path, not a git repo, or internal error |
Rules
| ID | Default | What it catches |
|---|---|---|
| NE001 | error | Placeholder phrases in real comments (Rest of code unchanged, Insert your code here, stub-like TODO/FIXME) |
| NE002 | error | Bare ellipsis / incomplete body. A Python function whose entire body is ... is an error |
| NE003 | warning | pass-only (or raise NotImplementedError-only) function, unless abstract or clearly intentional |
| NE004 | error | Unclosed Markdown code fence |
| NE005 | error | Unbalanced (), [], {} via a state machine that skips strings and comments |
| NE006 | error | Python syntax error / truncated statement (ast.parse) |
| NE007 | error | Unresolved merge-conflict markers |
| NE101 | error | Candidate dramatically shorter than the original (default 40% smaller) |
| NE102 | error | Top-level function, class, or method removed |
| NE103 | warning | Imports unexpectedly removed |
| NE104 | error | Probable full-file replacement with a partial snippet |
Each finding includes: rule id, severity, file, line (when known), a short explanation, and a suggested next action.
What NoEllipsis deliberately does not flag
These are regression-tested:
print("...")const copy = [...items];andfunction collect(...args) {}- Prose such as
Wait... what happened? - URLs and quoted documentation
- Placeholder-looking text inside docstrings, JS/TS templates, Go raw strings, Rust raw strings, and shell heredocs
- Intentional Python
@abstractmethod/Protocolstubs .pyistub files- Empty constructors (
def __init__(self): pass) - JavaScript spread / rest (
...args)
Language support: strong AST for Python. Conservative heuristics for JS/TS/JSX/TSX, Java, Go, Rust, C/C++, C#, Ruby, PHP, Shell, and Markdown. Prefer a miss over a false positive.
A shared lexer classifies code / string / comment regions. Suppression phrases and placeholder detection look at comments only. The tool never executes scanned code.
Configuration
pyproject.toml:
[tool.noellipsis]
shrink-threshold = 40
fail-on = "error"
exclude = ["vendor/**", "generated/**"]
disable = ["NE103"]
See noellipsis.example.toml. An explicit --config PATH wins over pyproject.toml. CLI flags override both.
noellipsis --config noellipsis.example.toml check src
noellipsis check src --config noellipsis.example.toml
Suppressions
Works in real comments (#, //, /* */, <!-- -->). Phrases inside strings or docstrings never suppress.
def experimental():
... # noellipsis: ignore[NE002]
# noellipsis: ignore-file
Multiple ids: noellipsis: ignore[NE002,NE003].
pre-commit
repos:
- repo: https://github.com/hoodbroskillson/NoEllipsis
rev: v1.1.2
hooks:
- id: noellipsis
The hook runs noellipsis git-diff --staged with no filename arguments and does not mutate Git. Exit codes match the table above.
GitHub Actions
- uses: actions/checkout@v7
- uses: actions/setup-python@v7
with:
python-version: "3.12"
- run: python -m pip install "noellipsis @ git+https://github.com/hoodbroskillson/NoEllipsis.git@v1.1.2"
- run: noellipsis git-diff --format github
Reusable action (exact tag, never a floating v1):
- uses: hoodbroskillson/NoEllipsis@v1.1.2
with:
path: .
command: check
format: github
fail-on: error
SARIF upload (file is written even when the scanner exits 1; fail after upload):
- uses: actions/checkout@v7
- uses: hoodbroskillson/NoEllipsis@v1.1.2
id: scan
continue-on-error: true
with:
command: check
path: src
format: sarif
- uses: github/codeql-action/upload-sarif@v4
if: always()
with:
sarif_file: ${{ steps.scan.outputs.sarif-file }}
- name: Fail if findings
run: test "${{ steps.scan.outputs.exit-code }}" = "0"
See action/README.md. Also .github/workflows/noellipsis-sarif.yml (contents: read, security-events: write).
This repository’s own CI is .github/workflows/ci.yml. The release.yml workflow builds once, checksums and attests artifacts, attaches the same bytes to the GitHub Release, and is prepared to publish to PyPI via Trusted Publisher (environment: pypi). Do not publish from a laptop.
Output
Text:
src/example.py:84:5 ERROR NE002 Bare ellipsis used as function body
Replace the placeholder with an implementation or suppress NE002 if intentional.
1 finding: 1 error
A clean scan prints No issues found.
GitHub Actions (special characters in paths and messages are escaped):
::error file=src/example.py,line=84,col=5::NE002 Bare ellipsis used as function body
JSON is deterministic (sorted keys, findings ordered by file / line / rule). SARIF is a standalone JSON document on stdout (see docs/sarif.md).
Limitations (honest)
- Not a type checker, compiler, formatter, or security scanner.
- Not an AI-content detector and will not tell you whether a human wrote a file.
- Heuristics for non-Python languages miss some syntax and ignore some comments inside unusual constructs.
--shrink-thresholdcompare is byte-size based; a shorter-but-complete rewrite can still trip NE101.- False positives should be reported as issues with a minimal file. Prefer a suppression over loosening a rule globally.
Safety / privacy
- No network requests at runtime
- No evaluation or
execof scanned files - Scanned files are never modified
- Git is read-only (
git rev-parse,git diff) - Runs entirely on the machine you invoke it on
License
MIT © 2026 hoodbroskillson
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file noellipsis-1.1.2.tar.gz.
File metadata
- Download URL: noellipsis-1.1.2.tar.gz
- Upload date:
- Size: 52.1 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via:
twine/7.0.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
9a24bbdc99d1a6fc76e7a5310117eee1ef9e2229b5c2e7d906dd6222552b44a6
|
|
| MD5 |
93866e975a66f5f57dfb7633c3f72509
|
|
| BLAKE2b-256 |
628c4039bb80d6e9cfbf103d3bc592e8e47747026ca0f2b0b4a1b498634af3d0
|
Provenance
The following attestation bundles were made for noellipsis-1.1.2.tar.gz:
Publisher:
release.yml on hoodbroskillson/NoEllipsis
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
noellipsis-1.1.2.tar.gz -
Subject digest:
9a24bbdc99d1a6fc76e7a5310117eee1ef9e2229b5c2e7d906dd6222552b44a6 - Sigstore transparency entry: 2574246495
- Sigstore integration time:
-
Permalink:
hoodbroskillson/NoEllipsis@d140b242c0c9a28e081671cdee4a510d34a7d14a -
Branch / Tag:
refs/tags/v1.1.2 - Owner: https://github.com/hoodbroskillson
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@d140b242c0c9a28e081671cdee4a510d34a7d14a -
Trigger Event:
push
-
Statement type:
File details
Details for the file noellipsis-1.1.2-py3-none-any.whl.
File metadata
- Download URL: noellipsis-1.1.2-py3-none-any.whl
- Upload date:
- Size: 34.6 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via:
twine/7.0.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
afb9375b286e2d3b050bc293b7cf8a0ee02679537664293d1d02f2779c226a4f
|
|
| MD5 |
a0307af3e0ab70f683c3f4220179f042
|
|
| BLAKE2b-256 |
3a1e57d5b23d43d64c9d29ce705ac758fe7699f7a06167700e311948b6051588
|
Provenance
The following attestation bundles were made for noellipsis-1.1.2-py3-none-any.whl:
Publisher:
release.yml on hoodbroskillson/NoEllipsis
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
noellipsis-1.1.2-py3-none-any.whl -
Subject digest:
afb9375b286e2d3b050bc293b7cf8a0ee02679537664293d1d02f2779c226a4f - Sigstore transparency entry: 2574246543
- Sigstore integration time:
-
Permalink:
hoodbroskillson/NoEllipsis@d140b242c0c9a28e081671cdee4a510d34a7d14a -
Branch / Tag:
refs/tags/v1.1.2 - Owner: https://github.com/hoodbroskillson
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@d140b242c0c9a28e081671cdee4a510d34a7d14a -
Trigger Event:
push
-
Statement type: