Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,10 @@ jobs:
run: uv run --group test pytest tests -q

- name: Self harness
run: uv run python-project-harness .
run: uv run --group test python-project-harness .

- name: Agent snapshot
run: uv run --group test python-project-harness --agent-snapshot .

- name: Build package
run: uv build
Expand Down
50 changes: 49 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,9 +25,15 @@ from pathlib import Path

from python_lang_project_harness import (
__version__,
PythonOwnerResponsibility,
PythonVerificationProfileHint,
PythonVerificationTaskKind,
assert_python_project_harness_clean,
default_python_harness_config,
plan_python_project_verification_with_config,
render_python_lang_harness,
render_python_reasoning_tree,
render_python_verification_plan,
run_python_project_harness,
)

Expand Down Expand Up @@ -69,15 +75,57 @@ shadows without forcing an LLM to consume the full JSON report first. In
project-scoped runs, tree paths are rendered relative to the project root to
avoid repeating long absolute prefixes.

`render_python_project_harness_agent_snapshot(".")` and the
`--agent-snapshot` CLI mode bundle compact policy findings, reasoning-tree
facts, verification-profile reminders, and active verification tasks into one
low-noise Agent repair surface. The snapshot uses capped module summaries,
branches, public owners, import edges, and branch-first profile candidates; it
does not print clean-run file counts or empty section summaries.

The console script follows the same render contract:

```shell
python-project-harness .
python-project-harness --json .
python-project-harness --agent-snapshot .
python-project-harness --source-dir lib --extra-path tools --no-tests .
python -m python_lang_project_harness .
```

## Verification Planning

Verification is a library-first Agent contract. The harness does not execute
benchmark, security, stress, or chaos tools. It plans parser-backed obligations
that external skills can satisfy with receipts or complete waivers:

```python
config = default_python_harness_config().with_verification_profile_hint(
PythonVerificationProfileHint(
"src/pkg/api.py",
(PythonOwnerResponsibility.PUBLIC_API,),
)
.with_task_kinds((PythonVerificationTaskKind.SECURITY,))
.with_rationale("this public API needs a security review")
)
plan = plan_python_project_verification_with_config(Path("."), config)
print(render_python_verification_plan(plan))
```

Profile hints, dependency signals, receipts, waivers, task-kind mappings, and
skill bindings are configurable through `PythonVerificationPolicy` or
`[tool.python-lang-project-harness.verification]`. Parser facts win over config
hints; mismatches become `responsibility_review` tasks instead of silent trust.
`build_python_verification_profile_index(...)` exposes `active_profile_hints()`
so Agents can turn parser-suggested owners into config-ready verification
hints. Public package branches aggregate child-module public API signals, so
large packages surface owner decisions instead of one reminder per file. Report
helpers can render or persist `verification_plan.json`,
`verification_task_index.json`, and `performance_index.json` obligations; source
manifests list only source-baseline artifacts, while runtime manifests carry
the complete bundle with `project_root`.
Profile drift output includes both configured and parser-suggested
responsibilities.

## Pytest Dev Dependency

Downstream projects can load the harness through their test/dev dependency
Expand Down Expand Up @@ -126,4 +174,4 @@ Detailed package material lives under [`docs/`](docs/index.md).

GitHub Actions runs the package contract on every pull request and on pushes to
the default branch: `uv sync --group test --locked`, ruff format/check, pytest,
self-harness, wheel/sdist build, and diff hygiene.
self-harness, agent snapshot, wheel/sdist build, and diff hygiene.
5 changes: 3 additions & 2 deletions development.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,8 @@
direnv exec . uv run --group test ruff format --check src tests
direnv exec . uv run --group test ruff check src tests
direnv exec . uv run --group test pytest tests -q
direnv exec . uv run python-project-harness .
direnv exec . uv run --group test python-project-harness .
direnv exec . uv run --group test python-project-harness --agent-snapshot .
direnv exec . uv build
direnv exec . git diff --check
```
Expand All @@ -16,7 +17,7 @@ consistently.

GitHub Actions runs the same validation surface without `direnv`: `uv sync
--group test --locked`, ruff format/check, pytest, self-harness, package build,
and `git diff --check`.
agent snapshot, and `git diff --check`.

## Library Boundary

Expand Down
2 changes: 2 additions & 0 deletions docs/03_features/201_rule_catalog.md
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,8 @@ The other default packs are blocking through `Warning` or `Error` findings.
resolve to parser-visible project module owners.
- `PY-PROJ-R009`: console script, GUI script, and entry point targets should
resolve to parser-visible project modules.
- `PY-PROJ-R010`: projects that declare the harness as a test/dev dependency
should mount a parser-visible pytest gate.
- `PY-MOD-R001`: wildcard imports must become explicit imports.
- `PY-MOD-R002`: library modules should not use bare `print`.
- `PY-MOD-R003`: package facades with re-exports should declare `__all__`.
Expand Down
14 changes: 12 additions & 2 deletions docs/03_features/203_cli.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,8 @@ The package exposes a thin command-line adapter over the default project
harness runner:

```shell
python-project-harness [--json] [--no-tests] [--source-dir DIR] [--test-dir DIR] [--extra-path PATH] [--disable-rule RULE_ID] [--block-rule RULE_ID] [PROJECT_ROOT]
python -m python_lang_project_harness [--json] [--no-tests] [--source-dir DIR] [--test-dir DIR] [--extra-path PATH] [--disable-rule RULE_ID] [--block-rule RULE_ID] [PROJECT_ROOT]
python-project-harness [--json | --agent-snapshot] [--no-tests] [--source-dir DIR] [--test-dir DIR] [--extra-path PATH] [--disable-rule RULE_ID] [--block-rule RULE_ID] [PROJECT_ROOT]
python -m python_lang_project_harness [--json | --agent-snapshot] [--no-tests] [--source-dir DIR] [--test-dir DIR] [--extra-path PATH] [--disable-rule RULE_ID] [--block-rule RULE_ID] [PROJECT_ROOT]
```

When `PROJECT_ROOT` is omitted, the current working directory is used.
Expand Down Expand Up @@ -73,6 +73,16 @@ Use `--json` when a tool needs the structured `PythonHarnessReport` payload:
python-project-harness --json .
```

