Metadata-Version: 2.4
Name: deploy-guard-engine
Version: 0.1.4
Summary: Static root-cause analysis for a Python stack trace - offline, no account. Plus a deployment-gate scanner.
Author: sai55387
License: MIT
Project-URL: Homepage, https://github.com/Dineshsai7/deploy-guard-engine
Project-URL: Issues, https://github.com/Dineshsai7/deploy-guard-engine/issues
Project-URL: Changelog, https://github.com/Dineshsai7/deploy-guard-engine/blob/main/CHANGELOG.md
Keywords: traceback,stacktrace,root-cause,debugging,static-analysis,control-flow,nullability,deployment,ci,incident
Classifier: Development Status :: 3 - Alpha
Classifier: Environment :: Console
Classifier: Intended Audience :: Developers
Classifier: License :: OSI Approved :: MIT License
Classifier: Operating System :: OS Independent
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Topic :: Software Development :: Debuggers
Classifier: Topic :: Software Development :: Quality Assurance
Classifier: Topic :: Software Development :: Testing
Requires-Python: >=3.10
Description-Content-Type: text/markdown
License-File: LICENSE
Provides-Extra: dev
Requires-Dist: pytest>=7.0; extra == "dev"
Requires-Dist: pytest-cov; extra == "dev"
Requires-Dist: ruff; extra == "dev"
Requires-Dist: build; extra == "dev"
Requires-Dist: twine; extra == "dev"
Dynamic: license-file

# deploy-guard

**Static root-cause analysis for a Python stack trace.** Paste a traceback,
point it at the repo, and get: which variable is `None` and *why*, the branch
path that reached the crash, where the bad value entered across the call
chain, and a concrete fix. Fully local &mdash; no server, no account, no
network. Zero dependencies. Python 3.10+.

It also ships a deployment-gate scanner (`scan` / `check`), but for
general-purpose linting you should run **[ruff](https://docs.astral.sh/ruff/)
+ [mypy](https://mypy-lang.org/)** &mdash; they are faster and deeper. What
deploy-guard does that they don't is turn *a traceback you already have* into
an explanation grounded in your code.

## Install

```bash
pip install deploy-guard-engine   # or: pipx install deploy-guard-engine
```

Or run it with no install at all &mdash; a single ~120 KB file:

```bash
./scripts/build-standalone.sh     # -> dist/deploy-guard.pyz
python deploy-guard.pyz explain --project ./service < traceback.txt
```

## Explain a traceback

```bash
deploy-guard explain --project ./service --file traceback.txt
# or pipe it
deploy-guard explain --project ./service < traceback.txt
```

```
Explaining:  AttributeError: 'NoneType' object has no attribute 'empty'

Crash site:  project_folder/file_name.py:231 — create_json

231 | if _df.empty or _df is None:

Why:

_df can be None because the default value passed to .get() is None.

It reaches this line when processing report_dict.

Although the condition checks _df is None, Python evaluates the or expression from left to right.

Therefore, _df.empty is evaluated first. If _df is None, this raises an AttributeError before the None check is reached.

Call chain (from the traceback):

project_file:164 function_name
└─ file_locator.py:231 create_json <- exception raised

Scan finding:
none-dereference (block) at file_locator.py:231

Fix:
Reorder the condition so _df is None is checked first:

if _df is None or _df.empty:

This ensures _df.empty is only accessed when _df is not None.
```

Dedicated explanations for `AttributeError` / `TypeError` (None), `KeyError`,
`IndexError`, `ZeroDivisionError`, `UnboundLocalError`, `NameError`, and
`int()` / `float()` `ValueError`. Anything else falls back to the call chain
+ the branch conditions that reach the line. The call chain is analysed
*interprocedurally* &mdash; each project frame is checked, and a `None` that
originates in an argument is traced back to the caller that passed it.

## Scan a project

```bash
deploy-guard scan  ./service                 # report, never fails
deploy-guard scan  ./service --behavior      # + the when-X-returns-Y table
deploy-guard check ./service --fail-on review  # gate: exit 1 on findings
deploy-guard scan  ./service --report report.json
```

| Rule | Severity | Catches |
|---|---|---|
| `none-dereference` | block / review | attr/subscript on a possibly-`None` value (interprocedural: a call to a project function that can return `None` counts) |
| `inconsistent-return` | review / note | returns a value on some paths, `None` on others &mdash; **review only when an in-project caller uses the result without a guard**, otherwise a note |
| `swallowed-exception` | review | `except …: pass` |
| `unreachable-code` | warn | code after an always-diverting branch |
| `bare-except` | warn | `except:` with no type |
| `mutable-default-arg` | warn | `def f(x=[])` |
| `invalid-escape` | note | `"\d"` in a non-raw string |
| `path-explosion` | note | too-branchy function (detection still complete) |

Notes are collapsed to a one-line count; `--notes` lists them. Identical
findings from copy-pasted functions fold into one; `--no-collapse` expands.

## Configuration

A `[tool.deploy-guard]` table in the **scanned project's** `pyproject.toml`
(Python 3.11+ for TOML reading):

```toml
[tool.deploy-guard]
disable = ["bare-except"]
exclude = ["vendor", "migrations"]
fail-on = "review"
include-tests = false
```

CLI flags override the file.

## How it works

```
discover -> lower to IR -> CFG per function -> call graph
   -> behavior spec (path conditions -> returns/raises)
   -> nullability data-flow (merges None facts at every branch join; interprocedural)
   -> findings   |   explain (traceback -> the above, focused on one failure)
```

The nullability pass is a forward fixpoint over the CFG &mdash; it merges
facts at branch joins, so it covers 100% of a function regardless of how
branchy it is, in roughly linear time.

`scan` and `explain` only **read** your code &mdash; never import or run it.

## Develop

```bash
pip install -e ".[dev]"
pytest
ruff check src
```
