diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index efb424a..c63bd92 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -1,6 +1,7 @@ name: CI on: + workflow_dispatch: pull_request: push: branches: @@ -48,12 +49,6 @@ jobs: - name: Test run: uv run --group test pytest tests -q - - name: Self harness - run: uv run --group test asp-python . - - - name: Agent snapshot - run: uv run --group test asp-python --agent-snapshot . - - name: Build package run: uv build diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index bc13bc5..0b0b024 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -29,7 +29,7 @@ jobs: - os: macos-latest target: aarch64-apple-darwin env: - BINARY: py-harness + BINARY: asp-python GH_TOKEN: ${{ github.token }} RELEASE_TAG: ${{ github.event.release.tag_name || inputs.tag || github.ref_name }} TARGET: ${{ matrix.target }} @@ -73,7 +73,7 @@ jobs: set -euo pipefail rm -rf build dist package py_harness_entry.py cat > py_harness_entry.py <<'PY' - from python_lang_project_harness import run_cli_from_env + from asp_python import run_cli_from_env raise SystemExit(run_cli_from_env()) PY @@ -82,7 +82,7 @@ jobs: --name "$BINARY" \ --paths src \ --collect-submodules python_lang_parser \ - --collect-submodules python_lang_project_harness \ + --collect-submodules asp_python \ py_harness_entry.py mkdir -p package cp -R "dist/${BINARY}/." package/ diff --git a/README.md b/README.md index 1b933bd..327a73e 100644 --- a/README.md +++ b/README.md @@ -1,50 +1,50 @@ -# python-lang-project-harness +# asp-python -`python-lang-project-harness` is a standalone Python project harness library for +`asp-python` is a standalone Python policy and semantic tooling library for modern Python packages. It ships two library boundaries in one repo: - `python_lang_parser`: Python-native AST, compiler, tokenize, symbol-table, module-shape, public-surface, and symbol-role facts. -- `python_lang_project_harness`: project discovery, deterministic rule +- `asp_python`: project discovery, deterministic rule packs, compact rendered diagnostics, and pytest-friendly assertions. -The harness is library-first. Callers pass a project root or explicit paths, +ASP Python is library-first. Callers pass a project root or explicit paths, then decide whether to assert, render compact text, or inspect the structured report. Compact text is the default agent repair surface; JSON is available for -tooling through `render_python_lang_harness_json()`. +tooling through `render_asp_python_report_json()`. -`python_lang_parser` is the semantic foundation. Harness policy consumes parser +`python_lang_parser` is the semantic foundation. ASP Python policy consumes parser reports and parser-owned `pyproject.toml` metadata instead of re-parsing Python source or guessing package scope in the rule layer; tests-root layout stays in -the harness. +ASP Python. ## Quick Use ```python from pathlib import Path -from python_lang_project_harness import ( +from asp_python import ( __version__, PythonOwnerResponsibility, PythonVerificationProfileHint, PythonVerificationTaskKind, - assert_python_project_harness_clean, - default_python_harness_config, + assert_asp_python_clean, + default_asp_python_config, plan_python_project_verification_with_config, - render_python_lang_harness, + render_asp_python_report, render_python_reasoning_tree, render_python_verification_plan, - run_python_project_harness, + run_asp_python, ) -def test_python_project_harness_policy() -> None: - assert_python_project_harness_clean(Path(".")) +def test_asp_python_policy() -> None: + assert_asp_python_clean(Path(".")) -report = run_python_project_harness(Path(".")) +report = run_asp_python(Path(".")) print(__version__) -print(render_python_lang_harness(report)) +print(render_asp_python_report(report)) print(render_python_reasoning_tree(report)) ``` @@ -52,20 +52,20 @@ The project runner scans the whole Python project root by default, excluding tool/cache/build directories such as `.venv`, `__pycache__`, `build`, and `dist`. Conventional source and test roots still classify project policy, but they do not narrow parser coverage. The explicit path runner, -`run_python_lang_harness([...])`, is useful for focused parser and syntax +`run_asp_python_paths([...])`, is useful for focused parser and syntax checks. -Use `PythonHarnessConfig` to change source-root classification, test-root +Use `AspPythonConfig` to change source-root classification, test-root classification, extra external project paths, test inclusion, or blocking severities without hardcoding project-specific policy into the library. -Project runners also read `[tool.python-lang-project-harness]` from -`pyproject.toml` when no explicit `PythonHarnessConfig` is passed, including +Project runners also read `[tool.asp-python]` from +`pyproject.toml` when no explicit `AspPythonConfig` is passed, including `disabled_rule_ids` and `blocking_rule_ids` for stable rule-id policy. Standard `[project]` metadata such as `name`, `requires-python`, `import-names`, scripts, and pytest entry points is parsed by `python_lang_parser` and appears in project policy and reasoning-tree facts. When `include_tests=False`, test files are not parsed, but tests-root layout policy still runs. Explained local exceptions can live in -`tests/python-project-harness-rules.toml`. +`tests/asp-python-rules.toml`. For agent repair loops, `render_python_reasoning_tree(report)` emits a compact package/module owner tree from parser-owned facts. It shows package branches, @@ -75,42 +75,32 @@ 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. +`render_asp_python_agent_snapshot(".")` bundles compact policy +findings, reasoning-tree facts, verification-profile reminders, and active +verification tasks into one low-noise library response. The snapshot uses +capped module summaries, branches, public owners, import edges, and +branch-first profile candidates. -The semantic-language console script exposes search, registry, and check -surfaces aligned with the Rust and TypeScript harnesses: +The provider console script exposes Query and registry surfaces. Public source +discovery is owned by the Runtime Search Playbook. Policy remains a dependency +API consumed by pytest/build ownership: ```shell -asp-python search workspace . -asp-python search prime . -asp-python search lexical PythonHarnessReport owner tests . -asp-python search lexical --query-set PythonHarnessReport --query-set PythonSemanticSearchOptions owner tests . -asp-python search public-external-types pytest . -asp-python search callsite PythonHarnessReport . -asp-python search deps pytest . +asp search playbook --language python --rg -n -e AspPythonReport . --tantivy 'title:AspPythonReport^2 OR body:AspPythonReport' +asp query playbook --language python --selector '' --projection source --workspace . asp-python agent doctor --json . asp-python agent guide . -asp-python check --full . -asp-python . -asp-python --json . -asp-python --agent-snapshot . -asp-python --source-dir lib --extra-path tools --no-tests . -python -m python_lang_project_harness . +python -c 'from asp_python import assert_asp_python_clean; assert_asp_python_clean(".")' ``` ## Verification Planning -Verification is a library-first Agent contract. The harness does not execute +Verification is a library-first Agent contract. ASP Python 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( +config = default_asp_python_config().with_verification_profile_hint( PythonVerificationProfileHint( "src/pkg/api.py", (PythonOwnerResponsibility.PUBLIC_API,), @@ -124,7 +114,7 @@ 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 +`[tool.asp-python.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 @@ -139,29 +129,29 @@ responsibilities. ## Pytest Dev Dependency -Downstream projects can load the harness through their test/dev dependency +Downstream projects can load ASP Python through their test/dev dependency group: ```toml [dependency-groups] test = [ "pytest>=8", - "python-lang-project-harness[pytest]>=0.1.0", + "asp-python[pytest]>=0.1.0", ] [tool.pytest.ini_options] -addopts = ["--python-project-harness"] +addopts = ["--asp-python"] ``` The pytest plugin is exposed through the package `pytest11` entry point. It is loaded by pytest when the dev dependency is installed, but it only runs the -harness when `--python-project-harness` is enabled. Projects that prefer an +ASP Python policy gate when `--asp-python` is enabled. Projects that prefer an explicit test file can use the public helper: ```python -from python_lang_project_harness.pytest import python_project_harness_test +from asp_python.pytest import asp_python_test -test_python_project_harness_policy = python_project_harness_test() +test_asp_python_policy = asp_python_test() ``` ## Rule Packs @@ -195,4 +185,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, agent snapshot, wheel/sdist build, and diff hygiene. +ASP Python self-check, agent snapshot, wheel/sdist build, and diff hygiene. diff --git a/development.md b/development.md index 34b5720..4752992 100644 --- a/development.md +++ b/development.md @@ -3,20 +3,20 @@ ## Format, Test, Lint ```shell -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 --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 +.devenv/devenv-profile-exec uv run --project languages/asp-python --group test ruff format --check languages/asp-python/src languages/asp-python/tests +.devenv/devenv-profile-exec uv run --project languages/asp-python --group test ruff check languages/asp-python/src languages/asp-python/tests +.devenv/devenv-profile-exec uv run --project languages/asp-python --group test pytest languages/asp-python/tests -q +.devenv/devenv-profile-exec uv run --project languages/asp-python --group test asp-python languages/asp-python +.devenv/devenv-profile-exec uv run --project languages/asp-python --group test asp-python --agent-snapshot languages/asp-python +.devenv/devenv-profile-exec uv build languages/asp-python +.devenv/devenv-profile-exec git diff --check ``` -Use `direnv exec .` so the devenv-managed Python and `uv` environment are used -consistently. +Use `.devenv/devenv-profile-exec` from the repository root so the captured +devenv-managed Python and `uv` environment are used consistently. GitHub Actions runs the same validation surface without `direnv`: `uv sync ---group test --locked`, ruff format/check, pytest, self-harness, package build, +--group test --locked`, ruff format/check, pytest, ASP Python self-check, package build, agent snapshot, and `git diff --check`. ## Library Boundary @@ -24,16 +24,16 @@ agent snapshot, and `git diff --check`. This repo is a standalone Python library project. It ships: - `python_lang_parser` for Python-native parser facts -- `python_lang_project_harness` for discovery, rule packs, rendering, +- `asp_python` for discovery, rule packs, rendering, and pytest embedding Keep these boundaries separate. Parser modules should not know about project -policy, pytest, or agent repair wording. Harness modules should consume parser +policy, pytest, or agent repair wording. ASP Python modules should consume parser reports and emit deterministic findings. ## Self-Applied Policy -`tests/unit/test_self_hosting.py` mounts the project harness against this repo. +`tests/unit/test_self_hosting.py` mounts the ASP Python against this repo. When adding tests, keep behavior coverage under `tests/unit` and avoid scattered `tests/test_*.py` files at the test root. @@ -41,14 +41,14 @@ Default assertions block on `Warning` and `Error`. `PY-AGENT-*` rules stay `Info`: rendered by default as repair advice, but non-blocking unless a caller opts into stricter severity selection. -The CLI is part of that same contract. Keep `python-project-harness` as a thin +The CLI is part of that same contract. Keep `asp-python` as a thin adapter over the library runner and renderers. ## Renderer Contract Compact text is the primary agent-facing repair surface. It should remain small: rule id, location, optional source line, pointer label, and one `Required:` -contract line. Use `render_python_lang_harness_json()` for tooling that needs +contract line. Use `render_asp_python_report_json()` for tooling that needs the full structured payload. ## Snapshot Workflow @@ -57,10 +57,11 @@ Rendered output and policy diagnostics are locked under `tests/unit/snapshots`. Normal tests compare snapshots only. Refresh them intentionally: ```shell -PYTHON_HARNESS_UPDATE_SNAPSHOTS=1 direnv exec . uv run --group test pytest \ - tests/unit/harness/test_render_snapshots.py \ - tests/unit/harness/test_agent_policy_snapshots.py \ - tests/unit/harness/test_policy_snapshots.py -q +.devenv/devenv-profile-exec env ASP_PYTHON_UPDATE_SNAPSHOTS=1 \ + uv run --project languages/asp-python --group test pytest \ + languages/asp-python/tests/unit/asp_python/test_render_snapshots.py \ + languages/asp-python/tests/unit/asp_python/test_agent_policy_snapshots.py \ + languages/asp-python/tests/unit/asp_python/test_policy_snapshots.py -q ``` Review the resulting `.snap` diff before keeping it. Snapshot changes are diff --git a/docs/01_core/101_harness_boundary.md b/docs/01_core/101_asp_python_boundary.md similarity index 75% rename from docs/01_core/101_harness_boundary.md rename to docs/01_core/101_asp_python_boundary.md index 91999a0..6b7208c 100644 --- a/docs/01_core/101_harness_boundary.md +++ b/docs/01_core/101_asp_python_boundary.md @@ -1,4 +1,4 @@ -# Harness Boundary +# ASP Python Boundary :PROPERTIES: :ID: 5fa0fe2dac2c4668b1ad949a8590d0098679cb1b @@ -7,8 +7,8 @@ :LAST_SYNC: 2026-04-30 :END: -`python-lang-project-harness` owns a standalone, library-first Python project -harness. It keeps parser facts and project policy in separate import packages, +`asp-python` owns a standalone, library-first Python project +ASP Python. It keeps parser facts and project policy in separate import packages, but ships them from the same repo so downstream users do not depend on the old monorepo workspace layout. @@ -22,7 +22,7 @@ This repository may: 3. evaluate deterministic rule packs over parser reports and project scope 4. render compact diagnostics for humans and repair-oriented agents 5. expose structured reports and JSON rendering for tooling -6. expose pytest-friendly assertion helpers and collectable harness callables +6. expose pytest-friendly assertion helpers and collectable ASP Python callables 7. expose a thin CLI over the default project runner This repository must not own: @@ -47,21 +47,21 @@ For class surfaces it owns data/type shape facts such as annotated class fields, `__init__` self-field storage, method counts, and visible anchors like `dataclass`, `Enum`, `Protocol`, `TypedDict`, `NamedTuple`, or model bases. -`python_lang_project_harness` consumes those reports. It owns rule +`asp_python` consumes those reports. It owns rule catalogs, project discovery, report aggregation, rendering, and pytest embedding. Rule packs should depend on parser facts rather than ad hoc source text matching when a structured fact exists. Parser-backed policy is the default architectural rule. Python semantic checks -must not re-parse source inside the harness layer; they should use +must not re-parse source inside ASP Python layer; they should use `PythonModuleReport` facts such as imports, calls, symbols, assignments, export contracts, source lines, and module shape. Standard Python project metadata -should flow through parser-owned `pyproject.toml` facts. Harness-owned policy -inputs such as tests-root policy TOML may still be read in the harness layer, +should flow through parser-owned `pyproject.toml` facts. ASP Python-owned policy +inputs such as tests-root policy TOML may still be read in ASP Python layer, but they must not infer Python semantics from raw text. `PY-TEST-R003` follows the same boundary: unit-test bloat is calculated from -parser-owned module shape and parser-classified test symbols. The harness owns +parser-owned module shape and parser-classified test symbols. ASP Python owns the pytest layout contract, while `python_lang_parser` owns Python syntax, tokenization, AST, compile validation, source-line capture, public-name policy, module public-surface classification, symbol-role classification, and @@ -76,7 +76,7 @@ resolves project-internal import edges from parser import records and import roots, so agents can see which modules depend on a subtree before editing. The tree nodes carry parser-owned export candidates and `__all__` contract kind, so the agent sees public API names without re-reading source text. The -harness turns those facts into `PY-MOD-R007` and `PY-AGENT-POLICY-007` findings; it +ASP Python turns those facts into `PY-MOD-R007` and `PY-AGENT-POLICY-007` findings; it does not re-parse Python source to infer tree shape or dependency direction. Project policy uses the same parser metadata to keep declared import names and entry point targets aligned with parser-visible project owners. @@ -89,15 +89,15 @@ before choosing a repair surface. ## Runner Modes -Use `run_python_project_harness()` or `assert_python_project_harness_clean()` +Use `run_asp_python()` or `assert_asp_python_clean()` when a caller has a project root. The project runner scans all Python files under the project root by default, with cache/build/environment directories excluded. `src/` and `tests/` remain source/test classification roots for -project policy; they do not narrow parser coverage. `PythonHarnessConfig` can +project policy; they do not narrow parser coverage. `AspPythonConfig` can change source-root classification, test-root classification, extra external project paths, and test inclusion behavior. -Use `run_python_lang_harness()` or `assert_python_lang_harness_clean()` for +Use `run_asp_python_paths()` or `assert_asp_python_paths_clean()` for explicit files or directories. This runner is useful for focused parser checks and editor integrations; project-scoped rule packs only emit findings when a project scope is available. @@ -110,31 +110,29 @@ when configured-blocking findings exist: ```python from pathlib import Path -from python_lang_project_harness import assert_python_project_harness_clean +from asp_python import assert_asp_python_clean -def test_python_project_harness_policy() -> None: - assert_python_project_harness_clean(Path(".")) +def test_asp_python_policy() -> None: + assert_asp_python_clean(Path(".")) ``` -`python_project_harness_test()` returns a pytest-collectable callable for +`asp_python_test()` returns a pytest-collectable callable for projects that prefer a one-line mount. Downstream projects can import it from -`python_lang_project_harness.pytest` and assign it to a test name. +`asp_python.pytest` and assign it to a test name. The package also exposes a pytest plugin through the `pytest11` entry point. When installed as a test/dev dependency, pytest loads the plugin and accepts -`--python-project-harness`. The plugin only inserts the harness item when that +`--asp-python`. The plugin only inserts the ASP Python item when that option is enabled, so installing the package does not silently add a policy gate. -## CLI Embedding +## Dependency API -`asp-python check [--json] [PROJECT_ROOT]` runs the same default project -runner. Compact text is the default output. `--json` emits the structured -`PythonHarnessReport` payload. `asp-python search ...` renders bounded -semantic-search packets from parser-owned facts. The CLI is a thin adapter over -library APIs: it does not own workflow orchestration or project-specific -policy. +`assert_asp_python_clean(PROJECT_ROOT)` is the sole project-policy +entry. The pytest plugin and downstream build/test owners import that API; +`asp-python` exposes search/evidence/agent protocol commands only and never +creates a second policy-check authority. ## Blocking And Advice diff --git a/docs/03_features/201_rule_catalog.md b/docs/03_features/201_rule_catalog.md index 170ea54..d39bbc3 100644 --- a/docs/03_features/201_rule_catalog.md +++ b/docs/03_features/201_rule_catalog.md @@ -7,7 +7,7 @@ :LAST_SYNC: 2026-05-05 :END: -The harness exposes deterministic rule metadata through compact library +ASP Python exposes deterministic rule metadata through compact library functions: - `python_rule_pack_descriptors()` @@ -52,7 +52,7 @@ policy can also emit `Info` configuration work orders for Agents. resolve to parser-visible project module owners. - `PY-AGENT-PROJECT-009`: console script, GUI script, and entry point targets should resolve to parser-visible project modules. -- `PY-AGENT-PROJECT-010`: projects that declare the harness as a test/dev dependency +- `PY-AGENT-PROJECT-010`: projects that declare ASP Python 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`. @@ -64,11 +64,11 @@ policy can also emit `Info` configuration work orders for Agents. avoiding `module.py` plus `module/__init__.py` reasoning-tree shadows. - `PY-TEST-R001`: pytest modules should not be scattered in the tests root. - `PY-TEST-R002`: tests root entries should be owned suite directories or - harness configuration files. + ASP Python configuration files. - `PY-TEST-R003`: large unit-test leaves should split into folder-first suites. Project-local pytest-layout exceptions live in -`tests/python-project-harness-rules.toml`; each exception needs a non-empty +`tests/asp-python-rules.toml`; each exception needs a non-empty explanation before it suppresses `PY-TEST-*` findings. `PY-MOD-R006` is a mixed-signal modularity gate, not a line-count gate. The @@ -83,9 +83,9 @@ not fail only because they are long. Some project-policy findings are intentionally `Info`: they are configuration work orders for the repair Agent, not immediate merge blockers. -- `PY-AGENT-PROJECT-011`: projects that declare the harness as a test/dev dependency +- `PY-AGENT-PROJECT-011`: projects that declare ASP Python as a test/dev dependency and expose parser-visible verification owners should configure - `[tool.python-lang-project-harness.verification].profile_hints`. The finding + `[tool.asp-python.verification].profile_hints`. The finding points the Agent to `asp-python --agent-snapshot`, whose compact `[verify-profile]` section is the config draft. @@ -129,7 +129,7 @@ enough to use as the first repair prompt. ## Reasoning Tree Policy -The harness treats a Python project as an agent reasoning tree: import roots +ASP Python treats a Python project as an agent reasoning tree: import roots lead to package branches, package branches lead to modules, and modules expose the parser-owned public surface. `python_lang_parser` owns the tree facts: tree nodes, child names, public/internal surface flags, module/package owner @@ -150,19 +150,19 @@ Packages that already expose an explicit public facade are treated as having an owner map. `PY-AGENT-POLICY-009` is backed by parser-owned function control-flow facts, not by -harness string scanning. The parser records branch count, loop count, maximum +ASP Python string scanning. The parser records branch count, loop count, maximum nesting, loop nesting, terminal `else` opportunities, and repeated literal dispatch chains for each function symbol during the normal AST collection pass. -The harness turns those facts into a compact repair hint when a public function +ASP Python turns those facts into a compact repair hint when a public function hides its algorithm behind nested `if`/loop structure. The rule stays advisory by default so teams can tune or promote it after seeing their project shape. -The implementation lives under `python_lang_project_harness.agent_readability` +The implementation lives under `asp_python.agent_readability` because the target reader is the repair agent, not a human style reviewer. The goal is short, explicit algorithm surfaces that an LLM can use from the reasoning tree: guard clauses instead of nested `else`, `match/case` or dispatch tables instead of literal branch ladders, and small named pipeline steps instead -of one broad loop body. Performance remains parser-first: the harness only +of one broad loop body. Performance remains parser-first: ASP Python only consumes `PythonFunctionControlFlow` facts and does not run a second AST parse. `PY-AGENT-POLICY-010` complements `PY-AGENT-POLICY-009`: the former catches long flat procedure-like public functions, while the latter catches nested control-flow @@ -170,7 +170,7 @@ shape. This keeps the advice compact and avoids telling the agent the same thing twice. `PY-AGENT-POLICY-011` is the native-Python idiom layer. It is backed by parser-owned -function facts for simple accumulator loops and predicate loops, so the harness +function facts for simple accumulator loops and predicate loops, so ASP Python can advise comprehensions, generator expressions, built-ins, or iterator pipeline helpers without parsing source in the policy layer. The rule is conservative: it targets module-level functions and public methods where a loop @@ -214,14 +214,14 @@ the file/parsed count needed for CI confidence. In project-scoped reports, compact text renders paths relative to the project root; JSON keeps the structured original paths for tooling. -`render_python_lang_harness()` includes advice by default. A report with only +`render_asp_python_report()` includes advice by default. A report with only `Info` findings is still clean, but its advice remains visible without run-summary noise. Use -`render_python_lang_harness_advice()` when a caller wants only non-blocking +`render_asp_python_report_advice()` when a caller wants only non-blocking repair hints; it returns an empty string when there is no advice to act on. -Structured consumers should use `render_python_lang_harness_json()` or the -`PythonHarnessReport.to_dict()` shape instead of parsing compact text. +Structured consumers should use `render_asp_python_report_json()` or the +`AspPythonReport.to_dict()` shape instead of parsing compact text. ## Parser-First Policy @@ -231,13 +231,13 @@ text. `python_lang_parser` owns AST, compile, tokenize, symbol-table, source line, module-shape, public-name, public-surface, symbol-role, and import-root module identity facts, standard `pyproject.toml` project metadata, and package reasoning-tree facts. -`python_lang_project_harness` owns rule catalogs, project/test layout, +`asp_python` owns rule catalogs, project/test layout, reporting, and assertion behavior. Repository tests enforce this boundary by rejecting direct `ast` or `tokenize` -usage under `src/python_lang_project_harness`. File and metadata checks may +usage under `src/asp_python`. File and metadata checks may still read non-Python policy inputs such as -`python-project-harness-rules.toml`; Python project metadata should flow +`asp-python-rules.toml`; Python project metadata should flow through parser-owned `pyproject.toml` facts. ## Snapshot Coverage @@ -245,10 +245,10 @@ through parser-owned `pyproject.toml` facts. The compact text and JSON render contracts are covered by repository snapshots under `tests/unit/snapshots`: -- `python_project_harness_compact_text.snap` -- `python_project_harness_json.snap` +- `asp_python_compact_text.snap` +- `asp_python_json.snap` -Policy snapshots are generated from real harness fixtures and normalized to +Policy snapshots are generated from real ASP Python fixtures and normalized to `$TEMP` paths. Every current `PY-AGENT-*` rule has a compact advice snapshot. The blocking policy surface also has snapshots for native syntax, `PY-MOD-*`, `PY-PROJ-*`, and `PY-TEST-*` findings. This keeps rule titles, @@ -258,12 +258,12 @@ ordinary snapshot diffs. Refresh snapshots explicitly: ```shell -PYTHON_HARNESS_UPDATE_SNAPSHOTS=1 direnv exec . uv run --group test pytest \ - tests/unit/harness/test_render_snapshots.py \ - tests/unit/harness/test_agent_policy_snapshots.py \ - tests/unit/harness/test_policy_snapshots.py -q +ASP_PYTHON_UPDATE_SNAPSHOTS=1 direnv exec . uv run --group test pytest \ + tests/unit/asp_python/test_render_snapshots.py \ + tests/unit/asp_python/test_agent_policy_snapshots.py \ + tests/unit/asp_python/test_policy_snapshots.py -q ``` :RELATIONS: -:LINKS: [Harness Boundary](../01_core/101_harness_boundary.md) +:LINKS: [ASP Python Boundary](../01_core/101_asp_python_boundary.md) :END: diff --git a/docs/03_features/202_agent_pythonic_policy_research.md b/docs/03_features/202_agent_pythonic_policy_research.md index 8cd7a1d..8cb8683 100644 --- a/docs/03_features/202_agent_pythonic_policy_research.md +++ b/docs/03_features/202_agent_pythonic_policy_research.md @@ -8,7 +8,7 @@ Python idioms that keep code small enough for an LLM to edit reliably. ## Target Reader Shift -The harness target reader has changed. The primary reader is no longer a human +ASP Python target reader has changed. The primary reader is no longer a human reviewer looking for pleasant style; it is an Agent or large language model that must choose a small, correct edit surface from a whole Python project. A human can tolerate incidental ceremony and remember local context outside the @@ -32,7 +32,7 @@ edits: - verification anchors: compact snapshot sections, task contracts, receipts, waivers, and responsibility-review tasks. -The harness should therefore reject policy ideas that are merely aesthetic. A +ASP Python should therefore reject policy ideas that are merely aesthetic. A new Agent rule needs four properties: it must be backed by parser-owned facts, it must reduce the model's search or edit surface, it must render as compact actionable advice rather than redundant explanation, and it must self-apply to @@ -100,13 +100,13 @@ Modern Python also has native constructs that remove common LLM boilerplate: GitHub practice shows the adjacent tool baseline. Current mature Python repos such as `pydantic/pydantic`, `pytest-dev/pytest`, `encode/httpx`, and `psf/black` centralize project metadata in `pyproject.toml` and commonly wire -pytest, ruff, mypy, or pyright. That means this harness should not duplicate +pytest, ruff, mypy, or pyright. That means ASP Python should not duplicate style or type-check rules. Its useful scope is the parser-backed project and algorithm contract that an LLM sees before it edits code. -## Harness Thesis +## ASP Python Thesis -The harness should classify Python quality in three layers: +ASP Python should classify Python quality in three layers: 1. Tool substrate: packaging metadata, pytest gate, ruff, and type-checker configuration. Parser facts expose this layer, but normal tools enforce it. @@ -141,7 +141,7 @@ The next policy step is not another size threshold. It is native-idiom advice: when the parser sees a simple module-level function or public method manually building a list, set, or dict in a loop, manually counting/grouping into a dictionary, manually summing numeric values, or returning a boolean through a -trivial predicate loop, the harness should ask the agent to use a comprehension, +trivial predicate loop, ASP Python should ask the agent to use a comprehension, generator expression, built-in such as `sum`/`any`/`all`, `collections.Counter`, `collections.defaultdict`, or named iterator pipeline. This is advisory because explicit loops remain correct for side effects, @@ -150,7 +150,7 @@ measured. ## Candidate Matrix -| Candidate | Evidence | Harness action | +| Candidate | Evidence | ASP Python action | | --- | --- | --- | | Map/filter/list/set/dict build loops | Python Functional HOWTO on comprehensions and generator expressions | Implemented by parser fact `manual_collection_loop_count` | | Predicate search loops | Python built-ins and Functional HOWTO predicate guidance | Implemented by parser fact `manual_predicate_loop_count` | diff --git a/docs/03_features/202_runner_modes.md b/docs/03_features/202_runner_modes.md index c98fab2..a1b54be 100644 --- a/docs/03_features/202_runner_modes.md +++ b/docs/03_features/202_runner_modes.md @@ -7,13 +7,13 @@ :LAST_SYNC: 2026-04-30 :END: -The harness exposes two runner modes with shared configuration. +ASP Python exposes two runner modes with shared configuration. ## Project Runner -Use `run_python_project_harness()` or `assert_python_project_harness_clean()` +Use `run_asp_python()` or `assert_asp_python_clean()` when a caller has a project root. The project runner scans the whole Python -project root by default, attaches `PythonProjectHarnessScope`, and runs the +project root by default, attaches `AspPythonProjectScope`, and runs the full default rule surface: 1. `python.syntax` @@ -28,12 +28,12 @@ and produce CLI exit code `2`. ## Configuration -`PythonHarnessConfig` owns project-resolution classification and parser inclusion: +`AspPythonConfig` owns project-resolution classification and parser inclusion: ```python -from python_lang_project_harness import PythonHarnessConfig +from asp_python import AspPythonConfig -config = PythonHarnessConfig( +config = AspPythonConfig( include_tests=False, source_dir_names=("lib",), test_dir_names=("checks",), @@ -53,7 +53,7 @@ for module-resolution policy. `source_dir_names` and `test_dir_names` classify roots for project and pytest-layout policy; they do not narrow parser coverage. `extra_path_names` can add an external project path or a single Python file outside the root. Extra paths are relative to the -project root and are part of `PythonProjectHarnessScope.monitored_paths` when +project root and are part of `AspPythonProjectScope.monitored_paths` when they exist. `include_tests=False` removes test roots from parser discovery, while keeping @@ -61,7 +61,7 @@ tests-root layout policy active. Callers can skip expensive or broken test parsing without hiding suite-shape drift. Explained local pytest-layout exceptions can be declared in -`tests/python-project-harness-rules.toml`: +`tests/asp-python-rules.toml`: ```toml [tests] @@ -83,9 +83,9 @@ visible advice. Policy can also be configured by stable rule id: ```python -from python_lang_project_harness import PythonHarnessConfig +from asp_python import AspPythonConfig -config = PythonHarnessConfig( +config = AspPythonConfig( disabled_rule_ids=frozenset({"PY-MOD-R002"}), blocking_rule_ids=frozenset({"PY-AGENT-POLICY-007"}), ) @@ -97,10 +97,10 @@ blockers without changing their catalog severity, which keeps advisory rules visible as advice while allowing a project to enforce chosen agent policy. Project runners also read the same policy from `pyproject.toml` when no -explicit `PythonHarnessConfig` is passed: +explicit `AspPythonConfig` is passed: ```toml -[tool.python-lang-project-harness] +[tool.asp-python] disabled_rule_ids = ["PY-MOD-R002"] blocking_rule_ids = ["PY-AGENT-POLICY-007"] source_dir_names = ["lib"] @@ -109,12 +109,12 @@ include_tests = false ``` Explicit function parameters still win for one-call source/test/extra path -classification. Passing an explicit `PythonHarnessConfig` from Python code +classification. Passing an explicit `AspPythonConfig` from Python code opts out of project-local config loading for that call. ## Explicit-Path Runner -Use `run_python_lang_harness()` or `assert_python_lang_harness_clean()` for +Use `run_asp_python_paths()` or `assert_asp_python_paths_clean()` for explicit files or directories. Requested paths must exist. This runner does not attach a project scope, so project-resolution evaluators stay quiet. File-local rule packs can still run when they only need parser facts. @@ -134,5 +134,5 @@ project-scoped reports, rendered paths are project-relative. Use it before repair-oriented agents choose which subtree to edit. :RELATIONS: -:LINKS: [Harness Boundary](../01_core/101_harness_boundary.md), [Rule Catalog](201_rule_catalog.md), [CLI](203_cli.md) +:LINKS: [ASP Python Boundary](../01_core/101_asp_python_boundary.md), [Rule Catalog](201_rule_catalog.md), [CLI](203_cli.md) :END: diff --git a/docs/03_features/203_cli.md b/docs/03_features/203_cli.md deleted file mode 100644 index 2773cdd..0000000 --- a/docs/03_features/203_cli.md +++ /dev/null @@ -1,176 +0,0 @@ -# CLI - -:PROPERTIES: -:ID: 7f8492d9dbe34f9795a04b2a6f9d72e102c7cf21 -:TYPE: FEATURE -:STATUS: ACTIVE -:LAST_SYNC: 2026-04-30 -:END: - -The package exposes `asp-python` as the semantic-language provider binary and -as a thin command-line adapter over the default project harness runner: - -```shell -asp-python search ... [--json] [--package PATH] [PROJECT_ROOT] -asp-python check [--changed | --full] [--json] [PROJECT_ROOT] -asp-python agent doctor [--json] [PROJECT_ROOT] -asp-python agent guide [PROJECT_ROOT] -asp-python [--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. - -## Semantic Language Identity - -The public semantic-language identity is -`languageId=python`, `providerId=asp-python`, `binary=asp-python`, and -`namespace=agent.semantic-protocols.languages.python.asp-python`. -`asp-python agent doctor --json` emits a `semantic-language-registry.v1` -document with method descriptors, `capabilities`, `ingestRequiredFor`, and -schema registrations for: - -- `schemas/semantic-search-packet.v1.schema.json` -- `schemas/semantic-language-registry.v1.schema.json` -- `schemas/python-semantic-capabilities.v1.schema.json` - -The common registry schema owns the capability descriptor shape. The Python -provider owns its capability vocabulary in the Python-local schema. - -`asp-python agent guide` emits the provider-owned searchflow guide consumed by -root hook deny messages. The root `semantic-agent-hook` only points agents to -this command; the Python provider owns the actual prime, owner, text, ingest, -check, and subagent guidance. - -## Search Views - -`search workspace` is the workspace/router entry point. It emits package-root -facts, dependency counts, import edges, and `next=prime:` targets. - -`search prime` is the low-token project map. It ranks public source owners -before test owners, emits dependency nodes, import edges, findings, and next -actions for `owner`, owner-scoped `text`, and de-duplicated `deps` follow-up -queries. Compact owner lines show only the first few public exports and text -seeds; the JSON packet keeps the full owner export list. - -`search lexical ` searches parser-visible owner paths, namespaces, public -exports, symbols, and source text. `search lexical owner tests` keeps the -matched owners and their importing tests in the same packet, so an agent can -avoid a broad second pass after finding a rule id, symbol, or source token. -Repeated same-axis text probes can be combined with -`search lexical --query-set --query-set [owner tests] [--owner ]`. -Each term is reported in `querySet`, and the packet records -`queryComposition` with `selector=exact-set`. `--owner ` scopes the -query-set to one parser-visible owner. Comma-separated text remains a literal -query, not query-set syntax; a first text query that looks like a flag is also -accepted as a literal query. - -`search ingest` accepts stdin from tools such as `rg -n`, vimgrep output, -plain path lists, or NUL path lists. It detects the shape and groups the input -back to parser-visible owners when possible. - -Dependency and API views are Python-native: - -- `search dependency ` and `search deps ` - combine `pyproject.toml` dependency facts with local import usage. Version - scope is rendered as `current`, `external`, or `unknown`. The `deps` view - also advertises follow-up routes for `dependency`, `public-external-types`, - `api`, and, for current-version API queries, `text` and `tests`. -- `search api ` searches public exports and public symbol shapes. -- `search public-external-types ` searches public functions, classes, - and annotated assignments whose parser-owned signature/base/decorator/type - text exposes that dependency. Direct package/alias references are reported as - `public-external-type`; named imports are reported as - `possible-public-external-type`. -- `search symbol `, `search callsite `, `search import `, - and `search tests ` expose focused parser facts for follow-up repair - loops. Callsite search uses parser-collected `PythonCall` facts, including - dotted method calls such as `client.worker.process(...)`. - -## Scope Options - -The CLI mirrors the project runner's classification and inclusion options: - -```shell -asp-python --source-dir lib --test-dir checks --extra-path tools . -asp-python --no-tests . -``` - -`--source-dir`, `--test-dir`, and `--extra-path` can be repeated. Supplying one -or more source or test roots replaces the default classification names for -that run. The parser still scans the whole project root by default. -`--extra-path` adds an external project path or a single Python file relative -to the project root. `--no-tests` skips test parser discovery but keeps -tests-root layout policy active. - -## Policy Options - -Rule-level policy can be adjusted for one run: - -```shell -asp-python --disable-rule PY-MOD-R002 . -asp-python --block-rule PY-AGENT-POLICY-007 . -``` - -`--disable-rule` suppresses a stable rule id. `--block-rule` promotes a stable -rule id to blocking, which is useful when a project wants selected -`PY-AGENT-*` advice to fail CI without changing the default catalog severity. -Both options can be repeated. - -When these flags are omitted, the CLI reads `[tool.python-lang-project-harness]` -from the target project's `pyproject.toml`. CLI rule flags override the matching -rule-id fields for that run while preserving the rest of the project-local -config. - -## Output Modes - -Compact text is the default output for humans and repair-oriented agents: - -```shell -asp-python . -``` - -When configured-blocking findings exist, the first line is the first concrete -`[RULE] Severity:` finding rather than a run-summary header. Clean runs still -print the short `[ok]` summary. Advice-only runs start with `[advice]` and the -concrete advisory findings, without an `[ok]` preamble or issue-count line. -Project-scoped compact text renders locations relative to the project root; -use `--json` for structured original paths. - -Use `--json` when a tool needs the structured `PythonHarnessReport` payload: - -```shell -asp-python --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 -asp-python --agent-snapshot . -``` - -`--json` and `--agent-snapshot` are mutually exclusive. - -## Exit Codes - -- `0`: no configured-blocking findings -- `1`: configured-blocking findings exist -- `2`: CLI argument or project-root error - -`PY-AGENT-*` findings remain advisory by default. Advice is rendered in compact -text, but it does not change the exit code unless the caller promotes `Info` -severity through library APIs or promotes selected rule ids with -`--block-rule`. - -## Library Boundary - -`run_cli_from_env()` is the console-script entrypoint. `run_cli(...)` is exposed -for tests and embeddings that want to provide explicit streams or a current -working directory. Both functions delegate to `run_python_project_harness()` and -the public renderers. - -:RELATIONS: -:LINKS: [Harness Boundary](../01_core/101_harness_boundary.md), [Rule Catalog](201_rule_catalog.md) -:END: diff --git a/docs/03_features/204_pytest.md b/docs/03_features/204_pytest.md index 46afd34..e1de1f2 100644 --- a/docs/03_features/204_pytest.md +++ b/docs/03_features/204_pytest.md @@ -7,7 +7,7 @@ :LAST_SYNC: 2026-04-30 :END: -`python-lang-project-harness` is designed to be loaded by downstream Python +`asp-python` is designed to be loaded by downstream Python projects as a test/dev dependency. The pytest surface has two supported entry points: an auto-loaded pytest plugin and an explicit test helper. @@ -19,58 +19,59 @@ Add the package to the downstream test dependency group together with pytest: [dependency-groups] test = [ "pytest>=8", - "python-lang-project-harness[pytest]>=0.1.0", + "asp-python[pytest]>=0.1.0", ] [tool.pytest.ini_options] -addopts = ["--python-project-harness"] +addopts = ["--asp-python"] ``` The distribution exposes this plugin entry point: ```toml [project.entry-points.pytest11] -python_lang_project_harness = "python_lang_project_harness.pytest_plugin" +asp_python = "asp_python.pytest_plugin" ``` -Pytest auto-loads the plugin when the package is installed, but the harness is -quiet unless `--python-project-harness` is enabled. This keeps the package safe +Pytest auto-loads the plugin when the package is installed, but ASP Python is +quiet unless `--asp-python` 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 +show that a project depends on `asp-python` for test/dev use, +the project must expose either `--asp-python` in pytest addopts or +an explicit `asp_python_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 -[tool.python-lang-project-harness] +[tool.asp-python] disabled_rule_ids = ["PY-MOD-R002"] blocking_rule_ids = ["PY-AGENT-POLICY-007"] ``` Supported plugin options: -- `--python-project-harness`: collect and run one harness item. -- `--python-project-harness-root PATH`: choose the project root; defaults to - pytest `rootdir`. -- `--python-project-harness-no-tests`: skip parsing test files while still +- `--asp-python`: collect and run one ASP Python item. +- `--asp-python-root PATH`: choose the project root. When omitted, + a single path-scoped pytest invocation uses the nearest real Python project + metadata; mixed or workspace-level invocations default to pytest `rootdir`. +- `--asp-python-no-tests`: skip parsing test files while still evaluating tests-root layout. -- `--python-project-harness-source-dir NAME`: add one source classification +- `--asp-python-source-dir NAME`: add one source classification root name; can be repeated. -- `--python-project-harness-test-dir NAME`: add one test classification root +- `--asp-python-test-dir NAME`: add one test classification root name; can be repeated. -- `--python-project-harness-extra-path NAME`: add one external project path; +- `--asp-python-extra-path NAME`: add one external project path; can be repeated. -- `--python-project-harness-disable-rule RULE_ID`: suppress one stable rule +- `--asp-python-disable-rule RULE_ID`: suppress one stable rule id; can be repeated. -- `--python-project-harness-block-rule RULE_ID`: promote one stable rule id to +- `--asp-python-block-rule RULE_ID`: promote one stable rule id to blocking; can be repeated. -- `--python-project-harness-error-only`: fail only on parser errors. -- `--python-project-harness-no-advice`: hide non-blocking advice in assertion +- `--asp-python-error-only`: fail only on parser errors. +- `--asp-python-no-advice`: hide non-blocking advice in assertion output. ## Explicit Test Helper @@ -78,9 +79,9 @@ Supported plugin options: Projects that prefer a committed test file can mount the same runner directly: ```python -from python_lang_project_harness.pytest import python_project_harness_test +from asp_python.pytest import asp_python_test -test_python_project_harness_policy = python_project_harness_test() +test_asp_python_policy = asp_python_test() ``` The helper defaults to `Path(".")` and returns a pytest-collectable callable. @@ -88,11 +89,11 @@ Callers can pass the same project-resolution options used by the library runner: ```python from python_lang_parser import PythonDiagnosticSeverity -from python_lang_project_harness import PythonHarnessConfig -from python_lang_project_harness.pytest import python_project_harness_test +from asp_python import AspPythonConfig +from asp_python.pytest import asp_python_test -test_python_project_harness_policy = python_project_harness_test( - config=PythonHarnessConfig( +test_asp_python_policy = asp_python_test( + config=AspPythonConfig( disabled_rule_ids=frozenset({"PY-MOD-R002"}), blocking_rule_ids=frozenset({"PY-AGENT-POLICY-007"}), ), @@ -108,5 +109,5 @@ roots rather than narrowing parser coverage. The pytest layer does not own Python parsing, source scanning semantics, or policy-specific AST logic. :RELATIONS: -:LINKS: [Harness Boundary](../01_core/101_harness_boundary.md), [Runner Modes](202_runner_modes.md), [CLI](203_cli.md) +:LINKS: [ASP Python Boundary](../01_core/101_asp_python_boundary.md), [Runner Modes](202_runner_modes.md), [CLI](203_cli.md) :END: diff --git a/docs/03_features/205_verification.md b/docs/03_features/205_verification.md index d1c7125..2b0086a 100644 --- a/docs/03_features/205_verification.md +++ b/docs/03_features/205_verification.md @@ -7,22 +7,22 @@ :LAST_SYNC: 2026-05-03 :END: -Python verification planning is a library-first Agent contract. The harness +Python verification planning is a library-first Agent contract. ASP Python 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 ( +from asp_python import ( PythonOwnerResponsibility, PythonVerificationProfileHint, PythonVerificationTaskKind, - default_python_harness_config, + default_asp_python_config, plan_python_project_verification_with_config, render_python_verification_plan, ) -config = default_python_harness_config().with_verification_profile_hint( +config = default_asp_python_config().with_verification_profile_hint( PythonVerificationProfileHint( "src/pkg/api.py", (PythonOwnerResponsibility.PUBLIC_API,), @@ -73,21 +73,21 @@ can patch the policy from the profile index without reparsing `pyproject.toml`. 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]`. +`[tool.asp-python.verification]`. ```toml -[tool.python-lang-project-harness.verification] +[tool.asp-python.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] +[tool.asp-python.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] +[tool.asp-python.verification.skill_bindings] security = { skill = "python-security-review", adapter = "bandit" } -[tool.python-lang-project-harness.verification.skill_descriptors] +[tool.asp-python.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" }] } ``` @@ -97,5 +97,5 @@ 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) +:LINKS: [ASP Python Boundary](../01_core/101_asp_python_boundary.md), [CLI](203_cli.md) :END: diff --git a/docs/index.md b/docs/index.md index f2db3b9..fa29b72 100644 --- a/docs/index.md +++ b/docs/index.md @@ -1,4 +1,4 @@ -# Python Lang Project Harness: Map Of Content +# ASP Python: Map Of Content :PROPERTIES: :ID: efd68dc9011b23c38164eb503920b77bdfdd6c68 @@ -7,14 +7,14 @@ :LAST_SYNC: 2026-04-30 :END: -Documentation surface for the standalone Python language project harness. The +Documentation surface for ASP Python. The README stays compact; durable package details live here so runner modes, rule catalogs, and embedding contracts can evolve without turning the package entrypoint into a catch-all reference page. ## 01_core: Architecture And Foundation -- [Harness Boundary](01_core/101_harness_boundary.md): package ownership, +- [ASP Python Boundary](01_core/101_asp_python_boundary.md): package ownership, parser boundary, project runner, explicit-path runner, pytest embedding, and non-goals. @@ -33,7 +33,7 @@ entrypoint into a catch-all reference page. 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), [Verification Planning](03_features/205_verification.md) +:LINKS: [ASP Python Boundary](01_core/101_asp_python_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: --- diff --git a/provider/asp-provider-registration.json b/provider/asp-provider-registration.json index f359b9a..f96ff67 100644 --- a/provider/asp-provider-registration.json +++ b/provider/asp-provider-registration.json @@ -56,18 +56,18 @@ "operations": [ { "operation": "projection-batch", - "requestSchemaId": "https://schemas.agent-semantic-protocols.dev/provider-language-projection-batch-request.schema.json", - "responseSchemaId": "https://schemas.agent-semantic-protocols.dev/provider-language-projection-batch-response.schema.json" + "requestSchema": {"schemaId": "agent.semantic-protocols.provider-language-projection-batch-request", "schemaVersion": "1"}, + "responseSchema": {"schemaId": "agent.semantic-protocols.provider-language-projection-batch-response", "schemaVersion": "1"} }, { "operation": "project-resolution", - "requestSchemaId": "https://schemas.agent-semantic-protocols.dev/provider-project-resolution-request.schema.json", - "responseSchemaId": "https://schemas.agent-semantic-protocols.dev/provider-project-resolution-response.schema.json" + "requestSchema": {"schemaId": "agent.semantic-protocols.provider-project-resolution-request", "schemaVersion": "1"}, + "responseSchema": {"schemaId": "agent.semantic-protocols.provider-project-resolution-response", "schemaVersion": "1"} }, { "operation": "query", - "requestSchemaId": "https://agent-semantic-protocols.dev/schemas/provider-native-exact-request.v1.schema.json", - "responseSchemaId": "https://agent-semantic-protocols.dev/schemas/provider-native-exact-response.v1.schema.json" + "requestSchema": {"schemaId": "agent.semantic-protocols.provider-native-exact-request", "schemaVersion": "1"}, + "responseSchema": {"schemaId": "agent.semantic-protocols.provider-native-exact-projection", "schemaVersion": "1"} } ] }, @@ -96,10 +96,13 @@ "concurrency": "shared-read", "streaming": false }, - "output": { - "schemaId": "agent.semantic-protocols.source-index-projection", - "mediaType": "application/json" - }, + "output": { + "schema": { + "schemaId": "agent.semantic-protocols.source-index-projection", + "schemaVersion": "1" + }, + "mediaType": "application/json" + }, "failureSchemaIds": [ "agent.semantic-protocols.route-failure" ], @@ -118,7 +121,7 @@ "schemaVersion": "1", "routeId": "python.search", "operation": "search", - "requestSchemaId": "agent.semantic-protocols.asp-client-search-request", + "requestSchema": {"schemaId": "agent.semantic-protocols.asp-client-search-request", "schemaVersion": "1"}, "authority": "asp-server", "target": { "languageId": "python", @@ -143,10 +146,13 @@ "concurrency": "shared-read", "streaming": false }, - "output": { - "schemaId": "agent.semantic-protocols.search-packet", - "mediaType": "application/json" - }, + "output": { + "schema": { + "schemaId": "agent.semantic-protocols.search-packet", + "schemaVersion": "1" + }, + "mediaType": "application/json" + }, "failureSchemaIds": [ "agent.semantic-protocols.route-failure" ], @@ -165,7 +171,7 @@ "schemaVersion": "1", "routeId": "python.query", "operation": "query", - "requestSchemaId": "agent.semantic-protocols.asp-client-exact-query-request", + "requestSchema": {"schemaId": "agent.semantic-protocols.asp-client-exact-query-request", "schemaVersion": "1"}, "authority": "asp-server", "target": { "languageId": "python", @@ -190,10 +196,13 @@ "concurrency": "shared-read", "streaming": false }, - "output": { - "schemaId": "agent.semantic-protocols.query-result", - "mediaType": "application/json" - }, + "output": { + "schema": { + "schemaId": "agent.semantic-protocols.query-result", + "schemaVersion": "1" + }, + "mediaType": "application/json" + }, "failureSchemaIds": [ "agent.semantic-protocols.route-failure" ], @@ -206,54 +215,6 @@ "spanName": "asp.route.python.query", "attributeSlots": [] } - }, - { - "schemaId": "agent.semantic-protocols.provider-route", - "schemaVersion": "1", - "routeId": "python.search.owner", - "operation": "search.owner", - "requestSchemaId": "agent.semantic-protocols.asp-client-owner-search-request", - "authority": "asp-server", - "target": { - "languageId": "python", - "providerId": "asp-python" - }, - "inputs": [ - {"name": "schemaId", "valueType": "string", "cardinality": "required", "source": "request"}, - {"name": "schemaVersion", "valueType": "string", "cardinality": "required", "source": "request"}, - {"name": "ownerPath", "valueType": "workspace-relative-path", "cardinality": "required", "source": "request"}, - {"name": "query", "valueType": "string", "cardinality": "optional", "source": "request"}, - {"name": "view", "valueType": "presentation", "cardinality": "optional", "source": "request"} - ], - "requirements": [ - { - "kind": "state", - "state": "terminal-generation" - } - ], - "effects": { - "access": "read", - "idempotent": true, - "cancellable": true, - "concurrency": "shared-read", - "streaming": false - }, - "output": { - "schemaId": "agent.semantic-protocols.search-packet", - "mediaType": "application/json" - }, - "failureSchemaIds": [ - "agent.semantic-protocols.route-failure" - ], - "cache": { - "authority": "asp-server", - "scope": "workspace", - "keySlots": [] - }, - "telemetry": { - "spanName": "asp.route.python.search.owner", - "attributeSlots": [] - } } ], "schemas": [ diff --git a/provider/asp-provider-workspace-install.json b/provider/asp-provider-workspace-install.json index 2eeba4d..508314e 100644 --- a/provider/asp-provider-workspace-install.json +++ b/provider/asp-provider-workspace-install.json @@ -9,7 +9,7 @@ "providerRegistration": "asp-provider-registration.json", "schemaBundleReceipt": "../schemas/.asp-schema-manager-receipt.json", "workspaceArtifact": { - "root": "languages/python-lang-project-harness/.venv", + "root": "languages/asp-python/.venv", "entrypoint": "bin/asp-python", "runtimeDependencies": [ { @@ -34,15 +34,15 @@ "--no-editable", "--no-cache", "--reinstall-package", - "python-lang-project-harness" + "asp-python" ], - "workingDirectory": "languages/python-lang-project-harness", + "workingDirectory": "languages/asp-python", "sourceSnapshotAnchors": [ - "languages/python-lang-project-harness/pyproject.toml", - "languages/python-lang-project-harness/uv.lock" + "languages/asp-python/pyproject.toml", + "languages/asp-python/uv.lock" ], "derivedPaths": [ - "languages/python-lang-project-harness/.venv" + "languages/asp-python/.venv" ], "env": {} } diff --git a/pyproject.toml b/pyproject.toml index f71d32d..d1591f4 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,12 +1,12 @@ [project] -name = "python-lang-project-harness" +name = "asp-python" version = "0.1.0" -description = "Project-level Python language harness with compact diagnostics for agents" +description = "Python-native policy and semantic tooling with compact diagnostics for agents" readme = "README.md" requires-python = ">=3.12" import-names = [ "python_lang_parser", - "python_lang_project_harness", + "asp_python", ] dependencies = ["blake3>=1.0.8,<2"] @@ -16,10 +16,10 @@ pytest = [ ] [project.scripts] -asp-python = "python_lang_project_harness:run_cli_from_env" +asp-python = "asp_python:run_cli_from_env" [project.entry-points.pytest11] -python_lang_project_harness = "python_lang_project_harness.pytest_plugin" +asp_python = "asp_python.pytest_plugin" [dependency-groups] test = [ @@ -35,11 +35,11 @@ build-backend = "hatchling.build" [tool.hatch.build.targets.wheel] packages = [ "src/python_lang_parser", - "src/python_lang_project_harness", + "src/asp_python", ] [tool.hatch.build.targets.wheel.force-include] -"schemas/semantic-language-registry.v1.schema.json" = "python_lang_project_harness/schemas/semantic-language-registry.v1.schema.json" +"schemas/python-semantic-capabilities.v1.schema.json" = "asp_python/schemas/python-semantic-capabilities.v1.schema.json" [tool.uv] package = true @@ -47,26 +47,26 @@ package = true [tool.pytest.ini_options] pythonpath = ["src"] -[[tool.python-lang-project-harness.verification.profile_hints]] +[[tool.asp-python.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" +rationale = "self pytest addopts and CI cover the ASP Python gate contract" -[[tool.python-lang-project-harness.verification.profile_hints]] +[[tool.asp-python.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" +rationale = "self parser tests, ASP Python CLI, and build cover the parser public facade" -[[tool.python-lang-project-harness.verification.profile_hints]] -owner_path = "src/python_lang_project_harness/__init__.py" +[[tool.asp-python.verification.profile_hints]] +owner_path = "src/asp_python/__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" +rationale = "self public API tests, CLI tests, pytest gate, and build cover the ASP Python facade" -[[tool.python-lang-project-harness.verification.profile_hints]] -owner_path = "src/python_lang_project_harness/pytest_plugin.py" +[[tool.asp-python.verification.profile_hints]] +owner_path = "src/asp_python/pytest_plugin.py" responsibilities = ["cli"] verification_tasks_enabled = false rationale = "self pytest addopts exercise the pytest plugin gate" diff --git a/schemas/.asp-schema-manager-membership.json b/schemas/.asp-schema-manager-membership.json new file mode 100644 index 0000000..b6770ff --- /dev/null +++ b/schemas/.asp-schema-manager-membership.json @@ -0,0 +1,507 @@ +{ + "languageId": "python", + "profileDigest": "blake3-256:a1c247a2b628f4e7fe173ecaa855dfc90e1416c1d168f3b9d67c907a7d12beb2", + "bundleDigest": "blake3-256:5f087ca57ea83ca4943780b03bda861dd6bfd0ba81524ab9f9991a5ec765db9d", + "schemas": [ + { + "name": "asp-client-cancellation-probe-request.schema.json", + "digest": "blake3-256:b34c40da202136d5377f5584f095319a7d4877837cb7c57275bd8a1ab177bf92" + }, + { + "name": "asp-client-cancellation-probe-response.schema.json", + "digest": "blake3-256:ea85dd8bba3d5049893fb029e227c9837a55804274879e70f8c3c4ebeaac95d7" + }, + { + "name": "asp-client-conformance.schema.json", + "digest": "blake3-256:bc12a1065c633e47a848f42a89975206c0538e5b66b0b54085468481b44145c9" + }, + { + "name": "asp-client-dispatch-failure.schema.json", + "digest": "blake3-256:7fcefb16f740f471c32e91f233a82760b8e3aaf57b0dafcee08f11c3fa457f36" + }, + { + "name": "asp-client-exact-query-failure.schema.json", + "digest": "blake3-256:0a59369cdd4dd4a305f60fd073733acddabd0593c475817f93064ba54e065cf7" + }, + { + "name": "asp-client-exact-query-request.schema.json", + "digest": "blake3-256:95e9b4cf48223f31bbe86a816890e4689070790288b300799290d7041adaa7ba" + }, + { + "name": "asp-client-exact-query-response.schema.json", + "digest": "blake3-256:95e3897fce365809610d2ee3eea636cb2d21d56037721dd81fd8e1e96cc257ef" + }, + { + "name": "asp-client-frame.schema.json", + "digest": "blake3-256:67df97458bb86ef192bfe03f6200860fa2c1b952199a92881a9d474acc04666b" + }, + { + "name": "asp-client-graphs-timeline-request.v1.schema.json", + "digest": "blake3-256:dc856c6a65c352081670053d6347d7632d71cc7f90e9248b2553833f30b178df" + }, + { + "name": "asp-client-protocol-catalog.schema.json", + "digest": "blake3-256:e3b6411bd9e583608a41d01852b052ba1438948f485e4ec08750c7d6d454c64a" + }, + { + "name": "asp-client-query-readiness-failure.schema.json", + "digest": "blake3-256:451736da76395cdb07ffc14523323657db1be90f22bd4e776282f47bc3e32fb3" + }, + { + "name": "asp-client-schema-bundle-request.schema.json", + "digest": "blake3-256:b04fed590c0e023686f28cd77c045726c851abd960b5aa1f9734f45a4daacf1c" + }, + { + "name": "asp-client-schema-bundle-response.schema.json", + "digest": "blake3-256:9e42923bf92de5b10b74ec2412b1d855f46219e2153e14a4cf7560a5598675a4" + }, + { + "name": "asp-client-server-descriptor.schema.json", + "digest": "blake3-256:d4e432eacba544a5eb504c1e6d1161d9599a14cfb85d62d2d5da0e1d47f6cdde" + }, + { + "name": "asp-client-source-index-lookup-request.schema.json", + "digest": "blake3-256:2c4e52f1a8d8d39b8a42903ccd662c966166e28a20f9f7645b7a6d6d622a67c0" + }, + { + "name": "asp-client-work-counters.schema.json", + "digest": "blake3-256:aba81ca4e8f518bb515cb7b014b5c535cab3d1634dfd9ae6ccdf24d553ba3133" + }, + { + "name": "asp-client-workspace-generation-ensure-ready-request.schema.json", + "digest": "blake3-256:9f7c9a437713874b8ea5c4e701f61adf355f26bbc35ddd5a59a2d6889d1f2bd0" + }, + { + "name": "asp-client-workspace-query-playbook-request.v1.schema.json", + "digest": "blake3-256:bc7510bad29790b74a91ec1070451c311909f5e154e2980d25a337171f649332" + }, + { + "name": "asp-client-workspace-search-playbook-request.v1.schema.json", + "digest": "blake3-256:f32ea2abb85995ed2e2b5cbc9182de75b6e6b59228851184d80407d7fca4fa06" + }, + { + "name": "asp-client-workspace-source-mutation.schema.json", + "digest": "blake3-256:74b0baf814e96b3f2f47eefcc537ae53c5ac740315cdbc8c4f1079dcc2f484a8" + }, + { + "name": "asp-client-workspace-syntax-plan-context-request.v1.schema.json", + "digest": "blake3-256:5de15bb202116b438456fce1077baf739360c3a01791f9e6f6c97a6f7efed961" + }, + { + "name": "asp-client-workspace-syntax-plan-context-response.v1.schema.json", + "digest": "blake3-256:ef83ac19ddf8dc9dbd874272e9de12bd861ca82bface838fab5d8d9a28fa5517" + }, + { + "name": "asp-client-workspace-syntax-query-request.v1.schema.json", + "digest": "blake3-256:f37f7e4f5ba66e48d3b0f6d2b7b6bd8770e4164b290f90f2b05a9da21be5f2c4" + }, + { + "name": "asp-client-workspace-syntax-query-response.v1.schema.json", + "digest": "blake3-256:ea0a59d79d102ff37d34583d2075962fad175dd93c64eee582843863d38c5b16" + }, + { + "name": "asp-python-graphs-session.v1.schema.json", + "digest": "blake3-256:74ced1d824c781b78062fd9f1f4ef4d3859adf07bb22a7cecb0e1011a99bbe7e" + }, + { + "name": "callable-skeleton.schema.json", + "digest": "blake3-256:094f5d077b8aeaf1be0d835246a8bf1f6c6ff26e2238e6d9147236daaa22e9bd" + }, + { + "name": "canonical-item-selector.v1.schema.json", + "digest": "blake3-256:97540b46b0f4fb0b450771d010b9e0000a1366fb0fb1cb7bab209b081c095c68" + }, + { + "name": "canonical-language-item-identity.schema.json", + "digest": "blake3-256:29932815327cfba4424cc6443f04a9b3586c6ab8ce1f64d76904e9e80fc970e4" + }, + { + "name": "content-binding.schema.json", + "digest": "blake3-256:d994d67092a39eb4c7cec8df470839372d06c815f9068c7e685336ad251a0b1a" + }, + { + "name": "content-publication-commit.v1.schema.json", + "digest": "blake3-256:afac75ce2bece1c60ffb9435a5c107c9687088459988dc7c3e79fd78c38aacfe" + }, + { + "name": "enhanced-tree-sitter-query-capability-table.v1.schema.json", + "digest": "blake3-256:14089324f44d5f9725cad72280575cc753c03892eff81e790ece6f9a4e4a4f6d" + }, + { + "name": "enhanced-tree-sitter-query-operator-table.v1.schema.json", + "digest": "blake3-256:eef530969846082023b91405104663a9a28fe56ce449ee1368f6916c10c5e2a7" + }, + { + "name": "exact-definitions.v1.schema.json", + "digest": "blake3-256:f2a9d369c221ad07ec3376150fec180c617d40318d4b0d49def6e68b0c5d00f1" + }, + { + "name": "exact-structural-selector.v1.schema.json", + "digest": "blake3-256:3def98370fcc3d4a3c7958b5b31e32398d8b5d4aa33d2831a95026078603f8f9" + }, + { + "name": "grpc-warm-latency-receipt.schema.json", + "digest": "blake3-256:b05cb70a17c9169e81fef1491fb6e6c972e9bab8843131af5637d52405424297" + }, + { + "name": "host-workspace-initialization-binding.v1.schema.json", + "digest": "blake3-256:bd2fd298d4b3f8e469986a092aa40653a3bbfc59c4f51810c1829f4f6dd146a0" + }, + { + "name": "language-package-graph.schema.json", + "digest": "blake3-256:4c2a985b14d27fb574e989452f031c545df08312d7e656044743af851550fafd" + }, + { + "name": "language-schema-bundle-receipt.schema.json", + "digest": "blake3-256:11f17ec46eb769fc3d650236af1fc19ba02486bce478b57d2fdd62a0cab3512d" + }, + { + "name": "large-search-playbook-performance-receipt.v1.schema.json", + "digest": "blake3-256:fc1ad6a88802f416a49e73c6b020a237f0bae61a5b3f385e82981c31956796c0" + }, + { + "name": "lexical-postings-work-reduction-receipt.schema.json", + "digest": "blake3-256:416d3bbbd584a7268632aa9852eae189501f923bd39c5af9ed5469d001c9fff5" + }, + { + "name": "project-resolution.schema.json", + "digest": "blake3-256:bfa2f1966d9d05e0f0c05cdb635112513498dedb076edf760f0504db60a358bb" + }, + { + "name": "project-topology-inference-receipt.v1.schema.json", + "digest": "blake3-256:a55c11817cde68db3a30092f06467ae49ff43508ccd155d15432df5d57cb52bb" + }, + { + "name": "project-topology-library.v1.schema.json", + "digest": "blake3-256:bb4d6d756d488b210d2ab104c60db812616bf100c150864c4531256052a4830c" + }, + { + "name": "project-workspace-binding.v1.schema.json", + "digest": "blake3-256:85a1cfdff5e432e593bb346ae447116a4bf11e5e49be10530ff1362e81603061" + }, + { + "name": "provider-definitions.v1.schema.json", + "digest": "blake3-256:0ff66bd35a2c1561424f0aa2b95df689cfcb6513059ea5d06245f7efb38c45ce" + }, + { + "name": "provider-document-resolution-descriptor.v1.schema.json", + "digest": "blake3-256:b8ac27d78e7450932817b7018f4c2b98130d64e8fb5f9425c48df39cd6d0c5b5" + }, + { + "name": "provider-language-projection-batch-request.schema.json", + "digest": "blake3-256:9352612e33943a7953a327dcf691797cf4f177ffabe7c920d975f34b5d4f9722" + }, + { + "name": "provider-language-projection-batch-response.schema.json", + "digest": "blake3-256:1fb2e8d535c278bb46e09f391b6b1812d1242491089a6abf49603783b8c6d122" + }, + { + "name": "provider-manifest.schema.json", + "digest": "blake3-256:5beb3ddeeac806d3dc69285b36b77b2cdf262be154d1c9f11fa8db780907c0f9" + }, + { + "name": "provider-method-argument-projection.v1.schema.json", + "digest": "blake3-256:4f6716b4dda5710f523cd06292a33dd91b9b6b171c022cd9ff04f3796d3765ed" + }, + { + "name": "provider-native-exact-request.v1.schema.json", + "digest": "blake3-256:57125f2235a6a8350d7f73d8bc2b5c7a89a8a793b9f9653201a7138c49b598c8" + }, + { + "name": "provider-native-exact-response.v1.schema.json", + "digest": "blake3-256:81a3c3149a15bdd3171e780e2786f7eac2be280345d50254cb620c6ef39aa25e" + }, + { + "name": "provider-project-resolution-descriptor.schema.json", + "digest": "blake3-256:1bd422dbb2b0c8d561aff3df34f6ac0361c0725279bd64c55e63b17a15036e53" + }, + { + "name": "provider-project-resolution-request.schema.json", + "digest": "blake3-256:d72002bef3a99e162e3f338a38933250666f058450c68d29708086716ad513a8" + }, + { + "name": "provider-project-resolution-response.schema.json", + "digest": "blake3-256:bc489fdb7a8bf86c8460acb50a436f7a2c6233afa4cb1bd97d7acdf16d5b208c" + }, + { + "name": "provider-query-pack-descriptor.schema.json", + "digest": "blake3-256:78b4ad05a5b20488f4104bf598f6dfa5edfdf1d33eb10cf4f33a2d3307b519aa" + }, + { + "name": "provider-registration.schema.json", + "digest": "blake3-256:c21cf1d2d5b5403255f38a789e0d942b6817acf7b19f2b98363be15453de6ae7" + }, + { + "name": "provider-route.schema.json", + "digest": "blake3-256:350834ac053462f48b68d7fa66aabfe01a507b83dbe6fb5547b378fa5a5180c2" + }, + { + "name": "provider-runtime-contract-descriptor.schema.json", + "digest": "blake3-256:ab45c308f484d03a7c3f5106976a8621ced27c4cd5867542b27b85d1b9240bcb" + }, + { + "name": "provider-runtime-request-stream-ack.schema.json", + "digest": "blake3-256:5082d2dbfb7fb05e23c18aaf15534a3f61ba4ed1579cc9704793f02c6e47b3aa" + }, + { + "name": "provider-runtime-request-stream-frame.schema.json", + "digest": "blake3-256:958c8f4bbabfa06b7600be3067e9feb7c5257a97597bf8221a1f424302ba2336" + }, + { + "name": "provider-workspace-install.schema.json", + "digest": "blake3-256:a607ae5a7ea24b889a6bac9ee4624c03def801da7932b8c9818de176a66f856f" + }, + { + "name": "python-generation-graph-performance-receipt.v1.schema.json", + "digest": "blake3-256:a643303b31fadc141b265c8e073b24ad69c7edd95a773bd044ac4ab5c18e8135" + }, + { + "name": "query-playbook-materialization-receipt.v1.schema.json", + "digest": "blake3-256:05d6793b11c3b2643bf394b8773c1b9131aa17cf9ddcc588f3f592542553d83c" + }, + { + "name": "query-playbook-materialization-request.v1.schema.json", + "digest": "blake3-256:080aa7a4443f7f694d38da9cd26dee1cc86f75618159c3f91f799e28591ef6d3" + }, + { + "name": "resident-search-result.v1.schema.json", + "digest": "blake3-256:f725fd1b7a004f28e7e579e6b6b76626880931ff9663bbd1de42519d66afbe78" + }, + { + "name": "resident-syntax-query-plan.v1.schema.json", + "digest": "blake3-256:b106844f168b1d0d7226050294b83395a3c4819d9496c289e3d8e24f0bb5613c" + }, + { + "name": "resolved-source-scope.v1.schema.json", + "digest": "blake3-256:0dcc57d7b2f8ffc931901231a72c61f4dca1b22d8eb7ffeccb544e3aad9c5b01" + }, + { + "name": "rg-coverage-receipt.schema.json", + "digest": "blake3-256:9faa9e8e584171ff85907fbe6715fab6408e77c505e97cd484a286c17f2aa836" + }, + { + "name": "runtime-artifact-bundle-binding.v2.schema.json", + "digest": "blake3-256:33502b4f75b3213cba8f24ba74d460525838b9627542c7fec9bdb28e35d1cbce" + }, + { + "name": "runtime-artifact-execution-closure-member.v1.schema.json", + "digest": "blake3-256:8d18d295e3e6651d665a01efc2e53e8b434261927579259d2faf48074f0d8077" + }, + { + "name": "runtime-binary-bundle.v2.schema.json", + "digest": "blake3-256:70ad1c9d87f4841788ba348fba007f8e12152bb74aa34a00f3e52c1a016eab91" + }, + { + "name": "runtime-client-terminal.schema.json", + "digest": "blake3-256:bdf3e6b1bd11b201cc7802a364d678ad7a99a74cc40df2be90f76e2f6e050e70" + }, + { + "name": "runtime-execution-binding.v2.schema.json", + "digest": "blake3-256:86f2829a05451dd84d180a6dab2cf621a14bd7e0c1ffcb3552ab9b7e61f6feb6" + }, + { + "name": "runtime-provider-search-receipt.v1.schema.json", + "digest": "blake3-256:2e439b2ee83198710238aed803c9c37cd2cb06bc7a5e2c010486cdaa605df691" + }, + { + "name": "runtime-resident-request-plane-receipt.v1.schema.json", + "digest": "blake3-256:57b822406c38a630efba30c231d09ced69d5a0f1cda34fd688a5a042816eb8da" + }, + { + "name": "runtime-search-client-timing-witness.v1.schema.json", + "digest": "blake3-256:076543ae9ba44348c9867a5454f043aeb36211742a6c299f57f33543cc071881" + }, + { + "name": "runtime-search-execution-budget.v1.schema.json", + "digest": "blake3-256:e7af7abd057b887c63490bcb143e6be322ed8e10ce244fa93e9cb043ed1107a7" + }, + { + "name": "runtime-workspace-execution-pointer.v1.schema.json", + "digest": "blake3-256:858db6ddc09da3fc1b71e45aa7cea58680bee44bfd78b0fa4c36bfb9b56b9276" + }, + { + "name": "runtime-workspace-execution-publication.v1.schema.json", + "digest": "blake3-256:889e816172637c38d87e8e0fdd8d186d0a1562149823774638f0128189d2f5d5" + }, + { + "name": "search-generation-change-set.v1.schema.json", + "digest": "blake3-256:2c367cda53c1d03672c268fe2576fff2d11b7cd349992bc0f2c96bea206a2d80" + }, + { + "name": "search-topology-settlement.v1.schema.json", + "digest": "blake3-256:0417bbaeaf787da8ab63931270b8d99d3dcfc0db64418bd4ca26b4170cd867ba" + }, + { + "name": "semantic-assurance-definitions.v1.schema.json", + "digest": "blake3-256:b8afa21d876917b25887799e41a7710f8dd1cbf22be26c515a91d5b97c91497e" + }, + { + "name": "semantic-ast-patch-definitions.v1.schema.json", + "digest": "blake3-256:5d462a785c510104a7f5ca25cfe2849453452798362cc23863164ab35cfab43e" + }, + { + "name": "semantic-ast-patch-receipt.v1.schema.json", + "digest": "blake3-256:234ddf5df9c58b68c7d4fdbd58e4815aeaf54018c13c6d352466568a73e6641d" + }, + { + "name": "semantic-ast-patch.v1.schema.json", + "digest": "blake3-256:d8fc49ecf5b6bc85b735cc22b7b15610eb00ab8a9e72b432bb0bb84349249701" + }, + { + "name": "semantic-behavior-snapshot.v1.schema.json", + "digest": "blake3-256:84354e29c9b8e7854df3361d29e65e7d914ac202bdc1a99d1e2a8072b7588dc2" + }, + { + "name": "semantic-codeql-evidence.v1.schema.json", + "digest": "blake3-256:33713f45901c55bf13286646c87135918f95e1534d522aaaa42d8e2fb3cf4cfc" + }, + { + "name": "semantic-content-compaction.v1.schema.json", + "digest": "blake3-256:8567552403687cfb06c97604862d754dcf044e168bbd9d66348a22fdce336ca0" + }, + { + "name": "semantic-definitions.v1.schema.json", + "digest": "blake3-256:6567efcdc71e72f1bb66c536dc746d5240c3e4566b098f5f7c1ef8e6a01291b2" + }, + { + "name": "semantic-dependency-topology.v1.schema.json", + "digest": "blake3-256:b52c183a98032ea7634a956c50d1b9d3bf5ce5033635949a85d136a3feda6c05" + }, + { + "name": "semantic-determinism-readiness.v1.schema.json", + "digest": "blake3-256:775296e77bde56263c8568006fdec741292d441b855a1ede63c76624863d418d" + }, + { + "name": "semantic-dev-command-log.v1.schema.json", + "digest": "blake3-256:d42cd166c1e47a2769584f86efde7f89cab0bd24174a805552b623d39a23e6ee" + }, + { + "name": "semantic-exact-selector-receipt.v1.schema.json", + "digest": "blake3-256:eabd5d76ef05a8ed48745ce6efe896a91224649fff64f469cb4b221f4e764cb9" + }, + { + "name": "semantic-fact-definitions.v1.schema.json", + "digest": "blake3-256:f49dbf083d84d1a44c8dcea72b8ed6160a36532380ce966da5110c24e44e30a5" + }, + { + "name": "semantic-fact-graph.v1.schema.json", + "digest": "blake3-256:5cb49f85016a9250b4926a9d2e685974aca681b1374b2890b0bad7720baf9dcb" + }, + { + "name": "semantic-fact-ontology.v1.schema.json", + "digest": "blake3-256:90481a76d65c4e088c221dca528a9c69e4b1f1bdcb4de9aa32dedb54f4adc017" + }, + { + "name": "semantic-flow-lite.v1.schema.json", + "digest": "blake3-256:2362a5be3afd923fe7eb9109214529b692c66c0ee97268a4c4f1866a9b407c41" + }, + { + "name": "semantic-formal-proof-pilot.v1.schema.json", + "digest": "blake3-256:f3d04cc56caa24b4a48f524810ccafe3e77838c96d600d78776108e3373202db" + }, + { + "name": "semantic-graph-resident-evaluation-request.v1.schema.json", + "digest": "blake3-256:3147c5f98f8e0af8a461c74fba6d0114dab0f173fae6e2df4a43bee8e8cd1839" + }, + { + "name": "semantic-graph-resident-evaluation-result.v1.schema.json", + "digest": "blake3-256:0b4c0b2ff31409e0e61745f2db72b20a7a786370efa304cb7c31cacc0059e3d4" + }, + { + "name": "semantic-graph-turbo-artifact-events.v1.schema.json", + "digest": "blake3-256:2be62f0ff356bbeeb0eeca549018c47709c3e8cbb34c528066579c3ab397ab88" + }, + { + "name": "semantic-graph-turbo-definitions.v1.schema.json", + "digest": "blake3-256:75fd86c5e08a03949ab0f82bae5a3c91439494c8ffdc7543f5e02aca49b26fd5" + }, + { + "name": "semantic-graph-turbo-request.v1.schema.json", + "digest": "blake3-256:4d684bf1ffe87d634cc04fd8006c4b2cab4facad7fc4830df460efbbd3aeb8ba" + }, + { + "name": "semantic-graph.v1.schema.json", + "digest": "blake3-256:294ada02587507054162c766fc932eae693c99337c869661c4627adcb2ff43c1" + }, + { + "name": "semantic-handle.v1.schema.json", + "digest": "blake3-256:ebdeb44c571eac4a69a4bfb7f5871e9f0c0063819aac8967eb0c25849eaf7d08" + }, + { + "name": "semantic-language-projection.v1.schema.json", + "digest": "blake3-256:e1e62111d74a6fe408130e1de0d3a8c2d7a16bfff2ff7e16eb842742cb5ffac5" + }, + { + "name": "semantic-language-registry.v1.schema.json", + "digest": "blake3-256:a76fc564023e63cab6b81131e96d9472788fee6c5a8f2dadd553e56df0c8be29" + }, + { + "name": "semantic-native-syntax-fact-index.v1.schema.json", + "digest": "blake3-256:0f7f98c4281e7fb025b9aaf4f643f915aa5cc6f4cbbe093c66c5d9c89d0fe030" + }, + { + "name": "semantic-owner-item-evidence.v1.schema.json", + "digest": "blake3-256:5b68dd2fbb5042beb19874401a94b4371fbdacd3b1f239e8193c604c23ec580e" + }, + { + "name": "semantic-query-packet.v1.schema.json", + "digest": "blake3-256:fb181854af59044d1c25819087924118df835af466b6a869ccf36a2ccc46ed27" + }, + { + "name": "semantic-read-packet.v1.schema.json", + "digest": "blake3-256:c6141d808a7ce236cb25506fc122aedfae69379b856846ceb3b6ec3a4de19058" + }, + { + "name": "semantic-relation-plan.v1.schema.json", + "digest": "blake3-256:fa0ca667f89e068257fd82ca1e28d5d9c988b6a907c9d7c7a13eba922031c1ff" + }, + { + "name": "semantic-review-packet.v1.schema.json", + "digest": "blake3-256:5114705b02088801a5ca0e446c749dc0b515f8990fcc3e3a2284b9a6656defbd" + }, + { + "name": "semantic-search-definitions.v1.schema.json", + "digest": "blake3-256:e9766a7280a5fe3960ab4c478b20a82c1d8bb06a9ea52b0eededc69a33495159" + }, + { + "name": "semantic-search-storage-route.v1.schema.json", + "digest": "blake3-256:cd1011b2097bfdc9b9f0a878ebadac50a7d227405ee637358a64d76763783fa0" + }, + { + "name": "semantic-source-location.v1.schema.json", + "digest": "blake3-256:a447fe4cfca0853f84c64496455b49fb8315e0abcaf841df0caecc47120a56d3" + }, + { + "name": "semantic-structural-index.v1.schema.json", + "digest": "blake3-256:9101c64b8621b7a748a47d86adcef03eedf1e786e3f3ddf47273343d545048a7" + }, + { + "name": "semantic-tree-sitter-grammar-profile.v1.schema.json", + "digest": "blake3-256:822c1ad9b0221b319a1a53e439591bdd0035462be3b34aa51f95db4d0c6dd47e" + }, + { + "name": "semantic-tree-sitter-provenance.v1.schema.json", + "digest": "blake3-256:bae387d2ac500d1752bbfe01ebd33a49bd577fcd25b140837a4e633d21ca8a67" + }, + { + "name": "semantic-tree-sitter-query.v1.schema.json", + "digest": "blake3-256:5dd9530db1fcb34a89e4c33086128dc1df62879529126fae8dac242a1ef298b5" + }, + { + "name": "semantic-type-surface.v1.schema.json", + "digest": "blake3-256:c266f095fa652b0ebc442cca930baed8fed2113f003c9980489d1183de91f8df" + }, + { + "name": "semantic-verification-receipt.v1.schema.json", + "digest": "blake3-256:4cc1c1dab5c0c4f3e544817e6dc05f66a86304fe315bd711e0d3feca45d4d41c" + }, + { + "name": "software-criterion-catalog.v1.schema.json", + "digest": "blake3-256:a9bcc4f2cd83ff3f26f7a8bc31e7f3032f8dc5679649ace6c6aec913345bc478" + }, + { + "name": "source-snapshot-evidence.v1.schema.json", + "digest": "blake3-256:b22f980c1c70e1927670be25e8d86c4d3b78538e81e6aad28b608ca9707f99cb" + } + ] +} \ No newline at end of file diff --git a/schemas/.asp-schema-manager-receipt.json b/schemas/.asp-schema-manager-receipt.json index 3d20cb3..f5285dd 100644 --- a/schemas/.asp-schema-manager-receipt.json +++ b/schemas/.asp-schema-manager-receipt.json @@ -1,321 +1,5 @@ { "schemaId": "agent.semantic-protocols.language-schema-bundle-receipt", "schemaVersion": "1", - "languageId": "python", - "profileDigest": "blake3-256:38866be447e1e414aed4bacdd8a4d9be33c453e585168a6955bd9582965d9685", - "bundleDigest": "blake3-256:1aef2b560c8c30da8b7b0731f00d5bac91edd0247a33ac8f6e2874bfbdbe5f1e", - "schemas": [ - { - "name": "asp-client-cancellation-probe-request.schema.json", - "digest": "blake3-256:9a5c1addcf17ee0a317722438c5fb2c9c7b3088acc5add96324553a74ac9c286" - }, - { - "name": "asp-client-cancellation-probe-response.schema.json", - "digest": "blake3-256:ea85dd8bba3d5049893fb029e227c9837a55804274879e70f8c3c4ebeaac95d7" - }, - { - "name": "asp-client-conformance.schema.json", - "digest": "blake3-256:bc12a1065c633e47a848f42a89975206c0538e5b66b0b54085468481b44145c9" - }, - { - "name": "asp-client-exact-query-request.schema.json", - "digest": "blake3-256:e412c1f02d08d1632e7104487d6b08d60dde6736d59254c6c2f5f45b702d4aa4" - }, - { - "name": "asp-client-exact-query-response.schema.json", - "digest": "blake3-256:65d9e3c624b301c6b8c103c8ec739237559e8e59ebc5d832847ffdf3ff85e4cd" - }, - { - "name": "asp-client-frame.schema.json", - "digest": "blake3-256:d546eb0bc0bc4539e0542526094255e25f51d2c28ac7f6089d42ecba4187c15b" - }, - { - "name": "asp-client-owner-search-request.schema.json", - "digest": "blake3-256:1c8f421a1bc84116f3a6f70593f15e9579b8a5e6a425d4051759001e7d1e6044" - }, - { - "name": "asp-client-protocol-catalog.schema.json", - "digest": "blake3-256:7faec1ae7dadc5ee28b57faadc8dedea58f320cd04aa34d9f6ffe78a5159382c" - }, - { - "name": "asp-client-search-request.schema.json", - "digest": "blake3-256:f10cb97d3d72b3c081ed1f4a67dd3aef607a9906dee7ef86032aabc938ec17fa" - }, - { - "name": "asp-client-server-descriptor.schema.json", - "digest": "blake3-256:d4e432eacba544a5eb504c1e6d1161d9599a14cfb85d62d2d5da0e1d47f6cdde" - }, - { - "name": "asp-client-workspace-source-mutation.schema.json", - "digest": "blake3-256:74b0baf814e96b3f2f47eefcc537ae53c5ac740315cdbc8c4f1079dcc2f484a8" - }, - { - "name": "callable-skeleton.schema.json", - "digest": "blake3-256:7559a2b84114bd27785e26cc079b9200cbf9b902e76a77afd74c47cc7f04b8dd" - }, - { - "name": "canonical-item-selector.v1.schema.json", - "digest": "blake3-256:97540b46b0f4fb0b450771d010b9e0000a1366fb0fb1cb7bab209b081c095c68" - }, - { - "name": "canonical-language-item-identity.schema.json", - "digest": "blake3-256:29932815327cfba4424cc6443f04a9b3586c6ab8ce1f64d76904e9e80fc970e4" - }, - { - "name": "exact-definitions.v1.schema.json", - "digest": "blake3-256:f2a9d369c221ad07ec3376150fec180c617d40318d4b0d49def6e68b0c5d00f1" - }, - { - "name": "exact-structural-selector.v1.schema.json", - "digest": "blake3-256:3def98370fcc3d4a3c7958b5b31e32398d8b5d4aa33d2831a95026078603f8f9" - }, - { - "name": "language-package-graph.schema.json", - "digest": "blake3-256:4c2a985b14d27fb574e989452f031c545df08312d7e656044743af851550fafd" - }, - { - "name": "language-schema-bundle-receipt.schema.json", - "digest": "blake3-256:43b7cb204c9d74ba05f3420940f1028555527bf82b6e010b3daf5986039ace03" - }, - { - "name": "project-resolution.schema.json", - "digest": "blake3-256:bfa2f1966d9d05e0f0c05cdb635112513498dedb076edf760f0504db60a358bb" - }, - { - "name": "provider-definitions.v1.schema.json", - "digest": "blake3-256:a380166bfe786f23a33972c357d7a058d3b116f12bbc91ff18bd8ec55640b0da" - }, - { - "name": "provider-language-projection-batch-request.schema.json", - "digest": "blake3-256:9352612e33943a7953a327dcf691797cf4f177ffabe7c920d975f34b5d4f9722" - }, - { - "name": "provider-language-projection-batch-response.schema.json", - "digest": "blake3-256:5053370366b61b10844e084d9a0d622f93d7df7fa244798a69d74d5b592122ae" - }, - { - "name": "provider-method-argument-projection.v1.schema.json", - "digest": "blake3-256:4f6716b4dda5710f523cd06292a33dd91b9b6b171c022cd9ff04f3796d3765ed" - }, - { - "name": "provider-native-exact-request.v1.schema.json", - "digest": "blake3-256:57125f2235a6a8350d7f73d8bc2b5c7a89a8a793b9f9653201a7138c49b598c8" - }, - { - "name": "provider-native-exact-response.v1.schema.json", - "digest": "blake3-256:81a3c3149a15bdd3171e780e2786f7eac2be280345d50254cb620c6ef39aa25e" - }, - { - "name": "provider-project-resolution-request.schema.json", - "digest": "blake3-256:d72002bef3a99e162e3f338a38933250666f058450c68d29708086716ad513a8" - }, - { - "name": "provider-project-resolution-response.schema.json", - "digest": "blake3-256:bc489fdb7a8bf86c8460acb50a436f7a2c6233afa4cb1bd97d7acdf16d5b208c" - }, - { - "name": "provider-query-pack-descriptor.schema.json", - "digest": "blake3-256:78b4ad05a5b20488f4104bf598f6dfa5edfdf1d33eb10cf4f33a2d3307b519aa" - }, - { - "name": "provider-registration.schema.json", - "digest": "blake3-256:c21cf1d2d5b5403255f38a789e0d942b6817acf7b19f2b98363be15453de6ae7" - }, - { - "name": "provider-route.schema.json", - "digest": "blake3-256:218789334872f4f58da9b6dde26022760399ec3e5023f7f9c24f6014efff215d" - }, - { - "name": "provider-runtime-contract-descriptor.schema.json", - "digest": "blake3-256:0fd6c3e6b93f5cabd4c959e6dfdc5349aed279050438fc69108710a4b679c6a8" - }, - { - "name": "provider-runtime-request-stream-ack.schema.json", - "digest": "blake3-256:5082d2dbfb7fb05e23c18aaf15534a3f61ba4ed1579cc9704793f02c6e47b3aa" - }, - { - "name": "provider-runtime-request-stream-frame.schema.json", - "digest": "blake3-256:958c8f4bbabfa06b7600be3067e9feb7c5257a97597bf8221a1f424302ba2336" - }, - { - "name": "provider-workspace-install.schema.json", - "digest": "blake3-256:a607ae5a7ea24b889a6bac9ee4624c03def801da7932b8c9818de176a66f856f" - }, - { - "name": "resolved-source-scope.v1.schema.json", - "digest": "blake3-256:0dcc57d7b2f8ffc931901231a72c61f4dca1b22d8eb7ffeccb544e3aad9c5b01" - }, - { - "name": "semantic-assurance-case.v1.schema.json", - "digest": "blake3-256:f672ea13226296635f24c033bb6e238f6eae8ca1639d13d9f20c94fc9cb9ac3c" - }, - { - "name": "semantic-assurance-definitions.v1.schema.json", - "digest": "blake3-256:b8afa21d876917b25887799e41a7710f8dd1cbf22be26c515a91d5b97c91497e" - }, - { - "name": "semantic-ast-patch-definitions.v1.schema.json", - "digest": "blake3-256:5d462a785c510104a7f5ca25cfe2849453452798362cc23863164ab35cfab43e" - }, - { - "name": "semantic-ast-patch-receipt.v1.schema.json", - "digest": "blake3-256:234ddf5df9c58b68c7d4fdbd58e4815aeaf54018c13c6d352466568a73e6641d" - }, - { - "name": "semantic-ast-patch.v1.schema.json", - "digest": "blake3-256:d8fc49ecf5b6bc85b735cc22b7b15610eb00ab8a9e72b432bb0bb84349249701" - }, - { - "name": "semantic-behavior-snapshot.v1.schema.json", - "digest": "blake3-256:84354e29c9b8e7854df3361d29e65e7d914ac202bdc1a99d1e2a8072b7588dc2" - }, - { - "name": "semantic-codeql-evidence.v1.schema.json", - "digest": "blake3-256:33713f45901c55bf13286646c87135918f95e1534d522aaaa42d8e2fb3cf4cfc" - }, - { - "name": "semantic-content-compaction.v1.schema.json", - "digest": "blake3-256:8567552403687cfb06c97604862d754dcf044e168bbd9d66348a22fdce336ca0" - }, - { - "name": "semantic-definitions.v1.schema.json", - "digest": "blake3-256:da29ac203939d09c6b7e8e62e529ae820489817ec74e101baa8635a9eeb35df2" - }, - { - "name": "semantic-dependency-topology.v1.schema.json", - "digest": "blake3-256:b52c183a98032ea7634a956c50d1b9d3bf5ce5033635949a85d136a3feda6c05" - }, - { - "name": "semantic-determinism-readiness.v1.schema.json", - "digest": "blake3-256:775296e77bde56263c8568006fdec741292d441b855a1ede63c76624863d418d" - }, - { - "name": "semantic-dev-command-log.v1.schema.json", - "digest": "blake3-256:d42cd166c1e47a2769584f86efde7f89cab0bd24174a805552b623d39a23e6ee" - }, - { - "name": "semantic-evidence-graph.v1.schema.json", - "digest": "blake3-256:abc6ed9c3a39730d55f34d0f8ac17f9569cf191449570851c9c6905d1166c746" - }, - { - "name": "semantic-exact-selector-receipt.v1.schema.json", - "digest": "blake3-256:eabd5d76ef05a8ed48745ce6efe896a91224649fff64f469cb4b221f4e764cb9" - }, - { - "name": "semantic-fact-definitions.v1.schema.json", - "digest": "blake3-256:f49dbf083d84d1a44c8dcea72b8ed6160a36532380ce966da5110c24e44e30a5" - }, - { - "name": "semantic-fact-graph.v1.schema.json", - "digest": "blake3-256:5cb49f85016a9250b4926a9d2e685974aca681b1374b2890b0bad7720baf9dcb" - }, - { - "name": "semantic-fact-ontology.v1.schema.json", - "digest": "blake3-256:90481a76d65c4e088c221dca528a9c69e4b1f1bdcb4de9aa32dedb54f4adc017" - }, - { - "name": "semantic-flow-lite.v1.schema.json", - "digest": "blake3-256:2362a5be3afd923fe7eb9109214529b692c66c0ee97268a4c4f1866a9b407c41" - }, - { - "name": "semantic-formal-proof-pilot.v1.schema.json", - "digest": "blake3-256:f3d04cc56caa24b4a48f524810ccafe3e77838c96d600d78776108e3373202db" - }, - { - "name": "semantic-graph-turbo-definitions.v1.schema.json", - "digest": "blake3-256:ab404159bbec13e0b35ef35a8994009ef79fa1abd3025eefce3ddc70780c554e" - }, - { - "name": "semantic-graph-turbo-request.v1.schema.json", - "digest": "blake3-256:3869108c49bb06f42f1bb370b333ffca30cb8ece9a1b5d63cba92355aee9b39c" - }, - { - "name": "semantic-graph.v1.schema.json", - "digest": "blake3-256:0ecb8b9b830d2212dd6a6bf776f10f763e96e232cdfcbc2effc5f5189ea37a34" - }, - { - "name": "semantic-handle.v1.schema.json", - "digest": "blake3-256:385ae734595c8df436ad937b23bfe9ad8bb79af7bca650917a9a0ea6222f6529" - }, - { - "name": "semantic-invariant-candidate.v1.schema.json", - "digest": "blake3-256:dfbbb5dde44825022063dbc5435434d19b684f8a5e4c4f374a5ff035a1e29b43" - }, - { - "name": "semantic-language-projection.v1.schema.json", - "digest": "blake3-256:70113f49e94ac82bc3d2a1dfaec34da537516cf77a680aa05f1758cfbe57734a" - }, - { - "name": "semantic-language-registry.v1.schema.json", - "digest": "blake3-256:5d440b89b13de434b5fdc6144f0e41678bb5dd89569e28cccd13db73ff37fff7" - }, - { - "name": "semantic-native-syntax-fact-index.v1.schema.json", - "digest": "blake3-256:0f7f98c4281e7fb025b9aaf4f643f915aa5cc6f4cbbe093c66c5d9c89d0fe030" - }, - { - "name": "semantic-owner-item-evidence.v1.schema.json", - "digest": "blake3-256:5b68dd2fbb5042beb19874401a94b4371fbdacd3b1f239e8193c604c23ec580e" - }, - { - "name": "semantic-query-packet.v1.schema.json", - "digest": "blake3-256:fb181854af59044d1c25819087924118df835af466b6a869ccf36a2ccc46ed27" - }, - { - "name": "semantic-read-packet.v1.schema.json", - "digest": "blake3-256:c6141d808a7ce236cb25506fc122aedfae69379b856846ceb3b6ec3a4de19058" - }, - { - "name": "semantic-relation-plan.v1.schema.json", - "digest": "blake3-256:fa0ca667f89e068257fd82ca1e28d5d9c988b6a907c9d7c7a13eba922031c1ff" - }, - { - "name": "semantic-review-packet.v1.schema.json", - "digest": "blake3-256:5114705b02088801a5ca0e446c749dc0b515f8990fcc3e3a2284b9a6656defbd" - }, - { - "name": "semantic-search-packet.v1.schema.json", - "digest": "blake3-256:993623779091e9fa3e0689a591481cda8393c0f42d3b55dd58b53af620598d71" - }, - { - "name": "semantic-search-storage-route.v1.schema.json", - "digest": "blake3-256:cd1011b2097bfdc9b9f0a878ebadac50a7d227405ee637358a64d76763783fa0" - }, - { - "name": "semantic-source-location.v1.schema.json", - "digest": "blake3-256:a447fe4cfca0853f84c64496455b49fb8315e0abcaf841df0caecc47120a56d3" - }, - { - "name": "semantic-structural-index.v1.schema.json", - "digest": "blake3-256:9101c64b8621b7a748a47d86adcef03eedf1e786e3f3ddf47273343d545048a7" - }, - { - "name": "semantic-tree-sitter-grammar-profile.v1.schema.json", - "digest": "blake3-256:e88ed4a014b4b5d5e5c97a10bca3f5b723c7b23fec4aeae4f0e9b992c5700f77" - }, - { - "name": "semantic-tree-sitter-provenance.v1.schema.json", - "digest": "blake3-256:bae387d2ac500d1752bbfe01ebd33a49bd577fcd25b140837a4e633d21ca8a67" - }, - { - "name": "semantic-tree-sitter-query.v1.schema.json", - "digest": "blake3-256:5dd9530db1fcb34a89e4c33086128dc1df62879529126fae8dac242a1ef298b5" - }, - { - "name": "semantic-type-surface.v1.schema.json", - "digest": "blake3-256:4ac7fb4a0a1fb1230cdb66d0b674049d79d180da19883d001a8c2e4fc55d30dc" - }, - { - "name": "semantic-verification-receipt.v1.schema.json", - "digest": "blake3-256:4cc1c1dab5c0c4f3e544817e6dc05f66a86304fe315bd711e0d3feca45d4d41c" - }, - { - "name": "software-criterion-catalog.v1.schema.json", - "digest": "blake3-256:a9bcc4f2cd83ff3f26f7a8bc31e7f3032f8dc5679649ace6c6aec913345bc478" - }, - { - "name": "source-snapshot-evidence.v1.schema.json", - "digest": "blake3-256:b22f980c1c70e1927670be25e8d86c4d3b78538e81e6aad28b608ca9707f99cb" - } - ] + "schemaDigest": "blake3-256:5f087ca57ea83ca4943780b03bda861dd6bfd0ba81524ab9f9991a5ec765db9d" } \ No newline at end of file diff --git a/schemas/asp-client-cancellation-probe-request.schema.json b/schemas/asp-client-cancellation-probe-request.schema.json index fb6d1af..c002842 100644 --- a/schemas/asp-client-cancellation-probe-request.schema.json +++ b/schemas/asp-client-cancellation-probe-request.schema.json @@ -4,13 +4,6 @@ "title": "ASP Client Cancellation Probe Request", "type": "object", "additionalProperties": false, - "required": ["schemaId", "schemaVersion"], - "properties": { - "schemaId": { - "const": "agent.semantic-protocols.asp-client-cancellation-probe-request" - }, - "schemaVersion": { - "const": "1" - } - } + "required": [], + "properties": {} } diff --git a/schemas/asp-client-dispatch-failure.schema.json b/schemas/asp-client-dispatch-failure.schema.json new file mode 100644 index 0000000..71509be --- /dev/null +++ b/schemas/asp-client-dispatch-failure.schema.json @@ -0,0 +1,18 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://agent-semantic-protocols.dev/schemas/asp-client-dispatch-failure.schema.json", + "title": "ASP Client Dispatch Failure", + "type": "object", + "additionalProperties": false, + "required": [ + "schemaId", "schemaVersion", "state", "phase", "reasonKind", "budgetMs" + ], + "properties": { + "schemaId": { "const": "agent.semantic-protocols.asp-client-dispatch-failure" }, + "schemaVersion": { "const": "1" }, + "state": { "const": "failed" }, + "phase": { "const": "runtime-client-dispatch" }, + "reasonKind": { "const": "client-request-deadline-exceeded" }, + "budgetMs": { "type": "integer", "minimum": 1 } + } +} diff --git a/schemas/asp-client-exact-query-failure.schema.json b/schemas/asp-client-exact-query-failure.schema.json new file mode 100644 index 0000000..0b49d17 --- /dev/null +++ b/schemas/asp-client-exact-query-failure.schema.json @@ -0,0 +1,43 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://agent-semantic-protocols.dev/schemas/asp-client-exact-query-failure.schema.json", + "title": "ASP Client Exact Query Failure", + "type": "object", + "additionalProperties": false, + "required": [ + "schemaId", "schemaVersion", "state", "operationId", "projectId", "workspaceId", "languageId", + "providerId", "phase", "reasonKind", + "residentReadElapsedMicros", "serviceElapsedMicros", "elapsedMicros", + "workCounters", "details" + ], + "properties": { + "schemaId": { + "const": "agent.semantic-protocols.asp-client-exact-query-failure" + }, + "schemaVersion": { "const": "1" }, + "state": { "const": "failed" }, + "operationId": { "type": "string", "minLength": 1 }, + "projectId": { "type": "string", "minLength": 1 }, + "workspaceId": { "type": "string", "minLength": 1 }, + "languageId": { "type": "string", "minLength": 1 }, + "providerId": { "type": "string", "minLength": 1 }, + "requestedSelector": { "type": ["string", "null"] }, + "resolvedSelector": { "type": ["string", "null"] }, + "projectionKind": { "type": ["string", "null"] }, + "phase": { "type": "string", "minLength": 1 }, + "reasonKind": { "type": "string", "minLength": 1 }, + "generationDigest": { + "type": ["string", "null"], + "pattern": "^blake3-256:[0-9a-f]{64}$" + }, + "rootDigest": { + "type": ["string", "null"], + "pattern": "^[0-9a-f]{64}$" + }, + "residentReadElapsedMicros": { "type": "integer", "minimum": 0 }, + "serviceElapsedMicros": { "type": "integer", "minimum": 0 }, + "elapsedMicros": { "type": "integer", "minimum": 0 }, + "workCounters": { "$ref": "asp-client-work-counters.schema.json" }, + "details": { "type": "object" } + } +} diff --git a/schemas/asp-client-exact-query-request.schema.json b/schemas/asp-client-exact-query-request.schema.json index 4aca5a4..155b97f 100644 --- a/schemas/asp-client-exact-query-request.schema.json +++ b/schemas/asp-client-exact-query-request.schema.json @@ -4,13 +4,79 @@ "title": "ASP Client Exact Query Request", "type": "object", "additionalProperties": false, - "required": ["schemaId", "schemaVersion", "selector", "projection"], + "required": ["schemaId", "schemaVersion", "mode", "projection"], "properties": { - "schemaId": { - "const": "agent.semantic-protocols.asp-client-exact-query-request" + "schemaId": {"const": "agent.semantic-protocols.asp-client-exact-query-request"}, + "schemaVersion": {"const": "1"}, + "mode": {"enum": ["selector", "syntax", "playbook"]}, + "selector": { + "type": "string", + "minLength": 1, + "description": "A parser-owned exact structural selector resolved against the admitted CompleteGeneration." }, - "schemaVersion": { "const": "1" }, - "selector": { "type": "string", "minLength": 1 }, - "projection": { "enum": ["source", "callable-skeleton"] } + "selectors": { + "type": "array", + "minItems": 1, + "uniqueItems": true, + "items": { + "type": "string", + "minLength": 1, + "description": "A parser-owned exact structural selector. Its producer scheme is the sole language or document authority." + } + }, + "languages": {"$ref": "#/$defs/producerExpression"}, + "documents": {"$ref": "#/$defs/producerExpression"}, + "workspace": {"$ref": "#/$defs/identity"}, + "syntax": { + "type": "array", + "minItems": 1, + "items": {"$ref": "#/$defs/producerNativeBlock"} + }, + "projection": {"enum": ["matches", "source", "callable-skeleton"]} + }, + "oneOf": [ + { + "properties": {"mode": {"const": "selector"}}, + "required": ["selector"], + "not": {"anyOf": [ + {"required": ["languages"]}, {"required": ["documents"]}, + {"required": ["syntax"]} + ]} + }, + { + "properties": {"mode": {"const": "syntax"}}, + "required": ["syntax"], + "allOf": [ + {"anyOf": [{"required": ["languages"]}, {"required": ["documents"]}]}, + {"not": {"required": ["selector"]}} + ] + }, + { + "properties": {"mode": {"const": "playbook"}}, + "required": ["selectors"], + "allOf": [ + {"not": {"required": ["selector"]}}, + {"not": {"required": ["languages"]}}, + {"not": {"required": ["documents"]}}, + {"not": {"required": ["syntax"]}} + ] + } + ], + "$defs": { + "identity": {"type": "string", "minLength": 1, "maxLength": 2048}, + "producerExpression": { + "type": "string", + "pattern": "^[A-Za-z0-9][A-Za-z0-9._+-]*(\\|[A-Za-z0-9][A-Za-z0-9._+-]*)*$" + }, + "nativeArgv": {"type": "array", "minItems": 1, "items": {"type": "string"}}, + "producerNativeBlock": { + "type": "object", + "additionalProperties": false, + "required": ["producer", "argv"], + "properties": { + "producer": {"$ref": "#/$defs/identity"}, + "argv": {"$ref": "#/$defs/nativeArgv"} + } + } } } diff --git a/schemas/asp-client-exact-query-response.schema.json b/schemas/asp-client-exact-query-response.schema.json index 0641323..5fa53f5 100644 --- a/schemas/asp-client-exact-query-response.schema.json +++ b/schemas/asp-client-exact-query-response.schema.json @@ -8,6 +8,8 @@ "schemaId", "schemaVersion", "operationId", + "projectId", + "workspaceId", "languageId", "providerId", "generationDigest", @@ -24,6 +26,8 @@ }, "schemaVersion": { "const": "1" }, "operationId": { "type": "string", "minLength": 1 }, + "projectId": { "type": "string", "minLength": 1 }, + "workspaceId": { "type": "string", "minLength": 1 }, "languageId": { "type": "string", "minLength": 1 }, "providerId": { "type": "string", "minLength": 1 }, "generationDigest": { @@ -34,27 +38,23 @@ "type": "string", "pattern": "^[0-9a-f]{64}$" }, - "result": { "type": "object" }, - "residentReadElapsedMicros": { "type": "integer", "minimum": 0 }, - "serviceElapsedMicros": { "type": "integer", "minimum": 0 }, - "elapsedMicros": { "type": "integer", "minimum": 0 }, - "workCounters": { + "result": { "type": "object", - "additionalProperties": false, - "required": [ - "databaseReadCount", - "filesystemReadCount", - "providerProcessCount", - "schedulerTaskCount", - "socketOperationCount" - ], + "additionalProperties": true, + "required": ["state", "resolvedSelector", "bytes"], "properties": { - "databaseReadCount": { "type": "integer", "minimum": 0 }, - "filesystemReadCount": { "type": "integer", "minimum": 0 }, - "providerProcessCount": { "type": "integer", "minimum": 0 }, - "schedulerTaskCount": { "type": "integer", "minimum": 0 }, - "socketOperationCount": { "type": "integer", "minimum": 0 } + "state": { "enum": ["projection", "provider-projection"] }, + "resolvedSelector": { "type": "string", "minLength": 1 }, + "bytes": { + "type": "array", + "minItems": 1, + "items": { "type": "integer", "minimum": 0, "maximum": 255 } + } } - } + }, + "residentReadElapsedMicros": { "type": "integer", "minimum": 0 }, + "serviceElapsedMicros": { "type": "integer", "minimum": 0 }, + "elapsedMicros": { "type": "integer", "minimum": 0 }, + "workCounters": { "$ref": "asp-client-work-counters.schema.json" } } } diff --git a/schemas/asp-client-frame.schema.json b/schemas/asp-client-frame.schema.json index eac399b..4f260c5 100644 --- a/schemas/asp-client-frame.schema.json +++ b/schemas/asp-client-frame.schema.json @@ -12,9 +12,18 @@ { "$ref": "#/$defs/event" } ], "$defs": { + "clientInfo": { + "type": "object", + "additionalProperties": false, + "required": ["name", "version"], + "properties": { + "name": { "type": "string", "minLength": 1 }, + "version": { "type": "string", "minLength": 1 } + } + }, "base": { "type": "object", - "required": ["schemaId", "schemaVersion", "protocolId", "protocolVersion", "kind", "sessionId", "workspaceIdentity"], + "required": ["schemaId", "schemaVersion", "protocolId", "protocolVersion", "kind", "sessionId", "projectId", "workspaceId"], "properties": { "schemaId": { "const": "agent.semantic-protocols.client.frame" }, "schemaVersion": { "const": "1" }, @@ -22,7 +31,8 @@ "protocolVersion": { "const": "1" }, "kind": { "type": "string" }, "sessionId": { "type": "string", "minLength": 1 }, - "workspaceIdentity": { "type": "string", "minLength": 1 }, + "projectId": { "type": "string", "minLength": 1 }, + "workspaceId": { "type": "string", "minLength": 1 }, "traceContext": { "type": "object", "additionalProperties": false, @@ -40,18 +50,12 @@ { "type": "object", "additionalProperties": false, - "required": ["schemaId", "schemaVersion", "protocolId", "protocolVersion", "kind", "sessionId", "workspaceIdentity", "requestId", "projectRoot", "clientInfo", "capabilities"], + "required": ["schemaId", "schemaVersion", "protocolId", "protocolVersion", "kind", "sessionId", "projectId", "workspaceId", "requestId", "clientInfo", "capabilities"], "properties": { "schemaId": {}, "schemaVersion": {}, "protocolId": {}, "protocolVersion": {}, - "kind": { "const": "initialize" }, "sessionId": {}, "workspaceIdentity": {}, "traceContext": {}, + "kind": { "const": "initialize" }, "sessionId": {}, "projectId": {}, "workspaceId": {}, "traceContext": {}, "requestId": { "type": "string", "minLength": 1 }, - "projectRoot": { "type": "string", "minLength": 1 }, - "clientInfo": { - "type": "object", - "additionalProperties": false, - "required": ["name", "version"], - "properties": { "name": { "type": "string", "minLength": 1 }, "version": { "type": "string", "minLength": 1 } } - }, + "clientInfo": { "$ref": "#/$defs/clientInfo" }, "capabilities": { "type": "object" } } } @@ -63,15 +67,16 @@ { "type": "object", "additionalProperties": false, - "required": ["schemaId", "schemaVersion", "protocolId", "protocolVersion", "kind", "sessionId", "workspaceIdentity", "requestId", "catalogGeneration", "workspaceGeneration", "method", "params"], + "required": ["schemaId", "schemaVersion", "protocolId", "protocolVersion", "kind", "sessionId", "projectId", "workspaceId", "requestId", "catalogGeneration", "workspaceGeneration", "method", "params"], "properties": { "schemaId": {}, "schemaVersion": {}, "protocolId": {}, "protocolVersion": {}, - "kind": { "const": "request" }, "sessionId": {}, "workspaceIdentity": {}, "traceContext": {}, + "kind": { "const": "request" }, "sessionId": {}, "projectId": {}, "workspaceId": {}, "traceContext": {}, "requestId": { "type": "string", "minLength": 1 }, "catalogGeneration": { "type": "string", "minLength": 1 }, "workspaceGeneration": { "type": "string", "minLength": 1 }, "method": { "type": "string", "minLength": 1 }, - "params": {} + "params": {}, + "clientTimingWitness": { "$ref": "runtime-search-client-timing-witness.v1.schema.json" } } } ] @@ -82,10 +87,10 @@ { "type": "object", "additionalProperties": false, - "required": ["schemaId", "schemaVersion", "protocolId", "protocolVersion", "kind", "sessionId", "workspaceIdentity", "requestId"], + "required": ["schemaId", "schemaVersion", "protocolId", "protocolVersion", "kind", "sessionId", "projectId", "workspaceId", "requestId"], "properties": { "schemaId": {}, "schemaVersion": {}, "protocolId": {}, "protocolVersion": {}, - "kind": { "const": "cancel" }, "sessionId": {}, "workspaceIdentity": {}, "traceContext": {}, + "kind": { "const": "cancel" }, "sessionId": {}, "projectId": {}, "workspaceId": {}, "traceContext": {}, "requestId": { "type": "string", "minLength": 1 } } } diff --git a/schemas/asp-client-graphs-timeline-request.v1.schema.json b/schemas/asp-client-graphs-timeline-request.v1.schema.json new file mode 100644 index 0000000..cec1d3d --- /dev/null +++ b/schemas/asp-client-graphs-timeline-request.v1.schema.json @@ -0,0 +1,15 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://agent-semantic-protocols.local/schemas/asp-client-graphs-timeline-request.v1.schema.json", + "title": "ASP Client Graphs Timeline Request v1", + "description": "Server-owned history/timeline request. Graph-Turbo is the algorithm identity; asp-python-graphs is the service owner.", + "type": "object", + "additionalProperties": false, + "required": ["schemaId", "schemaVersion", "eventPacket", "arguments"], + "properties": { + "schemaId": {"const": "agent.semantic-protocols.asp-client-graphs-timeline-request"}, + "schemaVersion": {"const": "1"}, + "eventPacket": {"$ref": "semantic-graph-turbo-artifact-events.v1.schema.json"}, + "arguments": {"type": "array", "items": {"type": "string"}} + } +} diff --git a/schemas/asp-client-owner-search-request.schema.json b/schemas/asp-client-owner-search-request.schema.json deleted file mode 100644 index eab07dd..0000000 --- a/schemas/asp-client-owner-search-request.schema.json +++ /dev/null @@ -1,17 +0,0 @@ -{ - "$schema": "https://json-schema.org/draft/2020-12/schema", - "$id": "https://agent-semantic-protocols.dev/schemas/asp-client-owner-search-request.schema.json", - "title": "ASP Client Owner Search Request", - "type": "object", - "additionalProperties": false, - "required": ["schemaId", "schemaVersion", "ownerPath", "query", "view"], - "properties": { - "schemaId": { - "const": "agent.semantic-protocols.asp-client-owner-search-request" - }, - "schemaVersion": { "const": "1" }, - "ownerPath": { "type": "string", "minLength": 1 }, - "query": { "type": "string" }, - "view": { "type": "string", "minLength": 1 } - } -} diff --git a/schemas/asp-client-protocol-catalog.schema.json b/schemas/asp-client-protocol-catalog.schema.json index 76bd3d4..e861268 100644 --- a/schemas/asp-client-protocol-catalog.schema.json +++ b/schemas/asp-client-protocol-catalog.schema.json @@ -26,7 +26,7 @@ "type": "array", "minItems": 1, "uniqueItems": true, - "items": { "enum": ["http-json", "runtime-ipc"] } + "items": { "const": "runtime-ipc" } }, "capabilities": { "type": "object", @@ -47,15 +47,24 @@ "$defs": { "digest": { "type": "string", "pattern": "^(blake3-256|sha256):[0-9a-f]{64}$" }, "schemaIdentifier": { "type": "string", "minLength": 1 }, + "schemaReference": { + "type": "object", + "additionalProperties": false, + "required": ["schemaId", "schemaVersion"], + "properties": { + "schemaId": { "$ref": "#/$defs/schemaIdentifier" }, + "schemaVersion": { "const": "1" } + } + }, "method": { "type": "object", "additionalProperties": false, "required": [ "method", "routeId", - "requestSchemaId", - "responseSchemaId", - "errorSchemaIds", + "requestSchema", + "responseSchema", + "errorSchemas", "parameters", "cancellable", "streaming" @@ -63,11 +72,11 @@ "properties": { "method": { "type": "string", "pattern": "^[a-z][a-z0-9-]*(\\.[a-z][a-z0-9-]*)+$" }, "routeId": { "type": "string", "pattern": "^[a-z][a-z0-9-]*(\\.[a-z][a-z0-9-]*)+$" }, - "requestSchemaId": { "$ref": "#/$defs/schemaIdentifier" }, - "responseSchemaId": { "$ref": "#/$defs/schemaIdentifier" }, - "errorSchemaIds": { + "requestSchema": { "$ref": "#/$defs/schemaReference" }, + "responseSchema": { "$ref": "#/$defs/schemaReference" }, + "errorSchemas": { "type": "array", - "items": { "$ref": "#/$defs/schemaIdentifier" } + "items": { "$ref": "#/$defs/schemaReference" } }, "parameters": { "type": "array", @@ -86,6 +95,7 @@ "valueType": { "enum": [ "string", + "string-array", "workspace-relative-path", "structural-selector", "presentation", diff --git a/schemas/asp-client-query-readiness-failure.schema.json b/schemas/asp-client-query-readiness-failure.schema.json new file mode 100644 index 0000000..9767290 --- /dev/null +++ b/schemas/asp-client-query-readiness-failure.schema.json @@ -0,0 +1,29 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://agent-semantic-protocols.dev/schemas/asp-client-query-readiness-failure.schema.json", + "title": "ASP Client Query Readiness Failure", + "type": "object", + "additionalProperties": false, + "required": [ + "schemaId", "schemaVersion", "state", "phase", "reasonKind", + "projectId", "workspaceId", "languageId", "providerId", "generationState", + "publicationError", "elapsedMicros", "workCounters" + ], + "properties": { + "schemaId": { + "const": "agent.semantic-protocols.asp-client-query-readiness-failure" + }, + "schemaVersion": { "const": "1" }, + "state": { "const": "failed" }, + "phase": { "const": "runtime-generation-authority" }, + "reasonKind": { "const": "query-not-ready" }, + "projectId": { "type": "string", "minLength": 1 }, + "workspaceId": { "type": "string", "minLength": 1 }, + "languageId": { "type": "string", "minLength": 1 }, + "providerId": { "type": "string", "minLength": 1 }, + "generationState": { "enum": ["unpublished", "failed"] }, + "publicationError": { "type": ["string", "null"] }, + "elapsedMicros": { "type": "integer", "minimum": 0 }, + "workCounters": { "$ref": "asp-client-work-counters.schema.json" } + } +} diff --git a/schemas/asp-client-schema-bundle-request.schema.json b/schemas/asp-client-schema-bundle-request.schema.json new file mode 100644 index 0000000..0a7b174 --- /dev/null +++ b/schemas/asp-client-schema-bundle-request.schema.json @@ -0,0 +1,23 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://schemas.agent-semantic-protocols.dev/asp-client-schema-bundle-request.schema.json", + "title": "ASP Client Schema Bundle Request", + "type": "object", + "additionalProperties": false, + "required": ["schemaId", "schemaVersion", "languageId", "rootSetIds"], + "properties": { + "schemaId": { "const": "agent.semantic-protocols.asp-client-schema-bundle-request" }, + "schemaVersion": { "const": "1" }, + "languageId": { "type": "string", "minLength": 1 }, + "rootSetIds": { + "type": "array", + "minItems": 1, + "uniqueItems": true, + "items": { "type": "string", "minLength": 1 } + }, + "knownBundleDigest": { "$ref": "#/$defs/digest" } + }, + "$defs": { + "digest": { "type": "string", "pattern": "^(blake3-256|sha256):[0-9a-f]{64}$" } + } +} diff --git a/schemas/asp-client-schema-bundle-response.schema.json b/schemas/asp-client-schema-bundle-response.schema.json new file mode 100644 index 0000000..4619e80 --- /dev/null +++ b/schemas/asp-client-schema-bundle-response.schema.json @@ -0,0 +1,98 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://schemas.agent-semantic-protocols.dev/asp-client-schema-bundle-response.schema.json", + "title": "ASP Client Schema Bundle Response", + "oneOf": [ + { "$ref": "#/$defs/ready" }, + { "$ref": "#/$defs/unchanged" }, + { "$ref": "#/$defs/failed" } + ], + "$defs": { + "digest": { "type": "string", "pattern": "^(blake3-256|sha256):[0-9a-f]{64}$" }, + "schemaName": { "type": "string", "pattern": "^[^/]+\\.schema\\.json$" }, + "entry": { + "type": "object", + "additionalProperties": false, + "required": ["familyId", "schemaId", "schemaVersion", "name", "digest"], + "properties": { + "familyId": { "type": "string", "minLength": 1 }, + "schemaId": { "type": "string", "minLength": 1 }, + "schemaVersion": { "const": "1" }, + "name": { "$ref": "#/$defs/schemaName" }, + "digest": { "$ref": "#/$defs/digest" } + } + }, + "receipt": { + "type": "object", + "additionalProperties": false, + "required": ["languageId", "rootSetIds", "bundleDigest"], + "properties": { + "languageId": { "type": "string", "minLength": 1 }, + "rootSetIds": { + "type": "array", + "minItems": 1, + "uniqueItems": true, + "items": { "type": "string", "minLength": 1 } + }, + "bundleDigest": { "$ref": "#/$defs/digest" } + } + }, + "entries": { + "type": "array", + "minItems": 1, + "items": { "$ref": "#/$defs/entry" } + }, + "document": { + "type": "object", + "additionalProperties": false, + "required": ["entry", "document"], + "properties": { + "entry": { "$ref": "#/$defs/entry" }, + "document": { "type": ["object", "boolean"] } + } + }, + "ready": { + "type": "object", + "additionalProperties": false, + "required": ["state", "schemaId", "schemaVersion", "receipt", "entries", "documents"], + "properties": { + "state": { "const": "ready" }, + "schemaId": { "const": "agent.semantic-protocols.asp-client-schema-bundle-response" }, + "schemaVersion": { "const": "1" }, + "receipt": { "$ref": "#/$defs/receipt" }, + "entries": { "$ref": "#/$defs/entries" }, + "documents": { + "type": "array", + "minItems": 1, + "items": { "$ref": "#/$defs/document" } + } + } + }, + "unchanged": { + "type": "object", + "additionalProperties": false, + "required": ["state", "schemaId", "schemaVersion", "receipt", "entries"], + "properties": { + "state": { "const": "unchanged" }, + "schemaId": { "const": "agent.semantic-protocols.asp-client-schema-bundle-response" }, + "schemaVersion": { "const": "1" }, + "receipt": { "$ref": "#/$defs/receipt" }, + "entries": { "$ref": "#/$defs/entries" } + } + }, + "failed": { + "type": "object", + "additionalProperties": false, + "required": ["state", "schemaId", "schemaVersion", "languageId", "reasonKind", "recommendedNext", "details"], + "properties": { + "state": { "const": "failed" }, + "schemaId": { "const": "agent.semantic-protocols.asp-client-schema-bundle-response" }, + "schemaVersion": { "const": "1" }, + "languageId": { "type": "string", "minLength": 1 }, + "reasonKind": { "type": "string", "minLength": 1 }, + "recommendedNext": true, + "details": { "type": "object" } + } + } + } +} diff --git a/schemas/asp-client-search-request.schema.json b/schemas/asp-client-search-request.schema.json deleted file mode 100644 index 2f349fd..0000000 --- a/schemas/asp-client-search-request.schema.json +++ /dev/null @@ -1,19 +0,0 @@ -{ - "$schema": "https://json-schema.org/draft/2020-12/schema", - "$id": "https://agent-semantic-protocols.dev/schemas/asp-client-search-request.schema.json", - "title": "ASP Client Search Request", - "type": "object", - "additionalProperties": false, - "required": ["schemaId", "schemaVersion", "operation", "query"], - "properties": { - "schemaId": { - "const": "agent.semantic-protocols.asp-client-search-request" - }, - "schemaVersion": { "const": "1" }, - "operation": { - "type": "string", - "pattern": "^[a-z][a-z0-9-]*$" - }, - "query": { "type": "string" } - } -} diff --git a/schemas/asp-client-source-index-lookup-request.schema.json b/schemas/asp-client-source-index-lookup-request.schema.json new file mode 100644 index 0000000..9e0d3a2 --- /dev/null +++ b/schemas/asp-client-source-index-lookup-request.schema.json @@ -0,0 +1,17 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://agent-semantic-protocols.dev/schemas/asp-client-source-index-lookup-request.schema.json", + "title": "ASP Client Source Index Lookup Request", + "type": "object", + "additionalProperties": false, + "required": ["schemaId", "schemaVersion", "query", "indexRoot", "limit"], + "properties": { + "schemaId": { + "const": "agent.semantic-protocols.asp-client-source-index-lookup-request" + }, + "schemaVersion": { "const": "1" }, + "query": { "type": "string", "minLength": 1 }, + "indexRoot": { "type": "string", "minLength": 1 }, + "limit": { "type": "integer", "minimum": 1, "maximum": 100 } + } +} diff --git a/schemas/asp-client-work-counters.schema.json b/schemas/asp-client-work-counters.schema.json new file mode 100644 index 0000000..a3727f9 --- /dev/null +++ b/schemas/asp-client-work-counters.schema.json @@ -0,0 +1,21 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://agent-semantic-protocols.dev/schemas/asp-client-work-counters.schema.json", + "title": "ASP Client Work Counters", + "type": "object", + "additionalProperties": false, + "required": [ + "databaseReadCount", + "filesystemReadCount", + "providerProcessCount", + "schedulerTaskCount", + "socketOperationCount" + ], + "properties": { + "databaseReadCount": { "type": "integer", "minimum": 0 }, + "filesystemReadCount": { "type": "integer", "minimum": 0 }, + "providerProcessCount": { "type": "integer", "minimum": 0 }, + "schedulerTaskCount": { "type": "integer", "minimum": 0 }, + "socketOperationCount": { "type": "integer", "minimum": 0 } + } +} diff --git a/schemas/asp-client-workspace-generation-ensure-ready-request.schema.json b/schemas/asp-client-workspace-generation-ensure-ready-request.schema.json new file mode 100644 index 0000000..263abc1 --- /dev/null +++ b/schemas/asp-client-workspace-generation-ensure-ready-request.schema.json @@ -0,0 +1,14 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://agent-semantic-protocols.dev/schemas/asp-client-workspace-generation-ensure-ready-request.schema.json", + "title": "ASP Client Workspace Generation Ensure Ready Request", + "type": "object", + "additionalProperties": false, + "required": [], + "properties": { + "languageId": { + "type": "string", + "minLength": 1 + } + } +} diff --git a/schemas/asp-client-workspace-query-playbook-request.v1.schema.json b/schemas/asp-client-workspace-query-playbook-request.v1.schema.json new file mode 100644 index 0000000..12b2c5f --- /dev/null +++ b/schemas/asp-client-workspace-query-playbook-request.v1.schema.json @@ -0,0 +1,18 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://schemas.agent-semantic-protocols.dev/asp-client-workspace-query-playbook-request.v1.schema.json", + "title": "ASP Client Workspace Query Playbook Request V1", + "description": "The sole public Query request. Code selectors are declared through --language and document selectors through --documents.", + "type": "object", + "additionalProperties": false, + "required": ["schemaId", "schemaVersion", "selectors", "projection"], + "properties": { + "schemaId": {"const": "agent.semantic-protocols.asp-client-workspace-query-playbook-request"}, + "schemaVersion": {"const": "1"}, + "language": {"type": "string", "pattern": "^[A-Za-z0-9][A-Za-z0-9._+-]*(\\|[A-Za-z0-9][A-Za-z0-9._+-]*)*$"}, + "documents": {"type": "string", "pattern": "^[A-Za-z0-9][A-Za-z0-9._+-]*(\\|[A-Za-z0-9][A-Za-z0-9._+-]*)*$"}, + "selectors": {"type": "array", "minItems": 1, "uniqueItems": true, "items": {"type": "string", "pattern": "^[a-z][a-z0-9+.-]*://[^\\s#]+#item/.+$(?![\\s\\S])"}}, + "projection": {"enum": ["source", "callable-skeleton"]} + }, + "anyOf": [{"required": ["language"]}, {"required": ["documents"]}] +} diff --git a/schemas/asp-client-workspace-search-playbook-request.v1.schema.json b/schemas/asp-client-workspace-search-playbook-request.v1.schema.json new file mode 100644 index 0000000..b43f9cd --- /dev/null +++ b/schemas/asp-client-workspace-search-playbook-request.v1.schema.json @@ -0,0 +1,37 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://agent-semantic-protocols.dev/schemas/asp-client-workspace-search-playbook-request.v1.schema.json", + "title": "ASP Client Workspace Search Playbook Request V1", + "description": "The normalized northbound Search request produced from one admitted Scheme composition expression. Code and document producer axes are projected into language and documents; Scheme source and POO implementation objects do not cross this boundary.", + "type": "object", + "additionalProperties": false, + "required": ["schemaId", "schemaVersion", "clauseOrder"], + "dependentRequired": {"rg": ["tantivy"], "tantivy": ["rg"]}, + "properties": { + "schemaId": {"const": "agent.semantic-protocols.asp-client-workspace-search-playbook-request"}, + "schemaVersion": {"const": "1"}, + "language": {"$ref": "#/$defs/producerExpression"}, + "documents": {"$ref": "#/$defs/producerExpression"}, + "workspace": {"description": "Explicit registered cross-workspace identity. Filesystem paths are not workspace identities.", "$ref": "#/$defs/registeredName"}, + "rg": {"$ref": "#/$defs/nativeBlocks"}, + "tantivy": {"$ref": "#/$defs/nativeBlocks"}, + "syntax": {"description": "Compiled resident structural query plans whose admitted matches establish selector scope inside the fused rg/Tantivy file context. Tree-sitter Query source does not cross this normalized boundary.", "type": "array", "minItems": 1, "items": {"$ref": "#/$defs/residentSyntaxBlock"}}, + "nativeSyntax": {"description": "Exact selector queries whose singleton matches establish structural scope inside the fused rg/Tantivy file context.", "type": "array", "minItems": 1, "items": {"$ref": "#/$defs/exactSelector"}}, + "graph": {"type": "array", "minItems": 1, "items": {"$ref": "#/$defs/graphNativeBlock"}}, + "clauseOrder": {"description": "Exact source order of leaves in the admitted V1 composition. This preserves repeated-leaf identity but does not independently create execution edges; the admitted Search Layout owns shared-scope and serial composition.", "type": "array", "minItems": 1, "items": {"$ref": "#/$defs/clauseRef"}} + }, + "allOf": [ + {"anyOf": [{"required": ["language"]}, {"required": ["documents"]}]}, + {"required": ["rg", "tantivy"]} + ], + "$defs": { + "registeredName": {"type": "string", "minLength": 1, "maxLength": 512, "pattern": "^[A-Za-z0-9][A-Za-z0-9._+-]*$"}, + "exactSelector": {"type": "string", "pattern": "^[A-Za-z][A-Za-z0-9+.-]*://[^\\s]+#item/[^\\s]+$(?![\\s\\S])"}, + "producerExpression": {"description": "One or more registered producers from the axis named by the enclosing field.", "type": "string", "pattern": "^[A-Za-z0-9][A-Za-z0-9._+-]*(\\|[A-Za-z0-9][A-Za-z0-9._+-]*)*$"}, + "nativeArgv": {"type": "array", "minItems": 1, "items": {"type": "string"}}, + "nativeBlocks": {"type": "array", "minItems": 1, "items": {"$ref": "#/$defs/nativeArgv"}}, + "residentSyntaxBlock": {"type": "object", "additionalProperties": false, "required": ["producer", "plan"], "properties": {"producer": {"$ref": "#/$defs/registeredName"}, "plan": {"$ref": "resident-syntax-query-plan.v1.schema.json"}}}, + "graphNativeBlock": {"type": "object", "additionalProperties": false, "required": ["language", "argv"], "properties": {"language": {"enum": ["gql", "pgql"]}, "argv": {"$ref": "#/$defs/nativeArgv"}}}, + "clauseRef": {"type": "object", "additionalProperties": false, "required": ["axis", "blockIndex"], "properties": {"axis": {"enum": ["rg", "tantivy", "syntax", "native-syntax", "graph"]}, "blockIndex": {"type": "integer", "minimum": 0}}} + } +} diff --git a/schemas/asp-client-workspace-syntax-plan-context-request.v1.schema.json b/schemas/asp-client-workspace-syntax-plan-context-request.v1.schema.json new file mode 100644 index 0000000..b554425 --- /dev/null +++ b/schemas/asp-client-workspace-syntax-plan-context-request.v1.schema.json @@ -0,0 +1,13 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://agent-semantic-protocols.dev/schemas/asp-client-workspace-syntax-plan-context-request.v1.schema.json", + "title": "ASP Client Workspace Syntax Plan Context Request V1", + "type": "object", + "additionalProperties": false, + "required": ["schemaId", "schemaVersion", "producer"], + "properties": { + "schemaId": {"const": "agent.semantic-protocols.asp-client-workspace-syntax-plan-context-request"}, + "schemaVersion": {"const": "1"}, + "producer": {"type": "string", "pattern": "^[A-Za-z][A-Za-z0-9._+:/-]*$"} + } +} diff --git a/schemas/asp-client-workspace-syntax-plan-context-response.v1.schema.json b/schemas/asp-client-workspace-syntax-plan-context-response.v1.schema.json new file mode 100644 index 0000000..0b0db93 --- /dev/null +++ b/schemas/asp-client-workspace-syntax-plan-context-response.v1.schema.json @@ -0,0 +1,14 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://agent-semantic-protocols.dev/schemas/asp-client-workspace-syntax-plan-context-response.v1.schema.json", + "title": "ASP Client Workspace Syntax Plan Context Response V1", + "type": "object", + "additionalProperties": false, + "required": ["schemaId", "schemaVersion", "generationDigest", "capability"], + "properties": { + "schemaId": {"const": "agent.semantic-protocols.asp-client-workspace-syntax-plan-context-response"}, + "schemaVersion": {"const": "1"}, + "generationDigest": {"type": "string", "pattern": "^blake3-256:[0-9a-f]{64}$"}, + "capability": {"$ref": "enhanced-tree-sitter-query-capability-table.v1.schema.json"} + } +} diff --git a/schemas/asp-client-workspace-syntax-query-request.v1.schema.json b/schemas/asp-client-workspace-syntax-query-request.v1.schema.json new file mode 100644 index 0000000..f7465e6 --- /dev/null +++ b/schemas/asp-client-workspace-syntax-query-request.v1.schema.json @@ -0,0 +1,33 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://agent-semantic-protocols.dev/schemas/asp-client-workspace-syntax-query-request.v1.schema.json", + "title": "ASP Client Workspace Syntax Query Request V1", + "type": "object", + "additionalProperties": false, + "required": ["schemaId", "schemaVersion", "syntax", "projection"], + "properties": { + "schemaId": {"const": "agent.semantic-protocols.asp-client-workspace-syntax-query-request"}, + "schemaVersion": {"const": "1"}, + "languages": {"type": "string", "pattern": "^[^|\\s]+(\\|[^|\\s]+)*$"}, + "documents": {"type": "string", "pattern": "^[^|\\s]+(\\|[^|\\s]+)*$"}, + "workspace": { + "description": "Explicit registered cross-workspace identity. Filesystem paths are not workspace identities.", + "type": "string", + "pattern": "^[A-Za-z0-9][A-Za-z0-9._+-]*$" + }, + "syntax": { + "type": "array", + "minItems": 1, + "items": { + "type": "object", + "additionalProperties": false, + "required": ["producer", "plan"], + "properties": { + "producer": {"type": "string", "minLength": 1}, + "plan": {"$ref": "resident-syntax-query-plan.v1.schema.json"} + } + } + }, + "projection": {"const": "matches"} + } +} diff --git a/schemas/asp-client-workspace-syntax-query-response.v1.schema.json b/schemas/asp-client-workspace-syntax-query-response.v1.schema.json new file mode 100644 index 0000000..cba234c --- /dev/null +++ b/schemas/asp-client-workspace-syntax-query-response.v1.schema.json @@ -0,0 +1,88 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://agent-semantic-protocols.dev/schemas/asp-client-workspace-syntax-query-response.v1.schema.json", + "title": "ASP Client Workspace Syntax Query Response V1", + "type": "object", + "additionalProperties": false, + "required": ["schemaId", "schemaVersion", "state", "evidence"], + "properties": { + "schemaId": {"const": "agent.semantic-protocols.asp-client-workspace-syntax-query-response"}, + "schemaVersion": {"const": "1"}, + "state": {"const": "ready"}, + "evidence": { + "type": "array", + "maxItems": 3, + "items": { + "type": "object", + "additionalProperties": false, + "required": ["owner", "selector", "capture", "relation", "selected"], + "properties": { + "owner": {"type": "string", "minLength": 1}, + "selector": {"type": "string", "pattern": "^[^:]+://.+#item/.+$"}, + "capture": {"type": "string", "pattern": "^[A-Za-z_][A-Za-z0-9_.-]*$"}, + "relation": {"type": "string", "minLength": 1}, + "selected": {"$ref": "#/$defs/selection"} + } + } + } + }, + "$defs": { + "selection": { + "type": "object", + "additionalProperties": false, + "minProperties": 1, + "properties": { + "kind": {"type": "string", "minLength": 1}, + "name": {"type": "string", "minLength": 1}, + "selector": {"type": "string", "pattern": "^[^:]+://.+#item/.+$"}, + "byteRange": { + "type": "array", + "prefixItems": [ + {"type": "integer", "minimum": 0}, + {"type": "integer", "minimum": 0} + ], + "minItems": 2, + "maxItems": 2 + }, + "scopes": { + "type": "array", + "items": {"$ref": "#/$defs/scope"} + }, + "queryKeys": { + "type": "array", + "uniqueItems": true, + "items": {"type": "string", "minLength": 1} + }, + "projections": { + "type": "array", + "uniqueItems": true, + "items": {"$ref": "#/$defs/projection"} + }, + "relations": { + "type": "array", + "uniqueItems": true, + "items": {"$ref": "provider-definitions.v1.schema.json#/$defs/relation"} + } + } + }, + "scope": { + "type": "object", + "additionalProperties": false, + "required": ["relation", "kind", "symbol"], + "properties": { + "relation": {"type": "string", "minLength": 1}, + "kind": {"type": "string", "minLength": 1}, + "symbol": {"type": "string", "minLength": 1} + } + }, + "projection": { + "type": "object", + "additionalProperties": false, + "required": ["kind", "digest"], + "properties": { + "kind": {"type": "string", "minLength": 1}, + "digest": {"type": "string", "pattern": "^blake3-256:[0-9a-f]{64}$"} + } + } + } +} diff --git a/schemas/asp-python-graphs-session.v1.schema.json b/schemas/asp-python-graphs-session.v1.schema.json new file mode 100644 index 0000000..7662b95 --- /dev/null +++ b/schemas/asp-python-graphs-session.v1.schema.json @@ -0,0 +1,76 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://tao3k.github.io/agent-semantic-protocols/schemas/asp-python-graphs-session.v1.schema.json", + "title": "ASP Python Graphs Session Envelope v1", + "type": "object", + "additionalProperties": false, + "required": [ + "schemaId", + "schemaVersion", + "sessionId", + "serviceEpoch", + "requestId", + "sequence", + "messageKind", + "payloadSchemaId", + "payload" + ], + "properties": { + "schemaId": {"const": "agent.semantic-protocols.asp-python-graphs-session"}, + "schemaVersion": {"const": "1"}, + "sessionId": {"type": "string", "minLength": 1}, + "serviceEpoch": {"type": "string", "minLength": 1}, + "requestId": {"type": "string", "minLength": 1}, + "clientRequestId": {"type": "string", "minLength": 1}, + "sequence": {"type": "integer", "minimum": 1}, + "messageKind": { + "enum": [ + "hello", + "generation-graph", + "evaluate-resident", + "release-generation", + "timeline", + "cancel", + "health", + "shutdown", + "receipt" + ] + }, + "workspaceIdentity": {"type": "string", "minLength": 1}, + "generationDigest": {"$ref": "#/$defs/blake3Digest"}, + "runtimeArtifactDigest": {"$ref": "#/$defs/blake3Digest"}, + "executionArtifactDigest": {"$ref": "#/$defs/blake3Digest"}, + "deadlineUnixMillis": {"type": "integer", "minimum": 0}, + "cancellationId": {"type": "string", "minLength": 1}, + "payloadSchemaId": {"type": "string", "minLength": 1}, + "payload": {"type": "object"} + }, + "allOf": [ + { + "if": {"properties": {"messageKind": {"const": "hello"}}}, + "then": { + "required": ["runtimeArtifactDigest", "executionArtifactDigest"] + } + }, + { + "if": {"properties": {"messageKind": {"const": "cancel"}}}, + "then": {"required": ["cancellationId"]} + }, + { + "if": { + "properties": { + "messageKind": { + "enum": ["generation-graph", "evaluate-resident", "release-generation"] + } + } + }, + "then": {"required": ["workspaceIdentity", "generationDigest"]} + } + ], + "$defs": { + "blake3Digest": { + "type": "string", + "pattern": "^blake3-256:[0-9a-f]{64}$" + } + } +} diff --git a/schemas/callable-skeleton.schema.json b/schemas/callable-skeleton.schema.json index e0791c6..8484898 100644 --- a/schemas/callable-skeleton.schema.json +++ b/schemas/callable-skeleton.schema.json @@ -6,8 +6,7 @@ "additionalProperties": false, "required": ["rootSelector", "rootNodeId", "callable", "nodes", "relations", "cost"], "properties": { - "projectionKind": {"const": "callable-skeleton"}, - "rootSelector": {}, + "rootSelector": {"type": "string", "minLength": 1}, "rootNodeId": {"type": "string", "minLength": 1}, "callable": {"type": "object"}, "nodes": {"type": "array"}, diff --git a/schemas/content-binding.schema.json b/schemas/content-binding.schema.json new file mode 100644 index 0000000..f4293c9 --- /dev/null +++ b/schemas/content-binding.schema.json @@ -0,0 +1,28 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://agent.semantic.protocols/schemas/content-binding", + "title": "ASP Content Binding", + "type": "object", + "additionalProperties": false, + "required": ["schemaId", "schemaVersion", "runtimeArtifactDigest", "workspaceSnapshotDigest", "sourceGenerationDigest", "sourceIndexDigest", "schemaDigest", "providerCatalogDigest", "authorityStamp"], + "properties": { + "schemaId": {"const": "asp.content-binding"}, + "schemaVersion": {"const": "1"}, + "runtimeArtifactDigest": {"type": "string", "pattern": "^blake3-256:[0-9a-f]{64}$"}, + "workspaceSnapshotDigest": {"type": "string", "pattern": "^blake3-256:[0-9a-f]{64}$"}, + "sourceGenerationDigest": {"type": "string", "pattern": "^blake3-256:[0-9a-f]{64}$"}, + "sourceIndexDigest": {"type": "string", "pattern": "^blake3-256:[0-9a-f]{64}$"}, + "schemaDigest": {"type": "string", "pattern": "^blake3-256:[0-9a-f]{64}$"}, + "providerCatalogDigest": {"type": "string", "pattern": "^blake3-256:[0-9a-f]{64}$"}, + "authorityStamp": { + "type": "object", + "additionalProperties": false, + "required": ["keyId", "canonicalDigest", "signature"], + "properties": { + "keyId": {"type": "string", "minLength": 1}, + "canonicalDigest": {"type": "string", "pattern": "^blake3-256:[0-9a-f]{64}$"}, + "signature": {"type": "string", "minLength": 1} + } + } + } +} diff --git a/schemas/content-publication-commit.v1.schema.json b/schemas/content-publication-commit.v1.schema.json new file mode 100644 index 0000000..f83080a --- /dev/null +++ b/schemas/content-publication-commit.v1.schema.json @@ -0,0 +1,37 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://schemas.agent-semantic-protocols.dev/content-publication-commit.v1.schema.json", + "title": "Content Publication Commit V1", + "description": "Immutable linearization record binding one complete ContentBinding to its predecessor fence. Persistence authority, not this document alone, establishes durability.", + "type": "object", + "additionalProperties": false, + "required": [ + "schemaId", + "schemaVersion", + "contentBinding", + "commitDigest", + "predecessorCommitDigest", + "mutationId", + "leaseId" + ], + "properties": { + "schemaId": {"const": "agent.semantic-protocols.content-publication-commit"}, + "schemaVersion": {"const": "1"}, + "contentBinding": {"$ref": "content-binding.schema.json"}, + "commitDigest": {"$ref": "#/$defs/contentDigest"}, + "predecessorCommitDigest": { + "oneOf": [ + {"$ref": "#/$defs/contentDigest"}, + {"type": "null"} + ] + }, + "mutationId": {"type": "string", "minLength": 1}, + "leaseId": {"type": "string", "minLength": 1} + }, + "$defs": { + "contentDigest": { + "type": "string", + "pattern": "^blake3-256:[0-9a-f]{64}$" + } + } +} diff --git a/schemas/enhanced-tree-sitter-query-capability-table.v1.schema.json b/schemas/enhanced-tree-sitter-query-capability-table.v1.schema.json new file mode 100644 index 0000000..bda2e20 --- /dev/null +++ b/schemas/enhanced-tree-sitter-query-capability-table.v1.schema.json @@ -0,0 +1,124 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://agent-semantic-protocols.dev/schemas/enhanced-tree-sitter-query-capability-table.v1.schema.json", + "title": "Provider Enhanced Tree-sitter Query Capability Table V1", + "type": "object", + "additionalProperties": false, + "required": [ + "schemaId", + "schemaVersion", + "languageId", + "providerId", + "parserAbi", + "queryGrammar", + "operatorTableDigest", + "tableDigest", + "rows" + ], + "properties": { + "schemaId": {"const": "agent.semantic-protocols.enhanced-tree-sitter-query-capability-table"}, + "schemaVersion": {"const": "1"}, + "languageId": {"$ref": "#/$defs/identity"}, + "providerId": {"$ref": "#/$defs/identity"}, + "parserAbi": {"$ref": "#/$defs/versionedIdentity"}, + "queryGrammar": {"$ref": "#/$defs/versionedIdentity"}, + "operatorTableDigest": {"$ref": "#/$defs/digest"}, + "tableDigest": {"$ref": "#/$defs/digest"}, + "rows": { + "type": "array", + "minItems": 1, + "items": {"$ref": "#/$defs/row"} + } + }, + "$defs": { + "digest": {"type": "string", "pattern": "^blake3-256:[0-9a-f]{64}$"}, + "identity": {"type": "string", "pattern": "^[A-Za-z][A-Za-z0-9._+:/-]*$"}, + "versionedIdentity": { + "type": "object", + "additionalProperties": false, + "required": ["id", "version", "digest"], + "properties": { + "id": {"$ref": "#/$defs/identity"}, + "version": {"type": "string", "minLength": 1}, + "digest": {"$ref": "#/$defs/digest"} + } + }, + "lowering": { + "type": "object", + "additionalProperties": false, + "required": ["residentFactPath", "constraintKind"], + "properties": { + "residentFactPath": { + "enum": [ + "kind", + "name", + "selector", + "byte-range", + "scopes.relation", + "scopes.kind", + "scopes.symbol", + "queryKeys", + "projections.kind", + "relations" + ] + }, + "constraintKind": {"enum": ["scalar", "set", "range", "relation", "capture"]}, + "residentValue": {"type": "string", "minLength": 1} + } + }, + "equivalenceEvidence": { + "type": "object", + "additionalProperties": false, + "required": ["method", "corpusDigest", "receiptDigest"], + "properties": { + "method": {"const": "native-parser-tree-sitter-differential-v1"}, + "corpusDigest": {"$ref": "#/$defs/digest"}, + "receiptDigest": {"$ref": "#/$defs/digest"} + } + }, + "row": { + "type": "object", + "additionalProperties": false, + "required": ["rowId", "kind", "sourceName", "publicationState"], + "properties": { + "rowId": {"$ref": "#/$defs/identity"}, + "kind": { + "enum": [ + "node-type", + "field", + "capture-binding", + "fact-path", + "projection-kind", + "relation-kind" + ] + }, + "sourceName": {"type": "string", "minLength": 1}, + "publicationState": {"enum": ["runtime", "provider-local", "not-materialized"]}, + "lowering": {"$ref": "#/$defs/lowering"}, + "equivalenceEvidence": {"$ref": "#/$defs/equivalenceEvidence"} + }, + "allOf": [ + { + "if": {"properties": {"publicationState": {"const": "runtime"}}, "required": ["publicationState"]}, + "then": {"required": ["lowering", "equivalenceEvidence"]} + }, + { + "if": { + "properties": { + "kind": {"const": "node-type"}, + "publicationState": {"const": "runtime"} + }, + "required": ["kind", "publicationState"] + }, + "then": { + "properties": {"lowering": {"required": ["residentValue"]}} + } + }, + { + "if": {"properties": {"publicationState": {"enum": ["provider-local", "not-materialized"]}}, "required": ["publicationState"]}, + "then": {"not": {"required": ["equivalenceEvidence"]}} + } + ] + } + } +} diff --git a/schemas/enhanced-tree-sitter-query-operator-table.v1.schema.json b/schemas/enhanced-tree-sitter-query-operator-table.v1.schema.json new file mode 100644 index 0000000..cd211a0 --- /dev/null +++ b/schemas/enhanced-tree-sitter-query-operator-table.v1.schema.json @@ -0,0 +1,110 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://agent-semantic-protocols.dev/schemas/enhanced-tree-sitter-query-operator-table.v1.schema.json", + "title": "Enhanced Tree-sitter Query Operator Table V1", + "type": "object", + "additionalProperties": false, + "required": [ + "schemaId", + "schemaVersion", + "profileId", + "owner", + "declarationDigest", + "operators", + "recoveries" + ], + "properties": { + "schemaId": {"const": "agent.semantic-protocols.enhanced-tree-sitter-query-operator-table"}, + "schemaVersion": {"const": "1"}, + "profileId": {"const": "mrr.enhanced-tree-sitter-query.v1"}, + "owner": {"const": "mrr-gerbil-aot"}, + "declarationDigest": {"$ref": "#/$defs/digest"}, + "operators": { + "type": "array", + "minItems": 1, + "items": {"$ref": "#/$defs/operator"} + }, + "recoveries": { + "type": "array", + "minItems": 1, + "items": {"$ref": "#/$defs/recovery"} + } + }, + "$defs": { + "digest": {"type": "string", "pattern": "^blake3-256:[0-9a-f]{64}$"}, + "identity": {"type": "string", "pattern": "^[A-Za-z][A-Za-z0-9._+-]*$"}, + "operand": { + "type": "object", + "additionalProperties": false, + "required": ["position", "kind", "domain", "cardinality"], + "properties": { + "position": {"type": "integer", "minimum": 0}, + "kind": {"enum": ["capture", "string"]}, + "domain": { + "enum": [ + "item-capture", + "scalar-fact-path", + "set-fact-path", + "range-fact-path", + "literal", + "regex", + "direction", + "relation-kind", + "endpoint-selector", + "range-mode", + "canonical-u64", + "result-field" + ] + }, + "cardinality": {"enum": ["one", "optional", "one-or-more"]} + } + }, + "operator": { + "type": "object", + "additionalProperties": false, + "required": [ + "spelling", + "kind", + "minimumArity", + "maximumArity", + "operands", + "lowering", + "failureCode" + ], + "properties": { + "spelling": {"type": "string", "pattern": "^#asp-[a-z][a-z-]*[?!]$"}, + "kind": {"enum": ["predicate", "directive"]}, + "minimumArity": {"type": "integer", "minimum": 1}, + "maximumArity": {"type": ["integer", "null"], "minimum": 1}, + "operands": {"type": "array", "minItems": 1, "items": {"$ref": "#/$defs/operand"}}, + "lowering": { + "enum": [ + "scalar-eq", + "scalar-not-eq", + "scalar-match", + "scalar-not-match", + "set-any-eq", + "set-none-eq", + "set-any-match", + "set-none-match", + "range", + "related", + "not-related", + "select" + ] + }, + "failureCode": {"$ref": "#/$defs/identity"} + } + }, + "recovery": { + "type": "object", + "additionalProperties": false, + "required": ["site", "code", "strategy"], + "properties": { + "site": {"$ref": "#/$defs/identity"}, + "code": {"$ref": "#/$defs/identity"}, + "strategy": {"const": "reject"} + } + } + } +} diff --git a/schemas/grpc-warm-latency-receipt.schema.json b/schemas/grpc-warm-latency-receipt.schema.json new file mode 100644 index 0000000..5beeb1f --- /dev/null +++ b/schemas/grpc-warm-latency-receipt.schema.json @@ -0,0 +1,44 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://agent-semantic-protocols.dev/schemas/grpc-warm-latency-receipt.schema.json", + "title": "ASP Client gRPC Warm Latency Receipt", + "type": "object", + "additionalProperties": false, + "required": [ + "schemaId", + "schemaVersion", + "state", + "thresholdNanos", + "sequentialCount", + "sequentialTerminalCount", + "sequentialP95Nanos", + "concurrentCount", + "concurrentTerminalCount", + "concurrentP95Nanos" + ], + "properties": { + "schemaId": { + "const": "agent.semantic-protocols.grpc-warm-latency-receipt" + }, + "schemaVersion": { "const": "1" }, + "state": { "enum": ["passed", "failed"] }, + "thresholdNanos": { "const": 1000000 }, + "sequentialCount": { "const": 10 }, + "sequentialTerminalCount": { "const": 10 }, + "sequentialP95Nanos": { "type": "integer", "minimum": 0 }, + "concurrentCount": { "const": 32 }, + "concurrentTerminalCount": { "const": 32 }, + "concurrentP95Nanos": { "type": "integer", "minimum": 0 } + }, + "allOf": [ + { + "if": { "properties": { "state": { "const": "passed" } } }, + "then": { + "properties": { + "sequentialP95Nanos": { "exclusiveMaximum": 1000000 }, + "concurrentP95Nanos": { "exclusiveMaximum": 1000000 } + } + } + } + ] +} diff --git a/schemas/host-workspace-initialization-binding.v1.schema.json b/schemas/host-workspace-initialization-binding.v1.schema.json new file mode 100644 index 0000000..3d87408 --- /dev/null +++ b/schemas/host-workspace-initialization-binding.v1.schema.json @@ -0,0 +1,19 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://schemas.agent-semantic-protocols.dev/host-workspace-initialization-binding.v1.schema.json", + "title": "Host Workspace Initialization Binding V1", + "description": "The independently admitted Project Workspace and Host-local worktree instance retained by Runtime workspace initialization. This is not an Agent-facing handle or a Query parameter.", + "type": "object", + "additionalProperties": false, + "required": ["schemaId", "schemaVersion", "projectWorkspace", "worktreeInstanceId"], + "properties": { + "schemaId": {"const": "agent.semantic-protocols.host-workspace-initialization-binding"}, + "schemaVersion": {"const": "1"}, + "projectWorkspace": {"$ref": "project-workspace-binding.v1.schema.json"}, + "worktreeInstanceId": { + "type": "string", + "minLength": 1, + "description": "Opaque Host-local worktree identity minted and supplied by workspace initialization; never inferred from projectId, workspaceId, sessionId, or a filesystem path." + } + } +} diff --git a/schemas/language-schema-bundle-receipt.schema.json b/schemas/language-schema-bundle-receipt.schema.json index a14b363..6622f07 100644 --- a/schemas/language-schema-bundle-receipt.schema.json +++ b/schemas/language-schema-bundle-receipt.schema.json @@ -4,33 +4,11 @@ "title": "ASP Language Schema Bundle Receipt", "type": "object", "additionalProperties": false, - "required": [ - "schemaId", - "schemaVersion", - "languageId", - "profileDigest", - "bundleDigest", - "schemas" - ], + "required": ["schemaId", "schemaVersion", "schemaDigest"], "properties": { "schemaId": { "const": "agent.semantic-protocols.language-schema-bundle-receipt" }, "schemaVersion": { "const": "1" }, - "languageId": { "type": "string", "minLength": 1 }, - "profileDigest": { "$ref": "#/$defs/digest" }, - "bundleDigest": { "$ref": "#/$defs/digest" }, - "schemas": { - "type": "array", - "minItems": 1, - "items": { - "type": "object", - "additionalProperties": false, - "required": ["name", "digest"], - "properties": { - "name": { "type": "string", "pattern": "^[^/]+\\.schema\\.json$" }, - "digest": { "$ref": "#/$defs/digest" } - } - } - } + "schemaDigest": { "$ref": "#/$defs/digest" } }, "$defs": { "digest": { "type": "string", "pattern": "^(blake3-256|sha256):[0-9a-f]{64}$" } diff --git a/schemas/language-schema-profiles.json b/schemas/language-schema-profiles.json new file mode 100644 index 0000000..13d7675 --- /dev/null +++ b/schemas/language-schema-profiles.json @@ -0,0 +1,1103 @@ +{ + "$schema": "language-schema-profile-registry.schema.json", + "schemaId": "agent.semantic-protocols.language-schema-profile-registry", + "schemaVersion": "1", + "wireArtifacts": { + "asp-client-protocol-v1": "asp-client-protocol.v1.proto" + }, + "rootSets": { + "provider-contract": [ + "provider-registration.schema.json", + "provider-manifest.schema.json", + "provider-route.schema.json", + "provider-query-pack-descriptor.schema.json", + "provider-runtime-contract-descriptor.schema.json", + "asp-client-server-descriptor.schema.json", + "provider-runtime-request-stream-frame.schema.json", + "provider-runtime-request-stream-ack.schema.json", + "provider-language-projection-batch-request.schema.json", + "provider-language-projection-batch-response.schema.json", + "provider-workspace-install.schema.json", + "asp-python-graphs-session.v1.schema.json", + "language-schema-bundle-receipt.schema.json" + ], + "client-protocol": [ + "asp-client-protocol-catalog.schema.json", + "asp-client-frame.schema.json", + "asp-client-conformance.schema.json", + "asp-client-search-request.schema.json", + "asp-client-workspace-search-playbook-request.v1.schema.json", + "asp-client-workspace-query-playbook-request.v1.schema.json", + "asp-client-workspace-syntax-query-request.v1.schema.json", + "asp-client-workspace-syntax-query-response.v1.schema.json", + "search-topology-settlement.v1.schema.json", + "large-search-playbook-performance-receipt.v1.schema.json", + "asp-client-source-index-lookup-request.schema.json", + "resident-search-result.v1.schema.json", + "asp-client-exact-query-request.schema.json", + "asp-client-exact-query-response.schema.json", + "asp-client-exact-query-failure.schema.json", + "query-playbook-materialization-receipt.v1.schema.json", + "asp-client-query-readiness-failure.schema.json", + "asp-client-dispatch-failure.schema.json", + "asp-client-work-counters.schema.json", + "asp-client-schema-bundle-request.schema.json", + "asp-client-schema-bundle-response.schema.json", + "grpc-warm-latency-receipt.schema.json", + "lexical-postings-work-reduction-receipt.schema.json", + "rg-coverage-receipt.schema.json", + "runtime-client-terminal.schema.json", + "asp-client-workspace-source-mutation.schema.json", + "asp-client-cancellation-probe-request.schema.json", + "asp-client-cancellation-probe-response.schema.json", + "asp-client-workspace-generation-ensure-ready-request.schema.json", + "asp-client-graphs-timeline-request.v1.schema.json" + ], + "project-resolution": [ + "provider-project-resolution-request.schema.json", + "provider-project-resolution-response.schema.json" + ], + "core-query": [ + "callable-skeleton.schema.json", + "exact-structural-selector.v1.schema.json", + "provider-native-exact-request.v1.schema.json", + "provider-native-exact-response.v1.schema.json", + "semantic-search-storage-route.v1.schema.json", + "semantic-query-packet.v1.schema.json", + "semantic-exact-selector-receipt.v1.schema.json", + "semantic-owner-item-evidence.v1.schema.json", + "semantic-content-compaction.v1.schema.json", + "search-generation-change-set.v1.schema.json", + "semantic-read-packet.v1.schema.json", + "semantic-source-location.v1.schema.json", + "semantic-tree-sitter-provenance.v1.schema.json", + "semantic-tree-sitter-query.v1.schema.json", + "semantic-tree-sitter-grammar-profile.v1.schema.json", + "semantic-relation-plan.v1.schema.json", + "semantic-flow-lite.v1.schema.json", + "semantic-codeql-evidence.v1.schema.json", + "semantic-fact-graph.v1.schema.json", + "semantic-fact-ontology.v1.schema.json", + "semantic-dependency-topology.v1.schema.json", + "semantic-structural-index.v1.schema.json", + "semantic-native-syntax-fact-index.v1.schema.json", + "semantic-graph.v1.schema.json", + "semantic-type-surface.v1.schema.json", + "semantic-handle.v1.schema.json", + "semantic-language-registry.v1.schema.json", + "semantic-language-projection.v1.schema.json" + ], + "agent-reasoning": [ + "software-criterion-catalog.v1.schema.json", + "semantic-verification-receipt.v1.schema.json", + "semantic-behavior-snapshot.v1.schema.json", + "semantic-determinism-readiness.v1.schema.json", + "semantic-dev-command-log.v1.schema.json", + "semantic-formal-proof-pilot.v1.schema.json", + "semantic-review-packet.v1.schema.json", + "semantic-graph-resident-evaluation-request.v1.schema.json", + "semantic-graph-resident-evaluation-result.v1.schema.json", + "python-generation-graph-performance-receipt.v1.schema.json", + "semantic-graph-turbo-request.v1.schema.json", + "semantic-ast-patch.v1.schema.json", + "semantic-ast-patch-receipt.v1.schema.json" + ] + }, + "profiles": [ + { + "languageId": "rust", + "packageRoot": "languages/asp-rust", + "bundleRoot": "languages/asp-rust/schemas", + "rootSets": [ + "provider-contract", + "client-protocol", + "project-resolution", + "core-query", + "agent-reasoning" + ], + "roots": [ + "provider-syntax-query-request.schema.json", + "provider-syntax-query-response.schema.json", + "semantic-invariant-candidate.v1.schema.json", + "semantic-compare-packet.v1.schema.json" + ], + "providerOwned": [ + "rust-ast-patch-real-project-evidence.v1.schema.json", + "rust-semantic-capabilities.v1.schema.json" + ] + }, + { + "languageId": "typescript", + "packageRoot": "languages/asp-typescript", + "bundleRoot": "languages/asp-typescript/schemas", + "rootSets": [ + "provider-contract", + "client-protocol", + "project-resolution", + "core-query", + "agent-reasoning" + ], + "roots": [], + "providerOwned": [ + "typescript-semantic-capabilities.v1.schema.json" + ] + }, + { + "languageId": "python", + "packageRoot": "languages/asp-python", + "bundleRoot": "languages/asp-python/schemas", + "rootSets": [ + "provider-contract", + "client-protocol", + "project-resolution", + "core-query", + "agent-reasoning" + ], + "roots": [], + "providerOwned": [ + "python-semantic-capabilities.v1.schema.json" + ] + }, + { + "languageId": "julia", + "packageRoot": "languages/AspJulia.jl", + "bundleRoot": "languages/AspJulia.jl/schemas", + "rootSets": [ + "provider-contract", + "client-protocol", + "project-resolution", + "core-query", + "agent-reasoning" + ], + "roots": [], + "providerOwned": [] + }, + { + "languageId": "gerbil-scheme", + "packageRoot": "languages/asp-gerbil-scheme", + "bundleRoot": "languages/asp-gerbil-scheme/schemas", + "rootSets": [ + "provider-contract", + "client-protocol", + "project-resolution", + "core-query", + "agent-reasoning" + ], + "roots": [ + "callable-skeleton.schema.json", + "exact-structural-selector.v1.schema.json", + "provider-native-exact-request.v1.schema.json", + "provider-native-exact-response.v1.schema.json", + "semantic-query-packet.v1.schema.json", + "semantic-owner-item-evidence.v1.schema.json", + "semantic-extension-pattern-mapping.v1.schema.json", + "semantic-language-evidence.v1.schema.json", + "semantic-runtime-source-acquisition.v1.schema.json", + "semantic-type-proof.v1.schema.json" + ], + "providerOwned": [ + "semantic-asp-gerbil-scheme-info.v1.schema.json" + ] + }, + { + "languageId": "org", + "packageRoot": "languages/orgize", + "bundleRoot": "languages/orgize/provider/org/schemas", + "rootSets": [ + "client-protocol", + "project-resolution", + "core-query", + "agent-reasoning" + ], + "roots": [], + "providerOwned": [] + }, + { + "languageId": "md", + "packageRoot": "languages/orgize", + "bundleRoot": "languages/orgize/provider/md/schemas", + "rootSets": [ + "client-protocol", + "project-resolution", + "core-query", + "agent-reasoning" + ], + "roots": [], + "providerOwned": [] + } + ], + "families": [ + { + "familyId": "asp.schema-family.semantic-assurance", + "owner": "schemas", + "rationale": "Assurance cases, determinism readiness, evidence graphs, review packets, and formal-proof pilot contracts.", + "priority": 300, + "parentFamilyId": "asp.schema-family.semantic", + "namespace": { + "filenamePrefixes": [ + "semantic-assurance-", + "semantic-determinism-", + "semantic-evidence-", + "semantic-review-", + "semantic-formal-proof-" + ] + }, + "definitionSchemaPath": "schemas/semantic-assurance-definitions.v1.schema.json", + "definitionVisibility": "family" + }, + { + "familyId": "asp.schema-family.semantic-proof", + "owner": "schemas", + "rationale": "Proof obligations, recipes, receipts, and formal verification contracts.", + "priority": 200, + "parentFamilyId": "asp.schema-family.semantic", + "definitionSchemaPath": "schemas/semantic-proof-definitions.v1.schema.json", + "definitionVisibility": "family", + "namespace": { + "filenamePrefixes": [ + "semantic-proof-", + "semantic-formal-", + "axle-", + "lean-", + "fallback-reflection-" + ] + } + }, + { + "familyId": "asp.schema-family.semantic-policy", + "owner": "schemas", + "rationale": "Semantic policy facts and executable policy recipe contracts.", + "priority": 200, + "parentFamilyId": "asp.schema-family.semantic", + "definitionSchemaPath": "schemas/semantic-policy-definitions.v1.schema.json", + "definitionVisibility": "family", + "namespace": { + "filenamePrefixes": [ + "semantic-policy-" + ] + } + }, + { + "familyId": "asp.schema-family.semantic-ast-patch", + "definitionSchemaPath": "schemas/semantic-ast-patch-definitions.v1.schema.json", + "definitionVisibility": "family", + "owner": "schemas", + "rationale": "Semantic AST patch request and receipt contracts.", + "priority": 200, + "parentFamilyId": "asp.schema-family.semantic", + "namespace": { + "filenamePrefixes": [ + "semantic-ast-patch" + ] + } + }, + { + "familyId": "asp.schema-family.semantic-graph-turbo", + "membershipOverrides": { + "includeSchemaPaths": [ + "schemas/asp-python-graphs-session.v1.schema.json" + ] + }, + "definitionSchemaPath": "schemas/semantic-graph-turbo-definitions.v1.schema.json", + "definitionVisibility": "family", + "owner": "schemas", + "rationale": "Graph Turbo request, result, lifecycle, cache, benchmark, and assurance contracts.", + "priority": 300, + "parentFamilyId": "asp.schema-family.semantic-graph", + "namespace": { + "filenamePrefixes": [ + "semantic-graph-turbo-" + ] + } + }, + { + "familyId": "asp.schema-family.semantic-graph", + "owner": "schemas", + "rationale": "Semantic graph contracts outside the narrower Graph Turbo namespace.", + "priority": 200, + "parentFamilyId": "asp.schema-family.semantic", + "namespace": { + "filenamePrefixes": [ + "semantic-graph-", + "graph-search-" + ] + }, + "membershipOverrides": { + "includeSchemaPaths": [ + "schemas/semantic-graph.v1.schema.json" + ] + } + }, + { + "familyId": "asp.schema-family.hook", + "owner": "schemas", + "rationale": "Hook activation, policy, matching, and break-glass capability contracts.", + "priority": 200, + "parentFamilyId": "asp.schema-family.semantic", + "namespace": { + "filenamePrefixes": [ + "hook-", + "activation-" + ] + }, + "membershipOverrides": { + "includeSchemaPaths": [ + "schemas/host-native-execution-required.v1.schema.json" + ] + } + }, + { + "familyId": "asp.schema-family.semantic-agent", + "definitionSchemaPath": "schemas/semantic-agent-definitions.schema.json", + "definitionVisibility": "family", + "owner": "schemas", + "rationale": "Agent hook, session, search, policy, and runtime profile contracts.", + "priority": 200, + "parentFamilyId": "asp.schema-family.semantic", + "namespace": { + "filenamePrefixes": [ + "semantic-agent-", + "agent-action-", + "agent-action.", + "agent-route-", + "agent-root-", + "agent-session-", + "multi-agent-", + "subagent-", + "host-agent-", + "host-session-", + "agent-semantic-project-", + "codex-session-", + "codex-child-", + "codex-collaboration-", + "codex-thread-" + ] + }, + "membershipOverrides": { + "includeSchemaPaths": [ + "schemas/codex-multi-agent-v2-host-lifecycle-event.v1.schema.json" + ] + } + }, + { + "familyId": "asp.schema-family.agent-semantic-client", + "owner": "schemas", + "rationale": "Agent semantic client cache and receipt contracts.", + "priority": 200, + "parentFamilyId": "asp.schema-family.semantic", + "namespace": { + "filenamePrefixes": [ + "agent-semantic-client-" + ] + } + }, + { + "familyId": "asp.schema-family.semantic-sandtable", + "owner": "schemas", + "rationale": "Semantic sandtable scenario, receipt, report, and comparison contracts.", + "priority": 200, + "parentFamilyId": "asp.schema-family.semantic", + "definitionSchemaPath": "schemas/semantic-sandtable-definitions.v1.schema.json", + "definitionVisibility": "family", + "namespace": { + "filenamePrefixes": [ + "semantic-sandtable-" + ] + } + }, + { + "familyId": "asp.schema-family.semantic-db", + "owner": "schemas", + "rationale": "Semantic DB engine manifest, report, route, and shared definition contracts.", + "priority": 200, + "parentFamilyId": "asp.schema-family.semantic", + "definitionSchemaPath": "schemas/semantic-db-definitions.v1.schema.json", + "definitionVisibility": "family", + "namespace": { + "filenamePrefixes": [ + "semantic-db-" + ] + } + }, + { + "familyId": "asp.schema-family.semantic-fact", + "definitionSchemaPath": "schemas/semantic-fact-definitions.v1.schema.json", + "definitionVisibility": "family", + "owner": "schemas", + "rationale": "Semantic fact graph, ontology, frontier, and benchmark contracts.", + "priority": 200, + "parentFamilyId": "asp.schema-family.semantic", + "namespace": { + "filenamePrefixes": [ + "semantic-fact-" + ] + } + }, + { + "familyId": "asp.schema-family.live-corpus", + "owner": "schemas", + "rationale": "Live corpus path, synchronization, materialization, artifact, and qualification contracts.", + "priority": 200, + "parentFamilyId": "asp.schema-family.semantic", + "namespace": { + "filenamePrefixes": [ + "asp.live-corpus-" + ] + } + }, + { + "familyId": "asp.schema-family.content-identity", + "owner": "crates/agent-semantic-content-identity", + "rationale": "Content identity owns the canonical binding between source bytes and their content-addressed identity.", + "priority": 260, + "parentFamilyId": "asp.schema-family.semantic", + "namespace": { + "filenamePrefixes": [ + "content-binding" + ] + } + }, + { + "familyId": "asp.schema-family.source-evidence", + "owner": "schemas", + "rationale": "Source index, snapshot, overlay, derived-source, and resource synchronization evidence contracts.", + "priority": 200, + "parentFamilyId": "asp.schema-family.semantic", + "namespace": { + "filenamePrefixes": [ + "derived-source-", + "source-index-", + "source-overlay-", + "source-overlay.", + "source-snapshot-" + ] + }, + "membershipOverrides": { + "includeSchemaPaths": [ + "schemas/asp.resource-sync-receipt.v1.schema.json", + "schemas/parser-read-authority.v1.schema.json" + ] + } + }, + { + "familyId": "asp.schema-family.semantic-artifact", + "owner": "schemas", + "rationale": "Semantic artifact identity, edge, and repair-chain contracts.", + "priority": 200, + "parentFamilyId": "asp.schema-family.semantic", + "namespace": { + "filenamePrefixes": [ + "semantic-artifact-", + "active-asp-artifact-" + ] + }, + "definitionSchemaPath": "schemas/semantic-artifact-definitions.v1.schema.json", + "definitionVisibility": "public" + }, + { + "familyId": "asp.schema-family.semantic-search", + "owner": "schemas", + "rationale": "Search packet, projection, trace, and evidence contracts under the semantic-search namespace.", + "priority": 210, + "parentFamilyId": "asp.schema-family.semantic", + "namespace": { + "filenamePrefixes": [ + "semantic-search-", + "searchloop-", + "agent-facing-search-", + "asp-search-", + "incremental-search-", + "interactive-search-", + "large-search-", + "memory-search-", + "cold-rg-", + "content-search-", + "lexical-", + "python-generation-graph-", + "rendered-search-", + "repository-candidate-", + "search-", + "fused-search-", + "evidence-query-", + "callable-skeleton-" + ] + }, + "membershipOverrides": { + "includeSchemaPaths": [ + "schemas/asp.exact-selector-execution-receipt.v1.schema.json", + "schemas/projection-evidence-context.v1.schema.json", + "schemas/lexical-postings-work-reduction-receipt.schema.json", + "schemas/rg-coverage-receipt.schema.json", + "schemas/query-playbook-materialization-request.v1.schema.json", + "schemas/query-playbook-materialization-receipt.v1.schema.json", + "schemas/asp-client-workspace-search-playbook-request.v1.schema.json", + "schemas/asp-client-workspace-syntax-query-request.v1.schema.json", + "schemas/asp-client-workspace-syntax-query-response.v1.schema.json", + "schemas/search-topology-settlement.v1.schema.json" + ] + } + }, + { + "familyId": "asp.schema-family.language", + "owner": "schemas", + "rationale": "Language package graphs and registered/resolved source-scope contracts.", + "priority": 200, + "parentFamilyId": "asp.schema-family.semantic", + "namespace": { + "filenamePrefixes": [ + "language-", + "registered-language-", + "resolved-source-", + "python-semantic-", + "typescript-semantic-" + ] + } + }, + { + "familyId": "asp.schema-family.context-product", + "owner": "schemas", + "rationale": "Context product state, route execution, proof reuse, and blocked decision contracts.", + "priority": 200, + "parentFamilyId": "asp.schema-family.semantic", + "namespace": { + "filenamePrefixes": [ + "context-product-" + ] + } + }, + { + "familyId": "asp.schema-family.parser-compact", + "owner": "schemas", + "rationale": "Parser compact-case and token-cost contracts.", + "priority": 200, + "parentFamilyId": "asp.schema-family.semantic", + "namespace": { + "filenamePrefixes": [ + "parser-compact-" + ] + } + }, + { + "familyId": "asp.schema-family.schema-manager", + "owner": "packages/python/asp_schema_manager", + "rationale": "ASP Schema Manager family-registry, lifecycle-manifest, and management-report contracts.", + "priority": 300, + "namespace": { + "filenamePrefixes": [ + "asp-schema-" + ] + } + }, + { + "familyId": "asp.schema-family.software-criterion", + "owner": "schemas", + "rationale": "Software criterion catalogs and extension report contracts.", + "priority": 200, + "parentFamilyId": "asp.schema-family.semantic", + "namespace": { + "filenamePrefixes": [ + "software-criterion-" + ] + } + }, + { + "familyId": "asp.schema-family.semantic", + "owner": "schemas", + "rationale": "Broad semantic contract namespace pending narrower family promotion.", + "priority": 100, + "namespace": { + "filenamePrefixes": [ + "semantic-" + ] + }, + "membershipOverrides": { + "includeSchemaPaths": [ + "schemas/relationship-contract.v1.schema.json" + ] + }, + "definitionSchemaPath": "schemas/semantic-definitions.v1.schema.json", + "definitionVisibility": "descendants" + }, + { + "familyId": "asp.schema-family.rust-project-harness", + "owner": "languages/asp-rust", + "rationale": "Rust project harness build gates and dependency policy contracts.", + "priority": 200, + "parentFamilyId": "asp.schema-family.provider", + "namespace": { + "filenamePrefixes": [ + "rust-project-harness-", + "rust-ast-patch-" + ] + }, + "definitionSchemaPath": "schemas/rust-project-harness-definitions.v1.schema.json", + "definitionVisibility": "family" + }, + { + "familyId": "asp.schema-family.provider", + "definitionSchemaPath": "schemas/provider-definitions.v1.schema.json", + "definitionVisibility": "public", + "owner": "schemas", + "rationale": "Provider registration, dispatch, resolution, and projection contracts.", + "priority": 100, + "namespace": { + "filenamePrefixes": [ + "provider-", + "gerbil-scheme-" + ] + }, + "membershipOverrides": { + "includeSchemaPaths": [ + "schemas/asp.activated-ranker.v1.schema.json", + "schemas/asp-gerbil-scheme-bench.v1.schema.json" + ] + } + }, + { + "familyId": "asp.schema-family.client-storage", + "owner": "schemas", + "rationale": "Client storage, Turso synchronization, migration, MVCC, and storage receipt contracts.", + "priority": 200, + "parentFamilyId": "asp.schema-family.runtime", + "namespace": { + "filenamePrefixes": [ + "client-storage-", + "client-db-", + "storage-", + "turso-" + ] + }, + "definitionSchemaPath": "schemas/client-storage-definitions.v1.schema.json", + "definitionVisibility": "family" + }, + { + "familyId": "asp.schema-family.workspace", + "owner": "schemas", + "rationale": "Workspace authority, generation evidence, project resolution, and workspace identity contracts.", + "priority": 200, + "parentFamilyId": "asp.schema-family.runtime", + "namespace": { + "filenamePrefixes": [ + "workspace-", + "project-resolution." + ] + }, + "membershipOverrides": { + "includeSchemaPaths": [ + "schemas/asp.active-workspace-generation.v1.schema.json", + "schemas/active-generation-projection-capability.v1.schema.json", + "schemas/active-search-generation.schema.json", + "schemas/state-home-project-binding.schema.json" + ] + }, + "definitionSchemaPath": "schemas/workspace-definitions.v1.schema.json", + "definitionVisibility": "family" + }, + { + "familyId": "asp.schema-family.org-babel-runtime-validation", + "owner": "schemas", + "rationale": "Org Babel runtime validation receipts and execution-environment acceptance evidence.", + "priority": 200, + "parentFamilyId": "asp.schema-family.runtime", + "namespace": { + "filenamePrefixes": [ + "org-babel-runtime-validation-" + ] + } + }, + { + "familyId": "asp.schema-family.resident-runtime", + "owner": "schemas", + "rationale": "Global resident control/data and resident workspace lifecycle/performance contracts, distinct from runtime-server authority.", + "priority": 200, + "parentFamilyId": "asp.schema-family.runtime", + "namespace": { + "filenamePrefixes": [ + "global-resident-", + "resident-" + ] + } + }, + { + "familyId": "asp.schema-family.runtime-server", + "definitionSchemaPath": "schemas/runtime-server-definitions.v1.schema.json", + "definitionVisibility": "family", + "owner": "schemas", + "rationale": "Runtime Server authority, lifecycle, performance, and workspace contracts.", + "priority": 200, + "parentFamilyId": "asp.schema-family.runtime", + "namespace": { + "filenamePrefixes": [ + "runtime-server-" + ] + }, + "membershipOverrides": { + "includeSchemaPaths": [ + "schemas/grpc-warm-latency-receipt.schema.json" + ] + } + }, + { + "familyId": "asp.schema-family.runtime", + "owner": "schemas", + "rationale": "Runtime contracts outside the narrower Runtime Server namespace.", + "priority": 100, + "namespace": { + "filenamePrefixes": [ + "runtime-" + ] + }, + "membershipOverrides": { + "includeSchemaPaths": [ + "schemas/state-home-cleanup-receipt.schema.json", + "schemas/state-home-retention-plan.schema.json" + ] + } + }, + { + "familyId": "asp.schema-family.exact", + "owner": "schemas", + "rationale": "Exact query, selector, source, projection, and materialization contracts.", + "priority": 100, + "namespace": { + "filenamePrefixes": [ + "exact-", + "canonical-" + ] + }, + "definitionSchemaPath": "schemas/exact-definitions.v1.schema.json", + "definitionVisibility": "family", + "membershipOverrides": { + "includeSchemaPaths": [ + "schemas/callable-skeleton.schema.json" + ] + } + }, + { + "familyId": "asp.schema-family.asp-client", + "owner": "crates/agent-semantic-client", + "rationale": "Public ASP client protocol frames, requests, lifecycle, conformance, and CLI failure contracts.", + "priority": 300, + "parentFamilyId": "asp.schema-family.semantic", + "namespace": { + "filenamePrefixes": [ + "asp-client-", + "asp-cli-" + ] + } + } + ], + "referenceDecisions": [ + { + "fingerprint": "sha256:d2a44cebe27ba00bd88b6c4ff33fc15998bdb0d89bd77e0ebaff6d2dcaf019e5", + "occurrenceSetDigest": "sha256:8c9d04d708fa6b25eafdc06649771357223a9d39ddf20b10140f8820d611849e", + "familyIds": [ + "asp.schema-family.semantic", + "asp.schema-family.semantic-agent", + "asp.schema-family.semantic-assurance", + "asp.schema-family.semantic-ast-patch", + "asp.schema-family.software-criterion" + ], + "decision": "defer", + "reviewState": "accepted", + "owner": "asp.schema-family.semantic", + "rationale": "Keep the generic fields shape local until the semantic parent contract defines public ownership." + }, + { + "fingerprint": "sha256:7245937b80aac15d7d70b80e82eaa5fe6c5c4136d7d641250b268b09aa37f96a", + "occurrenceSetDigest": "sha256:08a3ccf3bc65fa35febb4210c9acde40feb8dcd19f0fc5e437dea04a620170b5", + "familyIds": [ + "asp.schema-family.rust-project-harness", + "asp.schema-family.semantic", + "asp.schema-family.semantic-assurance", + "asp.schema-family.semantic-ast-patch", + "asp.schema-family.semantic-graph", + "asp.schema-family.semantic-policy", + "asp.schema-family.software-criterion" + ], + "decision": "defer", + "reviewState": "accepted", + "owner": "asp.schema-family.semantic", + "rationale": "Promote projectPath only after all external families can consume a public semantic definition." + }, + { + "fingerprint": "sha256:c22956b8502199745da256f13a5747fe11075368a61e8f7fff5950512adaed06", + "occurrenceSetDigest": "sha256:5f831420dec56dc99250c5c693789d877e6f97fa7b5ab89a130927bd313d2200", + "familyIds": [ + "asp.schema-family.rust-project-harness", + "asp.schema-family.semantic", + "asp.schema-family.semantic-ast-patch", + "asp.schema-family.semantic-search" + ], + "decision": "defer", + "reviewState": "accepted", + "owner": "asp.schema-family.semantic", + "rationale": "Source locator promotion needs a public semantic definition and consumer migration plan." + }, + { + "fingerprint": "sha256:1f364075c230f171d7602ad4b547e1fdbd3347b1d9c915b82e8bc5211851c603", + "occurrenceSetDigest": "sha256:70084139dd6bfbb404a72faabcadfc8e053eac1295e6dd19e6d6a4c74afbbeb1", + "familyIds": [ + "asp.schema-family.semantic-agent", + "asp.schema-family.semantic-sandtable" + ], + "decision": "defer", + "reviewState": "accepted", + "owner": "asp.schema-family.semantic-agent", + "rationale": "Sandtable should reference the agent answer contract after semantic-agent definitions become public." + }, + { + "fingerprint": "sha256:61e6652dd7efec7949d7c84082718b3d029f68a15fdd908fee97c1972c865a84", + "occurrenceSetDigest": "sha256:1a0f15b0cc7d5d6ae200da598de8109075a62a7a42ab04918d589d52abf6ef9e", + "familyIds": [ + "asp.schema-family.semantic-fact", + "asp.schema-family.semantic-graph-turbo" + ], + "decision": "defer", + "reviewState": "accepted", + "owner": "asp.schema-family.semantic-fact", + "rationale": "Context metrics need an explicit semantic-fact public definition before graph-turbo migration." + }, + { + "fingerprint": "sha256:4298e3a6c7efa53e6107da8778159f6f334067d031a28db5813d71372b733c44", + "occurrenceSetDigest": "sha256:01d92f91de88a9ba8971dae708abf6e3848c5edf21016fc1897c86094fa71a9d", + "familyIds": [ + "asp.schema-family.semantic-agent", + "asp.schema-family.semantic-sandtable" + ], + "decision": "defer", + "reviewState": "accepted", + "owner": "asp.schema-family.semantic-agent", + "rationale": "Quality finding lists will move with the semantic-agent quality finding definition." + }, + { + "fingerprint": "sha256:071137bccfb1096132378b47e221f2c3144dcbd8a74c29b753a0f834bc3248a7", + "occurrenceSetDigest": "sha256:a88de522b09996eb0b6d7499234e71788995692e302dea0c1265ab99354ae958", + "familyIds": [ + "asp.schema-family.semantic-graph", + "asp.schema-family.software-criterion" + ], + "decision": "intentional-duplication", + "reviewState": "accepted", + "owner": "asp.schema-family.schema-manager", + "rationale": "Graph owner frontiers and software criterion fixture owners share syntax but not semantics." + }, + { + "fingerprint": "sha256:5e72be959793d55b4bcf45a22fb8a652efaf763afb88ee90976c3edb5e586cfc", + "occurrenceSetDigest": "sha256:b08c6a0d1e77eadebb3e48882cae72f3f3cb1065606e66966b0ac3da36a11620", + "familyIds": [ + "asp.schema-family.semantic-agent", + "asp.schema-family.semantic-sandtable" + ], + "decision": "defer", + "reviewState": "accepted", + "owner": "asp.schema-family.semantic-agent", + "rationale": "Sandtable quality findings should consume a public semantic-agent definition." + }, + { + "fingerprint": "sha256:4df374a1de0f11d980a365f7cf475a1ee4bc5f183cbfe13545a3b6fa0931c2d9", + "occurrenceSetDigest": "sha256:e6245dc970122ab3e42e102f24380a10dcdd323e2ff88c30a9a0f6741ed60350", + "familyIds": [ + "asp.schema-family.semantic-policy", + "asp.schema-family.semantic-proof" + ], + "decision": "intentional-duplication", + "reviewState": "accepted", + "owner": "asp.schema-family.schema-manager", + "rationale": "Policy fields and proof fields are independently owned open extension maps." + }, + { + "fingerprint": "sha256:bc994518650f8e6c90a3a7d5f98ccee5f8fc25dd0e1c1985d086ee65a2485ff9", + "occurrenceSetDigest": "sha256:73994c6fc3f4ca4a3fee4e90512e5644a45b83c2c4e0745b3146ed543b544bfc", + "familyIds": [ + "asp.schema-family.semantic-fact", + "asp.schema-family.semantic-graph-turbo" + ], + "decision": "defer", + "reviewState": "accepted", + "owner": "asp.schema-family.semantic-fact", + "rationale": "Range ownership belongs in semantic-fact after public visibility is explicit." + }, + { + "fingerprint": "sha256:8cc19f1f4f9cfd31bbcd575289d8fecbc0256f323c263d40e56422ab8ea2aeff", + "occurrenceSetDigest": "sha256:655bf74036c2581321f4c8c9f2df41aea706c8d149a5ca1ad4095b0eaa493579", + "familyIds": [ + "asp.schema-family.semantic-policy", + "asp.schema-family.semantic-proof" + ], + "decision": "intentional-duplication", + "reviewState": "accepted", + "owner": "asp.schema-family.schema-manager", + "rationale": "Policy and proof scalar slots have distinct domain meaning despite equal JSON shape." + }, + { + "fingerprint": "sha256:59205d978b02f80380cda4e83960a29fa839edff1940d279efeac7a885438853", + "occurrenceSetDigest": "sha256:6c8df2abb67e9e36000d0916d6819e10239e175b3c25ab00af57f54ca96142f5", + "familyIds": [ + "asp.schema-family.semantic", + "asp.schema-family.semantic-assurance" + ], + "decision": "intentional-duplication", + "reviewState": "accepted", + "owner": "asp.schema-family.schema-manager", + "rationale": "Semantic and assurance scalar slots are domain-local value envelopes." + }, + { + "fingerprint": "sha256:981fdc978f075173c7cbab937d334511db859e72f8758ab3310cf787e3802b0a", + "occurrenceSetDigest": "sha256:987af08244da4bcebad04b149bbe18885748598494852e0ec6a82124058d7907", + "familyIds": [ + "asp.schema-family.semantic", + "asp.schema-family.semantic-ast-patch" + ], + "decision": "defer", + "reviewState": "accepted", + "owner": "asp.schema-family.semantic", + "rationale": "Location should be promoted only with a public semantic definition and complete consumer migration." + }, + { + "fingerprint": "sha256:9fafb8a5c930d882c0221d8aade7855fba09ba30d884891607dfbf2179c2ab2f", + "occurrenceSetDigest": "sha256:3ce62c507b90752b0ce20b4842bbd52fcf90e15ed28f35f1e0dcdd36af123083", + "familyIds": [ + "asp.schema-family.semantic", + "asp.schema-family.semantic-assurance" + ], + "decision": "defer", + "reviewState": "accepted", + "owner": "asp.schema-family.semantic", + "rationale": "Assurance fields will reference semantic fields after public ownership is established." + }, + { + "fingerprint": "sha256:f44b93d3e1a66d00bef14c09cc2f27d0efb8e552b12da07d92cd11994e8c3eb2", + "occurrenceSetDigest": "sha256:4efd316ddd677c115f41e7fa5efc5affd219cc86d27616998cd9957f3803ac37", + "familyIds": [ + "asp.schema-family.semantic-graph-turbo", + "asp.schema-family.semantic-search" + ], + "decision": "defer", + "reviewState": "accepted", + "owner": "asp.schema-family.semantic-search", + "rationale": "Delegation receipts need a shared search definition before graph-turbo references it." + }, + { + "fingerprint": "sha256:1733d812bb1b355644fb1907ba739cbfa6446038604ae4fe16614eb3421abbfd", + "occurrenceSetDigest": "sha256:e6e8f94e9650c529c0f21d3af2715c4bd2645525da6aece9bf1879f7282411e8", + "familyIds": [ + "asp.schema-family.semantic-agent", + "asp.schema-family.semantic-sandtable" + ], + "decision": "defer", + "reviewState": "accepted", + "owner": "asp.schema-family.semantic-agent", + "rationale": "Sandtable project data should reference a public semantic-agent project definition." + }, + { + "fingerprint": "sha256:a54a35ec3bf1a54c70e7f23f45a2ed23f8afe94228cf6124c6aa228c058db493", + "occurrenceSetDigest": "sha256:8e0f145390a761d262f25b117068e24ba335ebc95dc127f876753c1b76159b03", + "familyIds": [ + "asp.schema-family.language", + "asp.schema-family.provider", + "asp.schema-family.semantic-search" + ], + "decision": "intentional-duplication", + "reviewState": "accepted", + "owner": "asp.schema-family.schema-manager", + "rationale": "Provider owner paths, language target roots, and search proof sources are distinct domain responsibilities despite sharing a non-empty path-list shape." + }, + { + "fingerprint": "sha256:811c570f79fff148fd4ecb5fdf999fc87c87c21908f3bdd3d4ddc99dda1e28dc", + "occurrenceSetDigest": "sha256:4a902b643e3cf9760b5906b00ee5f37d868ea92b2293adf415d16b6d52c6534e", + "familyIds": [ + "asp.schema-family.semantic", + "asp.schema-family.semantic-assurance" + ], + "decision": "defer", + "reviewState": "accepted", + "owner": "asp.schema-family.semantic", + "rationale": "Invariant kinds need a semantic parent definition before assurance migration." + }, + { + "fingerprint": "sha256:82eea660dabf702a5523ee26a394fbe06202e1e955804410a76c0aa13dc42209", + "occurrenceSetDigest": "sha256:da08d9539f3d9be53710e42a95df93257651167b97d1903b16d0037d7b6d0130", + "familyIds": [ + "asp.schema-family.semantic-fact", + "asp.schema-family.semantic-graph-turbo" + ], + "decision": "defer", + "reviewState": "accepted", + "owner": "asp.schema-family.semantic-fact", + "rationale": "Source ranges should use one public semantic-fact range definition." + }, + { + "fingerprint": "sha256:0c936f70dafce1c7cc33abb734c0b0bfa6b43fb3410937c5f193aa13129cbe79", + "occurrenceSetDigest": "sha256:99b0ec69eb815786a980df13fd0a53965eeb30dbf3a7092654b49061ebf11bb4", + "familyIds": [ + "asp.schema-family.semantic", + "asp.schema-family.semantic-ast-patch" + ], + "decision": "defer", + "reviewState": "accepted", + "owner": "asp.schema-family.semantic", + "rationale": "Changed and test paths need a public semantic path-list definition." + }, + { + "fingerprint": "sha256:62f71a66c2be93e2dd7208a0c5ecfb4783ee86dcd753fa7b8ef10039f85a870f", + "occurrenceSetDigest": "sha256:53d80724abee8c7e08219932e4136d896b5927abeb766246e105d67d3f629bfc", + "familyIds": [ + "asp.schema-family.semantic-fact", + "asp.schema-family.semantic-graph" + ], + "decision": "defer", + "reviewState": "accepted", + "owner": "asp.schema-family.semantic-graph", + "rationale": "Graph node fields require an explicitly public semantic graph fact map." + }, + { + "fingerprint": "sha256:c59ce57a6a1272108ca7a166b9c63c8061b42cd5eefa848b151e461285ef6d51", + "occurrenceSetDigest": "sha256:94e74453dd616ac6978bc77c6f3345cfc5560287eeb13cc578b56059c6469b64", + "familyIds": [ + "asp.schema-family.provider", + "asp.schema-family.semantic" + ], + "decision": "intentional-duplication", + "reviewState": "accepted", + "owner": "asp.schema-family.schema-manager", + "rationale": "Provider process failures and semantic failures have different lifecycle semantics." + }, + { + "fingerprint": "sha256:11465fde644bba159bfc73175a389bab91c90a46416296274ce387cdfccd9477", + "occurrenceSetDigest": "sha256:2adb2f0211ee7cea88ad11e82a20557545c4c9b9fd8d5332c65c19f7d6ee974c", + "familyIds": [ + "asp.schema-family.semantic-graph", + "asp.schema-family.semantic-search" + ], + "decision": "defer", + "reviewState": "accepted", + "owner": "asp.schema-family.semantic-graph", + "rationale": "Search graph node kinds need a public semantic-graph definition." + }, + { + "fingerprint": "sha256:e5c6d1791bd4981e88a37880d1b8d111f966294cd130fe5e6b541dd2344239f2", + "occurrenceSetDigest": "sha256:5e4c67dd434f94b5f5f37c3536120aabea0626cede00cc2c906f48bf95090b88", + "familyIds": [ + "asp.schema-family.semantic", + "asp.schema-family.semantic-agent" + ], + "decision": "intentional-duplication", + "reviewState": "accepted", + "owner": "asp.schema-family.schema-manager", + "rationale": "Semantic failure envelopes and agent document notes are independently owned concepts." + }, + { + "fingerprint": "sha256:cdc2dcd81e732654fa694ca6c35927e035f59839a13122496ad8fd2c53646119", + "occurrenceSetDigest": "sha256:4ab6c39ad4c67fb9b0c3de112215ab8a0deb9ee1cc8035d7a3db6d9e7ed9d045", + "familyIds": [ + "asp.schema-family.asp-client", + "asp.schema-family.provider" + ], + "decision": "extract-family-definition", + "reviewState": "accepted", + "owner": "asp.schema-family.schema-manager", + "rationale": "Structured SchemaReference is a shared manager-owned declaration used by client and provider contracts." + } + ] +} diff --git a/schemas/large-search-playbook-performance-receipt.v1.schema.json b/schemas/large-search-playbook-performance-receipt.v1.schema.json new file mode 100644 index 0000000..634576e --- /dev/null +++ b/schemas/large-search-playbook-performance-receipt.v1.schema.json @@ -0,0 +1,197 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://agent-semantic-protocols.dev/schemas/large-search-playbook-performance-receipt.v1.schema.json", + "title": "Large Search Playbook performance receipt V1", + "type": "object", + "additionalProperties": false, + "required": [ + "schemaId", "schemaVersion", "owners", "contentGeneration", "coldRgQuery", + "bootstrapAccelerator", "deltaAccelerator", "firstTantivyOpen", "warm", "concurrent", + "fusedCache", "pythonGraph", "requestTimeExternalWork" + ], + "properties": { + "schemaId": {"const": "agent.semantic-protocols.large-search-playbook-performance-receipt"}, + "schemaVersion": {"const": "1"}, + "owners": {"type": "integer", "minimum": 4096}, + "contentGeneration": { + "unevaluatedProperties": false, + "allOf": [ + {"$ref": "https://schemas.agent-semantic-protocols.dev/semantic-search-definitions.v1.schema.json#/$defs/performanceDistribution"}, + { + "type": "object", + "required": ["stageP95Nanos"], + "properties": { + "samples": {"type": "integer", "minimum": 32}, + "stageP95Nanos": { + "type": "object", + "additionalProperties": false, + "required": [ + "fdInventory", "repositoryAdmission", "sourceByteAcquisition", + "nativeSyntax", "rustGraph", "publication" + ], + "properties": { + "fdInventory": {"$ref": "#/$defs/nanos"}, + "repositoryAdmission": {"$ref": "#/$defs/nanos"}, + "sourceByteAcquisition": {"$ref": "#/$defs/nanos"}, + "nativeSyntax": {"$ref": "#/$defs/nanos"}, + "rustGraph": {"$ref": "#/$defs/nanos"}, + "publication": {"$ref": "#/$defs/nanos"} + } + } + } + } + ] + }, + "coldRgQuery": { + "unevaluatedProperties": false, + "allOf": [ + {"$ref": "https://schemas.agent-semantic-protocols.dev/semantic-search-definitions.v1.schema.json#/$defs/performanceDistribution"}, + { + "type": "object", + "required": [ + "fdProcessCount", "rgProcessCount", "tantivyBuildCount", + "contentVerificationP95Nanos", "nativeSyntaxVerificationP95Nanos" + ], + "properties": { + "samples": {"type": "integer", "minimum": 32}, + "fdProcessCount": {"const": 0}, + "rgProcessCount": {"const": 0}, + "tantivyBuildCount": {"const": 0}, + "p99Nanos": {"type": "integer", "minimum": 0, "exclusiveMaximum": 1000000}, + "maxNanos": {"type": "integer", "minimum": 0, "exclusiveMaximum": 1000000}, + "contentVerificationP95Nanos": {"$ref": "#/$defs/nanos"}, + "nativeSyntaxVerificationP95Nanos": {"$ref": "#/$defs/nanos"} + } + } + ] + }, + "bootstrapAccelerator": { + "unevaluatedProperties": false, + "allOf": [ + {"$ref": "https://schemas.agent-semantic-protocols.dev/semantic-search-definitions.v1.schema.json#/$defs/performanceDistribution"}, + { + "type": "object", + "required": [ + "samples", "reusedShardCount", "rebuiltShardCount", + "shardPlanP95Nanos", "tantivyCommitP95Nanos", + "equivalenceValidationP95Nanos", "pointerPublicationP95Nanos" + ], + "properties": { + "samples": {"type": "integer", "minimum": 32}, + "reusedShardCount": {"const": 0}, + "rebuiltShardCount": {"type": "integer", "minimum": 4096}, + "shardPlanP95Nanos": {"$ref": "#/$defs/nanos"}, + "tantivyCommitP95Nanos": {"$ref": "#/$defs/nanos"}, + "equivalenceValidationP95Nanos": {"$ref": "#/$defs/nanos"}, + "pointerPublicationP95Nanos": {"$ref": "#/$defs/nanos"} + } + } + ] + }, + "deltaAccelerator": { + "unevaluatedProperties": false, + "allOf": [ + {"$ref": "https://schemas.agent-semantic-protocols.dev/semantic-search-definitions.v1.schema.json#/$defs/performanceDistribution"}, + { + "type": "object", + "required": [ + "samples", "changedOwnerCount", "reusedShardCount", + "rebuiltShardCount", "retiredShardCount", "unchangedOwnerRescanCount" + ], + "properties": { + "samples": {"type": "integer", "minimum": 32}, + "changedOwnerCount": {"type": "integer", "minimum": 1}, + "reusedShardCount": {"type": "integer", "minimum": 1}, + "rebuiltShardCount": {"type": "integer", "minimum": 1}, + "retiredShardCount": {"type": "integer", "minimum": 0}, + "unchangedOwnerRescanCount": {"const": 0} + } + } + ] + }, + "firstTantivyOpen": { + "unevaluatedProperties": false, + "allOf": [ + {"$ref": "https://schemas.agent-semantic-protocols.dev/semantic-search-definitions.v1.schema.json#/$defs/performanceDistribution"}, + { + "type": "object", + "required": ["filesystemReadCount", "fdProcessCount", "rgProcessCount", "tantivyBuildCount"], + "properties": { + "samples": {"type": "integer", "minimum": 32}, + "filesystemReadCount": {"type": "integer", "minimum": 32}, + "fdProcessCount": {"const": 0}, + "rgProcessCount": {"const": 0}, + "tantivyBuildCount": {"const": 0} + } + } + ] + }, + "warm": { + "unevaluatedProperties": false, + "allOf": [ + {"$ref": "https://schemas.agent-semantic-protocols.dev/semantic-search-definitions.v1.schema.json#/$defs/performanceDistribution"}, + {"properties": { + "samples": {"type": "integer", "minimum": 512}, + "p99Nanos": {"type": "integer", "minimum": 0, "exclusiveMaximum": 1000000}, + "maxNanos": {"type": "integer", "minimum": 0, "exclusiveMaximum": 1000000} + }} + ] + }, + "concurrent": { + "type": "object", + "additionalProperties": false, + "required": ["queries", "p99Nanos", "maxNanos", "wallNanos"], + "properties": { + "queries": {"type": "integer", "minimum": 32}, + "p99Nanos": {"$ref": "#/$defs/nanos"}, + "maxNanos": {"$ref": "#/$defs/nanos"}, + "wallNanos": {"$ref": "#/$defs/nanos"} + } + }, + "fusedCache": { + "type": "object", + "additionalProperties": false, + "required": [ + "hitCount", "missCount", "entryCount", "valueBytes", "capacity", + "shardCount", "externalWorkCount" + ], + "properties": { + "hitCount": {"const": 1}, + "missCount": {"const": 0}, + "entryCount": {"type": "integer", "minimum": 1}, + "valueBytes": {"type": "integer", "minimum": 1}, + "capacity": {"type": "integer", "minimum": 1}, + "shardCount": {"type": "integer", "minimum": 1}, + "externalWorkCount": {"const": 0} + } + }, + "pythonGraph": { + "type": "object", + "additionalProperties": false, + "required": ["state", "generationDigest", "receiptValidationNanos"], + "properties": { + "state": {"const": "executed"}, + "generationDigest": {"type": "string", "pattern": "^blake3-256:[0-9a-f]{64}$"}, + "receiptValidationNanos": {"$ref": "#/$defs/nanos"} + } + }, + "requestTimeExternalWork": { + "type": "object", + "additionalProperties": false, + "required": [ + "databaseReadCount", "filesystemReadCount", "providerProcessCount", + "socketOperationCount", "schedulerTaskCount" + ], + "properties": { + "databaseReadCount": {"const": 0}, + "filesystemReadCount": {"const": 0}, + "providerProcessCount": {"const": 0}, + "socketOperationCount": {"const": 0}, + "schedulerTaskCount": {"const": 0} + } + } + }, + "$defs": { + "nanos": {"type": "integer", "minimum": 0} + } +} diff --git a/schemas/lexical-postings-work-reduction-receipt.schema.json b/schemas/lexical-postings-work-reduction-receipt.schema.json new file mode 100644 index 0000000..8838321 --- /dev/null +++ b/schemas/lexical-postings-work-reduction-receipt.schema.json @@ -0,0 +1,15 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://agent-semantic-protocols.dev/schemas/lexical-postings-work-reduction-receipt.schema.json", + "title": "ASP Lexical Postings Work Reduction Receipt", + "type": "object", + "additionalProperties": false, + "required": ["schemaId", "schemaVersion", "fullScanCandidates", "indexedCandidates", "reductionFactor"], + "properties": { + "schemaId": { "const": "agent.semantic-protocols.lexical-postings-work-reduction-receipt" }, + "schemaVersion": { "const": "1" }, + "fullScanCandidates": { "type": "integer", "minimum": 1 }, + "indexedCandidates": { "type": "integer", "minimum": 1 }, + "reductionFactor": { "type": "integer", "minimum": 1000 } + } +} diff --git a/schemas/project-topology-inference-receipt.v1.schema.json b/schemas/project-topology-inference-receipt.v1.schema.json new file mode 100644 index 0000000..27f3c6d --- /dev/null +++ b/schemas/project-topology-inference-receipt.v1.schema.json @@ -0,0 +1,152 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://schemas.agent-semantic-protocols.dev/project-topology-inference-receipt.v1.schema.json", + "title": "Project Topology Inference Receipt V1", + "description": "Complete bounded MRR/Ascent topology closure retaining relation and rule identity for every derived result.", + "type": "object", + "additionalProperties": false, + "required": [ + "schemaId", + "schemaVersion", + "state", + "generationIdentity", + "rulePackIdentity", + "inputEdges", + "relationships", + "mrrClosureDigest", + "ascentSemanticDigest", + "mrrMaterializationDigest", + "receiptDigest", + "terminal" + ], + "properties": { + "schemaId": { + "const": "agent.semantic-protocols.project-topology-inference-receipt" + }, + "schemaVersion": { + "const": "1" + }, + "state": { + "const": "admitted" + }, + "generationIdentity": { + "type": "string", + "minLength": 1 + }, + "rulePackIdentity": { + "const": "project-topology-reachability" + }, + "inputEdges": { + "type": "array", + "items": { + "type": "object", + "additionalProperties": false, + "required": [ + "id", + "relation", + "from", + "to" + ], + "properties": { + "id": { + "type": "string", + "minLength": 1 + }, + "relation": { + "type": "string", + "pattern": "^[A-Z][A-Z0-9_]*$(?![\\s\\S])" + }, + "from": { + "type": "string", + "minLength": 1 + }, + "to": { + "type": "string", + "minLength": 1 + } + } + } + }, + "relationships": { + "type": "array", + "items": { + "type": "object", + "additionalProperties": false, + "required": [ + "relation", + "from", + "to", + "ruleId", + "premiseEdgeIds" + ], + "properties": { + "relation": { + "type": "string", + "pattern": "^[A-Z][A-Z0-9_]*$(?![\\s\\S])" + }, + "from": { + "type": "string", + "minLength": 1 + }, + "to": { + "type": "string", + "minLength": 1 + }, + "ruleId": { + "type": "string", + "minLength": 1 + }, + "premiseEdgeIds": { + "type": "array", + "minItems": 1, + "items": { + "type": "string", + "minLength": 1 + }, + "uniqueItems": true + } + } + } + }, + "mrrClosureDigest": { + "$ref": "#/$defs/digest" + }, + "ascentSemanticDigest": { + "$ref": "#/$defs/digest" + }, + "mrrMaterializationDigest": { + "$ref": "#/$defs/digest" + }, + "receiptDigest": { + "$ref": "#/$defs/digest" + }, + "terminal": { + "type": "object", + "additionalProperties": false, + "required": [ + "state", + "terminalCount", + "reasonKind" + ], + "properties": { + "state": { + "const": "admitted" + }, + "terminalCount": { + "const": 1 + }, + "reasonKind": { + "type": "null" + } + } + } + }, + "$defs": { + "digest": { + "type": "string", + "pattern": "^blake3-256:[0-9a-f]{64}$" + } + }, + "$comment": "V1 semantic admission additionally requires deterministic ordering, unique input edge identities, every premiseEdgeIds entry to resolve in inputEdges, and the receipt to be independently admitted before Runtime attachment." +} + diff --git a/schemas/project-topology-library.v1.schema.json b/schemas/project-topology-library.v1.schema.json new file mode 100644 index 0000000..d72218c --- /dev/null +++ b/schemas/project-topology-library.v1.schema.json @@ -0,0 +1,932 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://schemas.agent-semantic-protocols.dev/project-topology-library.v1.schema.json", + "title": "Reusable Project Topology Library V1", + "description": "Content-addressed structural and semantic project topology with source-owned expected relations, coverage, and frontiers, shared by Search, Query, code understanding, framework calibration, refactoring, and context recovery. Retrieval ranges and request-local ranks are deliberately excluded.", + "type": "object", + "additionalProperties": false, + "required": [ + "schemaId", + "schemaVersion", + "projectWorkspace", + "sourceGenerationDigest", + "providerCatalogDigest", + "libraryDigest", + "generation", + "fromScratchRebuildReceipt", + "semanticAdmissionReceipts", + "identities", + "segments", + "nodes", + "edges", + "expectedRelations", + "coverageCertificates", + "frontiers", + "closure", + "consumers", + "terminal" + ], + "properties": { + "schemaId": { + "const": "agent.semantic-protocols.project-topology-library" + }, + "schemaVersion": { + "const": "1" + }, + "projectWorkspace": { + "$ref": "project-workspace-binding.v1.schema.json" + }, + "sourceGenerationDigest": { + "$ref": "#/$defs/digest" + }, + "providerCatalogDigest": { + "$ref": "#/$defs/digest" + }, + "libraryDigest": { + "$ref": "#/$defs/digest" + }, + "generation": { + "$ref": "#/$defs/generation" + }, + "fromScratchRebuildReceipt": { + "$ref": "#/$defs/fromScratchRebuildReceipt" + }, + "semanticAdmissionReceipts": { + "type": "array", + "items": { + "$ref": "#/$defs/semanticAdmissionReceipt" + } + }, + "identities": { + "$ref": "#/$defs/identities" + }, + "segments": { + "type": "array", + "minItems": 1, + "items": { + "$ref": "#/$defs/segment" + } + }, + "nodes": { + "type": "array", + "minItems": 1, + "items": { + "$ref": "#/$defs/node" + } + }, + "edges": { + "type": "array", + "items": { + "$ref": "#/$defs/edge" + } + }, + "coverageCertificates": { + "type": "array", + "items": { + "$ref": "#/$defs/coverage" + } + }, + "closure": { + "$ref": "#/$defs/closure" + }, + "consumers": { + "type": "array", + "minItems": 1, + "uniqueItems": true, + "items": { + "enum": [ + "search", + "query", + "code-understanding", + "framework-calibration", + "refactoring", + "context-recovery" + ] + } + }, + "terminal": { + "$ref": "#/$defs/terminal" + }, + "expectedRelations": { + "type": "array", + "items": { + "$ref": "#/$defs/expectedRelation" + } + }, + "frontiers": { + "type": "array", + "items": { + "$ref": "#/$defs/frontier" + } + } + }, + "$defs": { + "digest": { + "type": "string", + "pattern": "^blake3-256:[0-9a-f]{64}$(?![\\s\\S])" + }, + "identifier": { + "type": "string", + "pattern": "^[a-z][a-z0-9_.:-]*$(?![\\s\\S])" + }, + "nodeId": { + "type": "string", + "pattern": "^[a-z][a-z0-9_-]*$(?![\\s\\S])" + }, + "selector": { + "type": "string", + "pattern": "^[A-Za-z][A-Za-z0-9+.-]*://[^\\s]+#item/[^\\s]+$(?![\\s\\S])" + }, + "ownerLocator": { + "type": "string", + "pattern": "^[A-Za-z][A-Za-z0-9+.-]*://[^\\s#]+$(?![\\s\\S])" + }, + "generation": { + "type": "object", + "additionalProperties": false, + "required": [ + "generationDigest", + "parentGenerationDigest", + "state", + "changeSetDigest", + "rebuiltSegmentIds", + "removedNodeIds", + "removedEdgeIds", + "fromScratchEquivalentDigest" + ], + "properties": { + "generationDigest": { + "$ref": "#/$defs/digest" + }, + "parentGenerationDigest": { + "oneOf": [ + { + "$ref": "#/$defs/digest" + }, + { + "type": "null" + } + ] + }, + "state": { + "const": "complete" + }, + "changeSetDigest": { + "$ref": "#/$defs/digest" + }, + "rebuiltSegmentIds": { + "type": "array", + "uniqueItems": true, + "items": { + "$ref": "#/$defs/identifier" + } + }, + "removedNodeIds": { + "type": "array", + "uniqueItems": true, + "items": { + "$ref": "#/$defs/nodeId" + } + }, + "removedEdgeIds": { + "type": "array", + "uniqueItems": true, + "items": { + "$ref": "#/$defs/identifier" + } + }, + "fromScratchEquivalentDigest": { + "$ref": "#/$defs/digest" + } + } + }, + "fromScratchRebuildReceipt": { + "type": "object", + "additionalProperties": false, + "required": [ + "id", + "sourceGenerationDigest", + "inferenceProgramDigest", + "topologyGenerationDigest", + "recomputedLibraryDigest", + "authority", + "state" + ], + "properties": { + "id": { + "$ref": "#/$defs/identifier" + }, + "sourceGenerationDigest": { + "$ref": "#/$defs/digest" + }, + "inferenceProgramDigest": { + "$ref": "#/$defs/digest" + }, + "topologyGenerationDigest": { + "$ref": "#/$defs/digest" + }, + "recomputedLibraryDigest": { + "$ref": "#/$defs/digest" + }, + "authority": { + "const": "project-topology-rebuild.v1" + }, + "state": { + "const": "admitted" + } + } + }, + "semanticAdmissionReceipt": { + "type": "object", + "additionalProperties": false, + "required": [ + "id", + "annotationNodeId", + "semanticTopologyDigest", + "authority", + "state" + ], + "properties": { + "id": { + "$ref": "#/$defs/identifier" + }, + "annotationNodeId": { + "$ref": "#/$defs/nodeId" + }, + "semanticTopologyDigest": { + "$ref": "#/$defs/digest" + }, + "authority": { + "enum": [ + "source-contract", + "human-admission" + ] + }, + "state": { + "const": "admitted" + } + } + }, + "identities": { + "type": "object", + "additionalProperties": false, + "required": [ + "structuralTopologyDigest", + "semanticTopologyDigest", + "inferenceProgramDigest", + "providerGrammarDigest", + "resolverDigest", + "topologySchemaDigest" + ], + "properties": { + "structuralTopologyDigest": { + "$ref": "#/$defs/digest" + }, + "semanticTopologyDigest": { + "$ref": "#/$defs/digest" + }, + "inferenceProgramDigest": { + "$ref": "#/$defs/digest" + }, + "providerGrammarDigest": { + "$ref": "#/$defs/digest" + }, + "resolverDigest": { + "$ref": "#/$defs/digest" + }, + "topologySchemaDigest": { + "$ref": "#/$defs/digest" + } + } + }, + "segment": { + "type": "object", + "additionalProperties": false, + "required": [ + "id", + "ownerPath", + "contentDigest", + "skeletonDigest", + "locatorDigest", + "sccDigest", + "stratum", + "nodeIds", + "edgeIds" + ], + "properties": { + "id": { + "$ref": "#/$defs/identifier" + }, + "ownerPath": { + "type": "string", + "pattern": "^(?!/)(?!.*\\\\)[^\\s]+$(?![\\s\\S])" + }, + "contentDigest": { + "$ref": "#/$defs/digest" + }, + "skeletonDigest": { + "$ref": "#/$defs/digest" + }, + "locatorDigest": { + "$ref": "#/$defs/digest" + }, + "sccDigest": { + "$ref": "#/$defs/digest" + }, + "stratum": { + "type": "integer", + "minimum": 0 + }, + "nodeIds": { + "type": "array", + "uniqueItems": true, + "items": { + "$ref": "#/$defs/nodeId" + } + }, + "edgeIds": { + "type": "array", + "uniqueItems": true, + "items": { + "$ref": "#/$defs/identifier" + } + } + } + }, + "annotation": { + "type": "object", + "additionalProperties": false, + "required": [ + "text", + "state", + "premiseWitnesses", + "producer", + "bindingDigest" + ], + "properties": { + "text": { + "type": "string", + "minLength": 1, + "maxLength": 600 + }, + "state": { + "enum": [ + "proposed", + "contested", + "accepted" + ] + }, + "premiseWitnesses": { + "type": "array", + "minItems": 1, + "uniqueItems": true, + "items": { + "$ref": "#/$defs/identifier" + } + }, + "producer": { + "type": "string", + "minLength": 1 + }, + "bindingDigest": { + "$ref": "#/$defs/digest" + }, + "admissionReceiptRef": { + "type": "string", + "minLength": 1 + } + }, + "allOf": [ + { + "if": { + "properties": { + "state": { + "const": "accepted" + } + }, + "required": [ + "state" + ] + }, + "then": { + "required": [ + "admissionReceiptRef" + ] + }, + "else": { + "not": { + "required": [ + "admissionReceiptRef" + ] + } + } + } + ] + }, + "node": { + "type": "object", + "additionalProperties": false, + "required": [ + "id", + "segmentId", + "plane", + "language", + "kind" + ], + "properties": { + "id": { + "$ref": "#/$defs/nodeId" + }, + "segmentId": { + "oneOf": [ + { + "$ref": "#/$defs/identifier" + }, + { + "type": "null" + } + ] + }, + "plane": { + "enum": [ + "structural", + "declared-semantic", + "synthesized-semantic" + ] + }, + "language": { + "type": "string", + "minLength": 1 + }, + "kind": { + "type": "string", + "minLength": 1 + }, + "name": { + "type": "string", + "minLength": 1 + }, + "selector": { + "$ref": "#/$defs/selector" + }, + "ownerLocator": { + "$ref": "#/$defs/ownerLocator" + }, + "annotation": { + "$ref": "#/$defs/annotation" + } + }, + "oneOf": [ + { + "required": [ + "selector" + ], + "not": { + "anyOf": [ + { + "required": [ + "ownerLocator" + ] + }, + { + "required": [ + "annotation" + ] + } + ] + } + }, + { + "required": [ + "ownerLocator" + ], + "not": { + "anyOf": [ + { + "required": [ + "selector" + ] + }, + { + "required": [ + "annotation" + ] + } + ] + } + }, + { + "required": [ + "annotation" + ], + "not": { + "anyOf": [ + { + "required": [ + "selector" + ] + }, + { + "required": [ + "ownerLocator" + ] + } + ] + } + } + ], + "allOf": [ + { + "if": { + "properties": { + "plane": { + "const": "synthesized-semantic" + } + }, + "required": [ + "plane" + ] + }, + "then": { + "properties": { + "segmentId": { + "type": "null" + } + } + }, + "else": { + "properties": { + "segmentId": { + "$ref": "#/$defs/identifier" + } + } + } + } + ] + }, + "edge": { + "type": "object", + "additionalProperties": false, + "required": [ + "id", + "segmentId", + "from", + "to", + "relation", + "modality", + "bindingDigest", + "witnesses" + ], + "properties": { + "id": { + "$ref": "#/$defs/identifier" + }, + "segmentId": { + "oneOf": [ + { + "$ref": "#/$defs/identifier" + }, + { + "type": "null" + } + ] + }, + "from": { + "$ref": "#/$defs/nodeId" + }, + "to": { + "$ref": "#/$defs/nodeId" + }, + "relation": { + "type": "string", + "pattern": "^[A-Z][A-Z0-9_]*$(?![\\s\\S])" + }, + "modality": { + "enum": [ + "parser-direct", + "declared", + "derived", + "proposed" + ] + }, + "bindingDigest": { + "$ref": "#/$defs/digest" + }, + "witnesses": { + "type": "array", + "minItems": 1, + "uniqueItems": true, + "items": { + "$ref": "#/$defs/identifier" + } + }, + "proofRef": { + "type": "string", + "minLength": 1 + } + }, + "allOf": [ + { + "if": { + "properties": { + "modality": { + "enum": [ + "derived", + "proposed" + ] + } + }, + "required": [ + "modality" + ] + }, + "then": { + "properties": { + "segmentId": { + "type": "null" + } + } + }, + "else": { + "properties": { + "segmentId": { + "$ref": "#/$defs/identifier" + } + } + } + }, + { + "if": { + "properties": { + "modality": { + "const": "derived" + } + }, + "required": [ + "modality" + ] + }, + "then": { + "required": [ + "proofRef" + ] + }, + "else": { + "not": { + "required": [ + "proofRef" + ] + } + } + } + ] + }, + "coverage": { + "type": "object", + "additionalProperties": false, + "required": [ + "id", + "relation", + "targetKind", + "scope", + "digest" + ], + "properties": { + "id": { + "$ref": "#/$defs/identifier" + }, + "relation": { + "type": "string", + "pattern": "^[A-Z][A-Z0-9_]*$(?![\\s\\S])" + }, + "targetKind": { + "type": "string", + "minLength": 1 + }, + "scope": { + "enum": [ + "complete", + "partial" + ] + }, + "digest": { + "$ref": "#/$defs/digest" + } + } + }, + "closure": { + "type": "object", + "additionalProperties": false, + "required": [ + "state", + "digest", + "proofDagDigest", + "derivedEdgeIds", + "proofDependencies" + ], + "properties": { + "state": { + "const": "stable" + }, + "digest": { + "$ref": "#/$defs/digest" + }, + "proofDagDigest": { + "$ref": "#/$defs/digest" + }, + "derivedEdgeIds": { + "type": "array", + "uniqueItems": true, + "items": { + "$ref": "#/$defs/identifier" + } + }, + "proofDependencies": { + "type": "array", + "items": { + "$ref": "#/$defs/proofDependency" + } + } + } + }, + "proofDependency": { + "type": "object", + "additionalProperties": false, + "required": [ + "derivedEdgeId", + "premiseEdgeIds" + ], + "properties": { + "derivedEdgeId": { + "$ref": "#/$defs/identifier" + }, + "premiseEdgeIds": { + "type": "array", + "minItems": 1, + "uniqueItems": true, + "items": { + "$ref": "#/$defs/identifier" + } + } + } + }, + "terminal": { + "type": "object", + "additionalProperties": false, + "required": [ + "state", + "terminalCount" + ], + "properties": { + "state": { + "enum": [ + "ready", + "failed" + ] + }, + "terminalCount": { + "const": 1 + }, + "reasonKind": { + "type": "string", + "minLength": 1 + } + }, + "allOf": [ + { + "if": { + "properties": { + "state": { + "const": "failed" + } + }, + "required": [ + "state" + ] + }, + "then": { + "required": [ + "reasonKind" + ] + }, + "else": { + "not": { + "required": [ + "reasonKind" + ] + } + } + } + ] + }, + "expectedRelation": { + "type": "object", + "additionalProperties": false, + "required": [ + "id", + "anchor", + "target", + "relation", + "targetKind", + "depth", + "coverage" + ], + "properties": { + "id": { + "$ref": "project-topology-library.v1.schema.json#/$defs/identifier" + }, + "anchor": { + "$ref": "project-topology-library.v1.schema.json#/$defs/nodeId" + }, + "target": { + "$ref": "project-topology-library.v1.schema.json#/$defs/nodeId" + }, + "relation": { + "type": "string", + "pattern": "^[A-Z][A-Z0-9_]*$(?![\\s\\S])" + }, + "targetKind": { + "type": "string", + "minLength": 1 + }, + "depth": { + "type": "integer", + "minimum": 0 + }, + "coverage": { + "enum": [ + "none", + "partial", + "complete" + ] + } + } + }, + "frontier": { + "type": "object", + "additionalProperties": false, + "required": [ + "anchor", + "target", + "relation", + "targetKind", + "depth", + "state", + "reason" + ], + "properties": { + "anchor": { + "$ref": "project-topology-library.v1.schema.json#/$defs/nodeId" + }, + "target": { + "$ref": "project-topology-library.v1.schema.json#/$defs/nodeId" + }, + "relation": { + "type": "string", + "pattern": "^[A-Z][A-Z0-9_]*$(?![\\s\\S])" + }, + "targetKind": { + "type": "string", + "minLength": 1 + }, + "depth": { + "type": "integer", + "minimum": 0 + }, + "state": { + "enum": [ + "unknown", + "certified-missing" + ] + }, + "reason": { + "$ref": "project-topology-library.v1.schema.json#/$defs/identifier" + }, + "coverageRef": { + "type": "string", + "minLength": 1 + } + }, + "allOf": [ + { + "if": { + "properties": { + "state": { + "const": "certified-missing" + } + }, + "required": [ + "state" + ] + }, + "then": { + "required": [ + "coverageRef" + ] + } + } + ] + } + }, + "$comment": "Semantic admission requires frontier set equality with unresolved expectations, positive-witness suppression, and complete coverage for certified-missing." +} diff --git a/schemas/project-workspace-binding.v1.schema.json b/schemas/project-workspace-binding.v1.schema.json new file mode 100644 index 0000000..c99e0c3 --- /dev/null +++ b/schemas/project-workspace-binding.v1.schema.json @@ -0,0 +1,71 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://schemas.agent-semantic-protocols.dev/project-workspace-binding.v1.schema.json", + "title": "Project Workspace Binding V1", + "description": "A GitOps-owned logical workspace identity and repository-relative containment boundary. Source snapshots and host-local worktree instances are deliberately excluded.", + "type": "object", + "additionalProperties": false, + "required": [ + "schemaId", + "schemaVersion", + "projectWorkspaceIdentity", + "workspaceRootPath", + "portability", + "repositoryAliases" + ], + "properties": { + "schemaId": {"const": "agent.semantic-protocols.project-workspace-binding"}, + "schemaVersion": {"const": "1"}, + "projectWorkspaceIdentity": {"$ref": "#/$defs/projectWorkspaceIdentity"}, + "workspaceRootPath": {"$ref": "#/$defs/workspaceRootPath"}, + "portability": {"enum": ["cross-machine", "local-only"]}, + "repositoryAliases": { + "type": "array", + "uniqueItems": true, + "items": {"$ref": "#/$defs/repositoryLocator"} + } + }, + "oneOf": [ + { + "properties": { + "portability": {"const": "cross-machine"}, + "projectWorkspaceIdentity": {"$ref": "#/$defs/crossMachineIdentity"} + } + }, + { + "properties": { + "portability": {"const": "local-only"}, + "projectWorkspaceIdentity": {"$ref": "#/$defs/localIdentity"}, + "repositoryAliases": {"maxItems": 0} + } + } + ], + "$defs": { + "projectWorkspaceIdentity": { + "oneOf": [ + {"$ref": "#/$defs/crossMachineIdentity"}, + {"$ref": "#/$defs/localIdentity"} + ] + }, + "workspaceKey": { + "type": "string", + "pattern": "^[a-z][a-z0-9._-]*(?:/[a-z][a-z0-9._-]*)*$(?![\\s\\S])" + }, + "workspaceRootPath": { + "type": "string", + "pattern": "^(?:\\.|(?!/)(?!.*(?:^|/)\\.\\.(?:/|$))(?!.*//)[^\\s#]+)$(?![\\s\\S])" + }, + "repositoryLocator": { + "type": "string", + "pattern": "^git\\+(?:https|ssh)://[^\\s#]+\\.git$(?![\\s\\S])" + }, + "crossMachineIdentity": { + "type": "string", + "pattern": "^git\\+(?:https|ssh)://[^\\s#]+\\.git#workspace/[a-z][a-z0-9._-]*(?:/[a-z][a-z0-9._-]*)*$(?![\\s\\S])" + }, + "localIdentity": { + "type": "string", + "pattern": "^git\\+file:///[^\\s#]+#workspace/[a-z][a-z0-9._-]*(?:/[a-z][a-z0-9._-]*)*$(?![\\s\\S])" + } + } +} diff --git a/schemas/provider-definitions.v1.schema.json b/schemas/provider-definitions.v1.schema.json index 5dffd90..ed75690 100644 --- a/schemas/provider-definitions.v1.schema.json +++ b/schemas/provider-definitions.v1.schema.json @@ -4,6 +4,15 @@ "title": "Provider Family Definitions v1", "description": "Shared definitions owned by the provider schema family.", "$defs": { + "schemaReference": { + "type": "object", + "additionalProperties": false, + "required": ["schemaId", "schemaVersion"], + "properties": { + "schemaId": { "type": "string", "minLength": 1 }, + "schemaVersion": { "const": "1" } + } + }, "providerReference": { "type": "object", "additionalProperties": false, diff --git a/schemas/provider-language-projection-batch-response.schema.json b/schemas/provider-language-projection-batch-response.schema.json index ad61a71..5b2b859 100644 --- a/schemas/provider-language-projection-batch-response.schema.json +++ b/schemas/provider-language-projection-batch-response.schema.json @@ -45,6 +45,8 @@ "required": [ "ownerPath", "sourceLeafDigest", + "projectionState", + "diagnostic", "items", "relations" ], @@ -57,6 +59,15 @@ "type": "string", "minLength": 1 }, + "projectionState": { + "enum": ["ready", "syntax-unavailable"] + }, + "diagnostic": { + "oneOf": [ + { "type": "null" }, + { "$ref": "#/$defs/projectionDiagnostic" } + ] + }, "items": { "type": "array", "items": { @@ -69,6 +80,47 @@ "$ref": "#/$defs/relation" } } + }, + "allOf": [ + { + "if": { + "properties": { "projectionState": { "const": "ready" } } + }, + "then": { + "properties": { "diagnostic": { "type": "null" } } + } + }, + { + "if": { + "properties": { + "projectionState": { "const": "syntax-unavailable" } + } + }, + "then": { + "properties": { + "diagnostic": { "$ref": "#/$defs/projectionDiagnostic" }, + "items": { "maxItems": 0 }, + "relations": { "maxItems": 0 } + } + } + } + ] + }, + "projectionDiagnostic": { + "type": "object", + "additionalProperties": false, + "required": ["schemaId", "schemaVersion", "reasonKind", "message"], + "properties": { + "schemaId": { + "const": "agent.semantic-protocols.provider-language-projection-diagnostic" + }, + "schemaVersion": { "const": "1" }, + "reasonKind": { "const": "source-syntax-unavailable" }, + "message": { + "type": "string", + "minLength": 1, + "maxLength": 4096 + } } }, "projectedItem": { diff --git a/schemas/provider-manifest.schema.json b/schemas/provider-manifest.schema.json index 6fb51d3..2e8d7bf 100644 --- a/schemas/provider-manifest.schema.json +++ b/schemas/provider-manifest.schema.json @@ -408,40 +408,18 @@ }, "methodId": { "type": "string", - "pattern": "^(?:guide|query|(search|query|check|proof|review|verification|evidence|ast-patch|agent)/[a-z][a-z0-9_-]*)$" + "pattern": "^(?:guide|query|(query|proof|review|ast-patch|agent)/[a-z][a-z0-9_-]*)$" }, "hookRouteBindings": { "type": "object", "additionalProperties": false, - "required": [ - "prime", - "owner", - "lexical", - "ingest", - "checkChanged" - ], "properties": { - "prime": { - "$ref": "#/$defs/methodId" - }, - "owner": { - "$ref": "#/$defs/methodId" - }, - "lexical": { - "$ref": "#/$defs/methodId" - }, "query": { "$ref": "#/$defs/methodId" }, "exactSelectorNative": { "$ref": "#/$defs/methodId" }, - "ingest": { - "$ref": "#/$defs/methodId" - }, - "checkChanged": { - "$ref": "#/$defs/methodId" - }, "dependencyTopology": { "$ref": "#/$defs/methodId" }, diff --git a/schemas/provider-route.schema.json b/schemas/provider-route.schema.json index 33d3a4a..3674a8c 100644 --- a/schemas/provider-route.schema.json +++ b/schemas/provider-route.schema.json @@ -38,8 +38,8 @@ "target": { "$ref": "#/$defs/target" }, - "requestSchemaId": { - "$ref": "#/$defs/schemaId" + "requestSchema": { + "$ref": "#/$defs/schemaReference" }, "inputs": { "type": "array", @@ -90,6 +90,15 @@ "minLength": 1, "pattern": "^[A-Za-z][A-Za-z0-9._:/-]*$" }, + "schemaReference": { + "type": "object", + "additionalProperties": false, + "required": ["schemaId", "schemaVersion"], + "properties": { + "schemaId": { "$ref": "#/$defs/schemaId" }, + "schemaVersion": { "const": "1" } + } + }, "target": { "type": "object", "additionalProperties": false, @@ -196,10 +205,10 @@ "output": { "type": "object", "additionalProperties": false, - "required": ["schemaId", "mediaType"], + "required": ["schema", "mediaType"], "properties": { - "schemaId": { - "$ref": "#/$defs/schemaId" + "schema": { + "$ref": "#/$defs/schemaReference" }, "mediaType": { "const": "application/json" diff --git a/schemas/provider-runtime-contract-descriptor.schema.json b/schemas/provider-runtime-contract-descriptor.schema.json index 5b2ebe3..465cced 100644 --- a/schemas/provider-runtime-contract-descriptor.schema.json +++ b/schemas/provider-runtime-contract-descriptor.schema.json @@ -18,14 +18,14 @@ "items": { "type": "object", "additionalProperties": false, - "required": ["operation", "requestSchemaId", "responseSchemaId"], + "required": ["operation", "requestSchema", "responseSchema"], "properties": { "operation": { "type": "string", "pattern": "^[a-z][a-z0-9-]*(\\.[a-z][a-z0-9-]*)*$" }, - "requestSchemaId": { "type": "string", "minLength": 1 }, - "responseSchemaId": { "type": "string", "minLength": 1 } + "requestSchema": { "$ref": "provider-definitions.v1.schema.json#/$defs/schemaReference" }, + "responseSchema": { "$ref": "provider-definitions.v1.schema.json#/$defs/schemaReference" } } } } diff --git a/schemas/python-generation-graph-performance-receipt.v1.schema.json b/schemas/python-generation-graph-performance-receipt.v1.schema.json new file mode 100644 index 0000000..fb67399 --- /dev/null +++ b/schemas/python-generation-graph-performance-receipt.v1.schema.json @@ -0,0 +1,47 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://agent-semantic-protocols.dev/schemas/python-generation-graph-performance-receipt.v1.schema.json", + "title": "Python generation graph performance receipt V1", + "type": "object", + "additionalProperties": false, + "required": [ + "schemaId", "schemaVersion", "owners", "cold", "warm", "concurrent", + "artifactDigest" + ], + "properties": { + "schemaId": {"const": "agent.semantic-protocols.python-generation-graph-performance-receipt"}, + "schemaVersion": {"const": "1"}, + "owners": {"type": "integer", "minimum": 4096}, + "cold": { + "allOf": [ + {"$ref": "https://schemas.agent-semantic-protocols.dev/semantic-search-definitions.v1.schema.json#/$defs/performanceDistribution"}, + {"properties": {"samples": {"type": "integer", "minimum": 8}}} + ], + "unevaluatedProperties": false + }, + "warm": { + "allOf": [ + {"$ref": "https://schemas.agent-semantic-protocols.dev/semantic-search-definitions.v1.schema.json#/$defs/performanceDistribution"}, + {"properties": {"samples": {"type": "integer", "minimum": 128}}} + ], + "unevaluatedProperties": false + }, + "concurrent": { + "allOf": [ + {"$ref": "https://schemas.agent-semantic-protocols.dev/semantic-search-definitions.v1.schema.json#/$defs/performanceDistribution"}, + { + "required": ["queries", "wallNanos"], + "properties": { + "queries": {"type": "integer", "minimum": 32}, + "wallNanos": {"$ref": "#/$defs/nanos"} + } + } + ], + "unevaluatedProperties": false + }, + "artifactDigest": {"type": "string", "pattern": "^blake3-256:[0-9a-f]{64}$"} + }, + "$defs": { + "nanos": {"type": "integer", "minimum": 0} + } +} diff --git a/schemas/query-playbook-materialization-receipt.v1.schema.json b/schemas/query-playbook-materialization-receipt.v1.schema.json new file mode 100644 index 0000000..8bce483 --- /dev/null +++ b/schemas/query-playbook-materialization-receipt.v1.schema.json @@ -0,0 +1,132 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://schemas.agent-semantic-protocols.dev/query-playbook-materialization-receipt.v1.schema.json", + "title": "Query Playbook Materialization Receipt V1", + "description": "One all-or-nothing Runtime-bound terminal for a selector-native Query Playbook request. It preserves caller order and contains no Search relationship, recommendation, or explanation fields.", + "type": "object", + "additionalProperties": false, + "required": [ + "schemaId", + "schemaVersion", + "protocolId", + "protocolVersion", + "requestId", + "projectWorkspaceIdentity", + "worktreeInstanceId", + "runtimeExecutionBinding", + "runtimeWorkspaceExecutionPublicationDigest", + "runtimeBundleDigest", + "projection", + "requestedSelectors", + "materializations", + "terminal" + ], + "properties": { + "schemaId": {"const": "agent.semantic-protocols.query-playbook-materialization-receipt"}, + "schemaVersion": {"const": "1"}, + "protocolId": {"const": "agent.semantic-protocols.query-playbook"}, + "protocolVersion": {"const": "1"}, + "requestId": {"type": "string", "minLength": 1}, + "projectWorkspaceIdentity": { + "$ref": "project-workspace-binding.v1.schema.json#/$defs/projectWorkspaceIdentity" + }, + "worktreeInstanceId": {"type": "string", "minLength": 1}, + "runtimeExecutionBinding": { + "$ref": "runtime-execution-binding.v2.schema.json" + }, + "runtimeWorkspaceExecutionPublicationDigest": {"$ref": "#/$defs/contentDigest"}, + "runtimeBundleDigest": {"$ref": "#/$defs/contentDigest"}, + "projection": {"enum": ["source", "callable-skeleton"]}, + "requestedSelectors": { + "type": "array", + "minItems": 1, + "uniqueItems": true, + "items": {"$ref": "#/$defs/selector"} + }, + "materializations": { + "type": "array", + "uniqueItems": true, + "items": {"$ref": "#/$defs/materialization"} + }, + "terminal": {"$ref": "#/$defs/terminal"} + }, + "oneOf": [ + { + "properties": { + "materializations": {"minItems": 1}, + "terminal": { + "properties": {"state": {"const": "ready"}}, + "required": ["state"] + } + } + }, + { + "properties": { + "materializations": {"maxItems": 0}, + "terminal": { + "properties": {"state": {"const": "failed"}}, + "required": ["state", "reasonKind"] + } + } + } + ], + "$defs": { + "contentDigest": { + "type": "string", + "pattern": "^blake3-256:[0-9a-f]{64}$" + }, + "selector": { + "type": "string", + "pattern": "^[a-z][a-z0-9+.-]*://[^\\s#]+#item/.+$(?![\\s\\S])" + }, + "materialization": { + "type": "object", + "additionalProperties": false, + "required": [ + "selector", + "languageId", + "providerId", + "ownerPath", + "projection", + "sourceContentDigest", + "bytes" + ], + "properties": { + "selector": {"$ref": "#/$defs/selector"}, + "languageId": {"type": "string", "minLength": 1}, + "providerId": {"type": "string", "minLength": 1}, + "ownerPath": { + "type": "string", + "minLength": 1, + "pattern": "^(?!/)(?!.*(?:^|/)\\.\\.(?:/|$)).+$" + }, + "projection": {"enum": ["source", "callable-skeleton"]}, + "sourceContentDigest": {"pattern": "^[0-9a-f]{64}$"}, + "bytes": { + "type": "array", + "minItems": 1, + "items": {"type": "integer", "minimum": 0, "maximum": 255} + } + } + }, + "terminal": { + "type": "object", + "additionalProperties": false, + "required": ["state", "terminalCount"], + "properties": { + "state": {"enum": ["ready", "failed"]}, + "terminalCount": {"const": 1}, + "reasonKind": {"type": "string", "minLength": 1} + }, + "allOf": [ + { + "if": { + "properties": {"state": {"const": "ready"}}, + "required": ["state"] + }, + "then": {"not": {"required": ["reasonKind"]}} + } + ] + } + } +} diff --git a/schemas/query-playbook-materialization-request.v1.schema.json b/schemas/query-playbook-materialization-request.v1.schema.json new file mode 100644 index 0000000..c56b998 --- /dev/null +++ b/schemas/query-playbook-materialization-request.v1.schema.json @@ -0,0 +1,54 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://schemas.agent-semantic-protocols.dev/query-playbook-materialization-request.v1.schema.json", + "title": "Query Playbook Materialization Request V1", + "description": "An internal Runtime-bound packet for selector-native Query Playbook materialization. It preserves caller order and is independent of Search topology or relationships.", + "type": "object", + "additionalProperties": false, + "required": [ + "schemaId", + "schemaVersion", + "protocolId", + "protocolVersion", + "requestId", + "projectWorkspaceIdentity", + "worktreeInstanceId", + "runtimeExecutionBinding", + "runtimeWorkspaceExecutionPublicationDigest", + "runtimeBundleDigest", + "selectors", + "projection" + ], + "properties": { + "schemaId": {"const": "agent.semantic-protocols.query-playbook-materialization-request"}, + "schemaVersion": {"const": "1"}, + "protocolId": {"const": "agent.semantic-protocols.query-playbook"}, + "protocolVersion": {"const": "1"}, + "requestId": {"type": "string", "minLength": 1}, + "projectWorkspaceIdentity": { + "$ref": "project-workspace-binding.v1.schema.json#/$defs/projectWorkspaceIdentity" + }, + "worktreeInstanceId": {"type": "string", "minLength": 1}, + "runtimeExecutionBinding": { + "$ref": "runtime-execution-binding.v2.schema.json" + }, + "runtimeWorkspaceExecutionPublicationDigest": {"$ref": "#/$defs/contentDigest"}, + "runtimeBundleDigest": {"$ref": "#/$defs/contentDigest"}, + "selectors": { + "type": "array", + "minItems": 1, + "uniqueItems": true, + "items": { + "type": "string", + "pattern": "^[a-z][a-z0-9+.-]*://[^\\s#]+#item/.+$(?![\\s\\S])" + } + }, + "projection": {"enum": ["source", "callable-skeleton"]} + }, + "$defs": { + "contentDigest": { + "type": "string", + "pattern": "^blake3-256:[0-9a-f]{64}$" + } + } +} diff --git a/schemas/resident-search-result.v1.schema.json b/schemas/resident-search-result.v1.schema.json new file mode 100644 index 0000000..96afbbd --- /dev/null +++ b/schemas/resident-search-result.v1.schema.json @@ -0,0 +1,68 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://agent-semantic-protocols.dev/schemas/resident-search-result.v1.schema.json", + "title": "Resident Search Result V1", + "type": "object", + "additionalProperties": false, + "required": [ + "schemaId", + "schemaVersion", + "state", + "generationDigest", + "rootDigest", + "providerDigest", + "indexArtifactDigest", + "hits", + "workCounters" + ], + "properties": { + "schemaId": { "const": "agent.semantic-protocols.resident-search-result" }, + "schemaVersion": { "const": "1" }, + "state": { "const": "Ready" }, + "generationDigest": { + "type": "string", + "pattern": "^blake3-256:[0-9a-f]{64}$" + }, + "rootDigest": { + "type": "string", + "pattern": "^[0-9a-f]{64}$" + }, + "providerDigest": { "type": "string", "minLength": 1 }, + "indexArtifactDigest": { "type": "string", "minLength": 1 }, + "hits": { + "type": "array", + "items": { "$ref": "#/$defs/hit" } + }, + "workCounters": { "$ref": "runtime-provider-search-receipt.v1.schema.json#/$defs/zeroWorkCounters" } + }, + "$defs": { + "hit": { + "type": "object", + "additionalProperties": false, + "required": [ + "ownerPath", + "ownerContentDigest", + "projectionTier", + "lineCount", + "queryKeys" + ], + "properties": { + "ownerPath": { "type": "string", "minLength": 1 }, + "ownerContentDigest": { "type": "string", "minLength": 1 }, + "languageId": { "type": "string", "minLength": 1 }, + "projectionTier": { + "enum": ["shallow-navigation", "owner-local-dynamic"] + }, + "lineCount": { "type": "integer", "minimum": 1 }, + "queryKeys": { + "type": "array", + "maxItems": 64, + "uniqueItems": true, + "items": { "type": "string", "minLength": 1 } + }, + "selector": { "type": "string", "minLength": 1 }, + "score": { "type": "integer", "minimum": 0 } + } + } + } +} diff --git a/schemas/resident-syntax-query-plan.v1.schema.json b/schemas/resident-syntax-query-plan.v1.schema.json new file mode 100644 index 0000000..bf7d2a6 --- /dev/null +++ b/schemas/resident-syntax-query-plan.v1.schema.json @@ -0,0 +1,238 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://agent-semantic-protocols.dev/schemas/resident-syntax-query-plan.v1.schema.json", + "title": "Resident Syntax Query Plan V1", + "type": "object", + "additionalProperties": false, + "required": [ + "schemaId", + "schemaVersion", + "profileId", + "planDigest", + "queryDigest", + "languageId", + "providerId", + "parserAbiDigest", + "queryGrammarDigest", + "operatorTableDigest", + "capabilityTableDigest", + "generationDigest", + "patterns", + "selectedFields", + "requiredCapabilityRows", + "regexPrograms" + ], + "properties": { + "schemaId": {"const": "agent.semantic-protocols.resident-syntax-query-plan"}, + "schemaVersion": {"const": "1"}, + "profileId": {"const": "asp.enhanced-tree-sitter-query.v1"}, + "planDigest": {"$ref": "#/$defs/digest"}, + "queryDigest": {"$ref": "#/$defs/digest"}, + "languageId": {"$ref": "#/$defs/identity"}, + "providerId": {"$ref": "#/$defs/identity"}, + "parserAbiDigest": {"$ref": "#/$defs/digest"}, + "queryGrammarDigest": {"$ref": "#/$defs/digest"}, + "operatorTableDigest": {"$ref": "#/$defs/digest"}, + "capabilityTableDigest": {"$ref": "#/$defs/digest"}, + "generationDigest": {"$ref": "#/$defs/digest"}, + "patterns": {"type": "array", "minItems": 1, "items": {"$ref": "#/$defs/pattern"}}, + "selectedFields": { + "type": "array", + "minItems": 1, + "uniqueItems": true, + "items": {"$ref": "#/$defs/resultField"} + }, + "requiredCapabilityRows": { + "type": "array", + "minItems": 1, + "uniqueItems": true, + "items": {"$ref": "#/$defs/identity"} + }, + "regexPrograms": { + "type": "array", + "items": {"$ref": "#/$defs/regexProgram"} + } + }, + "$defs": { + "digest": {"type": "string", "pattern": "^blake3-256:[0-9a-f]{64}$"}, + "identity": {"type": "string", "pattern": "^[A-Za-z][A-Za-z0-9._+:/-]*$"}, + "resultField": { + "enum": ["kind", "name", "selector", "byte-range", "scopes", "queryKeys", "projections", "relations"] + }, + "cardinality": { + "type": "object", + "additionalProperties": false, + "required": ["minimum", "maximum"], + "properties": { + "minimum": {"type": "integer", "minimum": 0}, + "maximum": {"type": ["integer", "null"], "minimum": 1} + } + }, + "capture": { + "type": "object", + "additionalProperties": false, + "required": ["name", "residentFactPath", "cardinality", "capabilityRowId"], + "properties": { + "name": {"type": "string", "pattern": "^[A-Za-z_][A-Za-z0-9_.-]*$"}, + "residentFactPath": {"$ref": "#/$defs/factPath"}, + "cardinality": {"$ref": "#/$defs/cardinality"}, + "capabilityRowId": {"$ref": "#/$defs/identity"} + } + }, + "factPath": { + "enum": [ + "kind", + "name", + "selector", + "byte-range", + "scopes.relation", + "scopes.kind", + "scopes.symbol", + "queryKeys", + "projections.kind", + "relations" + ] + }, + "origin": { + "type": "object", + "additionalProperties": false, + "required": ["kind", "capabilityRowId"], + "properties": { + "kind": {"enum": ["node", "field", "capture", "anchor", "quantifier", "standard-predicate", "asp-predicate"]}, + "capabilityRowId": {"$ref": "#/$defs/identity"} + } + }, + "condition": { + "oneOf": [ + {"$ref": "#/$defs/trueCondition"}, + {"$ref": "#/$defs/compositeCondition"}, + {"$ref": "#/$defs/scalarCondition"}, + {"$ref": "#/$defs/setCondition"}, + {"$ref": "#/$defs/rangeCondition"}, + {"$ref": "#/$defs/relationCondition"} + ] + }, + "trueCondition": { + "type": "object", + "additionalProperties": false, + "required": ["kind", "origin"], + "properties": { + "kind": {"const": "true"}, + "origin": {"$ref": "#/$defs/origin"} + } + }, + "compositeCondition": { + "type": "object", + "additionalProperties": false, + "required": ["kind", "terms"], + "properties": { + "kind": {"enum": ["all", "any"]}, + "terms": {"type": "array", "minItems": 1, "items": {"$ref": "#/$defs/condition"}} + } + }, + "scalarCondition": { + "type": "object", + "additionalProperties": false, + "required": ["kind", "capture", "factPath", "operator", "value", "origin"], + "allOf": [ + { + "if": {"properties": {"operator": {"enum": ["any-of", "not-any-of"]}}, "required": ["operator"]}, + "then": {"properties": {"value": {"type": "array", "minItems": 1, "uniqueItems": true, "items": {"type": "string"}}}}, + "else": {"properties": {"value": {"type": "string"}}} + }, + { + "if": {"properties": {"operator": {"enum": ["match", "not-match", "any-match", "any-not-match"]}}, "required": ["operator"]}, + "then": {"required": ["regexProgramId"]}, + "else": {"not": {"required": ["regexProgramId"]}} + } + ], + "properties": { + "kind": {"const": "scalar"}, + "capture": {"type": "string", "minLength": 1}, + "factPath": {"enum": ["kind", "name", "selector"]}, + "operator": {"enum": ["eq", "not-eq", "match", "not-match", "any-eq", "any-not-eq", "any-match", "any-not-match", "any-of", "not-any-of"]}, + "value": { + "oneOf": [ + {"type": "string"}, + {"type": "array", "minItems": 1, "uniqueItems": true, "items": {"type": "string"}} + ] + }, + "regexProgramId": {"$ref": "#/$defs/identity"}, + "origin": {"$ref": "#/$defs/origin"} + } + }, + "setCondition": { + "type": "object", + "additionalProperties": false, + "required": ["kind", "capture", "factPath", "operator", "value", "origin"], + "allOf": [ + { + "if": {"properties": {"operator": {"enum": ["any-match", "none-match"]}}, "required": ["operator"]}, + "then": {"required": ["regexProgramId"]}, + "else": {"not": {"required": ["regexProgramId"]}} + } + ], + "properties": { + "kind": {"const": "set"}, + "capture": {"type": "string", "minLength": 1}, + "factPath": {"enum": ["scopes.relation", "scopes.kind", "scopes.symbol", "queryKeys", "projections.kind"]}, + "operator": {"enum": ["any-eq", "none-eq", "any-match", "none-match"]}, + "value": {"type": "string"}, + "regexProgramId": {"$ref": "#/$defs/identity"}, + "origin": {"$ref": "#/$defs/origin"} + } + }, + "rangeCondition": { + "type": "object", + "additionalProperties": false, + "required": ["kind", "capture", "mode", "start", "end", "origin"], + "properties": { + "kind": {"const": "range"}, + "capture": {"type": "string", "minLength": 1}, + "mode": {"enum": ["within", "contains", "overlaps"]}, + "start": {"type": "string", "pattern": "^(0|[1-9][0-9]*)$"}, + "end": {"type": "string", "pattern": "^(0|[1-9][0-9]*)$"}, + "origin": {"$ref": "#/$defs/origin"} + } + }, + "relationCondition": { + "type": "object", + "additionalProperties": false, + "required": ["kind", "capture", "operator", "direction", "relationKind", "origin"], + "properties": { + "kind": {"const": "relation"}, + "capture": {"type": "string", "minLength": 1}, + "operator": {"enum": ["related", "not-related"]}, + "direction": {"enum": ["in", "out", "either"]}, + "relationKind": {"type": "string", "minLength": 1}, + "endpointSelector": {"type": "string", "minLength": 1}, + "origin": {"$ref": "#/$defs/origin"} + } + }, + "pattern": { + "type": "object", + "additionalProperties": false, + "required": ["index", "captures", "structure", "predicates"], + "properties": { + "index": {"type": "integer", "minimum": 0}, + "captures": {"type": "array", "minItems": 1, "items": {"$ref": "#/$defs/capture"}}, + "structure": {"$ref": "#/$defs/condition"}, + "predicates": {"type": "array", "items": {"$ref": "#/$defs/condition"}} + } + }, + "regexProgram": { + "type": "object", + "additionalProperties": false, + "required": ["id", "engineId", "engineVersion", "syntaxProfile", "encoding", "program", "programDigest"], + "properties": { + "id": {"$ref": "#/$defs/identity"}, + "engineId": {"$ref": "#/$defs/identity"}, + "engineVersion": {"type": "string", "minLength": 1}, + "syntaxProfile": {"const": "tree-sitter-rust-regex-v1"}, + "encoding": {"const": "base64"}, + "program": {"type": "string", "pattern": "^[A-Za-z0-9+/]*={0,2}$"}, + "programDigest": {"$ref": "#/$defs/digest"} + } + } + } +} diff --git a/schemas/rg-coverage-receipt.schema.json b/schemas/rg-coverage-receipt.schema.json new file mode 100644 index 0000000..47a9633 --- /dev/null +++ b/schemas/rg-coverage-receipt.schema.json @@ -0,0 +1,32 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://agent-semantic-protocols.dev/schemas/rg-coverage-receipt.schema.json", + "title": "ASP Explicit Ripgrep Coverage Receipt", + "type": "object", + "additionalProperties": false, + "required": ["schemaId", "schemaVersion", "state", "generationDigest", "sourceRootDigest", "providerDigest", "indexArtifactDigest", "coverageInputDigest", "committedOwnerCount", "inputBytes", "recordCount", "candidateCount"], + "properties": { + "schemaId": { "const": "agent.semantic-protocols.rg-coverage-receipt" }, + "schemaVersion": { "const": "1" }, + "state": { "enum": ["ready", "failed"] }, + "reasonKind": { "enum": ["input-budget-exceeded", "record-budget-exceeded", "candidate-budget-exceeded", "owner-set-invalid", "owner-not-admitted", "owner-content-mismatch", "record-content-mismatch"] }, + "generationDigest": { "type": "string", "minLength": 1 }, + "sourceRootDigest": { "type": "string", "minLength": 1 }, + "providerDigest": { "type": "string", "minLength": 1 }, + "indexArtifactDigest": { "type": "string", "minLength": 1 }, + "coverageInputDigest": { + "type": "string", + "pattern": "^blake3-256:[0-9a-f]{64}$", + "description": "Digest of generation identity, committed owner identities, and the bounded executor output. It is evidence, not a cache key authority." + }, + "committedOwnerCount": { "type": "integer", "minimum": 1 }, + "inputBytes": { "type": "integer", "minimum": 0 }, + "recordCount": { "type": "integer", "minimum": 0 }, + "candidateCount": { "type": "integer", "minimum": 0 } + }, + "allOf": [{ + "if": { "properties": { "state": { "const": "failed" } } }, + "then": { "required": ["reasonKind"] }, + "else": { "not": { "required": ["reasonKind"] } } + }] +} diff --git a/schemas/runtime-artifact-bundle-binding.v2.schema.json b/schemas/runtime-artifact-bundle-binding.v2.schema.json new file mode 100644 index 0000000..50ded02 --- /dev/null +++ b/schemas/runtime-artifact-bundle-binding.v2.schema.json @@ -0,0 +1,28 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://agent-semantic-protocols.dev/schemas/runtime-artifact-bundle-binding.v2.schema.json", + "title": "Runtime Artifact Bundle Binding V2", + "type": "object", + "additionalProperties": false, + "required": [ + "schemaId", + "schemaVersion", + "providerRegistrationDigest", + "providerArtifactSetDigest", + "evaluatorPolicyDigest", + "evaluatorAbiDigest", + "schemaBundleDigest" + ], + "properties": { + "schemaId": {"const": "agent.semantic-protocols.runtime-artifact-bundle-binding"}, + "schemaVersion": {"const": "2"}, + "providerRegistrationDigest": {"$ref": "#/$defs/blake3Digest"}, + "providerArtifactSetDigest": {"$ref": "#/$defs/blake3Digest"}, + "evaluatorPolicyDigest": {"$ref": "#/$defs/blake3Digest"}, + "evaluatorAbiDigest": {"$ref": "#/$defs/blake3Digest"}, + "schemaBundleDigest": {"$ref": "#/$defs/blake3Digest"} + }, + "$defs": { + "blake3Digest": {"type": "string", "pattern": "^blake3-256:[0-9a-f]{64}$"} + } +} diff --git a/schemas/runtime-artifact-execution-closure-member.v1.schema.json b/schemas/runtime-artifact-execution-closure-member.v1.schema.json new file mode 100644 index 0000000..1ed5bd9 --- /dev/null +++ b/schemas/runtime-artifact-execution-closure-member.v1.schema.json @@ -0,0 +1,150 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://schemas.agent-semantic-protocols.dev/runtime-artifact-execution-closure-member.v1.schema.json", + "title": "Runtime Artifact Execution Closure Member V1", + "description": "One canonical member of the immutable Runtime execution closure. The bundle binding contains the BLAKE3 digest of each complete member document.", + "oneOf": [ + {"$ref": "#/$defs/providerRegistration"}, + {"$ref": "#/$defs/providerArtifactSet"}, + {"$ref": "#/$defs/evaluatorPolicy"}, + {"$ref": "#/$defs/evaluatorAbi"}, + {"$ref": "#/$defs/schemaBundle"} + ], + "$defs": { + "base": { + "type": "object", + "required": ["schemaId", "schemaVersion", "memberKind", "entries"], + "properties": { + "schemaId": {"const": "agent.semantic-protocols.runtime-artifact-execution-closure-member"}, + "schemaVersion": {"const": "1"} + } + }, + "digest": {"type": "string", "pattern": "^blake3-256:[0-9a-f]{64}$"}, + "identifier": {"type": "string", "minLength": 1}, + "providerRegistrationEntry": { + "type": "object", + "additionalProperties": false, + "required": ["providerId", "languageId", "registrationDigest", "artifactMember"], + "properties": { + "providerId": {"$ref": "#/$defs/identifier"}, + "languageId": {"$ref": "#/$defs/identifier"}, + "registrationDigest": {"$ref": "#/$defs/digest"}, + "artifactMember": {"$ref": "#/$defs/identifier"} + } + }, + "providerArtifactEntry": { + "type": "object", + "additionalProperties": false, + "required": ["providerId", "artifactMember", "artifactDigest"], + "properties": { + "providerId": {"$ref": "#/$defs/identifier"}, + "artifactMember": {"$ref": "#/$defs/identifier"}, + "artifactDigest": {"$ref": "#/$defs/digest"} + } + }, + "namedDigestEntry": { + "type": "object", + "additionalProperties": false, + "required": ["id", "digest"], + "properties": { + "id": {"$ref": "#/$defs/identifier"}, + "digest": {"$ref": "#/$defs/digest"} + } + }, + "namedDigestEntries": { + "type": "array", + "minItems": 1, + "uniqueItems": true, + "items": {"$ref": "#/$defs/namedDigestEntry"} + }, + "languageSchemaEntry": { + "type": "object", + "additionalProperties": false, + "required": ["languageId", "schemaDigest"], + "properties": { + "languageId": {"$ref": "#/$defs/identifier"}, + "schemaDigest": {"$ref": "#/$defs/digest"} + } + }, + "providerRegistration": { + "allOf": [ + {"$ref": "#/$defs/base"}, + { + "type": "object", + "additionalProperties": false, + "required": ["schemaId", "schemaVersion", "memberKind", "entries"], + "properties": { + "schemaId": {"const": "agent.semantic-protocols.runtime-artifact-execution-closure-member"}, + "schemaVersion": {"const": "1"}, + "memberKind": {"const": "provider-registration"}, + "entries": {"type": "array", "uniqueItems": true, "items": {"$ref": "#/$defs/providerRegistrationEntry"}} + } + } + ] + }, + "providerArtifactSet": { + "allOf": [ + {"$ref": "#/$defs/base"}, + { + "type": "object", + "additionalProperties": false, + "required": ["schemaId", "schemaVersion", "memberKind", "entries"], + "properties": { + "schemaId": {"const": "agent.semantic-protocols.runtime-artifact-execution-closure-member"}, + "schemaVersion": {"const": "1"}, + "memberKind": {"const": "provider-artifact-set"}, + "entries": {"type": "array", "uniqueItems": true, "items": {"$ref": "#/$defs/providerArtifactEntry"}} + } + } + ] + }, + "evaluatorPolicy": { + "allOf": [ + {"$ref": "#/$defs/base"}, + { + "type": "object", + "additionalProperties": false, + "required": ["schemaId", "schemaVersion", "memberKind", "entries"], + "properties": { + "schemaId": {"const": "agent.semantic-protocols.runtime-artifact-execution-closure-member"}, + "schemaVersion": {"const": "1"}, + "memberKind": {"const": "evaluator-policy"}, + "entries": {"$ref": "#/$defs/namedDigestEntries"} + } + } + ] + }, + "evaluatorAbi": { + "allOf": [ + {"$ref": "#/$defs/base"}, + { + "type": "object", + "additionalProperties": false, + "required": ["schemaId", "schemaVersion", "memberKind", "entries"], + "properties": { + "schemaId": {"const": "agent.semantic-protocols.runtime-artifact-execution-closure-member"}, + "schemaVersion": {"const": "1"}, + "memberKind": {"const": "evaluator-abi"}, + "entries": {"$ref": "#/$defs/namedDigestEntries"} + } + } + ] + }, + "schemaBundle": { + "allOf": [ + {"$ref": "#/$defs/base"}, + { + "type": "object", + "additionalProperties": false, + "required": ["schemaId", "schemaVersion", "memberKind", "entries"], + "properties": { + "schemaId": {"const": "agent.semantic-protocols.runtime-artifact-execution-closure-member"}, + "schemaVersion": {"const": "1"}, + "memberKind": {"const": "schema-bundle"}, + "entries": {"type": "array", "minItems": 1, "uniqueItems": true, "items": {"$ref": "#/$defs/languageSchemaEntry"}} + } + } + ] + } + } +} diff --git a/schemas/runtime-binary-bundle.v2.schema.json b/schemas/runtime-binary-bundle.v2.schema.json new file mode 100644 index 0000000..dc145a8 --- /dev/null +++ b/schemas/runtime-binary-bundle.v2.schema.json @@ -0,0 +1,30 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://agent-semantic-protocols.dev/schemas/runtime-binary-bundle.v2.schema.json", + "title": "Runtime Binary Bundle V2", + "type": "object", + "additionalProperties": false, + "required": ["schemaId", "schemaVersion", "bundleDigest", "members", "executionBinding"], + "properties": { + "schemaId": {"const": "agent.semantic-protocols.runtime-binary-bundle"}, + "schemaVersion": {"const": 2}, + "bundleDigest": {"$ref": "#/$defs/blake3Digest"}, + "members": { + "type": "object", + "additionalProperties": {"$ref": "#/$defs/blake3Digest"}, + "required": [ + "provider-registration.json", + "provider-artifact-set", + "evaluator-policy.json", + "evaluator-abi.json", + "schema-bundle.json" + ], + "minProperties": 6, + "propertyNames": {"pattern": "^[A-Za-z0-9][A-Za-z0-9._-]*$"} + }, + "executionBinding": {"$ref": "runtime-artifact-bundle-binding.v2.schema.json"} + }, + "$defs": { + "blake3Digest": {"type": "string", "pattern": "^blake3-256:[0-9a-f]{64}$"} + } +} diff --git a/schemas/runtime-client-terminal.schema.json b/schemas/runtime-client-terminal.schema.json new file mode 100644 index 0000000..94ba3f5 --- /dev/null +++ b/schemas/runtime-client-terminal.schema.json @@ -0,0 +1,23 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://agent-semantic-protocols.dev/schemas/runtime-client-terminal.schema.json", + "title": "ASP Runtime Client Boundary Terminal", + "type": "object", + "additionalProperties": false, + "required": ["schemaId", "schemaVersion", "state", "reasonKind", "retryAdmitted", "argv", "cause"], + "properties": { + "schemaId": { "const": "agent.semantic-protocols.runtime-client-terminal" }, + "schemaVersion": { "const": "1" }, + "state": { "const": "failed" }, + "reasonKind": { + "enum": [ + "runtime-client-connect-deadline-exceeded", + "runtime-client-session-deadline-exceeded", + "runtime-client-response-deadline-exceeded" + ] + }, + "retryAdmitted": { "const": false }, + "argv": { "type": "array", "minItems": 1, "items": { "type": "string" } }, + "cause": { "type": "string", "minLength": 1 } + } +} diff --git a/schemas/runtime-execution-binding.v2.schema.json b/schemas/runtime-execution-binding.v2.schema.json new file mode 100644 index 0000000..e5ae78b --- /dev/null +++ b/schemas/runtime-execution-binding.v2.schema.json @@ -0,0 +1,42 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://schemas.agent-semantic-protocols.dev/runtime-execution-binding.v2.schema.json", + "title": "Runtime Execution Binding V2", + "description": "One immutable Project Workspace, worktree, content, Runtime artifact, evaluator policy, publication receipt, and ABI product for Search and Query admission.", + "type": "object", + "additionalProperties": false, + "required": [ + "schemaId", + "schemaVersion", + "projectWorkspace", + "worktreeInstanceId", + "publicationNonce", + "contentBinding", + "runtimeArtifactDigest", + "evaluatorPolicyDigest", + "activeArtifactReceiptDigest", + "evaluatorAbiDigest" + ], + "properties": { + "schemaId": {"const": "agent.semantic-protocols.runtime-execution-binding"}, + "schemaVersion": {"const": "2"}, + "projectWorkspace": { + "$ref": "project-workspace-binding.v1.schema.json" + }, + "worktreeInstanceId": {"type": "string", "minLength": 1}, + "publicationNonce": {"type": "string", "minLength": 1}, + "contentBinding": { + "$ref": "https://agent.semantic.protocols/schemas/content-binding" + }, + "runtimeArtifactDigest": {"$ref": "#/$defs/contentDigest"}, + "evaluatorPolicyDigest": {"$ref": "#/$defs/contentDigest"}, + "activeArtifactReceiptDigest": {"$ref": "#/$defs/contentDigest"}, + "evaluatorAbiDigest": {"$ref": "#/$defs/contentDigest"} + }, + "$defs": { + "contentDigest": { + "type": "string", + "pattern": "^blake3-256:[0-9a-f]{64}$" + } + } +} diff --git a/schemas/runtime-provider-search-receipt.v1.schema.json b/schemas/runtime-provider-search-receipt.v1.schema.json new file mode 100644 index 0000000..65e18ed --- /dev/null +++ b/schemas/runtime-provider-search-receipt.v1.schema.json @@ -0,0 +1,80 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://agent-semantic-protocols.dev/schemas/runtime-provider-search-receipt.v1.schema.json", + "title": "Runtime Provider Search Receipt v1", + "type": "object", + "additionalProperties": false, + "required": [ + "schemaId", + "schemaVersion", + "operationId", + "status", + "languageId", + "generationDigest", + "rootDigest", + "providerDigest", + "indexArtifactDigest", + "candidateCount", + "selectorProjectionBudget", + "projectedOwnerCount", + "selectors", + "ownerPaths", + "residentReadElapsedMicros", + "serviceElapsedMicros", + "elapsedMicros", + "workCounters" + ], + "properties": { + "schemaId": { + "const": "agent.semantic-protocols.runtime-provider-search-receipt" + }, + "schemaVersion": { "const": "1" }, + "operationId": { "type": "string", "minLength": 1 }, + "status": { "enum": ["matches", "no-matches"] }, + "languageId": { "type": "string", "minLength": 1 }, + "generationDigest": { + "type": "string", + "pattern": "^blake3-256:[0-9a-f]{64}$" + }, + "rootDigest": { "type": "string", "pattern": "^[0-9a-f]{64}$" }, + "providerDigest": { "type": "string", "minLength": 1 }, + "indexArtifactDigest": { "type": "string", "minLength": 1 }, + "candidateCount": { "type": "integer", "minimum": 0 }, + "selectorProjectionBudget": { "type": "integer", "minimum": 1 }, + "projectedOwnerCount": { "type": "integer", "minimum": 0, "maximum": 16 }, + "selectors": { + "type": "array", + "uniqueItems": true, + "items": { "type": "string", "minLength": 1 } + }, + "ownerPaths": { + "type": "array", + "uniqueItems": true, + "items": { "type": "string", "minLength": 1 } + }, + "residentReadElapsedMicros": { "type": "integer", "minimum": 0 }, + "serviceElapsedMicros": { "type": "integer", "minimum": 0 }, + "elapsedMicros": { "type": "integer", "minimum": 0 }, + "workCounters": { "$ref": "#/$defs/zeroWorkCounters" } + }, + "$defs": { + "zeroWorkCounters": { + "type": "object", + "additionalProperties": false, + "required": [ + "databaseReadCount", + "filesystemReadCount", + "providerProcessCount", + "socketOperationCount", + "schedulerTaskCount" + ], + "properties": { + "databaseReadCount": { "const": 0 }, + "filesystemReadCount": { "const": 0 }, + "providerProcessCount": { "const": 0 }, + "socketOperationCount": { "const": 0 }, + "schedulerTaskCount": { "const": 0 } + } + } + } +} diff --git a/schemas/runtime-resident-request-plane-receipt.v1.schema.json b/schemas/runtime-resident-request-plane-receipt.v1.schema.json new file mode 100644 index 0000000..90eec89 --- /dev/null +++ b/schemas/runtime-resident-request-plane-receipt.v1.schema.json @@ -0,0 +1,73 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://agent-semantic-protocols.dev/schemas/runtime-resident-request-plane-receipt.v1.schema.json", + "title": "Runtime Resident Request Plane Receipt v1", + "type": "object", + "additionalProperties": false, + "required": [ + "schemaId", + "schemaVersion", + "operation", + "requestTemperature", + "state", + "generationDigest", + "elapsedMicros", + "generationLookupCount", + "generationWaitCount", + "generationBuildCount", + "filesystemReadCount", + "databaseReadCount", + "providerProcessCount", + "parserInvocationCount", + "secondaryRuntimeRpcCount", + "socketDiscoveryCount", + "terminalWaitCount" + ], + "properties": { + "schemaId": { + "const": "agent.semantic-protocols.runtime-resident-request-plane-receipt" + }, + "schemaVersion": { "const": "1" }, + "operation": { "enum": ["search", "query"] }, + "requestTemperature": { "enum": ["cold", "warm"] }, + "state": { "enum": ["ready", "query-not-ready"] }, + "generationDigest": { + "type": ["string", "null"], + "pattern": "^blake3-256:[0-9a-f]{64}$" + }, + "elapsedMicros": { + "type": "integer", + "minimum": 0, + "exclusiveMaximum": 1000 + }, + "generationLookupCount": { "const": 1 }, + "generationWaitCount": { "const": 0 }, + "generationBuildCount": { "const": 0 }, + "filesystemReadCount": { "const": 0 }, + "databaseReadCount": { "const": 0 }, + "providerProcessCount": { "const": 0 }, + "parserInvocationCount": { "const": 0 }, + "secondaryRuntimeRpcCount": { "const": 0 }, + "socketDiscoveryCount": { "const": 0 }, + "terminalWaitCount": { "const": 0 } + }, + "allOf": [ + { + "if": { + "properties": { "state": { "const": "ready" } }, + "required": ["state"] + }, + "then": { + "properties": { + "generationDigest": { + "type": "string", + "pattern": "^blake3-256:[0-9a-f]{64}$" + } + } + }, + "else": { + "properties": { "generationDigest": { "const": null } } + } + } + ] +} diff --git a/schemas/runtime-search-client-timing-witness.v1.schema.json b/schemas/runtime-search-client-timing-witness.v1.schema.json new file mode 100644 index 0000000..9633a98 --- /dev/null +++ b/schemas/runtime-search-client-timing-witness.v1.schema.json @@ -0,0 +1,56 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://tao3k.dev/schemas/runtime-search-client-timing-witness.v1.schema.json", + "title": "Runtime Search Client Timing Witness", + "type": "object", + "additionalProperties": false, + "required": ["schemaId", "schemaVersion", "sessionId", "requestId", "phases"], + "properties": { + "schemaId": { + "const": "agent.semantic-protocols.runtime-search-client-timing-witness" + }, + "schemaVersion": { "const": "1" }, + "sessionId": { "type": "string", "minLength": 1 }, + "requestId": { "type": "string", "minLength": 1 }, + "phases": { + "type": "array", + "minItems": 3, + "maxItems": 3, + "prefixItems": [ + { "$ref": "#/$defs/launcher" }, + { "$ref": "#/$defs/clientFrameEncode" }, + { "$ref": "#/$defs/ipcConnect" } + ], + "items": false + } + }, + "$defs": { + "phase": { + "type": "object", + "additionalProperties": false, + "required": ["name", "elapsedMicros"], + "properties": { + "name": { "type": "string" }, + "elapsedMicros": { "type": "integer", "minimum": 0 } + } + }, + "launcher": { + "allOf": [ + { "$ref": "#/$defs/phase" }, + { "properties": { "name": { "const": "launcher" } } } + ] + }, + "clientFrameEncode": { + "allOf": [ + { "$ref": "#/$defs/phase" }, + { "properties": { "name": { "const": "client-frame-encode" } } } + ] + }, + "ipcConnect": { + "allOf": [ + { "$ref": "#/$defs/phase" }, + { "properties": { "name": { "const": "ipc-connect" } } } + ] + } + } +} diff --git a/schemas/runtime-search-execution-budget.v1.schema.json b/schemas/runtime-search-execution-budget.v1.schema.json new file mode 100644 index 0000000..fe59554 --- /dev/null +++ b/schemas/runtime-search-execution-budget.v1.schema.json @@ -0,0 +1,76 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://schemas.agent-semantic-protocols.dev/runtime-search-execution-budget.v1.schema.json", + "title": "Runtime Search execution budget V1", + "description": "A Runtime-generation-owned Search cardinality receipt. It bounds semantic work and output; it does not configure Tokio scheduling concurrency.", + "type": "object", + "additionalProperties": false, + "required": [ + "schemaId", + "schemaVersion", + "authority", + "generationDigest", + "cardinality", + "limits" + ], + "properties": { + "schemaId": { + "const": "agent.semantic-protocols.runtime-search-execution-budget" + }, + "schemaVersion": {"const": "1"}, + "authority": {"const": "runtime-generation"}, + "generationDigest": {"$ref": "#/$defs/digest"}, + "cardinality": {"$ref": "#/$defs/cardinality"}, + "limits": {"$ref": "#/$defs/limits"} + }, + "$defs": { + "digest": { + "type": "string", + "pattern": "^blake3-256:[0-9a-f]{64}$(?![\\s\\S])" + }, + "cardinality": { + "type": "object", + "additionalProperties": false, + "required": [ + "indexedOwnerCount", + "corpusByteCount", + "corpusLineCount", + "graphNodeCount", + "graphEdgeCount" + ], + "properties": { + "indexedOwnerCount": {"type": "integer", "minimum": 1}, + "corpusByteCount": {"type": "integer", "minimum": 1}, + "corpusLineCount": {"type": "integer", "minimum": 1, "maximum": 4294967295}, + "graphNodeCount": {"type": "integer", "minimum": 0}, + "graphEdgeCount": {"type": "integer", "minimum": 0} + } + }, + "limits": { + "type": "object", + "additionalProperties": false, + "required": [ + "rgMatchCount", + "lexicalOwnerCount", + "syntaxSelectorCount", + "graphCandidateOwnerCount", + "graphDepth", + "graphNodeCount", + "graphEdgeCount", + "graphResultCount", + "evidenceItemCount" + ], + "properties": { + "rgMatchCount": {"type": "integer", "minimum": 1, "maximum": 4294967295}, + "lexicalOwnerCount": {"type": "integer", "minimum": 1, "maximum": 4294967295}, + "syntaxSelectorCount": {"type": "integer", "minimum": 1}, + "graphCandidateOwnerCount": {"type": "integer", "minimum": 1}, + "graphDepth": {"type": "integer", "minimum": 0, "maximum": 16}, + "graphNodeCount": {"type": "integer", "minimum": 0, "maximum": 256}, + "graphEdgeCount": {"type": "integer", "minimum": 0, "maximum": 1024}, + "graphResultCount": {"type": "integer", "minimum": 0, "maximum": 30}, + "evidenceItemCount": {"const": 30} + } + } + } +} diff --git a/schemas/runtime-workspace-execution-pointer.v1.schema.json b/schemas/runtime-workspace-execution-pointer.v1.schema.json new file mode 100644 index 0000000..cad82d3 --- /dev/null +++ b/schemas/runtime-workspace-execution-pointer.v1.schema.json @@ -0,0 +1,30 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://schemas.agent-semantic-protocols.dev/runtime-workspace-execution-pointer.v1.schema.json", + "title": "Runtime Workspace Execution Pointer V1", + "description": "Single canonical pointer atomically binding a durable source generation to its immutable Runtime execution publication.", + "type": "object", + "additionalProperties": false, + "required": [ + "schemaId", + "schemaVersion", + "workspaceIdentity", + "generationDigest", + "sourceRootDigest", + "executionPublicationDigest" + ], + "properties": { + "schemaId": {"const": "agent.semantic-protocols.runtime-workspace-execution-pointer"}, + "schemaVersion": {"const": "1"}, + "workspaceIdentity": {"type": "string", "minLength": 1}, + "generationDigest": {"$ref": "#/$defs/contentDigest"}, + "sourceRootDigest": {"$ref": "#/$defs/contentDigest"}, + "executionPublicationDigest": {"$ref": "#/$defs/contentDigest"} + }, + "$defs": { + "contentDigest": { + "type": "string", + "pattern": "^blake3-256:[0-9a-f]{64}$" + } + } +} diff --git a/schemas/runtime-workspace-execution-publication.v1.schema.json b/schemas/runtime-workspace-execution-publication.v1.schema.json new file mode 100644 index 0000000..ab96d11 --- /dev/null +++ b/schemas/runtime-workspace-execution-publication.v1.schema.json @@ -0,0 +1,36 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://schemas.agent-semantic-protocols.dev/runtime-workspace-execution-publication.v1.schema.json", + "title": "Runtime Workspace Execution Publication V1", + "description": "Immutable sidecar atomically binding one durable workspace source generation to one RuntimeExecutionBinding V2 product.", + "type": "object", + "additionalProperties": false, + "required": [ + "schemaId", + "schemaVersion", + "workspaceIdentity", + "generationDigest", + "sourceRootDigest", + "contentPublicationCommit", + "runtimeExecutionBinding", + "runtimeBundleDigest", + "publicationDigest" + ], + "properties": { + "schemaId": {"const": "agent.semantic-protocols.runtime-workspace-execution-publication"}, + "schemaVersion": {"const": "1"}, + "workspaceIdentity": {"type": "string", "minLength": 1}, + "generationDigest": {"$ref": "#/$defs/contentDigest"}, + "sourceRootDigest": {"$ref": "#/$defs/contentDigest"}, + "contentPublicationCommit": {"$ref": "content-publication-commit.v1.schema.json"}, + "runtimeExecutionBinding": {"$ref": "runtime-execution-binding.v2.schema.json"}, + "runtimeBundleDigest": {"$ref": "#/$defs/contentDigest"}, + "publicationDigest": {"$ref": "#/$defs/contentDigest"} + }, + "$defs": { + "contentDigest": { + "type": "string", + "pattern": "^blake3-256:[0-9a-f]{64}$" + } + } +} diff --git a/schemas/search-generation-change-set.v1.schema.json b/schemas/search-generation-change-set.v1.schema.json new file mode 100644 index 0000000..4da6c49 --- /dev/null +++ b/schemas/search-generation-change-set.v1.schema.json @@ -0,0 +1,74 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://agent-semantic-protocols.dev/schemas/search-generation-change-set.v1.schema.json", + "title": "ASP Search Generation Merkle Change Set v1", + "type": "object", + "additionalProperties": false, + "required": [ + "schemaId", "schemaVersion", "workspaceIdentity", "baseRootDigest", + "candidateRootDigest", "providerDigest", "schemaDigest", "analyzerDigest", + "changeSetDigest", "changes" + ], + "properties": { + "schemaId": { "const": "agent.semantic-protocols.search-generation-change-set" }, + "schemaVersion": { "const": "1" }, + "workspaceIdentity": { "type": "string", "minLength": 1 }, + "baseRootDigest": { "type": ["string", "null"] }, + "candidateRootDigest": { "type": "string", "minLength": 1 }, + "providerDigest": { "type": "string", "minLength": 1 }, + "schemaDigest": { "type": "string", "minLength": 1 }, + "analyzerDigest": { "type": "string", "minLength": 1 }, + "changeSetDigest": { "type": "string", "minLength": 1 }, + "changes": { + "type": "array", + "items": { "$ref": "#/$defs/change" } + } + }, + "$defs": { + "change": { + "type": "object", + "additionalProperties": false, + "required": ["ownerPath", "kind"], + "properties": { + "ownerPath": { "type": "string", "minLength": 1 }, + "kind": { "enum": ["added", "changed", "removed"] }, + "previousContentDigest": { "type": "string", "minLength": 1 }, + "contentDigest": { "type": "string", "minLength": 1 }, + "lineCount": { "type": "integer", "minimum": 1 }, + "lexicalQueryKeys": { + "type": "array", + "items": { "type": "string", "minLength": 1 }, + "uniqueItems": true + }, + "graphFragmentDigest": { "type": "string", "minLength": 1 } + }, + "allOf": [ + { + "if": { "properties": { "kind": { "const": "added" } } }, + "then": { + "required": [ + "contentDigest", "lineCount", "lexicalQueryKeys", "graphFragmentDigest" + ], + "not": { "required": ["previousContentDigest"] } + } + }, + { + "if": { "properties": { "kind": { "const": "changed" } } }, + "then": { + "required": [ + "previousContentDigest", "contentDigest", "lineCount", + "lexicalQueryKeys", "graphFragmentDigest" + ] + } + }, + { + "if": { "properties": { "kind": { "const": "removed" } } }, + "then": { + "required": ["previousContentDigest"], + "not": { "required": ["contentDigest"] } + } + } + ] + } + } +} diff --git a/schemas/search-topology-settlement.v1.schema.json b/schemas/search-topology-settlement.v1.schema.json new file mode 100644 index 0000000..cc4c267 --- /dev/null +++ b/schemas/search-topology-settlement.v1.schema.json @@ -0,0 +1,769 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://schemas.agent-semantic-protocols.dev/search-topology-settlement.v1.schema.json", + "title": "Search topology single-GQL settlement V1", + "description": "A request-bound Search projection whose selector-bearing GQL nodes, direct and derived relations, and frontiers are the complete Agent-facing reasoning surface. Query selectors remain on their owning GQL nodes; there is no duplicate MaterializationSet handoff.", + "type": "object", + "additionalProperties": false, + "required": [ + "schemaId", + "schemaVersion", + "protocolId", + "protocolVersion", + "resultState", + "binding", + "inference", + "nodes", + "edges", + "coverageCertificates", + "frontiers", + "rendering", + "terminal" + ], + "properties": { + "schemaId": { + "const": "agent.semantic-protocols.search-topology-settlement" + }, + "schemaVersion": { + "const": "1" + }, + "protocolId": { + "const": "agent.semantic-protocols.search-playbook" + }, + "protocolVersion": { + "const": "1" + }, + "resultState": { + "enum": [ + "queryable", + "empty", + "incomplete", + "blocked" + ] + }, + "binding": { + "$ref": "#/$defs/binding" + }, + "inference": { + "$ref": "#/$defs/inference" + }, + "nodes": { + "type": "array", + "items": { + "$ref": "#/$defs/node" + } + }, + "edges": { + "type": "array", + "items": { + "$ref": "#/$defs/edge" + } + }, + "coverageCertificates": { + "$ref": "project-topology-library.v1.schema.json#/properties/coverageCertificates" + }, + "frontiers": { + "type": "array", + "items": { + "$ref": "#/$defs/frontier" + } + }, + "rendering": { + "$ref": "#/$defs/rendering" + }, + "terminal": { + "$ref": "#/$defs/terminal" + } + }, + "$defs": { + "digest": { + "type": "string", + "pattern": "^blake3-256:[0-9a-f]{64}$(?![\\s\\S])" + }, + "identifier": { + "type": "string", + "pattern": "^[a-z][a-z0-9_.:-]*$(?![\\s\\S])" + }, + "nodeId": { + "type": "string", + "pattern": "^[a-z][a-z0-9_-]*$(?![\\s\\S])" + }, + "selector": { + "type": "string", + "pattern": "^[A-Za-z][A-Za-z0-9+.-]*://[^\\s]+#item/[^\\s]+$(?![\\s\\S])" + }, + "binding": { + "type": "object", + "additionalProperties": false, + "required": [ + "projectWorkspaceIdentity", + "sourceGenerationDigest", + "providerCatalogDigest", + "topologyLibraryDigest", + "topologyGenerationDigest", + "structuralTopologyDigest", + "semanticTopologyDigest", + "inferenceProgramDigest", + "topologyClosureDigest", + "evidenceBindingDigest", + "decisionCoreDigest", + "topologyDeltaDigest" + ], + "properties": { + "projectWorkspaceIdentity": { + "$ref": "project-workspace-binding.v1.schema.json#/$defs/projectWorkspaceIdentity" + }, + "sourceGenerationDigest": { + "$ref": "#/$defs/digest" + }, + "providerCatalogDigest": { + "$ref": "#/$defs/digest" + }, + "topologyLibraryDigest": { + "$ref": "#/$defs/digest" + }, + "topologyGenerationDigest": { + "$ref": "#/$defs/digest" + }, + "structuralTopologyDigest": { + "$ref": "#/$defs/digest" + }, + "semanticTopologyDigest": { + "$ref": "#/$defs/digest" + }, + "inferenceProgramDigest": { + "$ref": "#/$defs/digest" + }, + "topologyClosureDigest": { + "$ref": "#/$defs/digest" + }, + "evidenceBindingDigest": { + "$ref": "#/$defs/digest" + }, + "decisionCoreDigest": { + "$ref": "#/$defs/digest" + }, + "topologyDeltaDigest": { + "$ref": "#/$defs/digest" + } + } + }, + "inference": { + "type": "object", + "additionalProperties": false, + "required": [ + "programId", + "engineProfile", + "scope", + "state", + "terminationKind", + "iterationCount", + "traversalDepth", + "derivedRelationCount", + "proofDagDigest", + "proofArtifactLocator", + "rankingReceiptDigest", + "candidateClosureReceiptDigest", + "candidateRelationSetDigest", + "nextRelationSetDigest", + "postRankingCertified" + ], + "properties": { + "programId": { + "const": "project-topology-inference.v1" + }, + "engineProfile": { + "type": "string", + "minLength": 1 + }, + "scope": { + "enum": [ + "bounded", + "complete" + ] + }, + "state": { + "enum": [ + "complete", + "incomplete", + "blocked" + ] + }, + "terminationKind": { + "enum": [ + "fixed-point", + "budget-exhausted", + "blocked" + ] + }, + "iterationCount": { + "type": "integer", + "minimum": 0 + }, + "traversalDepth": { + "type": "integer", + "minimum": 0 + }, + "derivedRelationCount": { + "type": "integer", + "minimum": 0 + }, + "proofDagDigest": { + "$ref": "#/$defs/digest" + }, + "proofArtifactLocator": { + "type": "string", + "minLength": 1 + }, + "rankingReceiptDigest": { + "$ref": "#/$defs/digest" + }, + "candidateClosureReceiptDigest": { + "$ref": "#/$defs/digest" + }, + "candidateRelationSetDigest": { + "$ref": "#/$defs/digest" + }, + "nextRelationSetDigest": { + "$ref": "#/$defs/digest" + }, + "postRankingCertified": { + "type": "boolean" + }, + "reasonKind": { + "$ref": "#/$defs/identifier" + } + }, + "oneOf": [ + { + "properties": { + "state": { + "const": "complete" + }, + "terminationKind": { + "const": "fixed-point" + }, + "postRankingCertified": { + "const": true + } + }, + "not": { + "required": [ + "reasonKind" + ] + } + }, + { + "properties": { + "state": { + "const": "incomplete" + }, + "terminationKind": { + "const": "budget-exhausted" + }, + "postRankingCertified": { + "const": false + } + }, + "required": [ + "reasonKind" + ] + }, + { + "properties": { + "state": { + "const": "blocked" + }, + "terminationKind": { + "const": "blocked" + }, + "postRankingCertified": { + "const": false + } + }, + "required": [ + "reasonKind" + ] + } + ] + }, + "hitProjection": { + "type": "object", + "additionalProperties": false, + "properties": { + "rg": { + "$ref": "https://schemas.agent-semantic-protocols.dev/semantic-search-definitions.v1.schema.json#/$defs/lineRanges" + }, + "tantivy": { + "type": "array", + "uniqueItems": true, + "items": { + "type": "string", + "minLength": 1 + } + }, + "native": { + "type": "boolean" + } + }, + "minProperties": 1 + }, + "projection": { + "type": "object", + "additionalProperties": false, + "required": [ + "rank", + "depth" + ], + "properties": { + "rank": { + "type": "integer", + "minimum": 1 + }, + "depth": { + "type": "integer", + "minimum": 0 + }, + "hit": { + "$ref": "#/$defs/hitProjection" + }, + "jq": { + "type": "string", + "minLength": 1 + } + }, + "anyOf": [ + { + "required": [ + "hit" + ] + }, + { + "required": [ + "jq" + ] + } + ] + }, + "semanticAnnotation": { + "$ref": "project-topology-library.v1.schema.json#/$defs/annotation" + }, + "sourceExcerpt": { + "type": "object", + "additionalProperties": false, + "required": [ + "path", + "match", + "read", + "witness" + ], + "properties": { + "path": { + "type": "string", + "pattern": "^(?!/)(?!.*\\\\)[^\\s]+$(?![\\s\\S])" + }, + "match": { + "$ref": "https://schemas.agent-semantic-protocols.dev/semantic-search-definitions.v1.schema.json#/$defs/lineRanges" + }, + "read": { + "$ref": "https://schemas.agent-semantic-protocols.dev/semantic-search-definitions.v1.schema.json#/$defs/lineRanges" + }, + "witness": { + "$ref": "#/$defs/identifier" + } + } + }, + "node": { + "type": "object", + "additionalProperties": false, + "required": [ + "id", + "language", + "kind" + ], + "properties": { + "id": { + "$ref": "#/$defs/nodeId" + }, + "language": { + "type": "string", + "minLength": 1 + }, + "kind": { + "type": "string", + "minLength": 1 + }, + "name": { + "type": "string", + "minLength": 1 + }, + "selector": { + "$ref": "#/$defs/selector" + }, + "ownerLocator": { + "$ref": "project-topology-library.v1.schema.json#/$defs/ownerLocator" + }, + "projection": { + "$ref": "#/$defs/projection" + }, + "annotation": { + "$ref": "#/$defs/semanticAnnotation" + }, + "excerpt": { + "$ref": "#/$defs/sourceExcerpt" + } + }, + "oneOf": [ + { + "required": [ + "selector" + ], + "not": { + "anyOf": [ + { + "required": [ + "ownerLocator" + ] + }, + { + "required": [ + "annotation" + ] + }, + { + "required": [ + "excerpt" + ] + } + ] + } + }, + { + "required": [ + "ownerLocator" + ], + "not": { + "anyOf": [ + { + "required": [ + "selector" + ] + }, + { + "required": [ + "annotation" + ] + }, + { + "required": [ + "excerpt" + ] + }, + { + "required": [ + "projection" + ] + } + ] + } + }, + { + "required": [ + "annotation" + ], + "not": { + "anyOf": [ + { + "required": [ + "selector" + ] + }, + { + "required": [ + "ownerLocator" + ] + }, + { + "required": [ + "excerpt" + ] + }, + { + "required": [ + "projection" + ] + } + ] + } + }, + { + "required": [ + "excerpt" + ], + "not": { + "anyOf": [ + { + "required": [ + "selector" + ] + }, + { + "required": [ + "ownerLocator" + ] + }, + { + "required": [ + "annotation" + ] + }, + { + "required": [ + "projection" + ] + } + ] + } + } + ] + }, + "edge": { + "type": "object", + "additionalProperties": false, + "required": [ + "from", + "to", + "relation", + "modality", + "producerAuthority", + "evidenceAuthority", + "witnesses" + ], + "properties": { + "from": { + "$ref": "#/$defs/nodeId" + }, + "to": { + "$ref": "#/$defs/nodeId" + }, + "relation": { + "type": "string", + "pattern": "^[A-Z][A-Z0-9_]*$(?![\\s\\S])" + }, + "modality": { + "enum": [ + "parser-direct", + "declared", + "derived", + "proposed" + ] + }, + "producerAuthority": { + "enum": [ + "provider-parser", + "source-contract", + "project-topology-inference.v1", + "model-proposal" + ] + }, + "evidenceAuthority": { + "enum": [ + "provider-witness", + "contract-witness", + "proof-dag", + "model-premises" + ] + }, + "witnesses": { + "type": "array", + "minItems": 1, + "uniqueItems": true, + "items": { + "$ref": "#/$defs/identifier" + } + }, + "derivedBy": { + "const": "project-topology-inference.v1" + }, + "proofRef": { + "type": "string", + "minLength": 1 + } + }, + "allOf": [ + { + "if": { + "properties": { + "modality": { + "const": "derived" + } + }, + "required": [ + "modality" + ] + }, + "then": { + "required": [ + "derivedBy", + "proofRef" + ] + }, + "else": { + "not": { + "anyOf": [ + { + "required": [ + "derivedBy" + ] + }, + { + "required": [ + "proofRef" + ] + } + ] + } + } + } + ] + }, + "coverageCertificate": { + "$ref": "project-topology-library.v1.schema.json#/$defs/coverage" + }, + "frontier": { + "type": "object", + "additionalProperties": false, + "required": [ + "anchor", + "target", + "relation", + "targetKind", + "depth", + "state", + "reason" + ], + "properties": { + "anchor": { + "$ref": "search-topology-settlement.v1.schema.json#/$defs/nodeId" + }, + "target": { + "$ref": "search-topology-settlement.v1.schema.json#/$defs/nodeId" + }, + "relation": { + "type": "string", + "pattern": "^[A-Z][A-Z0-9_]*$(?![\\s\\S])" + }, + "targetKind": { + "type": "string", + "minLength": 1 + }, + "depth": { + "type": "integer", + "minimum": 0 + }, + "state": { + "enum": [ + "unknown", + "certified-missing" + ] + }, + "reason": { + "$ref": "search-topology-settlement.v1.schema.json#/$defs/identifier" + }, + "coverageRef": { + "type": "string", + "minLength": 1 + } + }, + "allOf": [ + { + "if": { + "properties": { + "state": { + "const": "certified-missing" + } + }, + "required": [ + "state" + ] + }, + "then": { + "required": [ + "coverageRef" + ] + } + } + ] + }, + "rendering": { + "type": "object", + "additionalProperties": false, + "required": [ + "format", + "gqlBlockCount", + "ascentSourceExposed" + ], + "properties": { + "format": { + "const": "org-gql" + }, + "gqlBlockCount": { + "const": 1 + }, + "ascentSourceExposed": { + "const": false + } + } + }, + "terminal": { + "type": "object", + "additionalProperties": false, + "required": [ + "state", + "terminalCount" + ], + "properties": { + "state": { + "enum": [ + "ready", + "incomplete", + "failed" + ] + }, + "terminalCount": { + "const": 1 + }, + "reasonKind": { + "$ref": "#/$defs/identifier" + } + }, + "oneOf": [ + { + "properties": { + "state": { + "const": "ready" + } + }, + "not": { + "required": [ + "reasonKind" + ] + } + }, + { + "properties": { + "state": { + "enum": [ + "incomplete", + "failed" + ] + } + }, + "required": [ + "reasonKind" + ] + } + ] + } + } +} diff --git a/schemas/semantic-assurance-case.v1.schema.json b/schemas/semantic-assurance-case.v1.schema.json deleted file mode 100644 index 114bc4a..0000000 --- a/schemas/semantic-assurance-case.v1.schema.json +++ /dev/null @@ -1,295 +0,0 @@ -{ - "$schema": "https://json-schema.org/draft/2020-12/schema", - "$id": "https://agent-semantic-protocols.local/schemas/semantic-assurance-case.v1.schema.json", - "title": "Semantic Assurance Case", - "description": "Reviewer-first assurance-case artifact derived from a semantic evidence graph.", - "type": "object", - "additionalProperties": false, - "required": [ - "schemaId", - "schemaVersion", - "protocolId", - "protocolVersion", - "caseSetId", - "producer", - "project", - "summary", - "cases" - ], - "properties": { - "schemaId": { - "const": "agent.semantic-protocols.semantic-assurance-case" - }, - "schemaVersion": { - "const": "1" - }, - "protocolId": { - "const": "agent.semantic-protocols.assurance-case" - }, - "protocolVersion": { - "const": "1" - }, - "caseSetId": { - "type": "string", - "pattern": "^[a-z][a-z0-9_.:-]*$" - }, - "producer": { - "$ref": "semantic-assurance-definitions.v1.schema.json#/$defs/producer" - }, - "project": { - "$ref": "semantic-assurance-definitions.v1.schema.json#/$defs/project" - }, - "summary": { - "$ref": "#/$defs/summary" - }, - "cases": { - "type": "array", - "items": { - "$ref": "#/$defs/case" - } - }, - "fields": { - "$ref": "#/$defs/fields" - } - }, - "$defs": { - "scalar": { - "oneOf": [ - { "type": "string" }, - { "type": "number" }, - { "type": "boolean" }, - { - "type": "array", - "items": { - "oneOf": [ - { "type": "string" }, - { "type": "number" }, - { "type": "boolean" } - ] - } - } - ] - }, - "fields": { - "type": "object", - "additionalProperties": { - "$ref": "#/$defs/scalar" - } - }, - "projectPath": { - "type": "string", - "minLength": 1, - "pattern": "^(?:\\.|(?!/)(?![A-Za-z]:)(?![0-9]+:)(?!\\.\\.?($|/))(?!.*(^|/)\\.\\.?($|/))(?!.*//)[^\\s:\\\\]+(?:/[^\\s:\\\\]+)*)$" - }, - "summary": { - "type": "object", - "additionalProperties": false, - "required": ["cases", "claims", "supportedClaims", "openGaps", "staleItems"], - "properties": { - "cases": { - "type": "integer", - "minimum": 0 - }, - "claims": { - "type": "integer", - "minimum": 0 - }, - "supportedClaims": { - "type": "integer", - "minimum": 0 - }, - "openGaps": { - "type": "integer", - "minimum": 0 - }, - "staleItems": { - "type": "integer", - "minimum": 0 - }, - "fields": { - "$ref": "#/$defs/fields" - } - } - }, - "caseStatus": { - "enum": ["supported", "needs-review", "blocked", "unknown"] - }, - "claimKind": { - "enum": [ - "invariant", - "proof", - "review", - "owner", - "behavior", - "determinism", - "custom" - ] - }, - "claim": { - "type": "object", - "additionalProperties": false, - "required": ["claimId", "kind", "statement"], - "properties": { - "claimId": { - "type": "string", - "pattern": "^[a-z][a-z0-9_.:-]*$" - }, - "kind": { - "$ref": "#/$defs/claimKind" - }, - "statement": { - "type": "string", - "minLength": 1 - }, - "targetNodeId": { - "type": "string", - "pattern": "^[a-z][a-z0-9_.:-]*$" - }, - "severity": { - "enum": ["info", "warning", "error"] - }, - "fields": { - "$ref": "#/$defs/fields" - } - } - }, - "nodeRefList": { - "type": "array", - "items": { - "$ref": "#/$defs/nodeRef" - } - }, - "nodeRef": { - "type": "object", - "additionalProperties": false, - "required": ["nodeId", "kind", "label"], - "properties": { - "nodeId": { - "type": "string", - "pattern": "^[a-z][a-z0-9_.:-]*$" - }, - "kind": { - "$ref": "semantic-assurance-definitions.v1.schema.json#/$defs/nodeKind" - }, - "label": { - "type": "string", - "minLength": 1 - }, - "status": { - "$ref": "semantic-assurance-definitions.v1.schema.json#/$defs/nodeStatus" - }, - "summary": { - "type": "string", - "minLength": 1 - }, - "fields": { - "$ref": "#/$defs/fields" - } - } - }, - "actionRef": { - "type": "object", - "additionalProperties": false, - "required": ["nodeId", "summary"], - "properties": { - "nodeId": { - "type": "string", - "pattern": "^[a-z][a-z0-9_.:-]*$" - }, - "actionId": { - "type": "string", - "minLength": 1 - }, - "summary": { - "type": "string", - "minLength": 1 - }, - "priority": { - "enum": ["p0", "p1", "p2", "p3"] - }, - "fields": { - "$ref": "#/$defs/fields" - } - } - }, - "gap": { - "type": "object", - "additionalProperties": false, - "required": ["gapId", "summary"], - "properties": { - "gapId": { - "type": "string", - "pattern": "^[a-z][a-z0-9_.:-]*$" - }, - "sourceGapId": { - "type": "string", - "minLength": 1 - }, - "ownerPath": { - "$ref": "#/$defs/projectPath" - }, - "summary": { - "type": "string", - "minLength": 1 - }, - "severity": { - "enum": ["info", "warning", "error"] - }, - "fields": { - "$ref": "#/$defs/fields" - } - } - }, - "case": { - "type": "object", - "additionalProperties": false, - "required": ["caseId", "claim", "status"], - "properties": { - "caseId": { - "type": "string", - "pattern": "^[a-z][a-z0-9_.:-]*$" - }, - "claim": { - "$ref": "#/$defs/claim" - }, - "status": { - "$ref": "#/$defs/caseStatus" - }, - "subjectNodeId": { - "type": "string", - "pattern": "^[a-z][a-z0-9_.:-]*$" - }, - "ownerPath": { - "$ref": "#/$defs/projectPath" - }, - "supportedBy": { - "$ref": "#/$defs/nodeRefList" - }, - "observedBy": { - "$ref": "#/$defs/nodeRefList" - }, - "reviewedBy": { - "$ref": "#/$defs/nodeRefList" - }, - "waivedBy": { - "$ref": "#/$defs/nodeRefList" - }, - "actions": { - "type": "array", - "items": { - "$ref": "#/$defs/actionRef" - } - }, - "gaps": { - "type": "array", - "items": { - "$ref": "#/$defs/gap" - } - }, - "fields": { - "$ref": "#/$defs/fields" - } - } - } - } -} diff --git a/schemas/semantic-definitions.v1.schema.json b/schemas/semantic-definitions.v1.schema.json index 9696a69..3cd444e 100644 --- a/schemas/semantic-definitions.v1.schema.json +++ b/schemas/semantic-definitions.v1.schema.json @@ -6,7 +6,9 @@ "$defs": { "projectPath": { "type": "string", - "minLength": 1 + "description": "Canonical project-root-relative path. This is a path value, not a display locator: no rank prefixes, URI schemes, command prefixes, line ranges, columns, absolute paths, parent segments, backslashes, or numeric node slots.", + "minLength": 1, + "pattern": "^(?:\\.|(?!/)(?![A-Za-z]:)(?![0-9]+:)(?!\\.\\.?($|/))(?!.*(^|/)\\.\\.?($|/))(?!.*//)[^\\s:\\\\]+(?:/[^\\s:\\\\]+)*)$" }, "lineRange": { "type": "string", diff --git a/schemas/semantic-evidence-graph.v1.schema.json b/schemas/semantic-evidence-graph.v1.schema.json deleted file mode 100644 index 3220059..0000000 --- a/schemas/semantic-evidence-graph.v1.schema.json +++ /dev/null @@ -1,263 +0,0 @@ -{ - "$schema": "https://json-schema.org/draft/2020-12/schema", - "$id": "https://agent-semantic-protocols.local/schemas/semantic-evidence-graph.v1.schema.json", - "title": "Semantic Evidence Graph", - "description": "Portable graph artifact that links review packets, invariants, receipts, behavior snapshots, determinism readiness, proof pilots, waivers, and reviewer actions.", - "type": "object", - "additionalProperties": false, - "required": [ - "schemaId", - "schemaVersion", - "protocolId", - "protocolVersion", - "graphId", - "producer", - "project", - "summary", - "nodes", - "edges" - ], - "properties": { - "schemaId": { - "const": "agent.semantic-protocols.semantic-evidence-graph" - }, - "schemaVersion": { - "const": "1" - }, - "protocolId": { - "const": "agent.semantic-protocols.evidence-graph" - }, - "protocolVersion": { - "const": "1" - }, - "graphId": { - "type": "string", - "pattern": "^[a-z][a-z0-9_.:-]*$" - }, - "producer": { - "$ref": "semantic-assurance-definitions.v1.schema.json#/$defs/producer" - }, - "project": { - "$ref": "semantic-assurance-definitions.v1.schema.json#/$defs/project" - }, - "summary": { - "$ref": "#/$defs/summary" - }, - "nodes": { - "type": "array", - "items": { - "$ref": "#/$defs/node" - } - }, - "edges": { - "type": "array", - "items": { - "$ref": "#/$defs/edge" - } - }, - "gaps": { - "type": "array", - "items": { - "$ref": "#/$defs/gap" - } - }, - "fields": { - "$ref": "#/$defs/fields" - } - }, - "$defs": { - "scalar": { - "oneOf": [ - { "type": "string" }, - { "type": "number" }, - { "type": "boolean" }, - { - "type": "array", - "items": { - "oneOf": [ - { "type": "string" }, - { "type": "number" }, - { "type": "boolean" } - ] - } - } - ] - }, - "fields": { - "type": "object", - "additionalProperties": { - "$ref": "#/$defs/scalar" - } - }, - "projectPath": { - "type": "string", - "minLength": 1, - "pattern": "^(?:\\.|(?!/)(?![A-Za-z]:)(?![0-9]+:)(?!\\.\\.?($|/))(?!.*(^|/)\\.\\.?($|/))(?!.*//)[^\\s:\\\\]+(?:/[^\\s:\\\\]+)*)$" - }, - "summary": { - "type": "object", - "additionalProperties": false, - "required": ["nodes", "edges", "owners", "claims", "staleItems", "gaps"], - "properties": { - "nodes": { - "type": "integer", - "minimum": 0 - }, - "edges": { - "type": "integer", - "minimum": 0 - }, - "owners": { - "type": "integer", - "minimum": 0 - }, - "claims": { - "type": "integer", - "minimum": 0 - }, - "staleItems": { - "type": "integer", - "minimum": 0 - }, - "gaps": { - "type": "integer", - "minimum": 0 - }, - "fields": { - "$ref": "#/$defs/fields" - } - } - }, - "node": { - "type": "object", - "additionalProperties": false, - "required": ["nodeId", "kind", "label"], - "properties": { - "nodeId": { - "type": "string", - "pattern": "^[a-z][a-z0-9_.:-]*$" - }, - "kind": { - "$ref": "semantic-assurance-definitions.v1.schema.json#/$defs/nodeKind" - }, - "label": { - "type": "string", - "minLength": 1 - }, - "ownerPath": { - "$ref": "#/$defs/projectPath" - }, - "candidateId": { - "type": "string", - "minLength": 1 - }, - "receiptId": { - "type": "string", - "minLength": 1 - }, - "snapshotId": { - "type": "string", - "minLength": 1 - }, - "readinessId": { - "type": "string", - "minLength": 1 - }, - "proofId": { - "type": "string", - "minLength": 1 - }, - "packetId": { - "type": "string", - "minLength": 1 - }, - "waiverId": { - "type": "string", - "minLength": 1 - }, - "actionId": { - "type": "string", - "minLength": 1 - }, - "status": { - "$ref": "semantic-assurance-definitions.v1.schema.json#/$defs/nodeStatus" - }, - "summary": { - "type": "string", - "minLength": 1 - }, - "location": { - "$ref": "semantic-assurance-definitions.v1.schema.json#/$defs/location" - }, - "fields": { - "$ref": "#/$defs/fields" - } - } - }, - "edgeKind": { - "enum": [ - "derived-from", - "requires-evidence", - "verified-by", - "observed-by", - "waived-by", - "reviewed-by", - "suggests-action", - "supports-claim" - ] - }, - "edge": { - "type": "object", - "additionalProperties": false, - "required": ["edgeId", "kind", "fromNodeId", "toNodeId"], - "properties": { - "edgeId": { - "type": "string", - "pattern": "^[a-z][a-z0-9_.:-]*$" - }, - "kind": { - "$ref": "#/$defs/edgeKind" - }, - "fromNodeId": { - "type": "string", - "pattern": "^[a-z][a-z0-9_.:-]*$" - }, - "toNodeId": { - "type": "string", - "pattern": "^[a-z][a-z0-9_.:-]*$" - }, - "label": { - "type": "string", - "minLength": 1 - }, - "fields": { - "$ref": "#/$defs/fields" - } - } - }, - "gap": { - "type": "object", - "additionalProperties": false, - "required": ["gapId", "summary"], - "properties": { - "gapId": { - "type": "string", - "pattern": "^[a-z][a-z0-9_.:-]*$" - }, - "ownerPath": { - "$ref": "#/$defs/projectPath" - }, - "summary": { - "type": "string", - "minLength": 1 - }, - "severity": { - "enum": ["info", "warning", "error"] - }, - "fields": { - "$ref": "#/$defs/fields" - } - } - } - } -} diff --git a/schemas/semantic-graph-resident-evaluation-request.v1.schema.json b/schemas/semantic-graph-resident-evaluation-request.v1.schema.json new file mode 100644 index 0000000..fd04f90 --- /dev/null +++ b/schemas/semantic-graph-resident-evaluation-request.v1.schema.json @@ -0,0 +1,57 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://agent-semantic-protocols.dev/schemas/semantic-graph-resident-evaluation-request.v1.schema.json", + "title": "Resident graph evaluation request V1", + "type": "object", + "required": [ + "schemaId", "schemaVersion", "protocolId", "protocolVersion", + "packetKind", "languageId", "surface", "queryTerms", "profile", + "entryNodeIds", "budget" + ], + "properties": { + "schemaId": {"const": "agent.semantic-protocols.semantic-graph-resident-evaluation-request"}, + "schemaVersion": {"const": "1"}, + "protocolId": {"const": "agent.semantic-protocols.search"}, + "protocolVersion": {"const": "1"}, + "packetKind": {"const": "resident-graph-evaluation-request"}, + "languageId": {"type": "string", "pattern": "^[a-z][a-z0-9-]*$"}, + "surface": { + "enum": ["search-playbook", "query"] + }, + "queryTerms": { + "type": "array", "maxItems": 32, "uniqueItems": true, + "items": {"type": "string", "minLength": 1, "maxLength": 256} + }, + "queryClauses": { + "description": "Ordered Agent-authored Graph reasoning clauses; array order is semantic priority.", + "type": "array", "maxItems": 64, + "items": {"type": "string", "minLength": 1, "maxLength": 4096} + }, + "profile": {"enum": ["balanced", "structural", "dependency"]}, + "entryNodeIds": { + "type": "array", "maxItems": 4096, "uniqueItems": true, + "items": {"type": "string", "minLength": 1, "maxLength": 1024} + }, + "candidateNodeIds": { + "description": "Acquisition frontier that Graph may rank or filter but may not extend.", + "type": "array", "maxItems": 4096, "uniqueItems": true, + "items": {"type": "string", "minLength": 1, "maxLength": 1024} + }, + "budget": { + "type": "object", + "required": ["maxDepth", "maxNodes", "maxEdges", "maxResults"], + "properties": { + "maxDepth": {"type": "integer", "minimum": 0, "maximum": 16}, + "maxNodes": {"type": "integer", "minimum": 1, "maximum": 256}, + "maxEdges": {"type": "integer", "minimum": 0, "maximum": 1024}, + "maxResults": {"type": "integer", "minimum": 1, "maximum": 100} + }, + "additionalProperties": false + } + }, + "anyOf": [ + {"properties": {"queryTerms": {"minItems": 1}}}, + {"properties": {"entryNodeIds": {"minItems": 1}}} + ], + "additionalProperties": false +} diff --git a/schemas/semantic-graph-resident-evaluation-result.v1.schema.json b/schemas/semantic-graph-resident-evaluation-result.v1.schema.json new file mode 100644 index 0000000..9a6dddb --- /dev/null +++ b/schemas/semantic-graph-resident-evaluation-result.v1.schema.json @@ -0,0 +1,80 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://agent-semantic-protocols.dev/schemas/semantic-graph-resident-evaluation-result.v1.schema.json", + "title": "Resident graph evaluation result V1", + "type": "object", + "required": [ + "schemaId", "schemaVersion", "protocolId", "protocolVersion", + "packetKind", "state", "surface", "workspaceIdentity", + "generationDigest", "rootDigest", "profile", "entryNodeIds", + "rankedNodes", "edges", "workCounters" + ], + "properties": { + "schemaId": {"const": "agent.semantic-protocols.semantic-graph-resident-evaluation-result"}, + "schemaVersion": {"const": "1"}, + "protocolId": {"const": "agent.semantic-protocols.search"}, + "protocolVersion": {"const": "1"}, + "packetKind": {"const": "resident-graph-evaluation-result"}, + "state": {"const": "Ready"}, + "surface": { + "enum": ["search-playbook", "query"] + }, + "workspaceIdentity": {"type": "string", "minLength": 1}, + "generationDigest": {"type": "string", "pattern": "^blake3-256:[0-9a-f]{64}$"}, + "rootDigest": {"type": "string", "pattern": "^[0-9a-f]{64}$"}, + "profile": {"enum": ["balanced", "structural", "dependency"]}, + "entryNodeIds": { + "type": "array", "maxItems": 128, "uniqueItems": true, + "items": {"type": "string", "minLength": 1} + }, + "rankedNodes": { + "type": "array", "maxItems": 100, + "items": {"$ref": "#/$defs/rankedNode"} + }, + "edges": { + "type": "array", "maxItems": 1024, + "items": {"$ref": "#/$defs/edge"} + }, + "workCounters": {"$ref": "#/$defs/workCounters"} + }, + "$defs": { + "rankedNode": { + "type": "object", + "required": ["id", "kind", "score", "distance"], + "properties": { + "id": {"type": "string", "minLength": 1}, + "kind": {"type": "string", "minLength": 1}, + "ownerPath": {"type": ["string", "null"]}, + "score": {"type": "integer", "minimum": 0}, + "distance": {"type": "integer", "minimum": 0, "maximum": 16} + }, + "additionalProperties": false + }, + "edge": { + "type": "object", + "required": ["source", "target", "relation"], + "properties": { + "source": {"type": "string", "minLength": 1}, + "target": {"type": "string", "minLength": 1}, + "relation": {"type": "string", "minLength": 1} + }, + "additionalProperties": false + }, + "workCounters": { + "type": "object", + "required": [ + "graphNodesVisited", "graphEdgesVisited", "providerRpcCount", + "durableReadCount", "generationMutationCount" + ], + "properties": { + "graphNodesVisited": {"type": "integer", "minimum": 0, "maximum": 256}, + "graphEdgesVisited": {"type": "integer", "minimum": 0, "maximum": 1024}, + "providerRpcCount": {"const": 0}, + "durableReadCount": {"const": 0}, + "generationMutationCount": {"const": 0} + }, + "additionalProperties": false + } + }, + "additionalProperties": false +} diff --git a/schemas/semantic-graph-turbo-artifact-events.v1.schema.json b/schemas/semantic-graph-turbo-artifact-events.v1.schema.json new file mode 100644 index 0000000..fba61fd --- /dev/null +++ b/schemas/semantic-graph-turbo-artifact-events.v1.schema.json @@ -0,0 +1,118 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://agent-semantic-protocols.local/schemas/semantic-graph-turbo-artifact-events.v1.schema.json", + "title": "Semantic Graph Turbo Artifact Events", + "description": "Schema-owned event stream consumed by graph-turbo timeline audits. Rust DB Engine indexes can emit this packet so timeline analysis does not need to rescan artifact directories.", + "type": "object", + "additionalProperties": false, + "required": [ + "schemaId", + "schemaVersion", + "artifactDir", + "source", + "events" + ], + "properties": { + "schemaId": { + "const": "agent.semantic-protocols.graph-turbo-artifact-events" + }, + "schemaVersion": { + "const": "1" + }, + "artifactDir": { + "type": "string", + "minLength": 1 + }, + "source": { + "$ref": "#/$defs/source" + }, + "events": { + "type": "array", + "items": { + "$ref": "#/$defs/event" + } + } + }, + "$defs": { + "source": { + "type": "object", + "additionalProperties": false, + "required": [ + "kind", + "clientDir" + ], + "properties": { + "kind": { + "enum": [ + "artifact-scan", + "db-engine" + ] + }, + "clientDir": { + "type": "string" + } + } + }, + "event": { + "type": "object", + "additionalProperties": false, + "required": [ + "timestamp", + "kind", + "language", + "method", + "target", + "query", + "projectRoot", + "projectRootArg", + "path", + "bytes" + ], + "properties": { + "timestamp": { + "type": "number", + "minimum": 0 + }, + "kind": { + "enum": [ + "command", + "prompt-output", + "query", + "search", + "search-output", + "analysis-metadata", + "tree-sitter-query" + ] + }, + "language": { + "type": "string", + "minLength": 1 + }, + "method": { + "type": "string", + "minLength": 1 + }, + "target": { + "type": "string" + }, + "query": { + "type": "string" + }, + "projectRoot": { + "type": "string" + }, + "projectRootArg": { + "type": "string" + }, + "path": { + "type": "string", + "minLength": 1 + }, + "bytes": { + "type": "integer", + "minimum": 0 + } + } + } + } +} diff --git a/schemas/semantic-graph-turbo-definitions.v1.schema.json b/schemas/semantic-graph-turbo-definitions.v1.schema.json index 43b3578..54315e6 100644 --- a/schemas/semantic-graph-turbo-definitions.v1.schema.json +++ b/schemas/semantic-graph-turbo-definitions.v1.schema.json @@ -9,7 +9,6 @@ "owner-query", "query-deps", "owner-tests", - "prime", "read-frontier", "failure-frontier", "field-impact", diff --git a/schemas/semantic-graph-turbo-request.v1.schema.json b/schemas/semantic-graph-turbo-request.v1.schema.json index 049b297..0b335c8 100644 --- a/schemas/semantic-graph-turbo-request.v1.schema.json +++ b/schemas/semantic-graph-turbo-request.v1.schema.json @@ -2,7 +2,7 @@ "$schema": "https://json-schema.org/draft/2020-12/schema", "$id": "https://agent-semantic-protocols.local/schemas/semantic-graph-turbo-request.v1.schema.json", "title": "Semantic Graph Turbo Request", - "description": "Schema-owned algorithm request packet consumed by asp-graph-turbo before it emits a semantic-graph-turbo-result response.", + "description": "Schema-owned graph-turbo algorithm request packet consumed by the asp-python-graphs service before it emits a semantic-graph-turbo-result response.", "type": "object", "additionalProperties": false, "required": [ @@ -17,7 +17,7 @@ "queryTerms", "profile", "algorithm", - "seedIds", + "entryNodeIds", "budget" ], "anyOf": [ @@ -49,13 +49,10 @@ "const": "graph-turbo-request" }, "surface": { - "description": "ASP search surface that produced this graph-turbo request. This is the cache and receipt boundary for ranking semantics, not an agent-facing graph command.", + "description": "ASP public surface that produced this graph-turbo request. This is the cache and receipt boundary for ranking semantics, not an agent-facing graph command.", "enum": [ - "search-pipe", - "search-rg", - "search-fd", - "search-lexical", - "search-ingest", + "search-playbook", + "query", "evidence-analyze" ] }, @@ -119,7 +116,7 @@ "uniqueItems": true }, "source": { - "description": "Candidate source selection requested by search pipe. This selects candidate acquisition only; it is not an output projection.", + "description": "Candidate source selection chosen internally by the search playbook planner. This selects candidate acquisition only; it is not a public command or output projection.", "enum": [ "auto", "provider", @@ -152,7 +149,7 @@ "$ref": "#/$defs/sourceTraceEntry" } }, - "seedIds": { + "entryNodeIds": { "type": "array", "items": { "$ref": "#/$defs/nodeId" @@ -226,7 +223,7 @@ "uniqueItems": true }, "actionFrontier": { - "description": "Typed action facts projected from the search-pipe frontier. These facts are the materializer input for prompt-visible nextCommand text; they must not carry materialized shell strings or argv.", + "description": "Typed action facts projected from the Search Playbook frontier. These facts are the materializer input for prompt-visible next-action text; they must not carry materialized shell strings or argv.", "type": "array", "items": { "$ref": "#/$defs/actionFrontierEntry" @@ -380,7 +377,7 @@ "minLength": 1 }, "seedPlan": { - "description": "First-phase seed selection diagnostics for graph-turbo ranking. This explains why seedIds were selected before traversal and rendering.", + "description": "First-phase seed selection diagnostics for graph-turbo ranking. This explains why entryNodeIds were selected before traversal and rendering.", "type": "object", "additionalProperties": false, "required": [ @@ -395,7 +392,7 @@ "queryOwnerSeedCount", "fallbackOwnerSeedCount", "selectedSeedCount", - "seedIds", + "entryNodeIds", "riskFactors", "recommendedActions" ], @@ -447,7 +444,7 @@ "type": "integer", "minimum": 0 }, - "seedIds": { + "entryNodeIds": { "type": "array", "items": { "$ref": "#/$defs/nodeId" @@ -801,7 +798,7 @@ "properties": { "kind": { "enum": [ - "search-owner-items" + "query-owner-items" ] }, "languageId": { @@ -861,7 +858,7 @@ "treesitter-query", "multi-clause-rg-query", "scoped-rg-query", - "search-deps", + "dependency-query", "syntax" ] }, @@ -873,7 +870,7 @@ "rg", "owner-items", "treesitter-query", - "search-deps" + "query" ] }, "target": { diff --git a/schemas/semantic-graph.v1.schema.json b/schemas/semantic-graph.v1.schema.json index 508ef0b..7e25f8a 100644 --- a/schemas/semantic-graph.v1.schema.json +++ b/schemas/semantic-graph.v1.schema.json @@ -60,16 +60,14 @@ "scope": { "enum": [ "workspace", - "prime", + "search-playbook", "owner", "dependency", "policy", "query", "query-set", - "lexical", "text", "tests", - "ingest", "custom" ] }, @@ -241,15 +239,13 @@ "scope": { "enum": [ "workspace", - "prime", + "search-playbook", "owner", "dependency", "policy", "query", "query-set", - "lexical", "tests", - "ingest", "failure", "custom" ] @@ -314,10 +310,7 @@ } }, "seeds": { - "type": "array", - "items": { - "$ref": "#/$defs/nextAction" - } + "$ref": "#/$defs/nextActionList" }, "fields": { "$ref": "#/$defs/fields" @@ -368,10 +361,16 @@ } } }, + "nextActionList": { + "type": "array", + "items": { + "$ref": "#/$defs/nextAction" + } + }, "nextAction": { "type": "object", "additionalProperties": false, - "required": ["kind", "target"], + "required": ["kind", "target", "command"], "properties": { "kind": { "type": "string", @@ -388,6 +387,25 @@ "type": "string", "minLength": 1 }, + "command": { + "type": "object", + "additionalProperties": false, + "required": ["executable", "argv"], + "properties": { + "executable": { + "type": "string", + "minLength": 1 + }, + "argv": { + "type": "array", + "minItems": 1, + "items": { + "type": "string", + "minLength": 1 + } + } + } + }, "fields": { "$ref": "#/$defs/fields" } diff --git a/schemas/semantic-handle.v1.schema.json b/schemas/semantic-handle.v1.schema.json index 07f2e33..ce90fc9 100644 --- a/schemas/semantic-handle.v1.schema.json +++ b/schemas/semantic-handle.v1.schema.json @@ -47,7 +47,7 @@ "scope": { "enum": [ "workspace", - "prime", + "search-playbook", "owner", "policy", "schema", diff --git a/schemas/semantic-invariant-candidate.v1.schema.json b/schemas/semantic-invariant-candidate.v1.schema.json deleted file mode 100644 index a349d05..0000000 --- a/schemas/semantic-invariant-candidate.v1.schema.json +++ /dev/null @@ -1,225 +0,0 @@ -{ - "$schema": "https://json-schema.org/draft/2020-12/schema", - "$id": "https://agent-semantic-protocols.local/schemas/semantic-invariant-candidate.v1.schema.json", - "title": "Semantic Invariant Candidate", - "description": "Language-neutral candidate invariant raised from parser-owned findings before receipt, proof, or review evaluation.", - "type": "object", - "additionalProperties": false, - "required": [ - "schemaId", - "schemaVersion", - "candidates" - ], - "properties": { - "schemaId": { - "const": "agent.semantic-protocols.semantic-invariant-candidate" - }, - "schemaVersion": { - "const": "1" - }, - "candidates": { - "type": "array", - "items": { - "$ref": "#/$defs/invariantCandidate" - } - } - }, - "$defs": { - "scalar": { - "oneOf": [ - { - "type": "string" - }, - { - "type": "number" - }, - { - "type": "boolean" - }, - { - "type": "array", - "items": { - "oneOf": [ - { - "type": "string" - }, - { - "type": "number" - }, - { - "type": "boolean" - } - ] - } - } - ] - }, - "fields": { - "type": "object", - "additionalProperties": { - "$ref": "#/$defs/scalar" - } - }, - "projectPath": { - "type": "string", - "minLength": 1, - "pattern": "^(?:\\.|(?!/)(?![A-Za-z]:)(?![0-9]+:)(?!\\.\\.?($|/))(?!.*(^|/)\\.\\.?($|/))(?!.*//)[^\\s:\\\\]+(?:/[^\\s:\\\\]+)*)$" - }, - "location": { - "type": "object", - "additionalProperties": false, - "properties": { - "path": { - "$ref": "#/$defs/projectPath" - }, - "lineRange": { - "type": "string", - "description": "Compact source line range as start:end, for example 10:43.", - "pattern": "^[1-9][0-9]*:[1-9][0-9]*$" - } - } - }, - "severity": { - "enum": [ - "info", - "warning", - "error" - ] - }, - "status": { - "enum": [ - "candidate", - "accepted", - "verified", - "waived", - "stale" - ] - }, - "invariantKind": { - "enum": [ - "primitive-identifier-boundary", - "public-data-primitive-fields", - "anonymous-tuple-api-surface", - "primitive-type-alias-boundary", - "stringly-state-boundary", - "parser-fact", - "public-api-shape", - "module-reasoning-tree", - "dependency-graph-acyclicity", - "custom" - ] - }, - "receiptKind": { - "enum": [ - "cargo-check", - "cargo-test", - "clippy", - "expect-test", - "proptest", - "cargo-fuzz", - "kani", - "creusot", - "verus", - "waiver" - ] - }, - "evidence": { - "type": "object", - "additionalProperties": false, - "required": [ - "kind", - "summary" - ], - "properties": { - "kind": { - "type": "string", - "pattern": "^[a-z][a-z0-9_-]*$" - }, - "summary": { - "type": "string", - "minLength": 1 - }, - "location": { - "$ref": "#/$defs/location" - }, - "fields": { - "$ref": "#/$defs/fields" - } - } - }, - "invariantCandidate": { - "type": "object", - "additionalProperties": false, - "required": [ - "invariantId", - "sourceRuleId", - "rulePackId", - "kind", - "status", - "severity", - "title", - "hypothesis", - "location", - "evidence", - "requiredReceipts" - ], - "properties": { - "invariantId": { - "type": "string", - "pattern": "^[a-z][a-z0-9_.:-]*$" - }, - "sourceRuleId": { - "type": "string", - "minLength": 1 - }, - "rulePackId": { - "type": "string", - "minLength": 1 - }, - "kind": { - "$ref": "#/$defs/invariantKind" - }, - "status": { - "$ref": "#/$defs/status" - }, - "severity": { - "$ref": "#/$defs/severity" - }, - "title": { - "type": "string", - "minLength": 1 - }, - "hypothesis": { - "type": "string", - "minLength": 1 - }, - "location": { - "$ref": "#/$defs/location" - }, - "evidence": { - "type": "array", - "minItems": 1, - "items": { - "$ref": "#/$defs/evidence" - } - }, - "requiredReceipts": { - "type": "array", - "items": { - "$ref": "#/$defs/receiptKind" - } - }, - "proofTargets": { - "type": "array", - "uniqueItems": true, - "items": { - "$ref": "#/$defs/invariantKind" - } - }, - "fields": { - "$ref": "#/$defs/fields" - } - } - } - } -} diff --git a/schemas/semantic-language-projection.v1.schema.json b/schemas/semantic-language-projection.v1.schema.json index f78cd80..70671eb 100644 --- a/schemas/semantic-language-projection.v1.schema.json +++ b/schemas/semantic-language-projection.v1.schema.json @@ -2,7 +2,7 @@ "$schema": "https://json-schema.org/draft/2020-12/schema", "$id": "https://agent-semantic-protocols.local/schemas/semantic-language-projection.v1.schema.json", "title": "Semantic Language Projection", - "description": "Query-free, parser-owned projection artifact published by a language harness for ASP lifecycle import.", + "description": "Query-free, parser-owned projection artifact published by an ASP language provider for ASP lifecycle import.", "type": "object", "additionalProperties": false, "required": [ diff --git a/schemas/semantic-language-registry.v1.schema.json b/schemas/semantic-language-registry.v1.schema.json index 899905d..2020c0c 100644 --- a/schemas/semantic-language-registry.v1.schema.json +++ b/schemas/semantic-language-registry.v1.schema.json @@ -44,18 +44,14 @@ }, "method": { "type": "string", - "pattern": "^(?:guide|query|(search|query|check|proof|review|verification|evidence|ast-patch|agent)/[a-z][a-z0-9_-]*)$" + "pattern": "^(?:guide|query|(query|proof|review|ast-patch|agent)/[a-z][a-z0-9_-]*)$" }, "command": { "enum": [ "guide", - "search", "query", - "check", "proof", "review", - "verification", - "evidence", "ast-patch", "agent" ] @@ -249,36 +245,6 @@ "type": "boolean" } }, - "allOf": [ - { - "if": { - "properties": { - "argumentProjection": { - "properties": { - "tokens": { - "contains": { - "type": "object", - "required": ["kind", "name"], - "properties": { - "kind": {"const": "slot"}, - "name": {"const": "owner"} - } - } - } - } - } - }, - "required": ["argumentProjection"] - }, - "then": { - "properties": { - "method": { - "pattern": "^search/owner(?:$|-)" - } - } - } - } - ], "additionalProperties": true } } diff --git a/schemas/semantic-search-definitions.v1.schema.json b/schemas/semantic-search-definitions.v1.schema.json new file mode 100644 index 0000000..ebb2af6 --- /dev/null +++ b/schemas/semantic-search-definitions.v1.schema.json @@ -0,0 +1,101 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://schemas.agent-semantic-protocols.dev/semantic-search-definitions.v1.schema.json", + "title": "Semantic Search Family Definitions V1", + "description": "Shared definitions owned by the semantic-search schema family.", + "$defs": { + "digest": { + "type": "string", + "pattern": "^blake3-256:[0-9a-f]{64}$" + }, + "generationIdentity": { + "type": "object", + "additionalProperties": false, + "required": [ + "projectId", + "workspaceId", + "sourceRootDigest", + "providerDigest", + "schemaDigest", + "generationCandidateDigest" + ], + "properties": { + "projectId": { + "type": "string", + "minLength": 1 + }, + "workspaceId": { + "type": "string", + "minLength": 1 + }, + "sourceRootDigest": { + "$ref": "#/$defs/digest" + }, + "providerDigest": { + "$ref": "#/$defs/digest" + }, + "schemaDigest": { + "$ref": "#/$defs/digest" + }, + "generationCandidateDigest": { + "$ref": "#/$defs/digest" + } + } + }, + "nanos": { + "type": "integer", + "minimum": 0 + }, + "performanceDistribution": { + "type": "object", + "required": [ + "samples", + "p50Nanos", + "p95Nanos", + "p99Nanos", + "maxNanos" + ], + "properties": { + "samples": { + "type": "integer", + "minimum": 1 + }, + "p50Nanos": { + "$ref": "#/$defs/nanos" + }, + "p95Nanos": { + "$ref": "#/$defs/nanos" + }, + "p99Nanos": { + "$ref": "#/$defs/nanos" + }, + "maxNanos": { + "$ref": "#/$defs/nanos" + } + } + }, + "lineRange": { + "type": "array", + "minItems": 2, + "maxItems": 2, + "prefixItems": [ + { + "type": "integer", + "minimum": 1 + }, + { + "type": "integer", + "minimum": 1 + } + ], + "items": false + }, + "lineRanges": { + "type": "array", + "minItems": 1, + "items": { + "$ref": "#/$defs/lineRange" + } + } + } +} diff --git a/schemas/semantic-search-packet.v1.schema.json b/schemas/semantic-search-packet.v1.schema.json deleted file mode 100644 index 30a4f88..0000000 --- a/schemas/semantic-search-packet.v1.schema.json +++ /dev/null @@ -1,2803 +0,0 @@ -{ - "$schema": "https://json-schema.org/draft/2020-12/schema", - "$id": "https://agent-semantic-protocols.local/schemas/semantic-search-packet.v1.schema.json", - "title": "Semantic Search Packet", - "description": "Language-neutral bounded semantic search packet emitted by semantic language providers.", - "type": "object", - "additionalProperties": false, - "required": [ - "schemaId", - "schemaVersion", - "protocolId", - "protocolVersion", - "languageId", - "providerId", - "binary", - "namespace", - "method", - "projectRoot", - "view", - "renderMode", - "header", - "nodes", - "edges", - "owners", - "hits", - "findings", - "nextActions", - "notes" - ], - "properties": { - "schemaId": { - "const": "agent.semantic-protocols.semantic-search-packet" - }, - "schemaVersion": { - "const": "1" - }, - "protocolId": { - "const": "agent.semantic-protocols.semantic-language" - }, - "protocolVersion": { - "const": "1" - }, - "languageId": { - "type": "string", - "pattern": "^[a-z][a-z0-9_-]*$" - }, - "providerId": { - "type": "string", - "pattern": "^[a-z][a-z0-9_-]*$" - }, - "binary": { - "type": "string", - "pattern": "^[a-z][a-z0-9_-]*$" - }, - "namespace": { - "type": "string", - "pattern": "^[a-z][a-z0-9_-]*(\\.[a-z][a-z0-9_-]*)+$" - }, - "method": { - "type": "string", - "pattern": "^search/[a-z][a-z0-9_-]*$" - }, - "projectRoot": { - "type": "string", - "minLength": 1 - }, - "packageName": { - "type": "string", - "minLength": 1 - }, - "projectPackage": { - "$ref": "#/$defs/projectPackage" - }, - "extensions": { - "type": "array", - "items": { - "$ref": "#/$defs/providerExtension" - } - }, - "view": { - "enum": [ - "workspace", - "prime", - "owner", - "dependency", - "deps", - "features", - "targets", - "symbol", - "callsite", - "import", - "query", - "cfg", - "patterns", - "pattern", - "docs", - "api", - "public-external-types", - "policy", - "tests", - "lexical", - "reasoning", - "env", - "runtime-source", - "compiler-evidence", - "lang", - "std", - "proof", - "capability", - "extension", - "compare", - "ingest", - "failure" - ] - }, - "renderMode": { - "enum": [ - "graph", - "hits", - "both", - "seeds", - "facts" - ] - }, - "query": { - "type": "string" - }, - "querySet": { - "type": "array", - "minItems": 1, - "items": { - "$ref": "#/$defs/queryTerm" - } - }, - "queryComposition": { - "$ref": "#/$defs/queryComposition" - }, - "queryCoverage": { - "type": "array", - "items": { - "$ref": "#/$defs/queryCoverage" - } - }, - "ownerResolution": { - "type": "array", - "items": { - "$ref": "#/$defs/ownerResolution" - } - }, - "sourceCoverage": { - "type": "array", - "items": { - "$ref": "#/$defs/sourceCoverage" - } - }, - "testResolution": { - "type": "array", - "items": { - "$ref": "#/$defs/testResolution" - } - }, - "runtimeCost": { - "$ref": "#/$defs/runtimeCost" - }, - "cache": { - "$ref": "#/$defs/cache" - }, - "searchSynthesis": { - "$ref": "#/$defs/searchSynthesis" - }, - "routeGraph": { - "$ref": "#/$defs/routeGraph" - }, - "actionFrontier": { - "type": "array", - "items": { - "$ref": "#/$defs/routeAction" - } - }, - "finder": { - "$ref": "#/$defs/finder" - }, - "noOutput": { - "$ref": "#/$defs/noOutput" - }, - "avoidNextActions": { - "$ref": "#/$defs/avoidNextActionList" - }, - "header": { - "$ref": "#/$defs/header" - }, - "inputDetection": { - "$ref": "#/$defs/inputDetection" - }, - "packages": { - "type": "array", - "items": { - "$ref": "#/$defs/fact" - } - }, - "nodes": { - "type": "array", - "items": { - "$ref": "#/$defs/node" - } - }, - "edges": { - "type": "array", - "items": { - "$ref": "#/$defs/edge" - } - }, - "owners": { - "type": "array", - "items": { - "$ref": "#/$defs/owner" - } - }, - "items": { - "type": "array", - "items": { - "$ref": "#/$defs/item" - } - }, - "typeSurfaces": { - "type": "array", - "items": { - "$ref": "semantic-type-surface.v1.schema.json#/$defs/typeSurface" - } - }, - "invariantCandidates": { - "type": "array", - "items": { - "$ref": "semantic-invariant-candidate.v1.schema.json#/$defs/invariantCandidate" - } - }, - "semanticHandles": { - "type": "array", - "items": { - "$ref": "semantic-handle.v1.schema.json#/$defs/semanticHandle" - } - }, - "syntaxQueryRef": { - "$ref": "semantic-tree-sitter-provenance.v1.schema.json#/$defs/syntaxQueryRef" - }, - "syntaxMatchRefs": { - "$ref": "semantic-tree-sitter-provenance.v1.schema.json#/$defs/syntaxMatchRefs" - }, - "syntaxCaptureRefs": { - "$ref": "semantic-tree-sitter-provenance.v1.schema.json#/$defs/syntaxCaptureRefs" - }, - "syntaxAnchor": { - "$ref": "semantic-tree-sitter-provenance.v1.schema.json#/$defs/syntaxAnchor" - }, - "reasoningProfiles": { - "type": "array", - "items": { - "$ref": "#/$defs/reasoningProfile" - } - }, - "nativeSyntaxFacts": { - "type": "array", - "items": { - "$ref": "semantic-native-syntax-fact-index.v1.schema.json#/$defs/nativeSyntaxFact" - } - }, - "hits": { - "type": "array", - "items": { - "$ref": "#/$defs/hit" - } - }, - "findings": { - "type": "array", - "items": { - "$ref": "#/$defs/finding" - } - }, - "nextActions": { - "$ref": "#/$defs/nextActionList" - }, - "delegationHints": { - "type": "array", - "items": { - "$ref": "#/$defs/delegationHint" - } - }, - "notes": { - "type": "array", - "items": { - "$ref": "#/$defs/note" - } - } - }, - "$defs": { - "nextActionList": { - "type": "array", - "items": { - "$ref": "#/$defs/nextAction" - } - }, - "avoidNextActionList": { - "type": "array", - "items": { - "$ref": "#/$defs/avoidNextAction" - } - }, - "scalar": { - "oneOf": [ - { - "type": "string" - }, - { - "type": "number" - }, - { - "type": "boolean" - }, - { - "type": "array", - "items": { - "oneOf": [ - { - "type": "string" - }, - { - "type": "number" - }, - { - "type": "boolean" - } - ] - } - } - ] - }, - "stringList": { - "type": "array", - "items": { - "type": "string" - } - }, - "fields": { - "type": "object", - "properties": { - "requestedVersion": { - "type": "string", - "minLength": 1 - }, - "currentWorkspaceVersion": { - "type": "string", - "minLength": 1 - }, - "apiQuery": { - "type": "string", - "minLength": 1 - }, - "subpath": { - "type": "string", - "minLength": 1 - }, - "versionScope": { - "enum": [ - "current", - "external", - "unknown" - ] - } - }, - "additionalProperties": { - "$ref": "#/$defs/scalar" - } - }, - "finder": { - "type": "object", - "description": "Provider-owned finder pipeline provenance for lexical/fuzzy search surfaces such as search lexical. This records normalized agent-requested finder options; it is not raw shell argv.", - "additionalProperties": false, - "required": [ - "engine", - "surface", - "options", - "acceptedArgs", - "rejectedArgs" - ], - "properties": { - "engine": { - "enum": [ - "lexical", - "rg", - "rg-lexical", - "provider" - ] - }, - "surface": { - "enum": [ - "search-lexical", - "search-ingest", - "search-pattern", - "search-custom" - ] - }, - "pipelineId": { - "type": "string", - "minLength": 1 - }, - "options": { - "$ref": "#/$defs/finderOptions" - }, - "acceptedArgs": { - "type": "array", - "items": { - "type": "string", - "minLength": 1 - } - }, - "rejectedArgs": { - "type": "array", - "items": { - "$ref": "#/$defs/finderRejectedArg" - } - }, - "fields": { - "$ref": "#/$defs/fields" - } - } - }, - "finderOptions": { - "type": "object", - "description": "Normalized, provider-approved finder options that can be mapped to headless finder behavior across language providers.", - "additionalProperties": false, - "properties": { - "matchMode": { - "enum": [ - "fuzzy", - "exact" - ] - }, - "caseMode": { - "enum": [ - "smart", - "ignore", - "respect" - ] - }, - "nth": { - "type": "string", - "minLength": 1 - }, - "delimiter": { - "type": "string", - "minLength": 1 - }, - "tiebreak": { - "type": "array", - "uniqueItems": true, - "items": { - "enum": [ - "score", - "begin", - "end", - "index", - "length", - "chunk", - "pathname" - ] - } - }, - "scheme": { - "enum": [ - "default", - "path", - "history" - ] - }, - "nativeArgs": { - "type": "array", - "description": "The provider-normalized lexical-native argument tokens accepted for this headless run.", - "items": { - "type": "string", - "minLength": 1 - } - } - } - }, - "finderRejectedArg": { - "type": "object", - "additionalProperties": false, - "required": [ - "value", - "reason" - ], - "properties": { - "value": { - "type": "string", - "minLength": 1 - }, - "reason": { - "type": "string", - "minLength": 1 - } - } - }, - "surfaceKind": { - "enum": [ - "real-source", - "test-source", - "test-fixture-string", - "generated-source", - "external-source", - "unknown" - ] - }, - "targetRole": { - "enum": [ - "path", - "range", - "symbol", - "term", - "pkg", - "dep", - "test", - "finding", - "failure", - "hot", - "evidence", - "result", - "self", - "feature", - "cfg", - "value", - "custom" - ] - }, - "projectPath": { - "$ref": "semantic-source-location.v1.schema.json#/$defs/projectPath" - }, - "projectPackage": { - "type": "object", - "description": "Provider-owned package manifest summary for the active project root.", - "additionalProperties": false, - "required": [ - "path", - "name" - ], - "properties": { - "path": { - "$ref": "#/$defs/projectPath" - }, - "name": { - "type": "string", - "minLength": 1 - }, - "dependencies": { - "type": "array", - "items": { - "type": "string", - "minLength": 1 - } - }, - "fields": { - "$ref": "#/$defs/fields" - } - } - }, - "providerExtension": { - "type": "object", - "description": "Provider-owned optional package extension capability surfaced through the shared search packet envelope.", - "additionalProperties": false, - "required": [ - "name", - "activation", - "capabilities" - ], - "properties": { - "name": { - "type": "string", - "minLength": 1 - }, - "activation": { - "type": "string", - "minLength": 1 - }, - "dependencyMode": { - "description": "How the package manager relation activates this provider extension.", - "enum": [ - "required", - "optional", - "implicit", - "unknown" - ] - }, - "packageManager": { - "type": "string", - "minLength": 1 - }, - "package": { - "type": "string", - "minLength": 1 - }, - "dependencies": { - "type": "array", - "items": { - "type": "string", - "minLength": 1 - } - }, - "capabilities": { - "type": "array", - "minItems": 1, - "items": { - "type": "string", - "minLength": 1 - } - }, - "fields": { - "$ref": "#/$defs/fields" - } - } - }, - "windowTarget": { - "description": "A window-set target is either a canonical project path or a graph frontier token such as feature:cli, cfg:unix, or import:serde. It is not a display locator or rank-prefixed path.", - "anyOf": [ - { - "$ref": "#/$defs/projectPath" - }, - { - "type": "string", - "minLength": 1, - "pattern": "^[a-z][a-z0-9_-]*:[^\\s:\\\\][^\\s\\\\]*$" - } - ] - }, - "queryTerm": { - "type": "object", - "additionalProperties": false, - "required": [ - "value", - "kind", - "selector" - ], - "allOf": [ - { - "if": { - "properties": { - "kind": { - "enum": [ - "owner", - "path" - ] - } - }, - "required": [ - "kind" - ] - }, - "then": { - "properties": { - "value": { - "$ref": "#/$defs/projectPath" - } - } - } - } - ], - "properties": { - "value": { - "type": "string", - "minLength": 1 - }, - "kind": { - "enum": [ - "dependency", - "owner", - "path", - "symbol", - "text", - "feature", - "cfg", - "api", - "custom" - ] - }, - "selector": { - "enum": [ - "exact", - "prefix", - "fuzzy", - "stdin-path" - ] - }, - "fields": { - "$ref": "#/$defs/fields" - } - } - }, - "queryComposition": { - "type": "object", - "additionalProperties": false, - "required": [ - "mode", - "view", - "selector", - "merge" - ], - "properties": { - "mode": { - "enum": [ - "single", - "query-set" - ] - }, - "view": { - "type": "string", - "pattern": "^[a-z][a-z0-9_-]*$" - }, - "selector": { - "enum": [ - "single", - "exact-set", - "prefix-set", - "lexical-set", - "stdin-path-set" - ] - }, - "scope": { - "$ref": "#/$defs/queryScope" - }, - "clauses": { - "type": "array", - "description": "Provider-planned semantic query clauses. Explicit query-set clauses and provider-synthesized clauses share this shape.", - "items": { - "type": "object", - "additionalProperties": false, - "required": [ - "id", - "terms" - ], - "properties": { - "id": { - "type": "integer", - "minimum": 1 - }, - "role": { - "enum": [ - "path", - "package", - "symbol", - "concept", - "context" - ] - }, - "terms": { - "$ref": "#/$defs/stringList" - } - } - } - }, - "termRoles": { - "type": "object", - "description": "Normalized query term role map used by clause planning and action ranking.", - "additionalProperties": { - "enum": [ - "context", - "concept", - "symbol" - ] - } - }, - "driftSuppression": { - "type": "object", - "description": "Search-pipe safety decision that prevents weak or cross-package evidence from selecting code reads.", - "additionalProperties": false, - "properties": { - "packageCohesion": { - "enum": [ - "low", - "medium", - "high" - ] - }, - "weakTerms": { - "$ref": "#/$defs/stringList" - }, - "risks": { - "$ref": "#/$defs/stringList" - }, - "selectorReadsAllowed": { - "type": "boolean" - }, - "reason": { - "type": "string" - } - } - }, - "merge": { - "type": "array", - "minItems": 1, - "uniqueItems": true, - "items": { - "enum": [ - "packages", - "nodes", - "edges", - "owners", - "items", - "typeSurfaces", - "invariantCandidates", - "nativeSyntaxFacts", - "hits", - "findings", - "nextActions", - "delegationHints", - "notes" - ] - } - }, - "fields": { - "$ref": "#/$defs/fields" - } - } - }, - "queryCoverage": { - "type": "object", - "additionalProperties": false, - "required": [ - "value", - "kind", - "status", - "hitCount" - ], - "properties": { - "value": { - "type": "string", - "minLength": 1 - }, - "kind": { - "enum": [ - "dependency", - "owner", - "path", - "symbol", - "text", - "feature", - "cfg", - "api", - "custom" - ] - }, - "selector": { - "enum": [ - "exact", - "prefix", - "fuzzy", - "stdin-path" - ] - }, - "status": { - "enum": [ - "hit", - "miss", - "partial", - "error" - ] - }, - "hitCount": { - "type": "integer", - "minimum": 0 - }, - "surfaces": { - "type": "array", - "uniqueItems": true, - "items": { - "$ref": "#/$defs/surfaceKind" - } - }, - "ownerPaths": { - "type": "array", - "items": { - "$ref": "#/$defs/projectPath" - } - }, - "fixturePaths": { - "type": "array", - "items": { - "$ref": "#/$defs/projectPath" - } - }, - "fields": { - "$ref": "#/$defs/fields" - } - } - }, - "ownerResolution": { - "type": "object", - "additionalProperties": false, - "required": [ - "target", - "status", - "realOwner" - ], - "properties": { - "target": { - "$ref": "#/$defs/projectPath" - }, - "status": { - "enum": [ - "workspace-owner", - "fixture-path", - "missing", - "ambiguous", - "external", - "unknown" - ] - }, - "realOwner": { - "type": "boolean" - }, - "ownerPath": { - "$ref": "#/$defs/projectPath" - }, - "fixturePath": { - "$ref": "#/$defs/projectPath" - }, - "fixtureOwner": { - "$ref": "#/$defs/projectPath" - }, - "reason": { - "type": "string", - "minLength": 1 - }, - "fields": { - "$ref": "#/$defs/fields" - } - } - }, - "sourceCoverage": { - "type": "object", - "additionalProperties": false, - "required": [ - "scope", - "status" - ], - "properties": { - "scope": { - "$ref": "#/$defs/queryScope" - }, - "status": { - "enum": [ - "complete", - "partial", - "missing", - "unknown" - ] - }, - "coverageKind": { - "enum": [ - "parser-source", - "config-root", - "workspace-package", - "generated", - "unknown" - ] - }, - "configPaths": { - "type": "array", - "items": { - "$ref": "#/$defs/projectPath" - } - }, - "sourceRoots": { - "type": "array", - "items": { - "$ref": "#/$defs/projectPath" - } - }, - "coveredRoots": { - "type": "array", - "items": { - "$ref": "#/$defs/projectPath" - } - }, - "missingRoots": { - "type": "array", - "items": { - "$ref": "#/$defs/projectPath" - } - }, - "coveredOwners": { - "type": "array", - "items": { - "$ref": "#/$defs/projectPath" - } - }, - "missingOwners": { - "type": "array", - "items": { - "$ref": "#/$defs/projectPath" - } - }, - "sourceFiles": { - "type": "integer", - "minimum": 0 - }, - "visibleOwners": { - "type": "integer", - "minimum": 0 - }, - "missingOwnersCount": { - "type": "integer", - "minimum": 0 - }, - "reason": { - "type": "string", - "minLength": 1 - }, - "fields": { - "$ref": "#/$defs/fields" - } - } - }, - "sourceTrace": { - "type": "object", - "additionalProperties": false, - "required": [ - "source", - "status" - ], - "properties": { - "source": { - "type": "string", - "pattern": "^[a-z][a-z0-9_-]*$" - }, - "status": { - "enum": [ - "empty", - "used", - "partial", - "skipped", - "unknown" - ] - }, - "candidateCount": { - "type": "integer", - "minimum": 0 - }, - "fields": { - "$ref": "#/$defs/fields" - } - } - }, - "testResolution": { - "type": "object", - "additionalProperties": false, - "required": [ - "targetOwner", - "status" - ], - "properties": { - "targetOwner": { - "$ref": "#/$defs/projectPath" - }, - "status": { - "enum": [ - "linked", - "missing", - "partial", - "noisy", - "ambiguous", - "unsupported", - "unknown" - ] - }, - "scope": { - "$ref": "#/$defs/queryScope" - }, - "testPaths": { - "type": "array", - "items": { - "$ref": "#/$defs/projectPath" - } - }, - "candidateTestPaths": { - "type": "array", - "items": { - "$ref": "#/$defs/projectPath" - } - }, - "unrelatedTestPaths": { - "type": "array", - "items": { - "$ref": "#/$defs/projectPath" - } - }, - "candidateCount": { - "type": "integer", - "minimum": 0 - }, - "selectedCount": { - "type": "integer", - "minimum": 0 - }, - "noiseCount": { - "type": "integer", - "minimum": 0 - }, - "reason": { - "type": "string", - "minLength": 1 - }, - "fields": { - "$ref": "#/$defs/fields" - } - } - }, - "searchSynthesis": { - "type": "object", - "additionalProperties": false, - "required": [ - "algorithm", - "scope" - ], - "properties": { - "algorithm": { - "type": "string", - "pattern": "^[a-z][a-z0-9_-]*$" - }, - "scope": { - "enum": [ - "workspace", - "prime", - "owner", - "dependency", - "policy", - "query", - "query-set", - "lexical", - "tests", - "ingest", - "failure", - "custom" - ] - }, - "summary": { - "type": "string", - "minLength": 1 - }, - "ownerPath": { - "$ref": "#/$defs/projectPath" - }, - "selectedOwners": { - "type": "integer", - "minimum": 0 - }, - "selectedEdges": { - "type": "integer", - "minimum": 0 - }, - "incomingOwners": { - "type": "integer", - "minimum": 0 - }, - "outgoingOwners": { - "type": "integer", - "minimum": 0 - }, - "highImpactOwners": { - "type": "array", - "items": { - "$ref": "#/$defs/projectPath" - } - }, - "frontierOwners": { - "type": "array", - "items": { - "$ref": "#/$defs/projectPath" - } - }, - "editFrontier": { - "type": "array", - "items": { - "$ref": "#/$defs/projectPath" - } - }, - "testFrontier": { - "type": "array", - "items": { - "$ref": "#/$defs/projectPath" - } - }, - "windowSet": { - "type": "array", - "items": { - "$ref": "#/$defs/windowSetTarget" - } - }, - "findingOwners": { - "type": "array", - "items": { - "$ref": "#/$defs/projectPath" - } - }, - "seeds": { - "$ref": "#/$defs/nextActionList" - }, - "fields": { - "$ref": "#/$defs/fields" - } - } - }, - "windowSetTarget": { - "type": "object", - "additionalProperties": false, - "required": [ - "kind", - "target" - ], - "properties": { - "kind": { - "enum": [ - "owner", - "tests", - "read", - "query", - "lexical", - "deps", - "dependency", - "docs", - "docs-use", - "crate-source", - "features", - "cfg", - "import", - "items", - "code", - "symbol", - "finding", - "custom" - ] - }, - "target": { - "$ref": "#/$defs/windowTarget" - }, - "query": { - "type": "string", - "minLength": 1 - }, - "reason": { - "type": "string", - "minLength": 1 - }, - "ownerPath": { - "$ref": "#/$defs/projectPath" - }, - "read": { - "type": "string", - "description": "Compatibility source transport locator for exact graph/code frontier actions, formatted as project/path:start:end. This is not the semantic selector identity for syntax exploration.", - "pattern": "^(?:\\.|(?!/)(?![A-Za-z]:)(?![0-9]+:)(?!\\.\\.?($|/))(?!.*(^|/)\\.\\.?($|/))(?!.*//)[^\\s:\\\\]+(?:/[^\\s:\\\\]+)*):[1-9][0-9]*:[1-9][0-9]*$" - }, - "structuralSelector": { - "$ref": "semantic-source-location.v1.schema.json#/$defs/structuralSelector" - }, - "displayLineRange": { - "$ref": "semantic-source-location.v1.schema.json#/$defs/lineRange" - }, - "sourceLocatorHint": { - "$ref": "semantic-source-location.v1.schema.json#/$defs/sourceLocator" - }, - "projection": { - "$ref": "semantic-source-location.v1.schema.json#/$defs/projection" - }, - "codePolicy": { - "$ref": "semantic-source-location.v1.schema.json#/$defs/codePolicy" - }, - "fields": { - "$ref": "#/$defs/fields" - } - } - }, - "avoidNextAction": { - "type": "object", - "additionalProperties": false, - "required": [ - "kind", - "target", - "reason" - ], - "properties": { - "kind": { - "type": "string", - "pattern": "^[a-z][a-z0-9_-]*$" - }, - "target": { - "type": "string", - "minLength": 1 - }, - "reason": { - "type": "string", - "minLength": 1 - }, - "ownerPath": { - "$ref": "#/$defs/projectPath" - }, - "read": { - "type": "string", - "description": "Canonical source read locator for graph/code frontier actions, formatted as project/path:start:end.", - "pattern": "^(?:\\.|(?!/)(?![A-Za-z]:)(?![0-9]+:)(?!\\.\\.?($|/))(?!.*(^|/)\\.\\.?($|/))(?!.*//)[^\\s:\\\\]+(?:/[^\\s:\\\\]+)*):[1-9][0-9]*:[1-9][0-9]*$" - }, - "fields": { - "$ref": "#/$defs/fields" - } - } - }, - "noOutput": { - "type": "object", - "description": "Compact no-candidate receipt for prompt-facing search views.", - "additionalProperties": false, - "required": [ - "reason", - "sourceTrace", - "nextActions", - "avoidNextActions" - ], - "properties": { - "reason": { - "enum": [ - "no-candidates", - "query-too-broad" - ] - }, - "sourceTrace": { - "type": "array", - "items": { - "$ref": "#/$defs/sourceTrace" - } - }, - "nextActions": { - "type": "array", - "minItems": 1, - "items": { - "$ref": "#/$defs/nextAction" - } - }, - "avoidNextActions": { - "$ref": "#/$defs/avoidNextActionList" - }, - "queryBudget": { - "$ref": "#/$defs/queryBudget" - } - } - }, - "queryBudget": { - "type": "object", - "description": "Shared wrapper admission receipt for queries rejected before backend execution.", - "additionalProperties": false, - "required": [ - "blocked", - "reason", - "termCount", - "genericTerms", - "refineHint" - ], - "properties": { - "blocked": { - "type": "boolean", - "const": true - }, - "reason": { - "enum": [ - "query-too-broad" - ] - }, - "termCount": { - "type": "integer", - "minimum": 1 - }, - "genericTerms": { - "$ref": "#/$defs/stringList" - }, - "specificTerms": { - "$ref": "#/$defs/stringList" - }, - "refineHint": { - "type": "string", - "minLength": 1 - }, - "exampleCommand": { - "type": "string", - "minLength": 1 - } - } - }, - "runtimeCost": { - "type": "object", - "additionalProperties": false, - "required": [ - "cacheStatus" - ], - "properties": { - "cacheStatus": { - "enum": [ - "cold", - "warm", - "reused", - "disabled", - "unknown" - ] - }, - "elapsedMs": { - "type": "integer", - "minimum": 0 - }, - "parseMs": { - "type": "integer", - "minimum": 0 - }, - "sourceFilesParsed": { - "type": "integer", - "minimum": 0 - }, - "packagesScanned": { - "type": "integer", - "minimum": 0 - }, - "parserFactsReused": { - "type": "boolean" - }, - "indexId": { - "type": "string", - "minLength": 1 - }, - "reason": { - "type": "string", - "minLength": 1 - }, - "fields": { - "$ref": "#/$defs/fields" - } - } - }, - "cache": { - "type": "object", - "description": "Provider-owned cache invalidation facts for replay-safe search packets. Providers decide which workspace files affect the packet; clients only validate and compare these hashes.", - "additionalProperties": false, - "required": [ - "fileHashes" - ], - "properties": { - "fileHashes": { - "type": "array", - "minItems": 1, - "items": { - "$ref": "#/$defs/fileHash" - } - }, - "rawSourceStored": { - "const": false - } - } - }, - "fileHash": { - "type": "object", - "additionalProperties": false, - "required": [ - "path", - "sha256" - ], - "properties": { - "path": { - "$ref": "#/$defs/projectPath" - }, - "sha256": { - "type": "string", - "pattern": "^[a-f0-9]{64}$" - } - } - }, - "queryScope": { - "type": "object", - "additionalProperties": false, - "properties": { - "projectRoot": { - "type": "string", - "minLength": 1 - }, - "packageName": { - "type": "string", - "minLength": 1 - }, - "ownerPath": { - "$ref": "#/$defs/projectPath" - }, - "roots": { - "type": "array", - "items": { - "$ref": "#/$defs/projectPath" - } - } - } - }, - "location": { - "$ref": "semantic-source-location.v1.schema.json#/$defs/location" - }, - "header": { - "type": "object", - "additionalProperties": false, - "required": [ - "kind", - "fields" - ], - "properties": { - "kind": { - "type": "string", - "pattern": "^search-[a-z0-9_-]+$" - }, - "fields": { - "$ref": "#/$defs/fields" - } - } - }, - "inputDetection": { - "type": "object", - "additionalProperties": false, - "required": [ - "source", - "lineCount", - "byteCount" - ], - "properties": { - "source": { - "enum": [ - "rg-n", - "vimgrep", - "rg-json", - "path-list", - "path-list-nul", - "diff-paths", - "unknown" - ] - }, - "lineCount": { - "type": "integer", - "minimum": 0 - }, - "byteCount": { - "type": "integer", - "minimum": 0 - }, - "sample": { - "type": "string" - }, - "unsupportedLanguage": { - "$ref": "#/$defs/unsupportedLanguageDetection", - "description": "Advisory emitted when a user requested a language facade that is not active or not a protocol language id. It is a fail-closed routing hint, not provider evidence." - } - } - }, - "unsupportedLanguageDetection": { - "type": "object", - "additionalProperties": false, - "required": [ - "requestedFacade", - "activeFacades", - "knownFacades", - "recoveryCommands" - ], - "properties": { - "requestedFacade": { - "type": "string", - "minLength": 1 - }, - "activeFacades": { - "type": "array", - "items": { - "type": "string", - "minLength": 1 - }, - "uniqueItems": true - }, - "knownFacades": { - "type": "array", - "items": { - "type": "string", - "minLength": 1 - }, - "uniqueItems": true - }, - "suggestedFacade": { - "type": "string", - "minLength": 1, - "description": "Present only when the requested facade has a protocol-owned or configuration-owned explicit mapping to one active language facade. Do not derive this from substrings, suffixes, or hyphen components." - }, - "recoveryCommands": { - "type": "array", - "minItems": 1, - "items": { - "type": "string", - "minLength": 1 - } - }, - "reason": { - "type": "string", - "minLength": 1 - } - } - }, - "fact": { - "type": "object", - "additionalProperties": false, - "required": [ - "id", - "fields" - ], - "properties": { - "id": { - "type": "string", - "minLength": 1 - }, - "fields": { - "$ref": "#/$defs/fields" - } - } - }, - "node": { - "type": "object", - "additionalProperties": false, - "required": [ - "id", - "kind", - "fields" - ], - "properties": { - "id": { - "type": "string", - "minLength": 1 - }, - "kind": { - "enum": [ - "package", - "owner", - "dependency", - "test", - "finding", - "failure", - "assert", - "hot", - "key", - "evidence", - "symbol", - "config", - "tsconfig", - "extension", - "build_tool", - "test_surface", - "custom" - ] - }, - "path": { - "$ref": "#/$defs/projectPath" - }, - "rank": { - "type": "number" - }, - "targetRole": { - "$ref": "#/$defs/targetRole" - }, - "fields": { - "$ref": "#/$defs/fields" - } - } - }, - "edge": { - "type": "object", - "additionalProperties": false, - "required": [ - "from", - "kind", - "to" - ], - "properties": { - "from": { - "type": "string", - "minLength": 1 - }, - "kind": { - "type": "string", - "pattern": "^[a-z][a-z0-9_-]*$" - }, - "to": { - "type": "string", - "minLength": 1 - }, - "label": { - "type": "string" - }, - "location": { - "$ref": "#/$defs/location" - }, - "weight": { - "type": "number" - }, - "fields": { - "$ref": "#/$defs/fields" - } - } - }, - "owner": { - "type": "object", - "additionalProperties": false, - "required": [ - "path", - "role", - "public", - "fields" - ], - "properties": { - "path": { - "$ref": "#/$defs/projectPath" - }, - "namespace": { - "type": "string" - }, - "role": { - "type": "string" - }, - "public": { - "type": "boolean" - }, - "exports": { - "type": "array", - "items": { - "type": "string" - } - }, - "nextActions": { - "$ref": "#/$defs/nextActionList" - }, - "fields": { - "$ref": "#/$defs/fields" - } - } - }, - "item": { - "type": "object", - "additionalProperties": false, - "required": [ - "name", - "kind", - "ownerPath", - "fields" - ], - "properties": { - "name": { - "type": "string" - }, - "kind": { - "type": "string" - }, - "ownerPath": { - "$ref": "#/$defs/projectPath" - }, - "location": { - "$ref": "#/$defs/location" - }, - "structuralSelector": { - "$ref": "semantic-source-location.v1.schema.json#/$defs/structuralSelector" - }, - "displayLineRange": { - "$ref": "semantic-source-location.v1.schema.json#/$defs/lineRange" - }, - "sourceLocatorHint": { - "$ref": "semantic-source-location.v1.schema.json#/$defs/sourceLocator" - }, - "projection": { - "$ref": "semantic-source-location.v1.schema.json#/$defs/projection" - }, - "fields": { - "$ref": "#/$defs/fields" - } - } - }, - "hit": { - "type": "object", - "additionalProperties": false, - "required": [ - "kind", - "ownerPath", - "location", - "score", - "reason" - ], - "properties": { - "kind": { - "type": "string" - }, - "ownerPath": { - "$ref": "#/$defs/projectPath" - }, - "symbol": { - "type": "string" - }, - "location": { - "$ref": "#/$defs/location" - }, - "score": { - "type": "number" - }, - "reason": { - "type": "string" - }, - "snippet": { - "type": "string" - }, - "surface": { - "$ref": "#/$defs/surfaceKind" - }, - "realOwner": { - "type": "boolean" - }, - "fixturePath": { - "$ref": "#/$defs/projectPath" - }, - "fixtureOwner": { - "$ref": "#/$defs/projectPath" - }, - "fields": { - "$ref": "#/$defs/fields" - } - } - }, - "finding": { - "type": "object", - "additionalProperties": false, - "required": [ - "ruleId", - "severity", - "count", - "location" - ], - "properties": { - "ruleId": { - "type": "string" - }, - "severity": { - "enum": [ - "info", - "warning", - "error" - ] - }, - "count": { - "type": "integer", - "minimum": 1 - }, - "title": { - "type": "string" - }, - "location": { - "$ref": "#/$defs/location" - }, - "fields": { - "$ref": "#/$defs/fields" - } - } - }, - "routeGraph": { - "type": "object", - "description": "Agent-facing search route topology derived from current evidence. This is the machine-owned source for compact route render output.", - "additionalProperties": false, - "required": [ - "profile", - "evidenceState", - "routes", - "chosenRouteId", - "actionFrontier" - ], - "properties": { - "profile": { - "type": "string", - "pattern": "^[a-z][a-z0-9_-]*$" - }, - "evidenceState": { - "$ref": "#/$defs/routeEvidenceState" - }, - "routes": { - "type": "array", - "minItems": 1, - "items": { - "$ref": "#/$defs/routeDecision" - } - }, - "chosenRouteId": { - "$ref": "#/$defs/routeId" - }, - "actionFrontier": { - "type": "array", - "minItems": 1, - "items": { - "type": "string", - "pattern": "^[A-Z][A-Z0-9]*\\.[a-z][a-z0-9_-]*$" - } - }, - "recommendedNext": { - "type": "string", - "pattern": "^[A-Z][A-Z0-9]*\\.[a-z][a-z0-9_-]*$" - }, - "reason": { - "type": "string", - "minLength": 1 - }, - "omit": { - "$ref": "#/$defs/stringList" - }, - "avoid": { - "$ref": "#/$defs/stringList" - }, - "fields": { - "$ref": "#/$defs/fields" - } - } - }, - "routeDecision": { - "type": "object", - "additionalProperties": false, - "required": [ - "id", - "kind", - "preconditions", - "projection", - "codePolicy" - ], - "properties": { - "id": { - "$ref": "#/$defs/routeId" - }, - "kind": { - "enum": [ - "known-selector", - "known-owner", - "known-symbol", - "known-dependency", - "failure-frontier", - "broad-query", - "unknown-workspace", - "no-asp", - "custom" - ] - }, - "preconditions": { - "$ref": "#/$defs/stringList" - }, - "targetRole": { - "$ref": "#/$defs/targetRole" - }, - "projection": { - "$ref": "#/$defs/routeProjection" - }, - "codePolicy": { - "$ref": "#/$defs/routeCodePolicy" - }, - "requiresExact": { - "type": "boolean" - }, - "cost": { - "enum": [ - "low", - "medium", - "high" - ] - }, - "actions": { - "type": "array", - "items": { - "type": "string", - "pattern": "^[A-Z][A-Z0-9]*\\.[a-z][a-z0-9_-]*$" - } - }, - "reason": { - "type": "string", - "minLength": 1 - }, - "avoid": { - "$ref": "#/$defs/stringList" - }, - "fields": { - "$ref": "#/$defs/fields" - } - } - }, - "routeAction": { - "type": "object", - "description": "Typed route action fact. Renderers may derive display commands from this object, but providers must not store command strings or argv here.", - "additionalProperties": false, - "required": [ - "id", - "kind", - "routeId", - "targetRole", - "projection", - "codePolicy" - ], - "properties": { - "id": { - "type": "string", - "pattern": "^[A-Z][A-Z0-9]*\\.[a-z][a-z0-9_-]*$" - }, - "kind": { - "type": "string", - "pattern": "^[a-z][a-z0-9_-]*$" - }, - "routeId": { - "$ref": "#/$defs/routeId" - }, - "targetRole": { - "$ref": "#/$defs/targetRole" - }, - "target": { - "type": "string", - "minLength": 1 - }, - "ownerPath": { - "$ref": "#/$defs/projectPath" - }, - "selector": { - "type": "string", - "minLength": 1 - }, - "query": { - "type": "string", - "minLength": 1 - }, - "projection": { - "$ref": "#/$defs/routeProjection" - }, - "codePolicy": { - "$ref": "#/$defs/routeCodePolicy" - }, - "requiresExact": { - "type": "boolean" - }, - "displayLineRange": { - "type": "string", - "description": "Display-only line span. It is not an executable selector.", - "pattern": "^[1-9][0-9]*:[1-9][0-9]*$" - }, - "sourceLocatorHint": { - "type": "string", - "description": "Display-only source locator hint. Agents must execute owner/symbol/structural selectors instead.", - "pattern": "^(?:\\.|(?!/)(?![A-Za-z]:)(?![0-9]+:)(?!\\.\\.?($|/))(?!.*(^|/)\\.\\.?($|/))(?!.*//)[^\\s:\\\\]+(?:/[^\\s:\\\\]+)*):[1-9][0-9]*:[1-9][0-9]*$" - }, - "avoid": { - "$ref": "#/$defs/stringList" - }, - "fields": { - "$ref": "#/$defs/fields" - } - } - }, - "routeEvidenceState": { - "type": "object", - "additionalProperties": false, - "properties": { - "anchors": { - "type": "array", - "uniqueItems": true, - "items": { - "enum": [ - "ownerPath", - "symbol", - "structuralSelector", - "dependency", - "failure", - "workspace", - "query", - "none" - ] - } - }, - "ownerPath": { - "$ref": "#/$defs/projectPath" - }, - "symbol": { - "type": "string", - "minLength": 1 - }, - "structuralSelector": { - "type": "string", - "minLength": 1 - }, - "dependency": { - "type": "string", - "minLength": 1 - }, - "failure": { - "type": "string", - "minLength": 1 - }, - "query": { - "type": "string", - "minLength": 1 - }, - "workspaceKnown": { - "type": "boolean" - }, - "intentRequiresExactCode": { - "type": "boolean" - }, - "fields": { - "$ref": "#/$defs/fields" - } - } - }, - "routeId": { - "type": "string", - "pattern": "^[A-Z][A-Z0-9_]*$" - }, - "routeProjection": { - "enum": [ - "none", - "metadata", - "names", - "skeleton", - "outline", - "topology", - "dependency", - "tests", - "failure", - "code" - ] - }, - "routeCodePolicy": { - "enum": [ - "disabled", - "metadata-only", - "names-only", - "exact-only", - "requires-exact-code" - ] - }, - "nextAction": { - "type": "object", - "additionalProperties": false, - "required": [ - "kind", - "target", - "command" - ], - "properties": { - "kind": { - "type": "string", - "pattern": "^[a-z][a-z0-9_-]*$" - }, - "target": { - "type": "string", - "minLength": 1 - }, - "targetRole": { - "$ref": "#/$defs/targetRole" - }, - "scope": { - "type": "string" - }, - "ownerPath": { - "$ref": "#/$defs/projectPath" - }, - "command": { - "type": "object", - "additionalProperties": false, - "description": "Canonical executable action. Consumers execute executable plus argv directly and must not reconstruct commands from prose, fields, or historical CLI spellings.", - "required": [ - "executable", - "argv" - ], - "properties": { - "executable": { - "type": "string", - "minLength": 1 - }, - "argv": { - "type": "array", - "minItems": 1, - "items": { - "type": "string", - "minLength": 1 - } - } - } - }, - "fields": { - "$ref": "#/$defs/fields" - } - } - }, - "delegationHint": { - "type": "object", - "additionalProperties": false, - "required": [ - "profile", - "decision", - "runtimeOwner", - "readOnly", - "noCode", - "targetActions", - "reason", - "receipt" - ], - "properties": { - "profile": { - "type": "string", - "pattern": "^[a-z][a-z0-9_-]*$" - }, - "fanout": { - "const": "parallel" - }, - "instances": { - "const": "targetActions" - }, - "branchPrompt": { - "const": "reasoning-tree" - }, - "stateOwner": { - "const": "parent" - }, - "fanin": { - "const": "receipt" - }, - "iterative": { - "type": "boolean" - }, - "decision": { - "const": "advisory" - }, - "runtimeOwner": { - "const": "agent-client" - }, - "modelClass": { - "enum": [ - "cheap", - "standard", - "strong", - "inherit", - "custom" - ] - }, - "readOnly": { - "type": "boolean" - }, - "noCode": { - "type": "boolean" - }, - "targetActions": { - "type": "array", - "minItems": 1, - "uniqueItems": true, - "items": { - "type": "string", - "pattern": "^A[1-9][0-9]*\\.[a-z][a-z0-9_-]*$" - } - }, - "maxCommands": { - "type": "integer", - "minimum": 1 - }, - "maxTurns": { - "type": "integer", - "minimum": 1 - }, - "reason": { - "type": "string", - "minLength": 1 - }, - "receipt": { - "$ref": "#/$defs/delegationReceipt" - }, - "fields": { - "$ref": "#/$defs/fields" - } - } - }, - "delegationReceipt": { - "type": "object", - "additionalProperties": false, - "required": [ - "kind", - "requiredFields" - ], - "properties": { - "kind": { - "const": "search-subagent" - }, - "requiredFields": { - "type": "array", - "minItems": 5, - "uniqueItems": true, - "items": { - "enum": [ - "role", - "action", - "evidence", - "missing", - "next", - "risk" - ] - } - } - } - }, - "reasoningProfileName": { - "enum": [ - "owner-query", - "query-deps", - "owner-tests", - "finding-frontier", - "feature-cfg" - ] - }, - "reasoningProfile": { - "type": "object", - "additionalProperties": false, - "required": [ - "profile", - "selectors", - "returns" - ], - "properties": { - "profile": { - "$ref": "#/$defs/reasoningProfileName" - }, - "description": { - "type": "string", - "minLength": 1 - }, - "selectors": { - "type": "array", - "minItems": 1, - "items": { - "$ref": "#/$defs/reasoningProfileSelector" - } - }, - "returns": { - "type": "array", - "minItems": 1, - "uniqueItems": true, - "items": { - "type": "string", - "pattern": "^[a-z][a-z0-9_.-]*$" - } - }, - "frontier": { - "type": "array", - "items": { - "type": "string", - "pattern": "^[A-Z][A-Z0-9]*\\.[a-z][a-z0-9_-]*$" - } - }, - "avoid": { - "type": "array", - "uniqueItems": true, - "items": { - "type": "string", - "pattern": "^[a-z][a-z0-9_.-]*$" - } - }, - "fields": { - "$ref": "#/$defs/fields" - } - }, - "allOf": [ - { - "$ref": "#/$defs/reasoningProfileOwnerQueryContract" - }, - { - "$ref": "#/$defs/reasoningProfileQueryDepsContract" - }, - { - "$ref": "#/$defs/reasoningProfileOwnerTestsContract" - }, - { - "$ref": "#/$defs/reasoningProfileFindingFrontierContract" - }, - { - "$ref": "#/$defs/reasoningProfileFeatureCfgContract" - } - ] - }, - "reasoningProfileCatalog": { - "const": [ - { - "profile": "owner-query", - "selectors": [ - { - "kind": "owner", - "required": true - }, - { - "kind": "query", - "required": true - } - ], - "returns": [ - "items", - "tests", - "dependency-usage" - ] - }, - { - "profile": "query-deps", - "selectors": [ - { - "kind": "query", - "required": true - }, - { - "kind": "dependency", - "required": true - } - ], - "returns": [ - "owners", - "imports", - "usage-tests" - ] - }, - { - "profile": "owner-tests", - "selectors": [ - { - "kind": "owner", - "required": true - } - ], - "returns": [ - "covering-tests", - "test-entrypoints", - "fixtures" - ] - }, - { - "profile": "finding-frontier", - "selectors": [ - { - "kind": "finding", - "required": true - }, - { - "kind": "owner", - "required": false - } - ], - "returns": [ - "affected-owners", - "tests", - "verification-actions" - ] - }, - { - "profile": "feature-cfg", - "selectors": [ - { - "kind": "feature", - "required": true - } - ], - "returns": [ - "cfg-gates", - "owners", - "verification-surfaces" - ] - } - ] - }, - "reasoningProfileOwnerQueryContract": { - "if": { - "properties": { - "profile": { - "const": "owner-query" - } - }, - "required": [ - "profile" - ] - }, - "then": { - "properties": { - "selectors": { - "prefixItems": [ - { - "$ref": "#/$defs/reasoningProfileOwnerSelector" - }, - { - "$ref": "#/$defs/reasoningProfileQuerySelector" - } - ], - "items": false, - "minItems": 2, - "maxItems": 2 - }, - "returns": { - "const": [ - "items", - "tests", - "dependency-usage" - ] - } - } - } - }, - "reasoningProfileQueryDepsContract": { - "if": { - "properties": { - "profile": { - "const": "query-deps" - } - }, - "required": [ - "profile" - ] - }, - "then": { - "properties": { - "selectors": { - "prefixItems": [ - { - "$ref": "#/$defs/reasoningProfileQuerySelector" - }, - { - "$ref": "#/$defs/reasoningProfileDependencySelector" - } - ], - "items": false, - "minItems": 2, - "maxItems": 2 - }, - "returns": { - "type": "array", - "minItems": 3, - "maxItems": 5, - "prefixItems": [ - { - "const": "owners" - }, - { - "const": "imports" - } - ], - "items": { - "enum": [ - "usage-tests", - "local-docs", - "docs-use", - "crate-source" - ] - } - } - } - } - }, - "reasoningProfileOwnerTestsContract": { - "if": { - "properties": { - "profile": { - "const": "owner-tests" - } - }, - "required": [ - "profile" - ] - }, - "then": { - "properties": { - "selectors": { - "prefixItems": [ - { - "$ref": "#/$defs/reasoningProfileOwnerSelector" - } - ], - "items": false, - "minItems": 1, - "maxItems": 1 - }, - "returns": { - "const": [ - "covering-tests", - "test-entrypoints", - "fixtures" - ] - } - } - } - }, - "reasoningProfileFindingFrontierContract": { - "if": { - "properties": { - "profile": { - "const": "finding-frontier" - } - }, - "required": [ - "profile" - ] - }, - "then": { - "properties": { - "selectors": { - "oneOf": [ - { - "prefixItems": [ - { - "$ref": "#/$defs/reasoningProfileFindingSelector" - } - ], - "items": false, - "minItems": 1, - "maxItems": 1 - }, - { - "prefixItems": [ - { - "$ref": "#/$defs/reasoningProfileFindingSelector" - }, - { - "$ref": "#/$defs/reasoningProfileOwnerSelector" - } - ], - "items": false, - "minItems": 2, - "maxItems": 2 - } - ] - }, - "returns": { - "const": [ - "affected-owners", - "tests", - "verification-actions" - ] - } - } - } - }, - "reasoningProfileFeatureCfgContract": { - "if": { - "properties": { - "profile": { - "const": "feature-cfg" - } - }, - "required": [ - "profile" - ] - }, - "then": { - "properties": { - "selectors": { - "prefixItems": [ - { - "$ref": "#/$defs/reasoningProfileFeatureSelector" - } - ], - "items": false, - "minItems": 1, - "maxItems": 1 - }, - "returns": { - "const": [ - "cfg-gates", - "owners", - "verification-surfaces" - ] - } - } - } - }, - "reasoningProfileOwnerSelector": { - "allOf": [ - { - "$ref": "#/$defs/reasoningProfileSelector" - }, - { - "properties": { - "kind": { - "const": "owner" - } - } - } - ] - }, - "reasoningProfileQuerySelector": { - "allOf": [ - { - "$ref": "#/$defs/reasoningProfileSelector" - }, - { - "properties": { - "kind": { - "const": "query" - } - } - } - ] - }, - "reasoningProfileDependencySelector": { - "allOf": [ - { - "$ref": "#/$defs/reasoningProfileSelector" - }, - { - "properties": { - "kind": { - "const": "dependency" - } - } - } - ] - }, - "reasoningProfileFindingSelector": { - "allOf": [ - { - "$ref": "#/$defs/reasoningProfileSelector" - }, - { - "properties": { - "kind": { - "const": "finding" - } - } - } - ] - }, - "reasoningProfileFeatureSelector": { - "allOf": [ - { - "$ref": "#/$defs/reasoningProfileSelector" - }, - { - "properties": { - "kind": { - "const": "feature" - } - } - } - ] - }, - "reasoningProfileSelector": { - "type": "object", - "additionalProperties": false, - "required": [ - "kind", - "alias" - ], - "properties": { - "kind": { - "enum": [ - "owner", - "query", - "dependency", - "test", - "finding", - "import", - "feature", - "custom" - ] - }, - "alias": { - "type": "string", - "pattern": "^[A-Z][A-Z0-9]*$" - }, - "target": { - "type": "string", - "minLength": 1 - }, - "targetRole": { - "$ref": "#/$defs/targetRole" - }, - "required": { - "type": "boolean" - }, - "fields": { - "$ref": "#/$defs/fields" - } - } - }, - "note": { - "type": "object", - "additionalProperties": false, - "required": [ - "kind", - "message" - ], - "properties": { - "kind": { - "type": "string", - "pattern": "^[a-z][a-z0-9_-]*$" - }, - "message": { - "type": "string" - }, - "fields": { - "$ref": "#/$defs/fields" - } - } - } - } -} diff --git a/schemas/semantic-tree-sitter-grammar-profile.v1.schema.json b/schemas/semantic-tree-sitter-grammar-profile.v1.schema.json index 4515a38..cce0eb9 100644 --- a/schemas/semantic-tree-sitter-grammar-profile.v1.schema.json +++ b/schemas/semantic-tree-sitter-grammar-profile.v1.schema.json @@ -53,6 +53,9 @@ "corpusProfilePath": { "$ref": "#/$defs/projectPath" }, + "enhancedQueryCapabilityTablePath": { + "$ref": "#/$defs/projectPath" + }, "queryCorpus": { "$ref": "#/$defs/queryCorpus" }, diff --git a/schemas/semantic-type-surface.v1.schema.json b/schemas/semantic-type-surface.v1.schema.json index 6f64e38..e09e9aa 100644 --- a/schemas/semantic-type-surface.v1.schema.json +++ b/schemas/semantic-type-surface.v1.schema.json @@ -47,7 +47,7 @@ "scope": { "enum": [ "workspace", - "prime", + "search-playbook", "owner", "dependency", "api", diff --git a/src/python_lang_project_harness/__init__.py b/src/asp_python/__init__.py similarity index 81% rename from src/python_lang_project_harness/__init__.py rename to src/asp_python/__init__.py index 8ff5716..a7dc2fe 100644 --- a/src/python_lang_project_harness/__init__.py +++ b/src/asp_python/__init__.py @@ -1,18 +1,18 @@ -"""Project-level Python language harness helpers.""" +"""Project-level ASP Python helpers.""" from __future__ import annotations from importlib import import_module from typing import Any -DISTRIBUTION_NAME = "python-lang-project-harness" +DISTRIBUTION_NAME = "asp-python" _CLI_EXPORTS = frozenset({"run_cli", "run_cli_from_env"}) -_HARNESS_RULES_EXPORTS = frozenset( +_ASP_RULES_EXPORTS = frozenset( { - "python_harness_rules_markdown", - "render_python_harness_rules_markdown", - "write_python_harness_rules_to_unit_tests", + "asp_python_rules_markdown", + "render_asp_python_rules_markdown", + "write_asp_python_rules_to_unit_tests", } ) @@ -83,12 +83,12 @@ "PythonExportContract", "PythonExportContractKind", "PythonFunctionControlFlow", - "PythonHarnessConfig", - "PythonHarnessFinding", - "PythonHarnessReport", - "PythonHarnessRule", + "AspPythonConfig", + "AspPythonFinding", + "AspPythonReport", + "AspPythonRule", "PythonImport", - "PythonLangRulePack", + "AspPythonRulePack", "PythonModernDesignRulePack", "PythonModularityRulePack", "PythonModuleReport", @@ -97,7 +97,7 @@ "PythonOwnerResponsibility", "PythonProjectDependency", "PythonProjectEntryPoint", - "PythonProjectHarnessScope", + "AspPythonProjectScope", "PythonProjectImportName", "PythonProjectMetadata", "PythonProjectPolicyRulePack", @@ -112,7 +112,6 @@ "PythonReasoningTreeShadow", "PythonRulePackDescriptor", "PythonScope", - "PythonSemanticSearchOptions", "PythonSymbol", "PythonSymbolKind", "PythonSyntaxRulePack", @@ -142,11 +141,10 @@ "PythonVerificationWaiver", "SourceLocation", "__version__", - "assert_python_lang_harness_clean", - "assert_python_project_harness_clean", - "build_python_semantic_search_packet", - "default_python_harness_config", - "default_python_lang_rule_packs", + "assert_asp_python_paths_clean", + "assert_asp_python_clean", + "default_asp_python_config", + "default_asp_python_rule_packs", "discover_python_files", "build_python_verification_performance_index", "build_python_verification_profile_index", @@ -176,27 +174,25 @@ "python_symbol_is_test_function", "python_symbol_is_top_level_callable", "python_agent_policy_rules", - "python_harness_rules_markdown", + "asp_python_rules_markdown", "python_modern_design_rules", "python_modularity_rules", - "python_project_harness_paths", - "python_project_harness_scope", - "python_project_harness_test", + "asp_python_paths", + "asp_python_scope", + "asp_python_test", "python_project_policy_rules", "python_rule_pack_descriptors", "python_semantic_language_registration", "python_syntax_rules", "python_test_layout_rules", - "read_python_project_harness_config", - "render_python_lang_harness", - "render_python_lang_harness_advice", - "render_python_lang_harness_json", - "render_python_harness_rules_markdown", - "render_python_project_harness_agent_snapshot", - "render_python_project_harness_agent_snapshot_with_config", + "read_asp_python_config", + "render_asp_python_report", + "render_asp_python_report_advice", + "render_asp_python_report_json", + "render_asp_python_rules_markdown", + "render_asp_python_agent_snapshot", + "render_asp_python_agent_snapshot_with_config", "render_python_reasoning_tree", - "render_python_semantic_search_packet", - "render_python_semantic_search_packet_json", "render_python_verification_performance_index_json", "render_python_verification_plan", "render_python_verification_plan_json", @@ -208,10 +204,10 @@ "render_python_verification_task_index_json", "run_cli", "run_cli_from_env", - "run_python_lang_harness", - "run_python_project_harness", + "run_asp_python_paths", + "run_asp_python", "semantic_language_registry_document", - "write_python_harness_rules_to_unit_tests", + "write_asp_python_rules_to_unit_tests", "write_python_verification_reports", ] @@ -223,12 +219,12 @@ def __getattr__(name: str) -> Any: return _load_export("._version", name) if name in _CLI_EXPORTS: return _load_export("._cli", name) - if name in _HARNESS_RULES_EXPORTS: - return _load_export("._harness_rules", name) + if name in _ASP_RULES_EXPORTS: + return _load_export("._asp_rules", name) if name in _PARSER_EXPORTS: return _load_export("python_lang_parser", name) if name in __all__: - return _load_export(".harness", name) + return _load_export(".api", name) raise AttributeError(f"module {__name__!r} has no attribute {name!r}") diff --git a/src/python_lang_project_harness/__main__.py b/src/asp_python/__main__.py similarity index 61% rename from src/python_lang_project_harness/__main__.py rename to src/asp_python/__main__.py index 53d01ac..68530d4 100644 --- a/src/python_lang_project_harness/__main__.py +++ b/src/asp_python/__main__.py @@ -1,4 +1,4 @@ -"""Module entrypoint for `python -m python_lang_project_harness`.""" +"""Module entrypoint for `python -m asp_python`.""" from __future__ import annotations diff --git a/src/python_lang_project_harness/_agent_namespace.py b/src/asp_python/_agent_namespace.py similarity index 89% rename from src/python_lang_project_harness/_agent_namespace.py rename to src/asp_python/_agent_namespace.py index f128402..0071ca5 100644 --- a/src/python_lang_project_harness/_agent_namespace.py +++ b/src/asp_python/_agent_namespace.py @@ -1,4 +1,4 @@ -"""Project-level namespace policy for agent-oriented Python harness runs.""" +"""Project-level namespace policy for agent-oriented ASP Python runs.""" from __future__ import annotations @@ -19,7 +19,7 @@ PY_AGENT_R006, agent_policy_rule, ) -from ._model import PythonHarnessFinding +from ._model import AspPythonFinding from ._source import path_location if TYPE_CHECKING: @@ -27,7 +27,7 @@ from python_lang_parser import PythonModuleReport - from ._model import PythonProjectHarnessScope + from ._model import AspPythonProjectScope @dataclass(frozen=True, slots=True) @@ -61,13 +61,13 @@ class _NamespaceConflictSpec: def agent_namespace_findings( - scope: PythonProjectHarnessScope, + scope: AspPythonProjectScope, modules: Sequence[PythonModuleReport], pack_id: str, -) -> tuple[PythonHarnessFinding, ...]: +) -> tuple[AspPythonFinding, ...]: """Return compact project-wide namespace findings for agent repair.""" - findings: list[PythonHarnessFinding] = [] + findings: list[AspPythonFinding] = [] namespace_items = collect_agent_namespace_items(modules) for spec in _NAMESPACE_CONFLICT_SPECS: findings.extend(_duplicate_namespace_findings(namespace_items, spec, pack_id)) @@ -79,10 +79,10 @@ def _duplicate_namespace_findings( namespace_items: tuple[AgentNamespaceItem, ...], spec: _NamespaceConflictSpec, pack_id: str, -) -> tuple[PythonHarnessFinding, ...]: +) -> tuple[AspPythonFinding, ...]: first_seen: dict[str, AgentNamespaceItem] = {} emitted: set[tuple[str, str]] = set() - findings: list[PythonHarnessFinding] = [] + findings: list[AspPythonFinding] = [] rule = agent_policy_rule(spec.rule_id) for item in namespace_items: if item.surface != spec.surface: @@ -96,7 +96,7 @@ def _duplicate_namespace_findings( continue emitted.add(key) findings.append( - PythonHarnessFinding( + AspPythonFinding( rule_id=rule.rule_id, pack_id=pack_id, severity=rule.severity, @@ -116,12 +116,12 @@ def _duplicate_namespace_findings( def _repeated_namespace_segment_findings( - scope: PythonProjectHarnessScope, + scope: AspPythonProjectScope, modules: Sequence[PythonModuleReport], pack_id: str, -) -> tuple[PythonHarnessFinding, ...]: +) -> tuple[AspPythonFinding, ...]: emitted_branches: set[tuple[str, tuple[str, ...]]] = set() - findings: list[PythonHarnessFinding] = [] + findings: list[AspPythonFinding] = [] rule = agent_policy_rule(PY_AGENT_R004) for report in modules: if not report.path: @@ -140,7 +140,7 @@ def _repeated_namespace_segment_findings( continue emitted_branches.add(key) findings.append( - PythonHarnessFinding( + AspPythonFinding( rule_id=rule.rule_id, pack_id=pack_id, severity=rule.severity, diff --git a/src/python_lang_project_harness/_agent_namespace_index.py b/src/asp_python/_agent_namespace_index.py similarity index 100% rename from src/python_lang_project_harness/_agent_namespace_index.py rename to src/asp_python/_agent_namespace_index.py diff --git a/src/python_lang_project_harness/_agent_policy.py b/src/asp_python/_agent_policy.py similarity index 89% rename from src/python_lang_project_harness/_agent_policy.py rename to src/asp_python/_agent_policy.py index 977b20b..41d9d36 100644 --- a/src/python_lang_project_harness/_agent_policy.py +++ b/src/asp_python/_agent_policy.py @@ -19,7 +19,7 @@ agent_policy_rule, ) from ._agent_reasoning_tree import agent_reasoning_tree_findings -from ._model import PythonHarnessFinding, PythonRulePackDescriptor +from ._model import AspPythonFinding, PythonRulePackDescriptor from ._source import path_location from .agent_readability import ( agent_algorithm_shape_findings, @@ -33,7 +33,7 @@ from python_lang_parser import PythonModuleReport, PythonSymbol - from ._model import PythonProjectHarnessScope + from ._model import AspPythonProjectScope @dataclass(frozen=True, slots=True) @@ -52,13 +52,13 @@ def descriptor(self) -> PythonRulePackDescriptor: default_mode="advisory", ) - def evaluate(self, report: PythonModuleReport) -> Iterable[PythonHarnessFinding]: + def evaluate(self, report: PythonModuleReport) -> Iterable[AspPythonFinding]: """Evaluate agent-oriented policy rules for one parsed module report.""" if not report.is_valid: return () - findings: list[PythonHarnessFinding] = [] + findings: list[AspPythonFinding] = [] findings.extend(_module_docstring_findings(report, self.pack_id)) findings.extend(_public_callable_annotation_findings(report, self.pack_id)) findings.extend(agent_algorithm_shape_findings(report, self.pack_id)) @@ -69,9 +69,9 @@ def evaluate(self, report: PythonModuleReport) -> Iterable[PythonHarnessFinding] def evaluate_project_modules( self, - scope: PythonProjectHarnessScope, + scope: AspPythonProjectScope, modules: Sequence[PythonModuleReport], - ) -> Iterable[PythonHarnessFinding]: + ) -> Iterable[AspPythonFinding]: """Evaluate agent-oriented namespace rules across a project scope.""" return ( @@ -83,13 +83,13 @@ def evaluate_project_modules( def _module_docstring_findings( report: PythonModuleReport, pack_id: str, -) -> tuple[PythonHarnessFinding, ...]: +) -> tuple[AspPythonFinding, ...]: if report.module_docstring or not _module_has_agent_surface(report): return () rule = agent_policy_rule(PY_AGENT_R001) path = Path(report.path or "") return ( - PythonHarnessFinding( + AspPythonFinding( rule_id=rule.rule_id, pack_id=pack_id, severity=rule.severity, @@ -107,14 +107,14 @@ def _module_docstring_findings( def _public_callable_annotation_findings( report: PythonModuleReport, pack_id: str, -) -> tuple[PythonHarnessFinding, ...]: - findings: list[PythonHarnessFinding] = [] +) -> tuple[AspPythonFinding, ...]: + findings: list[AspPythonFinding] = [] rule = agent_policy_rule(PY_AGENT_R002) for symbol in report.symbols: if not _is_public_callable(symbol) or symbol.has_annotations: continue findings.append( - PythonHarnessFinding( + AspPythonFinding( rule_id=rule.rule_id, pack_id=pack_id, severity=rule.severity, diff --git a/src/python_lang_project_harness/_agent_policy_catalog.py b/src/asp_python/_agent_policy_catalog.py similarity index 93% rename from src/python_lang_project_harness/_agent_policy_catalog.py rename to src/asp_python/_agent_policy_catalog.py index ec4a4f8..97ddfb2 100644 --- a/src/python_lang_project_harness/_agent_policy_catalog.py +++ b/src/asp_python/_agent_policy_catalog.py @@ -6,7 +6,7 @@ from python_lang_parser._diagnostic_model import PythonDiagnosticSeverity -from ._model import PythonHarnessRule +from ._model import AspPythonRule AGENT_POLICY_PACK_ID = "python.agent_policy" PY_AGENT_R001 = "PY-AGENT-POLICY-001" @@ -27,7 +27,7 @@ "domain": "agent-policy", } _RULES = ( - PythonHarnessRule( + AspPythonRule( rule_id=PY_AGENT_R001, pack_id=AGENT_POLICY_PACK_ID, severity=PythonDiagnosticSeverity.INFO, @@ -35,7 +35,7 @@ requirement="Add a concise module docstring that names the module responsibility for agent search and repair.", labels=dict(_RULE_LABELS), ), - PythonHarnessRule( + AspPythonRule( rule_id=PY_AGENT_R002, pack_id=AGENT_POLICY_PACK_ID, severity=PythonDiagnosticSeverity.INFO, @@ -43,7 +43,7 @@ requirement="Annotate public function and method boundaries so agents can reason from native syntax without guessing shapes.", labels=dict(_RULE_LABELS), ), - PythonHarnessRule( + AspPythonRule( rule_id=PY_AGENT_R003, pack_id=AGENT_POLICY_PACK_ID, severity=PythonDiagnosticSeverity.INFO, @@ -51,7 +51,7 @@ requirement="Give project-level public callables unambiguous names or move them behind a clear domain namespace so agents can resolve intent without guessing.", labels=dict(_RULE_LABELS), ), - PythonHarnessRule( + AspPythonRule( rule_id=PY_AGENT_R004, pack_id=AGENT_POLICY_PACK_ID, severity=PythonDiagnosticSeverity.INFO, @@ -59,7 +59,7 @@ requirement="Keep Python module namespaces branch-unique; rename repeated path segments so agents see one clear ownership path.", labels=dict(_RULE_LABELS), ), - PythonHarnessRule( + AspPythonRule( rule_id=PY_AGENT_R005, pack_id=AGENT_POLICY_PACK_ID, severity=PythonDiagnosticSeverity.INFO, @@ -67,7 +67,7 @@ requirement="Give project-level public classes unambiguous type names or move them behind a clear domain namespace so agents can resolve intent without guessing.", labels=dict(_RULE_LABELS), ), - PythonHarnessRule( + AspPythonRule( rule_id=PY_AGENT_R006, pack_id=AGENT_POLICY_PACK_ID, severity=PythonDiagnosticSeverity.INFO, @@ -75,7 +75,7 @@ requirement="Give project-level public values and configuration exports unambiguous names or move them behind a clear domain namespace so agents can resolve intent without guessing.", labels=dict(_RULE_LABELS), ), - PythonHarnessRule( + AspPythonRule( rule_id=PY_AGENT_R007, pack_id=AGENT_POLICY_PACK_ID, severity=PythonDiagnosticSeverity.INFO, @@ -83,7 +83,7 @@ requirement="Add a concise package docstring to branch `__init__.py` files so agents can choose the right owner subtree before editing.", labels=dict(_RULE_LABELS), ), - PythonHarnessRule( + AspPythonRule( rule_id=PY_AGENT_R008, pack_id=AGENT_POLICY_PACK_ID, severity=PythonDiagnosticSeverity.INFO, @@ -91,7 +91,7 @@ requirement="Split crowded branch packages into focused subpackages, or document the facade and owner map so agents do not treat one folder as one responsibility.", labels=dict(_RULE_LABELS), ), - PythonHarnessRule( + AspPythonRule( rule_id=PY_AGENT_R009, pack_id=AGENT_POLICY_PACK_ID, severity=PythonDiagnosticSeverity.INFO, @@ -99,7 +99,7 @@ requirement="Flatten deeply nested if/loop logic into guard clauses, explicit dispatch, match/case, or small named pipeline steps so agents can reason about the algorithm shape.", labels=dict(_RULE_LABELS), ), - PythonHarnessRule( + AspPythonRule( rule_id=PY_AGENT_R010, pack_id=AGENT_POLICY_PACK_ID, severity=PythonDiagnosticSeverity.INFO, @@ -107,7 +107,7 @@ requirement="Split long public functions with broad linear statement blocks into small named helpers or pipeline steps so agents can edit one algorithm responsibility at a time.", labels=dict(_RULE_LABELS), ), - PythonHarnessRule( + AspPythonRule( rule_id=PY_AGENT_R011, pack_id=AGENT_POLICY_PACK_ID, severity=PythonDiagnosticSeverity.INFO, @@ -115,7 +115,7 @@ requirement="Use comprehensions, generator expressions, built-ins such as sum/any/all, collections.Counter/defaultdict, or named iterator pipeline helpers when a loop only maps, filters, counts, groups, accumulates, or answers a predicate.", labels=dict(_RULE_LABELS), ), - PythonHarnessRule( + AspPythonRule( rule_id=PY_AGENT_R012, pack_id=AGENT_POLICY_PACK_ID, severity=PythonDiagnosticSeverity.INFO, @@ -127,13 +127,13 @@ _RULE_BY_ID = {rule.rule_id: rule for rule in _RULES} -def python_agent_policy_rules() -> tuple[PythonHarnessRule, ...]: +def python_agent_policy_rules() -> tuple[AspPythonRule, ...]: """Return compact metadata for the default agent-oriented policy rules.""" return tuple(replace(rule, labels=dict(rule.labels)) for rule in _RULES) -def agent_policy_rule(rule_id: str) -> PythonHarnessRule: +def agent_policy_rule(rule_id: str) -> AspPythonRule: """Return one agent-policy rule descriptor by stable rule id.""" return _RULE_BY_ID[rule_id] diff --git a/src/python_lang_project_harness/_agent_reasoning_tree.py b/src/asp_python/_agent_reasoning_tree.py similarity index 93% rename from src/python_lang_project_harness/_agent_reasoning_tree.py rename to src/asp_python/_agent_reasoning_tree.py index 3109a29..7744aec 100644 --- a/src/python_lang_project_harness/_agent_reasoning_tree.py +++ b/src/asp_python/_agent_reasoning_tree.py @@ -8,7 +8,7 @@ from python_lang_parser import python_reasoning_tree_facts from ._agent_policy_catalog import PY_AGENT_R007, PY_AGENT_R008, agent_policy_rule -from ._model import PythonHarnessFinding +from ._model import AspPythonFinding from ._source import path_location if TYPE_CHECKING: @@ -21,7 +21,7 @@ PythonReasoningTreeNode, ) - from ._model import PythonProjectHarnessScope + from ._model import AspPythonProjectScope _MAX_AGENT_BRANCH_CHILDREN = 6 _MIN_AGENT_BRANCH_PUBLIC_CHILDREN = 4 @@ -29,10 +29,10 @@ def agent_reasoning_tree_findings( - scope: PythonProjectHarnessScope, + scope: AspPythonProjectScope, modules: Sequence[PythonModuleReport], pack_id: str, -) -> tuple[PythonHarnessFinding, ...]: +) -> tuple[AspPythonFinding, ...]: """Return agent advice for package-tree navigation gaps.""" facts = python_reasoning_tree_facts( @@ -46,7 +46,7 @@ def agent_reasoning_tree_findings( } intent_rule = agent_policy_rule(PY_AGENT_R007) broad_rule = agent_policy_rule(PY_AGENT_R008) - findings: list[PythonHarnessFinding] = [] + findings: list[AspPythonFinding] = [] for branch in facts.branches: if branch.has_intent_doc: findings.extend( @@ -62,7 +62,7 @@ def agent_reasoning_tree_findings( module = modules_by_path.get(branch.path) namespace = ".".join(branch.namespace) findings.append( - PythonHarnessFinding( + AspPythonFinding( rule_id=intent_rule.rule_id, pack_id=pack_id, severity=intent_rule.severity, @@ -98,7 +98,7 @@ def _broad_branch_findings( rule_id: str, pack_id: str, modules_by_path: dict[str, PythonModuleReport], -) -> tuple[PythonHarnessFinding, ...]: +) -> tuple[AspPythonFinding, ...]: child_nodes = _branch_child_nodes(branch, facts) public_child_count = sum(node.has_public_surface for node in child_nodes) effective_lines = sum(node.effective_code_lines for node in child_nodes) @@ -114,7 +114,7 @@ def _broad_branch_findings( rule = agent_policy_rule(rule_id) namespace = ".".join(branch.namespace) return ( - PythonHarnessFinding( + AspPythonFinding( rule_id=rule.rule_id, pack_id=pack_id, severity=rule.severity, @@ -193,7 +193,7 @@ def _module_documents_owner_map(module: PythonModuleReport | None) -> bool: return "owner map" in normalized -def _reasoning_tree_import_roots(scope: PythonProjectHarnessScope) -> tuple[Path, ...]: +def _reasoning_tree_import_roots(scope: AspPythonProjectScope) -> tuple[Path, ...]: if scope.source_paths: return scope.source_paths return scope.monitored_paths diff --git a/src/python_lang_project_harness/_agent_snapshot.py b/src/asp_python/_agent_snapshot.py similarity index 65% rename from src/python_lang_project_harness/_agent_snapshot.py rename to src/asp_python/_agent_snapshot.py index ce92c0e..6ced4bf 100644 --- a/src/python_lang_project_harness/_agent_snapshot.py +++ b/src/asp_python/_agent_snapshot.py @@ -1,4 +1,4 @@ -"""Agent-facing project snapshot renderer for Python harness runs.""" +"""Agent-facing project snapshot renderer for ASP Python runs.""" from __future__ import annotations @@ -7,10 +7,10 @@ from ._agent_snapshot_tree import render_python_agent_snapshot_tree from ._render import ( - render_python_lang_harness, + render_asp_python_report, ) -from ._rule_packs import resolve_project_harness_config -from ._runner import run_python_project_harness +from ._rule_packs import resolve_asp_python_project_config +from ._runner import run_asp_python from .verification import ( build_python_verification_profile_index_report, plan_python_project_verification_report, @@ -19,39 +19,39 @@ ) if TYPE_CHECKING: - from ._model import PythonHarnessConfig, PythonHarnessReport + from ._model import AspPythonConfig, AspPythonReport -def render_python_project_harness_agent_snapshot(project_root: str | Path) -> str: +def render_asp_python_agent_snapshot(project_root: str | Path) -> str: """Render a compact parser-backed project snapshot for repair agents.""" - return render_python_project_harness_agent_snapshot_with_config( + return render_asp_python_agent_snapshot_with_config( project_root, None, ) -def render_python_project_harness_agent_snapshot_with_config( +def render_asp_python_agent_snapshot_with_config( project_root: str | Path, - config: PythonHarnessConfig | None, + config: AspPythonConfig | None, ) -> str: - """Render an agent snapshot using an explicit harness config.""" + """Render an agent snapshot using an explicit ASP Python config.""" root = Path(project_root) - selected_config = resolve_project_harness_config(root, config, rule_packs=None) - report = run_python_project_harness(root, config=selected_config) - return render_python_project_harness_agent_snapshot_report( + selected_config = resolve_asp_python_project_config(root, config, rule_packs=None) + report = run_asp_python(root, config=selected_config) + return render_asp_python_agent_snapshot_report( report, config=selected_config, ) -def render_python_project_harness_agent_snapshot_report( - report: PythonHarnessReport, +def render_asp_python_agent_snapshot_report( + report: AspPythonReport, *, - config: PythonHarnessConfig | None = None, + config: AspPythonConfig | None = None, ) -> str: - """Render an already-built project harness report as an agent snapshot.""" + """Render an already-built ASP Python report as an agent snapshot.""" project_root = ( None @@ -84,8 +84,8 @@ def render_python_project_harness_agent_snapshot_report( return "\n".join(sections) + "\n" -def _render_policy_section(report: PythonHarnessReport) -> str: - rendered = render_python_lang_harness(report) +def _render_policy_section(report: AspPythonReport) -> str: + rendered = render_asp_python_report(report) if rendered.startswith("[ok]"): return "" return "[policy]\n" + rendered diff --git a/src/python_lang_project_harness/_agent_snapshot_tree.py b/src/asp_python/_agent_snapshot_tree.py similarity index 98% rename from src/python_lang_project_harness/_agent_snapshot_tree.py rename to src/asp_python/_agent_snapshot_tree.py index b1040fa..269843f 100644 --- a/src/python_lang_project_harness/_agent_snapshot_tree.py +++ b/src/asp_python/_agent_snapshot_tree.py @@ -19,7 +19,7 @@ PythonReasoningTreeShadow, ) - from ._model import PythonHarnessReport + from ._model import AspPythonReport @dataclass(frozen=True, slots=True) @@ -44,7 +44,7 @@ class _SourceTreeFacts: def render_python_agent_snapshot_tree( - report: PythonHarnessReport, + report: AspPythonReport, *, target: str, ) -> str: @@ -55,7 +55,7 @@ def render_python_agent_snapshot_tree( @dataclass(frozen=True, slots=True) class _SnapshotTreeRenderer: - report: PythonHarnessReport + report: AspPythonReport target: str limits: _SnapshotTreeLimits @@ -141,8 +141,8 @@ def metadata_lines(self, metadata: PythonProjectMetadata) -> list[str]: self.add_metadata_package_roots(lines, metadata) self.add_metadata_scripts(lines, metadata) self.add_metadata_entry_points(lines, metadata) - if metadata.pytest_options.enables_python_project_harness: - lines.append("- pytest=python-project-harness") + if metadata.pytest_options.enables_asp_python: + lines.append("- pytest=asp-python") return lines def add_metadata_identity( diff --git a/src/asp_python/_asp_rules.py b/src/asp_python/_asp_rules.py new file mode 100644 index 0000000..99b4e0c --- /dev/null +++ b/src/asp_python/_asp_rules.py @@ -0,0 +1,42 @@ +"""Render source-embedded ASP Python rule fixtures.""" + +from __future__ import annotations + +from importlib.resources import files +from pathlib import Path + +_ASP_RULES_RESOURCE = "asp-rules.md" + + +def asp_python_rules_markdown() -> str: + """Return the source-embedded ASP Python rule list.""" + + return files(__package__).joinpath(_ASP_RULES_RESOURCE).read_text(encoding="utf-8") + + +def render_asp_python_rules_markdown() -> str: + """Render the source-embedded ASP Python rules as markdown.""" + + output = [ + "# asp-python", + "", + "## ASP Python Rules", + "", + "Generated from embedded `src/asp_python/asp-rules.md`.", + "", + ] + for line in asp_python_rules_markdown().splitlines(): + if item := line.removeprefix("- "): + if ": " in item: + rule_id, sentence = item.split(": ", 1) + output.append(f"- **{rule_id}**: {sentence}") + return "\n".join(output) + "\n" + + +def write_asp_python_rules_to_unit_tests(unit_test_dir: Path) -> Path: + """Write the generated ASP Python rules into a downstream unit test directory.""" + + output_path = unit_test_dir / "asp-rules.generated.md" + output_path.parent.mkdir(parents=True, exist_ok=True) + output_path.write_text(render_asp_python_rules_markdown(), encoding="utf-8") + return output_path diff --git a/src/python_lang_project_harness/_callable_skeleton_projection.py b/src/asp_python/_callable_skeleton_projection.py similarity index 99% rename from src/python_lang_project_harness/_callable_skeleton_projection.py rename to src/asp_python/_callable_skeleton_projection.py index d148d9f..366532a 100644 --- a/src/python_lang_project_harness/_callable_skeleton_projection.py +++ b/src/asp_python/_callable_skeleton_projection.py @@ -140,6 +140,7 @@ def callable_skeleton_payload( ) projected_bytes = min(source_bytes, structural_bytes) return { + "rootSelector": root_exact["selector"], "rootNodeId": "callable:root", "callable": { "kind": selector.kind, diff --git a/src/asp_python/_cli.py b/src/asp_python/_cli.py new file mode 100644 index 0000000..d7b0afe --- /dev/null +++ b/src/asp_python/_cli.py @@ -0,0 +1,62 @@ +"""Command-line execution for the ASP Python.""" + +from __future__ import annotations + +import sys +from pathlib import Path +from typing import TextIO + +from ._cli_args import ProtocolArgs, help_text +from ._cli_protocol import run_protocol_cli + + +def run_cli_from_env() -> int: + """Run the CLI using process environment arguments.""" + + from ._dev_command_log import start_dev_command_log + + args = sys.argv[1:] + log = start_dev_command_log(args, Path.cwd()) + try: + if args == ["serve"]: + from ._runtime import serve_provider_runtime + + exit_code = serve_provider_runtime(Path.cwd()) + log.finish(exit_code) + return exit_code + stdin = "" if sys.stdin.isatty() else sys.stdin.read() + exit_code = run_cli(args, stdin=stdin) + log.finish(exit_code) + return exit_code + except Exception: + log.finish(2) + raise + + +def run_cli( + args: list[str] | tuple[str, ...], + *, + stdout: TextIO | None = None, + stderr: TextIO | None = None, + stdin: str | bytes | None = None, + cwd: Path | None = None, +) -> int: + """Run the default package-level ASP Python CLI.""" + + selected_stdout = sys.stdout if stdout is None else stdout + selected_stderr = sys.stderr if stderr is None else stderr + selected_cwd = Path.cwd() if cwd is None else cwd + if not args or args[0] in {"--help", "-h"}: + selected_stdout.write(help_text()) + return 0 + protocol_args = ProtocolArgs.parse(args) + if protocol_args is not None: + return run_protocol_cli( + protocol_args, + stdout=selected_stdout, + stderr=selected_stderr, + stdin="" if stdin is None else stdin, + cwd=selected_cwd, + ) + selected_stderr.write(f"unknown command: {args[0]}\n") + return 2 diff --git a/src/asp_python/_cli_agent.py b/src/asp_python/_cli_agent.py new file mode 100644 index 0000000..d5c31f8 --- /dev/null +++ b/src/asp_python/_cli_agent.py @@ -0,0 +1,102 @@ +"""Agent-facing guide and doctor rendering for the ASP Python CLI.""" + +from __future__ import annotations + +import json +from pathlib import Path + +from ._semantic_provider_doctor import semantic_provider_doctor_document + + +def render_agent_guide(project_root: Path) -> str: + project = str(project_root) + workspace = "--workspace " + return ( + "\n".join( + ( + f"[asp-python-guide] project={project}", + "|catalog provider=native-facts routes=syntax-locate,exact-source,callable-skeleton", + ( + "|route syntax-locate selectors=S:tree-sitter-query,Scope:owner-or-structural " + "returns=locator,capture,frontier cmd=asp search playbook " + "--language python --rg -n -e '' . " + "--tantivy 'title:^2 OR body:' --syntax python " + "'(function_definition name: (identifier) @function.name)'" + ), + f"|route exact-source selectors=R:exact-selector returns=source cmd=asp query playbook --language python --selector --projection source {workspace}", + f"|route callable-skeleton selectors=R:exact-callable-selector returns=callable-skeleton cmd=asp query playbook --language python --selector --projection callable-skeleton {workspace}", + "|cmd playbook=asp search playbook --language python --rg -n -e . --tantivy 'title:^2 OR body:'", + ( + "|cmd syntax-locate=asp search playbook --language python " + "--rg -n -e '' . --tantivy 'title:^2 OR body:' " + "--syntax python '(function_definition name: (identifier) @function.name)'" + ), + f"|cmd exact-source=asp query playbook --language python --selector --projection source {workspace}", + f"|cmd callable-skeleton=asp query playbook --language python --selector --projection callable-skeleton {workspace}", + "|cmd ast-patch=asp python ast-patch dry-run --packet ", + "|policy authority=asp-python-api trigger=pytest-plugin", + "|rule agent hook install/runtime is owned by asp", + ( + "|rule selector queries do not need a trailing project root; " + "--workspace is the independent workspace override" + ), + ( + "|rule syntax query ABI is compiled by asp; provider projects " + "native parser facts into tree-sitter-compatible captures" + ), + ( + "|rule syntax predicates supported=#eq?,#any-eq?,#any-of?," + "#match?,#any-match?,#not-eq?,#not-match? " + "unsupported=none unsupportedReported=true" + ), + "|rule exact query requires a parser-owned selector and an explicit source or callable-skeleton projection", + ( + "|rule displayLineRange/sourceLocatorHint are display hints; " + "execute structural selectors or owner/symbol routes, not line ranges" + ), + "|rule the root ASP Client owns the only public search surface; provider-local search views are removed", + "|rule native syntax facts remain provider-owned inputs to the root search playbook", + ( + "|rule use the asp python facade; run one command at a time; " + "no raw Python source reads" + ), + "|subagent give one |cmd line; require evidence/missing/next/risk", + ) + ) + + "\n" + ) + + +def render_agent_doctor(project_root: Path) -> str: + from . import _semantic_language_ids as ids + from ._semantic_language import python_semantic_language_registration + + registration = python_semantic_language_registration() + return ( + "\n".join( + ( + "[agent-doctor] " + f"status=ok protocol={ids.SEMANTIC_LANGUAGE_PROTOCOL_ID} " + f"registry=semantic-language-registry.v{ids.SEMANTIC_LANGUAGE_REGISTRY_VERSION}", + f"|project {project_root}", + ( + f"|language id={ids.PYTHON_LANGUAGE_ID} provider={ids.PYTHON_PROVIDER_ID} " + f"binary={ids.PYTHON_BINARY}" + ), + f"|namespace {ids.PYTHON_PROVIDER_NAMESPACE}", + f"|method {','.join(registration['methods'])}", + "|schema semantic-query-packet.v1", + ) + ) + + "\n" + ) + + +def render_agent_doctor_json(project_root: Path) -> str: + return ( + json.dumps( + semantic_provider_doctor_document(), + separators=(",", ":"), + ) + + "\n" + ) diff --git a/src/asp_python/_cli_args.py b/src/asp_python/_cli_args.py new file mode 100644 index 0000000..4d7b04e --- /dev/null +++ b/src/asp_python/_cli_args.py @@ -0,0 +1,220 @@ +"""Argument parsing helpers for the ASP Python CLI.""" + +from __future__ import annotations + +from dataclasses import dataclass +from pathlib import Path + +from ._tree_sitter_query_predicates import SyntaxQueryPredicate + + +@dataclass(slots=True) +class ProtocolArgs: + command: str + view: str | None = None + action: str | None = None + client: str | None = None + hook_event: str | None = None + query: str | None = None + item_query: str | None = None + project_root: Path | None = None + package_path: Path | None = None + workspace: bool = False + owner_path: str | None = None + dependency: str | None = None + selector: str | None = None + catalog: str | None = None + flow_lite_where: str | None = None + tree_sitter_query: str | None = None + asp_syntax_query_captures: tuple[str, ...] = () + asp_syntax_query_node_types: tuple[str, ...] = () + asp_syntax_query_fields: tuple[str, ...] = () + asp_syntax_query_predicates: tuple[SyntaxQueryPredicate, ...] = () + packet_path: str | None = None + query_set: tuple[str, ...] = () + pipes: tuple[str, ...] = () + json: bool = False + names_only: bool = False + source_version: str = "worktree" + render_mode: str | None = None + error: str | None = None + + @classmethod + def parse(cls, args: list[str] | tuple[str, ...]) -> ProtocolArgs | None: + command = args[0] if args else None + if command == "query": + return cls._parse_query(args[1:]) + if command == "agent": + return cls._parse_agent(args[1:]) + if command == "ast-patch": + return cls._parse_ast_patch(args[1:]) + return None + + @classmethod + def _parse_query(cls, args: list[str] | tuple[str, ...]) -> ProtocolArgs: + from ._cli_query_args import parse_query_args + + return parse_query_args(cls, args) + + @classmethod + def _parse_ast_patch(cls, args: list[str] | tuple[str, ...]) -> ProtocolArgs: + mode = args[0] if args else None + if mode in {"--help", "-h"}: + return cls("help") + if mode != "dry-run": + return cls("error", error="expected ast-patch dry-run") + + packet_path: str | None = None + positionals: list[Path] = [] + index = 1 + while index < len(args): + arg = args[index] + if arg == "--packet": + value = args[index + 1] if index + 1 < len(args) else None + if value is None or (value.startswith("-") and value != "-"): + return cls("error", error="--packet requires a path or -") + packet_path = value + index += 2 + continue + if arg.startswith("-"): + return cls("error", error=f"unknown ast-patch option: {arg}") + positionals.append(Path(arg)) + index += 1 + if packet_path is None: + return cls("error", error="--packet requires a path or -") + if len(positionals) > 1: + return cls("error", error="expected at most one PROJECT_ROOT argument") + return cls( + "ast-patch", + packet_path=packet_path, + project_root=positionals[0] if positionals else None, + ) + + @classmethod + def _parse_agent(cls, args: list[str] | tuple[str, ...]) -> ProtocolArgs: + action = args[0] if args else "doctor" + if action in {"install", "hook"}: + replacement = ( + "asp hook install --client codex" + if action == "install" + else "asp hook --client codex" + ) + return cls( + "error", + error=f"asp-python agent {action} moved to asp; use `{replacement}`", + ) + if action == "guide": + return cls._parse_agent_guide(args[1:]) + if action != "doctor": + return cls("error", error=f"unknown agent action: {action}") + client: str | None = None + hook_event: str | None = None + json_output = False + positionals: list[str] = [] + index = 1 + while index < len(args): + arg = args[index] + if arg == "--json": + json_output = True + elif arg == "--client": + value = _optional_arg(args, index + 1) + if value is None: + return cls("error", error="--client requires a client name") + if value != "codex": + return cls("error", error=f"unsupported agent client: {value}") + client = value + index += 1 + elif arg in {"--help", "-h"}: + continue + elif arg.startswith("-"): + return cls("error", error=f"unknown agent option: {arg}") + elif action == "hook" and hook_event is None: + hook_event = arg + else: + positionals.append(arg) + index += 1 + if len(positionals) > 1: + return cls("error", error="expected at most one PROJECT_ROOT argument") + return cls( + "agent", + action=action, + client=client, + hook_event=hook_event, + project_root=None if not positionals else Path(positionals[0]), + json=json_output, + ) + + @classmethod + def _parse_agent_guide(cls, args: list[str] | tuple[str, ...]) -> ProtocolArgs: + positionals: list[str] = [] + index = 0 + while index < len(args): + arg = args[index] + if arg == "--client": + value = _optional_arg(args, index + 1) + if value is None: + return cls("error", error="--client requires a client name") + index += 1 + elif arg in {"--help", "-h"}: + pass + elif arg.startswith("-"): + return cls("error", error=f"unknown agent option: {arg}") + else: + positionals.append(arg) + index += 1 + if len(positionals) > 1: + return cls("error", error="expected at most one PROJECT_ROOT argument") + return cls( + "agent", + action="guide", + project_root=None if not positionals else Path(positionals[0]), + ) + + +def help_text() -> str: + return ( + "asp-python — Python provider runtime and ASP Python\n\n" + "Usage:\n" + " asp search playbook --language python --rg -n -e . --tantivy 'title:^2 OR body:'\n" + " asp query playbook --language python --selector --projection --workspace \n" + " asp-python query --catalog flow-lite --where 'source.call=NAME sink.constructs=TYPE scope.fn=FUNCTION' [--json] [--workspace ]\n" + " asp-python ast-patch dry-run --packet \n" + " asp-python agent doctor [--json]\n" + " asp-python agent guide\n" + "\n" + "SEARCH\n" + " Search is owned by the root ASP Client. The single public surface is\n" + " `asp search playbook --language python`, which composes raw candidates, provider\n" + " native syntax, lexical ranking, and graph expansion. Provider-local\n" + " search views are intentionally unavailable.\n\n" + "QUERY\n" + " asp query playbook --language python --selector --projection source --workspace \n" + " Exact source materialization through ASP authority\n" + " asp query playbook --language python --selector --projection callable-skeleton --workspace \n" + " Typed callable skeleton materialization through ASP authority\n\n" + " query --catalog flow-lite --where 'source.call=NAME sink.constructs=TYPE scope.fn=FUNCTION'\n" + " Flow-lite ABI compatibility surface; Python executor is not enabled yet\n\n" + "AST PATCH\n" + " ast-patch dry-run --packet \n" + " Provider-native structural patch receipt; never mutates files\n\n" + "AGENT\n" + " agent doctor Print semantic-language provider readiness\n" + " agent doctor --json Semantic language registry document\n\n" + " agent guide Print provider role and playbook guidance\n\n" + " Hook install/runtime is owned by asp in the root toolchain.\n\n" + "\nEXAMPLES\n" + " asp search playbook --language python --rg -n -e PythonSemanticSearchOptions . --tantivy 'title:PythonSemanticSearchOptions^2 OR body:PythonSemanticSearchOptions'\n" + " asp query playbook --language python --selector 'python://src/asp_python/_cli.py#item/function/run_cli' --projection source --workspace .\n" + " asp-python query --catalog flow-lite --where 'source.call=payload sink.constructs=Action scope.fn=collect' .\n" + " asp-python agent doctor --json .\n" + " asp-python agent guide\n" + ) + + +def _optional_arg(args: list[str] | tuple[str, ...], index: int) -> str | None: + if index >= len(args): + return None + value = args[index] + if value.startswith("-"): + return None + return value diff --git a/src/python_lang_project_harness/_cli_ast_patch.py b/src/asp_python/_cli_ast_patch.py similarity index 97% rename from src/python_lang_project_harness/_cli_ast_patch.py rename to src/asp_python/_cli_ast_patch.py index 606ae26..e4e770b 100644 --- a/src/python_lang_project_harness/_cli_ast_patch.py +++ b/src/asp_python/_cli_ast_patch.py @@ -118,7 +118,7 @@ def _receipt( "failureKind": failure_kind, "failures": failures, "next": ( - "asp python query --selector " + "asp query playbook --language python --selector " f"--projection source --workspace {project_root}" ), } diff --git a/src/asp_python/_cli_protocol.py b/src/asp_python/_cli_protocol.py new file mode 100644 index 0000000..dee395f --- /dev/null +++ b/src/asp_python/_cli_protocol.py @@ -0,0 +1,105 @@ +"""Protocol command dispatch for the ASP Python CLI.""" + +from __future__ import annotations + +from pathlib import Path +from typing import TextIO + +from ._cli_agent import ( + render_agent_doctor, + render_agent_doctor_json, + render_agent_guide, +) +from ._cli_args import ProtocolArgs, help_text + + +def run_protocol_cli( + args: ProtocolArgs, + *, + stdout: TextIO, + stderr: TextIO, + stdin: str | bytes, + cwd: Path, +) -> int: + if args.command == "error": + stderr.write(f"{args.error}\n") + return 2 + if args.command == "help": + stdout.write(help_text()) + return 0 + project_root = _resolve_project_root(args, cwd) + if args.command == "agent": + return _run_agent_command(args, project_root=project_root, stdout=stdout) + if args.command == "ast-patch": + return _run_ast_patch_command( + args, project_root=project_root, stdout=stdout, stdin=stdin + ) + + try: + if args.command != "query": + raise ValueError("unsupported provider command") + return _run_query_protocol_command( + args, project_root=project_root, stdout=stdout + ) + except ValueError as error: + stderr.write(f"{error}\n") + return 3 + + +def _resolve_project_root(args: ProtocolArgs, cwd: Path) -> Path: + project_root = (cwd / args.project_root).resolve() if args.project_root else cwd + if args.package_path is not None: + return (project_root / args.package_path).resolve() + return project_root + + +def _run_agent_command( + args: ProtocolArgs, + *, + project_root: Path, + stdout: TextIO, +) -> int: + if args.action == "guide": + stdout.write(render_agent_guide(project_root)) + return 0 + if args.json: + stdout.write(render_agent_doctor_json(project_root)) + else: + stdout.write(render_agent_doctor(project_root)) + return 0 + + +def _run_ast_patch_command( + args: ProtocolArgs, + *, + project_root: Path, + stdout: TextIO, + stdin: str, +) -> int: + from ._cli_ast_patch import run_ast_patch_command + + return run_ast_patch_command( + args, project_root=project_root, stdout=stdout, stdin=stdin + ) + + +def _run_query_protocol_command( + args: ProtocolArgs, + *, + project_root: Path, + stdout: TextIO, +) -> int: + from ._cli_query import run_query_command + from ._rule_packs import resolve_asp_python_project_config + from ._runner import run_asp_python + + report = run_asp_python( + project_root, + config=resolve_asp_python_project_config(project_root, None, rule_packs=None), + ) + return run_query_command( + args, + report=report, + project_root=project_root, + stdout=stdout, + ) diff --git a/src/python_lang_project_harness/_cli_query.py b/src/asp_python/_cli_query.py similarity index 92% rename from src/python_lang_project_harness/_cli_query.py rename to src/asp_python/_cli_query.py index f58e87c..7e57713 100644 --- a/src/python_lang_project_harness/_cli_query.py +++ b/src/asp_python/_cli_query.py @@ -40,7 +40,7 @@ def run_query_command( return 0 raise ValueError( - "exact source projection is ASP-owned; use `asp python query " + "exact source projection is ASP-owned; use `asp query playbook --language python " "--selector --projection " "source|callable-skeleton --workspace `" ) diff --git a/src/python_lang_project_harness/_cli_query_arg_consume.py b/src/asp_python/_cli_query_arg_consume.py similarity index 100% rename from src/python_lang_project_harness/_cli_query_arg_consume.py rename to src/asp_python/_cli_query_arg_consume.py diff --git a/src/python_lang_project_harness/_cli_query_args.py b/src/asp_python/_cli_query_args.py similarity index 95% rename from src/python_lang_project_harness/_cli_query_args.py rename to src/asp_python/_cli_query_args.py index 5fe4cb4..673bbc8 100644 --- a/src/python_lang_project_harness/_cli_query_args.py +++ b/src/asp_python/_cli_query_args.py @@ -1,4 +1,4 @@ -"""Query command argument parsing for the Python harness CLI.""" +"""Query command argument parsing for the ASP Python CLI.""" from __future__ import annotations @@ -55,7 +55,7 @@ def _query_args_result( return args_type( "error", error=( - "exact source projection is ASP-owned; use `asp python query " + "exact source projection is ASP-owned; use `asp query playbook --language python " "--selector --projection " "source|callable-skeleton --workspace `" ), diff --git a/src/python_lang_project_harness/_cli_query_flow_lite_args.py b/src/asp_python/_cli_query_flow_lite_args.py similarity index 100% rename from src/python_lang_project_harness/_cli_query_flow_lite_args.py rename to src/asp_python/_cli_query_flow_lite_args.py diff --git a/src/python_lang_project_harness/_cli_query_hook_args.py b/src/asp_python/_cli_query_hook_args.py similarity index 93% rename from src/python_lang_project_harness/_cli_query_hook_args.py rename to src/asp_python/_cli_query_hook_args.py index 8fba94b..7b64fed 100644 --- a/src/python_lang_project_harness/_cli_query_hook_args.py +++ b/src/asp_python/_cli_query_hook_args.py @@ -1,4 +1,4 @@ -"""Hook query option helpers for the Python harness CLI.""" +"""Hook query option helpers for the ASP Python CLI.""" from __future__ import annotations @@ -6,7 +6,7 @@ def normalize_query_surfaces(value: str | None) -> tuple[tuple[str, ...], str | None]: - """Normalize shared hook query surfaces into asp-python search pipes.""" + """Normalize shared hook query surfaces into Python exact-query selectors.""" if value is None: return (), "--surface requires owner,tests style surfaces" surfaces = tuple(surface.strip() for surface in value.split(",") if surface.strip()) diff --git a/src/python_lang_project_harness/_cli_query_predicates.py b/src/asp_python/_cli_query_predicates.py similarity index 100% rename from src/python_lang_project_harness/_cli_query_predicates.py rename to src/asp_python/_cli_query_predicates.py diff --git a/src/python_lang_project_harness/_cli_query_tree_sitter_args.py b/src/asp_python/_cli_query_tree_sitter_args.py similarity index 100% rename from src/python_lang_project_harness/_cli_query_tree_sitter_args.py rename to src/asp_python/_cli_query_tree_sitter_args.py diff --git a/src/python_lang_project_harness/_constants.py b/src/asp_python/_constants.py similarity index 92% rename from src/python_lang_project_harness/_constants.py rename to src/asp_python/_constants.py index 4a67178..2c4beac 100644 --- a/src/python_lang_project_harness/_constants.py +++ b/src/asp_python/_constants.py @@ -1,4 +1,4 @@ -"""Shared constants for the Python language harness.""" +"""Shared constants for the ASP Python.""" from __future__ import annotations diff --git a/src/python_lang_project_harness/_dependency_topology.py b/src/asp_python/_dependency_topology.py similarity index 100% rename from src/python_lang_project_harness/_dependency_topology.py rename to src/asp_python/_dependency_topology.py diff --git a/src/python_lang_project_harness/_dev_command_log.py b/src/asp_python/_dev_command_log.py similarity index 100% rename from src/python_lang_project_harness/_dev_command_log.py rename to src/asp_python/_dev_command_log.py diff --git a/src/python_lang_project_harness/_dev_command_log_command.py b/src/asp_python/_dev_command_log_command.py similarity index 72% rename from src/python_lang_project_harness/_dev_command_log_command.py rename to src/asp_python/_dev_command_log_command.py index d269f08..d2f2f8c 100644 --- a/src/python_lang_project_harness/_dev_command_log_command.py +++ b/src/asp_python/_dev_command_log_command.py @@ -22,21 +22,6 @@ "--view", } -SEARCH_PIPES = { - "dependency", - "deps", - "docs", - "features", - "lexical", - "items", - "owner", - "owners", - "prime", - "symbol", - "tests", - "workspace", -} - @dataclass(frozen=True, slots=True) class NormalizedCommand: @@ -81,20 +66,10 @@ def normalize_command(argv: list[str]) -> NormalizedCommand: query_set_count = sum( 1 for arg in args if arg == "--query" or arg.startswith("--query=") ) - pipes = tuple(sorted({normalize_token(arg) for arg in args} & SEARCH_PIPES)) - view = ( - normalize_token(first_positional_after(args, namespace_index) or "") - if namespace == "search" - else None - ) - if view == "unknown": - view = None - method = command_method(namespace, view, args, namespace_index) - query = option_value(args, "--query") or first_query_positional( - args, - namespace_index, - view, - ) + pipes: tuple[str, ...] = () + view = None + method = command_method(namespace, args, namespace_index) + query = option_value(args, "--query") return NormalizedCommand( namespace=namespace, method=method, @@ -124,12 +99,9 @@ def command_payload(command: NormalizedCommand) -> dict[str, Any]: def command_method( namespace: str, - view: str | None, args: list[str], namespace_index: int, ) -> str: - if namespace == "search" and view is not None: - return f"search/{view}" if namespace == "agent": subcommand = first_positional_after(args, namespace_index) or "" return f"agent/{normalize_token(subcommand)}" @@ -150,32 +122,6 @@ def first_positional_after(args: list[str], start: int) -> str | None: return None -def first_query_positional( - args: list[str], - namespace_index: int, - view: str | None, -) -> str | None: - skip_next = False - skipped_view = view is None - for arg in args[max(0, namespace_index + 1) :]: - if skip_next: - skip_next = False - continue - if option_takes_value(arg): - skip_next = "=" not in arg - continue - if arg.startswith("-"): - continue - token = normalize_token(arg) - if not skipped_view and token == view: - skipped_view = True - continue - if token in SEARCH_PIPES: - continue - return arg - return None - - def option_value(args: list[str], name: str) -> str | None: for index, arg in enumerate(args): if arg == name: diff --git a/src/python_lang_project_harness/_dev_command_log_context.py b/src/asp_python/_dev_command_log_context.py similarity index 100% rename from src/python_lang_project_harness/_dev_command_log_context.py rename to src/asp_python/_dev_command_log_context.py diff --git a/src/python_lang_project_harness/_discovery.py b/src/asp_python/_discovery.py similarity index 84% rename from src/python_lang_project_harness/_discovery.py rename to src/asp_python/_discovery.py index 3321b3d..456180b 100644 --- a/src/python_lang_project_harness/_discovery.py +++ b/src/asp_python/_discovery.py @@ -1,4 +1,4 @@ -"""Python project path discovery for embedded harness runs.""" +"""Python project path discovery for embedded ASP Python runs.""" from __future__ import annotations @@ -6,7 +6,7 @@ from typing import TYPE_CHECKING from ._constants import IGNORED_DIR_NAMES, INCLUDE_HIDDEN_DIR_NAMES -from ._model import PythonProjectHarnessScope +from ._model import AspPythonProjectScope from ._project_metadata import read_python_project_metadata if TYPE_CHECKING: @@ -55,11 +55,8 @@ def _iter_python_file_candidates( continue if path.is_dir(): candidates.extend( - candidate - for candidate in path.rglob("*.py") - if is_scannable_python_file( - candidate, - scan_root=path, + _iter_python_directory_candidates( + path, ignored_dir_names=ignored_dir_names, include_hidden_dir_names=include_hidden_dir_names, ) @@ -67,7 +64,40 @@ def _iter_python_file_candidates( return tuple(candidates) -def python_project_harness_paths( +def _iter_python_directory_candidates( + scan_root: Path, + *, + ignored_dir_names: frozenset[str], + include_hidden_dir_names: frozenset[str], +) -> tuple[Path, ...]: + """Walk a root without descending into ignored project environments.""" + + candidates: list[Path] = [] + for directory, dir_names, file_names in scan_root.walk(): + dir_names[:] = sorted( + name + for name in dir_names + if not _ignored_path_part( + name, + ignored_dir_names, + include_hidden_dir_names, + ) + ) + for file_name in sorted(file_names): + if not file_name.endswith(".py"): + continue + candidate = directory / file_name + if is_scannable_python_file( + candidate, + scan_root=scan_root, + ignored_dir_names=ignored_dir_names, + include_hidden_dir_names=include_hidden_dir_names, + ): + candidates.append(candidate) + return tuple(candidates) + + +def asp_python_paths( project_root: str | Path, *, include_tests: bool = True, @@ -75,9 +105,9 @@ def python_project_harness_paths( test_dir_names: Sequence[str] = ("tests",), extra_path_names: Sequence[str] = (), ) -> tuple[Path, ...]: - """Return project scan paths for embedded pytest harness checks.""" + """Return project scan paths for embedded ASP Python pytest checks.""" - return python_project_harness_scope( + return asp_python_scope( project_root, include_tests=include_tests, source_dir_names=source_dir_names, @@ -86,7 +116,7 @@ def python_project_harness_paths( ).monitored_paths -def python_project_harness_scope( +def asp_python_scope( project_root: str | Path, *, include_tests: bool = True, @@ -95,7 +125,7 @@ def python_project_harness_scope( extra_path_names: Sequence[str] = (), ignored_dir_names: Iterable[str] | None = None, include_hidden_dir_names: Iterable[str] | None = None, -) -> PythonProjectHarnessScope: +) -> AspPythonProjectScope: """Return the default project-wide monitoring scope.""" root = Path(project_root) @@ -124,7 +154,7 @@ def python_project_harness_scope( ignored_dir_names=ignored_names, include_hidden_dir_names=included_hidden_names, ) - return PythonProjectHarnessScope( + return AspPythonProjectScope( project_root=root, project_metadata=metadata, project_paths=project_paths, @@ -213,7 +243,7 @@ def is_scannable_python_file( ignored_dir_names: frozenset[str], include_hidden_dir_names: frozenset[str] = INCLUDE_HIDDEN_DIR_NAMES, ) -> bool: - """Return whether a Python file belongs to the harness-owned scan scope.""" + """Return whether a Python file belongs to the ASP Python-owned scan scope.""" relative_parts = _scan_relative_parts(path, scan_root) return not any( diff --git a/src/python_lang_project_harness/_exact_projection_model.py b/src/asp_python/_exact_projection_model.py similarity index 76% rename from src/python_lang_project_harness/_exact_projection_model.py rename to src/asp_python/_exact_projection_model.py index a9d6fd6..33a7be0 100644 --- a/src/python_lang_project_harness/_exact_projection_model.py +++ b/src/asp_python/_exact_projection_model.py @@ -21,6 +21,7 @@ class ExactSelector: owner_path: str kind: str symbol: str + scopes: tuple[tuple[str, str, str], ...] segment_kind: str | None segment_identity: str | None @@ -43,8 +44,17 @@ def parse_selector(selector: str) -> ExactSelector: raise ValueError("exact selector must include owner and item fragment") root_fragment, segment_separator, descendant = fragment.partition("/segment/") parts = root_fragment.split("/") - if len(parts) < 3 or parts[0] != "item": + if len(parts) < 3 or parts[0] != "item" or not all(parts[:3]): raise ValueError("exact selector item fragment is invalid") + scope_parts = parts[3:] + scopes: list[tuple[str, str, str]] = [] + if len(scope_parts) % 4 != 0: + raise ValueError("exact selector item scope is invalid") + for offset in range(0, len(scope_parts), 4): + scope = scope_parts[offset : offset + 4] + if scope[0] != "scope" or not all(scope[1:]): + raise ValueError("exact selector item scope is invalid") + scopes.append((scope[1], scope[2], unquote(scope[3]))) root = f"python://{owner_path}#{root_fragment}" segment_kind = None segment_identity = None @@ -57,8 +67,9 @@ def parse_selector(selector: str) -> ExactSelector: requested=selector, root=root, owner_path=owner_path, - kind=parts[-2], - symbol=unquote(parts[-1]), + kind=parts[1], + symbol=unquote(parts[2]), + scopes=tuple(scopes), segment_kind=segment_kind, segment_identity=segment_identity, ) @@ -69,9 +80,21 @@ def find_function( ) -> ast.FunctionDef | ast.AsyncFunctionDef: if selector.kind not in {"function", "method"}: raise ValueError("callable projection requires function or method selector") + search_root = tree + for _role, owner_kind, owner_name in selector.scopes: + if owner_kind != "type": + raise ValueError("exact callable scope owner kind is unsupported") + owners = [ + node + for node in ast.iter_child_nodes(search_root) + if isinstance(node, ast.ClassDef) and node.name == owner_name + ] + if len(owners) != 1: + raise ValueError("exact callable scope owner is missing or ambiguous") + search_root = owners[0] matches = [ node - for node in ast.walk(tree) + for node in ast.walk(search_root) if isinstance(node, (ast.FunctionDef, ast.AsyncFunctionDef)) and node.name == selector.symbol ] diff --git a/src/python_lang_project_harness/_exact_source_projection.py b/src/asp_python/_exact_source_projection.py similarity index 94% rename from src/python_lang_project_harness/_exact_source_projection.py rename to src/asp_python/_exact_source_projection.py index bda9e23..f52b9e7 100644 --- a/src/python_lang_project_harness/_exact_source_projection.py +++ b/src/asp_python/_exact_source_projection.py @@ -141,24 +141,22 @@ def _projection_packet( "ownerPath": selector.owner_path, "projectionMode": projection_kind, "requestedStructuralSelector": selector.requested, - "resolutionState": "resolved", "structuralSelector": selector.requested, + "sourceContentDigest": required_text(request, "sourceDigest"), + "sourceByteStart": byte_start, + "sourceByteEnd": max(byte_start, byte_end - 1), "normalizedParserFacts": { - "parserKind": "python-ast", "itemKind": selector.kind, "itemName": selector.symbol, - **( + "scopes": [ { - "segmentKind": selector.segment_kind, - "segmentIdentity": selector.segment_identity, + "role": role, + "ownerKind": owner_kind, + "ownerName": owner_name, } - if selector.segment_kind is not None - else {} - ), + for role, owner_kind, owner_name in selector.scopes + ], }, - "sourceContentDigest": required_text(request, "sourceDigest"), - "sourceByteStart": byte_start, - "sourceByteEnd": max(byte_start, byte_end - 1), } if projection_text is not None: packet["projectionText"] = projection_text diff --git a/src/python_lang_project_harness/_flow_lite_query.py b/src/asp_python/_flow_lite_query.py similarity index 100% rename from src/python_lang_project_harness/_flow_lite_query.py rename to src/asp_python/_flow_lite_query.py diff --git a/src/python_lang_project_harness/_flow_lite_query_model.py b/src/asp_python/_flow_lite_query_model.py similarity index 100% rename from src/python_lang_project_harness/_flow_lite_query_model.py rename to src/asp_python/_flow_lite_query_model.py diff --git a/src/python_lang_project_harness/_flow_lite_query_packet.py b/src/asp_python/_flow_lite_query_packet.py similarity index 100% rename from src/python_lang_project_harness/_flow_lite_query_packet.py rename to src/asp_python/_flow_lite_query_packet.py diff --git a/src/python_lang_project_harness/_flow_lite_query_projector.py b/src/asp_python/_flow_lite_query_projector.py similarity index 100% rename from src/python_lang_project_harness/_flow_lite_query_projector.py rename to src/asp_python/_flow_lite_query_projector.py diff --git a/src/python_lang_project_harness/_model.py b/src/asp_python/_model.py similarity index 88% rename from src/python_lang_project_harness/_model.py rename to src/asp_python/_model.py index b1f1b11..213359f 100644 --- a/src/python_lang_project_harness/_model.py +++ b/src/asp_python/_model.py @@ -1,4 +1,4 @@ -"""Data model for embedded Python language harness reports.""" +"""Data model for embedded ASP Python reports.""" from __future__ import annotations @@ -36,7 +36,7 @@ @dataclass(frozen=True, slots=True) class PythonRulePackDescriptor: - """Stable metadata for one Python language harness rule pack.""" + """Stable metadata for one ASP Python rule pack.""" id: str version: str @@ -52,8 +52,8 @@ def to_dict(self) -> dict[str, object]: @dataclass(frozen=True, slots=True) -class PythonHarnessRule: - """Compact metadata for one deterministic harness rule.""" +class AspPythonRule: + """Compact metadata for one deterministic ASP Python rule.""" rule_id: str pack_id: str @@ -71,8 +71,8 @@ def to_dict(self) -> dict[str, object]: @dataclass(frozen=True, slots=True) -class PythonHarnessFinding: - """One deterministic Python harness finding.""" +class AspPythonFinding: + """One deterministic ASP Python finding.""" rule_id: str pack_id: str @@ -94,8 +94,8 @@ def to_dict(self) -> dict[str, object]: @dataclass(frozen=True, slots=True) -class PythonProjectHarnessScope: - """Concrete project paths monitored by an embedded Python harness run.""" +class AspPythonProjectScope: + """Concrete project paths monitored by an embedded ASP Python run.""" project_root: Path project_metadata: PythonProjectMetadata | None = None @@ -153,21 +153,21 @@ def _dedupe_paths(paths: Iterable[Path]) -> tuple[Path, ...]: return tuple(deduped) -class PythonLangRulePack(Protocol): - """Protocol for Python language harness rule packs.""" +class AspPythonRulePack(Protocol): + """Protocol for ASP Python rule packs.""" pack_id: str def descriptor(self) -> PythonRulePackDescriptor: """Return stable metadata for this rule pack.""" - def evaluate(self, report: PythonModuleReport) -> Iterable[PythonHarnessFinding]: + def evaluate(self, report: PythonModuleReport) -> Iterable[AspPythonFinding]: """Evaluate one parsed module report.""" @dataclass(frozen=True, slots=True) -class PythonHarnessConfig: - """Configuration for an embedded Python language harness run.""" +class AspPythonConfig: + """Configuration for an embedded ASP Python run.""" ignored_dir_names: frozenset[str] = IGNORED_DIR_NAMES include_hidden_dir_names: frozenset[str] = INCLUDE_HIDDEN_DIR_NAMES @@ -183,12 +183,12 @@ class PythonHarnessConfig: verification_policy: PythonVerificationPolicy = field( default_factory=PythonVerificationPolicy ) - rule_packs: tuple[PythonLangRulePack, ...] | None = None + rule_packs: tuple[AspPythonRulePack, ...] | None = None def with_verification_policy( self, policy: PythonVerificationPolicy, - ) -> PythonHarnessConfig: + ) -> AspPythonConfig: """Return a config with an explicit verification policy.""" return replace(self, verification_policy=policy) @@ -196,7 +196,7 @@ def with_verification_policy( def with_verification_profile_hint( self, hint: PythonVerificationProfileHint, - ) -> PythonHarnessConfig: + ) -> AspPythonConfig: """Return a config with one verification profile hint appended.""" return replace( @@ -207,7 +207,7 @@ def with_verification_profile_hint( def with_verification_dependency_signal( self, signal: PythonVerificationDependencySignal, - ) -> PythonHarnessConfig: + ) -> AspPythonConfig: """Return a config with one dependency-to-responsibility signal.""" return replace( @@ -218,7 +218,7 @@ def with_verification_dependency_signal( def with_verification_receipt( self, receipt: PythonVerificationReceipt, - ) -> PythonHarnessConfig: + ) -> AspPythonConfig: """Return a config with one verification receipt appended.""" return replace( @@ -229,7 +229,7 @@ def with_verification_receipt( def with_verification_waiver( self, waiver: PythonVerificationWaiver, - ) -> PythonHarnessConfig: + ) -> AspPythonConfig: """Return a config with one verification waiver appended.""" return replace( @@ -241,7 +241,7 @@ def with_verification_task_contract( self, kind: PythonVerificationTaskKind, contract: PythonVerificationTaskContract, - ) -> PythonHarnessConfig: + ) -> AspPythonConfig: """Return a config with one verification task contract override.""" return replace( @@ -256,7 +256,7 @@ def with_verification_skill_binding( self, kind: PythonVerificationTaskKind, binding: PythonVerificationSkillBinding, - ) -> PythonHarnessConfig: + ) -> AspPythonConfig: """Return a config with one verification skill binding.""" return replace( @@ -270,7 +270,7 @@ def with_verification_skill_binding( def with_verification_skill_descriptor( self, descriptor: PythonVerificationSkillDescriptor, - ) -> PythonHarnessConfig: + ) -> AspPythonConfig: """Return a config with one verification skill descriptor.""" return replace( @@ -282,16 +282,16 @@ def with_verification_skill_descriptor( @dataclass(frozen=True, slots=True) -class PythonHarnessReport: - """Aggregated Python language harness report.""" +class AspPythonReport: + """Aggregated ASP Python report.""" modules: tuple[PythonModuleReport, ...] - findings: tuple[PythonHarnessFinding, ...] + findings: tuple[AspPythonFinding, ...] root_paths: tuple[str, ...] blocking_severities: frozenset[PythonDiagnosticSeverity] = ( DEFAULT_BLOCKING_SEVERITIES ) - project_resolution: PythonProjectHarnessScope | None = None + project_resolution: AspPythonProjectScope | None = None disabled_rule_ids: frozenset[str] = frozenset() blocking_rule_ids: frozenset[str] = frozenset() @@ -339,7 +339,7 @@ def blocking_findings( self, *, severities: frozenset[PythonDiagnosticSeverity] | None = None, - ) -> tuple[PythonHarnessFinding, ...]: + ) -> tuple[AspPythonFinding, ...]: """Return findings that should block a pytest assertion.""" blocking_severities = ( @@ -356,7 +356,7 @@ def advisory_findings( self, *, severities: frozenset[PythonDiagnosticSeverity] | None = None, - ) -> tuple[PythonHarnessFinding, ...]: + ) -> tuple[AspPythonFinding, ...]: """Return non-blocking advisory findings for agent-guided repair.""" if severities is None: @@ -380,10 +380,10 @@ def assert_clean( """Raise `AssertionError` when blocking findings are present.""" if self.blocking_findings(severities=severities): - from ._render import render_python_lang_harness + from ._render import render_asp_python_report raise AssertionError( - render_python_lang_harness( + render_asp_python_report( self, severities=severities, include_advice=include_advice, diff --git a/src/python_lang_project_harness/_modern_design.py b/src/asp_python/_modern_design.py similarity index 89% rename from src/python_lang_project_harness/_modern_design.py rename to src/asp_python/_modern_design.py index 657bf39..f4b2413 100644 --- a/src/python_lang_project_harness/_modern_design.py +++ b/src/asp_python/_modern_design.py @@ -11,7 +11,7 @@ python_module_is_package_init, ) -from ._model import PythonHarnessFinding, PythonRulePackDescriptor +from ._model import AspPythonFinding, PythonRulePackDescriptor from ._modern_design_catalog import ( MODERN_DESIGN_PACK_ID, PY_MOD_R001, @@ -43,13 +43,13 @@ def descriptor(self) -> PythonRulePackDescriptor: default_mode="blocking", ) - def evaluate(self, report: PythonModuleReport) -> Iterable[PythonHarnessFinding]: + def evaluate(self, report: PythonModuleReport) -> Iterable[AspPythonFinding]: """Evaluate modern Python design rules for one parsed module report.""" if not report.is_valid: return () - findings: list[PythonHarnessFinding] = [] + findings: list[AspPythonFinding] = [] findings.extend(_wildcard_import_findings(report, self.pack_id)) findings.extend(_bare_print_findings(report, self.pack_id)) findings.extend(_debug_breakpoint_findings(report, self.pack_id)) @@ -60,15 +60,15 @@ def evaluate(self, report: PythonModuleReport) -> Iterable[PythonHarnessFinding] def _wildcard_import_findings( report: PythonModuleReport, pack_id: str, -) -> tuple[PythonHarnessFinding, ...]: - findings: list[PythonHarnessFinding] = [] +) -> tuple[AspPythonFinding, ...]: + findings: list[AspPythonFinding] = [] rule = modern_design_rule(PY_MOD_R001) for import_record in report.imports: if not import_record.is_wildcard: continue module = "." * import_record.level + (import_record.module or "") findings.append( - PythonHarnessFinding( + AspPythonFinding( rule_id=rule.rule_id, pack_id=pack_id, severity=rule.severity, @@ -87,17 +87,17 @@ def _wildcard_import_findings( def _bare_print_findings( report: PythonModuleReport, pack_id: str, -) -> tuple[PythonHarnessFinding, ...]: +) -> tuple[AspPythonFinding, ...]: if not _module_has_project_surface(report): return () - findings: list[PythonHarnessFinding] = [] + findings: list[AspPythonFinding] = [] rule = modern_design_rule(PY_MOD_R002) for call in report.calls: if call.effect != PythonCallEffect.STANDARD_OUTPUT: continue findings.append( - PythonHarnessFinding( + AspPythonFinding( rule_id=rule.rule_id, pack_id=pack_id, severity=rule.severity, @@ -116,17 +116,17 @@ def _bare_print_findings( def _debug_breakpoint_findings( report: PythonModuleReport, pack_id: str, -) -> tuple[PythonHarnessFinding, ...]: +) -> tuple[AspPythonFinding, ...]: if not _module_has_project_surface(report): return () - findings: list[PythonHarnessFinding] = [] + findings: list[AspPythonFinding] = [] rule = modern_design_rule(PY_MOD_R004) for call in report.calls: if call.effect != PythonCallEffect.DEBUG_BREAKPOINT: continue findings.append( - PythonHarnessFinding( + AspPythonFinding( rule_id=rule.rule_id, pack_id=pack_id, severity=rule.severity, @@ -145,7 +145,7 @@ def _debug_breakpoint_findings( def _facade_all_findings( report: PythonModuleReport, pack_id: str, -) -> tuple[PythonHarnessFinding, ...]: +) -> tuple[AspPythonFinding, ...]: if not report.path or not python_module_is_package_init(report.path): return () if not _has_facade_import(report) or report.export_contract.is_static: @@ -153,7 +153,7 @@ def _facade_all_findings( rule = modern_design_rule(PY_MOD_R003) return ( - PythonHarnessFinding( + AspPythonFinding( rule_id=rule.rule_id, pack_id=pack_id, severity=rule.severity, diff --git a/src/python_lang_project_harness/_modern_design_catalog.py b/src/asp_python/_modern_design_catalog.py similarity index 88% rename from src/python_lang_project_harness/_modern_design_catalog.py rename to src/asp_python/_modern_design_catalog.py index 4f895a3..39c15cb 100644 --- a/src/python_lang_project_harness/_modern_design_catalog.py +++ b/src/asp_python/_modern_design_catalog.py @@ -6,7 +6,7 @@ from python_lang_parser._diagnostic_model import PythonDiagnosticSeverity -from ._model import PythonHarnessRule +from ._model import AspPythonRule MODERN_DESIGN_PACK_ID = "python.modern_design" PY_MOD_R001 = "PY-MOD-R001" @@ -19,7 +19,7 @@ "domain": "modern-python", } _RULES = ( - PythonHarnessRule( + AspPythonRule( rule_id=PY_MOD_R001, pack_id=MODERN_DESIGN_PACK_ID, severity=PythonDiagnosticSeverity.WARNING, @@ -27,7 +27,7 @@ requirement="Import explicit names instead of `*` in project modules.", labels=dict(_RULE_LABELS), ), - PythonHarnessRule( + AspPythonRule( rule_id=PY_MOD_R002, pack_id=MODERN_DESIGN_PACK_ID, severity=PythonDiagnosticSeverity.WARNING, @@ -35,7 +35,7 @@ requirement="Use a logger, returned value, or explicit test assertion instead of bare `print` in library modules.", labels=dict(_RULE_LABELS), ), - PythonHarnessRule( + AspPythonRule( rule_id=PY_MOD_R003, pack_id=MODERN_DESIGN_PACK_ID, severity=PythonDiagnosticSeverity.WARNING, @@ -43,7 +43,7 @@ requirement="Declare `__all__` beside package facade imports so public exports stay explicit.", labels=dict(_RULE_LABELS), ), - PythonHarnessRule( + AspPythonRule( rule_id=PY_MOD_R004, pack_id=MODERN_DESIGN_PACK_ID, severity=PythonDiagnosticSeverity.WARNING, @@ -55,13 +55,13 @@ _RULE_BY_ID = {rule.rule_id: rule for rule in _RULES} -def python_modern_design_rules() -> tuple[PythonHarnessRule, ...]: +def python_modern_design_rules() -> tuple[AspPythonRule, ...]: """Return compact metadata for the default modern-design rules.""" return tuple(replace(rule, labels=dict(rule.labels)) for rule in _RULES) -def modern_design_rule(rule_id: str) -> PythonHarnessRule: +def modern_design_rule(rule_id: str) -> AspPythonRule: """Return one modern-design rule descriptor by stable rule id.""" return _RULE_BY_ID[rule_id] diff --git a/src/python_lang_project_harness/_modularity.py b/src/asp_python/_modularity.py similarity index 90% rename from src/python_lang_project_harness/_modularity.py rename to src/asp_python/_modularity.py index f7ec353..5121d0c 100644 --- a/src/python_lang_project_harness/_modularity.py +++ b/src/asp_python/_modularity.py @@ -14,8 +14,8 @@ ) from ._model import ( - PythonHarnessFinding, - PythonHarnessRule, + AspPythonFinding, + AspPythonRule, PythonRulePackDescriptor, ) from ._modularity_signals import ( @@ -29,7 +29,7 @@ from python_lang_parser import PythonModuleReport, PythonSymbol - from ._model import PythonProjectHarnessScope + from ._model import AspPythonProjectScope MODULARITY_PACK_ID = "python.modularity" PY_MOD_R006 = "PY-MOD-R006" @@ -40,7 +40,7 @@ "domain": "modularity", } _RULES = ( - PythonHarnessRule( + AspPythonRule( rule_id=PY_MOD_R006, pack_id=MODULARITY_PACK_ID, severity=PythonDiagnosticSeverity.WARNING, @@ -48,7 +48,7 @@ requirement="Split large multi-responsibility Python modules into focused modules behind an explicit package facade.", labels=dict(_RULE_LABELS), ), - PythonHarnessRule( + AspPythonRule( rule_id=PY_MOD_R007, pack_id=MODULARITY_PACK_ID, severity=PythonDiagnosticSeverity.WARNING, @@ -76,7 +76,7 @@ def descriptor(self) -> PythonRulePackDescriptor: default_mode="blocking", ) - def evaluate(self, report: PythonModuleReport) -> Iterable[PythonHarnessFinding]: + def evaluate(self, report: PythonModuleReport) -> Iterable[AspPythonFinding]: """Evaluate Python module-shape rules for one parsed module report.""" if not report.is_valid: @@ -85,15 +85,15 @@ def evaluate(self, report: PythonModuleReport) -> Iterable[PythonHarnessFinding] def evaluate_project_modules( self, - scope: PythonProjectHarnessScope, + scope: AspPythonProjectScope, modules: Sequence[PythonModuleReport], - ) -> Iterable[PythonHarnessFinding]: + ) -> Iterable[AspPythonFinding]: """Evaluate package-tree modularity rules over a parsed project.""" return _reasoning_tree_findings(scope, modules, self.pack_id) -def python_modularity_rules() -> tuple[PythonHarnessRule, ...]: +def python_modularity_rules() -> tuple[AspPythonRule, ...]: """Return compact metadata for the default Python modularity rules.""" return tuple(replace(rule, labels=dict(rule.labels)) for rule in _RULES) @@ -102,7 +102,7 @@ def python_modularity_rules() -> tuple[PythonHarnessRule, ...]: def _file_modularity_findings( report: PythonModuleReport, pack_id: str, -) -> tuple[PythonHarnessFinding, ...]: +) -> tuple[AspPythonFinding, ...]: shape = report.shape if shape is None: return () @@ -116,7 +116,7 @@ def _file_modularity_findings( rule = _rule(PY_MOD_R006) path = Path(report.path or "") return ( - PythonHarnessFinding( + AspPythonFinding( rule_id=rule.rule_id, pack_id=pack_id, severity=rule.severity, @@ -143,10 +143,10 @@ def _file_modularity_findings( def _reasoning_tree_findings( - scope: PythonProjectHarnessScope, + scope: AspPythonProjectScope, modules: Sequence[PythonModuleReport], pack_id: str, -) -> tuple[PythonHarnessFinding, ...]: +) -> tuple[AspPythonFinding, ...]: facts = python_reasoning_tree_facts( modules, import_roots=_reasoning_tree_import_roots(scope), @@ -157,12 +157,12 @@ def _reasoning_tree_findings( modules_by_path = { module.path: module for module in modules if module.path is not None } - findings: list[PythonHarnessFinding] = [] + findings: list[AspPythonFinding] = [] for shadow in facts.shadowed_module_sources: package_module = modules_by_path.get(shadow.package_init_path) namespace = ".".join(shadow.namespace) findings.append( - PythonHarnessFinding( + AspPythonFinding( rule_id=rule.rule_id, pack_id=pack_id, severity=rule.severity, @@ -183,7 +183,7 @@ def _reasoning_tree_findings( return tuple(findings) -def _reasoning_tree_import_roots(scope: PythonProjectHarnessScope) -> tuple[Path, ...]: +def _reasoning_tree_import_roots(scope: AspPythonProjectScope) -> tuple[Path, ...]: if scope.source_paths: return scope.source_paths return scope.monitored_paths @@ -234,5 +234,5 @@ def _is_data_model_base(base_class: str) -> bool: } -def _rule(rule_id: str) -> PythonHarnessRule: +def _rule(rule_id: str) -> AspPythonRule: return _RULE_BY_ID[rule_id] diff --git a/src/python_lang_project_harness/_modularity_signals.py b/src/asp_python/_modularity_signals.py similarity index 100% rename from src/python_lang_project_harness/_modularity_signals.py rename to src/asp_python/_modularity_signals.py diff --git a/src/python_lang_project_harness/_project_config.py b/src/asp_python/_project_config.py similarity index 96% rename from src/python_lang_project_harness/_project_config.py rename to src/asp_python/_project_config.py index 0301f37..96f9ed7 100644 --- a/src/python_lang_project_harness/_project_config.py +++ b/src/asp_python/_project_config.py @@ -1,4 +1,4 @@ -"""Project-local pyproject configuration for Python harness policy.""" +"""Project-local pyproject configuration for ASP Python policy.""" from __future__ import annotations @@ -9,7 +9,7 @@ from python_lang_parser._diagnostic_model import PythonDiagnosticSeverity -from ._model import PythonHarnessConfig +from ._model import AspPythonConfig from .verification import ( PythonOwnerResponsibility, PythonVerificationDependencySignal, @@ -25,18 +25,18 @@ PythonVerificationWaiver, ) -_TOOL_TABLE_NAME = "python-lang-project-harness" +_TOOL_TABLE_NAME = "asp-python" -def read_python_project_harness_config( +def read_asp_python_config( project_root: str | Path, -) -> PythonHarnessConfig | None: - """Read `[tool.python-lang-project-harness]` from `pyproject.toml`.""" +) -> AspPythonConfig | None: + """Read `[tool.asp-python]` from `pyproject.toml`.""" table = _read_project_config_table(Path(project_root) / "pyproject.toml") if not table: return None - return PythonHarnessConfig(**_harness_config_kwargs(table)) + return AspPythonConfig(**_asp_python_config_kwargs(table)) def read_pyproject_payload(pyproject_path: Path) -> dict[str, Any]: @@ -53,9 +53,9 @@ def read_pyproject_payload(pyproject_path: Path) -> dict[str, Any]: def apply_asp_project_discovery_config( project_root: str | Path, - config: PythonHarnessConfig, -) -> PythonHarnessConfig: - """Merge nearest `asp.toml` discovery settings into a harness config.""" + config: AspPythonConfig, +) -> AspPythonConfig: + """Merge nearest `asp.toml` discovery settings into a ASP Python config.""" table = _read_asp_discovery_table(Path(project_root)) if not table: @@ -82,7 +82,7 @@ def _read_project_config_table(pyproject_path: Path) -> dict[str, Any] | None: return table -def _harness_config_kwargs(table: dict[str, Any]) -> dict[str, object]: +def _asp_python_config_kwargs(table: dict[str, Any]) -> dict[str, object]: kwargs: dict[str, object] = {} _put_bool(kwargs, table, "include_tests") _put_string_tuple(kwargs, table, "source_dir_names") diff --git a/src/python_lang_project_harness/_project_evaluation.py b/src/asp_python/_project_evaluation.py similarity index 81% rename from src/python_lang_project_harness/_project_evaluation.py rename to src/asp_python/_project_evaluation.py index 12512c2..92f3a49 100644 --- a/src/python_lang_project_harness/_project_evaluation.py +++ b/src/asp_python/_project_evaluation.py @@ -13,20 +13,20 @@ from python_lang_parser import PythonModuleReport from ._model import ( - PythonHarnessFinding, - PythonLangRulePack, - PythonProjectHarnessScope, + AspPythonFinding, + AspPythonProjectScope, + AspPythonRulePack, ) def evaluate_project_rule_packs( - scope: PythonProjectHarnessScope, - rule_packs: Sequence[PythonLangRulePack], + scope: AspPythonProjectScope, + rule_packs: Sequence[AspPythonRulePack], modules: Sequence[PythonModuleReport], -) -> tuple[PythonHarnessFinding, ...]: +) -> tuple[AspPythonFinding, ...]: """Evaluate project-resolution hooks exposed by configured rule packs.""" - findings: list[PythonHarnessFinding] = [] + findings: list[AspPythonFinding] = [] for rule_pack in rule_packs: module_evaluator = getattr(rule_pack, "evaluate_project_modules", None) if module_evaluator is not None: @@ -44,9 +44,9 @@ def evaluate_project_rule_packs( def compact_project_findings( - module_findings: Sequence[PythonHarnessFinding], - project_findings: Sequence[PythonHarnessFinding], -) -> tuple[PythonHarnessFinding, ...]: + module_findings: Sequence[AspPythonFinding], + project_findings: Sequence[AspPythonFinding], +) -> tuple[AspPythonFinding, ...]: """Remove duplicate module advice covered by stricter project findings.""" typed_package_annotation_locations = { @@ -66,7 +66,7 @@ def compact_project_findings( def _finding_location_key( - finding: PythonHarnessFinding, + finding: AspPythonFinding, ) -> tuple[str | None, int, int]: return ( finding.location.path, diff --git a/src/python_lang_project_harness/_project_metadata.py b/src/asp_python/_project_metadata.py similarity index 87% rename from src/python_lang_project_harness/_project_metadata.py rename to src/asp_python/_project_metadata.py index b646dbb..838aaea 100644 --- a/src/python_lang_project_harness/_project_metadata.py +++ b/src/asp_python/_project_metadata.py @@ -1,4 +1,4 @@ -"""Harness adapter for parser-owned Python project metadata.""" +"""ASP Python adapter for parser-owned Python project metadata.""" from __future__ import annotations diff --git a/src/python_lang_project_harness/_project_policy.py b/src/asp_python/_project_policy.py similarity index 87% rename from src/python_lang_project_harness/_project_policy.py rename to src/asp_python/_project_policy.py index a943920..bd98fdf 100644 --- a/src/python_lang_project_harness/_project_policy.py +++ b/src/asp_python/_project_policy.py @@ -5,7 +5,7 @@ from dataclasses import dataclass from typing import TYPE_CHECKING -from ._model import PythonHarnessFinding, PythonRulePackDescriptor +from ._model import AspPythonFinding, PythonRulePackDescriptor from ._project_metadata import read_python_project_metadata from ._project_policy_catalog import PROJECT_POLICY_PACK_ID from ._project_policy_imports import project_import_name_findings @@ -20,7 +20,7 @@ from python_lang_parser import PythonModuleReport - from ._model import PythonProjectHarnessScope + from ._model import AspPythonProjectScope @dataclass(frozen=True, slots=True) @@ -39,16 +39,16 @@ def descriptor(self) -> PythonRulePackDescriptor: default_mode="blocking", ) - def evaluate(self, report: PythonModuleReport) -> Iterable[PythonHarnessFinding]: + def evaluate(self, report: PythonModuleReport) -> Iterable[AspPythonFinding]: """Evaluate per-module rules.""" return () def evaluate_project_modules( self, - scope: PythonProjectHarnessScope, + scope: AspPythonProjectScope, modules: Sequence[PythonModuleReport], - ) -> Iterable[PythonHarnessFinding]: + ) -> Iterable[AspPythonFinding]: """Evaluate project-shape rules over a parsed project.""" metadata = scope.project_metadata or read_python_project_metadata( @@ -57,7 +57,7 @@ def evaluate_project_modules( if metadata is None: return () - findings: list[PythonHarnessFinding] = [] + findings: list[AspPythonFinding] = [] findings.extend(project_metadata_findings(metadata, self.pack_id)) findings.extend(project_layout_findings(scope, metadata, self.pack_id)) findings.extend( diff --git a/src/python_lang_project_harness/_project_policy_catalog.py b/src/asp_python/_project_policy_catalog.py similarity index 85% rename from src/python_lang_project_harness/_project_policy_catalog.py rename to src/asp_python/_project_policy_catalog.py index a0a7bf7..19d0ebb 100644 --- a/src/python_lang_project_harness/_project_policy_catalog.py +++ b/src/asp_python/_project_policy_catalog.py @@ -6,7 +6,7 @@ from python_lang_parser._diagnostic_model import PythonDiagnosticSeverity -from ._model import PythonHarnessRule +from ._model import AspPythonRule PROJECT_POLICY_PACK_ID = "python.project_policy" PY_PROJ_R001 = "PY-AGENT-PROJECT-001" @@ -26,7 +26,7 @@ "domain": "project-policy", } _RULES = ( - PythonHarnessRule( + AspPythonRule( rule_id=PY_PROJ_R001, pack_id=PROJECT_POLICY_PACK_ID, severity=PythonDiagnosticSeverity.WARNING, @@ -34,7 +34,7 @@ requirement="Use a `src/` source layout so package imports resolve through the installed project shape instead of the repository root.", labels=dict(_RULE_LABELS), ), - PythonHarnessRule( + AspPythonRule( rule_id=PY_PROJ_R002, pack_id=PROJECT_POLICY_PACK_ID, severity=PythonDiagnosticSeverity.WARNING, @@ -42,7 +42,7 @@ requirement="Ensure each declared wheel package root exists and contains `__init__.py` so agents can map project metadata to import namespaces.", labels=dict(_RULE_LABELS), ), - PythonHarnessRule( + AspPythonRule( rule_id=PY_PROJ_R003, pack_id=PROJECT_POLICY_PACK_ID, severity=PythonDiagnosticSeverity.WARNING, @@ -50,7 +50,7 @@ requirement="Add `py.typed` to declared package roots that expose public parser surface so downstream agents and type checkers can trust inline types.", labels=dict(_RULE_LABELS), ), - PythonHarnessRule( + AspPythonRule( rule_id=PY_PROJ_R004, pack_id=PROJECT_POLICY_PACK_ID, severity=PythonDiagnosticSeverity.WARNING, @@ -58,7 +58,7 @@ requirement="Annotate public callable boundaries in `py.typed` packages so the declared typed surface remains complete and agent-readable.", labels=dict(_RULE_LABELS), ), - PythonHarnessRule( + AspPythonRule( rule_id=PY_PROJ_R005, pack_id=PROJECT_POLICY_PACK_ID, severity=PythonDiagnosticSeverity.WARNING, @@ -66,7 +66,7 @@ requirement="Declare `[project].name` in `pyproject.toml` so package identity is explicit for build tools, agents, and release metadata.", labels=dict(_RULE_LABELS), ), - PythonHarnessRule( + AspPythonRule( rule_id=PY_PROJ_R006, pack_id=PROJECT_POLICY_PACK_ID, severity=PythonDiagnosticSeverity.WARNING, @@ -74,7 +74,7 @@ requirement="Declare `[project].requires-python` so installers, CI, and agents can resolve the intended supported Python range.", labels=dict(_RULE_LABELS), ), - PythonHarnessRule( + AspPythonRule( rule_id=PY_PROJ_R007, pack_id=PROJECT_POLICY_PACK_ID, severity=PythonDiagnosticSeverity.WARNING, @@ -82,7 +82,7 @@ requirement="Declare `[build-system].requires` whenever `[build-system]` is present so isolated builds can install backend requirements deterministically.", labels=dict(_RULE_LABELS), ), - PythonHarnessRule( + AspPythonRule( rule_id=PY_PROJ_R008, pack_id=PROJECT_POLICY_PACK_ID, severity=PythonDiagnosticSeverity.WARNING, @@ -90,7 +90,7 @@ requirement="Keep `[project].import-names` and `[project].import-namespaces` aligned with parser-visible project owners so agents can trust package scope metadata.", labels=dict(_RULE_LABELS), ), - PythonHarnessRule( + AspPythonRule( rule_id=PY_PROJ_R009, pack_id=PROJECT_POLICY_PACK_ID, severity=PythonDiagnosticSeverity.WARNING, @@ -98,33 +98,33 @@ requirement="Keep console scripts, GUI scripts, and entry points pointed at parser-visible project modules so agent entry maps and packaging metadata agree.", labels=dict(_RULE_LABELS), ), - PythonHarnessRule( + AspPythonRule( rule_id=PY_PROJ_R010, pack_id=PROJECT_POLICY_PACK_ID, severity=PythonDiagnosticSeverity.WARNING, - title="Harness dev dependency should mount a pytest gate", - requirement="Enable `--python-project-harness` in pytest addopts or expose `python_project_harness_test()` so the dev dependency actually gates project policy.", + title="ASP Python dev dependency should mount a pytest gate", + requirement="Enable `--asp-python` in pytest addopts or expose `asp_python_test()` so the dev dependency actually gates project policy.", labels=dict(_RULE_LABELS), ), - PythonHarnessRule( + AspPythonRule( rule_id=PY_PROJ_R011, pack_id=PROJECT_POLICY_PACK_ID, severity=PythonDiagnosticSeverity.INFO, title="Verification profile hints are not configured", - requirement="Configure `[tool.python-lang-project-harness.verification].profile_hints` from parser-suggested owners, or run `asp-python --agent-snapshot` to copy the compact `[verify-profile]` hints.", + requirement="Configure `[tool.asp-python.verification].profile_hints` from parser-suggested owners or consume the dependency API verification-profile projection.", labels=dict(_RULE_LABELS), ), ) _RULE_BY_ID = {rule.rule_id: rule for rule in _RULES} -def python_project_policy_rules() -> tuple[PythonHarnessRule, ...]: +def python_project_policy_rules() -> tuple[AspPythonRule, ...]: """Return compact metadata for default project-shape rules.""" return tuple(replace(rule, labels=dict(rule.labels)) for rule in _RULES) -def project_policy_rule(rule_id: str) -> PythonHarnessRule: +def project_policy_rule(rule_id: str) -> AspPythonRule: """Return one project-policy rule descriptor by stable rule id.""" return _RULE_BY_ID[rule_id] diff --git a/src/python_lang_project_harness/_project_policy_imports.py b/src/asp_python/_project_policy_imports.py similarity index 92% rename from src/python_lang_project_harness/_project_policy_imports.py rename to src/asp_python/_project_policy_imports.py index 6e95a24..f4b35b8 100644 --- a/src/python_lang_project_harness/_project_policy_imports.py +++ b/src/asp_python/_project_policy_imports.py @@ -7,7 +7,7 @@ from python_lang_parser import python_reasoning_tree_facts -from ._model import PythonHarnessFinding +from ._model import AspPythonFinding from ._project_policy_catalog import PY_PROJ_R008, PY_PROJ_R009, project_policy_rule from ._source import path_location, source_line @@ -16,16 +16,16 @@ from python_lang_parser import PythonModuleReport - from ._model import PythonProjectHarnessScope + from ._model import AspPythonProjectScope from ._project_metadata import PythonProjectMetadata def project_import_name_findings( - scope: PythonProjectHarnessScope, + scope: AspPythonProjectScope, metadata: PythonProjectMetadata, modules: Sequence[PythonModuleReport], pack_id: str, -) -> tuple[PythonHarnessFinding, ...]: +) -> tuple[AspPythonFinding, ...]: """Return findings for declared import names not backed by parser owners.""" rule = project_policy_rule(PY_PROJ_R008) @@ -36,7 +36,7 @@ def project_import_name_findings( project_metadata=metadata, ) known_namespaces = {node.namespace for node in facts.nodes if node.namespace} - findings: list[PythonHarnessFinding] = [] + findings: list[AspPythonFinding] = [] findings.extend(_ambiguous_import_name_findings(metadata, pack_id)) findings.extend( _unresolved_entry_point_target_findings( @@ -49,7 +49,7 @@ def project_import_name_findings( if import_name.name == "" or import_name.namespace in known_namespaces: continue findings.append( - PythonHarnessFinding( + AspPythonFinding( rule_id=rule.rule_id, pack_id=pack_id, severity=rule.severity, @@ -74,14 +74,14 @@ def _unresolved_entry_point_target_findings( *, known_namespaces: set[tuple[str, ...]], pack_id: str, -) -> tuple[PythonHarnessFinding, ...]: +) -> tuple[AspPythonFinding, ...]: rule = project_policy_rule(PY_PROJ_R009) - findings: list[PythonHarnessFinding] = [] + findings: list[AspPythonFinding] = [] for kind, name, target, target_namespace in _entry_point_targets(metadata): if not target_namespace or target_namespace in known_namespaces: continue findings.append( - PythonHarnessFinding( + AspPythonFinding( rule_id=rule.rule_id, pack_id=pack_id, severity=rule.severity, @@ -129,7 +129,7 @@ def _entry_point_targets( def _ambiguous_import_name_findings( metadata: PythonProjectMetadata, pack_id: str, -) -> tuple[PythonHarnessFinding, ...]: +) -> tuple[AspPythonFinding, ...]: import_namespaces = {item.name for item in metadata.import_namespaces} ambiguous = tuple( item @@ -141,7 +141,7 @@ def _ambiguous_import_name_findings( rule = project_policy_rule(PY_PROJ_R008) return tuple( - PythonHarnessFinding( + AspPythonFinding( rule_id=rule.rule_id, pack_id=pack_id, severity=rule.severity, @@ -161,7 +161,7 @@ def _ambiguous_import_name_findings( def _reasoning_tree_import_roots( - scope: PythonProjectHarnessScope, + scope: AspPythonProjectScope, metadata: PythonProjectMetadata, ) -> tuple[Path, ...]: roots: list[Path] = [] diff --git a/src/python_lang_project_harness/_project_policy_layout.py b/src/asp_python/_project_policy_layout.py similarity index 89% rename from src/python_lang_project_harness/_project_policy_layout.py rename to src/asp_python/_project_policy_layout.py index 4894820..029b505 100644 --- a/src/python_lang_project_harness/_project_policy_layout.py +++ b/src/asp_python/_project_policy_layout.py @@ -4,22 +4,22 @@ from typing import TYPE_CHECKING -from ._model import PythonHarnessFinding +from ._model import AspPythonFinding from ._project_policy_catalog import PY_PROJ_R001, PY_PROJ_R002, project_policy_rule from ._source import path_location, source_line if TYPE_CHECKING: from pathlib import Path - from ._model import PythonProjectHarnessScope + from ._model import AspPythonProjectScope from ._project_metadata import PythonProjectMetadata def project_layout_findings( - scope: PythonProjectHarnessScope, + scope: AspPythonProjectScope, metadata: PythonProjectMetadata, pack_id: str, -) -> tuple[PythonHarnessFinding, ...]: +) -> tuple[AspPythonFinding, ...]: """Return findings for project source and package-root layout.""" if not _is_packaged_project(metadata): @@ -40,16 +40,16 @@ def _is_packaged_project(metadata: PythonProjectMetadata) -> bool: def _src_layout_findings( - scope: PythonProjectHarnessScope, + scope: AspPythonProjectScope, metadata: PythonProjectMetadata, pack_id: str, -) -> tuple[PythonHarnessFinding, ...]: +) -> tuple[AspPythonFinding, ...]: if _uses_src_layout(scope, metadata): return () rule = project_policy_rule(PY_PROJ_R001) return ( - PythonHarnessFinding( + AspPythonFinding( rule_id=rule.rule_id, pack_id=pack_id, severity=rule.severity, @@ -65,7 +65,7 @@ def _src_layout_findings( def _uses_src_layout( - scope: PythonProjectHarnessScope, + scope: AspPythonProjectScope, metadata: PythonProjectMetadata, ) -> bool: src_roots = _src_layout_roots(scope, metadata) @@ -80,7 +80,7 @@ def _uses_src_layout( def _src_layout_roots( - scope: PythonProjectHarnessScope, + scope: AspPythonProjectScope, metadata: PythonProjectMetadata, ) -> tuple[Path, ...]: src_roots: list[Path] = [] @@ -116,8 +116,8 @@ def _is_relative_to(path: Path, root: Path) -> bool: def _declared_package_root_findings( metadata: PythonProjectMetadata, pack_id: str, -) -> tuple[PythonHarnessFinding, ...]: - findings: list[PythonHarnessFinding] = [] +) -> tuple[AspPythonFinding, ...]: + findings: list[AspPythonFinding] = [] rule = project_policy_rule(PY_PROJ_R002) for package_root in metadata.package_roots: init_file = package_root / "__init__.py" @@ -127,7 +127,7 @@ def _declared_package_root_findings( package_root if package_root.exists() else metadata.pyproject_path ) findings.append( - PythonHarnessFinding( + AspPythonFinding( rule_id=rule.rule_id, pack_id=pack_id, severity=rule.severity, diff --git a/src/python_lang_project_harness/_project_policy_metadata.py b/src/asp_python/_project_policy_metadata.py similarity index 89% rename from src/python_lang_project_harness/_project_policy_metadata.py rename to src/asp_python/_project_policy_metadata.py index a6782d7..54e7954 100644 --- a/src/python_lang_project_harness/_project_policy_metadata.py +++ b/src/asp_python/_project_policy_metadata.py @@ -4,7 +4,7 @@ from typing import TYPE_CHECKING -from ._model import PythonHarnessFinding +from ._model import AspPythonFinding from ._project_policy_catalog import ( PY_PROJ_R005, PY_PROJ_R006, @@ -20,10 +20,10 @@ def project_metadata_findings( metadata: PythonProjectMetadata, pack_id: str, -) -> tuple[PythonHarnessFinding, ...]: +) -> tuple[AspPythonFinding, ...]: """Return findings for deterministic pyproject metadata contracts.""" - findings: list[PythonHarnessFinding] = [] + findings: list[AspPythonFinding] = [] findings.extend(_project_name_findings(metadata, pack_id)) findings.extend(_requires_python_findings(metadata, pack_id)) findings.extend(_build_requires_findings(metadata, pack_id)) @@ -33,13 +33,13 @@ def project_metadata_findings( def _project_name_findings( metadata: PythonProjectMetadata, pack_id: str, -) -> tuple[PythonHarnessFinding, ...]: +) -> tuple[AspPythonFinding, ...]: if not metadata.has_project_table or metadata.project_name is not None: return () rule = project_policy_rule(PY_PROJ_R005) return ( - PythonHarnessFinding( + AspPythonFinding( rule_id=rule.rule_id, pack_id=pack_id, severity=rule.severity, @@ -57,13 +57,13 @@ def _project_name_findings( def _requires_python_findings( metadata: PythonProjectMetadata, pack_id: str, -) -> tuple[PythonHarnessFinding, ...]: +) -> tuple[AspPythonFinding, ...]: if not metadata.has_project_table or metadata.requires_python is not None: return () rule = project_policy_rule(PY_PROJ_R006) return ( - PythonHarnessFinding( + AspPythonFinding( rule_id=rule.rule_id, pack_id=pack_id, severity=rule.severity, @@ -81,13 +81,13 @@ def _requires_python_findings( def _build_requires_findings( metadata: PythonProjectMetadata, pack_id: str, -) -> tuple[PythonHarnessFinding, ...]: +) -> tuple[AspPythonFinding, ...]: if not metadata.has_build_system_table or metadata.build_requires: return () rule = project_policy_rule(PY_PROJ_R007) return ( - PythonHarnessFinding( + AspPythonFinding( rule_id=rule.rule_id, pack_id=pack_id, severity=rule.severity, diff --git a/src/python_lang_project_harness/_project_policy_pytest_gate.py b/src/asp_python/_project_policy_pytest_gate.py similarity index 63% rename from src/python_lang_project_harness/_project_policy_pytest_gate.py rename to src/asp_python/_project_policy_pytest_gate.py index 8ae42a2..fa7ae6e 100644 --- a/src/python_lang_project_harness/_project_policy_pytest_gate.py +++ b/src/asp_python/_project_policy_pytest_gate.py @@ -1,10 +1,10 @@ -"""Project policy for parser-visible pytest harness gates.""" +"""Project policy for parser-visible ASP Python pytest gates.""" from __future__ import annotations from typing import TYPE_CHECKING -from ._model import PythonHarnessFinding +from ._model import AspPythonFinding from ._project_policy_catalog import PY_PROJ_R010, project_policy_rule from ._source import path_location, source_line @@ -13,45 +13,45 @@ from python_lang_parser import PythonModuleReport, PythonProjectMetadata -_DISTRIBUTION_NAME = "python-lang-project-harness" +_DISTRIBUTION_NAME = "asp-python" def project_pytest_gate_findings( metadata: PythonProjectMetadata, modules: Sequence[PythonModuleReport], pack_id: str, -) -> tuple[PythonHarnessFinding, ...]: - """Return findings when a harness dependency is not wired into pytest.""" +) -> tuple[AspPythonFinding, ...]: + """Return findings when a ASP Python dependency is not wired into pytest.""" - if not declares_python_harness_surface(metadata): + if not declares_asp_python_surface(metadata): return () - if metadata.pytest_options.enables_python_project_harness: + if metadata.pytest_options.enables_asp_python: return () - if _has_explicit_harness_helper(modules): + if _has_explicit_asp_python_helper(modules): return () rule = project_policy_rule(PY_PROJ_R010) return ( - PythonHarnessFinding( + AspPythonFinding( rule_id=rule.rule_id, pack_id=pack_id, severity=rule.severity, title=rule.title, summary=( - f"{metadata.pyproject_path.name} declares the Python project harness " + f"{metadata.pyproject_path.name} declares the ASP Python " "surface without a parser-visible pytest gate." ), location=path_location(metadata.pyproject_path), requirement=rule.requirement, source_line=source_line(str(metadata.pyproject_path), 1), - label="mount the parser-backed harness in pytest", + label="mount the parser-backed ASP Python in pytest", labels=dict(rule.labels), ), ) -def declares_python_harness_surface(metadata: PythonProjectMetadata) -> bool: - """Return whether project metadata declares this harness as a dev surface.""" +def declares_asp_python_surface(metadata: PythonProjectMetadata) -> bool: + """Return whether project metadata declares ASP Python as a dev surface.""" distribution_name = _canonical_distribution_name(_DISTRIBUTION_NAME) if _canonical_distribution_name(metadata.project_name or "") == distribution_name: @@ -63,19 +63,19 @@ def declares_python_harness_surface(metadata: PythonProjectMetadata) -> bool: return True return any( entry_point.group == "pytest11" - and entry_point.target_namespace[:1] == ("python_lang_project_harness",) + and entry_point.target_namespace[:1] == ("asp_python",) for entry_point in metadata.entry_points ) -def _has_explicit_harness_helper( +def _has_explicit_asp_python_helper( modules: Sequence[PythonModuleReport], ) -> bool: for module in modules: for call in module.calls: - if call.function == "python_project_harness_test": + if call.function == "asp_python_test": return True - if call.function.endswith(".python_project_harness_test"): + if call.function.endswith(".asp_python_test"): return True return False diff --git a/src/python_lang_project_harness/_project_policy_typed.py b/src/asp_python/_project_policy_typed.py similarity index 92% rename from src/python_lang_project_harness/_project_policy_typed.py rename to src/asp_python/_project_policy_typed.py index 82a0fe7..8204e94 100644 --- a/src/python_lang_project_harness/_project_policy_typed.py +++ b/src/asp_python/_project_policy_typed.py @@ -11,7 +11,7 @@ python_symbol_is_public_class, ) -from ._model import PythonHarnessFinding +from ._model import AspPythonFinding from ._project_policy_catalog import PY_PROJ_R003, PY_PROJ_R004, project_policy_rule from ._source import path_location @@ -27,7 +27,7 @@ def typed_package_findings( metadata: PythonProjectMetadata, modules: Sequence[PythonModuleReport], pack_id: str, -) -> tuple[PythonHarnessFinding, ...]: +) -> tuple[AspPythonFinding, ...]: """Return findings for typed package marker and annotation contracts.""" return ( @@ -40,8 +40,8 @@ def _typed_package_marker_findings( metadata: PythonProjectMetadata, modules: Sequence[PythonModuleReport], pack_id: str, -) -> tuple[PythonHarnessFinding, ...]: - findings: list[PythonHarnessFinding] = [] +) -> tuple[AspPythonFinding, ...]: + findings: list[AspPythonFinding] = [] rule = project_policy_rule(PY_PROJ_R003) for package_root in metadata.package_roots: if not (package_root / "__init__.py").is_file(): @@ -51,7 +51,7 @@ def _typed_package_marker_findings( if not _package_has_public_parser_surface(package_root, modules): continue findings.append( - PythonHarnessFinding( + AspPythonFinding( rule_id=rule.rule_id, pack_id=pack_id, severity=rule.severity, @@ -86,8 +86,8 @@ def _typed_package_annotation_findings( metadata: PythonProjectMetadata, modules: Sequence[PythonModuleReport], pack_id: str, -) -> tuple[PythonHarnessFinding, ...]: - findings: list[PythonHarnessFinding] = [] +) -> tuple[AspPythonFinding, ...]: + findings: list[AspPythonFinding] = [] rule = project_policy_rule(PY_PROJ_R004) for package_root in metadata.package_roots: if not (package_root / "py.typed").is_file(): @@ -113,15 +113,15 @@ def _unannotated_public_callable_findings( public_class_names: frozenset[str], pack_id: str, rule: object, -) -> tuple[PythonHarnessFinding, ...]: - findings: list[PythonHarnessFinding] = [] +) -> tuple[AspPythonFinding, ...]: + findings: list[AspPythonFinding] = [] for symbol in module.symbols: if not _is_public_callable_boundary(symbol, public_class_names): continue if symbol.has_annotations: continue findings.append( - PythonHarnessFinding( + AspPythonFinding( rule_id=rule.rule_id, pack_id=pack_id, severity=rule.severity, diff --git a/src/python_lang_project_harness/_project_policy_verification.py b/src/asp_python/_project_policy_verification.py similarity index 78% rename from src/python_lang_project_harness/_project_policy_verification.py rename to src/asp_python/_project_policy_verification.py index b8fdd71..1714c0f 100644 --- a/src/python_lang_project_harness/_project_policy_verification.py +++ b/src/asp_python/_project_policy_verification.py @@ -6,10 +6,10 @@ from python_lang_parser import python_reasoning_tree_facts -from ._model import PythonHarnessConfig, PythonHarnessFinding -from ._project_config import read_python_project_harness_config +from ._model import AspPythonConfig, AspPythonFinding +from ._project_config import read_asp_python_config from ._project_policy_catalog import PY_PROJ_R011, project_policy_rule -from ._project_policy_pytest_gate import declares_python_harness_surface +from ._project_policy_pytest_gate import declares_asp_python_surface from ._source import path_location, source_line from .verification.facts import ( is_test_path, @@ -21,21 +21,21 @@ from python_lang_parser import PythonModuleReport, PythonProjectMetadata - from ._model import PythonProjectHarnessScope + from ._model import AspPythonProjectScope def project_verification_profile_findings( - scope: PythonProjectHarnessScope, + scope: AspPythonProjectScope, metadata: PythonProjectMetadata, modules: Sequence[PythonModuleReport], pack_id: str, -) -> tuple[PythonHarnessFinding, ...]: +) -> tuple[AspPythonFinding, ...]: """Return Agent advice when parser facts need a verification profile.""" - if not declares_python_harness_surface(metadata): + if not declares_asp_python_surface(metadata): return () - config = read_python_project_harness_config(scope.project_root) - selected_config = config if config is not None else PythonHarnessConfig() + config = read_asp_python_config(scope.project_root) + selected_config = config if config is not None else AspPythonConfig() if selected_config.verification_policy.profile_hints: return () @@ -45,7 +45,7 @@ def project_verification_profile_findings( rule = project_policy_rule(PY_PROJ_R011) return ( - PythonHarnessFinding( + AspPythonFinding( rule_id=rule.rule_id, pack_id=pack_id, severity=rule.severity, @@ -64,10 +64,10 @@ def project_verification_profile_findings( def _verification_owner_count( - scope: PythonProjectHarnessScope, + scope: AspPythonProjectScope, metadata: PythonProjectMetadata, modules: Sequence[PythonModuleReport], - config: PythonHarnessConfig, + config: AspPythonConfig, ) -> int: facts = python_reasoning_tree_facts( modules, diff --git a/src/python_lang_project_harness/_project_resolution.py b/src/asp_python/_project_resolution.py similarity index 100% rename from src/python_lang_project_harness/_project_resolution.py rename to src/asp_python/_project_resolution.py diff --git a/src/python_lang_project_harness/_project_resolution_backends.py b/src/asp_python/_project_resolution_backends.py similarity index 100% rename from src/python_lang_project_harness/_project_resolution_backends.py rename to src/asp_python/_project_resolution_backends.py diff --git a/src/python_lang_project_harness/_project_resolution_candidates.py b/src/asp_python/_project_resolution_candidates.py similarity index 100% rename from src/python_lang_project_harness/_project_resolution_candidates.py rename to src/asp_python/_project_resolution_candidates.py diff --git a/src/python_lang_project_harness/_project_resolution_document.py b/src/asp_python/_project_resolution_document.py similarity index 100% rename from src/python_lang_project_harness/_project_resolution_document.py rename to src/asp_python/_project_resolution_document.py diff --git a/src/python_lang_project_harness/_project_resolution_graph.py b/src/asp_python/_project_resolution_graph.py similarity index 100% rename from src/python_lang_project_harness/_project_resolution_graph.py rename to src/asp_python/_project_resolution_graph.py diff --git a/src/python_lang_project_harness/_project_resolution_sources.py b/src/asp_python/_project_resolution_sources.py similarity index 100% rename from src/python_lang_project_harness/_project_resolution_sources.py rename to src/asp_python/_project_resolution_sources.py diff --git a/src/python_lang_project_harness/_projection_batch.py b/src/asp_python/_projection_batch.py similarity index 87% rename from src/python_lang_project_harness/_projection_batch.py rename to src/asp_python/_projection_batch.py index a3ade1e..7fc0c16 100644 --- a/src/python_lang_project_harness/_projection_batch.py +++ b/src/asp_python/_projection_batch.py @@ -8,7 +8,7 @@ from dataclasses import dataclass from ._callable_skeleton_projection import callable_skeleton_payload, collect_segments -from ._exact_projection_model import ExactSelector +from ._exact_projection_model import parse_selector _REQUEST_SCHEMA_ID = ( "agent.semantic-protocols.provider-language-projection-batch-request" @@ -30,7 +30,7 @@ def project_projection_batch(request: dict[str, object]) -> dict[str, object]: """Project one structured resident-runtime request with the native AST.""" owners, _auxiliary_owners = _decode_request(request) - projected = [_project_owner(owner, request) for owner in owners] + projected = [_project_owner_isolated(owner, request) for owner in owners] response = { "schemaId": _RESPONSE_SCHEMA_ID, "schemaVersion": "1", @@ -42,6 +42,28 @@ def project_projection_batch(request: dict[str, object]) -> dict[str, object]: return response +def _project_owner_isolated( + owner: _OwnerFrame, header: dict[str, object] +) -> dict[str, object]: + try: + return _project_owner(owner, header) + except (SyntaxError, UnicodeDecodeError) as error: + message = str(error).strip() or "Python parser rejected the source owner" + return { + "ownerPath": owner.path, + "sourceLeafDigest": owner.digest, + "projectionState": "syntax-unavailable", + "diagnostic": { + "schemaId": "agent.semantic-protocols.provider-language-projection-diagnostic", + "schemaVersion": "1", + "reasonKind": "source-syntax-unavailable", + "message": message[:4096], + }, + "items": [], + "relations": [], + } + + def _decode_request( request: dict[str, object], ) -> tuple[list[_OwnerFrame], list[_OwnerFrame]]: @@ -128,6 +150,8 @@ def _project_owner(owner: _OwnerFrame, header: dict[str, object]) -> dict[str, o return { "ownerPath": owner.path, "sourceLeafDigest": owner.digest, + "projectionState": "ready", + "diagnostic": None, "items": items, "relations": [], } @@ -150,15 +174,7 @@ def _project_item( selector = f"python://{owner.path}#item/{kind}/{node.name}{scope_path}" projections: list[dict[str, object]] = [] if isinstance(node, (ast.FunctionDef, ast.AsyncFunctionDef)): - exact_selector = ExactSelector( - requested=selector, - root=selector, - owner_path=owner.path, - kind=kind, - symbol=node.name, - segment_kind=None, - segment_identity=None, - ) + exact_selector = parse_selector(selector) projection_request = { "generationIdentityDigest": header["generationRootDigest"], "parserIdentityDigest": header["parserIdentityDigest"], diff --git a/src/python_lang_project_harness/_pytest.py b/src/asp_python/_pytest.py similarity index 56% rename from src/python_lang_project_harness/_pytest.py rename to src/asp_python/_pytest.py index 55d4662..10e5c74 100644 --- a/src/python_lang_project_harness/_pytest.py +++ b/src/asp_python/_pytest.py @@ -1,39 +1,39 @@ -"""Pytest-facing helpers for embedding the Python project harness.""" +"""Pytest-facing helpers for embedding the ASP Python.""" from __future__ import annotations from pathlib import Path from typing import TYPE_CHECKING -from ._runner import assert_python_project_harness_clean +from ._runner import assert_asp_python_clean if TYPE_CHECKING: from collections.abc import Callable, Sequence from python_lang_parser import PythonDiagnosticSeverity - from ._model import PythonHarnessConfig, PythonLangRulePack + from ._model import AspPythonConfig, AspPythonRulePack -def python_project_harness_test( +def asp_python_test( project_root: str | Path = ".", *, - config: PythonHarnessConfig | None = None, - rule_packs: Sequence[PythonLangRulePack] | None = None, + config: AspPythonConfig | None = None, + rule_packs: Sequence[AspPythonRulePack] | None = None, severities: frozenset[PythonDiagnosticSeverity] | None = None, include_tests: bool | None = None, source_dir_names: Sequence[str] | None = None, test_dir_names: Sequence[str] | None = None, extra_path_names: Sequence[str] | None = None, - test_name: str = "test_python_project_harness_policy", + test_name: str = "test_asp_python_policy", include_advice: bool = True, ) -> Callable[[], None]: """Return a pytest-collectable test function for one Python project.""" root = Path(project_root) - def test_python_project_harness_policy() -> None: - assert_python_project_harness_clean( + def test_asp_python_policy() -> None: + assert_asp_python_clean( root, config=config, rule_packs=rule_packs, @@ -45,9 +45,7 @@ def test_python_project_harness_policy() -> None: include_advice=include_advice, ) - test_python_project_harness_policy.__name__ = test_name - test_python_project_harness_policy.__qualname__ = test_name - test_python_project_harness_policy.__doc__ = ( - "Run the Python project harness over configured project paths." - ) - return test_python_project_harness_policy + test_asp_python_policy.__name__ = test_name + test_asp_python_policy.__qualname__ = test_name + test_asp_python_policy.__doc__ = "Run the ASP Python over configured project paths." + return test_asp_python_policy diff --git a/src/asp_python/_pytest_plugin_options.py b/src/asp_python/_pytest_plugin_options.py new file mode 100644 index 0000000..e877f51 --- /dev/null +++ b/src/asp_python/_pytest_plugin_options.py @@ -0,0 +1,160 @@ +"""Option registration and typed configuration for the pytest integration.""" + +from __future__ import annotations + +from dataclasses import replace +from typing import TYPE_CHECKING + +import pytest + +from python_lang_parser._diagnostic_model import PythonDiagnosticSeverity + +from ._model import AspPythonConfig +from ._project_config import read_asp_python_config +from ._pytest_plugin_project import project_root + +if TYPE_CHECKING: + from collections.abc import Sequence + + +ENABLE_OPTION = "--asp-python" +NO_TESTS_OPTION = "--asp-python-no-tests" +SOURCE_DIR_OPTION = "--asp-python-source-dir" +TEST_DIR_OPTION = "--asp-python-test-dir" +EXTRA_PATH_OPTION = "--asp-python-extra-path" +NO_ADVICE_OPTION = "--asp-python-no-advice" + +_ROOT_OPTION = "--asp-python-root" +_DISABLE_RULE_OPTION = "--asp-python-disable-rule" +_BLOCK_RULE_OPTION = "--asp-python-block-rule" +_ERROR_ONLY_OPTION = "--asp-python-error-only" + + +def add_options(parser: pytest.Parser) -> None: + """Register ASP Python pytest options.""" + + group = parser.getgroup("asp-python") + for name, kwargs in ( + ( + ENABLE_OPTION, + { + "action": "store_true", + "default": False, + "help": "Collect and run the asp-python policy test.", + }, + ), + ( + _ROOT_OPTION, + { + "action": "store", + "default": None, + "metavar": "PATH", + "help": "Project root for ASP Python test. Defaults to pytest rootdir.", + }, + ), + ( + NO_TESTS_OPTION, + { + "action": "store_true", + "default": False, + "help": "Do not parse test files; pytest layout checks still run.", + }, + ), + ( + SOURCE_DIR_OPTION, + { + "action": "append", + "default": [], + "metavar": "NAME", + "help": "Source directory name to scan. Can be provided more than once.", + }, + ), + ( + TEST_DIR_OPTION, + { + "action": "append", + "default": [], + "metavar": "NAME", + "help": "Test directory name to scan. Can be provided more than once.", + }, + ), + ( + EXTRA_PATH_OPTION, + { + "action": "append", + "default": [], + "metavar": "NAME", + "help": "Extra project path name to scan. Can be provided more than once.", + }, + ), + ( + _DISABLE_RULE_OPTION, + { + "action": "append", + "default": [], + "metavar": "RULE_ID", + "help": "ASP Python rule id to suppress. Can be provided more than once.", + }, + ), + ( + _BLOCK_RULE_OPTION, + { + "action": "append", + "default": [], + "metavar": "RULE_ID", + "help": "ASP Python rule id to treat as blocking. Can be provided more than once.", + }, + ), + ( + _ERROR_ONLY_OPTION, + { + "action": "store_true", + "default": False, + "help": "Only fail the ASP Python pytest item for parser errors.", + }, + ), + ( + NO_ADVICE_OPTION, + { + "action": "store_true", + "default": False, + "help": "Hide non-blocking advice from assertion output.", + }, + ), + ): + group.addoption(name, **kwargs) + + +def blocking_severities( + config: pytest.Config, +) -> frozenset[PythonDiagnosticSeverity] | None: + if config.getoption(_ERROR_ONLY_OPTION): + return frozenset({PythonDiagnosticSeverity.ERROR}) + return None + + +def asp_python_config(config: pytest.Config) -> AspPythonConfig | None: + disabled_rule_values = config.getoption(_DISABLE_RULE_OPTION) + blocking_rule_values = config.getoption(_BLOCK_RULE_OPTION) + if not disabled_rule_values and not blocking_rule_values: + return None + + base_config = read_asp_python_config(project_root(config)) + selected_config = base_config if base_config is not None else AspPythonConfig() + return replace( + selected_config, + disabled_rule_ids=( + frozenset(disabled_rule_values) + if disabled_rule_values + else selected_config.disabled_rule_ids + ), + blocking_rule_ids=( + frozenset(blocking_rule_values) + if blocking_rule_values + else selected_config.blocking_rule_ids + ), + ) + + +def optional_tuple(values: Sequence[str]) -> tuple[str, ...] | None: + return tuple(values) if values else None diff --git a/src/asp_python/_pytest_plugin_project.py b/src/asp_python/_pytest_plugin_project.py new file mode 100644 index 0000000..9ee8387 --- /dev/null +++ b/src/asp_python/_pytest_plugin_project.py @@ -0,0 +1,91 @@ +"""Project-scope resolution for the pytest integration.""" + +from __future__ import annotations + +from pathlib import Path +from typing import TYPE_CHECKING + +import pytest + +from python_lang_parser._pyproject_metadata import parse_python_project_metadata + +if TYPE_CHECKING: + from collections.abc import Sequence + + +def project_root(config: pytest.Config) -> Path: + """Resolve the configured or uniquely targeted Python project root.""" + + configured_root = config.getoption("--asp-python-root") + if configured_root: + return Path(configured_root) + root = Path(config.rootpath) + return ( + _package_scoped_root( + root, + config.invocation_params.args, + invocation_dir=Path(config.invocation_params.dir), + ) + or root + ) + + +def _package_scoped_root( + pytest_root: Path, + args: Sequence[str], + *, + invocation_dir: Path, +) -> Path | None: + """Find one package root when pytest targets exactly one Python project.""" + + candidates: list[Path] = [] + for raw_arg in args: + if raw_arg.startswith("-"): + continue + raw_path = raw_arg.split("::", 1)[0] + if not raw_path: + continue + path = Path(raw_path) + if not path.is_absolute(): + path = invocation_dir / path + path = path.resolve() + if not path.exists(): + return None + candidate = _nearest_python_project(path, pytest_root.resolve()) + if candidate is None: + return None + candidates.append(candidate) + + if not candidates or any( + candidate != candidates[0] for candidate in candidates[1:] + ): + return None + candidate = candidates[0] + if candidate == pytest_root.resolve(): + return None + return candidate + + +def _nearest_python_project(path: Path, pytest_root: Path) -> Path | None: + """Return the nearest real Python project containing ``path``.""" + + start = path if path.is_dir() else path.parent + for candidate in (start, *start.parents): + if not _is_relative_to(candidate, pytest_root): + break + metadata = parse_python_project_metadata(candidate) + if metadata is not None and ( + metadata.has_project_table or metadata.has_build_system_table + ): + return candidate + if candidate == pytest_root: + break + return None + + +def _is_relative_to(path: Path, parent: Path) -> bool: + try: + path.relative_to(parent) + except ValueError: + return False + return True diff --git a/src/python_lang_project_harness/_python_compact.py b/src/asp_python/_python_compact.py similarity index 89% rename from src/python_lang_project_harness/_python_compact.py rename to src/asp_python/_python_compact.py index 72e648b..b7f88bb 100644 --- a/src/python_lang_project_harness/_python_compact.py +++ b/src/asp_python/_python_compact.py @@ -5,14 +5,14 @@ from dataclasses import dataclass from typing import Any -from python_lang_project_harness._python_outline import ( +from asp_python._python_outline import ( fallback_python_compact, ) -from python_lang_project_harness._python_projection import ( +from asp_python._python_projection import ( CompactPythonProjectionNode, collect_python_projection_node, ) -from python_lang_project_harness._python_source import parse_python_lines +from asp_python._python_source import parse_python_lines @dataclass(frozen=True) diff --git a/src/python_lang_project_harness/_python_expr.py b/src/asp_python/_python_expr.py similarity index 100% rename from src/python_lang_project_harness/_python_expr.py rename to src/asp_python/_python_expr.py diff --git a/src/python_lang_project_harness/_python_outline.py b/src/asp_python/_python_outline.py similarity index 99% rename from src/python_lang_project_harness/_python_outline.py rename to src/asp_python/_python_outline.py index d728b5f..18c021e 100644 --- a/src/python_lang_project_harness/_python_outline.py +++ b/src/asp_python/_python_outline.py @@ -4,7 +4,7 @@ import ast -from python_lang_project_harness._python_expr import ( +from asp_python._python_expr import ( _args, _aug_assign_stmt, _expr, diff --git a/src/python_lang_project_harness/_python_projection.py b/src/asp_python/_python_projection.py similarity index 85% rename from src/python_lang_project_harness/_python_projection.py rename to src/asp_python/_python_projection.py index c5cb082..fa83390 100644 --- a/src/python_lang_project_harness/_python_projection.py +++ b/src/asp_python/_python_projection.py @@ -4,12 +4,12 @@ import ast -from python_lang_project_harness._python_projection_extras import ( +from asp_python._python_projection_extras import ( append_decorator_projection_nodes, append_expression_effect_projection_nodes, ) -from python_lang_project_harness._python_projection_facts import projection_fact -from python_lang_project_harness._python_projection_model import ( +from asp_python._python_projection_facts import projection_fact +from asp_python._python_projection_model import ( CompactPythonProjectionNode, ) diff --git a/src/python_lang_project_harness/_python_projection_extras.py b/src/asp_python/_python_projection_extras.py similarity index 93% rename from src/python_lang_project_harness/_python_projection_extras.py rename to src/asp_python/_python_projection_extras.py index 90380fa..0ba3635 100644 --- a/src/python_lang_project_harness/_python_projection_extras.py +++ b/src/asp_python/_python_projection_extras.py @@ -5,8 +5,8 @@ import ast from collections.abc import Iterable -from python_lang_project_harness._python_expr import _expr -from python_lang_project_harness._python_projection_model import ( +from asp_python._python_expr import _expr +from asp_python._python_projection_model import ( CompactPythonProjectionNode, python_ast_node_read, ) diff --git a/src/python_lang_project_harness/_python_projection_facts.py b/src/asp_python/_python_projection_facts.py similarity index 98% rename from src/python_lang_project_harness/_python_projection_facts.py rename to src/asp_python/_python_projection_facts.py index a905ca1..ff11a00 100644 --- a/src/python_lang_project_harness/_python_projection_facts.py +++ b/src/asp_python/_python_projection_facts.py @@ -4,13 +4,13 @@ import ast -from python_lang_project_harness._python_expr import ( +from asp_python._python_expr import ( _args, _aug_assign_stmt, _expr, _with_items, ) -from python_lang_project_harness._python_projection_model import ( +from asp_python._python_projection_model import ( CompactPythonProjectionNode, node_fact, ) diff --git a/src/python_lang_project_harness/_python_projection_model.py b/src/asp_python/_python_projection_model.py similarity index 100% rename from src/python_lang_project_harness/_python_projection_model.py rename to src/asp_python/_python_projection_model.py diff --git a/src/python_lang_project_harness/_python_source.py b/src/asp_python/_python_source.py similarity index 100% rename from src/python_lang_project_harness/_python_source.py rename to src/asp_python/_python_source.py diff --git a/src/python_lang_project_harness/_render.py b/src/asp_python/_render.py similarity index 93% rename from src/python_lang_project_harness/_render.py rename to src/asp_python/_render.py index 8792452..e2fc5dc 100644 --- a/src/python_lang_project_harness/_render.py +++ b/src/asp_python/_render.py @@ -1,4 +1,4 @@ -"""Compact snapshot rendering for Python harness diagnostics.""" +"""Compact snapshot rendering for ASP Python diagnostics.""" from __future__ import annotations @@ -22,7 +22,7 @@ PythonReasoningTreeNode, ) - from ._model import PythonHarnessFinding, PythonHarnessReport + from ._model import AspPythonFinding, AspPythonReport def _compact_render_fields(fields: dict[str, object]) -> dict[str, object]: @@ -49,8 +49,8 @@ def _line_protocol_field_value(value: object) -> str: return str(value) -def render_python_lang_harness( - report: PythonHarnessReport, +def render_asp_python_report( + report: AspPythonReport, *, severities: frozenset[PythonDiagnosticSeverity] | None = None, include_advice: bool = True, @@ -87,13 +87,13 @@ def render_python_lang_harness( return _render_ok_header(report) -def render_python_lang_harness_json(report: PythonHarnessReport) -> str: +def render_asp_python_report_json(report: AspPythonReport) -> str: """Render a structured JSON diagnostic report for tool consumers.""" return json.dumps(report.to_dict(), separators=(",", ":"), sort_keys=True) -def render_python_lang_harness_advice(report: PythonHarnessReport) -> str: +def render_asp_python_report_advice(report: AspPythonReport) -> str: """Render non-blocking advisory findings for agent-guided repair.""" advice_findings = _deduplicate_advice_findings( @@ -109,7 +109,7 @@ def render_python_lang_harness_advice(report: PythonHarnessReport) -> str: def render_python_reasoning_tree( - report: PythonHarnessReport, + report: AspPythonReport, *, max_nodes: int = 80, max_edges: int = 80, @@ -169,7 +169,7 @@ def render_python_reasoning_tree( return "\n".join(lines) + "\n" -def _render_ok_header(report: PythonHarnessReport) -> str: +def _render_ok_header(report: AspPythonReport) -> str: project_root = _report_project_root(report) target = ", ".join( _render_display_path(path, project_root=project_root) @@ -182,7 +182,7 @@ def _render_ok_header(report: PythonHarnessReport) -> str: def _render_findings( - findings: tuple[PythonHarnessFinding, ...], + findings: tuple[AspPythonFinding, ...], *, project_root: Path | None, ) -> str: @@ -196,8 +196,8 @@ def _render_findings( def _render_failure_frontier( - report: PythonHarnessReport, - findings: tuple[PythonHarnessFinding, ...], + report: AspPythonReport, + findings: tuple[AspPythonFinding, ...], *, project_root: Path | None, ) -> str: @@ -233,7 +233,7 @@ def _render_failure_frontier( def _failure_frontier_selector( - finding: PythonHarnessFinding, + finding: AspPythonFinding, *, project_root: Path | None, ) -> str | None: @@ -249,7 +249,7 @@ def _failure_frontier_text(value: str) -> str: def _render_finding( - finding: PythonHarnessFinding, + finding: AspPythonFinding, *, project_root: Path | None, ) -> str: @@ -280,7 +280,7 @@ def _render_finding( return rendered -def _software_criterion_labels(finding: PythonHarnessFinding) -> str: +def _software_criterion_labels(finding: AspPythonFinding) -> str: value = finding.labels.get("softwareCriteria") if not value: return "" @@ -298,7 +298,7 @@ def _format_software_criterion(criterion_id: str) -> str: return f"software-criterion/{criterion_id}" -def _reasoning_tree_import_roots(report: PythonHarnessReport) -> tuple[Path | str, ...]: +def _reasoning_tree_import_roots(report: AspPythonReport) -> tuple[Path | str, ...]: if report.project_resolution is None: return report.root_paths if report.project_resolution.source_paths: @@ -306,7 +306,7 @@ def _reasoning_tree_import_roots(report: PythonHarnessReport) -> tuple[Path | st return report.project_resolution.monitored_paths -def _report_project_root(report: PythonHarnessReport) -> Path | None: +def _report_project_root(report: AspPythonReport) -> Path | None: if report.project_resolution is None: return None return report.project_resolution.project_root @@ -480,10 +480,10 @@ def _render_display_path(path: Path | str, *, project_root: Path | None) -> str: def _deduplicate_advice_findings( - advice_findings: tuple[PythonHarnessFinding, ...], + advice_findings: tuple[AspPythonFinding, ...], *, - blocking_findings: tuple[PythonHarnessFinding, ...], -) -> tuple[PythonHarnessFinding, ...]: + blocking_findings: tuple[AspPythonFinding, ...], +) -> tuple[AspPythonFinding, ...]: blocking_keys = {_finding_key(finding) for finding in blocking_findings} return tuple( finding @@ -492,7 +492,7 @@ def _deduplicate_advice_findings( ) -def _finding_key(finding: PythonHarnessFinding) -> tuple[str, str | None, int, int]: +def _finding_key(finding: AspPythonFinding) -> tuple[str, str | None, int, int]: return ( finding.rule_id, finding.location.path, diff --git a/src/python_lang_project_harness/_rule_packs.py b/src/asp_python/_rule_packs.py similarity index 54% rename from src/python_lang_project_harness/_rule_packs.py rename to src/asp_python/_rule_packs.py index 1a5eca5..dcf19bc 100644 --- a/src/python_lang_project_harness/_rule_packs.py +++ b/src/asp_python/_rule_packs.py @@ -1,4 +1,4 @@ -"""Default rule-pack configuration for Python project harness runs.""" +"""Default rule-pack configuration for ASP Python runs.""" from __future__ import annotations @@ -6,12 +6,12 @@ from typing import TYPE_CHECKING from ._agent_policy import PythonAgentPolicyRulePack -from ._model import PythonHarnessConfig, PythonLangRulePack, PythonRulePackDescriptor +from ._model import AspPythonConfig, AspPythonRulePack, PythonRulePackDescriptor from ._modern_design import PythonModernDesignRulePack from ._modularity import PythonModularityRulePack from ._project_config import ( apply_asp_project_discovery_config, - read_python_project_harness_config, + read_asp_python_config, ) from ._project_policy import PythonProjectPolicyRulePack from ._syntax import PythonSyntaxRulePack @@ -22,7 +22,7 @@ from pathlib import Path -def default_python_lang_rule_packs() -> tuple[PythonLangRulePack, ...]: +def default_asp_python_rule_packs() -> tuple[AspPythonRulePack, ...]: """Return the default deterministic Python language rule packs.""" return ( @@ -36,52 +36,50 @@ def default_python_lang_rule_packs() -> tuple[PythonLangRulePack, ...]: def python_rule_pack_descriptors() -> tuple[PythonRulePackDescriptor, ...]: - """Return stable metadata for default Python harness rule packs.""" + """Return stable metadata for default ASP Python rule packs.""" return tuple( - rule_pack.descriptor() for rule_pack in default_python_lang_rule_packs() + rule_pack.descriptor() for rule_pack in default_asp_python_rule_packs() ) -def default_python_harness_config() -> PythonHarnessConfig: - """Return the default Python language harness configuration.""" +def default_asp_python_config() -> AspPythonConfig: + """Return the default ASP Python configuration.""" - return PythonHarnessConfig(rule_packs=default_python_lang_rule_packs()) + return AspPythonConfig(rule_packs=default_asp_python_rule_packs()) -def resolve_harness_config( - config: PythonHarnessConfig | None, +def resolve_asp_python_config( + config: AspPythonConfig | None, *, - rule_packs: Sequence[PythonLangRulePack] | None, -) -> PythonHarnessConfig: + rule_packs: Sequence[AspPythonRulePack] | None, +) -> AspPythonConfig: """Resolve caller config and one-shot rule-pack overrides.""" - selected_config = default_python_harness_config() if config is None else config + selected_config = default_asp_python_config() if config is None else config if rule_packs is None: return selected_config return replace(selected_config, rule_packs=tuple(rule_packs)) -def resolve_project_harness_config( +def resolve_asp_python_project_config( project_root: str | Path, - config: PythonHarnessConfig | None, + config: AspPythonConfig | None, *, - rule_packs: Sequence[PythonLangRulePack] | None, -) -> PythonHarnessConfig: + rule_packs: Sequence[AspPythonRulePack] | None, +) -> AspPythonConfig: """Resolve config for project-root runs, including pyproject policy.""" - selected_config = ( - read_python_project_harness_config(project_root) if config is None else config - ) - resolved = resolve_harness_config(selected_config, rule_packs=rule_packs) + selected_config = read_asp_python_config(project_root) if config is None else config + resolved = resolve_asp_python_config(selected_config, rule_packs=rule_packs) return apply_asp_project_discovery_config(project_root, resolved) def selected_rule_packs( - config: PythonHarnessConfig, -) -> tuple[PythonLangRulePack, ...]: + config: AspPythonConfig, +) -> tuple[AspPythonRulePack, ...]: """Return configured rule packs, falling back to the default catalog.""" if config.rule_packs is not None: return config.rule_packs - return default_python_lang_rule_packs() + return default_asp_python_rule_packs() diff --git a/src/python_lang_project_harness/_runner.py b/src/asp_python/_runner.py similarity index 64% rename from src/python_lang_project_harness/_runner.py rename to src/asp_python/_runner.py index a498c88..b881030 100644 --- a/src/python_lang_project_harness/_runner.py +++ b/src/asp_python/_runner.py @@ -1,37 +1,39 @@ -"""Runner API for embedding the Python language harness in pytest.""" +"""Runner API for embedding the ASP Python in pytest.""" from __future__ import annotations +from concurrent.futures import ThreadPoolExecutor from dataclasses import replace from pathlib import Path from typing import TYPE_CHECKING from python_lang_parser._diagnostic_model import PythonDiagnosticSeverity +from python_lang_parser.model import PythonModuleReport from python_lang_parser.parser import parse_python_file -from ._discovery import discover_python_files, python_project_harness_scope +from ._discovery import asp_python_scope, discover_python_files from ._model import ( - PythonHarnessConfig, - PythonHarnessFinding, - PythonHarnessReport, - PythonLangRulePack, + AspPythonConfig, + AspPythonFinding, + AspPythonReport, + AspPythonRulePack, ) if TYPE_CHECKING: from collections.abc import Sequence -def run_python_project_harness( +def run_asp_python( project_root: str | Path, *, - config: PythonHarnessConfig | None = None, - rule_packs: Sequence[PythonLangRulePack] | None = None, + config: AspPythonConfig | None = None, + rule_packs: Sequence[AspPythonRulePack] | None = None, include_tests: bool | None = None, source_dir_names: Sequence[str] | None = None, test_dir_names: Sequence[str] | None = None, extra_path_names: Sequence[str] | None = None, -) -> PythonHarnessReport: - """Run the harness over conventional Python project paths.""" +) -> AspPythonReport: + """Run ASP Python over conventional Python project paths.""" root = Path(project_root) if not root.exists(): @@ -40,15 +42,15 @@ def run_python_project_harness( compact_project_findings, evaluate_project_rule_packs, ) - from ._rule_packs import resolve_project_harness_config, selected_rule_packs + from ._rule_packs import resolve_asp_python_project_config, selected_rule_packs - selected_config = resolve_project_harness_config( + selected_config = resolve_asp_python_project_config( root, config, rule_packs=rule_packs, ) selected_packs = selected_rule_packs(selected_config) - scope = python_project_harness_scope( + scope = asp_python_scope( root, include_tests=( selected_config.include_tests if include_tests is None else include_tests @@ -71,7 +73,7 @@ def run_python_project_harness( ignored_dir_names=selected_config.ignored_dir_names, include_hidden_dir_names=selected_config.include_hidden_dir_names, ) - report = run_python_lang_harness( + report = run_asp_python_paths( scope.monitored_paths, config=selected_config, ) @@ -90,28 +92,28 @@ def run_python_project_harness( ) -def assert_python_project_harness_clean( +def assert_asp_python_clean( project_root: str | Path, *, - config: PythonHarnessConfig | None = None, - rule_packs: Sequence[PythonLangRulePack] | None = None, + config: AspPythonConfig | None = None, + rule_packs: Sequence[AspPythonRulePack] | None = None, severities: frozenset[PythonDiagnosticSeverity] | None = None, include_tests: bool | None = None, source_dir_names: Sequence[str] | None = None, test_dir_names: Sequence[str] | None = None, extra_path_names: Sequence[str] | None = None, include_advice: bool = True, -) -> PythonHarnessReport: - """Run the project harness and raise when configured-blocking findings exist.""" +) -> AspPythonReport: + """Run the ASP Python and raise when configured-blocking findings exist.""" - from ._rule_packs import resolve_project_harness_config + from ._rule_packs import resolve_asp_python_project_config - selected_config = resolve_project_harness_config( + selected_config = resolve_asp_python_project_config( Path(project_root), config, rule_packs=rule_packs, ) - report = run_python_project_harness( + report = run_asp_python( project_root, config=selected_config, include_tests=include_tests, @@ -130,33 +132,32 @@ def assert_python_project_harness_clean( return report -def run_python_lang_harness( +def run_asp_python_paths( paths: Sequence[str | Path], *, - config: PythonHarnessConfig | None = None, - rule_packs: Sequence[PythonLangRulePack] | None = None, -) -> PythonHarnessReport: - """Run the Python language harness over files or directories.""" + config: AspPythonConfig | None = None, + rule_packs: Sequence[AspPythonRulePack] | None = None, +) -> AspPythonReport: + """Run the ASP Python over files or directories.""" if rule_packs == (): selected_config = ( replace(config, rule_packs=()) if config is not None - else PythonHarnessConfig(rule_packs=()) + else AspPythonConfig(rule_packs=()) ) selected_packs = () else: - from ._rule_packs import resolve_harness_config, selected_rule_packs + from ._rule_packs import resolve_asp_python_config, selected_rule_packs - selected_config = resolve_harness_config(config, rule_packs=rule_packs) + selected_config = resolve_asp_python_config(config, rule_packs=rule_packs) selected_packs = selected_rule_packs(selected_config) root_paths = tuple(Path(path) for path in paths) for path in root_paths: if not path.exists(): - raise ValueError(f"harness path does not exist: {path}") - modules = tuple( - parse_python_file(path) - for path in discover_python_files( + raise ValueError(f"ASP Python path does not exist: {path}") + modules = _parse_python_files( + discover_python_files( root_paths, ignored_dir_names=selected_config.ignored_dir_names, include_hidden_dir_names=selected_config.include_hidden_dir_names, @@ -168,7 +169,7 @@ def run_python_lang_harness( for rule_pack in selected_packs for finding in rule_pack.evaluate(module) ) - return PythonHarnessReport( + return AspPythonReport( modules=modules, findings=_configured_findings(findings, config=selected_config), root_paths=tuple(str(path) for path in root_paths), @@ -178,20 +179,29 @@ def run_python_lang_harness( ) -def assert_python_lang_harness_clean( +def _parse_python_files(paths: Sequence[Path]) -> tuple[PythonModuleReport, ...]: + """Parse independent files concurrently while retaining discovery order.""" + + if len(paths) < 2: + return tuple(parse_python_file(path) for path in paths) + with ThreadPoolExecutor(thread_name_prefix="asp-python-parse") as executor: + return tuple(executor.map(parse_python_file, paths)) + + +def assert_asp_python_paths_clean( paths: Sequence[str | Path], *, - config: PythonHarnessConfig | None = None, - rule_packs: Sequence[PythonLangRulePack] | None = None, + config: AspPythonConfig | None = None, + rule_packs: Sequence[AspPythonRulePack] | None = None, severities: frozenset[PythonDiagnosticSeverity] | None = None, include_advice: bool = True, -) -> PythonHarnessReport: - """Run the harness and raise when configured-blocking findings are present.""" +) -> AspPythonReport: + """Run ASP Python and raise when configured-blocking findings are present.""" - from ._rule_packs import resolve_harness_config + from ._rule_packs import resolve_asp_python_config - selected_config = resolve_harness_config(config, rule_packs=rule_packs) - report = run_python_lang_harness(paths, config=selected_config) + selected_config = resolve_asp_python_config(config, rule_packs=rule_packs) + report = run_asp_python_paths(paths, config=selected_config) report.assert_clean( severities=( severities @@ -204,10 +214,10 @@ def assert_python_lang_harness_clean( def _configured_findings( - findings: tuple[PythonHarnessFinding, ...], + findings: tuple[AspPythonFinding, ...], *, - config: PythonHarnessConfig, -) -> tuple[PythonHarnessFinding, ...]: + config: AspPythonConfig, +) -> tuple[AspPythonFinding, ...]: if not config.disabled_rule_ids: return findings return tuple( diff --git a/src/python_lang_project_harness/_runtime.py b/src/asp_python/_runtime.py similarity index 59% rename from src/python_lang_project_harness/_runtime.py rename to src/asp_python/_runtime.py index 2da0a3d..0f013c7 100644 --- a/src/python_lang_project_harness/_runtime.py +++ b/src/asp_python/_runtime.py @@ -2,6 +2,7 @@ from __future__ import annotations +import json import os from pathlib import Path from typing import Any @@ -9,23 +10,31 @@ _REQUEST_SCHEMA_ID = "agent.semantic-protocols.provider-runtime-request-frame" _RESPONSE_SCHEMA_ID = "agent.semantic-protocols.provider-runtime-response-frame" _HEALTH_SCHEMA_ID = "agent.semantic-protocols.provider-runtime-contract-receipt" -_OPERATIONS = [ - { - "operation": "projection-batch", - "requestSchemaId": "https://schemas.agent-semantic-protocols.dev/provider-language-projection-batch-request.schema.json", - "responseSchemaId": "https://schemas.agent-semantic-protocols.dev/provider-language-projection-batch-response.schema.json", - }, - { - "operation": "project-resolution", - "requestSchemaId": "https://schemas.agent-semantic-protocols.dev/provider-project-resolution-request.schema.json", - "responseSchemaId": "https://schemas.agent-semantic-protocols.dev/provider-project-resolution-response.schema.json", - }, - { - "operation": "query", - "requestSchemaId": "https://agent-semantic-protocols.dev/schemas/provider-native-exact-request.v1.schema.json", - "responseSchemaId": "https://agent-semantic-protocols.dev/schemas/provider-native-exact-response.v1.schema.json", - }, -] + + +def _project_projection(payload: dict[str, Any], _cwd: Path) -> dict[str, Any]: + from ._projection_batch import project_projection_batch + + return project_projection_batch(payload) + + +def _resolve_project(payload: dict[str, Any], cwd: Path) -> dict[str, Any]: + from ._project_resolution import resolve_project_resolution_request + + return resolve_project_resolution_request(payload, cwd=cwd) + + +def _query_exact_source(payload: dict[str, Any], cwd: Path) -> dict[str, Any]: + from ._exact_source_projection import project_provider_native_exact_request + + return project_provider_native_exact_request(payload, cwd=cwd) + + +_OPERATION_HANDLERS = { + "projection-batch": _project_projection, + "project-resolution": _resolve_project, + "query": _query_exact_source, +} def _required_env(name: str) -> str: @@ -35,6 +44,28 @@ def _required_env(name: str) -> str: return value +def _runtime_operations() -> list[dict[str, str]]: + name = "ASP_PROVIDER_RUNTIME_OPERATIONS_JSON" + try: + operations = json.loads(_required_env(name)) + except json.JSONDecodeError as error: + raise RuntimeError( + f"resident Python provider received invalid {name}" + ) from error + if not isinstance(operations, list) or not all( + isinstance(operation, dict) for operation in operations + ): + raise RuntimeError(f"resident Python provider received invalid {name}") + operation_names = {operation.get("operation") for operation in operations} + if operation_names != set(_OPERATION_HANDLERS) or len(operations) != len( + operation_names + ): + raise RuntimeError( + "resident Python provider runtime operations do not match supported handlers" + ) + return operations + + def _health() -> dict[str, Any]: return { "schemaId": _HEALTH_SCHEMA_ID, @@ -45,26 +76,17 @@ def _health() -> dict[str, Any]: "registrationDigest": _required_env("ASP_PROVIDER_REGISTRATION_DIGEST"), "contractDigest": _required_env("ASP_PROVIDER_RUNTIME_CONTRACT_DIGEST"), "transport": "http-json", - "operations": _OPERATIONS, + "operations": _runtime_operations(), } def _execute(operation: str, payload: dict[str, Any], cwd: Path) -> dict[str, Any]: - if operation == "projection-batch": - from ._projection_batch import project_projection_batch - - return project_projection_batch(payload) - if operation == "project-resolution": - from ._project_resolution import resolve_project_resolution_request - - return resolve_project_resolution_request(payload, cwd=cwd) - if operation == "query": - from ._exact_source_projection import project_provider_native_exact_request - - return project_provider_native_exact_request(payload, cwd=cwd) - raise RuntimeError( - f"resident Python provider operation is not admitted: {operation}" - ) + handler = _OPERATION_HANDLERS.get(operation) + if handler is None: + raise RuntimeError( + f"resident Python provider operation is not admitted: {operation}" + ) + return handler(payload, cwd) def _response_frame(request: dict[str, Any], cwd: Path) -> dict[str, Any]: @@ -93,7 +115,7 @@ def _response_frame(request: dict[str, Any], cwd: Path) -> dict[str, Any]: if not isinstance(payload, dict): raise RuntimeError("provider runtime payload is not an object") result = _execute(operation, payload, cwd) - except (RuntimeError, ValueError) as execution_error: + except (RuntimeError, SyntaxError, ValueError) as execution_error: error = str(execution_error) else: return { diff --git a/src/python_lang_project_harness/_runtime_http.py b/src/asp_python/_runtime_http.py similarity index 100% rename from src/python_lang_project_harness/_runtime_http.py rename to src/asp_python/_runtime_http.py diff --git a/src/python_lang_project_harness/_semantic_graph_fact_model.py b/src/asp_python/_semantic_graph_fact_model.py similarity index 100% rename from src/python_lang_project_harness/_semantic_graph_fact_model.py rename to src/asp_python/_semantic_graph_fact_model.py diff --git a/src/asp_python/_semantic_language.py b/src/asp_python/_semantic_language.py new file mode 100644 index 0000000..fe4bda1 --- /dev/null +++ b/src/asp_python/_semantic_language.py @@ -0,0 +1,93 @@ +"""Semantic-language registry metadata for the Python provider.""" + +from __future__ import annotations + +from typing import Any + +from . import _semantic_language_ids as ids +from ._semantic_language_invocation import attach_semantic_language_invocations +from ._semantic_language_query import python_query_method_descriptors +from ._semantic_language_schemas import python_semantic_language_schemas +from ._semantic_provider_doctor import _provider_identity +from ._semantic_query_pack import python_query_pack_descriptor + +_PYTHON_QUERY_METHODS = ( + "query", + "query/exact-selector-native-v1", +) +_PYTHON_AST_PATCH_METHODS = ("ast-patch/dry-run",) +_PYTHON_AGENT_METHODS = ("agent/doctor", "agent/guide") + + +def semantic_language_registry_document() -> dict[str, Any]: + """Return the provider registry document advertised by agent doctor.""" + + payload: dict[str, Any] = { + "registryId": ids.SEMANTIC_LANGUAGE_REGISTRY_ID, + "registryVersion": ids.SEMANTIC_LANGUAGE_REGISTRY_VERSION, + "protocolId": ids.SEMANTIC_LANGUAGE_PROTOCOL_ID, + "protocolVersion": ids.SEMANTIC_LANGUAGE_PROTOCOL_VERSION, + "languages": [python_semantic_language_registration()], + } + return payload + + +def python_semantic_language_registration() -> dict[str, Any]: + """Return the Python semantic-language provider registration.""" + + identity = _provider_identity() + return { + "languageId": identity["languageId"], + "providerId": identity["providerId"], + "binary": identity["binary"], + "namespace": ids.PYTHON_PROVIDER_NAMESPACE, + "displayName": "Python", + "methods": [ + *_PYTHON_QUERY_METHODS, + *_PYTHON_AST_PATCH_METHODS, + *_PYTHON_AGENT_METHODS, + ], + "methodDescriptors": python_semantic_language_method_descriptors(), + "schemas": python_semantic_language_schemas(), + "queryPackDescriptor": python_query_pack_descriptor(), + } + + +def python_semantic_language_method_descriptors() -> list[dict[str, Any]]: + """Return method descriptors for the Python provider registry.""" + + descriptors: list[dict[str, Any]] = [] + descriptors.extend(python_query_method_descriptors()) + descriptors.extend( + { + "method": method, + "command": "ast-patch", + "input": "semantic-ast-patch packet", + "requiredOptions": ["--packet"], + "outputSchemaIds": ["agent.semantic-protocols.semantic-ast-patch-receipt"], + "supportsJson": True, + "supportsCompact": False, + "mutationAvailable": False, + } + for method in _PYTHON_AST_PATCH_METHODS + ) + descriptors.extend( + [ + { + "method": "agent/doctor", + "command": "agent", + "outputSchemaIds": [ + "agent.semantic-protocols.semantic-provider-doctor" + ], + "supportsJson": True, + "supportsCompact": True, + }, + { + "method": "agent/guide", + "command": "agent", + "supportsJson": False, + "supportsCompact": True, + }, + ] + ) + return attach_semantic_language_invocations(descriptors) diff --git a/src/python_lang_project_harness/_semantic_language_ids.py b/src/asp_python/_semantic_language_ids.py similarity index 84% rename from src/python_lang_project_harness/_semantic_language_ids.py rename to src/asp_python/_semantic_language_ids.py index f89ecec..8e8fefc 100644 --- a/src/python_lang_project_harness/_semantic_language_ids.py +++ b/src/asp_python/_semantic_language_ids.py @@ -4,16 +4,12 @@ SEMANTIC_LANGUAGE_REGISTRY_VERSION = "1" SEMANTIC_LANGUAGE_PROTOCOL_ID = "agent.semantic-protocols.semantic-language" SEMANTIC_LANGUAGE_PROTOCOL_VERSION = "1" -SEMANTIC_SEARCH_PACKET_SCHEMA_ID = "agent.semantic-protocols.semantic-search-packet" SEMANTIC_QUERY_PACKET_SCHEMA_ID = "agent.semantic-protocols.semantic-query-packet" SEMANTIC_SOURCE_LOCATION_SCHEMA_ID = "agent.semantic-protocols.semantic-source-location" SEMANTIC_TREE_SITTER_PROVENANCE_SCHEMA_ID = ( "agent.semantic-protocols.semantic-tree-sitter-provenance" ) SEMANTIC_GRAPH_SCHEMA_ID = "agent.semantic-protocols.semantic-graph" -SEMANTIC_GRAPH_TURBO_REQUEST_SCHEMA_ID = ( - "agent.semantic-protocols.semantic-graph-turbo-request" -) SEMANTIC_TYPE_SURFACE_SCHEMA_ID = "agent.semantic-protocols.semantic-type-surface" SEMANTIC_FACT_GRAPH_SCHEMA_ID = "agent.semantic-protocols.semantic-fact-graph" SEMANTIC_FACT_ONTOLOGY_SCHEMA_ID = "agent.semantic-protocols.semantic-fact-ontology" @@ -25,8 +21,6 @@ "agent.semantic-protocols.semantic-formal-proof-pilot" ) SEMANTIC_REVIEW_PACKET_SCHEMA_ID = "agent.semantic-protocols.semantic-review-packet" -SEMANTIC_EVIDENCE_GRAPH_SCHEMA_ID = "agent.semantic-protocols.semantic-evidence-graph" -SEMANTIC_ASSURANCE_CASE_SCHEMA_ID = "agent.semantic-protocols.semantic-assurance-case" SEMANTIC_AST_PATCH_SCHEMA_ID = "agent.semantic-protocols.semantic-ast-patch" SEMANTIC_AST_PATCH_RECEIPT_SCHEMA_ID = ( "agent.semantic-protocols.semantic-ast-patch-receipt" diff --git a/src/python_lang_project_harness/_semantic_language_invocation.py b/src/asp_python/_semantic_language_invocation.py similarity index 80% rename from src/python_lang_project_harness/_semantic_language_invocation.py rename to src/asp_python/_semantic_language_invocation.py index 18a6dc6..54d2cb5 100644 --- a/src/python_lang_project_harness/_semantic_language_invocation.py +++ b/src/asp_python/_semantic_language_invocation.py @@ -53,8 +53,6 @@ def _non_search_invocation(method: str) -> dict[str, list[str]]: "--workspace", "{workspace}", ], - "check/changed": [ids.PYTHON_BINARY, "check", "--changed", "{workspace}"], - "check/full": [ids.PYTHON_BINARY, "check", "--full", "{workspace}"], "ast-patch/dry-run": [ ids.PYTHON_BINARY, "ast-patch", @@ -62,20 +60,6 @@ def _non_search_invocation(method: str) -> dict[str, list[str]]: "--packet", "{packet}", ], - "evidence/graph": [ - ids.PYTHON_BINARY, - "evidence", - "graph", - "--json", - "{workspace}", - ], - "evidence/analyze": [ - ids.PYTHON_BINARY, - "evidence", - "analyze", - "--json", - "{workspace}", - ], "agent/doctor": [ids.PYTHON_BINARY, "agent", "doctor", "--json"], "agent/guide": [ids.PYTHON_BINARY, "agent", "guide"], } diff --git a/src/python_lang_project_harness/_semantic_language_query.py b/src/asp_python/_semantic_language_query.py similarity index 100% rename from src/python_lang_project_harness/_semantic_language_query.py rename to src/asp_python/_semantic_language_query.py diff --git a/src/asp_python/_semantic_language_schemas.py b/src/asp_python/_semantic_language_schemas.py new file mode 100644 index 0000000..d9002ea --- /dev/null +++ b/src/asp_python/_semantic_language_schemas.py @@ -0,0 +1,17 @@ +"""Provider-owned schema registrations for the Python semantic language.""" + +from __future__ import annotations + +from . import _semantic_language_ids as ids + + +def python_semantic_language_schemas() -> list[dict[str, str]]: + """Return only schemas owned by the Python provider.""" + + return [ + { + "schemaId": ids.PYTHON_CAPABILITIES_SCHEMA_ID, + "schemaVersion": "1", + "path": "schemas/python-semantic-capabilities.v1.schema.json", + } + ] diff --git a/src/python_lang_project_harness/_semantic_projection.py b/src/asp_python/_semantic_projection.py similarity index 100% rename from src/python_lang_project_harness/_semantic_projection.py rename to src/asp_python/_semantic_projection.py diff --git a/src/python_lang_project_harness/_semantic_provider_doctor.py b/src/asp_python/_semantic_provider_doctor.py similarity index 100% rename from src/python_lang_project_harness/_semantic_provider_doctor.py rename to src/asp_python/_semantic_provider_doctor.py diff --git a/src/python_lang_project_harness/_semantic_query_pack.py b/src/asp_python/_semantic_query_pack.py similarity index 100% rename from src/python_lang_project_harness/_semantic_query_pack.py rename to src/asp_python/_semantic_query_pack.py diff --git a/src/python_lang_project_harness/_semantic_query_packet.py b/src/asp_python/_semantic_query_packet.py similarity index 95% rename from src/python_lang_project_harness/_semantic_query_packet.py rename to src/asp_python/_semantic_query_packet.py index c0b35ff..67b3076 100644 --- a/src/python_lang_project_harness/_semantic_query_packet.py +++ b/src/asp_python/_semantic_query_packet.py @@ -2,6 +2,7 @@ from __future__ import annotations +import shlex from typing import Any from ._semantic_projection import semantic_query_projection @@ -126,9 +127,11 @@ def _routes_for_term(import_routes: list[Any], term: str) -> list[dict[str, str] def semantic_import_route_next(route: dict[str, str]) -> str: + owner = shlex.quote(route["ownerPath"]) + query = shlex.quote(route["query"]) return ( - "asp python search owner " - f"{route['ownerPath']} items --query {route['query']} --workspace . --view seeds" + "asp search playbook --language python " + f"--rg -n -e {query} {owner} --tantivy term {query}" ) diff --git a/src/python_lang_project_harness/_semantic_selector_identity.py b/src/asp_python/_semantic_selector_identity.py similarity index 100% rename from src/python_lang_project_harness/_semantic_selector_identity.py rename to src/asp_python/_semantic_selector_identity.py diff --git a/src/python_lang_project_harness/_semantic_syntax_refs.py b/src/asp_python/_semantic_syntax_refs.py similarity index 100% rename from src/python_lang_project_harness/_semantic_syntax_refs.py rename to src/asp_python/_semantic_syntax_refs.py diff --git a/src/python_lang_project_harness/_source.py b/src/asp_python/_source.py similarity index 90% rename from src/python_lang_project_harness/_source.py rename to src/asp_python/_source.py index 4f49448..71ca49c 100644 --- a/src/python_lang_project_harness/_source.py +++ b/src/asp_python/_source.py @@ -1,4 +1,4 @@ -"""Shared file-location helpers for deterministic harness rules.""" +"""Shared file-location helpers for deterministic ASP Python rules.""" from __future__ import annotations diff --git a/src/python_lang_project_harness/_syntax.py b/src/asp_python/_syntax.py similarity index 87% rename from src/python_lang_project_harness/_syntax.py rename to src/asp_python/_syntax.py index e8f0f1f..cb1b62f 100644 --- a/src/python_lang_project_harness/_syntax.py +++ b/src/asp_python/_syntax.py @@ -8,7 +8,7 @@ from python_lang_parser._diagnostic_model import PythonDiagnosticSeverity from ._model import ( - PythonHarnessFinding, + AspPythonFinding, PythonRulePackDescriptor, ) from ._syntax_catalog import SYNTAX_PACK_ID, syntax_rule @@ -21,7 +21,7 @@ @dataclass(frozen=True, slots=True) class PythonSyntaxRulePack: - """Rule pack that turns parser diagnostics into harness findings.""" + """Rule pack that turns parser diagnostics into ASP Python findings.""" pack_id: str = SYNTAX_PACK_ID @@ -35,14 +35,14 @@ def descriptor(self) -> PythonRulePackDescriptor: default_mode="blocking", ) - def evaluate(self, report: PythonModuleReport) -> Iterable[PythonHarnessFinding]: + def evaluate(self, report: PythonModuleReport) -> Iterable[AspPythonFinding]: """Evaluate parse diagnostics for one module report.""" for diagnostic in report.diagnostics: if diagnostic.severity != PythonDiagnosticSeverity.ERROR: continue rule = syntax_rule(diagnostic.code) - yield PythonHarnessFinding( + yield AspPythonFinding( rule_id=rule.rule_id, pack_id=self.pack_id, severity=rule.severity, diff --git a/src/python_lang_project_harness/_syntax_catalog.py b/src/asp_python/_syntax_catalog.py similarity index 88% rename from src/python_lang_project_harness/_syntax_catalog.py rename to src/asp_python/_syntax_catalog.py index 0363f35..72babc2 100644 --- a/src/python_lang_project_harness/_syntax_catalog.py +++ b/src/asp_python/_syntax_catalog.py @@ -6,7 +6,7 @@ from python_lang_parser._diagnostic_model import PythonDiagnosticSeverity -from ._model import PythonHarnessRule +from ._model import AspPythonRule SYNTAX_PACK_ID = "python.syntax" PYTHON_SYNTAX_INVALID = "python.syntax.invalid" @@ -17,7 +17,7 @@ "domain": "syntax", } _RULES = ( - PythonHarnessRule( + AspPythonRule( rule_id=PYTHON_SYNTAX_INVALID, pack_id=SYNTAX_PACK_ID, severity=PythonDiagnosticSeverity.ERROR, @@ -25,7 +25,7 @@ requirement="Python modules must parse with CPython native syntax before project rules run.", labels=dict(_RULE_LABELS), ), - PythonHarnessRule( + AspPythonRule( rule_id=PYTHON_COMPILE_INVALID, pack_id=SYNTAX_PACK_ID, severity=PythonDiagnosticSeverity.ERROR, @@ -37,19 +37,19 @@ _RULE_BY_ID = {rule.rule_id: rule for rule in _RULES} -def python_syntax_rules() -> tuple[PythonHarnessRule, ...]: +def python_syntax_rules() -> tuple[AspPythonRule, ...]: """Return compact metadata for native Python syntax rules.""" return tuple(replace(rule, labels=dict(rule.labels)) for rule in _RULES) -def syntax_rule(rule_id: str) -> PythonHarnessRule: +def syntax_rule(rule_id: str) -> AspPythonRule: """Return one syntax rule descriptor by stable rule id.""" rule = _RULE_BY_ID.get(rule_id) if rule is not None: return rule - return PythonHarnessRule( + return AspPythonRule( rule_id=rule_id, pack_id=SYNTAX_PACK_ID, severity=PythonDiagnosticSeverity.ERROR, diff --git a/src/python_lang_project_harness/_test_layout.py b/src/asp_python/_test_layout.py similarity index 69% rename from src/python_lang_project_harness/_test_layout.py rename to src/asp_python/_test_layout.py index 10fdff2..aa0174d 100644 --- a/src/python_lang_project_harness/_test_layout.py +++ b/src/asp_python/_test_layout.py @@ -1,12 +1,12 @@ -"""Pytest layout rule pack aligned with the project harness.""" +"""Pytest layout rule pack aligned with the ASP Python.""" from __future__ import annotations from dataclasses import dataclass from typing import TYPE_CHECKING -from ._discovery import python_project_harness_scope -from ._model import PythonHarnessFinding, PythonRulePackDescriptor +from ._discovery import asp_python_scope +from ._model import AspPythonFinding, PythonRulePackDescriptor from ._test_layout_bloat import bloated_unit_test_findings from ._test_layout_catalog import TEST_LAYOUT_PACK_ID from ._test_layout_entries import tests_root_entry_findings @@ -17,12 +17,12 @@ from python_lang_parser import PythonModuleReport - from ._model import PythonProjectHarnessScope + from ._model import AspPythonProjectScope @dataclass(frozen=True, slots=True) class PythonTestLayoutRulePack: - """Project-level pytest layout rules aligned with the Rust unit harness gate.""" + """Project-level pytest layout rules aligned with the Rust unit ASP Python gate.""" pack_id: str = TEST_LAYOUT_PACK_ID @@ -36,42 +36,40 @@ def descriptor(self) -> PythonRulePackDescriptor: default_mode="blocking", ) - def evaluate(self, report: PythonModuleReport) -> Iterable[PythonHarnessFinding]: + def evaluate(self, report: PythonModuleReport) -> Iterable[AspPythonFinding]: """Module-level parser reports do not carry project layout authority.""" return () - def evaluate_project(self, project_root: Path) -> Iterable[PythonHarnessFinding]: + def evaluate_project(self, project_root: Path) -> Iterable[AspPythonFinding]: """Evaluate project-level pytest layout rules.""" - return self.evaluate_project_resolution( - python_project_harness_scope(project_root) - ) + return self.evaluate_project_resolution(asp_python_scope(project_root)) def evaluate_project_resolution( self, - scope: PythonProjectHarnessScope, - ) -> Iterable[PythonHarnessFinding]: + scope: AspPythonProjectScope, + ) -> Iterable[AspPythonFinding]: """Evaluate project-level pytest layout rules for monitored test roots.""" return _test_layout_findings(scope, (), self.pack_id) def evaluate_project_modules( self, - scope: PythonProjectHarnessScope, + scope: AspPythonProjectScope, modules: Sequence[PythonModuleReport], - ) -> Iterable[PythonHarnessFinding]: + ) -> Iterable[AspPythonFinding]: """Evaluate pytest layout rules using parser-owned module facts.""" return _test_layout_findings(scope, modules, self.pack_id) def _test_layout_findings( - scope: PythonProjectHarnessScope, + scope: AspPythonProjectScope, modules: Sequence[PythonModuleReport], pack_id: str, -) -> tuple[PythonHarnessFinding, ...]: - findings: list[PythonHarnessFinding] = [] +) -> tuple[AspPythonFinding, ...]: + findings: list[AspPythonFinding] = [] for tests_dir in scope.test_paths: if not tests_dir.exists(): continue diff --git a/src/python_lang_project_harness/_test_layout_bloat.py b/src/asp_python/_test_layout_bloat.py similarity index 91% rename from src/python_lang_project_harness/_test_layout_bloat.py rename to src/asp_python/_test_layout_bloat.py index b11d041..d141989 100644 --- a/src/python_lang_project_harness/_test_layout_bloat.py +++ b/src/asp_python/_test_layout_bloat.py @@ -7,7 +7,7 @@ from python_lang_parser import python_symbol_is_test_function -from ._model import PythonHarnessFinding +from ._model import AspPythonFinding from ._source import path_location from ._test_layout_catalog import ( MAX_UNIT_TEST_EFFECTIVE_LINES, @@ -21,14 +21,14 @@ from python_lang_parser import PythonModuleReport, PythonSymbol - from ._model import PythonProjectHarnessScope + from ._model import AspPythonProjectScope def bloated_unit_test_findings( - scope: PythonProjectHarnessScope, + scope: AspPythonProjectScope, modules: Sequence[PythonModuleReport], pack_id: str, -) -> tuple[PythonHarnessFinding, ...]: +) -> tuple[AspPythonFinding, ...]: """Return oversized unit-test leaf findings from parser-owned facts.""" unit_dirs = tuple( @@ -39,7 +39,7 @@ def bloated_unit_test_findings( if not unit_dirs: return () - findings: list[PythonHarnessFinding] = [] + findings: list[AspPythonFinding] = [] for report in sorted(modules, key=lambda item: item.path or ""): if report.path is None or not report.is_valid or report.shape is None: continue @@ -85,9 +85,9 @@ def _bloated_unit_test_finding( effective_code_lines: int, test_functions: int, source_line: str | None, -) -> PythonHarnessFinding: +) -> AspPythonFinding: rule = test_layout_rule(PY_TEST_R003) - return PythonHarnessFinding( + return AspPythonFinding( rule_id=rule.rule_id, pack_id=pack_id, severity=rule.severity, diff --git a/src/python_lang_project_harness/_test_layout_catalog.py b/src/asp_python/_test_layout_catalog.py similarity index 82% rename from src/python_lang_project_harness/_test_layout_catalog.py rename to src/asp_python/_test_layout_catalog.py index 2e760d2..2bce4c9 100644 --- a/src/python_lang_project_harness/_test_layout_catalog.py +++ b/src/asp_python/_test_layout_catalog.py @@ -6,7 +6,7 @@ from python_lang_parser._diagnostic_model import PythonDiagnosticSeverity -from ._model import PythonHarnessRule +from ._model import AspPythonRule TEST_LAYOUT_PACK_ID = "python.test_layout" PY_TEST_R001 = "PY-TEST-R001" @@ -35,23 +35,23 @@ "domain": "pytest-layout", } _RULES = ( - PythonHarnessRule( + AspPythonRule( rule_id=PY_TEST_R001, pack_id=TEST_LAYOUT_PACK_ID, severity=PythonDiagnosticSeverity.WARNING, title="Pytest file is scattered in tests root", - requirement="Move pytest modules under `tests/unit/` or `tests/integration/` so the project harness owns suite shape.", + requirement="Move pytest modules under `tests/unit/` or `tests/integration/` so the ASP Python owns suite shape.", labels=dict(_RULE_LABELS), ), - PythonHarnessRule( + AspPythonRule( rule_id=PY_TEST_R002, pack_id=TEST_LAYOUT_PACK_ID, severity=PythonDiagnosticSeverity.WARNING, title="Unexpected tests root entry", - requirement="Keep tests root limited to harness configuration and owned suite directories.", + requirement="Keep tests root limited to ASP Python configuration and owned suite directories.", labels=dict(_RULE_LABELS), ), - PythonHarnessRule( + AspPythonRule( rule_id=PY_TEST_R003, pack_id=TEST_LAYOUT_PACK_ID, severity=PythonDiagnosticSeverity.WARNING, @@ -63,13 +63,13 @@ _RULE_BY_ID = {rule.rule_id: rule for rule in _RULES} -def python_test_layout_rules() -> tuple[PythonHarnessRule, ...]: +def python_test_layout_rules() -> tuple[AspPythonRule, ...]: """Return compact metadata for the default pytest-layout rules.""" return tuple(replace(rule, labels=dict(rule.labels)) for rule in _RULES) -def test_layout_rule(rule_id: str) -> PythonHarnessRule: +def test_layout_rule(rule_id: str) -> AspPythonRule: """Return one pytest-layout rule descriptor by stable rule id.""" return _RULE_BY_ID[rule_id] diff --git a/src/python_lang_project_harness/_test_layout_config.py b/src/asp_python/_test_layout_config.py similarity index 97% rename from src/python_lang_project_harness/_test_layout_config.py rename to src/asp_python/_test_layout_config.py index 761c88f..247ca09 100644 --- a/src/python_lang_project_harness/_test_layout_config.py +++ b/src/asp_python/_test_layout_config.py @@ -7,7 +7,7 @@ from pathlib import Path from typing import Any -TEST_LAYOUT_POLICY_CONFIG = "python-project-harness-rules.toml" +TEST_LAYOUT_POLICY_CONFIG = "asp-python-rules.toml" @dataclass(frozen=True, slots=True) diff --git a/src/python_lang_project_harness/_test_layout_entries.py b/src/asp_python/_test_layout_entries.py similarity index 93% rename from src/python_lang_project_harness/_test_layout_entries.py rename to src/asp_python/_test_layout_entries.py index eacd9d6..a6f433f 100644 --- a/src/python_lang_project_harness/_test_layout_entries.py +++ b/src/asp_python/_test_layout_entries.py @@ -5,7 +5,7 @@ from pathlib import Path from typing import TYPE_CHECKING -from ._model import PythonHarnessFinding +from ._model import AspPythonFinding from ._source import path_location, source_line from ._test_layout_catalog import ( ALLOWED_TEST_DIR_NAMES, @@ -26,12 +26,12 @@ def tests_root_entry_findings( tests_dir: Path, pack_id: str, modules: Sequence[PythonModuleReport] = (), -) -> tuple[PythonHarnessFinding, ...]: +) -> tuple[AspPythonFinding, ...]: """Return tests-root ownership findings for one test root.""" policy = load_test_layout_policy(tests_dir) modules_by_path = _modules_by_path(modules) - findings: list[PythonHarnessFinding] = [] + findings: list[AspPythonFinding] = [] for path in sorted(tests_dir.iterdir(), key=lambda item: item.as_posix()): name = path.name if name.startswith("."): @@ -61,9 +61,9 @@ def _root_pytest_file_finding( path: Path, pack_id: str, modules_by_path: Mapping[Path, PythonModuleReport], -) -> PythonHarnessFinding: +) -> AspPythonFinding: rule = test_layout_rule(PY_TEST_R001) - return PythonHarnessFinding( + return AspPythonFinding( rule_id=rule.rule_id, pack_id=pack_id, severity=rule.severity, @@ -81,9 +81,9 @@ def _unexpected_tests_root_entry_finding( path: Path, pack_id: str, modules_by_path: Mapping[Path, PythonModuleReport], -) -> PythonHarnessFinding: +) -> AspPythonFinding: rule = test_layout_rule(PY_TEST_R002) - return PythonHarnessFinding( + return AspPythonFinding( rule_id=rule.rule_id, pack_id=pack_id, severity=rule.severity, diff --git a/src/python_lang_project_harness/_tree_sitter_query.py b/src/asp_python/_tree_sitter_query.py similarity index 95% rename from src/python_lang_project_harness/_tree_sitter_query.py rename to src/asp_python/_tree_sitter_query.py index 6849909..901f2de 100644 --- a/src/python_lang_project_harness/_tree_sitter_query.py +++ b/src/asp_python/_tree_sitter_query.py @@ -16,13 +16,13 @@ if TYPE_CHECKING: from ._cli_args import ProtocolArgs - from ._model import PythonHarnessReport + from ._model import AspPythonReport def write_tree_sitter_query_response( args: ProtocolArgs, *, - report: PythonHarnessReport, + report: AspPythonReport, project_root: Path, stdout: TextIO, ) -> None: diff --git a/src/python_lang_project_harness/_tree_sitter_query_catalog.py b/src/asp_python/_tree_sitter_query_catalog.py similarity index 97% rename from src/python_lang_project_harness/_tree_sitter_query_catalog.py rename to src/asp_python/_tree_sitter_query_catalog.py index 3cf5620..6344c30 100644 --- a/src/python_lang_project_harness/_tree_sitter_query_catalog.py +++ b/src/asp_python/_tree_sitter_query_catalog.py @@ -188,8 +188,8 @@ def resolved_tree_sitter_query(args: Any) -> dict[str, Any]: source = args.tree_sitter_query.strip() if not args.asp_syntax_query_node_types: raise ValueError( - "tree-sitter query projection requires ASP-compiled query plan; use " - "`asp python query --treesitter-query ...` so ASP owns query ABI compilation" + "tree-sitter query projection requires an ASP-compiled query plan; use the " + "`--syntax python ...` block of `asp search playbook` so ASP owns query ABI compilation" ) return { "input": source, diff --git a/src/python_lang_project_harness/_tree_sitter_query_model.py b/src/asp_python/_tree_sitter_query_model.py similarity index 100% rename from src/python_lang_project_harness/_tree_sitter_query_model.py rename to src/asp_python/_tree_sitter_query_model.py diff --git a/src/python_lang_project_harness/_tree_sitter_query_packet.py b/src/asp_python/_tree_sitter_query_packet.py similarity index 100% rename from src/python_lang_project_harness/_tree_sitter_query_packet.py rename to src/asp_python/_tree_sitter_query_packet.py diff --git a/src/python_lang_project_harness/_tree_sitter_query_packet_rows.py b/src/asp_python/_tree_sitter_query_packet_rows.py similarity index 100% rename from src/python_lang_project_harness/_tree_sitter_query_packet_rows.py rename to src/asp_python/_tree_sitter_query_packet_rows.py diff --git a/src/python_lang_project_harness/_tree_sitter_query_predicates.py b/src/asp_python/_tree_sitter_query_predicates.py similarity index 100% rename from src/python_lang_project_harness/_tree_sitter_query_predicates.py rename to src/asp_python/_tree_sitter_query_predicates.py diff --git a/src/python_lang_project_harness/_tree_sitter_query_projection.py b/src/asp_python/_tree_sitter_query_projection.py similarity index 98% rename from src/python_lang_project_harness/_tree_sitter_query_projection.py rename to src/asp_python/_tree_sitter_query_projection.py index 612d7f0..f7318c4 100644 --- a/src/python_lang_project_harness/_tree_sitter_query_projection.py +++ b/src/asp_python/_tree_sitter_query_projection.py @@ -7,7 +7,7 @@ from typing import TYPE_CHECKING from ._python_expr import _expr -from ._semantic_search_common import semantic_search_display_path +from ._render import _render_display_path from ._tree_sitter_query_model import ( LEAF_TREE_SITTER_QUERY_NODES, MAX_SYNTAX_QUERY_ROWS, @@ -50,11 +50,11 @@ ) if TYPE_CHECKING: - from ._model import PythonHarnessReport + from ._model import AspPythonReport def project_tree_sitter_query( - report: PythonHarnessReport, + report: AspPythonReport, project_root: Path, query_node_types: tuple[str, ...], captures: tuple[str, ...], @@ -114,7 +114,7 @@ def _module_syntax_query_rows( ) -> list[SyntaxQueryRow]: if module.path is None: return [] - owner_path = semantic_search_display_path(module.path, project_root) + owner_path = _render_display_path(module.path, project_root=project_root) effective = effective_selector( owner_path, selector, diff --git a/src/python_lang_project_harness/_tree_sitter_query_projection_capture.py b/src/asp_python/_tree_sitter_query_projection_capture.py similarity index 100% rename from src/python_lang_project_harness/_tree_sitter_query_projection_capture.py rename to src/asp_python/_tree_sitter_query_projection_capture.py diff --git a/src/python_lang_project_harness/_tree_sitter_query_projection_source.py b/src/asp_python/_tree_sitter_query_projection_source.py similarity index 97% rename from src/python_lang_project_harness/_tree_sitter_query_projection_source.py rename to src/asp_python/_tree_sitter_query_projection_source.py index 87ab6c9..e3ad033 100644 --- a/src/python_lang_project_harness/_tree_sitter_query_projection_source.py +++ b/src/asp_python/_tree_sitter_query_projection_source.py @@ -9,7 +9,7 @@ from ._tree_sitter_query_model import SyntaxQuerySelector if TYPE_CHECKING: - from ._model import PythonHarnessReport + from ._model import AspPythonReport @dataclass(frozen=True) @@ -45,7 +45,7 @@ def resolve_selector_source( def syntax_sources( - report: PythonHarnessReport, + report: AspPythonReport, resolved_selector_source: ResolvedSelectorSource | None, ) -> list[SyntaxSource]: if resolved_selector_source is not None: diff --git a/src/python_lang_project_harness/_version.py b/src/asp_python/_version.py similarity index 72% rename from src/python_lang_project_harness/_version.py rename to src/asp_python/_version.py index c9189ed..61e3e3a 100644 --- a/src/python_lang_project_harness/_version.py +++ b/src/asp_python/_version.py @@ -1,11 +1,11 @@ -"""Installed package identity for the Python project harness.""" +"""Installed package identity for the ASP Python.""" from __future__ import annotations from importlib.metadata import PackageNotFoundError, version from typing import Final -_DISTRIBUTION_NAME: Final = "python-lang-project-harness" +_DISTRIBUTION_NAME: Final = "asp-python" def _installed_version() -> str: diff --git a/src/python_lang_project_harness/agent_readability/__init__.py b/src/asp_python/agent_readability/__init__.py similarity index 100% rename from src/python_lang_project_harness/agent_readability/__init__.py rename to src/asp_python/agent_readability/__init__.py diff --git a/src/python_lang_project_harness/agent_readability/_boundaries.py b/src/asp_python/agent_readability/_boundaries.py similarity index 100% rename from src/python_lang_project_harness/agent_readability/_boundaries.py rename to src/asp_python/agent_readability/_boundaries.py diff --git a/src/python_lang_project_harness/agent_readability/_software_criteria.py b/src/asp_python/agent_readability/_software_criteria.py similarity index 100% rename from src/python_lang_project_harness/agent_readability/_software_criteria.py rename to src/asp_python/agent_readability/_software_criteria.py diff --git a/src/python_lang_project_harness/agent_readability/algorithm_shape.py b/src/asp_python/agent_readability/algorithm_shape.py similarity index 96% rename from src/python_lang_project_harness/agent_readability/algorithm_shape.py rename to src/asp_python/agent_readability/algorithm_shape.py index c020d36..1048007 100644 --- a/src/python_lang_project_harness/agent_readability/algorithm_shape.py +++ b/src/asp_python/agent_readability/algorithm_shape.py @@ -5,7 +5,7 @@ from typing import TYPE_CHECKING from .._agent_policy_catalog import PY_AGENT_R009, agent_policy_rule -from .._model import PythonHarnessFinding +from .._model import AspPythonFinding from ._boundaries import ( agent_readability_function_is_boundary, agent_readability_public_class_scopes, @@ -29,12 +29,12 @@ def agent_algorithm_shape_findings( report: PythonModuleReport, pack_id: str, -) -> tuple[PythonHarnessFinding, ...]: +) -> tuple[AspPythonFinding, ...]: """Return machine-readability advice for public algorithm boundaries.""" if not agent_readability_report_is_in_scope(report): return () - findings: list[PythonHarnessFinding] = [] + findings: list[AspPythonFinding] = [] rule = agent_policy_rule(PY_AGENT_R009) public_class_scopes = agent_readability_public_class_scopes(report) for symbol in report.symbols: @@ -52,7 +52,7 @@ def agent_algorithm_shape_findings( continue criterion_ids = _agent_algorithm_software_criteria(profile) findings.append( - PythonHarnessFinding( + AspPythonFinding( rule_id=rule.rule_id, pack_id=pack_id, severity=rule.severity, diff --git a/src/python_lang_project_harness/agent_readability/function_compactness.py b/src/asp_python/agent_readability/function_compactness.py similarity index 95% rename from src/python_lang_project_harness/agent_readability/function_compactness.py rename to src/asp_python/agent_readability/function_compactness.py index 0d53ca7..fc579d5 100644 --- a/src/python_lang_project_harness/agent_readability/function_compactness.py +++ b/src/asp_python/agent_readability/function_compactness.py @@ -5,7 +5,7 @@ from typing import TYPE_CHECKING from .._agent_policy_catalog import PY_AGENT_R010, agent_policy_rule -from .._model import PythonHarnessFinding +from .._model import AspPythonFinding from ._boundaries import ( agent_readability_function_is_boundary, agent_readability_public_class_scopes, @@ -29,12 +29,12 @@ def agent_function_compactness_findings( report: PythonModuleReport, pack_id: str, -) -> tuple[PythonHarnessFinding, ...]: +) -> tuple[AspPythonFinding, ...]: """Return machine-readability advice for broad public function bodies.""" if not agent_readability_report_is_in_scope(report): return () - findings: list[PythonHarnessFinding] = [] + findings: list[AspPythonFinding] = [] rule = agent_policy_rule(PY_AGENT_R010) public_class_scopes = agent_readability_public_class_scopes(report) for symbol in report.symbols: @@ -52,7 +52,7 @@ def agent_function_compactness_findings( if not profile: continue findings.append( - PythonHarnessFinding( + AspPythonFinding( rule_id=rule.rule_id, pack_id=pack_id, severity=rule.severity, diff --git a/src/python_lang_project_harness/agent_readability/native_idioms.py b/src/asp_python/agent_readability/native_idioms.py similarity index 95% rename from src/python_lang_project_harness/agent_readability/native_idioms.py rename to src/asp_python/agent_readability/native_idioms.py index 7025af4..ad6e5e4 100644 --- a/src/python_lang_project_harness/agent_readability/native_idioms.py +++ b/src/asp_python/agent_readability/native_idioms.py @@ -5,7 +5,7 @@ from typing import TYPE_CHECKING from .._agent_policy_catalog import PY_AGENT_R011, agent_policy_rule -from .._model import PythonHarnessFinding +from .._model import AspPythonFinding from ._boundaries import ( agent_readability_function_is_boundary, agent_readability_public_class_scopes, @@ -24,12 +24,12 @@ def agent_native_idiom_findings( report: PythonModuleReport, pack_id: str, -) -> tuple[PythonHarnessFinding, ...]: +) -> tuple[AspPythonFinding, ...]: """Return repair advice for public functions that miss native Python idioms.""" if not agent_readability_report_is_in_scope(report): return () - findings: list[PythonHarnessFinding] = [] + findings: list[AspPythonFinding] = [] rule = agent_policy_rule(PY_AGENT_R011) public_class_scopes = agent_readability_public_class_scopes(report) for symbol in report.symbols: @@ -46,7 +46,7 @@ def agent_native_idiom_findings( if not profile: continue findings.append( - PythonHarnessFinding( + AspPythonFinding( rule_id=rule.rule_id, pack_id=pack_id, severity=rule.severity, diff --git a/src/python_lang_project_harness/agent_readability/type_shapes.py b/src/asp_python/agent_readability/type_shapes.py similarity index 95% rename from src/python_lang_project_harness/agent_readability/type_shapes.py rename to src/asp_python/agent_readability/type_shapes.py index 7469ed3..f911a52 100644 --- a/src/python_lang_project_harness/agent_readability/type_shapes.py +++ b/src/asp_python/agent_readability/type_shapes.py @@ -7,7 +7,7 @@ from python_lang_parser import python_symbol_is_public_class from .._agent_policy_catalog import PY_AGENT_R012, agent_policy_rule -from .._model import PythonHarnessFinding +from .._model import AspPythonFinding from ._boundaries import agent_readability_report_is_in_scope if TYPE_CHECKING: @@ -17,14 +17,14 @@ def agent_type_shape_findings( report: PythonModuleReport, pack_id: str, -) -> tuple[PythonHarnessFinding, ...]: +) -> tuple[AspPythonFinding, ...]: """Return repair advice for public classes that hide data shape.""" if not agent_readability_report_is_in_scope(report): return () rule = agent_policy_rule(PY_AGENT_R012) return tuple( - PythonHarnessFinding( + AspPythonFinding( rule_id=rule.rule_id, pack_id=pack_id, severity=rule.severity, diff --git a/src/python_lang_project_harness/harness.py b/src/asp_python/api.py similarity index 72% rename from src/python_lang_project_harness/harness.py rename to src/asp_python/api.py index 0c2be85..daf43a6 100644 --- a/src/python_lang_project_harness/harness.py +++ b/src/asp_python/api.py @@ -1,62 +1,56 @@ -"""Public facade for the embedded Python language project harness.""" +"""Public facade for ASP Python.""" from __future__ import annotations from ._agent_policy import PythonAgentPolicyRulePack from ._agent_policy_catalog import python_agent_policy_rules from ._agent_snapshot import ( - render_python_project_harness_agent_snapshot, - render_python_project_harness_agent_snapshot_with_config, + render_asp_python_agent_snapshot, + render_asp_python_agent_snapshot_with_config, ) from ._cli import run_cli, run_cli_from_env from ._discovery import ( + asp_python_paths, + asp_python_scope, discover_python_files, - python_project_harness_paths, - python_project_harness_scope, ) from ._model import ( - PythonHarnessConfig, - PythonHarnessFinding, - PythonHarnessReport, - PythonHarnessRule, - PythonLangRulePack, - PythonProjectHarnessScope, + AspPythonConfig, + AspPythonFinding, + AspPythonProjectScope, + AspPythonReport, + AspPythonRule, + AspPythonRulePack, PythonRulePackDescriptor, ) from ._modern_design import PythonModernDesignRulePack from ._modern_design_catalog import python_modern_design_rules from ._modularity import PythonModularityRulePack, python_modularity_rules -from ._project_config import read_python_project_harness_config +from ._project_config import read_asp_python_config from ._project_policy import PythonProjectPolicyRulePack from ._project_policy_catalog import python_project_policy_rules -from ._pytest import python_project_harness_test +from ._pytest import asp_python_test from ._render import ( - render_python_lang_harness, - render_python_lang_harness_advice, - render_python_lang_harness_json, + render_asp_python_report, + render_asp_python_report_advice, + render_asp_python_report_json, render_python_reasoning_tree, ) from ._rule_packs import ( - default_python_harness_config, - default_python_lang_rule_packs, + default_asp_python_config, + default_asp_python_rule_packs, python_rule_pack_descriptors, ) from ._runner import ( - assert_python_lang_harness_clean, - assert_python_project_harness_clean, - run_python_lang_harness, - run_python_project_harness, + assert_asp_python_clean, + assert_asp_python_paths_clean, + run_asp_python, + run_asp_python_paths, ) from ._semantic_language import ( python_semantic_language_registration, semantic_language_registry_document, ) -from ._semantic_search import ( - PythonSemanticSearchOptions, - build_python_semantic_search_packet, - render_python_semantic_search_packet, - render_python_semantic_search_packet_json, -) from ._syntax import PythonSyntaxRulePack from ._syntax_catalog import python_syntax_rules from ._test_layout import PythonTestLayoutRulePack @@ -107,17 +101,16 @@ __all__ = [ "PythonAgentPolicyRulePack", - "PythonHarnessConfig", - "PythonHarnessFinding", - "PythonHarnessReport", - "PythonHarnessRule", - "PythonLangRulePack", + "AspPythonConfig", + "AspPythonFinding", + "AspPythonReport", + "AspPythonRule", + "AspPythonRulePack", "PythonModernDesignRulePack", "PythonModularityRulePack", - "PythonProjectHarnessScope", + "AspPythonProjectScope", "PythonProjectPolicyRulePack", "PythonRulePackDescriptor", - "PythonSemanticSearchOptions", "PythonSyntaxRulePack", "PythonTestLayoutRulePack", "PythonOwnerResponsibility", @@ -144,20 +137,19 @@ "PythonVerificationTaskKind", "PythonVerificationTaskState", "PythonVerificationWaiver", - "assert_python_lang_harness_clean", - "assert_python_project_harness_clean", - "build_python_semantic_search_packet", - "default_python_harness_config", - "default_python_lang_rule_packs", + "assert_asp_python_paths_clean", + "assert_asp_python_clean", + "default_asp_python_config", + "default_asp_python_rule_packs", "discover_python_files", "python_agent_policy_rules", "python_modern_design_rules", "python_modularity_rules", - "python_project_harness_paths", - "python_project_harness_scope", - "python_project_harness_test", + "asp_python_paths", + "asp_python_scope", + "asp_python_test", "python_project_policy_rules", - "read_python_project_harness_config", + "read_asp_python_config", "python_semantic_language_registration", "python_rule_pack_descriptors", "python_syntax_rules", @@ -169,14 +161,12 @@ "build_python_verification_task_index", "plan_python_project_verification", "plan_python_project_verification_with_config", - "render_python_lang_harness", - "render_python_lang_harness_advice", - "render_python_lang_harness_json", + "render_asp_python_report", + "render_asp_python_report_advice", + "render_asp_python_report_json", "render_python_reasoning_tree", - "render_python_project_harness_agent_snapshot", - "render_python_project_harness_agent_snapshot_with_config", - "render_python_semantic_search_packet", - "render_python_semantic_search_packet_json", + "render_asp_python_agent_snapshot", + "render_asp_python_agent_snapshot_with_config", "render_python_verification_performance_index_json", "render_python_verification_plan", "render_python_verification_plan_json", @@ -189,7 +179,7 @@ "write_python_verification_reports", "run_cli", "run_cli_from_env", - "run_python_lang_harness", - "run_python_project_harness", + "run_asp_python_paths", + "run_asp_python", "semantic_language_registry_document", ] diff --git a/src/python_lang_project_harness/harness-rules.md b/src/asp_python/asp-rules.md similarity index 93% rename from src/python_lang_project_harness/harness-rules.md rename to src/asp_python/asp-rules.md index c2dddb6..3e844c3 100644 --- a/src/python_lang_project_harness/harness-rules.md +++ b/src/asp_python/asp-rules.md @@ -25,8 +25,8 @@ - PY-AGENT-PROJECT-007: Requires build-system tables to declare build requirements. - PY-AGENT-PROJECT-008: Requires declared import names to resolve to parser-visible project modules. - PY-AGENT-PROJECT-009: Requires entry point targets to resolve to parser-visible project modules. -- PY-AGENT-PROJECT-010: Requires harness dev dependencies to mount an actual pytest gate. +- PY-AGENT-PROJECT-010: Requires ASP Python dev dependencies to mount an actual pytest gate. - PY-AGENT-PROJECT-011: Requires verification profile hints or agent snapshot guidance for parser-suggested owners. - PY-TEST-R001: Moves pytest modules out of the tests root and into owned suites. -- PY-TEST-R002: Keeps tests root limited to harness configuration and owned suite directories. +- PY-TEST-R002: Keeps tests root limited to ASP Python configuration and owned suite directories. - PY-TEST-R003: Splits oversized unit test leaves into focused folder-first suites. diff --git a/src/python_lang_project_harness/py.typed b/src/asp_python/py.typed similarity index 100% rename from src/python_lang_project_harness/py.typed rename to src/asp_python/py.typed diff --git a/src/python_lang_project_harness/pytest.py b/src/asp_python/pytest.py similarity index 57% rename from src/python_lang_project_harness/pytest.py rename to src/asp_python/pytest.py index 467c098..4a8ce7b 100644 --- a/src/python_lang_project_harness/pytest.py +++ b/src/asp_python/pytest.py @@ -2,8 +2,8 @@ from __future__ import annotations -from ._pytest import python_project_harness_test +from ._pytest import asp_python_test __all__ = [ - "python_project_harness_test", + "asp_python_test", ] diff --git a/src/asp_python/pytest_plugin.py b/src/asp_python/pytest_plugin.py new file mode 100644 index 0000000..9c411cc --- /dev/null +++ b/src/asp_python/pytest_plugin.py @@ -0,0 +1,79 @@ +"""Pytest plugin entry point for ASP Python dev-dependency mounting.""" + +from __future__ import annotations + +from pathlib import Path + +import pytest + +from ._pytest_plugin_options import ( + ENABLE_OPTION, + EXTRA_PATH_OPTION, + NO_ADVICE_OPTION, + NO_TESTS_OPTION, + SOURCE_DIR_OPTION, + TEST_DIR_OPTION, + add_options, + asp_python_config, + blocking_severities, + optional_tuple, +) +from ._pytest_plugin_project import project_root +from ._runner import assert_asp_python_clean + + +def pytest_addoption(parser: pytest.Parser) -> None: + """Register ASP Python pytest options.""" + + add_options(parser) + + +def pytest_collection_modifyitems( + session: pytest.Session, + config: pytest.Config, + items: list[pytest.Item], +) -> None: + """Insert one explicit ASP Python item when the plugin option is enabled.""" + + if not config.getoption(ENABLE_OPTION): + return + item = AspPythonPytestItem.from_parent( + session, + name="asp-python", + nodeid="asp-python", + ) + items.insert(0, item) + + +class AspPythonPytestItem(pytest.Item): + """Pytest item that runs the parser-backed ASP Python.""" + + def runtest(self) -> None: + """Run the configured ASP Python and raise a compact assertion.""" + + assert_asp_python_clean( + project_root(self.config), + config=asp_python_config(self.config), + severities=blocking_severities(self.config), + include_tests=not self.config.getoption(NO_TESTS_OPTION), + source_dir_names=optional_tuple(self.config.getoption(SOURCE_DIR_OPTION)), + test_dir_names=optional_tuple(self.config.getoption(TEST_DIR_OPTION)), + extra_path_names=optional_tuple(self.config.getoption(EXTRA_PATH_OPTION)), + include_advice=not self.config.getoption(NO_ADVICE_OPTION), + ) + + def repr_failure( + self, + excinfo: pytest.ExceptionInfo[BaseException], + style: str | None = None, + ) -> str: + """Return compact ASP Python assertion text without pytest traceback noise.""" + + if isinstance(excinfo.value, AssertionError): + return str(excinfo.value) + return super().repr_failure(excinfo, style=style) + + def reportinfo(self) -> tuple[Path, int, str]: + """Return stable report metadata for pytest output.""" + + return (Path("asp-python"), 0, "ASP Python") diff --git a/src/python_lang_project_harness/verification/__init__.py b/src/asp_python/verification/__init__.py similarity index 98% rename from src/python_lang_project_harness/verification/__init__.py rename to src/asp_python/verification/__init__.py index 762ab3a..afffeb6 100644 --- a/src/python_lang_project_harness/verification/__init__.py +++ b/src/asp_python/verification/__init__.py @@ -1,4 +1,4 @@ -"""Public verification planning surface for Python project harnesses.""" +"""Public verification planning surface for ASP Pythones.""" from __future__ import annotations diff --git a/src/python_lang_project_harness/verification/facts.py b/src/asp_python/verification/facts.py similarity index 95% rename from src/python_lang_project_harness/verification/facts.py rename to src/asp_python/verification/facts.py index c26bd2d..aef8d48 100644 --- a/src/python_lang_project_harness/verification/facts.py +++ b/src/asp_python/verification/facts.py @@ -26,13 +26,13 @@ PythonReasoningTreeNode, ) - from .._model import PythonHarnessReport + from .._model import AspPythonReport def verification_reasoning_tree_facts( - report: PythonHarnessReport, + report: AspPythonReport, ) -> PythonReasoningTreeFacts: - """Return parser-owned reasoning-tree facts for one harness report.""" + """Return parser-owned reasoning-tree facts for one ASP Python report.""" scope = report.project_resolution return python_reasoning_tree_facts( @@ -43,8 +43,8 @@ def verification_reasoning_tree_facts( ) -def verification_project_root(report: PythonHarnessReport) -> Path: - """Return the project root represented by a harness report.""" +def verification_project_root(report: AspPythonReport) -> Path: + """Return the project root represented by a ASP Python report.""" if report.project_resolution is not None: return report.project_resolution.project_root @@ -144,7 +144,7 @@ def parser_visible_owner_responsibilities( metadata = facts.project_metadata metadata_path = project_metadata_owner_path(facts, project_root=project_root) if metadata is not None and metadata_path is not None: - if metadata.pytest_options.enables_python_project_harness or any( + if metadata.pytest_options.enables_asp_python or any( entry.group == "pytest11" for entry in metadata.entry_points ): _append_owner_responsibilities( @@ -238,7 +238,7 @@ def _append_owner_responsibilities( def _reasoning_tree_import_roots( - report: PythonHarnessReport, + report: AspPythonReport, ) -> tuple[Path | str, ...]: if report.project_resolution is None: return report.root_paths diff --git a/src/python_lang_project_harness/verification/indices.py b/src/asp_python/verification/indices.py similarity index 100% rename from src/python_lang_project_harness/verification/indices.py rename to src/asp_python/verification/indices.py diff --git a/src/python_lang_project_harness/verification/model.py b/src/asp_python/verification/model.py similarity index 98% rename from src/python_lang_project_harness/verification/model.py rename to src/asp_python/verification/model.py index 69cf409..67e274e 100644 --- a/src/python_lang_project_harness/verification/model.py +++ b/src/asp_python/verification/model.py @@ -1,4 +1,4 @@ -"""Library-first verification planning model for Python project harnesses.""" +"""Library-first verification planning model for ASP Pythones.""" from __future__ import annotations @@ -19,7 +19,7 @@ class PythonOwnerResponsibility(StrEnum): class PythonVerificationTaskKind(StrEnum): - """External task classes planned by the harness but executed by skills.""" + """External task classes planned by ASP Python but executed by skills.""" PERFORMANCE = "performance" SECURITY = "security" @@ -341,7 +341,7 @@ def to_dict(self) -> dict[str, object]: @dataclass(frozen=True, slots=True) class PythonVerificationTask: - """One external verification obligation planned by the harness.""" + """One external verification obligation planned by ASP Python.""" owner_path: str owner_namespace: tuple[str, ...] @@ -415,7 +415,7 @@ def to_dict(self) -> dict[str, object]: @dataclass(frozen=True, slots=True) class PythonVerificationPolicy: - """Configurable verification policy attached to a harness config.""" + """Configurable verification policy attached to a ASP Python config.""" profile_hints: tuple[PythonVerificationProfileHint, ...] = () dependency_signals: tuple[PythonVerificationDependencySignal, ...] = () diff --git a/src/python_lang_project_harness/verification/obligations.py b/src/asp_python/verification/obligations.py similarity index 100% rename from src/python_lang_project_harness/verification/obligations.py rename to src/asp_python/verification/obligations.py diff --git a/src/python_lang_project_harness/verification/planner.py b/src/asp_python/verification/planner.py similarity index 94% rename from src/python_lang_project_harness/verification/planner.py rename to src/asp_python/verification/planner.py index 6661146..70e5414 100644 --- a/src/python_lang_project_harness/verification/planner.py +++ b/src/asp_python/verification/planner.py @@ -1,4 +1,4 @@ -"""Parser-backed verification planner for Python project harnesses.""" +"""Parser-backed verification planner for ASP Pythones.""" from __future__ import annotations @@ -6,10 +6,10 @@ from pathlib import Path from typing import TYPE_CHECKING -from .._model import PythonHarnessConfig, PythonHarnessReport +from .._model import AspPythonConfig, AspPythonReport from .._render import _render_display_path -from .._rule_packs import resolve_project_harness_config -from .._runner import run_python_project_harness +from .._rule_packs import resolve_asp_python_project_config +from .._runner import run_asp_python from .facts import ( matched_dependency_signals, node_evidence, @@ -50,21 +50,21 @@ def plan_python_project_verification( def plan_python_project_verification_with_config( project_root: str | Path, - config: PythonHarnessConfig | None, + config: AspPythonConfig | None, ) -> PythonVerificationPlan: - """Plan verification obligations with an explicit harness config.""" + """Plan verification obligations with an explicit ASP Python config.""" root = Path(project_root) - selected_config = resolve_project_harness_config(root, config, rule_packs=None) - report = run_python_project_harness(root, config=selected_config) + selected_config = resolve_asp_python_project_config(root, config, rule_packs=None) + report = run_asp_python(root, config=selected_config) return plan_python_project_verification_report(report, selected_config) def plan_python_project_verification_report( - report: PythonHarnessReport, - config: PythonHarnessConfig, + report: AspPythonReport, + config: AspPythonConfig, ) -> PythonVerificationPlan: - """Plan verification obligations from an already-built harness report.""" + """Plan verification obligations from an already-built ASP Python report.""" project_root = verification_project_root(report) policy = config.verification_policy diff --git a/src/python_lang_project_harness/verification/profile_index.py b/src/asp_python/verification/profile_index.py similarity index 94% rename from src/python_lang_project_harness/verification/profile_index.py rename to src/asp_python/verification/profile_index.py index 8dc3fc0..40c2dcd 100644 --- a/src/python_lang_project_harness/verification/profile_index.py +++ b/src/asp_python/verification/profile_index.py @@ -6,8 +6,8 @@ from typing import TYPE_CHECKING from .._render import _render_display_path -from .._rule_packs import resolve_project_harness_config -from .._runner import run_python_project_harness +from .._rule_packs import resolve_asp_python_project_config +from .._runner import run_asp_python from .facts import ( entry_point_owner_paths, is_test_path, @@ -32,7 +32,7 @@ if TYPE_CHECKING: from python_lang_parser import PythonReasoningTreeFacts - from .._model import PythonHarnessConfig, PythonHarnessReport + from .._model import AspPythonConfig, AspPythonReport def build_python_verification_profile_index( @@ -45,21 +45,21 @@ def build_python_verification_profile_index( def build_python_verification_profile_index_with_config( project_root: str | Path, - config: PythonHarnessConfig | None, + config: AspPythonConfig | None, ) -> PythonVerificationProfileIndex: - """Build profile candidates with an explicit harness config.""" + """Build profile candidates with an explicit ASP Python config.""" root = Path(project_root) - selected_config = resolve_project_harness_config(root, config, rule_packs=None) - report = run_python_project_harness(root, config=selected_config) + selected_config = resolve_asp_python_project_config(root, config, rule_packs=None) + report = run_asp_python(root, config=selected_config) return build_python_verification_profile_index_report(report, selected_config) def build_python_verification_profile_index_report( - report: PythonHarnessReport, - config: PythonHarnessConfig, + report: AspPythonReport, + config: AspPythonConfig, ) -> PythonVerificationProfileIndex: - """Build profile candidates from an already-built harness report.""" + """Build profile candidates from an already-built ASP Python report.""" project_root = verification_project_root(report) policy = config.verification_policy @@ -142,7 +142,7 @@ def _append_metadata_candidates( candidate_index_by_path=candidate_index_by_path, ) if metadata_owner_path is not None and ( - metadata.pytest_options.enables_python_project_harness + metadata.pytest_options.enables_asp_python or any(entry.group == "pytest11" for entry in metadata.entry_points) ): _append_candidate( diff --git a/src/python_lang_project_harness/verification/render.py b/src/asp_python/verification/render.py similarity index 100% rename from src/python_lang_project_harness/verification/render.py rename to src/asp_python/verification/render.py diff --git a/src/python_lang_project_harness/verification/report.py b/src/asp_python/verification/report.py similarity index 100% rename from src/python_lang_project_harness/verification/report.py rename to src/asp_python/verification/report.py diff --git a/src/python_lang_parser/_project_model.py b/src/python_lang_parser/_project_model.py index c93ecfd..01a1c00 100644 --- a/src/python_lang_parser/_project_model.py +++ b/src/python_lang_parser/_project_model.py @@ -103,17 +103,17 @@ class PythonPytestOptions: addopts: tuple[str, ...] = () @property - def enables_python_project_harness(self) -> bool: + def enables_asp_python(self) -> bool: """Return whether pytest addopts mounts the project harness plugin.""" - return "--python-project-harness" in self.addopts + return "--asp-python" in self.addopts def to_dict(self) -> dict[str, object]: """Return a JSON-compatible representation.""" return { "addopts": list(self.addopts), - "enables_python_project_harness": self.enables_python_project_harness, + "enables_asp_python": self.enables_asp_python, } diff --git a/src/python_lang_parser/_version.py b/src/python_lang_parser/_version.py index a337c8f..9d75aff 100644 --- a/src/python_lang_parser/_version.py +++ b/src/python_lang_parser/_version.py @@ -5,7 +5,7 @@ from importlib.metadata import PackageNotFoundError, version from typing import Final -_DISTRIBUTION_NAME: Final = "python-lang-project-harness" +_DISTRIBUTION_NAME: Final = "asp-python" def _installed_version() -> str: diff --git a/src/python_lang_project_harness/_cli.py b/src/python_lang_project_harness/_cli.py deleted file mode 100644 index 8c0f580..0000000 --- a/src/python_lang_project_harness/_cli.py +++ /dev/null @@ -1,103 +0,0 @@ -"""Command-line execution for the Python project harness.""" - -from __future__ import annotations - -import sys -from pathlib import Path -from typing import TextIO - -from ._cli_args import CliOptions, ProtocolArgs, help_text -from ._cli_protocol import run_protocol_cli - - -def run_cli_from_env() -> int: - """Run the CLI using process environment arguments.""" - - from ._dev_command_log import start_dev_command_log - - args = sys.argv[1:] - log = start_dev_command_log(args, Path.cwd()) - try: - if args == ["serve"]: - from ._runtime import serve_provider_runtime - - exit_code = serve_provider_runtime(Path.cwd()) - log.finish(exit_code) - return exit_code - stdin = "" if sys.stdin.isatty() else sys.stdin.read() - exit_code = run_cli(args, stdin=stdin) - log.finish(exit_code) - return exit_code - except Exception: - log.finish(2) - raise - - -def run_cli( - args: list[str] | tuple[str, ...], - *, - stdout: TextIO | None = None, - stderr: TextIO | None = None, - stdin: str | bytes | None = None, - cwd: Path | None = None, -) -> int: - """Run the default package-level Python harness CLI.""" - - selected_stdout = sys.stdout if stdout is None else stdout - selected_stderr = sys.stderr if stderr is None else stderr - selected_cwd = Path.cwd() if cwd is None else cwd - protocol_args = ProtocolArgs.parse(args) - if protocol_args is not None: - return run_protocol_cli( - protocol_args, - stdout=selected_stdout, - stderr=selected_stderr, - stdin="" if stdin is None else stdin, - cwd=selected_cwd, - ) - try: - options = CliOptions.parse(args) - if options.help: - selected_stdout.write(help_text()) - return 0 - project_root = options.project_root(cwd) - if not project_root.exists(): - raise ValueError(f"project root does not exist: {project_root}") - from ._render import render_python_lang_harness, render_python_lang_harness_json - from ._runner import run_python_project_harness - - config = options.harness_config(project_root) - report = run_python_project_harness( - project_root, - config=config, - include_tests=options.include_tests, - source_dir_names=options.source_dir_names, - test_dir_names=options.test_dir_names, - extra_path_names=options.extra_path_names, - ) - if options.json: - selected_stdout.write(render_python_lang_harness_json(report)) - selected_stdout.write("\n") - elif options.agent_snapshot: - from ._agent_snapshot import ( - render_python_project_harness_agent_snapshot_report, - ) - from ._model import PythonHarnessConfig - from ._project_config import read_python_project_harness_config - - selected_stdout.write( - render_python_project_harness_agent_snapshot_report( - report, - config=( - config - or read_python_project_harness_config(project_root) - or PythonHarnessConfig() - ), - ) - ) - else: - selected_stdout.write(render_python_lang_harness(report)) - return 0 if report.is_clean else 1 - except ValueError as error: - selected_stderr.write(f"{error}\n") - return 2 diff --git a/src/python_lang_project_harness/_cli_agent.py b/src/python_lang_project_harness/_cli_agent.py deleted file mode 100644 index a90e199..0000000 --- a/src/python_lang_project_harness/_cli_agent.py +++ /dev/null @@ -1,148 +0,0 @@ -"""Agent-facing guide and doctor rendering for the Python harness CLI.""" - -from __future__ import annotations - -import json -from pathlib import Path - -from ._semantic_provider_doctor import semantic_provider_doctor_document - - -def render_agent_guide(project_root: Path) -> str: - project = str(project_root) - workspace = "--workspace " - root = workspace - return ( - "\n".join( - ( - f"[asp-python-guide] project={project}", - ( - "|catalog reasoningProfiles=owner-query,query-deps,owner-tests," - "finding-frontier,feature-cfg entries=owner-query,query-deps," - "owner-tests routes=syntax-locate,exact-source,callable-skeleton" - ), - "|routing evidence-state prime=owner-map-only pipe=ambiguous-query " - "owner=known-owner selector=exact-parser-id deps=known-dependency " - "tests=known-owner ingest=stdin", - ( - f"|route syntax-locate selectors=S:tree-sitter-query,Scope:owner-or-structural " - f"returns=locator,capture,frontier code=false cmd=asp python " - f"query --treesitter-query " - f"'(function_definition name: (identifier) @function.name)' " - f"--selector {workspace}" - ), - f"|route exact-source selectors=R:exact-selector returns=source cmd=asp python query --selector --projection source {workspace}", - f"|route callable-skeleton selectors=R:exact-callable-selector returns=callable-skeleton cmd=asp python query --selector --projection callable-skeleton {workspace}", - f"|cmd prime=asp python search prime {root} --view seeds condition=owner-map-unknown", - f"|cmd pipe=asp python search pipe {root} --view seeds condition=ambiguous-query", - f"|cmd owner=asp python search owner {root} --view seeds", - ( - f"|cmd reasoning-owner-tests=asp python search reasoning " - f"owner-tests --owner {root} --view seeds" - ), - ( - f"|cmd reasoning-owner-query=asp python search reasoning " - f"owner-query --owner --query " - f"{root} --view seeds" - ), - ( - f"|cmd reasoning-query-deps=asp python search reasoning " - f"query-deps --query --dependency " - f"{root} --view seeds" - ), - f"|cmd catalog-json=asp python query --catalog declarations --json {root}", - ( - f"|cmd syntax-locate=asp python query --treesitter-query " - f"'(function_definition name: (identifier) @function.name)' " - f"--selector {workspace}" - ), - f"|cmd exact-source=asp python query --selector --projection source {workspace}", - f"|cmd callable-skeleton=asp python query --selector --projection callable-skeleton {workspace}", - ( - f"|cmd policy=asp python search policy " - f"owner tests {root} --view seeds" - ), - f"|cmd lexical=asp python search lexical owner tests {root} --view seeds", - "|cmd ast-patch=asp python ast-patch dry-run --packet ", - f"|cmd evidence-graph=asp python evidence graph --json {root}", - f"|cmd evidence-analyze=asp python evidence analyze --json {root}", - f"|cmd deps=asp python search deps {root}", - f"|cmd env=asp python search env [term ...] {workspace} --view seeds", - f"|cmd runtime-source=asp python search runtime-source [term ...] {workspace} --view seeds", - f"|cmd lang=asp python search lang [term ...] {workspace} --view seeds", - f"|cmd std=asp python search std [term ...] {workspace} --view seeds", - f"|cmd capability=asp python search capability [term ...] {workspace} --view seeds", - f"|cmd extension=asp python search extension [term ...] {workspace} --view seeds", - f"|cmd pattern=asp python search pattern [term ...] {workspace} --view seeds", - f"|cmd compare=asp python search compare [left right] {workspace} --view seeds", - f"|pipe | asp python search ingest {root} --view seeds", - "|cmd check=asp python check --changed", - "|rule agent hook install/runtime is owned by asp", - ( - "|rule selector queries do not need a trailing project root; " - "--workspace is the independent workspace override" - ), - ( - "|rule syntax query ABI is compiled by asp; provider projects " - "native parser facts into tree-sitter-compatible captures" - ), - ( - "|rule syntax predicates supported=#eq?,#any-eq?,#any-of?," - "#match?,#any-match?,#not-eq?,#not-match? " - "unsupported=none unsupportedReported=true" - ), - "|rule exact query requires a parser-owned selector and an explicit source or callable-skeleton projection", - ( - "|rule displayLineRange/sourceLocatorHint are display hints; " - "execute structural selectors or owner/symbol routes, not line ranges" - ), - "|rule Python discovery uses search --view seeds; query only materializes an exact structural selector", - ( - "|rule provider-knowledge-axes env/lang/std/pattern/runtime-source " - "return facts or explicit frontier gaps; do not fill missing " - "facts from memory" - ), - ( - "|rule use the asp python facade; run one command at a time; " - "no raw Python source reads" - ), - "|subagent give one |cmd or |pipe line; require evidence/missing/next/risk", - ) - ) - + "\n" - ) - - -def render_agent_doctor(project_root: Path) -> str: - from . import _semantic_language_ids as ids - from ._semantic_language import python_semantic_language_registration - - registration = python_semantic_language_registration() - return ( - "\n".join( - ( - "[agent-doctor] " - f"status=ok protocol={ids.SEMANTIC_LANGUAGE_PROTOCOL_ID} " - f"registry=semantic-language-registry.v{ids.SEMANTIC_LANGUAGE_REGISTRY_VERSION}", - f"|project {project_root}", - ( - f"|language id={ids.PYTHON_LANGUAGE_ID} provider={ids.PYTHON_PROVIDER_ID} " - f"binary={ids.PYTHON_BINARY}" - ), - f"|namespace {ids.PYTHON_PROVIDER_NAMESPACE}", - f"|method {','.join(registration['methods'])}", - "|schema semantic-search-packet.v1", - ) - ) - + "\n" - ) - - -def render_agent_doctor_json(project_root: Path) -> str: - return ( - json.dumps( - semantic_provider_doctor_document(), - separators=(",", ":"), - ) - + "\n" - ) diff --git a/src/python_lang_project_harness/_cli_args.py b/src/python_lang_project_harness/_cli_args.py deleted file mode 100644 index 4f42a93..0000000 --- a/src/python_lang_project_harness/_cli_args.py +++ /dev/null @@ -1,499 +0,0 @@ -"""Argument parsing helpers for the Python harness CLI.""" - -from __future__ import annotations - -from dataclasses import dataclass, field, replace -from pathlib import Path -from typing import TYPE_CHECKING - -if TYPE_CHECKING: - from ._model import PythonHarnessConfig - from ._tree_sitter_query_predicates import SyntaxQueryPredicate - - -@dataclass(slots=True) -class ProtocolArgs: - command: str - view: str | None = None - action: str | None = None - client: str | None = None - hook_event: str | None = None - query: str | None = None - item_query: str | None = None - project_root: Path | None = None - package_path: Path | None = None - workspace: bool = False - owner_path: str | None = None - dependency: str | None = None - selector: str | None = None - catalog: str | None = None - flow_lite_where: str | None = None - tree_sitter_query: str | None = None - asp_syntax_query_captures: tuple[str, ...] = () - asp_syntax_query_node_types: tuple[str, ...] = () - asp_syntax_query_fields: tuple[str, ...] = () - asp_syntax_query_predicates: tuple[SyntaxQueryPredicate, ...] = () - packet_path: str | None = None - query_set: tuple[str, ...] = () - pipes: tuple[str, ...] = () - json: bool = False - names_only: bool = False - source_version: str = "worktree" - render_mode: str | None = None - error: str | None = None - - @classmethod - def parse(cls, args: list[str] | tuple[str, ...]) -> ProtocolArgs | None: - command = args[0] if args else None - if command == "search": - return cls._parse_search(args[1:]) - if command == "query": - return cls._parse_query(args[1:]) - if command == "check": - return cls._parse_check(args[1:]) - if command == "evidence": - return cls._parse_evidence(args[1:]) - if command == "agent": - return cls._parse_agent(args[1:]) - if command == "ast-patch": - return cls._parse_ast_patch(args[1:]) - return None - - @classmethod - def _parse_search(cls, args: list[str] | tuple[str, ...]) -> ProtocolArgs: - if args and args[0] in {"--help", "-h"}: - return cls("help") - from ._semantic_search_cli import parse_semantic_search_args - - parsed = parse_semantic_search_args(args) - if parsed.error is not None: - return cls("error", error=parsed.error) - return cls( - "search", - view=parsed.view, - query=parsed.query, - item_query=parsed.item_query, - owner_path=parsed.owner_path, - dependency=parsed.dependency, - query_set=parsed.query_set, - project_root=parsed.project_root, - package_path=parsed.package_path, - workspace=parsed.workspace, - pipes=parsed.pipes, - json=parsed.json, - render_mode=parsed.render_mode, - ) - - @classmethod - def _parse_query(cls, args: list[str] | tuple[str, ...]) -> ProtocolArgs: - from ._cli_query_args import parse_query_args - - return parse_query_args(cls, args) - - @classmethod - def _parse_ast_patch(cls, args: list[str] | tuple[str, ...]) -> ProtocolArgs: - mode = args[0] if args else None - if mode in {"--help", "-h"}: - return cls("help") - if mode != "dry-run": - return cls("error", error="expected ast-patch dry-run") - - packet_path: str | None = None - positionals: list[Path] = [] - index = 1 - while index < len(args): - arg = args[index] - if arg == "--packet": - value = args[index + 1] if index + 1 < len(args) else None - if value is None or (value.startswith("-") and value != "-"): - return cls("error", error="--packet requires a path or -") - packet_path = value - index += 2 - continue - if arg.startswith("-"): - return cls("error", error=f"unknown ast-patch option: {arg}") - positionals.append(Path(arg)) - index += 1 - if packet_path is None: - return cls("error", error="--packet requires a path or -") - if len(positionals) > 1: - return cls("error", error="expected at most one PROJECT_ROOT argument") - return cls( - "ast-patch", - packet_path=packet_path, - project_root=positionals[0] if positionals else None, - ) - - @classmethod - def _parse_check(cls, args: list[str] | tuple[str, ...]) -> ProtocolArgs: - json_output = False - positionals: list[str] = [] - for arg in args: - if arg == "--json": - json_output = True - elif arg in {"--changed", "--full"}: - continue - elif arg in {"--help", "-h"}: - return cls( - "error", - error="usage: asp-python check [--changed | --full] [--json] [PROJECT_ROOT]", - ) - elif arg.startswith("-"): - return cls("error", error=f"unknown check option: {arg}") - else: - positionals.append(arg) - if len(positionals) > 1: - return cls("error", error="expected at most one PROJECT_ROOT argument") - return cls( - "check", - project_root=None if not positionals else Path(positionals[0]), - json=json_output, - ) - - @classmethod - def _parse_evidence(cls, args: list[str] | tuple[str, ...]) -> ProtocolArgs: - action = args[0] if args else None - if action in {"--help", "-h"}: - return cls("help") - if action not in {"graph", "analyze", "analysis"}: - return cls("error", error="expected evidence ") - json_output = False - positionals: list[str] = [] - for arg in args[1:]: - if arg == "--json": - json_output = True - elif arg in {"--help", "-h"}: - return cls("help") - elif arg.startswith("-"): - return cls("error", error=f"unknown evidence option: {arg}") - else: - positionals.append(arg) - if len(positionals) > 1: - return cls("error", error="expected at most one PROJECT_ROOT argument") - return cls( - "evidence", - action="analyze" if action == "analysis" else action, - project_root=None if not positionals else Path(positionals[0]), - json=json_output, - ) - - @classmethod - def _parse_agent(cls, args: list[str] | tuple[str, ...]) -> ProtocolArgs: - action = args[0] if args else "doctor" - if action in {"install", "hook"}: - replacement = ( - "asp hook install --client codex" - if action == "install" - else "asp hook --client codex" - ) - return cls( - "error", - error=f"asp-python agent {action} moved to asp; use `{replacement}`", - ) - if action == "guide": - return cls._parse_agent_guide(args[1:]) - if action != "doctor": - return cls("error", error=f"unknown agent action: {action}") - client: str | None = None - hook_event: str | None = None - json_output = False - positionals: list[str] = [] - index = 1 - while index < len(args): - arg = args[index] - if arg == "--json": - json_output = True - elif arg == "--client": - value = _optional_arg(args, index + 1) - if value is None: - return cls("error", error="--client requires a client name") - if value != "codex": - return cls("error", error=f"unsupported agent client: {value}") - client = value - index += 1 - elif arg in {"--help", "-h"}: - continue - elif arg.startswith("-"): - return cls("error", error=f"unknown agent option: {arg}") - elif action == "hook" and hook_event is None: - hook_event = arg - else: - positionals.append(arg) - index += 1 - if len(positionals) > 1: - return cls("error", error="expected at most one PROJECT_ROOT argument") - return cls( - "agent", - action=action, - client=client, - hook_event=hook_event, - project_root=None if not positionals else Path(positionals[0]), - json=json_output, - ) - - @classmethod - def _parse_agent_guide(cls, args: list[str] | tuple[str, ...]) -> ProtocolArgs: - positionals: list[str] = [] - index = 0 - while index < len(args): - arg = args[index] - if arg == "--client": - value = _optional_arg(args, index + 1) - if value is None: - return cls("error", error="--client requires a client name") - index += 1 - elif arg in {"--help", "-h"}: - pass - elif arg.startswith("-"): - return cls("error", error=f"unknown agent option: {arg}") - else: - positionals.append(arg) - index += 1 - if len(positionals) > 1: - return cls("error", error="expected at most one PROJECT_ROOT argument") - return cls( - "agent", - action="guide", - project_root=None if not positionals else Path(positionals[0]), - ) - - -@dataclass(slots=True) -class CliOptions: - json: bool = False - agent_snapshot: bool = False - help: bool = False - include_tests: bool | None = None - source_dir_values: list[str] = field(default_factory=list) - test_dir_values: list[str] = field(default_factory=list) - extra_path_values: list[str] = field(default_factory=list) - disabled_rule_values: list[str] = field(default_factory=list) - blocking_rule_values: list[str] = field(default_factory=list) - paths: list[Path] = field(default_factory=list) - - @classmethod - def parse(cls, args: list[str] | tuple[str, ...]) -> CliOptions: - options = cls() - positional_only = False - index = 0 - while index < len(args): - arg = args[index] - index += 1 - if positional_only: - options.paths.append(Path(arg)) - continue - match arg: - case "--": - positional_only = True - case "--json": - options.json = True - case "--agent-snapshot": - options.agent_snapshot = True - case "--no-tests": - options.include_tests = False - case "--source-dir": - options.source_dir_values.append( - _option_value(args, index, "--source-dir") - ) - index += 1 - case "--test-dir": - options.test_dir_values.append( - _option_value(args, index, "--test-dir") - ) - index += 1 - case "--extra-path": - options.extra_path_values.append( - _option_value(args, index, "--extra-path") - ) - index += 1 - case "--disable-rule": - options.disabled_rule_values.append( - _option_value(args, index, "--disable-rule") - ) - index += 1 - case "--block-rule": - options.blocking_rule_values.append( - _option_value(args, index, "--block-rule") - ) - index += 1 - case "--help" | "-h": - options.help = True - case value if value.startswith("-"): - raise ValueError(f"unknown option: {value}") - case value: - options.paths.append(Path(value)) - if len(options.paths) > 1: - raise ValueError("expected at most one PROJECT_ROOT argument") - if options.json and options.agent_snapshot: - raise ValueError("--json and --agent-snapshot are mutually exclusive") - return options - - def project_root(self, cwd: Path | None) -> Path: - if self.paths: - return self.paths[0] - if cwd is not None: - return cwd - return Path.cwd() - - @property - def source_dir_names(self) -> tuple[str, ...] | None: - """Return explicit source roots, when the CLI supplied any.""" - - if not self.source_dir_values: - return None - return tuple(self.source_dir_values) - - @property - def test_dir_names(self) -> tuple[str, ...] | None: - """Return explicit test roots, when the CLI supplied any.""" - - if not self.test_dir_values: - return None - return tuple(self.test_dir_values) - - @property - def extra_path_names(self) -> tuple[str, ...] | None: - """Return explicit extra project paths, when the CLI supplied any.""" - - if not self.extra_path_values: - return None - return tuple(self.extra_path_values) - - def harness_config(self, project_root: Path) -> PythonHarnessConfig | None: - """Return CLI policy config when rule-level options were supplied.""" - - if not self.disabled_rule_values and not self.blocking_rule_values: - return None - from ._model import PythonHarnessConfig - from ._project_config import read_python_project_harness_config - - base_config = read_python_project_harness_config(project_root) - config = base_config if base_config is not None else PythonHarnessConfig() - return replace( - config, - disabled_rule_ids=( - frozenset(self.disabled_rule_values) - if self.disabled_rule_values - else config.disabled_rule_ids - ), - blocking_rule_ids=( - frozenset(self.blocking_rule_values) - if self.blocking_rule_values - else config.blocking_rule_ids - ), - ) - - -def help_text() -> str: - return ( - "asp-python — Python semantic search and project harness\n\n" - "Usage:\n" - " asp-python search ... [--json] [--package PATH] [--workspace ]\n" - " asp python query --selector --projection --workspace \n" - " asp-python query --catalog flow-lite --where 'source.call=NAME sink.constructs=TYPE scope.fn=FUNCTION' [--json] [--workspace ]\n" - " asp-python check [--changed | --full] [--json]\n" - " asp-python evidence graph [--json] [PROJECT_ROOT]\n" - " asp-python evidence analyze [--json] [PROJECT_ROOT]\n" - " asp-python ast-patch dry-run --packet \n" - " asp-python agent doctor [--json]\n" - " asp-python agent guide\n" - " asp-python [--json | --agent-snapshot] [--no-tests] " - "[--source-dir DIR] [--test-dir DIR] [--extra-path PATH] " - "[--disable-rule RULE_ID] [--block-rule RULE_ID] [PROJECT_ROOT]\n\n" - "SEARCH VIEWS\n" - " search workspace Workspace package/router index\n" - " search prime Project reasoning-tree map\n" - " search owner Owner graph slice\n" - " search owner items --query \n" - " Parser-owned structural selector discovery\n" - " search dependency Dependency manifest and local import usage\n" - " search deps \n" - " Versioned dependency API usage evidence\n" - " search api Parser-owned public API facts\n" - " search public-external-types \n" - " Public API type surfaces exposing a dependency\n" - " search symbol Symbol/export definitions\n" - " search callsite Parser-owned function and method callsites\n" - " search import Import owner edges\n" - " search tests Tests that import an owner\n" - " search lexical --query --query \n" - " Lexical owner/source-text candidates\n" - " search lexical --query --query owner tests\n" - " Minimal final-only lexical -> owner -> tests pipe\n" - " search reasoning owner-tests --owner \n" - " Typed graph entry returning covering tests, entrypoints, and fixtures\n" - " search reasoning owner-query --owner --query \n" - " Typed graph entry returning owner items, tests, and dependency usage\n" - " search reasoning query-deps --query --dependency \n" - " Typed graph entry returning owners, imports, and usage tests\n" - " search ingest Detect stdin shape and group hits by owner\n\n" - "QUERY\n" - " asp python query --selector --projection source --workspace \n" - " Exact source materialization through ASP authority\n" - " asp python query --selector --projection callable-skeleton --workspace \n" - " Typed callable skeleton materialization through ASP authority\n\n" - " query --catalog flow-lite --where 'source.call=NAME sink.constructs=TYPE scope.fn=FUNCTION'\n" - " Flow-lite ABI compatibility surface; Python executor is not enabled yet\n\n" - "CHECK\n" - " check --changed Fast lane alias; currently delegates to project check\n" - " check --full Full project harness check\n" - " check --json Structured PythonHarnessReport JSON\n\n" - "EVIDENCE\n" - " evidence graph --json Portable semantic-evidence-graph packet\n" - " evidence analyze --json Graph-turbo request for evidence-quality ranking\n\n" - "AST PATCH\n" - " ast-patch dry-run --packet \n" - " Provider-native structural patch receipt; never mutates files\n\n" - "AGENT\n" - " agent doctor Print semantic-language provider readiness\n" - " agent doctor --json Semantic language registry document\n\n" - " agent guide Print command-line search flow guide\n\n" - " Hook install/runtime is owned by asp in the root toolchain.\n\n" - "DIRECT CHECK\n" - "The no-command form still runs the default package-level Python harness.\n\n" - "Compact text is the default output for humans and repair-oriented agents.\n" - "Use --json to emit the structured PythonHarnessReport JSON shape.\n" - "Use --agent-snapshot to emit parser facts for project repair agents.\n" - "Repeat --source-dir or --test-dir to customize policy root classification.\n" - "Repeat --extra-path to include external project paths.\n" - "Repeat --disable-rule or --block-rule to customize policy by rule id.\n" - "\nEXAMPLES\n" - " asp-python search workspace .\n" - " asp-python search prime .\n" - " asp-python search public-external-types pytest .\n" - " asp-python search callsite PythonSemanticSearchOptions .\n" - " asp python search lexical --query PythonSemanticSearchOptions --query owner --workspace .\n" - " asp-python search reasoning owner-tests --owner src/python_lang_project_harness/_cli.py .\n" - " asp-python search reasoning owner-query --owner src/python_lang_project_harness/_cli.py --query run_cli .\n" - " asp-python search reasoning query-deps --query Session --dependency requests .\n" - " asp python query --selector 'python://src/python_lang_project_harness/_cli.py#item/function/run_cli' --projection source --workspace .\n" - " asp-python query --catalog flow-lite --where 'source.call=payload sink.constructs=Action scope.fn=collect' .\n" - " asp python search lexical --query PythonSemanticSearchOptions --workspace . --view seeds\n" - " asp-python check --full .\n" - " asp-python evidence graph --json .\n" - " asp-python evidence analyze --json .\n" - " asp-python agent doctor --json .\n" - " asp-python agent guide\n" - ) - - -def _optional_arg(args: list[str] | tuple[str, ...], index: int) -> str | None: - if index >= len(args): - return None - value = args[index] - if value.startswith("-"): - return None - return value - - -def _option_value( - args: list[str] | tuple[str, ...], - index: int, - option_name: str, -) -> str: - if index >= len(args): - raise ValueError(f"missing value for {option_name}") - value = args[index] - if value.startswith("-"): - raise ValueError(f"missing value for {option_name}") - return value diff --git a/src/python_lang_project_harness/_cli_protocol.py b/src/python_lang_project_harness/_cli_protocol.py deleted file mode 100644 index a44c306..0000000 --- a/src/python_lang_project_harness/_cli_protocol.py +++ /dev/null @@ -1,278 +0,0 @@ -"""Protocol command dispatch for the Python harness CLI.""" - -from __future__ import annotations - -from pathlib import Path -from typing import TextIO - -from ._cli_agent import ( - render_agent_doctor, - render_agent_doctor_json, - render_agent_guide, -) -from ._cli_args import ProtocolArgs, help_text -from ._cli_search_runtime import _run_search_harness - - -def run_protocol_cli( - args: ProtocolArgs, - *, - stdout: TextIO, - stderr: TextIO, - stdin: str | bytes, - cwd: Path, -) -> int: - if args.command == "error": - stderr.write(f"{args.error}\n") - return 2 - if args.command == "help": - stdout.write(help_text()) - return 0 - project_root = _resolve_project_root(args, cwd) - if args.command == "agent": - return _run_agent_command(args, project_root=project_root, stdout=stdout) - if args.command == "evidence": - return _run_evidence_command(args, project_root=project_root, stdout=stdout) - if args.command == "ast-patch": - return _run_ast_patch_command( - args, project_root=project_root, stdout=stdout, stdin=stdin - ) - - try: - fast_exit = _run_fast_protocol_command( - args, - project_root=project_root, - stdout=stdout, - stdin=stdin, - ) - if fast_exit is not None: - return fast_exit - return _run_harness_protocol_command( - args, - project_root=project_root, - stdout=stdout, - stdin=stdin, - ) - except ValueError as error: - stderr.write(f"{error}\n") - return 3 - - -def _resolve_project_root(args: ProtocolArgs, cwd: Path) -> Path: - project_root = (cwd / args.project_root).resolve() if args.project_root else cwd - if args.package_path is not None: - return (project_root / args.package_path).resolve() - return project_root - - -def _run_agent_command( - args: ProtocolArgs, - *, - project_root: Path, - stdout: TextIO, -) -> int: - if args.action == "guide": - stdout.write(render_agent_guide(project_root)) - return 0 - if args.json: - stdout.write(render_agent_doctor_json(project_root)) - else: - stdout.write(render_agent_doctor(project_root)) - return 0 - - -def _run_evidence_command( - args: ProtocolArgs, - *, - project_root: Path, - stdout: TextIO, -) -> int: - from ._evidence_graph import ( - build_python_evidence_graph, - render_python_evidence_graph, - render_python_evidence_graph_json, - ) - from ._evidence_graph_turbo import ( - build_python_evidence_analysis_request, - render_python_evidence_analysis_request, - render_python_evidence_analysis_request_json, - ) - - if args.action == "graph": - graph = build_python_evidence_graph(project_root) - stdout.write( - render_python_evidence_graph_json(graph) - if args.json - else render_python_evidence_graph(graph) - ) - return 0 - if args.action == "analyze": - request = build_python_evidence_analysis_request(project_root) - stdout.write( - render_python_evidence_analysis_request_json(request) - if args.json - else render_python_evidence_analysis_request(request) - ) - return 0 - raise ValueError("expected evidence ") - - -def _run_ast_patch_command( - args: ProtocolArgs, - *, - project_root: Path, - stdout: TextIO, - stdin: str, -) -> int: - from ._cli_ast_patch import run_ast_patch_command - - return run_ast_patch_command( - args, project_root=project_root, stdout=stdout, stdin=stdin - ) - - -def _run_fast_protocol_command( - args: ProtocolArgs, - *, - project_root: Path, - stdout: TextIO, - stdin: str, -) -> int | None: - rendered = _render_fast_protocol_command( - args, - project_root=project_root, - stdin=stdin, - ) - if rendered is None: - return None - stdout.write(rendered) - return 0 - - -def _render_fast_protocol_command( - args: ProtocolArgs, - *, - project_root: Path, - stdin: str, -) -> str | None: - if args.command == "search" and args.view == "dependency-topology": - from ._dependency_topology import render_dependency_topology_packet - - return render_dependency_topology_packet(project_root) - from ._semantic_graph_facts import render_semantic_graph_facts - from ._semantic_search_ingest_fast import render_fast_empty_ingest_search - from ._semantic_search_lexical_fast import render_fast_lexical_seed_search - from ._semantic_search_owner_fast import render_fast_owner_seed_search - from ._semantic_search_prime_fast import render_fast_prime_search - - renderers = ( - lambda: render_semantic_graph_facts( - args, project_root=project_root, stdin=stdin - ), - lambda: render_fast_empty_ingest_search(args, project_root, stdin), - lambda: render_fast_prime_search(args, project_root), - lambda: render_fast_owner_seed_search(args, project_root), - lambda: render_fast_lexical_seed_search(args, project_root), - ) - for render in renderers: - rendered = render() - if rendered is not None: - return rendered - return None - - -def _run_harness_protocol_command( - args: ProtocolArgs, - *, - project_root: Path, - stdout: TextIO, - stdin: str, -) -> int: - report, runtime_cost = _run_search_harness(project_root, args) - if args.command == "query": - return _run_query_command( - args, report=report, project_root=project_root, stdout=stdout - ) - if args.command == "check": - return _run_check_command(args, report=report, stdout=stdout) - return _run_search_command( - args, - report=report, - runtime_cost=runtime_cost, - stdout=stdout, - stdin=stdin, - ) - - -def _run_query_command( - args: ProtocolArgs, - *, - report: object, - project_root: Path, - stdout: TextIO, -) -> int: - from ._cli_query import run_query_command - - return run_query_command( - args, - report=report, - project_root=project_root, - stdout=stdout, - ) - - -def _run_check_command( - args: ProtocolArgs, - *, - report: object, - stdout: TextIO, -) -> int: - from ._render import ( - render_python_lang_harness, - render_python_lang_harness_json, - ) - - if args.json: - stdout.write(render_python_lang_harness_json(report)) - stdout.write("\n") - else: - stdout.write(render_python_lang_harness(report)) - return 0 if report.is_clean else 1 - - -def _run_search_command( - args: ProtocolArgs, - *, - report: object, - runtime_cost: dict[str, object] | None, - stdout: TextIO, - stdin: str, -) -> int: - from ._semantic_search import ( - PythonSemanticSearchOptions, - build_python_semantic_search_packet, - render_python_semantic_search_packet, - render_python_semantic_search_packet_json, - ) - - packet = build_python_semantic_search_packet( - report, - PythonSemanticSearchOptions( - view=args.view or "prime", - query=args.query, - item_query=args.item_query, - query_set=args.query_set, - owner_path=args.owner_path, - dependency=args.dependency, - pipes=args.pipes, - render_mode=args.render_mode, - stdin=stdin, - runtime_cost=runtime_cost, - ), - ) - stdout.write( - render_python_semantic_search_packet_json(packet) - if args.json - else render_python_semantic_search_packet(packet) - ) - return 0 diff --git a/src/python_lang_project_harness/_cli_search_runtime.py b/src/python_lang_project_harness/_cli_search_runtime.py deleted file mode 100644 index 399020a..0000000 --- a/src/python_lang_project_harness/_cli_search_runtime.py +++ /dev/null @@ -1,377 +0,0 @@ -"""Search runtime helpers for the Python harness CLI.""" - -from __future__ import annotations - -from dataclasses import dataclass -from pathlib import Path - -from ._cli_args import ProtocolArgs - -_KNOWLEDGE_VIEWS = frozenset( - { - "env", - "runtime-source", - "lang", - "std", - "capability", - "extension", - "pattern", - "compare", - } -) -_QUERY_PREFILTER_VIEWS = frozenset( - { - "api", - "callsite", - "import", - "public-external-types", - "policy", - "symbol", - "tests", - } -) - - -def _run_search_harness( - project_root: Path, - args: ProtocolArgs, -) -> tuple[object, dict[str, object] | None]: - owner_items_report = _run_exact_owner_items_search(project_root, args) - if owner_items_report is not None: - return owner_items_report, { - "reason": "owner-items-exact-owner-prefilter", - "fields": { - "paths": 1, - "ownerPath": _owner_items_query_path(args) or "", - }, - } - owner_report = _run_exact_owner_search(project_root, args) - if owner_report is not None: - return owner_report, { - "reason": "owner-exact-path-prefilter", - "fields": { - "paths": 1, - "ownerPath": _owner_query_path(args) or "", - }, - } - dependency_report = _run_metadata_dependency_search(project_root, args) - if dependency_report is not None: - return dependency_report, { - "reason": "dependency-metadata-prefilter", - "fields": { - "paths": 0, - "dependency": args.query or "", - }, - } - workspace_seed_report = _run_workspace_seed_metadata_search(project_root, args) - if workspace_seed_report is not None: - return workspace_seed_report, { - "reason": "workspace-seed-metadata-route", - "fields": {"paths": 0, "view": args.view or ""}, - } - metadata_report = _run_metadata_only_search(project_root, args) - if metadata_report is not None: - return metadata_report, { - "reason": "knowledge-metadata-route", - "fields": {"paths": 0, "view": args.view or ""}, - } - from ._rule_packs import resolve_project_harness_config - - config = resolve_project_harness_config( - project_root, - None, - rule_packs=None, - ) - if args.command != "search": - from ._runner import run_python_project_harness - - return run_python_project_harness(project_root, config=config), None - if config.include_hidden_dir_names: - from ._runner import run_python_project_harness - - return run_python_project_harness(project_root, config=config), None - query_terms = _prefilter_query_terms(args) - if query_terms is None: - from ._runner import run_python_project_harness - - return run_python_project_harness(project_root, config=config), None - from ._semantic_search_prefilter import prefilter_python_text_search_paths - - prefilter = prefilter_python_text_search_paths( - project_root, - query_terms, - owner_path=args.owner_path, - ) - if prefilter is None: - from ._runner import run_python_project_harness - - return run_python_project_harness(project_root, config=config), None - return _run_prefiltered_text_search(project_root, prefilter.paths), ( - prefilter.runtime_cost() - ) - - -def _prefilter_query_terms(args: ProtocolArgs) -> tuple[str, ...] | None: - if args.view == "lexical": - return args.query_set or ( - () if args.query is None else tuple(args.query.split()) - ) - if ( - args.view == "reasoning" - and args.query == "query-deps" - and args.item_query is not None - and args.dependency is not None - ): - return (args.item_query, args.dependency) - if args.view in _QUERY_PREFILTER_VIEWS and args.query is not None: - return (args.query,) - return None - - -def _run_exact_owner_items_search( - project_root: Path, - args: ProtocolArgs, -) -> _TextSearchReport | None: - owner_path = _exact_owner_items_path(project_root, args) - if owner_path is None: - return None - from python_lang_parser.parser import parse_python_file - - return _TextSearchReport( - modules=(parse_python_file(owner_path),), - project_resolution=_fast_owner_items_scope(project_root, owner_path), - root_paths=(str(owner_path),), - ) - - -def _run_exact_owner_search( - project_root: Path, - args: ProtocolArgs, -) -> _TextSearchReport | None: - owner_path = _exact_owner_path(project_root, args) - if owner_path is None: - return None - from python_lang_parser.parser import parse_python_file - - paths = _exact_owner_related_paths(project_root, owner_path) - return _TextSearchReport( - modules=tuple(parse_python_file(path) for path in paths), - project_resolution=_fast_text_search_scope(project_root), - root_paths=tuple(str(path) for path in paths), - ) - - -def _run_metadata_dependency_search( - project_root: Path, - args: ProtocolArgs, -) -> _TextSearchReport | None: - if ( - args.command != "search" - or args.view not in {"dependency", "deps"} - or args.query is None - or "::" in args.query - ): - return None - from ._project_metadata import read_python_project_metadata - - return _TextSearchReport( - modules=(), - project_resolution=_TextSearchScope( - project_root=project_root, - project_metadata=read_python_project_metadata(project_root), - fallback_paths=(project_root,), - ), - root_paths=(str(project_root),), - ) - - -def _run_metadata_only_search( - project_root: Path, args: ProtocolArgs -) -> _TextSearchReport | None: - if args.command != "search" or args.view not in _KNOWLEDGE_VIEWS: - return None - from ._project_metadata import read_python_project_metadata - - return _TextSearchReport( - modules=(), - project_resolution=_TextSearchScope( - project_root=project_root, - project_metadata=read_python_project_metadata(project_root), - fallback_paths=(project_root,), - ), - root_paths=(str(project_root),), - ) - - -def _run_workspace_seed_metadata_search( - project_root: Path, args: ProtocolArgs -) -> _TextSearchReport | None: - if ( - args.command != "search" - or args.view != "workspace" - or args.render_mode != "seeds" - ): - return None - from ._project_metadata import read_python_project_metadata - - return _TextSearchReport( - modules=(), - project_resolution=_TextSearchScope( - project_root=project_root, - project_metadata=read_python_project_metadata(project_root), - fallback_paths=(project_root,), - ), - root_paths=(str(project_root),), - ) - - -def _exact_owner_items_path(project_root: Path, args: ProtocolArgs) -> Path | None: - if ( - args.command != "search" - or args.view != "owner" - or "items" not in args.pipes - or _owner_items_query_path(args) is None - ): - return None - raw_path = Path(_owner_items_query_path(args) or "") - owner_path = raw_path if raw_path.is_absolute() else project_root / raw_path - try: - resolved_root = project_root.resolve() - resolved_owner = owner_path.resolve() - resolved_owner.relative_to(resolved_root) - except ValueError: - return None - if not resolved_owner.is_file() or resolved_owner.suffix != ".py": - return None - return resolved_owner - - -def _exact_owner_path(project_root: Path, args: ProtocolArgs) -> Path | None: - if ( - args.command != "search" - or args.view != "owner" - or args.pipes - or _owner_query_path(args) is None - ): - return None - return _resolve_project_python_file( - project_root, Path(_owner_query_path(args) or "") - ) - - -def _owner_items_query_path(args: ProtocolArgs) -> str | None: - return args.owner_path or args.query - - -def _owner_query_path(args: ProtocolArgs) -> str | None: - return args.query - - -def _resolve_project_python_file(project_root: Path, raw_path: Path) -> Path | None: - owner_path = raw_path if raw_path.is_absolute() else project_root / raw_path - try: - resolved_root = project_root.resolve() - resolved_owner = owner_path.resolve() - resolved_owner.relative_to(resolved_root) - except ValueError: - return None - if not resolved_owner.is_file() or resolved_owner.suffix != ".py": - return None - return resolved_owner - - -def _exact_owner_related_paths( - project_root: Path, owner_path: Path -) -> tuple[Path, ...]: - project_root = project_root.resolve() - owner_path = owner_path.resolve() - paths = [owner_path] - try: - owner_module = owner_path.relative_to(project_root).with_suffix("") - except ValueError: - return tuple(paths) - owner_parts = tuple(part for part in owner_module.parts if part != "__init__") - if owner_parts[:1] == ("src",): - owner_parts = owner_parts[1:] - module_tail = ".".join(owner_parts) - if not module_tail: - return tuple(paths) - import_markers = ( - f"import {module_tail}", - f"from {module_tail} import", - f"from .{owner_path.stem} import", - ) - for path in sorted(project_root.rglob("*.py")): - if path == owner_path or any(part.startswith(".") for part in path.parts): - continue - try: - text = path.read_text(encoding="utf-8") - except OSError: - continue - if any(marker in text for marker in import_markers): - paths.append(path) - return tuple(dict.fromkeys(paths)) - - -@dataclass(frozen=True, slots=True) -class _TextSearchReport: - modules: tuple[object, ...] - project_resolution: _TextSearchScope - findings: tuple[object, ...] = () - root_paths: tuple[str, ...] = () - - -@dataclass(frozen=True, slots=True) -class _TextSearchScope: - project_root: Path - source_paths: tuple[Path, ...] = () - test_paths: tuple[Path, ...] = () - project_metadata: object | None = None - project_paths: tuple[Path, ...] = () - extra_paths: tuple[Path, ...] = () - include_tests: bool = True - fallback_paths: tuple[Path, ...] = () - - @property - def monitored_paths(self) -> tuple[Path, ...]: - selected = ( - (*self.source_paths, *self.test_paths, *self.extra_paths) - if self.include_tests - else (*self.source_paths, *self.extra_paths) - ) - return selected or self.fallback_paths - - -def _run_prefiltered_text_search( - project_root: Path, - paths: tuple[Path, ...], -) -> _TextSearchReport: - from python_lang_parser.parser import parse_python_file - - return _TextSearchReport( - modules=tuple(parse_python_file(path) for path in paths), - project_resolution=_fast_text_search_scope(project_root), - root_paths=tuple(str(path) for path in paths), - ) - - -def _fast_text_search_scope(project_root: Path) -> _TextSearchScope: - source_paths = tuple( - path for name in ("src",) for path in (project_root / name,) if path.exists() - ) - test_paths = tuple( - path for name in ("tests",) for path in (project_root / name,) if path.exists() - ) - return _TextSearchScope( - project_root=project_root, - source_paths=source_paths, - test_paths=test_paths, - fallback_paths=(project_root,), - ) - - -def _fast_owner_items_scope(project_root: Path, owner_path: Path) -> _TextSearchScope: - return _TextSearchScope( - project_root=project_root, - fallback_paths=(owner_path,), - ) diff --git a/src/python_lang_project_harness/_evidence_graph.py b/src/python_lang_project_harness/_evidence_graph.py deleted file mode 100644 index 0faad49..0000000 --- a/src/python_lang_project_harness/_evidence_graph.py +++ /dev/null @@ -1,214 +0,0 @@ -"""Provider-owned evidence graph packets for Python projects.""" - -from __future__ import annotations - -import json -from pathlib import Path -from typing import Any - -_EVIDENCE_GRAPH_SCHEMA_ID = "agent.semantic-protocols.semantic-evidence-graph" -_EVIDENCE_GRAPH_PROTOCOL_ID = "agent.semantic-protocols.evidence-graph" -_LANGUAGE_ID = "python" -_PROVIDER_ID = "asp-python" -_NAMESPACE = "agent.semantic-protocols.languages.python.asp-python" - - -def build_python_evidence_graph(project_root: Path) -> dict[str, Any]: - """Return a portable evidence graph for a Python project.""" - - root = project_root.resolve() - owner_path = _select_owner_path(root) - owner_id = _node_id("python:owner", owner_path) - claim_id = _node_id("python:claim", owner_path) - receipt_id = _node_id("python:receipt", "asp-python-check-full") - action_id = _node_id("python:action", "run-asp-python-check-full") - gap_id = _node_id("python:gap", f"{owner_path}:receipt") - check_command = "asp-python check --full ." - nodes: list[dict[str, Any]] = [ - { - "nodeId": owner_id, - "kind": "owner", - "label": owner_path, - "ownerPath": owner_path, - "status": "current", - "location": {"path": owner_path, "line": 1, "column": 0}, - "fields": {"languageId": _LANGUAGE_ID, "source": "provider-project"}, - }, - { - "nodeId": claim_id, - "kind": "invariant-candidate", - "label": "Python provider behavior needs executable evidence", - "ownerPath": owner_path, - "candidateId": "python.evidence.project-harness", - "status": "needs-injection", - "summary": "Project-level Python policy and semantic search behavior should be linked to verification receipts.", - "location": {"path": owner_path, "line": 1, "column": 0}, - "fields": { - "sourceRuleId": "PY-EVIDENCE-GRAPH", - "receiptKind": "harness-check", - }, - }, - { - "nodeId": receipt_id, - "kind": "verification-receipt", - "label": check_command, - "receiptId": "python.asp-python.check.full", - "status": "needs-injection", - "summary": "Run the Python harness full check and attach the receipt before treating the claim as verified.", - "fields": {"command": check_command}, - }, - { - "nodeId": action_id, - "kind": "review-action", - "label": "Run asp-python check --full .", - "actionId": "python.run-asp-python-check-full", - "status": "missing", - "summary": "run-receipt", - "fields": { - "priority": "p0", - "targetId": "python.evidence.project-harness", - }, - }, - ] - edges = [ - _edge("python:edge:owner-claim", "supports-claim", owner_id, claim_id), - _edge("python:edge:claim-receipt", "requires-evidence", claim_id, receipt_id), - _edge("python:edge:action-claim", "requires-evidence", action_id, claim_id), - ] - gaps = [ - { - "gapId": gap_id, - "ownerPath": owner_path, - "summary": "No attached asp-python full-check receipt for this evidence graph.", - "severity": "warning", - "fields": {"nextCommand": check_command}, - } - ] - return { - "schemaId": _EVIDENCE_GRAPH_SCHEMA_ID, - "schemaVersion": "1", - "protocolId": _EVIDENCE_GRAPH_PROTOCOL_ID, - "protocolVersion": "1", - "graphId": "python.evidence.graph", - "producer": _producer(), - "project": _project(root), - "summary": _summary(nodes, edges, gaps), - "nodes": nodes, - "edges": edges, - "gaps": gaps, - "fields": { - "next": "pipe JSON to `asp graph render --packet - --view seeds`", - }, - } - - -def render_python_evidence_graph(graph: dict[str, Any]) -> str: - """Render an agent-facing compact evidence graph summary.""" - - summary = graph["summary"] - return ( - "evidence-graph " - f"nodes={summary['nodes']} edges={summary['edges']} " - f"owners={summary['owners']} claims={summary['claims']} " - f"stale-items={summary['staleItems']} gaps={summary['gaps']}\n" - ) - - -def render_python_evidence_graph_json(graph: dict[str, Any]) -> str: - """Render evidence graph JSON.""" - - return json.dumps(graph, separators=(",", ":")) + "\n" - - -def _summary( - nodes: list[dict[str, Any]], - edges: list[dict[str, Any]], - gaps: list[dict[str, Any]], -) -> dict[str, int]: - return { - "nodes": len(nodes), - "edges": len(edges), - "owners": sum(1 for node in nodes if node["kind"] == "owner"), - "claims": sum(1 for node in nodes if node["kind"] == "invariant-candidate"), - "staleItems": sum( - 1 for node in nodes if node.get("status") in {"stale", "expired"} - ), - "gaps": len(gaps), - } - - -def _producer() -> dict[str, str]: - return { - "languageId": _LANGUAGE_ID, - "providerId": _PROVIDER_ID, - "namespace": _NAMESPACE, - } - - -def _project(root: Path) -> dict[str, Any]: - project: dict[str, Any] = {"root": str(root), "fields": {}} - package_name = _package_name(root) - if package_name is not None: - project["package"] = package_name - return project - - -def _edge( - edge_id: str, - kind: str, - from_node_id: str, - to_node_id: str, -) -> dict[str, str]: - return { - "edgeId": edge_id, - "kind": kind, - "fromNodeId": from_node_id, - "toNodeId": to_node_id, - } - - -def _select_owner_path(root: Path) -> str: - for candidate in ("pyproject.toml", "setup.cfg", "setup.py"): - if (root / candidate).is_file(): - return candidate - for source_root in ("src", "."): - base = root / source_root - if not base.exists(): - continue - for path in sorted(base.rglob("*.py")): - if any(part.startswith(".") for part in path.relative_to(root).parts): - continue - return _relative_path(root, path) - return "." - - -def _package_name(root: Path) -> str | None: - pyproject = root / "pyproject.toml" - if not pyproject.is_file(): - return None - from ._project_config import read_pyproject_payload - - value = read_pyproject_payload(pyproject) - name = value.get("project", {}).get("name") - return str(name) if name else None - - -def _relative_path(root: Path, path: Path) -> str: - return path.resolve().relative_to(root.resolve()).as_posix() - - -def _node_id(prefix: str, raw: str) -> str: - return f"{prefix}:{_sanitize_id_part(raw)}" - - -def _sanitize_id_part(raw: str) -> str: - output = "".join( - character.lower() - if character.isascii() - and (character.isalnum() or character in {".", "_", ":", "-"}) - else "." - for character in raw - ) - while ".." in output: - output = output.replace("..", ".") - return output.strip(".") or "root" diff --git a/src/python_lang_project_harness/_evidence_graph_turbo.py b/src/python_lang_project_harness/_evidence_graph_turbo.py deleted file mode 100644 index 7f908e9..0000000 --- a/src/python_lang_project_harness/_evidence_graph_turbo.py +++ /dev/null @@ -1,138 +0,0 @@ -"""Graph-turbo request projection for Python evidence graphs.""" - -from __future__ import annotations - -import json -from pathlib import Path -from typing import Any - -from ._evidence_graph import build_python_evidence_graph - -_GRAPH_TURBO_REQUEST_SCHEMA_ID = "agent.semantic-protocols.semantic-graph-turbo-request" -_SEMANTIC_LANGUAGE_PROTOCOL_ID = "agent.semantic-protocols.semantic-language" - - -def build_python_evidence_analysis_request(project_root: Path) -> dict[str, Any]: - """Return a graph-turbo request for the Python evidence graph.""" - - graph = build_python_evidence_graph(project_root) - analysis_graph = _analysis_graph(graph) - summary = { - "graphs": 1, - "nodes": graph["summary"]["nodes"], - "edges": graph["summary"]["edges"], - "owners": graph["summary"]["owners"], - "claims": graph["summary"]["claims"], - "staleItems": graph["summary"]["staleItems"], - "gaps": graph["summary"]["gaps"], - } - return { - "schemaId": _GRAPH_TURBO_REQUEST_SCHEMA_ID, - "schemaVersion": "1", - "protocolId": _SEMANTIC_LANGUAGE_PROTOCOL_ID, - "protocolVersion": "1", - "packetKind": "graph-turbo-request", - "requestId": ( - "python.evidence.analysis." - f"graphs-{summary['graphs']}.nodes-{summary['nodes']}.gaps-{summary['gaps']}" - ), - "surface": "evidence-analyze", - "queryTerms": ["python evidence quality"], - "profile": "evidence-quality", - "algorithm": "typed-ppr-diverse", - "seedIds": _analysis_seed_ids(analysis_graph), - "budget": 8, - "producer": graph["producer"], - "project": _analysis_project(project_root.resolve(), graph), - "summary": summary, - "graphs": [analysis_graph], - "fields": { - "next": "pipe JSON to `asp graph render --packet - --view seeds`", - }, - } - - -def render_python_evidence_analysis_request(request: dict[str, Any]) -> str: - """Render an agent-facing compact graph-turbo request summary.""" - - summary = request["summary"] - return ( - "evidence-analysis " - f"profile={request['profile']} graphs={summary['graphs']} " - f"nodes={summary['nodes']} edges={summary['edges']} " - f"owners={summary['owners']} claims={summary['claims']} " - f"stale-items={summary['staleItems']} gaps={summary['gaps']} " - 'next="asp graph render --packet - --view seeds"\n' - ) - - -def render_python_evidence_analysis_request_json(request: dict[str, Any]) -> str: - """Render graph-turbo request JSON.""" - - return json.dumps(request, separators=(",", ":")) + "\n" - - -def _analysis_graph(graph: dict[str, Any]) -> dict[str, Any]: - return { - "graphId": graph["graphId"], - "summary": graph["summary"], - "nodes": [_analysis_node(node) for node in graph["nodes"]], - "edges": [_analysis_edge(edge) for edge in graph["edges"]], - "gaps": graph.get("gaps", []), - } - - -def _analysis_node(node: dict[str, Any]) -> dict[str, Any]: - location = node.get("location") if isinstance(node.get("location"), dict) else {} - path = node.get("ownerPath") or location.get("path") - line = location.get("line") - rendered: dict[str, Any] = { - "id": node["nodeId"], - "kind": node["kind"], - "role": _node_role(str(node["kind"])), - "value": node["label"], - "fields": dict(node.get("fields", {})), - } - if path is not None: - rendered["path"] = path - rendered["ownerPath"] = node.get("ownerPath", path) - if isinstance(line, int): - rendered["locator"] = f"{path}:{line}:{line}" - rendered["startLine"] = line - rendered["endLine"] = line - for key in ("candidateId", "receiptId", "actionId", "summary", "status"): - if key in node: - rendered["fields"][key] = str(node[key]) - return rendered - - -def _analysis_edge(edge: dict[str, Any]) -> dict[str, Any]: - return { - "source": edge["fromNodeId"], - "target": edge["toNodeId"], - "relation": edge["kind"], - "fields": {"edgeId": edge["edgeId"]}, - } - - -def _analysis_seed_ids(graph: dict[str, Any]) -> list[str]: - seeds = [str(node["id"]) for node in graph["nodes"] if node.get("kind") == "owner"] - if seeds: - return seeds - nodes = graph["nodes"] - return [str(nodes[0]["id"])] if nodes else [] - - -def _analysis_project(root: Path, graph: dict[str, Any]) -> dict[str, Any]: - project = graph.get("project", {}) - package = project.get("package") if isinstance(project, dict) else None - return {"root": str(root), "package": package, "fields": {}} - - -def _node_role(kind: str) -> str: - return { - "owner": "path", - "invariant-candidate": "claim", - "verification-receipt": "receipt", - "review-action": "action", - }.get(kind, "evidence") diff --git a/src/python_lang_project_harness/_harness_rules.py b/src/python_lang_project_harness/_harness_rules.py deleted file mode 100644 index 8c30465..0000000 --- a/src/python_lang_project_harness/_harness_rules.py +++ /dev/null @@ -1,44 +0,0 @@ -"""Render source-embedded Python harness rule fixtures.""" - -from __future__ import annotations - -from importlib.resources import files -from pathlib import Path - -_HARNESS_RULES_RESOURCE = "harness-rules.md" - - -def python_harness_rules_markdown() -> str: - """Return the source-embedded harness rule list.""" - - return ( - files(__package__).joinpath(_HARNESS_RULES_RESOURCE).read_text(encoding="utf-8") - ) - - -def render_python_harness_rules_markdown() -> str: - """Render the source-embedded Python harness rules as markdown.""" - - output = [ - "# python-lang-project-harness", - "", - "## Harness Rules", - "", - "Generated from embedded `src/python_lang_project_harness/harness-rules.md`.", - "", - ] - for line in python_harness_rules_markdown().splitlines(): - if item := line.removeprefix("- "): - if ": " in item: - rule_id, sentence = item.split(": ", 1) - output.append(f"- **{rule_id}**: {sentence}") - return "\n".join(output) + "\n" - - -def write_python_harness_rules_to_unit_tests(unit_test_dir: Path) -> Path: - """Write the generated harness rules into a downstream unit test directory.""" - - output_path = unit_test_dir / "harness-rules.generated.md" - output_path.parent.mkdir(parents=True, exist_ok=True) - output_path.write_text(render_python_harness_rules_markdown(), encoding="utf-8") - return output_path diff --git a/src/python_lang_project_harness/_semantic_graph_fact_collect.py b/src/python_lang_project_harness/_semantic_graph_fact_collect.py deleted file mode 100644 index f9c7c36..0000000 --- a/src/python_lang_project_harness/_semantic_graph_fact_collect.py +++ /dev/null @@ -1,215 +0,0 @@ -"""Collect Python field/type facts from parser-owned AST.""" - -from __future__ import annotations - -import ast -from pathlib import Path - -from ._semantic_graph_fact_model import FieldFact, collection_kind - -SKIP_DIRS = { - ".git", - ".hg", - ".mypy_cache", - ".pytest_cache", - ".ruff_cache", - "__pycache__", - "build", - "dist", - "node_modules", - "target", - "venv", - ".venv", -} - - -def collect_field_facts(project_root: Path, query: str, stdin: str) -> list[FieldFact]: - terms = query_terms(query) - paths = candidate_paths(project_root, stdin) or python_source_paths(project_root) - facts: list[FieldFact] = [] - seen: set[tuple[str, str, str, int]] = set() - for path in paths: - for fact in field_facts_for_path(project_root, path): - key = (fact.path, fact.container_name, fact.field_name, fact.line) - if key in seen or not fact_matches_terms(fact, terms): - continue - seen.add(key) - facts.append(fact) - if len(facts) >= 64: - return facts - return facts - - -def candidate_paths(project_root: Path, stdin: str) -> list[Path]: - paths: list[Path] = [] - seen: set[Path] = set() - for line in stdin.splitlines(): - path_text = line.split(":", 1)[0].strip() - if not path_text: - continue - path = Path(path_text) - absolute = path if path.is_absolute() else project_root / path - if absolute.suffix != ".py" or not absolute.exists() or absolute in seen: - continue - seen.add(absolute) - paths.append(absolute) - return paths - - -def python_source_paths(project_root: Path) -> list[Path]: - paths: list[Path] = [] - for path in project_root.rglob("*.py"): - if any(part in SKIP_DIRS for part in path.relative_to(project_root).parts): - continue - paths.append(path) - return sorted(paths) - - -def field_facts_for_path(project_root: Path, path: Path) -> list[FieldFact]: - try: - source = path.read_text(encoding="utf-8") - module = ast.parse(source) - except (OSError, SyntaxError, UnicodeDecodeError): - return [] - relative_path = path.relative_to(project_root).as_posix() - facts: list[FieldFact] = [] - for node in ast.walk(module): - if isinstance(node, ast.ClassDef): - facts.extend(class_field_facts(relative_path, node)) - return facts - - -def class_field_facts(path: str, class_node: ast.ClassDef) -> list[FieldFact]: - facts: list[FieldFact] = [] - for item in class_node.body: - if isinstance(item, ast.AnnAssign) and isinstance(item.target, ast.Name): - type_value = annotation_text(item.annotation) - if type_value is not None: - facts.append( - field_fact( - path, - class_node.name, - item.target.id, - type_value, - item.lineno, - class_node.lineno, - getattr(class_node, "end_lineno", item.lineno), - ) - ) - if isinstance(item, ast.FunctionDef) and item.name == "__init__": - facts.extend(init_self_field_facts(path, class_node, item)) - return facts - - -def init_self_field_facts( - path: str, - class_node: ast.ClassDef, - init_node: ast.FunctionDef, -) -> list[FieldFact]: - facts: list[FieldFact] = [] - for item in ast.walk(init_node): - if isinstance(item, ast.AnnAssign): - fact = self_field_fact(path, class_node, init_node, item) - if fact is not None: - facts.append(fact) - return facts - - -def self_field_fact( - path: str, - class_node: ast.ClassDef, - init_node: ast.FunctionDef, - item: ast.AnnAssign, -) -> FieldFact | None: - target = item.target - if not ( - isinstance(target, ast.Attribute) - and isinstance(target.value, ast.Name) - and target.value.id == "self" - ): - return None - type_value = annotation_text(item.annotation) - if type_value is None: - return None - return field_fact( - path, - class_node.name, - target.attr, - type_value, - item.lineno, - init_node.lineno, - getattr(init_node, "end_lineno", item.lineno), - ) - - -def field_fact( - path: str, - container_name: str, - field_name: str, - type_value: str, - line: int, - context_start: int, - context_end: int, -) -> FieldFact: - return FieldFact( - path=path, - container_name=container_name, - field_name=field_name, - type_value=type_value, - collection_kind=collection_kind(type_value), - line=line, - context_start=context_start, - context_end=context_end, - ) - - -def annotation_text(annotation: ast.expr) -> str | None: - try: - return ast.unparse(annotation) - except (AttributeError, ValueError): - return None - - -def query_terms(query: str) -> set[str]: - normalized = "".join( - character.lower() if character == "_" or character.isalnum() else " " - for character in query - ) - return {term for term in normalized.split() if term} - - -def fact_matches_terms(fact: FieldFact, terms: set[str]) -> bool: - if not terms or terms & semantic_shape_terms(): - return True - text = " ".join( - part - for part in ( - fact.container_name, - fact.field_name, - fact.type_value, - fact.collection_kind or "", - ) - if part - ).lower() - return any(term in text for term in terms) - - -def semantic_shape_terms() -> set[str]: - return { - "field", - "fields", - "type", - "types", - "scalar", - "scalars", - "collection", - "collections", - "list", - "lists", - "map", - "maps", - "set", - "sets", - "dict", - "tuple", - } diff --git a/src/python_lang_project_harness/_semantic_graph_fact_render.py b/src/python_lang_project_harness/_semantic_graph_fact_render.py deleted file mode 100644 index 832b710..0000000 --- a/src/python_lang_project_harness/_semantic_graph_fact_render.py +++ /dev/null @@ -1,187 +0,0 @@ -"""Render Python data-shape facts as graph-turbo nodes and edges.""" - -from __future__ import annotations - -from typing import Any - -from ._semantic_graph_fact_model import FieldFact -from ._semantic_graph_fact_render_fields import ( - render_collection_fact, - render_collection_family, - render_field_fact, - render_type_fact, -) - -LANGUAGE_ID = "python" -PROVIDER_ID = "asp-python" - - -def graph_payload( - query: str, facts: list[FieldFact] -) -> dict[str, list[dict[str, Any]]]: - nodes: list[dict[str, Any]] = [] - edges: list[dict[str, Any]] = [] - collection_ids: set[str] = set() - for fact in facts: - field_id = field_id_for(fact) - type_id = type_id_for(fact) - locator = f"{fact.path}:{fact.line}:{fact.line}" - fields = graph_fields(fact) - nodes.append(field_node(fact, field_id, locator, fields)) - nodes.append(type_node(fact, type_id, locator, fields)) - edges.append({"source": field_id, "target": type_id, "relation": "has_type"}) - append_collection(nodes, edges, collection_ids, fact, field_id, type_id) - if query: - edges.append( - { - "source": stable_id("query", query), - "target": field_id, - "relation": "matches", - } - ) - return {"nodes": nodes, "edges": edges} - - -def graph_fields(fact: FieldFact) -> dict[str, Any]: - family = render_collection_family(fact.collection_kind) - fields: dict[str, Any] = { - "languageId": LANGUAGE_ID, - "providerId": PROVIDER_ID, - "semanticFactKind": "field", - "provenance": "parser", - "confidence": "exact", - "freshness": "fresh", - "containerName": fact.container_name, - "fieldName": fact.field_name, - "typeValue": fact.type_value, - "elementShape": "collection" if fact.collection_kind else "scalar", - "contextLocator": f"{fact.path}:{fact.context_start}:{fact.context_end}", - "contextStartLine": fact.context_start, - "contextEndLine": fact.context_end, - "field": render_field_fact(fact, family), - } - if fact.collection_kind is not None: - fields["collectionKind"] = fact.collection_kind - fields["collectionFamily"] = family - fields["collectionImpl"] = fact.collection_kind - return fields - - -def field_node( - fact: FieldFact, - field_id: str, - locator: str, - fields: dict[str, Any], -) -> dict[str, Any]: - return { - "id": field_id, - "kind": "field", - "role": "class-field", - "value": f"{fact.field_name}: {fact.type_value}", - "action": "code", - "path": fact.path, - "ownerPath": fact.path, - "symbol": fact.field_name, - "startLine": fact.line, - "endLine": fact.line, - "locator": locator, - "matchText": f"{fact.container_name}.{fact.field_name}: {fact.type_value}", - "fields": fields, - } - - -def type_node( - fact: FieldFact, - type_id: str, - locator: str, - fields: dict[str, Any], -) -> dict[str, Any]: - type_fields = { - **fields, - "semanticFactKind": "type", - "type": render_type_fact(fact), - } - type_fields.pop("field", None) - return { - "id": type_id, - "kind": "type", - "role": "field-type", - "value": fact.type_value, - "action": "evidence", - "path": fact.path, - "ownerPath": fact.path, - "symbol": fact.type_value.split("[", 1)[0], - "startLine": fact.line, - "endLine": fact.line, - "locator": locator, - "fields": type_fields, - } - - -def append_collection( - nodes: list[dict[str, Any]], - edges: list[dict[str, Any]], - collection_ids: set[str], - fact: FieldFact, - field_id: str, - type_id: str, -) -> None: - if fact.collection_kind is None: - return - collection_id = f"collection:{fact.collection_kind}" - if collection_id not in collection_ids: - collection_ids.add(collection_id) - nodes.append( - { - "id": collection_id, - "kind": "collection", - "role": "family", - "value": fact.collection_kind, - "action": "evidence", - "symbol": fact.collection_kind, - "fields": { - "languageId": LANGUAGE_ID, - "providerId": PROVIDER_ID, - "semanticFactKind": "collection", - "provenance": "parser", - "confidence": "exact", - "freshness": "fresh", - "collectionFamily": render_collection_family(fact.collection_kind), - "collectionImpl": fact.collection_kind, - "collectionKind": fact.collection_kind, - "collection": render_collection_fact(fact), - }, - } - ) - edges.append( - {"source": field_id, "target": collection_id, "relation": "collection_of"} - ) - edges.append( - {"source": type_id, "target": collection_id, "relation": "collection_of"} - ) - - -def field_id_for(fact: FieldFact) -> str: - return stable_id( - "field", - f"{fact.path}:{fact.container_name}:{fact.field_name}:{fact.line}", - ) - - -def type_id_for(fact: FieldFact) -> str: - return stable_id( - "type", - f"{fact.path}:{fact.field_name}:{fact.type_value}:{fact.line}", - ) - - -def stable_id(kind: str, value: str) -> str: - rendered = [kind, ":"] - for character in value: - if character.isalnum(): - rendered.append(character.lower()) - elif character in {"/", ".", "_", "-"}: - rendered.append(character) - else: - rendered.append("-") - return "".join(rendered).strip("-") diff --git a/src/python_lang_project_harness/_semantic_graph_fact_render_fields.py b/src/python_lang_project_harness/_semantic_graph_fact_render_fields.py deleted file mode 100644 index d677e6f..0000000 --- a/src/python_lang_project_harness/_semantic_graph_fact_render_fields.py +++ /dev/null @@ -1,81 +0,0 @@ -"""Field payload helpers for Python semantic graph rendering.""" - -from __future__ import annotations - -from typing import Any - -from ._semantic_graph_fact_model import FieldFact - - -def render_field_fact(fact: FieldFact, family: str | None) -> dict[str, Any]: - return { - "ownerKind": "class", - "name": fact.field_name, - "ownerPath": fact.path, - "access": render_access_modes(family), - } - - -def render_type_fact(fact: FieldFact) -> dict[str, str]: - args = render_collection_type_args(fact.type_value) - rendered: dict[str, str] = {"name": fact.type_value} - if render_collection_family(fact.collection_kind) == "map": - if args: - rendered["key"] = args[0] - if len(args) > 1: - rendered["value"] = args[1] - elif args: - rendered["element"] = args[0] - return rendered - - -def render_collection_fact(fact: FieldFact) -> dict[str, Any]: - family = render_collection_family(fact.collection_kind) - rendered: dict[str, Any] = { - "family": family, - "impl": fact.collection_kind, - "mutation": render_mutation_modes(family), - } - args = render_collection_type_args(fact.type_value) - if family == "map": - if args: - rendered["keyType"] = args[0] - if len(args) > 1: - rendered["valueType"] = args[1] - elif args: - rendered["elementType"] = args[0] - return rendered - - -def render_collection_family(collection_kind: str | None) -> str | None: - if collection_kind in {"list", "tuple"}: - return "sequence" - if collection_kind == "dict": - return "map" - if collection_kind == "set": - return "set" - return None - - -def render_access_modes(family: str | None) -> list[str]: - if family == "map": - return ["read", "write", "validate"] - return ["read", "append", "validate"] - - -def render_mutation_modes(family: str | None) -> list[str]: - if family == "map": - return ["insert", "remove", "update"] - if family == "set": - return ["insert", "remove"] - return ["append", "remove"] - - -def render_collection_type_args(type_value: str) -> list[str]: - if "[" not in type_value or not type_value.endswith("]"): - return [] - return [ - part.strip() - for part in type_value.split("[", 1)[1].removesuffix("]").split(",") - if part.strip() - ] diff --git a/src/python_lang_project_harness/_semantic_graph_facts.py b/src/python_lang_project_harness/_semantic_graph_facts.py deleted file mode 100644 index b9d4335..0000000 --- a/src/python_lang_project_harness/_semantic_graph_facts.py +++ /dev/null @@ -1,78 +0,0 @@ -"""Provider-owned graph facts for Python data-shape queries.""" - -from __future__ import annotations - -import json -from pathlib import Path - -from ._cli_args import ProtocolArgs -from ._semantic_graph_fact_collect import collect_field_facts -from ._semantic_graph_fact_render import graph_payload -from ._semantic_graph_project_collect import collect_project_facts -from ._semantic_graph_project_render import project_graph_payload - - -def render_semantic_graph_facts( - args: ProtocolArgs, - *, - project_root: Path, - stdin: str, -) -> str | None: - """Render graph-turbo provider facts for Python field/type/collection queries.""" - - if not _supports_semantic_graph_facts(args): - return None - field_payload = graph_payload( - args.query or "", - collect_field_facts(project_root, args.query or "", stdin), - ) - project_payload = project_graph_payload(collect_project_facts(project_root)) - package_bridge_edges = _package_bridge_edges(field_payload, project_payload) - payload = { - "schemaId": "agent.semantic-protocols.semantic-fact-graph", - "schemaVersion": "1", - "protocolId": "agent.semantic-protocols.semantic-language", - "protocolVersion": "1", - "languageId": "python", - "providerId": "asp-python", - "projectRoot": project_root.as_posix(), - "query": args.query or "", - "nodes": [*field_payload["nodes"], *project_payload["nodes"]], - "edges": [ - *field_payload["edges"], - *project_payload["edges"], - *package_bridge_edges, - ], - } - return json.dumps(payload, sort_keys=True) + "\n" - - -def _supports_semantic_graph_facts(args: ProtocolArgs) -> bool: - return ( - args.command == "search" - and args.view == "semantic-facts" - and args.json - and args.query is not None - ) - - -def _package_bridge_edges( - field_payload: dict[str, list[dict[str, object]]], - project_payload: dict[str, list[dict[str, object]]], -) -> list[dict[str, str]]: - package_id = next( - ( - str(node["id"]) - for node in project_payload["nodes"] - if node.get("kind") == "package" and isinstance(node.get("id"), str) - ), - None, - ) - if package_id is None: - return [] - return [ - {"source": str(node["id"]), "target": package_id, "relation": "belongs_to"} - for node in field_payload["nodes"] - if node.get("kind") in {"field", "hot", "owner"} - and isinstance(node.get("id"), str) - ] diff --git a/src/python_lang_project_harness/_semantic_graph_project_collect.py b/src/python_lang_project_harness/_semantic_graph_project_collect.py deleted file mode 100644 index ad74728..0000000 --- a/src/python_lang_project_harness/_semantic_graph_project_collect.py +++ /dev/null @@ -1,151 +0,0 @@ -"""Collect Python package/build/test graph facts from project metadata.""" - -from __future__ import annotations - -import ast -from pathlib import Path -from typing import Any - -from ._project_config import read_pyproject_payload -from ._semantic_graph_fact_collect import SKIP_DIRS -from ._semantic_graph_fact_model import ( - DependencyFact, - PackageFact, - ProjectFact, - TestFact, - display_path, -) - -DEPENDENCY_LIMIT = 64 -TEST_LIMIT = 64 -PYTHON_SUFFIX = ".py" - - -def collect_project_facts(project_root: Path) -> ProjectFact | None: - manifest_path = project_root / "pyproject.toml" - pyproject = _read_pyproject(manifest_path) - project = pyproject.get("project", {}) if isinstance(pyproject, dict) else {} - if not isinstance(project, dict): - return None - package_name = project.get("name") - if not isinstance(package_name, str) or not package_name.strip(): - return None - package = PackageFact( - name=package_name.strip(), - manifest_path=display_path(project_root, manifest_path), - ) - return ProjectFact( - package=package, - dependencies=tuple(_dependency_facts(project, package.manifest_path)), - tests=tuple(_test_facts(project_root)), - ) - - -def _read_pyproject(path: Path) -> dict[str, Any]: - return read_pyproject_payload(path) - - -def _dependency_facts( - project: dict[str, Any], - manifest_path: str, -) -> list[DependencyFact]: - facts: list[DependencyFact] = [] - dependencies = project.get("dependencies", []) - if isinstance(dependencies, list): - facts.extend( - fact - for dependency in dependencies - if isinstance(dependency, str) - if (fact := _dependency_fact(dependency, "normal", manifest_path)) - is not None - ) - optional = project.get("optional-dependencies", {}) - if isinstance(optional, dict): - for extra, dependencies in sorted(optional.items()): - if not isinstance(extra, str) or not isinstance(dependencies, list): - continue - dependency_kind = "dev" if extra in {"dev", "test", "tests"} else "optional" - facts.extend( - fact - for dependency in dependencies - if isinstance(dependency, str) - if ( - fact := _dependency_fact( - dependency, - dependency_kind, - manifest_path, - extra=extra, - ) - ) - is not None - ) - return sorted(facts, key=lambda fact: (fact.dependency_kind, fact.dependency_name))[ - :DEPENDENCY_LIMIT - ] - - -def _dependency_fact( - requirement: str, - dependency_kind: str, - manifest_path: str, - *, - extra: str | None = None, -) -> DependencyFact | None: - dependency_name = _dependency_name(requirement) - if dependency_name is None: - return None - version_req = requirement[len(dependency_name) :].strip() - return DependencyFact( - package_name=dependency_name.replace("_", "-"), - dependency_name=dependency_name, - dependency_kind=dependency_kind, - manifest_path=manifest_path, - version_req=version_req or None, - extra=extra, - ) - - -def _dependency_name(requirement: str) -> str | None: - name_chars: list[str] = [] - for character in requirement.strip(): - if character.isalnum() or character in {"_", ".", "-"}: - name_chars.append(character) - continue - break - name = "".join(name_chars) - return name or None - - -def _test_facts(project_root: Path) -> list[TestFact]: - tests_dir = project_root / "tests" - if not tests_dir.exists(): - return [] - facts: list[TestFact] = [] - for path in sorted(tests_dir.rglob("*")): - if not path.is_file() or path.suffix != PYTHON_SUFFIX: - continue - if any(part in SKIP_DIRS for part in path.relative_to(project_root).parts): - continue - function_count = _test_function_count(path) - facts.append( - TestFact( - path=display_path(project_root, path), - name=path.stem, - function_count=function_count, - ) - ) - if len(facts) >= TEST_LIMIT: - break - return facts - - -def _test_function_count(path: Path) -> int: - try: - module = ast.parse(path.read_text(encoding="utf-8")) - except (OSError, SyntaxError, UnicodeDecodeError): - return 0 - return sum( - 1 - for node in ast.walk(module) - if isinstance(node, ast.FunctionDef) and node.name.startswith("test_") - ) diff --git a/src/python_lang_project_harness/_semantic_graph_project_render.py b/src/python_lang_project_harness/_semantic_graph_project_render.py deleted file mode 100644 index c22af4f..0000000 --- a/src/python_lang_project_harness/_semantic_graph_project_render.py +++ /dev/null @@ -1,198 +0,0 @@ -"""Render Python package/build/test facts as semantic graph nodes.""" - -from __future__ import annotations - -from typing import Any - -from ._semantic_graph_fact_model import DependencyFact, ProjectFact, TestFact -from ._semantic_graph_fact_render import LANGUAGE_ID, PROVIDER_ID, stable_id - - -def project_graph_payload( - project: ProjectFact | None, -) -> dict[str, list[dict[str, Any]]]: - if project is None: - return {"nodes": [], "edges": []} - nodes = [_package_node(project), _build_node(project)] - edges = [ - { - "source": _package_id_for(project.package.name), - "target": _build_id_for(project.package.name), - "relation": "builds", - } - ] - for dependency in project.dependencies: - dependency_id = _dependency_id_for(project.package.name, dependency) - nodes.append(_dependency_node(project, dependency, dependency_id)) - edges.append( - { - "source": _package_id_for(project.package.name), - "target": dependency_id, - "relation": "depends_on", - } - ) - for test in project.tests: - test_id = _test_id_for(project.package.name, test) - nodes.append(_test_node(project, test, test_id)) - edges.append( - { - "source": _build_id_for(project.package.name), - "target": test_id, - "relation": "tests", - } - ) - edges.append( - { - "source": test_id, - "target": _package_id_for(project.package.name), - "relation": "belongs_to", - } - ) - return {"nodes": nodes, "edges": edges} - - -def _package_node(project: ProjectFact) -> dict[str, Any]: - manifest_path = project.package.manifest_path - return { - "id": _package_id_for(project.package.name), - "kind": "package", - "role": "python-project", - "value": project.package.name, - "action": "package", - "path": manifest_path, - "ownerPath": manifest_path, - "startLine": 1, - "endLine": 1, - "locator": f"{manifest_path}:1:1", - "matchText": project.package.name, - "fields": { - "languageId": LANGUAGE_ID, - "providerId": PROVIDER_ID, - "semanticFactKind": "package", - "provenance": "parser", - "confidence": "exact", - "freshness": "fresh", - "packageName": project.package.name, - "manifestPath": manifest_path, - }, - } - - -def _build_node(project: ProjectFact) -> dict[str, Any]: - manifest_path = project.package.manifest_path - command = _test_command() - return { - "id": _build_id_for(project.package.name), - "kind": "build", - "role": "pytest", - "value": command, - "action": "build", - "path": manifest_path, - "ownerPath": manifest_path, - "startLine": 1, - "endLine": 1, - "locator": f"{manifest_path}:1:1", - "matchText": command, - "fields": { - "languageId": LANGUAGE_ID, - "providerId": PROVIDER_ID, - "semanticFactKind": "build", - "provenance": "build", - "confidence": "exact", - "freshness": "fresh", - "packageName": project.package.name, - "manifestPath": manifest_path, - "tool": "pytest", - "command": command, - }, - } - - -def _dependency_node( - project: ProjectFact, - dependency: DependencyFact, - dependency_id: str, -) -> dict[str, Any]: - fields: dict[str, Any] = { - "languageId": LANGUAGE_ID, - "providerId": PROVIDER_ID, - "semanticFactKind": "dependency", - "provenance": "parser", - "confidence": "exact", - "freshness": "fresh", - "packageName": project.package.name, - "manifestPath": dependency.manifest_path, - "dependencyName": dependency.dependency_name, - "dependencyPackageName": dependency.package_name, - "dependencyKind": dependency.dependency_kind, - } - if dependency.version_req is not None: - fields["versionReq"] = dependency.version_req - if dependency.extra is not None: - fields["extra"] = dependency.extra - return { - "id": dependency_id, - "kind": "dependency", - "role": dependency.dependency_kind, - "value": dependency.package_name, - "action": "deps", - "path": dependency.manifest_path, - "ownerPath": dependency.manifest_path, - "startLine": 1, - "endLine": 1, - "locator": f"{dependency.manifest_path}:1:1", - "matchText": dependency.package_name, - "fields": fields, - } - - -def _test_node(project: ProjectFact, test: TestFact, test_id: str) -> dict[str, Any]: - return { - "id": test_id, - "kind": "test", - "role": "pytest-target", - "value": test.name, - "action": "tests", - "path": test.path, - "ownerPath": test.path, - "startLine": 1, - "endLine": 1, - "locator": f"{test.path}:1:1", - "matchText": test.name, - "fields": { - "languageId": LANGUAGE_ID, - "providerId": PROVIDER_ID, - "semanticFactKind": "test", - "provenance": "test", - "confidence": "exact", - "freshness": "fresh", - "packageName": project.package.name, - "testName": test.name, - "testPath": test.path, - "functionCount": test.function_count, - "command": _test_command(), - }, - } - - -def _package_id_for(package_name: str) -> str: - return stable_id("package", package_name) - - -def _build_id_for(package_name: str) -> str: - return stable_id("build", f"{_test_command()}:{package_name}") - - -def _dependency_id_for(package_name: str, dependency: DependencyFact) -> str: - return stable_id( - "dependency", - f"{package_name}:{dependency.dependency_kind}:{dependency.package_name}", - ) - - -def _test_id_for(package_name: str, test: TestFact) -> str: - return stable_id("test", f"{package_name}:{test.path}") - - -def _test_command() -> str: - return "uv run --project . pytest" diff --git a/src/python_lang_project_harness/_semantic_language.py b/src/python_lang_project_harness/_semantic_language.py deleted file mode 100644 index b71cf1f..0000000 --- a/src/python_lang_project_harness/_semantic_language.py +++ /dev/null @@ -1,206 +0,0 @@ -"""Semantic-language registry metadata for the Python provider.""" - -from __future__ import annotations - -from typing import Any - -from . import _semantic_language_ids as ids -from ._semantic_language_benchmark import python_search_benchmark_invocation -from ._semantic_language_catalog import python_search_view_descriptors -from ._semantic_language_invocation import attach_semantic_language_invocations -from ._semantic_language_query import python_query_method_descriptors -from ._semantic_language_schemas import python_semantic_language_schemas -from ._semantic_provider_doctor import _provider_identity -from ._semantic_query_pack import python_query_pack_descriptor - -_PYTHON_CHECK_METHODS = ("check/changed", "check/full") -_PYTHON_QUERY_METHODS = ( - "query", - "query/exact-selector-native-v1", -) -_PYTHON_AST_PATCH_METHODS = ("ast-patch/dry-run",) -_PYTHON_EVIDENCE_METHODS = ("evidence/graph", "evidence/analyze") -_PYTHON_AGENT_METHODS = ("agent/doctor", "agent/guide") -_PYTHON_SEARCH_VIEW_DESCRIPTORS = python_search_view_descriptors() -_PYTHON_SEARCH_VIEWS = tuple( - descriptor["view"] for descriptor in _PYTHON_SEARCH_VIEW_DESCRIPTORS -) -_PYTHON_SEARCH_METHODS = tuple(f"search/{view}" for view in _PYTHON_SEARCH_VIEWS) - - -def semantic_language_registry_document() -> dict[str, Any]: - """Return the provider registry document advertised by agent doctor.""" - - payload: dict[str, Any] = { - "registryId": ids.SEMANTIC_LANGUAGE_REGISTRY_ID, - "registryVersion": ids.SEMANTIC_LANGUAGE_REGISTRY_VERSION, - "protocolId": ids.SEMANTIC_LANGUAGE_PROTOCOL_ID, - "protocolVersion": ids.SEMANTIC_LANGUAGE_PROTOCOL_VERSION, - "languages": [python_semantic_language_registration()], - } - return payload - - -def python_semantic_language_registration() -> dict[str, Any]: - """Return the Python semantic-language provider registration.""" - - identity = _provider_identity() - return { - "languageId": identity["languageId"], - "providerId": identity["providerId"], - "binary": identity["binary"], - "namespace": ids.PYTHON_PROVIDER_NAMESPACE, - "displayName": "Python", - "methods": [ - *_PYTHON_SEARCH_METHODS, - *_PYTHON_QUERY_METHODS, - *_PYTHON_CHECK_METHODS, - *_PYTHON_AST_PATCH_METHODS, - *_PYTHON_EVIDENCE_METHODS, - *_PYTHON_AGENT_METHODS, - ], - "methodDescriptors": python_semantic_language_method_descriptors(), - "schemas": python_semantic_language_schemas(), - "queryPackDescriptor": python_query_pack_descriptor(), - } - - -def python_semantic_language_method_descriptors() -> list[dict[str, Any]]: - """Return method descriptors for the Python provider registry.""" - - descriptors = [ - _python_search_method_descriptor(descriptor) - for descriptor in _PYTHON_SEARCH_VIEW_DESCRIPTORS - ] - descriptors.extend(python_query_method_descriptors()) - descriptors.extend( - { - "method": method, - "command": "check", - "supportsJson": True, - "supportsCompact": True, - } - for method in _PYTHON_CHECK_METHODS - ) - descriptors.extend( - { - "method": method, - "command": "ast-patch", - "input": "semantic-ast-patch packet", - "requiredOptions": ["--packet"], - "outputSchemaIds": ["agent.semantic-protocols.semantic-ast-patch-receipt"], - "supportsJson": True, - "supportsCompact": False, - "mutationAvailable": False, - } - for method in _PYTHON_AST_PATCH_METHODS - ) - descriptors.extend( - [ - { - "method": "evidence/graph", - "command": "evidence", - "input": "provider project root", - "outputSchemaIds": [ids.SEMANTIC_EVIDENCE_GRAPH_SCHEMA_ID], - "supportsJson": True, - "supportsCompact": True, - }, - { - "method": "evidence/analyze", - "command": "evidence", - "input": "provider project root", - "outputSchemaIds": [ids.SEMANTIC_GRAPH_TURBO_REQUEST_SCHEMA_ID], - "packetSchemas": ["semantic-graph-turbo-request.v1"], - "clients": ["asp-graph-turbo"], - "supportsJson": True, - "supportsCompact": True, - }, - ] - ) - descriptors.extend( - [ - { - "method": "agent/doctor", - "command": "agent", - "outputSchemaIds": [ - "agent.semantic-protocols.semantic-provider-doctor" - ], - "supportsJson": True, - "supportsCompact": True, - }, - { - "method": "agent/guide", - "command": "agent", - "supportsJson": False, - "supportsCompact": True, - }, - ] - ) - return attach_semantic_language_invocations(descriptors) - - -def _python_search_method_descriptor(descriptor: dict[str, Any]) -> dict[str, Any]: - rendered = { - **descriptor, - "benchmarkInvocation": python_search_benchmark_invocation( - str(descriptor["view"]) - ), - "outputSchemaIds": _search_output_schema_ids(descriptor["view"]), - "supportsJson": True, - "supportsCompact": True, - } - if descriptor["view"] == "semantic-facts": - rendered["supportsCompact"] = False - rendered["outputModes"] = ["json"] - rendered["packetSchemas"] = [ - "semantic-fact-graph.v1", - "semantic-fact-ontology.v1", - ] - rendered["input"] = "search semantic-facts " - return rendered - - -def _search_output_schema_ids(view: str) -> list[str]: - if view == "semantic-facts": - return [ids.SEMANTIC_FACT_GRAPH_SCHEMA_ID] - schema_ids = [ids.SEMANTIC_SEARCH_PACKET_SCHEMA_ID] - if view == "public-external-types": - schema_ids.append(ids.SEMANTIC_TYPE_SURFACE_SCHEMA_ID) - if view == "policy": - schema_ids.append("agent.semantic-protocols.semantic-handle") - return schema_ids - - -def python_semantic_search_view_descriptor(view: str) -> dict[str, Any] | None: - if view == "dependency-topology": - return { - "method": "search/dependency-topology", - "command": "search", - "view": "dependency-topology", - "requiresQuery": False, - "acceptsStdin": False, - "supportsPackageScope": True, - "capabilities": [ - { - "languageId": "python", - "namespace": "semantic", - "name": "dependency-topology", - } - ], - } - """Return the registry descriptor for one search view.""" - - return next( - ( - descriptor - for descriptor in _PYTHON_SEARCH_VIEW_DESCRIPTORS - if descriptor["view"] == view - ), - None, - ) - - -def is_python_semantic_search_view(view: str) -> bool: - """Return whether a view is implemented by the Python provider.""" - - return python_semantic_search_view_descriptor(view) is not None diff --git a/src/python_lang_project_harness/_semantic_language_benchmark.py b/src/python_lang_project_harness/_semantic_language_benchmark.py deleted file mode 100644 index 2542f94..0000000 --- a/src/python_lang_project_harness/_semantic_language_benchmark.py +++ /dev/null @@ -1,104 +0,0 @@ -"""Provider-owned large-library benchmark invocation templates.""" - -from __future__ import annotations - -from collections.abc import Callable -from typing import Any - -_QUERY_VIEWS = { - "api", - "public-external-types", - "policy", - "symbol", - "callsite", - "import", - "pattern", - "compare", -} - - -def python_search_benchmark_invocation(view: str) -> dict[str, Any]: - """Return the parser-valid public search command for one registry view.""" - builder = _SPECIAL_VIEW_BUILDERS.get(view) - if builder is not None: - return builder() - if view in {"dependency", "deps"}: - return _seed_invocation(view, "{dependency}") - if view in _QUERY_VIEWS: - return _seed_invocation(view, "{query}") - return _seed_invocation(view) - - -def _owner_invocation() -> dict[str, Any]: - return _seed_invocation("owner", "{owner}", "items", "--query", "{query}") - - -def _lexical_invocation() -> dict[str, Any]: - return _seed_invocation("lexical", "--query", "{query}", "--query", "{dependency}") - - -def _tests_invocation() -> dict[str, Any]: - return _seed_invocation("tests", "{owner}") - - -def _reasoning_invocation() -> dict[str, Any]: - return _seed_invocation( - "reasoning", - "query-deps", - "--query", - "{query}", - "--dependency", - "{dependency}", - ) - - -def _ingest_invocation() -> dict[str, Any]: - return { - **_seed_invocation("ingest"), - "stdinTemplate": "{owner}:1:{query}\\n", - } - - -def _semantic_facts_invocation() -> dict[str, Any]: - return { - "args": ["search", "semantic-facts", "{query}", *_workspace(), "--json"], - "expectsJson": True, - "maxElapsedMs": 15_000, - } - - -def _extension_invocation() -> dict[str, Any]: - return _seed_invocation("extension", "{dependency}") - - -def _public_external_types_invocation() -> dict[str, Any]: - return _seed_invocation("public-external-types", "{dependency}") - - -def _policy_invocation() -> dict[str, Any]: - return _seed_invocation("policy", "PY-AGENT-POLICY-001") - - -def _seed_invocation(view: str, *args: str) -> dict[str, Any]: - return { - "args": ["search", view, *args, *_workspace(), "--view", "seeds"], - "expectsJson": False, - "maxElapsedMs": 15_000, - } - - -def _workspace() -> list[str]: - return ["--workspace", "{workspace}"] - - -_SPECIAL_VIEW_BUILDERS: dict[str, Callable[[], dict[str, Any]]] = { - "owner": _owner_invocation, - "lexical": _lexical_invocation, - "tests": _tests_invocation, - "reasoning": _reasoning_invocation, - "ingest": _ingest_invocation, - "semantic-facts": _semantic_facts_invocation, - "extension": _extension_invocation, - "public-external-types": _public_external_types_invocation, - "policy": _policy_invocation, -} diff --git a/src/python_lang_project_harness/_semantic_language_catalog.py b/src/python_lang_project_harness/_semantic_language_catalog.py deleted file mode 100644 index ac307c6..0000000 --- a/src/python_lang_project_harness/_semantic_language_catalog.py +++ /dev/null @@ -1,225 +0,0 @@ -"""Search method descriptors for the Python semantic-language provider.""" - -from __future__ import annotations - -from collections.abc import Sequence -from typing import Any - -_PYTHON_LANGUAGE_ID = "python" - - -def python_search_view_descriptors() -> list[dict[str, Any]]: - """Return implemented Python search view descriptors.""" - - descriptors = [ - _view( - "workspace", - capabilities=[ - _semantic("workspace-router"), - _python("python-package-root-search"), - ], - ), - _view( - "prime", - capabilities=[ - _semantic("package-prime-map"), - _python("python-reasoning-tree-prime"), - _python("python-entry-point-search"), - ], - ), - _view( - "owner", - requires_query=True, - accepted_pipes=["items"], - capabilities=[ - _semantic("reasoning-owner-search"), - _python("parser-visible-module-owner-search"), - _python("python-owner-item-query"), - _python("pytest-test-owner-search"), - _semantic("path-owner-fallback"), - ], - fallbacks=[ - { - "name": "owner-top-items", - "trigger": "item-query-miss", - "appliesToPipes": ["items"], - "maxItems": 4, - } - ], - ingest_required_for=[_ingest("non-parser-path")], - ), - _view( - "dependency", - requires_query=True, - capabilities=[ - _semantic("dependency-manifest-search"), - _python("dependency-local-usage-search"), - ], - ), - _view( - "deps", - requires_query=True, - capabilities=[ - _semantic("dependency-manifest-search"), - _python("dependency-local-usage-search"), - _semantic("dependency-version-scope"), - _python("dependency-api-token-usage-search"), - ], - ), - _view( - "api", - requires_query=True, - capabilities=[ - _python("exported-api-shape-search"), - _python("public-function-api-shape-search"), - _python("public-data-api-shape-search"), - _semantic("dependency-version-scope"), - ], - ingest_required_for=[_ingest("external-api-docs")], - ), - _view( - "public-external-types", - requires_query=True, - capabilities=[ - _semantic("dependency-manifest-search"), - _python("public-external-type-search"), - _python("public-api-type-text-search"), - ], - ), - _view( - "policy", - requires_query=True, - accepted_pipes=["owner", "tests"], - capabilities=[ - _semantic("policy-rule-handle-search"), - _python("python-project-policy-rule-handle-search"), - _python("python-agent-policy-rule-handle-search"), - ], - ), - _view( - "symbol", - requires_query=True, - capabilities=[_python("symbol-definition-search")], - ), - _view( - "callsite", - requires_query=True, - capabilities=[_python("owner-callsite-search")], - ), - _view( - "import", - requires_query=True, - capabilities=[_python("import-edge-search")], - ), - _view( - "tests", - requires_query=True, - capabilities=[_python("pytest-test-owner-search")], - ), - _view( - "lexical", - requires_query=True, - accepted_pipes=["owner", "tests"], - supports_query_set=True, - accepted_query_set_selectors=["lexical-set"], - query_set_scopes=["project", "owner"], - capabilities=[ - _semantic("lexical-candidate-search"), - _python("parser-visible-source-lexical-search"), - ], - ingest_required_for=[ - _ingest("non-parser-text"), - _ingest("docs-text"), - _ingest("schema-json"), - _ingest("generated-artifact"), - ], - ), - _view( - "reasoning", - requires_query=True, - capabilities=[ - _semantic("reasoning-owner-search"), - _semantic("dependency-manifest-search"), - _python("python-owner-item-query"), - _python("pytest-test-owner-search"), - _python("dependency-local-usage-search"), - ], - ), - _view( - "ingest", - accepts_stdin=True, - accepted_pipes=["items", "tests"], - capabilities=[ - _semantic("external-candidate-ingest"), - _semantic("stdin-shape-detection"), - _semantic("owner-grouped-ingest"), - ], - ), - _view( - "semantic-facts", - requires_query=True, - accepts_stdin=True, - capabilities=[ - _semantic("graph-turbo-provider-facts"), - _python("python-ast-field-type-collection-facts"), - ], - ), - ] - from ._semantic_language_knowledge import python_knowledge_search_view_descriptors - - return ( - descriptors[:-2] + python_knowledge_search_view_descriptors() + descriptors[-2:] - ) - - -def _view( - view: str, - *, - capabilities: Sequence[dict[str, str]], - requires_query: bool = False, - accepts_stdin: bool = False, - accepted_pipes: Sequence[str] = (), - supports_query_set: bool = False, - accepted_query_set_selectors: Sequence[str] = (), - query_set_scopes: Sequence[str] = (), - fallbacks: Sequence[dict[str, object]] = (), - ingest_required_for: Sequence[dict[str, str]] = (), -) -> dict[str, Any]: - descriptor: dict[str, Any] = { - "method": f"search/{view}", - "command": "search", - "view": view, - "requiresQuery": requires_query, - "acceptsStdin": accepts_stdin, - "supportsPackageScope": True, - "capabilities": list(capabilities), - } - if accepted_pipes: - descriptor["acceptedPipes"] = list(accepted_pipes) - if supports_query_set: - descriptor["supportsQuerySet"] = True - if accepted_query_set_selectors: - descriptor["acceptedQuerySetSelectors"] = list(accepted_query_set_selectors) - if query_set_scopes: - descriptor["querySetScopes"] = list(query_set_scopes) - if fallbacks: - descriptor["fallbacks"] = list(fallbacks) - if ingest_required_for: - descriptor["ingestRequiredFor"] = list(ingest_required_for) - return descriptor - - -def _semantic(name: str) -> dict[str, str]: - return _capability("semantic", name) - - -def _python(name: str) -> dict[str, str]: - return _capability(_PYTHON_LANGUAGE_ID, name) - - -def _ingest(name: str) -> dict[str, str]: - return _capability(_PYTHON_LANGUAGE_ID, name) - - -def _capability(namespace: str, name: str) -> dict[str, str]: - return {"languageId": _PYTHON_LANGUAGE_ID, "namespace": namespace, "name": name} diff --git a/src/python_lang_project_harness/_semantic_language_knowledge.py b/src/python_lang_project_harness/_semantic_language_knowledge.py deleted file mode 100644 index e753fc8..0000000 --- a/src/python_lang_project_harness/_semantic_language_knowledge.py +++ /dev/null @@ -1,103 +0,0 @@ -"""Knowledge-axis search descriptors for the Python semantic-language provider.""" - -from __future__ import annotations - -from collections.abc import Sequence -from typing import Any - -_PYTHON_LANGUAGE_ID = "python" - - -def python_knowledge_search_view_descriptors() -> list[dict[str, Any]]: - """Return provider-owned language and ecosystem knowledge search views.""" - - return [ - _view( - "env", - capabilities=[ - _semantic("provider-knowledge-axis"), - _python("python-project-environment-facts"), - ], - ), - _view( - "runtime-source", - capabilities=[ - _semantic("provider-knowledge-axis"), - _python("python-runtime-source-frontier"), - ], - ), - _view( - "lang", - capabilities=[ - _semantic("provider-knowledge-axis"), - _python("python-language-semantics-facts"), - ], - ), - _view( - "std", - capabilities=[ - _semantic("provider-knowledge-axis"), - _python("python-standard-api-facts"), - ], - ), - _view( - "capability", - capabilities=[ - _semantic("provider-knowledge-axis"), - _python("python-provider-capability-facts"), - ], - ), - _view( - "extension", - requires_query=True, - capabilities=[ - _semantic("provider-knowledge-axis"), - _python("python-ecosystem-extension-facts"), - ], - ), - _view( - "pattern", - requires_query=True, - capabilities=[ - _semantic("provider-knowledge-axis"), - _python("python-executable-pattern-facts"), - ], - ), - _view( - "compare", - requires_query=True, - capabilities=[ - _semantic("provider-knowledge-axis"), - _python("python-semantic-comparison-facts"), - ], - ), - ] - - -def _view( - view: str, - *, - capabilities: Sequence[dict[str, str]], - requires_query: bool = False, -) -> dict[str, Any]: - return { - "method": f"search/{view}", - "command": "search", - "view": view, - "requiresQuery": requires_query, - "acceptsStdin": False, - "supportsPackageScope": True, - "capabilities": list(capabilities), - } - - -def _semantic(name: str) -> dict[str, str]: - return _capability("semantic", name) - - -def _python(name: str) -> dict[str, str]: - return _capability(_PYTHON_LANGUAGE_ID, name) - - -def _capability(namespace: str, name: str) -> dict[str, str]: - return {"languageId": _PYTHON_LANGUAGE_ID, "namespace": namespace, "name": name} diff --git a/src/python_lang_project_harness/_semantic_language_schemas.py b/src/python_lang_project_harness/_semantic_language_schemas.py deleted file mode 100644 index e8890aa..0000000 --- a/src/python_lang_project_harness/_semantic_language_schemas.py +++ /dev/null @@ -1,132 +0,0 @@ -"""Schema registrations advertised by the Python semantic-language provider.""" - -from __future__ import annotations - -from . import _semantic_language_ids as ids - - -def python_semantic_language_schemas() -> list[dict[str, str]]: - """Return package-local schema registrations for agent doctor.""" - - return [ - { - "schemaId": ids.SEMANTIC_SEARCH_PACKET_SCHEMA_ID, - "schemaVersion": "1", - "path": "schemas/semantic-search-packet.v1.schema.json", - }, - { - "schemaId": ids.SEMANTIC_QUERY_PACKET_SCHEMA_ID, - "schemaVersion": "1", - "path": "schemas/semantic-query-packet.v1.schema.json", - }, - { - "schemaId": ids.SEMANTIC_SOURCE_LOCATION_SCHEMA_ID, - "schemaVersion": "1", - "path": "schemas/semantic-source-location.v1.schema.json", - }, - { - "schemaId": ids.SEMANTIC_TREE_SITTER_PROVENANCE_SCHEMA_ID, - "schemaVersion": "1", - "path": "schemas/semantic-tree-sitter-provenance.v1.schema.json", - }, - { - "schemaId": ids.SEMANTIC_GRAPH_SCHEMA_ID, - "schemaVersion": "1", - "path": "schemas/semantic-graph.v1.schema.json", - }, - { - "schemaId": ids.SEMANTIC_GRAPH_TURBO_REQUEST_SCHEMA_ID, - "schemaVersion": "1", - "path": "schemas/semantic-graph-turbo-request.v1.schema.json", - }, - { - "schemaId": "agent.semantic-protocols.semantic-verification-receipt", - "schemaVersion": "1", - "path": "schemas/semantic-verification-receipt.v1.schema.json", - }, - { - "schemaId": "agent.semantic-protocols.semantic-behavior-snapshot", - "schemaVersion": "1", - "path": "schemas/semantic-behavior-snapshot.v1.schema.json", - }, - { - "schemaId": ids.SEMANTIC_DETERMINISM_READINESS_SCHEMA_ID, - "schemaVersion": "1", - "path": "schemas/semantic-determinism-readiness.v1.schema.json", - }, - { - "schemaId": ids.SEMANTIC_DEV_COMMAND_LOG_SCHEMA_ID, - "schemaVersion": "1", - "path": "schemas/semantic-dev-command-log.v1.schema.json", - }, - { - "schemaId": ids.SEMANTIC_FORMAL_PROOF_PILOT_SCHEMA_ID, - "schemaVersion": "1", - "path": "schemas/semantic-formal-proof-pilot.v1.schema.json", - }, - { - "schemaId": ids.SEMANTIC_REVIEW_PACKET_SCHEMA_ID, - "schemaVersion": "1", - "path": "schemas/semantic-review-packet.v1.schema.json", - }, - { - "schemaId": ids.SEMANTIC_EVIDENCE_GRAPH_SCHEMA_ID, - "schemaVersion": "1", - "path": "schemas/semantic-evidence-graph.v1.schema.json", - }, - { - "schemaId": ids.SEMANTIC_ASSURANCE_CASE_SCHEMA_ID, - "schemaVersion": "1", - "path": "schemas/semantic-assurance-case.v1.schema.json", - }, - { - "schemaId": ids.SEMANTIC_AST_PATCH_SCHEMA_ID, - "schemaVersion": "1", - "path": "schemas/semantic-ast-patch.v1.schema.json", - }, - { - "schemaId": ids.SEMANTIC_AST_PATCH_RECEIPT_SCHEMA_ID, - "schemaVersion": "1", - "path": "schemas/semantic-ast-patch-receipt.v1.schema.json", - }, - { - "schemaId": ids.SEMANTIC_TREE_SITTER_QUERY_SCHEMA_ID, - "schemaVersion": "1", - "path": "schemas/semantic-tree-sitter-query.v1.schema.json", - }, - { - "schemaId": ids.SEMANTIC_TREE_SITTER_GRAMMAR_PROFILE_SCHEMA_ID, - "schemaVersion": "1", - "path": "schemas/semantic-tree-sitter-grammar-profile.v1.schema.json", - }, - { - "schemaId": ids.SEMANTIC_TYPE_SURFACE_SCHEMA_ID, - "schemaVersion": "1", - "path": "schemas/semantic-type-surface.v1.schema.json", - }, - { - "schemaId": ids.SEMANTIC_FACT_GRAPH_SCHEMA_ID, - "schemaVersion": "1", - "path": "schemas/semantic-fact-graph.v1.schema.json", - }, - { - "schemaId": ids.SEMANTIC_FACT_ONTOLOGY_SCHEMA_ID, - "schemaVersion": "1", - "path": "schemas/semantic-fact-ontology.v1.schema.json", - }, - { - "schemaId": "agent.semantic-protocols.semantic-handle", - "schemaVersion": "1", - "path": "schemas/semantic-handle.v1.schema.json", - }, - { - "schemaId": ids.SEMANTIC_LANGUAGE_REGISTRY_ID, - "schemaVersion": ids.SEMANTIC_LANGUAGE_REGISTRY_VERSION, - "path": "schemas/semantic-language-registry.v1.schema.json", - }, - { - "schemaId": ids.PYTHON_CAPABILITIES_SCHEMA_ID, - "schemaVersion": "1", - "path": "schemas/python-semantic-capabilities.v1.schema.json", - }, - ] diff --git a/src/python_lang_project_harness/_semantic_search.py b/src/python_lang_project_harness/_semantic_search.py deleted file mode 100644 index e35dd8e..0000000 --- a/src/python_lang_project_harness/_semantic_search.py +++ /dev/null @@ -1,17 +0,0 @@ -"""Public semantic-search facade for the Python provider.""" - -from __future__ import annotations - -from ._semantic_search_model import PythonSemanticSearchOptions -from ._semantic_search_packet import build_python_semantic_search_packet -from ._semantic_search_render import ( - render_python_semantic_search_packet, - render_python_semantic_search_packet_json, -) - -__all__ = [ - "PythonSemanticSearchOptions", - "build_python_semantic_search_packet", - "render_python_semantic_search_packet", - "render_python_semantic_search_packet_json", -] diff --git a/src/python_lang_project_harness/_semantic_search_callsite_hits.py b/src/python_lang_project_harness/_semantic_search_callsite_hits.py deleted file mode 100644 index 82158b2..0000000 --- a/src/python_lang_project_harness/_semantic_search_callsite_hits.py +++ /dev/null @@ -1,71 +0,0 @@ -"""Callsite hit builders for Python semantic search.""" - -from __future__ import annotations - -from pathlib import Path -from typing import TYPE_CHECKING, Any - -from ._semantic_search_common import dedupe_hits, location_from_source -from ._semantic_search_deps import module_owner_path - -if TYPE_CHECKING: - from python_lang_parser import PythonCall - - from ._model import PythonHarnessReport - - -def callsite_hits( - report: PythonHarnessReport, - project_root: Path, - query: str, -) -> list[dict[str, Any]]: - """Return parser-owned Python callsite hits.""" - - query_folded = query.casefold() - hits = [ - callsite_hit(call, module_owner_path(module, project_root), project_root) - for module in report.modules - for call in module.calls - if _call_matches(call, query_folded) - ] - return dedupe_hits(hits) - - -def callsite_hit( - call: PythonCall, - owner_path: str, - project_root: Path, -) -> dict[str, Any]: - """Return one callsite hit.""" - - fields: dict[str, Any] = { - "scope": call.scope or "module", - "effect": call.effect.value, - "positional": call.positional_count, - } - if call.keyword_names: - fields["keywords"] = list(call.keyword_names) - if call.expression: - fields["expr"] = call.expression - return { - "kind": "callsite", - "ownerPath": owner_path, - "location": location_from_source(call.location, project_root), - "score": _callsite_score(call), - "reason": "call-expression", - "symbol": call.function, - "fields": fields, - } - - -def _call_matches(call: PythonCall, query_folded: str) -> bool: - if not query_folded: - return False - haystacks = (call.function, call.function.rsplit(".", 1)[-1]) - return any(query_folded in item.casefold() for item in haystacks if item) - - -def _callsite_score(call: PythonCall) -> int: - if "." not in call.function: - return 4 - return 3 diff --git a/src/python_lang_project_harness/_semantic_search_cli.py b/src/python_lang_project_harness/_semantic_search_cli.py deleted file mode 100644 index 5848704..0000000 --- a/src/python_lang_project_harness/_semantic_search_cli.py +++ /dev/null @@ -1,432 +0,0 @@ -"""CLI parsing helpers for Python semantic-search commands.""" - -from __future__ import annotations - -from dataclasses import dataclass, field -from pathlib import Path - -from ._semantic_language import python_semantic_search_view_descriptor - - -@dataclass(slots=True) -class ParsedSemanticSearchArgs: - view: str | None = None - query: str | None = None - item_query: str | None = None - project_root: Path | None = None - package_path: Path | None = None - workspace: bool = False - owner_path: str | None = None - dependency: str | None = None - query_set: tuple[str, ...] = () - pipes: tuple[str, ...] = () - json: bool = False - render_mode: str | None = None - error: str | None = None - - -@dataclass(slots=True) -class _SearchOptionState: - positionals: list[str] = field(default_factory=list) - query_set: list[str] = field(default_factory=list) - item_query: str | None = None - json: bool = False - render_mode: str | None = None - package_path: Path | None = None - workspace: bool = False - workspace_root: Path | None = None - owner_path: str | None = None - dependency: str | None = None - - -@dataclass(slots=True) -class _ConsumedOption: - advance: int = 1 - error: str | None = None - - -def parse_semantic_search_args( - args: list[str] | tuple[str, ...], -) -> ParsedSemanticSearchArgs: - view, descriptor, error = _search_view_descriptor(args) - if error is not None or view is None or descriptor is None: - return ParsedSemanticSearchArgs(error=error) - state, error = _parse_search_option_state(view, args[1:]) - if error is not None: - return ParsedSemanticSearchArgs(error=error) - error = _validate_search_option_state(view, state) - if error is not None: - return ParsedSemanticSearchArgs(error=error) - return _search_args_for_descriptor(view, descriptor, state) - - -def _search_view_descriptor( - args: list[str] | tuple[str, ...], -) -> tuple[str | None, dict[str, object] | None, str | None]: - view = args[0] if args else None - if view is None or view in {"--help", "-h"}: - return None, None, _semantic_search_usage() - descriptor = python_semantic_search_view_descriptor(view) - if descriptor is None: - return view, None, f"unknown search view: {view}" - return view, descriptor, None - - -def _semantic_search_usage() -> str: - return ( - "usage: asp-python search " - " " - "... [--json] [--package PATH] [--workspace ]; " - "dependency/deps are manifest-first, import-usage backed, and cache hashes not raw source" - ) - - -def _validate_search_option_state( - view: str, - state: _SearchOptionState, -) -> str | None: - if state.query_set and not _search_view_supports_query_bundle(view): - return f"search {view} does not support repeated --query" - if state.item_query is not None and view not in {"owner", "reasoning"}: - return "--query is only supported by search owner items" - if state.owner_path is not None and view not in {"lexical", "reasoning"}: - return "--owner is only supported by search lexical or reasoning" - if state.dependency is not None and view != "reasoning": - return "--dependency is only supported by search reasoning" - return None - - -def _search_args_for_descriptor( - view: str, - descriptor: dict[str, object], - state: _SearchOptionState, -) -> ParsedSemanticSearchArgs: - accepted_pipes = descriptor.get("acceptedPipes", ()) - normalized_pipes = ( - accepted_pipes if isinstance(accepted_pipes, tuple | list) else () - ) - if view == "lexical": - return _optional_query_args(view, state, normalized_pipes) - if descriptor["requiresQuery"]: - return _required_query_args(view, descriptor, state) - return _project_only_args(view, descriptor, state) - - -def _parse_search_option_state( - view: str, - args: list[str] | tuple[str, ...], -) -> tuple[_SearchOptionState, str | None]: - state = _SearchOptionState() - index = 0 - while index < len(args): - consumed = _consume_search_option(view, args, index, state) - if consumed.error is not None: - return state, consumed.error - index += consumed.advance - return state, None - - -def _consume_search_option( - view: str, - args: list[str] | tuple[str, ...], - index: int, - state: _SearchOptionState, -) -> _ConsumedOption: - arg = args[index] - if _is_flag_like_literal_search_query( - view, state.positionals, state.query_set, arg - ): - state.positionals.append(arg) - return _ConsumedOption() - - match arg: - case "--json": - state.json = True - case "--view": - value = _optional_arg(args, index + 1) - if value not in {"graph", "hits", "both", "seeds"}: - return _ConsumedOption( - error="--view requires graph, hits, both, or seeds", - ) - state.render_mode = value - return _ConsumedOption(advance=2) - case "--package": - value = _optional_arg(args, index + 1) - if value is None: - return _ConsumedOption(error="--package requires a package path") - state.package_path = Path(value) - return _ConsumedOption(advance=2) - case "--workspace": - value = _optional_arg(args, index + 1) - if value is None: - return _ConsumedOption(error="--workspace requires a workspace root") - state.workspace = True - state.workspace_root = Path(value) - return _ConsumedOption(advance=2) - case "--owner": - value = _literal_arg(args, index + 1) - if value is None: - return _ConsumedOption(error="--owner requires an owner path") - state.owner_path = value - return _ConsumedOption(advance=2) - case "--dependency": - value = _literal_arg(args, index + 1) - if value is None: - return _ConsumedOption(error="--dependency requires a dependency") - state.dependency = value - return _ConsumedOption(advance=2) - case "--query": - value = _literal_arg(args, index + 1) - if value is None: - return _ConsumedOption(error="--query requires a query term") - if view == "lexical": - state.query_set.append(value) - return _ConsumedOption(advance=2) - state.item_query = value - return _ConsumedOption(advance=2) - case _ if arg.startswith("-"): - return _ConsumedOption(error=f"unknown search option: {arg}") - case _: - state.positionals.append(arg) - return _ConsumedOption() - - -def _required_query_args( - view: str, - descriptor: dict[str, object], - state: _SearchOptionState, -) -> ParsedSemanticSearchArgs: - if _search_view_accepts_optional_terms(view): - return _required_term_query_args(view, state) - - query = ( - ",".join(state.query_set) - if state.query_set - else (state.positionals[0] if state.positionals else None) - ) - if query is None: - return ParsedSemanticSearchArgs(error=f"search {view} requires a query") - - accepted_pipes = descriptor.get("acceptedPipes", ()) - pipes, project_root, error = _parse_search_pipe_positionals( - state.positionals if state.query_set else state.positionals[1:], - accepted_pipes if isinstance(accepted_pipes, tuple | list) else (), - ) - if error is not None: - return ParsedSemanticSearchArgs(error=error) - if project_root is not None: - return ParsedSemanticSearchArgs( - error="search does not accept positional WORKSPACE; use --workspace ", - ) - project_root = ( - str(state.workspace_root) if state.workspace_root is not None else None - ) - return ParsedSemanticSearchArgs( - view=view, - query=query, - item_query=state.item_query, - owner_path=state.owner_path, - dependency=state.dependency, - query_set=tuple(state.query_set), - project_root=None if project_root is None else Path(project_root), - package_path=state.package_path, - workspace=state.workspace, - pipes=tuple(pipes), - json=state.json, - render_mode=state.render_mode, - ) - - -def _required_term_query_args( - view: str, - state: _SearchOptionState, -) -> ParsedSemanticSearchArgs: - query = " ".join(state.positionals) - if not query: - return ParsedSemanticSearchArgs(error=f"search {view} requires a query") - project_root = ( - str(state.workspace_root) if state.workspace_root is not None else None - ) - return ParsedSemanticSearchArgs( - view=view, - query=query, - item_query=state.item_query, - owner_path=state.owner_path, - dependency=state.dependency, - query_set=tuple(state.query_set), - project_root=None if project_root is None else Path(project_root), - package_path=state.package_path, - workspace=state.workspace, - json=state.json, - render_mode=state.render_mode, - ) - - -def _project_only_args( - view: str, - descriptor: dict[str, object], - state: _SearchOptionState, -) -> ParsedSemanticSearchArgs: - accepted_pipes = descriptor.get("acceptedPipes", ()) - normalized_pipes = ( - accepted_pipes if isinstance(accepted_pipes, tuple | list) else () - ) - if _search_view_accepts_optional_terms(view): - return _optional_query_args( - view, - state, - normalized_pipes, - ) - pipes, project_root, error = _parse_search_pipe_positionals( - state.positionals, - normalized_pipes, - ) - if error is not None: - return ParsedSemanticSearchArgs(error=error) - if project_root is not None: - return ParsedSemanticSearchArgs( - error="search does not accept positional WORKSPACE; use --workspace ", - ) - project_root = ( - str(state.workspace_root) if state.workspace_root is not None else None - ) - return ParsedSemanticSearchArgs( - view=view, - project_root=None if project_root is None else Path(project_root), - package_path=state.package_path, - workspace=state.workspace, - pipes=tuple(pipes), - json=state.json, - render_mode=state.render_mode, - ) - - -def _optional_query_args( - view: str, - state: _SearchOptionState, - accepted_pipes: list[str] | tuple[str, ...], -) -> ParsedSemanticSearchArgs: - if view == "lexical": - if state.query_set: - query = ",".join(state.query_set) - positionals = state.positionals - elif state.positionals: - query = state.positionals[0] - positionals = state.positionals[1:] - else: - query = None - positionals = [] - pipes, project_root, error = _parse_search_pipe_positionals( - positionals, - accepted_pipes, - ) - if error is not None: - return ParsedSemanticSearchArgs(error=error) - if project_root is not None: - return ParsedSemanticSearchArgs( - error="search does not accept positional WORKSPACE; use --workspace ", - ) - project_root = ( - str(state.workspace_root) if state.workspace_root is not None else None - ) - return ParsedSemanticSearchArgs( - view=view, - query=query, - owner_path=state.owner_path, - project_root=None if project_root is None else Path(project_root), - package_path=state.package_path, - workspace=state.workspace, - query_set=tuple(state.query_set), - pipes=tuple(pipes), - json=state.json, - render_mode=state.render_mode, - ) - project_root = ( - str(state.workspace_root) if state.workspace_root is not None else None - ) - return ParsedSemanticSearchArgs( - view=view, - query=" ".join(state.positionals) if state.positionals else None, - project_root=None if project_root is None else Path(project_root), - package_path=state.package_path, - workspace=state.workspace, - json=state.json, - render_mode=state.render_mode, - ) - - -def _parse_search_pipe_positionals( - positionals: list[str], - accepted_pipes: list[str] | tuple[str, ...], -) -> tuple[list[str], str | None, str | None]: - pipes: list[str] = [] - index = 0 - while index < len(positionals) and index < len(accepted_pipes): - if positionals[index] != accepted_pipes[index]: - break - pipes.append(positionals[index]) - index += 1 - remaining = positionals[index:] - if len(remaining) > 1: - looks_like_out_of_order_pipe = ( - index < len(accepted_pipes) and remaining[0] in accepted_pipes - ) - if not accepted_pipes or not looks_like_out_of_order_pipe: - return ( - pipes, - remaining[0], - "search does not accept positional WORKSPACE; use --workspace ", - ) - return ( - pipes, - remaining[0], - f"expected pipes ({','.join(accepted_pipes)}) before workspace selector", - ) - return pipes, (remaining[0] if remaining else None), None - - -def _optional_arg(args: list[str] | tuple[str, ...], index: int) -> str | None: - if index >= len(args): - return None - value = args[index] - if value.startswith("-"): - return None - return value - - -def _literal_arg(args: list[str] | tuple[str, ...], index: int) -> str | None: - return None if index >= len(args) else args[index] - - -def _is_flag_like_literal_search_query( - view: str, - positionals: list[str], - query_set: list[str], - arg: str, -) -> bool: - return ( - view == "lexical" - and not positionals - and not query_set - and arg.startswith("-") - and not arg.startswith("--") - and arg != "-h" - ) - - -def _search_view_supports_query_bundle(view: str) -> bool: - return view == "lexical" - - -def _search_view_accepts_optional_terms(view: str) -> bool: - return view in { - "env", - "runtime-source", - "lang", - "std", - "capability", - "extension", - "pattern", - "compare", - "lexical", - } diff --git a/src/python_lang_project_harness/_semantic_search_common.py b/src/python_lang_project_harness/_semantic_search_common.py deleted file mode 100644 index 0145d07..0000000 --- a/src/python_lang_project_harness/_semantic_search_common.py +++ /dev/null @@ -1,170 +0,0 @@ -"""Small helpers shared by Python semantic-search modules.""" - -from __future__ import annotations - -import json -import re -from pathlib import Path -from typing import TYPE_CHECKING, Any - -from ._render import _render_display_path -from ._semantic_search_model import Fields, FieldValue - -if TYPE_CHECKING: - from collections.abc import Iterable - - from python_lang_parser import SourceLocation - - -def semantic_search_display_path(path: str | Path, project_root: Path) -> str: - """Return an agent-facing path relative to the project root when possible.""" - - return _render_display_path(path, project_root=project_root) - - -def location( - path: str, - line: int | None = None, - column: int | None = None, -) -> dict[str, Any]: - """Return a schema-compatible semantic-search location.""" - - payload: dict[str, Any] = {"path": path} - if line is not None and line > 0: - payload["line"] = line - if column is not None: - payload["column"] = max(1, column) - return payload - - -def location_from_source( - source_location: SourceLocation, - project_root: Path, -) -> dict[str, Any]: - """Return a packet location from a parser source location.""" - - return location( - semantic_search_display_path(source_location.path or ".", project_root), - source_location.line, - source_location.column, - ) - - -def header(view: str, fields: Fields) -> dict[str, Any]: - """Return a semantic-search packet header.""" - - return {"kind": f"search-{view}", "fields": compact_fields(fields)} - - -def compact_fields(fields: Fields) -> Fields: - """Drop empty fields from compact and JSON payloads.""" - - return { - key: value - for key, value in fields.items() - if value is not None and value != [] and value != "" - } - - -def dedupe(values: Iterable[str]) -> list[str]: - """Return values in first-seen order.""" - - seen: set[str] = set() - result: list[str] = [] - for value in values: - if value in seen: - continue - seen.add(value) - result.append(value) - return result - - -def path_hit( - owner_path: str, - path: str, - *, - kind: str = "path", - symbol: str | None = None, - score: int = 2, - reason: str = "path", - fields: Fields | None = None, -) -> dict[str, Any]: - """Return a simple path-like search hit.""" - - hit: dict[str, Any] = { - "kind": kind, - "ownerPath": owner_path, - "location": {"path": path}, - "score": score, - "reason": reason, - } - if symbol is not None: - hit["symbol"] = symbol - if fields: - hit["fields"] = fields - return hit - - -def dedupe_hits(hits: Iterable[dict[str, Any]]) -> list[dict[str, Any]]: - """Return deterministic unique hits.""" - - seen: set[tuple[str, str, str, str]] = set() - result: list[dict[str, Any]] = [] - for hit in hits: - key = ( - hit["kind"], - hit["ownerPath"], - hit.get("symbol", ""), - json.dumps(hit["location"], sort_keys=True), - ) - if key in seen: - continue - seen.add(key) - result.append(hit) - return sorted( - result, key=lambda hit: (-hit["score"], hit["ownerPath"], hit["kind"]) - ) - - -def render_fields(fields: Fields) -> str: - """Render compact `key=value` fields.""" - - return " ".join( - f"{key}={escape_field_value(value)}" - for key, value in fields.items() - if value != [] and value != "" - ) - - -def escape_field_value(value: FieldValue) -> str: - """Render one compact field value.""" - - if isinstance(value, list): - return ",".join(escape_scalar(item) for item in value) - return escape_scalar(value) - - -def escape_scalar(value: str | int | float | bool) -> str: - """Render one compact scalar.""" - - text = str(value).lower() if isinstance(value, bool) else str(value) - if re.search(r"[\s,=]", text): - return json.dumps(text) - return text - - -def render_location(search_location: dict[str, Any]) -> str: - """Render a compact location.""" - - fields: Fields = {"path": search_location["path"]} - if "line" in search_location and "column" in search_location: - fields["line"] = search_location["line"] - fields["column"] = search_location["column"] - return render_fields(fields) - if "line" in search_location: - fields["line"] = search_location["line"] - elif "lineRange" in search_location: - line_range = str(search_location["lineRange"]) - start, _, end = line_range.partition(":") - fields["line"] = start if start and (not end or start == end) else line_range - return render_fields(fields) diff --git a/src/python_lang_project_harness/_semantic_search_deps.py b/src/python_lang_project_harness/_semantic_search_deps.py deleted file mode 100644 index ac376df..0000000 --- a/src/python_lang_project_harness/_semantic_search_deps.py +++ /dev/null @@ -1,140 +0,0 @@ -"""Dependency-specific Python semantic-search facts.""" - -from __future__ import annotations - -import re -from pathlib import Path -from typing import TYPE_CHECKING, Any - -from ._semantic_search_common import location_from_source -from ._semantic_search_model import Fields - -if TYPE_CHECKING: - from collections.abc import Sequence - - from python_lang_parser import PythonProjectDependency - - from ._model import PythonHarnessReport - - -def dependency_node( - dependency: PythonProjectDependency, - *, - parts: dict[str, str] | None = None, -) -> dict[str, Any]: - """Return one dependency node for a search packet.""" - - fields: Fields = { - "requirement": dependency.requirement, - "source": dependency.source, - "versionScope": version_scope(parts or {}, [dependency]), - } - if dependency.group: - fields["group"] = dependency.group - if dependency.extra: - fields["extra"] = dependency.extra - if parts is not None and parts.get("requestedVersion"): - fields["requestedVersion"] = parts["requestedVersion"] - if parts is not None and parts.get("apiQuery"): - fields["apiQuery"] = parts["apiQuery"] - return {"id": f"D:{dependency.name}", "kind": "dependency", "fields": fields} - - -def dependency_query_parts(query: str) -> dict[str, str]: - """Parse `` into compact fields.""" - - package_part, _, api_query = query.partition("::") - package_name = package_part - requested_version = "" - if "@" in package_part and not package_part.startswith("@"): - package_name, requested_version = package_part.rsplit("@", 1) - elif "==" in package_part: - package_name, requested_version = package_part.split("==", 1) - return { - "package": normalize_dependency_name(package_name.strip()), - "requestedVersion": requested_version.strip(), - "apiQuery": api_query.strip(), - } - - -def normalize_dependency_name(value: str) -> str: - """Normalize Python dependency names for query matching.""" - - return re.sub(r"[-_.]+", "-", value).casefold() - - -def dependency_matches(dependency: PythonProjectDependency, query: str) -> bool: - """Return whether a metadata dependency matches a normalized query.""" - - return query in { - normalize_dependency_name(dependency.name), - normalize_dependency_name(dependency.requirement.split()[0]), - } - - -def version_scope( - parts: dict[str, str], - matches: Sequence[PythonProjectDependency], -) -> str: - """Return the current/external/unknown version scope label.""" - - if not matches: - return "external" if parts.get("requestedVersion") else "unknown" - requested = parts.get("requestedVersion", "") - if not requested: - return "current" - return ( - "current" - if any(requested in item.requirement for item in matches) - else "external" - ) - - -def dependency_usage_hits( - report: PythonHarnessReport, - project_root: Path, - package_query: str, -) -> list[dict[str, Any]]: - """Return local import usage hits for a dependency name.""" - - hits: list[dict[str, Any]] = [] - for module in report.modules: - owner_path = module_owner_path(module, project_root) - for import_record in module.imports: - root_name = import_root(import_record.module, import_record.names) - if root_name is None: - continue - if normalize_dependency_name(root_name) != package_query: - continue - hits.append( - { - "kind": "dependency", - "ownerPath": owner_path, - "location": location_from_source( - import_record.location, project_root - ), - "score": 3, - "reason": "import-usage", - "symbol": root_name, - "fields": {"scope": import_record.scope or "module"}, - } - ) - return hits - - -def module_owner_path(module, project_root: Path) -> str: - """Return a display path for a parsed module.""" - - from ._semantic_search_common import semantic_search_display_path - - return semantic_search_display_path(module.path or ".", project_root) - - -def import_root(module: str | None, names) -> str | None: - """Return the root package named by a Python import statement.""" - - if module: - return module.split(".", 1)[0] - if names: - return names[0].split(".", 1)[0] - return None diff --git a/src/python_lang_project_harness/_semantic_search_findings.py b/src/python_lang_project_harness/_semantic_search_findings.py deleted file mode 100644 index 050f8cf..0000000 --- a/src/python_lang_project_harness/_semantic_search_findings.py +++ /dev/null @@ -1,47 +0,0 @@ -"""Finding facts for Python semantic-search packets.""" - -from __future__ import annotations - -from collections import Counter -from pathlib import Path -from typing import TYPE_CHECKING, Any - -from ._semantic_search_common import ( - location_from_source, - semantic_search_display_path, -) -from ._semantic_search_model import MAX_FINDINGS - -if TYPE_CHECKING: - from ._model import PythonHarnessFinding, PythonHarnessReport - - -def finding_facts( - report: PythonHarnessReport, - project_root: Path, - *, - owner_paths: set[str] | None = None, -) -> list[dict[str, Any]]: - """Return grouped harness findings.""" - - counter: Counter[tuple[str, str, str, str]] = Counter() - finding_by_key: dict[tuple[str, str, str, str], PythonHarnessFinding] = {} - for finding in report.findings: - path = semantic_search_display_path(finding.location.path or ".", project_root) - if owner_paths is not None and path not in owner_paths: - continue - key = (finding.rule_id, finding.severity.value, finding.title, path) - counter[key] += 1 - finding_by_key[key] = finding - return [ - { - "ruleId": finding_by_key[key].rule_id, - "severity": finding_by_key[key].severity.value, - "count": count, - "title": finding_by_key[key].title, - "location": location_from_source( - finding_by_key[key].location, project_root - ), - } - for key, count in counter.most_common(MAX_FINDINGS) - ] diff --git a/src/python_lang_project_harness/_semantic_search_graph_render.py b/src/python_lang_project_harness/_semantic_search_graph_render.py deleted file mode 100644 index 879e731..0000000 --- a/src/python_lang_project_harness/_semantic_search_graph_render.py +++ /dev/null @@ -1,107 +0,0 @@ -"""Delegate Python compact graph rendering to the shared protocol binary.""" - -from __future__ import annotations - -import json -import os -import subprocess -from collections.abc import Callable -from typing import Any - -from ._semantic_search_common import escape_field_value -from ._semantic_search_render_lines import render_next_action - -DEFAULT_GRAPH_SEED_LIMIT = 8 -SEMANTIC_AGENT_PROTOCOL_BIN_ENV = "SEMANTIC_AGENT_PROTOCOL_BIN" -GRAPH_NATIVE_NEXT_ACTION_KINDS = frozenset({"owner", "tests"}) - - -class CompactGraphRenderError(RuntimeError): - """Raised when the shared compact graph renderer cannot produce output.""" - - -def compact_graph_seed_packet_text( - packet: dict[str, Any], - render_fields: Callable[[dict[str, Any]], str], -) -> str: - del render_fields - rendered = render_compact_graph_packet( - graph_render_packet(packet), - seed_limit=DEFAULT_GRAPH_SEED_LIMIT, - ) - flow_lines = compact_graph_flow_lines(packet) - if not flow_lines: - return rendered - return f"{rendered.rstrip()}\n" + "\n".join(flow_lines) + "\n" - - -def graph_render_packet(packet: dict[str, Any]) -> dict[str, Any]: - """Return the packet projection expected by the graph-only renderer.""" - - next_actions = [ - action - for action in packet.get("nextActions", []) - if action.get("kind") in GRAPH_NATIVE_NEXT_ACTION_KINDS - ] - if len(next_actions) == len(packet.get("nextActions", [])): - return packet - return {**packet, "nextActions": next_actions} - - -def compact_graph_flow_lines(packet: dict[str, Any]) -> list[str]: - """Render non-graph flow hints after compact graph output.""" - - lines = [ - f"|note kind={note['kind']} message={escape_field_value(note['message'])}" - for note in packet.get("notes", []) - ] - non_graph_actions = [ - action - for action in packet.get("nextActions", []) - if action.get("kind") not in GRAPH_NATIVE_NEXT_ACTION_KINDS - ] - if non_graph_actions: - lines.append( - "|next " - + ",".join(render_next_action(action) for action in non_graph_actions) - ) - return lines - - -def render_compact_graph_packet( - packet: dict[str, Any], - *, - seed_limit: int = DEFAULT_GRAPH_SEED_LIMIT, -) -> str: - command = [ - os.environ.get(SEMANTIC_AGENT_PROTOCOL_BIN_ENV, "asp"), - "graph", - "render", - "--packet", - "-", - "--view", - "seeds", - "--seeds", - str(seed_limit), - ] - try: - completed = subprocess.run( - command, - input=json.dumps(packet, separators=(",", ":")), - text=True, - capture_output=True, - check=True, - ) - except FileNotFoundError as exc: - raise CompactGraphRenderError( - "asp graph renderer not found; " - f"set {SEMANTIC_AGENT_PROTOCOL_BIN_ENV} or install " - "asp on PATH" - ) from exc - except subprocess.CalledProcessError as exc: - stderr = exc.stderr.strip() - detail = f": {stderr}" if stderr else "" - raise CompactGraphRenderError( - f"asp graph render failed with exit code {exc.returncode}{detail}" - ) from exc - return completed.stdout diff --git a/src/python_lang_project_harness/_semantic_search_hits.py b/src/python_lang_project_harness/_semantic_search_hits.py deleted file mode 100644 index 73da3ec..0000000 --- a/src/python_lang_project_harness/_semantic_search_hits.py +++ /dev/null @@ -1,18 +0,0 @@ -"""Public hit-builder facade for Python semantic search.""" - -from __future__ import annotations - -from ._semantic_search_callsite_hits import callsite_hits -from ._semantic_search_import_test_hits import import_hits, test_path_hits -from ._semantic_search_symbol_hits import api_hits, symbol_hit, symbol_hits -from ._semantic_search_text_hits import text_hits - -__all__ = [ - "api_hits", - "callsite_hits", - "import_hits", - "symbol_hit", - "symbol_hits", - "test_path_hits", - "text_hits", -] diff --git a/src/python_lang_project_harness/_semantic_search_import_routes.py b/src/python_lang_project_harness/_semantic_search_import_routes.py deleted file mode 100644 index c17cbe2..0000000 --- a/src/python_lang_project_harness/_semantic_search_import_routes.py +++ /dev/null @@ -1,102 +0,0 @@ -"""Import-definition route candidates for Python owner item queries.""" - -from __future__ import annotations - -from pathlib import Path -from typing import TYPE_CHECKING, Any - -from ._semantic_search_common import semantic_search_display_path - -if TYPE_CHECKING: - from python_lang_parser import PythonModuleReport - - from ._project_policy_context import PythonHarnessReport - - -def import_definition_routes( - report: PythonHarnessReport, - project_root: Path, - module: PythonModuleReport, - terms: list[str], -) -> list[dict[str, str]]: - owner_paths = tuple(_report_owner_paths(report, project_root)) - routes: list[dict[str, str]] = [] - seen: set[tuple[str, str, str]] = set() - for term in terms: - routes.extend(_routes_for_term(term, module.imports, owner_paths, seen)) - return routes - - -def _routes_for_term( - term: str, - imports: object, - owner_paths: tuple[str, ...], - seen: set[tuple[str, str, str]], -) -> list[dict[str, str]]: - routes: list[dict[str, str]] = [] - for imported in imports: - route = _route_for_import(term, imported, owner_paths) - if route is None: - continue - key = (route["term"], route["ownerPath"], route["query"]) - if key in seen: - continue - seen.add(key) - routes.append(route) - return routes - - -def _route_for_import( - term: str, - imported: Any, - owner_paths: tuple[str, ...], -) -> dict[str, str] | None: - source_name = _import_source_name_for_term(imported, term) - if source_name is None: - return None - owner_path = _import_target_owner(owner_paths, imported) - if owner_path is None: - return None - return {"term": term, "ownerPath": owner_path, "query": source_name} - - -def _report_owner_paths( - report: PythonHarnessReport, - project_root: Path, -) -> list[str]: - owner_paths: list[str] = [] - for report_module in report.modules: - path = report_module.path - if path is None: - continue - owner_paths.append(semantic_search_display_path(path, project_root)) - return owner_paths - - -def _import_source_name_for_term(imported: Any, term: str) -> str | None: - names = tuple(str(name) for name in getattr(imported, "names", ())) - source_names = tuple(str(name) for name in getattr(imported, "source_names", ())) - if term in names: - index = names.index(term) - if index < len(source_names) and source_names[index] != "*": - return source_names[index] - return term - if term in source_names and term != "*": - return term - return None - - -def _import_target_owner(owner_paths: tuple[str, ...], imported: Any) -> str | None: - module_name = getattr(imported, "module", None) - if not isinstance(module_name, str) or not module_name: - return None - candidates = _module_owner_suffixes(module_name) - return next( - (owner_path for owner_path in owner_paths if owner_path.endswith(candidates)), - None, - ) - - -def _module_owner_suffixes(module_name: str) -> tuple[str, str]: - module_suffix = module_name.replace(".", "/") - return (f"{module_suffix}.py", f"{module_suffix}/__init__.py") diff --git a/src/python_lang_project_harness/_semantic_search_import_test_hits.py b/src/python_lang_project_harness/_semantic_search_import_test_hits.py deleted file mode 100644 index b3c991c..0000000 --- a/src/python_lang_project_harness/_semantic_search_import_test_hits.py +++ /dev/null @@ -1,74 +0,0 @@ -"""Import and test hit builders for Python semantic search.""" - -from __future__ import annotations - -from pathlib import Path -from typing import TYPE_CHECKING, Any - -from ._semantic_search_common import location_from_source, path_hit -from ._semantic_search_deps import module_owner_path -from .verification.facts import is_test_path - -if TYPE_CHECKING: - from ._model import PythonHarnessReport - - -def import_hits( - report: PythonHarnessReport, - project_root: Path, - query: str, -) -> list[dict[str, Any]]: - """Return raw import-statement hits.""" - - query_folded = query.casefold() - return [ - _import_hit( - import_record, module_owner_path(module, project_root), project_root - ) - for module in report.modules - for import_record in module.imports - if _import_matches(import_record, query_folded) - ] - - -def test_path_hits( - report: PythonHarnessReport, - project_root: Path, - query: str, -) -> list[dict[str, Any]]: - """Return test path/function hits.""" - - query_folded = query.casefold() - return [ - path_hit(owner_path, owner_path, kind="test", score=3, reason="test-path") - for module in report.modules - if is_test_path(owner_path := module_owner_path(module, project_root)) - if _test_module_matches(module, owner_path, query_folded) - ] - - -def _import_hit(import_record, owner_path: str, project_root: Path) -> dict[str, Any]: - return { - "kind": "import", - "ownerPath": owner_path, - "location": location_from_source(import_record.location, project_root), - "score": 3, - "reason": "import-statement", - "symbol": import_record.module or ",".join(import_record.names), - "fields": {"scope": import_record.scope or "module"}, - } - - -def _import_matches(import_record, query_folded: str) -> bool: - haystacks = [ - import_record.module or "", - *import_record.names, - *import_record.source_names, - ] - return any(query_folded in item.casefold() for item in haystacks) - - -def _test_module_matches(module, owner_path: str, query_folded: str) -> bool: - if query_folded in owner_path.casefold(): - return True - return any(query_folded in symbol.name.casefold() for symbol in module.symbols) diff --git a/src/python_lang_project_harness/_semantic_search_ingest.py b/src/python_lang_project_harness/_semantic_search_ingest.py deleted file mode 100644 index 0e114bd..0000000 --- a/src/python_lang_project_harness/_semantic_search_ingest.py +++ /dev/null @@ -1,132 +0,0 @@ -"""Stdin ingest helpers for Python semantic search.""" - -from __future__ import annotations - -import os -import re -from pathlib import Path -from typing import TYPE_CHECKING, Any - -from ._semantic_search_common import location - -if TYPE_CHECKING: - from python_lang_parser import PythonReasoningTreeFacts - - -def ingest_hits( - facts: PythonReasoningTreeFacts, - project_root: Path, - stdin: str, -) -> tuple[dict[str, Any], list[dict[str, Any]]]: - """Detect stdin shape and return owner-grouped hits.""" - - detection, records = detect_ingest_records(stdin) - owner_paths = { - _display_path(node.path, project_root) for node in facts.nodes if node.is_valid - } - hits: list[dict[str, Any]] = [] - for record in records: - owner_path = ingest_owner_path(record["path"], owner_paths) - hits.append( - { - "kind": "text", - "ownerPath": owner_path, - "location": location(record["path"], record.get("line")), - "score": 2, - "reason": f"ingest-{detection['source']}", - "snippet": record.get("text", ""), - "fields": {"source": "ingest"}, - } - ) - return detection, hits - - -def detect_ingest_records(stdin: str) -> tuple[dict[str, Any], list[dict[str, Any]]]: - """Detect rg/vimgrep/path-list stdin shapes.""" - - byte_count = len(stdin.encode()) - line_count = ( - 0 if not stdin else stdin.count("\n") + (0 if stdin.endswith("\n") else 1) - ) - source = "unknown" - records: list[dict[str, Any]] = [] - if "\0" in stdin: - source = "path-list-nul" - records = [{"path": path} for path in stdin.split("\0") if path] - else: - lines = [line for line in stdin.splitlines() if line.strip()] - if lines and all(looks_like_path(line) for line in lines): - source = "path-list" - records = [{"path": line.strip()} for line in lines] - else: - parsed = [parse_rg_line(line) for line in lines] - records = [record for record in parsed if record is not None] - if records: - source = ( - "vimgrep" - if any("column" in record for record in records) - else "rg-n" - ) - elif lines and all(line.lstrip().startswith("{") for line in lines[:3]): - source = "rg-json" - sample = {"sample": stdin[:160]} if stdin else {} - return { - "source": source, - "lineCount": line_count, - "byteCount": byte_count, - **sample, - }, records - - -def parse_rg_line(line: str) -> dict[str, Any] | None: - """Parse `rg -n` or vimgrep-like output.""" - - match = re.match( - r"^(?P.*?):(?P\d+)(?::(?P\d+))?:(?P.*)$", line - ) - if match is None: - return None - return { - "path": match.group("path"), - "line": int(match.group("line")), - **( - {"column": int(match.group("column"))} - if match.group("column") is not None - else {} - ), - "text": match.group("text").strip(), - } - - -def looks_like_path(line: str) -> bool: - """Return whether a line looks like a plain path list entry.""" - - stripped = line.strip() - return ( - stripped.endswith(".py") or os.sep in stripped or stripped.startswith(".") - ) and ":" not in stripped - - -def ingest_owner_path(path: str, owner_paths: set[str]) -> str: - """Map an ingested path back to a parser-visible owner when possible.""" - - normalized = path.removeprefix("./") - if normalized in owner_paths: - return normalized - matches = sorted( - ( - owner_path - for owner_path in owner_paths - if normalized == owner_path - or normalized.startswith(owner_path.rstrip("/") + "/") - ), - key=len, - reverse=True, - ) - return matches[0] if matches else normalized - - -def _display_path(path: str, project_root: Path) -> str: - from ._semantic_search_common import semantic_search_display_path - - return semantic_search_display_path(path, project_root) diff --git a/src/python_lang_project_harness/_semantic_search_ingest_fast.py b/src/python_lang_project_harness/_semantic_search_ingest_fast.py deleted file mode 100644 index 985489c..0000000 --- a/src/python_lang_project_harness/_semantic_search_ingest_fast.py +++ /dev/null @@ -1,58 +0,0 @@ -"""Fast empty-stdin guidance for Python search ingest seed view.""" - -from __future__ import annotations - -from pathlib import Path - -from ._cli_args import ProtocolArgs - -_STDIN_REQUIRED_NOTE = ( - '|note kind=stdin-required message="search ingest consumes stdin candidate ' - 'paths; use search prime --view seeds for project discovery"' -) -_EMPTY_STDIN_NEXT = ( - '|next prime:"search prime --view seeds"(scope=project-discovery),' - 'ingest:"pipe candidate paths into search ingest items tests --view seeds"' - "(scope=stdin-candidates)" -) - - -def render_fast_empty_ingest_search( - args: ProtocolArgs, - project_root: Path, - stdin: str, -) -> str | None: - """Render empty ingest seed guidance without running the full harness.""" - - del project_root - if not _supports_fast_empty_ingest(args, stdin): - return None - return _render_fast_empty_ingest_seed_text(args) - - -def _supports_fast_empty_ingest(args: ProtocolArgs, stdin: str) -> bool: - return ( - args.command == "search" - and args.view == "ingest" - and args.render_mode == "seeds" - and not args.json - and stdin == "" - and args.query is None - and args.item_query is None - and args.owner_path is None - and not args.query_set - ) - - -def _render_fast_empty_ingest_seed_text(args: ProtocolArgs) -> str: - root = args.project_root.as_posix() if args.project_root is not None else "." - lines = [ - f"[search-ingest] root={root or '.'} alg=seed-frontier", - "legend: ID=kind:role(value)!next; edge SRC>{DST:rel}; frontier ID.next", - "aliases: graph:{G=search}", - "G>{}", - "rank= frontier=", - _STDIN_REQUIRED_NOTE, - _EMPTY_STDIN_NEXT, - ] - return "\n".join(lines) + "\n" diff --git a/src/python_lang_project_harness/_semantic_search_item_lines.py b/src/python_lang_project_harness/_semantic_search_item_lines.py deleted file mode 100644 index bc19db2..0000000 --- a/src/python_lang_project_harness/_semantic_search_item_lines.py +++ /dev/null @@ -1,174 +0,0 @@ -"""Line rendering for Python owner item query results.""" - -from __future__ import annotations - -from pathlib import Path -from typing import TYPE_CHECKING, Any - -from ._semantic_search_common import compact_fields, render_fields -from ._semantic_search_items import owner_item_query_payload - -if TYPE_CHECKING: - from ._project_policy_context import PythonHarnessReport - - -def owner_item_query_lines( - report: PythonHarnessReport, - project_root: Path, - owner_path: str, - item_query: str, - *, - names_only: bool = False, -) -> str: - """Render compact owner item query lines for the top-level query command.""" - - payload = owner_item_query_payload(report, project_root, owner_path, item_query) - return owner_item_payload_lines(owner_path, item_query, payload, names_only) - - -def owner_item_payload_lines( - owner_path: str, - item_query: str, - payload: dict[str, Any], - names_only: bool = False, -) -> str: - """Render compact owner item query lines from a precomputed payload.""" - - lines = [_header_line(owner_path, item_query, payload, names_only)] - lines.append(_query_line(item_query, payload, names_only)) - lines.extend(_note_lines(payload)) - lines.extend(_item_lines(payload["items"], names_only)) - return "\n".join(lines) - - -def _header_line( - owner_path: str, item_query: str, payload: dict[str, Any], names_only: bool -) -> str: - return _search_owner_header_line( - owner_path, item_query, payload, names_only - ).replace( - "[search-owner]", - "[query-item]", - 1, - ) - - -def _search_owner_header_line( - owner_path: str, - item_query: str, - payload: dict[str, Any], - names_only: bool, -) -> str: - fields = payload["fields"] - output_fields = compact_fields( - { - "q": owner_path, - "pkg": ".", - "own": 1, - "item": len(payload["items"]), - "itemQuery": item_query, - "output": "names" if names_only else None, - "fallback": fields.get("fallback"), - } - ) - return f"[search-owner] {render_fields(output_fields)}" - - -def _query_line( - item_query: str, - payload: dict[str, Any], - names_only: bool, -) -> str: - fields = payload["fields"] - item_count = fields.get("item") - query_fields = compact_fields( - { - "itemQuery": item_query, - "status": fields.get("itemStatus"), - "match": fields.get("itemMatch"), - "item": item_count, - "reason": "parser-item-query", - "output": "names" if names_only else None, - "next": fields.get("next") - or ( - "revise-query" - if fields.get("itemStatus") == "miss" or not item_count - else "select-item" - if names_only and isinstance(item_count, int) and item_count > 1 - else "query-code" - ), - } - ) - return f"|query {render_fields(query_fields)}" - - -def _note_lines(payload: dict[str, Any]) -> list[str]: - lines: list[str] = [] - for note in payload.get("notes", []): - if not isinstance(note, dict): - continue - lines.append( - "|note " - + render_fields( - compact_fields( - { - "kind": note.get("kind"), - "message": note.get("message"), - } - ) - ) - ) - return lines - - -def _item_lines(items: list[dict[str, Any]], names_only: bool) -> list[str]: - return [_item_summary_line(item) for item in items] - - -def _item_summary_line(item: dict[str, Any]) -> str: - item_fields = item.get("fields", {}) - item_name = item["name"] - return f"|item {item['name']} " + render_fields( - compact_fields( - { - "kind": item.get("kind"), - "public": True if item_fields.get("public") is True else None, - "doc": True if item_fields.get("doc") is True else None, - "next": f"syntax:{item_name}", - "read": item_fields.get("read"), - "syn": _syntax_atom_for_kind(item.get("kind")), - "tsqRef": "semantic-tree-sitter-query/python-owner-items.v1", - } - ) - ) - - -def _syntax_atom_for_kind(kind: object) -> str | None: - if kind == "function": - return "function_definition/name" - if kind == "class": - return "class_definition/name" - if kind == "import": - return "import_statement/name" - if kind == "import-from": - return "import_from_statement/name" - return None - - -def _item_code_line(item: dict[str, Any], names_only: bool) -> str | None: - item_fields = item.get("fields", {}) - location = item.get("location", {}) - code = item_fields.get("code") - if names_only or not isinstance(code, str) or not code: - return None - return "|code " + render_fields( - compact_fields( - { - "path": location.get("path"), - "lineRange": location.get("lineRange"), - "reason": "item-query", - "truncated": False, - "text": code, - } - ) - ) diff --git a/src/python_lang_project_harness/_semantic_search_items.py b/src/python_lang_project_harness/_semantic_search_items.py deleted file mode 100644 index 1c2bb91..0000000 --- a/src/python_lang_project_harness/_semantic_search_items.py +++ /dev/null @@ -1,375 +0,0 @@ -"""Parser-owned compact item extraction for Python owner searches.""" - -from __future__ import annotations - -from collections.abc import Iterable -from pathlib import Path -from typing import TYPE_CHECKING, Any - -from . import _semantic_language_ids as ids -from ._python_compact import compact_python_item -from ._semantic_query_packet import ( - semantic_import_route_next, - semantic_query_coverage, - semantic_query_match, - semantic_query_match_mode, -) -from ._semantic_search_common import compact_fields, semantic_search_display_path -from ._semantic_search_import_routes import import_definition_routes -from ._semantic_search_model import MAX_OWNER_QUERY_ITEMS -from ._semantic_selector_identity import python_structural_selector_identity - -if TYPE_CHECKING: - from python_lang_parser import PythonModuleReport, PythonSymbol - - from ._model import PythonHarnessReport - - -def owner_item_query_payload( - report: PythonHarnessReport, - project_root: Path, - owner_path: str, - item_query: str | None, -) -> dict[str, Any]: - """Return compact parser item facts for one owner path.""" - - module = _module_for_owner(report, project_root, owner_path) - if module is None: - return { - "items": [], - "fields": {"item": 0, "itemStatus": "miss", "itemMatch": "none"}, - "notes": [{"kind": "owner-not-found", "message": owner_path}], - } - - symbols = _sorted_symbols(module) - terms = _query_terms(item_query) - selected, match = _select_symbols(module, symbols, terms) - import_routes = ( - import_definition_routes(report, project_root, module, terms) - if terms and match != "exact" - else [] - ) - fallback = False - if import_routes: - selected = [] - match = "candidate" - elif not selected: - selected = [symbol for symbol in symbols if symbol.is_top_level][ - :MAX_OWNER_QUERY_ITEMS - ] - match = "none" if terms else "top-items" - fallback = bool(terms) - - items = [ - _item_record(module, project_root, owner_path, symbol) - for symbol in selected[:MAX_OWNER_QUERY_ITEMS] - ] - fields: dict[str, object] = { - "item": len(items), - "itemQuery": item_query, - "itemStatus": "hit" if items and not fallback else "miss", - "itemMatch": match if items or import_routes else "none", - "fallback": "owner-top-items" if fallback and items else None, - "next": semantic_import_route_next(import_routes[0]) if import_routes else None, - } - return { - "items": items, - "fields": compact_fields(fields), - "notes": _item_query_notes(item_query, owner_path, items, import_routes), - "importRoutes": import_routes, - } - - -def owner_item_semantic_query_packet( - report: PythonHarnessReport, - project_root: Path, - owner_path: str, - item_query: str, - *, - output_mode: str, - selector: str | None = None, -) -> dict[str, Any]: - """Return a semantic-query-packet for owner-local Python item lookup.""" - - payload = owner_item_query_payload(report, project_root, owner_path, item_query) - items, fields, import_routes = _selector_resolved_owner_items( - report, - project_root, - owner_path, - selector, - payload, - ) - return _owner_item_semantic_query_packet( - payload, - project_root, - owner_path, - item_query, - output_mode, - items, - fields, - import_routes, - ) - - -def _selector_resolved_owner_items( - report: PythonHarnessReport, - project_root: Path, - owner_path: str, - selector: str | None, - payload: dict[str, Any], -) -> tuple[list[dict[str, Any]], dict[str, Any], list[Any]]: - items = payload["items"] - fields = payload["fields"] - import_routes = payload.get("importRoutes", []) - module = _module_for_owner(report, project_root, owner_path) - selector_identity = python_structural_selector_identity(selector) - if selector_identity is not None: - selector_owner_path, selector_kind, selector_name = selector_identity - items = ( - [ - _item_record(module, project_root, owner_path, symbol) - for symbol in _sorted_symbols(module) - if symbol.kind.value == selector_kind - and symbol.qualified_name == selector_name - ] - if selector_owner_path == owner_path and module is not None - else [] - ) - fields = { - **fields, - "item": len(items), - "itemStatus": "hit" if items else "miss", - "itemMatch": "exact" if items else "none", - } - import_routes = [] - return items, fields, import_routes - - -def _owner_item_semantic_query_packet( - payload: dict[str, Any], - project_root: Path, - owner_path: str, - item_query: str, - output_mode: str, - items: list[dict[str, Any]], - fields: dict[str, Any], - import_routes: list[Any], -) -> dict[str, Any]: - terms = _query_terms(item_query) - from ._semantic_syntax_refs import ( - annotate_python_owner_item_syntax_refs, - attach_python_syntax_refs, - ) - - syntax_refs = annotate_python_owner_item_syntax_refs(items) - packet = { - "schemaId": ids.SEMANTIC_QUERY_PACKET_SCHEMA_ID, - "schemaVersion": "1", - "protocolId": ids.SEMANTIC_LANGUAGE_PROTOCOL_ID, - "protocolVersion": ids.SEMANTIC_LANGUAGE_PROTOCOL_VERSION, - "languageId": ids.PYTHON_LANGUAGE_ID, - "providerId": ids.PYTHON_PROVIDER_ID, - "binary": ids.PYTHON_BINARY, - "namespace": ids.PYTHON_PROVIDER_NAMESPACE, - "method": "query/owner-items", - "projectRoot": str(project_root), - "ownerPath": owner_path, - "query": item_query, - "queryTerms": terms, - "matchMode": semantic_query_match_mode(str(fields.get("itemMatch", "none"))), - "outputMode": output_mode, - "patchSafety": { - "level": "read-safe", - "reason": "compact query packet is not a mutation authority", - "nextAction": ( - "query --selector --projection source" - ), - }, - "queryCoverage": [ - semantic_query_coverage( - term, - items, - str(fields.get("itemMatch", "none")), - import_routes if isinstance(import_routes, list) else [], - ) - for term in terms - ], - "matches": [ - semantic_query_match(item, include_code=output_mode != "names") - for item in items - ], - "truncated": any( - bool(item.get("fields", {}).get("truncated")) for item in items - ), - "notes": payload.get("notes", []), - } - attach_python_syntax_refs(packet, syntax_refs) - return packet - - -def _module_for_owner( - report: PythonHarnessReport, - project_root: Path, - owner_path: str, -) -> PythonModuleReport | None: - for module in report.modules: - if ( - module.path is not None - and semantic_search_display_path(module.path, project_root) == owner_path - ): - return module - return None - - -def _sorted_symbols(module: PythonModuleReport) -> list[PythonSymbol]: - return sorted( - module.symbols, - key=lambda symbol: ( - symbol.location.line, - symbol.location.column, - symbol.qualified_name, - ), - ) - - -def _query_terms(item_query: str | None) -> list[str]: - if item_query is None: - return [] - return [term.strip() for term in item_query.split("|") if term.strip()] - - -def _item_query_notes( - item_query: str | None, - owner_path: str, - items: list[dict[str, Any]], - import_routes: list[dict[str, str]], -) -> list[dict[str, str]]: - if import_routes: - route = import_routes[0] - return [ - { - "kind": "imported-definition", - "message": ( - f"{item_query or owner_path} is imported in {owner_path}; " - f"next={semantic_import_route_next(route)}" - ), - } - ] - if items: - return [] - return [{"kind": "item-not-found", "message": item_query or owner_path}] - - -def _select_symbols( - module: PythonModuleReport, - symbols: list[PythonSymbol], - terms: list[str], -) -> tuple[list[PythonSymbol], str]: - if not terms: - return [symbol for symbol in symbols if symbol.is_top_level], "top-items" - exact = _dedupe_symbols( - symbol - for term in terms - for symbol in symbols - if term in {symbol.name, symbol.qualified_name} - ) - if exact: - return exact, "exact" - folded_terms = [term.casefold() for term in terms] - contains = _dedupe_symbols( - symbol - for term in folded_terms - for symbol in symbols - if term in _symbol_query_text(module, symbol) - ) - return contains, "fallback-contains" if contains else "none" - - -def _symbol_query_text(module: PythonModuleReport, symbol: PythonSymbol) -> str: - end_line = symbol.end_line or symbol.location.line - code, _, _ = _compact_code( - module, - str(symbol.location.path or ""), - symbol.location.line, - end_line, - ) - return "\n".join((symbol.name, symbol.qualified_name, code)).casefold() - - -def _dedupe_symbols(symbols: Iterable[PythonSymbol]) -> list[PythonSymbol]: - selected: list[PythonSymbol] = [] - seen: set[tuple[str, int, int]] = set() - for symbol in symbols: - key = (symbol.qualified_name, symbol.location.line, symbol.location.column) - if key in seen: - continue - seen.add(key) - selected.append(symbol) - return selected - - -def _item_record( - module: PythonModuleReport, - project_root: Path, - owner_path: str, - symbol: PythonSymbol, -) -> dict[str, Any]: - end_line = symbol.end_line or symbol.location.line - line_range = f"{symbol.location.line}:{end_line}" - source_locator_hint = f"{owner_path}:{symbol.location.line}:{end_line}" - structural_selector = _structural_selector(owner_path, symbol) - code, truncated, projection_nodes = _compact_code( - module, - owner_path, - symbol.location.line, - end_line, - ) - return { - "name": symbol.qualified_name, - "kind": symbol.kind.value, - "ownerPath": owner_path, - "location": { - "path": owner_path, - "lineRange": line_range, - }, - "fields": compact_fields( - { - "public": symbol.is_public, - "doc": bool(symbol.docstring), - "structuralSelector": structural_selector, - "displayLineRange": line_range, - "sourceLocatorHint": source_locator_hint, - "read": source_locator_hint, - "reason": "item-query", - "truncated": truncated, - "code": code, - "projectionNodes": projection_nodes, - "sourcePath": semantic_search_display_path( - symbol.location.path or owner_path, project_root - ), - } - ), - } - - -def _structural_selector(owner_path: str, symbol: PythonSymbol) -> str: - return f"python://{owner_path}#item/{symbol.kind.value}/{symbol.qualified_name}" - - -def _compact_code( - module: PythonModuleReport, - owner_path: str, - start_line: int, - end_line: int, - *, - max_lines: int = 80, -) -> tuple[str, bool, list[dict[str, Any]]]: - raw_lines = module.source_lines[start_line - 1 : end_line] - compact = compact_python_item(raw_lines, owner_path, start_line) - if compact.projection_nodes: - return compact.code, False, compact.projection_nodes - - truncated = len(raw_lines) > max_lines - selected_lines = raw_lines[:max_lines] - compact = compact_python_item(selected_lines, owner_path, start_line) - return compact.code, truncated, compact.projection_nodes diff --git a/src/python_lang_project_harness/_semantic_search_knowledge_facts.py b/src/python_lang_project_harness/_semantic_search_knowledge_facts.py deleted file mode 100644 index 9a649d8..0000000 --- a/src/python_lang_project_harness/_semantic_search_knowledge_facts.py +++ /dev/null @@ -1,180 +0,0 @@ -"""Provider-owned fact catalog for Python language knowledge axes.""" - -from __future__ import annotations - -from pathlib import Path -from typing import Any - -AxisDetail = dict[str, str] -KnowledgeFact = dict[str, Any] - -_AXIS_DETAILS: dict[str, AxisDetail] = { - "env": { - "authority": "project-environment", - "summary": "Python environment facts from pyproject, package metadata, and import roots.", - "next": "search lang import packaging", - }, - "runtime-source": { - "authority": "local-source", - "summary": "Python provider has no runtime checkout resolver; use owner/query/deps evidence.", - "next": "search deps ", - }, - "lang": { - "authority": "language-rules", - "summary": "Python syntax and runtime semantics visible to ast/tokenize/symtable facts.", - "next": "query guide treesitter", - }, - "std": { - "authority": "standard-library", - "summary": "Python standard-library API and idiom facts for agent code generation.", - "next": "search api ", - }, - "capability": { - "authority": "provider-registry", - "summary": "Python provider method and capability registry facts.", - "next": "guide", - }, - "extension": { - "authority": "ecosystem-extension", - "summary": "Framework or package-specific Python ecosystem extension evidence.", - "next": "search deps ", - }, - "pattern": { - "authority": "executable-pattern", - "summary": "Executable syntax and API patterns backed by owner/deps/tree-sitter evidence.", - "next": "search owner items --query ", - }, - "compare": { - "authority": "semantic-comparison", - "summary": "Compare Python project, dependency, or syntax axes using provider-owned facts.", - "next": "run each side through matching provider axis", - }, -} - -_LANG_FACTS = [ - ("module-import", {"syntax": "import/from", "selector": "query --catalog imports"}), - ("decorator", {"syntax": "@decorator", "selector": "query --catalog declarations"}), - ("class-protocol", {"syntax": "class/Protocol", "selector": "search api Protocol"}), -] - -_STD_FACTS = [ - ("pathlib", {"symbol": "pathlib.Path", "pattern": "filesystem path API"}), - ("dataclasses", {"symbol": "dataclasses.dataclass", "pattern": "record modeling"}), - ("typing", {"symbol": "typing", "pattern": "type contracts and protocols"}), - ("itertools", {"symbol": "itertools", "pattern": "iterator composition"}), -] - -_CAPABILITY_FACTS = [ - ("owner-items", {"command": "search owner items"}), - ("deps", {"command": "search deps "}), - ("tree-sitter", {"command": "query --treesitter-query "}), -] - -_PATTERN_FACTS = [ - ( - "declaration-to-owner-query", - { - "command": "query --catalog declarations then search owner items", - "qualitySignal": "parser-owned declaration before code read", - }, - ), - ( - "dependency-api-usage", - { - "command": "search deps ::", - "qualitySignal": "dependency and local usage evidence", - }, - ), -] - - -def axis_detail(axis: str) -> AxisDetail: - """Return stable packet metadata for a provider knowledge axis.""" - - return _AXIS_DETAILS.get(axis, _AXIS_DETAILS["capability"]) - - -def knowledge_facts( - project_root: Path, axis: str, terms: list[str] -) -> list[KnowledgeFact]: - """Return provider-owned facts for the requested axis.""" - - if axis == "env": - return _env_facts(project_root, terms) - if axis == "runtime-source": - return [] - if axis == "extension": - return _extension_facts(project_root, terms) - if axis == "compare": - return _compare_facts(axis, terms) - return _filter_facts(_static_axis_facts(axis), terms) - - -def _static_axis_facts(axis: str) -> list[KnowledgeFact]: - if axis == "lang": - return _facts(axis, _LANG_FACTS) - if axis == "std": - return _facts(axis, _STD_FACTS) - if axis == "capability": - return _facts(axis, _CAPABILITY_FACTS) - if axis == "pattern": - return _facts(axis, _PATTERN_FACTS) - return [] - - -def _env_facts(project_root: Path, terms: list[str]) -> list[KnowledgeFact]: - candidates = (project_root / "pyproject.toml", project_root / "setup.cfg") - facts = [ - _fact( - path.name, - "env", - path=str(path.relative_to(project_root)), - source="project-config", - ) - for path in candidates - if path.exists() - ] - return _filter_facts(facts, terms) - - -def _extension_facts(project_root: Path, terms: list[str]) -> list[KnowledgeFact]: - pyproject = project_root / "pyproject.toml" - if not pyproject.exists(): - return [] - text = pyproject.read_text(encoding="utf-8", errors="replace") - return [ - _fact("pyproject", "extension", source="pyproject.toml", match=term) - for term in terms - if term and term in text.lower() - ] - - -def _compare_facts(axis: str, terms: list[str]) -> list[KnowledgeFact]: - return [ - _fact( - "compare-query", - axis, - left=terms[0] if len(terms) > 0 else "-", - right=terms[1] if len(terms) > 1 else "-", - route="run each side through the matching provider axis and compare facts", - ) - ] - - -def _facts(axis: str, rows: list[tuple[str, dict[str, str]]]) -> list[KnowledgeFact]: - return [_fact(fact_id, axis, **fields) for fact_id, fields in rows] - - -def _fact(fact_id: str, axis: str, **fields: str) -> KnowledgeFact: - return {"id": fact_id, "fields": {"axis": axis, **fields}} - - -def _filter_facts(facts: list[KnowledgeFact], terms: list[str]) -> list[KnowledgeFact]: - if not terms: - return facts - return [fact for fact in facts if _matches_terms(str(fact), terms)] - - -def _matches_terms(value: str, terms: list[str]) -> bool: - normalized = value.lower() - return any(term in normalized for term in terms) diff --git a/src/python_lang_project_harness/_semantic_search_lexical_fast.py b/src/python_lang_project_harness/_semantic_search_lexical_fast.py deleted file mode 100644 index 88ad828..0000000 --- a/src/python_lang_project_harness/_semantic_search_lexical_fast.py +++ /dev/null @@ -1,102 +0,0 @@ -"""Fast compact frontiers for Python lexical seed views.""" - -from __future__ import annotations - -from pathlib import Path - -from ._cli_args import ProtocolArgs -from ._semantic_search_prefilter import prefilter_python_text_search_paths - - -def render_fast_lexical_seed_search( - args: ProtocolArgs, project_root: Path -) -> str | None: - """Render lexical owner seeds without parsing candidate modules.""" - - if not _supports_fast_lexical_seed_search(args): - return None - query_terms = _fast_lexical_query_terms(args) - prefilter = prefilter_python_text_search_paths( - project_root, - query_terms, - owner_path=args.owner_path, - ) - if prefilter is None or not prefilter.paths: - return None - owners = tuple(_relative_owner_path(path, project_root) for path in prefilter.paths) - return _render_fast_lexical_seed_text(query_terms, owners, prefilter.runtime_cost()) - - -def _supports_fast_lexical_seed_search(args: ProtocolArgs) -> bool: - return ( - args.command == "search" - and args.view == "lexical" - and args.render_mode == "seeds" - and not args.json - and bool(_fast_lexical_query_terms(args)) - and args.pipes in {("owner",), ("owner", "tests")} - and args.item_query is None - and args.dependency is None - ) - - -def _fast_lexical_query_terms(args: ProtocolArgs) -> tuple[str, ...]: - if args.query_set: - return args.query_set - if args.query: - return (args.query,) - return () - - -def _relative_owner_path(path: Path, root: Path) -> str: - try: - return path.relative_to(root).as_posix() - except ValueError: - return path.as_posix() - - -def _render_fast_lexical_seed_text( - query_terms: tuple[str, ...], - owners: tuple[str, ...], - runtime_cost: dict[str, object], -) -> str: - query = ",".join(query_terms) - declarations = [f"Q=query:term({query})!lexical"] - edges = ["Q:matches"] - rank = ["Q"] - for index, owner in enumerate(owners, start=1): - suffix = "" if index == 1 else str(index) - owner_id = f"O{suffix}" - test_id = f"T{suffix}" - declarations.append(f"{owner_id}=owner:path({owner})!owner") - declarations.append(f"{test_id}=test:path({owner})!tests") - edges.extend((f"{owner_id}:selects", f"{test_id}:covers")) - rank.extend((owner_id, test_id)) - return "\n".join( - [ - ( - f"[search-lexical] q={query} querySet={len(query_terms)} " - "selector=lexical-set view=hits alg=query-set-owner-resolution" - ), - "legend: ID=kind:role(value)!next; edge SRC>{DST:rel}; frontier ID.next", - "aliases: graph:{G=search,Q=query,O=owner,T=test}", - ";".join(declarations), - f"G>{{{','.join(edges)}}}", - f"rank={','.join(rank)} frontier={_frontier(rank)}", - "entries=owner-query(O,Q=>items+tests+dependency-usage),owner-tests(O=>covering-tests+test-entrypoints+fixtures)", - f'|note kind=runtime-prefilter message="{runtime_cost["reason"]}"', - "", - ] - ) - - -def _frontier(rank: list[str]) -> str: - return ",".join(f"{item}.{_frontier_kind(item)}" for item in rank) - - -def _frontier_kind(item: str) -> str: - if item == "Q": - return "lexical" - if item.startswith("T"): - return "tests" - return "owner" diff --git a/src/python_lang_project_harness/_semantic_search_model.py b/src/python_lang_project_harness/_semantic_search_model.py deleted file mode 100644 index f6630a5..0000000 --- a/src/python_lang_project_harness/_semantic_search_model.py +++ /dev/null @@ -1,37 +0,0 @@ -"""Shared semantic-search model aliases for the Python provider.""" - -from __future__ import annotations - -from dataclasses import dataclass -from typing import Any - -FieldValue = str | int | float | bool | list[str | int | float | bool] -Fields = dict[str, FieldValue] - -MAX_PRIME_OWNERS = 8 -MAX_PRIME_EDGES = 24 -MAX_WORKSPACE_PACKAGES = 24 -MAX_WORKSPACE_EDGES = 8 -MAX_FINDINGS = 8 -MAX_LEXICAL_HITS = 12 -MAX_SYMBOL_HITS = 20 -MAX_IMPORT_HITS = 30 -MAX_DEPENDENCY_HITS = 24 -MAX_TEST_HITS = 8 -MAX_OWNER_QUERY_ITEMS = 4 - - -@dataclass(frozen=True, slots=True) -class PythonSemanticSearchOptions: - """Options parsed from an `asp-python search` invocation.""" - - view: str - query: str | None = None - item_query: str | None = None - query_set: tuple[str, ...] = () - owner_path: str | None = None - dependency: str | None = None - pipes: tuple[str, ...] = () - render_mode: str | None = None - stdin: str = "" - runtime_cost: dict[str, Any] | None = None diff --git a/src/python_lang_project_harness/_semantic_search_owner_fast.py b/src/python_lang_project_harness/_semantic_search_owner_fast.py deleted file mode 100644 index 5d1a26c..0000000 --- a/src/python_lang_project_harness/_semantic_search_owner_fast.py +++ /dev/null @@ -1,61 +0,0 @@ -"""Fast compact frontiers for Python exact-owner seed view.""" - -from __future__ import annotations - -from pathlib import Path - -from ._cli_args import ProtocolArgs - - -def render_fast_owner_seed_search(args: ProtocolArgs, project_root: Path) -> str | None: - """Render exact owner seeds without parsing the owner file.""" - - owner_path = _fast_owner_path(args, project_root) - if owner_path is None: - return None - owner = _relative_owner_path(owner_path, project_root) - return "\n".join( - [ - f"[search-owner] q={owner} alg=fast-exact-owner-frontier", - "legend: ID=kind:role(value)!next; edge SRC>{DST:rel}; frontier ID.next", - "aliases: graph:{G=search,O=owner,T=test}", - f"O=owner:path({owner})!owner;T=test:path({owner})!tests", - "G>{O:selects,T:covers}", - "rank=O,T frontier=O.owner,T.tests", - "entries=owner-tests(O=>covering-tests+test-entrypoints+fixtures)", - "", - ] - ) - - -def _fast_owner_path(args: ProtocolArgs, project_root: Path) -> Path | None: - if ( - args.command != "search" - or args.view != "owner" - or args.render_mode != "seeds" - or args.json - or args.query is None - or args.item_query is not None - or args.owner_path is not None - or args.query_set - or args.pipes - ): - return None - raw_path = Path(args.query) - owner_path = raw_path if raw_path.is_absolute() else project_root / raw_path - try: - resolved_root = project_root.resolve() - resolved_owner = owner_path.resolve() - resolved_owner.relative_to(resolved_root) - except (OSError, ValueError): - return None - if not resolved_owner.is_file() or resolved_owner.suffix != ".py": - return None - return resolved_owner - - -def _relative_owner_path(path: Path, root: Path) -> str: - try: - return path.relative_to(root.resolve()).as_posix() - except ValueError: - return path.as_posix() diff --git a/src/python_lang_project_harness/_semantic_search_owners.py b/src/python_lang_project_harness/_semantic_search_owners.py deleted file mode 100644 index 412eabb..0000000 --- a/src/python_lang_project_harness/_semantic_search_owners.py +++ /dev/null @@ -1,179 +0,0 @@ -"""Owner and import-edge facts for Python semantic search.""" - -from __future__ import annotations - -from pathlib import Path -from typing import TYPE_CHECKING, Any - -from ._semantic_search_common import location, semantic_search_display_path -from ._semantic_search_model import Fields -from .verification.facts import is_test_path - -if TYPE_CHECKING: - from collections.abc import Iterable - - from python_lang_parser import ( - PythonReasoningTreeFacts, - PythonReasoningTreeNode, - ) - - -def owner_nodes(facts: PythonReasoningTreeFacts) -> tuple[PythonReasoningTreeNode, ...]: - """Return parser-valid reasoning-tree owner nodes.""" - - return tuple(node for node in facts.nodes if node.is_valid) - - -def ranked_owner_records( - facts: PythonReasoningTreeFacts, - project_root: Path, -) -> list[dict[str, Any]]: - """Return owners ranked for a prime packet.""" - - records = [owner_record(node, project_root) for node in owner_nodes(facts)] - return sorted( - records, - key=lambda owner: ( - 1 if owner["fields"].get("surface") == "test" else 0, - 0 if owner["public"] else 1, - -int(owner["fields"].get("lines", 0)), - owner["path"].count("/"), - owner["path"], - ), - ) - - -def owner_record(node: PythonReasoningTreeNode, project_root: Path) -> dict[str, Any]: - """Return one semantic-search owner record.""" - - path = semantic_search_display_path(node.path, project_root) - exports = list(node.public_names) - fields: Fields = { - "kind": node.kind, - "surface": "test" if is_test_path(path) else "source", - "doc": node.has_intent_doc, - "lines": node.effective_code_lines, - "exportKind": node.export_contract_kind, - } - if node.child_names: - fields["children"] = list(node.child_names[:8]) - return { - "path": path, - "namespace": ".".join(node.namespace), - "role": _owner_role(node, path), - "public": node.has_public_surface, - "exports": exports, - "nextActions": [ - {"kind": "owner", "target": path}, - {"kind": "tests", "target": path}, - *( - {"kind": "text", "target": name, "ownerPath": path} - for name in exports[:2] - ), - ], - "fields": fields, - } - - -def matching_owner_nodes( - facts: PythonReasoningTreeFacts, - project_root: Path, - query: str, -) -> list[PythonReasoningTreeNode]: - """Return owner nodes matching a path, namespace, or public export.""" - - query_folded = query.casefold() - matches = [] - for node in facts.nodes: - path = semantic_search_display_path(node.path, project_root) - namespace = ".".join(node.namespace) - if ( - query_folded in path.casefold() - or query_folded in namespace.casefold() - or any(query_folded in name.casefold() for name in node.public_names) - ): - matches.append(node) - return sorted( - matches, key=lambda node: semantic_search_display_path(node.path, project_root) - ) - - -def owners_for_paths( - facts: PythonReasoningTreeFacts, - project_root: Path, - paths: Iterable[str], -) -> list[dict[str, Any]]: - """Return owner records for display paths.""" - - wanted = set(paths) - return [ - owner_record(node, project_root) - for node in facts.nodes - if semantic_search_display_path(node.path, project_root) in wanted - ] - - -def import_edges( - facts: PythonReasoningTreeFacts, - project_root: Path, - *, - limit: int, -) -> list[dict[str, Any]]: - """Return parser-resolved import edges.""" - - edges = [] - for edge in facts.import_edges[:limit]: - importer = semantic_search_display_path(edge.importer_path, project_root) - imported = semantic_search_display_path(edge.imported_path, project_root) - edges.append( - { - "from": f"O:{importer}", - "kind": "import", - "to": f"O:{imported}", - "location": location(importer, edge.line, edge.column), - "fields": { - "import": edge.import_name, - "bound": edge.bound_name, - "scope": edge.scope or "module", - "relative": edge.is_relative, - }, - } - ) - return edges - - -def test_edges( - facts: PythonReasoningTreeFacts, - project_root: Path, - owner_paths: set[str], -) -> list[dict[str, Any]]: - """Return test-import edges for selected owners.""" - - edges = [] - for edge in facts.import_edges: - importer = semantic_search_display_path(edge.importer_path, project_root) - imported = semantic_search_display_path(edge.imported_path, project_root) - if not is_test_path(importer): - continue - if owner_paths and imported not in owner_paths: - continue - edges.append( - { - "from": f"O:{imported}", - "kind": "test", - "to": f"O:{importer}", - "location": location(importer, edge.line, edge.column), - "fields": {"import": edge.import_name, "scope": edge.scope or "module"}, - } - ) - return edges - - -def _owner_role(node: PythonReasoningTreeNode, path: str) -> str: - if is_test_path(path): - return "test" - if node.parent_namespace is None: - return f"root,{node.kind}" - if node.has_public_surface: - return f"public,{node.kind}" - return node.kind diff --git a/src/python_lang_project_harness/_semantic_search_packages.py b/src/python_lang_project_harness/_semantic_search_packages.py deleted file mode 100644 index 6945cdd..0000000 --- a/src/python_lang_project_harness/_semantic_search_packages.py +++ /dev/null @@ -1,108 +0,0 @@ -"""Workspace package and dependency facts for Python semantic search.""" - -from __future__ import annotations - -from pathlib import Path -from typing import TYPE_CHECKING, Any - -from ._semantic_search_common import semantic_search_display_path -from ._semantic_search_model import MAX_WORKSPACE_PACKAGES -from .verification.facts import is_test_path - -if TYPE_CHECKING: - from collections.abc import Sequence - - from python_lang_parser import PythonReasoningTreeFacts - - from ._model import PythonHarnessReport - - -def project_name(facts: PythonReasoningTreeFacts) -> str: - """Return the project package name used in packet headers.""" - - metadata = facts.project_metadata - if metadata is None or metadata.project_name is None: - return "python-project" - return metadata.project_name - - -def dependencies(facts: PythonReasoningTreeFacts): - """Return parser-owned project dependency declarations.""" - - metadata = facts.project_metadata - return () if metadata is None else metadata.dependencies - - -def workspace_packages( - report: PythonHarnessReport, - facts: PythonReasoningTreeFacts, - project_root: Path, - owner_count: int, -) -> list[dict[str, Any]]: - """Return ranked workspace package/root facts.""" - - metadata = facts.project_metadata - packages: list[dict[str, Any]] = [ - { - "id": ".", - "fields": { - "name": project_name(facts), - "role": "workspace-root", - "packages": owner_count, - "dependencies": len(dependencies(facts)), - "next": ["prime:."], - }, - } - ] - roots = () if metadata is None else metadata.package_roots - for path in roots: - shown = semantic_search_display_path(path, project_root) - packages.append(_workspace_package(shown, name=Path(shown).name)) - if len(packages) == 1 and report.project_resolution is not None: - packages.extend( - _workspace_package( - semantic_search_display_path(path, project_root), name=path.name - ) - for path in report.project_resolution.source_paths - ) - return _dedupe_packages(packages)[:MAX_WORKSPACE_PACKAGES] - - -def _workspace_package(path: str, *, name: str) -> dict[str, Any]: - return { - "id": path, - "fields": { - "name": name, - "role": "workspace-package", - "surface": _workspace_package_surface(path), - "next": [f"prime:{path}"], - }, - } - - -def _workspace_package_surface(path: str) -> str: - if is_test_path(path): - return "test" - if path == "docs" or path.startswith("docs/"): - return "docs" - if path == "examples" or path.startswith(("examples/", "demo/", "demos/")): - return "demo" - return "source" - - -def _dedupe_packages(packages: Sequence[dict[str, Any]]) -> list[dict[str, Any]]: - seen: set[str] = set() - result = [] - for package in packages: - if package["id"] in seen: - continue - seen.add(package["id"]) - result.append(package) - return sorted( - result, - key=lambda package: ( - 0 if package["id"] == "." else 1, - package["id"].count("/"), - package["id"], - ), - ) diff --git a/src/python_lang_project_harness/_semantic_search_packet.py b/src/python_lang_project_harness/_semantic_search_packet.py deleted file mode 100644 index cf76261..0000000 --- a/src/python_lang_project_harness/_semantic_search_packet.py +++ /dev/null @@ -1,195 +0,0 @@ -"""Top-level semantic-search packet assembly for the Python provider.""" - -from __future__ import annotations - -from typing import TYPE_CHECKING, Any - -from . import _semantic_language_ids as ids -from ._semantic_search_model import PythonSemanticSearchOptions -from ._semantic_search_views import payload_for_view -from .verification.facts import ( - verification_project_root, - verification_reasoning_tree_facts, -) - -if TYPE_CHECKING: - from ._model import PythonHarnessReport - - -def build_python_semantic_search_packet( - report: PythonHarnessReport, - options: PythonSemanticSearchOptions, -) -> dict[str, Any]: - """Build a language-neutral semantic-search packet from Python parser facts.""" - - facts = verification_reasoning_tree_facts(report) - project_root = verification_project_root(report) - payload = payload_for_view(report, facts, project_root, options) - packet = _base_python_search_packet(project_root, options, payload) - _attach_query(packet, options) - _attach_query_set(packet, options) - _attach_package_name(packet, facts) - _attach_reasoning_profiles(packet, options) - _attach_runtime_cost(packet, options) - _attach_payload_optionals(packet, payload) - _attach_syntax_refs(packet, payload) - return _normalize_packet_locations(packet) - - -def _attach_query(packet: dict[str, Any], options: PythonSemanticSearchOptions) -> None: - if options.query is not None: - packet["query"] = options.query - - -def _attach_query_set( - packet: dict[str, Any], options: PythonSemanticSearchOptions -) -> None: - query_terms = [ - { - "value": term, - "kind": "text", - "selector": "lexical" if options.view == "lexical" else "exact", - } - for term in _normalized_query_set(options.query_set) - ] - if query_terms: - packet["querySet"] = query_terms - scope = ( - {"ownerPath": options.owner_path} - if options.owner_path is not None - else None - ) - packet["queryComposition"] = { - "mode": "query-set", - "view": options.view, - "selector": "lexical-set" if options.view == "lexical" else "exact-set", - **({} if scope is None else {"scope": scope}), - "merge": [ - "nodes", - "edges", - "owners", - "hits", - "typeSurfaces", - "nextActions", - "notes", - ], - } - - -def _attach_package_name(packet: dict[str, Any], facts: Any) -> None: - if facts.project_metadata is not None and facts.project_metadata.project_name: - packet["packageName"] = facts.project_metadata.project_name - - -def _attach_reasoning_profiles( - packet: dict[str, Any], options: PythonSemanticSearchOptions -) -> None: - if (options.render_mode or "both") in {"graph", "seeds", "both", "facts"}: - from ._semantic_search_profiles import python_reasoning_profiles - - packet["reasoningProfiles"] = python_reasoning_profiles() - - -def _attach_runtime_cost( - packet: dict[str, Any], options: PythonSemanticSearchOptions -) -> None: - if options.runtime_cost is not None: - packet["runtimeCost"] = options.runtime_cost - packet["notes"] = [ - *packet["notes"], - { - "kind": "runtime-prefilter", - "message": str(options.runtime_cost.get("reason", "")), - "fields": options.runtime_cost.get("fields", {}), - }, - ] - - -def _attach_payload_optionals(packet: dict[str, Any], payload: dict[str, Any]) -> None: - for optional_key in ( - "inputDetection", - "packages", - "items", - "typeSurfaces", - "semanticHandles", - "queryCoverage", - "ownerResolution", - "runtimeCost", - "searchSynthesis", - "avoidNextActions", - ): - if payload.get(optional_key) is not None: - packet[optional_key] = payload[optional_key] - - -def _attach_syntax_refs(packet: dict[str, Any], payload: dict[str, Any]) -> None: - items = payload.get("items") - if not isinstance(items, list): - return - - from ._semantic_syntax_refs import ( - annotate_python_owner_item_syntax_refs, - attach_python_syntax_refs, - ) - - syntax_refs = annotate_python_owner_item_syntax_refs(items) - attach_python_syntax_refs(packet, syntax_refs) - - -def _base_python_search_packet( - project_root: Any, - options: PythonSemanticSearchOptions, - payload: dict[str, Any], -) -> dict[str, Any]: - return { - "schemaId": ids.SEMANTIC_SEARCH_PACKET_SCHEMA_ID, - "schemaVersion": "1", - "protocolId": ids.SEMANTIC_LANGUAGE_PROTOCOL_ID, - "protocolVersion": ids.SEMANTIC_LANGUAGE_PROTOCOL_VERSION, - "languageId": ids.PYTHON_LANGUAGE_ID, - "providerId": ids.PYTHON_PROVIDER_ID, - "binary": ids.PYTHON_BINARY, - "namespace": ids.PYTHON_PROVIDER_NAMESPACE, - "method": f"search/{options.view}", - "projectRoot": str(project_root), - "view": options.view, - "renderMode": options.render_mode or "both", - "header": payload["header"], - "nodes": payload.get("nodes", []), - "edges": payload.get("edges", []), - "owners": payload.get("owners", []), - "hits": payload.get("hits", []), - "findings": payload.get("findings", []), - "nextActions": payload.get("nextActions", []), - "notes": payload.get("notes", []), - } - - -def _normalize_packet_locations(value: Any) -> Any: - if isinstance(value, list): - return [_normalize_packet_locations(item) for item in value] - if not isinstance(value, dict): - return value - - normalized = {key: _normalize_packet_locations(item) for key, item in value.items()} - line = normalized.pop("line", None) - if "path" in normalized and isinstance(line, int): - end_line = normalized.pop("endLine", None) - normalized.pop("column", None) - normalized.pop("endColumn", None) - if not isinstance(end_line, int): - end_line = line - normalized["lineRange"] = f"{line}:{end_line}" - return normalized - - -def _normalized_query_set(query_set: tuple[str, ...]) -> list[str]: - terms: list[str] = [] - seen: set[str] = set() - for raw_term in query_set: - term = raw_term.strip() - if not term or term in seen: - continue - seen.add(term) - terms.append(term) - return terms diff --git a/src/python_lang_project_harness/_semantic_search_policy.py b/src/python_lang_project_harness/_semantic_search_policy.py deleted file mode 100644 index 6c736ff..0000000 --- a/src/python_lang_project_harness/_semantic_search_policy.py +++ /dev/null @@ -1,231 +0,0 @@ -"""Own semantic search policy findings for Python agent search and repair.""" - -from __future__ import annotations - -from pathlib import Path -from typing import Any - -from ._agent_policy_catalog import python_agent_policy_rules -from ._model import PythonHarnessReport, PythonHarnessRule -from ._project_policy_catalog import python_project_policy_rules -from ._semantic_search_common import compact_fields, header, path_hit - -PROJECT_POLICY_CATALOG_OWNER = ( - "src/python_lang_project_harness/_project_policy_catalog.py" -) -AGENT_POLICY_CATALOG_OWNER = "src/python_lang_project_harness/_agent_policy_catalog.py" - -PROJECT_POLICY_TEST_PATHS = ( - "tests/unit/harness/project_policy/test_catalog.py", - "tests/unit/harness/project_policy/test_layout.py", - "tests/unit/harness/test_policy_contract.py", - "tests/unit/harness/test_policy_snapshots.py", -) -AGENT_POLICY_TEST_PATHS = ( - "tests/unit/harness/test_agent_policy.py", - "tests/unit/harness/test_agent_policy_snapshots.py", - "tests/unit/harness/test_policy_contract.py", -) - - -def policy_payload( - report: PythonHarnessReport, - facts: Any, - project_root: Path, - query: str, - *, - pipes: tuple[str, ...] = (), -) -> dict[str, Any]: - """Build semantic handles for provider-owned Python policy rules.""" - del report, facts, project_root - handles = [ - handle for handle in _policy_handles() if _matches_policy_query(handle, query) - ] - owner_paths = _handle_owner_paths(handles) - test_paths = _handle_test_paths(handles) - hits = [ - path_hit(path, path, kind="policy", score=5, reason="policy-handle") - for path in owner_paths - ] - return { - "header": header( - "policy", - { - "q": query, - "handle": len(handles), - "owner": len(owner_paths), - "tests": len(test_paths), - "pipes": list(pipes), - }, - ), - "semanticHandles": handles, - "hits": hits, - "nextActions": _policy_next_actions(owner_paths, test_paths), - "queryCoverage": [ - { - "value": query, - "kind": "custom", - "selector": "exact", - "status": "hit" if handles else "miss", - "hitCount": len(handles), - "ownerPaths": owner_paths, - "fields": {"selectedHits": len(handles)}, - } - ], - "searchSynthesis": { - "algorithm": "policy-handle-catalog", - "scope": "policy", - "summary": ( - "resolved provider-owned policy handles" - if handles - else "no provider-owned policy handle matched query" - ), - "selectedOwners": len(owner_paths), - "testFrontier": test_paths, - }, - "notes": [] if handles else [{"kind": "policy-not-found", "message": query}], - } - - -def _policy_handles() -> list[dict[str, Any]]: - handles: list[dict[str, Any]] = [] - handles.extend( - _rule_handle( - rule, - owner_path=PROJECT_POLICY_CATALOG_OWNER, - test_paths=PROJECT_POLICY_TEST_PATHS, - domain="project-policy", - ) - for rule in python_project_policy_rules() - ) - handles.extend( - _rule_handle( - rule, - owner_path=AGENT_POLICY_CATALOG_OWNER, - test_paths=AGENT_POLICY_TEST_PATHS, - domain="agent-policy", - ) - for rule in python_agent_policy_rules() - ) - return handles - - -def _rule_handle( - rule: PythonHarnessRule, - *, - owner_path: str, - test_paths: tuple[str, ...], - domain: str, -) -> dict[str, Any]: - rule_terms = _rule_query_terms(rule) - return { - "id": rule.rule_id, - "kind": "policy-rule", - "source": "provider-policy", - "title": rule.title, - "languageName": "python", - "qualifiedName": f"{rule.pack_id}.{rule.rule_id}", - "aliases": _rule_aliases(rule), - "labels": sorted({domain, *rule.labels.values()}), - "status": "advisory", - "ownerPath": owner_path, - "testPaths": list(test_paths), - "locations": [{"path": owner_path}], - "queryTerms": rule_terms, - "fields": compact_fields( - { - "packId": rule.pack_id, - "severity": rule.severity.value, - "requirement": rule.requirement, - } - ), - } - - -def _rule_aliases(rule: PythonHarnessRule) -> list[str]: - return sorted( - { - rule.rule_id.lower(), - rule.rule_id.replace("-", "_"), - rule.rule_id.lower().replace("-", "_"), - rule.pack_id, - rule.labels.get("domain", ""), - } - - {""} - ) - - -def _rule_query_terms(rule: PythonHarnessRule) -> list[str]: - return sorted( - { - rule.rule_id, - rule.rule_id.lower(), - rule.rule_id.replace("-", "_"), - rule.pack_id, - rule.title, - rule.requirement, - *rule.labels.values(), - } - - {""} - ) - - -def _matches_policy_query(handle: dict[str, Any], query: str) -> bool: - needle = query.casefold().strip() - if not needle: - return True - haystack = [ - handle["id"], - handle["title"], - handle.get("qualifiedName", ""), - *handle.get("aliases", []), - *handle.get("labels", []), - *handle.get("queryTerms", []), - *(str(value) for value in handle.get("fields", {}).values()), - ] - return any(needle in value.casefold() for value in haystack if value) - - -def _handle_owner_paths(handles: list[dict[str, Any]]) -> list[str]: - return _dedupe( - [ - path - for handle in handles - for path in ( - handle.get("implementationOwnerPath"), - handle.get("ownerPath"), - ) - if isinstance(path, str) - ] - ) - - -def _handle_test_paths(handles: list[dict[str, Any]]) -> list[str]: - return _dedupe( - [ - path - for handle in handles - for path in handle.get("testPaths", []) - if isinstance(path, str) - ] - ) - - -def _policy_next_actions( - owner_paths: list[str], - test_paths: list[str], -) -> list[dict[str, str]]: - actions = [{"kind": "owner", "target": path} for path in owner_paths] - actions.extend({"kind": "tests", "target": path} for path in test_paths) - return actions[:8] - - -def _dedupe(values: list[str]) -> list[str]: - seen: set[str] = set() - unique: list[str] = [] - for value in values: - if value in seen: - continue - seen.add(value) - unique.append(value) - return unique diff --git a/src/python_lang_project_harness/_semantic_search_prefilter.py b/src/python_lang_project_harness/_semantic_search_prefilter.py deleted file mode 100644 index bc288f1..0000000 --- a/src/python_lang_project_harness/_semantic_search_prefilter.py +++ /dev/null @@ -1,152 +0,0 @@ -"""Fast candidate-file pruning for Python semantic lexical search.""" - -from __future__ import annotations - -import shutil -import time -from pathlib import Path -from typing import TYPE_CHECKING - -from ._semantic_search_prefilter_file_scan import ( - python_file_path_matches_by_term, -) -from ._semantic_search_prefilter_path import path_only_term_capped_matches -from ._semantic_search_prefilter_result import ( - MIN_PREFILTER_FILES, - PythonSearchPrefilterResult, -) -from ._semantic_search_prefilter_select import ( - normalized_terms, - selected_paths, - source_matched_files, - source_only_term_capped_matches, - term_capped_matches, -) -from ._semantic_search_prefilter_tools import ( - source_match_scores_by_term, -) - -if TYPE_CHECKING: - from collections.abc import Sequence - - -def prefilter_python_text_search_paths( - project_root: Path, - query_terms: Sequence[str], - *, - owner_path: str | None = None, -) -> PythonSearchPrefilterResult | None: - """Return parser input files preselected by path and source text.""" - - terms = normalized_terms(query_terms) - rg = shutil.which("rg") - if not terms and owner_path is None: - return None - return _prefilter_with_tools( - project_root, - rg, - terms, - owner_path=owner_path, - ) - - -def _prefilter_with_tools( - project_root: Path, - rg: str | None, - terms: tuple[str, ...], - *, - owner_path: str | None, -) -> PythonSearchPrefilterResult | None: - started = time.perf_counter() - path_match_scan = python_file_path_matches_by_term( - project_root, - terms, - rg=rg, - ) - path_only_term_capped = path_only_term_capped_matches( - project_root, - terms, - path_match_scan, - ) - if path_only_term_capped is not None: - selected = selected_paths( - project_root, - path_only_term_capped, - terms, - owner_path, - ) - elapsed_ms = round((time.perf_counter() - started) * 1000) - return PythonSearchPrefilterResult( - paths=selected, - total_files=path_match_scan.total_files, - term_capped_files=len(set(path_only_term_capped)), - matched_files=len(selected), - elapsed_ms=elapsed_ms, - tool=path_match_scan.tool, - reason=( - f"{path_match_scan.tool} path prefilter selected parser input files " - "for lexical query" - ), - query_terms=len(terms), - source_search_passes=0, - file_list_passes=1, - candidate_file_basis="path-matched-files", - ) - - source_scores_by_term = source_match_scores_by_term(project_root, rg, terms) - source_tool = "rg" if rg is not None else "rglob-source" - matched_source_files = source_matched_files(source_scores_by_term) - source_only_term_capped = source_only_term_capped_matches( - project_root, - terms, - source_scores_by_term, - matched_source_files, - ) - if source_only_term_capped is not None: - selected = selected_paths( - project_root, - source_only_term_capped, - terms, - owner_path, - ) - elapsed_ms = round((time.perf_counter() - started) * 1000) - return PythonSearchPrefilterResult( - paths=selected, - total_files=len(matched_source_files), - term_capped_files=len(set(source_only_term_capped)), - matched_files=len(selected), - elapsed_ms=elapsed_ms, - tool=source_tool, - reason=f"{source_tool} source prefilter selected parser input files for lexical query", - query_terms=len(terms), - source_search_passes=1 if terms else 0, - file_list_passes=0, - candidate_file_basis="source-matched-files", - ) - - if path_match_scan.total_files <= MIN_PREFILTER_FILES: - return None - term_capped = term_capped_matches( - project_root, - terms, - path_match_scan.matches_by_term, - source_scores_by_term, - ) - selected = selected_paths(project_root, term_capped, terms, owner_path) - elapsed_ms = round((time.perf_counter() - started) * 1000) - return PythonSearchPrefilterResult( - paths=selected, - total_files=path_match_scan.total_files, - term_capped_files=len(set(term_capped)), - matched_files=len(selected), - elapsed_ms=elapsed_ms, - tool=f"{path_match_scan.tool}+{source_tool}", - reason=( - f"{path_match_scan.tool}/{source_tool} prefilter selected parser input " - "files for lexical query" - ), - query_terms=len(terms), - source_search_passes=1 if terms else 0, - file_list_passes=1, - candidate_file_basis="all-python-files", - ) diff --git a/src/python_lang_project_harness/_semantic_search_prefilter_file_scan.py b/src/python_lang_project_harness/_semantic_search_prefilter_file_scan.py deleted file mode 100644 index 67dcc03..0000000 --- a/src/python_lang_project_harness/_semantic_search_prefilter_file_scan.py +++ /dev/null @@ -1,245 +0,0 @@ -"""Python file-list scanning for semantic-search prefilters.""" - -from __future__ import annotations - -import shutil -from dataclasses import dataclass -from pathlib import Path -from typing import TYPE_CHECKING - -from ._constants import IGNORED_DIR_NAMES, INCLUDE_HIDDEN_DIR_NAMES -from ._semantic_search_prefilter_process import run_prefilter_command - -if TYPE_CHECKING: - from collections.abc import Sequence - - -@dataclass(frozen=True, slots=True) -class PythonFilePathMatchScan: - """A single file-list pass with path matches grouped by query term.""" - - total_files: int - matches_by_term: dict[str, set[str]] - tool: str - - -def list_python_files( - project_root: Path, - *, - ignored_dir_names: frozenset[str] = IGNORED_DIR_NAMES, - include_hidden_dir_names: frozenset[str] = INCLUDE_HIDDEN_DIR_NAMES, -) -> tuple[Path, ...]: - """Return scannable Python files below a project root.""" - - fd = shutil.which("fd") or shutil.which("fdfind") - if fd is not None: - process = run_prefilter_command( - _fd_python_files_command(fd, ignored_dir_names), - cwd=project_root, - ) - if process.returncode == 0: - return trusted_tool_paths_from_output(project_root, process.stdout) - return tuple( - sorted( - ( - path.resolve() - for path in project_root.rglob("*.py") - if not _ignored( - path, project_root, ignored_dir_names, include_hidden_dir_names - ) - ), - key=lambda path: path.as_posix(), - ) - ) - - -def python_file_path_matches_by_term( - project_root: Path, - terms: Sequence[str], - *, - rg: str | None = None, - ignored_dir_names: frozenset[str] = IGNORED_DIR_NAMES, - include_hidden_dir_names: frozenset[str] = INCLUDE_HIDDEN_DIR_NAMES, -) -> PythonFilePathMatchScan: - """Return Python file counts and path matches from one file-list pass.""" - - normalized_terms = tuple(dict.fromkeys(term for term in terms if term)) - if rg is not None: - process = run_prefilter_command( - _rg_python_files_command(rg, ignored_dir_names), - cwd=project_root, - ) - if process.returncode == 0: - return _path_match_scan_from_output( - project_root, - process.stdout, - normalized_terms, - tool="rg", - ) - fd = shutil.which("fd") or shutil.which("fdfind") - if fd is not None: - process = run_prefilter_command( - _fd_python_files_command(fd, ignored_dir_names), - cwd=project_root, - ) - if process.returncode == 0: - return _path_match_scan_from_output( - project_root, - process.stdout, - normalized_terms, - tool="fd+rg", - ) - return _path_match_scan_from_rglob( - project_root, - normalized_terms, - ignored_dir_names=ignored_dir_names, - include_hidden_dir_names=include_hidden_dir_names, - ) - - -def paths_from_output(project_root: Path, stdout: str) -> tuple[Path, ...]: - """Return existing Python files named by command output.""" - - paths: list[Path] = [] - for line in stdout.splitlines(): - if not line: - continue - path = Path(line) - if not path.is_absolute(): - path = project_root / path - resolved = path.resolve() - if resolved.is_file() and resolved.suffix == ".py": - paths.append(resolved) - return tuple(sorted(set(paths), key=lambda path: path.as_posix())) - - -def trusted_tool_paths_from_output(project_root: Path, stdout: str) -> tuple[Path, ...]: - """Return Python files from a trusted fd/rg file-list command.""" - - paths = [ - path if path.is_absolute() else project_root / path - for line in stdout.splitlines() - if line - for path in (Path(line),) - ] - return tuple(sorted(set(paths), key=lambda path: path.as_posix())) - - -def _path_match_scan_from_output( - project_root: Path, - stdout: str, - terms: Sequence[str], - *, - tool: str, -) -> PythonFilePathMatchScan: - folded_terms = tuple((term, term.casefold()) for term in terms) - matches_by_term: dict[str, set[str]] = {term: set() for term in terms} - total_files = 0 - for line in stdout.splitlines(): - if not line: - continue - total_files += 1 - for term, folded_term in folded_terms: - if _path_contains_term(line, folded_term): - matches_by_term[term].add(line) - return PythonFilePathMatchScan( - total_files=total_files, - matches_by_term=matches_by_term, - tool=tool, - ) - - -def _path_match_scan_from_rglob( - project_root: Path, - terms: Sequence[str], - *, - ignored_dir_names: frozenset[str], - include_hidden_dir_names: frozenset[str], -) -> PythonFilePathMatchScan: - folded_terms = tuple((term, term.casefold()) for term in terms) - matches_by_term: dict[str, set[str]] = {term: set() for term in terms} - total_files = 0 - for path in project_root.rglob("*.py"): - if _ignored(path, project_root, ignored_dir_names, include_hidden_dir_names): - continue - total_files += 1 - relative = path.relative_to(project_root).as_posix() - for term, folded_term in folded_terms: - if _path_contains_term(relative, folded_term): - matches_by_term[term].add(relative) - return PythonFilePathMatchScan( - total_files=total_files, - matches_by_term=matches_by_term, - tool="rglob", - ) - - -def _path_contains_term(path: str, folded_term: str) -> bool: - folded_path = path.casefold() - if folded_term in folded_path: - return True - compact_term = _compact_path_token(folded_term) - return len(compact_term) >= 3 and compact_term in _compact_path_token(folded_path) - - -def _compact_path_token(value: str) -> str: - return "".join(char for char in value if char.isalnum()) - - -def _fd_python_files_command(fd: str, ignored_dir_names: frozenset[str]) -> list[str]: - return [ - fd, - "--color", - "never", - "-t", - "f", - "-e", - "py", - *(_fd_excludes(ignored_dir_names)), - ".", - ".", - ] - - -def _rg_python_files_command(rg: str, ignored_dir_names: frozenset[str]) -> list[str]: - return [ - rg, - "--color", - "never", - "--files", - "--glob", - "*.py", - *(_rg_excludes(ignored_dir_names)), - ".", - ] - - -def _fd_excludes(ignored_dir_names: frozenset[str]) -> tuple[str, ...]: - args: list[str] = [] - for name in sorted(ignored_dir_names): - args.extend(("-E", name)) - return tuple(args) - - -def _rg_excludes(ignored_dir_names: frozenset[str]) -> tuple[str, ...]: - args: list[str] = [] - for name in sorted(ignored_dir_names): - args.extend(("--glob", f"!{name}/**")) - return tuple(args) - - -def _ignored( - path: Path, - project_root: Path, - ignored_dir_names: frozenset[str], - include_hidden_dir_names: frozenset[str], -) -> bool: - return any( - part in ignored_dir_names - or ( - part.startswith(".") - and part not in {".", ".."} - and part not in include_hidden_dir_names - ) - for part in path.relative_to(project_root).parts - ) diff --git a/src/python_lang_project_harness/_semantic_search_prefilter_path.py b/src/python_lang_project_harness/_semantic_search_prefilter_path.py deleted file mode 100644 index 1307576..0000000 --- a/src/python_lang_project_harness/_semantic_search_prefilter_path.py +++ /dev/null @@ -1,31 +0,0 @@ -"""Path-only candidate pruning for Python semantic-search prefilters.""" - -from __future__ import annotations - -from pathlib import Path - -from ._semantic_search_prefilter_file_scan import PythonFilePathMatchScan -from ._semantic_search_prefilter_rank import ranked_term_matches - - -def path_only_term_capped_matches( - project_root: Path, - terms: tuple[str, ...], - path_match_scan: PythonFilePathMatchScan, -) -> tuple[str, ...] | None: - """Return path-ranked matches when every query term appears in owner paths.""" - - if not terms or not all( - path_match_scan.matches_by_term.get(term) for term in terms - ): - return None - matched: list[str] = [] - for term in terms: - term_matches = ranked_term_matches( - project_root, - term, - path_match_scan.matches_by_term.get(term, set()), - {}, - ) - matched.extend(path for path, _score in term_matches) - return tuple(matched) diff --git a/src/python_lang_project_harness/_semantic_search_prefilter_process.py b/src/python_lang_project_harness/_semantic_search_prefilter_process.py deleted file mode 100644 index 330e311..0000000 --- a/src/python_lang_project_harness/_semantic_search_prefilter_process.py +++ /dev/null @@ -1,26 +0,0 @@ -"""Subprocess helpers for semantic-search prefilter tools.""" - -from __future__ import annotations - -import subprocess -from pathlib import Path - - -def run_prefilter_command( - command: list[str], - *, - cwd: Path, -) -> subprocess.CompletedProcess[str]: - """Run one bounded filesystem prefilter command.""" - - try: - return subprocess.run( - command, - cwd=cwd, - text=True, - capture_output=True, - timeout=5, - check=False, - ) - except subprocess.TimeoutExpired: - return subprocess.CompletedProcess(command, 124, "", "") diff --git a/src/python_lang_project_harness/_semantic_search_prefilter_rank.py b/src/python_lang_project_harness/_semantic_search_prefilter_rank.py deleted file mode 100644 index 1819247..0000000 --- a/src/python_lang_project_harness/_semantic_search_prefilter_rank.py +++ /dev/null @@ -1,110 +0,0 @@ -"""Ranking and caps for Python semantic-search prefilter candidates.""" - -from __future__ import annotations - -from pathlib import Path -from typing import TYPE_CHECKING - -if TYPE_CHECKING: - from collections.abc import Sequence - -MAX_PREFILTER_FILES_PER_TERM = 2 -MAX_PREFILTER_FILES_TOTAL = 6 - - -def path_matches( - project_root: Path, - paths: Sequence[Path], - terms: Sequence[str], -) -> set[Path]: - """Return files whose owner path matches at least one query term.""" - - folded_terms = tuple(term.casefold() for term in terms) - matches: set[Path] = set() - for path in paths: - relative = relative_posix(path, project_root).casefold() - if any(term in relative for term in folded_terms): - matches.add(path) - return matches - - -def ranked_term_matches( - _project_root: Path, - term: str, - path_match_set: set[str], - source_scores: dict[str, int], -) -> list[tuple[str, int]]: - """Return one term's candidates ranked before parser parsing.""" - - best_scores = {path: 1 for path in path_match_set} - for path, score in source_scores.items(): - best_scores[path] = min(best_scores.get(path, score), score) - return sorted( - best_scores.items(), - key=lambda item: (item[1], path_key_rank(item[0], (term,))), - ) - - -def ranked_capped_matches( - _project_root: Path, - paths: Sequence[str], -) -> tuple[str, ...]: - """Return globally capped candidate paths.""" - - return tuple(sorted(set(paths), key=lambda path: path_key_rank(path, ())))[ - :MAX_PREFILTER_FILES_TOTAL - ] - - -def path_rank( - project_root: Path, - path: Path, - terms: Sequence[str], -) -> tuple[int, int, int, int, str]: - """Rank source owners before tests, shallow files, and long paths.""" - - relative = relative_posix(path, project_root) - return path_key_rank(relative, terms) - - -def path_key_rank( - relative: str, - terms: Sequence[str], -) -> tuple[int, int, int, int, str]: - """Rank project-relative source keys before path objects are needed.""" - - folded = relative.casefold() - folded_terms = tuple(term.casefold() for term in terms) - return ( - 1 if _is_test_path(relative) else 0, - 0 if folded_terms and any(term in folded for term in folded_terms) else 1, - relative.count("/"), - len(relative), - relative, - ) - - -def relative_posix(path: Path, project_root: Path) -> str: - """Return a stable path key relative to the search root.""" - - root_key = project_root.as_posix().rstrip("/") - path_key = path.as_posix() - if path_key == root_key: - return "." - prefix = f"{root_key}/" - if path_key.startswith(prefix): - return path_key.removeprefix(prefix) - try: - return path.relative_to(project_root).as_posix() - except ValueError: - return path_key - - -def _is_test_path(relative_path: str) -> bool: - return ( - relative_path == "tests" - or relative_path.startswith("tests/") - or "/tests/" in relative_path - or relative_path.startswith("test_") - or "/test_" in relative_path - ) diff --git a/src/python_lang_project_harness/_semantic_search_prefilter_result.py b/src/python_lang_project_harness/_semantic_search_prefilter_result.py deleted file mode 100644 index 44972a1..0000000 --- a/src/python_lang_project_harness/_semantic_search_prefilter_result.py +++ /dev/null @@ -1,54 +0,0 @@ -"""Runtime metadata for Python semantic-search prefilters.""" - -from __future__ import annotations - -from dataclasses import dataclass -from pathlib import Path - -from ._semantic_search_prefilter_rank import ( - MAX_PREFILTER_FILES_PER_TERM, - MAX_PREFILTER_FILES_TOTAL, -) - -MIN_PREFILTER_FILES = 128 - - -@dataclass(frozen=True, slots=True) -class PythonSearchPrefilterResult: - """Files selected before parser-owned fact extraction.""" - - paths: tuple[Path, ...] - total_files: int - term_capped_files: int - matched_files: int - elapsed_ms: int - tool: str - reason: str - query_terms: int - source_search_passes: int - file_list_passes: int - candidate_file_basis: str - - def runtime_cost(self) -> dict[str, object]: - """Return schema-owned runtime-cost metadata for the search packet.""" - - return { - "cacheStatus": "disabled", - "elapsedMs": self.elapsed_ms, - "sourceFilesParsed": self.matched_files, - "reason": self.reason, - "fields": { - "prefilterTool": self.tool, - "candidateFiles": self.total_files, - "minCandidateFiles": MIN_PREFILTER_FILES, - "termCappedFiles": self.term_capped_files, - "matchedFiles": self.matched_files, - "maxFilesPerTerm": MAX_PREFILTER_FILES_PER_TERM, - "maxFilesTotal": MAX_PREFILTER_FILES_TOTAL, - "mode": "text-query-prefilter", - "queryTerms": self.query_terms, - "sourceSearchPasses": self.source_search_passes, - "fileListPasses": self.file_list_passes, - "candidateFileBasis": self.candidate_file_basis, - }, - } diff --git a/src/python_lang_project_harness/_semantic_search_prefilter_select.py b/src/python_lang_project_harness/_semantic_search_prefilter_select.py deleted file mode 100644 index 1351c31..0000000 --- a/src/python_lang_project_harness/_semantic_search_prefilter_select.py +++ /dev/null @@ -1,109 +0,0 @@ -"""Candidate selection helpers for Python semantic-search prefilters.""" - -from __future__ import annotations - -from pathlib import Path -from typing import TYPE_CHECKING - -from ._semantic_search_prefilter_rank import ( - MAX_PREFILTER_FILES_PER_TERM, - path_key_rank, - ranked_capped_matches, - ranked_term_matches, -) -from ._semantic_search_prefilter_result import MIN_PREFILTER_FILES - -if TYPE_CHECKING: - from collections.abc import Sequence - - -def normalized_terms(query_terms: Sequence[str]) -> tuple[str, ...]: - return tuple(dict.fromkeys(term.strip() for term in query_terms if term.strip())) - - -def source_only_term_capped_matches( - project_root: Path, - terms: tuple[str, ...], - source_scores_by_term: dict[str, dict[str, int]], - source_matched_files: frozenset[str], -) -> tuple[str, ...] | None: - if not _source_only_prefilter_is_sufficient( - terms, - source_scores_by_term, - source_matched_files, - ): - return None - matched: list[str] = [] - for term in terms: - term_matches = ranked_term_matches( - project_root, - term, - set(), - source_scores_by_term.get(term, {}), - ) - matched.extend( - path for path, _score in term_matches[:MAX_PREFILTER_FILES_PER_TERM] - ) - return tuple(matched) - - -def term_capped_matches( - project_root: Path, - terms: tuple[str, ...], - path_matches_by_term: dict[str, set[str]], - source_scores_by_term: dict[str, dict[str, int]], -) -> tuple[str, ...]: - matched: list[str] = [] - for term in terms: - term_matches = ranked_term_matches( - project_root, - term, - path_matches_by_term.get(term, set()), - source_scores_by_term.get(term, {}), - ) - matched.extend( - path for path, _score in term_matches[:MAX_PREFILTER_FILES_PER_TERM] - ) - return tuple(matched) - - -def selected_paths( - project_root: Path, - term_capped: tuple[str, ...], - terms: tuple[str, ...], - owner_path: str | None, -) -> tuple[Path, ...]: - matched = ranked_capped_matches(project_root, term_capped) - if owner_path is not None: - owner_candidate = (project_root / owner_path).resolve() - if owner_candidate.is_file() and owner_candidate.suffix == ".py": - matched = (*matched, owner_path) - return tuple( - project_root / path - for path in sorted(set(matched), key=lambda path: path_key_rank(path, terms)) - ) - - -def source_matched_files( - source_scores_by_term: dict[str, dict[str, int]], -) -> frozenset[str]: - return frozenset( - path - for source_scores in source_scores_by_term.values() - for path in source_scores - ) - - -def _source_only_prefilter_is_sufficient( - terms: tuple[str, ...], - source_scores_by_term: dict[str, dict[str, int]], - source_matched_files: frozenset[str], -) -> bool: - return ( - bool(terms) - and len(source_matched_files) > MIN_PREFILTER_FILES - and all( - len(source_scores_by_term.get(term, {})) >= MAX_PREFILTER_FILES_PER_TERM - for term in terms - ) - ) diff --git a/src/python_lang_project_harness/_semantic_search_prefilter_tools.py b/src/python_lang_project_harness/_semantic_search_prefilter_tools.py deleted file mode 100644 index 31728a1..0000000 --- a/src/python_lang_project_harness/_semantic_search_prefilter_tools.py +++ /dev/null @@ -1,202 +0,0 @@ -"""Filesystem-backed candidate discovery for Python semantic search.""" - -from __future__ import annotations - -from pathlib import Path -from typing import TYPE_CHECKING - -if TYPE_CHECKING: - from collections.abc import Sequence - -from ._constants import IGNORED_DIR_NAMES -from ._semantic_search_prefilter_file_scan import list_python_files -from ._semantic_search_prefilter_process import run_prefilter_command - - -def source_match_scores( - project_root: Path, rg: str | None, term: str -) -> dict[str, int]: - """Return candidate files scored by parser-likely source hits.""" - - return source_match_scores_by_term(project_root, rg, (term,)).get(term, {}) - - -def source_match_scores_by_term( - project_root: Path, - rg: str | None, - terms: Sequence[str], - *, - ignored_dir_names: frozenset[str] = IGNORED_DIR_NAMES, -) -> dict[str, dict[str, int]]: - """Return source-hit scores for all query terms using one rg scan.""" - - normalized_terms = tuple(dict.fromkeys(term for term in terms if term)) - if not normalized_terms: - return {} - folded_terms = tuple((term, term.casefold()) for term in normalized_terms) - if rg is None: - return _source_match_scores_by_term_rglob( - project_root, - folded_terms, - ignored_dir_names, - ) - process = run_prefilter_command( - _rg_source_command(rg, normalized_terms, ignored_dir_names), - cwd=project_root, - ) - if process.returncode not in {0, 1}: - return {} - scores_by_term: dict[str, dict[str, int]] = {term: {} for term in normalized_terms} - for line in process.stdout.splitlines(): - _merge_source_line_scores( - scores_by_term, - project_root, - line, - folded_terms, - ) - return scores_by_term - - -def _source_match_scores_by_term_rglob( - project_root: Path, - folded_terms: Sequence[tuple[str, str]], - ignored_dir_names: frozenset[str], -) -> dict[str, dict[str, int]]: - scores_by_term: dict[str, dict[str, int]] = { - term: {} for term, _folded_term in folded_terms - } - for path in list_python_files(project_root, ignored_dir_names=ignored_dir_names): - relative = path.relative_to(project_root).as_posix() - _merge_source_file_scores(scores_by_term, relative, path, folded_terms) - return scores_by_term - - -def _merge_source_file_scores( - scores_by_term: dict[str, dict[str, int]], - relative_path: str, - path: Path, - folded_terms: Sequence[tuple[str, str]], -) -> None: - try: - with path.open("r", encoding="utf-8", errors="ignore") as source_file: - for source in source_file: - for term, folded_term in _matching_terms(source, folded_terms): - term_scores = scores_by_term[term] - score = _source_line_score(source, folded_term) - term_scores[relative_path] = min( - term_scores.get(relative_path, score), - score, - ) - except OSError: - return - - -def _merge_source_line_scores( - scores_by_term: dict[str, dict[str, int]], - project_root: Path, - line: str, - folded_terms: Sequence[tuple[str, str]], -) -> None: - parsed = _python_source_line(project_root, line) - if parsed is None: - return - resolved, source = parsed - for term, folded_term in _matching_terms(source, folded_terms): - term_scores = scores_by_term[term] - score = _source_line_score(source, folded_term) - term_scores[resolved] = min(term_scores.get(resolved, score), score) - - -def _python_source_line(_project_root: Path, line: str) -> tuple[str, str] | None: - path, source = _split_rg_line(line) - if path is None: - return None - if not path.endswith(".py"): - return None - return path, source - - -def _matching_terms( - source: str, - folded_terms: Sequence[tuple[str, str]], -) -> tuple[tuple[str, str], ...]: - folded_source = source.casefold() - return tuple( - (term, folded_term) - for term, folded_term in folded_terms - if folded_term in folded_source - ) - - -def _rg_source_command( - rg: str, - terms: Sequence[str], - ignored_dir_names: frozenset[str], -) -> list[str]: - expressions: list[str] = [] - for term in terms: - expressions.extend(("-e", term)) - return [ - rg, - "--color", - "never", - "-i", - "-F", - "-n", - "--max-count", - "50", - "--glob", - "*.py", - *(_rg_excludes(ignored_dir_names)), - *expressions, - ".", - ] - - -def _rg_excludes(ignored_dir_names: frozenset[str]) -> tuple[str, ...]: - args: list[str] = [] - for name in sorted(ignored_dir_names): - args.extend(("--glob", f"!{name}/**")) - return tuple(args) - - -def _split_rg_line(line: str) -> tuple[str | None, str]: - first = line.find(":") - if first == -1: - return None, "" - second = line.find(":", first + 1) - if second == -1: - return None, "" - return line[:first], line[second + 1 :] - - -def _source_line_score(source_line: str, folded_term: str) -> int: - stripped = source_line.lstrip() - folded = stripped.casefold() - if _starts_with_definition(folded, folded_term): - return 0 - if folded.startswith(folded_term) and folded[ - len(folded_term) : - ].lstrip().startswith("="): - return 1 - if "__all__" in source_line: - return 2 - return 3 - - -def _starts_with_definition(folded_source_line: str, folded_term: str) -> bool: - for prefix in ("class ", "def "): - if not folded_source_line.startswith(prefix): - continue - name_start = len(prefix) - if not folded_source_line.startswith(folded_term, name_start): - continue - name_end = name_start + len(folded_term) - return name_end >= len(folded_source_line) or not _is_identifier_character( - folded_source_line[name_end] - ) - return False - - -def _is_identifier_character(character: str) -> bool: - return character == "_" or character.isalnum() diff --git a/src/python_lang_project_harness/_semantic_search_prime_fast.py b/src/python_lang_project_harness/_semantic_search_prime_fast.py deleted file mode 100644 index 724332c..0000000 --- a/src/python_lang_project_harness/_semantic_search_prime_fast.py +++ /dev/null @@ -1,190 +0,0 @@ -"""Fast compact frontiers for Python search prime seed view.""" - -from __future__ import annotations - -import os -from collections import deque -from pathlib import Path - -from ._cli_args import ProtocolArgs - -_MAX_FAST_PRIME_OWNERS = 12 -_MAX_FAST_PRIME_DIRS = 128 -_MAX_FAST_PRIME_FILES = 512 -_SKIPPED_DIR_NAMES = frozenset( - { - ".cache", - ".git", - ".hg", - ".mypy_cache", - ".pytest_cache", - ".ruff_cache", - ".tox", - ".venv", - "__pycache__", - "build", - "dist", - "node_modules", - "target", - "venv", - } -) -_PREFERRED_DIRS = ( - "src", - "tests", - "test", - "scripts", - "tools", -) - - -def render_fast_prime_search(args: ProtocolArgs, project_root: Path) -> str | None: - """Render prime seeds without running the full project harness.""" - - if not _supports_fast_prime_search(args): - return None - owners = _discover_python_owner_paths(project_root) - return _render_fast_prime_seed_text(project_root, owners) - - -def _supports_fast_prime_search(args: ProtocolArgs) -> bool: - return ( - args.command == "search" - and args.view == "prime" - and args.render_mode == "seeds" - and not args.json - and args.query is None - and args.item_query is None - and args.owner_path is None - and not args.query_set - and not args.pipes - ) - - -def _discover_python_owner_paths(project_root: Path) -> tuple[str, ...]: - root = project_root.resolve() - owners: list[str] = [] - seen: set[Path] = set() - for start in _candidate_roots(root): - _scan_python_files(start, root=root, owners=owners, seen=seen) - if len(owners) >= _MAX_FAST_PRIME_OWNERS: - break - return tuple(owners[:_MAX_FAST_PRIME_OWNERS]) - - -def _candidate_roots(root: Path) -> tuple[Path, ...]: - selected: list[Path] = [] - for name in _PREFERRED_DIRS: - candidate = root / name - if candidate.exists(): - selected.append(candidate) - if root not in selected: - selected.append(root) - return tuple(selected) - - -def _scan_python_files( - start: Path, - *, - root: Path, - owners: list[str], - seen: set[Path], -) -> None: - if len(owners) >= _MAX_FAST_PRIME_OWNERS: - return - queue: deque[Path] = deque((start,)) - visited_dirs = 0 - visited_files = 0 - while queue and len(owners) < _MAX_FAST_PRIME_OWNERS: - directory = queue.popleft() - try: - resolved_dir = directory.resolve() - except OSError: - continue - if resolved_dir in seen: - continue - seen.add(resolved_dir) - if directory.name in _SKIPPED_DIR_NAMES: - continue - visited_dirs += 1 - if visited_dirs > _MAX_FAST_PRIME_DIRS: - return - try: - entries = list(os.scandir(directory)) - except OSError: - continue - dirs: list[Path] = [] - files: list[str] = [] - for entry in entries: - name = entry.name - if name.startswith(".") and name not in {"."}: - continue - try: - if entry.is_dir(follow_symlinks=False): - if name not in _SKIPPED_DIR_NAMES: - dirs.append(Path(entry.path)) - continue - if entry.is_file(follow_symlinks=False) and name.endswith(".py"): - files.append(entry.path) - except OSError: - continue - for file_path in sorted(files): - visited_files += 1 - if visited_files > _MAX_FAST_PRIME_FILES: - return - owners.append(_relative_owner_path(Path(file_path), root)) - if len(owners) >= _MAX_FAST_PRIME_OWNERS: - return - queue.extend(sorted(dirs, key=lambda path: path.as_posix())) - - -def _relative_owner_path(path: Path, root: Path) -> str: - try: - return path.relative_to(root).as_posix() - except ValueError: - return path.as_posix() - - -def _render_fast_prime_seed_text(project_root: Path, owners: tuple[str, ...]) -> str: - root_label = project_root.name or "." - lines = [ - ( - f"[search-prime] root={root_label} alg=fast-prime-frontier-v1 " - f"budget=owners:{_MAX_FAST_PRIME_OWNERS} mode=seeds" - ), - ( - "|decision purpose=decision-primer answer=false code=false " - "capabilities=pipe,lexical,fd-query,rg-query,owner-items,selector-code,treesitter-query " - "ladder=pipe>lexical>fd-query|rg-query>owner-items>selector-code " - "history=asp-artifacts:directReadRisk,repeatedPrime,repeatedPipe,bestPath " - "risk=broad-direct-read,manual-window-scan,repeat-prime " - "next=\"asp python search pipe '' --workspace . --view seeds\"" - ).replace( - "--workspace . --view seeds", "--workspace --view seeds" - ), - "legend: ID=kind:role(value)!next; entries profile(selectors=>returns); frontier ID.next", - "aliases: graph:{G=search,O=owner}", - ] - owner_ids = [f"O{index}" for index, _ in enumerate(owners, start=1)] - if owners: - lines.append( - ";".join( - f"{owner_id}=owner:path({owner})!owner" - for owner_id, owner in zip(owner_ids, owners, strict=True) - ) - ) - lines.append( - "G>{" + ",".join(f"{owner_id}:selects" for owner_id in owner_ids) + "}" - ) - else: - lines.append("G>{}") - rank = ",".join(owner_ids) - frontier = ",".join(f"{owner_id}.owner" for owner_id in owner_ids) - lines.extend( - [ - f"rank={rank} frontier={frontier}", - "entries=owner-tests(O=>covering-tests+test-entrypoints+fixtures)", - "omit=items,blocks,code,full-json reason=fast-seeds-frontier", - ] - ) - return "\n".join(lines) + "\n" diff --git a/src/python_lang_project_harness/_semantic_search_profiles.py b/src/python_lang_project_harness/_semantic_search_profiles.py deleted file mode 100644 index d3b7547..0000000 --- a/src/python_lang_project_harness/_semantic_search_profiles.py +++ /dev/null @@ -1,103 +0,0 @@ -"""Typed reasoning profile catalog for Python semantic search.""" - -from __future__ import annotations - -from typing import Any - - -def python_reasoning_profiles() -> list[dict[str, Any]]: - return [ - { - "profile": "owner-query", - "description": "Combine an owner and query term to find matching items, tests, and dependency usage.", - "selectors": [ - { - "kind": "owner", - "alias": "O", - "targetRole": "path", - "required": True, - }, - { - "kind": "query", - "alias": "Q", - "targetRole": "term", - "required": True, - }, - ], - "returns": ["items", "tests", "dependency-usage"], - "frontier": ["O.items", "Q.owner", "Q.tests"], - "fields": {"source": "search-guide"}, - }, - { - "profile": "query-deps", - "description": "Combine a query term and dependency handle to find owners, imports, usage, and tests.", - "selectors": [ - { - "kind": "query", - "alias": "Q", - "targetRole": "term", - "required": True, - }, - { - "kind": "dependency", - "alias": "D", - "targetRole": "pkg", - "required": True, - }, - ], - "returns": ["owners", "imports", "usage-tests"], - "frontier": ["Q.owner", "D.public-api", "D.tests"], - "fields": {"source": "search-guide"}, - }, - { - "profile": "owner-tests", - "description": "Use an owner to inspect covering tests, entrypoints, and fixtures.", - "selectors": [ - { - "kind": "owner", - "alias": "O", - "targetRole": "path", - "required": True, - }, - ], - "returns": ["covering-tests", "test-entrypoints", "fixtures"], - "frontier": ["O.tests", "T.owner"], - "fields": {"source": "search-guide"}, - }, - { - "profile": "finding-frontier", - "description": "Combine a finding and optional owner to find affected owners, tests, and verification actions.", - "selectors": [ - { - "kind": "finding", - "alias": "F", - "targetRole": "finding", - "required": True, - }, - { - "kind": "owner", - "alias": "O", - "targetRole": "path", - "required": False, - }, - ], - "returns": ["affected-owners", "tests", "verification-actions"], - "frontier": ["F.owner", "F.tests", "O.policy"], - "fields": {"source": "search-guide"}, - }, - { - "profile": "feature-cfg", - "description": "Use a feature or cfg gate to find guarded owners and verification surfaces.", - "selectors": [ - { - "kind": "feature", - "alias": "F", - "targetRole": "feature", - "required": True, - }, - ], - "returns": ["cfg-gates", "owners", "verification-surfaces"], - "frontier": ["F.cfg", "F.owner", "F.tests"], - "fields": {"source": "search-guide"}, - }, - ] diff --git a/src/python_lang_project_harness/_semantic_search_public_external_type_hits.py b/src/python_lang_project_harness/_semantic_search_public_external_type_hits.py deleted file mode 100644 index 085c6bc..0000000 --- a/src/python_lang_project_harness/_semantic_search_public_external_type_hits.py +++ /dev/null @@ -1,82 +0,0 @@ -"""Hit extraction for Python public external type search.""" - -from __future__ import annotations - -from pathlib import Path -from typing import TYPE_CHECKING, Any - -from ._semantic_search_common import dedupe_hits, location_from_source -from ._semantic_search_deps import module_owner_path -from ._semantic_search_public_external_type_imports import ( - dependency_import_contexts, - surface_import_context, -) -from ._semantic_search_public_external_type_model import ( - PublicExternalTypeImportContext, - PublicExternalTypeSurface, -) -from ._semantic_search_public_external_type_surfaces import public_type_surfaces - -if TYPE_CHECKING: - from python_lang_parser import PythonModuleReport - - from ._model import PythonHarnessReport - - -def public_external_type_hits( - report: PythonHarnessReport, - project_root: Path, - package: str, -) -> list[dict[str, Any]]: - """Return public API surfaces that expose a dependency type.""" - - hits = [ - hit - for module in report.modules - for hit in _module_public_external_type_hits(module, project_root, package) - ] - return dedupe_hits(hits) - - -def _module_public_external_type_hits( - module: PythonModuleReport, - project_root: Path, - package: str, -) -> list[dict[str, Any]]: - contexts = dependency_import_contexts(module, package) - if not contexts: - return [] - owner_path = module_owner_path(module, project_root) - return [ - _surface_hit(surface, owner_path, project_root, context, direct=direct) - for surface in public_type_surfaces(module) - for context, direct in [surface_import_context(surface, contexts)] - if context is not None - ] - - -def _surface_hit( - surface: PublicExternalTypeSurface, - owner_path: str, - project_root: Path, - context: PublicExternalTypeImportContext, - *, - direct: bool, -) -> dict[str, Any]: - return { - "kind": "api", - "ownerPath": owner_path, - "location": location_from_source(surface.location, project_root), - "score": 9 if direct else 4, - "reason": "public-external-type" if direct else "possible-public-external-type", - "symbol": surface.symbol, - "fields": { - "source": "native-parser", - "dependency": context.dependency, - "confidence": "direct" if direct else "possible", - "apiKind": surface.api_kind, - "surface": surface.surface, - "typeText": surface.type_text, - "importSpecifier": context.import_specifier, - }, - } diff --git a/src/python_lang_project_harness/_semantic_search_public_external_type_imports.py b/src/python_lang_project_harness/_semantic_search_public_external_type_imports.py deleted file mode 100644 index 0801957..0000000 --- a/src/python_lang_project_harness/_semantic_search_public_external_type_imports.py +++ /dev/null @@ -1,96 +0,0 @@ -"""Import-context matching for Python public external type search.""" - -from __future__ import annotations - -import re -from typing import TYPE_CHECKING - -from ._semantic_search_deps import import_root, normalize_dependency_name -from ._semantic_search_public_external_type_model import ( - PublicExternalTypeImportContext, - PublicExternalTypeSurface, -) - -if TYPE_CHECKING: - from python_lang_parser import PythonImport, PythonModuleReport - - -def dependency_import_contexts( - module: PythonModuleReport, - package: str, -) -> list[PublicExternalTypeImportContext]: - """Return imports in a module that refer to the queried dependency.""" - - return [ - context - for import_record in module.imports - for context in [_import_context(import_record, package)] - if context is not None - ] - - -def surface_import_context( - surface: PublicExternalTypeSurface, - contexts: list[PublicExternalTypeImportContext], -) -> tuple[PublicExternalTypeImportContext | None, bool]: - """Return the matching import context and whether attribution is direct.""" - - direct = next( - ( - context - for context in contexts - if any( - _contains_reference(surface.type_text, name) - for name in context.direct_names - ) - ), - None, - ) - if direct is not None: - return direct, True - possible = next( - ( - context - for context in contexts - if any( - _contains_reference(surface.type_text, name) - for name in context.imported_names - ) - ), - None, - ) - return (possible, False) if possible is not None else (None, False) - - -def _import_context( - import_record: PythonImport, - package: str, -) -> PublicExternalTypeImportContext | None: - root = import_root(import_record.module, import_record.source_names) - if root is None or normalize_dependency_name(root) != package: - return None - return PublicExternalTypeImportContext( - dependency=package, - import_specifier=import_record.module or root, - direct_names=_direct_import_names(import_record, root), - imported_names=tuple( - name - for name in (*import_record.names, *import_record.source_names) - if name != "*" - ), - ) - - -def _direct_import_names(import_record: PythonImport, root: str) -> tuple[str, ...]: - names = [root] - if import_record.module is None: - names.extend(import_record.names) - else: - names.append(import_record.module) - return tuple(dict.fromkeys(name for name in names if name and name != "*")) - - -def _contains_reference(text: str, name: str) -> bool: - if not name: - return False - return bool(re.search(rf"(? list[PublicExternalTypeSurface]: - """Return parser-owned public type surfaces for one module.""" - - surfaces: list[PublicExternalTypeSurface] = [] - for symbol in module.symbols: - if symbol.is_public and symbol.is_top_level: - surfaces.extend(_symbol_type_surfaces(module, symbol)) - for assignment in module.assignments: - if assignment.is_public and assignment.is_top_level: - surface = _assignment_type_surface(module, assignment) - if surface is not None: - surfaces.append(surface) - return surfaces - - -def _symbol_type_surfaces( - module: PythonModuleReport, - symbol: PythonSymbol, -) -> list[PublicExternalTypeSurface]: - surfaces: list[PublicExternalTypeSurface] = [] - header_text = _symbol_header_text(module, symbol) - if header_text: - surfaces.append( - PublicExternalTypeSurface( - symbol=symbol.qualified_name, - api_kind=symbol.kind.value, - surface="signature", - type_text=header_text, - location=symbol.location, - ) - ) - surfaces.extend( - PublicExternalTypeSurface( - symbol=symbol.qualified_name, - api_kind=symbol.kind.value, - surface="decorator", - type_text=decorator, - location=symbol.location, - ) - for decorator in symbol.decorators - ) - surfaces.extend( - PublicExternalTypeSurface( - symbol=symbol.qualified_name, - api_kind=symbol.kind.value, - surface="base", - type_text=base_class, - location=symbol.location, - ) - for base_class in symbol.base_classes - ) - return surfaces - - -def _assignment_type_surface( - module: PythonModuleReport, - assignment: PythonAssignmentTarget, -) -> PublicExternalTypeSurface | None: - source_line = module.source_line(assignment.location.line) - if source_line is None or ":" not in source_line: - return None - return PublicExternalTypeSurface( - symbol=assignment.name, - api_kind="assignment", - surface="annotation", - type_text=source_line.strip()[:200], - location=assignment.location, - ) - - -def _symbol_header_text(module: PythonModuleReport, symbol: PythonSymbol) -> str: - start = max(1, symbol.location.line) - end = min(symbol.end_line or start, start + 8) - lines = module.source_lines[start - 1 : end] - if not lines: - return "" - balance = 0 - selected: list[str] = [] - for line in lines: - stripped = line.strip() - selected.append(stripped) - balance += stripped.count("(") + stripped.count("[") + stripped.count("{") - balance -= stripped.count(")") + stripped.count("]") + stripped.count("}") - if stripped.endswith(":") and balance <= 0: - break - if len(selected) >= 8: - break - return " ".join(selected)[:240] diff --git a/src/python_lang_project_harness/_semantic_search_public_external_types.py b/src/python_lang_project_harness/_semantic_search_public_external_types.py deleted file mode 100644 index 44b8cf9..0000000 --- a/src/python_lang_project_harness/_semantic_search_public_external_types.py +++ /dev/null @@ -1,299 +0,0 @@ -"""Public API surfaces that expose external dependency types.""" - -from __future__ import annotations - -from pathlib import Path -from typing import TYPE_CHECKING, Any - -from ._semantic_search_common import ( - header, -) -from ._semantic_search_deps import ( - dependency_matches, - normalize_dependency_name, -) -from ._semantic_search_model import MAX_SYMBOL_HITS -from ._semantic_search_owners import owners_for_paths -from ._semantic_search_packages import dependencies -from ._semantic_search_public_external_type_hits import public_external_type_hits -from ._semantic_search_view_hits import hit_next_actions - -if TYPE_CHECKING: - from python_lang_parser import ( - PythonReasoningTreeFacts, - ) - - from ._model import PythonHarnessReport - - -def public_external_types_payload( - report: PythonHarnessReport, - facts: PythonReasoningTreeFacts, - project_root: Path, - query: str, -) -> dict[str, Any]: - """Build public external type/API payloads.""" - - package = normalize_dependency_name(query.strip()) - manifest_matches = _manifest_matches(facts, package) - hits = ( - [] - if not package - else public_external_type_hits(report, project_root, package)[:MAX_SYMBOL_HITS] - ) - type_surfaces = _public_external_type_surfaces(hits, package) - owners = owners_for_paths(facts, project_root, [hit["ownerPath"] for hit in hits]) - confirmed = sum(1 for hit in hits if hit["reason"] == "public-external-type") - possible = sum( - 1 for hit in hits if hit["reason"] == "possible-public-external-type" - ) - return { - "header": header( - "public-external-types", - { - "q": query, - "package": package, - "manifest": len(manifest_matches), - "own": len(owners), - "hit": len(hits), - "source": "native-parser", - "view": "hits", - }, - ), - "nodes": [ - { - "id": f"D:{package}", - "kind": "dependency", - "fields": { - "manifest": len(manifest_matches), - "confirmed": confirmed, - "possible": possible, - }, - } - ] - if package - else [], - "owners": owners, - "hits": hits, - "typeSurfaces": type_surfaces, - "nextActions": ( - ([] if not package else [{"kind": "dependency", "target": package}]) - + hit_next_actions(hits) - )[:8], - "notes": _public_external_type_notes(query, hits), - } - - -def _manifest_matches(facts: PythonReasoningTreeFacts, package: str): - return [item for item in dependencies(facts) if dependency_matches(item, package)] - - -def _public_external_type_surfaces( - hits: list[dict[str, Any]], - package: str, -) -> list[dict[str, Any]]: - surfaces: list[dict[str, Any]] = [] - for index, hit in enumerate(hits): - surface_doc = _public_external_type_surface(hit, package, index) - if surface_doc is None: - continue - surfaces.append(surface_doc) - return surfaces - - -def _public_external_type_surface( - hit: dict[str, Any], - package: str, - index: int, -) -> dict[str, Any] | None: - parts = _public_external_type_hit_parts(hit, package) - if parts is None: - return None - surface_doc = _public_external_type_surface_doc( - parts["owner_path"], - parts["name"], - parts["type_text"], - parts["surface"], - parts["api_kind"], - package, - parts["import_specifier"], - parts["fields"], - index, - ) - _attach_public_external_type_location(surface_doc, hit) - return surface_doc - - -def _public_external_type_hit_parts( - hit: dict[str, Any], - package: str, -) -> dict[str, Any] | None: - fields = hit.get("fields", {}) - if not isinstance(fields, dict): - return None - owner_path = _string_field(hit.get("ownerPath")) - if owner_path is None: - return None - symbol = _string_field(hit.get("symbol")) - surface = _string_field(fields.get("surface")) - api_kind = _string_field(fields.get("apiKind")) - type_text = _string_field(fields.get("typeText")) or symbol or "unknown" - import_specifier = _string_field(fields.get("importSpecifier")) or package - name = symbol or type_text - return { - "fields": fields, - "owner_path": owner_path, - "name": name, - "type_text": type_text, - "surface": surface, - "api_kind": api_kind, - "import_specifier": import_specifier, - } - - -def _attach_public_external_type_location( - surface_doc: dict[str, Any], - hit: dict[str, Any], -) -> None: - location = hit.get("location") - if isinstance(location, dict): - surface_doc["location"] = location - - -def _public_external_type_surface_doc( - owner_path: str, - name: str, - type_text: str, - surface: str | None, - api_kind: str | None, - package: str, - import_specifier: str, - fields: dict[Any, Any], - index: int, -) -> dict[str, Any]: - return { - "id": f"PY:{owner_path}:{name}:{surface or index}", - "name": name, - "languageName": name, - "qualifiedName": type_text, - "kind": _type_surface_kind(api_kind, surface), - "role": _type_surface_role(api_kind, surface), - "ownerPath": owner_path, - "visibility": "public", - "external": True, - "source": _string_field(fields.get("source")) or "native-parser", - "package": package, - "module": import_specifier, - "symbol": name, - "carrier": _public_external_type_carrier( - type_text, - api_kind, - package, - import_specifier, - ), - "fields": _type_surface_fields(fields, package), - } - - -def _public_external_type_carrier( - type_text: str, - api_kind: str | None, - package: str, - import_specifier: str, -) -> dict[str, Any]: - return { - "name": type_text, - "languageName": type_text, - "qualifiedName": type_text, - "carrier": _carrier_kind(type_text, api_kind), - "package": package, - "module": import_specifier, - "versionScope": "external", - "external": True, - } - - -def _type_surface_fields( - fields: dict[Any, Any], - package: str, -) -> dict[str, Any]: - surface_fields: dict[str, Any] = {"dependency": package} - for key, value in fields.items(): - if not isinstance(key, str): - continue - if isinstance(value, (str, int, float, bool)): - surface_fields[key] = value - elif isinstance(value, list) and all( - isinstance(item, (str, int, float, bool)) for item in value - ): - surface_fields[key] = value - return surface_fields - - -def _string_field(value: Any) -> str | None: - return value if isinstance(value, str) and value else None - - -def _type_surface_kind(api_kind: str | None, surface: str | None) -> str: - if api_kind == "class": - return "class" - if api_kind == "function": - return "function" - if api_kind == "type": - return "alias" - if surface and surface.startswith("field:"): - return "object" - return "unknown" - - -def _type_surface_role(api_kind: str | None, surface: str | None) -> str: - if surface and surface.startswith("param:"): - return "api-input" - if surface in {"return", "success"}: - return "api-output" - if surface == "error": - return "api-error" - if surface and surface.startswith("field:"): - return "api-field" - if surface == "alias" or api_kind == "type": - return "public-type-alias" - return "external-dependency" - - -def _carrier_kind(type_text: str, api_kind: str | None) -> str: - stripped = type_text.strip() - lowered = stripped.lower() - if "|" in stripped or lowered.startswith("typing.union["): - return "union" - if lowered.startswith(("list[", "tuple[", "set[", "frozenset[", "sequence[")): - return "array" - if lowered.startswith(("dict[", "mapping[", "mutablemapping[")): - return "map" - if api_kind == "class" or lowered.startswith("class "): - return "class" - if api_kind == "function" or lowered.startswith("def "): - return "function" - if stripped in {"str", "int", "float", "bool", "bytes", "None"}: - return "primitive" - return "external" - - -def _public_external_type_notes( - query: str, - hits: list[dict[str, Any]], -) -> list[dict[str, str]]: - if not query.strip(): - return [ - { - "kind": "empty-query", - "message": "public-external-types search requires a dependency package query", - } - ] - if not hits: - return [ - { - "kind": "not-found", - "message": f"public external type surfaces not found: {query}", - } - ] - return [] diff --git a/src/python_lang_project_harness/_semantic_search_reasoning.py b/src/python_lang_project_harness/_semantic_search_reasoning.py deleted file mode 100644 index acb6ee5..0000000 --- a/src/python_lang_project_harness/_semantic_search_reasoning.py +++ /dev/null @@ -1,126 +0,0 @@ -"""Typed reasoning entry payloads for Python semantic search.""" - -from __future__ import annotations - -from pathlib import Path -from typing import TYPE_CHECKING, Any - -from ._semantic_search_common import header -from ._semantic_search_model import PythonSemanticSearchOptions -from ._semantic_search_owners import owners_for_paths -from ._semantic_search_view_core import owner_payload -from ._semantic_search_view_deps_imports import dependency_payload -from ._semantic_search_view_hits import tests_payload - -if TYPE_CHECKING: - from python_lang_parser import PythonReasoningTreeFacts - - from ._model import PythonHarnessReport - - -_OWNER_TESTS_RETURNS = ["covering-tests", "test-entrypoints", "fixtures"] -_OWNER_QUERY_RETURNS = ["items", "tests", "dependency-usage"] -_QUERY_DEPS_RETURNS = ["owners", "imports", "usage-tests"] - - -def reasoning_payload( - report: PythonHarnessReport, - facts: PythonReasoningTreeFacts, - project_root: Path, - options: PythonSemanticSearchOptions, -) -> dict[str, Any]: - """Build payloads for explicit graph reasoning entries.""" - - profile = options.query or "" - match profile: - case "owner-tests": - owner = _required(options.owner_path, "--owner", profile) - payload = tests_payload(report, facts, project_root, owner) - _ensure_owner_selector(payload, facts, project_root, owner) - return _with_reasoning_header( - payload, - profile=profile, - owner_path=owner, - returns=_OWNER_TESTS_RETURNS, - ) - case "owner-query": - owner = _required(options.owner_path, "--owner", profile) - query = _required(options.item_query, "--query", profile) - payload = owner_payload( - report, - facts, - project_root, - owner, - pipes=("items",), - item_query=query, - ) - return _with_reasoning_header( - payload, - profile=profile, - owner_path=owner, - query=query, - returns=_OWNER_QUERY_RETURNS, - ) - case "query-deps": - query = _required(options.item_query, "--query", profile) - dependency = _required(options.dependency, "--dependency", profile) - dep_query = f"{dependency}::{query}" - payload = dependency_payload(report, facts, project_root, dep_query, "deps") - return _with_reasoning_header( - payload, - profile=profile, - query=query, - dependency=dependency, - returns=_QUERY_DEPS_RETURNS, - ) - case _: - raise ValueError( - "unknown reasoning profile: " - f"{profile}; expected owner-tests, owner-query, or query-deps" - ) - - -def _with_reasoning_header( - payload: dict[str, Any], - *, - profile: str, - returns: list[str], - owner_path: str | None = None, - query: str | None = None, - dependency: str | None = None, -) -> dict[str, Any]: - payload["header"] = header( - "reasoning", - { - "profile": profile, - "ownerPath": owner_path, - "query": query, - "dependency": dependency, - "returns": returns, - "owner": len(payload.get("owners", [])), - "hit": len(payload.get("hits", [])), - "item": len(payload.get("items", [])), - }, - ) - return payload - - -def _ensure_owner_selector( - payload: dict[str, Any], - facts: PythonReasoningTreeFacts, - project_root: Path, - owner_path: str, -) -> None: - existing_paths = {owner["path"] for owner in payload.get("owners", [])} - if owner_path in existing_paths: - return - payload["owners"] = [ - *payload.get("owners", []), - *owners_for_paths(facts, project_root, [owner_path]), - ] - - -def _required(value: str | None, flag: str, profile: str) -> str: - if value is None or not value: - raise ValueError(f"search reasoning {profile} requires {flag}") - return value diff --git a/src/python_lang_project_harness/_semantic_search_render.py b/src/python_lang_project_harness/_semantic_search_render.py deleted file mode 100644 index f5b63b7..0000000 --- a/src/python_lang_project_harness/_semantic_search_render.py +++ /dev/null @@ -1,20 +0,0 @@ -"""Compact and JSON renderers for Python semantic-search packets.""" - -from __future__ import annotations - -import json -from typing import Any - -from ._semantic_search_render_compact import render_compact_packet - - -def render_python_semantic_search_packet_json(packet: dict[str, Any]) -> str: - """Render a semantic-search packet as compact JSON.""" - - return json.dumps(packet, separators=(",", ":"), sort_keys=False) + "\n" - - -def render_python_semantic_search_packet(packet: dict[str, Any]) -> str: - """Render a compact line-oriented semantic-search packet.""" - - return render_compact_packet(packet) diff --git a/src/python_lang_project_harness/_semantic_search_render_compact.py b/src/python_lang_project_harness/_semantic_search_render_compact.py deleted file mode 100644 index ad8b8be..0000000 --- a/src/python_lang_project_harness/_semantic_search_render_compact.py +++ /dev/null @@ -1,238 +0,0 @@ -"""Compact text rendering orchestration for Python search packets.""" - -from __future__ import annotations - -from typing import Any - -from ._semantic_search_common import compact_fields, render_fields -from ._semantic_search_render_flow import ( - avoid_next_action_lines, - finding_lines, - note_lines, - seed_packet_text, - synthesis_lines, -) -from ._semantic_search_render_lines import ( - edge_lines, - hit_lines, - node_lines, - owner_lines, - package_lines, - query_coverage_lines, - render_next_action, -) - - -def render_compact_packet(packet: dict[str, Any]) -> str: - if packet["renderMode"] == "seeds": - return seed_packet_text(packet) - return "\n".join(_compact_packet_lines(packet)) + "\n" - - -def _compact_packet_lines(packet: dict[str, Any]) -> list[str]: - if _is_item_inventory_packet(packet): - return _item_inventory_packet_lines(packet) - owner_by_path = {owner["path"]: owner for owner in packet["owners"]} - lines = [ - f"[{packet['header']['kind']}] {render_fields(packet['header']['fields'])}" - ] - _extend_payload_lines(packet, owner_by_path, lines) - _extend_flow_lines(packet, lines) - return lines - - -def _is_item_inventory_packet(packet: dict[str, Any]) -> bool: - fields = packet["header"]["fields"] - return fields.get("itemQuery") is None and ( - bool(packet.get("items")) or fields.get("itemStatus") is not None - ) - - -def _item_inventory_packet_lines(packet: dict[str, Any]) -> list[str]: - fields = packet["header"]["fields"] - header_fields = compact_fields( - { - "profile": fields.get("profile"), - "ownerPath": fields.get("ownerPath"), - "query": fields.get("query"), - "dependency": fields.get("dependency"), - "returns": fields.get("returns"), - "q": fields.get("q"), - "owner": fields.get("owner"), - "item": len(packet.get("items", [])), - "pipes": fields.get("pipes"), - } - ) - lines = [f"[{packet['header']['kind']}] {render_fields(header_fields)}"] - lines.extend(_item_inventory_owner_lines(packet)) - lines.extend(item_lines(packet)) - lines.extend( - line for line in note_lines(packet) if "kind=item-not-found" not in line - ) - lines.extend(runtime_cost_lines(packet)) - return lines - - -def _item_inventory_owner_lines(packet: dict[str, Any]) -> list[str]: - lines: list[str] = [] - for owner in packet["owners"]: - fields = { - "role": owner["role"], - "public": owner["public"], - "exp": owner.get("exports", [])[:4], - **owner["fields"], - } - lines.append(f"|owner {owner['path']} {render_fields(fields)}".rstrip()) - return lines - - -def _extend_payload_lines( - packet: dict[str, Any], - owner_by_path: dict[str, dict[str, Any]], - lines: list[str], -) -> None: - from ._semantic_search_render_lines import handle_lines - - lines.extend(item_query_lines(packet)) - lines.extend(query_coverage_lines(packet)) - lines.extend(package_lines(packet)) - lines.extend(node_lines(packet)) - lines.extend(owner_lines(packet)) - lines.extend(handle_lines(packet)) - lines.extend(item_lines(packet)) - lines.extend(code_lines(packet)) - lines.extend(hit_lines(packet, owner_by_path)) - if packet["view"] not in {"workspace", "prime"}: - lines.extend(edge_lines(packet)) - lines.extend(finding_lines(packet)) - - -def _extend_flow_lines(packet: dict[str, Any], lines: list[str]) -> None: - lines.extend(note_lines(packet)) - lines.extend(runtime_cost_lines(packet)) - lines.extend(synthesis_lines(packet)) - lines.extend(avoid_next_action_lines(packet)) - lines.extend( - f"|next {','.join(render_next_action(action) for action in packet['nextActions'])}" - for _ in [None] - if packet["nextActions"] - ) - - -def runtime_cost_lines(packet: dict[str, Any]) -> list[str]: - runtime_cost = packet.get("runtimeCost") - if not isinstance(runtime_cost, dict): - return [] - fields = compact_fields( - { - "cache": runtime_cost.get("cacheStatus"), - "elapsedMs": runtime_cost.get("elapsedMs"), - "parseMs": runtime_cost.get("parseMs"), - "sourceFiles": runtime_cost.get("sourceFilesParsed"), - "packages": runtime_cost.get("packagesScanned"), - "reused": runtime_cost.get("parserFactsReused"), - **runtime_cost.get("fields", {}), - "reason": runtime_cost.get("reason"), - } - ) - return [f"|runtime {render_fields(fields)}"] - - -def item_query_lines(packet: dict[str, Any]) -> list[str]: - fields = packet["header"]["fields"] - item_query = fields.get("itemQuery") - if item_query is None: - return [] - rendered = compact_fields( - { - "itemQuery": item_query, - "status": fields.get("itemStatus"), - "match": fields.get("itemMatch"), - "item": fields.get("item"), - "reason": "parser-item-query", - "output": "names" if _item_query_names_only(fields) else None, - "next": _item_query_next(fields), - } - ) - return [f"|query {render_fields(rendered)}"] - - -def item_lines(packet: dict[str, Any]) -> list[str]: - lines: list[str] = [] - for item in packet.get("items", []): - item_fields = item.get("fields", {}) - item_name = item["name"] - fields = compact_fields( - { - "kind": item.get("kind"), - "public": True if item_fields.get("public") is True else None, - "doc": True if item_fields.get("doc") is True else None, - "next": f"syntax:{item_name}", - "structuralSelector": item_fields.get("structuralSelector"), - "displayLineRange": item_fields.get("displayLineRange"), - "sourceLocatorHint": item_fields.get("sourceLocatorHint"), - "read": item_fields.get("read"), - "syn": _syntax_atom_for_kind(item.get("kind")), - "tsqRef": "semantic-tree-sitter-query/python-owner-items.v1", - } - ) - lines.append(f"|item {item['name']} {render_fields(fields)}") - return lines - - -def _syntax_atom_for_kind(kind: object) -> str | None: - if kind == "function": - return "function_definition/name" - if kind == "class": - return "class_definition/name" - if kind == "import": - return "import_statement/name" - if kind == "import-from": - return "import_from_statement/name" - return None - - -def code_lines(packet: dict[str, Any]) -> list[str]: - if packet["header"]["fields"].get("itemQuery") is not None: - return [] - lines: list[str] = [] - for item in packet.get("items", []): - item_fields = item.get("fields", {}) - code = item_fields.get("code") - if not isinstance(code, str) or not code: - continue - location = item.get("location", {}) - line_range = location.get("lineRange") - if not isinstance(line_range, str) and location.get("line") is not None: - line_range = f"{location['line']}:{location['line']}" - fields = compact_fields( - { - "path": location.get("path"), - "lineRange": line_range, - "reason": item_fields.get("reason"), - "truncated": item_fields.get("truncated"), - "text": code, - } - ) - lines.append(f"|code {render_fields(fields)}") - return lines - - -def _item_query_names_only(fields: dict[str, Any]) -> bool: - if fields.get("itemQuery") is None: - return False - item = fields.get("item") - item_count = item if isinstance(item, int) else 0 - if fields.get("itemStatus") == "miss": - return True - return item_count > 1 and fields.get("itemMatch") != "exact" - - -def _item_query_next(fields: dict[str, Any]) -> str: - item = fields.get("item") - item_count = item if isinstance(item, int) else 0 - if fields.get("itemStatus") == "miss" or item_count == 0: - return "revise-query" - if _item_query_names_only(fields) and item_count > 1: - return "select-item" - return "query-code" diff --git a/src/python_lang_project_harness/_semantic_search_render_flow.py b/src/python_lang_project_harness/_semantic_search_render_flow.py deleted file mode 100644 index 06c1ebf..0000000 --- a/src/python_lang_project_harness/_semantic_search_render_flow.py +++ /dev/null @@ -1,121 +0,0 @@ -"""Flow, note, and synthesis renderers for semantic-search packets.""" - -from __future__ import annotations - -from typing import Any - -from ._semantic_search_common import escape_field_value, escape_scalar, render_fields -from ._semantic_search_render_lines import ( - render_next_action, -) - - -def finding_lines(packet: dict[str, Any]) -> list[str]: - lines: list[str] = [] - for finding in packet["findings"]: - location = finding["location"] - fields: dict[str, Any] = { - "path": location["path"], - } - if "line" in location: - fields["line"] = location["line"] - if "column" in location: - fields["column"] = location["column"] - fields["node"] = f"O:{location['path']}" - fields["severity"] = finding["severity"] - lines.append( - f"|find {finding['ruleId']} x{finding['count']} {render_fields(fields)}".rstrip() - ) - return lines - - -def note_lines(packet: dict[str, Any]) -> list[str]: - return [ - f"|note kind={note['kind']} message={escape_field_value(note['message'])}" - for note in packet["notes"] - ] - - -def synthesis_lines(packet: dict[str, Any]) -> list[str]: - synthesis = packet.get("searchSynthesis") - if not synthesis: - return [] - fields = { - "algorithm": synthesis.get("algorithm", ""), - "scope": synthesis.get("scope", ""), - "summary": synthesis.get("summary", ""), - "ownerPath": synthesis.get("ownerPath", ""), - "selectedOwners": synthesis.get("selectedOwners", ""), - "selectedEdges": synthesis.get("selectedEdges", ""), - "incomingOwners": synthesis.get("incomingOwners", ""), - "outgoingOwners": synthesis.get("outgoingOwners", ""), - "highImpactOwners": synthesis.get("highImpactOwners", []), - "frontierOwners": synthesis.get("frontierOwners", []), - "editFrontier": synthesis.get("editFrontier", []), - "testFrontier": synthesis.get("testFrontier", []), - "windowSet": [ - render_next_action(action) for action in synthesis.get("windowSet", []) - ], - "findingOwners": synthesis.get("findingOwners", []), - } - lines = [f"|synthesis {render_fields(fields)}".rstrip()] - seeds = synthesis.get("seeds", []) - if seeds: - lines.extend(_seed_action_lines(seeds)) - return lines - - -def avoid_next_action_lines(packet: dict[str, Any]) -> list[str]: - return [ - f"|avoid {render_next_action(action)} reason={escape_scalar(action['reason'])}" - for action in packet.get("avoidNextActions", []) - ] - - -def seed_packet_text(packet: dict[str, Any]) -> str: - from ._semantic_search_graph_render import compact_graph_seed_packet_text - - return compact_graph_seed_packet_text(packet, render_fields) - - -def _seed_lines(packet: dict[str, Any]) -> list[str]: - groups = _seed_groups(packet) - return [f"|seed {kind}:{','.join(values)}" for kind, values in groups.items()] - - -def _seed_action_lines(actions: list[dict[str, Any]]) -> list[str]: - groups: dict[str, list[str]] = {} - for action in actions: - kind = action.get("kind") - target = action.get("target") - if isinstance(kind, str) and isinstance(target, str): - _add_seed(groups, kind, target) - return [f"|seed {kind}:{','.join(values)}" for kind, values in groups.items()] - - -def _seed_groups(packet: dict[str, Any]) -> dict[str, list[str]]: - groups: dict[str, list[str]] = {} - for owner in packet["owners"]: - _add_seed(groups, "owner", owner["path"]) - for export_name in owner.get("exports", [])[:4]: - _add_seed(groups, "symbol", export_name) - for hit in packet["hits"]: - _add_seed(groups, "owner", hit["ownerPath"]) - if "symbol" in hit: - _add_seed(groups, "symbol", hit["symbol"]) - for handle in packet.get("semanticHandles", []): - owner_path = handle.get("implementationOwnerPath") or handle.get("ownerPath") - if isinstance(owner_path, str): - _add_seed(groups, "owner", owner_path) - for test_path in handle.get("testPaths", [])[:4]: - _add_seed(groups, "tests", test_path) - for action in packet["nextActions"]: - _add_seed(groups, action["kind"], action["target"]) - return groups - - -def _add_seed(groups: dict[str, list[str]], kind: str, target: str) -> None: - values = groups.setdefault(kind, []) - if len(values) >= 8 or target in values: - return - values.append(target) diff --git a/src/python_lang_project_harness/_semantic_search_render_lines.py b/src/python_lang_project_harness/_semantic_search_render_lines.py deleted file mode 100644 index 917ed67..0000000 --- a/src/python_lang_project_harness/_semantic_search_render_lines.py +++ /dev/null @@ -1,186 +0,0 @@ -"""Line renderers for Python semantic-search packet facts.""" - -from __future__ import annotations - -from typing import Any - -from ._semantic_search_common import escape_scalar, render_fields, render_location -from ._semantic_search_model import Fields -from .verification.facts import is_test_path - -COMPACT_PIPE_OWNER_LINES = 4 -COMPACT_PIPE_HIT_LINES = 6 -COMPACT_PIPE_EDGE_LINES = 4 - - -def package_lines(packet: dict[str, Any]) -> list[str]: - return [ - f"|package {package['id']} {render_fields(package['fields'])}".rstrip() - for package in packet.get("packages", []) - ] - - -def node_lines(packet: dict[str, Any]) -> list[str]: - allowed = {"owner", "dependency", "test", "symbol", "package"} - return [ - f"|{node['kind']} {node.get('path') or node['id']} {render_fields(node['fields'])}".rstrip() - for node in packet["nodes"] - if node["kind"] in allowed - ] - - -def owner_lines(packet: dict[str, Any]) -> list[str]: - lines = [] - for owner in _compact_items(packet, packet["owners"], COMPACT_PIPE_OWNER_LINES): - fields: Fields = { - "role": owner["role"], - "public": owner["public"], - "exp": owner.get("exports", [])[:4], - **owner["fields"], - } - owner_next = render_owner_next_actions( - owner["path"], - owner.get("nextActions", []), - ) - if owner_next: - fields["next"] = owner_next - lines.append(f"|owner {owner['path']} {render_fields(fields)}".rstrip()) - return lines - - -def hit_lines( - packet: dict[str, Any], - owner_by_path: dict[str, dict[str, Any]], -) -> list[str]: - lines = [] - for hit in _compact_hits(packet): - owner_path = hit["ownerPath"] - location = hit["location"] - owner_role = owner_by_path.get(owner_path, {}).get("role") - fields: Fields = { - "kind": hit["kind"], - "score": hit["score"], - "reason": hit["reason"], - **({"symbol": hit["symbol"]} if "symbol" in hit else {}), - **({"owner": owner_path} if owner_path != location.get("path") else {}), - **_hit_evidence_fields(packet, owner_role, hit), - **hit.get("fields", {}), - } - line_kind = "api" if hit["kind"] == "api" else "hit" - lines.append( - f"|{line_kind} {render_location(location)} {render_fields(fields)}".rstrip() - ) - return lines - - -def query_coverage_lines(packet: dict[str, Any]) -> list[str]: - lines = [] - for query in packet.get("queryCoverage", []): - fields: Fields = { - "status": query["status"], - "hit": query["hitCount"], - "selected": query.get("fields", {}).get("selectedHits", 0), - **({"surface": query["surfaces"][:4]} if query.get("surfaces") else {}), - **({"owner": query["ownerPaths"][:4]} if query.get("ownerPaths") else {}), - } - lines.append(f"|query {escape_scalar(query['value'])} {render_fields(fields)}") - return lines - - -def handle_lines(packet: dict[str, Any]) -> list[str]: - lines = [] - for handle in packet.get("semanticHandles", []): - raw_fields = { - "kind": handle["kind"], - "source": handle["source"], - "title": handle["title"], - "owner": handle.get("ownerPath"), - "implementation": handle.get("implementationOwnerPath"), - "tests": handle.get("testPaths", [])[:4], - **handle.get("fields", {}), - } - fields = { - key: value - for key, value in raw_fields.items() - if value is not None and value != [] and value != "" - } - lines.append(f"|handle {handle['id']} {render_fields(fields)}".rstrip()) - return lines - - -def edge_lines(packet: dict[str, Any]) -> list[str]: - lines = [] - for edge in _compact_items(packet, packet["edges"], COMPACT_PIPE_EDGE_LINES): - fields = ( - f" {render_fields(edge.get('fields', {}))}" if edge.get("fields") else "" - ) - lines.append(f"|edge {edge['from']} -{edge['kind']}-> {edge['to']}{fields}") - return lines - - -def render_owner_next_actions( - owner_path: str, - actions: list[dict[str, Any]], -) -> list[str]: - return [ - render_next_action(action, context_owner_path=owner_path) - for action in actions - if action["kind"] != "owner" or action["target"] != owner_path - ] - - -def render_next_action( - action: dict[str, Any], - *, - context_owner_path: str | None = None, -) -> str: - suffix = "" - if action.get("ownerPath") and action["ownerPath"] != context_owner_path: - suffix = f"(owner={action['ownerPath']})" - elif action.get("scope"): - suffix = f"(scope={action['scope']})" - return f"{action['kind']}:{escape_scalar(action['target'])}{suffix}" - - -def _compact_hits(packet: dict[str, Any]) -> list[dict[str, Any]]: - hits = packet["hits"] - if packet["view"] != "text": - return _compact_items(packet, hits, COMPACT_PIPE_HIT_LINES) - return sorted(hits, key=_text_hit_compact_rank)[:COMPACT_PIPE_HIT_LINES] - - -def _text_hit_compact_rank(hit: dict[str, Any]) -> tuple[int, int]: - fields = hit.get("fields", {}) - if fields.get("source") == "parser-visible-source": - return (0, -int(hit.get("score", 0))) - if hit.get("kind") == "symbol": - return (1, -int(hit.get("score", 0))) - if hit.get("kind") == "export": - return (2, -int(hit.get("score", 0))) - return (3, -int(hit.get("score", 0))) - - -def _compact_items( - packet: dict[str, Any], - items: list[dict[str, Any]], - limit: int, -) -> list[dict[str, Any]]: - if packet["view"] in {"text", "ingest"}: - return items[:limit] - return items - - -def _hit_evidence_fields( - packet: dict[str, Any], - owner_role: str | None, - hit: dict[str, Any], -) -> Fields: - if packet["view"] not in {"text", "ingest"}: - return {} - owner_path = hit["ownerPath"] - fields: Fields = {"surface": "test" if is_test_path(owner_path) else "source"} - if owner_role: - fields["ownerRole"] = owner_role - if hit.get("snippet"): - fields["text"] = hit["snippet"] - return fields diff --git a/src/python_lang_project_harness/_semantic_search_symbol_hits.py b/src/python_lang_project_harness/_semantic_search_symbol_hits.py deleted file mode 100644 index a29a387..0000000 --- a/src/python_lang_project_harness/_semantic_search_symbol_hits.py +++ /dev/null @@ -1,144 +0,0 @@ -"""Symbol and API hit builders for Python semantic search.""" - -from __future__ import annotations - -from pathlib import Path -from typing import TYPE_CHECKING, Any - -from ._semantic_search_common import ( - dedupe_hits, - location_from_source, - path_hit, - semantic_search_display_path, -) -from ._semantic_search_deps import module_owner_path - -if TYPE_CHECKING: - from python_lang_parser import PythonReasoningTreeFacts, PythonSymbol - - from ._model import PythonHarnessReport - - -def api_hits( - report: PythonHarnessReport, - facts: PythonReasoningTreeFacts, - project_root: Path, - query: str, -) -> list[dict[str, Any]]: - """Return public export and public symbol hits.""" - - query_folded = query.casefold() - hits = [ - path_hit( - semantic_search_display_path(node.path, project_root), - semantic_search_display_path(node.path, project_root), - kind="api", - symbol=name, - score=4, - reason="public-export", - fields={"namespace": ".".join(node.namespace)}, - ) - for node in facts.nodes - for name in node.public_names - if query_folded in name.casefold() - ] - hits.extend(symbol_hits(report, project_root, query, public_only=True)) - return dedupe_hits(hits) - - -def symbol_hits( - report: PythonHarnessReport, - project_root: Path, - query: str, - *, - public_only: bool = False, -) -> list[dict[str, Any]]: - """Return parser symbol/export hits.""" - - query_folded = query.casefold() - hits = [ - hit - for module in report.modules - for hit in ( - *_module_symbol_hits( - module, - project_root, - query_folded, - public_only=public_only, - ), - *_module_export_hits(module, project_root, query_folded), - ) - ] - return dedupe_hits(hits) - - -def symbol_hit( - symbol: PythonSymbol, - owner_path: str, - project_root: Path, - *, - kind: str, -) -> dict[str, Any]: - """Return one symbol hit.""" - - return { - "kind": kind, - "ownerPath": owner_path, - "location": location_from_source(symbol.location, project_root), - "score": 4, - "reason": "symbol-name", - "symbol": symbol.name, - "fields": { - "symbolKind": symbol.kind.value, - "qualified": symbol.qualified_name, - "public": symbol.is_public, - }, - } - - -def _module_symbol_hits( - module, - project_root: Path, - query_folded: str, - *, - public_only: bool, -) -> list[dict[str, Any]]: - owner_path = module_owner_path(module, project_root) - kind = "api" if public_only else "symbol" - return [ - symbol_hit(symbol, owner_path, project_root, kind=kind) - for symbol in module.symbols - if _symbol_matches(symbol, query_folded, public_only=public_only) - ] - - -def _module_export_hits( - module, - project_root: Path, - query_folded: str, -) -> list[dict[str, Any]]: - owner_path = module_owner_path(module, project_root) - return [ - path_hit( - owner_path, - owner_path, - kind="export", - symbol=export_name, - score=3, - reason="export-name", - ) - for export_name in module.export_candidates - if query_folded in export_name.casefold() - ] - - -def _symbol_matches( - symbol: PythonSymbol, - query_folded: str, - *, - public_only: bool, -) -> bool: - if public_only and not symbol.is_public: - return False - names = (symbol.name, symbol.qualified_name) - return any(query_folded in name.casefold() for name in names) diff --git a/src/python_lang_project_harness/_semantic_search_text_hits.py b/src/python_lang_project_harness/_semantic_search_text_hits.py deleted file mode 100644 index b4d4ece..0000000 --- a/src/python_lang_project_harness/_semantic_search_text_hits.py +++ /dev/null @@ -1,207 +0,0 @@ -"""Text hit builders for Python semantic search.""" - -from __future__ import annotations - -from pathlib import Path -from typing import TYPE_CHECKING, Any - -from ._semantic_search_common import ( - dedupe_hits, - location, - path_hit, - semantic_search_display_path, -) -from ._semantic_search_deps import module_owner_path -from ._semantic_search_symbol_hits import symbol_hits - -if TYPE_CHECKING: - from python_lang_parser import PythonReasoningTreeFacts - - from ._model import PythonHarnessReport - - -def text_hits( - report: PythonHarnessReport, - facts: PythonReasoningTreeFacts, - project_root: Path, - query: str, -) -> list[dict[str, Any]]: - """Return owner path, export, symbol, and source-line text hits.""" - - if not query: - return [] - query_folded = query.casefold() - hits = [ - hit - for node in facts.nodes - for hit in _node_text_hits(node, project_root, query_folded) - ] - hits.extend(symbol_hits(report, project_root, query)) - hits.extend( - hit - for module in report.modules - for hit in _module_source_line_hits(module, project_root, query_folded) - ) - return dedupe_hits(hits) - - -def fuzzy_text_hits( - report: PythonHarnessReport, - facts: PythonReasoningTreeFacts, - project_root: Path, - query: str, -) -> list[dict[str, Any]]: - """Return owner path, export, symbol, and source-line fuzzy hits.""" - if not query: - return [] - query_folded = query.casefold() - hits = [ - hit - for node in facts.nodes - for hit in _node_fuzzy_text_hits(node, project_root, query_folded) - ] - hits.extend(symbol_hits(report, project_root, query)) - hits.extend( - hit - for module in report.modules - for hit in _module_fuzzy_source_line_hits(module, project_root, query_folded) - ) - return dedupe_hits(hits) - - -def _node_text_hits( - node, project_root: Path, query_folded: str -) -> list[dict[str, Any]]: - owner_path = semantic_search_display_path(node.path, project_root) - hits = _node_path_hits(node, owner_path, query_folded) - hits.extend( - path_hit( - owner_path, - owner_path, - kind="export", - symbol=export_name, - score=4, - reason="export-name", - ) - for export_name in node.public_names - if query_folded in export_name.casefold() - ) - return hits - - -def _node_fuzzy_text_hits( - node, project_root: Path, query_folded: str -) -> list[dict[str, Any]]: - owner_path = semantic_search_display_path(node.path, project_root) - hits = _node_fuzzy_path_hits(node, owner_path, query_folded) - export_hits = [] - for export_name in node.public_names: - score = _fuzzy_score(export_name, query_folded) - if score is None: - continue - export_hits.append( - path_hit( - owner_path, - owner_path, - kind="export", - symbol=export_name, - score=score, - reason="export-name-fuzzy", - ) - ) - hits.extend(sorted(export_hits, key=lambda hit: -hit["score"])[:6]) - return hits - - -def _node_path_hits(node, owner_path: str, query_folded: str) -> list[dict[str, Any]]: - namespace = ".".join(node.namespace) - if ( - query_folded not in owner_path.casefold() - and query_folded not in namespace.casefold() - ): - return [] - return [path_hit(owner_path, owner_path, score=3, reason="owner-path")] - - -def _node_fuzzy_path_hits( - node, owner_path: str, query_folded: str -) -> list[dict[str, Any]]: - namespace = ".".join(node.namespace) - score = max( - _fuzzy_score(owner_path, query_folded) or 0, - _fuzzy_score(namespace, query_folded) or 0, - ) - if score == 0: - return [] - return [path_hit(owner_path, owner_path, score=score, reason="owner-path-fuzzy")] - - -def _module_source_line_hits( - module, - project_root: Path, - query_folded: str, -) -> list[dict[str, Any]]: - owner_path = module_owner_path(module, project_root) - return [ - { - "kind": "text", - "ownerPath": owner_path, - "location": location(owner_path, line_number), - "score": 2, - "reason": "source-text", - "snippet": source_line.strip()[:160], - "fields": {"source": "parser-visible-source"}, - } - for line_number, source_line in enumerate(module.source_lines, start=1) - if query_folded in source_line.casefold() - ] - - -def _module_fuzzy_source_line_hits( - module, - project_root: Path, - query_folded: str, -) -> list[dict[str, Any]]: - owner_path = module_owner_path(module, project_root) - hits: list[dict[str, Any]] = [] - for line_number, source_line in enumerate(module.source_lines, start=1): - score = _fuzzy_score(source_line, query_folded) - if score is None: - continue - hits.append( - { - "kind": "text", - "ownerPath": owner_path, - "location": location(owner_path, line_number), - "score": score, - "reason": "source-text-fuzzy", - "snippet": source_line.strip()[:160], - "fields": {"source": "parser-visible-source", "matchMode": "fuzzy"}, - } - ) - return hits - - -def _fuzzy_score(candidate: str, query_folded: str) -> int | None: - query = "".join(query_folded.casefold().split()) - if not query: - return None - candidate_folded = candidate.casefold() - exact_index = candidate_folded.find(query) - if exact_index >= 0: - return 12 + len(query) - positions: list[int] = [] - cursor = 0 - for char in query: - index = candidate_folded.find(char, cursor) - if index < 0: - return None - positions.append(index) - cursor = index + len(char) - if not positions: - return None - span = positions[-1] - positions[0] + 1 - if span > max(len(query) * 3, len(query) + 12): - return None - compactness = max(0, len(positions) * 2 - (span - len(positions))) - return 4 + compactness diff --git a/src/python_lang_project_harness/_semantic_search_view_actions.py b/src/python_lang_project_harness/_semantic_search_view_actions.py deleted file mode 100644 index 200b3f3..0000000 --- a/src/python_lang_project_harness/_semantic_search_view_actions.py +++ /dev/null @@ -1,25 +0,0 @@ -"""Shared next-action helpers for Python semantic-search views.""" - -from __future__ import annotations - -from typing import Any - - -def hit_next_actions(hits: list[dict[str, Any]]) -> list[dict[str, Any]]: - """Return bounded owner/tests follow-up actions for hits.""" - - actions: list[dict[str, Any]] = [] - seen: set[tuple[str, str]] = set() - for hit in hits: - for action in ( - {"kind": "owner", "target": hit["ownerPath"]}, - {"kind": "tests", "target": hit["ownerPath"]}, - ): - key = (action["kind"], action["target"]) - if key in seen: - continue - seen.add(key) - actions.append(action) - if len(actions) >= 8: - return actions - return actions diff --git a/src/python_lang_project_harness/_semantic_search_view_core.py b/src/python_lang_project_harness/_semantic_search_view_core.py deleted file mode 100644 index 96b2c64..0000000 --- a/src/python_lang_project_harness/_semantic_search_view_core.py +++ /dev/null @@ -1,333 +0,0 @@ -"""Core workspace, prime, and owner semantic-search views.""" - -from __future__ import annotations - -from collections.abc import Iterable -from pathlib import Path -from typing import TYPE_CHECKING, Any - -from ._semantic_search_common import header, path_hit -from ._semantic_search_deps import dependency_node -from ._semantic_search_findings import finding_facts -from ._semantic_search_items import owner_item_query_payload -from ._semantic_search_model import ( - MAX_DEPENDENCY_HITS, - MAX_IMPORT_HITS, - MAX_PRIME_EDGES, - MAX_PRIME_OWNERS, - MAX_WORKSPACE_EDGES, -) -from ._semantic_search_owners import ( - import_edges, - matching_owner_nodes, - owner_nodes, - owner_record, - ranked_owner_records, -) -from ._semantic_search_packages import dependencies, project_name, workspace_packages - -if TYPE_CHECKING: - from python_lang_parser import PythonReasoningTreeFacts - - from ._model import PythonHarnessReport - - -def workspace_payload( - report: PythonHarnessReport, - facts: PythonReasoningTreeFacts, - project_root: Path, -) -> dict[str, Any]: - """Build the workspace/router packet payload.""" - - packages = workspace_packages(report, facts, project_root, len(owner_nodes(facts))) - edges = import_edges(facts, project_root, limit=MAX_WORKSPACE_EDGES) - owners = _workspace_owner_records(packages) - return { - "header": header( - "workspace", - { - "mode": "workspace-index", - "package": project_name(facts), - "packages": len(packages), - "shown": len(packages), - "edge": len(edges), - "external": len(dependencies(facts)), - "find": len(report.findings), - }, - ), - "packages": packages, - "owners": owners, - "edges": edges, - "findings": finding_facts(report, project_root), - "nextActions": [ - {"kind": "owner", "target": item["path"]} for item in owners[:8] - ], - } - - -def prime_payload( - report: PythonHarnessReport, - facts: PythonReasoningTreeFacts, - project_root: Path, -) -> dict[str, Any]: - """Build the prime project map payload.""" - - owners = ranked_owner_records(facts, project_root)[:MAX_PRIME_OWNERS] - dep_nodes = [dependency_node(item) for item in dependencies(facts)] - edges = import_edges(facts, project_root, limit=MAX_PRIME_EDGES) - findings = finding_facts(report, project_root) - return { - "header": header( - "prime", - { - "mode": "prime-map", - "package": project_name(facts), - "owner": len(owner_nodes(facts)), - "shown": len(owners), - "edge": len(edges), - "external": len(dep_nodes), - "find": len(report.findings), - }, - ), - "nodes": dep_nodes[:MAX_DEPENDENCY_HITS], - "owners": owners, - "edges": edges, - "findings": findings, - "nextActions": _prime_next_actions(owners, dep_nodes), - "searchSynthesis": _prime_graph_synthesis( - owners, - edges, - findings, - ), - } - - -def owner_payload( - report: PythonHarnessReport, - facts: PythonReasoningTreeFacts, - project_root: Path, - query: str, - *, - pipes: tuple[str, ...] = (), - item_query: str | None = None, -) -> dict[str, Any]: - """Build an owner slice payload.""" - - matches = matching_owner_nodes(facts, project_root, query) - owners = [owner_record(node, project_root) for node in matches[:MAX_PRIME_OWNERS]] - owner_paths = {owner["path"] for owner in owners} - edges = [ - edge - for edge in import_edges(facts, project_root, limit=MAX_IMPORT_HITS) - if edge["from"].removeprefix("O:") in owner_paths - or edge["to"].removeprefix("O:") in owner_paths - ] - findings = finding_facts(report, project_root, owner_paths=owner_paths) - item_payload = ( - owner_item_query_payload(report, project_root, query, item_query) - if "items" in pipes - else {"items": [], "fields": {}, "notes": []} - ) - return { - "header": header( - "owner", - { - "q": query, - "owner": len(owners), - "edge": len(edges), - **item_payload["fields"], - }, - ), - "owners": owners, - "edges": edges, - "items": item_payload["items"], - "hits": [ - path_hit(owner["path"], owner["path"], score=4, reason="owner-match") - for owner in owners - ], - "findings": findings, - "nextActions": _owner_next_actions(owners), - "searchSynthesis": _owner_graph_synthesis(owners, edges, findings), - "notes": [ - *([] if owners else [{"kind": "owner-not-found", "message": query}]), - *item_payload["notes"], - ], - } - - -def _prime_next_actions( - owners: list[dict[str, Any]], - dep_nodes: list[dict[str, Any]], -) -> list[dict[str, Any]]: - actions: list[dict[str, Any]] = [] - for owner in owners[:3]: - actions.append({"kind": "owner", "target": owner["path"]}) - if owner.get("exports"): - actions.append( - { - "kind": "text", - "target": owner["exports"][0], - "ownerPath": owner["path"], - } - ) - actions.extend( - {"kind": "deps", "target": dependency} - for dependency in _unique_dependencies(dep_nodes) - ) - return actions[:8] - - -def _workspace_owner_records(packages: list[dict[str, Any]]) -> list[dict[str, Any]]: - owners: list[dict[str, Any]] = [] - for package in packages: - package_id = str(package["id"]) - fields = dict(package.get("fields", {})) - owners.append( - { - "path": package_id, - "namespace": "." if package_id == "." else package_id.replace("/", "."), - "role": fields.get("role", "workspace-package"), - "public": True, - "exports": [], - "nextActions": [{"kind": "owner", "target": package_id}], - "fields": { - "kind": "package", - "surface": fields.get("surface", "workspace"), - "name": fields.get("name", package_id), - }, - } - ) - return owners - - -def _owner_next_actions(owners: list[dict[str, Any]]) -> list[dict[str, Any]]: - actions: list[dict[str, Any]] = [] - for owner in owners[:8]: - actions.append({"kind": "tests", "target": owner["path"]}) - actions.extend( - {"kind": "text", "target": name, "ownerPath": owner["path"]} - for name in owner.get("exports", [])[:2] - ) - return actions - - -def _unique_dependencies(dep_nodes: list[dict[str, Any]]) -> list[str]: - dependencies: list[str] = [] - seen: set[str] = set() - for node in dep_nodes: - dependency = node["id"].removeprefix("D:") - if dependency in seen: - continue - seen.add(dependency) - dependencies.append(dependency) - return dependencies - - -def _prime_graph_synthesis( - owners: list[dict[str, Any]], - edges: list[dict[str, Any]], - findings: list[dict[str, Any]], -) -> dict[str, Any] | None: - selected_owners = [owner["path"] for owner in owners] - if not selected_owners: - return None - frontier_owners = _frontier_owner_paths(selected_owners, edges) - seeds = [{"kind": "owner", "target": path} for path in frontier_owners[:4]] - return _compact_synthesis( - { - "algorithm": "owner-rank-frontier", - "scope": "prime", - "summary": "owner-graph-frontier", - "selectedOwners": len(selected_owners), - "selectedEdges": len(edges), - "highImpactOwners": selected_owners[:4], - "frontierOwners": frontier_owners[:4], - "findingOwners": _finding_owner_paths(findings), - "seeds": seeds, - } - ) - - -def _owner_graph_synthesis( - owners: list[dict[str, Any]], - edges: list[dict[str, Any]], - findings: list[dict[str, Any]], -) -> dict[str, Any] | None: - selected_owners = [owner["path"] for owner in owners] - if not selected_owners: - return None - incoming_owners, outgoing_owners = _incoming_outgoing_owner_paths( - selected_owners, - edges, - ) - frontier_owners = _dedupe([*incoming_owners, *outgoing_owners])[:4] - synthesis: dict[str, Any] = { - "algorithm": "bounded-reachability-depth1", - "scope": "owner", - "summary": "owner-graph-frontier", - "selectedOwners": len(selected_owners), - "selectedEdges": len(edges), - "incomingOwners": len(incoming_owners), - "outgoingOwners": len(outgoing_owners), - "frontierOwners": frontier_owners, - "findingOwners": _finding_owner_paths(findings), - "seeds": [{"kind": "owner", "target": path} for path in frontier_owners], - } - if len(selected_owners) == 1: - synthesis["ownerPath"] = selected_owners[0] - return _compact_synthesis(synthesis) - - -def _frontier_owner_paths( - selected_owners: list[str], - edges: list[dict[str, Any]], -) -> list[str]: - incoming_owners, outgoing_owners = _incoming_outgoing_owner_paths( - selected_owners, - edges, - ) - return _dedupe([*incoming_owners, *outgoing_owners]) - - -def _incoming_outgoing_owner_paths( - selected_owners: list[str], - edges: list[dict[str, Any]], -) -> tuple[list[str], list[str]]: - selected = set(selected_owners) - incoming: list[str] = [] - outgoing: list[str] = [] - for edge in edges: - source = _owner_id_path(edge["from"]) - target = _owner_id_path(edge["to"]) - if source in selected and target not in selected: - outgoing.append(target) - if target in selected and source not in selected: - incoming.append(source) - return _dedupe(incoming), _dedupe(outgoing) - - -def _owner_id_path(owner_id: str) -> str: - return owner_id.removeprefix("O:") - - -def _finding_owner_paths(findings: list[dict[str, Any]]) -> list[str]: - return _dedupe(finding["location"]["path"] for finding in findings)[:4] - - -def _dedupe(values: Iterable[str]) -> list[str]: - result: list[str] = [] - seen: set[str] = set() - for value in values: - if value in seen: - continue - seen.add(value) - result.append(value) - return result - - -def _compact_synthesis(synthesis: dict[str, Any]) -> dict[str, Any]: - return { - key: value - for key, value in synthesis.items() - if value is not None and value != [] and value != "" - } diff --git a/src/python_lang_project_harness/_semantic_search_view_deps_imports.py b/src/python_lang_project_harness/_semantic_search_view_deps_imports.py deleted file mode 100644 index 47ea6bf..0000000 --- a/src/python_lang_project_harness/_semantic_search_view_deps_imports.py +++ /dev/null @@ -1,191 +0,0 @@ -"""Dependency and import semantic-search views for Python.""" - -from __future__ import annotations - -from pathlib import Path -from typing import TYPE_CHECKING, Any - -from ._semantic_search_common import header -from ._semantic_search_deps import ( - dependency_matches, - dependency_node, - dependency_query_parts, - dependency_usage_hits, - version_scope, -) -from ._semantic_search_hits import import_hits -from ._semantic_search_model import MAX_DEPENDENCY_HITS, MAX_IMPORT_HITS -from ._semantic_search_owners import import_edges, owners_for_paths -from ._semantic_search_packages import dependencies -from ._semantic_search_view_hits import hit_next_actions - -if TYPE_CHECKING: - from python_lang_parser import PythonReasoningTreeFacts - - from ._model import PythonHarnessReport - - -def dependency_payload( - report: PythonHarnessReport, - facts: PythonReasoningTreeFacts, - project_root: Path, - query: str, - view: str, -) -> dict[str, Any]: - """Build dependency/deps payloads.""" - - parts = dependency_query_parts(query) - matches = [ - item - for item in dependencies(facts) - if dependency_matches(item, parts["package"]) - ] - include_usage = bool(parts["apiQuery"]) - usage_hits = ( - dependency_usage_hits(report, project_root, parts["package"]) - if include_usage - else [] - ) - nodes = [dependency_node(item, parts=parts) for item in matches] - scope = version_scope(parts, matches) - hits = usage_hits[:MAX_DEPENDENCY_HITS] - owners = owners_for_paths(facts, project_root, [hit["ownerPath"] for hit in hits]) - return { - "header": header( - view, - _dependency_header_fields(query, parts, nodes, usage_hits, scope, view), - ), - "nodes": nodes[:MAX_DEPENDENCY_HITS], - "owners": owners, - "hits": hits, - "nextActions": _dependency_next_actions(parts, query, hits, scope, view), - "notes": _dependency_notes(query, nodes, usage_hits, scope, view), - } - - -def import_payload( - report: PythonHarnessReport, - facts: PythonReasoningTreeFacts, - project_root: Path, - query: str, -) -> dict[str, Any]: - """Build import edge/search payloads.""" - - hits = import_hits(report, project_root, query)[:MAX_IMPORT_HITS] - edge_matches = _matching_import_edges(facts, project_root, query) - paths = [hit["ownerPath"] for hit in hits] + _edge_owner_paths(edge_matches) - return { - "header": header( - "import", {"q": query, "edge": len(edge_matches), "hit": len(hits)} - ), - "owners": owners_for_paths(facts, project_root, paths), - "edges": edge_matches, - "hits": hits, - "nextActions": hit_next_actions(hits), - "notes": [] - if edge_matches or hits - else [{"kind": "not-found", "message": query}], - } - - -def _matching_import_edges( - facts: PythonReasoningTreeFacts, - project_root: Path, - query: str, -) -> list[dict[str, Any]]: - query_folded = query.casefold() - return [ - edge - for edge in import_edges(facts, project_root, limit=MAX_IMPORT_HITS) - if query_folded in edge["from"].casefold() - or query_folded in edge["to"].casefold() - or query_folded in str(edge.get("fields", {}).get("import", "")).casefold() - ] - - -def _edge_owner_paths(edges: list[dict[str, Any]]) -> list[str]: - paths: list[str] = [] - for edge in edges: - paths.extend((edge["from"].removeprefix("O:"), edge["to"].removeprefix("O:"))) - return paths - - -def _dependency_header_fields( - query: str, - parts: dict[str, str], - nodes: list[dict[str, Any]], - usage_hits: list[dict[str, Any]], - scope: str, - view: str, -) -> dict[str, Any]: - fields: dict[str, Any] = { - "q": query, - "manifest": len(nodes), - "usage": len(usage_hits), - "versionScope": scope, - "topology": "asp-owned", - } - if view == "deps": - fields.update( - { - "dep": 1 if parts["package"] else 0, - "package": parts["package"], - "api": parts["apiQuery"], - "hit": len(nodes) + len(usage_hits), - "view": "hits", - } - ) - if parts["requestedVersion"]: - fields["requestedVersion"] = parts["requestedVersion"] - return fields - - -def _dependency_next_actions( - parts: dict[str, str], - query: str, - hits: list[dict[str, Any]], - scope: str, - view: str, -) -> list[dict[str, Any]]: - package = parts["package"] - api_query = parts["apiQuery"] - if view == "deps": - actions = [ - {"kind": "dependency", "target": package}, - {"kind": "public-external-types", "target": package}, - ] - if api_query: - actions.append({"kind": "api", "target": query}) - if api_query and scope == "current": - actions.extend( - ( - {"kind": "text", "target": api_query}, - {"kind": "tests", "target": api_query}, - ) - ) - return [action for action in actions if action["target"]] - return [ - *hit_next_actions(hits)[:4], - {"kind": "public-external-types", "target": package}, - {"kind": "import", "target": query or package}, - ] - - -def _dependency_notes( - query: str, - nodes: list[dict[str, Any]], - usage_hits: list[dict[str, Any]], - scope: str, - view: str, -) -> list[dict[str, str]]: - notes: list[dict[str, str]] = [] - if not nodes and not usage_hits: - notes.append({"kind": "not-found", "message": query}) - if view == "deps" and scope == "external": - notes.append( - { - "kind": "fact-scope", - "message": "requested dependency version is outside the current workspace metadata; local usage is not attributed to that version", - } - ) - return notes diff --git a/src/python_lang_project_harness/_semantic_search_view_hits.py b/src/python_lang_project_harness/_semantic_search_view_hits.py deleted file mode 100644 index b6fdba5..0000000 --- a/src/python_lang_project_harness/_semantic_search_view_hits.py +++ /dev/null @@ -1,153 +0,0 @@ -"""Hit-oriented semantic-search views for Python.""" - -from __future__ import annotations - -from pathlib import Path -from typing import TYPE_CHECKING, Any - -from ._semantic_search_common import dedupe, header, path_hit -from ._semantic_search_hits import test_path_hits -from ._semantic_search_model import ( - MAX_TEST_HITS, - PythonSemanticSearchOptions, -) -from ._semantic_search_owners import ( - matching_owner_nodes, - owner_record, - owners_for_paths, - test_edges, -) -from ._semantic_search_view_actions import hit_next_actions -from ._semantic_search_view_lexical_queries import ( - fair_merged_text_hits, - lexical_query_hits_by_term, - normalized_query_terms, -) -from ._semantic_search_view_lexical_synthesis import ( - avoid_next_actions, - owner_resolution, - query_coverage, - search_synthesis, -) - -if TYPE_CHECKING: - from python_lang_parser import PythonReasoningTreeFacts - - from ._model import PythonHarnessReport - - -def tests_payload( - report: PythonHarnessReport, - facts: PythonReasoningTreeFacts, - project_root: Path, - query: str, -) -> dict[str, Any]: - """Build tests-for-owner payloads.""" - - owner_paths = { - owner_record(node, project_root)["path"] - for node in matching_owner_nodes(facts, project_root, query) - } - edges = test_edges(facts, project_root, owner_paths) - hits = [ - path_hit( - edge["to"].removeprefix("O:"), - edge["to"].removeprefix("O:"), - kind="test", - score=4, - reason="test-import", - ) - for edge in edges - ] - if not hits and not owner_paths: - hits = test_path_hits(report, project_root, query) - return generic_hits_payload( - "tests", - hits[:MAX_TEST_HITS], - facts, - project_root, - query, - edges=edges[:MAX_TEST_HITS], - ) - - -def text_payload( - report: PythonHarnessReport, - facts: PythonReasoningTreeFacts, - project_root: Path, - options: PythonSemanticSearchOptions, -) -> dict[str, Any]: - """Build parser-visible lexical search payloads.""" - - query_terms = normalized_query_terms(options) - hits_by_term = lexical_query_hits_by_term( - report, facts, project_root, query_terms, options.owner_path - ) - hits = fair_merged_text_hits(hits_by_term) - owner_paths = dedupe( - [ - *(hit["ownerPath"] for hit in hits), - *([] if options.owner_path is None else [options.owner_path]), - ] - ) - owners = ( - owners_for_paths(facts, project_root, owner_paths) - if "owner" in options.pipes - else [] - ) - edges = ( - test_edges(facts, project_root, set(owner_paths)) - if "tests" in options.pipes - else [] - ) - return { - "header": header( - "lexical", - { - "q": options.query or ",".join(query_terms), - "querySet": len(query_terms) if options.query_set else None, - "selector": "lexical-set" if options.query_set else None, - "mode": "lexical", - "backend": "provider", - "scopeOwner": options.owner_path, - "own": len(owner_paths), - "hit": len(hits), - "view": "hits", - "pipes": list(options.pipes), - }, - ), - "owners": owners, - "edges": edges[:MAX_TEST_HITS], - "hits": hits, - "nextActions": hit_next_actions(hits), - "queryCoverage": query_coverage(hits_by_term, hits), - "ownerResolution": owner_resolution(owner_paths), - "searchSynthesis": search_synthesis(query_terms, hits, owner_paths), - "avoidNextActions": avoid_next_actions(query_terms, owner_paths), - "notes": [] - if hits - else [{"kind": "not-found", "message": options.query or ",".join(query_terms)}], - } - - -def generic_hits_payload( - view: str, - hits: list[dict[str, Any]], - facts: PythonReasoningTreeFacts, - project_root: Path, - query: str, - *, - edges: list[dict[str, Any]] | None = None, -) -> dict[str, Any]: - """Build a generic hit-oriented payload.""" - - return { - "header": header(view, {"q": query, "hit": len(hits)}), - "owners": owners_for_paths( - facts, project_root, [hit["ownerPath"] for hit in hits] - ), - "edges": edges or [], - "hits": hits, - "nextActions": hit_next_actions(hits), - "notes": [] if hits else [{"kind": "not-found", "message": query}], - } diff --git a/src/python_lang_project_harness/_semantic_search_view_ingest.py b/src/python_lang_project_harness/_semantic_search_view_ingest.py deleted file mode 100644 index 2611353..0000000 --- a/src/python_lang_project_harness/_semantic_search_view_ingest.py +++ /dev/null @@ -1,84 +0,0 @@ -"""Stdin ingest semantic-search view for Python.""" - -from __future__ import annotations - -from pathlib import Path -from typing import TYPE_CHECKING, Any - -from ._semantic_search_common import header -from ._semantic_search_ingest import ingest_hits -from ._semantic_search_model import MAX_LEXICAL_HITS -from ._semantic_search_owners import owners_for_paths -from ._semantic_search_view_actions import hit_next_actions - -if TYPE_CHECKING: - from python_lang_parser import PythonReasoningTreeFacts - - -def ingest_payload( - facts: PythonReasoningTreeFacts, - project_root: Path, - stdin: str, -) -> dict[str, Any]: - """Build stdin ingest payloads.""" - - detection, hits = ingest_hits(facts, project_root, stdin) - hits = hits[:MAX_LEXICAL_HITS] - next_actions = hit_next_actions(hits) - notes = _ingest_notes(detection, hits) - if not hits and _empty_stdin(detection): - next_actions = _empty_stdin_actions() - return { - "header": header( - "ingest", - { - "source": detection["source"], - "hit": len(hits), - "bytes": detection["byteCount"], - }, - ), - "inputDetection": detection, - "owners": owners_for_paths( - facts, project_root, [hit["ownerPath"] for hit in hits] - ), - "hits": hits, - "nextActions": next_actions, - "notes": notes, - } - - -def _ingest_notes( - detection: dict[str, Any], hits: list[dict[str, Any]] -) -> list[dict[str, Any]]: - if hits: - return [] - if _empty_stdin(detection): - return [ - { - "kind": "stdin-required", - "message": ( - "search ingest consumes stdin candidate paths; " - "use search prime --view seeds for project discovery" - ), - } - ] - return [{"kind": "unrecognized-input", "message": "stdin produced no path hits"}] - - -def _empty_stdin(detection: dict[str, Any]) -> bool: - return detection["byteCount"] == 0 and detection["lineCount"] == 0 - - -def _empty_stdin_actions() -> list[dict[str, str]]: - return [ - { - "kind": "prime", - "target": "search prime --view seeds", - "scope": "project-discovery", - }, - { - "kind": "ingest", - "target": "pipe candidate paths into search ingest items tests --view seeds", - "scope": "stdin-candidates", - }, - ] diff --git a/src/python_lang_project_harness/_semantic_search_view_knowledge.py b/src/python_lang_project_harness/_semantic_search_view_knowledge.py deleted file mode 100644 index 032d35f..0000000 --- a/src/python_lang_project_harness/_semantic_search_view_knowledge.py +++ /dev/null @@ -1,81 +0,0 @@ -"""Provider-owned language and ecosystem knowledge search axes.""" - -from __future__ import annotations - -from pathlib import Path -from typing import Any - -from ._semantic_search_common import header, path_hit -from ._semantic_search_knowledge_facts import axis_detail, knowledge_facts -from ._semantic_search_model import PythonSemanticSearchOptions - - -def knowledge_payload( - project_root: Path, - options: PythonSemanticSearchOptions, -) -> dict[str, Any]: - """Return a semantic-search payload for provider knowledge axes.""" - - axis = options.view - detail = axis_detail(axis) - query = options.query or "" - terms = _query_terms(query) - facts = knowledge_facts(project_root, axis, terms) - hits = [ - path_hit( - ".", - ".", - kind="text", - symbol=str(fact["id"]), - score=2 if terms else 1, - reason=f"{axis}:{detail['authority']}", - fields={"axis": axis, "authority": detail["authority"], **fact["fields"]}, - ) - for fact in facts[:12] - ] - missing = not facts - return { - "header": header( - axis, - { - "q": query, - "evidenceGrade": "unknown" if missing else "fact", - "authority": detail["authority"], - "fact": len(facts), - "hit": len(hits), - }, - ), - "packages": facts, - "nodes": [ - { - "id": f"knowledge:{axis}:{fact['id']}", - "kind": "fact", - "fields": {"axis": axis, **fact["fields"]}, - } - for fact in facts - ], - "edges": [], - "owners": [], - "hits": hits, - "findings": [], - "nextActions": [ - {"kind": "lexical", "target": query or axis}, - {"kind": "owner", "target": "."}, - ], - "notes": [ - { - "kind": "fact-scope", - "message": ( - f"{axis} search did not find a provider-owned fact for the query; " - "refine the axis query or route through owner/deps/tree-sitter evidence" - if missing - else detail["summary"] - ), - }, - {"kind": "next-step", "message": detail["next"]}, - ], - } - - -def _query_terms(query: str) -> list[str]: - return [term for term in query.lower().split() if term] diff --git a/src/python_lang_project_harness/_semantic_search_view_lexical_queries.py b/src/python_lang_project_harness/_semantic_search_view_lexical_queries.py deleted file mode 100644 index 8afcbfd..0000000 --- a/src/python_lang_project_harness/_semantic_search_view_lexical_queries.py +++ /dev/null @@ -1,163 +0,0 @@ -"""Text query-set hit selection helpers for Python semantic search.""" - -from __future__ import annotations - -import json -from pathlib import Path -from typing import TYPE_CHECKING, Any - -from ._semantic_search_common import dedupe -from ._semantic_search_hits import text_hits -from ._semantic_search_model import MAX_LEXICAL_HITS -from .verification.facts import is_test_path - -if TYPE_CHECKING: - from python_lang_parser import PythonReasoningTreeFacts - - from ._model import PythonHarnessReport - from ._semantic_search_model import PythonSemanticSearchOptions - - -def normalized_query_terms(options: PythonSemanticSearchOptions) -> list[str]: - if not options.query_set: - return [] if options.query is None else [options.query] - return dedupe(term.strip() for term in options.query_set if term.strip()) - - -def lexical_query_hits_by_term( - report: PythonHarnessReport, - facts: PythonReasoningTreeFacts, - project_root: Path, - query_terms: list[str], - owner_path: str | None, -) -> dict[str, list[dict[str, Any]]]: - return { - term: sorted( - ( - _with_owner_surface(hit) - for hit in text_hits(report, facts, project_root, term) - if owner_path is None or hit["ownerPath"] == owner_path - ), - key=_text_hit_rank, - ) - for term in query_terms - } - - -def fuzzy_lexical_query_hits_by_term( - report: PythonHarnessReport, - facts: PythonReasoningTreeFacts, - project_root: Path, - query_terms: list[str], - owner_path: str | None, -) -> dict[str, list[dict[str, Any]]]: - from ._semantic_search_text_hits import fuzzy_text_hits - - return { - term: sorted( - ( - _with_owner_surface(hit) - for hit in fuzzy_text_hits(report, facts, project_root, term) - if owner_path is None or hit["ownerPath"] == owner_path - ), - key=_text_hit_rank, - ) - for term in query_terms - } - - -def fair_merged_text_hits( - hits_by_term: dict[str, list[dict[str, Any]]], -) -> list[dict[str, Any]]: - merged: dict[tuple[str, str, str, str], dict[str, Any]] = {} - ordered_keys: list[tuple[str, str, str, str]] = [] - ordered_terms = sorted( - hits_by_term, - key=lambda term: (-_term_specificity(term), term.casefold()), - ) - depth = max((len(hits) for hits in hits_by_term.values()), default=0) - for depth_index in range(depth): - _merge_text_hits_at_depth( - hits_by_term, - ordered_terms, - depth_index, - merged, - ordered_keys, - ) - return [merged[key] for key in ordered_keys] - - -def _merge_text_hits_at_depth( - hits_by_term: dict[str, list[dict[str, Any]]], - ordered_terms: list[str], - depth_index: int, - merged: dict[tuple[str, str, str, str], dict[str, Any]], - ordered_keys: list[tuple[str, str, str, str]], -) -> None: - for term in ordered_terms: - hits = hits_by_term[term] - if depth_index < len(hits): - _merge_text_hit(term, hits[depth_index], merged, ordered_keys) - - -def _merge_text_hit( - term: str, - hit: dict[str, Any], - merged: dict[tuple[str, str, str, str], dict[str, Any]], - ordered_keys: list[tuple[str, str, str, str]], -) -> None: - key = _hit_key(hit) - current = merged.get(key) - if current is not None: - _merge_duplicate_text_hit(term, hit, current) - return - if len(ordered_keys) >= MAX_LEXICAL_HITS: - return - fields = {**hit.get("fields", {}), "queryTerms": [term]} - merged[key] = {**hit, "fields": fields} - ordered_keys.append(key) - - -def _merge_duplicate_text_hit( - term: str, - hit: dict[str, Any], - current: dict[str, Any], -) -> None: - fields = dict(current.get("fields", {})) - fields["queryTerms"] = dedupe([*fields.get("queryTerms", []), term]) - current["fields"] = fields - current["score"] = max(int(current["score"]), int(hit["score"])) - - -def _text_hit_rank(hit: dict[str, Any]) -> tuple[int, str, str, str]: - return ( - -int(hit["score"]), - hit["ownerPath"], - hit["kind"], - json.dumps(hit["location"], sort_keys=True), - ) - - -def _hit_key(hit: dict[str, Any]) -> tuple[str, str, str, str]: - return ( - hit["kind"], - hit["ownerPath"], - hit.get("symbol", ""), - json.dumps(hit["location"], sort_keys=True), - ) - - -def _term_specificity(term: str) -> int: - compact = term.strip() - structural = sum( - 1 - for character in compact - if character in {".", "_", "/", '"', "'", "(", ")", "[", "]", ":"} - ) - return len(set(compact.casefold())) + len(compact) + (structural * 8) - - -def _with_owner_surface(hit: dict[str, Any]) -> dict[str, Any]: - owner_path = hit["ownerPath"] - surface = "test-source" if is_test_path(owner_path) else "real-source" - return {**hit, "surface": surface, "realOwner": True} diff --git a/src/python_lang_project_harness/_semantic_search_view_lexical_synthesis.py b/src/python_lang_project_harness/_semantic_search_view_lexical_synthesis.py deleted file mode 100644 index 3d44246..0000000 --- a/src/python_lang_project_harness/_semantic_search_view_lexical_synthesis.py +++ /dev/null @@ -1,170 +0,0 @@ -"""Text query-set coverage and next-action synthesis helpers.""" - -from __future__ import annotations - -from typing import Any - -from ._semantic_search_common import dedupe - - -def query_coverage( - hits_by_term: dict[str, list[dict[str, Any]]], - selected_hits: list[dict[str, Any]], -) -> list[dict[str, Any]]: - selected_counts = _selected_query_term_counts(selected_hits) - return [ - _query_term_coverage(term, hits, selected_counts.get(term, 0)) - for term, hits in hits_by_term.items() - ] - - -def owner_resolution(owner_paths: list[str]) -> list[dict[str, Any]]: - return [ - { - "target": owner_path, - "status": "workspace-owner", - "realOwner": True, - "ownerPath": owner_path, - "reason": "parser-visible owner selected by lexical search", - } - for owner_path in owner_paths[:8] - ] - - -def search_synthesis( - query_terms: list[str], - hits: list[dict[str, Any]], - owner_paths: list[str], -) -> dict[str, Any] | None: - if not query_terms: - return None - ranked_owners = _rank_synthesis_owners(hits, owner_paths) - edit_frontier = [ - owner_path - for owner_path in ranked_owners - if not _is_test_owner_path(owner_path) - ][:4] - test_frontier = [ - owner_path for owner_path in ranked_owners if _is_test_owner_path(owner_path) - ][:4] - window_set = [ - *({"kind": "owner", "target": owner_path} for owner_path in edit_frontier), - *({"kind": "tests", "target": owner_path} for owner_path in test_frontier), - ][:8] - return { - "algorithm": "query-set-owner-resolution", - "scope": "query-set", - "summary": ( - f"query-set compressed {len(query_terms)} query terms into " - f"{len(owner_paths)} parser-visible owners" - ), - "selectedOwners": len(ranked_owners), - **({"editFrontier": edit_frontier} if edit_frontier else {}), - **({"testFrontier": test_frontier} if test_frontier else {}), - **({"windowSet": window_set} if window_set else {}), - "seeds": window_set, - "fields": { - "querySet": len(query_terms), - "owners": len(owner_paths), - "hits": len(hits), - }, - } - - -def avoid_next_actions( - query_terms: list[str], - owner_paths: list[str], -) -> list[dict[str, Any]]: - owner_path_set = set(owner_paths) - return [ - { - "kind": "owner", - "target": term, - "reason": "query-term-not-parser-visible-owner", - } - for term in query_terms - if _looks_like_project_path(term) and term not in owner_path_set - ][:8] - - -def _selected_query_term_counts( - selected_hits: list[dict[str, Any]], -) -> dict[str, int]: - selected_counts: dict[str, int] = {} - for hit in selected_hits: - for term in hit.get("fields", {}).get("queryTerms", []): - selected_counts[term] = selected_counts.get(term, 0) + 1 - return selected_counts - - -def _query_term_coverage( - term: str, - hits: list[dict[str, Any]], - selected_count: int, -) -> dict[str, Any]: - hit_count = len(hits) - return { - "value": term, - "kind": "text", - "selector": "exact", - "status": _coverage_status(hit_count, selected_count), - "hitCount": hit_count, - "surfaces": dedupe(hit["surface"] for hit in hits), - "ownerPaths": dedupe(hit["ownerPath"] for hit in hits)[:8], - "fields": {"selectedHits": selected_count}, - } - - -def _coverage_status(hit_count: int, selected_count: int) -> str: - if hit_count and selected_count < hit_count: - return "partial" - if hit_count: - return "hit" - return "miss" - - -def _rank_synthesis_owners( - hits: list[dict[str, Any]], - owner_paths: list[str], -) -> list[str]: - term_counts: dict[str, set[str]] = {owner_path: set() for owner_path in owner_paths} - best_scores: dict[str, int] = {owner_path: 0 for owner_path in owner_paths} - for hit in hits: - owner_path = hit["ownerPath"] - term_counts.setdefault(owner_path, set()).update( - hit.get("fields", {}).get("queryTerms", []) - ) - best_scores[owner_path] = max(best_scores.get(owner_path, 0), int(hit["score"])) - return sorted( - owner_paths, - key=lambda owner_path: ( - -len(term_counts.get(owner_path, set())), - -best_scores.get(owner_path, 0), - owner_path, - ), - ) - - -def _is_test_owner_path(owner_path: str) -> bool: - return ( - owner_path.startswith("tests/") - or owner_path.startswith("test/") - or "/tests/" in owner_path - or "/test/" in owner_path - or "/__tests__/" in owner_path - or owner_path.endswith("_test.py") - or owner_path.endswith("_test.pyi") - or owner_path.endswith(".test.py") - or owner_path.endswith(".spec.py") - ) - - -def _looks_like_project_path(term: str) -> bool: - return ( - "/" in term - and " " not in term - and "\\" not in term - and ":" not in term - and not term.startswith("/") - and all(part not in {"", ".", ".."} for part in term.split("/")) - ) diff --git a/src/python_lang_project_harness/_semantic_search_views.py b/src/python_lang_project_harness/_semantic_search_views.py deleted file mode 100644 index 9951242..0000000 --- a/src/python_lang_project_harness/_semantic_search_views.py +++ /dev/null @@ -1,113 +0,0 @@ -"""Semantic-search view dispatcher for Python packets.""" - -from __future__ import annotations - -from pathlib import Path -from typing import TYPE_CHECKING, Any - -from ._semantic_search_common import header -from ._semantic_search_hits import api_hits, callsite_hits, symbol_hits -from ._semantic_search_model import MAX_SYMBOL_HITS, PythonSemanticSearchOptions -from ._semantic_search_public_external_types import public_external_types_payload -from ._semantic_search_view_core import owner_payload, prime_payload, workspace_payload -from ._semantic_search_view_deps_imports import dependency_payload, import_payload -from ._semantic_search_view_hits import ( - generic_hits_payload, - tests_payload, - text_payload, -) -from ._semantic_search_view_ingest import ingest_payload -from ._semantic_search_view_knowledge import knowledge_payload - -if TYPE_CHECKING: - from python_lang_parser import PythonReasoningTreeFacts - - from ._model import PythonHarnessReport - - -def payload_for_view( - report: PythonHarnessReport, - facts: PythonReasoningTreeFacts, - project_root: Path, - options: PythonSemanticSearchOptions, -) -> dict[str, Any]: - """Dispatch one semantic-search view.""" - - return _payload_for_view(report, facts, project_root, options) - - -def _payload_for_view( - report: PythonHarnessReport, - facts: PythonReasoningTreeFacts, - project_root: Path, - options: PythonSemanticSearchOptions, -) -> dict[str, Any]: - """Dispatch one semantic-search view behind the thin public facade.""" - - query = options.query or "" - match options.view: - case "workspace": - return workspace_payload(report, facts, project_root) - case "prime": - return prime_payload(report, facts, project_root) - case "owner": - return owner_payload( - report, - facts, - project_root, - query, - pipes=options.pipes, - item_query=options.item_query, - ) - case "dependency" | "deps": - return dependency_payload(report, facts, project_root, query, options.view) - case "api": - hits = api_hits(report, facts, project_root, query)[:MAX_SYMBOL_HITS] - return generic_hits_payload("api", hits, facts, project_root, query) - case "public-external-types": - return public_external_types_payload(report, facts, project_root, query) - case "policy": - from ._semantic_search_policy import policy_payload - - return policy_payload( - report, - facts, - project_root, - query, - pipes=options.pipes, - ) - case "symbol": - hits = symbol_hits(report, project_root, query)[:MAX_SYMBOL_HITS] - return generic_hits_payload("symbol", hits, facts, project_root, query) - case "callsite": - hits = callsite_hits(report, project_root, query)[:MAX_SYMBOL_HITS] - return generic_hits_payload("callsite", hits, facts, project_root, query) - case "import": - return import_payload(report, facts, project_root, query) - case "tests": - return tests_payload(report, facts, project_root, query) - case "lexical": - return text_payload(report, facts, project_root, options) - case "reasoning": - from ._semantic_search_reasoning import reasoning_payload - - return reasoning_payload(report, facts, project_root, options) - case ( - "env" - | "runtime-source" - | "lang" - | "std" - | "capability" - | "extension" - | "pattern" - | "compare" - ): - return knowledge_payload(project_root, options) - case "ingest": - return ingest_payload(facts, project_root, options.stdin) - case _: - return { - "header": header( - options.view, {"error": f"unknown view {options.view}"} - ) - } diff --git a/src/python_lang_project_harness/pytest_plugin.py b/src/python_lang_project_harness/pytest_plugin.py deleted file mode 100644 index e3c115e..0000000 --- a/src/python_lang_project_harness/pytest_plugin.py +++ /dev/null @@ -1,195 +0,0 @@ -"""Pytest plugin entry point for dev-dependency harness mounting.""" - -from __future__ import annotations - -from dataclasses import replace -from pathlib import Path -from typing import TYPE_CHECKING - -import pytest - -from python_lang_parser._diagnostic_model import PythonDiagnosticSeverity - -from ._model import PythonHarnessConfig -from ._project_config import read_python_project_harness_config -from ._runner import assert_python_project_harness_clean - -if TYPE_CHECKING: - from collections.abc import Sequence - - -_ENABLE_OPTION = "--python-project-harness" -_ROOT_OPTION = "--python-project-harness-root" -_NO_TESTS_OPTION = "--python-project-harness-no-tests" -_SOURCE_DIR_OPTION = "--python-project-harness-source-dir" -_TEST_DIR_OPTION = "--python-project-harness-test-dir" -_EXTRA_PATH_OPTION = "--python-project-harness-extra-path" -_DISABLE_RULE_OPTION = "--python-project-harness-disable-rule" -_BLOCK_RULE_OPTION = "--python-project-harness-block-rule" -_ERROR_ONLY_OPTION = "--python-project-harness-error-only" -_NO_ADVICE_OPTION = "--python-project-harness-no-advice" - - -def pytest_addoption(parser: pytest.Parser) -> None: - """Register Python project harness pytest options.""" - - group = parser.getgroup("python-lang-project-harness") - group.addoption( - _ENABLE_OPTION, - action="store_true", - default=False, - help="Collect and run the python-lang-project-harness policy test.", - ) - group.addoption( - _ROOT_OPTION, - action="store", - default=None, - metavar="PATH", - help="Project root for the harness test. Defaults to pytest rootdir.", - ) - group.addoption( - _NO_TESTS_OPTION, - action="store_true", - default=False, - help="Do not parse test files; pytest layout checks still run.", - ) - group.addoption( - _SOURCE_DIR_OPTION, - action="append", - default=[], - metavar="NAME", - help="Source directory name to scan. Can be provided more than once.", - ) - group.addoption( - _TEST_DIR_OPTION, - action="append", - default=[], - metavar="NAME", - help="Test directory name to scan. Can be provided more than once.", - ) - group.addoption( - _EXTRA_PATH_OPTION, - action="append", - default=[], - metavar="NAME", - help="Extra project path name to scan. Can be provided more than once.", - ) - group.addoption( - _DISABLE_RULE_OPTION, - action="append", - default=[], - metavar="RULE_ID", - help="Harness rule id to suppress. Can be provided more than once.", - ) - group.addoption( - _BLOCK_RULE_OPTION, - action="append", - default=[], - metavar="RULE_ID", - help="Harness rule id to treat as blocking. Can be provided more than once.", - ) - group.addoption( - _ERROR_ONLY_OPTION, - action="store_true", - default=False, - help="Only fail the pytest harness item for parser errors.", - ) - group.addoption( - _NO_ADVICE_OPTION, - action="store_true", - default=False, - help="Hide non-blocking advice from assertion output.", - ) - - -def pytest_collection_modifyitems( - session: pytest.Session, - config: pytest.Config, - items: list[pytest.Item], -) -> None: - """Insert one explicit harness item when the plugin option is enabled.""" - - if not config.getoption(_ENABLE_OPTION): - return - item = PythonProjectHarnessItem.from_parent( - session, - name="python-project-harness", - nodeid="python-project-harness", - ) - items.insert(0, item) - - -class PythonProjectHarnessItem(pytest.Item): - """Pytest item that runs the parser-backed project harness.""" - - def runtest(self) -> None: - """Run the configured project harness and raise a compact assertion.""" - - assert_python_project_harness_clean( - _project_root(self.config), - config=_harness_config(self.config), - severities=_blocking_severities(self.config), - include_tests=not self.config.getoption(_NO_TESTS_OPTION), - source_dir_names=_optional_tuple(self.config.getoption(_SOURCE_DIR_OPTION)), - test_dir_names=_optional_tuple(self.config.getoption(_TEST_DIR_OPTION)), - extra_path_names=_optional_tuple(self.config.getoption(_EXTRA_PATH_OPTION)), - include_advice=not self.config.getoption(_NO_ADVICE_OPTION), - ) - - def repr_failure( - self, - excinfo: pytest.ExceptionInfo[BaseException], - style: str | None = None, - ) -> str: - """Return compact harness assertion text without pytest traceback noise.""" - - if isinstance(excinfo.value, AssertionError): - return str(excinfo.value) - return super().repr_failure(excinfo, style=style) - - def reportinfo(self) -> tuple[Path, int, str]: - """Return stable report metadata for pytest output.""" - - return (Path("python-project-harness"), 0, "python project harness") - - -def _project_root(config: pytest.Config) -> Path: - configured_root = config.getoption(_ROOT_OPTION) - if configured_root: - return Path(configured_root) - return Path(config.rootpath) - - -def _blocking_severities( - config: pytest.Config, -) -> frozenset[PythonDiagnosticSeverity] | None: - if config.getoption(_ERROR_ONLY_OPTION): - return frozenset({PythonDiagnosticSeverity.ERROR}) - return None - - -def _harness_config(config: pytest.Config) -> PythonHarnessConfig | None: - disabled_rule_values = config.getoption(_DISABLE_RULE_OPTION) - blocking_rule_values = config.getoption(_BLOCK_RULE_OPTION) - if not disabled_rule_values and not blocking_rule_values: - return None - - base_config = read_python_project_harness_config(_project_root(config)) - selected_config = base_config if base_config is not None else PythonHarnessConfig() - return replace( - selected_config, - disabled_rule_ids=( - frozenset(disabled_rule_values) - if disabled_rule_values - else selected_config.disabled_rule_ids - ), - blocking_rule_ids=( - frozenset(blocking_rule_values) - if blocking_rule_values - else selected_config.blocking_rule_ids - ), - ) - - -def _optional_tuple(values: Sequence[str]) -> tuple[str, ...] | None: - return tuple(values) if values else None diff --git a/tests/fixtures/bin/asp b/tests/fixtures/bin/asp deleted file mode 100755 index 4f017d0..0000000 --- a/tests/fixtures/bin/asp +++ /dev/null @@ -1,506 +0,0 @@ -#!/usr/bin/env node - -import { stdin, stderr } from "node:process"; - -const args = process.argv.slice(2); - -if (args[0] === "--help" || args.includes("--help")) { - process.stdout.write("asp test fixture\n"); - process.exit(0); -} - -if ( - args[0] !== "graph" || - args[1] !== "render" || - !args.includes("--packet") || - !args.includes("-") || - !args.includes("--view") || - !args.includes("seeds") -) { - stderr.write("test asp fixture only supports graph render --packet - --view seeds\n"); - process.exit(2); -} - -const packet = JSON.parse(await readStdin()); -const kind = packet.header?.kind ?? packet.method ?? "search"; -const fields = packet.header?.fields ?? {}; -const synthesis = packet.searchSynthesis ?? {}; -const seeds = collectSeeds(packet); -const aliases = assignAliases(seeds); -const query = fields.q ?? fields.query ?? packet.query ?? firstQuerySetValue(packet); -const algorithm = selectAlgorithm(kind, synthesis.algorithm ?? fields.alg); -const headerFields = { ...fields }; -delete headerFields.q; -delete headerFields.query; -delete headerFields.alg; -if ((kind === "search-prime" || kind === "search-workspace") && headerFields.root === undefined) { - headerFields.root = "."; -} - -const leadingHeaderFields = orderedHeaderFields(kind, headerFields, query, algorithm); - -const lines = [ - `[${kind}] ${formatFields(leadingHeaderFields)}` - .replace(/\s+/gu, " ") - .trimEnd(), - "legend: ID=kind:role(value)!next; edge SRC>{DST:rel}; frontier ID.next", - `aliases: graph:{G=search${aliasLegend(aliases)}}`, -]; - -if (aliases.length > 0) { - lines.push(aliases.map((alias) => alias.rendered).join(";")); - lines.push(`G>{${aliases.map((alias) => `${alias.id}:${alias.edge}`).join(",")}}`); - lines.push( - `rank=${aliases.map((alias) => alias.id).join(",")} frontier=${aliases - .map((alias) => `${alias.id}.${alias.next}`) - .join(",")}`, - ); -} else { - lines.push("G>{}"); - lines.push("rank= frontier="); -} - -const entries = renderedEntries(packet.reasoningProfiles, aliases); -if (entries.length > 0) { - lines.push(`entries=${entries.join(",")}`); -} - -const profiles = renderedProfiles(aliases); -if (profiles.length > 0) { - lines.push(`profiles=${profiles.join(",")}`); -} - -const avoid = synthesis.avoid ?? packet.avoid ?? defaultAvoid(packet.header?.kind); -if (avoid !== undefined) { - lines.push(`avoid=${formatValue(avoid)}`); -} - -process.stdout.write(`${lines.join("\n")}\n`); - -async function readStdin() { - let input = ""; - stdin.setEncoding("utf8"); - for await (const chunk of stdin) { - input += chunk; - } - return input; -} - -function collectSeeds(packet) { - const querySeeds = []; - const ownerSeeds = []; - const testSeeds = []; - const symbolSeeds = []; - const packageSeeds = []; - const dependencySeeds = []; - const findingSeeds = []; - const otherSeeds = []; - - const query = packet.query ?? firstQuerySetValue(packet); - if ((packet.header?.kind === "search-lexical" || packet.header?.kind === "search-query") && query !== undefined) { - querySeeds.push({ kind: "query", target: query }); - } - - for (const action of packet.nextActions ?? []) { - addSeed(action, { - querySeeds, - ownerSeeds, - testSeeds, - symbolSeeds, - packageSeeds, - dependencySeeds, - findingSeeds, - otherSeeds, - }); - } - for (const resolution of packet.ownerResolution ?? []) { - if (resolution.ownerPath !== undefined) { - pushOwnerOrTest(resolution.ownerPath, ownerSeeds, testSeeds); - } - } - for (const node of asArray(packet.nodes)) { - addNodeSeed(node, { - querySeeds, - ownerSeeds, - testSeeds, - symbolSeeds, - packageSeeds, - dependencySeeds, - findingSeeds, - otherSeeds, - }); - } - for (const windowTarget of packet.searchSynthesis?.windowSet ?? []) { - if (windowTarget.target !== undefined) { - pushOwnerOrTest(windowTarget.target, ownerSeeds, testSeeds); - } - } - for (const path of [ - ...(packet.searchSynthesis?.highImpactOwners ?? []), - ...(packet.searchSynthesis?.editFrontier ?? []), - ...(packet.searchSynthesis?.frontierOwners ?? []), - ]) { - pushOwnerOrTest(path, ownerSeeds, testSeeds); - } - - const synthesisTests = []; - const synthesisSymbols = []; - const synthesisOthers = []; - for (const seed of packet.searchSynthesis?.seeds ?? []) { - if (seed.kind === "owner") { - ownerSeeds.push(seed); - } else if (seed.kind === "tests" || seed.kind === "test") { - synthesisTests.push(seed); - } else if (seed.kind === "symbol") { - synthesisSymbols.push(seed); - } else if (seed.kind === "package" || seed.kind === "pkg") { - packageSeeds.push(seed); - } else if (seed.kind === "dependency" || seed.kind === "deps") { - dependencySeeds.push(seed); - } else if (seed.kind === "finding") { - findingSeeds.push(seed); - } else if (seed.kind === "lexical" || seed.kind === "query") { - querySeeds.push(seed); - } else { - synthesisOthers.push(seed); - } - } - for (const owner of packet.owners ?? []) { - const path = owner.path ?? owner.target; - if (path === undefined) { - continue; - } - if (owner.role === "test" || isTestPath(path)) { - testSeeds.push({ kind: "tests", target: path }); - } else { - ownerSeeds.push({ kind: "owner", target: path }); - } - } - for (const hit of packet.hits ?? []) { - const path = hit.ownerPath ?? hit.path ?? hit.target; - if (path !== undefined) { - pushOwnerOrTest(path, ownerSeeds, testSeeds); - } - } - for (const finding of packet.findings ?? []) { - const target = finding.ruleId ?? finding.id ?? finding.code ?? finding.target; - if (target !== undefined) { - findingSeeds.push({ kind: "finding", target }); - } - } - for (const handle of packet.semanticHandles ?? []) { - if (handle.ownerPath !== undefined) { - pushOwnerOrTest(handle.ownerPath, ownerSeeds, testSeeds); - } - for (const testPath of handle.testPaths ?? []) { - testSeeds.push({ kind: "tests", target: testPath }); - } - } - for (const path of packet.searchSynthesis?.testFrontier ?? []) { - testSeeds.push({ kind: "tests", target: path }); - } - - return dedupeSeeds([ - ...querySeeds, - ...packageSeeds, - ...ownerSeeds, - ...testSeeds, - ...interleaveTestsAndSymbols(synthesisTests, synthesisSymbols), - ...symbolSeeds, - ...dependencySeeds, - ...findingSeeds, - ...otherSeeds, - ...synthesisOthers, - ]); -} - -function pushOwnerOrTest(path, ownerSeeds, testSeeds) { - if (isTestPath(path)) { - testSeeds.push({ kind: "tests", target: path }); - } else { - ownerSeeds.push({ kind: "owner", target: path }); - } -} - -function addSeed(seed, groups) { - if (seed?.target === undefined || seed?.kind === undefined) { - return; - } - if (seed.kind === "owner" || seed.kind === "ingest") { - if (isTestPath(seed.target)) { - groups.testSeeds.push({ kind: "tests", target: seed.target }); - } else { - groups.ownerSeeds.push({ kind: "owner", target: seed.target }); - } - } else if (seed.kind === "tests" || seed.kind === "test") { - groups.testSeeds.push({ kind: "tests", target: seed.target }); - } else if (seed.kind === "symbol") { - groups.symbolSeeds.push(seed); - } else if (seed.kind === "package" || seed.kind === "pkg") { - groups.packageSeeds.push(seed); - } else if (seed.kind === "dependency" || seed.kind === "deps") { - groups.dependencySeeds.push(seed); - } else if (seed.kind === "finding") { - groups.findingSeeds.push(seed); - } else if (seed.kind === "query" && seed.targetRole === "path") { - const target = seed.ownerPath ?? seed.target; - if (isTestPath(target)) { - groups.testSeeds.push({ kind: "tests", target }); - } else { - groups.ownerSeeds.push({ kind: "owner", target }); - } - } else if (seed.kind === "query" || seed.kind === "lexical") { - groups.querySeeds.push(seed); - } else { - groups.otherSeeds.push(seed); - } -} - -function addNodeSeed(node, groups) { - if (node === undefined || node === null) { - return; - } - const kind = node.kind ?? node.type ?? node.nodeKind; - const target = node.target ?? node.value ?? node.path ?? node.pkg ?? node.name ?? node.id; - if (kind === "search" || node.id === "G") { - return; - } - if (kind === undefined || target === undefined) { - return; - } - addSeed({ kind, target, targetRole: node.role }, groups); -} - -function interleaveTestsAndSymbols(testSeeds, symbolSeeds) { - const maxLength = Math.max(testSeeds.length, symbolSeeds.length); - const result = []; - for (let index = 0; index < maxLength; index += 1) { - if (testSeeds[index] !== undefined) { - result.push(testSeeds[index]); - } - if (symbolSeeds[index] !== undefined) { - result.push(symbolSeeds[index]); - } - } - return result; -} - -function dedupeSeeds(seeds) { - const seen = new Set(); - const result = []; - for (const seed of seeds) { - if (seed?.target === undefined || seed?.kind === undefined) { - continue; - } - const key = `${seed.kind}\0${seed.target}`; - if (seen.has(key)) { - continue; - } - seen.add(key); - result.push(seed); - } - return result; -} - -function assignAliases(seeds) { - const counts = new Map(); - return seeds.map((seed) => { - const descriptor = describeSeed(seed); - const count = counts.get(descriptor.prefix) ?? 0; - counts.set(descriptor.prefix, count + 1); - const id = count === 0 ? descriptor.prefix : `${descriptor.prefix}${count + 1}`; - return { - id, - prefix: descriptor.prefix, - kind: descriptor.kind, - next: descriptor.next, - edge: descriptor.edge, - rendered: `${id}=${descriptor.kind}:${descriptor.role}(${escapeRoleValue(seed.target)})!${descriptor.next}`, - }; - }); -} - -function describeSeed(seed) { - switch (seed.kind) { - case "lexical": - case "query": - return { prefix: "Q", kind: "query", role: "term", next: "lexical", edge: "matches" }; - case "owner": - case "ingest": - return { prefix: "O", kind: "owner", role: "path", next: "owner", edge: "selects" }; - case "tests": - case "test": - return { prefix: "T", kind: "test", role: "path", next: "tests", edge: "covers" }; - case "symbol": - return { prefix: "S", kind: "symbol", role: "symbol", next: "symbol", edge: "selects" }; - case "dependency": - case "deps": - return { prefix: "D", kind: "dependency", role: "dep", next: "dependency", edge: "selects" }; - case "finding": - return { prefix: "F", kind: "finding", role: "rule", next: "owner", edge: "selects" }; - case "package": - case "pkg": - return { prefix: "P", kind: "package", role: "pkg", next: "owner", edge: "selects" }; - default: - return { - prefix: "N", - kind: seed.kind, - role: "target", - next: seed.kind, - edge: "selects", - }; - } -} - -function aliasLegend(aliases) { - const seen = new Set(); - const parts = []; - for (const alias of aliases) { - if (seen.has(alias.prefix)) { - continue; - } - seen.add(alias.prefix); - parts.push(`${alias.prefix}=${alias.kind}`); - } - return parts.length === 0 ? "" : `,${parts.join(",")}`; -} - -function renderedEntries(reasoningProfiles, aliases) { - if (Array.isArray(reasoningProfiles) && reasoningProfiles.length > 0) { - return reasoningProfiles - .filter((profile) => profileSelectorsSatisfied(profile, aliases)) - .map((profile) => { - const selectors = profile.selectors ?? []; - const selectorAliases = selectors - .filter((selector) => aliasForSelector(selector, aliases) !== undefined) - .map((selector) => aliasForSelector(selector, aliases)); - return `${profile.profile}(${dedupeStrings(selectorAliases).join(",")}=>${(profile.returns ?? []).join("+")})`; - }); - } - - const hasOwner = aliases.some((alias) => alias.kind === "owner"); - const hasQuery = aliases.some((alias) => alias.kind === "query"); - const entries = []; - if (hasOwner && hasQuery) { - entries.push("owner-query(O,Q=>items+tests+dependency-usage)"); - } - if (hasOwner) { - entries.push("owner-tests(O=>covering-tests+test-entrypoints+fixtures)"); - } - return entries; -} - -function renderedProfiles(aliases) { - if (aliases.some((alias) => alias.kind === "owner") && !aliases.some((alias) => alias.kind === "query")) { - return ["owner-tests(O)"]; - } - return []; -} - -function profileSelectorsSatisfied(profile, aliases) { - return (profile.selectors ?? []) - .filter((selector) => selector.required !== false) - .every((selector) => aliasForSelector(selector, aliases) !== undefined); -} - -function aliasForSelector(selector, aliases) { - const byAlias = aliases.find((alias) => selector.alias !== undefined && alias.prefix === selector.alias); - if (byAlias !== undefined) { - return byAlias.prefix; - } - const byKind = aliases.find((alias) => selector.kind !== undefined && alias.kind === selector.kind); - return byKind?.prefix; -} - -function firstQuerySetValue(packet) { - if (Array.isArray(packet.querySet) && packet.querySet.length > 1) { - return packet.querySet.map((entry) => entry.value).join(","); - } - return packet.querySet?.[0]?.value; -} - -function formatFields(fields) { - return Object.entries(fields) - .filter(([, value]) => value !== undefined) - .map(([key, value]) => `${key}=${formatValue(value)}`) - .join(" "); -} - -function formatValue(value) { - if (Array.isArray(value)) { - return value.map((entry) => String(entry)).join(","); - } - return String(value); -} - -function escapeRoleValue(value) { - return String(value).replaceAll(")", "\\)"); -} - -function withoutKeys(object, keys) { - const keySet = new Set(keys); - return Object.fromEntries(Object.entries(object).filter(([key]) => !keySet.has(key))); -} - -function asArray(value) { - return Array.isArray(value) ? value : []; -} - -function dedupeStrings(values) { - return [...new Set(values.filter((value) => value !== undefined))]; -} - -function defaultAvoid(kind) { - return kind === "search-lexical" || kind === "search-query" - ? "broad-lexical,raw-read,repeat-glob" - : undefined; -} - -function orderedHeaderFields(kind, headerFields, query, algorithm) { - if (kind === "search-lexical" || kind === "search-query") { - const leading = { - ...(query === undefined ? {} : { q: query }), - ...(headerFields.querySet === undefined ? {} : { querySet: headerFields.querySet }), - ...(headerFields.selector === undefined ? {} : { selector: headerFields.selector }), - ...(headerFields.view === undefined ? {} : { view: headerFields.view }), - ...(algorithm === undefined ? {} : { alg: algorithm }), - }; - return { - ...leading, - ...withoutKeys(headerFields, ["view", "querySet", "selector"]), - }; - } - if (kind === "search-prime" || kind === "search-workspace") { - const leading = { - ...(query === undefined ? {} : { q: query }), - ...(headerFields.root === undefined ? {} : { root: headerFields.root }), - ...(algorithm === undefined ? {} : { alg: algorithm }), - }; - return { - ...leading, - ...withoutKeys(headerFields, ["root"]), - }; - } - const leading = { - ...(query === undefined ? {} : { q: query }), - ...(headerFields.view === undefined ? {} : { view: headerFields.view }), - ...(headerFields.root === undefined ? {} : { root: headerFields.root }), - ...(algorithm === undefined ? {} : { alg: algorithm }), - }; - return { - ...leading, - ...withoutKeys(headerFields, ["view", "root"]), - }; -} - -function selectAlgorithm(kind, algorithm) { - if (kind === "search-prime") { - return "budgeted-prime-frontier-v1"; - } - return algorithm; -} - -function isTestPath(path) { - const text = String(path); - return text.startsWith("test/") || text.startsWith("tests/") || text.includes("/test_") || text.endsWith("_test.rs"); -} diff --git a/tests/unit/harness/agent_readability/test_native_idiom_binding_state.py b/tests/unit/asp_python/agent_readability/test_native_idiom_binding_state.py similarity index 89% rename from tests/unit/harness/agent_readability/test_native_idiom_binding_state.py rename to tests/unit/asp_python/agent_readability/test_native_idiom_binding_state.py index 05c36f1..fccd1a2 100644 --- a/tests/unit/harness/agent_readability/test_native_idiom_binding_state.py +++ b/tests/unit/asp_python/agent_readability/test_native_idiom_binding_state.py @@ -2,7 +2,7 @@ from typing import TYPE_CHECKING -from python_lang_project_harness import run_python_lang_harness +from asp_python import run_asp_python_paths if TYPE_CHECKING: from pathlib import Path @@ -35,7 +35,7 @@ def collect(values: list[int]) -> tuple[list[int], dict[int, int], int]: encoding="utf-8", ) - report = run_python_lang_harness([source]) + report = run_asp_python_paths([source]) assert not any( finding.rule_id == "PY-AGENT-POLICY-011" for finding in report.findings diff --git a/tests/unit/harness/harness-rules.generated.md b/tests/unit/asp_python/asp-rules.generated.md similarity index 90% rename from tests/unit/harness/harness-rules.generated.md rename to tests/unit/asp_python/asp-rules.generated.md index f2b7808..1c1faa8 100644 --- a/tests/unit/harness/harness-rules.generated.md +++ b/tests/unit/asp_python/asp-rules.generated.md @@ -1,8 +1,8 @@ -# python-lang-project-harness +# asp-python -## Harness Rules +## ASP Python Rules -Generated from embedded `src/python_lang_project_harness/harness-rules.md`. +Generated from embedded `src/asp_python/asp-rules.md`. - **PY-AGENT-POLICY-001**: Requires library modules to declare concise intent docstrings for agent search and repair. - **PY-AGENT-POLICY-002**: Requires public callable boundaries to carry type annotations for native syntax reasoning. @@ -31,8 +31,8 @@ Generated from embedded `src/python_lang_project_harness/harness-rules.md`. - **PY-AGENT-PROJECT-007**: Requires build-system tables to declare build requirements. - **PY-AGENT-PROJECT-008**: Requires declared import names to resolve to parser-visible project modules. - **PY-AGENT-PROJECT-009**: Requires entry point targets to resolve to parser-visible project modules. -- **PY-AGENT-PROJECT-010**: Requires harness dev dependencies to mount an actual pytest gate. +- **PY-AGENT-PROJECT-010**: Requires ASP Python dev dependencies to mount an actual pytest gate. - **PY-AGENT-PROJECT-011**: Requires verification profile hints or agent snapshot guidance for parser-suggested owners. - **PY-TEST-R001**: Moves pytest modules out of the tests root and into owned suites. -- **PY-TEST-R002**: Keeps tests root limited to harness configuration and owned suite directories. +- **PY-TEST-R002**: Keeps tests root limited to ASP Python configuration and owned suite directories. - **PY-TEST-R003**: Splits oversized unit test leaves into focused folder-first suites. diff --git a/tests/unit/harness/project_policy/test_catalog.py b/tests/unit/asp_python/project_policy/test_catalog.py similarity index 95% rename from tests/unit/harness/project_policy/test_catalog.py rename to tests/unit/asp_python/project_policy/test_catalog.py index ce758ca..fa57ee4 100644 --- a/tests/unit/harness/project_policy/test_catalog.py +++ b/tests/unit/asp_python/project_policy/test_catalog.py @@ -1,6 +1,6 @@ from __future__ import annotations -from python_lang_project_harness import ( +from asp_python import ( PythonProjectPolicyRulePack, python_project_policy_rules, ) diff --git a/tests/unit/harness/project_policy/test_layout.py b/tests/unit/asp_python/project_policy/test_layout.py similarity index 90% rename from tests/unit/harness/project_policy/test_layout.py rename to tests/unit/asp_python/project_policy/test_layout.py index 1c3e36a..578928f 100644 --- a/tests/unit/harness/project_policy/test_layout.py +++ b/tests/unit/asp_python/project_policy/test_layout.py @@ -2,7 +2,7 @@ from typing import TYPE_CHECKING -from python_lang_project_harness import run_python_project_harness +from asp_python import run_asp_python if TYPE_CHECKING: from pathlib import Path @@ -16,7 +16,7 @@ def test_project_policy_noops_without_pyproject(tmp_path: Path) -> None: encoding="utf-8", ) - report = run_python_project_harness(tmp_path) + report = run_asp_python(tmp_path) assert not any( finding.rule_id.startswith("PY-PROJ-") for finding in report.findings @@ -32,7 +32,7 @@ def test_project_policy_blocks_flat_layout_with_pyproject(tmp_path: Path) -> Non (package / "py.typed").write_text("", encoding="utf-8") _write_pyproject(tmp_path, packages='["pkg"]') - report = run_python_project_harness(tmp_path) + report = run_asp_python(tmp_path) assert [ (finding.rule_id, finding.location.path) for finding in report.findings @@ -58,7 +58,7 @@ def test_project_policy_accepts_pyproject_declared_nested_src_layout( (package / "py.typed").write_text("", encoding="utf-8") _write_pyproject(tmp_path, packages='["packages/python/src/tools"]') - report = run_python_project_harness(tmp_path) + report = run_asp_python(tmp_path) assert report.is_clean assert report.project_resolution is not None @@ -71,7 +71,7 @@ def test_project_policy_blocks_missing_declared_package_root( (tmp_path / "src").mkdir() _write_pyproject(tmp_path, packages='["src/missing_pkg"]') - report = run_python_project_harness(tmp_path) + report = run_asp_python(tmp_path) assert [ (finding.rule_id, finding.location.path) for finding in report.findings @@ -85,7 +85,7 @@ def test_project_policy_blocks_package_root_without_init(tmp_path: Path) -> None package.mkdir(parents=True) _write_pyproject(tmp_path, packages='["src/example_pkg"]') - report = run_python_project_harness(tmp_path) + report = run_asp_python(tmp_path) assert [ (finding.rule_id, finding.location.path) for finding in report.findings @@ -103,7 +103,7 @@ def test_project_policy_deduplicates_declared_package_findings( packages='["src/missing_pkg", "src/./missing_pkg", "src/missing_pkg"]', ) - report = run_python_project_harness(tmp_path) + report = run_asp_python(tmp_path) assert [ (finding.rule_id, finding.location.path) for finding in report.findings diff --git a/tests/unit/harness/project_policy/test_metadata.py b/tests/unit/asp_python/project_policy/test_metadata.py similarity index 98% rename from tests/unit/harness/project_policy/test_metadata.py rename to tests/unit/asp_python/project_policy/test_metadata.py index 127eaa2..4cb0b05 100644 --- a/tests/unit/harness/project_policy/test_metadata.py +++ b/tests/unit/asp_python/project_policy/test_metadata.py @@ -2,7 +2,7 @@ from typing import TYPE_CHECKING -from python_lang_project_harness._project_metadata import ( +from asp_python._project_metadata import ( read_python_project_metadata, ) diff --git a/tests/unit/harness/project_policy/test_metadata_policy.py b/tests/unit/asp_python/project_policy/test_metadata_policy.py similarity index 81% rename from tests/unit/harness/project_policy/test_metadata_policy.py rename to tests/unit/asp_python/project_policy/test_metadata_policy.py index e6567a3..1d85f72 100644 --- a/tests/unit/harness/project_policy/test_metadata_policy.py +++ b/tests/unit/asp_python/project_policy/test_metadata_policy.py @@ -2,7 +2,7 @@ from typing import TYPE_CHECKING -from python_lang_project_harness import run_python_project_harness +from asp_python import run_asp_python if TYPE_CHECKING: from pathlib import Path @@ -21,7 +21,7 @@ def test_project_policy_blocks_project_table_without_name(tmp_path: Path) -> Non """, ) - report = run_python_project_harness(tmp_path) + report = run_asp_python(tmp_path) assert [ (finding.rule_id, finding.location.path) for finding in report.findings @@ -45,7 +45,7 @@ def test_project_policy_blocks_project_table_without_requires_python( """, ) - report = run_python_project_harness(tmp_path) + report = run_asp_python(tmp_path) assert [ (finding.rule_id, finding.location.path) for finding in report.findings @@ -69,7 +69,7 @@ def test_project_policy_blocks_build_system_table_without_requires( """, ) - report = run_python_project_harness(tmp_path) + report = run_asp_python(tmp_path) assert [ (finding.rule_id, finding.location.path) for finding in report.findings @@ -87,7 +87,7 @@ def test_project_policy_allows_tool_only_pyproject(tmp_path: Path) -> None: """, ) - report = run_python_project_harness(tmp_path) + report = run_asp_python(tmp_path) assert not any( finding.rule_id.startswith("PY-PROJ-") for finding in report.findings @@ -106,12 +106,12 @@ def test_project_policy_requires_pytest_gate_for_harness_dev_dependency( [dependency-groups] test = [ - "python-lang-project-harness[pytest]>=0.1.0", + "asp-python[pytest]>=0.1.0", ] """, ) - report = run_python_project_harness(tmp_path) + report = run_asp_python(tmp_path) assert [ (finding.rule_id, finding.location.path) for finding in report.findings @@ -130,15 +130,15 @@ def test_project_policy_accepts_pytest_addopts_gate(tmp_path: Path) -> None: [dependency-groups] test = [ - "python-lang-project-harness[pytest]>=0.1.0", + "asp-python[pytest]>=0.1.0", ] [tool.pytest.ini_options] -addopts = ["--python-project-harness"] +addopts = ["--asp-python"] """, ) - report = run_python_project_harness(tmp_path) + report = run_asp_python(tmp_path) assert not any( finding.rule_id == "PY-AGENT-PROJECT-010" for finding in report.findings @@ -155,19 +155,19 @@ def test_project_policy_accepts_explicit_pytest_helper_gate(tmp_path: Path) -> N [dependency-groups] test = [ - "python-lang-project-harness[pytest]>=0.1.0", + "asp-python[pytest]>=0.1.0", ] """, ) tests = tmp_path / "tests" / "unit" tests.mkdir(parents=True) - (tests / "test_harness_policy.py").write_text( - "from python_lang_project_harness.pytest import python_project_harness_test\n" - "test_python_project_harness_policy = python_project_harness_test()\n", + (tests / "test_asp_python_policy.py").write_text( + "from asp_python.pytest import asp_python_test\n" + "test_asp_python_policy = asp_python_test()\n", encoding="utf-8", ) - report = run_python_project_harness(tmp_path) + report = run_asp_python(tmp_path) assert not any( finding.rule_id == "PY-AGENT-PROJECT-010" for finding in report.findings @@ -186,11 +186,11 @@ def test_project_policy_advises_missing_verification_profile_hints( [dependency-groups] test = [ - "python-lang-project-harness[pytest]>=0.1.0", + "asp-python[pytest]>=0.1.0", ] [tool.pytest.ini_options] -addopts = ["--python-project-harness"] +addopts = ["--asp-python"] """, ) package = tmp_path / "src" / "pkg" @@ -204,7 +204,7 @@ def test_project_policy_advises_missing_verification_profile_hints( encoding="utf-8", ) - report = run_python_project_harness(tmp_path) + report = run_asp_python(tmp_path) finding = next( finding for finding in report.findings @@ -228,13 +228,13 @@ def test_project_policy_accepts_configured_verification_profile_hint( [dependency-groups] test = [ - "python-lang-project-harness[pytest]>=0.1.0", + "asp-python[pytest]>=0.1.0", ] [tool.pytest.ini_options] -addopts = ["--python-project-harness"] +addopts = ["--asp-python"] -[tool.python-lang-project-harness.verification] +[tool.asp-python.verification] profile_hints = [ { owner_path = "src/pkg/__init__.py", responsibilities = ["public_api"], task_kinds = ["regression"], rationale = "package facade" }, ] @@ -251,7 +251,7 @@ def test_project_policy_accepts_configured_verification_profile_hint( encoding="utf-8", ) - report = run_python_project_harness(tmp_path) + report = run_asp_python(tmp_path) assert not any( finding.rule_id == "PY-AGENT-PROJECT-011" for finding in report.findings diff --git a/tests/unit/harness/project_policy/test_typed_packages.py b/tests/unit/asp_python/project_policy/test_typed_packages.py similarity index 92% rename from tests/unit/harness/project_policy/test_typed_packages.py rename to tests/unit/asp_python/project_policy/test_typed_packages.py index 2e13f91..5c088d6 100644 --- a/tests/unit/harness/project_policy/test_typed_packages.py +++ b/tests/unit/asp_python/project_policy/test_typed_packages.py @@ -2,7 +2,7 @@ from typing import TYPE_CHECKING -from python_lang_project_harness import run_python_project_harness +from asp_python import run_asp_python if TYPE_CHECKING: from pathlib import Path @@ -19,7 +19,7 @@ def test_project_policy_blocks_missing_py_typed_for_public_package( ) _write_pyproject(tmp_path, packages='["src/example_pkg"]') - report = run_python_project_harness(tmp_path) + report = run_asp_python(tmp_path) assert [ (finding.rule_id, finding.location.path) for finding in report.findings @@ -43,7 +43,7 @@ def test_project_policy_blocks_missing_py_typed_for_public_facade_imports( ) _write_pyproject(tmp_path, packages='["src/example_pkg"]') - report = run_python_project_harness(tmp_path) + report = run_asp_python(tmp_path) assert [ (finding.rule_id, finding.location.path) for finding in report.findings @@ -64,7 +64,7 @@ def test_project_policy_allows_private_package_without_py_typed( ) _write_pyproject(tmp_path, packages='["src/example_pkg"]') - report = run_python_project_harness(tmp_path) + report = run_asp_python(tmp_path) assert report.is_clean @@ -79,7 +79,7 @@ def test_project_policy_accepts_src_package_with_py_typed(tmp_path: Path) -> Non (package / "py.typed").write_text("", encoding="utf-8") _write_pyproject(tmp_path, packages='["src/example_pkg"]') - report = run_python_project_harness(tmp_path) + report = run_asp_python(tmp_path) assert report.is_clean @@ -96,7 +96,7 @@ def test_project_policy_accepts_nested_src_package_with_py_typed( (package / "py.typed").write_text("", encoding="utf-8") _write_pyproject(tmp_path, packages='["packages/python/src/example_pkg"]') - report = run_python_project_harness(tmp_path) + report = run_asp_python(tmp_path) assert report.is_clean @@ -116,7 +116,7 @@ def test_project_policy_blocks_unannotated_public_callable_in_typed_package( (package / "py.typed").write_text("", encoding="utf-8") _write_pyproject(tmp_path, packages='["src/example_pkg"]') - report = run_python_project_harness(tmp_path) + report = run_asp_python(tmp_path) assert [ (finding.rule_id, finding.location.path) for finding in report.findings @@ -141,7 +141,7 @@ def test_project_policy_blocks_unannotated_public_method_in_typed_package( (package / "py.typed").write_text("", encoding="utf-8") _write_pyproject(tmp_path, packages='["src/example_pkg"]') - report = run_python_project_harness(tmp_path) + report = run_asp_python(tmp_path) assert [ (finding.rule_id, finding.location.path) for finding in report.findings @@ -166,7 +166,7 @@ def test_project_policy_allows_private_callable_without_annotations_in_typed_pac (package / "py.typed").write_text("", encoding="utf-8") _write_pyproject(tmp_path, packages='["src/example_pkg"]') - report = run_python_project_harness(tmp_path) + report = run_asp_python(tmp_path) assert report.is_clean @@ -186,7 +186,7 @@ def test_project_policy_accepts_annotated_method_in_typed_package( (package / "py.typed").write_text("", encoding="utf-8") _write_pyproject(tmp_path, packages='["src/example_pkg"]') - report = run_python_project_harness(tmp_path) + report = run_asp_python(tmp_path) assert report.is_clean diff --git a/tests/unit/harness/provider_runtime_live_support.py b/tests/unit/asp_python/provider_runtime_live_support.py similarity index 81% rename from tests/unit/harness/provider_runtime_live_support.py rename to tests/unit/asp_python/provider_runtime_live_support.py index 2598d80..0dbd0ee 100644 --- a/tests/unit/harness/provider_runtime_live_support.py +++ b/tests/unit/asp_python/provider_runtime_live_support.py @@ -11,7 +11,7 @@ from pathlib import Path from urllib.parse import SplitResult -from python_lang_project_harness._runtime import _response_frame +from asp_python._runtime import _response_frame def environment() -> dict[str, str]: @@ -20,6 +20,44 @@ def environment() -> dict[str, str]: "ASP_PROVIDER_ARTIFACT_DIGEST": "blake3-256:" + "a" * 64, "ASP_PROVIDER_REGISTRATION_DIGEST": "sha256:" + "b" * 64, "ASP_PROVIDER_RUNTIME_CONTRACT_DIGEST": "blake3-256:" + "c" * 64, + "ASP_PROVIDER_RUNTIME_OPERATIONS_JSON": json.dumps( + [ + { + "operation": "projection-batch", + "requestSchema": { + "schemaId": "schema:projection-request", + "schemaVersion": "1", + }, + "responseSchema": { + "schemaId": "schema:projection-response", + "schemaVersion": "1", + }, + }, + { + "operation": "project-resolution", + "requestSchema": { + "schemaId": "schema:resolution-request", + "schemaVersion": "1", + }, + "responseSchema": { + "schemaId": "schema:resolution-response", + "schemaVersion": "1", + }, + }, + { + "operation": "query", + "requestSchema": { + "schemaId": "schema:query-request", + "schemaVersion": "1", + }, + "responseSchema": { + "schemaId": "schema:query-response", + "schemaVersion": "1", + }, + }, + ], + separators=(",", ":"), + ), "ASP_PROVIDER_ID": "asp-python", "ASP_PROVIDER_LANGUAGE_ID": "python", "ASP_CLIENT_SERVER_HOST": "127.0.0.1:0", diff --git a/tests/unit/harness/semantic_search_fixture.py b/tests/unit/asp_python/python_project_fixture.py similarity index 65% rename from tests/unit/harness/semantic_search_fixture.py rename to tests/unit/asp_python/python_project_fixture.py index ef7191f..487daee 100644 --- a/tests/unit/harness/semantic_search_fixture.py +++ b/tests/unit/asp_python/python_project_fixture.py @@ -1,31 +1,11 @@ -"""Fixtures for semantic-search CLI tests.""" +"""Provider-native Python project fixtures.""" from __future__ import annotations -import os -import shutil from pathlib import Path -import pytest -from python_lang_project_harness._semantic_search_graph_render import ( - SEMANTIC_AGENT_PROTOCOL_BIN_ENV, -) - - -def compact_graph_renderer_available() -> bool: - configured_bin = os.environ.get(SEMANTIC_AGENT_PROTOCOL_BIN_ENV) - if configured_bin is not None: - return Path(configured_bin).exists() - return shutil.which("asp") is not None - - -def require_compact_graph_renderer() -> None: - if not compact_graph_renderer_available(): - pytest.skip("asp graph renderer is not available") - - -def write_search_fixture(project_root: Path) -> None: +def write_python_project_fixture(project_root: Path) -> None: package = project_root / "src" / "pkg" tests = project_root / "tests" package.mkdir(parents=True) diff --git a/tests/unit/harness/scenarios/software_criteria/control_flow_v1/benchmark.toml b/tests/unit/asp_python/scenarios/software_criteria/control_flow_v1/benchmark.toml similarity index 92% rename from tests/unit/harness/scenarios/software_criteria/control_flow_v1/benchmark.toml rename to tests/unit/asp_python/scenarios/software_criteria/control_flow_v1/benchmark.toml index 5817178..0ef3819 100644 --- a/tests/unit/harness/scenarios/software_criteria/control_flow_v1/benchmark.toml +++ b/tests/unit/asp_python/scenarios/software_criteria/control_flow_v1/benchmark.toml @@ -1,5 +1,5 @@ harness = "pytest" -test = "tests/unit/harness/test_software_criterion_snapshots.py" +test = "tests/unit/asp_python/test_software_criterion_snapshots.py" snapshot = "software_criteria/control_flow_v1" phase = "cold" target_total = "25ms" diff --git a/tests/unit/harness/scenarios/software_criteria/control_flow_v1/expect/findings.json b/tests/unit/asp_python/scenarios/software_criteria/control_flow_v1/expect/findings.json similarity index 100% rename from tests/unit/harness/scenarios/software_criteria/control_flow_v1/expect/findings.json rename to tests/unit/asp_python/scenarios/software_criteria/control_flow_v1/expect/findings.json diff --git a/tests/unit/harness/scenarios/software_criteria/control_flow_v1/inputs/criterion.py b/tests/unit/asp_python/scenarios/software_criteria/control_flow_v1/inputs/criterion.py similarity index 100% rename from tests/unit/harness/scenarios/software_criteria/control_flow_v1/inputs/criterion.py rename to tests/unit/asp_python/scenarios/software_criteria/control_flow_v1/inputs/criterion.py diff --git a/tests/unit/harness/scenarios/software_criteria/control_flow_v1/scenario.toml b/tests/unit/asp_python/scenarios/software_criteria/control_flow_v1/scenario.toml similarity index 100% rename from tests/unit/harness/scenarios/software_criteria/control_flow_v1/scenario.toml rename to tests/unit/asp_python/scenarios/software_criteria/control_flow_v1/scenario.toml diff --git a/tests/unit/harness/snapshot_support.py b/tests/unit/asp_python/snapshot_support.py similarity index 91% rename from tests/unit/harness/snapshot_support.py rename to tests/unit/asp_python/snapshot_support.py index c2c0c9b..ca6a089 100644 --- a/tests/unit/harness/snapshot_support.py +++ b/tests/unit/asp_python/snapshot_support.py @@ -1,4 +1,4 @@ -"""Snapshot assertion helpers for harness policy and renderer tests.""" +"""Snapshot assertion helpers for ASP Python policy and renderer tests.""" from __future__ import annotations @@ -6,7 +6,7 @@ from pathlib import Path SNAPSHOT_ROOT = Path(__file__).parent.parent / "snapshots" -UPDATE_ENV_VAR = "PYTHON_HARNESS_UPDATE_SNAPSHOTS" +UPDATE_ENV_VAR = "ASP_PYTHON_UPDATE_SNAPSHOTS" def assert_snapshot( diff --git a/tests/unit/harness/test_agent_algorithm_policy.py b/tests/unit/asp_python/test_agent_algorithm_policy.py similarity index 86% rename from tests/unit/harness/test_agent_algorithm_policy.py rename to tests/unit/asp_python/test_agent_algorithm_policy.py index 4b3a679..7647dc8 100644 --- a/tests/unit/harness/test_agent_algorithm_policy.py +++ b/tests/unit/asp_python/test_agent_algorithm_policy.py @@ -5,9 +5,9 @@ from snapshot_support import assert_snapshot, normalize_temp_root -from python_lang_project_harness import ( - render_python_lang_harness, - run_python_lang_harness, +from asp_python import ( + render_asp_python_report, + run_asp_python_paths, ) if TYPE_CHECKING: @@ -39,7 +39,7 @@ def classify(kind: str, rows: list[object]) -> int: encoding="utf-8", ) - report = run_python_lang_harness([source]) + report = run_asp_python_paths([source]) filtered = replace( report, findings=tuple( @@ -48,7 +48,7 @@ def classify(kind: str, rows: list[object]) -> int: if finding.rule_id == "PY-AGENT-POLICY-009" ), ) - rendered = normalize_temp_root(render_python_lang_harness(filtered), tmp_path) + rendered = normalize_temp_root(render_asp_python_report(filtered), tmp_path) assert filtered.findings, "expected PY-AGENT-POLICY-009 finding" assert ( @@ -58,7 +58,7 @@ def classify(kind: str, rows: list[object]) -> int: assert_snapshot( "unit_test__agent_policy_snapshot__py_agent_r009_algorithm_shape", rendered, - source="tests/unit/harness/test_agent_algorithm_policy.py", + source="tests/unit/asp_python/test_agent_algorithm_policy.py", ) @@ -80,7 +80,7 @@ def classify(enabled: bool, ready: bool, valid: bool, active: bool) -> int: encoding="utf-8", ) - report = run_python_lang_harness([source]) + report = run_asp_python_paths([source]) findings = tuple( finding @@ -115,7 +115,7 @@ def classify(groups: list[list[int]]) -> int: encoding="utf-8", ) - report = run_python_lang_harness([source]) + report = run_asp_python_paths([source]) findings = tuple( finding @@ -149,7 +149,7 @@ def classify(kind: str) -> int: encoding="utf-8", ) - report = run_python_lang_harness([source]) + report = run_asp_python_paths([source]) assert not any( finding.rule_id == "PY-AGENT-POLICY-009" for finding in report.findings @@ -160,7 +160,7 @@ def test_py_agent_r010_function_compactness_snapshot(tmp_path: Path) -> None: source = tmp_path / "service.py" source.write_text(_broad_linear_function_source(), encoding="utf-8") - report = run_python_lang_harness([source]) + report = run_asp_python_paths([source]) filtered = replace( report, findings=tuple( @@ -169,7 +169,7 @@ def test_py_agent_r010_function_compactness_snapshot(tmp_path: Path) -> None: if finding.rule_id == "PY-AGENT-POLICY-010" ), ) - rendered = normalize_temp_root(render_python_lang_harness(filtered), tmp_path) + rendered = normalize_temp_root(render_asp_python_report(filtered), tmp_path) assert filtered.findings, "expected PY-AGENT-POLICY-010 finding" assert ( @@ -179,7 +179,7 @@ def test_py_agent_r010_function_compactness_snapshot(tmp_path: Path) -> None: assert_snapshot( "unit_test__agent_policy_snapshot__py_agent_r010_function_compactness", rendered, - source="tests/unit/harness/test_agent_algorithm_policy.py", + source="tests/unit/asp_python/test_agent_algorithm_policy.py", ) @@ -216,7 +216,7 @@ def has_admin(values: list[str]) -> bool: encoding="utf-8", ) - report = run_python_lang_harness([source]) + report = run_asp_python_paths([source]) filtered = replace( report, findings=tuple( @@ -225,7 +225,7 @@ def has_admin(values: list[str]) -> bool: if finding.rule_id == "PY-AGENT-POLICY-011" ), ) - rendered = normalize_temp_root(render_python_lang_harness(filtered), tmp_path) + rendered = normalize_temp_root(render_asp_python_report(filtered), tmp_path) assert filtered.findings, "expected PY-AGENT-POLICY-011 finding" assert ( @@ -235,7 +235,7 @@ def has_admin(values: list[str]) -> bool: assert_snapshot( "unit_test__agent_policy_snapshot__py_agent_r011_native_idiom", rendered, - source="tests/unit/harness/test_agent_algorithm_policy.py", + source="tests/unit/asp_python/test_agent_algorithm_policy.py", ) @@ -258,7 +258,7 @@ def __repr__(self) -> str: encoding="utf-8", ) - report = run_python_lang_harness([source]) + report = run_asp_python_paths([source]) filtered = replace( report, findings=tuple( @@ -267,13 +267,13 @@ def __repr__(self) -> str: if finding.rule_id == "PY-AGENT-POLICY-012" ), ) - rendered = normalize_temp_root(render_python_lang_harness(filtered), tmp_path) + rendered = normalize_temp_root(render_asp_python_report(filtered), tmp_path) assert filtered.findings, "expected PY-AGENT-POLICY-012 finding" assert_snapshot( "unit_test__agent_policy_snapshot__py_agent_r012_type_shape", rendered, - source="tests/unit/harness/test_agent_algorithm_policy.py", + source="tests/unit/asp_python/test_agent_algorithm_policy.py", ) @@ -295,7 +295,7 @@ class CustomerRecord: encoding="utf-8", ) - report = run_python_lang_harness([source]) + report = run_asp_python_paths([source]) assert not any( finding.rule_id == "PY-AGENT-POLICY-012" for finding in report.findings diff --git a/tests/unit/harness/test_agent_policy.py b/tests/unit/asp_python/test_agent_policy.py similarity index 90% rename from tests/unit/harness/test_agent_policy.py rename to tests/unit/asp_python/test_agent_policy.py index 9f45300..9fc460c 100644 --- a/tests/unit/harness/test_agent_policy.py +++ b/tests/unit/asp_python/test_agent_policy.py @@ -3,15 +3,15 @@ from dataclasses import replace from typing import TYPE_CHECKING -from python_lang_parser import PythonDiagnosticSeverity -from python_lang_project_harness import ( +from asp_python import ( PythonAgentPolicyRulePack, python_agent_policy_rules, - render_python_lang_harness, - render_python_lang_harness_advice, - run_python_lang_harness, - run_python_project_harness, + render_asp_python_report, + render_asp_python_report_advice, + run_asp_python, + run_asp_python_paths, ) +from python_lang_parser import PythonDiagnosticSeverity if TYPE_CHECKING: from pathlib import Path @@ -21,12 +21,12 @@ def test_agent_policy_reports_compact_repairable_snapshot(tmp_path: Path) -> Non source = tmp_path / "service.py" source.write_text("def build(value):\n return value\n", encoding="utf-8") - report = run_python_lang_harness([source]) - output = render_python_lang_harness_advice(report) + report = run_asp_python_paths([source]) + output = render_asp_python_report_advice(report) output = output.replace(str(source), "$TMP/service.py") assert report.is_clean - rendered = render_python_lang_harness(report) + rendered = render_asp_python_report(report) assert rendered.startswith("[advice]\n[PY-AGENT-POLICY-001]") assert "[ok]" not in rendered assert ( @@ -55,10 +55,10 @@ def test_agent_policy_accepts_documented_annotated_public_callable( encoding="utf-8", ) - report = run_python_lang_harness([source]) + report = run_asp_python_paths([source]) assert report.is_clean - assert render_python_lang_harness_advice(report) == "" + assert render_asp_python_report_advice(report) == "" def test_agent_policy_skips_test_modules(tmp_path: Path) -> None: @@ -67,7 +67,7 @@ def test_agent_policy_skips_test_modules(tmp_path: Path) -> None: source = tests / "test_service.py" source.write_text("def test_value():\n assert True\n", encoding="utf-8") - report = run_python_lang_harness([tmp_path]) + report = run_asp_python_paths([tmp_path]) assert report.is_clean @@ -86,7 +86,7 @@ def test_agent_policy_blocks_duplicate_public_callable_names( encoding="utf-8", ) - report = run_python_project_harness(tmp_path) + report = run_asp_python(tmp_path) assert [ (finding.rule_id, finding.location.path) for finding in report.findings @@ -111,7 +111,7 @@ def test_agent_policy_blocks_duplicate_public_type_names( encoding="utf-8", ) - report = run_python_project_harness(tmp_path) + report = run_asp_python(tmp_path) assert [ (finding.rule_id, finding.location.path) for finding in report.findings @@ -136,7 +136,7 @@ def test_agent_policy_blocks_duplicate_public_value_names( encoding="utf-8", ) - report = run_python_project_harness(tmp_path) + report = run_asp_python(tmp_path) assert [ (finding.rule_id, finding.location.path) for finding in report.findings @@ -157,7 +157,7 @@ def test_agent_policy_blocks_repeated_module_namespace_segments( '"""Domain service namespace."""\n\nVALUE = 1\n', encoding="utf-8" ) - report = run_python_project_harness(tmp_path) + report = run_asp_python(tmp_path) assert [ (finding.rule_id, finding.location.path) for finding in report.findings @@ -179,7 +179,7 @@ def test_agent_policy_deduplicates_repeated_namespace_branches( encoding="utf-8", ) - report = run_python_project_harness(tmp_path) + report = run_asp_python(tmp_path) assert [finding.rule_id for finding in report.findings] == ["PY-AGENT-POLICY-004"] @@ -199,7 +199,7 @@ def test_agent_policy_reports_broad_branch_package_surface(tmp_path: Path) -> No encoding="utf-8", ) - report = run_python_project_harness(tmp_path) + report = run_asp_python(tmp_path) assert [ (finding.rule_id, finding.location.path) for finding in report.findings @@ -230,7 +230,7 @@ def test_agent_policy_accepts_owner_map_documented_branch_package( encoding="utf-8", ) - report = run_python_project_harness(tmp_path) + report = run_asp_python(tmp_path) assert not report.findings @@ -241,7 +241,7 @@ def test_agent_policy_advice_can_be_promoted_to_blocking( source = tmp_path / "service.py" source.write_text("def build(value):\n return value\n", encoding="utf-8") - report = run_python_lang_harness([source]) + report = run_asp_python_paths([source]) assert report.is_clean assert [finding.rule_id for finding in report.advisory_findings()] == [ @@ -259,7 +259,7 @@ def test_agent_policy_advice_can_be_promoted_to_blocking( "PY-AGENT-POLICY-002", ] promoted = replace(report, blocking_rule_ids=frozenset({"PY-AGENT-POLICY-001"})) - advice = render_python_lang_harness_advice(promoted) + advice = render_asp_python_report_advice(promoted) assert "[PY-AGENT-POLICY-001]" not in advice assert advice.startswith("[PY-AGENT-POLICY-002]") diff --git a/tests/unit/harness/test_agent_policy_snapshots.py b/tests/unit/asp_python/test_agent_policy_snapshots.py similarity index 93% rename from tests/unit/harness/test_agent_policy_snapshots.py rename to tests/unit/asp_python/test_agent_policy_snapshots.py index b698a56..fcf0629 100644 --- a/tests/unit/harness/test_agent_policy_snapshots.py +++ b/tests/unit/asp_python/test_agent_policy_snapshots.py @@ -5,10 +5,10 @@ from snapshot_support import assert_snapshot, normalize_temp_root -from python_lang_project_harness import ( - render_python_lang_harness, - run_python_lang_harness, - run_python_project_harness, +from asp_python import ( + render_asp_python_report, + run_asp_python, + run_asp_python_paths, ) if TYPE_CHECKING: @@ -157,7 +157,7 @@ def _assert_lang_snapshot( rule_id: str, snapshot_name: str, ) -> None: - report = run_python_lang_harness(paths) + report = run_asp_python_paths(paths) _assert_filtered_snapshot(root, report, rule_id, snapshot_name) @@ -166,7 +166,7 @@ def _assert_project_snapshot( rule_id: str, snapshot_name: str, ) -> None: - report = run_python_project_harness(root) + report = run_asp_python(root) _assert_filtered_snapshot(root, report, rule_id, snapshot_name) @@ -183,9 +183,9 @@ def _assert_filtered_snapshot( ), ) assert filtered.findings, f"expected at least one {rule_id} finding" - rendered = normalize_temp_root(render_python_lang_harness(filtered), root) + rendered = normalize_temp_root(render_asp_python_report(filtered), root) assert_snapshot( f"unit_test__agent_policy_snapshot__{snapshot_name}", rendered, - source="tests/unit/harness/test_agent_policy_snapshots.py", + source="tests/unit/asp_python/test_agent_policy_snapshots.py", ) diff --git a/tests/unit/asp_python/test_asp_rules.py b/tests/unit/asp_python/test_asp_rules.py new file mode 100644 index 0000000..90b4e54 --- /dev/null +++ b/tests/unit/asp_python/test_asp_rules.py @@ -0,0 +1,83 @@ +from __future__ import annotations + +import os +import tempfile +from pathlib import Path + +from asp_python._agent_policy_catalog import python_agent_policy_rules +from asp_python._asp_rules import ( + asp_python_rules_markdown, + render_asp_python_rules_markdown, + write_asp_python_rules_to_unit_tests, +) +from asp_python._modern_design_catalog import ( + python_modern_design_rules, +) +from asp_python._modularity import python_modularity_rules +from asp_python._project_policy_catalog import ( + python_project_policy_rules, +) +from asp_python._test_layout_catalog import python_test_layout_rules + + +def _asp_rules_rule_ids() -> list[str]: + rule_ids: list[str] = [] + for line in asp_python_rules_markdown().splitlines(): + rule_id, _ = line.removeprefix("- ").split(": ", 1) + rule_ids.append(rule_id) + return rule_ids + + +def _catalog_rule_ids() -> list[str]: + rules = ( + *python_agent_policy_rules(), + *python_modern_design_rules(), + *python_modularity_rules(), + *python_project_policy_rules(), + *python_test_layout_rules(), + ) + return [rule.rule_id for rule in rules] + + +def test_asp_rules_markdown_is_plain_rule_id_list() -> None: + count = 0 + for index, line in enumerate(asp_python_rules_markdown().splitlines(), start=1): + assert line.startswith("- "), index + rule_id, sentence = line.removeprefix("- ").split(": ", 1) + + assert rule_id.startswith( + ( + "PY-AGENT-R", + "PY-AGENT-POLICY", + "PY-AGENT-PROJECT", + "PY-MOD-R", + "PY-PROJ-R", + "PY-TEST-R", + ) + ) + assert sentence.endswith(".") + assert sum(sentence.count(mark) for mark in ".!?") == 1 + count += 1 + + assert count == 32 + + +def test_asp_rules_ids_match_rule_catalog() -> None: + assert sorted(_asp_rules_rule_ids()) == sorted(_catalog_rule_ids()) + + +def test_generated_asp_rules_matches_unit_fixture() -> None: + unit_dir = Path(__file__).resolve().parent + fixture = unit_dir / "asp-rules.generated.md" + if os.environ.get("UPDATE_HARNESS_RULES"): + write_asp_python_rules_to_unit_tests(unit_dir) + + assert fixture.read_text(encoding="utf-8") == render_asp_python_rules_markdown() + + +def test_asp_rules_writer_targets_requested_unit_dir() -> None: + with tempfile.TemporaryDirectory() as directory: + output = write_asp_python_rules_to_unit_tests(Path(directory)) + + assert output == Path(directory) / "asp-rules.generated.md" + assert output.read_text(encoding="utf-8") == render_asp_python_rules_markdown() diff --git a/tests/unit/asp_python/test_cli.py b/tests/unit/asp_python/test_cli.py new file mode 100644 index 0000000..ad16b85 --- /dev/null +++ b/tests/unit/asp_python/test_cli.py @@ -0,0 +1,58 @@ +from __future__ import annotations + +import io +from typing import TYPE_CHECKING + +from asp_python import run_cli + +if TYPE_CHECKING: + from pathlib import Path + + +def test_cli_help_advertises_the_current_provider_protocol() -> None: + stdout = io.StringIO() + + exit_code = run_cli(["--help"], stdout=stdout) + + rendered = stdout.getvalue() + assert exit_code == 0 + assert "asp search playbook --language python" in rendered + assert "asp-python search " not in rendered + assert "asp query playbook --language python --selector" in rendered + assert "asp-python evidence" not in rendered + assert "asp-python agent doctor" in rendered + + +def test_cli_subcommand_help_advertises_exact_projection() -> None: + for args in (["query", "--help"],): + stdout = io.StringIO() + + exit_code = run_cli(args, stdout=stdout) + + rendered = stdout.getvalue() + assert exit_code == 0 + assert "--selector " in rendered + assert "--projection " in rendered + + +def test_cli_agent_guide_advertises_exact_source_route(tmp_path: Path) -> None: + stdout = io.StringIO() + + exit_code = run_cli(["agent", "guide", str(tmp_path)], stdout=stdout) + + assert exit_code == 0 + assert ( + "asp query playbook --language python --selector " + in stdout.getvalue() + ) + assert "|cmd playbook=asp search playbook --language python" in stdout.getvalue() + assert "|policy authority=asp-python-api trigger=pytest-plugin" in stdout.getvalue() + + +def test_cli_without_command_renders_help_instead_of_running_policy() -> None: + stdout = io.StringIO() + + exit_code = run_cli((), stdout=stdout) + + assert exit_code == 0 + assert stdout.getvalue().startswith("asp-python ") diff --git a/tests/unit/harness/test_dependency_topology.py b/tests/unit/asp_python/test_dependency_topology.py similarity index 97% rename from tests/unit/harness/test_dependency_topology.py rename to tests/unit/asp_python/test_dependency_topology.py index 2535bc8..75285ae 100644 --- a/tests/unit/harness/test_dependency_topology.py +++ b/tests/unit/asp_python/test_dependency_topology.py @@ -3,7 +3,7 @@ import re from pathlib import Path -from python_lang_project_harness._dependency_topology import ( +from asp_python._dependency_topology import ( build_dependency_topology_packet, ) diff --git a/tests/unit/harness/test_dev_command_log.py b/tests/unit/asp_python/test_dev_command_log.py similarity index 94% rename from tests/unit/harness/test_dev_command_log.py rename to tests/unit/asp_python/test_dev_command_log.py index a019840..a44e206 100644 --- a/tests/unit/harness/test_dev_command_log.py +++ b/tests/unit/asp_python/test_dev_command_log.py @@ -5,7 +5,7 @@ import json from pathlib import Path -from python_lang_project_harness._dev_command_log import start_dev_command_log +from asp_python._dev_command_log import start_dev_command_log def test_dev_command_log_records_ordered_active_context_events( @@ -46,7 +46,7 @@ def test_dev_command_log_records_ordered_active_context_events( monkeypatch.delenv("AGENT_HOOK_RUN_ID", raising=False) log = start_dev_command_log( - ["search", "lexical", "metadata", str(project)], project + ["query", "--selector", "python://src/demo.py#item/function/run"], project ) log.finish(0) diff --git a/tests/unit/harness/test_exact_source_projection.py b/tests/unit/asp_python/test_exact_source_projection.py similarity index 66% rename from tests/unit/harness/test_exact_source_projection.py rename to tests/unit/asp_python/test_exact_source_projection.py index c9c7258..596b8f9 100644 --- a/tests/unit/harness/test_exact_source_projection.py +++ b/tests/unit/asp_python/test_exact_source_projection.py @@ -1,10 +1,8 @@ from __future__ import annotations import base64 -import json -from pathlib import Path -from python_lang_project_harness._exact_source_projection import ( +from asp_python._exact_source_projection import ( project_provider_native_exact_request, ) @@ -43,6 +41,7 @@ def invoke(selector: str, projection_kind: str) -> dict[str, object]: skeleton = invoke(root_selector, "callable-skeleton") payload = skeleton["projectionPayload"] assert isinstance(payload, dict) + assert payload["rootSelector"] == root_selector assert "schemaId" not in payload assert "schemaVersion" not in payload assert "projectionKind" not in payload @@ -55,6 +54,12 @@ def invoke(selector: str, projection_kind: str) -> dict[str, object]: branch = invoke(branch_selector, "source") assert branch["schemaVersion"] == "1" + assert branch["ownerPath"] == "src/example.py" + assert branch["normalizedParserFacts"] == { + "itemKind": "function", + "itemName": "selected", + "scopes": [], + } assert branch["requestedStructuralSelector"] == branch_selector assert branch["structuralSelector"] == branch_selector assert branch["projectionText"] == "if value > 1:\n return value" @@ -84,40 +89,47 @@ def test_provider_does_not_recompute_asp_source_digest(tmp_path) -> None: assert packet["sourceContentDigest"] == "asp-owned-content-identity" -def test_resolved_projection_satisfies_central_schema_branch(tmp_path) -> None: - source = b"def selected() -> int:\n return 1\n" - selector = "python://src/example.py#item/function/selected" - packet = project_provider_native_exact_request( - { - "schemaId": "agent.semantic-protocols.provider-native-exact-request", - "schemaVersion": "1", - "languageId": "python", - "providerId": "asp-python", - "structuralSelector": selector, - "ownerPath": "src/example.py", - "projectionKind": "source", - "generationIdentityDigest": "a" * 64, - "parserIdentityDigest": "b" * 64, - "queryPackDigest": "c" * 64, - "sourceDigest": "d" * 64, - "sourceByteLength": len(source), - "sourceEncoding": "base64", - "sourceBytesBase64": base64.b64encode(source).decode(), - "transport": "stdin-json", - }, - cwd=tmp_path, +def test_scoped_method_selector_resolves_the_declaring_type(tmp_path) -> None: + source = ( + b"class Agent:\n" + b" def run(self) -> int:\n" + b" return 1\n\n" + b"class Other:\n" + b" def run(self) -> int:\n" + b" return 2\n" ) - schema = json.loads( - Path("schemas/provider-native-exact-response.v1.schema.json").read_text() + selector = ( + "python://src/example.py#item/method/run/scope/implementation-owner/type/Agent" ) - required = set(schema["required"]) | set(schema["oneOf"][0]["required"]) + request = { + "schemaId": "agent.semantic-protocols.provider-native-exact-request", + "schemaVersion": "1", + "languageId": "python", + "providerId": "asp-python", + "structuralSelector": selector, + "ownerPath": "src/example.py", + "projectionKind": "source", + "generationIdentityDigest": "a" * 64, + "parserIdentityDigest": "b" * 64, + "queryPackDigest": "c" * 64, + "sourceDigest": "d" * 64, + "sourceByteLength": len(source), + "sourceEncoding": "base64", + "sourceBytesBase64": base64.b64encode(source).decode(), + "transport": "stdin-json", + } - assert required <= packet.keys() - assert packet.keys() <= schema["properties"].keys() - assert packet["ownerPath"] == "src/example.py" - assert packet["resolutionState"] == "resolved" + packet = project_provider_native_exact_request(request, cwd=tmp_path) + + assert packet["projectionText"] == "def run(self) -> int:\n return 1" assert packet["normalizedParserFacts"] == { - "parserKind": "python-ast", - "itemKind": "function", - "itemName": "selected", + "itemKind": "method", + "itemName": "run", + "scopes": [ + { + "role": "implementation-owner", + "ownerKind": "type", + "ownerName": "Agent", + } + ], } diff --git a/tests/unit/harness/test_modern_design.py b/tests/unit/asp_python/test_modern_design.py similarity index 88% rename from tests/unit/harness/test_modern_design.py rename to tests/unit/asp_python/test_modern_design.py index 07fc697..b2a8c8b 100644 --- a/tests/unit/harness/test_modern_design.py +++ b/tests/unit/asp_python/test_modern_design.py @@ -2,13 +2,13 @@ from typing import TYPE_CHECKING -from python_lang_parser import PythonDiagnosticSeverity -from python_lang_project_harness import ( +from asp_python import ( PythonModernDesignRulePack, python_modern_design_rules, - render_python_lang_harness, - run_python_lang_harness, + render_asp_python_report, + run_asp_python_paths, ) +from python_lang_parser import PythonDiagnosticSeverity if TYPE_CHECKING: from pathlib import Path @@ -23,8 +23,8 @@ def test_modern_design_rule_pack_reports_numbered_rules_in_compact_snapshot( encoding="utf-8", ) - output = render_python_lang_harness( - run_python_lang_harness([source], rule_packs=(PythonModernDesignRulePack(),)) + output = render_asp_python_report( + run_asp_python_paths([source], rule_packs=(PythonModernDesignRulePack(),)) ) output = output.replace(str(source), "$TMP/module.py") @@ -60,9 +60,7 @@ def test_modern_design_rule_pack_requires_all_for_package_facade( init_file.write_text("from .api import Runner\n", encoding="utf-8") (package / "api.py").write_text("class Runner:\n pass\n", encoding="utf-8") - report = run_python_lang_harness( - [package], rule_packs=(PythonModernDesignRulePack(),) - ) + report = run_asp_python_paths([package], rule_packs=(PythonModernDesignRulePack(),)) assert [ (finding.rule_id, finding.location.path) for finding in report.findings @@ -80,9 +78,7 @@ def test_modern_design_rule_pack_accepts_explicit_facade_all(tmp_path: Path) -> ) (package / "api.py").write_text("class Runner:\n pass\n", encoding="utf-8") - report = run_python_lang_harness( - [package], rule_packs=(PythonModernDesignRulePack(),) - ) + report = run_asp_python_paths([package], rule_packs=(PythonModernDesignRulePack(),)) assert report.is_clean @@ -97,9 +93,7 @@ def test_modern_design_rule_pack_rejects_dynamic_facade_all(tmp_path: Path) -> N ) (package / "api.py").write_text("class Runner:\n pass\n", encoding="utf-8") - report = run_python_lang_harness( - [package], rule_packs=(PythonModernDesignRulePack(),) - ) + report = run_asp_python_paths([package], rule_packs=(PythonModernDesignRulePack(),)) assert [ (finding.rule_id, finding.location.path) for finding in report.findings @@ -118,9 +112,7 @@ def test_modern_design_rule_pack_rejects_augmented_facade_all(tmp_path: Path) -> ) (package / "api.py").write_text("class Runner:\n pass\n", encoding="utf-8") - report = run_python_lang_harness( - [package], rule_packs=(PythonModernDesignRulePack(),) - ) + report = run_asp_python_paths([package], rule_packs=(PythonModernDesignRulePack(),)) assert [ (finding.rule_id, finding.location.path) for finding in report.findings @@ -138,7 +130,7 @@ def test_modern_design_rule_pack_skips_prints_in_tests(tmp_path: Path) -> None: encoding="utf-8", ) - report = run_python_lang_harness( + report = run_asp_python_paths( [tmp_path], rule_packs=(PythonModernDesignRulePack(),) ) diff --git a/tests/unit/harness/test_modularity_catalog.py b/tests/unit/asp_python/test_modularity_catalog.py similarity index 92% rename from tests/unit/harness/test_modularity_catalog.py rename to tests/unit/asp_python/test_modularity_catalog.py index 6d474ec..d5e0a46 100644 --- a/tests/unit/harness/test_modularity_catalog.py +++ b/tests/unit/asp_python/test_modularity_catalog.py @@ -2,7 +2,7 @@ from typing import TYPE_CHECKING -from python_lang_project_harness import run_python_project_harness +from asp_python import run_asp_python if TYPE_CHECKING: from pathlib import Path @@ -16,7 +16,7 @@ def test_modularity_rule_pack_blocks_large_class_only_service_module( source = src / "services.py" source.write_text(_large_class_only_service_module_source(), encoding="utf-8") - report = run_python_project_harness(tmp_path) + report = run_asp_python(tmp_path) assert [ (finding.rule_id, finding.location.path) for finding in report.findings @@ -33,7 +33,7 @@ def test_modularity_rule_pack_allows_large_single_signal_state_module( source = src / "constants.py" source.write_text(_large_state_only_module_source(), encoding="utf-8") - report = run_python_project_harness(tmp_path) + report = run_asp_python(tmp_path) assert [finding.rule_id for finding in report.findings] == [] @@ -46,7 +46,7 @@ def test_modularity_rule_pack_allows_large_single_return_literal_fixture( source = src / "schema_fixture.py" source.write_text(_large_return_literal_fixture_module_source(), encoding="utf-8") - report = run_python_project_harness(tmp_path) + report = run_asp_python(tmp_path) assert [finding.rule_id for finding in report.findings] == [] @@ -59,7 +59,7 @@ def test_modularity_rule_pack_blocks_large_module_with_long_function( source = src / "orchestrator.py" source.write_text(_large_long_function_module_source(), encoding="utf-8") - report = run_python_project_harness(tmp_path) + report = run_asp_python(tmp_path) findings = [ finding for finding in report.findings if finding.rule_id == "PY-MOD-R006" diff --git a/tests/unit/harness/test_parser_boundary_contract.py b/tests/unit/asp_python/test_parser_boundary_contract.py similarity index 68% rename from tests/unit/harness/test_parser_boundary_contract.py rename to tests/unit/asp_python/test_parser_boundary_contract.py index 099370c..e7c7306 100644 --- a/tests/unit/harness/test_parser_boundary_contract.py +++ b/tests/unit/asp_python/test_parser_boundary_contract.py @@ -9,12 +9,12 @@ ) -def test_harness_policy_does_not_parse_python_source_directly() -> None: - harness_sources = sorted( - (_PROJECT_ROOT / "src" / "python_lang_project_harness").glob("*_policy*.py") +def test_asp_python_policy_does_not_parse_python_source_directly() -> None: + asp_python_sources = sorted( + (_PROJECT_ROOT / "src" / "asp_python").glob("*_policy*.py") ) - for path in harness_sources: + for path in asp_python_sources: source = path.read_text(encoding="utf-8") assert "import ast" not in source, path assert "import tokenize" not in source, path @@ -22,33 +22,25 @@ def test_harness_policy_does_not_parse_python_source_directly() -> None: assert "tokenize." not in source, path -def test_harness_semantic_roles_use_parser_symbol_helpers() -> None: - harness_sources = sorted( - (_PROJECT_ROOT / "src" / "python_lang_project_harness").rglob("*.py") - ) - for path in harness_sources: +def test_asp_python_semantic_roles_use_parser_symbol_helpers() -> None: + asp_python_sources = sorted((_PROJECT_ROOT / "src" / "asp_python").rglob("*.py")) + for path in asp_python_sources: if path.name == "__init__.py": continue source = path.read_text(encoding="utf-8") assert "PythonSymbolKind" not in source, path test_bloat = ( - _PROJECT_ROOT / "src" / "python_lang_project_harness" / "_test_layout_bloat.py" + _PROJECT_ROOT / "src" / "asp_python" / "_test_layout_bloat.py" ).read_text(encoding="utf-8") agent_policy = ( - _PROJECT_ROOT / "src" / "python_lang_project_harness" / "_agent_policy.py" + _PROJECT_ROOT / "src" / "asp_python" / "_agent_policy.py" ).read_text(encoding="utf-8") typed_policy = ( - _PROJECT_ROOT - / "src" - / "python_lang_project_harness" - / "_project_policy_typed.py" + _PROJECT_ROOT / "src" / "asp_python" / "_project_policy_typed.py" ).read_text(encoding="utf-8") namespace_index = ( - _PROJECT_ROOT - / "src" - / "python_lang_project_harness" - / "_agent_namespace_index.py" + _PROJECT_ROOT / "src" / "asp_python" / "_agent_namespace_index.py" ).read_text(encoding="utf-8") assert "python_symbol_is_test_function(" in test_bloat @@ -70,9 +62,9 @@ def test_harness_semantic_roles_use_parser_symbol_helpers() -> None: def test_agent_namespace_policy_uses_parser_module_identity_helpers() -> None: - source = ( - _PROJECT_ROOT / "src" / "python_lang_project_harness" / "_agent_namespace.py" - ).read_text(encoding="utf-8") + source = (_PROJECT_ROOT / "src" / "asp_python" / "_agent_namespace.py").read_text( + encoding="utf-8" + ) assert "python_module_namespace_parts(" in source assert "relative_to(" not in source @@ -81,13 +73,10 @@ def test_agent_namespace_policy_uses_parser_module_identity_helpers() -> None: def test_python_semantic_policy_uses_parser_name_helpers() -> None: typed_policy = ( - _PROJECT_ROOT - / "src" - / "python_lang_project_harness" - / "_project_policy_typed.py" + _PROJECT_ROOT / "src" / "asp_python" / "_project_policy_typed.py" ).read_text(encoding="utf-8") modern_design = ( - _PROJECT_ROOT / "src" / "python_lang_project_harness" / "_modern_design.py" + _PROJECT_ROOT / "src" / "asp_python" / "_modern_design.py" ).read_text(encoding="utf-8") assert "python_symbol_is_public_class(" in typed_policy @@ -97,14 +86,11 @@ def test_python_semantic_policy_uses_parser_name_helpers() -> None: def test_test_layout_python_source_lines_use_parser_reports() -> None: - layout = ( - _PROJECT_ROOT / "src" / "python_lang_project_harness" / "_test_layout.py" - ).read_text(encoding="utf-8") + layout = (_PROJECT_ROOT / "src" / "asp_python" / "_test_layout.py").read_text( + encoding="utf-8" + ) entries = ( - _PROJECT_ROOT - / "src" - / "python_lang_project_harness" - / "_test_layout_entries.py" + _PROJECT_ROOT / "src" / "asp_python" / "_test_layout_entries.py" ).read_text(encoding="utf-8") assert "tests_root_entry_findings(tests_dir, pack_id, modules)" in layout @@ -112,12 +98,10 @@ def test_test_layout_python_source_lines_use_parser_reports() -> None: assert "return report.source_line(line)" in entries -def test_harness_pyproject_metadata_comes_from_parser_boundary() -> None: - harness_sources = sorted( - (_PROJECT_ROOT / "src" / "python_lang_project_harness").rglob("*.py") - ) +def test_asp_python_pyproject_metadata_comes_from_parser_boundary() -> None: + asp_python_sources = sorted((_PROJECT_ROOT / "src" / "asp_python").rglob("*.py")) - for path in harness_sources: + for path in asp_python_sources: if path.name in { "_project_config.py", "_project_resolution_candidates.py", @@ -130,9 +114,7 @@ def test_harness_pyproject_metadata_comes_from_parser_boundary() -> None: def test_agent_readability_policy_consumes_parser_function_facts() -> None: - readability_root = ( - _PROJECT_ROOT / "src" / "python_lang_project_harness" / "agent_readability" - ) + readability_root = _PROJECT_ROOT / "src" / "asp_python" / "agent_readability" for path in sorted(readability_root.glob("*.py")): source = path.read_text(encoding="utf-8") diff --git a/tests/unit/harness/test_policy_contract.py b/tests/unit/asp_python/test_policy_contract.py similarity index 96% rename from tests/unit/harness/test_policy_contract.py rename to tests/unit/asp_python/test_policy_contract.py index 37ac32c..cfb0cfe 100644 --- a/tests/unit/harness/test_policy_contract.py +++ b/tests/unit/asp_python/test_policy_contract.py @@ -2,9 +2,8 @@ from pathlib import Path -from python_lang_parser import PythonDiagnosticSeverity -from python_lang_project_harness import ( - default_python_harness_config, +from asp_python import ( + default_asp_python_config, python_agent_policy_rules, python_modern_design_rules, python_modularity_rules, @@ -12,9 +11,10 @@ python_rule_pack_descriptors, python_syntax_rules, python_test_layout_rules, - render_python_lang_harness, - run_python_project_harness, + render_asp_python_report, + run_asp_python, ) +from python_lang_parser import PythonDiagnosticSeverity _PROJECT_ROOT = next( parent @@ -73,7 +73,7 @@ def test_default_policy_blocks_only_warning_and_error() -> None: - config = default_python_harness_config() + config = default_asp_python_config() assert config.blocking_severities == { PythonDiagnosticSeverity.ERROR, @@ -241,9 +241,9 @@ def test_agent_facing_snapshots_avoid_redundant_render_preambles() -> None: assert line not in tree_text -def test_project_is_clean_under_its_own_harness() -> None: - report = run_python_project_harness(_PROJECT_ROOT) - rendered = render_python_lang_harness(report) +def test_project_is_clean_under_its_own_asp_python() -> None: + report = run_asp_python(_PROJECT_ROOT) + rendered = render_asp_python_report(report) assert report.is_clean, rendered assert "[fail]" not in rendered diff --git a/tests/unit/harness/test_policy_snapshots.py b/tests/unit/asp_python/test_policy_snapshots.py similarity index 96% rename from tests/unit/harness/test_policy_snapshots.py rename to tests/unit/asp_python/test_policy_snapshots.py index 7f67cb1..9d14ea1 100644 --- a/tests/unit/harness/test_policy_snapshots.py +++ b/tests/unit/asp_python/test_policy_snapshots.py @@ -5,9 +5,9 @@ from snapshot_support import assert_snapshot, normalize_temp_root -from python_lang_project_harness import ( - render_python_lang_harness, - run_python_project_harness, +from asp_python import ( + render_asp_python_report, + run_asp_python, ) if TYPE_CHECKING: @@ -261,7 +261,7 @@ def test_py_proj_r010_pytest_gate_snapshot(tmp_path: Path) -> None: [dependency-groups] test = [ - "python-lang-project-harness[pytest]>=0.1.0", + "asp-python[pytest]>=0.1.0", ] """.lstrip(), encoding="utf-8", @@ -291,11 +291,11 @@ def test_py_proj_r011_verification_profile_snapshot(tmp_path: Path) -> None: [dependency-groups] test = [ - "python-lang-project-harness[pytest]>=0.1.0", + "asp-python[pytest]>=0.1.0", ] [tool.pytest.ini_options] -addopts = ["--python-project-harness"] +addopts = ["--asp-python"] """.lstrip(), encoding="utf-8", ) @@ -334,7 +334,7 @@ def test_py_test_r003_unit_bloat_snapshot(tmp_path: Path) -> None: def _assert_project_snapshot(root: Path, rule_id: str, snapshot_name: str) -> None: - report = run_python_project_harness(root) + report = run_asp_python(root) findings = tuple( finding for finding in report.findings if finding.rule_id == rule_id ) @@ -342,11 +342,11 @@ def _assert_project_snapshot(root: Path, rule_id: str, snapshot_name: str) -> No assert len(filtered.findings) == 1, ( f"expected one {rule_id} finding, got {filtered.findings!r}" ) - rendered = normalize_temp_root(render_python_lang_harness(filtered), root) + rendered = normalize_temp_root(render_asp_python_report(filtered), root) assert_snapshot( f"unit_test__policy_snapshot__{snapshot_name}", rendered, - source="tests/unit/harness/test_policy_snapshots.py", + source="tests/unit/asp_python/test_policy_snapshots.py", ) diff --git a/tests/unit/harness/test_project_api.py b/tests/unit/asp_python/test_project_api.py similarity index 80% rename from tests/unit/harness/test_project_api.py rename to tests/unit/asp_python/test_project_api.py index 3aea6d8..6d7f5f2 100644 --- a/tests/unit/harness/test_project_api.py +++ b/tests/unit/asp_python/test_project_api.py @@ -2,22 +2,22 @@ from typing import TYPE_CHECKING -from python_lang_project_harness import ( +from asp_python import ( PythonModularityRulePack, PythonTestLayoutRulePack, - assert_python_project_harness_clean, + asp_python_paths, + asp_python_scope, + assert_asp_python_clean, python_modularity_rules, - python_project_harness_paths, - python_project_harness_scope, python_test_layout_rules, - run_python_project_harness, + run_asp_python, ) if TYPE_CHECKING: from pathlib import Path -def test_python_project_harness_paths_use_project_root_by_default( +def test_asp_python_paths_use_project_root_by_default( tmp_path: Path, ) -> None: src = tmp_path / "src" @@ -25,11 +25,11 @@ def test_python_project_harness_paths_use_project_root_by_default( src.mkdir() tests.mkdir() - assert python_project_harness_paths(tmp_path) == (tmp_path,) - assert python_project_harness_paths(tmp_path, include_tests=False) == (src,) + assert asp_python_paths(tmp_path) == (tmp_path,) + assert asp_python_paths(tmp_path, include_tests=False) == (src,) -def test_python_project_harness_scope_exposes_project_and_classification_paths( +def test_asp_python_scope_exposes_project_and_classification_paths( tmp_path: Path, ) -> None: src = tmp_path / "src" @@ -37,7 +37,7 @@ def test_python_project_harness_scope_exposes_project_and_classification_paths( src.mkdir() tests.mkdir() - scope = python_project_harness_scope(tmp_path) + scope = asp_python_scope(tmp_path) assert scope.source_paths == (src,) assert scope.test_paths == (tests,) @@ -47,14 +47,14 @@ def test_python_project_harness_scope_exposes_project_and_classification_paths( assert scope.to_dict()["monitored_paths"] == [str(tmp_path)] -def test_python_project_harness_paths_fall_back_to_root(tmp_path: Path) -> None: +def test_asp_python_paths_fall_back_to_root(tmp_path: Path) -> None: module = tmp_path / "module.py" module.write_text("VALUE = 1\n", encoding="utf-8") - assert python_project_harness_paths(tmp_path) == (tmp_path,) + assert asp_python_paths(tmp_path) == (tmp_path,) -def test_run_python_project_harness_uses_project_paths(tmp_path: Path) -> None: +def test_run_asp_python_uses_project_paths(tmp_path: Path) -> None: src = tmp_path / "src" tests = tmp_path / "tests" / "unit" src.mkdir() @@ -65,7 +65,7 @@ def test_run_python_project_harness_uses_project_paths(tmp_path: Path) -> None: encoding="utf-8", ) - report = run_python_project_harness(tmp_path) + report = run_asp_python(tmp_path) assert report.is_clean assert report.file_count == 2 @@ -83,7 +83,7 @@ def test_run_python_project_harness_uses_project_paths(tmp_path: Path) -> None: } -def test_run_python_project_harness_monitors_src_and_tests_by_default( +def test_run_asp_python_monitors_src_and_tests_by_default( tmp_path: Path, ) -> None: src = tmp_path / "src" / "pkg" @@ -95,7 +95,7 @@ def test_run_python_project_harness_monitors_src_and_tests_by_default( source_file.write_text("VALUE = 1\n", encoding="utf-8") test_file.write_text("def broken(:\n pass\n", encoding="utf-8") - report = run_python_project_harness(tmp_path) + report = run_asp_python(tmp_path) assert [module.path for module in report.modules] == [ str(source_file), @@ -108,7 +108,7 @@ def test_run_python_project_harness_monitors_src_and_tests_by_default( ] -def test_run_python_project_harness_can_exclude_tests_from_scope( +def test_run_asp_python_can_exclude_tests_from_scope( tmp_path: Path, ) -> None: src = tmp_path / "src" @@ -121,7 +121,7 @@ def test_run_python_project_harness_can_exclude_tests_from_scope( encoding="utf-8", ) - report = run_python_project_harness(tmp_path, include_tests=False) + report = run_asp_python(tmp_path, include_tests=False) assert report.is_clean assert [module.path for module in report.modules] == [str(src / "library.py")] @@ -130,7 +130,7 @@ def test_run_python_project_harness_can_exclude_tests_from_scope( assert report.project_resolution.monitored_paths == (src,) -def test_run_python_project_harness_does_not_fallback_into_excluded_tests( +def test_run_asp_python_does_not_fallback_into_excluded_tests( tmp_path: Path, ) -> None: package = tmp_path / "pkg" @@ -140,7 +140,7 @@ def test_run_python_project_harness_does_not_fallback_into_excluded_tests( (package / "__init__.py").write_text('"""Package docs."""\n', encoding="utf-8") (tests / "test_bad.py").write_text("def broken(:\n pass\n", encoding="utf-8") - report = run_python_project_harness(tmp_path, include_tests=False) + report = run_asp_python(tmp_path, include_tests=False) assert report.is_clean assert [module.path for module in report.modules] == [str(package / "__init__.py")] @@ -161,7 +161,7 @@ def test_include_tests_false_skips_test_parsing_not_layout_policy( encoding="utf-8", ) - report = run_python_project_harness(tmp_path, include_tests=False) + report = run_asp_python(tmp_path, include_tests=False) assert report.file_count == 1 assert [finding.rule_id for finding in report.findings] == ["PY-TEST-R001"] @@ -169,18 +169,18 @@ def test_include_tests_false_skips_test_parsing_not_layout_policy( assert report.project_resolution.monitored_paths == (src,) -def test_assert_python_project_harness_clean_blocks_for_pytest(tmp_path: Path) -> None: +def test_assert_asp_python_clean_blocks_for_pytest(tmp_path: Path) -> None: src = tmp_path / "src" src.mkdir() source = src / "library.py" source.write_text('def run() -> None:\n print("debug")\n', encoding="utf-8") try: - assert_python_project_harness_clean(tmp_path) + assert_asp_python_clean(tmp_path) except AssertionError as error: message = str(error) else: - raise AssertionError("project harness should block package warnings") + raise AssertionError("ASP Python should block package warnings") assert "rule=PY-MOD-R002 severity=warning" in message assert "|message Library module uses bare print" in message @@ -188,7 +188,7 @@ def test_assert_python_project_harness_clean_blocks_for_pytest(tmp_path: Path) - assert str(source) not in message -def test_project_harness_blocks_root_pytest_files(tmp_path: Path) -> None: +def test_asp_python_blocks_root_pytest_files(tmp_path: Path) -> None: tests = tmp_path / "tests" tests.mkdir() source = tests / "test_scattered.py" @@ -197,11 +197,11 @@ def test_project_harness_blocks_root_pytest_files(tmp_path: Path) -> None: ) try: - assert_python_project_harness_clean(tmp_path) + assert_asp_python_clean(tmp_path) except AssertionError as error: message = str(error) else: - raise AssertionError("project harness should block root pytest files") + raise AssertionError("ASP Python should block root pytest files") assert "rule=PY-TEST-R001 severity=warning" in message assert "|message Pytest file is scattered in tests root" in message @@ -210,11 +210,11 @@ def test_project_harness_blocks_root_pytest_files(tmp_path: Path) -> None: assert str(source) not in message -def test_project_harness_blocks_unexpected_tests_root_entries(tmp_path: Path) -> None: +def test_asp_python_blocks_unexpected_tests_root_entries(tmp_path: Path) -> None: unexpected = tmp_path / "tests" / "misc" unexpected.mkdir(parents=True) - report = run_python_project_harness(tmp_path) + report = run_asp_python(tmp_path) assert [ (finding.rule_id, finding.location.path) for finding in report.findings @@ -223,13 +223,13 @@ def test_project_harness_blocks_unexpected_tests_root_entries(tmp_path: Path) -> ] -def test_project_harness_blocks_bloated_unit_test_leaf(tmp_path: Path) -> None: +def test_asp_python_blocks_bloated_unit_test_leaf(tmp_path: Path) -> None: unit = tmp_path / "tests" / "unit" unit.mkdir(parents=True) source = unit / "test_large_policy.py" source.write_text(_large_unit_test_source(), encoding="utf-8") - report = run_python_project_harness(tmp_path) + report = run_asp_python(tmp_path) assert [ (finding.rule_id, finding.location.path) for finding in report.findings @@ -260,7 +260,7 @@ def test_modularity_rule_pack_blocks_bloated_multi_responsibility_module( source = src / "feature.py" source.write_text(_large_multi_responsibility_module_source(), encoding="utf-8") - report = run_python_project_harness(tmp_path) + report = run_asp_python(tmp_path) assert [ (finding.rule_id, finding.location.path) for finding in report.findings @@ -288,7 +288,7 @@ def get_value(self) -> int: encoding="utf-8", ) - report = run_python_project_harness(tmp_path) + report = run_asp_python(tmp_path) assert report.is_clean diff --git a/tests/unit/harness/test_project_config.py b/tests/unit/asp_python/test_project_config.py similarity index 72% rename from tests/unit/harness/test_project_config.py rename to tests/unit/asp_python/test_project_config.py index c931004..d0f6d77 100644 --- a/tests/unit/harness/test_project_config.py +++ b/tests/unit/asp_python/test_project_config.py @@ -2,19 +2,19 @@ from typing import TYPE_CHECKING +from asp_python import read_asp_python_config from python_lang_parser import PythonDiagnosticSeverity -from python_lang_project_harness import read_python_project_harness_config if TYPE_CHECKING: from pathlib import Path -def test_read_python_project_harness_config_from_pyproject( +def test_read_asp_python_config_from_pyproject( tmp_path: Path, ) -> None: (tmp_path / "pyproject.toml").write_text( """ -[tool.python-lang-project-harness] +[tool.asp-python] include_tests = false source_dir_names = ["lib"] test_dir_names = ["checks"] @@ -27,7 +27,7 @@ def test_read_python_project_harness_config_from_pyproject( encoding="utf-8", ) - config = read_python_project_harness_config(tmp_path) + config = read_asp_python_config(tmp_path) assert config is not None assert config.include_tests is False @@ -40,7 +40,7 @@ def test_read_python_project_harness_config_from_pyproject( assert config.blocking_severities == frozenset({PythonDiagnosticSeverity.ERROR}) -def test_read_python_project_harness_config_returns_none_without_table( +def test_read_asp_python_config_returns_none_without_table( tmp_path: Path, ) -> None: (tmp_path / "pyproject.toml").write_text( @@ -51,23 +51,23 @@ def test_read_python_project_harness_config_returns_none_without_table( encoding="utf-8", ) - assert read_python_project_harness_config(tmp_path) is None + assert read_asp_python_config(tmp_path) is None -def test_read_python_project_harness_config_rejects_invalid_values( +def test_read_asp_python_config_rejects_invalid_values( tmp_path: Path, ) -> None: (tmp_path / "pyproject.toml").write_text( """ -[tool.python-lang-project-harness] +[tool.asp-python] blocking_severities = ["critical"] """.lstrip(), encoding="utf-8", ) try: - read_python_project_harness_config(tmp_path) + read_asp_python_config(tmp_path) except ValueError as error: assert "unknown severity: critical" in str(error) else: - raise AssertionError("invalid project harness config should fail") + raise AssertionError("invalid ASP Python config should fail") diff --git a/tests/unit/asp_python/test_project_fixture_scope.py b/tests/unit/asp_python/test_project_fixture_scope.py new file mode 100644 index 0000000..e976b08 --- /dev/null +++ b/tests/unit/asp_python/test_project_fixture_scope.py @@ -0,0 +1,45 @@ +from __future__ import annotations + +from pathlib import Path + +import pytest + +from asp_python import run_asp_python +from asp_python._discovery import discover_python_files + + +def test_run_asp_python_skips_test_fixture_sources_by_default( + tmp_path: Path, +) -> None: + """Keep borrowed fixture projects out of root policy scans.""" + + src = tmp_path / "src" + fixture_src = tmp_path / "tests" / "fixtures" / "parser-compact" / "project" / "src" + src.mkdir() + fixture_src.mkdir(parents=True) + source_file = src / "library.py" + fixture_file = fixture_src / "borrowed_library.py" + source_file.write_text('"""Library docs."""\n\nVALUE = 1\n', encoding="utf-8") + fixture_file.write_text("def broken(:\n pass\n", encoding="utf-8") + + report = run_asp_python(tmp_path) + + assert report.is_clean + assert [module.path for module in report.modules] == [str(source_file)] + + +def test_discovery_prunes_ignored_directories_before_descending( + tmp_path: Path, + monkeypatch: pytest.MonkeyPatch, +) -> None: + source = tmp_path / "src" / "library.py" + + def observed_walk(_root: Path): + root_directories = [".venv", "src", "target"] + yield tmp_path, root_directories, [] + assert root_directories == ["src"] + yield tmp_path / "src", [], ["library.py"] + + monkeypatch.setattr(Path, "walk", observed_walk) + + assert discover_python_files([tmp_path]) == (source,) diff --git a/tests/unit/harness/test_project_resolution.py b/tests/unit/asp_python/test_project_resolution.py similarity index 94% rename from tests/unit/harness/test_project_resolution.py rename to tests/unit/asp_python/test_project_resolution.py index 983ce82..1f605ea 100644 --- a/tests/unit/harness/test_project_resolution.py +++ b/tests/unit/asp_python/test_project_resolution.py @@ -5,7 +5,7 @@ import json from pathlib import Path -from python_lang_project_harness._runtime import _response_frame +from asp_python._runtime import _response_frame def request(candidate_paths: list[str]) -> dict[str, object]: @@ -230,12 +230,14 @@ def test_provider_registration_advertises_project_resolution() -> None: for operation in manifest["runtimeContract"]["operations"] } project_resolution = operations["project-resolution"] - assert project_resolution["requestSchemaId"].endswith( - "/provider-project-resolution-request.schema.json" + assert project_resolution["requestSchema"]["schemaId"] == ( + "agent.semantic-protocols.provider-project-resolution-request" ) - assert project_resolution["responseSchemaId"].endswith( - "/provider-project-resolution-response.schema.json" + assert project_resolution["requestSchema"]["schemaVersion"] == "1" + assert project_resolution["responseSchema"]["schemaId"] == ( + "agent.semantic-protocols.provider-project-resolution-response" ) + assert project_resolution["responseSchema"]["schemaVersion"] == "1" def test_explicit_owner_collection_scope_is_required_and_normalized( diff --git a/tests/unit/harness/test_project_resolution_extra_paths.py b/tests/unit/asp_python/test_project_resolution_extra_paths.py similarity index 84% rename from tests/unit/harness/test_project_resolution_extra_paths.py rename to tests/unit/asp_python/test_project_resolution_extra_paths.py index 9f6e1a7..2015ce3 100644 --- a/tests/unit/harness/test_project_resolution_extra_paths.py +++ b/tests/unit/asp_python/test_project_resolution_extra_paths.py @@ -2,13 +2,13 @@ from typing import TYPE_CHECKING -from python_lang_project_harness import run_python_project_harness +from asp_python import run_asp_python if TYPE_CHECKING: from pathlib import Path -def test_run_python_project_harness_can_include_extra_project_paths( +def test_run_asp_python_can_include_extra_project_paths( tmp_path: Path, ) -> None: src = tmp_path / "src" @@ -24,8 +24,8 @@ def test_run_python_project_harness_can_include_extra_project_paths( tool.write_text('"""Tool docs."""\n', encoding="utf-8") (shared / "shared.py").write_text('"""Shared docs."""\n', encoding="utf-8") - default_report = run_python_project_harness(tmp_path) - report = run_python_project_harness( + default_report = run_asp_python(tmp_path) + report = run_asp_python( tmp_path, extra_path_names=("../shared_tools",), ) diff --git a/tests/unit/harness/test_projection_batch.py b/tests/unit/asp_python/test_projection_batch.py similarity index 55% rename from tests/unit/harness/test_projection_batch.py rename to tests/unit/asp_python/test_projection_batch.py index 01bb070..227e354 100644 --- a/tests/unit/harness/test_projection_batch.py +++ b/tests/unit/asp_python/test_projection_batch.py @@ -2,7 +2,7 @@ from __future__ import annotations -from python_lang_project_harness._projection_batch import project_projection_batch +from asp_python._projection_batch import project_projection_batch def test_projection_batch_projects_canonical_python_items() -> None: @@ -44,6 +44,8 @@ def test_projection_batch_projects_canonical_python_items() -> None: owner = response["owners"][0] assert len(response["owners"]) == 1 assert owner["sourceLeafDigest"] == "source-test" + assert owner["projectionState"] == "ready" + assert owner["diagnostic"] is None assert [item["selector"] for item in owner["items"]] == [ "python://src/example.py#item/class/Agent", "python://src/example.py#item/method/run/scope/implementation-owner/type/Agent", @@ -59,3 +61,49 @@ def test_projection_batch_projects_canonical_python_items() -> None: assert "schemaId" not in payload assert "schemaVersion" not in payload assert "projectionKind" not in payload + + +def test_projection_batch_isolates_one_syntax_unavailable_owner() -> None: + request = { + "schemaId": "agent.semantic-protocols.provider-language-projection-batch-request", + "schemaVersion": "1", + "languageId": "python", + "providerId": "asp-python", + "workspaceIdentity": "workspace-test", + "generationRootDigest": "generation-test", + "parserIdentityDigest": "parser-test", + "queryPackDigest": "query-pack-test", + "baseGenerationRootDigest": None, + "owners": [ + { + "ownerPath": "src/ready.py", + "sourceLeafDigest": "ready-source", + "sourceEncoding": "utf8", + "sourceText": "def ready():\n return 1\n", + }, + { + "ownerPath": "src/unavailable.py", + "sourceLeafDigest": "unavailable-source", + "sourceEncoding": "utf8", + "sourceText": "def unavailable(:\n", + }, + ], + "auxiliaryOwners": [], + } + + response = project_projection_batch(request) + + ready, unavailable = response["owners"] + assert ready["projectionState"] == "ready" + assert ready["diagnostic"] is None + assert ready["items"] + assert unavailable["projectionState"] == "syntax-unavailable" + assert unavailable["diagnostic"] == { + "schemaId": "agent.semantic-protocols.provider-language-projection-diagnostic", + "schemaVersion": "1", + "reasonKind": "source-syntax-unavailable", + "message": unavailable["diagnostic"]["message"], + } + assert 0 < len(unavailable["diagnostic"]["message"]) <= 4096 + assert unavailable["items"] == [] + assert unavailable["relations"] == [] diff --git a/tests/unit/harness/test_provider_runtime.py b/tests/unit/asp_python/test_provider_runtime.py similarity index 58% rename from tests/unit/harness/test_provider_runtime.py rename to tests/unit/asp_python/test_provider_runtime.py index bb2cb58..cd8ada8 100644 --- a/tests/unit/harness/test_provider_runtime.py +++ b/tests/unit/asp_python/test_provider_runtime.py @@ -6,6 +6,8 @@ import json import subprocess import sys +from concurrent.futures import ThreadPoolExecutor +from concurrent.futures import TimeoutError as FutureTimeoutError from pathlib import Path from urllib.parse import urlsplit @@ -17,9 +19,32 @@ frame, latency_receipt, post, + projection_payload, ) -from python_lang_project_harness._runtime import _health, _response_frame +from asp_python._runtime import _health, _response_frame + + +def _read_bootstrap(process: subprocess.Popen[str]) -> dict[str, object]: + assert process.stdout is not None + assert process.stderr is not None + with ThreadPoolExecutor(max_workers=1) as executor: + line = executor.submit(process.stdout.readline) + try: + bootstrap_line = line.result(timeout=2) + except FutureTimeoutError as error: + process.kill() + _, stderr = process.communicate(timeout=2) + raise AssertionError( + f"Python provider exceeded the 2s bootstrap deadline: stderr={stderr!r}" + ) from error + if bootstrap_line: + return json.loads(bootstrap_line) + status = process.wait(timeout=2) + stderr = process.stderr.read() + raise AssertionError( + f"Python provider exited before bootstrap: status={status} stderr={stderr!r}" + ) def test_resident_runtime_publishes_manifest_operations_and_structured_frames( @@ -46,9 +71,31 @@ def test_resident_runtime_publishes_manifest_operations_and_structured_frames( ) +def test_runtime_isolates_live_edit_syntax_errors_in_owner_results() -> None: + response = _response_frame( + frame( + "syntax-error-1", + "projection-batch", + projection_payload("def broken(:\n pass\n"), + ), + Path("."), + ) + + assert response["requestId"] == "syntax-error-1" + assert response["outcome"] == "ready" + owner = response["payload"]["owners"][0] + assert owner["projectionState"] == "syntax-unavailable" + assert owner["diagnostic"]["reasonKind"] == "source-syntax-unavailable" + assert "invalid syntax" in owner["diagnostic"]["message"] + + def test_http_json_live_corpus_stream_query_concurrency_and_latency() -> None: + provider = Path(sys.executable).with_name("asp-python") + assert provider.is_file(), ( + "asp-python entrypoint is absent beside the active Python" + ) process = subprocess.Popen( - [sys.executable, "-m", "python_lang_project_harness", "serve"], + [provider, "serve"], cwd=Path(__file__).parents[3], env=environment(), stdin=subprocess.DEVNULL, @@ -56,8 +103,7 @@ def test_http_json_live_corpus_stream_query_concurrency_and_latency() -> None: stderr=subprocess.PIPE, text=True, ) - assert process.stdout is not None - bootstrap = json.loads(process.stdout.readline()) + bootstrap = _read_bootstrap(process) assert ( bootstrap["schemaId"] == "agent.semantic-protocols.asp-client-server-bootstrap" ) diff --git a/tests/unit/asp_python/test_public_cli_identity.py b/tests/unit/asp_python/test_public_cli_identity.py new file mode 100644 index 0000000..8f2675f --- /dev/null +++ b/tests/unit/asp_python/test_public_cli_identity.py @@ -0,0 +1,11 @@ +from __future__ import annotations + +from asp_python._cli_args import help_text + + +def test_public_cli_identity_is_asp_python() -> None: + rendered = help_text() + + assert rendered.startswith("asp-python ") + assert "asp search playbook --language python" in rendered + assert "asp-python search " not in rendered diff --git a/tests/unit/harness/test_pyproject_package_scope.py b/tests/unit/asp_python/test_pyproject_package_scope.py similarity index 89% rename from tests/unit/harness/test_pyproject_package_scope.py rename to tests/unit/asp_python/test_pyproject_package_scope.py index 88ac56a..01ea20e 100644 --- a/tests/unit/harness/test_pyproject_package_scope.py +++ b/tests/unit/asp_python/test_pyproject_package_scope.py @@ -2,13 +2,13 @@ from typing import TYPE_CHECKING -from python_lang_project_harness import run_python_project_harness +from asp_python import run_asp_python if TYPE_CHECKING: from pathlib import Path -def test_run_python_project_harness_uses_pyproject_package_scope( +def test_run_asp_python_uses_pyproject_package_scope( tmp_path: Path, ) -> None: default_src = tmp_path / "src" @@ -47,7 +47,7 @@ def test_run_python_project_harness_uses_pyproject_package_scope( encoding="utf-8", ) - report = run_python_project_harness(tmp_path) + report = run_asp_python(tmp_path) assert report.is_clean assert [module.path for module in report.modules] == [ diff --git a/tests/unit/harness/test_pytest.py b/tests/unit/asp_python/test_pytest.py similarity index 63% rename from tests/unit/harness/test_pytest.py rename to tests/unit/asp_python/test_pytest.py index ef4ca83..41f3241 100644 --- a/tests/unit/harness/test_pytest.py +++ b/tests/unit/asp_python/test_pytest.py @@ -2,17 +2,17 @@ from typing import TYPE_CHECKING -from python_lang_parser import PythonDiagnosticSeverity -from python_lang_project_harness import PythonHarnessConfig, python_project_harness_test -from python_lang_project_harness.pytest import ( - python_project_harness_test as facade_python_project_harness_test, +from asp_python import AspPythonConfig, asp_python_test +from asp_python.pytest import ( + asp_python_test as facade_asp_python_test, ) +from python_lang_parser import PythonDiagnosticSeverity if TYPE_CHECKING: from pathlib import Path -def test_python_project_harness_test_returns_pytest_collectable_callable( +def test_asp_python_test_returns_pytest_collectable_callable( tmp_path: Path, ) -> None: src = tmp_path / "src" @@ -27,14 +27,14 @@ def test_python_project_harness_test_returns_pytest_collectable_callable( encoding="utf-8", ) - harness_test = python_project_harness_test(tmp_path) + asp_python_test_case = asp_python_test(tmp_path) - assert harness_test.__name__ == "test_python_project_harness_policy" - assert harness_test.__qualname__ == "test_python_project_harness_policy" - harness_test() + assert asp_python_test_case.__name__ == "test_asp_python_policy" + assert asp_python_test_case.__qualname__ == "test_asp_python_policy" + asp_python_test_case() -def test_python_project_harness_test_defaults_to_current_project_root( +def test_asp_python_test_defaults_to_current_project_root( tmp_path: Path, monkeypatch, ) -> None: @@ -50,30 +50,30 @@ def test_python_project_harness_test_defaults_to_current_project_root( monkeypatch.chdir(tmp_path) - harness_test = python_project_harness_test() + asp_python_test_case = asp_python_test() - harness_test() + asp_python_test_case() def test_public_pytest_facade_exposes_collectable_helper() -> None: - assert facade_python_project_harness_test is python_project_harness_test + assert facade_asp_python_test is asp_python_test -def test_python_project_harness_test_blocks_with_compact_snapshot( +def test_asp_python_test_blocks_with_compact_snapshot( tmp_path: Path, ) -> None: src = tmp_path / "src" src.mkdir() source = src / "library.py" source.write_text('def run() -> None:\n print("debug")\n', encoding="utf-8") - harness_test = python_project_harness_test(tmp_path) + asp_python_test_case = asp_python_test(tmp_path) try: - harness_test() + asp_python_test_case() except AssertionError as error: message = str(error) else: - raise AssertionError("pytest harness callable should block policy findings") + raise AssertionError("ASP Python pytest callable should block policy findings") assert "rule=PY-MOD-R002 severity=warning" in message assert "|message Library module uses bare print" in message @@ -82,28 +82,28 @@ def test_python_project_harness_test_blocks_with_compact_snapshot( assert "[advice]" in message -def test_python_project_harness_test_can_disable_agent_advice( +def test_asp_python_test_can_disable_agent_advice( tmp_path: Path, ) -> None: src = tmp_path / "src" src.mkdir() source = src / "library.py" source.write_text('def run() -> None:\n print("debug")\n', encoding="utf-8") - harness_test = python_project_harness_test(tmp_path, include_advice=False) + asp_python_test_case = asp_python_test(tmp_path, include_advice=False) try: - harness_test() + asp_python_test_case() except AssertionError as error: message = str(error) else: - raise AssertionError("pytest harness callable should block policy findings") + raise AssertionError("ASP Python pytest callable should block policy findings") assert "rule=PY-MOD-R002 severity=warning" in message assert "|message Library module uses bare print" in message assert "[advice]" not in message -def test_python_project_harness_test_honors_embedded_options( +def test_asp_python_test_honors_embedded_options( tmp_path: Path, ) -> None: lib = tmp_path / "lib" @@ -116,7 +116,7 @@ def test_python_project_harness_test_honors_embedded_options( ) (tests / "test_bad.py").write_text("def broken(:\n pass\n", encoding="utf-8") - harness_test = python_project_harness_test( + asp_python_test_case = asp_python_test( tmp_path, severities=frozenset({PythonDiagnosticSeverity.ERROR}), include_tests=False, @@ -124,12 +124,12 @@ def test_python_project_harness_test_honors_embedded_options( test_name="test_custom_python_project_policy", ) - assert harness_test.__name__ == "test_custom_python_project_policy" - assert harness_test.__qualname__ == "test_custom_python_project_policy" - harness_test() + assert asp_python_test_case.__name__ == "test_custom_python_project_policy" + assert asp_python_test_case.__qualname__ == "test_custom_python_project_policy" + asp_python_test_case() -def test_python_project_harness_test_honors_configured_project_resolution( +def test_asp_python_test_honors_configured_project_resolution( tmp_path: Path, ) -> None: lib = tmp_path / "lib" @@ -139,15 +139,15 @@ def test_python_project_harness_test_honors_configured_project_resolution( (lib / "library.py").write_text('"""Library docs."""\n', encoding="utf-8") (tests / "test_bad.py").write_text("def broken(:\n pass\n", encoding="utf-8") - harness_test = python_project_harness_test( + asp_python_test_case = asp_python_test( tmp_path, - config=PythonHarnessConfig(source_dir_names=("lib",), include_tests=False), + config=AspPythonConfig(source_dir_names=("lib",), include_tests=False), ) - harness_test() + asp_python_test_case() -def test_python_project_harness_test_honors_extra_project_paths( +def test_asp_python_test_honors_extra_project_paths( tmp_path: Path, ) -> None: src = tmp_path / "src" @@ -157,9 +157,9 @@ def test_python_project_harness_test_honors_extra_project_paths( (src / "library.py").write_text('"""Library docs."""\n', encoding="utf-8") (tools / "check.py").write_text('"""Check docs."""\n', encoding="utf-8") - harness_test = python_project_harness_test( + asp_python_test_case = asp_python_test( tmp_path, extra_path_names=("tools",), ) - harness_test() + asp_python_test_case() diff --git a/tests/unit/harness/test_pytest_plugin.py b/tests/unit/asp_python/test_pytest_plugin.py similarity index 69% rename from tests/unit/harness/test_pytest_plugin.py rename to tests/unit/asp_python/test_pytest_plugin.py index 7b4e914..fa06486 100644 --- a/tests/unit/harness/test_pytest_plugin.py +++ b/tests/unit/asp_python/test_pytest_plugin.py @@ -4,6 +4,8 @@ import sys from typing import TYPE_CHECKING +from asp_python._pytest_plugin_project import _package_scoped_root + if TYPE_CHECKING: from pathlib import Path @@ -13,7 +15,7 @@ def test_pytest_plugin_collects_harness_item_from_dev_dependency( ) -> None: _write_clean_project(tmp_path) - result = _run_pytest_plugin(tmp_path, "--python-project-harness") + result = _run_pytest_plugin(tmp_path, "--asp-python") assert result.returncode == 0, result.stdout + result.stderr assert "2 passed" in result.stdout @@ -37,7 +39,7 @@ def test_pytest_plugin_can_be_enabled_from_pyproject_addopts( (tmp_path / "pyproject.toml").write_text( """ [tool.pytest.ini_options] -addopts = ["--python-project-harness"] +addopts = ["--asp-python"] """.lstrip(), encoding="utf-8", ) @@ -55,7 +57,7 @@ def test_pytest_plugin_runs_without_downstream_test_files( src.mkdir() (src / "library.py").write_text('"""Library docs."""\n', encoding="utf-8") - result = _run_pytest_plugin(tmp_path, "--python-project-harness") + result = _run_pytest_plugin(tmp_path, "--asp-python") assert result.returncode == 0, result.stdout + result.stderr assert "1 passed" in result.stdout @@ -77,7 +79,7 @@ def test_pytest_plugin_reports_compact_harness_failure( encoding="utf-8", ) - result = _run_pytest_plugin(tmp_path, "--python-project-harness") + result = _run_pytest_plugin(tmp_path, "--asp-python") assert result.returncode == 1 assert "rule=PY-MOD-R002 severity=warning" in result.stdout @@ -85,13 +87,71 @@ def test_pytest_plugin_reports_compact_harness_failure( assert "src/library.py" in result.stdout assert str(tmp_path) not in result.stdout assert "../" not in result.stdout - assert "FAILED python-project-harness" in result.stdout + assert "FAILED asp-python" in result.stdout assert ( "|repair replace bare print with a project-owned reporting surface" in result.stdout ) +def test_pytest_plugin_scopes_package_target_to_nearest_project( + tmp_path: Path, +) -> None: + package_root = tmp_path / "packages" / "python" / "graphs" + test_path = package_root / "tests" / "test_graphs.py" + test_path.parent.mkdir(parents=True) + (package_root / "pyproject.toml").write_text( + """ +[project] +name = "graphs" +version = "0.1.0" +""".lstrip(), + encoding="utf-8", + ) + test_path.write_text( + "def test_graphs() -> None:\n assert True\n", + encoding="utf-8", + ) + + assert ( + _package_scoped_root( + tmp_path, + ("packages/python/graphs/tests",), + invocation_dir=tmp_path, + ) + == package_root + ) + + +def test_pytest_plugin_keeps_workspace_scope_for_mixed_projects( + tmp_path: Path, +) -> None: + first = tmp_path / "packages" / "python" / "first" + second = tmp_path / "packages" / "python" / "second" + for package in (first, second): + (package / "tests").mkdir(parents=True) + (package / "pyproject.toml").write_text( + '[project]\nname = "package"\nversion = "0.1.0"\n', + encoding="utf-8", + ) + (package / "tests" / "test_package.py").write_text( + "def test_package() -> None:\n assert True\n", + encoding="utf-8", + ) + + assert ( + _package_scoped_root( + tmp_path, + ( + "packages/python/first/tests", + "packages/python/second/tests", + ), + invocation_dir=tmp_path, + ) + is None + ) + + def test_pytest_plugin_honors_dev_dependency_options( tmp_path: Path, ) -> None: @@ -114,10 +174,10 @@ def test_pytest_plugin_honors_dev_dependency_options( result = _run_pytest_plugin( tmp_path, - "--python-project-harness", - "--python-project-harness-source-dir=lib", - "--python-project-harness-no-tests", - "--python-project-harness-error-only", + "--asp-python", + "--asp-python-source-dir=lib", + "--asp-python-no-tests", + "--asp-python-error-only", ) assert result.returncode == 0, result.stdout + result.stderr @@ -141,8 +201,8 @@ def test_pytest_plugin_honors_policy_rule_options( result = _run_pytest_plugin( tmp_path, - "--python-project-harness", - "--python-project-harness-disable-rule=PY-MOD-R002", + "--asp-python", + "--asp-python-disable-rule=PY-MOD-R002", ) assert result.returncode == 0, result.stdout + result.stderr @@ -157,7 +217,7 @@ def test_pytest_plugin_loads_project_policy_config_from_pyproject( tests.mkdir(parents=True) (tmp_path / "pyproject.toml").write_text( """ -[tool.python-lang-project-harness] +[tool.asp-python] disabled_rule_ids = ["PY-MOD-R002"] """.lstrip(), encoding="utf-8", @@ -171,7 +231,7 @@ def test_pytest_plugin_loads_project_policy_config_from_pyproject( encoding="utf-8", ) - result = _run_pytest_plugin(tmp_path, "--python-project-harness") + result = _run_pytest_plugin(tmp_path, "--asp-python") assert result.returncode == 0, result.stdout + result.stderr diff --git a/tests/unit/harness/test_reasoning_tree_policy.py b/tests/unit/asp_python/test_reasoning_tree_policy.py similarity index 84% rename from tests/unit/harness/test_reasoning_tree_policy.py rename to tests/unit/asp_python/test_reasoning_tree_policy.py index 87a5d01..b5642d8 100644 --- a/tests/unit/harness/test_reasoning_tree_policy.py +++ b/tests/unit/asp_python/test_reasoning_tree_policy.py @@ -2,7 +2,7 @@ from pathlib import Path -from python_lang_project_harness import run_python_project_harness +from asp_python import run_asp_python _PROJECT_ROOT = next( parent @@ -26,7 +26,7 @@ def test_modularity_rule_pack_blocks_shadowed_reasoning_tree_owner( encoding="utf-8", ) - report = run_python_project_harness(tmp_path) + report = run_asp_python(tmp_path) assert [ (finding.rule_id, finding.location.path) for finding in report.findings @@ -45,7 +45,7 @@ def test_agent_policy_reports_branch_package_without_reasoning_tree_intent( (branch / "service.py").write_text('"""Service leaf."""\n', encoding="utf-8") (branch / "models.py").write_text('"""Model leaf."""\n', encoding="utf-8") - report = run_python_project_harness(tmp_path) + report = run_asp_python(tmp_path) assert [ (finding.rule_id, finding.location.path) for finding in report.findings @@ -68,20 +68,17 @@ def test_agent_policy_accepts_documented_reasoning_tree_branch( (branch / "service.py").write_text('"""Service leaf."""\n', encoding="utf-8") (branch / "models.py").write_text('"""Model leaf."""\n', encoding="utf-8") - report = run_python_project_harness(tmp_path) + report = run_asp_python(tmp_path) assert report.is_clean def test_reasoning_tree_policy_uses_parser_facts() -> None: - modularity = ( - _PROJECT_ROOT / "src" / "python_lang_project_harness" / "_modularity.py" - ).read_text(encoding="utf-8") + modularity = (_PROJECT_ROOT / "src" / "asp_python" / "_modularity.py").read_text( + encoding="utf-8" + ) agent_reasoning_tree = ( - _PROJECT_ROOT - / "src" - / "python_lang_project_harness" - / "_agent_reasoning_tree.py" + _PROJECT_ROOT / "src" / "asp_python" / "_agent_reasoning_tree.py" ).read_text(encoding="utf-8") assert "python_reasoning_tree_facts(" in modularity diff --git a/tests/unit/harness/test_render_snapshots.py b/tests/unit/asp_python/test_render_snapshots.py similarity index 86% rename from tests/unit/harness/test_render_snapshots.py rename to tests/unit/asp_python/test_render_snapshots.py index 50489f8..0b2dc48 100644 --- a/tests/unit/harness/test_render_snapshots.py +++ b/tests/unit/asp_python/test_render_snapshots.py @@ -4,6 +4,14 @@ from snapshot_support import assert_snapshot +from asp_python import ( + AspPythonFinding, + AspPythonProjectScope, + AspPythonReport, + render_asp_python_report, + render_asp_python_report_json, + render_python_reasoning_tree, +) from python_lang_parser import ( PythonDiagnosticSeverity, PythonModuleReport, @@ -14,26 +22,18 @@ SourceLocation, parse_python_source, ) -from python_lang_project_harness import ( - PythonHarnessFinding, - PythonHarnessReport, - PythonProjectHarnessScope, - render_python_lang_harness, - render_python_lang_harness_json, - render_python_reasoning_tree, -) def test_compact_text_render_matches_snapshot() -> None: - rendered = render_python_lang_harness(_snapshot_report()) + rendered = render_asp_python_report(_snapshot_report()) - assert_snapshot("python_project_harness_compact_text", rendered) + assert_snapshot("asp_python_compact_text", rendered) def test_json_render_matches_snapshot() -> None: - rendered = render_python_lang_harness_json(_snapshot_report()) + rendered = render_asp_python_report_json(_snapshot_report()) - assert_snapshot("python_project_harness_json", rendered) + assert_snapshot("asp_python_json", rendered) def test_reasoning_tree_render_matches_snapshot() -> None: @@ -46,7 +46,7 @@ def test_reasoning_tree_render_uses_project_relative_paths(tmp_path: Path) -> No src = tmp_path / "src" package = src / "pkg" package.mkdir(parents=True) - report = PythonHarnessReport( + report = AspPythonReport( modules=( parse_python_source( '"""Package docs."""\n\nVALUE = 1\n', @@ -55,7 +55,7 @@ def test_reasoning_tree_render_uses_project_relative_paths(tmp_path: Path) -> No ), findings=(), root_paths=(str(tmp_path),), - project_resolution=PythonProjectHarnessScope( + project_resolution=AspPythonProjectScope( project_root=tmp_path, project_metadata=PythonProjectMetadata( project_root=tmp_path, @@ -95,10 +95,10 @@ def test_compact_text_render_uses_project_relative_finding_paths( ) -> None: src = tmp_path / "src" src.mkdir() - report = PythonHarnessReport( + report = AspPythonReport( modules=(), findings=( - PythonHarnessFinding( + AspPythonFinding( rule_id="PY-MOD-R002", pack_id="python.modern_design", severity=PythonDiagnosticSeverity.WARNING, @@ -111,7 +111,7 @@ def test_compact_text_render_uses_project_relative_finding_paths( ), ), root_paths=(str(tmp_path),), - project_resolution=PythonProjectHarnessScope( + project_resolution=AspPythonProjectScope( project_root=tmp_path, project_paths=(tmp_path,), source_paths=(src,), @@ -119,15 +119,15 @@ def test_compact_text_render_uses_project_relative_finding_paths( ), ) - rendered = render_python_lang_harness(report) + rendered = render_asp_python_report(report) assert str(tmp_path) not in rendered assert "path=src/library.py line=3 column=5" in rendered -def _snapshot_report() -> PythonHarnessReport: +def _snapshot_report() -> AspPythonReport: source_path = "$TEMP/src/service.py" - return PythonHarnessReport( + return AspPythonReport( modules=( PythonModuleReport( path=source_path, @@ -135,7 +135,7 @@ def _snapshot_report() -> PythonHarnessReport: ), ), findings=( - PythonHarnessFinding( + AspPythonFinding( rule_id="PY-MOD-R002", pack_id="python.modern_design", severity=PythonDiagnosticSeverity.WARNING, @@ -155,10 +155,10 @@ def _snapshot_report() -> PythonHarnessReport: ) -def _reasoning_tree_snapshot_report() -> PythonHarnessReport: +def _reasoning_tree_snapshot_report() -> AspPythonReport: root = Path("$TEMP") src = root / "src" - return PythonHarnessReport( + return AspPythonReport( modules=( parse_python_source( '"""Domain package owner."""\n\nfrom .service import build\n\n__all__ = ("build",)\n', @@ -175,7 +175,7 @@ def _reasoning_tree_snapshot_report() -> PythonHarnessReport: ), findings=(), root_paths=(str(root),), - project_resolution=PythonProjectHarnessScope( + project_resolution=AspPythonProjectScope( project_root=root, project_metadata=PythonProjectMetadata( project_root=root, diff --git a/tests/unit/harness/test_runner_config.py b/tests/unit/asp_python/test_runner_config.py similarity index 80% rename from tests/unit/harness/test_runner_config.py rename to tests/unit/asp_python/test_runner_config.py index 4ba77bc..7563e0b 100644 --- a/tests/unit/harness/test_runner_config.py +++ b/tests/unit/asp_python/test_runner_config.py @@ -2,10 +2,10 @@ from typing import TYPE_CHECKING -from python_lang_project_harness import ( - PythonHarnessConfig, - run_python_lang_harness, - run_python_project_harness, +from asp_python import ( + AspPythonConfig, + run_asp_python, + run_asp_python_paths, ) if TYPE_CHECKING: @@ -31,9 +31,9 @@ def test_project_runner_uses_configured_source_and_test_roots( encoding="utf-8", ) - report = run_python_project_harness( + report = run_asp_python( tmp_path, - config=PythonHarnessConfig( + config=AspPythonConfig( source_dir_names=("lib",), test_dir_names=("checks",), ), @@ -60,9 +60,9 @@ def test_project_runner_parameters_override_configured_roots( (lib / "included.py").write_text('"""Included docs."""\n', encoding="utf-8") (src / "service.py").write_text('"""Service docs."""\n', encoding="utf-8") - report = run_python_project_harness( + report = run_asp_python( tmp_path, - config=PythonHarnessConfig(source_dir_names=("lib",)), + config=AspPythonConfig(source_dir_names=("lib",)), source_dir_names=("src",), include_tests=False, ) @@ -89,9 +89,9 @@ def test_project_runner_parameters_override_configured_extra_paths( (examples / "demo.py").write_text('"""Example docs."""\n', encoding="utf-8") (tools / "check.py").write_text('"""Check docs."""\n', encoding="utf-8") - report = run_python_project_harness( + report = run_asp_python( tmp_path, - config=PythonHarnessConfig(extra_path_names=("examples",)), + config=AspPythonConfig(extra_path_names=("examples",)), extra_path_names=("tools",), ) @@ -113,9 +113,9 @@ def test_project_runner_can_exclude_tests_from_config(tmp_path: Path) -> None: (src / "service.py").write_text('"""Service docs."""\n', encoding="utf-8") (tests / "test_bad.py").write_text("def broken(:\n pass\n", encoding="utf-8") - report = run_python_project_harness( + report = run_asp_python( tmp_path, - config=PythonHarnessConfig(include_tests=False), + config=AspPythonConfig(include_tests=False), ) assert report.is_clean @@ -133,9 +133,9 @@ def test_project_runner_can_disable_policy_rules_from_config(tmp_path: Path) -> encoding="utf-8", ) - report = run_python_project_harness( + report = run_asp_python( tmp_path, - config=PythonHarnessConfig(disabled_rule_ids=frozenset({"PY-MOD-R002"})), + config=AspPythonConfig(disabled_rule_ids=frozenset({"PY-MOD-R002"})), ) assert report.is_clean @@ -148,7 +148,7 @@ def test_project_runner_loads_policy_config_from_pyproject(tmp_path: Path) -> No src.mkdir() (tmp_path / "pyproject.toml").write_text( """ -[tool.python-lang-project-harness] +[tool.asp-python] disabled_rule_ids = ["PY-MOD-R002"] """.lstrip(), encoding="utf-8", @@ -158,7 +158,7 @@ def test_project_runner_loads_policy_config_from_pyproject(tmp_path: Path) -> No encoding="utf-8", ) - report = run_python_project_harness(tmp_path) + report = run_asp_python(tmp_path) assert report.is_clean assert report.disabled_rule_ids == frozenset({"PY-MOD-R002"}) @@ -171,7 +171,7 @@ def test_explicit_project_config_overrides_pyproject_policy_config( src.mkdir() (tmp_path / "pyproject.toml").write_text( """ -[tool.python-lang-project-harness] +[tool.asp-python] disabled_rule_ids = ["PY-MOD-R002"] """.lstrip(), encoding="utf-8", @@ -181,7 +181,7 @@ def test_explicit_project_config_overrides_pyproject_policy_config( encoding="utf-8", ) - report = run_python_project_harness(tmp_path, config=PythonHarnessConfig()) + report = run_asp_python(tmp_path, config=AspPythonConfig()) assert not report.is_clean assert report.disabled_rule_ids == frozenset() @@ -195,11 +195,9 @@ def test_project_runner_can_promote_policy_rules_from_config(tmp_path: Path) -> encoding="utf-8", ) - report = run_python_project_harness( + report = run_asp_python( tmp_path, - config=PythonHarnessConfig( - blocking_rule_ids=frozenset({"PY-AGENT-POLICY-001"}) - ), + config=AspPythonConfig(blocking_rule_ids=frozenset({"PY-AGENT-POLICY-001"})), ) assert not report.is_clean @@ -213,15 +211,28 @@ def test_runner_rejects_missing_project_root_and_explicit_path(tmp_path: Path) - missing = tmp_path / "missing" try: - run_python_project_harness(missing) + run_asp_python(missing) except ValueError as error: assert str(error) == f"project root does not exist: {missing}" else: raise AssertionError("missing project root should fail") try: - run_python_lang_harness([missing]) + run_asp_python_paths([missing]) except ValueError as error: - assert str(error) == f"harness path does not exist: {missing}" + assert str(error) == f"ASP Python path does not exist: {missing}" else: - raise AssertionError("missing harness path should fail") + raise AssertionError("missing ASP Python path should fail") + + +def test_concurrent_parser_retains_deterministic_discovery_order( + tmp_path: Path, +) -> None: + last = tmp_path / "z_last.py" + first = tmp_path / "a_first.py" + last.write_text("LAST = 1\n", encoding="utf-8") + first.write_text("FIRST = 1\n", encoding="utf-8") + + report = run_asp_python_paths([tmp_path], rule_packs=()) + + assert [module.path for module in report.modules] == [str(first), str(last)] diff --git a/tests/unit/harness/test_semantic_agent_cli.py b/tests/unit/asp_python/test_semantic_agent_cli.py similarity index 90% rename from tests/unit/harness/test_semantic_agent_cli.py rename to tests/unit/asp_python/test_semantic_agent_cli.py index a5bd882..9d1df64 100644 --- a/tests/unit/harness/test_semantic_agent_cli.py +++ b/tests/unit/asp_python/test_semantic_agent_cli.py @@ -1,11 +1,11 @@ -"""Semantic agent CLI tests for the Python harness provider.""" +"""Semantic agent CLI tests for the ASP Python provider.""" from __future__ import annotations import io from pathlib import Path -from python_lang_project_harness import run_cli +from asp_python import run_cli def test_cli_agent_install_reports_root_asp_owner( diff --git a/tests/unit/harness/test_semantic_cli.py b/tests/unit/asp_python/test_semantic_cli.py similarity index 85% rename from tests/unit/harness/test_semantic_cli.py rename to tests/unit/asp_python/test_semantic_cli.py index 6bf1c56..f2707a7 100644 --- a/tests/unit/harness/test_semantic_cli.py +++ b/tests/unit/asp_python/test_semantic_cli.py @@ -1,4 +1,4 @@ -"""Semantic CLI protocol tests for the Python harness provider.""" +"""Semantic CLI protocol tests for the ASP Python provider.""" from __future__ import annotations @@ -6,7 +6,7 @@ import json from pathlib import Path -from python_lang_project_harness import python_semantic_language_registration, run_cli +from asp_python import python_semantic_language_registration, run_cli def test_cli_agent_doctor_advertises_provider(tmp_path: Path) -> None: @@ -31,7 +31,7 @@ def test_cli_agent_guide_uses_asp_owned_exact_projection(tmp_path: Path) -> None def test_search_descriptors_publish_benchmark_invocations() -> None: descriptors = python_semantic_language_registration()["methodDescriptors"] assert all( - descriptor["method"] != "search/owner-native" for descriptor in descriptors + descriptor["method"] != "search/playbook-native" for descriptor in descriptors ) exact = next( descriptor diff --git a/tests/unit/harness/test_semantic_cli_ast_patch.py b/tests/unit/asp_python/test_semantic_cli_ast_patch.py similarity index 95% rename from tests/unit/harness/test_semantic_cli_ast_patch.py rename to tests/unit/asp_python/test_semantic_cli_ast_patch.py index 7135139..d5db252 100644 --- a/tests/unit/harness/test_semantic_cli_ast_patch.py +++ b/tests/unit/asp_python/test_semantic_cli_ast_patch.py @@ -4,7 +4,7 @@ import json from pathlib import Path -from python_lang_project_harness import run_cli +from asp_python import run_cli def test_ast_patch_dry_run_returns_provider_unsupported_operation_receipt( diff --git a/tests/unit/asp_python/test_semantic_cli_benchmark_registry.py b/tests/unit/asp_python/test_semantic_cli_benchmark_registry.py new file mode 100644 index 0000000..45c2864 --- /dev/null +++ b/tests/unit/asp_python/test_semantic_cli_benchmark_registry.py @@ -0,0 +1,14 @@ +"""Registry benchmark invocation contract tests.""" + +from asp_python import python_semantic_language_registration + + +def test_registry_publishes_no_search_orchestration_invocation() -> None: + descriptors = python_semantic_language_registration()["methodDescriptors"] + search_descriptors = [ + descriptor + for descriptor in descriptors + if descriptor["method"].startswith("search/") + ] + + assert search_descriptors == [] diff --git a/tests/unit/harness/test_semantic_cli_structural_selector_registry.py b/tests/unit/asp_python/test_semantic_cli_structural_selector_registry.py similarity index 96% rename from tests/unit/harness/test_semantic_cli_structural_selector_registry.py rename to tests/unit/asp_python/test_semantic_cli_structural_selector_registry.py index 7514f00..bc84c10 100644 --- a/tests/unit/harness/test_semantic_cli_structural_selector_registry.py +++ b/tests/unit/asp_python/test_semantic_cli_structural_selector_registry.py @@ -4,7 +4,7 @@ import json from pathlib import Path -from python_lang_project_harness import run_cli +from asp_python import run_cli def test_cli_query_registry_owns_structural_selector_projection( diff --git a/tests/unit/harness/test_semantic_cli_tree_sitter_predicates.py b/tests/unit/asp_python/test_semantic_cli_tree_sitter_predicates.py similarity index 96% rename from tests/unit/harness/test_semantic_cli_tree_sitter_predicates.py rename to tests/unit/asp_python/test_semantic_cli_tree_sitter_predicates.py index 39059cf..cfe6246 100644 --- a/tests/unit/harness/test_semantic_cli_tree_sitter_predicates.py +++ b/tests/unit/asp_python/test_semantic_cli_tree_sitter_predicates.py @@ -6,15 +6,15 @@ import json from pathlib import Path -from semantic_search_fixture import write_search_fixture +from python_project_fixture import write_python_project_fixture -from python_lang_project_harness import run_cli +from asp_python import run_cli def test_cli_query_inline_s_expression_applies_predicate_matrix( tmp_path: Path, ) -> None: - write_search_fixture(tmp_path) + write_python_project_fixture(tmp_path) cases = [ ( "#eq?", @@ -88,7 +88,7 @@ def test_cli_query_inline_s_expression_applies_predicate_matrix( def test_cli_query_inline_s_expression_renders_multi_path_corpus_locators( tmp_path: Path, ) -> None: - write_search_fixture(tmp_path) + write_python_project_fixture(tmp_path) (tmp_path / "src" / "pkg" / "extra.py").write_text( "def alpha() -> str:\n return 'alpha'\n", encoding="utf-8", diff --git a/tests/unit/harness/test_semantic_cli_tree_sitter_registry.py b/tests/unit/asp_python/test_semantic_cli_tree_sitter_registry.py similarity index 90% rename from tests/unit/harness/test_semantic_cli_tree_sitter_registry.py rename to tests/unit/asp_python/test_semantic_cli_tree_sitter_registry.py index 8d50358..7761519 100644 --- a/tests/unit/harness/test_semantic_cli_tree_sitter_registry.py +++ b/tests/unit/asp_python/test_semantic_cli_tree_sitter_registry.py @@ -6,7 +6,7 @@ import json from pathlib import Path -from python_lang_project_harness import run_cli +from asp_python import run_cli def test_agent_doctor_advertises_tree_sitter_query_descriptor( @@ -52,7 +52,6 @@ def test_agent_guide_lists_tree_sitter_query_entrypoint(tmp_path: Path) -> None: assert exit_code == 0 assert ( - "|cmd catalog-json=asp python query --catalog declarations --json --workspace " - in stdout.getvalue() + "|cmd syntax-locate=asp search playbook --language python" in stdout.getvalue() ) assert "syntax predicates supported=#eq?,#any-eq?,#any-of?" in stdout.getvalue() diff --git a/tests/unit/asp_python/test_semantic_language_schemas.py b/tests/unit/asp_python/test_semantic_language_schemas.py new file mode 100644 index 0000000..bf06391 --- /dev/null +++ b/tests/unit/asp_python/test_semantic_language_schemas.py @@ -0,0 +1,27 @@ +"""Provider-owned schema registration tests for the Python provider.""" + +from __future__ import annotations + +from asp_python import python_semantic_language_registration + + +def test_python_registration_advertises_only_provider_owned_schemas() -> None: + registration = python_semantic_language_registration() + + assert registration["schemas"] == [ + { + "schemaId": "agent.semantic-protocols.languages.python.asp-python.capabilities", + "schemaVersion": "1", + "path": "schemas/python-semantic-capabilities.v1.schema.json", + } + ] + + +def test_python_registration_does_not_advertise_shared_schema_ids() -> None: + registration = python_semantic_language_registration() + provider_owned_schema_ids = { + "agent.semantic-protocols.languages.python.asp-python.capabilities" + } + advertised_schema_ids = {schema["schemaId"] for schema in registration["schemas"]} + + assert advertised_schema_ids - provider_owned_schema_ids == set() diff --git a/tests/unit/harness/test_semantic_provider_doctor.py b/tests/unit/asp_python/test_semantic_provider_doctor.py similarity index 89% rename from tests/unit/harness/test_semantic_provider_doctor.py rename to tests/unit/asp_python/test_semantic_provider_doctor.py index 6099087..16cd507 100644 --- a/tests/unit/harness/test_semantic_provider_doctor.py +++ b/tests/unit/asp_python/test_semantic_provider_doctor.py @@ -1,11 +1,11 @@ -"""Validate the Python provider doctor-v2 response contract.""" +"""Validate the ASP Python provider doctor response contract.""" import io import json from hashlib import sha256 from pathlib import Path -from python_lang_project_harness._cli import run_cli +from asp_python._cli import run_cli def test_cli_agent_doctor_json_validates_v1_envelope_and_registry( @@ -43,7 +43,10 @@ def test_cli_agent_doctor_json_validates_v1_envelope_and_registry( registration["binary"], ) descriptors = registration["methodDescriptors"] - assert len(descriptors) == len(registration["methods"]) == 33 + assert len(descriptors) == len(registration["methods"]) == 5 + assert not any( + descriptor["method"].startswith("search/") for descriptor in descriptors + ) exact_query = next( descriptor for descriptor in descriptors diff --git a/tests/unit/harness/test_software_criterion_snapshots.py b/tests/unit/asp_python/test_software_criterion_snapshots.py similarity index 93% rename from tests/unit/harness/test_software_criterion_snapshots.py rename to tests/unit/asp_python/test_software_criterion_snapshots.py index cbb5057..e95a0b7 100644 --- a/tests/unit/harness/test_software_criterion_snapshots.py +++ b/tests/unit/asp_python/test_software_criterion_snapshots.py @@ -8,14 +8,14 @@ from syrupy.extensions.json import JSONSnapshotExtension -from python_lang_project_harness import ( - run_python_lang_harness, +from asp_python import ( + run_asp_python_paths, ) if TYPE_CHECKING: from syrupy.assertion import SnapshotAssertion - from python_lang_project_harness import PythonHarnessReport + from asp_python import AspPythonReport _SCENARIO = ( @@ -41,7 +41,7 @@ def test_py_software_criterion_control_flow_v1_snapshot( ) -> None: _copy_inputs(_SCENARIO / "inputs", tmp_path) - report = run_python_lang_harness([tmp_path / "criterion.py"]) + report = run_asp_python_paths([tmp_path / "criterion.py"]) filtered = _filter_software_criterion_findings(report) findings = [ { @@ -105,7 +105,8 @@ def test_py_software_criterion_control_flow_v1_scenario_benchmark_contract() -> assert benchmark["harness"] == "pytest" assert ( - benchmark["test"] == "tests/unit/harness/test_software_criterion_snapshots.py" + benchmark["test"] + == "tests/unit/asp_python/test_software_criterion_snapshots.py" ) _assert_benchmark_durations(benchmark) comparison = benchmark["input_expected_comparison"] @@ -126,8 +127,8 @@ def _copy_inputs(source_dir: Path, destination_dir: Path) -> None: def _filter_software_criterion_findings( - report: PythonHarnessReport, -) -> PythonHarnessReport: + report: AspPythonReport, +) -> AspPythonReport: return replace( report, findings=tuple( diff --git a/tests/unit/harness/test_test_layout_config.py b/tests/unit/asp_python/test_test_layout_config.py similarity index 84% rename from tests/unit/harness/test_test_layout_config.py rename to tests/unit/asp_python/test_test_layout_config.py index 22dd346..3c91fa6 100644 --- a/tests/unit/harness/test_test_layout_config.py +++ b/tests/unit/asp_python/test_test_layout_config.py @@ -2,7 +2,7 @@ from typing import TYPE_CHECKING -from python_lang_project_harness import run_python_project_harness +from asp_python import run_asp_python if TYPE_CHECKING: from pathlib import Path @@ -28,7 +28,7 @@ def test_layout_policy_requires_explanation_for_root_file_exception( ] """, ) - report = run_python_project_harness(tmp_path) + report = run_asp_python(tmp_path) assert [finding.rule_id for finding in report.findings] == ["PY-TEST-R001"] _write_policy( @@ -40,7 +40,7 @@ def test_layout_policy_requires_explanation_for_root_file_exception( ] """, ) - report = run_python_project_harness(tmp_path) + report = run_asp_python(tmp_path) assert not any(finding.rule_id == "PY-TEST-R001" for finding in report.findings) @@ -64,7 +64,7 @@ def test_layout_policy_requires_explanation_for_directory_exception( ] """, ) - report = run_python_project_harness(tmp_path) + report = run_asp_python(tmp_path) assert [finding.rule_id for finding in report.findings] == ["PY-TEST-R002"] _write_policy( @@ -76,12 +76,12 @@ def test_layout_policy_requires_explanation_for_directory_exception( ] """, ) - report = run_python_project_harness(tmp_path) + report = run_asp_python(tmp_path) assert not any(finding.rule_id == "PY-TEST-R002" for finding in report.findings) def _write_policy(tests_dir: Path, content: str) -> None: - (tests_dir / "python-project-harness-rules.toml").write_text( + (tests_dir / "asp-python-rules.toml").write_text( content.lstrip(), encoding="utf-8", ) diff --git a/tests/unit/harness/test_verification.py b/tests/unit/asp_python/test_verification.py similarity index 92% rename from tests/unit/harness/test_verification.py rename to tests/unit/asp_python/test_verification.py index c0f91cc..fcb5e3c 100644 --- a/tests/unit/harness/test_verification.py +++ b/tests/unit/asp_python/test_verification.py @@ -3,7 +3,7 @@ import json from typing import TYPE_CHECKING -from python_lang_project_harness import ( +from asp_python import ( PythonOwnerResponsibility, PythonVerificationDependencySignal, PythonVerificationPhase, @@ -17,10 +17,10 @@ PythonVerificationTaskKind, build_python_verification_profile_index_with_config, build_python_verification_report_bundle, - default_python_harness_config, + default_asp_python_config, plan_python_project_verification_with_config, - read_python_project_harness_config, - render_python_project_harness_agent_snapshot_with_config, + read_asp_python_config, + render_asp_python_agent_snapshot_with_config, render_python_verification_plan, render_python_verification_profile_index, render_python_verification_profile_index_json, @@ -38,7 +38,7 @@ def test_verification_profile_hint_plans_external_task( tmp_path: Path, ) -> None: _write_public_api_project(tmp_path) - config = default_python_harness_config().with_verification_profile_hint( + config = default_asp_python_config().with_verification_profile_hint( PythonVerificationProfileHint( "src/pkg/api.py", (PythonOwnerResponsibility.PUBLIC_API,), @@ -61,7 +61,7 @@ def test_verification_receipt_satisfies_matching_task( tmp_path: Path, ) -> None: _write_public_api_project(tmp_path) - base_config = default_python_harness_config().with_verification_profile_hint( + base_config = default_asp_python_config().with_verification_profile_hint( PythonVerificationProfileHint( "src/pkg/api.py", (PythonOwnerResponsibility.PUBLIC_API,), @@ -90,7 +90,7 @@ def test_verification_profile_index_uses_parser_and_dependency_facts( tmp_path, dependencies='dependencies = ["httpx>=0.28"]', ) - config = default_python_harness_config().with_verification_dependency_signal( + config = default_asp_python_config().with_verification_dependency_signal( PythonVerificationDependencySignal( "httpx", (PythonOwnerResponsibility.NETWORK,), @@ -129,7 +129,7 @@ def test_verification_report_bundle_and_writer_emit_modular_artifacts( tmp_path: Path, ) -> None: _write_public_api_project(tmp_path) - config = default_python_harness_config().with_verification_profile_hint( + config = default_asp_python_config().with_verification_profile_hint( PythonVerificationProfileHint( "src/pkg/api.py", (PythonOwnerResponsibility.PUBLIC_API,), @@ -191,8 +191,8 @@ def test_verification_policy_can_be_loaded_from_pyproject_config( ) -> None: _write_public_api_project( tmp_path, - harness_config=""" -[tool.python-lang-project-harness.verification] + asp_python_config=""" +[tool.asp-python.verification] profile_hints = [ { owner_path = "src/pkg/api.py", responsibilities = ["public_api"], task_kinds = ["security"], rationale = "authz-sensitive public API" }, ] @@ -200,18 +200,18 @@ def test_verification_policy_can_be_loaded_from_pyproject_config( { package_name = "httpx", responsibilities = ["network"], task_kinds = ["stress"] }, ] -[tool.python-lang-project-harness.verification.task_contracts] +[tool.asp-python.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] +[tool.asp-python.verification.skill_bindings] security = { skill = "python-security-review", adapter = "bandit" } -[tool.python-lang-project-harness.verification.skill_descriptors] +[tool.asp-python.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" }] } """, ) - config = read_python_project_harness_config(tmp_path) + config = read_asp_python_config(tmp_path) assert config is not None assert config.verification_policy.profile_hints[0].owner_path == "src/pkg/api.py" @@ -260,7 +260,7 @@ def test_verification_skill_binding_renders_contract_reference( .with_rationale("this public API changes tenant authorization") ) config = ( - default_python_harness_config() + default_asp_python_config() .with_verification_profile_hint(hint) .with_verification_skill_binding( PythonVerificationTaskKind.SECURITY, @@ -300,7 +300,7 @@ def test_agent_snapshot_includes_active_verification_tasks( tmp_path: Path, ) -> None: _write_public_api_project(tmp_path) - config = default_python_harness_config().with_verification_profile_hint( + config = default_asp_python_config().with_verification_profile_hint( PythonVerificationProfileHint( "src/pkg/api.py", (PythonOwnerResponsibility.PUBLIC_API,), @@ -309,7 +309,7 @@ def test_agent_snapshot_includes_active_verification_tasks( .with_rationale("this public API needs a security review") ) - rendered = render_python_project_harness_agent_snapshot_with_config( + rendered = render_asp_python_agent_snapshot_with_config( tmp_path, config, ) @@ -325,7 +325,7 @@ def _write_public_api_project( project_root: Path, *, dependencies: str = "", - harness_config: str = "", + asp_python_config: str = "", ) -> None: package = project_root / "src" / "pkg" package.mkdir(parents=True) @@ -353,7 +353,7 @@ def _write_public_api_project( [tool.hatch.build.targets.wheel] packages = ["src/pkg"] -{harness_config} +{asp_python_config} """.lstrip(), encoding="utf-8", ) diff --git a/tests/unit/harness/verification/test_agent_snapshot_profile_index.py b/tests/unit/asp_python/verification/test_agent_snapshot_profile_index.py similarity index 84% rename from tests/unit/harness/verification/test_agent_snapshot_profile_index.py rename to tests/unit/asp_python/verification/test_agent_snapshot_profile_index.py index 69e6caa..2a1ed03 100644 --- a/tests/unit/harness/verification/test_agent_snapshot_profile_index.py +++ b/tests/unit/asp_python/verification/test_agent_snapshot_profile_index.py @@ -2,9 +2,9 @@ from typing import TYPE_CHECKING -from python_lang_project_harness import ( - default_python_harness_config, - render_python_project_harness_agent_snapshot_with_config, +from asp_python import ( + default_asp_python_config, + render_asp_python_agent_snapshot_with_config, ) if TYPE_CHECKING: @@ -16,9 +16,9 @@ def test_agent_snapshot_reminds_when_verification_profile_is_unconfigured( ) -> None: _write_public_api_project(tmp_path) - rendered = render_python_project_harness_agent_snapshot_with_config( + rendered = render_asp_python_agent_snapshot_with_config( tmp_path, - default_python_harness_config(), + default_asp_python_config(), ) assert "[verify-profile] profile_hints" in rendered diff --git a/tests/unit/harness/verification/test_performance_microbench.py b/tests/unit/asp_python/verification/test_performance_microbench.py similarity index 96% rename from tests/unit/harness/verification/test_performance_microbench.py rename to tests/unit/asp_python/verification/test_performance_microbench.py index e5f1327..f5b02a6 100644 --- a/tests/unit/harness/verification/test_performance_microbench.py +++ b/tests/unit/asp_python/verification/test_performance_microbench.py @@ -2,7 +2,7 @@ from pathlib import Path -from python_lang_project_harness import ( +from asp_python import ( PythonOwnerResponsibility, PythonVerificationEvidence, PythonVerificationProfileHint, @@ -11,7 +11,7 @@ PythonVerificationSkillDescriptor, PythonVerificationTaskKind, build_python_verification_performance_index, - default_python_harness_config, + default_asp_python_config, plan_python_project_verification_with_config, ) @@ -21,7 +21,7 @@ def test_python_package_microbench_gate_is_verification_owned( ) -> None: _write_public_api_project(tmp_path) config = ( - default_python_harness_config() + default_asp_python_config() .with_verification_profile_hint( PythonVerificationProfileHint( "src/pkg/api.py", diff --git a/tests/unit/harness/verification/test_policy_regressions.py b/tests/unit/asp_python/verification/test_policy_regressions.py similarity index 93% rename from tests/unit/harness/verification/test_policy_regressions.py rename to tests/unit/asp_python/verification/test_policy_regressions.py index 9ea7cd1..a9d95f9 100644 --- a/tests/unit/harness/verification/test_policy_regressions.py +++ b/tests/unit/asp_python/verification/test_policy_regressions.py @@ -2,12 +2,12 @@ from typing import TYPE_CHECKING -from python_lang_project_harness import ( +from asp_python import ( PythonOwnerResponsibility, PythonVerificationDependencySignal, PythonVerificationProfileHint, PythonVerificationTaskKind, - default_python_harness_config, + default_asp_python_config, plan_python_project_verification_with_config, ) @@ -22,7 +22,7 @@ def test_dependency_signal_uses_pep503_distribution_names( tmp_path, dependencies='dependencies = ["zope.interface>=6"]', ) - config = default_python_harness_config().with_verification_dependency_signal( + config = default_asp_python_config().with_verification_dependency_signal( PythonVerificationDependencySignal( "zope-interface", (PythonOwnerResponsibility.NETWORK,), @@ -44,7 +44,7 @@ def test_profile_hint_uses_configured_responsibility_task_mapping( tmp_path: Path, ) -> None: _write_public_api_project(tmp_path) - base_config = default_python_harness_config() + base_config = default_asp_python_config() policy = base_config.verification_policy.with_responsibility_task_kinds( PythonOwnerResponsibility.PUBLIC_API, (PythonVerificationTaskKind.SECURITY,), diff --git a/tests/unit/harness/verification/test_profile_index.py b/tests/unit/asp_python/verification/test_profile_index.py similarity index 92% rename from tests/unit/harness/verification/test_profile_index.py rename to tests/unit/asp_python/verification/test_profile_index.py index b59d89b..f536a06 100644 --- a/tests/unit/harness/verification/test_profile_index.py +++ b/tests/unit/asp_python/verification/test_profile_index.py @@ -2,11 +2,11 @@ from typing import TYPE_CHECKING -from python_lang_project_harness import ( +from asp_python import ( PythonOwnerResponsibility, PythonVerificationProfileHint, build_python_verification_profile_index_with_config, - default_python_harness_config, + default_asp_python_config, render_python_verification_profile_index, ) @@ -19,7 +19,7 @@ def test_profile_index_aggregates_public_branch_owners(tmp_path: Path) -> None: index = build_python_verification_profile_index_with_config( tmp_path, - default_python_harness_config(), + default_asp_python_config(), ) rendered = render_python_verification_profile_index(index) @@ -37,7 +37,7 @@ def test_profile_index_renders_configured_responsibilities_for_drift( tmp_path: Path, ) -> None: _write_public_branch_project(tmp_path) - config = default_python_harness_config().with_verification_profile_hint( + config = default_asp_python_config().with_verification_profile_hint( PythonVerificationProfileHint( "src/pkg/__init__.py", (PythonOwnerResponsibility.CLI,), @@ -59,7 +59,7 @@ def test_profile_index_renders_configured_responsibilities_for_drift( def test_profile_index_omits_configured_candidates(tmp_path: Path) -> None: _write_public_branch_project(tmp_path) - config = default_python_harness_config().with_verification_profile_hint( + config = default_asp_python_config().with_verification_profile_hint( PythonVerificationProfileHint( "src/pkg/__init__.py", (PythonOwnerResponsibility.PUBLIC_API,), diff --git a/tests/unit/lang_harness/test_config_contracts.py b/tests/unit/asp_python_paths/test_config_contracts.py similarity index 85% rename from tests/unit/lang_harness/test_config_contracts.py rename to tests/unit/asp_python_paths/test_config_contracts.py index a614aa4..3c0011b 100644 --- a/tests/unit/lang_harness/test_config_contracts.py +++ b/tests/unit/asp_python_paths/test_config_contracts.py @@ -3,21 +3,21 @@ import json from typing import TYPE_CHECKING -from python_lang_parser import PythonDiagnosticSeverity -from python_lang_project_harness import ( - default_python_harness_config, +from asp_python import ( + default_asp_python_config, python_rule_pack_descriptors, python_syntax_rules, - render_python_lang_harness_json, - run_python_lang_harness, + render_asp_python_report_json, + run_asp_python_paths, ) +from python_lang_parser import PythonDiagnosticSeverity if TYPE_CHECKING: from pathlib import Path -def test_default_python_harness_config_uses_default_rule_packs() -> None: - config = default_python_harness_config() +def test_default_asp_python_config_uses_default_rule_packs() -> None: + config = default_asp_python_config() assert config.ignored_dir_names assert config.blocking_severities == { @@ -44,8 +44,8 @@ def test_rule_pack_descriptors_and_json_renderer_are_stable(tmp_path: Path) -> N source = tmp_path / "module.py" source.write_text('"""Module docs."""\n\nVALUE = 1\n', encoding="utf-8") - report = run_python_lang_harness([source]) - payload = json.loads(render_python_lang_harness_json(report)) + report = run_asp_python_paths([source]) + payload = json.loads(render_asp_python_report_json(report)) assert [descriptor.id for descriptor in python_rule_pack_descriptors()] == [ "python.syntax", diff --git a/tests/unit/lang_harness/test_discovery_runner.py b/tests/unit/asp_python_paths/test_discovery_runner.py similarity index 86% rename from tests/unit/lang_harness/test_discovery_runner.py rename to tests/unit/asp_python_paths/test_discovery_runner.py index 6632666..6f306a5 100644 --- a/tests/unit/lang_harness/test_discovery_runner.py +++ b/tests/unit/asp_python_paths/test_discovery_runner.py @@ -2,21 +2,21 @@ from typing import TYPE_CHECKING +from asp_python import ( + AspPythonConfig, + AspPythonFinding, + PythonSyntaxRulePack, + discover_python_files, + render_asp_python_report, + run_asp_python, + run_asp_python_paths, +) from python_lang_parser import ( PythonDiagnostic, PythonDiagnosticSeverity, PythonModuleReport, SourceLocation, ) -from python_lang_project_harness import ( - PythonHarnessConfig, - PythonHarnessFinding, - PythonSyntaxRulePack, - discover_python_files, - render_python_lang_harness, - run_python_lang_harness, - run_python_project_harness, -) if TYPE_CHECKING: from pathlib import Path @@ -66,7 +66,7 @@ def test_asp_toml_can_include_hidden_python_dirs(tmp_path: Path) -> None: encoding="utf-8", ) - report = run_python_project_harness(tmp_path) + report = run_asp_python(tmp_path) assert report.file_count == 1 assert report.modules[0].path == str(fixture) @@ -112,7 +112,7 @@ def test_discover_python_files_deduplicates_nested_scan_roots( assert discover_python_files([tmp_path, src]) == (source,) -def test_run_python_lang_harness_collects_parse_findings(tmp_path: Path) -> None: +def test_run_asp_python_paths_collects_parse_findings(tmp_path: Path) -> None: good = tmp_path / "good.py" bad = tmp_path / "bad.py" good.write_text( @@ -120,7 +120,7 @@ def test_run_python_lang_harness_collects_parse_findings(tmp_path: Path) -> None ) bad.write_text("def broken(:\n pass\n", encoding="utf-8") - report = run_python_lang_harness([tmp_path]) + report = run_asp_python_paths([tmp_path]) assert report.file_count == 2 assert report.parsed_count == 1 @@ -133,15 +133,15 @@ def test_run_python_lang_harness_collects_parse_findings(tmp_path: Path) -> None assert report.to_dict()["is_clean"] is False -def test_run_python_lang_harness_uses_configured_discovery(tmp_path: Path) -> None: +def test_run_asp_python_paths_uses_configured_discovery(tmp_path: Path) -> None: generated = tmp_path / "generated" generated.mkdir() ignored = generated / "debug.py" ignored.write_text('def run():\n print("debug")\n', encoding="utf-8") - report = run_python_lang_harness( + report = run_asp_python_paths( [tmp_path], - config=PythonHarnessConfig(ignored_dir_names=frozenset({"generated"})), + config=AspPythonConfig(ignored_dir_names=frozenset({"generated"})), ) assert report.file_count == 0 @@ -176,17 +176,17 @@ def test_syntax_rule_pack_handles_unknown_parser_error_codes() -> None: assert findings[0].summary == "permission denied" -def test_run_python_lang_harness_uses_configured_blocking_severities( +def test_run_asp_python_paths_uses_configured_blocking_severities( tmp_path: Path, ) -> None: source = tmp_path / "module.py" source.write_text("VALUE = 1\n", encoding="utf-8") - config = PythonHarnessConfig( + config = AspPythonConfig( blocking_severities=frozenset({PythonDiagnosticSeverity.ERROR}), rule_packs=(_WarningRulePack(),), ) - report = run_python_lang_harness([source], config=config) + report = run_asp_python_paths([source], config=config) assert [finding.rule_id for finding in report.findings] == [ "python.project.warning" @@ -194,15 +194,15 @@ def test_run_python_lang_harness_uses_configured_blocking_severities( assert report.blocking_findings() == () assert report.is_clean assert report.to_dict()["blocking_severities"] == ["error"] - assert render_python_lang_harness(report).startswith("[ok]") + assert render_asp_python_report(report).startswith("[ok]") class _WarningRulePack: pack_id = "test.warning" - def evaluate(self, report: PythonModuleReport) -> tuple[PythonHarnessFinding, ...]: + def evaluate(self, report: PythonModuleReport) -> tuple[AspPythonFinding, ...]: return ( - PythonHarnessFinding( + AspPythonFinding( rule_id="python.project.warning", pack_id=self.pack_id, severity=PythonDiagnosticSeverity.WARNING, diff --git a/tests/unit/lang_harness/test_render_assertions.py b/tests/unit/asp_python_paths/test_render_assertions.py similarity index 71% rename from tests/unit/lang_harness/test_render_assertions.py rename to tests/unit/asp_python_paths/test_render_assertions.py index 3ff5fc9..cc9db06 100644 --- a/tests/unit/lang_harness/test_render_assertions.py +++ b/tests/unit/asp_python_paths/test_render_assertions.py @@ -2,14 +2,14 @@ from typing import TYPE_CHECKING -from python_lang_parser import PythonDiagnosticSeverity, SourceLocation -from python_lang_project_harness import ( - PythonHarnessConfig, - PythonHarnessFinding, - assert_python_lang_harness_clean, - render_python_lang_harness, - run_python_lang_harness, +from asp_python import ( + AspPythonConfig, + AspPythonFinding, + assert_asp_python_paths_clean, + render_asp_python_report, + run_asp_python_paths, ) +from python_lang_parser import PythonDiagnosticSeverity, SourceLocation if TYPE_CHECKING: from pathlib import Path @@ -17,13 +17,13 @@ from python_lang_parser import PythonModuleReport -def test_render_python_lang_harness_uses_compact_source_diagnostic( +def test_render_asp_python_report_uses_compact_source_diagnostic( tmp_path: Path, ) -> None: bad = tmp_path / "bad.py" bad.write_text("def broken(:\n pass\n", encoding="utf-8") - output = render_python_lang_harness(run_python_lang_harness([bad])) + output = render_asp_python_report(run_asp_python_paths([bad])) assert output.startswith("[fail] python blockingFindings=1 parsed=0/1") assert "|failureFrontier rule=python.syntax.invalid severity=error" in output @@ -36,15 +36,15 @@ def test_render_python_lang_harness_uses_compact_source_diagnostic( assert "Evidence:" not in output -def test_render_python_lang_harness_attaches_agent_advice_by_default( +def test_render_asp_python_report_attaches_agent_advice_by_default( tmp_path: Path, ) -> None: source = tmp_path / "module.py" source.write_text("def run(value):\n print(value)\n", encoding="utf-8") - report = run_python_lang_harness([source]) + report = run_asp_python_paths([source]) - default_output = render_python_lang_harness(report) - quiet_output = render_python_lang_harness(report, include_advice=False) + default_output = render_asp_python_report(report) + quiet_output = render_asp_python_report(report, include_advice=False) assert default_output.startswith("[fail] python blockingFindings=1 parsed=1/1") assert "|failureFrontier rule=PY-MOD-R002 severity=warning" in default_output @@ -61,33 +61,33 @@ def test_render_python_lang_harness_attaches_agent_advice_by_default( assert "PY-AGENT" not in quiet_output -def test_assert_python_lang_harness_clean_blocks_for_pytest(tmp_path: Path) -> None: +def test_assert_asp_python_paths_clean_blocks_for_pytest(tmp_path: Path) -> None: bad = tmp_path / "bad.py" bad.write_text("def broken(:\n pass\n", encoding="utf-8") try: - assert_python_lang_harness_clean([bad]) + assert_asp_python_paths_clean([bad]) except AssertionError as error: message = str(error) else: - raise AssertionError("harness should block invalid Python source") + raise AssertionError("ASP Python should block invalid Python source") assert "[fail] python blockingFindings=1 parsed=0/1" in message assert "python.syntax.invalid" in message -def test_assert_python_lang_harness_clean_includes_agent_advice_by_default( +def test_assert_asp_python_paths_clean_includes_agent_advice_by_default( tmp_path: Path, ) -> None: source = tmp_path / "module.py" source.write_text("def run(value):\n print(value)\n", encoding="utf-8") try: - assert_python_lang_harness_clean([source]) + assert_asp_python_paths_clean([source]) except AssertionError as error: message = str(error) else: - raise AssertionError("harness should block warning findings") + raise AssertionError("ASP Python should block warning findings") assert "[advice]" in message assert ( @@ -96,52 +96,52 @@ def test_assert_python_lang_harness_clean_includes_agent_advice_by_default( ) -def test_assert_python_lang_harness_clean_can_disable_agent_advice( +def test_assert_asp_python_paths_clean_can_disable_agent_advice( tmp_path: Path, ) -> None: source = tmp_path / "module.py" source.write_text("def run(value):\n print(value)\n", encoding="utf-8") try: - assert_python_lang_harness_clean([source], include_advice=False) + assert_asp_python_paths_clean([source], include_advice=False) except AssertionError as error: message = str(error) else: - raise AssertionError("harness should block warning findings") + raise AssertionError("ASP Python should block warning findings") assert "[fail] python blockingFindings=1 parsed=1/1" in message assert "|failureFrontier rule=PY-MOD-R002 severity=warning" in message assert "[advice]" not in message -def test_assert_python_lang_harness_clean_blocks_warning_findings( +def test_assert_asp_python_paths_clean_blocks_warning_findings( tmp_path: Path, ) -> None: source = tmp_path / "module.py" source.write_text("VALUE = 1\n", encoding="utf-8") try: - assert_python_lang_harness_clean([source], rule_packs=(_WarningRulePack(),)) + assert_asp_python_paths_clean([source], rule_packs=(_WarningRulePack(),)) except AssertionError as error: message = str(error) else: - raise AssertionError("harness should block warning findings") + raise AssertionError("ASP Python should block warning findings") assert "[fail] python blockingFindings=1 parsed=1/1" in message assert "|failureFrontier rule=python.project.warning severity=warning" in message -def test_assert_python_lang_harness_clean_honors_configured_blocking_severities( +def test_assert_asp_python_paths_clean_honors_configured_blocking_severities( tmp_path: Path, ) -> None: source = tmp_path / "module.py" source.write_text("VALUE = 1\n", encoding="utf-8") - config = PythonHarnessConfig( + config = AspPythonConfig( blocking_severities=frozenset({PythonDiagnosticSeverity.ERROR}), rule_packs=(_WarningRulePack(),), ) - report = assert_python_lang_harness_clean([source], config=config) + report = assert_asp_python_paths_clean([source], config=config) assert [finding.rule_id for finding in report.findings] == [ "python.project.warning" @@ -149,18 +149,18 @@ def test_assert_python_lang_harness_clean_honors_configured_blocking_severities( assert report.is_clean -def test_assert_python_lang_harness_clean_honors_severities_override( +def test_assert_asp_python_paths_clean_honors_severities_override( tmp_path: Path, ) -> None: source = tmp_path / "module.py" source.write_text("VALUE = 1\n", encoding="utf-8") - config = PythonHarnessConfig( + config = AspPythonConfig( blocking_severities=frozenset({PythonDiagnosticSeverity.ERROR}), rule_packs=(_WarningRulePack(),), ) try: - assert_python_lang_harness_clean( + assert_asp_python_paths_clean( [source], config=config, severities=frozenset({PythonDiagnosticSeverity.WARNING}), @@ -177,9 +177,9 @@ def test_assert_python_lang_harness_clean_honors_severities_override( class _WarningRulePack: pack_id = "test.warning" - def evaluate(self, report: PythonModuleReport) -> tuple[PythonHarnessFinding, ...]: + def evaluate(self, report: PythonModuleReport) -> tuple[AspPythonFinding, ...]: return ( - PythonHarnessFinding( + AspPythonFinding( rule_id="python.project.warning", pack_id=self.pack_id, severity=PythonDiagnosticSeverity.WARNING, diff --git a/tests/unit/harness/test_cli.py b/tests/unit/harness/test_cli.py deleted file mode 100644 index db009d4..0000000 --- a/tests/unit/harness/test_cli.py +++ /dev/null @@ -1,328 +0,0 @@ -from __future__ import annotations - -import io -import json -from typing import TYPE_CHECKING - -from python_lang_project_harness import run_cli - -if TYPE_CHECKING: - from pathlib import Path - - -def test_cli_help_advertises_exact_projection_routes() -> None: - stdout = io.StringIO() - exit_code = run_cli(["--help"], stdout=stdout) - rendered = stdout.getvalue() - assert exit_code == 0 - assert "asp-python search ... [--json] [--package PATH]" in rendered - assert ( - "asp python query --selector " - "--projection " in rendered - ) - - -def test_cli_subcommand_help_advertises_exact_projection() -> None: - for args in (["search", "--help"], ["query", "--help"]): - stdout = io.StringIO() - exit_code = run_cli(args, stdout=stdout) - rendered = stdout.getvalue() - assert exit_code == 0 - assert "--selector " in rendered - assert "--projection " in rendered - - -def test_cli_agent_guide_advertises_exact_source_route(tmp_path: Path) -> None: - stdout = io.StringIO() - - exit_code = run_cli(["agent", "guide", str(tmp_path)], stdout=stdout) - rendered = stdout.getvalue() - - assert exit_code == 0 - assert ( - "asp python query --selector " - "--projection source --workspace " in rendered - ) - - -def test_cli_renders_compact_text_by_default(tmp_path: Path) -> None: - package = tmp_path / "src" / "pkg" - package.mkdir(parents=True) - (package / "__init__.py").write_text('"""Package docs."""\n', encoding="utf-8") - - stdout = io.StringIO() - stderr = io.StringIO() - - exit_code = run_cli([str(tmp_path)], stdout=stdout, stderr=stderr) - - assert exit_code == 0 - assert stderr.getvalue() == "" - assert stdout.getvalue().startswith("[ok] . python") - assert "Files: 1 Parsed: 1" in stdout.getvalue() - assert str(tmp_path) not in stdout.getvalue() - - -def test_cli_json_flag_renders_structured_report(tmp_path: Path) -> None: - package = tmp_path / "src" / "pkg" - package.mkdir(parents=True) - (package / "__init__.py").write_text('"""Package docs."""\n', encoding="utf-8") - stdout = io.StringIO() - - exit_code = run_cli(["--json", str(tmp_path)], stdout=stdout) - payload = json.loads(stdout.getvalue()) - - assert exit_code == 0 - assert payload["is_clean"] is True - assert payload["file_count"] == 1 - assert payload["project_resolution"]["project_root"] == str(tmp_path) - - -def test_cli_agent_snapshot_renders_parser_backed_project_shape( - tmp_path: Path, -) -> None: - package = tmp_path / "src" / "pkg" - package.mkdir(parents=True) - (package / "__init__.py").write_text('"""Package docs."""\n', encoding="utf-8") - stdout = io.StringIO() - - exit_code = run_cli(["--agent-snapshot", str(tmp_path)], stdout=stdout) - - assert exit_code == 0 - rendered = stdout.getvalue() - assert rendered.startswith("[agent-snapshot] . python\n") - assert "[tree] . python" in rendered - assert "Modules: source=1" in rendered - assert "[nodes]" not in rendered - assert "[ok]" not in rendered - assert str(tmp_path) not in rendered - - -def test_cli_keeps_agent_advice_non_blocking(tmp_path: Path) -> None: - src = tmp_path / "src" - src.mkdir() - (src / "service.py").write_text( - "def build(value):\n return value\n", encoding="utf-8" - ) - stdout = io.StringIO() - - exit_code = run_cli([str(tmp_path)], stdout=stdout) - - assert exit_code == 0 - assert "[advice]" in stdout.getvalue() - assert "PY-AGENT-POLICY-001" in stdout.getvalue() - - -def test_cli_exits_nonzero_for_blocking_findings(tmp_path: Path) -> None: - src = tmp_path / "src" - src.mkdir() - (src / "service.py").write_text( - 'def build() -> None:\n print("debug")\n', - encoding="utf-8", - ) - stdout = io.StringIO() - - exit_code = run_cli([str(tmp_path)], stdout=stdout) - - assert exit_code == 1 - assert "PY-MOD-R002" in stdout.getvalue() - assert stdout.getvalue().startswith("[fail] python") - assert "severity=warning" in stdout.getvalue() - assert "src/service.py" in stdout.getvalue() - assert str(tmp_path) not in stdout.getvalue() - - -def test_cli_can_disable_policy_rule_ids(tmp_path: Path) -> None: - src = tmp_path / "src" - src.mkdir() - (src / "service.py").write_text( - 'def run() -> None:\n print("debug")\n', - encoding="utf-8", - ) - stdout = io.StringIO() - - exit_code = run_cli( - ["--disable-rule", "PY-MOD-R002", str(tmp_path)], - stdout=stdout, - ) - - assert exit_code == 0 - assert "PY-MOD-R002" not in stdout.getvalue() - - -def test_cli_loads_project_policy_config_from_pyproject(tmp_path: Path) -> None: - src = tmp_path / "src" - src.mkdir() - (tmp_path / "pyproject.toml").write_text( - """ -[tool.python-lang-project-harness] -disabled_rule_ids = ["PY-MOD-R002"] -""".lstrip(), - encoding="utf-8", - ) - (src / "service.py").write_text( - 'def run() -> None:\n print("debug")\n', - encoding="utf-8", - ) - stdout = io.StringIO() - - exit_code = run_cli([str(tmp_path)], stdout=stdout) - - assert exit_code == 0 - assert "PY-MOD-R002" not in stdout.getvalue() - - -def test_cli_uses_pyproject_declared_package_source_scope(tmp_path: Path) -> None: - default_src = tmp_path / "src" - package_source = tmp_path / "packages" / "python" / "src" - package = package_source / "tools" - tests = tmp_path / "tests" / "unit" - default_src.mkdir() - package.mkdir(parents=True) - tests.mkdir(parents=True) - (default_src / "ignored.py").write_text( - "def broken(:\n pass\n", encoding="utf-8" - ) - (package / "__init__.py").write_text( - '"""Package public API."""\n\n\ndef build(value: int) -> int:\n return value\n', - encoding="utf-8", - ) - (package / "py.typed").write_text("", encoding="utf-8") - (tests / "test_tools.py").write_text( - "def test_tools() -> None:\n assert True\n", - encoding="utf-8", - ) - (tmp_path / "pyproject.toml").write_text( - """ -[project] -name = "example-pkg" -requires-python = ">=3.12" - -[build-system] -requires = ["hatchling"] -build-backend = "hatchling.build" - -[tool.hatch.build.targets.wheel] -packages = ["packages/python/src/tools"] -""".lstrip(), - encoding="utf-8", - ) - stdout = io.StringIO() - - exit_code = run_cli(["--json", str(tmp_path)], stdout=stdout) - payload = json.loads(stdout.getvalue()) - - assert exit_code == 0 - assert payload["is_clean"] is True - assert [finding["rule_id"] for finding in payload["findings"]] == [] - assert payload["project_resolution"]["source_paths"] == [str(package_source)] - assert payload["project_resolution"]["project_paths"] == [ - str(package_source), - str(tmp_path / "tests"), - ] - - -def test_cli_can_promote_policy_rule_ids(tmp_path: Path) -> None: - src = tmp_path / "src" - src.mkdir() - (src / "service.py").write_text( - "def build(value):\n return value\n", - encoding="utf-8", - ) - stdout = io.StringIO() - - exit_code = run_cli( - ["--block-rule", "PY-AGENT-POLICY-001", str(tmp_path)], stdout=stdout - ) - - assert exit_code == 1 - assert "PY-AGENT-POLICY-001" in stdout.getvalue() - assert stdout.getvalue().startswith("[fail] python") - assert "severity=info" in stdout.getvalue() - - -def test_cli_help_and_argument_errors_are_stable(tmp_path: Path) -> None: - help_stdout = io.StringIO() - error_stderr = io.StringIO() - - assert run_cli(["--help"], stdout=help_stdout) == 0 - assert "asp-python search " in help_stdout.getvalue() - assert ( - "asp-python [--json | --agent-snapshot] [--no-tests]" in help_stdout.getvalue() - ) - assert run_cli(["--bogus"], stderr=error_stderr) == 2 - assert "unknown option: --bogus" in error_stderr.getvalue() - assert run_cli([str(tmp_path), str(tmp_path)], stderr=io.StringIO()) == 2 - mutually_exclusive_stderr = io.StringIO() - assert ( - run_cli( - ["--json", "--agent-snapshot", str(tmp_path)], - stderr=mutually_exclusive_stderr, - ) - == 2 - ) - assert "mutually exclusive" in mutually_exclusive_stderr.getvalue() - - -def test_cli_scope_flags_customize_project_paths(tmp_path: Path) -> None: - lib = tmp_path / "lib" - tools = tmp_path / "tools" - tests = tmp_path / "tests" / "unit" - lib.mkdir() - tools.mkdir() - tests.mkdir(parents=True) - (lib / "service.py").write_text('"""Service docs."""\n', encoding="utf-8") - (tools / "check.py").write_text('"""Check docs."""\n', encoding="utf-8") - (tests / "test_bad.py").write_text("def broken(:\n pass\n", encoding="utf-8") - stdout = io.StringIO() - - exit_code = run_cli( - [ - "--source-dir", - "lib", - "--extra-path", - "tools", - "--no-tests", - str(tmp_path), - ], - stdout=stdout, - ) - - assert exit_code == 0 - assert "Files: 2 Parsed: 2" in stdout.getvalue() - assert "[ok] lib, tools python" in stdout.getvalue() - - -def test_cli_no_tests_skips_test_parser_discovery(tmp_path: Path) -> None: - src = tmp_path / "src" - tests = tmp_path / "tests" / "unit" - src.mkdir() - tests.mkdir(parents=True) - (src / "service.py").write_text('"""Service docs."""\n', encoding="utf-8") - (tests / "test_bad.py").write_text("def broken(:\n pass\n", encoding="utf-8") - stdout = io.StringIO() - - exit_code = run_cli(["--no-tests", str(tmp_path)], stdout=stdout) - - assert exit_code == 0 - assert "Files: 1 Parsed: 1" in stdout.getvalue() - - -def test_cli_scope_flag_values_are_required() -> None: - stderr = io.StringIO() - - assert run_cli(["--source-dir"], stderr=stderr) == 2 - assert "missing value for --source-dir" in stderr.getvalue() - assert run_cli(["--disable-rule"], stderr=io.StringIO()) == 2 - - -def test_cli_defaults_to_current_working_directory(tmp_path: Path) -> None: - src = tmp_path / "src" - src.mkdir() - (src / "service.py").write_text('"""Service docs."""\n', encoding="utf-8") - stdout = io.StringIO() - - exit_code = run_cli((), stdout=stdout, cwd=tmp_path) - - assert exit_code == 0 - assert stdout.getvalue().startswith("[ok] . python") - assert str(tmp_path) not in stdout.getvalue() diff --git a/tests/unit/harness/test_dependency_topology_cli.py b/tests/unit/harness/test_dependency_topology_cli.py deleted file mode 100644 index 3b35946..0000000 --- a/tests/unit/harness/test_dependency_topology_cli.py +++ /dev/null @@ -1,67 +0,0 @@ -from __future__ import annotations - -import io -import json -import re -from pathlib import Path - -from python_lang_project_harness._cli_args import ProtocolArgs -from python_lang_project_harness._cli_protocol import run_protocol_cli - - -def test_dependency_topology_cli_emits_canonical_packet(tmp_path: Path) -> None: - (tmp_path / "requirements.txt").write_text( - "requests>=2.31\n", - encoding="utf-8", - ) - args = ProtocolArgs.parse( - [ - "search", - "dependency-topology", - "--json", - "--workspace", - str(tmp_path), - ] - ) - assert args is not None - stdout = io.StringIO() - stderr = io.StringIO() - - exit_code = run_protocol_cli( - args, - stdout=stdout, - stderr=stderr, - stdin="", - cwd=tmp_path, - ) - - assert exit_code == 0 - assert stderr.getvalue() == "" - packet = json.loads(stdout.getvalue()) - assert packet["packetKind"] == "dependency-topology" - assert re.fullmatch(r"sha256:[0-9a-f]{64}", packet["fingerprint"]) - assert packet["graph"]["nodes"] == [ - { - "id": "dependency:requests", - "kind": "dependency", - "value": "requests", - "path": "requirements.txt", - "fields": { - "dependencyName": "requests", - "manifestPath": "requirements.txt", - }, - }, - { - "id": "dependency-version:requests", - "kind": "dependency-version", - "value": ">=2.31", - "fields": {"version": ">=2.31"}, - }, - ] - assert packet["graph"]["edges"] == [ - { - "source": "dependency:requests", - "target": "dependency-version:requests", - "relation": "version_locked", - } - ] diff --git a/tests/unit/harness/test_evidence_graph.py b/tests/unit/harness/test_evidence_graph.py deleted file mode 100644 index ac9a8f2..0000000 --- a/tests/unit/harness/test_evidence_graph.py +++ /dev/null @@ -1,111 +0,0 @@ -"""Evidence graph CLI tests for the Python harness provider.""" - -from __future__ import annotations - -import io -import json -from pathlib import Path - -from python_lang_project_harness import run_cli - - -def test_cli_evidence_graph_renders_json_contract(tmp_path: Path) -> None: - (tmp_path / "pyproject.toml").write_text( - '[project]\nname = "evidence-fixture"\nversion = "0.1.0"\n', - encoding="utf-8", - ) - stdout = io.StringIO() - - exit_code = run_cli( - ["evidence", "graph", "--json", str(tmp_path)], - stdout=stdout, - cwd=tmp_path, - ) - - assert exit_code == 0 - payload = json.loads(stdout.getvalue()) - assert payload["schemaId"] == "agent.semantic-protocols.semantic-evidence-graph" - assert payload["protocolId"] == "agent.semantic-protocols.evidence-graph" - assert payload["producer"]["languageId"] == "python" - assert payload["producer"]["providerId"] == "asp-python" - assert payload["project"]["package"] == "evidence-fixture" - assert payload["summary"] == { - "nodes": 4, - "edges": 3, - "owners": 1, - "claims": 1, - "staleItems": 0, - "gaps": 1, - } - assert any(node["kind"] == "owner" for node in payload["nodes"]) - assert any(edge["kind"] == "requires-evidence" for edge in payload["edges"]) - assert payload["gaps"][0]["fields"]["nextCommand"] == "asp-python check --full ." - - -def test_cli_evidence_analyze_renders_graph_turbo_request(tmp_path: Path) -> None: - (tmp_path / "pyproject.toml").write_text( - '[project]\nname = "analysis-fixture"\nversion = "0.1.0"\n', - encoding="utf-8", - ) - stdout = io.StringIO() - - exit_code = run_cli( - ["evidence", "analyze", "--json", str(tmp_path)], - stdout=stdout, - cwd=tmp_path, - ) - - assert exit_code == 0 - payload = json.loads(stdout.getvalue()) - assert ( - payload["schemaId"] == "agent.semantic-protocols.semantic-graph-turbo-request" - ) - assert payload["packetKind"] == "graph-turbo-request" - assert payload["surface"] == "evidence-analyze" - assert payload["profile"] == "evidence-quality" - assert payload["producer"]["languageId"] == "python" - assert payload["summary"]["graphs"] == 1 - assert payload["summary"]["nodes"] == 4 - assert payload["summary"]["gaps"] == 1 - assert payload["graphs"][0]["graphId"] == "python.evidence.graph" - assert payload["seedIds"] == ["python:owner:pyproject.toml"] - assert any( - edge["relation"] == "requires-evidence" - for edge in payload["graphs"][0]["edges"] - ) - - -def test_agent_registry_advertises_evidence_methods(tmp_path: Path) -> None: - stdout = io.StringIO() - - exit_code = run_cli( - ["agent", "doctor", "--json", str(tmp_path)], - stdout=stdout, - cwd=tmp_path, - ) - - assert exit_code == 0 - registry = json.loads(stdout.getvalue()) - language = registry["registry"]["languages"][0] - assert "evidence/graph" in language["methods"] - assert "evidence/analyze" in language["methods"] - analyze = next( - descriptor - for descriptor in language["methodDescriptors"] - if descriptor["method"] == "evidence/analyze" - ) - assert analyze["command"] == "evidence" - assert analyze["outputSchemaIds"] == [ - "agent.semantic-protocols.semantic-graph-turbo-request" - ] - - -def test_agent_guide_advertises_evidence_commands(tmp_path: Path) -> None: - stdout = io.StringIO() - - exit_code = run_cli(["agent", "guide"], stdout=stdout, cwd=tmp_path) - - assert exit_code == 0 - guide = stdout.getvalue() - assert "evidence graph --json" in guide - assert "evidence analyze --json" in guide diff --git a/tests/unit/harness/test_harness_rules.py b/tests/unit/harness/test_harness_rules.py deleted file mode 100644 index 991e8d0..0000000 --- a/tests/unit/harness/test_harness_rules.py +++ /dev/null @@ -1,85 +0,0 @@ -from __future__ import annotations - -import os -import tempfile -from pathlib import Path - -from python_lang_project_harness._agent_policy_catalog import python_agent_policy_rules -from python_lang_project_harness._harness_rules import ( - python_harness_rules_markdown, - render_python_harness_rules_markdown, - write_python_harness_rules_to_unit_tests, -) -from python_lang_project_harness._modern_design_catalog import ( - python_modern_design_rules, -) -from python_lang_project_harness._modularity import python_modularity_rules -from python_lang_project_harness._project_policy_catalog import ( - python_project_policy_rules, -) -from python_lang_project_harness._test_layout_catalog import python_test_layout_rules - - -def _harness_rules_rule_ids() -> list[str]: - rule_ids: list[str] = [] - for line in python_harness_rules_markdown().splitlines(): - rule_id, _ = line.removeprefix("- ").split(": ", 1) - rule_ids.append(rule_id) - return rule_ids - - -def _catalog_rule_ids() -> list[str]: - rules = ( - *python_agent_policy_rules(), - *python_modern_design_rules(), - *python_modularity_rules(), - *python_project_policy_rules(), - *python_test_layout_rules(), - ) - return [rule.rule_id for rule in rules] - - -def test_harness_rules_markdown_is_plain_rule_id_list() -> None: - count = 0 - for index, line in enumerate(python_harness_rules_markdown().splitlines(), start=1): - assert line.startswith("- "), index - rule_id, sentence = line.removeprefix("- ").split(": ", 1) - - assert rule_id.startswith( - ( - "PY-AGENT-R", - "PY-AGENT-POLICY", - "PY-AGENT-PROJECT", - "PY-MOD-R", - "PY-PROJ-R", - "PY-TEST-R", - ) - ) - assert sentence.endswith(".") - assert sum(sentence.count(mark) for mark in ".!?") == 1 - count += 1 - - assert count == 32 - - -def test_harness_rules_ids_match_rule_catalog() -> None: - assert sorted(_harness_rules_rule_ids()) == sorted(_catalog_rule_ids()) - - -def test_generated_harness_rules_matches_unit_fixture() -> None: - unit_dir = Path(__file__).resolve().parent - fixture = unit_dir / "harness-rules.generated.md" - if os.environ.get("UPDATE_HARNESS_RULES"): - write_python_harness_rules_to_unit_tests(unit_dir) - - assert fixture.read_text(encoding="utf-8") == render_python_harness_rules_markdown() - - -def test_harness_rules_writer_targets_requested_unit_dir() -> None: - with tempfile.TemporaryDirectory() as directory: - output = write_python_harness_rules_to_unit_tests(Path(directory)) - - assert output == Path(directory) / "harness-rules.generated.md" - assert ( - output.read_text(encoding="utf-8") == render_python_harness_rules_markdown() - ) diff --git a/tests/unit/harness/test_project_fixture_scope.py b/tests/unit/harness/test_project_fixture_scope.py deleted file mode 100644 index e820af2..0000000 --- a/tests/unit/harness/test_project_fixture_scope.py +++ /dev/null @@ -1,25 +0,0 @@ -from __future__ import annotations - -from pathlib import Path - -from python_lang_project_harness import run_python_project_harness - - -def test_run_python_project_harness_skips_test_fixture_sources_by_default( - tmp_path: Path, -) -> None: - """Keep borrowed fixture projects out of root policy scans.""" - - src = tmp_path / "src" - fixture_src = tmp_path / "tests" / "fixtures" / "parser-compact" / "project" / "src" - src.mkdir() - fixture_src.mkdir(parents=True) - source_file = src / "library.py" - fixture_file = fixture_src / "borrowed_library.py" - source_file.write_text('"""Library docs."""\n\nVALUE = 1\n', encoding="utf-8") - fixture_file.write_text("def broken(:\n pass\n", encoding="utf-8") - - report = run_python_project_harness(tmp_path) - - assert report.is_clean - assert [module.path for module in report.modules] == [str(source_file)] diff --git a/tests/unit/harness/test_public_cli_identity.py b/tests/unit/harness/test_public_cli_identity.py deleted file mode 100644 index 66cad63..0000000 --- a/tests/unit/harness/test_public_cli_identity.py +++ /dev/null @@ -1,12 +0,0 @@ -from __future__ import annotations - -from python_lang_project_harness._cli_args import help_text - - -def test_public_cli_identity_is_asp_python_without_legacy_aliases() -> None: - rendered = help_text() - - assert rendered.startswith("asp-python ") - assert "asp-python search" in rendered - assert "asp-python check" in rendered - assert "py-harness" not in rendered diff --git a/tests/unit/harness/test_semantic_cli_benchmark_registry.py b/tests/unit/harness/test_semantic_cli_benchmark_registry.py deleted file mode 100644 index 96e52b3..0000000 --- a/tests/unit/harness/test_semantic_cli_benchmark_registry.py +++ /dev/null @@ -1,21 +0,0 @@ -"""Registry benchmark invocation contract tests.""" - -from python_lang_project_harness import python_semantic_language_registration - - -def test_registered_search_methods_publish_public_benchmark_invocations() -> None: - descriptors = python_semantic_language_registration()["methodDescriptors"] - search_descriptors = [ - descriptor - for descriptor in descriptors - if descriptor["method"].startswith("search/") - and "benchmarkInvocation" in descriptor - ] - - assert search_descriptors - for descriptor in search_descriptors: - invocation = descriptor["benchmarkInvocation"] - assert invocation["args"][:2] == ["search", descriptor["view"]] - assert "{workspace}" in invocation["args"] - assert isinstance(invocation["expectsJson"], bool) - assert invocation["maxElapsedMs"] > 0 diff --git a/tests/unit/harness/test_semantic_cli_fast_prime.py b/tests/unit/harness/test_semantic_cli_fast_prime.py deleted file mode 100644 index b2f1189..0000000 --- a/tests/unit/harness/test_semantic_cli_fast_prime.py +++ /dev/null @@ -1,70 +0,0 @@ -"""Fast semantic CLI prime frontier tests.""" - -from __future__ import annotations - -import io -import time -from pathlib import Path - -import pytest - -from python_lang_project_harness import run_cli - -FAST_SEARCH_BUDGET_SECONDS = 0.25 - - -def test_cli_search_prime_seed_view_uses_fast_frontier( - tmp_path: Path, - monkeypatch: pytest.MonkeyPatch, -) -> None: - source_path = tmp_path / "src" / "pkg" / "service.py" - source_path.parent.mkdir(parents=True) - source_path.write_text("def build():\n return 1\n", encoding="utf-8") - - from python_lang_project_harness import _cli_protocol - - def fail_full_harness(*_args: object, **_kwargs: object) -> object: - raise AssertionError("full harness should not run for prime seed view") - - monkeypatch.setattr(_cli_protocol, "_run_search_harness", fail_full_harness) - stdout = io.StringIO() - - started_at = time.perf_counter() - exit_code = run_cli( - ["search", "prime", "--view", "seeds", "--workspace", str(tmp_path)], - stdout=stdout, - ) - elapsed = time.perf_counter() - started_at - - rendered = stdout.getvalue() - assert exit_code == 0 - assert rendered.startswith("[search-prime]") - assert "alg=fast-prime-frontier-v1" in rendered - assert "|decision purpose=decision-primer" in rendered - assert "answer=false code=false" in rendered - assert ( - "capabilities=pipe,lexical,fd-query,rg-query,owner-items,selector-code,treesitter-query" - in rendered - ) - assert "ladder=pipe>lexical>fd-query|rg-query>owner-items>selector-code" in rendered - assert ( - "history=asp-artifacts:directReadRisk,repeatedPrime,repeatedPipe,bestPath" - in rendered - ) - assert "risk=broad-direct-read,manual-window-scan,repeat-prime" in rendered - assert ( - "next=\"asp python search pipe '' --workspace --view seeds\"" - in rendered - ) - assert "owner:path(src/pkg/service.py)" in rendered - assert "frontier=O1.owner" in rendered - assert ( - "legend: ID=kind:role(value)!next; entries profile(selectors=>returns); frontier ID.next" - in rendered - ) - assert ( - "entries=owner-tests(O=>covering-tests+test-entrypoints+fixtures)" in rendered - ) - assert "A1=owner-items" not in rendered - assert "recommendedNext=owner-items" not in rendered - assert elapsed < FAST_SEARCH_BUDGET_SECONDS diff --git a/tests/unit/harness/test_semantic_cli_lexical.py b/tests/unit/harness/test_semantic_cli_lexical.py deleted file mode 100644 index cf6fb65..0000000 --- a/tests/unit/harness/test_semantic_cli_lexical.py +++ /dev/null @@ -1,253 +0,0 @@ -"""Semantic CLI lexical protocol tests.""" - -from __future__ import annotations - -import io -from pathlib import Path - -from semantic_search_fixture import require_compact_graph_renderer, write_search_fixture - -from python_lang_project_harness._cli import run_cli - - -def test_cli_search_lexical_query_set(tmp_path: Path) -> None: - write_search_fixture(tmp_path) - stdout = io.StringIO() - exit_code = run_cli( - [ - "search", - "lexical", - "--query", - "build", - "--query", - "Session", - "owner", - "tests", - "--workspace", - str(tmp_path), - ], - stdout=stdout, - ) - rendered = stdout.getvalue() - assert exit_code == 0 - assert rendered.startswith('[search-lexical] q="build,Session" querySet=2') - assert "selector=lexical-set" in rendered - for line in rendered.splitlines(): - if line.startswith("|seed "): - assert ",owner:" not in line - assert ",tests:" not in line - - -def test_cli_search_lexical_matches_path_only_candidate(tmp_path: Path) -> None: - require_compact_graph_renderer() - write_search_fixture(tmp_path) - path_owner = tmp_path / "src" / "pkg" / "hook_runtime.py" - path_owner.write_text( - '"""Path-only lexical owner."""\n\ndef execute() -> None:\n pass\n', - encoding="utf-8", - ) - stdout = io.StringIO() - exit_code = run_cli( - [ - "search", - "lexical", - "--query", - "hookruntime", - "--query", - "execute", - "owner", - "tests", - "--view", - "seeds", - "--workspace", - str(tmp_path), - ], - stdout=stdout, - ) - rendered = stdout.getvalue() - assert exit_code == 0 - assert rendered.startswith("[search-lexical] q=hookruntime,execute") - assert "O=owner:path(src/pkg/hook_runtime.py)!owner" in rendered - assert "rank=Q,O,T frontier=Q.lexical,O.owner,T.tests" in rendered - assert "|seed " not in rendered - - -def test_protocol_search_lexical_query_uses_fast_frontier( - tmp_path: Path, -) -> None: - from python_lang_project_harness._cli_args import ProtocolArgs - from python_lang_project_harness._cli_protocol import run_protocol_cli - - write_search_fixture(tmp_path) - path_owner = tmp_path / "src" / "pkg" / "hook_runtime.py" - path_owner.write_text( - '"""Path-only lexical owner."""\n\ndef execute() -> None:\n pass\n', - encoding="utf-8", - ) - args = ProtocolArgs.parse( - [ - "search", - "lexical", - "--query", - "hookruntime", - "--query", - "execute", - "owner", - "tests", - "--view", - "seeds", - "--workspace", - str(tmp_path), - ] - ) - stdout = io.StringIO() - stderr = io.StringIO() - assert args is not None - exit_code = run_protocol_cli( - args, - stdout=stdout, - stderr=stderr, - stdin="", - cwd=tmp_path, - ) - rendered = stdout.getvalue() - assert stderr.getvalue() == "" - assert exit_code == 0 - assert rendered.startswith( - "[search-lexical] q=hookruntime,execute querySet=2 selector=lexical-set " - "view=hits alg=query-set-owner-resolution" - ) - assert "Q=query:term(hookruntime,execute)!lexical" in rendered - assert "entries=owner-query(O,Q=>items+tests+dependency-usage)" in rendered - assert "rank=Q,O,T frontier=Q.lexical,O.owner,T.tests" in rendered - assert "|seed " not in rendered - - -def test_protocol_search_lexical_query_uses_native_prefilter_without_tools( - tmp_path: Path, - monkeypatch, -) -> None: - from python_lang_project_harness import ( - _semantic_search_prefilter, - _semantic_search_prefilter_file_scan, - ) - from python_lang_project_harness._cli_args import ProtocolArgs - from python_lang_project_harness._cli_protocol import run_protocol_cli - - monkeypatch.setattr(_semantic_search_prefilter.shutil, "which", lambda _name: None) - monkeypatch.setattr( - _semantic_search_prefilter_file_scan.shutil, - "which", - lambda _name: None, - ) - write_search_fixture(tmp_path) - path_owner = tmp_path / "src" / "pkg" / "hook_runtime.py" - path_owner.write_text( - '"""Path-only lexical owner."""\n\ndef execute() -> None:\n pass\n', - encoding="utf-8", - ) - args = ProtocolArgs.parse( - [ - "search", - "lexical", - "--query", - "hookruntime", - "--query", - "execute", - "owner", - "tests", - "--view", - "seeds", - "--workspace", - str(tmp_path), - ] - ) - stdout = io.StringIO() - stderr = io.StringIO() - assert args is not None - exit_code = run_protocol_cli( - args, - stdout=stdout, - stderr=stderr, - stdin="", - cwd=tmp_path, - ) - rendered = stdout.getvalue() - assert stderr.getvalue() == "" - assert exit_code == 0 - assert rendered.startswith( - "[search-lexical] q=hookruntime,execute querySet=2 selector=lexical-set " - "view=hits alg=query-set-owner-resolution" - ) - assert "Q=query:term(hookruntime,execute)!lexical" in rendered - assert "O=owner:path(src/pkg/hook_runtime.py)!owner" in rendered - assert "entries=owner-query(O,Q=>items+tests+dependency-usage)" in rendered - - -def test_protocol_search_lexical_source_query_uses_rglob_source_without_tools( - tmp_path: Path, - monkeypatch, -) -> None: - from python_lang_project_harness import ( - _semantic_search_prefilter, - _semantic_search_prefilter_file_scan, - ) - from python_lang_project_harness._cli_args import ProtocolArgs - from python_lang_project_harness._cli_protocol import run_protocol_cli - - monkeypatch.setattr(_semantic_search_prefilter.shutil, "which", lambda _name: None) - monkeypatch.setattr( - _semantic_search_prefilter_file_scan.shutil, - "which", - lambda _name: None, - ) - write_search_fixture(tmp_path) - for index in range(130): - filler = tmp_path / "src" / "pkg" / f"filler_{index:03d}.py" - filler.write_text(f"VALUE_{index} = {index}\n", encoding="utf-8") - query_owner = tmp_path / "src" / "pkg" / "cli_query.py" - query_owner.write_text( - "def run_query_command() -> None:\n pass\n", - encoding="utf-8", - ) - protocol_owner = tmp_path / "src" / "pkg" / "cli_protocol.py" - protocol_owner.write_text( - "from .cli_query import run_query_command\n", - encoding="utf-8", - ) - args = ProtocolArgs.parse( - [ - "search", - "lexical", - "--query", - "run_query_command", - "--query", - "command", - "owner", - "tests", - "--view", - "seeds", - "--workspace", - str(tmp_path), - ] - ) - stdout = io.StringIO() - stderr = io.StringIO() - assert args is not None - exit_code = run_protocol_cli( - args, - stdout=stdout, - stderr=stderr, - stdin="", - cwd=tmp_path, - ) - rendered = stdout.getvalue() - assert stderr.getvalue() == "" - assert exit_code == 0 - assert rendered.startswith( - "[search-lexical] q=run_query_command,command querySet=2" - ) - assert "Q=query:term(run_query_command,command)!lexical" in rendered - assert "O=owner:path(src/pkg/cli_query.py)!owner" in rendered - assert "entries=owner-query(O,Q=>items+tests+dependency-usage)" in rendered - assert "rglob-source" in rendered diff --git a/tests/unit/harness/test_semantic_cli_owner_item_broad.py b/tests/unit/harness/test_semantic_cli_owner_item_broad.py deleted file mode 100644 index 8800221..0000000 --- a/tests/unit/harness/test_semantic_cli_owner_item_broad.py +++ /dev/null @@ -1,85 +0,0 @@ -from __future__ import annotations - -import io -from pathlib import Path - -from python_lang_project_harness._cli import run_cli - - -def _write_project(tmp_path: Path) -> None: - (tmp_path / "pyproject.toml").write_text( - "[project]\nname = 'sample'\nversion = '0.1.0'\n", - encoding="utf-8", - ) - package = tmp_path / "src" / "pkg" - package.mkdir(parents=True) - (package / "__init__.py").write_text("", encoding="utf-8") - (package / "service.py").write_text( - "\n".join( - [ - "def item_query_payload():", - " return None", - "def candidate_route():", - " return None", - "def fallback_mode():", - " return None", - "def owner_top_items():", - " return None", - ] - ), - encoding="utf-8", - ) - - -def test_owner_item_broad_fallback_uses_names_only(tmp_path: Path) -> None: - _write_project(tmp_path) - stdout = io.StringIO() - - exit_code = run_cli( - [ - "search", - "owner", - "src/pkg/service.py", - "items", - "--query", - "item_query|candidate|fallback|owner_top", - "--workspace", - str(tmp_path), - ], - stdout=stdout, - ) - rendered = stdout.getvalue() - - assert exit_code == 0 - assert "match=fallback-contains" in rendered - assert "output=names" in rendered - assert "next=select-item" in rendered - assert "|item item_query_payload kind=function" in rendered - assert "|code path=src/pkg/service.py" not in rendered - - -def test_owner_item_miss_fallback_does_not_dump_code(tmp_path: Path) -> None: - _write_project(tmp_path) - stdout = io.StringIO() - - exit_code = run_cli( - [ - "search", - "owner", - "src/pkg/service.py", - "items", - "--query", - "render_semantic_query_json|projection_from_code_line", - "--workspace", - str(tmp_path), - ], - stdout=stdout, - ) - rendered = stdout.getvalue() - - assert exit_code == 0 - assert "status=miss" in rendered - assert "output=names" in rendered - assert "next=revise-query" in rendered - assert "|item item_query_payload kind=function" in rendered - assert "|code path=src/pkg/service.py" not in rendered diff --git a/tests/unit/harness/test_semantic_cli_owner_item_inventory.py b/tests/unit/harness/test_semantic_cli_owner_item_inventory.py deleted file mode 100644 index 483dd00..0000000 --- a/tests/unit/harness/test_semantic_cli_owner_item_inventory.py +++ /dev/null @@ -1,64 +0,0 @@ -"""Owner item inventory tests for the Python semantic CLI.""" - -from __future__ import annotations - -import io -from pathlib import Path - -from python_lang_project_harness import run_cli - - -def test_cli_search_owner_items_without_query_returns_inventory( - tmp_path: Path, -) -> None: - _write_fixture(tmp_path) - stdout = io.StringIO() - - exit_code = run_cli( - [ - "search", - "owner", - "src/pkg/service.py", - "items", - "--workspace", - str(tmp_path), - ], - stdout=stdout, - ) - - rendered = stdout.getvalue() - assert exit_code == 0 - assert "[search-owner] q=src/pkg/service.py owner=1 item=" in rendered - assert "|owner src/pkg/service.py " in rendered - assert "|item SessionClient kind=class" in rendered - assert " itemStatus=" not in rendered - assert "|code " not in rendered - assert "|hit " not in rendered - assert "|synthesis " not in rendered - assert "|next " not in rendered - assert " next=syntax:SessionClient " in rendered - assert " tsqRef=semantic-tree-sitter-query/python-owner-items.v1" in rendered - - -def _write_fixture(root: Path) -> None: - (root / "pyproject.toml").write_text( - '[project]\nname = "owner-item-inventory-fixture"\nversion = "0.1.0"\n', - encoding="utf-8", - ) - package_dir = root / "src" / "pkg" - package_dir.mkdir(parents=True) - (package_dir / "__init__.py").write_text("", encoding="utf-8") - (package_dir / "service.py").write_text( - "\n".join( - [ - "class SessionClient:", - " def fetch_user(self, user_id: str) -> str:", - " return user_id", - "", - "def build_user(user_id: str) -> dict[str, str]:", - " return {'id': user_id}", - "", - ] - ), - encoding="utf-8", - ) diff --git a/tests/unit/harness/test_semantic_cli_owner_items_fast_path.py b/tests/unit/harness/test_semantic_cli_owner_items_fast_path.py deleted file mode 100644 index 05f6001..0000000 --- a/tests/unit/harness/test_semantic_cli_owner_items_fast_path.py +++ /dev/null @@ -1,248 +0,0 @@ -"""Owner item fast-path tests for the Python semantic CLI.""" - -from __future__ import annotations - -import io -import json -import time -from pathlib import Path - -from semantic_search_fixture import write_search_fixture - -from python_lang_project_harness import run_cli - -OWNER_ITEMS_WARM_PATH_GATE_MS = 100.0 -OWNER_WARM_PATH_GATE_MS = 100.0 -DEPENDENCY_WARM_PATH_GATE_MS = 100.0 - - -def test_cli_search_owner_items_query_uses_exact_owner_fast_path( - tmp_path: Path, - monkeypatch, -) -> None: - write_search_fixture(tmp_path) - json_stdout = io.StringIO() - - def fail_full_harness(*_args: object, **_kwargs: object) -> object: - raise AssertionError("owner-items should not run the full Python harness") - - from python_lang_project_harness import _runner - - monkeypatch.setattr(_runner, "run_python_project_harness", fail_full_harness) - - exit_code = run_cli( - [ - "search", - "owner", - "src/pkg/service.py", - "items", - "--query", - "fetch|build", - "--json", - "--workspace", - str(tmp_path), - ], - stdout=json_stdout, - ) - - packet = json.loads(json_stdout.getvalue()) - assert exit_code == 0 - assert packet["runtimeCost"]["reason"] == "owner-items-exact-owner-prefilter" - assert packet["runtimeCost"]["fields"] == { - "ownerPath": "src/pkg/service.py", - "paths": 1, - } - assert [item["name"] for item in packet["items"]] == ["fetch", "build"] - - -def test_cli_search_owner_items_query_stays_inside_warm_path_gate( - tmp_path: Path, -) -> None: - write_search_fixture(tmp_path) - args = [ - "search", - "owner", - "src/pkg/service.py", - "items", - "--query", - "fetch|build", - "--json", - "--workspace", - str(tmp_path), - ] - - warmup_stdout = io.StringIO() - assert run_cli(args, stdout=warmup_stdout) == 0 - assert "owner-items-exact-owner-prefilter" in warmup_stdout.getvalue() - - timed_stdout = io.StringIO() - started_at = time.perf_counter() - exit_code = run_cli(args, stdout=timed_stdout) - elapsed_ms = (time.perf_counter() - started_at) * 1000 - - assert exit_code == 0 - assert "owner-items-exact-owner-prefilter" in timed_stdout.getvalue() - assert elapsed_ms < OWNER_ITEMS_WARM_PATH_GATE_MS - - -def test_cli_search_owner_path_uses_exact_owner_fast_path( - tmp_path: Path, - monkeypatch, -) -> None: - write_search_fixture(tmp_path) - json_stdout = io.StringIO() - - def fail_full_harness(*_args: object, **_kwargs: object) -> object: - raise AssertionError("owner path search should not run the full Python harness") - - from python_lang_project_harness import _runner - - monkeypatch.setattr(_runner, "run_python_project_harness", fail_full_harness) - - exit_code = run_cli( - [ - "search", - "owner", - "src/pkg/service.py", - "--json", - "--workspace", - str(tmp_path), - ], - stdout=json_stdout, - ) - - packet = json.loads(json_stdout.getvalue()) - assert exit_code == 0 - assert packet["runtimeCost"]["reason"] == "owner-exact-path-prefilter" - assert packet["runtimeCost"]["fields"] == { - "ownerPath": "src/pkg/service.py", - "paths": 1, - } - assert [owner["path"] for owner in packet["owners"]] == ["src/pkg/service.py"] - - -def test_cli_search_owner_path_stays_inside_warm_path_gate( - tmp_path: Path, -) -> None: - write_search_fixture(tmp_path) - args = [ - "search", - "owner", - "src/pkg/service.py", - "--json", - "--workspace", - str(tmp_path), - ] - - warmup_stdout = io.StringIO() - assert run_cli(args, stdout=warmup_stdout) == 0 - assert "owner-exact-path-prefilter" in warmup_stdout.getvalue() - - timed_stdout = io.StringIO() - started_at = time.perf_counter() - exit_code = run_cli(args, stdout=timed_stdout) - elapsed_ms = (time.perf_counter() - started_at) * 1000 - - assert exit_code == 0 - assert "owner-exact-path-prefilter" in timed_stdout.getvalue() - assert elapsed_ms < OWNER_WARM_PATH_GATE_MS - - -def test_cli_search_owner_seed_view_uses_text_fast_path( - tmp_path: Path, - monkeypatch, -) -> None: - write_search_fixture(tmp_path) - stdout = io.StringIO() - - def fail_full_harness(*_args: object, **_kwargs: object) -> object: - raise AssertionError("owner seed view should not run the full Python harness") - - from python_lang_project_harness import _cli_protocol - - monkeypatch.setattr(_cli_protocol, "_run_search_harness", fail_full_harness) - - exit_code = run_cli( - [ - "search", - "owner", - "src/pkg/service.py", - "--view", - "seeds", - "--workspace", - str(tmp_path), - ], - stdout=stdout, - ) - - rendered = stdout.getvalue() - assert exit_code == 0 - assert rendered.startswith("[search-owner]") - assert "alg=fast-exact-owner-frontier" in rendered - assert "O=owner:path(src/pkg/service.py)!owner" in rendered - assert ( - "entries=owner-tests(O=>covering-tests+test-entrypoints+fixtures)" in rendered - ) - - -def test_cli_search_dependency_uses_metadata_fast_path( - tmp_path: Path, - monkeypatch, -) -> None: - write_search_fixture(tmp_path) - json_stdout = io.StringIO() - - def fail_full_harness(*_args: object, **_kwargs: object) -> object: - raise AssertionError("dependency search should not run the full Python harness") - - from python_lang_project_harness import _runner - - monkeypatch.setattr(_runner, "run_python_project_harness", fail_full_harness) - - exit_code = run_cli( - [ - "search", - "deps", - "requests", - "--json", - "--workspace", - str(tmp_path), - ], - stdout=json_stdout, - ) - - packet = json.loads(json_stdout.getvalue()) - assert exit_code == 0 - assert packet["runtimeCost"]["reason"] == "dependency-metadata-prefilter" - assert packet["runtimeCost"]["fields"] == { - "dependency": "requests", - "paths": 0, - } - assert [node["id"] for node in packet["nodes"]] == ["D:requests"] - - -def test_cli_search_dependency_stays_inside_warm_path_gate( - tmp_path: Path, -) -> None: - write_search_fixture(tmp_path) - args = [ - "search", - "deps", - "requests", - "--json", - "--workspace", - str(tmp_path), - ] - - warmup_stdout = io.StringIO() - assert run_cli(args, stdout=warmup_stdout) == 0 - assert "dependency-metadata-prefilter" in warmup_stdout.getvalue() - - timed_stdout = io.StringIO() - started_at = time.perf_counter() - exit_code = run_cli(args, stdout=timed_stdout) - elapsed_ms = (time.perf_counter() - started_at) * 1000 - - assert exit_code == 0 - assert "dependency-metadata-prefilter" in timed_stdout.getvalue() - assert elapsed_ms < DEPENDENCY_WARM_PATH_GATE_MS diff --git a/tests/unit/harness/test_semantic_cli_policy.py b/tests/unit/harness/test_semantic_cli_policy.py deleted file mode 100644 index 72d74b8..0000000 --- a/tests/unit/harness/test_semantic_cli_policy.py +++ /dev/null @@ -1,122 +0,0 @@ -from __future__ import annotations - -import io -import json -from pathlib import Path - -from semantic_search_fixture import compact_graph_renderer_available - -from python_lang_project_harness._cli import run_cli - - -def test_cli_agent_doctor_json_advertises_policy_search( - tmp_path: Path, -) -> None: - stdout = io.StringIO() - exit_code = run_cli(["agent", "doctor", "--json", str(tmp_path)], stdout=stdout) - payload = json.loads(stdout.getvalue()) - assert exit_code == 0 - registration = payload["registry"]["languages"][0] - assert "search/policy" in registration["methods"] - assert any( - schema["schemaId"] == "agent.semantic-protocols.semantic-handle" - and schema["path"] == "schemas/semantic-handle.v1.schema.json" - for schema in registration["schemas"] - ) - assert any( - descriptor["method"] == "search/policy" - and descriptor["acceptedPipes"] == ["owner", "tests"] - and descriptor["outputSchemaIds"] - == [ - "agent.semantic-protocols.semantic-search-packet", - "agent.semantic-protocols.semantic-handle", - ] - and any( - capability["name"] == "python-project-policy-rule-handle-search" - for capability in descriptor["capabilities"] - ) - for descriptor in registration["methodDescriptors"] - ) - - -def test_cli_search_policy_renders_semantic_handles( - tmp_path: Path, -) -> None: - seeds_stdout = io.StringIO() - compact_stdout = io.StringIO() - json_stdout = io.StringIO() - if compact_graph_renderer_available(): - assert ( - run_cli( - [ - "search", - "policy", - "PY-AGENT-PROJECT-001", - "owner", - "tests", - "--view", - "seeds", - "--workspace", - str(tmp_path), - ], - stdout=seeds_stdout, - ) - == 0 - ) - assert ( - run_cli( - [ - "search", - "policy", - "src layout", - "owner", - "tests", - "--workspace", - str(tmp_path), - ], - stdout=compact_stdout, - ) - == 0 - ) - assert ( - run_cli( - [ - "search", - "policy", - "PY-AGENT-POLICY-008", - "owner", - "tests", - "--json", - "--workspace", - str(tmp_path), - ], - stdout=json_stdout, - ) - == 0 - ) - seeds = seeds_stdout.getvalue() - compact = compact_stdout.getvalue() - packet = json.loads(json_stdout.getvalue()) - if compact_graph_renderer_available(): - assert seeds.startswith("[search-policy] q=PY-AGENT-PROJECT-001") - assert "alg=policy-handle-catalog" in seeds - assert ( - "O=owner:path(src/python_lang_project_harness/_project_policy_catalog.py)!owner" - in seeds - ) - assert "tests/unit/harness/project_policy/test_layout.py" in seeds - assert ( - "|handle PY-AGENT-PROJECT-001 kind=policy-rule source=provider-policy" - in compact - ) - assert 'title="Python project should use src layout"' in compact - assert "implementation=None" not in compact - assert packet["view"] == "policy" - assert packet["semanticHandles"][0]["id"] == "PY-AGENT-POLICY-008" - assert packet["semanticHandles"][0]["ownerPath"] == ( - "src/python_lang_project_harness/_agent_policy_catalog.py" - ) - assert ( - "tests/unit/harness/test_agent_policy.py" - in packet["semanticHandles"][0]["testPaths"] - ) diff --git a/tests/unit/harness/test_semantic_cli_public_external_types.py b/tests/unit/harness/test_semantic_cli_public_external_types.py deleted file mode 100644 index b41c1ed..0000000 --- a/tests/unit/harness/test_semantic_cli_public_external_types.py +++ /dev/null @@ -1,82 +0,0 @@ -"""Public external type surface CLI tests.""" - -from __future__ import annotations - -import io -import json -from pathlib import Path - -from semantic_search_fixture import write_search_fixture - -from python_lang_project_harness import run_cli - - -def test_cli_search_public_external_types_uses_public_api_facts( - tmp_path: Path, -) -> None: - write_search_fixture(tmp_path) - stdout = io.StringIO() - json_stdout = io.StringIO() - - exit_code = run_cli( - ["search", "public-external-types", "requests", "--workspace", str(tmp_path)], - stdout=stdout, - ) - json_exit_code = run_cli( - [ - "search", - "public-external-types", - "requests", - "--json", - "--workspace", - str(tmp_path), - ], - stdout=json_stdout, - ) - - rendered = stdout.getvalue() - assert exit_code == 0 - assert rendered.startswith("[search-public-external-types] q=requests") - assert "package=requests" in rendered - assert "|api path=src/pkg/service.py line=6" in rendered - assert "reason=public-external-type" in rendered - assert "confidence=direct" in rendered - assert "|api path=src/pkg/service.py line=9" in rendered - assert "reason=possible-public-external-type" in rendered - assert "confidence=possible" in rendered - - packet = json.loads(json_stdout.getvalue()) - assert json_exit_code == 0 - assert packet["method"] == "search/public-external-types" - assert packet["view"] == "public-external-types" - assert packet["header"]["fields"]["package"] == "requests" - type_surfaces = packet["typeSurfaces"] - assert len(type_surfaces) == len(packet["hits"]) - assert any( - surface["kind"] == "class" - and surface["role"] == "external-dependency" - and surface["package"] == "requests" - and surface["carrier"]["name"] == "class SessionClient(requests.Session):" - and surface["carrier"]["carrier"] == "class" - and surface["carrier"]["external"] is True - and surface["fields"]["confidence"] == "direct" - for surface in type_surfaces - ) - assert any( - surface["kind"] == "function" - and surface["carrier"]["name"] == "def fetch() -> Response:" - and surface["fields"]["confidence"] == "possible" - for surface in type_surfaces - ) - assert any( - hit["reason"] == "public-external-type" - and hit["fields"]["dependency"] == "requests" - and hit["fields"]["confidence"] == "direct" - for hit in packet["hits"] - ) - assert any( - hit["reason"] == "possible-public-external-type" - and hit["fields"]["dependency"] == "requests" - and hit["fields"]["confidence"] == "possible" - for hit in packet["hits"] - ) diff --git a/tests/unit/harness/test_semantic_cli_query_set.py b/tests/unit/harness/test_semantic_cli_query_set.py deleted file mode 100644 index cbd8be1..0000000 --- a/tests/unit/harness/test_semantic_cli_query_set.py +++ /dev/null @@ -1,112 +0,0 @@ -"""Semantic CLI query-set protocol tests.""" - -from __future__ import annotations - -import io -import json -import shutil -from pathlib import Path - -from semantic_search_fixture import write_search_fixture - -from python_lang_project_harness import run_cli - - -def test_cli_search_text_prefilter_large_project_records_runtime_cost( - tmp_path: Path, -) -> None: - if shutil.which("rg") is None: - return - write_search_fixture(tmp_path) - generated = tmp_path / "src" / "pkg" / "generated" - generated.mkdir() - for index in range(140): - (generated / f"candidate_{index}.py").write_text( - f'def large_need_{index}() -> str:\n return "LargeNeedle-{index}"\n', - encoding="utf-8", - ) - - stdout = io.StringIO() - exit_code = run_cli( - [ - "search", - "lexical", - "--query", - "LargeNeedle", - "--query", - "large_need", - "--query", - "generated", - "--owner", - "src/pkg/service.py", - "--json", - "--workspace", - str(tmp_path), - ], - stdout=stdout, - ) - - assert exit_code == 0 - packet = json.loads(stdout.getvalue()) - runtime_cost = packet["runtimeCost"] - fields = runtime_cost["fields"] - assert runtime_cost["sourceFilesParsed"] == fields["matchedFiles"] - assert fields["candidateFiles"] > 128 - assert fields["minCandidateFiles"] == 128 - assert fields["mode"] == "text-query-prefilter" - assert fields["queryTerms"] == 3 - assert fields["sourceSearchPasses"] == 1 - assert fields["fileListPasses"] == 1 - assert fields["candidateFileBasis"] == "all-python-files" - assert fields["matchedFiles"] <= 17 - assert any(note["kind"] == "runtime-prefilter" for note in packet["notes"]) - - -def test_cli_search_text_prefilter_skips_file_list_for_source_rich_terms( - tmp_path: Path, -) -> None: - if shutil.which("rg") is None: - return - write_search_fixture(tmp_path) - generated = tmp_path / "src" / "pkg" / "generated" - generated.mkdir() - for index in range(140): - (generated / f"source_rich_{index}.py").write_text( - "\n".join( - ( - f"def LargeNeedle_{index}() -> str:", - f' large_need = "generated-{index}"', - " return large_need", - "", - ) - ), - encoding="utf-8", - ) - - stdout = io.StringIO() - exit_code = run_cli( - [ - "search", - "lexical", - "--query", - "LargeNeedle", - "--query", - "large_need", - "--query", - "generated", - "--json", - "--workspace", - str(tmp_path), - ], - stdout=stdout, - ) - - assert exit_code == 0 - packet = json.loads(stdout.getvalue()) - fields = packet["runtimeCost"]["fields"] - assert fields["candidateFiles"] > 128 - assert fields["candidateFileBasis"] == "source-matched-files" - assert fields["sourceSearchPasses"] == 1 - assert fields["fileListPasses"] == 0 - assert fields["prefilterTool"] == "rg" - assert fields["matchedFiles"] <= 48 diff --git a/tests/unit/harness/test_semantic_cli_reasoning.py b/tests/unit/harness/test_semantic_cli_reasoning.py deleted file mode 100644 index 2e5726b..0000000 --- a/tests/unit/harness/test_semantic_cli_reasoning.py +++ /dev/null @@ -1,116 +0,0 @@ -"""Executable reasoning-entry tests for the Python semantic CLI.""" - -from __future__ import annotations - -import io -import json -from pathlib import Path - -from semantic_search_fixture import write_search_fixture - -from python_lang_project_harness import run_cli - - -def test_cli_agent_doctor_advertises_reasoning_search(tmp_path: Path) -> None: - stdout = io.StringIO() - - assert run_cli(["agent", "doctor", "--json", str(tmp_path)], stdout=stdout) == 0 - - registration = json.loads(stdout.getvalue())["registry"]["languages"][0] - assert "search/reasoning" in registration["methods"] - assert any( - descriptor["method"] == "search/reasoning" - and descriptor["supportsCompact"] is True - and descriptor["supportsJson"] is True - for descriptor in registration["methodDescriptors"] - ) - - -def test_cli_agent_guide_prints_reasoning_entry_commands(tmp_path: Path) -> None: - stdout = io.StringIO() - - assert run_cli(["agent", "guide", str(tmp_path)], stdout=stdout) == 0 - - rendered = stdout.getvalue() - assert ( - "|cmd reasoning-owner-tests=asp python search reasoning owner-tests " - "--owner --workspace --view seeds" - ) in rendered - assert ( - "|cmd reasoning-owner-query=asp python search reasoning owner-query " - "--owner --query --workspace --view seeds" - ) in rendered - assert ( - "|cmd reasoning-query-deps=asp python search reasoning query-deps " - "--query --dependency --workspace --view seeds" - ) in rendered - - -def test_cli_search_reasoning_profiles_are_executable(tmp_path: Path) -> None: - write_search_fixture(tmp_path) - owner_tests_stdout = io.StringIO() - owner_query_stdout = io.StringIO() - query_deps_stdout = io.StringIO() - - assert ( - run_cli( - [ - "search", - "reasoning", - "owner-tests", - "--owner", - "src/pkg/service.py", - "--workspace", - str(tmp_path), - ], - stdout=owner_tests_stdout, - ) - == 0 - ) - assert ( - run_cli( - [ - "search", - "reasoning", - "owner-query", - "--owner", - "src/pkg/service.py", - "--query", - "build", - "--workspace", - str(tmp_path), - ], - stdout=owner_query_stdout, - ) - == 0 - ) - assert ( - run_cli( - [ - "search", - "reasoning", - "query-deps", - "--query", - "Session", - "--dependency", - "requests", - "--workspace", - str(tmp_path), - ], - stdout=query_deps_stdout, - ) - == 0 - ) - - owner_tests = owner_tests_stdout.getvalue() - owner_query = owner_query_stdout.getvalue() - query_deps = query_deps_stdout.getvalue() - assert owner_tests.startswith("[search-reasoning] profile=owner-tests") - assert "returns=covering-tests,test-entrypoints,fixtures" in owner_tests - assert "|hit path=tests/test_service.py" in owner_tests - assert owner_query.startswith("[search-reasoning] profile=owner-query") - assert "returns=items,tests,dependency-usage" in owner_query - assert "|item build" in owner_query - assert query_deps.startswith("[search-reasoning] profile=query-deps") - assert "returns=owners,imports,usage-tests" in query_deps - assert "dependency=requests" in query_deps diff --git a/tests/unit/harness/test_semantic_cli_workspace_search.py b/tests/unit/harness/test_semantic_cli_workspace_search.py deleted file mode 100644 index 5ae9022..0000000 --- a/tests/unit/harness/test_semantic_cli_workspace_search.py +++ /dev/null @@ -1,153 +0,0 @@ -"""Semantic CLI workspace search protocol tests.""" - -from __future__ import annotations - -import io -import json -from pathlib import Path - -from semantic_search_fixture import write_search_fixture - -from python_lang_project_harness import run_cli - - -def test_cli_search_workspace_prime_and_text_pipe(tmp_path: Path) -> None: - write_search_fixture(tmp_path) - - workspace_stdout = io.StringIO() - prime_stdout = io.StringIO() - prime_json_stdout = io.StringIO() - owner_stdout = io.StringIO() - owner_json_stdout = io.StringIO() - text_stdout = io.StringIO() - - assert ( - run_cli( - ["search", "workspace", "--workspace", str(tmp_path)], - stdout=workspace_stdout, - ) - == 0 - ) - assert ( - run_cli(["search", "prime", "--workspace", str(tmp_path)], stdout=prime_stdout) - == 0 - ) - assert ( - run_cli( - ["search", "prime", "--json", "--workspace", str(tmp_path)], - stdout=prime_json_stdout, - ) - == 0 - ) - assert ( - run_cli( - ["search", "owner", "src/pkg/service.py", "--workspace", str(tmp_path)], - stdout=owner_stdout, - ) - == 0 - ) - assert ( - run_cli( - [ - "search", - "owner", - "src/pkg/service.py", - "--json", - "--workspace", - str(tmp_path), - ], - stdout=owner_json_stdout, - ) - == 0 - ) - assert ( - run_cli( - [ - "search", - "lexical", - "build", - "owner", - "tests", - "--workspace", - str(tmp_path), - ], - stdout=text_stdout, - ) - == 0 - ) - - workspace = workspace_stdout.getvalue() - prime = prime_stdout.getvalue() - owner = owner_stdout.getvalue() - text = text_stdout.getvalue() - assert workspace.startswith("[search-workspace]") - assert "|package . name=demo-python role=workspace-root" in workspace - assert ( - "|package src/pkg name=pkg role=workspace-package surface=source" in workspace - ) - assert prime.startswith("[search-prime]") - assert '|dependency D:requests requirement="requests>=2"' in prime - assert "|owner src/pkg/service.py" in prime - assert "|synthesis algorithm=owner-rank-frontier scope=prime" in prime - assert "highImpactOwners=" in prime - assert "src/pkg/service.py" in prime - assert "text:build(owner=" in prime - assert prime.count("deps:requests") == 1 - assert "symbol:build" not in prime - prime_packet = json.loads(prime_json_stdout.getvalue()) - assert prime_packet["searchSynthesis"]["algorithm"] == "owner-rank-frontier" - assert prime_packet["searchSynthesis"]["scope"] == "prime" - assert "src/pkg/service.py" in prime_packet["searchSynthesis"]["highImpactOwners"] - assert owner.startswith("[search-owner] q=src/pkg/service.py") - assert "|synthesis algorithm=bounded-reachability-depth1 scope=owner" in owner - assert "ownerPath=src/pkg/service.py" in owner - owner_packet = json.loads(owner_json_stdout.getvalue()) - assert owner_packet["searchSynthesis"]["algorithm"] == "bounded-reachability-depth1" - assert owner_packet["searchSynthesis"]["scope"] == "owner" - assert owner_packet["searchSynthesis"]["ownerPath"] == "src/pkg/service.py" - assert owner_packet["searchSynthesis"]["incomingOwners"] >= 1 - assert text.startswith("[search-lexical] q=build") - assert "|owner src/pkg/service.py" in text - assert "|edge O:src/pkg/service.py -test-> O:tests/test_service.py" in text - - -def test_cli_search_workspace_seeds_uses_metadata_route(tmp_path: Path) -> None: - write_search_fixture(tmp_path) - - stdout = io.StringIO() - assert ( - run_cli( - ["search", "workspace", "--view", "seeds", "--workspace", str(tmp_path)], - stdout=stdout, - ) - == 0 - ) - - workspace = stdout.getvalue() - assert workspace.startswith("[search-workspace]") - assert ( - "|note kind=runtime-prefilter message=workspace-seed-metadata-route" - in workspace - ) - - -def test_cli_search_deps_exposes_manifest_topology_only(tmp_path: Path) -> None: - write_search_fixture(tmp_path) - - deps_stdout = io.StringIO() - assert ( - run_cli( - ["search", "deps", "requests", "--workspace", str(tmp_path)], - stdout=deps_stdout, - ) - == 0 - ) - - deps = deps_stdout.getvalue() - assert deps.startswith("[search-deps] q=requests") - assert "manifest=1" in deps - assert "usage=0" in deps - assert "topology=asp-owned" in deps - assert '|dependency D:requests requirement="requests>=2"' in deps - assert "|owner src/pkg/service.py" not in deps - assert "|hit path=src/pkg/service.py" not in deps diff --git a/tests/unit/harness/test_semantic_graph_facts.py b/tests/unit/harness/test_semantic_graph_facts.py deleted file mode 100644 index 6581eee..0000000 --- a/tests/unit/harness/test_semantic_graph_facts.py +++ /dev/null @@ -1,204 +0,0 @@ -"""Validate provider-owned Python graph facts.""" - -from __future__ import annotations - -import io -import json - -from python_lang_project_harness import run_cli - - -def test_search_semantic_facts_emits_field_type_collection_graph(tmp_path): - source = tmp_path / "models.py" - source.write_text( - "from dataclasses import dataclass\n\n" - "@dataclass\n" - "class Bag:\n" - " items: list[str]\n" - " counts: dict[str, int]\n\n" - "class Runtime:\n" - " def __init__(self):\n" - " self.values: list[int] = []\n", - encoding="utf-8", - ) - stdout = io.StringIO() - - exit_code = run_cli( - [ - "search", - "semantic-facts", - "list collection fields", - "--json", - "--workspace", - str(tmp_path), - ], - stdout=stdout, - cwd=tmp_path, - stdin="models.py:5:1:items\n", - ) - - assert exit_code == 0 - payload = json.loads(stdout.getvalue()) - assert payload["schemaId"] == "agent.semantic-protocols.semantic-fact-graph" - assert payload["languageId"] == "python" - assert payload["providerId"] == "asp-python" - assert payload["query"] == "list collection fields" - nodes = payload["nodes"] - edges = payload["edges"] - assert any( - node["kind"] == "field" - and node["symbol"] == "items" - and node["fields"]["typeValue"] == "list[str]" - and node["fields"]["collectionKind"] == "list" - and node["fields"]["languageId"] == "python" - and node["fields"]["providerId"] == "asp-python" - and node["fields"]["semanticFactKind"] == "field" - and node["fields"]["provenance"] == "parser" - and node["fields"]["confidence"] == "exact" - and node["fields"]["freshness"] == "fresh" - and node["fields"]["collectionFamily"] == "sequence" - and node["fields"]["collectionImpl"] == "list" - and node["fields"]["field"]["ownerKind"] == "class" - and node["fields"]["field"]["name"] == "items" - and node["fields"]["field"]["ownerPath"] == "models.py" - and "append" in node["fields"]["field"]["access"] - and node["fields"]["contextLocator"] == "models.py:4:6" - for node in nodes - ) - assert any( - node["kind"] == "type" - and node["value"] == "list[str]" - and node["fields"]["semanticFactKind"] == "type" - and node["fields"]["type"]["name"] == "list[str]" - and node["fields"]["type"]["element"] == "str" - for node in nodes - ) - assert any( - node["kind"] == "collection" - and node["symbol"] == "list" - and node["fields"]["semanticFactKind"] == "collection" - and node["fields"]["collection"]["family"] == "sequence" - and node["fields"]["collection"]["impl"] == "list" - and node["fields"]["collection"]["elementType"] == "str" - for node in nodes - ) - assert any( - node["kind"] == "field" - and node["symbol"] == "counts" - and node["fields"]["collectionFamily"] == "map" - and node["fields"]["field"]["access"] == ["read", "write", "validate"] - for node in nodes - ) - assert any( - node["kind"] == "type" - and node["value"] == "dict[str, int]" - and node["fields"]["type"]["key"] == "str" - and node["fields"]["type"]["value"] == "int" - for node in nodes - ) - assert any( - node["kind"] == "collection" - and node["symbol"] == "dict" - and node["fields"]["collection"]["family"] == "map" - and node["fields"]["collection"]["keyType"] == "str" - and node["fields"]["collection"]["valueType"] == "int" - for node in nodes - ) - assert any(edge["relation"] == "has_type" for edge in edges) - assert any(edge["relation"] == "collection_of" for edge in edges) - - -def test_search_semantic_facts_emits_package_build_dependency_and_tests(tmp_path): - (tmp_path / "pyproject.toml").write_text( - "[project]\n" - 'name = "fact-pkg"\n' - 'version = "0.1.0"\n' - 'dependencies = ["requests>=2", "attrs"]\n' - "\n" - "[project.optional-dependencies]\n" - 'dev = ["pytest>=8"]\n', - encoding="utf-8", - ) - (tmp_path / "model.py").write_text( - "class Cache:\n entries: list[str]\n", - encoding="utf-8", - ) - tests_dir = tmp_path / "tests" - tests_dir.mkdir() - (tests_dir / "test_api.py").write_text( - "def test_one():\n assert True\n\ndef helper():\n pass\n", - encoding="utf-8", - ) - stdout = io.StringIO() - - exit_code = run_cli( - [ - "search", - "semantic-facts", - "field pytest requests dependency", - "--json", - "--workspace", - str(tmp_path), - ], - stdout=stdout, - cwd=tmp_path, - stdin="model.py:2:1:entries\n", - ) - - assert exit_code == 0 - payload = json.loads(stdout.getvalue()) - nodes = payload["nodes"] - edges = payload["edges"] - assert any( - node["kind"] == "package" - and node["value"] == "fact-pkg" - and node["action"] == "package" - and node["fields"]["semanticFactKind"] == "package" - and node["fields"]["manifestPath"] == "pyproject.toml" - for node in nodes - ) - assert any( - node["kind"] == "build" - and node["action"] == "build" - and node["fields"]["semanticFactKind"] == "build" - and node["fields"]["command"] == "uv run --project . pytest" - for node in nodes - ) - assert any( - node["kind"] == "dependency" - and node["value"] == "requests" - and node["action"] == "deps" - and node["fields"]["semanticFactKind"] == "dependency" - and node["fields"]["dependencyKind"] == "normal" - and node["fields"]["versionReq"] == ">=2" - for node in nodes - ) - assert any( - node["kind"] == "dependency" - and node["value"] == "pytest" - and node["fields"]["dependencyKind"] == "dev" - and node["fields"]["extra"] == "dev" - for node in nodes - ) - assert any( - node["kind"] == "test" - and node["path"] == "tests/test_api.py" - and node["action"] == "tests" - and node["fields"]["semanticFactKind"] == "test" - and node["fields"]["functionCount"] == 1 - for node in nodes - ) - for relation in ["builds", "depends_on", "tests", "belongs_to"]: - assert any(edge["relation"] == relation for edge in edges), relation - field_node = next( - node - for node in nodes - if node["kind"] == "field" and node["symbol"] == "entries" - ) - package_node = next(node for node in nodes if node["kind"] == "package") - assert any( - edge["source"] == field_node["id"] - and edge["target"] == package_node["id"] - and edge["relation"] == "belongs_to" - for edge in edges - ) diff --git a/tests/unit/harness/test_semantic_language_schemas.py b/tests/unit/harness/test_semantic_language_schemas.py deleted file mode 100644 index 47435ff..0000000 --- a/tests/unit/harness/test_semantic_language_schemas.py +++ /dev/null @@ -1,21 +0,0 @@ -"""Focused schema registration tests for the Python provider.""" - -from __future__ import annotations - -from python_lang_project_harness import python_semantic_language_registration - - -def test_python_registration_advertises_semantic_fact_graph_schemas() -> None: - registration = python_semantic_language_registration() - schema_entries = { - (schema["schemaId"], schema["path"]) for schema in registration["schemas"] - } - - assert ( - "agent.semantic-protocols.semantic-fact-graph", - "schemas/semantic-fact-graph.v1.schema.json", - ) in schema_entries - assert ( - "agent.semantic-protocols.semantic-fact-ontology", - "schemas/semantic-fact-ontology.v1.schema.json", - ) in schema_entries diff --git a/tests/unit/harness/test_semantic_render_flow.py b/tests/unit/harness/test_semantic_render_flow.py deleted file mode 100644 index 717f419..0000000 --- a/tests/unit/harness/test_semantic_render_flow.py +++ /dev/null @@ -1,29 +0,0 @@ -"""Semantic search flow renderer contract tests.""" - -from __future__ import annotations - -from python_lang_project_harness._semantic_search_render_flow import finding_lines - - -def test_semantic_search_findings_render_path_first() -> None: - lines = finding_lines( - { - "findings": [ - { - "ruleId": "PY-AGENT-POLICY-001", - "count": 1, - "location": { - "path": "src/pkg/service.py", - "line": 3, - "column": 1, - }, - "severity": "info", - } - ] - } - ) - - assert lines == [ - "|find PY-AGENT-POLICY-001 x1 path=src/pkg/service.py line=3 column=1 node=O:src/pkg/service.py severity=info" - ] - assert "at=O:" not in lines[0] diff --git a/tests/unit/harness/test_semantic_schema_registry.py b/tests/unit/harness/test_semantic_schema_registry.py deleted file mode 100644 index 371d521..0000000 --- a/tests/unit/harness/test_semantic_schema_registry.py +++ /dev/null @@ -1,25 +0,0 @@ -"""Schema registry contract tests for the Python semantic provider.""" - -from __future__ import annotations - -import json -from pathlib import Path - -import pytest - - -def test_package_local_semantic_schemas_stay_synchronized() -> None: - package_root = Path(__file__).resolve().parents[3] - protocol_root = package_root.parents[1] - protocol_schema_dir = protocol_root / "schemas" - if not protocol_schema_dir.exists(): - pytest.skip("protocol repository schemas are not available") - - for package_schema_path in sorted((package_root / "schemas").glob("*.schema.json")): - protocol_schema_path = protocol_schema_dir / package_schema_path.name - if not protocol_schema_path.exists(): - continue - package_schema = json.loads(package_schema_path.read_text(encoding="utf-8")) - protocol_schema = json.loads(protocol_schema_path.read_text(encoding="utf-8")) - - assert package_schema == protocol_schema diff --git a/tests/unit/harness/test_semantic_search_graph_profiles.py b/tests/unit/harness/test_semantic_search_graph_profiles.py deleted file mode 100644 index bf0dd41..0000000 --- a/tests/unit/harness/test_semantic_search_graph_profiles.py +++ /dev/null @@ -1,98 +0,0 @@ -from __future__ import annotations - -import os -from pathlib import Path -from typing import Any - -import pytest - -from python_lang_project_harness._semantic_search_graph_render import ( - compact_graph_seed_packet_text, -) - - -def _render_fields(fields: dict[str, Any]) -> str: - return " ".join(f"{key}={value}" for key, value in fields.items()) - - -def test_compact_graph_profiles_filter_to_rendered_aliases() -> None: - workspace_renderer = ( - Path(os.environ["SEMANTIC_AGENT_PROTOCOL_BIN"]) - if os.environ.get("SEMANTIC_AGENT_PROTOCOL_BIN") - else Path(__file__).resolve().parents[5] / ".bin" / "asp" - ) - if not workspace_renderer.exists(): - pytest.skip("workspace graph renderer is not built") - os.environ["SEMANTIC_AGENT_PROTOCOL_BIN"] = str(workspace_renderer) - - packet: dict[str, Any] = { - "schemaId": "agent.semantic-protocols.semantic-search-packet", - "schemaVersion": "1", - "protocolId": "agent.semantic-protocols.search", - "protocolVersion": "1", - "languageId": "python", - "providerId": "asp-python", - "binary": "asp-python", - "namespace": "agent.semantic-protocols.languages.python.asp-python", - "method": "search/owner", - "projectRoot": ".", - "view": "seeds", - "renderMode": "compact", - "header": {"kind": "search-owner", "fields": {}}, - "nodes": [], - "edges": [], - "nextActions": [ - {"kind": "owner", "target": "src/pkg/service.py"}, - {"kind": "tests", "target": "tests/test_service.py"}, - ], - "owners": [], - "hits": [], - "findings": [], - "notes": [], - "searchSynthesis": {"algorithm": "seed-frontier"}, - "reasoningProfiles": [ - { - "profile": "owner-query", - "selectors": [ - {"kind": "owner", "alias": "O", "required": True}, - {"kind": "query", "alias": "Q", "required": True}, - ], - "returns": ["items", "tests", "dependency-usage"], - }, - { - "profile": "query-deps", - "selectors": [ - {"kind": "query", "alias": "Q", "required": True}, - {"kind": "dependency", "alias": "D", "required": True}, - ], - "returns": ["owners", "imports", "usage-tests"], - }, - { - "profile": "owner-tests", - "selectors": [ - {"kind": "owner", "alias": "O", "required": True}, - ], - "returns": [ - "covering-tests", - "test-entrypoints", - "fixtures", - ], - }, - { - "profile": "finding-frontier", - "selectors": [ - {"kind": "finding", "alias": "F", "required": True}, - {"kind": "owner", "alias": "O", "required": False}, - ], - "returns": ["affected-owners", "tests", "verification-actions"], - }, - ], - } - - output = compact_graph_seed_packet_text(packet, _render_fields) - - assert "aliases: graph:{G=search,O=owner,T=test}" in output - assert "entries=owner-tests(O=>covering-tests+test-entrypoints+fixtures)" in output - assert "owner-query(" not in output - assert "query-deps(" not in output - assert "finding-frontier(" not in output diff --git a/tests/unit/harness/test_semantic_search_graph_render_shell_out.py b/tests/unit/harness/test_semantic_search_graph_render_shell_out.py deleted file mode 100644 index fde2015..0000000 --- a/tests/unit/harness/test_semantic_search_graph_render_shell_out.py +++ /dev/null @@ -1,109 +0,0 @@ -from __future__ import annotations - -import json -from pathlib import Path - -import pytest - -from python_lang_project_harness._semantic_search_graph_render import ( - SEMANTIC_AGENT_PROTOCOL_BIN_ENV, - CompactGraphRenderError, - compact_graph_seed_packet_text, - render_compact_graph_packet, -) - - -def test_compact_graph_renderer_shells_out_to_protocol_bin( - tmp_path: Path, - monkeypatch: pytest.MonkeyPatch, -) -> None: - argv_path = tmp_path / "argv.txt" - stdin_path = tmp_path / "stdin.json" - protocol_bin = tmp_path / "semantic-agent-protocol" - protocol_bin.write_text( - "#!/bin/sh\n" - 'printf "%s\\n" "$@" > "$ASP_ARGV_PATH"\n' - 'cat > "$ASP_STDIN_PATH"\n' - 'printf "[search-lexical] q=test\\n"\n' - ) - protocol_bin.chmod(0o755) - monkeypatch.setenv(SEMANTIC_AGENT_PROTOCOL_BIN_ENV, str(protocol_bin)) - monkeypatch.setenv("ASP_ARGV_PATH", str(argv_path)) - monkeypatch.setenv("ASP_STDIN_PATH", str(stdin_path)) - - packet = {"header": {"kind": "search-lexical"}} - - output = render_compact_graph_packet(packet, seed_limit=3) - - assert output == "[search-lexical] q=test\n" - assert argv_path.read_text().splitlines() == [ - "graph", - "render", - "--packet", - "-", - "--view", - "seeds", - "--seeds", - "3", - ] - assert json.loads(stdin_path.read_text()) == packet - - -def test_compact_graph_renderer_missing_bin_does_not_fallback( - tmp_path: Path, - monkeypatch: pytest.MonkeyPatch, -) -> None: - monkeypatch.setenv( - SEMANTIC_AGENT_PROTOCOL_BIN_ENV, - str(tmp_path / "missing-semantic-agent-protocol"), - ) - - with pytest.raises(CompactGraphRenderError, match="not found"): - render_compact_graph_packet({"header": {"kind": "search-lexical"}}) - - -def test_compact_graph_seed_packet_appends_non_graph_flow_lines( - tmp_path: Path, - monkeypatch: pytest.MonkeyPatch, -) -> None: - stdin_path = tmp_path / "stdin.json" - protocol_bin = tmp_path / "semantic-agent-protocol" - protocol_bin.write_text( - "#!/bin/sh\n" - 'cat > "$ASP_STDIN_PATH"\n' - 'printf "[search-ingest] root=. alg=seed-frontier\\n"\n' - 'printf "G>{}\\n"\n' - 'printf "rank= frontier=\\n"\n', - encoding="utf-8", - ) - protocol_bin.chmod(0o755) - monkeypatch.setenv(SEMANTIC_AGENT_PROTOCOL_BIN_ENV, str(protocol_bin)) - monkeypatch.setenv("ASP_STDIN_PATH", str(stdin_path)) - - packet = { - "header": {"kind": "search-ingest"}, - "notes": [ - { - "kind": "stdin-required", - "message": "search ingest consumes stdin candidate paths", - } - ], - "nextActions": [ - {"kind": "owner", "target": "src/service.py"}, - { - "kind": "prime", - "target": "search prime --view seeds", - "scope": "project-discovery", - }, - ], - } - - output = compact_graph_seed_packet_text(packet, lambda _fields: "") - - rendered_packet = json.loads(stdin_path.read_text()) - assert rendered_packet["nextActions"] == [ - {"kind": "owner", "target": "src/service.py"} - ] - assert "|note kind=stdin-required" in output - assert "|next prime:" in output - assert "owner:path(search prime" not in output diff --git a/tests/unit/harness/test_semantic_search_ingest_cli.py b/tests/unit/harness/test_semantic_search_ingest_cli.py deleted file mode 100644 index bbbe7ce..0000000 --- a/tests/unit/harness/test_semantic_search_ingest_cli.py +++ /dev/null @@ -1,93 +0,0 @@ -"""Semantic search ingest CLI parsing tests.""" - -from __future__ import annotations - -import time -from io import StringIO -from pathlib import Path - -import pytest - -from python_lang_project_harness import python_semantic_language_registration, run_cli -from python_lang_project_harness._semantic_search_cli import parse_semantic_search_args - -FAST_INGEST_BUDGET_SECONDS = 0.25 - - -def test_search_ingest_descriptor_accepts_items_tests_pipes() -> None: - descriptors = python_semantic_language_registration()["methodDescriptors"] - - assert any( - descriptor["method"] == "search/ingest" - and descriptor["acceptedPipes"] == ["items", "tests"] - and descriptor["acceptsStdin"] is True - for descriptor in descriptors - ) - - -def test_search_ingest_accepts_pipes_with_explicit_workspace() -> None: - parsed = parse_semantic_search_args( - ["ingest", "items", "tests", "--view", "seeds", "--workspace", "."] - ) - - assert parsed.error is None - assert parsed.view == "ingest" - assert parsed.pipes == ("items", "tests") - assert parsed.project_root == Path(".") - assert parsed.render_mode == "seeds" - - -def test_search_ingest_rejects_positional_workspace_after_pipes() -> None: - parsed = parse_semantic_search_args( - ["ingest", "items", "tests", "extra", "--view", "seeds", "."] - ) - - assert ( - parsed.error - == "search does not accept positional WORKSPACE; use --workspace " - ) - - -def test_search_ingest_empty_stdin_seeds_explains_prime_route( - tmp_path: Path, - monkeypatch: pytest.MonkeyPatch, -) -> None: - (tmp_path / "pyproject.toml").write_text( - '[project]\nname = "sample"\nversion = "0.1.0"\n', - encoding="utf-8", - ) - from python_lang_project_harness import _cli_protocol - - def fail_full_harness(*_args: object, **_kwargs: object) -> object: - raise AssertionError("full harness should not run for empty ingest seeds") - - monkeypatch.setattr(_cli_protocol, "_run_search_harness", fail_full_harness) - stdout = StringIO() - - started_at = time.perf_counter() - exit_code = run_cli( - [ - "search", - "ingest", - "items", - "tests", - "--view", - "seeds", - "--workspace", - ".", - ], - stdout=stdout, - stdin="", - cwd=tmp_path, - ) - elapsed = time.perf_counter() - started_at - - output = stdout.getvalue() - assert exit_code == 0 - assert output.startswith("[search-ingest]") - assert "|note kind=stdin-required" in output - assert "search ingest consumes stdin candidate paths" in output - assert "search prime --view seeds" in output - assert "|next prime:" in output - assert "owner:path(search prime" not in output - assert elapsed < FAST_INGEST_BUDGET_SECONDS diff --git a/tests/unit/python_lang_parser/test_pyproject_metadata.py b/tests/unit/python_lang_parser/test_pyproject_metadata.py index 67612f8..fe43568 100644 --- a/tests/unit/python_lang_parser/test_pyproject_metadata.py +++ b/tests/unit/python_lang_parser/test_pyproject_metadata.py @@ -37,12 +37,12 @@ def test_parse_python_project_metadata_collects_modern_project_facts( { package = "mkdocs>=1.6" }, ] test = [ - "python-lang-project-harness[pytest]>=0.1.0", + "asp-python[pytest]>=0.1.0", { dependency = "ruff>=0.13" }, ] [tool.pytest.ini_options] -addopts = ["--import-mode=importlib", "--python-project-harness"] +addopts = ["--import-mode=importlib", "--asp-python"] [build-system] requires = ["hatchling"] @@ -84,8 +84,8 @@ def test_parse_python_project_metadata_collects_modern_project_facts( "extra": None, }, { - "requirement": "python-lang-project-harness[pytest]>=0.1.0", - "name": "python-lang-project-harness", + "requirement": "asp-python[pytest]>=0.1.0", + "name": "asp-python", "source": "dependency-groups", "group": "test", "extra": None, @@ -100,9 +100,9 @@ def test_parse_python_project_metadata_collects_modern_project_facts( ] assert metadata.pytest_options.addopts == ( "--import-mode=importlib", - "--python-project-harness", + "--asp-python", ) - assert metadata.pytest_options.enables_python_project_harness is True + assert metadata.pytest_options.enables_asp_python is True assert metadata.wheel_packages == ("src/example_pkg",) assert metadata.package_roots == (package,) assert [item.to_dict() for item in metadata.import_names] == [ diff --git a/tests/unit/snapshots/python_project_harness_compact_text.snap b/tests/unit/snapshots/asp_python_compact_text.snap similarity index 100% rename from tests/unit/snapshots/python_project_harness_compact_text.snap rename to tests/unit/snapshots/asp_python_compact_text.snap diff --git a/tests/unit/snapshots/python_project_harness_json.snap b/tests/unit/snapshots/asp_python_json.snap similarity index 100% rename from tests/unit/snapshots/python_project_harness_json.snap rename to tests/unit/snapshots/asp_python_json.snap diff --git a/tests/unit/snapshots/unit_test__agent_policy_snapshot__py_agent_r001_module_intent.snap b/tests/unit/snapshots/unit_test__agent_policy_snapshot__py_agent_r001_module_intent.snap index 3f6175b..87e80ca 100644 --- a/tests/unit/snapshots/unit_test__agent_policy_snapshot__py_agent_r001_module_intent.snap +++ b/tests/unit/snapshots/unit_test__agent_policy_snapshot__py_agent_r001_module_intent.snap @@ -1,5 +1,5 @@ --- -source: tests/unit/harness/test_agent_policy_snapshots.py +source: tests/unit/asp_python/test_agent_policy_snapshots.py expression: rendered --- [advice] diff --git a/tests/unit/snapshots/unit_test__agent_policy_snapshot__py_agent_r002_callable_annotations.snap b/tests/unit/snapshots/unit_test__agent_policy_snapshot__py_agent_r002_callable_annotations.snap index f88c8b7..c920101 100644 --- a/tests/unit/snapshots/unit_test__agent_policy_snapshot__py_agent_r002_callable_annotations.snap +++ b/tests/unit/snapshots/unit_test__agent_policy_snapshot__py_agent_r002_callable_annotations.snap @@ -1,5 +1,5 @@ --- -source: tests/unit/harness/test_agent_policy_snapshots.py +source: tests/unit/asp_python/test_agent_policy_snapshots.py expression: rendered --- [advice] diff --git a/tests/unit/snapshots/unit_test__agent_policy_snapshot__py_agent_r003_callable_conflict.snap b/tests/unit/snapshots/unit_test__agent_policy_snapshot__py_agent_r003_callable_conflict.snap index 29564eb..753b544 100644 --- a/tests/unit/snapshots/unit_test__agent_policy_snapshot__py_agent_r003_callable_conflict.snap +++ b/tests/unit/snapshots/unit_test__agent_policy_snapshot__py_agent_r003_callable_conflict.snap @@ -1,5 +1,5 @@ --- -source: tests/unit/harness/test_agent_policy_snapshots.py +source: tests/unit/asp_python/test_agent_policy_snapshots.py expression: rendered --- [advice] diff --git a/tests/unit/snapshots/unit_test__agent_policy_snapshot__py_agent_r004_repeated_namespace.snap b/tests/unit/snapshots/unit_test__agent_policy_snapshot__py_agent_r004_repeated_namespace.snap index 4d5190f..f20555b 100644 --- a/tests/unit/snapshots/unit_test__agent_policy_snapshot__py_agent_r004_repeated_namespace.snap +++ b/tests/unit/snapshots/unit_test__agent_policy_snapshot__py_agent_r004_repeated_namespace.snap @@ -1,5 +1,5 @@ --- -source: tests/unit/harness/test_agent_policy_snapshots.py +source: tests/unit/asp_python/test_agent_policy_snapshots.py expression: rendered --- [advice] diff --git a/tests/unit/snapshots/unit_test__agent_policy_snapshot__py_agent_r005_type_conflict.snap b/tests/unit/snapshots/unit_test__agent_policy_snapshot__py_agent_r005_type_conflict.snap index f9c3b7c..532e64f 100644 --- a/tests/unit/snapshots/unit_test__agent_policy_snapshot__py_agent_r005_type_conflict.snap +++ b/tests/unit/snapshots/unit_test__agent_policy_snapshot__py_agent_r005_type_conflict.snap @@ -1,5 +1,5 @@ --- -source: tests/unit/harness/test_agent_policy_snapshots.py +source: tests/unit/asp_python/test_agent_policy_snapshots.py expression: rendered --- [advice] diff --git a/tests/unit/snapshots/unit_test__agent_policy_snapshot__py_agent_r006_value_conflict.snap b/tests/unit/snapshots/unit_test__agent_policy_snapshot__py_agent_r006_value_conflict.snap index fb63d12..a900386 100644 --- a/tests/unit/snapshots/unit_test__agent_policy_snapshot__py_agent_r006_value_conflict.snap +++ b/tests/unit/snapshots/unit_test__agent_policy_snapshot__py_agent_r006_value_conflict.snap @@ -1,5 +1,5 @@ --- -source: tests/unit/harness/test_agent_policy_snapshots.py +source: tests/unit/asp_python/test_agent_policy_snapshots.py expression: rendered --- [advice] diff --git a/tests/unit/snapshots/unit_test__agent_policy_snapshot__py_agent_r007_branch_intent.snap b/tests/unit/snapshots/unit_test__agent_policy_snapshot__py_agent_r007_branch_intent.snap index 012510d..0ed7873 100644 --- a/tests/unit/snapshots/unit_test__agent_policy_snapshot__py_agent_r007_branch_intent.snap +++ b/tests/unit/snapshots/unit_test__agent_policy_snapshot__py_agent_r007_branch_intent.snap @@ -1,5 +1,5 @@ --- -source: tests/unit/harness/test_agent_policy_snapshots.py +source: tests/unit/asp_python/test_agent_policy_snapshots.py expression: rendered --- [advice] diff --git a/tests/unit/snapshots/unit_test__agent_policy_snapshot__py_agent_r008_branch_surface.snap b/tests/unit/snapshots/unit_test__agent_policy_snapshot__py_agent_r008_branch_surface.snap index d6a68fd..3be53d0 100644 --- a/tests/unit/snapshots/unit_test__agent_policy_snapshot__py_agent_r008_branch_surface.snap +++ b/tests/unit/snapshots/unit_test__agent_policy_snapshot__py_agent_r008_branch_surface.snap @@ -1,5 +1,5 @@ --- -source: tests/unit/harness/test_agent_policy_snapshots.py +source: tests/unit/asp_python/test_agent_policy_snapshots.py expression: rendered --- [advice] diff --git a/tests/unit/snapshots/unit_test__agent_policy_snapshot__py_agent_r009_algorithm_shape.snap b/tests/unit/snapshots/unit_test__agent_policy_snapshot__py_agent_r009_algorithm_shape.snap index a517553..ccb26a1 100644 --- a/tests/unit/snapshots/unit_test__agent_policy_snapshot__py_agent_r009_algorithm_shape.snap +++ b/tests/unit/snapshots/unit_test__agent_policy_snapshot__py_agent_r009_algorithm_shape.snap @@ -1,5 +1,5 @@ --- -source: tests/unit/harness/test_agent_algorithm_policy.py +source: tests/unit/asp_python/test_agent_algorithm_policy.py expression: rendered --- [advice] diff --git a/tests/unit/snapshots/unit_test__agent_policy_snapshot__py_agent_r010_function_compactness.snap b/tests/unit/snapshots/unit_test__agent_policy_snapshot__py_agent_r010_function_compactness.snap index f4e9662..9c62b71 100644 --- a/tests/unit/snapshots/unit_test__agent_policy_snapshot__py_agent_r010_function_compactness.snap +++ b/tests/unit/snapshots/unit_test__agent_policy_snapshot__py_agent_r010_function_compactness.snap @@ -1,5 +1,5 @@ --- -source: tests/unit/harness/test_agent_algorithm_policy.py +source: tests/unit/asp_python/test_agent_algorithm_policy.py expression: rendered --- [advice] diff --git a/tests/unit/snapshots/unit_test__agent_policy_snapshot__py_agent_r011_native_idiom.snap b/tests/unit/snapshots/unit_test__agent_policy_snapshot__py_agent_r011_native_idiom.snap index 26c4445..1ce1963 100644 --- a/tests/unit/snapshots/unit_test__agent_policy_snapshot__py_agent_r011_native_idiom.snap +++ b/tests/unit/snapshots/unit_test__agent_policy_snapshot__py_agent_r011_native_idiom.snap @@ -1,5 +1,5 @@ --- -source: tests/unit/harness/test_agent_algorithm_policy.py +source: tests/unit/asp_python/test_agent_algorithm_policy.py expression: rendered --- [advice] diff --git a/tests/unit/snapshots/unit_test__agent_policy_snapshot__py_agent_r012_type_shape.snap b/tests/unit/snapshots/unit_test__agent_policy_snapshot__py_agent_r012_type_shape.snap index 104c43a..b050bc1 100644 --- a/tests/unit/snapshots/unit_test__agent_policy_snapshot__py_agent_r012_type_shape.snap +++ b/tests/unit/snapshots/unit_test__agent_policy_snapshot__py_agent_r012_type_shape.snap @@ -1,5 +1,5 @@ --- -source: tests/unit/harness/test_agent_algorithm_policy.py +source: tests/unit/asp_python/test_agent_algorithm_policy.py expression: rendered --- [advice] diff --git a/tests/unit/snapshots/unit_test__policy_snapshot__py_mod_r001_wildcard_import.snap b/tests/unit/snapshots/unit_test__policy_snapshot__py_mod_r001_wildcard_import.snap index a70b799..c8fd11e 100644 --- a/tests/unit/snapshots/unit_test__policy_snapshot__py_mod_r001_wildcard_import.snap +++ b/tests/unit/snapshots/unit_test__policy_snapshot__py_mod_r001_wildcard_import.snap @@ -1,5 +1,5 @@ --- -source: tests/unit/harness/test_policy_snapshots.py +source: tests/unit/asp_python/test_policy_snapshots.py expression: rendered --- [fail] python blockingFindings=1 parsed=1/1 diff --git a/tests/unit/snapshots/unit_test__policy_snapshot__py_mod_r002_bare_print.snap b/tests/unit/snapshots/unit_test__policy_snapshot__py_mod_r002_bare_print.snap index 1fc7f07..86bbefb 100644 --- a/tests/unit/snapshots/unit_test__policy_snapshot__py_mod_r002_bare_print.snap +++ b/tests/unit/snapshots/unit_test__policy_snapshot__py_mod_r002_bare_print.snap @@ -1,5 +1,5 @@ --- -source: tests/unit/harness/test_policy_snapshots.py +source: tests/unit/asp_python/test_policy_snapshots.py expression: rendered --- [fail] python blockingFindings=1 parsed=1/1 diff --git a/tests/unit/snapshots/unit_test__policy_snapshot__py_mod_r003_facade_all.snap b/tests/unit/snapshots/unit_test__policy_snapshot__py_mod_r003_facade_all.snap index 862d8d6..2a2aec4 100644 --- a/tests/unit/snapshots/unit_test__policy_snapshot__py_mod_r003_facade_all.snap +++ b/tests/unit/snapshots/unit_test__policy_snapshot__py_mod_r003_facade_all.snap @@ -1,5 +1,5 @@ --- -source: tests/unit/harness/test_policy_snapshots.py +source: tests/unit/asp_python/test_policy_snapshots.py expression: rendered --- [fail] python blockingFindings=1 parsed=2/2 diff --git a/tests/unit/snapshots/unit_test__policy_snapshot__py_mod_r004_breakpoint.snap b/tests/unit/snapshots/unit_test__policy_snapshot__py_mod_r004_breakpoint.snap index 5f794cc..7f526b8 100644 --- a/tests/unit/snapshots/unit_test__policy_snapshot__py_mod_r004_breakpoint.snap +++ b/tests/unit/snapshots/unit_test__policy_snapshot__py_mod_r004_breakpoint.snap @@ -1,5 +1,5 @@ --- -source: tests/unit/harness/test_policy_snapshots.py +source: tests/unit/asp_python/test_policy_snapshots.py expression: rendered --- [fail] python blockingFindings=1 parsed=1/1 diff --git a/tests/unit/snapshots/unit_test__policy_snapshot__py_mod_r006_module_bloat.snap b/tests/unit/snapshots/unit_test__policy_snapshot__py_mod_r006_module_bloat.snap index 593c797..5725be2 100644 --- a/tests/unit/snapshots/unit_test__policy_snapshot__py_mod_r006_module_bloat.snap +++ b/tests/unit/snapshots/unit_test__policy_snapshot__py_mod_r006_module_bloat.snap @@ -1,5 +1,5 @@ --- -source: tests/unit/harness/test_policy_snapshots.py +source: tests/unit/asp_python/test_policy_snapshots.py expression: rendered --- [fail] python blockingFindings=1 parsed=1/1 diff --git a/tests/unit/snapshots/unit_test__policy_snapshot__py_mod_r007_reasoning_tree_shadow.snap b/tests/unit/snapshots/unit_test__policy_snapshot__py_mod_r007_reasoning_tree_shadow.snap index 006934c..b6c7d1a 100644 --- a/tests/unit/snapshots/unit_test__policy_snapshot__py_mod_r007_reasoning_tree_shadow.snap +++ b/tests/unit/snapshots/unit_test__policy_snapshot__py_mod_r007_reasoning_tree_shadow.snap @@ -1,5 +1,5 @@ --- -source: tests/unit/harness/test_policy_snapshots.py +source: tests/unit/asp_python/test_policy_snapshots.py expression: rendered --- [fail] python blockingFindings=1 parsed=2/2 diff --git a/tests/unit/snapshots/unit_test__policy_snapshot__py_proj_r001_src_layout.snap b/tests/unit/snapshots/unit_test__policy_snapshot__py_proj_r001_src_layout.snap index ede3cca..5a63cf8 100644 --- a/tests/unit/snapshots/unit_test__policy_snapshot__py_proj_r001_src_layout.snap +++ b/tests/unit/snapshots/unit_test__policy_snapshot__py_proj_r001_src_layout.snap @@ -1,5 +1,5 @@ --- -source: tests/unit/harness/test_policy_snapshots.py +source: tests/unit/asp_python/test_policy_snapshots.py expression: rendered --- [fail] python blockingFindings=1 parsed=1/1 diff --git a/tests/unit/snapshots/unit_test__policy_snapshot__py_proj_r002_declared_package.snap b/tests/unit/snapshots/unit_test__policy_snapshot__py_proj_r002_declared_package.snap index 65b6f3d..676880a 100644 --- a/tests/unit/snapshots/unit_test__policy_snapshot__py_proj_r002_declared_package.snap +++ b/tests/unit/snapshots/unit_test__policy_snapshot__py_proj_r002_declared_package.snap @@ -1,5 +1,5 @@ --- -source: tests/unit/harness/test_policy_snapshots.py +source: tests/unit/asp_python/test_policy_snapshots.py expression: rendered --- [fail] python blockingFindings=1 parsed=0/0 diff --git a/tests/unit/snapshots/unit_test__policy_snapshot__py_proj_r003_py_typed.snap b/tests/unit/snapshots/unit_test__policy_snapshot__py_proj_r003_py_typed.snap index e610792..21f2dd6 100644 --- a/tests/unit/snapshots/unit_test__policy_snapshot__py_proj_r003_py_typed.snap +++ b/tests/unit/snapshots/unit_test__policy_snapshot__py_proj_r003_py_typed.snap @@ -1,5 +1,5 @@ --- -source: tests/unit/harness/test_policy_snapshots.py +source: tests/unit/asp_python/test_policy_snapshots.py expression: rendered --- [fail] python blockingFindings=1 parsed=1/1 diff --git a/tests/unit/snapshots/unit_test__policy_snapshot__py_proj_r004_typed_annotations.snap b/tests/unit/snapshots/unit_test__policy_snapshot__py_proj_r004_typed_annotations.snap index 457930f..c7f9d7a 100644 --- a/tests/unit/snapshots/unit_test__policy_snapshot__py_proj_r004_typed_annotations.snap +++ b/tests/unit/snapshots/unit_test__policy_snapshot__py_proj_r004_typed_annotations.snap @@ -1,5 +1,5 @@ --- -source: tests/unit/harness/test_policy_snapshots.py +source: tests/unit/asp_python/test_policy_snapshots.py expression: rendered --- [fail] python blockingFindings=1 parsed=2/2 diff --git a/tests/unit/snapshots/unit_test__policy_snapshot__py_proj_r005_project_name.snap b/tests/unit/snapshots/unit_test__policy_snapshot__py_proj_r005_project_name.snap index 1566116..63acb88 100644 --- a/tests/unit/snapshots/unit_test__policy_snapshot__py_proj_r005_project_name.snap +++ b/tests/unit/snapshots/unit_test__policy_snapshot__py_proj_r005_project_name.snap @@ -1,5 +1,5 @@ --- -source: tests/unit/harness/test_policy_snapshots.py +source: tests/unit/asp_python/test_policy_snapshots.py expression: rendered --- [fail] python blockingFindings=1 parsed=0/0 diff --git a/tests/unit/snapshots/unit_test__policy_snapshot__py_proj_r006_requires_python.snap b/tests/unit/snapshots/unit_test__policy_snapshot__py_proj_r006_requires_python.snap index b15ad6c..b992b51 100644 --- a/tests/unit/snapshots/unit_test__policy_snapshot__py_proj_r006_requires_python.snap +++ b/tests/unit/snapshots/unit_test__policy_snapshot__py_proj_r006_requires_python.snap @@ -1,5 +1,5 @@ --- -source: tests/unit/harness/test_policy_snapshots.py +source: tests/unit/asp_python/test_policy_snapshots.py expression: rendered --- [fail] python blockingFindings=1 parsed=0/0 diff --git a/tests/unit/snapshots/unit_test__policy_snapshot__py_proj_r007_build_requires.snap b/tests/unit/snapshots/unit_test__policy_snapshot__py_proj_r007_build_requires.snap index f5346a8..e6a4bf3 100644 --- a/tests/unit/snapshots/unit_test__policy_snapshot__py_proj_r007_build_requires.snap +++ b/tests/unit/snapshots/unit_test__policy_snapshot__py_proj_r007_build_requires.snap @@ -1,5 +1,5 @@ --- -source: tests/unit/harness/test_policy_snapshots.py +source: tests/unit/asp_python/test_policy_snapshots.py expression: rendered --- [fail] python blockingFindings=1 parsed=0/0 diff --git a/tests/unit/snapshots/unit_test__policy_snapshot__py_proj_r008_import_names.snap b/tests/unit/snapshots/unit_test__policy_snapshot__py_proj_r008_import_names.snap index 57daa5e..6c49385 100644 --- a/tests/unit/snapshots/unit_test__policy_snapshot__py_proj_r008_import_names.snap +++ b/tests/unit/snapshots/unit_test__policy_snapshot__py_proj_r008_import_names.snap @@ -1,5 +1,5 @@ --- -source: tests/unit/harness/test_policy_snapshots.py +source: tests/unit/asp_python/test_policy_snapshots.py expression: rendered --- [fail] python blockingFindings=1 parsed=0/0 diff --git a/tests/unit/snapshots/unit_test__policy_snapshot__py_proj_r009_entry_point_target.snap b/tests/unit/snapshots/unit_test__policy_snapshot__py_proj_r009_entry_point_target.snap index ff7f5ef..75b4873 100644 --- a/tests/unit/snapshots/unit_test__policy_snapshot__py_proj_r009_entry_point_target.snap +++ b/tests/unit/snapshots/unit_test__policy_snapshot__py_proj_r009_entry_point_target.snap @@ -1,5 +1,5 @@ --- -source: tests/unit/harness/test_policy_snapshots.py +source: tests/unit/asp_python/test_policy_snapshots.py expression: rendered --- [fail] python blockingFindings=1 parsed=0/0 diff --git a/tests/unit/snapshots/unit_test__policy_snapshot__py_proj_r010_pytest_gate.snap b/tests/unit/snapshots/unit_test__policy_snapshot__py_proj_r010_pytest_gate.snap index 44f7e26..553b93c 100644 --- a/tests/unit/snapshots/unit_test__policy_snapshot__py_proj_r010_pytest_gate.snap +++ b/tests/unit/snapshots/unit_test__policy_snapshot__py_proj_r010_pytest_gate.snap @@ -1,10 +1,10 @@ --- -source: tests/unit/harness/test_policy_snapshots.py +source: tests/unit/asp_python/test_policy_snapshots.py expression: rendered --- [fail] python blockingFindings=1 parsed=0/0 |failureFrontier rule=PY-AGENT-PROJECT-010 severity=warning path=pyproject.toml line=1 column=1 -|message Harness dev dependency should mount a pytest gate -|summary pyproject.toml declares the Python project harness surface without a parser-visible pytest gate. -|repair mount the parser-backed harness in pytest +|message ASP Python dev dependency should mount a pytest gate +|summary pyproject.toml declares the ASP Python surface without a parser-visible pytest gate. +|repair mount the parser-backed ASP Python in pytest |hotBlock selector=pyproject.toml:1:1 reason=blocking-finding diff --git a/tests/unit/snapshots/unit_test__policy_snapshot__py_proj_r011_verification_profile.snap b/tests/unit/snapshots/unit_test__policy_snapshot__py_proj_r011_verification_profile.snap index c6b6a3f..875f605 100644 --- a/tests/unit/snapshots/unit_test__policy_snapshot__py_proj_r011_verification_profile.snap +++ b/tests/unit/snapshots/unit_test__policy_snapshot__py_proj_r011_verification_profile.snap @@ -1,5 +1,5 @@ --- -source: tests/unit/harness/test_policy_snapshots.py +source: tests/unit/asp_python/test_policy_snapshots.py expression: rendered --- [advice] @@ -7,4 +7,4 @@ expression: rendered --> pyproject.toml:1:1 1 | [project] | `- configure parser-backed verification profile hints - | Required: Configure `[tool.python-lang-project-harness.verification].profile_hints` from parser-suggested owners, or run `asp-python --agent-snapshot` to copy the compact `[verify-profile]` hints. + | Required: Configure `[tool.asp-python.verification].profile_hints` from parser-suggested owners or consume the dependency API verification-profile projection. diff --git a/tests/unit/snapshots/unit_test__policy_snapshot__py_test_r001_root_pytest.snap b/tests/unit/snapshots/unit_test__policy_snapshot__py_test_r001_root_pytest.snap index ed4c281..e1144d5 100644 --- a/tests/unit/snapshots/unit_test__policy_snapshot__py_test_r001_root_pytest.snap +++ b/tests/unit/snapshots/unit_test__policy_snapshot__py_test_r001_root_pytest.snap @@ -1,5 +1,5 @@ --- -source: tests/unit/harness/test_policy_snapshots.py +source: tests/unit/asp_python/test_policy_snapshots.py expression: rendered --- [fail] python blockingFindings=1 parsed=1/1 diff --git a/tests/unit/snapshots/unit_test__policy_snapshot__py_test_r002_unexpected_root.snap b/tests/unit/snapshots/unit_test__policy_snapshot__py_test_r002_unexpected_root.snap index 25e1432..f948c77 100644 --- a/tests/unit/snapshots/unit_test__policy_snapshot__py_test_r002_unexpected_root.snap +++ b/tests/unit/snapshots/unit_test__policy_snapshot__py_test_r002_unexpected_root.snap @@ -1,5 +1,5 @@ --- -source: tests/unit/harness/test_policy_snapshots.py +source: tests/unit/asp_python/test_policy_snapshots.py expression: rendered --- [fail] python blockingFindings=1 parsed=0/0 diff --git a/tests/unit/snapshots/unit_test__policy_snapshot__py_test_r003_unit_bloat.snap b/tests/unit/snapshots/unit_test__policy_snapshot__py_test_r003_unit_bloat.snap index 50da0d6..e4c458f 100644 --- a/tests/unit/snapshots/unit_test__policy_snapshot__py_test_r003_unit_bloat.snap +++ b/tests/unit/snapshots/unit_test__policy_snapshot__py_test_r003_unit_bloat.snap @@ -1,5 +1,5 @@ --- -source: tests/unit/harness/test_policy_snapshots.py +source: tests/unit/asp_python/test_policy_snapshots.py expression: rendered --- [fail] python blockingFindings=1 parsed=1/1 diff --git a/tests/unit/snapshots/unit_test__policy_snapshot__python_compile_invalid.snap b/tests/unit/snapshots/unit_test__policy_snapshot__python_compile_invalid.snap index 2a4bebf..3e41ec0 100644 --- a/tests/unit/snapshots/unit_test__policy_snapshot__python_compile_invalid.snap +++ b/tests/unit/snapshots/unit_test__policy_snapshot__python_compile_invalid.snap @@ -1,5 +1,5 @@ --- -source: tests/unit/harness/test_policy_snapshots.py +source: tests/unit/asp_python/test_policy_snapshots.py expression: rendered --- [fail] python blockingFindings=1 parsed=0/1 diff --git a/tests/unit/snapshots/unit_test__policy_snapshot__python_syntax_invalid.snap b/tests/unit/snapshots/unit_test__policy_snapshot__python_syntax_invalid.snap index a88e71d..5587ee3 100644 --- a/tests/unit/snapshots/unit_test__policy_snapshot__python_syntax_invalid.snap +++ b/tests/unit/snapshots/unit_test__policy_snapshot__python_syntax_invalid.snap @@ -1,5 +1,5 @@ --- -source: tests/unit/harness/test_policy_snapshots.py +source: tests/unit/asp_python/test_policy_snapshots.py expression: rendered --- [fail] python blockingFindings=1 parsed=0/1 diff --git a/tests/unit/test_package_metadata.py b/tests/unit/test_package_metadata.py index 5ce7fd9..eaaa5a6 100644 --- a/tests/unit/test_package_metadata.py +++ b/tests/unit/test_package_metadata.py @@ -4,31 +4,29 @@ from importlib import metadata from pathlib import Path +import asp_python import python_lang_parser -import python_lang_project_harness def test_distribution_metadata_uses_project_name() -> None: - project = metadata.metadata("python-lang-project-harness") + project = metadata.metadata("asp-python") - assert project["Name"] == "python-lang-project-harness" + assert project["Name"] == "asp-python" assert project["Version"] == "0.1.0" def test_runtime_package_identity_matches_distribution_metadata() -> None: - installed_version = metadata.version("python-lang-project-harness") + installed_version = metadata.version("asp-python") - assert python_lang_project_harness.DISTRIBUTION_NAME == ( - "python-lang-project-harness" - ) - assert python_lang_project_harness.__version__ == installed_version + assert asp_python.DISTRIBUTION_NAME == ("asp-python") + assert asp_python.__version__ == installed_version assert python_lang_parser.__version__ == installed_version def test_distribution_import_packages_are_current_project_surfaces() -> None: top_level_names = { path.parts[0] - for path in metadata.files("python-lang-project-harness") or () + for path in metadata.files("asp-python") or () if path.parts and not path.parts[0].endswith(".dist-info") and path.parts[0] != ".." @@ -37,7 +35,7 @@ def test_distribution_import_packages_are_current_project_surfaces() -> None: assert top_level_names <= { "python_lang_parser", - "python_lang_project_harness", + "asp_python", } @@ -47,11 +45,11 @@ def test_distribution_exposes_console_script() -> None: for entry_point in metadata.entry_points(group="console_scripts") } - assert scripts["asp-python"] == ("python_lang_project_harness:run_cli_from_env") + assert scripts["asp-python"] == ("asp_python:run_cli_from_env") def test_distribution_exposes_pytest_optional_dependency() -> None: - project = metadata.metadata("python-lang-project-harness") + project = metadata.metadata("asp-python") assert "pytest" in project.get_all("Provides-Extra", []) assert any( @@ -66,9 +64,7 @@ def test_distribution_exposes_pytest_plugin_entry_point() -> None: for entry_point in metadata.entry_points(group="pytest11") } - assert plugins["python_lang_project_harness"] == ( - "python_lang_project_harness.pytest_plugin" - ) + assert plugins["asp_python"] == ("asp_python.pytest_plugin") def test_wheel_package_configuration_lists_current_import_packages() -> None: @@ -83,9 +79,9 @@ def test_wheel_package_configuration_lists_current_import_packages() -> None: assert pyproject["tool"]["hatch"]["build"]["targets"]["wheel"]["packages"] == [ "src/python_lang_parser", - "src/python_lang_project_harness", + "src/asp_python", ] assert pyproject["project"]["import-names"] == [ "python_lang_parser", - "python_lang_project_harness", + "asp_python", ] diff --git a/tests/unit/test_public_api.py b/tests/unit/test_public_api.py index ce831f2..e203295 100644 --- a/tests/unit/test_public_api.py +++ b/tests/unit/test_public_api.py @@ -1,210 +1,213 @@ from __future__ import annotations +import asp_python as asp_python_api +import asp_python.api as asp_python_facade import python_lang_parser as parser_api -import python_lang_project_harness as harness_api -import python_lang_project_harness.harness as harness_facade def test_root_package_reexports_parser_fact_models() -> None: - assert harness_api.PythonCallEffect is parser_api.PythonCallEffect - assert harness_api.PythonClassShape is parser_api.PythonClassShape - assert harness_api.PythonExportContract is parser_api.PythonExportContract - assert harness_api.PythonExportContractKind is parser_api.PythonExportContractKind - assert harness_api.PythonFunctionControlFlow is parser_api.PythonFunctionControlFlow - assert harness_api.PythonModuleShape is parser_api.PythonModuleShape - assert harness_api.PythonProjectDependency is parser_api.PythonProjectDependency - assert harness_api.PythonProjectEntryPoint is parser_api.PythonProjectEntryPoint - assert harness_api.PythonProjectImportName is parser_api.PythonProjectImportName - assert harness_api.PythonProjectMetadata is parser_api.PythonProjectMetadata - assert harness_api.PythonProjectScript is parser_api.PythonProjectScript - assert harness_api.PythonPytestOptions is parser_api.PythonPytestOptions - assert harness_api.PythonReasoningTreeFacts is parser_api.PythonReasoningTreeFacts - assert ( - harness_api.PythonReasoningTreeImportEdge + assert asp_python_api.PythonCallEffect is parser_api.PythonCallEffect + assert asp_python_api.PythonClassShape is parser_api.PythonClassShape + assert asp_python_api.PythonExportContract is parser_api.PythonExportContract + assert ( + asp_python_api.PythonExportContractKind is parser_api.PythonExportContractKind + ) + assert ( + asp_python_api.PythonFunctionControlFlow is parser_api.PythonFunctionControlFlow + ) + assert asp_python_api.PythonModuleShape is parser_api.PythonModuleShape + assert asp_python_api.PythonProjectDependency is parser_api.PythonProjectDependency + assert asp_python_api.PythonProjectEntryPoint is parser_api.PythonProjectEntryPoint + assert asp_python_api.PythonProjectImportName is parser_api.PythonProjectImportName + assert asp_python_api.PythonProjectMetadata is parser_api.PythonProjectMetadata + assert asp_python_api.PythonProjectScript is parser_api.PythonProjectScript + assert asp_python_api.PythonPytestOptions is parser_api.PythonPytestOptions + assert ( + asp_python_api.PythonReasoningTreeFacts is parser_api.PythonReasoningTreeFacts + ) + assert ( + asp_python_api.PythonReasoningTreeImportEdge is parser_api.PythonReasoningTreeImportEdge ) - assert harness_api.PythonReasoningTreeNode is parser_api.PythonReasoningTreeNode + assert asp_python_api.PythonReasoningTreeNode is parser_api.PythonReasoningTreeNode assert ( - harness_api.python_reasoning_tree_facts + asp_python_api.python_reasoning_tree_facts is parser_api.python_reasoning_tree_facts ) assert ( - harness_api.parse_python_project_metadata + asp_python_api.parse_python_project_metadata is parser_api.parse_python_project_metadata ) assert ( - harness_api.python_module_namespace_parts + asp_python_api.python_module_namespace_parts is parser_api.python_module_namespace_parts ) assert ( - harness_api.python_module_name_from_path + asp_python_api.python_module_name_from_path is parser_api.python_module_name_from_path ) assert ( - harness_api.python_module_is_package_init + asp_python_api.python_module_is_package_init is parser_api.python_module_is_package_init ) - assert harness_api.python_name_is_public is parser_api.python_name_is_public - assert harness_api.python_scope_is_public is parser_api.python_scope_is_public + assert asp_python_api.python_name_is_public is parser_api.python_name_is_public + assert asp_python_api.python_scope_is_public is parser_api.python_scope_is_public assert ( - harness_api.python_assignment_is_public_top_level + asp_python_api.python_assignment_is_public_top_level is parser_api.python_assignment_is_public_top_level ) assert ( - harness_api.python_module_has_public_surface + asp_python_api.python_module_has_public_surface is parser_api.python_module_has_public_surface ) assert ( - harness_api.python_module_has_public_symbol_surface + asp_python_api.python_module_has_public_symbol_surface is parser_api.python_module_has_public_symbol_surface ) - assert harness_api.python_symbol_is_callable is parser_api.python_symbol_is_callable - assert harness_api.python_symbol_is_class is parser_api.python_symbol_is_class assert ( - harness_api.python_symbol_is_public_callable + asp_python_api.python_symbol_is_callable is parser_api.python_symbol_is_callable + ) + assert asp_python_api.python_symbol_is_class is parser_api.python_symbol_is_class + assert ( + asp_python_api.python_symbol_is_public_callable is parser_api.python_symbol_is_public_callable ) assert ( - harness_api.python_symbol_is_public_callable_boundary + asp_python_api.python_symbol_is_public_callable_boundary is parser_api.python_symbol_is_public_callable_boundary ) assert ( - harness_api.python_symbol_is_public_class + asp_python_api.python_symbol_is_public_class is parser_api.python_symbol_is_public_class ) assert ( - harness_api.python_symbol_is_public_top_level + asp_python_api.python_symbol_is_public_top_level is parser_api.python_symbol_is_public_top_level ) assert ( - harness_api.python_symbol_is_top_level_callable + asp_python_api.python_symbol_is_top_level_callable is parser_api.python_symbol_is_top_level_callable ) assert ( - harness_api.python_symbol_is_test_function + asp_python_api.python_symbol_is_test_function is parser_api.python_symbol_is_test_function ) - assert "python_module_namespace_parts" in harness_api.__all__ - assert "python_module_name_from_path" in harness_api.__all__ - assert "python_module_is_package_init" in harness_api.__all__ - assert "python_name_is_public" in harness_api.__all__ - assert "PythonReasoningTreeFacts" in harness_api.__all__ - assert "PythonProjectMetadata" in harness_api.__all__ - assert "PythonProjectDependency" in harness_api.__all__ - assert "PythonPytestOptions" in harness_api.__all__ - assert "parse_python_project_metadata" in harness_api.__all__ - assert "PythonReasoningTreeImportEdge" in harness_api.__all__ - assert "PythonReasoningTreeNode" in harness_api.__all__ + assert "python_module_namespace_parts" in asp_python_api.__all__ + assert "python_module_name_from_path" in asp_python_api.__all__ + assert "python_module_is_package_init" in asp_python_api.__all__ + assert "python_name_is_public" in asp_python_api.__all__ + assert "PythonReasoningTreeFacts" in asp_python_api.__all__ + assert "PythonProjectMetadata" in asp_python_api.__all__ + assert "PythonProjectDependency" in asp_python_api.__all__ + assert "PythonPytestOptions" in asp_python_api.__all__ + assert "parse_python_project_metadata" in asp_python_api.__all__ + assert "PythonReasoningTreeImportEdge" in asp_python_api.__all__ + assert "PythonReasoningTreeNode" in asp_python_api.__all__ assert "PythonProjectMetadata" in parser_api.__all__ assert "PythonClassShape" in parser_api.__all__ - assert "PythonClassShape" in harness_api.__all__ + assert "PythonClassShape" in asp_python_api.__all__ assert "PythonProjectDependency" in parser_api.__all__ assert "PythonPytestOptions" in parser_api.__all__ assert "PythonReasoningTreeImportEdge" in parser_api.__all__ assert "PythonFunctionControlFlow" in parser_api.__all__ - assert "PythonFunctionControlFlow" in harness_api.__all__ + assert "PythonFunctionControlFlow" in asp_python_api.__all__ assert "parse_python_project_metadata" in parser_api.__all__ - assert "python_reasoning_tree_facts" in harness_api.__all__ - assert "python_scope_is_public" in harness_api.__all__ - assert "python_assignment_is_public_top_level" in harness_api.__all__ - assert "python_module_has_public_surface" in harness_api.__all__ - assert "python_module_has_public_symbol_surface" in harness_api.__all__ - assert "python_symbol_is_callable" in harness_api.__all__ - assert "python_symbol_is_class" in harness_api.__all__ - assert "python_symbol_is_public_callable" in harness_api.__all__ - assert "python_symbol_is_public_callable_boundary" in harness_api.__all__ - assert "python_symbol_is_public_class" in harness_api.__all__ - assert "python_symbol_is_public_top_level" in harness_api.__all__ - assert "python_symbol_is_top_level_callable" in harness_api.__all__ - assert "python_symbol_is_test_function" in harness_api.__all__ + assert "python_reasoning_tree_facts" in asp_python_api.__all__ + assert "python_scope_is_public" in asp_python_api.__all__ + assert "python_assignment_is_public_top_level" in asp_python_api.__all__ + assert "python_module_has_public_surface" in asp_python_api.__all__ + assert "python_module_has_public_symbol_surface" in asp_python_api.__all__ + assert "python_symbol_is_callable" in asp_python_api.__all__ + assert "python_symbol_is_class" in asp_python_api.__all__ + assert "python_symbol_is_public_callable" in asp_python_api.__all__ + assert "python_symbol_is_public_callable_boundary" in asp_python_api.__all__ + assert "python_symbol_is_public_class" in asp_python_api.__all__ + assert "python_symbol_is_public_top_level" in asp_python_api.__all__ + assert "python_symbol_is_top_level_callable" in asp_python_api.__all__ + assert "python_symbol_is_test_function" in asp_python_api.__all__ -def test_root_package_reexports_embedding_harness_surface() -> None: - assert harness_api.PythonHarnessConfig is harness_facade.PythonHarnessConfig - assert harness_api.PythonHarnessReport is harness_facade.PythonHarnessReport - assert ( - harness_api.PythonVerificationPolicy is harness_facade.PythonVerificationPolicy - ) - assert ( - harness_api.PythonVerificationProfileHint - is harness_facade.PythonVerificationProfileHint - ) +def test_root_package_reexports_asp_python_surface() -> None: + assert asp_python_api.AspPythonConfig is asp_python_facade.AspPythonConfig + assert asp_python_api.AspPythonReport is asp_python_facade.AspPythonReport assert ( - harness_api.PythonVerificationTaskKind - is harness_facade.PythonVerificationTaskKind + asp_python_api.PythonVerificationPolicy + is asp_python_facade.PythonVerificationPolicy ) assert ( - harness_api.PythonProjectPolicyRulePack - is harness_facade.PythonProjectPolicyRulePack + asp_python_api.PythonVerificationProfileHint + is asp_python_facade.PythonVerificationProfileHint ) assert ( - harness_api.default_python_harness_config - is harness_facade.default_python_harness_config + asp_python_api.PythonVerificationTaskKind + is asp_python_facade.PythonVerificationTaskKind ) assert ( - harness_api.python_project_harness_test - is harness_facade.python_project_harness_test + asp_python_api.PythonProjectPolicyRulePack + is asp_python_facade.PythonProjectPolicyRulePack ) assert ( - harness_api.python_project_policy_rules - is harness_facade.python_project_policy_rules + asp_python_api.default_asp_python_config + is asp_python_facade.default_asp_python_config ) + assert asp_python_api.asp_python_test is asp_python_facade.asp_python_test assert ( - harness_api.render_python_lang_harness - is harness_facade.render_python_lang_harness + asp_python_api.python_project_policy_rules + is asp_python_facade.python_project_policy_rules ) assert ( - harness_api.render_python_lang_harness_advice - is harness_facade.render_python_lang_harness_advice + asp_python_api.render_asp_python_report + is asp_python_facade.render_asp_python_report ) assert ( - harness_api.render_python_lang_harness_json - is harness_facade.render_python_lang_harness_json + asp_python_api.render_asp_python_report_advice + is asp_python_facade.render_asp_python_report_advice ) assert ( - harness_api.render_python_reasoning_tree - is harness_facade.render_python_reasoning_tree + asp_python_api.render_asp_python_report_json + is asp_python_facade.render_asp_python_report_json ) assert ( - harness_api.render_python_project_harness_agent_snapshot - is harness_facade.render_python_project_harness_agent_snapshot + asp_python_api.render_python_reasoning_tree + is asp_python_facade.render_python_reasoning_tree ) assert ( - harness_api.render_python_project_harness_agent_snapshot_with_config - is harness_facade.render_python_project_harness_agent_snapshot_with_config + asp_python_api.render_asp_python_agent_snapshot + is asp_python_facade.render_asp_python_agent_snapshot ) assert ( - harness_api.read_python_project_harness_config - is harness_facade.read_python_project_harness_config + asp_python_api.render_asp_python_agent_snapshot_with_config + is asp_python_facade.render_asp_python_agent_snapshot_with_config ) assert ( - harness_api.python_rule_pack_descriptors - is harness_facade.python_rule_pack_descriptors + asp_python_api.read_asp_python_config + is asp_python_facade.read_asp_python_config ) - assert harness_api.python_syntax_rules is harness_facade.python_syntax_rules - assert harness_api.run_cli is harness_facade.run_cli - assert harness_api.run_cli_from_env is harness_facade.run_cli_from_env assert ( - harness_api.plan_python_project_verification - is harness_facade.plan_python_project_verification + asp_python_api.python_rule_pack_descriptors + is asp_python_facade.python_rule_pack_descriptors ) + assert asp_python_api.python_syntax_rules is asp_python_facade.python_syntax_rules + assert asp_python_api.run_cli is asp_python_facade.run_cli + assert asp_python_api.run_cli_from_env is asp_python_facade.run_cli_from_env assert ( - harness_api.render_python_verification_plan - is harness_facade.render_python_verification_plan + asp_python_api.plan_python_project_verification + is asp_python_facade.plan_python_project_verification ) - assert "render_python_lang_harness_advice" in harness_api.__all__ - assert "render_python_lang_harness_json" in harness_api.__all__ - assert "render_python_project_harness_agent_snapshot" in harness_api.__all__ assert ( - "render_python_project_harness_agent_snapshot_with_config" - in harness_api.__all__ + asp_python_api.render_python_verification_plan + is asp_python_facade.render_python_verification_plan ) - assert "render_python_reasoning_tree" in harness_api.__all__ - assert "read_python_project_harness_config" in harness_api.__all__ - assert "run_cli_from_env" in harness_api.__all__ - assert "python_syntax_rules" in harness_api.__all__ - assert "PythonVerificationPolicy" in harness_api.__all__ - assert "PythonVerificationProfileHint" in harness_api.__all__ - assert "PythonVerificationTaskKind" in harness_api.__all__ - assert "plan_python_project_verification" in harness_api.__all__ - assert "render_python_verification_plan" in harness_api.__all__ + assert "render_asp_python_report_advice" in asp_python_api.__all__ + assert "render_asp_python_report_json" in asp_python_api.__all__ + assert "render_asp_python_agent_snapshot" in asp_python_api.__all__ + assert "render_asp_python_agent_snapshot_with_config" in asp_python_api.__all__ + assert "render_python_reasoning_tree" in asp_python_api.__all__ + assert "read_asp_python_config" in asp_python_api.__all__ + assert "run_cli_from_env" in asp_python_api.__all__ + assert "python_syntax_rules" in asp_python_api.__all__ + assert "PythonVerificationPolicy" in asp_python_api.__all__ + assert "PythonVerificationProfileHint" in asp_python_api.__all__ + assert "PythonVerificationTaskKind" in asp_python_api.__all__ + assert "plan_python_project_verification" in asp_python_api.__all__ + assert "render_python_verification_plan" in asp_python_api.__all__ diff --git a/tests/unit/test_self_hosting.py b/tests/unit/test_self_hosting.py index 976d167..a562370 100644 --- a/tests/unit/test_self_hosting.py +++ b/tests/unit/test_self_hosting.py @@ -2,7 +2,7 @@ from pathlib import Path -from python_lang_project_harness import python_project_harness_test +from asp_python import asp_python_test _PROJECT_ROOT = next( parent @@ -11,6 +11,4 @@ ) -test_python_lang_project_harness_self_policy = python_project_harness_test( - _PROJECT_ROOT -) +test_asp_python_self_policy = asp_python_test(_PROJECT_ROOT) diff --git a/tree-sitter/tree-sitter-python/grammar-profile.json b/tree-sitter/tree-sitter-python/grammar-profile.json index cfaf165..cf69788 100644 --- a/tree-sitter/tree-sitter-python/grammar-profile.json +++ b/tree-sitter/tree-sitter-python/grammar-profile.json @@ -13,7 +13,7 @@ "owner": "main-asp", "repository": "https://github.com/tao3k/agent-semantic-protocols", "revision": "4e3417721d576716bcf82b7cbf8b9c2dc9a2b32a", - "contractFingerprint": "sha256:679a7adbe48d53d703da3802757ca12b7c89e0c5feb58b4a17b3b8ef022e3325", + "contractFingerprint": "sha256:403556a55568509092dcb59625e0782614f3e0a42540996f64c15c2f3903d419", "queryCorpusValidator": "asp-tree-sitter-validate-python-query-corpus" }, "queryCorpus": { diff --git a/uv.lock b/uv.lock index eb971fa..047b39b 100644 --- a/uv.lock +++ b/uv.lock @@ -124,7 +124,7 @@ wheels = [ ] [[package]] -name = "python-lang-project-harness" +name = "asp-python" version = "0.1.0" source = { editable = "." } dependencies = [