Use `--agent-snapshot` when an Agent needs capped parser facts, project
metadata, active policy findings, branch-first verification profile reminders,
and active verification tasks without clean-run counters:

```shell
python-project-harness --agent-snapshot .
```

`--json` and `--agent-snapshot` are mutually exclusive.

## Exit Codes

- `0`: no configured-blocking findings
Expand Down
6 changes: 6 additions & 0 deletions docs/03_features/204_pytest.md
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,12 @@ quiet unless `--python-project-harness` is enabled. This keeps the package safe
as a normal library dependency while making the policy gate easy to opt into
from pytest config.

Project policy validates this wiring. If parser-owned `pyproject.toml` facts
show that a project depends on `python-lang-project-harness` for test/dev use,
the project must expose either `--python-project-harness` in pytest addopts or
an explicit `python_project_harness_test()` callable. This keeps the dependency
from becoming decorative metadata that CI can bypass.

Project-local policy can live beside pytest config in `pyproject.toml`:

```toml
Expand Down
95 changes: 95 additions & 0 deletions docs/03_features/205_verification.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
# Verification Planning

:PROPERTIES:
:ID: 884403d80a274f9d975119079f286b5e
:TYPE: FEATURE
:STATUS: ACTIVE
:LAST_SYNC: 2026-05-03
:END:

Python verification planning is a library-first Agent contract. The harness
does not run benchmark, security, stress, or chaos tools. It uses parser-owned
project facts to produce external obligations that an Agent skill can satisfy
with receipts or complete waivers.

```python
from python_lang_project_harness import (
PythonOwnerResponsibility,
PythonVerificationProfileHint,
PythonVerificationTaskKind,
default_python_harness_config,
plan_python_project_verification_with_config,
render_python_verification_plan,
)

config = default_python_harness_config().with_verification_profile_hint(
PythonVerificationProfileHint(
"src/pkg/api.py",
(PythonOwnerResponsibility.PUBLIC_API,),
)
.with_task_kinds((PythonVerificationTaskKind.SECURITY,))
.with_rationale("this public API needs a security review")
)

plan = plan_python_project_verification_with_config(".", config)
print(render_python_verification_plan(plan))
```

The compact renderer emits active `[verify]` tasks and `[verify-report]`
obligations. Report helpers can render or persist `verification_plan.json`,
`verification_task_index.json`, and `performance_index.json`. Source manifests
only list source-baseline artifacts; runtime manifests carry the complete
bundle with `project_root`, so an Agent can reconstruct the verification
contract from the cache without reading source-control-only baselines first.

## Parser Priority

Profile hints and dependency signals are configuration, not authority. Parser
facts decide whether an owner path exists and whether a declared responsibility
matches the source tree. When configuration drifts from parser facts, the plan
emits a `responsibility_review` task instead of trusting the stale hint.

`build_python_verification_profile_index(...)` is the low-noise discovery
surface for this policy. Each index exposes parser-suggested candidates and
`active_profile_hints()` for config-ready hints that still need attention. The
same parser-visible owner map is used by the planner, so metadata owners such
as `pyproject.toml` and script entry-point owners can be accepted without
falling into false `responsibility_review` tasks.
Profile candidates are branch-first: public package branches aggregate their
child-module public API signal, while unowned public leaves still surface as
their own candidates. When the same owner has multiple responsibilities, the
index merges them into one candidate and one config-ready hint.
For drift, compact output includes both `configured` and `suggest`, so an Agent
can patch the policy from the profile index without reparsing `pyproject.toml`.

## Config Surface

The verification policy supports profile hints, dependency signals, receipts,
waivers, responsibility task-kind mappings, task contracts, skill bindings, and
skill descriptors through `PythonVerificationPolicy` or
`[tool.python-lang-project-harness.verification]`.

```toml
[tool.python-lang-project-harness.verification]
profile_hints = [
{ owner_path = "src/pkg/api.py", responsibilities = ["public_api"], task_kinds = ["security"], rationale = "authz-sensitive public API" },
]

