Metadata-Version: 2.4
Name: pysorry
Version: 0.1.0
Summary: Mark unfinished Python code as structured debt that CI can find and refuse to ship.
Author: James Beck
License-Expression: Apache-2.0
License-File: LICENSE
License-File: NOTICE
Keywords: ast,ci,debt,linter,todo
Classifier: Development Status :: 3 - Alpha
Classifier: Intended Audience :: Developers
Classifier: License :: OSI Approved :: Apache Software License
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Topic :: Software Development :: Quality Assurance
Requires-Python: >=3.11
Provides-Extra: dev
Requires-Dist: pytest>=8; extra == 'dev'
Description-Content-Type: text/markdown

# sorry

`sorry` marks unfinished Python code as structured debt that CI can find and refuse to ship.

It is not a productivity aid. It is a nagging conscience for the build pipeline.
Standard `TODO` comments are a graveyard of good intentions. `sorry` makes them
load-bearing: structured metadata, an AST scanner, and a calendar.

## Install

```
pip install pysorry
```

The distribution is `pysorry`. The import is `sorry`.

## Mark the hole

Two markers. Use whichever fits the shape of the unfinished thing.

### Runtime marker

```python
from sorry import sorry


def reconcile_authority(step):
    return sorry(
        "corrective monotonicity not discharged",
        issue="AG-27",
        owner="agent-gov",
        expires="2026-06-01",
    )
```

`sorry(...)` always raises `UnprovenObligation` (an `AssertionError` subclass).
The return type is `NoReturn`, so type checkers treat anything after it as
unreachable.

### Decorator marker

```python
from sorry import unfinished


@unfinished(
    "corrective monotonicity not discharged",
    issue="AG-27",
    expires="2026-06-01",
)
def corrective_never_mints_authority(step) -> bool:
    ...
```

The decorator runs cleanly at import time. The wrapped function raises
`UnprovenObligation` when called.

## Scan the hole

```
sorry list src tests
```

Lists every obligation. Always exits 0.

```
sorry check src tests
```

Exits 1 if *any* obligation exists. No warn-only mode. No per-path policy file.
The tool is binary by design: either the debt is discharged, or the build is
broken.

## Metadata

All fields are optional except the reason. All values must be string literals
when scanned — non-literal metadata is reported as **invalid**.

| field     | meaning                                         |
| --------- | ----------------------------------------------- |
| `reason`  | what is unfinished                              |
| `issue`   | tracker ID or URL                               |
| `owner`   | who is responsible                              |
| `expires` | ISO date `YYYY-MM-DD`; valid through that date  |
| `basis`   | informal justification or reference             |

## Status

Each obligation lands in one of three buckets:

- **OPEN** — unresolved, not yet expired
- **EXPIRED** — `today > expires`
- **INVALID** — non-literal metadata, malformed `expires`, missing reason, etc.

`sorry check` fails on any non-empty bucket. Expired and invalid are visually
separated in output, but they fail the same gate.

## Scanner limitations

The V0 scanner is **import-aware** but not **scope-aware**. It tracks
`from sorry import …` and `import sorry [as …]` at the module level, but it
does not model lexical scope. Two consequences:

- A function-local import (`def f(): from sorry import sorry as s`) leaks
  its alias to the rest of the module — calls to `s(...)` in sibling
  functions will be reported as obligations.
- A parameter or local variable named `sorry` (or matching an alias)
  shadows the import at runtime, but the scanner will still flag calls
  to it.

In practice this means: keep `sorry` imports at the top of the file and
don't shadow them with locals. Scope-aware resolution may land in a later
release; until then, the limitation is documented and the false-positive
shape is predictable.

## Non-goals

- No `--warn-only` flag.
- No `pyproject.toml` policy section.
- No per-path config.
- No pytest plugin.
- No "discharge" subcommand. To discharge an obligation, delete the call.

The tool is meant to stay tiny. If you need governance, build it elsewhere.

## License

Licensed under Apache-2.0. See `LICENSE` and `NOTICE`.