[tool.python-lang-project-harness.verification.task_contracts]
security = { phase = "before_release", summary = "security skill must report authz evidence", requirements = [{ label = "authz", detail = "tenant authorization result" }] }

[tool.python-lang-project-harness.verification.skill_bindings]
security = { skill = "python-security-review", adapter = "bandit" }

[tool.python-lang-project-harness.verification.skill_descriptors]
python-security-review = { task_kind = "security", adapter = "bandit", summary = "run bandit plus tenant authz probes", requirements = [{ label = "bandit", detail = "bandit report artifact" }] }
```

When a task has a skill binding and matching descriptor, compact verification
output stays short with `skill=<binding>` and `contract_ref=<descriptor>`.
Agents can call `render_python_verification_skill_contracts(plan)` only when
they need to expand the referenced contract.

:RELATIONS:
:LINKS: [Harness Boundary](../01_core/101_harness_boundary.md), [CLI](203_cli.md)
:END:
4 changes: 3 additions & 1 deletion docs/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,9 +29,11 @@ entrypoint into a catch-all reference page.
modes, and exit-code contract.
- [Pytest Dev Dependency](03_features/204_pytest.md): pytest plugin entry
point, one-line test helper, and downstream dev dependency examples.
- [Verification Planning](03_features/205_verification.md): parser-backed
verification tasks, profile hints, receipts, waivers, and report artifacts.

:RELATIONS:
:LINKS: [Harness Boundary](01_core/101_harness_boundary.md), [Rule Catalog](03_features/201_rule_catalog.md), [Runner Modes](03_features/202_runner_modes.md), [CLI](03_features/203_cli.md), [Pytest Dev Dependency](03_features/204_pytest.md)
:LINKS: [Harness Boundary](01_core/101_harness_boundary.md), [Rule Catalog](03_features/201_rule_catalog.md), [Runner Modes](03_features/202_runner_modes.md), [CLI](03_features/203_cli.md), [Pytest Dev Dependency](03_features/204_pytest.md), [Verification Planning](03_features/205_verification.md)
:END:

---
Expand Down
29 changes: 29 additions & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,35 @@ packages = [
[tool.uv]
package = true

[tool.pytest.ini_options]
addopts = [
"--python-project-harness",
]

[[tool.python-lang-project-harness.verification.profile_hints]]
owner_path = "pyproject.toml"
responsibilities = ["pytest_gate"]
verification_tasks_enabled = false
rationale = "self pytest addopts and CI cover the harness gate contract"

[[tool.python-lang-project-harness.verification.profile_hints]]
owner_path = "src/python_lang_parser/__init__.py"
responsibilities = ["public_api"]
verification_tasks_enabled = false
rationale = "self parser tests, CLI harness, and build cover the parser public facade"

[[tool.python-lang-project-harness.verification.profile_hints]]
owner_path = "src/python_lang_project_harness/__init__.py"
responsibilities = ["public_api", "cli"]
verification_tasks_enabled = false
rationale = "self public API tests, CLI tests, pytest gate, and build cover the harness facade"

[[tool.python-lang-project-harness.verification.profile_hints]]
owner_path = "src/python_lang_project_harness/pytest_plugin.py"
responsibilities = ["cli"]
verification_tasks_enabled = false
rationale = "self pytest addopts exercise the pytest plugin gate"

[tool.ruff]
line-length = 88
target-version = "py312"
Expand Down
4 changes: 4 additions & 0 deletions src/python_lang_parser/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,10 +13,12 @@
)
from ._name_policy import python_name_is_public, python_scope_is_public
from ._project_model import (
PythonProjectDependency,
PythonProjectEntryPoint,
PythonProjectImportName,
PythonProjectMetadata,
PythonProjectScript,
PythonPytestOptions,
)
from ._pyproject_metadata import parse_python_project_metadata
from ._reasoning_tree import (
Expand Down Expand Up @@ -71,10 +73,12 @@
"PythonModuleReport",
"PythonModuleShape",
"PythonNameBinding",
"PythonProjectDependency",
"PythonProjectEntryPoint",
"PythonProjectImportName",
"PythonProjectMetadata",
"PythonProjectScript",
"PythonPytestOptions",
"PythonReference",
"PythonReferenceKind",
"PythonReasoningTreeBranch",
Expand Down
5 changes: 5 additions & 0 deletions src/python_lang_parser/_ast_collector.py
Original file line number Diff line number Diff line change
Expand Up @@ -222,6 +222,11 @@ def _visit_symbol(
decorators=tuple(
unparse(decorator) for decorator in node.decorator_list
),
base_classes=(
tuple(unparse(base) for base in node.bases)
if isinstance(node, ast.ClassDef)
else ()
),
docstring=ast.get_docstring(node),
has_annotations=symbol_has_annotations(node),
is_public=is_public_name(node.name),
Expand Down
Loading
Loading