diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index fad55fc..efb424a 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -49,10 +49,10 @@ jobs: run: uv run --group test pytest tests -q - name: Self harness - run: uv run --group test py-harness . + run: uv run --group test asp-python . - name: Agent snapshot - run: uv run --group test py-harness --agent-snapshot . + run: uv run --group test asp-python --agent-snapshot . - name: Build package run: uv build diff --git a/README.md b/README.md index ae4f02b..1b933bd 100644 --- a/README.md +++ b/README.md @@ -86,20 +86,20 @@ The semantic-language console script exposes search, registry, and check surfaces aligned with the Rust and TypeScript harnesses: ```shell -py-harness search workspace . -py-harness search prime . -py-harness search lexical PythonHarnessReport owner tests . -py-harness search lexical --query-set PythonHarnessReport --query-set PythonSemanticSearchOptions owner tests . -py-harness search public-external-types pytest . -py-harness search callsite PythonHarnessReport . -py-harness search deps pytest . -py-harness agent doctor --json . -py-harness agent guide . -py-harness check --full . -py-harness . -py-harness --json . -py-harness --agent-snapshot . -py-harness --source-dir lib --extra-path tools --no-tests . +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-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 . ``` diff --git a/docs/01_core/101_harness_boundary.md b/docs/01_core/101_harness_boundary.md index acce1c3..91999a0 100644 --- a/docs/01_core/101_harness_boundary.md +++ b/docs/01_core/101_harness_boundary.md @@ -129,9 +129,9 @@ gate. ## CLI Embedding -`py-harness check [--json] [PROJECT_ROOT]` runs the same default project +`asp-python check [--json] [PROJECT_ROOT]` runs the same default project runner. Compact text is the default output. `--json` emits the structured -`PythonHarnessReport` payload. `py-harness search ...` renders bounded +`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. diff --git a/docs/03_features/201_rule_catalog.md b/docs/03_features/201_rule_catalog.md index b084a17..170ea54 100644 --- a/docs/03_features/201_rule_catalog.md +++ b/docs/03_features/201_rule_catalog.md @@ -86,7 +86,7 @@ work orders for the repair Agent, not immediate merge blockers. - `PY-AGENT-PROJECT-011`: projects that declare the harness as a test/dev dependency and expose parser-visible verification owners should configure `[tool.python-lang-project-harness.verification].profile_hints`. The finding - points the Agent to `py-harness --agent-snapshot`, whose compact + points the Agent to `asp-python --agent-snapshot`, whose compact `[verify-profile]` section is the config draft. ## Agent Advice Rules diff --git a/docs/03_features/202_runner_modes.md b/docs/03_features/202_runner_modes.md index de3f6d1..c98fab2 100644 --- a/docs/03_features/202_runner_modes.md +++ b/docs/03_features/202_runner_modes.md @@ -28,7 +28,7 @@ and produce CLI exit code `2`. ## Configuration -`PythonHarnessConfig` owns project-scope classification and parser inclusion: +`PythonHarnessConfig` owns project-resolution classification and parser inclusion: ```python from python_lang_project_harness import PythonHarnessConfig @@ -116,7 +116,7 @@ opts out of project-local config loading for that call. Use `run_python_lang_harness()` or `assert_python_lang_harness_clean()` for explicit files or directories. Requested paths must exist. This runner does not -attach a project scope, so project-scope evaluators stay quiet. File-local rule +attach a project scope, so project-resolution evaluators stay quiet. File-local rule packs can still run when they only need parser facts. The explicit-path runner is useful for editor integrations, focused parser diff --git a/docs/03_features/203_cli.md b/docs/03_features/203_cli.md index cc15409..2773cdd 100644 --- a/docs/03_features/203_cli.md +++ b/docs/03_features/203_cli.md @@ -7,15 +7,15 @@ :LAST_SYNC: 2026-04-30 :END: -The package exposes `py-harness` as the semantic-language provider binary and +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 -py-harness search ... [--json] [--package PATH] [PROJECT_ROOT] -py-harness check [--changed | --full] [--json] [PROJECT_ROOT] -py-harness agent doctor [--json] [PROJECT_ROOT] -py-harness agent guide [PROJECT_ROOT] -py-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] +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] ``` @@ -24,9 +24,9 @@ When `PROJECT_ROOT` is omitted, the current working directory is used. ## Semantic Language Identity The public semantic-language identity is -`languageId=python`, `providerId=py-harness`, `binary=py-harness`, and -`namespace=agent.semantic-protocols.languages.python.py-harness`. -`py-harness agent doctor --json` emits a `semantic-language-registry.v1` +`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: @@ -37,7 +37,7 @@ schema registrations for: The common registry schema owns the capability descriptor shape. The Python provider owns its capability vocabulary in the Python-local schema. -`py-harness agent guide` emits the provider-owned searchflow guide consumed by +`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. @@ -92,8 +92,8 @@ Dependency and API views are Python-native: The CLI mirrors the project runner's classification and inclusion options: ```shell -py-harness --source-dir lib --test-dir checks --extra-path tools . -py-harness --no-tests . +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 @@ -108,8 +108,8 @@ tests-root layout policy active. Rule-level policy can be adjusted for one run: ```shell -py-harness --disable-rule PY-MOD-R002 . -py-harness --block-rule PY-AGENT-POLICY-007 . +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 @@ -127,7 +127,7 @@ config. Compact text is the default output for humans and repair-oriented agents: ```shell -py-harness . +asp-python . ``` When configured-blocking findings exist, the first line is the first concrete @@ -140,7 +140,7 @@ use `--json` for structured original paths. Use `--json` when a tool needs the structured `PythonHarnessReport` payload: ```shell -py-harness --json . +asp-python --json . ``` Use `--agent-snapshot` when an Agent needs capped parser facts, project @@ -148,7 +148,7 @@ metadata, active policy findings, branch-first verification profile reminders, and active verification tasks without clean-run counters: ```shell -py-harness --agent-snapshot . +asp-python --agent-snapshot . ``` `--json` and `--agent-snapshot` are mutually exclusive. diff --git a/docs/03_features/204_pytest.md b/docs/03_features/204_pytest.md index a449886..46afd34 100644 --- a/docs/03_features/204_pytest.md +++ b/docs/03_features/204_pytest.md @@ -84,7 +84,7 @@ test_python_project_harness_policy = python_project_harness_test() ``` The helper defaults to `Path(".")` and returns a pytest-collectable callable. -Callers can pass the same project-scope options used by the library runner: +Callers can pass the same project-resolution options used by the library runner: ```python from python_lang_parser import PythonDiagnosticSeverity diff --git a/provider/asp-provider-manifest.json b/provider/asp-provider-manifest.json deleted file mode 100644 index 1710aa3..0000000 --- a/provider/asp-provider-manifest.json +++ /dev/null @@ -1,142 +0,0 @@ -{ - "schemaId": "agent.semantic-protocols.hook.provider-manifest", - "schemaVersion": "1", - "protocolId": "agent.semantic-protocols.hook", - "protocolVersion": "1", - "manifestId": "agent.semantic-protocols.providers.python.py-harness", - "manifestVersion": "v1", - "languageId": "python", - "providerId": "py-harness", - "namespace": "agent.semantic-protocols.languages.python.py-harness", - "binary": "py-harness", - "execution": "external-process", - "source": { - "defaultExtensions": [ - ".py" - ], - "defaultConfigFiles": [ - "pyproject.toml", - "setup.py", - "setup.cfg" - ], - "defaultSourceRoots": [ - "src", - "tests" - ], - "defaultIgnoredPathPrefixes": [ - ".venv", - "venv", - "__pycache__", - ".mypy_cache" - ], - "defaultProjectMarkers": [ - "pyproject.toml", - "setup.py", - "setup.cfg" - ], - "defaultDependencyMarkers": [ - "pyproject.toml", - "requirements.txt", - "requirements-dev.txt", - "uv.lock", - "poetry.lock", - "Pipfile", - "Pipfile.lock" - ] - }, - "searchCapabilities": { - "ownerItems": true, - "semanticFacts": true - }, - "policy": { - "directSourceRead": "block", - "bulkSourceDump": "block", - "rawSourceSearch": "block", - "agentSearchJson": "block" - }, - "routes": { - "prime": { - "argv": [ - "py-harness", - "search", - "prime", - "--workspace", - "{projectRoot}", - "--view", - "seeds" - ] - }, - "owner": { - "argv": [ - "py-harness", - "search", - "owner", - "{path}", - "--workspace", - "{projectRoot}", - "--view", - "seeds" - ] - }, - "lexical": { - "argv": [ - "py-harness", - "search", - "lexical", - "{query}", - "owner", - "tests", - "--workspace", - "{projectRoot}", - "--view", - "seeds" - ] - }, - "query": { - "argv": [ - "py-harness", - "query", - "--from-hook", - "direct-source-read", - "--selector", - "{selector}", - "{termArgs}", - "--surface", - "owners,tests", - "--workspace", - "{projectRoot}", - "--view", - "seeds" - ] - }, - "ingest": { - "argv": [ - "py-harness", - "search", - "ingest", - "items", - "tests", - "--workspace", - "{projectRoot}", - "--view", - "seeds" - ], - "stdinMode": "pipe-candidates" - }, - "checkChanged": { - "argv": [ - "py-harness", - "check", - "--changed", - "{projectRoot}" - ] - }, - "guide": { - "argv": [ - "py-harness", - "guide", - "{projectRoot}" - ] - } - } -} diff --git a/provider/asp-provider-registration.json b/provider/asp-provider-registration.json new file mode 100644 index 0000000..f359b9a --- /dev/null +++ b/provider/asp-provider-registration.json @@ -0,0 +1,267 @@ +{ + "$schema": "../schemas/provider-registration.schema.json", + "languageId": "python", + "providerId": "asp-python", + "namespace": "agent.semantic-protocols.languages.python.asp-python", + "displayName": "Python", + "binary": "asp-python", + "execution": "provider", + "providerDescriptor": { + "$ref": "asp-provider-workspace-install.json" + }, + "sourceInventory": { + "packageRoots": [], + "configFiles": [ + "pyproject.toml" + ], + "sourceExtensions": [ + ".py", + ".pyi" + ], + "projectResolution": { + "entryMarkers": [ + "pyproject.toml" + ] + }, + "documentResolution": null + }, + "searchCapabilities": { + "ownerItems": true, + "semanticFacts": true, + "dependencyTopology": true, + "dependencyTopologyMetadata": true + }, + "queryPackDescriptor": { + "descriptorId": "python.search", + "descriptorVersion": "1", + "languageId": "python", + "termRoleOverrides": [], + "recipes": [] + }, + "runtimeContract": { + "transport": "http-json", + "clientBinding": "schema-driven", + "aspClientServer": { + "schemaId": "agent.semantic-protocols.asp-client-server-descriptor", + "schemaVersion": "1", + "transport": "http-json", + "command": [ + "serve" + ], + "healthPath": "/health", + "requestPath": "/v1/provider-runtime", + "shutdownPath": "/shutdown", + "warmupPolicy": "before-ready" + }, + "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" + } + ] + }, + "routes": [ + { + "schemaId": "agent.semantic-protocols.provider-route", + "schemaVersion": "1", + "routeId": "python.projection-batch", + "operation": "projection-batch", + "authority": "asp-server", + "target": { + "languageId": "python", + "providerId": "asp-python" + }, + "inputs": [], + "requirements": [ + { + "kind": "state", + "state": "provider-ready" + } + ], + "effects": { + "access": "read", + "idempotent": true, + "cancellable": true, + "concurrency": "shared-read", + "streaming": false + }, + "output": { + "schemaId": "agent.semantic-protocols.source-index-projection", + "mediaType": "application/json" + }, + "failureSchemaIds": [ + "agent.semantic-protocols.route-failure" + ], + "cache": { + "authority": "asp-server", + "scope": "workspace", + "keySlots": [] + }, + "telemetry": { + "spanName": "asp.route.python.projection-batch", + "attributeSlots": [] + } + }, + { + "schemaId": "agent.semantic-protocols.provider-route", + "schemaVersion": "1", + "routeId": "python.search", + "operation": "search", + "requestSchemaId": "agent.semantic-protocols.asp-client-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": "operation", "valueType": "string", "cardinality": "required", "source": "request"}, + {"name": "query", "valueType": "string", "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", + "attributeSlots": [] + } + }, + { + "schemaId": "agent.semantic-protocols.provider-route", + "schemaVersion": "1", + "routeId": "python.query", + "operation": "query", + "requestSchemaId": "agent.semantic-protocols.asp-client-exact-query-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": "selector", "valueType": "structural-selector", "cardinality": "required", "source": "request"}, + {"name": "projection", "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.query-result", + "mediaType": "application/json" + }, + "failureSchemaIds": [ + "agent.semantic-protocols.route-failure" + ], + "cache": { + "authority": "asp-server", + "scope": "workspace", + "keySlots": [] + }, + "telemetry": { + "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": [ + { + "schemaId": "agent.semantic-protocols.provider-route", + "schemaVersion": "1", + "authority": "asp", + "path": "schemas/provider-route.schema.json" + } + ] +} diff --git a/provider/asp-provider-workspace-install.json b/provider/asp-provider-workspace-install.json new file mode 100644 index 0000000..2eeba4d --- /dev/null +++ b/provider/asp-provider-workspace-install.json @@ -0,0 +1,49 @@ +{ + "$schema": "../schemas/provider-workspace-install.schema.json", + "schemaId": "agent.semantic-protocols.provider-workspace-install", + "schemaVersion": "1", + "schemaAuthority": "https://tao3k.github.io/agent-semantic-protocols/schemas/", + "languageId": "python", + "providerId": "asp-python", + "binary": "asp-python", + "providerRegistration": "asp-provider-registration.json", + "schemaBundleReceipt": "../schemas/.asp-schema-manager-receipt.json", + "workspaceArtifact": { + "root": "languages/python-lang-project-harness/.venv", + "entrypoint": "bin/asp-python", + "runtimeDependencies": [ + { + "source": "lib/libpython3.13.dylib", + "target": "lib/libpython3.13.dylib" + } + ], + "launch": { + "program": "bin/python3", + "args": [ + "bin/asp-python" + ], + "programRelativeToArtifact": true, + "argsRelativeToArtifact": true + } + }, + "workspaceBuild": { + "program": "uv", + "args": [ + "sync", + "--frozen", + "--no-editable", + "--no-cache", + "--reinstall-package", + "python-lang-project-harness" + ], + "workingDirectory": "languages/python-lang-project-harness", + "sourceSnapshotAnchors": [ + "languages/python-lang-project-harness/pyproject.toml", + "languages/python-lang-project-harness/uv.lock" + ], + "derivedPaths": [ + "languages/python-lang-project-harness/.venv" + ], + "env": {} + } +} diff --git a/pyproject.toml b/pyproject.toml index e76a41b..f71d32d 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -8,7 +8,7 @@ import-names = [ "python_lang_parser", "python_lang_project_harness", ] -dependencies = [] +dependencies = ["blake3>=1.0.8,<2"] [project.optional-dependencies] pytest = [ @@ -16,7 +16,7 @@ pytest = [ ] [project.scripts] -py-harness = "python_lang_project_harness:run_cli_from_env" +asp-python = "python_lang_project_harness:run_cli_from_env" [project.entry-points.pytest11] python_lang_project_harness = "python_lang_project_harness.pytest_plugin" @@ -38,13 +38,14 @@ packages = [ "src/python_lang_project_harness", ] +[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" + [tool.uv] package = true [tool.pytest.ini_options] -addopts = [ - "--python-project-harness", -] +pythonpath = ["src"] [[tool.python-lang-project-harness.verification.profile_hints]] owner_path = "pyproject.toml" diff --git a/schemas/.asp-schema-manager-receipt.json b/schemas/.asp-schema-manager-receipt.json new file mode 100644 index 0000000..3d20cb3 --- /dev/null +++ b/schemas/.asp-schema-manager-receipt.json @@ -0,0 +1,321 @@ +{ + "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" + } + ] +} \ 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 new file mode 100644 index 0000000..fb6d1af --- /dev/null +++ b/schemas/asp-client-cancellation-probe-request.schema.json @@ -0,0 +1,16 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://agent-semantic-protocols.dev/schemas/asp-client-cancellation-probe-request.schema.json", + "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" + } + } +} diff --git a/schemas/asp-client-cancellation-probe-response.schema.json b/schemas/asp-client-cancellation-probe-response.schema.json new file mode 100644 index 0000000..cac7adf --- /dev/null +++ b/schemas/asp-client-cancellation-probe-response.schema.json @@ -0,0 +1,20 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://agent-semantic-protocols.dev/schemas/asp-client-cancellation-probe-response.schema.json", + "title": "ASP Client Cancellation Probe Response", + "description": "Catalog contract for a lifecycle probe whose conforming terminal wire outcome is ClientOutcome::Cancelled rather than a successful result payload.", + "type": "object", + "additionalProperties": false, + "required": ["schemaId", "schemaVersion", "state"], + "properties": { + "schemaId": { + "const": "agent.semantic-protocols.asp-client-cancellation-probe-response" + }, + "schemaVersion": { + "const": "1" + }, + "state": { + "const": "armed" + } + } +} diff --git a/schemas/asp-client-conformance.schema.json b/schemas/asp-client-conformance.schema.json new file mode 100644 index 0000000..5fa1096 --- /dev/null +++ b/schemas/asp-client-conformance.schema.json @@ -0,0 +1,30 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://schemas.agent-semantic-protocols.dev/asp-client-conformance.schema.json", + "title": "ASP Client Protocol Conformance Suite", + "type": "object", + "additionalProperties": false, + "required": ["schemaId", "schemaVersion", "protocolId", "protocolVersion", "cases"], + "properties": { + "$schema": { "type": "string", "minLength": 1 }, + "schemaId": { "const": "agent.semantic-protocols.client.conformance" }, + "schemaVersion": { "const": "1" }, + "protocolId": { "const": "agent.semantic-protocols.client" }, + "protocolVersion": { "const": "1" }, + "cases": { + "type": "array", + "minItems": 1, + "items": { + "type": "object", + "additionalProperties": false, + "required": ["caseId", "request", "expectedOutcome"], + "properties": { + "caseId": { "type": "string", "minLength": 1 }, + "request": { "$ref": "asp-client-frame.schema.json" }, + "expectedOutcome": { "enum": ["ready", "error", "cancelled", "stale-generation"] }, + "expectedReasonKind": { "type": "string", "minLength": 1 } + } + } + } + } +} diff --git a/schemas/asp-client-exact-query-request.schema.json b/schemas/asp-client-exact-query-request.schema.json new file mode 100644 index 0000000..4aca5a4 --- /dev/null +++ b/schemas/asp-client-exact-query-request.schema.json @@ -0,0 +1,16 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://agent-semantic-protocols.dev/schemas/asp-client-exact-query-request.schema.json", + "title": "ASP Client Exact Query Request", + "type": "object", + "additionalProperties": false, + "required": ["schemaId", "schemaVersion", "selector", "projection"], + "properties": { + "schemaId": { + "const": "agent.semantic-protocols.asp-client-exact-query-request" + }, + "schemaVersion": { "const": "1" }, + "selector": { "type": "string", "minLength": 1 }, + "projection": { "enum": ["source", "callable-skeleton"] } + } +} diff --git a/schemas/asp-client-exact-query-response.schema.json b/schemas/asp-client-exact-query-response.schema.json new file mode 100644 index 0000000..0641323 --- /dev/null +++ b/schemas/asp-client-exact-query-response.schema.json @@ -0,0 +1,60 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://agent-semantic-protocols.dev/schemas/asp-client-exact-query-response.schema.json", + "title": "ASP Client Exact Query Response", + "type": "object", + "additionalProperties": false, + "required": [ + "schemaId", + "schemaVersion", + "operationId", + "languageId", + "providerId", + "generationDigest", + "rootDigest", + "result", + "residentReadElapsedMicros", + "serviceElapsedMicros", + "elapsedMicros", + "workCounters" + ], + "properties": { + "schemaId": { + "const": "agent.semantic-protocols.asp-client-exact-query-response" + }, + "schemaVersion": { "const": "1" }, + "operationId": { "type": "string", "minLength": 1 }, + "languageId": { "type": "string", "minLength": 1 }, + "providerId": { "type": "string", "minLength": 1 }, + "generationDigest": { + "type": "string", + "pattern": "^blake3-256:[0-9a-f]{64}$" + }, + "rootDigest": { + "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": { + "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-frame.schema.json b/schemas/asp-client-frame.schema.json new file mode 100644 index 0000000..eac399b --- /dev/null +++ b/schemas/asp-client-frame.schema.json @@ -0,0 +1,146 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://schemas.agent-semantic-protocols.dev/asp-client-frame.schema.json", + "title": "ASP Client Protocol Frame", + "oneOf": [ + { "$ref": "#/$defs/initialize" }, + { "$ref": "#/$defs/request" }, + { "$ref": "#/$defs/cancel" }, + { "$ref": "#/$defs/shutdown" }, + { "$ref": "#/$defs/exit" }, + { "$ref": "#/$defs/response" }, + { "$ref": "#/$defs/event" } + ], + "$defs": { + "base": { + "type": "object", + "required": ["schemaId", "schemaVersion", "protocolId", "protocolVersion", "kind", "sessionId", "workspaceIdentity"], + "properties": { + "schemaId": { "const": "agent.semantic-protocols.client.frame" }, + "schemaVersion": { "const": "1" }, + "protocolId": { "const": "agent.semantic-protocols.client" }, + "protocolVersion": { "const": "1" }, + "kind": { "type": "string" }, + "sessionId": { "type": "string", "minLength": 1 }, + "workspaceIdentity": { "type": "string", "minLength": 1 }, + "traceContext": { + "type": "object", + "additionalProperties": false, + "required": ["traceparent"], + "properties": { + "traceparent": { "type": "string", "minLength": 1 }, + "tracestate": { "type": "string" } + } + } + } + }, + "initialize": { + "allOf": [ + { "$ref": "#/$defs/base" }, + { + "type": "object", + "additionalProperties": false, + "required": ["schemaId", "schemaVersion", "protocolId", "protocolVersion", "kind", "sessionId", "workspaceIdentity", "requestId", "projectRoot", "clientInfo", "capabilities"], + "properties": { + "schemaId": {}, "schemaVersion": {}, "protocolId": {}, "protocolVersion": {}, + "kind": { "const": "initialize" }, "sessionId": {}, "workspaceIdentity": {}, "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 } } + }, + "capabilities": { "type": "object" } + } + } + ] + }, + "request": { + "allOf": [ + { "$ref": "#/$defs/base" }, + { + "type": "object", + "additionalProperties": false, + "required": ["schemaId", "schemaVersion", "protocolId", "protocolVersion", "kind", "sessionId", "workspaceIdentity", "requestId", "catalogGeneration", "workspaceGeneration", "method", "params"], + "properties": { + "schemaId": {}, "schemaVersion": {}, "protocolId": {}, "protocolVersion": {}, + "kind": { "const": "request" }, "sessionId": {}, "workspaceIdentity": {}, "traceContext": {}, + "requestId": { "type": "string", "minLength": 1 }, + "catalogGeneration": { "type": "string", "minLength": 1 }, + "workspaceGeneration": { "type": "string", "minLength": 1 }, + "method": { "type": "string", "minLength": 1 }, + "params": {} + } + } + ] + }, + "cancel": { + "allOf": [ + { "$ref": "#/$defs/base" }, + { + "type": "object", + "additionalProperties": false, + "required": ["schemaId", "schemaVersion", "protocolId", "protocolVersion", "kind", "sessionId", "workspaceIdentity", "requestId"], + "properties": { + "schemaId": {}, "schemaVersion": {}, "protocolId": {}, "protocolVersion": {}, + "kind": { "const": "cancel" }, "sessionId": {}, "workspaceIdentity": {}, "traceContext": {}, + "requestId": { "type": "string", "minLength": 1 } + } + } + ] + }, + "shutdown": { + "allOf": [ + { "$ref": "#/$defs/base" }, + { + "type": "object", + "required": ["requestId"], + "properties": { + "kind": { "const": "shutdown" }, + "requestId": { "type": "string", "minLength": 1 } + } + } + ] + }, + "exit": { + "allOf": [ + { "$ref": "#/$defs/base" }, + { "type": "object", "properties": { "kind": { "const": "exit" } } } + ] + }, + "response": { + "allOf": [ + { "$ref": "#/$defs/base" }, + { + "type": "object", + "required": ["requestId", "outcome"], + "properties": { + "kind": { "const": "response" }, + "requestId": { "type": "string", "minLength": 1 }, + "outcome": { "enum": ["ready", "error", "cancelled", "stale-generation"] }, + "result": {}, + "error": { "type": "object" }, + "catalog": { "$ref": "asp-client-protocol-catalog.schema.json" } + } + } + ] + }, + "event": { + "allOf": [ + { "$ref": "#/$defs/base" }, + { + "type": "object", + "required": ["eventId", "event", "payload"], + "properties": { + "kind": { "const": "event" }, + "eventId": { "type": "string", "minLength": 1 }, + "event": { "type": "string", "minLength": 1 }, + "payload": {} + } + } + ] + } + } +} diff --git a/schemas/asp-client-owner-search-request.schema.json b/schemas/asp-client-owner-search-request.schema.json new file mode 100644 index 0000000..eab07dd --- /dev/null +++ b/schemas/asp-client-owner-search-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-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 new file mode 100644 index 0000000..76bd3d4 --- /dev/null +++ b/schemas/asp-client-protocol-catalog.schema.json @@ -0,0 +1,102 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://schemas.agent-semantic-protocols.dev/asp-client-protocol-catalog.schema.json", + "title": "ASP Client Protocol Catalog", + "type": "object", + "additionalProperties": false, + "required": [ + "schemaId", + "schemaVersion", + "protocolId", + "protocolVersion", + "catalogGeneration", + "workspaceGeneration", + "transports", + "capabilities", + "methods" + ], + "properties": { + "schemaId": { "const": "agent.semantic-protocols.client.protocol-catalog" }, + "schemaVersion": { "const": "1" }, + "protocolId": { "const": "agent.semantic-protocols.client" }, + "protocolVersion": { "const": "1" }, + "catalogGeneration": { "$ref": "#/$defs/digest" }, + "workspaceGeneration": { "$ref": "#/$defs/digest" }, + "transports": { + "type": "array", + "minItems": 1, + "uniqueItems": true, + "items": { "enum": ["http-json", "runtime-ipc"] } + }, + "capabilities": { + "type": "object", + "additionalProperties": false, + "required": ["requestCancellation", "events", "streaming", "traceContext"], + "properties": { + "requestCancellation": { "type": "boolean" }, + "events": { "type": "boolean" }, + "streaming": { "type": "boolean" }, + "traceContext": { "type": "boolean" } + } + }, + "methods": { + "type": "array", + "items": { "$ref": "#/$defs/method" } + } + }, + "$defs": { + "digest": { "type": "string", "pattern": "^(blake3-256|sha256):[0-9a-f]{64}$" }, + "schemaIdentifier": { "type": "string", "minLength": 1 }, + "method": { + "type": "object", + "additionalProperties": false, + "required": [ + "method", + "routeId", + "requestSchemaId", + "responseSchemaId", + "errorSchemaIds", + "parameters", + "cancellable", + "streaming" + ], + "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": { + "type": "array", + "items": { "$ref": "#/$defs/schemaIdentifier" } + }, + "parameters": { + "type": "array", + "items": { "$ref": "#/$defs/parameter" } + }, + "cancellable": { "type": "boolean" }, + "streaming": { "type": "boolean" } + } + }, + "parameter": { + "type": "object", + "additionalProperties": false, + "required": ["name", "valueType", "cardinality", "source"], + "properties": { + "name": { "type": "string", "pattern": "^[a-z][A-Za-z0-9]*$" }, + "valueType": { + "enum": [ + "string", + "workspace-relative-path", + "structural-selector", + "presentation", + "boolean", + "unsigned-integer", + "json" + ] + }, + "cardinality": { "enum": ["required", "optional", "many"] }, + "source": { "enum": ["request", "runtime-context"] } + } + } + } +} diff --git a/schemas/asp-client-search-request.schema.json b/schemas/asp-client-search-request.schema.json new file mode 100644 index 0000000..2f349fd --- /dev/null +++ b/schemas/asp-client-search-request.schema.json @@ -0,0 +1,19 @@ +{ + "$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-server-descriptor.schema.json b/schemas/asp-client-server-descriptor.schema.json new file mode 100644 index 0000000..84b6dc3 --- /dev/null +++ b/schemas/asp-client-server-descriptor.schema.json @@ -0,0 +1,18 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://schemas.agent-semantic-protocols.dev/asp-client-server-descriptor.schema.json", + "title": "ASP Client Server Descriptor", + "type": "object", + "additionalProperties": false, + "required": ["schemaId", "schemaVersion", "transport", "command", "healthPath", "requestPath", "shutdownPath", "warmupPolicy"], + "properties": { + "schemaId": { "const": "agent.semantic-protocols.asp-client-server-descriptor" }, + "schemaVersion": { "const": "1" }, + "transport": { "const": "http-json" }, + "command": { "const": ["serve"] }, + "healthPath": { "const": "/health" }, + "requestPath": { "const": "/v1/provider-runtime" }, + "shutdownPath": { "const": "/shutdown" }, + "warmupPolicy": { "enum": ["before-ready"] } + } +} diff --git a/schemas/asp-client-workspace-source-mutation.schema.json b/schemas/asp-client-workspace-source-mutation.schema.json new file mode 100644 index 0000000..0bd00e4 --- /dev/null +++ b/schemas/asp-client-workspace-source-mutation.schema.json @@ -0,0 +1,98 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://schemas.agent-semantic-protocols.dev/asp-client-workspace-source-mutation.schema.json", + "title": "Workspace Source Mutation", + "type": "object", + "additionalProperties": false, + "required": [ + "schemaId", + "schemaVersion", + "mutationId", + "workspaceIdentity", + "changedOwners", + "removedOwners" + ], + "properties": { + "schemaId": { + "const": "agent.semantic-protocols.workspace-source-mutation" + }, + "schemaVersion": { + "const": "1" + }, + "mutationId": { + "type": "string", + "minLength": 1 + }, + "workspaceIdentity": { + "type": "string", + "minLength": 1 + }, + "baseGenerationDigest": { + "type": "string", + "minLength": 1 + }, + "changedOwners": { + "type": "array", + "items": { + "$ref": "#/$defs/changedOwner" + }, + "uniqueItems": true + }, + "removedOwners": { + "type": "array", + "items": { + "$ref": "#/$defs/removedOwner" + }, + "uniqueItems": true + } + }, + "anyOf": [ + { + "properties": { + "changedOwners": { + "minItems": 1 + } + } + }, + { + "properties": { + "removedOwners": { + "minItems": 1 + } + } + } + ], + "$defs": { + "changedOwner": { + "type": "object", + "additionalProperties": false, + "required": [ + "ownerPath", + "sourceSnapshotDigest" + ], + "properties": { + "ownerPath": { + "type": "string", + "minLength": 1 + }, + "sourceSnapshotDigest": { + "type": "string", + "minLength": 1 + } + } + }, + "removedOwner": { + "type": "object", + "additionalProperties": false, + "required": [ + "ownerPath" + ], + "properties": { + "ownerPath": { + "type": "string", + "minLength": 1 + } + } + } + } +} diff --git a/schemas/callable-skeleton.schema.json b/schemas/callable-skeleton.schema.json new file mode 100644 index 0000000..e0791c6 --- /dev/null +++ b/schemas/callable-skeleton.schema.json @@ -0,0 +1,19 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://agent-semantic-protocols.dev/schemas/callable-skeleton.schema.json", + "title": "Callable Skeleton Payload", + "type": "object", + "additionalProperties": false, + "required": ["rootSelector", "rootNodeId", "callable", "nodes", "relations", "cost"], + "properties": { + "projectionKind": {"const": "callable-skeleton"}, + "rootSelector": {}, + "rootNodeId": {"type": "string", "minLength": 1}, + "callable": {"type": "object"}, + "nodes": {"type": "array"}, + "relations": {"type": "array"}, + "cost": {"type": "object"}, + "omissionReasons": {"type": "array"}, + "languageFacts": {"type": "object"} + } +} diff --git a/schemas/canonical-item-selector.v1.schema.json b/schemas/canonical-item-selector.v1.schema.json new file mode 100644 index 0000000..009af91 --- /dev/null +++ b/schemas/canonical-item-selector.v1.schema.json @@ -0,0 +1,45 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://agent-semantic-protocols.dev/schemas/canonical-item-selector.v1.schema.json", + "title": "Canonical item selector v1", + "type": "object", + "additionalProperties": false, + "required": [ + "schemaId", + "schemaVersion", + "languageId", + "kind", + "symbol", + "scopes", + "structuralSelector" + ], + "properties": { + "schemaId": { + "const": "asp.canonical-item-selector.v1" + }, + "schemaVersion": { + "const": "1" + }, + "languageId": { + "type": "string", + "minLength": 1 + }, + "kind": { + "type": "string", + "minLength": 1 + }, + "symbol": { + "type": "string", + "minLength": 1 + }, + "scopes": { + "$ref": "exact-definitions.v1.schema.json#/$defs/scopeList" + }, + "structuralSelector": { + "type": "string", + "description": "Canonical selector whose rightmost # separates the verbatim owner path from the item identity fragment.", + "minLength": 1 + } + }, + "$defs": {} +} diff --git a/schemas/canonical-language-item-identity.schema.json b/schemas/canonical-language-item-identity.schema.json new file mode 100644 index 0000000..c55b2da --- /dev/null +++ b/schemas/canonical-language-item-identity.schema.json @@ -0,0 +1,38 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://schemas.agent-semantic-protocols.dev/canonical-language-item-identity.schema.json", + "title": "Canonical Language Item Identity", + "type": "object", + "additionalProperties": false, + "required": [ + "schemaId", + "schemaVersion", + "languageId", + "kind", + "symbol", + "scopes" + ], + "properties": { + "schemaId": { + "const": "agent.semantic-protocols.canonical-language-item-identity" + }, + "schemaVersion": { + "const": "1" + }, + "languageId": { + "type": "string", + "minLength": 1 + }, + "kind": { + "type": "string", + "minLength": 1 + }, + "symbol": { + "type": "string", + "minLength": 1 + }, + "scopes": { + "$ref": "exact-definitions.v1.schema.json#/$defs/scopeList" + } + } +} diff --git a/schemas/exact-definitions.v1.schema.json b/schemas/exact-definitions.v1.schema.json new file mode 100644 index 0000000..5add1ac --- /dev/null +++ b/schemas/exact-definitions.v1.schema.json @@ -0,0 +1,26 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://agent-semantic-protocols.dev/schemas/exact-definitions.v1.schema.json", + "title": "Exact Shared Definitions v1", + "type": "object", + "$defs": { + "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 } + } + }, + "scopeList": { + "type": "array", + "items": { "$ref": "#/$defs/scope" } + } + } +} diff --git a/schemas/exact-structural-selector.v1.schema.json b/schemas/exact-structural-selector.v1.schema.json new file mode 100644 index 0000000..2a04816 --- /dev/null +++ b/schemas/exact-structural-selector.v1.schema.json @@ -0,0 +1,94 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://agent-semantic-protocols.dev/schemas/exact-structural-selector.v1.schema.json", + "title": "Exact Structural Selector v1", + "description": "A parser-owned, generation-bound selector for an item or a queryable structural descendant. Display locations are deliberately excluded from selector identity.", + "type": "object", + "additionalProperties": false, + "required": [ + "schemaId", + "schemaVersion", + "languageId", + "ownerPath", + "selector", + "generationIdentityDigest", + "parserIdentityDigest", + "queryPackDigest", + "rootItemSelector", + "segments" + ], + "properties": { + "schemaId": { + "const": "asp.exact-structural-selector.v1" + }, + "schemaVersion": { + "const": "1" + }, + "languageId": { + "type": "string", + "minLength": 1 + }, + "ownerPath": { + "type": "string", + "minLength": 1, + "pattern": "^[^/].*" + }, + "selector": { + "type": "string", + "minLength": 1, + "description": "The provider-minted selector accepted by exact query. Consumers must not construct it from display locations." + }, + "generationIdentityDigest": { + "$ref": "#/$defs/digest" + }, + "parserIdentityDigest": { + "$ref": "#/$defs/digest" + }, + "queryPackDigest": { + "$ref": "#/$defs/digest" + }, + "rootItemSelector": { + "$ref": "https://agent-semantic-protocols.dev/schemas/canonical-item-selector.v1.schema.json" + }, + "segments": { + "type": "array", + "description": "Parser-owned semantic descent from the root item. An empty array selects the root item.", + "items": { + "$ref": "#/$defs/segment" + } + } + }, + "$defs": { + "digest": { + "type": "string", + "pattern": "^[0-9a-f]{64}$" + }, + "segment": { + "type": "object", + "additionalProperties": false, + "required": [ + "relation", + "kind", + "identity" + ], + "properties": { + "relation": { + "type": "string", + "minLength": 1 + }, + "kind": { + "type": "string", + "minLength": 1 + }, + "identity": { + "type": "string", + "minLength": 1, + "description": "Opaque parser-owned identity within the selected generation; never a line or byte offset." + }, + "label": { + "type": "string" + } + } + } + } +} diff --git a/schemas/language-package-graph.schema.json b/schemas/language-package-graph.schema.json new file mode 100644 index 0000000..4b2f375 --- /dev/null +++ b/schemas/language-package-graph.schema.json @@ -0,0 +1,284 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://schemas.agent-semantic-protocols.dev/language-package-graph.schema.json", + "title": "Language Package Graph", + "type": "object", + "additionalProperties": false, + "required": [ + "schemaId", + "schemaVersion", + "languageId", + "providerId", + "projectEntry", + "parserId", + "manifests", + "lockfiles", + "packages", + "internalDependencyEdges", + "externalDependencies", + "unresolved" + ], + "properties": { + "schemaId": { + "const": "agent.semantic-protocols.language-package-graph" + }, + "schemaVersion": { + "const": "1" + }, + "languageId": { + "type": "string", + "minLength": 1 + }, + "providerId": { + "type": "string", + "minLength": 1 + }, + "projectEntry": { + "$ref": "#/$defs/path" + }, + "parserId": { + "type": "string", + "minLength": 1 + }, + "manifests": { + "$ref": "#/$defs/lockfileList" + }, + "lockfiles": { + "$ref": "#/$defs/lockfileList" + }, + "packages": { + "type": "array", + "items": { + "$ref": "#/$defs/package" + } + }, + "internalDependencyEdges": { + "type": "array", + "items": { + "$ref": "#/$defs/internalDependencyEdge" + } + }, + "externalDependencies": { + "type": "array", + "items": { + "$ref": "#/$defs/externalDependency" + } + }, + "unresolved": { + "type": "array", + "items": { + "$ref": "#/$defs/unresolved" + } + } + }, + "$defs": { + "lockfileList": { + "type": "array", + "items": { "$ref": "#/$defs/lockfile" } + }, + "path": { + "type": "string", + "minLength": 1 + }, + "lockfile": { + "type": "object", + "additionalProperties": false, + "required": [ + "path", + "kind", + "digest" + ], + "properties": { + "path": { + "$ref": "#/$defs/path" + }, + "kind": { + "type": "string", + "minLength": 1 + }, + "digest": { + "type": "string", + "minLength": 1 + } + } + }, + "target": { + "type": "object", + "additionalProperties": false, + "required": [ + "targetId", + "kind", + "name", + "explicit", + "sourceRoots", + "entrypoints", + "generatedRoots" + ], + "properties": { + "targetId": { + "type": "string", + "minLength": 1 + }, + "kind": { + "type": "string", + "minLength": 1 + }, + "name": { + "type": "string", + "minLength": 1 + }, + "explicit": { + "type": "boolean" + }, + "sourceRoots": { + "type": "array", + "items": { + "$ref": "#/$defs/path" + } + }, + "entrypoints": { + "type": "array", + "items": { + "$ref": "#/$defs/path" + } + }, + "generatedRoots": { + "type": "array", + "items": { + "$ref": "#/$defs/path" + } + } + } + }, + "package": { + "type": "object", + "additionalProperties": false, + "required": [ + "packageId", + "name", + "manifestPath", + "root", + "workspaceMember", + "targets" + ], + "properties": { + "packageId": { + "type": "string", + "minLength": 1 + }, + "name": { + "type": "string", + "minLength": 1 + }, + "version": { + "type": "string", + "minLength": 1 + }, + "manifestPath": { + "$ref": "#/$defs/path" + }, + "root": { + "$ref": "#/$defs/path" + }, + "workspaceMember": { + "type": "boolean" + }, + "targets": { + "type": "array", + "items": { + "$ref": "#/$defs/target" + } + } + } + }, + "internalDependencyEdge": { + "type": "object", + "additionalProperties": false, + "required": [ + "fromPackageId", + "toPackageId", + "kind" + ], + "properties": { + "fromPackageId": { + "type": "string", + "minLength": 1 + }, + "toPackageId": { + "type": "string", + "minLength": 1 + }, + "kind": { + "enum": [ + "normal", + "build", + "dev", + "path", + "workspace" + ] + } + } + }, + "externalDependency": { + "type": "object", + "additionalProperties": false, + "required": [ + "dependencyId", + "name", + "kind" + ], + "properties": { + "dependencyId": { + "type": "string", + "minLength": 1 + }, + "name": { + "type": "string", + "minLength": 1 + }, + "kind": { + "enum": [ + "normal", + "build", + "dev" + ] + }, + "requested": { + "type": "string", + "minLength": 1 + }, + "resolved": { + "type": "string", + "minLength": 1 + } + } + }, + "unresolved": { + "type": "object", + "additionalProperties": false, + "required": [ + "state", + "path", + "reasonKind" + ], + "properties": { + "state": { + "enum": [ + "manifest-missing", + "manifest-invalid", + "member-missing", + "target-source-missing", + "unsupported-declaration" + ] + }, + "path": { + "$ref": "#/$defs/path" + }, + "reasonKind": { + "type": "string", + "minLength": 1 + } + } + } + } +} diff --git a/schemas/language-schema-bundle-receipt.schema.json b/schemas/language-schema-bundle-receipt.schema.json new file mode 100644 index 0000000..a14b363 --- /dev/null +++ b/schemas/language-schema-bundle-receipt.schema.json @@ -0,0 +1,38 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://schemas.agent-semantic-protocols.dev/language-schema-bundle-receipt.schema.json", + "title": "ASP Language Schema Bundle Receipt", + "type": "object", + "additionalProperties": false, + "required": [ + "schemaId", + "schemaVersion", + "languageId", + "profileDigest", + "bundleDigest", + "schemas" + ], + "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" } + } + } + } + }, + "$defs": { + "digest": { "type": "string", "pattern": "^(blake3-256|sha256):[0-9a-f]{64}$" } + } +} diff --git a/schemas/project-resolution.schema.json b/schemas/project-resolution.schema.json new file mode 100644 index 0000000..548b2d9 --- /dev/null +++ b/schemas/project-resolution.schema.json @@ -0,0 +1,59 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://schemas.agent-semantic-protocols.dev/project-resolution.schema.json", + "title": "Project Scope", + "description": "One provider-owned package-manager resolution receipt rooted at one registered entry inside a Git-worktree workspace. The receipt contributes to that workspace generation's source scope; it does not define a child project, a standalone project scope, or any identity.", + "type": "object", + "additionalProperties": false, + "required": ["schemaId", "schemaVersion", "state", "completeness", "languageId", "providerId", "parserId", "candidateGenerationDigest", "projectEntry", "packageGraph", "sourceScopes", "conflicts", "metrics"], + "properties": { + "schemaId": { "const": "agent.semantic-protocols.project-resolution" }, + "schemaVersion": { "const": "1" }, + "state": { "const": "resolved" }, + "completeness": { "enum": ["exact", "complete", "partial"] }, + "languageId": { "type": "string", "minLength": 1 }, + "providerId": { "type": "string", "minLength": 1 }, + "parserId": { "type": "string", "minLength": 1 }, + "candidateGenerationDigest": { "type": "string", "minLength": 1 }, + "projectEntry": { + "description": "Project entry relative to the containing Git-worktree workspace or to the explicitly bounded non-Git candidate base.", + "$ref": "#/$defs/path" + }, + "packageGraph": { "$ref": "https://schemas.agent-semantic-protocols.dev/language-package-graph.schema.json" }, + "sourceScopes": { + "description": "Package and target scopes derived by the provider from this project entry; not workspace identities.", + "type": "array", + "items": { "$ref": "https://schemas.agent-semantic-protocols.dev/resolved-source-scope.v1.schema.json" } + }, + "conflicts": { "type": "array", "items": { "$ref": "#/$defs/conflict" } }, + "metrics": { "$ref": "#/$defs/metrics" } + }, + "$defs": { + "path": { "type": "string", "minLength": 1, "pattern": "^(?!/)(?!.*(?:^|/)\\.\\.(?:/|$)).+$" }, + "conflict": { + "type": "object", + "additionalProperties": false, + "required": ["path", "includeAuthority", "excludeAuthority", "reasonKind"], + "properties": { + "path": { "$ref": "#/$defs/path" }, + "includeAuthority": { "type": "string", "minLength": 1 }, + "excludeAuthority": { "type": "string", "minLength": 1 }, + "reasonKind": { "type": "string", "minLength": 1 } + } + }, + "metrics": { + "type": "object", + "additionalProperties": false, + "required": ["parsedManifestCount", "parsedLockfileCount", "affectedPackageCount", "fullWorkspaceReads", "fullManifestReparses", "dbOpens", "elapsedMicros"], + "properties": { + "parsedManifestCount": { "type": "integer", "minimum": 0 }, + "parsedLockfileCount": { "type": "integer", "minimum": 0 }, + "affectedPackageCount": { "type": "integer", "minimum": 0 }, + "fullWorkspaceReads": { "const": 0 }, + "fullManifestReparses": { "const": 0 }, + "dbOpens": { "const": 0 }, + "elapsedMicros": { "type": "integer", "minimum": 0 } + } + } + } +} diff --git a/schemas/provider-definitions.v1.schema.json b/schemas/provider-definitions.v1.schema.json new file mode 100644 index 0000000..5dffd90 --- /dev/null +++ b/schemas/provider-definitions.v1.schema.json @@ -0,0 +1,36 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://agent-semantic-protocols.local/schemas/provider-definitions.v1.schema.json", + "title": "Provider Family Definitions v1", + "description": "Shared definitions owned by the provider schema family.", + "$defs": { + "providerReference": { + "type": "object", + "additionalProperties": false, + "required": ["languageId", "providerId"], + "properties": { + "languageId": { "type": "string", "minLength": 1 }, + "providerId": { "type": "string", "minLength": 1 } + } + }, + "relationEndpoint": { + "type": "object", + "additionalProperties": false, + "required": ["kind", "id"], + "properties": { + "kind": {"type": "string", "minLength": 1}, + "id": {"type": "string", "minLength": 1} + } + }, + "relation": { + "type": "object", + "additionalProperties": false, + "required": ["from", "kind", "to"], + "properties": { + "from": {"$ref": "#/$defs/relationEndpoint"}, + "kind": {"type": "string", "minLength": 1}, + "to": {"$ref": "#/$defs/relationEndpoint"} + } + } + } +} diff --git a/schemas/provider-document-resolution-descriptor.v1.schema.json b/schemas/provider-document-resolution-descriptor.v1.schema.json new file mode 100644 index 0000000..3ad0039 --- /dev/null +++ b/schemas/provider-document-resolution-descriptor.v1.schema.json @@ -0,0 +1,42 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://schemas.agent-semantic-protocols.dev/provider-document-resolution-descriptor.v1.schema.json", + "title": "Provider Document Resolution Descriptor v1", + "type": "object", + "additionalProperties": false, + "required": [ + "schemaId", + "schemaVersion", + "capabilityId", + "extensions", + "parserId", + "supportsGitCandidates" + ], + "properties": { + "schemaId": { + "const": "agent.semantic-protocols.provider-document-resolution-descriptor" + }, + "schemaVersion": { + "const": "1" + }, + "capabilityId": { + "const": "document-resolution" + }, + "extensions": { + "type": "array", + "items": { + "type": "string", + "pattern": "^\\.[A-Za-z0-9._+-]+$" + }, + "minItems": 1, + "uniqueItems": true + }, + "parserId": { + "type": "string", + "minLength": 1 + }, + "supportsGitCandidates": { + "const": true + } + } +} diff --git a/schemas/provider-language-projection-batch-request.schema.json b/schemas/provider-language-projection-batch-request.schema.json new file mode 100644 index 0000000..c5ea80d --- /dev/null +++ b/schemas/provider-language-projection-batch-request.schema.json @@ -0,0 +1,92 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://schemas.agent-semantic-protocols.dev/provider-language-projection-batch-request.schema.json", + "title": "Provider Language Projection Batch Request", + "type": "object", + "additionalProperties": false, + "required": [ + "schemaId", + "schemaVersion", + "languageId", + "providerId", + "workspaceIdentity", + "generationRootDigest", + "parserIdentityDigest", + "queryPackDigest", + "owners" + ], + "properties": { + "schemaId": { + "const": "agent.semantic-protocols.provider-language-projection-batch-request" + }, + "schemaVersion": { + "const": "1" + }, + "languageId": { + "type": "string", + "minLength": 1 + }, + "providerId": { + "type": "string", + "minLength": 1 + }, + "workspaceIdentity": { + "type": "string", + "minLength": 1 + }, + "generationRootDigest": { + "type": "string", + "minLength": 32 + }, + "parserIdentityDigest": { + "type": "string", + "minLength": 32 + }, + "queryPackDigest": { + "type": "string", + "minLength": 32 + }, + "baseGenerationRootDigest": { + "type": "string", + "minLength": 32 + }, + "owners": { + "type": "array", + "minItems": 1, + "maxItems": 32, + "items": {"$ref": "#/$defs/sourceOwner"} + }, + "auxiliaryOwners": { + "description": "Provider-declared immutable config owners used only to restore parser and module-resolution context. Providers must not emit searchable items for these owners.", + "type": "array", + "maxItems": 128, + "items": {"$ref": "#/$defs/sourceOwner"} + } + }, + "$defs": { + "sourceOwner": { + "type": "object", + "additionalProperties": false, + "required": ["ownerPath", "sourceLeafDigest", "sourceEncoding"], + "oneOf": [ + { + "properties": {"sourceEncoding": {"const": "utf8"}}, + "required": ["sourceText"], + "not": {"required": ["sourceBytesBase64"]} + }, + { + "properties": {"sourceEncoding": {"const": "base64"}}, + "required": ["sourceBytesBase64"], + "not": {"required": ["sourceText"]} + } + ], + "properties": { + "ownerPath": {"type": "string", "minLength": 1}, + "sourceLeafDigest": {"type": "string", "minLength": 32}, + "sourceText": {"type": "string"}, + "sourceEncoding": {"enum": ["utf8", "base64"]}, + "sourceBytesBase64": {"type": "string", "contentEncoding": "base64"} + } + } + } +} diff --git a/schemas/provider-language-projection-batch-response.schema.json b/schemas/provider-language-projection-batch-response.schema.json new file mode 100644 index 0000000..ad61a71 --- /dev/null +++ b/schemas/provider-language-projection-batch-response.schema.json @@ -0,0 +1,146 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://schemas.agent-semantic-protocols.dev/provider-language-projection-batch-response.schema.json", + "title": "Provider Language Projection Batch Response", + "type": "object", + "additionalProperties": false, + "required": [ + "schemaId", + "schemaVersion", + "languageId", + "providerId", + "generationRootDigest", + "owners" + ], + "properties": { + "schemaId": { + "const": "agent.semantic-protocols.provider-language-projection-batch-response" + }, + "schemaVersion": { + "const": "1" + }, + "languageId": { + "type": "string", + "minLength": 1 + }, + "providerId": { + "type": "string", + "minLength": 1 + }, + "generationRootDigest": { + "type": "string", + "minLength": 1 + }, + "owners": { + "type": "array", + "items": { + "$ref": "#/$defs/projectedOwner" + } + } + }, + "$defs": { + "projectedOwner": { + "type": "object", + "additionalProperties": false, + "required": [ + "ownerPath", + "sourceLeafDigest", + "items", + "relations" + ], + "properties": { + "ownerPath": { + "type": "string", + "minLength": 1 + }, + "sourceLeafDigest": { + "type": "string", + "minLength": 1 + }, + "items": { + "type": "array", + "items": { + "$ref": "#/$defs/projectedItem" + } + }, + "relations": { + "type": "array", + "items": { + "$ref": "#/$defs/relation" + } + } + } + }, + "projectedItem": { + "type": "object", + "additionalProperties": false, + "required": [ + "itemId", + "ownerId", + "kind", + "name", + "selector", + "sourceByteStart", + "sourceByteEnd", + "identity", + "projections" + ], + "properties": { + "itemId": { + "type": "string", + "minLength": 1 + }, + "ownerId": { + "type": "string", + "minLength": 1 + }, + "kind": { + "type": "string", + "minLength": 1 + }, + "name": { + "type": "string", + "minLength": 1 + }, + "selector": { + "type": "string", + "minLength": 1 + }, + "sourceByteStart": { + "type": "integer", + "minimum": 0 + }, + "sourceByteEnd": { + "type": "integer", + "minimum": 0 + }, + "identity": { + "$ref": "canonical-language-item-identity.schema.json" + }, + "projections": { + "type": "array", + "items": { + "$ref": "#/$defs/derivedProjection" + } + } + } + }, + "derivedProjection": { + "type": "object", + "additionalProperties": false, + "required": ["projectionKind", "payload"], + "properties": { + "projectionKind": { + "type": "string", + "minLength": 1 + }, + "payload": { + "type": "object" + } + } + }, + "relation": { + "$ref": "provider-definitions.v1.schema.json#/$defs/relation" + } + } +} diff --git a/schemas/provider-manifest.schema.json b/schemas/provider-manifest.schema.json new file mode 100644 index 0000000..6fb51d3 --- /dev/null +++ b/schemas/provider-manifest.schema.json @@ -0,0 +1,460 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://tao3k.github.io/agent-semantic-protocols/schemas/provider-manifest.schema.json", + "title": "ASP Provider Manifest", + "type": "object", + "additionalProperties": false, + "required": [ + "schemaId", + "schemaVersion", + "protocolId", + "protocolVersion", + "manifestId", + "manifestVersion", + "languageId", + "providerId", + "namespace", + "binary", + "runtimeContract", + "development", + "searchCapabilities", + "queryPackDescriptor", + "policy", + "routeBindings" + ], + "properties": { + "schemaId": { + "const": "agent.semantic-protocols.hook.provider-manifest" + }, + "schemaVersion": { + "const": "1" + }, + "protocolId": { + "type": "string", + "minLength": 1 + }, + "protocolVersion": { + "type": "string", + "minLength": 1 + }, + "manifestId": { + "type": "string", + "minLength": 1 + }, + "manifestVersion": { + "type": "string", + "minLength": 1 + }, + "languageId": { + "type": "string", + "minLength": 1 + }, + "providerId": { + "type": "string", + "pattern": "^asp-[a-z0-9][a-z0-9-]*$" + }, + "namespace": { + "type": "string", + "minLength": 1 + }, + "binary": { + "type": "string", + "minLength": 1 + }, + "runtimeContract": { + "$ref": "https://schemas.agent-semantic-protocols.dev/provider-runtime-contract-descriptor.schema.json" + }, + "execution": { + "$ref": "#/$defs/providerExecution" + }, + "development": { + "$ref": "#/$defs/providerDevelopmentDescriptor" + }, + "projectResolution": { + "$ref": "https://schemas.agent-semantic-protocols.dev/provider-project-resolution-descriptor.schema.json" + }, + "documentResolution": { + "$ref": "https://schemas.agent-semantic-protocols.dev/provider-document-resolution-descriptor.v1.schema.json" + }, + "searchCapabilities": { + "$ref": "#/$defs/providerSearchCapabilities" + }, + "semanticFactsDescriptor": { + "$ref": "#/$defs/providerSemanticFactsDescriptor" + }, + "queryPackDescriptor": { + "$ref": "https://schemas.agent-semantic-protocols.dev/provider-query-pack-descriptor.schema.json" + }, + "policy": { + "$ref": "#/$defs/hookPolicy" + }, + "routeBindings": { + "$ref": "#/$defs/hookRouteBindings" + } + }, + "allOf": [ + { + "$ref": "#/$defs/canonicalProviderIdentity" + } + ], +"anyOf": [ + { + "required": [ + "projectResolution" + ] + }, + { + "required": [ + "documentResolution" + ] + } +], + "$defs": { + "canonicalProviderIdentity": { + "oneOf": [ + { + "properties": { + "languageId": {"const": "gerbil-scheme"}, + "providerId": {"const": "asp-gerbil-scheme"} + } + }, + { + "properties": { + "languageId": {"const": "julia"}, + "providerId": {"const": "asp-julia"} + } + }, + { + "properties": { + "languageId": {"const": "md"}, + "providerId": {"const": "asp-md"} + } + }, + { + "properties": { + "languageId": {"const": "org"}, + "providerId": {"const": "asp-org"} + } + }, + { + "properties": { + "languageId": {"const": "python"}, + "providerId": {"const": "asp-python"} + } + }, + { + "properties": { + "languageId": {"const": "rust"}, + "providerId": {"const": "asp-rust"} + } + }, + { + "properties": { + "languageId": {"const": "typescript"}, + "providerId": {"const": "asp-typescript"} + } + } + ] + }, + "stringArray": { + "type": "array", + "items": { + "type": "string" + } + }, + "providerExecution": { + "enum": [ + "external-process", + "embedded" + ] + }, + "providerDevelopmentDescriptor": { + "type": "object", + "additionalProperties": false, + "required": [ + "schemaId", + "schemaVersion", + "sourceRoot", + "buildBinding", + "artifactDomain" + ], + "properties": { + "schemaId": { + "const": "agent.semantic-protocols.provider-development-descriptor" + }, + "schemaVersion": { + "const": "1" + }, + "sourceRoot": { + "type": "string", + "minLength": 1, + "pattern": "^[^/].*$", + "not": { + "pattern": "(^|/)\\.\\.(/|$)" + } + }, + "buildBinding": { + "enum": [ + "root-development-installer-v1", + "provider-workspace-install-v1" + ] + }, + "workspaceInstall": { + "type": "string", + "minLength": 1, + "pattern": "^[^/].*\\.json$", + "not": { + "pattern": "(^|/)\\.\\.(/|$)" + } + }, + "artifactDomain": { + "enum": [ + "checkout", + "state-home-provider-staging" + ] + } + }, + "allOf": [ + { + "if": { + "properties": { + "buildBinding": { + "const": "provider-workspace-install-v1" + } + } + }, + "then": { + "required": [ + "workspaceInstall" + ] + } + } + ] + }, + "providerSearchCapabilities": { + "type": "object", + "additionalProperties": false, + "required": [ + "ownerItems", + "semanticFacts", + "dependencyTopology", + "dependencyTopologyMetadata" + ], + "properties": { + "ownerItems": { + "type": "boolean" + }, + "semanticFacts": { + "type": "boolean" + }, + "dependencyTopology": { + "type": "boolean" + }, + "dependencyTopologyMetadata": { + "type": "boolean" + }, + "sourceSnapshot": { + "$ref": "#/$defs/providerSourceSnapshotDescriptor" + } + } + }, + "providerSourceSnapshotDescriptor": { + "type": "object", + "additionalProperties": false, + "required": [ + "descriptorId", + "descriptorVersion", + "languageId", + "packetSchemaId", + "exactSourcePacketSchemaId", + "sourceSnapshotEnvelopeSchemaId", + "derivedArtifactEvidenceSchemaId", + "algorithm", + "authority", + "exactSelectorResolution", + "overlayMode" + ], + "properties": { + "descriptorId": { + "type": "string", + "minLength": 1 + }, + "descriptorVersion": { + "type": "string", + "minLength": 1 + }, + "languageId": { + "type": "string", + "minLength": 1 + }, + "packetSchemaId": { + "type": "string", + "minLength": 1 + }, + "exactSourcePacketSchemaId": { + "type": "string", + "minLength": 1 + }, + "sourceSnapshotEnvelopeSchemaId": { + "type": "string", + "minLength": 1 + }, + "derivedArtifactEvidenceSchemaId": { + "type": "string", + "minLength": 1 + }, + "canonicalItemSelectorSchemaId": { + "type": "string", + "minLength": 1 + }, + "algorithm": { + "type": "string", + "minLength": 1 + }, + "authority": { + "type": "string", + "minLength": 1 + }, + "exactSelectorResolution": { + "type": "string", + "minLength": 1 + }, + "overlayMode": { + "type": "string", + "minLength": 1 + } + } + }, + "providerSemanticFactsDescriptor": { + "type": "object", + "additionalProperties": false, + "required": [ + "descriptorId", + "descriptorVersion", + "packetSchemaIds", + "factKinds", + "intentAxes" + ], + "properties": { + "descriptorId": { + "type": "string", + "minLength": 1 + }, + "descriptorVersion": { + "type": "string", + "minLength": 1 + }, + "packetSchemaIds": { + "$ref": "#/$defs/stringArray" + }, + "factKinds": { + "$ref": "#/$defs/stringArray" + }, + "intentAxes": { + "type": "array", + "items": { + "type": "object", + "additionalProperties": false, + "required": [ + "axis", + "terms" + ], + "properties": { + "axis": { + "type": "string", + "minLength": 1 + }, + "terms": { + "$ref": "#/$defs/stringArray" + }, + "roles": { + "$ref": "#/$defs/stringArray" + } + } + } + } + } + }, + "hookPolicy": { + "type": "object", + "additionalProperties": false, + "required": [ + "directSourceRead", + "bulkSourceDump", + "rawSourceSearch", + "agentSearchJson" + ], + "properties": { + "directSourceRead": { + "$ref": "#/$defs/actionPolicy" + }, + "bulkSourceDump": { + "$ref": "#/$defs/actionPolicy" + }, + "rawSourceSearch": { + "$ref": "#/$defs/actionPolicy" + }, + "agentSearchJson": { + "$ref": "#/$defs/actionPolicy" + } + } + }, + "actionPolicy": { + "enum": [ + "block", + "allow", + "advisory" + ] + }, + "methodId": { + "type": "string", + "pattern": "^(?:guide|query|(search|query|check|proof|review|verification|evidence|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" + }, + "dependencyTopologyMetadata": { + "$ref": "#/$defs/methodId" + }, + "exportIndex": { + "$ref": "#/$defs/methodId" + }, + "guide": { + "$ref": "#/$defs/methodId" + } + } + } + } +} diff --git a/schemas/provider-method-argument-projection.v1.schema.json b/schemas/provider-method-argument-projection.v1.schema.json new file mode 100644 index 0000000..76dc04b --- /dev/null +++ b/schemas/provider-method-argument-projection.v1.schema.json @@ -0,0 +1,154 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://tao3k.github.io/agent-semantic-protocols/schemas/provider-method-argument-projection.v1.schema.json", + "title": "Provider Method Argument Projection v1", + "description": "Closed, language-neutral projection from facade fields to deterministic provider argv. Tokens are data; they are never evaluated as shell, format, or template source.", + "type": "object", + "additionalProperties": false, + "required": [ + "schemaVersion", + "tokens" + ], + "properties": { + "schemaVersion": { + "const": "1" + }, + "tokens": { + "type": "array", + "minItems": 1, + "items": { + "$ref": "#/$defs/token" + } + } + }, + "$defs": { + "token": { + "oneOf": [ + { + "$ref": "#/$defs/literalToken" + }, + { + "$ref": "#/$defs/querySlot" + }, + { + "$ref": "#/$defs/workspaceSlot" + }, + { + "$ref": "#/$defs/presentationSlot" + }, + { + "$ref": "#/$defs/ownerSlot" + } + ] + }, + "literalToken": { + "type": "object", + "additionalProperties": false, + "required": [ + "kind", + "value" + ], + "properties": { + "kind": { + "const": "literal" + }, + "value": { + "type": "string", + "minLength": 1 + } + } + }, + "querySlot": { + "$ref": "#/$defs/stringSlot", + "properties": { + "name": { + "const": "query" + } + } + }, + "workspaceSlot": { + "$ref": "#/$defs/pathSlot", + "properties": { + "name": { + "const": "workspace" + } + } + }, + "presentationSlot": { + "$ref": "#/$defs/presentationValueSlot", + "properties": { + "name": { + "const": "presentation" + } + } + }, + "ownerSlot": { + "$ref": "#/$defs/pathSlot", + "properties": { + "name": { + "const": "owner" + } + } + }, + "stringSlot": { + "type": "object", + "additionalProperties": false, + "required": [ + "kind", + "name", + "valueType" + ], + "properties": { + "kind": { + "const": "slot" + }, + "name": { + "type": "string" + }, + "valueType": { + "const": "string" + } + } + }, + "pathSlot": { + "type": "object", + "additionalProperties": false, + "required": [ + "kind", + "name", + "valueType" + ], + "properties": { + "kind": { + "const": "slot" + }, + "name": { + "type": "string" + }, + "valueType": { + "const": "path" + } + } + }, + "presentationValueSlot": { + "type": "object", + "additionalProperties": false, + "required": [ + "kind", + "name", + "valueType" + ], + "properties": { + "kind": { + "const": "slot" + }, + "name": { + "type": "string" + }, + "valueType": { + "const": "presentation" + } + } + } + } +} diff --git a/schemas/provider-native-exact-request.v1.schema.json b/schemas/provider-native-exact-request.v1.schema.json new file mode 100644 index 0000000..f0371b2 --- /dev/null +++ b/schemas/provider-native-exact-request.v1.schema.json @@ -0,0 +1,83 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://agent-semantic-protocols.dev/schemas/provider-native-exact-request.v1.schema.json", + "title": "ASP Provider Native Exact Request v1", + "type": "object", + "additionalProperties": false, + "required": [ + "schemaId", + "schemaVersion", + "languageId", + "providerId", + "projectionKind", + "structuralSelector", + "ownerPath", + "generationIdentityDigest", + "parserIdentityDigest", + "queryPackDigest", + "sourceDigest", + "sourceByteLength", + "sourceEncoding", + "sourceBytesBase64", + "transport" + ], + "properties": { + "schemaId": { + "const": "agent.semantic-protocols.provider-native-exact-request" + }, + "schemaVersion": { + "const": "1" + }, + "languageId": { + "type": "string", + "minLength": 1 + }, + "providerId": { + "type": "string", + "minLength": 1 + }, + "projectionKind": { + "enum": [ + "source", + "callable-skeleton" + ] + }, + "structuralSelector": { + "type": "string", + "minLength": 1 + }, + "ownerPath": { + "type": "string", + "minLength": 1 + }, + "generationIdentityDigest": { + "type": "string", + "pattern": "^[0-9a-f]{64}$" + }, + "parserIdentityDigest": { + "type": "string", + "pattern": "^[0-9a-f]{64}$" + }, + "queryPackDigest": { + "type": "string", + "pattern": "^[0-9a-f]{64}$" + }, + "sourceDigest": { + "type": "string", + "pattern": "^[0-9a-f]{64}$" + }, + "sourceByteLength": { + "type": "integer", + "minimum": 0 + }, + "sourceEncoding": { + "const": "base64" + }, + "sourceBytesBase64": { + "type": "string" + }, + "transport": { + "const": "stdin-json" + } + } +} diff --git a/schemas/provider-native-exact-response.v1.schema.json b/schemas/provider-native-exact-response.v1.schema.json new file mode 100644 index 0000000..e817db8 --- /dev/null +++ b/schemas/provider-native-exact-response.v1.schema.json @@ -0,0 +1,399 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://agent-semantic-protocols.dev/schemas/provider-native-exact-response.v1.schema.json", + "title": "ASP Provider Native Exact Response v1", + "type": "object", + "additionalProperties": false, + "required": [ + "schemaId", + "schemaVersion", + "languageId", + "providerId", + "ownerPath", + "requestedStructuralSelector" + ], + "properties": { + "schemaId": { + "const": "agent.semantic-protocols.provider-native-exact-projection" + }, + "schemaVersion": { + "const": "1" + }, + "languageId": { + "type": "string", + "minLength": 1 + }, + "providerId": { + "type": "string", + "minLength": 1 + }, + "ownerPath": { + "type": "string", + "minLength": 1 + }, + "requestedStructuralSelector": { + "type": "string", + "minLength": 1 + }, + "resolutionState": { + "enum": [ + "resolved", + "owner-missing", + "item-missing", + "identity-incomplete", + "selector-stale", + "kind-mismatch", + "ambiguous" + ] + }, + "reasonKind": { + "type": "string", + "minLength": 1 + }, + "activeGenerationDigest": { + "type": "string", + "pattern": "^blake3-256:[0-9a-f]{64}$" + }, + "rootDigest": { + "type": "string", + "pattern": "^[0-9a-f]{64}$" + }, + "itemKind": { + "type": "string", + "minLength": 1 + }, + "itemName": { + "type": "string", + "minLength": 1 + }, + "candidates": { + "type": "array", + "items": { + "type": "string", + "minLength": 1 + }, + "uniqueItems": true + }, + "actualKinds": { + "type": "array", + "items": { + "type": "string", + "minLength": 1 + }, + "uniqueItems": true + }, + "recommendedNext": { + "type": "object", + "additionalProperties": false, + "required": [ + "command" + ], + "properties": { + "command": { + "type": "string", + "minLength": 1 + } + } + }, + "structuralSelector": { + "type": "string", + "minLength": 1 + }, + "projectionMode": { + "enum": [ + "source", + "callable-skeleton" + ] + }, + "normalizedParserFacts": { + "type": "object" + }, + "projectionText": { + "type": "string" + }, + "projectionPayload": { + "$ref": "https://agent-semantic-protocols.dev/schemas/callable-skeleton.schema.json" + }, + "sourceContentDigest": { + "type": "string", + "pattern": "^[0-9a-f]{64}$" + }, + "sourceByteStart": { + "type": "integer", + "minimum": 0 + }, + "sourceByteEnd": { + "type": "integer", + "minimum": 0 + } + }, + "oneOf": [ + { + "required": [ + "structuralSelector", + "projectionMode", + "normalizedParserFacts", + "sourceContentDigest", + "sourceByteStart", + "sourceByteEnd" + ], + "properties": { + "resolutionState": { + "const": "resolved" + } + } + }, + { + "required": [ + "resolutionState", + "reasonKind", + "activeGenerationDigest", + "rootDigest", + "itemKind", + "itemName", + "candidates", + "actualKinds" + ], + "properties": { + "resolutionState": { + "enum": [ + "owner-missing", + "item-missing", + "identity-incomplete", + "selector-stale", + "kind-mismatch", + "ambiguous" + ] + } + }, + "not": { + "anyOf": [ + { + "required": [ + "projectionText" + ] + }, + { + "required": [ + "projectionPayload" + ] + } + ] + } + } + ], + "allOf": [ + { + "if": { + "properties": { + "resolutionState": { + "const": "item-missing" + } + }, + "required": [ + "resolutionState" + ] + }, + "then": { + "not": { + "required": [ + "recommendedNext" + ] + } + } + }, + { + "if": { + "properties": { + "resolutionState": { + "const": "identity-incomplete" + } + }, + "required": [ + "resolutionState" + ] + }, + "then": { + "properties": { + "reasonKind": { + "const": "canonical-item-scope-required" + } + } + } + }, + { + "if": { + "properties": { + "resolutionState": { + "const": "owner-missing" + } + }, + "required": [ + "resolutionState" + ] + }, + "then": { + "properties": { + "reasonKind": { + "const": "owner-not-in-workspace" + } + } + } + }, + { + "if": { + "properties": { + "resolutionState": { + "const": "item-missing" + } + }, + "required": [ + "resolutionState" + ] + }, + "then": { + "properties": { + "reasonKind": { + "const": "item-not-in-live-owner" + } + } + } + }, + { + "if": { + "properties": { + "resolutionState": { + "const": "kind-mismatch" + } + }, + "required": [ + "resolutionState" + ] + }, + "then": { + "properties": { + "reasonKind": { + "const": "owner-item-kind-mismatch" + } + } + } + }, + { + "if": { + "properties": { + "resolutionState": { + "const": "ambiguous" + } + }, + "required": [ + "resolutionState" + ] + }, + "then": { + "properties": { + "reasonKind": { + "enum": [ + "multiple-owner-items", + "multiple-snapshot-items" + ] + } + } + } + }, + { + "if": { + "properties": { + "projectionMode": { + "const": "source" + } + }, + "required": [ + "projectionMode" + ] + }, + "then": { + "required": [ + "projectionText" + ], + "not": { + "required": [ + "projectionPayload" + ] + } + } + }, + { + "if": { + "properties": { + "projectionMode": { + "const": "callable-skeleton" + } + }, + "required": [ + "projectionMode" + ] + }, + "then": { + "required": [ + "projectionPayload" + ], + "not": { + "required": [ + "projectionText" + ] + } + } + }, + { + "if": { + "anyOf": [ + { + "properties": { + "resolutionState": { + "const": "selector-stale" + } + }, + "required": [ + "resolutionState" + ] + }, + { + "properties": { + "resolutionState": { + "const": "owner-missing" + } + }, + "required": [ + "resolutionState" + ] + } + ] + }, + "then": { + "required": [ + "activeGenerationDigest", + "rootDigest" + ] + } + }, + { + "if": { + "properties": { + "resolutionState": { + "const": "selector-stale" + } + }, + "required": [ + "resolutionState" + ] + }, + "then": { + "properties": { + "reasonKind": { + "const": "selector-not-in-active-generation" + } + } + } + } + ] +} diff --git a/schemas/provider-project-resolution-descriptor.schema.json b/schemas/provider-project-resolution-descriptor.schema.json new file mode 100644 index 0000000..a72c127 --- /dev/null +++ b/schemas/provider-project-resolution-descriptor.schema.json @@ -0,0 +1,20 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://schemas.agent-semantic-protocols.dev/provider-project-resolution-descriptor.schema.json", + "title": "Provider Project Scope Descriptor", + "type": "object", + "additionalProperties": false, + "required": ["schemaId", "schemaVersion", "capabilityId", "entryMarkers", "sourceExtensions", "manifestKinds", "lockfileKinds", "parserId", "packageGraphSchema", "projectResolutionSchema"], + "properties": { + "schemaId": { "const": "agent.semantic-protocols.provider-project-resolution-descriptor" }, + "schemaVersion": { "const": "1" }, + "capabilityId": { "const": "project-resolution" }, + "entryMarkers": { "type": "array", "description": "Workspace-root-relative project entry paths matched exactly; nested basename matches do not activate the provider.", "items": { "type": "string", "minLength": 1 }, "minItems": 1, "uniqueItems": true }, + "sourceExtensions": { "type": "array", "items": { "type": "string", "pattern": "^\\.[A-Za-z0-9]+$" }, "minItems": 1, "uniqueItems": true }, + "manifestKinds": { "type": "array", "items": { "type": "string", "minLength": 1 }, "minItems": 1, "uniqueItems": true }, + "lockfileKinds": { "type": "array", "items": { "type": "string", "minLength": 1 }, "uniqueItems": true }, + "parserId": { "type": "string", "minLength": 1 }, + "packageGraphSchema": { "const": "https://schemas.agent-semantic-protocols.dev/language-package-graph.schema.json" }, + "projectResolutionSchema": { "const": "https://schemas.agent-semantic-protocols.dev/project-resolution.schema.json" } + } +} diff --git a/schemas/provider-project-resolution-request.schema.json b/schemas/provider-project-resolution-request.schema.json new file mode 100644 index 0000000..865ba36 --- /dev/null +++ b/schemas/provider-project-resolution-request.schema.json @@ -0,0 +1,60 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://schemas.agent-semantic-protocols.dev/provider-project-resolution-request.schema.json", + "title": "Provider Project Scope Request", + "type": "object", + "additionalProperties": false, + "required": ["schemaId", "schemaVersion", "languageId", "providerId", "candidateBase", "candidateGeneration", "collectionScope", "candidatePaths", "policyExclusions"], + "properties": { + "schemaId": { "const": "agent.semantic-protocols.provider-project-resolution-request" }, + "schemaVersion": { "const": "1" }, + "languageId": { "type": "string", "minLength": 1 }, + "providerId": { "type": "string", "minLength": 1 }, + "candidateBase": { "const": "." }, + "candidateGeneration": { "$ref": "#/$defs/candidateGeneration" }, + "collectionScope": { + "oneOf": [ + { + "type": "object", + "additionalProperties": false, + "required": ["kind"], + "properties": { "kind": { "const": "complete-generation" } } + }, + { + "type": "object", + "additionalProperties": false, + "required": ["kind", "ownerPaths"], + "properties": { + "kind": { "const": "explicit-owners" }, + "ownerPaths": { "type": "array", "items": { "$ref": "#/$defs/path" }, "uniqueItems": true, "minItems": 1 } + } + } + ] + }, + "candidatePaths": { "type": "array", "items": { "$ref": "#/$defs/path" }, "uniqueItems": true }, + "policyExclusions": { "type": "array", "items": { "$ref": "#/$defs/policyExclusion" } } + }, + "$defs": { + "path": { "type": "string", "minLength": 1, "pattern": "^(?!/)(?!.*(?:^|/)\\.\\.(?:/|$)).+$" }, + "candidateGeneration": { + "type": "object", + "additionalProperties": false, + "required": ["algorithm", "digest", "authorities"], + "properties": { + "algorithm": { "type": "string", "minLength": 1 }, + "digest": { "type": "string", "minLength": 1 }, + "authorities": { "type": "array", "items": { "type": "string", "minLength": 1 }, "minItems": 1, "uniqueItems": true } + } + }, + "policyExclusion": { + "type": "object", + "additionalProperties": false, + "required": ["path", "authority", "reasonKind"], + "properties": { + "path": { "$ref": "#/$defs/path" }, + "authority": { "type": "string", "minLength": 1 }, + "reasonKind": { "type": "string", "minLength": 1 } + } + } + } +} diff --git a/schemas/provider-project-resolution-response.schema.json b/schemas/provider-project-resolution-response.schema.json new file mode 100644 index 0000000..dba484c --- /dev/null +++ b/schemas/provider-project-resolution-response.schema.json @@ -0,0 +1,50 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://schemas.agent-semantic-protocols.dev/provider-project-resolution-response.schema.json", + "title": "Provider Project Scope Response", + "type": "object", + "additionalProperties": false, + "required": ["schemaId", "schemaVersion", "languageId", "providerId", "state"], + "properties": { + "schemaId": { "const": "agent.semantic-protocols.provider-project-resolution-response" }, + "schemaVersion": { "const": "1" }, + "languageId": { "type": "string", "minLength": 1 }, + "providerId": { "type": "string", "minLength": 1 }, + "state": { "enum": ["resolved", "failed", "not-applicable"] }, + "scope": { "$ref": "https://schemas.agent-semantic-protocols.dev/project-resolution.schema.json" }, + "failure": { "$ref": "#/$defs/failure" } + }, + "allOf": [ + { + "if": { "properties": { "state": { "const": "resolved" } }, "required": ["state"] }, + "then": { "required": ["scope"], "not": { "required": ["failure"] } } + }, + { + "if": { "properties": { "state": { "const": "failed" } }, "required": ["state"] }, + "then": { "required": ["failure"], "not": { "required": ["scope"] } } + }, + { + "if": { "properties": { "state": { "const": "not-applicable" } }, "required": ["state"] }, + "then": { + "not": { + "anyOf": [ + { "required": ["scope"] }, + { "required": ["failure"] } + ] + } + } + } + ], + "$defs": { + "failure": { + "type": "object", + "additionalProperties": false, + "required": ["reasonKind", "message", "nextAction"], + "properties": { + "reasonKind": { "type": "string", "minLength": 1 }, + "message": { "type": "string", "minLength": 1 }, + "nextAction": { "type": "string", "minLength": 1 } + } + } + } +} diff --git a/schemas/provider-query-pack-descriptor.schema.json b/schemas/provider-query-pack-descriptor.schema.json new file mode 100644 index 0000000..fe03f0b --- /dev/null +++ b/schemas/provider-query-pack-descriptor.schema.json @@ -0,0 +1,169 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://schemas.agent-semantic-protocols.dev/provider-query-pack-descriptor.schema.json", + "title": "Provider Query Pack Descriptor v1", + "type": "object", + "additionalProperties": false, + "required": [ + "descriptorId", + "descriptorVersion", + "languageId", + "recipes" + ], + "properties": { + "descriptorId": { + "type": "string", + "minLength": 1 + }, + "descriptorVersion": { + "const": "1" + }, + "languageId": { + "type": "string", + "minLength": 1 + }, + "semanticFactsDescriptorId": { + "type": "string", + "minLength": 1 + }, + "termRoleOverrides": { + "type": "array", + "default": [], + "items": { + "$ref": "#/$defs/termRoleOverride" + } + }, + "recipes": { + "type": "array", + "items": { + "$ref": "#/$defs/recipe" + } + } + }, + "$defs": { + "role": { + "enum": [ + "context", + "concept", + "symbol", + "literal", + "diagnostic-code" + ] + }, + "intentAxis": { + "enum": [ + "data-shape", + "collection", + "concurrency", + "cancellation", + "resource-lifecycle", + "stream" + ] + }, + "termRoleOverride": { + "type": "object", + "additionalProperties": false, + "required": [ + "term", + "role" + ], + "properties": { + "term": { + "type": "string", + "minLength": 1 + }, + "role": { + "$ref": "#/$defs/role" + }, + "caseSensitive": { + "type": "boolean", + "default": false + } + } + }, + "trigger": { + "type": "object", + "additionalProperties": false, + "required": [ + "terms", + "match" + ], + "properties": { + "terms": { + "type": "array", + "minItems": 1, + "uniqueItems": true, + "items": { + "type": "string", + "minLength": 1 + } + }, + "match": { + "enum": [ + "any", + "all" + ] + } + } + }, + "clause": { + "type": "object", + "additionalProperties": false, + "required": [ + "terms" + ], + "properties": { + "terms": { + "type": "array", + "minItems": 1, + "uniqueItems": true, + "items": { + "type": "string", + "minLength": 1 + } + }, + "roles": { + "type": "array", + "uniqueItems": true, + "default": [], + "items": { + "$ref": "#/$defs/role" + } + }, + "intentAxes": { + "type": "array", + "uniqueItems": true, + "default": [], + "items": { + "$ref": "#/$defs/intentAxis" + } + } + } + }, + "recipe": { + "type": "object", + "additionalProperties": false, + "required": [ + "recipeId", + "trigger", + "clauses" + ], + "properties": { + "recipeId": { + "type": "string", + "minLength": 1 + }, + "trigger": { + "$ref": "#/$defs/trigger" + }, + "clauses": { + "type": "array", + "minItems": 1, + "items": { + "$ref": "#/$defs/clause" + } + } + } + } + } +} diff --git a/schemas/provider-registration.schema.json b/schemas/provider-registration.schema.json new file mode 100644 index 0000000..b54dda8 --- /dev/null +++ b/schemas/provider-registration.schema.json @@ -0,0 +1,137 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "agent.semantic-protocols.provider-registration", + "schemaVersion": "1", + "title": "ASP Provider Registration", + "type": "object", + "required": [ + "$schema", + "binary", + "displayName", + "execution", + "languageId", + "namespace", + "providerDescriptor", + "providerId", + "queryPackDescriptor", + "runtimeContract", + "routes", + "searchCapabilities", + "schemas", + "sourceInventory" + ], + "properties": { + "$schema": { "const": "../schemas/provider-registration.schema.json" }, + "binary": { "$ref": "#/$defs/nonEmptyString" }, + "displayName": { "$ref": "#/$defs/nonEmptyString" }, + "execution": { "$ref": "#/$defs/nonEmptyString" }, + "languageId": { "$ref": "#/$defs/nonEmptyString" }, + "routes": { + "type": "array", + "minItems": 1, + "items": { "$ref": "provider-route.schema.json" } + }, + "namespace": { "$ref": "#/$defs/nonEmptyString" }, + "providerDescriptor": { + "type": "object", + "required": ["$ref"], + "properties": { "$ref": { "$ref": "#/$defs/nonEmptyString" } }, + "additionalProperties": false + }, + "providerId": { "$ref": "#/$defs/nonEmptyString" }, + "queryPackDescriptor": { "$ref": "provider-query-pack-descriptor.schema.json" }, + "runtimeContract": { "$ref": "provider-runtime-contract-descriptor.schema.json" }, + "searchCapabilities": { "type": "object" }, + "sourceInventory": { "$ref": "#/$defs/sourceInventory" }, + "schemas": { + "type": "array", + "items": { "$ref": "#/$defs/schemaRegistration" }, + "minItems": 1 + } + }, + "additionalProperties": false, + "$defs": { + "nonEmptyString": { "type": "string", "minLength": 1 }, + "schemaRegistration": { + "type": "object", + "required": ["authority", "path", "schemaId", "schemaVersion"], + "properties": { + "authority": { "enum": ["asp", "provider"] }, + "path": { "type": "string", "pattern": "^schemas/[^/]+\\.schema\\.json$" }, + "schemaId": { "$ref": "#/$defs/nonEmptyString" }, + "schemaVersion": { "$ref": "#/$defs/nonEmptyString" } + }, + "additionalProperties": false + }, + "sourceInventory": { + "type": "object", + "additionalProperties": false, + "required": [ + "packageRoots", + "configFiles", + "sourceExtensions", + "projectResolution", + "documentResolution" + ], + "properties": { + "packageRoots": { + "type": "array", + "uniqueItems": true, + "items": { "$ref": "#/$defs/nonEmptyString" } + }, + "configFiles": { + "type": "array", + "uniqueItems": true, + "items": { "$ref": "#/$defs/nonEmptyString" } + }, + "sourceExtensions": { + "type": "array", + "minItems": 1, + "uniqueItems": true, + "items": { "type": "string", "pattern": "^\\.[A-Za-z0-9+_-]+$" } + }, + "projectResolution": { + "oneOf": [ + { "type": "null" }, + { + "type": "object", + "additionalProperties": false, + "required": ["entryMarkers"], + "properties": { + "entryMarkers": { + "type": "array", + "minItems": 1, + "uniqueItems": true, + "items": { "$ref": "#/$defs/nonEmptyString" } + } + } + } + ] + }, + "documentResolution": { + "oneOf": [ + { "type": "null" }, + { + "type": "object", + "additionalProperties": false, + "required": ["extensions", "supportsGitCandidates"], + "properties": { + "extensions": { + "type": "array", + "minItems": 1, + "uniqueItems": true, + "items": { "type": "string", "pattern": "^\\.[A-Za-z0-9+_-]+$" } + }, + "supportsGitCandidates": { "type": "boolean" } + } + } + ] + } + }, + "anyOf": [ + { "properties": { "projectResolution": { "type": "object" } } }, + { "properties": { "documentResolution": { "type": "object" } } } + ] + } + } +} diff --git a/schemas/provider-route.schema.json b/schemas/provider-route.schema.json new file mode 100644 index 0000000..33d3a4a --- /dev/null +++ b/schemas/provider-route.schema.json @@ -0,0 +1,254 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://schemas.agent-semantic-protocols.dev/provider-route.schema.json", + "title": "ASP Provider Route DSL", + "type": "object", + "additionalProperties": false, + "required": [ + "schemaId", + "schemaVersion", + "routeId", + "operation", + "authority", + "target", + "inputs", + "requirements", + "effects", + "output", + "failureSchemaIds", + "cache", + "telemetry" + ], + "properties": { + "schemaId": { + "const": "agent.semantic-protocols.provider-route" + }, + "schemaVersion": { + "const": "1" + }, + "routeId": { + "$ref": "#/$defs/dottedId" + }, + "operation": { + "$ref": "#/$defs/semanticId" + }, + "authority": { + "const": "asp-server" + }, + "target": { + "$ref": "#/$defs/target" + }, + "requestSchemaId": { + "$ref": "#/$defs/schemaId" + }, + "inputs": { + "type": "array", + "items": { + "$ref": "#/$defs/inputSlot" + }, + "uniqueItems": true + }, + "requirements": { + "type": "array", + "items": { + "$ref": "#/$defs/requirement" + }, + "uniqueItems": true + }, + "effects": { + "$ref": "#/$defs/effects" + }, + "output": { + "$ref": "#/$defs/output" + }, + "failureSchemaIds": { + "type": "array", + "minItems": 1, + "uniqueItems": true, + "items": { + "$ref": "#/$defs/schemaId" + } + }, + "cache": { + "$ref": "#/$defs/cache" + }, + "telemetry": { + "$ref": "#/$defs/telemetry" + } + }, + "$defs": { + "semanticId": { + "type": "string", + "pattern": "^[a-z][a-z0-9-]*(\\.[a-z][a-z0-9-]*)*$" + }, + "dottedId": { + "type": "string", + "pattern": "^[a-z][a-z0-9-]*(\\.[a-z][a-z0-9-]*)+$" + }, + "schemaId": { + "type": "string", + "minLength": 1, + "pattern": "^[A-Za-z][A-Za-z0-9._:/-]*$" + }, + "target": { + "type": "object", + "additionalProperties": false, + "required": ["languageId", "providerId"], + "properties": { + "languageId": { + "type": "string", + "minLength": 1 + }, + "providerId": { + "type": "string", + "pattern": "^asp-[a-z0-9][a-z0-9-]*$" + } + } + }, + "inputSlot": { + "type": "object", + "additionalProperties": false, + "required": ["name", "valueType", "cardinality", "source"], + "properties": { + "name": { + "type": "string", + "pattern": "^[a-z][A-Za-z0-9]*$" + }, + "valueType": { + "enum": [ + "string", + "workspace-relative-path", + "structural-selector", + "presentation", + "boolean", + "unsigned-integer", + "json" + ] + }, + "cardinality": { + "enum": ["required", "optional", "many"] + }, + "source": { + "enum": ["request", "runtime-context"] + }, + "telemetry": { + "enum": ["omit", "identity", "measurement"] + } + } + }, + "requirement": { + "oneOf": [ + { + "type": "object", + "additionalProperties": false, + "required": ["kind", "capability"], + "properties": { + "kind": { + "const": "capability" + }, + "capability": { + "$ref": "#/$defs/dottedId" + } + } + }, + { + "type": "object", + "additionalProperties": false, + "required": ["kind", "state"], + "properties": { + "kind": { + "const": "state" + }, + "state": { + "enum": [ + "provider-ready", + "terminal-generation", + "live-owner", + "registered-workspace" + ] + } + } + } + ] + }, + "effects": { + "type": "object", + "additionalProperties": false, + "required": ["access", "idempotent", "cancellable", "concurrency", "streaming"], + "properties": { + "access": { + "enum": ["read", "write"] + }, + "idempotent": { + "type": "boolean" + }, + "cancellable": { + "type": "boolean" + }, + "concurrency": { + "enum": ["isolated", "shared-read", "exclusive"] + }, + "streaming": { + "type": "boolean" + } + } + }, + "output": { + "type": "object", + "additionalProperties": false, + "required": ["schemaId", "mediaType"], + "properties": { + "schemaId": { + "$ref": "#/$defs/schemaId" + }, + "mediaType": { + "const": "application/json" + }, + "projectionKind": { + "type": "string", + "minLength": 1 + } + } + }, + "cache": { + "type": "object", + "additionalProperties": false, + "required": ["authority", "scope", "keySlots"], + "properties": { + "authority": { + "const": "asp-server" + }, + "scope": { + "enum": ["none", "request", "generation", "workspace"] + }, + "keySlots": { + "type": "array", + "uniqueItems": true, + "items": { + "type": "string", + "pattern": "^[a-z][A-Za-z0-9]*$" + } + } + } + }, + "telemetry": { + "type": "object", + "additionalProperties": false, + "required": ["spanName", "attributeSlots"], + "properties": { + "spanName": { + "type": "string", + "pattern": "^asp\\.route\\.[a-z0-9.-]+$" + }, + "attributeSlots": { + "type": "array", + "uniqueItems": true, + "items": { + "type": "string", + "pattern": "^[a-z][A-Za-z0-9]*$" + } + } + } + } + } +} diff --git a/schemas/provider-runtime-contract-descriptor.schema.json b/schemas/provider-runtime-contract-descriptor.schema.json new file mode 100644 index 0000000..5b2ebe3 --- /dev/null +++ b/schemas/provider-runtime-contract-descriptor.schema.json @@ -0,0 +1,33 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://schemas.agent-semantic-protocols.dev/provider-runtime-contract-descriptor.schema.json", + "title": "ASP Provider Runtime Contract Descriptor", + "type": "object", + "additionalProperties": false, + "required": ["transport", "clientBinding", "aspClientServer", "operations"], + "properties": { + "transport": { "const": "http-json" }, + "clientBinding": { "const": "schema-driven" }, + "aspClientServer": { + "$ref": "asp-client-server-descriptor.schema.json" + }, + "operations": { + "type": "array", + "minItems": 1, + "uniqueItems": true, + "items": { + "type": "object", + "additionalProperties": false, + "required": ["operation", "requestSchemaId", "responseSchemaId"], + "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 } + } + } + } + } +} diff --git a/schemas/provider-runtime-request-stream-ack.schema.json b/schemas/provider-runtime-request-stream-ack.schema.json new file mode 100644 index 0000000..4a961ee --- /dev/null +++ b/schemas/provider-runtime-request-stream-ack.schema.json @@ -0,0 +1,15 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://schemas.agent-semantic-protocols.dev/provider-runtime-request-stream-ack.schema.json", + "title": "Provider Runtime Request Stream Acknowledgement", + "type": "object", + "additionalProperties": false, + "required": ["schemaId", "schemaVersion", "streamId", "frameIndex", "state"], + "properties": { + "schemaId": {"const": "agent.semantic-protocols.provider-runtime-request-stream-ack"}, + "schemaVersion": {"const": "1"}, + "streamId": {"type": "string", "minLength": 1}, + "frameIndex": {"type": "integer", "minimum": 0}, + "state": {"const": "accepted"} + } +} diff --git a/schemas/provider-runtime-request-stream-frame.schema.json b/schemas/provider-runtime-request-stream-frame.schema.json new file mode 100644 index 0000000..e7a18a9 --- /dev/null +++ b/schemas/provider-runtime-request-stream-frame.schema.json @@ -0,0 +1,16 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://schemas.agent-semantic-protocols.dev/provider-runtime-request-stream-frame.schema.json", + "title": "Provider Runtime Request Stream Frame", + "type": "object", + "additionalProperties": false, + "required": ["schemaId", "schemaVersion", "streamId", "frameIndex", "frameCount", "requestChunk"], + "properties": { + "schemaId": {"const": "agent.semantic-protocols.provider-runtime-request-stream-frame"}, + "schemaVersion": {"const": "1"}, + "streamId": {"type": "string", "minLength": 1}, + "frameIndex": {"type": "integer", "minimum": 0}, + "frameCount": {"type": "integer", "minimum": 2, "maximum": 1024}, + "requestChunk": {"type": "string"} + } +} diff --git a/schemas/provider-workspace-install.schema.json b/schemas/provider-workspace-install.schema.json new file mode 100644 index 0000000..fd6fb11 --- /dev/null +++ b/schemas/provider-workspace-install.schema.json @@ -0,0 +1,231 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://tao3k.github.io/agent-semantic-protocols/schemas/provider-workspace-install.v1.schema.json", + "title": "Provider Workspace Install Descriptor v1", + "type": "object", + "additionalProperties": false, + "required": [ + "$schema", + "schemaId", + "schemaVersion", + "schemaAuthority", + "languageId", + "providerId", + "binary", + "providerRegistration", + "schemaBundleReceipt", + "workspaceArtifact", + "workspaceBuild" + ], + "properties": { + "$schema": { + "const": "../schemas/provider-workspace-install.schema.json" + }, + "schemaId": { + "const": "agent.semantic-protocols.provider-workspace-install" + }, + "schemaVersion": { + "const": "1" + }, + "schemaAuthority": { + "type": "string", + "format": "uri" + }, + "languageId": { + "enum": [ + "gerbil-scheme", + "julia", + "python", + "rust", + "typescript" + ] + }, + "providerId": { + "type": "string", + "pattern": "^asp-[a-z0-9][a-z0-9-]*$" + }, + "binary": { + "type": "string", + "pattern": "^[^/]+$" + }, + "providerRegistration": { + "$ref": "#/$defs/relativePath" + }, + "schemaBundleReceipt": { + "$ref": "#/$defs/relativePath" + }, + "workspaceArtifact": { + "$ref": "#/$defs/workspaceArtifact" + }, + "dependencyMaterialization": { + "$ref": "#/$defs/command" + }, + "workspaceBuild": { + "$ref": "#/$defs/workspaceBuild" + } + }, + "oneOf": [ + { + "properties": { + "languageId": {"const": "gerbil-scheme"}, + "providerId": {"const": "asp-gerbil-scheme"} + } + }, + { + "properties": { + "languageId": {"const": "julia"}, + "providerId": {"const": "asp-julia"} + } + }, + { + "properties": { + "languageId": {"const": "python"}, + "providerId": {"const": "asp-python"} + } + }, + { + "properties": { + "languageId": {"const": "rust"}, + "providerId": {"const": "asp-rust"} + } + }, + { + "properties": { + "languageId": {"const": "typescript"}, + "providerId": {"const": "asp-typescript"} + } + } + ], + "$defs": { + "relativePath": { + "type": "string", + "minLength": 1, + "not": { + "anyOf": [ + { "pattern": "^/" }, + { "pattern": "(^|/)\\.\\.(/|$)" } + ] + } + }, + "workspaceArtifact": { + "type": "object", + "additionalProperties": false, + "required": ["root", "entrypoint"], + "properties": { + "root": { "$ref": "#/$defs/relativePath" }, + "entrypoint": { "$ref": "#/$defs/relativePath" }, + "runtimeDependencies": { + "type": "array", + "description": "Files copied from the canonical launch program installation prefix into the immutable artifact before its digest is computed.", + "items": { "$ref": "#/$defs/runtimeDependency" } + }, + "launch": { "$ref": "#/$defs/launch" } + } + }, + "launch": { + "type": "object", + "additionalProperties": false, + "required": [ + "program", + "args", + "programRelativeToArtifact", + "argsRelativeToArtifact" + ], + "properties": { + "program": { "type": "string", "minLength": 1 }, + "args": { "type": "array", "items": { "type": "string" } }, + "programRelativeToArtifact": { "type": "boolean" }, + "argsRelativeToArtifact": { "type": "boolean" } + } + }, + "runtimeDependency": { + "type": "object", + "additionalProperties": false, + "description": "A runtime file required by the artifact-relative launch program.", + "required": ["source", "target"], + "properties": { + "source": { + "description": "Path relative to the canonical launch program installation prefix.", + "$ref": "#/$defs/relativePath" + }, + "target": { + "description": "Destination path relative to the immutable artifact root.", + "$ref": "#/$defs/relativePath" + } + } + }, + "command": { + "type": "object", + "additionalProperties": false, + "required": ["program", "args", "workingDirectory", "env"], + "properties": { + "program": { "type": "string", "minLength": 1 }, + "args": { "type": "array", "items": { "type": "string" } }, + "workingDirectory": { "$ref": "#/$defs/relativePath" }, + "env": { + "type": "object", + "additionalProperties": { "type": "string" } + }, + "removeEnv": { + "$ref": "#/$defs/environmentNames" + }, + "removeEnvPrefixes": { + "$ref": "#/$defs/environmentPrefixes" + } + } + }, + "workspaceBuild": { + "type": "object", + "additionalProperties": false, + "required": [ + "program", + "args", + "workingDirectory", + "sourceSnapshotAnchors", + "derivedPaths", + "env" + ], + "properties": { + "program": { "type": "string", "minLength": 1 }, + "args": { "type": "array", "items": { "type": "string" } }, + "workingDirectory": { "$ref": "#/$defs/relativePath" }, + "sourceSnapshotAnchors": { + "type": "array", + "minItems": 1, + "items": { "$ref": "#/$defs/relativePath" } + }, + "derivedPaths": { + "type": "array", + "minItems": 1, + "items": { "$ref": "#/$defs/relativePath" } + }, + "env": { + "type": "object", + "additionalProperties": { "type": "string" } + }, + "removeEnv": { + "$ref": "#/$defs/environmentNames" + }, + "removeEnvPrefixes": { + "$ref": "#/$defs/environmentPrefixes" + } + } + }, + "environmentNames": { + "type": "array", + "uniqueItems": true, + "items": { + "type": "string", + "pattern": "^[A-Za-z_][A-Za-z0-9_]*$" + } + }, + "environmentPrefixes": { + "type": "array", + "uniqueItems": true, + "items": { + "type": "string", + "pattern": "^[A-Za-z_][A-Za-z0-9_]*$" + } + } + } +} diff --git a/schemas/python-semantic-capabilities.v1.schema.json b/schemas/python-semantic-capabilities.v1.schema.json index 6280740..954a693 100644 --- a/schemas/python-semantic-capabilities.v1.schema.json +++ b/schemas/python-semantic-capabilities.v1.schema.json @@ -8,7 +8,7 @@ "required": ["schemaId", "schemaVersion", "languageId", "providerId"], "properties": { "schemaId": { - "const": "agent.semantic-protocols.languages.python.py-harness.capabilities" + "const": "agent.semantic-protocols.languages.python.asp-python.capabilities" }, "schemaVersion": { "const": "1" @@ -17,7 +17,7 @@ "const": "python" }, "providerId": { - "const": "py-harness" + "const": "asp-python" }, "capability": { "$ref": "#/$defs/capabilityDescriptor" @@ -44,6 +44,7 @@ "name": { "enum": [ "workspace-router", + "workspace-candidate-admission", "python-package-root-search", "package-prime-map", "python-reasoning-tree-prime", diff --git a/schemas/resolved-source-scope.v1.schema.json b/schemas/resolved-source-scope.v1.schema.json new file mode 100644 index 0000000..e178a33 --- /dev/null +++ b/schemas/resolved-source-scope.v1.schema.json @@ -0,0 +1,189 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://schemas.agent-semantic-protocols.dev/resolved-source-scope.v1.schema.json", + "title": "Resolved Source Scope", + "type": "object", + "additionalProperties": false, + "required": [ + "scopeId", + "packageId", + "targetId", + "roots", + "explicitPaths", + "extensions", + "includeAuthority", + "exclusions" + ], + "properties": { + "schemaId": { + "const": "agent.semantic-protocols.resolved-source-scope" + }, + "schemaVersion": { + "const": "1" + }, + "scopeId": { + "type": "string", + "minLength": 1 + }, + "packageId": { + "type": "string", + "minLength": 1 + }, + "targetId": { + "type": "string", + "minLength": 1 + }, + "roots": { + "type": "array", + "items": { + "$ref": "#/$defs/path" + }, + "minItems": 1, + "uniqueItems": true + }, + "explicitPaths": { + "type": "array", + "items": { + "$ref": "#/$defs/path" + }, + "uniqueItems": true + }, + "extensions": { + "type": "array", + "items": { + "type": "string", + "pattern": "^\\.[A-Za-z0-9][A-Za-z0-9._+-]*$" + }, + "minItems": 1, + "uniqueItems": true + }, + "includeAuthority": { + "enum": [ + "manifest-explicit", + "package-manager", + "policy-overlay" + ] + }, + "classifications": { + "type": "array", + "items": { + "enum": [ + "production", + "generated", + "vendor", + "test", + "example", + "benchmark", + "build" + ] + }, + "uniqueItems": true + }, + "exclusions": { + "type": "array", + "items": { + "$ref": "#/$defs/exclusion" + } + }, + "providerFacts": { + "type": "object" + }, + "conflicts": { + "type": "array", + "items": { + "$ref": "#/$defs/conflict" + } + }, + "resolutionState": { + "enum": [ + "resolved", + "conflicted" + ] + }, + "scopeDigest": { + "type": "string", + "minLength": 1 + } + }, + "allOf": [ + { + "if": { + "properties": { + "resolutionState": { + "const": "conflicted" + } + }, + "required": [ + "resolutionState" + ] + }, + "then": { + "required": [ + "conflicts" + ], + "properties": { + "conflicts": { + "minItems": 1 + } + } + } + } + ], + "$defs": { + "path": { + "type": "string", + "minLength": 1, + "pattern": "^(?!/)(?!.*(?:^|/)\\.\\.(?:/|$)).+$" + }, + "exclusion": { + "type": "object", + "additionalProperties": false, + "required": [ + "prefix", + "authority" + ], + "properties": { + "prefix": { + "$ref": "#/$defs/path" + }, + "authority": { + "enum": [ + "infrastructure", + "package-manager", + "user-policy" + ] + } + } + }, + "conflict": { + "type": "object", + "additionalProperties": false, + "required": [ + "path", + "includeAuthority", + "excludeAuthority", + "reasonKind" + ], + "properties": { + "path": { + "$ref": "#/$defs/path" + }, + "includeAuthority": { + "type": "string", + "minLength": 1 + }, + "excludeAuthority": { + "type": "string", + "minLength": 1 + }, + "reasonKind": { + "enum": [ + "explicit-source-excluded", + "scope-layer-conflict", + "target-path-missing" + ] + } + } + } + } +} diff --git a/schemas/semantic-assurance-case.v1.schema.json b/schemas/semantic-assurance-case.v1.schema.json index 8d28456..114bc4a 100644 --- a/schemas/semantic-assurance-case.v1.schema.json +++ b/schemas/semantic-assurance-case.v1.schema.json @@ -34,10 +34,10 @@ "pattern": "^[a-z][a-z0-9_.:-]*$" }, "producer": { - "$ref": "#/$defs/producer" + "$ref": "semantic-assurance-definitions.v1.schema.json#/$defs/producer" }, "project": { - "$ref": "#/$defs/project" + "$ref": "semantic-assurance-definitions.v1.schema.json#/$defs/project" }, "summary": { "$ref": "#/$defs/summary" @@ -81,43 +81,6 @@ "minLength": 1, "pattern": "^(?:\\.|(?!/)(?![A-Za-z]:)(?![0-9]+:)(?!\\.\\.?($|/))(?!.*(^|/)\\.\\.?($|/))(?!.*//)[^\\s:\\\\]+(?:/[^\\s:\\\\]+)*)$" }, - "producer": { - "type": "object", - "additionalProperties": false, - "required": ["languageId", "providerId", "namespace"], - "properties": { - "languageId": { - "type": "string", - "pattern": "^[a-z][a-z0-9_-]*$" - }, - "providerId": { - "type": "string", - "pattern": "^[a-z][a-z0-9_-]*$" - }, - "namespace": { - "type": "string", - "pattern": "^[a-z][a-z0-9_-]*(\\.[a-z][a-z0-9_-]*)+$" - } - } - }, - "project": { - "type": "object", - "additionalProperties": false, - "required": ["root"], - "properties": { - "root": { - "type": "string", - "minLength": 1 - }, - "package": { - "type": "string", - "minLength": 1 - }, - "fields": { - "$ref": "#/$defs/fields" - } - } - }, "summary": { "type": "object", "additionalProperties": false, @@ -162,37 +125,6 @@ "custom" ] }, - "nodeKind": { - "enum": [ - "owner", - "invariant-candidate", - "verification-receipt", - "behavior-snapshot", - "determinism-readiness", - "formal-proof-pilot", - "review-packet", - "waiver", - "review-action" - ] - }, - "nodeStatus": { - "enum": [ - "current", - "changed", - "missing", - "stale", - "expired", - "ready", - "needs-injection", - "blocked", - "unknown", - "proved", - "proved-bounded", - "failed", - "skipped", - "error" - ] - }, "claim": { "type": "object", "additionalProperties": false, @@ -221,6 +153,12 @@ } } }, + "nodeRefList": { + "type": "array", + "items": { + "$ref": "#/$defs/nodeRef" + } + }, "nodeRef": { "type": "object", "additionalProperties": false, @@ -231,14 +169,14 @@ "pattern": "^[a-z][a-z0-9_.:-]*$" }, "kind": { - "$ref": "#/$defs/nodeKind" + "$ref": "semantic-assurance-definitions.v1.schema.json#/$defs/nodeKind" }, "label": { "type": "string", "minLength": 1 }, "status": { - "$ref": "#/$defs/nodeStatus" + "$ref": "semantic-assurance-definitions.v1.schema.json#/$defs/nodeStatus" }, "summary": { "type": "string", @@ -325,28 +263,16 @@ "$ref": "#/$defs/projectPath" }, "supportedBy": { - "type": "array", - "items": { - "$ref": "#/$defs/nodeRef" - } + "$ref": "#/$defs/nodeRefList" }, "observedBy": { - "type": "array", - "items": { - "$ref": "#/$defs/nodeRef" - } + "$ref": "#/$defs/nodeRefList" }, "reviewedBy": { - "type": "array", - "items": { - "$ref": "#/$defs/nodeRef" - } + "$ref": "#/$defs/nodeRefList" }, "waivedBy": { - "type": "array", - "items": { - "$ref": "#/$defs/nodeRef" - } + "$ref": "#/$defs/nodeRefList" }, "actions": { "type": "array", diff --git a/schemas/semantic-assurance-definitions.v1.schema.json b/schemas/semantic-assurance-definitions.v1.schema.json new file mode 100644 index 0000000..eb74505 --- /dev/null +++ b/schemas/semantic-assurance-definitions.v1.schema.json @@ -0,0 +1,139 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://agent-semantic-protocols.local/schemas/semantic-assurance-definitions.v1.schema.json", + "title": "Semantic Assurance Family Definitions v1", + "description": "Shared definitions owned by the semantic-assurance schema family.", + "$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:\\\\]+)*)$" + }, + "producer": { + "type": "object", + "additionalProperties": false, + "required": [ + "languageId", + "providerId", + "namespace" + ], + "properties": { + "languageId": { + "type": "string", + "pattern": "^[a-z][a-z0-9_-]*$" + }, + "providerId": { + "type": "string", + "pattern": "^[a-z][a-z0-9_-]*$" + }, + "namespace": { + "type": "string", + "pattern": "^[a-z][a-z0-9_-]*(\\.[a-z][a-z0-9_-]*)+$" + } + } + }, + "project": { + "type": "object", + "additionalProperties": false, + "required": [ + "root" + ], + "properties": { + "root": { + "type": "string", + "minLength": 1 + }, + "package": { + "type": "string", + "minLength": 1 + }, + "fields": { + "$ref": "#/$defs/fields" + } + } + }, + "location": { + "type": "object", + "additionalProperties": false, + "properties": { + "path": { + "$ref": "#/$defs/projectPath" + }, + "line": { + "type": "integer", + "minimum": 1 + }, + "column": { + "type": "integer", + "minimum": 0 + } + } + }, + "nodeKind": { + "enum": [ + "owner", + "invariant-candidate", + "verification-receipt", + "behavior-snapshot", + "determinism-readiness", + "formal-proof-pilot", + "review-packet", + "waiver", + "review-action" + ] + }, + "nodeStatus": { + "enum": [ + "current", + "changed", + "missing", + "stale", + "expired", + "ready", + "needs-injection", + "blocked", + "unknown", + "proved", + "proved-bounded", + "failed", + "skipped", + "error" + ] + } + } +} diff --git a/schemas/semantic-ast-patch-definitions.v1.schema.json b/schemas/semantic-ast-patch-definitions.v1.schema.json new file mode 100644 index 0000000..bf9ee0a --- /dev/null +++ b/schemas/semantic-ast-patch-definitions.v1.schema.json @@ -0,0 +1,25 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://agent-semantic-protocols.local/schemas/semantic-ast-patch-definitions.v1.schema.json", + "title": "Semantic AST Patch Family Definitions v1", + "description": "Shared definitions owned by the semantic-ast-patch schema family.", + "$defs": { + "operationName": { + "type": "string", + "enum": [ + "append_to_block", + "insert_before_statement", + "insert_after_statement", + "replace_statement", + "replace_expression", + "replace_call_arg", + "insert_import", + "remove_import", + "remove_statement", + "remove_item", + "replace_item", + "split_owner_items" + ] + } + } +} diff --git a/schemas/semantic-ast-patch-receipt.v1.schema.json b/schemas/semantic-ast-patch-receipt.v1.schema.json index 546decd..cb143a4 100644 --- a/schemas/semantic-ast-patch-receipt.v1.schema.json +++ b/schemas/semantic-ast-patch-receipt.v1.schema.json @@ -230,21 +230,7 @@ ], "$defs": { "operationName": { - "type": "string", - "enum": [ - "append_to_block", - "insert_before_statement", - "insert_after_statement", - "replace_statement", - "replace_expression", - "replace_call_arg", - "insert_import", - "remove_import", - "remove_statement", - "remove_item", - "replace_item", - "split_owner_items" - ] + "$ref": "semantic-ast-patch-definitions.v1.schema.json#/$defs/operationName" }, "identifier": { "type": "string", diff --git a/schemas/semantic-ast-patch.v1.schema.json b/schemas/semantic-ast-patch.v1.schema.json index 78d9084..4657be9 100644 --- a/schemas/semantic-ast-patch.v1.schema.json +++ b/schemas/semantic-ast-patch.v1.schema.json @@ -63,21 +63,7 @@ }, "$defs": { "operationName": { - "type": "string", - "enum": [ - "append_to_block", - "insert_before_statement", - "insert_after_statement", - "replace_statement", - "replace_expression", - "replace_call_arg", - "insert_import", - "remove_import", - "remove_statement", - "remove_item", - "replace_item", - "split_owner_items" - ] + "$ref": "semantic-ast-patch-definitions.v1.schema.json#/$defs/operationName" }, "identifier": { "type": "string", diff --git a/schemas/semantic-codeql-evidence.v1.schema.json b/schemas/semantic-codeql-evidence.v1.schema.json index 4aff919..be66232 100644 --- a/schemas/semantic-codeql-evidence.v1.schema.json +++ b/schemas/semantic-codeql-evidence.v1.schema.json @@ -136,7 +136,7 @@ "omissions": { "type": "array", "items": { - "$ref": "#/$defs/omission" + "$ref": "semantic-definitions.v1.schema.json#/$defs/omission" } }, "fields": { @@ -160,36 +160,8 @@ "type": "string", "pattern": "^[a-z][a-z0-9_-]*$" }, - "scalar": { - "type": [ - "string", - "number", - "integer", - "boolean", - "null" - ] - }, "fields": { - "type": "object", - "additionalProperties": { - "oneOf": [ - { - "$ref": "#/$defs/scalar" - }, - { - "type": "array", - "items": { - "$ref": "#/$defs/scalar" - } - }, - { - "type": "object", - "additionalProperties": { - "$ref": "#/$defs/scalar" - } - } - ] - } + "$ref": "semantic-definitions.v1.schema.json#/$defs/fields" }, "normalizedRow": { "type": "object", @@ -229,37 +201,6 @@ "$ref": "#/$defs/fields" } } - }, - "omission": { - "type": "object", - "additionalProperties": false, - "required": [ - "kind", - "message" - ], - "properties": { - "kind": { - "enum": [ - "unsupported", - "unavailable", - "backend-unavailable", - "too-expensive", - "ambiguous", - "policy-blocked" - ] - }, - "message": { - "type": "string", - "minLength": 1 - }, - "target": { - "type": "string", - "minLength": 1 - }, - "fields": { - "$ref": "#/$defs/fields" - } - } } } } diff --git a/schemas/semantic-content-compaction.v1.schema.json b/schemas/semantic-content-compaction.v1.schema.json index 2899675..12fb485 100644 --- a/schemas/semantic-content-compaction.v1.schema.json +++ b/schemas/semantic-content-compaction.v1.schema.json @@ -92,6 +92,13 @@ "debug-only" ] }, + "useCaseList": { + "type": "array", + "uniqueItems": true, + "items": { + "$ref": "#/$defs/useCase" + } + }, "useCase": { "enum": [ "discovery", @@ -164,18 +171,10 @@ ] }, "validFor": { - "type": "array", - "uniqueItems": true, - "items": { - "$ref": "#/$defs/useCase" - } + "$ref": "#/$defs/useCaseList" }, "notValidFor": { - "type": "array", - "uniqueItems": true, - "items": { - "$ref": "#/$defs/useCase" - } + "$ref": "#/$defs/useCaseList" }, "preserved": { "type": "array", @@ -194,11 +193,7 @@ } }, "requiresExactSourceFor": { - "type": "array", - "uniqueItems": true, - "items": { - "$ref": "#/$defs/useCase" - } + "$ref": "#/$defs/useCaseList" }, "exactSourceRequired": { "type": "boolean" diff --git a/schemas/semantic-definitions.v1.schema.json b/schemas/semantic-definitions.v1.schema.json new file mode 100644 index 0000000..9696a69 --- /dev/null +++ b/schemas/semantic-definitions.v1.schema.json @@ -0,0 +1,154 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://agent-semantic-protocols.local/schemas/semantic-definitions.v1.schema.json", + "title": "Semantic Family Definitions v1", + "description": "Shared definitions owned by the root semantic schema family.", + "$defs": { + "projectPath": { + "type": "string", + "minLength": 1 + }, + "lineRange": { + "type": "string", + "pattern": "^[1-9][0-9]*:[1-9][0-9]*$" + }, + "structuralSelector": { + "type": "string", + "pattern": "^[a-z][a-z0-9+.-]*://[^#\\s]+#[^\\s]+$" + }, + "location": { + "type": "object", + "additionalProperties": false, + "required": [ + "path", + "lineRange" + ], + "properties": { + "path": { + "$ref": "#/$defs/projectPath" + }, + "lineRange": { + "$ref": "#/$defs/lineRange" + }, + "displayLineRange": { + "$ref": "#/$defs/lineRange" + }, + "sourceLocatorHint": { + "$ref": "#/$defs/sourceLocator" + }, + "structuralSelector": { + "$ref": "#/$defs/structuralSelector" + } + } + }, + "scalar": { + "type": [ + "string", + "number", + "integer", + "boolean", + "null" + ] + }, + "fields": { + "type": "object", + "additionalProperties": { + "oneOf": [ + { + "$ref": "#/$defs/scalar" + }, + { + "type": "array", + "items": { + "$ref": "#/$defs/scalar" + } + }, + { + "type": "object", + "additionalProperties": { + "$ref": "#/$defs/scalar" + } + } + ] + } + }, + "omission": { + "type": "object", + "additionalProperties": false, + "required": [ + "kind", + "message" + ], + "properties": { + "kind": { + "enum": [ + "unsupported", + "unavailable", + "backend-unavailable", + "too-expensive", + "ambiguous", + "policy-blocked" + ] + }, + "message": { + "type": "string", + "minLength": 1 + }, + "target": { + "type": "string", + "minLength": 1 + }, + "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" + } + } + }, + "sourceLocator": { + "type": "string", + "description": "Project-root-relative source selector accepting path:start, path:start:end, or path:start-end.", + "pattern": "^(?:\\.|(?!/)(?![A-Za-z]:)(?![0-9]+:)(?!\\.\\.?($|/))(?!.*(^|/)\\.\\.?($|/))(?!.*//)[^\\s:\\\\]+(?:/[^\\s:\\\\]+)*):[1-9][0-9]*(?:(?::|-)[1-9][0-9]*)?$" + }, + "artifactRef": { + "type": "object", + "additionalProperties": false, + "required": [ + "artifactId", + "schemaId" + ], + "properties": { + "artifactId": { + "type": "string", + "minLength": 1 + }, + "schemaId": { + "type": "string", + "minLength": 1 + }, + "schemaVersion": { + "type": "string", + "minLength": 1 + }, + "fields": { + "$ref": "#/$defs/fields" + } + } + } + } +} diff --git a/schemas/semantic-dependency-topology.v1.schema.json b/schemas/semantic-dependency-topology.v1.schema.json index 1fb43b0..4024a3a 100644 --- a/schemas/semantic-dependency-topology.v1.schema.json +++ b/schemas/semantic-dependency-topology.v1.schema.json @@ -97,16 +97,10 @@ ], "properties": { "manifests": { - "type": "array", - "items": { - "$ref": "#/$defs/sourceFile" - } + "$ref": "#/$defs/sourceFileList" }, "lockfiles": { - "type": "array", - "items": { - "$ref": "#/$defs/sourceFile" - } + "$ref": "#/$defs/sourceFileList" }, "usageSites": { "type": "array", @@ -142,6 +136,12 @@ } }, "$defs": { + "sourceFileList": { + "type": "array", + "items": { + "$ref": "#/$defs/sourceFile" + } + }, "languageId": { "enum": [ "rust", diff --git a/schemas/semantic-determinism-readiness.v1.schema.json b/schemas/semantic-determinism-readiness.v1.schema.json index 1e57ff4..4af9818 100644 --- a/schemas/semantic-determinism-readiness.v1.schema.json +++ b/schemas/semantic-determinism-readiness.v1.schema.json @@ -35,7 +35,7 @@ "pattern": "^[a-z][a-z0-9_.:-]*$" }, "producer": { - "$ref": "#/$defs/producer" + "$ref": "semantic-assurance-definitions.v1.schema.json#/$defs/producer" }, "project": { "$ref": "#/$defs/project" @@ -71,25 +71,6 @@ } }, "$defs": { - "producer": { - "type": "object", - "additionalProperties": false, - "required": ["languageId", "providerId", "namespace"], - "properties": { - "languageId": { - "type": "string", - "pattern": "^[a-z][a-z0-9_-]*$" - }, - "providerId": { - "type": "string", - "pattern": "^[a-z][a-z0-9_-]*$" - }, - "namespace": { - "type": "string", - "pattern": "^[a-z][a-z0-9_-]*(\\.[a-z][a-z0-9_-]*)+$" - } - } - }, "project": { "type": "object", "additionalProperties": false, diff --git a/schemas/semantic-evidence-graph.v1.schema.json b/schemas/semantic-evidence-graph.v1.schema.json index fac8dd4..3220059 100644 --- a/schemas/semantic-evidence-graph.v1.schema.json +++ b/schemas/semantic-evidence-graph.v1.schema.json @@ -35,10 +35,10 @@ "pattern": "^[a-z][a-z0-9_.:-]*$" }, "producer": { - "$ref": "#/$defs/producer" + "$ref": "semantic-assurance-definitions.v1.schema.json#/$defs/producer" }, "project": { - "$ref": "#/$defs/project" + "$ref": "semantic-assurance-definitions.v1.schema.json#/$defs/project" }, "summary": { "$ref": "#/$defs/summary" @@ -94,43 +94,6 @@ "minLength": 1, "pattern": "^(?:\\.|(?!/)(?![A-Za-z]:)(?![0-9]+:)(?!\\.\\.?($|/))(?!.*(^|/)\\.\\.?($|/))(?!.*//)[^\\s:\\\\]+(?:/[^\\s:\\\\]+)*)$" }, - "producer": { - "type": "object", - "additionalProperties": false, - "required": ["languageId", "providerId", "namespace"], - "properties": { - "languageId": { - "type": "string", - "pattern": "^[a-z][a-z0-9_-]*$" - }, - "providerId": { - "type": "string", - "pattern": "^[a-z][a-z0-9_-]*$" - }, - "namespace": { - "type": "string", - "pattern": "^[a-z][a-z0-9_-]*(\\.[a-z][a-z0-9_-]*)+$" - } - } - }, - "project": { - "type": "object", - "additionalProperties": false, - "required": ["root"], - "properties": { - "root": { - "type": "string", - "minLength": 1 - }, - "package": { - "type": "string", - "minLength": 1 - }, - "fields": { - "$ref": "#/$defs/fields" - } - } - }, "summary": { "type": "object", "additionalProperties": false, @@ -165,54 +128,6 @@ } } }, - "nodeKind": { - "enum": [ - "owner", - "invariant-candidate", - "verification-receipt", - "behavior-snapshot", - "determinism-readiness", - "formal-proof-pilot", - "review-packet", - "waiver", - "review-action" - ] - }, - "nodeStatus": { - "enum": [ - "current", - "changed", - "missing", - "stale", - "expired", - "ready", - "needs-injection", - "blocked", - "unknown", - "proved", - "proved-bounded", - "failed", - "skipped", - "error" - ] - }, - "location": { - "type": "object", - "additionalProperties": false, - "properties": { - "path": { - "$ref": "#/$defs/projectPath" - }, - "line": { - "type": "integer", - "minimum": 1 - }, - "column": { - "type": "integer", - "minimum": 0 - } - } - }, "node": { "type": "object", "additionalProperties": false, @@ -223,7 +138,7 @@ "pattern": "^[a-z][a-z0-9_.:-]*$" }, "kind": { - "$ref": "#/$defs/nodeKind" + "$ref": "semantic-assurance-definitions.v1.schema.json#/$defs/nodeKind" }, "label": { "type": "string", @@ -265,14 +180,14 @@ "minLength": 1 }, "status": { - "$ref": "#/$defs/nodeStatus" + "$ref": "semantic-assurance-definitions.v1.schema.json#/$defs/nodeStatus" }, "summary": { "type": "string", "minLength": 1 }, "location": { - "$ref": "#/$defs/location" + "$ref": "semantic-assurance-definitions.v1.schema.json#/$defs/location" }, "fields": { "$ref": "#/$defs/fields" diff --git a/schemas/semantic-fact-definitions.v1.schema.json b/schemas/semantic-fact-definitions.v1.schema.json new file mode 100644 index 0000000..3d83434 --- /dev/null +++ b/schemas/semantic-fact-definitions.v1.schema.json @@ -0,0 +1,133 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://agent-semantic-protocols.local/schemas/semantic-fact-definitions.v1.schema.json", + "title": "Semantic Fact Family Definitions v1", + "description": "Shared definitions owned by the semantic-fact schema family.", + "$defs": { + "collectionFamily": { + "enum": [ + "sequence", + "map", + "set", + "iterator", + "optional", + "result" + ] + }, + "accessMode": { + "enum": [ + "read", + "write", + "append", + "mutate", + "construct", + "validate" + ] + }, + "collectionFact": { + "type": "object", + "additionalProperties": false, + "required": [ + "family", + "impl" + ], + "properties": { + "family": { + "$ref": "#/$defs/collectionFamily" + }, + "impl": { + "type": "string", + "minLength": 1 + }, + "elementType": { + "type": "string", + "minLength": 1 + }, + "keyType": { + "type": "string", + "minLength": 1 + }, + "valueType": { + "type": "string", + "minLength": 1 + }, + "mutation": { + "type": "array", + "items": { + "enum": [ + "append", + "insert", + "remove", + "update", + "clear", + "replace" + ] + }, + "uniqueItems": true + } + } + }, + "fieldFact": { + "type": "object", + "additionalProperties": false, + "required": [ + "ownerKind", + "name", + "ownerPath", + "access" + ], + "properties": { + "ownerKind": { + "enum": [ + "struct", + "class", + "interface", + "dataclass", + "module", + "object" + ] + }, + "name": { + "type": "string", + "minLength": 1 + }, + "ownerPath": { + "type": "string", + "minLength": 1 + }, + "access": { + "type": "array", + "items": { + "$ref": "#/$defs/accessMode" + }, + "uniqueItems": true + } + } + }, + "typeFact": { + "type": "object", + "additionalProperties": false, + "required": [ + "name" + ], + "properties": { + "name": { + "type": "string", + "minLength": 1 + }, + "element": { + "type": "string", + "minLength": 1 + }, + "key": { + "type": "string", + "minLength": 1 + }, + "value": { + "type": "string", + "minLength": 1 + } + } + } + } +} diff --git a/schemas/semantic-fact-graph.v1.schema.json b/schemas/semantic-fact-graph.v1.schema.json index 3d2d244..bdcff34 100644 --- a/schemas/semantic-fact-graph.v1.schema.json +++ b/schemas/semantic-fact-graph.v1.schema.json @@ -61,7 +61,7 @@ "enum": ["rust", "python", "typescript", "julia"] }, "providerId": { - "enum": ["rs-harness", "py-harness", "ts-harness", "julia-lang-project-harness"] + "enum": ["asp-rust", "asp-python", "asp-typescript", "asp-julia"] }, "provenance": { "enum": ["parser", "build", "test", "failure", "receipt", "heuristic"] @@ -75,9 +75,6 @@ "collectionFamily": { "enum": ["sequence", "map", "set", "iterator", "optional", "result"] }, - "accessMode": { - "enum": ["read", "write", "append", "mutate", "construct", "validate"] - }, "nodeId": { "type": "string", "minLength": 1 @@ -250,85 +247,13 @@ } }, "fieldFact": { - "type": "object", - "additionalProperties": false, - "required": ["ownerKind", "name", "ownerPath", "access"], - "properties": { - "ownerKind": { - "enum": ["struct", "class", "interface", "dataclass", "module", "object"] - }, - "name": { - "type": "string", - "minLength": 1 - }, - "ownerPath": { - "type": "string", - "minLength": 1 - }, - "access": { - "type": "array", - "items": { - "$ref": "#/$defs/accessMode" - }, - "uniqueItems": true - } - } + "$ref": "semantic-fact-definitions.v1.schema.json#/$defs/fieldFact" }, "typeFact": { - "type": "object", - "additionalProperties": false, - "required": ["name"], - "properties": { - "name": { - "type": "string", - "minLength": 1 - }, - "element": { - "type": "string", - "minLength": 1 - }, - "key": { - "type": "string", - "minLength": 1 - }, - "value": { - "type": "string", - "minLength": 1 - } - } + "$ref": "semantic-fact-definitions.v1.schema.json#/$defs/typeFact" }, "collectionFact": { - "type": "object", - "additionalProperties": false, - "required": ["family", "impl"], - "properties": { - "family": { - "$ref": "#/$defs/collectionFamily" - }, - "impl": { - "type": "string", - "minLength": 1 - }, - "elementType": { - "type": "string", - "minLength": 1 - }, - "keyType": { - "type": "string", - "minLength": 1 - }, - "valueType": { - "type": "string", - "minLength": 1 - }, - "mutation": { - "type": "array", - "items": { - "enum": ["append", "insert", "remove", "update", "clear", "replace"] - }, - "uniqueItems": true - } - } + "$ref": "semantic-fact-definitions.v1.schema.json#/$defs/collectionFact" }, "edge": { "type": "object", diff --git a/schemas/semantic-fact-ontology.v1.schema.json b/schemas/semantic-fact-ontology.v1.schema.json index 70ee5bd..852a87e 100644 --- a/schemas/semantic-fact-ontology.v1.schema.json +++ b/schemas/semantic-fact-ontology.v1.schema.json @@ -87,16 +87,6 @@ "result" ] }, - "accessMode": { - "enum": [ - "read", - "write", - "append", - "mutate", - "construct", - "validate" - ] - }, "provenance": { "enum": [ "parser", @@ -330,111 +320,9 @@ } ] }, - "fieldFact": { - "type": "object", - "additionalProperties": false, - "required": [ - "ownerKind", - "name", - "ownerPath", - "access" - ], - "properties": { - "ownerKind": { - "enum": [ - "struct", - "class", - "interface", - "dataclass", - "module", - "object" - ] - }, - "name": { - "type": "string", - "minLength": 1 - }, - "ownerPath": { - "type": "string", - "minLength": 1 - }, - "access": { - "type": "array", - "items": { - "$ref": "#/$defs/accessMode" - }, - "uniqueItems": true - } - } - }, - "typeFact": { - "type": "object", - "additionalProperties": false, - "required": [ - "name" - ], - "properties": { - "name": { - "type": "string", - "minLength": 1 - }, - "element": { - "type": "string", - "minLength": 1 - }, - "key": { - "type": "string", - "minLength": 1 - }, - "value": { - "type": "string", - "minLength": 1 - } - } - }, - "collectionFact": { - "type": "object", - "additionalProperties": false, - "required": [ - "family", - "impl" - ], - "properties": { - "family": { - "$ref": "#/$defs/collectionFamily" - }, - "impl": { - "type": "string", - "minLength": 1 - }, - "elementType": { - "type": "string", - "minLength": 1 - }, - "keyType": { - "type": "string", - "minLength": 1 - }, - "valueType": { - "type": "string", - "minLength": 1 - }, - "mutation": { - "type": "array", - "items": { - "enum": [ - "append", - "insert", - "remove", - "update", - "clear", - "replace" - ] - }, - "uniqueItems": true - } - } - }, + "fieldFact": {"$ref": "semantic-fact-definitions.v1.schema.json#/$defs/fieldFact"}, + "typeFact": {"$ref": "semantic-fact-definitions.v1.schema.json#/$defs/typeFact"}, + "collectionFact": {"$ref": "semantic-fact-definitions.v1.schema.json#/$defs/collectionFact"}, "edge": { "type": "object", "additionalProperties": false, diff --git a/schemas/semantic-flow-lite.v1.schema.json b/schemas/semantic-flow-lite.v1.schema.json index 0be5747..3fcbafb 100644 --- a/schemas/semantic-flow-lite.v1.schema.json +++ b/schemas/semantic-flow-lite.v1.schema.json @@ -103,21 +103,15 @@ } }, "guards": { - "type": "array", - "items": { - "$ref": "#/$defs/flowPoint" - } + "$ref": "#/$defs/flowPointList" }, "effects": { - "type": "array", - "items": { - "$ref": "#/$defs/flowPoint" - } + "$ref": "#/$defs/flowPointList" }, "artifacts": { "type": "array", "items": { - "$ref": "#/$defs/artifactRef" + "$ref": "semantic-definitions.v1.schema.json#/$defs/artifactRef" } }, "confidence": { @@ -126,7 +120,7 @@ "omissions": { "type": "array", "items": { - "$ref": "#/$defs/omission" + "$ref": "semantic-definitions.v1.schema.json#/$defs/omission" } }, "fields": { @@ -138,36 +132,8 @@ "type": "string", "pattern": "^[a-z][a-z0-9_-]*$" }, - "scalar": { - "type": [ - "string", - "number", - "integer", - "boolean", - "null" - ] - }, "fields": { - "type": "object", - "additionalProperties": { - "oneOf": [ - { - "$ref": "#/$defs/scalar" - }, - { - "type": "array", - "items": { - "$ref": "#/$defs/scalar" - } - }, - { - "type": "object", - "additionalProperties": { - "$ref": "#/$defs/scalar" - } - } - ] - } + "$ref": "semantic-definitions.v1.schema.json#/$defs/fields" }, "sourceAuthority": { "enum": [ @@ -249,6 +215,12 @@ } } }, + "flowPointList": { + "type": "array", + "items": { + "$ref": "#/$defs/flowPoint" + } + }, "flowPoint": { "type": "object", "additionalProperties": false, @@ -279,62 +251,6 @@ } } }, - "artifactRef": { - "type": "object", - "additionalProperties": false, - "required": [ - "artifactId", - "schemaId" - ], - "properties": { - "artifactId": { - "type": "string", - "minLength": 1 - }, - "schemaId": { - "type": "string", - "minLength": 1 - }, - "schemaVersion": { - "type": "string", - "minLength": 1 - }, - "fields": { - "$ref": "#/$defs/fields" - } - } - }, - "omission": { - "type": "object", - "additionalProperties": false, - "required": [ - "kind", - "message" - ], - "properties": { - "kind": { - "enum": [ - "unsupported", - "unavailable", - "backend-unavailable", - "too-expensive", - "ambiguous", - "policy-blocked" - ] - }, - "message": { - "type": "string", - "minLength": 1 - }, - "target": { - "type": "string", - "minLength": 1 - }, - "fields": { - "$ref": "#/$defs/fields" - } - } - }, "confidence": { "enum": [ "proved", diff --git a/schemas/semantic-formal-proof-pilot.v1.schema.json b/schemas/semantic-formal-proof-pilot.v1.schema.json index a112585..ac32f5a 100644 --- a/schemas/semantic-formal-proof-pilot.v1.schema.json +++ b/schemas/semantic-formal-proof-pilot.v1.schema.json @@ -36,7 +36,7 @@ "pattern": "^[a-z][a-z0-9_.:-]*$" }, "producer": { - "$ref": "#/$defs/producer" + "$ref": "semantic-assurance-definitions.v1.schema.json#/$defs/producer" }, "target": { "$ref": "#/$defs/target" @@ -73,25 +73,6 @@ } }, "$defs": { - "producer": { - "type": "object", - "additionalProperties": false, - "required": ["languageId", "providerId", "namespace"], - "properties": { - "languageId": { - "type": "string", - "pattern": "^[a-z][a-z0-9_-]*$" - }, - "providerId": { - "type": "string", - "pattern": "^[a-z][a-z0-9_-]*$" - }, - "namespace": { - "type": "string", - "pattern": "^[a-z][a-z0-9_-]*(\\.[a-z][a-z0-9_-]*)+$" - } - } - }, "target": { "type": "object", "additionalProperties": false, diff --git a/schemas/semantic-graph-turbo-definitions.v1.schema.json b/schemas/semantic-graph-turbo-definitions.v1.schema.json new file mode 100644 index 0000000..43b3578 --- /dev/null +++ b/schemas/semantic-graph-turbo-definitions.v1.schema.json @@ -0,0 +1,64 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://agent-semantic-protocols.local/schemas/semantic-graph-turbo-definitions.v1.schema.json", + "title": "Semantic Graph Turbo Family Definitions v1", + "description": "Shared definitions owned by the semantic-graph-turbo schema family.", + "$defs": { + "profile": { + "enum": [ + "owner-query", + "query-deps", + "owner-tests", + "prime", + "read-frontier", + "failure-frontier", + "field-impact", + "type-impact", + "collection-impact", + "failure-evidence", + "test-selection", + "affected", + "evidence-quality", + "rust-evidence-quality" + ] + }, + "variant": { + "enum": [ + "full", + "no-receipt", + "no-read-memory", + "no-quality-fields", + "no-provider-facts", + "relation-weight-flat", + "no-query-seed-prior", + "no-package-cohesion", + "no-query-clause-coverage", + "no-local-evidence", + "no-topology-membership" + ] + }, + "qualityFailureList": { + "type": "array", + "items": { + "type": "object", + "additionalProperties": false, + "required": [ + "field", + "actual", + "expected" + ], + "properties": { + "field": { + "type": "string", + "minLength": 1 + }, + "actual": {}, + "expected": { + "type": "string", + "minLength": 1 + } + } + } + } + } +} diff --git a/schemas/semantic-graph-turbo-request.v1.schema.json b/schemas/semantic-graph-turbo-request.v1.schema.json index 8941881..049b297 100644 --- a/schemas/semantic-graph-turbo-request.v1.schema.json +++ b/schemas/semantic-graph-turbo-request.v1.schema.json @@ -12,6 +12,8 @@ "protocolVersion", "packetKind", "surface", + "sourceSnapshot", + "workspaceGeneration", "queryTerms", "profile", "algorithm", @@ -97,22 +99,7 @@ } }, "profile": { - "enum": [ - "owner-query", - "query-deps", - "owner-tests", - "prime", - "read-frontier", - "failure-frontier", - "field-impact", - "type-impact", - "collection-impact", - "failure-evidence", - "test-selection", - "affected", - "evidence-quality", - "rust-evidence-quality" - ] + "$ref": "semantic-graph-turbo-definitions.v1.schema.json#/$defs/profile" }, "algorithm": { "const": "typed-ppr-diverse" @@ -137,9 +124,16 @@ "auto", "provider", "finder", + "search-overlay", "ingest" ] }, + "sourceSnapshot": { + "$ref": "https://agent-semantic-protocols.dev/schemas/source-snapshot-evidence.v1.schema.json#/$defs/sourceSnapshot" + }, + "workspaceGeneration": { + "$ref": "#/$defs/workspaceGenerationEvidence" + }, "candidateSources": { "type": "array", "items": { @@ -314,6 +308,34 @@ } }, "$defs": { + "workspaceGenerationEvidence": { + "type": "object", + "additionalProperties": false, + "required": [ + "rootDigest", + "rootDepth", + "leafCount", + "ownerCount" + ], + "properties": { + "rootDigest": { + "type": "string", + "minLength": 1 + }, + "rootDepth": { + "type": "integer", + "enum": [0, 1] + }, + "leafCount": { + "type": "integer", + "minimum": 1 + }, + "ownerCount": { + "type": "integer", + "minimum": 1 + } + } + }, "graph": { "type": "object", "additionalProperties": false, @@ -719,6 +741,13 @@ "minLength": 1 }, "uniqueItems": true + }, + "reachability": { + "description": "Whether the selected owner is proven reachable by the active language build graph. Parsed or ranked ownership alone must remain unknown.", + "enum": [ + "verified", + "unknown" + ] } } }, @@ -785,6 +814,23 @@ "query": { "type": "string", "minLength": 1 + }, + "requiredActor": { + "description": "Actor that can execute this exact router-produced action without another routing hop.", + "enum": [ + "current-session", + "verified-resident-search-agent" + ] + }, + "requiredCapability": { + "description": "Parser-owned capability required to execute this action.", + "enum": [ + "owner-items" + ] + }, + "executable": { + "description": "True only when the router has selected an actor that owns requiredCapability.", + "type": "boolean" } } }, @@ -808,6 +854,7 @@ "enum": [ "item-skeleton", "query-code", + "lexical-search", "fd-query", "rg-query", "owner-items", @@ -821,6 +868,7 @@ "capabilityId": { "enum": [ "query", + "lexical-search", "fd", "rg", "owner-items", @@ -1083,6 +1131,8 @@ "doc", "children", "content", + "source", + "callable-skeleton", "code" ] }, diff --git a/schemas/semantic-handle.v1.schema.json b/schemas/semantic-handle.v1.schema.json index af38af7..07f2e33 100644 --- a/schemas/semantic-handle.v1.schema.json +++ b/schemas/semantic-handle.v1.schema.json @@ -70,7 +70,7 @@ "notes": { "type": "array", "items": { - "$ref": "#/$defs/note" + "$ref": "semantic-definitions.v1.schema.json#/$defs/note" } } }, @@ -116,35 +116,7 @@ "pattern": "^(?:\\.|(?!/)(?![A-Za-z]:)(?![0-9]+:)(?!\\.\\.?($|/))(?!.*(^|/)\\.\\.?($|/))(?!.*//)[^\\s:\\\\]+(?:/[^\\s:\\\\]+)*)$" }, "location": { - "type": "object", - "additionalProperties": false, - "required": [ - "path", - "lineRange" - ], - "properties": { - "path": { - "$ref": "#/$defs/projectPath" - }, - "lineRange": { - "type": "string", - "description": "Compact source line range as start:end, for example 10:43. This is display metadata, not selector identity.", - "pattern": "^[1-9][0-9]*:[1-9][0-9]*$" - }, - "structuralSelector": { - "type": "string", - "minLength": 1, - "pattern": "^[a-z][a-z0-9+.-]*://[^#\\s]+#[^\\s]+$" - }, - "displayLineRange": { - "type": "string", - "pattern": "^[1-9][0-9]*:[1-9][0-9]*$" - }, - "sourceLocatorHint": { - "type": "string", - "pattern": "^(?:\\.|(?!/)(?![A-Za-z]:)(?![0-9]+:)(?!\\.\\.?($|/))(?!.*(^|/)\\.\\.?($|/))(?!.*//)[^\\s:\\\\]+(?:/[^\\s:\\\\]+)*):[1-9][0-9]*(?:(?::|-)[1-9][0-9]*)?$" - } - } + "$ref": "semantic-definitions.v1.schema.json#/$defs/location" }, "handleKind": { "enum": [ @@ -283,9 +255,8 @@ "pattern": "^[1-9][0-9]*:[1-9][0-9]*$" }, "sourceLocatorHint": { - "type": "string", "description": "Compatibility source locator for exact code transport after a semantic selector has been chosen.", - "pattern": "^(?:\\.|(?!/)(?![A-Za-z]:)(?![0-9]+:)(?!\\.\\.?($|/))(?!.*(^|/)\\.\\.?($|/))(?!.*//)[^\\s:\\\\]+(?:/[^\\s:\\\\]+)*):[1-9][0-9]*(?:(?::|-)[1-9][0-9]*)?$" + "$ref": "semantic-definitions.v1.schema.json#/$defs/sourceLocator" }, "projection": { "enum": [ @@ -339,23 +310,6 @@ "$ref": "#/$defs/fields" } } - }, - "note": { - "type": "object", - "additionalProperties": false, - "required": [ - "kind", - "message" - ], - "properties": { - "kind": { - "type": "string", - "pattern": "^[a-z][a-z0-9_-]*$" - }, - "message": { - "type": "string" - } - } } } } diff --git a/schemas/semantic-invariant-candidate.v1.schema.json b/schemas/semantic-invariant-candidate.v1.schema.json new file mode 100644 index 0000000..a349d05 --- /dev/null +++ b/schemas/semantic-invariant-candidate.v1.schema.json @@ -0,0 +1,225 @@ +{ + "$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 e5aa841..f78cd80 100644 --- a/schemas/semantic-language-projection.v1.schema.json +++ b/schemas/semantic-language-projection.v1.schema.json @@ -96,7 +96,8 @@ "required": [ "sourceId", "path", - "sourceKind" + "sourceKind", + "sourceContentDigest" ], "properties": { "sourceId": { @@ -113,6 +114,9 @@ "config", "generated" ] + }, + "sourceContentDigest": { + "$ref": "#/$defs/digest" } } }, @@ -146,7 +150,8 @@ "ownerId", "kind", "name", - "selector" + "selector", + "projections" ], "properties": { "itemId": { @@ -163,6 +168,54 @@ }, "selector": { "$ref": "#/$defs/identifier" + }, + "projections": { + "type": "array", + "minItems": 1, + "items": { + "$ref": "#/$defs/itemProjection" + } + } + } + }, + "digest": { + "type": "string", + "pattern": "^[0-9a-f]{64}$" + }, + "itemProjection": { + "type": "object", + "additionalProperties": false, + "required": [ + "projectionKind", + "sourceByteStart", + "sourceByteEnd", + "normalizedParserFactsDigest", + "projectionDigest", + "projectionText" + ], + "properties": { + "projectionKind": { + "enum": [ + "source", + "callable-skeleton" + ] + }, + "sourceByteStart": { + "type": "integer", + "minimum": 0 + }, + "sourceByteEnd": { + "type": "integer", + "minimum": 0 + }, + "normalizedParserFactsDigest": { + "$ref": "#/$defs/digest" + }, + "projectionDigest": { + "$ref": "#/$defs/digest" + }, + "projectionText": { + "type": "string" } } }, diff --git a/schemas/semantic-language-registry.v1.schema.json b/schemas/semantic-language-registry.v1.schema.json index 106a53b..899905d 100644 --- a/schemas/semantic-language-registry.v1.schema.json +++ b/schemas/semantic-language-registry.v1.schema.json @@ -1,8 +1,8 @@ { "$schema": "https://json-schema.org/draft/2020-12/schema", - "$id": "https://agent-semantic-protocols.local/schemas/semantic-language-registry.v1.schema.json", - "title": "Semantic Language Registry", - "description": "Language-server-style provider registry for semantic language protocol implementations.", + "$id": "https://tao3k.github.io/agent-semantic-protocols/schemas/semantic-language-registry.v1.schema.json", + "title": "Semantic Language Registry v1", + "description": "Language-server-style provider registry with authoritative hook invocation templates.", "type": "object", "additionalProperties": false, "required": [ @@ -60,74 +60,44 @@ "agent" ] }, - "outputSchemaId": { - "type": "string", - "pattern": "^[a-z][a-z0-9_.-]*$" - }, - "namespace": { - "type": "string", - "pattern": "^[a-z][a-z0-9_-]*(\\.[a-z][a-z0-9_-]*)+$" - }, "providerExecution": { - "type": "string", "enum": [ "external-process", "embedded" ] }, - "capabilityDescriptor": { - "type": "object", - "additionalProperties": false, - "required": [ - "languageId", - "namespace", - "name" - ], - "properties": { - "languageId": { - "$ref": "#/$defs/identifier" - }, - "namespace": { - "$ref": "#/$defs/identifier" - }, - "name": { - "$ref": "#/$defs/identifier" - } + "stringArray": { + "type": "array", + "items": { + "type": "string" } }, - "fallbackDescriptor": { + "commandTemplate": { "type": "object", "additionalProperties": false, "required": [ - "name", - "trigger" + "argv" ], "properties": { - "name": { - "$ref": "#/$defs/identifier" - }, - "trigger": { - "enum": [ - "query-miss", - "item-query-miss", - "path-only-owner" - ] - }, - "appliesToPipes": { + "argv": { "type": "array", - "uniqueItems": true, + "minItems": 1, "items": { - "$ref": "#/$defs/identifier" + "type": "string", + "minLength": 1 } }, - "maxItems": { - "type": "integer", - "minimum": 1 + "stdinMode": { + "enum": [ + "none", + "pipe-candidates", + "pipe-diff", + "unknown" + ] } } }, "benchmarkInvocation": { - "description": "Provider-owned public ASP search invocation for the large-library runtime gate.", "type": "object", "additionalProperties": false, "required": [ @@ -137,7 +107,6 @@ ], "properties": { "args": { - "description": "Arguments relative to `asp `, with named corpus placeholders such as {workspace}, {owner}, {query}, and {dependency}.", "type": "array", "minItems": 2, "items": { @@ -190,7 +159,8 @@ "namespace", "methods", "methodDescriptors", - "schemas" + "schemas", + "queryPackDescriptor" ], "properties": { "languageId": { @@ -214,7 +184,8 @@ } }, "namespace": { - "$ref": "#/$defs/namespace" + "type": "string", + "pattern": "^[a-z][a-z0-9_-]*(\\.[a-z][a-z0-9_-]*)+$" }, "displayName": { "type": "string", @@ -231,7 +202,6 @@ "methodDescriptors": { "type": "array", "minItems": 1, - "uniqueItems": true, "items": { "$ref": "#/$defs/methodDescriptor" } @@ -241,316 +211,20 @@ "items": { "$ref": "#/$defs/schemaRegistration" } + }, + "queryPackDescriptor": { + "$ref": "https://schemas.agent-semantic-protocols.dev/provider-query-pack-descriptor.schema.json" } } }, "methodDescriptor": { "type": "object", - "additionalProperties": false, "required": [ "method", "command", "supportsJson", - "supportsCompact" - ], - "allOf": [ - { - "if": { - "properties": { - "method": { "const": "guide" } - }, - "required": ["method"] - }, - "then": { - "properties": { - "command": { "const": "guide" } - }, - "not": { - "required": ["view"] - } - } - }, - { - "if": { - "properties": { - "method": { - "pattern": "^search/" - } - }, - "required": [ - "method" - ] - }, - "then": { - "required": [ - "view", - "outputSchemaIds", - "requiresQuery", - "acceptsStdin", - "supportsPackageScope" - ], - "properties": { - "command": { - "const": "search" - }, - "outputSchemaIds": { - "minItems": 1 - } - } - } - }, - { - "if": { - "properties": { - "method": { - "pattern": "^query(?:/|$)" - } - }, - "required": [ - "method" - ] - }, - "then": { - "required": [ - "outputSchemaIds" - ], - "properties": { - "command": { - "const": "query" - }, - "outputSchemaIds": { - "minItems": 1 - } - }, - "not": { - "required": [ - "view" - ] - } - } - }, - { - "if": { - "properties": { - "method": { - "pattern": "^check/" - } - }, - "required": [ - "method" - ] - }, - "then": { - "properties": { - "command": { - "const": "check" - } - }, - "not": { - "required": [ - "view" - ] - } - } - }, - { - "if": { - "properties": { - "method": { - "pattern": "^agent/" - } - }, - "required": [ - "method" - ] - }, - "then": { - "properties": { - "command": { - "const": "agent" - } - }, - "not": { - "required": [ - "view" - ] - } - } - }, - { - "if": { - "properties": { - "method": { - "pattern": "^proof/" - } - }, - "required": [ - "method" - ] - }, - "then": { - "required": [ - "outputSchemaIds" - ], - "properties": { - "command": { - "const": "proof" - }, - "outputSchemaIds": { - "minItems": 1 - } - }, - "not": { - "required": [ - "view" - ] - } - } - }, - { - "if": { - "properties": { - "method": { - "pattern": "^review/" - } - }, - "required": [ - "method" - ] - }, - "then": { - "required": [ - "outputSchemaIds" - ], - "properties": { - "command": { - "const": "review" - }, - "outputSchemaIds": { - "minItems": 1 - } - }, - "not": { - "required": [ - "view" - ] - } - } - }, - { - "if": { - "properties": { - "method": { - "pattern": "^evidence/" - } - }, - "required": [ - "method" - ] - }, - "then": { - "required": [ - "outputSchemaIds" - ], - "properties": { - "command": { - "const": "evidence" - }, - "outputSchemaIds": { - "minItems": 1 - } - }, - "not": { - "required": [ - "view" - ] - } - } - }, - { - "if": { - "properties": { - "method": { - "pattern": "^verification/" - } - }, - "required": [ - "method" - ] - }, - "then": { - "properties": { - "command": { - "const": "verification" - } - }, - "not": { - "required": [ - "view" - ] - } - } - }, - { - "if": { - "properties": { - "method": { - "pattern": "^ast-patch/" - } - }, - "required": [ - "method" - ] - }, - "then": { - "required": [ - "outputSchemaIds", - "mutationAvailable" - ], - "properties": { - "command": { - "const": "ast-patch" - }, - "outputSchemaIds": { - "minItems": 1 - }, - "mutationAvailable": { - "type": "boolean" - } - }, - "not": { - "required": [ - "view" - ] - } - } - }, - { - "if": { - "properties": { - "method": { - "pattern": "^agent/" - }, - "supportsJson": { - "const": true - } - }, - "required": [ - "method", - "supportsJson" - ] - }, - "then": { - "required": [ - "outputSchemaIds" - ], - "properties": { - "outputSchemaIds": { - "minItems": 1 - } - } - } - } + "supportsCompact", + "invocation" ], "properties": { "method": { @@ -559,376 +233,53 @@ "command": { "$ref": "#/$defs/command" }, - "view": { - "type": "string", - "pattern": "^[a-z][a-z0-9_-]*$" + "invocation": { + "$ref": "#/$defs/commandTemplate" }, - "outputSchemaIds": { - "type": "array", - "uniqueItems": true, - "items": { - "$ref": "#/$defs/outputSchemaId" - } - }, - "packetSchemas": { - "type": "array", - "description": "Short packet schema names advertised by this method, for example semantic-tree-sitter-query.v1.", - "uniqueItems": true, - "items": { - "type": "string", - "pattern": "^[a-z][a-z0-9_-]+\\.v[0-9]+$" - } - }, - "requiresQuery": { - "type": "boolean" - }, - "acceptsStdin": { - "type": "boolean" - }, - "supportsPackageScope": { - "type": "boolean" + "argumentProjection": { + "$ref": "https://tao3k.github.io/agent-semantic-protocols/schemas/provider-method-argument-projection.v1.schema.json" }, "benchmarkInvocation": { "$ref": "#/$defs/benchmarkInvocation" }, - "supportsQuerySet": { - "type": "boolean" - }, - "queryInputForms": { - "type": "array", - "uniqueItems": true, - "items": { - "enum": [ - "selector", - "code-shaped", - "catalog-id", - "s-expression", - "term", - "kind", - "field", - "metadata", - "content" - ] - } - }, - "adapterModes": { - "description": "Tree-sitter-compatible query projection modes supported by this method.", - "type": "array", - "uniqueItems": true, - "items": { - "enum": [ - "native-projection", - "hybrid", - "tree-sitter-runtime", - "codeql-query", - "cached-replay" - ] - } - }, - "sourceAuthorities": { - "description": "Authorities that may prove query packets emitted by this method.", - "type": "array", - "uniqueItems": true, - "items": { - "enum": [ - "native-parser", - "native-parser-adapter", - "tree-sitter-runtime", - "codeql", - "hybrid", - "cached-provider-export" - ] - } - }, - "executionBackends": { - "description": "Query execution engines this method can use while preserving the advertised packet shape. CodeQL is optional and must still render through frontier-first packets.", - "type": "array", - "uniqueItems": true, - "items": { - "enum": [ - "native-parser", - "tree-sitter-runtime", - "codeql", - "hybrid", - "cached-replay" - ] - } - }, - "renderProfiles": { - "description": "Prompt-facing render contracts this method can produce without changing the query packet shape.", - "type": "array", - "uniqueItems": true, - "items": { - "enum": [ - "compact-graph-frontier", - "corpus-locator", - "flow-lite-frontier", - "owner-local-projection" - ] - } - }, - "queryCatalogs": { - "type": "array", - "uniqueItems": true, - "items": { - "$ref": "#/$defs/queryCatalogDescriptor" - } - }, - "grammarId": { - "type": "string", - "pattern": "^tree-sitter-[a-z][a-z0-9_-]*$" - }, - "grammarProfileVersion": { - "type": "string", - "minLength": 1 - }, - "grammarProfileSchema": { - "type": "string", - "pattern": "^semantic-tree-sitter-grammar-profile\\.v[0-9]+$" - }, - "grammarProfilePath": { - "type": "string", - "minLength": 1, - "pattern": "^(?!/)(?![A-Za-z]:)(?![0-9]+:)(?!\\.\\.?($|/))(?!.*(^|/)\\.\\.?($|/))(?!.*//)[^\\s:\\\\]+(?:/[^\\s:\\\\]+)*\\.json$", - "description": "Canonical grammar profile path/handle. When the provider embeds profile source, this is not a downstream filesystem requirement." - }, - "supportedPredicates": { - "type": "array", - "uniqueItems": true, - "items": { - "enum": [ - "#eq?", - "#any-eq?", - "#any-of?", - "#match?", - "#any-match?", - "#not-eq?", - "#not-match?" - ] - } - }, - "unsupportedPredicates": { - "type": "array", - "uniqueItems": true, - "items": { - "enum": [ - "#eq?", - "#any-eq?", - "#any-of?", - "#match?", - "#any-match?", - "#not-eq?", - "#not-match?" - ] - } - }, - "cacheReplay": { - "type": "boolean" - }, - "codeOutput": { - "$ref": "#/$defs/codeOutputDescriptor" - }, - "unsupportedPatternBehavior": { - "description": "Provider response for unsupported tree-sitter-compatible pattern shapes. Providers must not silently fall back to raw search.", - "enum": [ - "diagnostic", - "empty-frontier" - ] - }, - "acceptedQuerySetSelectors": { - "type": "array", - "uniqueItems": true, - "items": { - "enum": [ - "exact-set", - "prefix-set", - "lexical-set", - "stdin-path-set" - ] - } - }, - "querySetScopes": { - "type": "array", - "uniqueItems": true, - "items": { - "enum": [ - "project", - "package", - "owner" - ] - } - }, - "acceptedPipes": { - "type": "array", - "uniqueItems": true, - "items": { - "type": "string", - "pattern": "^[a-z][a-z0-9_-]*$" - } - }, - "capabilities": { - "type": "array", - "uniqueItems": true, - "items": { - "$ref": "#/$defs/capabilityDescriptor" - } - }, - "ingestRequiredFor": { - "type": "array", - "uniqueItems": true, - "items": { - "$ref": "#/$defs/capabilityDescriptor" - } - }, - "fallbacks": { - "type": "array", - "uniqueItems": true, - "items": { - "$ref": "#/$defs/fallbackDescriptor" - } - }, - "clients": { - "type": "array", - "uniqueItems": true, - "items": { - "$ref": "#/$defs/identifier" - } - }, - "requiredOptions": { - "type": "array", - "uniqueItems": true, - "items": { - "type": "string", - "minLength": 1 - } - }, - "input": { - "type": "string", - "minLength": 1 - }, - "outputModes": { - "type": "array", - "uniqueItems": true, - "items": { - "enum": [ - "frontier", - "json", - "code", - "names", - "outline", - "read-packet" - ] - } - }, "supportsJson": { "type": "boolean" }, "supportsCompact": { "type": "boolean" - }, - "mutationAvailable": { - "type": "boolean" } - } - }, - "queryCatalogDescriptor": { - "type": "object", - "additionalProperties": false, - "required": [ - "id", - "path", - "captures" - ], - "properties": { - "id": { - "type": "string", - "pattern": "^[a-z][a-z0-9_.-]*$" - }, - "path": { - "type": "string", - "minLength": 1, - "pattern": "^(?!/)(?![A-Za-z]:)(?![0-9]+:)(?!\\.\\.?($|/))(?!.*(^|/)\\.\\.?($|/))(?!.*//)[^\\s:\\\\]+(?:/[^\\s:\\\\]+)*\\.scm$", - "description": "Canonical catalog path/handle. When sourceDelivery is provider-binary-embedded, this is not a downstream filesystem requirement." - }, - "captures": { - "type": "array", - "minItems": 1, - "uniqueItems": true, - "items": { - "type": "string", - "pattern": "^[A-Za-z][A-Za-z0-9_.-]*$" - } - }, - "nodeTypes": { - "type": "array", - "uniqueItems": true, - "items": { - "type": "string", - "pattern": "^[A-Za-z_][A-Za-z0-9_]*$" - } - }, - "fields": { - "type": "array", - "uniqueItems": true, - "items": { - "type": "string", - "pattern": "^[A-Za-z_][A-Za-z0-9_]*$" + }, + "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(?:$|-)" + } + } } - }, - "description": { - "type": "string", - "minLength": 1 - }, - "fingerprint": { - "type": "string", - "pattern": "^sha256:[0-9a-f]{64}$", - "description": "Content fingerprint for the canonical .scm catalog source embedded by the provider." - }, - "sourceDelivery": { - "enum": [ - "provider-binary-embedded" - ], - "description": "How the provider makes this canonical .scm source available to downstream clients. provider-binary-embedded means the provider binary carries the source and does not require package source files on the target machine." } - } - }, - "codeOutputDescriptor": { - "type": "object", - "additionalProperties": false, - "required": [ - "mode", - "multiMatch", - "requires" ], - "properties": { - "mode": { - "enum": [ - "pure-code" - ] - }, - "multiMatch": { - "enum": [ - "deny", - "allow", - "require-limit", - "first-only" - ] - }, - "requires": { - "type": "array", - "minItems": 1, - "uniqueItems": true, - "items": { - "enum": [ - "exact-selector", - "unique-predicate", - "unique-match", - "--limit", - "--first" - ] - } - } - } + "additionalProperties": true } } } diff --git a/schemas/semantic-native-syntax-fact-index.v1.schema.json b/schemas/semantic-native-syntax-fact-index.v1.schema.json index 0ccaa6d..3768c05 100644 --- a/schemas/semantic-native-syntax-fact-index.v1.schema.json +++ b/schemas/semantic-native-syntax-fact-index.v1.schema.json @@ -76,7 +76,7 @@ "notes": { "type": "array", "items": { - "$ref": "#/$defs/note" + "$ref": "semantic-definitions.v1.schema.json#/$defs/note" } } }, @@ -351,23 +351,6 @@ "$ref": "#/$defs/fields" } } - }, - "note": { - "type": "object", - "additionalProperties": false, - "required": [ - "kind", - "message" - ], - "properties": { - "kind": { - "type": "string", - "pattern": "^[a-z][a-z0-9_-]*$" - }, - "message": { - "type": "string" - } - } } } } diff --git a/schemas/semantic-owner-item-evidence.v1.schema.json b/schemas/semantic-owner-item-evidence.v1.schema.json new file mode 100644 index 0000000..bb33bd6 --- /dev/null +++ b/schemas/semantic-owner-item-evidence.v1.schema.json @@ -0,0 +1,140 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "agent.semantic-protocols.semantic-owner-item-evidence.v1", + "title": "Semantic owner-item evidence", + "type": "object", + "required": [ + "schemaId", + "schemaVersion", + "language", + "owner", + "items", + "edges" + ], + "properties": { + "schemaId": { + "const": "semantic-owner-item-evidence.v1" + }, + "schemaVersion": { + "const": "1" + }, + "language": { + "type": "string", + "minLength": 1 + }, + "owner": { + "type": "object", + "required": [ + "path" + ], + "properties": { + "path": { + "type": "string", + "minLength": 1 + } + }, + "additionalProperties": false + }, + "items": { + "type": "array", + "items": { + "$ref": "#/$defs/item" + } + }, + "edges": { + "type": "object" + }, + "admission": { + "$ref": "#/$defs/admission" + } + }, + "$defs": { + "admission": { + "type": "object", + "required": [ + "status", + "requestedLanguage" + ], + "properties": { + "status": { + "enum": [ + "accepted", + "owner-language-mismatch" + ] + }, + "requestedLanguage": { + "type": "string", + "minLength": 1 + }, + "ownerExtension": { + "type": "string", + "minLength": 1 + }, + "expectedExtensions": { + "type": "array", + "items": { + "type": "string", + "minLength": 1 + } + }, + "suggestedLanguage": { + "type": "string", + "minLength": 1 + } + }, + "additionalProperties": false + }, + "item": { + "type": "object", + "required": [ + "kind", + "name", + "proof" + ], + "properties": { + "kind": { + "type": "string", + "minLength": 1 + }, + "name": { + "type": "string", + "minLength": 1 + }, + "proof": { + "type": "object" + }, + "readSelector": { + "type": "string", + "minLength": 1 + }, + "evidenceSelector": { + "type": "string", + "minLength": 1 + }, + "enclosingReadSelector": { + "type": "string", + "minLength": 1 + }, + "graphOnly": { + "const": true + }, + "next": { + "type": "object" + } + }, + "oneOf": [ + { + "required": [ + "readSelector" + ] + }, + { + "required": [ + "evidenceSelector", + "graphOnly" + ] + } + ] + } + } +} diff --git a/schemas/semantic-owner-item-evidence.v2.schema.json b/schemas/semantic-owner-item-evidence.v2.schema.json deleted file mode 100644 index cb83737..0000000 --- a/schemas/semantic-owner-item-evidence.v2.schema.json +++ /dev/null @@ -1,61 +0,0 @@ -{ - "$schema": "https://json-schema.org/draft/2020-12/schema", - "$id": "agent.semantic-protocols.semantic-owner-item-evidence.v2", - "title": "Semantic owner-item evidence", - "type": "object", - "required": ["schemaId", "schemaVersion", "language", "owner", "items", "edges"], - "properties": { - "schemaId": { "const": "semantic-owner-item-evidence.v2" }, - "schemaVersion": { "const": "2" }, - "language": { "type": "string", "minLength": 1 }, - "owner": { - "type": "object", - "required": ["path"], - "properties": { "path": { "type": "string", "minLength": 1 } }, - "additionalProperties": false - }, - "items": { - "type": "array", - "items": { "$ref": "#/$defs/item" } - }, - "edges": { "type": "object" }, - "admission": { "$ref": "#/$defs/admission" } - }, - "$defs": { - "admission": { - "type": "object", - "required": ["status", "requestedLanguage"], - "properties": { - "status": { - "enum": ["accepted", "owner-language-mismatch"] - }, - "requestedLanguage": { "type": "string", "minLength": 1 }, - "ownerExtension": { "type": "string", "minLength": 1 }, - "expectedExtensions": { - "type": "array", - "items": { "type": "string", "minLength": 1 } - }, - "suggestedLanguage": { "type": "string", "minLength": 1 } - }, - "additionalProperties": false - }, - "item": { - "type": "object", - "required": ["kind", "name", "proof"], - "properties": { - "kind": { "type": "string", "minLength": 1 }, - "name": { "type": "string", "minLength": 1 }, - "proof": { "type": "object" }, - "readSelector": { "type": "string", "minLength": 1 }, - "evidenceSelector": { "type": "string", "minLength": 1 }, - "enclosingReadSelector": { "type": "string", "minLength": 1 }, - "graphOnly": { "const": true }, - "next": { "type": "object" } - }, - "oneOf": [ - { "required": ["readSelector"] }, - { "required": ["evidenceSelector", "graphOnly"] } - ] - } - } -} diff --git a/schemas/semantic-query-packet.v1.schema.json b/schemas/semantic-query-packet.v1.schema.json index 9936a14..fc13452 100644 --- a/schemas/semantic-query-packet.v1.schema.json +++ b/schemas/semantic-query-packet.v1.schema.json @@ -163,7 +163,7 @@ "notes": { "type": "array", "items": { - "$ref": "#/$defs/note" + "$ref": "semantic-definitions.v1.schema.json#/$defs/note" } } }, @@ -211,7 +211,7 @@ }, "code": { "type": "string", - "description": "Agent-facing compact code projection. This is parser-owned understanding text, not necessarily exact editable source. Line-protocol code text fields and --code output serialize this same projection value." + "description": "Agent-facing compact projection. This is parser-owned understanding text, not necessarily exact editable source. Exact editable bytes are requested through an exact selector with projection=source." }, "contentKind": { "const": "source-code" @@ -1087,23 +1087,6 @@ "$ref": "#/$defs/location" } } - }, - "note": { - "type": "object", - "additionalProperties": false, - "required": [ - "kind", - "message" - ], - "properties": { - "kind": { - "type": "string", - "pattern": "^[a-z][a-z0-9_-]*$" - }, - "message": { - "type": "string" - } - } } } } diff --git a/schemas/semantic-read-packet.v1.schema.json b/schemas/semantic-read-packet.v1.schema.json index 5da720a..cc29e70 100644 --- a/schemas/semantic-read-packet.v1.schema.json +++ b/schemas/semantic-read-packet.v1.schema.json @@ -190,7 +190,7 @@ "notes": { "type": "array", "items": { - "$ref": "#/$defs/note" + "$ref": "semantic-definitions.v1.schema.json#/$defs/note" } } }, @@ -659,23 +659,6 @@ "minLength": 1 } } - }, - "note": { - "type": "object", - "additionalProperties": false, - "required": [ - "kind", - "message" - ], - "properties": { - "kind": { - "type": "string", - "pattern": "^[a-z][a-z0-9_-]*$" - }, - "message": { - "type": "string" - } - } } } } diff --git a/schemas/semantic-relation-plan.v1.schema.json b/schemas/semantic-relation-plan.v1.schema.json index 1e668d9..f102de4 100644 --- a/schemas/semantic-relation-plan.v1.schema.json +++ b/schemas/semantic-relation-plan.v1.schema.json @@ -89,13 +89,13 @@ "artifacts": { "type": "array", "items": { - "$ref": "#/$defs/artifactRef" + "$ref": "semantic-definitions.v1.schema.json#/$defs/artifactRef" } }, "omissions": { "type": "array", "items": { - "$ref": "#/$defs/omission" + "$ref": "semantic-definitions.v1.schema.json#/$defs/omission" } }, "next": { @@ -113,36 +113,8 @@ "type": "string", "pattern": "^[a-z][a-z0-9_-]*$" }, - "scalar": { - "type": [ - "string", - "number", - "integer", - "boolean", - "null" - ] - }, "fields": { - "type": "object", - "additionalProperties": { - "oneOf": [ - { - "$ref": "#/$defs/scalar" - }, - { - "type": "array", - "items": { - "$ref": "#/$defs/scalar" - } - }, - { - "type": "object", - "additionalProperties": { - "$ref": "#/$defs/scalar" - } - } - ] - } + "$ref": "semantic-definitions.v1.schema.json#/$defs/fields" }, "sourceAuthority": { "enum": [ @@ -318,62 +290,6 @@ } } }, - "artifactRef": { - "type": "object", - "additionalProperties": false, - "required": [ - "artifactId", - "schemaId" - ], - "properties": { - "artifactId": { - "type": "string", - "minLength": 1 - }, - "schemaId": { - "type": "string", - "minLength": 1 - }, - "schemaVersion": { - "type": "string", - "minLength": 1 - }, - "fields": { - "$ref": "#/$defs/fields" - } - } - }, - "omission": { - "type": "object", - "additionalProperties": false, - "required": [ - "kind", - "message" - ], - "properties": { - "kind": { - "enum": [ - "unsupported", - "unavailable", - "backend-unavailable", - "too-expensive", - "ambiguous", - "policy-blocked" - ] - }, - "message": { - "type": "string", - "minLength": 1 - }, - "target": { - "type": "string", - "minLength": 1 - }, - "fields": { - "$ref": "#/$defs/fields" - } - } - }, "nextAction": { "type": "object", "additionalProperties": false, diff --git a/schemas/semantic-review-packet.v1.schema.json b/schemas/semantic-review-packet.v1.schema.json index b8df3b4..65a3c87 100644 --- a/schemas/semantic-review-packet.v1.schema.json +++ b/schemas/semantic-review-packet.v1.schema.json @@ -38,10 +38,10 @@ "pattern": "^[a-z][a-z0-9_.:-]*$" }, "producer": { - "$ref": "#/$defs/producer" + "$ref": "semantic-assurance-definitions.v1.schema.json#/$defs/producer" }, "project": { - "$ref": "#/$defs/project" + "$ref": "semantic-assurance-definitions.v1.schema.json#/$defs/project" }, "summary": { "$ref": "#/$defs/summary" @@ -116,48 +116,6 @@ "$ref": "#/$defs/scalar" } }, - "projectPath": { - "type": "string", - "minLength": 1, - "pattern": "^(?:\\.|(?!/)(?![A-Za-z]:)(?![0-9]+:)(?!\\.\\.?($|/))(?!.*(^|/)\\.\\.?($|/))(?!.*//)[^\\s:\\\\]+(?:/[^\\s:\\\\]+)*)$" - }, - "producer": { - "type": "object", - "additionalProperties": false, - "required": ["languageId", "providerId", "namespace"], - "properties": { - "languageId": { - "type": "string", - "pattern": "^[a-z][a-z0-9_-]*$" - }, - "providerId": { - "type": "string", - "pattern": "^[a-z][a-z0-9_-]*$" - }, - "namespace": { - "type": "string", - "pattern": "^[a-z][a-z0-9_-]*(\\.[a-z][a-z0-9_-]*)+$" - } - } - }, - "project": { - "type": "object", - "additionalProperties": false, - "required": ["root"], - "properties": { - "root": { - "type": "string", - "minLength": 1 - }, - "package": { - "type": "string", - "minLength": 1 - }, - "fields": { - "$ref": "#/$defs/fields" - } - } - }, "summary": { "type": "object", "additionalProperties": false, @@ -230,23 +188,6 @@ "waiver" ] }, - "location": { - "type": "object", - "additionalProperties": false, - "properties": { - "path": { - "$ref": "#/$defs/projectPath" - }, - "line": { - "type": "integer", - "minimum": 1 - }, - "column": { - "type": "integer", - "minimum": 0 - } - } - }, "changedInvariant": { "type": "object", "additionalProperties": false, @@ -284,7 +225,7 @@ "minLength": 1 }, "location": { - "$ref": "#/$defs/location" + "$ref": "semantic-assurance-definitions.v1.schema.json#/$defs/location" }, "requiredReceipts": { "type": "array", diff --git a/schemas/semantic-search-packet.v1.schema.json b/schemas/semantic-search-packet.v1.schema.json index 4180af6..30a4f88 100644 --- a/schemas/semantic-search-packet.v1.schema.json +++ b/schemas/semantic-search-packet.v1.schema.json @@ -183,11 +183,8 @@ "noOutput": { "$ref": "#/$defs/noOutput" }, - "avoidNextActions": { - "type": "array", - "items": { - "$ref": "#/$defs/avoidNextAction" - } + "avoidNextActions": { + "$ref": "#/$defs/avoidNextActionList" }, "header": { "$ref": "#/$defs/header" @@ -279,11 +276,8 @@ "$ref": "#/$defs/finding" } }, - "nextActions": { - "type": "array", - "items": { - "$ref": "#/$defs/nextAction" - } + "nextActions": { + "$ref": "#/$defs/nextActionList" }, "delegationHints": { "type": "array", @@ -299,6 +293,18 @@ } }, "$defs": { + "nextActionList": { + "type": "array", + "items": { + "$ref": "#/$defs/nextAction" + } + }, + "avoidNextActionList": { + "type": "array", + "items": { + "$ref": "#/$defs/avoidNextAction" + } + }, "scalar": { "oneOf": [ { @@ -1193,11 +1199,8 @@ "$ref": "#/$defs/projectPath" } }, - "seeds": { - "type": "array", - "items": { - "$ref": "#/$defs/nextAction" - } + "seeds": { + "$ref": "#/$defs/nextActionList" }, "fields": { "$ref": "#/$defs/fields" @@ -1337,11 +1340,8 @@ "$ref": "#/$defs/nextAction" } }, - "avoidNextActions": { - "type": "array", - "items": { - "$ref": "#/$defs/avoidNextAction" - } + "avoidNextActions": { + "$ref": "#/$defs/avoidNextActionList" }, "queryBudget": { "$ref": "#/$defs/queryBudget" @@ -1731,11 +1731,8 @@ "type": "string" } }, - "nextActions": { - "type": "array", - "items": { - "$ref": "#/$defs/nextAction" - } + "nextActions": { + "$ref": "#/$defs/nextActionList" }, "fields": { "$ref": "#/$defs/fields" @@ -2144,7 +2141,8 @@ "additionalProperties": false, "required": [ "kind", - "target" + "target", + "command" ], "properties": { "kind": { @@ -2164,10 +2162,28 @@ "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]*$" + "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" diff --git a/schemas/semantic-search-storage-route.v1.schema.json b/schemas/semantic-search-storage-route.v1.schema.json new file mode 100644 index 0000000..eda87fd --- /dev/null +++ b/schemas/semantic-search-storage-route.v1.schema.json @@ -0,0 +1,106 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://agent-semantic-protocols.dev/schemas/semantic-search-storage-route.v1.schema.json", + "title": "Semantic Search Storage Route", + "type": "object", + "additionalProperties": false, + "required": ["schemaId", "schemaVersion", "profile", "storageClass", "queryRoute"], + "properties": { + "schemaId": { "const": "agent.semantic-protocols.semantic-search-storage-route" }, + "schemaVersion": { "const": "1" }, + "profile": { "$ref": "#/$defs/profile" }, + "storageClass": { + "enum": ["static-shared-database", "generation-shallow-database", "owner-local-resident", "exact-resident"] + }, + "queryRoute": { + "enum": ["static-database-index", "shallow-database-index", "resident-memory", "exact-mmap"] + } + }, + "allOf": [ + { + "if": { "properties": { "queryRoute": { "const": "static-database-index" } } }, + "then": { + "properties": { + "storageClass": { "const": "static-shared-database" }, + "profile": { + "properties": { + "mutationClass": { "enum": ["immutable", "low-mutation"] }, + "sharingScope": { "enum": ["global", "toolchain-version", "package-version"] } + } + } + } + } + }, + { + "if": { "properties": { "queryRoute": { "const": "shallow-database-index" } } }, + "then": { + "properties": { + "storageClass": { "const": "generation-shallow-database" }, + "profile": { + "properties": { + "mutationClass": { "const": "generation-bound" }, + "sharingScope": { "const": "workspace-generation" }, + "algorithmEvidence": { + "properties": { + "treeDepth": { "maximum": 1 }, + "traversalRadius": { "maximum": 1 } + } + } + } + } + } + } + }, + { + "if": { "properties": { "queryRoute": { "const": "resident-memory" } } }, + "then": { "properties": { "storageClass": { "const": "owner-local-resident" } } } + }, + { + "if": { "properties": { "queryRoute": { "const": "exact-mmap" } } }, + "then": { "properties": { "storageClass": { "const": "exact-resident" } } } + } + ], + "$defs": { + "profile": { + "type": "object", + "additionalProperties": false, + "required": ["mutationClass", "sharingScope", "identityDigest", "algorithmEvidence"], + "properties": { + "mutationClass": { "enum": ["immutable", "low-mutation", "generation-bound", "owner-dynamic"] }, + "sharingScope": { "enum": ["global", "toolchain-version", "package-version", "workspace-generation", "owner-content"] }, + "identityDigest": { "type": "string", "minLength": 1 }, + "algorithmEvidence": { "$ref": "#/$defs/algorithmEvidence" } + } + }, + "algorithmEvidence": { + "type": "object", + "additionalProperties": false, + "required": ["treeDepth", "traversalRadius", "graphNodeCount", "graphEdgeCount", "merkleDeltaOwnerCount", "dependencyFanOut", "crossWorkspaceReuseCount"], + "properties": { + "treeDepth": { "type": "integer", "minimum": 0 }, + "traversalRadius": { "type": "integer", "minimum": 0 }, + "graphNodeCount": { "type": "integer", "minimum": 0 }, + "graphEdgeCount": { "type": "integer", "minimum": 0 }, + "merkleDeltaOwnerCount": { "type": "integer", "minimum": 0 }, + "dependencyFanOut": { "type": "integer", "minimum": 0 }, + "crossWorkspaceReuseCount": { "type": "integer", "minimum": 0 }, + "componentCount": { "type": "integer", "minimum": 0 }, + "cutVertexCount": { "type": "integer", "minimum": 0 }, + "providerGraphEvidence": { + "type": "array", + "items": { "$ref": "#/$defs/providerGraphEvidence" } + } + } + }, + "providerGraphEvidence": { + "type": "object", + "additionalProperties": false, + "required": ["providerId", "algorithmId", "evidenceDigest"], + "properties": { + "providerId": { "type": "string", "minLength": 1 }, + "algorithmId": { "type": "string", "minLength": 1 }, + "evidenceDigest": { "type": "string", "minLength": 1 } + } + } + } +} diff --git a/schemas/semantic-source-location.v1.schema.json b/schemas/semantic-source-location.v1.schema.json index d2aed61..89038ad 100644 --- a/schemas/semantic-source-location.v1.schema.json +++ b/schemas/semantic-source-location.v1.schema.json @@ -57,39 +57,15 @@ }, "structuralSelector": { "type": "string", - "description": "Parser-owned structural selector for an AST/document element. It identifies the element by language, project path, and structural path rather than by line range.", + "description": "Parser-owned structural selector for an AST/document element. It identifies the element by language, project path, and structural path rather than by line range. The rightmost # separates the verbatim project-relative owner path from the canonical identity fragment, so # remains legal inside an owner filename.", "minLength": 1, - "pattern": "^[a-z][a-z0-9+.-]*://[^#\\s]+#[^\\s]+$" + "pattern": "^[a-z][a-z0-9+.-]*://[^\\s]+#[^\\s]+$" }, "location": { - "type": "object", - "additionalProperties": false, - "required": [ - "path", - "lineRange" - ], - "properties": { - "path": { - "$ref": "#/$defs/projectPath" - }, - "lineRange": { - "$ref": "#/$defs/lineRange" - }, - "structuralSelector": { - "$ref": "#/$defs/structuralSelector" - }, - "displayLineRange": { - "$ref": "#/$defs/lineRange" - }, - "sourceLocatorHint": { - "$ref": "#/$defs/sourceLocator" - } - } + "$ref": "semantic-definitions.v1.schema.json#/$defs/location" }, "sourceLocator": { - "type": "string", - "description": "Project-root-relative source selector accepting path:start, path:start:end, or path:start-end.", - "pattern": "^(?:\\.|(?!/)(?![A-Za-z]:)(?![0-9]+:)(?!\\.\\.?($|/))(?!.*(^|/)\\.\\.?($|/))(?!.*//)[^\\s:\\\\]+(?:/[^\\s:\\\\]+)*):[1-9][0-9]*(?:(?::|-)[1-9][0-9]*)?$" + "$ref": "semantic-definitions.v1.schema.json#/$defs/sourceLocator" }, "sourceSpanLocator": { "type": "string", @@ -105,9 +81,11 @@ "doc", "children", "content", + "source", + "callable-skeleton", "code" ], - "description": "Requested or rendered projection for a semantic locator. Non-code projections are search/query frontiers; code is exact source transport." + "description": "Requested or rendered projection for a semantic locator. Exact query uses source or callable-skeleton; content is the document projection." }, "codePolicy": { "enum": [ @@ -115,7 +93,7 @@ "code-after-exact-selector", "requires-exact-code" ], - "description": "Boundary for when a source locator hint may be materialized into --code transport." + "description": "Boundary for when a source locator hint may be materialized through an exact selector with projection=source." } } } diff --git a/schemas/semantic-type-surface.v1.schema.json b/schemas/semantic-type-surface.v1.schema.json index 4c0c5a4..6f64e38 100644 --- a/schemas/semantic-type-surface.v1.schema.json +++ b/schemas/semantic-type-surface.v1.schema.json @@ -222,6 +222,12 @@ "unknown" ] }, + "typeRefList": { + "type": "array", + "items": { + "$ref": "#/$defs/typeRef" + } + }, "typeRef": { "type": "object", "additionalProperties": false, @@ -263,10 +269,7 @@ "type": "boolean" }, "typeArguments": { - "type": "array", - "items": { - "$ref": "#/$defs/typeRef" - } + "$ref": "#/$defs/typeRefList" }, "fields": { "$ref": "#/$defs/fields" @@ -394,10 +397,7 @@ } }, "relatedTypes": { - "type": "array", - "items": { - "$ref": "#/$defs/typeRef" - } + "$ref": "#/$defs/typeRefList" }, "fields": { "$ref": "#/$defs/fields" diff --git a/schemas/source-snapshot-evidence.v1.schema.json b/schemas/source-snapshot-evidence.v1.schema.json new file mode 100644 index 0000000..9cd723d --- /dev/null +++ b/schemas/source-snapshot-evidence.v1.schema.json @@ -0,0 +1,66 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://agent-semantic-protocols.dev/schemas/source-snapshot-evidence.v1.schema.json", + "title": "ASP Source Snapshot Evidence v1", + "description": "Language-neutral Merkle workspace snapshot and source resolution evidence.", + "type": "object", + "additionalProperties": false, + "required": ["sourceSnapshot", "resolutionEvidence"], + "properties": { + "sourceSnapshot": { "$ref": "#/$defs/sourceSnapshot" }, + "resolutionEvidence": { "$ref": "#/$defs/resolutionEvidence" } + }, + "$defs": { + "digest": { + "type": "string", + "minLength": 1, + "description": "Lowercase hex BLAKE3 digest over the canonical ASP artifact Merkle input defined by this contract.", + "pattern": "^[0-9a-f]{64}$" + }, + "sourceSnapshot": { + "type": "object", + "additionalProperties": false, + "required": ["schemaId", "algorithm", "rootDigest", "sourceKind", "leafCount", "providerDigest"], + "properties": { + "schemaId": { "const": "asp.source-snapshot.v1" }, + "algorithm": { "const": "blake3-merkle-v1" }, + "rootDigest": { "$ref": "#/$defs/digest" }, + "sourceKind": { + "enum": ["filesystem", "editor-buffer", "git-tree", "derived-overlay"] + }, + "leafCount": { "type": "integer", "minimum": 0 }, + "baseRootDigest": { "$ref": "#/$defs/digest" }, + "providerDigest": { "$ref": "#/$defs/digest" }, + "dirtyPathsDigest": { "$ref": "#/$defs/digest" } + } + }, + "resolutionEvidence": { + "type": "object", + "additionalProperties": false, + "required": ["schemaId", "snapshotRoot", "authority", "state"], + "properties": { + "schemaId": { "const": "asp.source-resolution.v1" }, + "snapshotRoot": { "$ref": "#/$defs/digest" }, + "authority": { "enum": ["live-parser", "content-cache", "derived-index"] }, + "state": { + "enum": [ + "live-hit", + "artifact-cache-hit", + "owner-not-in-snapshot", + "selector-path-namespace-mismatch", + "item-not-in-live-owner", + "parser-failed", + "index-unavailable", + "overlay-invalid", + "overlay-base-root-mismatch" + ] + }, + "ownerPath": { "type": "string", "minLength": 1 }, + "ownerBlobDigest": { "$ref": "#/$defs/digest" }, + "parserArtifactDigest": { "$ref": "#/$defs/digest" }, + "indexArtifactDigest": { "$ref": "#/$defs/digest" }, + "reason": { "type": "string", "minLength": 1 } + } + } + } +} diff --git a/src/python_lang_parser/__init__.py b/src/python_lang_parser/__init__.py index 94b8003..aad9a81 100644 --- a/src/python_lang_parser/__init__.py +++ b/src/python_lang_parser/__init__.py @@ -34,7 +34,13 @@ "PythonPytestOptions", } ) -_PYPROJECT_EXPORTS = frozenset({"parse_python_project_metadata"}) +_PYPROJECT_EXPORTS = frozenset( + { + "PythonPyprojectParseError", + "parse_python_project_metadata", + "parse_python_pyproject_document", + } +) _REASONING_TREE_EXPORTS = frozenset( { "PythonReasoningTreeBranch", @@ -106,6 +112,7 @@ "PythonProjectEntryPoint", "PythonProjectImportName", "PythonProjectMetadata", + "PythonPyprojectParseError", "PythonProjectScript", "PythonPytestOptions", "PythonReference", @@ -122,6 +129,7 @@ "__version__", "parse_python_file", "parse_python_project_metadata", + "parse_python_pyproject_document", "parse_python_source", "python_module_is_package_init", "python_module_has_public_surface", diff --git a/src/python_lang_parser/_pyproject_metadata.py b/src/python_lang_parser/_pyproject_metadata.py index 1121dae..7a9bcd1 100644 --- a/src/python_lang_parser/_pyproject_metadata.py +++ b/src/python_lang_parser/_pyproject_metadata.py @@ -24,20 +24,30 @@ def parse_python_project_metadata( root = Path(project_root) pyproject_path = root / "pyproject.toml" - payload = _read_pyproject_payload(pyproject_path) - if payload is None: + try: + payload = parse_python_pyproject_document(pyproject_path) + except PythonPyprojectParseError: return None return _metadata_from_payload(root, pyproject_path, payload) -def _read_pyproject_payload(pyproject_path: Path) -> dict[str, Any] | None: - if not pyproject_path.exists(): - return None +class PythonPyprojectParseError(ValueError): + """Raised when a pyproject document cannot be parsed by the Python parser.""" + +def parse_python_pyproject_document(path: str | Path) -> dict[str, Any]: + """Return the complete pyproject document or raise a parser-owned error.""" + pyproject_path = Path(path) + if not pyproject_path.is_file(): + raise PythonPyprojectParseError( + f"Python pyproject document is missing: {pyproject_path}" + ) try: return tomllib.loads(pyproject_path.read_text(encoding="utf-8")) - except (OSError, tomllib.TOMLDecodeError, UnicodeDecodeError): - return None + except (OSError, tomllib.TOMLDecodeError, UnicodeDecodeError) as error: + raise PythonPyprojectParseError( + f"failed to parse Python pyproject document `{pyproject_path}`: {error}" + ) from error def _metadata_from_payload( diff --git a/src/python_lang_project_harness/_agent_snapshot.py b/src/python_lang_project_harness/_agent_snapshot.py index fde8169..ce92c0e 100644 --- a/src/python_lang_project_harness/_agent_snapshot.py +++ b/src/python_lang_project_harness/_agent_snapshot.py @@ -54,7 +54,9 @@ def render_python_project_harness_agent_snapshot_report( """Render an already-built project harness report as an agent snapshot.""" project_root = ( - None if report.project_scope is None else report.project_scope.project_root + None + if report.project_resolution is None + else report.project_resolution.project_root ) target = ", ".join( _display_path(Path(path), project_root=project_root) diff --git a/src/python_lang_project_harness/_agent_snapshot_tree.py b/src/python_lang_project_harness/_agent_snapshot_tree.py index 7de717c..b1040fa 100644 --- a/src/python_lang_project_harness/_agent_snapshot_tree.py +++ b/src/python_lang_project_harness/_agent_snapshot_tree.py @@ -61,9 +61,9 @@ class _SnapshotTreeRenderer: @property def project_root(self) -> Path | None: - if self.report.project_scope is None: + if self.report.project_resolution is None: return None - return self.report.project_scope.project_root + return self.report.project_resolution.project_root def render(self) -> str: facts = verification_reasoning_tree_facts(self.report) diff --git a/src/python_lang_project_harness/_callable_skeleton_projection.py b/src/python_lang_project_harness/_callable_skeleton_projection.py new file mode 100644 index 0000000..d148d9f --- /dev/null +++ b/src/python_lang_project_harness/_callable_skeleton_projection.py @@ -0,0 +1,197 @@ +"""Project callable skeletons from native Python AST owners.""" + +from __future__ import annotations + +import ast +import json +from typing import Any + +from ._exact_projection_model import ( + CANONICAL_SELECTOR_SCHEMA_ID, + EXACT_SELECTOR_SCHEMA_ID, + ExactSelector, + ProjectionSegment, + node_byte_span, + required_text, +) + + +def collect_segments( + function: ast.FunctionDef | ast.AsyncFunctionDef, line_offsets: list[int] +) -> list[ProjectionSegment]: + segments: list[ProjectionSegment] = [] + + class Collector(ast.NodeVisitor): + def _push(self, node: ast.AST, kind: str, label: str) -> None: + start, end = node_byte_span(node, line_offsets) + segments.append( + ProjectionSegment( + kind=kind, + label=label, + ordinal=len(segments) + 1, + byte_start=start, + byte_end=end, + ) + ) + + def visit_Assign(self, node: ast.Assign) -> None: + self._push(node, "binding", "assign") + self.generic_visit(node) + + def visit_AnnAssign(self, node: ast.AnnAssign) -> None: + self._push(node, "binding", "assign") + self.generic_visit(node) + + def visit_AugAssign(self, node: ast.AugAssign) -> None: + self._push(node, "binding", "assign") + self.generic_visit(node) + + def visit_If(self, node: ast.If) -> None: + self._push(node, "branch", "if") + self.generic_visit(node) + + def visit_Match(self, node: ast.Match) -> None: + self._push(node, "branch", "match") + self.generic_visit(node) + + def visit_For(self, node: ast.For) -> None: + self._push(node, "loop", "for") + self.generic_visit(node) + + def visit_AsyncFor(self, node: ast.AsyncFor) -> None: + self._push(node, "loop", "async-for") + self.generic_visit(node) + + def visit_While(self, node: ast.While) -> None: + self._push(node, "loop", "while") + self.generic_visit(node) + + def visit_Return(self, node: ast.Return) -> None: + self._push(node, "exit", "return") + self.generic_visit(node) + + def visit_Raise(self, node: ast.Raise) -> None: + self._push(node, "exit", "raise") + self.generic_visit(node) + + collector = Collector() + for statement in function.body: + collector.visit(statement) + return segments + + +def callable_skeleton_payload( + request: dict[str, Any], + selector: ExactSelector, + function: ast.FunctionDef | ast.AsyncFunctionDef, + segments: list[ProjectionSegment], + root_start: int, + root_end: int, +) -> dict[str, Any]: + root_exact = exact_selector(request, selector, None) + nodes: list[dict[str, Any]] = [ + { + "nodeId": "callable:root", + "kind": "callable", + "label": function.name, + "order": 0, + "queryable": True, + "selector": root_exact["selector"], + "languageFacts": { + "async": isinstance(function, ast.AsyncFunctionDef), + "decoratorCount": len(function.decorator_list), + "inputCount": len(function.args.args) + + len(function.args.posonlyargs) + + len(function.args.kwonlyargs), + }, + } + ] + relations: list[dict[str, str]] = [] + for segment in segments: + node_id = f"{segment.kind}:{segment.ordinal}" + nodes.append( + { + "nodeId": node_id, + "kind": segment.kind, + "label": segment.label, + "order": segment.ordinal, + "queryable": True, + "selector": exact_selector(request, selector, segment)["selector"], + "sourceLocatorHint": { + "sourceByteStart": segment.byte_start, + "sourceByteEnd": segment.byte_end, + }, + } + ) + relations.append( + { + "fromNodeId": "callable:root", + "toNodeId": node_id, + "kind": "contains", + } + ) + source_bytes = root_end - root_start + structural_bytes = len( + json.dumps( + {"nodes": nodes, "relations": relations}, + separators=(",", ":"), + sort_keys=True, + ).encode() + ) + projected_bytes = min(source_bytes, structural_bytes) + return { + "rootNodeId": "callable:root", + "callable": { + "kind": selector.kind, + "displayName": function.name, + "signature": function.name, + }, + "nodes": nodes, + "relations": relations, + "cost": { + "sourceBytes": source_bytes, + "projectedBytes": projected_bytes, + "omittedBytes": source_bytes - projected_bytes, + }, + "languageFacts": {"parser": "ast", "syntax": "python"}, + } + + +def exact_selector( + request: dict[str, Any], + selector: ExactSelector, + segment: ProjectionSegment | None, +) -> dict[str, Any]: + segments: list[dict[str, str]] = [] + structural_selector = selector.root + if segment is not None: + identity = f"ordinal-{segment.ordinal}" + segments.append( + { + "relation": "contains", + "kind": segment.kind, + "identity": identity, + "label": segment.label, + } + ) + structural_selector = f"{selector.root}/segment/{segment.kind}/{identity}" + return { + "schemaId": EXACT_SELECTOR_SCHEMA_ID, + "schemaVersion": "1", + "languageId": "python", + "ownerPath": selector.owner_path, + "selector": structural_selector, + "generationIdentityDigest": required_text(request, "generationIdentityDigest"), + "parserIdentityDigest": required_text(request, "parserIdentityDigest"), + "queryPackDigest": required_text(request, "queryPackDigest"), + "rootItemSelector": { + "schemaId": CANONICAL_SELECTOR_SCHEMA_ID, + "schemaVersion": "1", + "languageId": "python", + "kind": selector.kind, + "symbol": selector.symbol, + "scopes": [], + "structuralSelector": selector.root, + }, + "segments": segments, + } diff --git a/src/python_lang_project_harness/_cli.py b/src/python_lang_project_harness/_cli.py index 5a5ace8..8c0f580 100644 --- a/src/python_lang_project_harness/_cli.py +++ b/src/python_lang_project_harness/_cli.py @@ -18,6 +18,12 @@ def run_cli_from_env() -> int: 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) @@ -32,7 +38,7 @@ def run_cli( *, stdout: TextIO | None = None, stderr: TextIO | None = None, - stdin: str | None = None, + stdin: str | bytes | None = None, cwd: Path | None = None, ) -> int: """Run the default package-level Python harness CLI.""" diff --git a/src/python_lang_project_harness/_cli_agent.py b/src/python_lang_project_harness/_cli_agent.py index a786996..a90e199 100644 --- a/src/python_lang_project_harness/_cli_agent.py +++ b/src/python_lang_project_harness/_cli_agent.py @@ -2,8 +2,11 @@ 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) @@ -12,12 +15,11 @@ def render_agent_guide(project_root: Path) -> str: return ( "\n".join( ( - f"[py-harness-guide] project={project}", + 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=read-frontier,syntax-locate,syntax-code," - "query-code" + "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 " @@ -29,23 +31,8 @@ def render_agent_guide(project_root: Path) -> str: f"'(function_definition name: (identifier) @function.name)' " f"--selector {workspace}" ), - ( - f"|route syntax-code selectors=S:tree-sitter-query,R:exact-selector " - f"returns=code code=pure cmd=asp python query --treesitter-query " - f"'(function_definition name: (identifier) @function.name)' " - f"--selector {workspace} --code" - ), - ( - f"|route read-plan selectors=R:selector,T:term " - f"returns=owners,tests,window-set code=false cmd=asp python " - f"query --from-hook owner-local-projection --selector " - f"--term --surface owners,tests {workspace} --view seeds" - ), - ( - f"|route query-code selectors=O:owner,Q:symbol returns=code " - f"code=pure cmd=asp python query --term " - f"{workspace} --code" - ), + 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", @@ -63,32 +50,18 @@ def render_agent_guide(project_root: Path) -> str: f"query-deps --query --dependency " f"{root} --view seeds" ), - f"|cmd names=asp python query --term {workspace} --names-only", - f"|cmd query-code=asp python query --term {workspace} --code", 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 syntax-code=asp python query --treesitter-query " - f"'(function_definition name: (identifier) @function.name)' " - f"--selector {workspace} --code" - ), - ( - f"|cmd owner-items-code=asp python search owner items " - f"--query {workspace} --code" - ), + 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 read-plan=asp python query --from-hook owner-local-projection " - f"--selector --term --surface owners,tests " - f"{workspace} --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}", @@ -118,19 +91,12 @@ def render_agent_guide(project_root: Path) -> str: "#match?,#any-match?,#not-eq?,#not-match? " "unsupported=none unsupportedReported=true" ), - ( - "|rule query --code is pure code; search/read-plan returns " - "locators/frontier, not inline code" - ), + "|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 --view metadata is document-only for asp md/org query; " - "Python code query uses search --view seeds for discovery and " - "query --term --code|--names-only" - ), + "|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 " @@ -173,13 +139,9 @@ def render_agent_doctor(project_root: Path) -> str: def render_agent_doctor_json(project_root: Path) -> str: - import json - - from ._semantic_language import semantic_language_registry_document - return ( json.dumps( - semantic_language_registry_document(str(project_root)), + 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 index dee6fcc..4f42a93 100644 --- a/src/python_lang_project_harness/_cli_args.py +++ b/src/python_lang_project_harness/_cli_args.py @@ -38,7 +38,6 @@ class ProtocolArgs: pipes: tuple[str, ...] = () json: bool = False names_only: bool = False - code_only: bool = False source_version: str = "worktree" render_mode: str | None = None error: str | None = None @@ -82,7 +81,6 @@ def _parse_search(cls, args: list[str] | tuple[str, ...]) -> ProtocolArgs: workspace=parsed.workspace, pipes=parsed.pipes, json=parsed.json, - code_only=parsed.code_only, render_mode=parsed.render_mode, ) @@ -138,7 +136,7 @@ def _parse_check(cls, args: list[str] | tuple[str, ...]) -> ProtocolArgs: elif arg in {"--help", "-h"}: return cls( "error", - error="usage: py-harness check [--changed | --full] [--json] [PROJECT_ROOT]", + error="usage: asp-python check [--changed | --full] [--json] [PROJECT_ROOT]", ) elif arg.startswith("-"): return cls("error", error=f"unknown check option: {arg}") @@ -190,7 +188,7 @@ def _parse_agent(cls, args: list[str] | tuple[str, ...]) -> ProtocolArgs: ) return cls( "error", - error=f"py-harness agent {action} moved to asp; use `{replacement}`", + error=f"asp-python agent {action} moved to asp; use `{replacement}`", ) if action == "guide": return cls._parse_agent_guide(args[1:]) @@ -388,26 +386,26 @@ def harness_config(self, project_root: Path) -> PythonHarnessConfig | None: def help_text() -> str: return ( - "py-harness — Python semantic search and project harness\n\n" + "asp-python — Python semantic search and project harness\n\n" "Usage:\n" - " py-harness search ... [--json] [--code] [--package PATH] [--workspace ]\n" - " py-harness query --term [--term ] [--workspace ] [--names-only | --code]\n" - " py-harness query --catalog flow-lite --where 'source.call=NAME sink.constructs=TYPE scope.fn=FUNCTION' [--json] [--workspace ]\n" - " py-harness check [--changed | --full] [--json]\n" - " py-harness evidence graph [--json] [PROJECT_ROOT]\n" - " py-harness evidence analyze [--json] [PROJECT_ROOT]\n" - " py-harness ast-patch dry-run --packet \n" - " py-harness agent doctor [--json]\n" - " py-harness agent guide\n" - " py-harness [--json | --agent-snapshot] [--no-tests] " + " 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 [--names-only | --code]\n" - " Parser-owned item query and compact code extraction\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" @@ -430,14 +428,10 @@ def help_text() -> str: " Typed graph entry returning owners, imports, and usage tests\n" " search ingest Detect stdin shape and group hits by owner\n\n" "QUERY\n" - " query --term \n" - " Parser-owned owner item query\n" - " query --term --term --names-only\n" - " Owner-local item discovery without code windows\n" - " query --term --code\n" - " Pure compact parser-owned code output\n\n" - " query --from-hook owner-local-projection --selector [--workspace ] --code\n" - " Source-preserved pure code read; --code consumes no path argument\n\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" @@ -464,23 +458,22 @@ def help_text() -> str: "Repeat --extra-path to include external project paths.\n" "Repeat --disable-rule or --block-rule to customize policy by rule id.\n" "\nEXAMPLES\n" - " py-harness search workspace .\n" - " py-harness search prime .\n" - " py-harness search public-external-types pytest .\n" - " py-harness search callsite PythonSemanticSearchOptions .\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" - " py-harness search reasoning owner-tests --owner src/python_lang_project_harness/_cli.py .\n" - " py-harness search reasoning owner-query --owner src/python_lang_project_harness/_cli.py --query run_cli .\n" - " py-harness search reasoning query-deps --query Session --dependency requests .\n" - " py-harness query src/python_lang_project_harness/_cli.py --term run_cli --workspace . --names-only\n" - " py-harness query src/python_lang_project_harness/_cli.py --term run_cli --workspace . --code\n" - " py-harness query --catalog flow-lite --where 'source.call=payload sink.constructs=Action scope.fn=collect' .\n" - ' rg -n "PythonSemanticSearchOptions" src tests | py-harness search ingest .\n' - " py-harness check --full .\n" - " py-harness evidence graph --json .\n" - " py-harness evidence analyze --json .\n" - " py-harness agent doctor --json .\n" - " py-harness agent guide\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" ) diff --git a/src/python_lang_project_harness/_cli_ast_patch.py b/src/python_lang_project_harness/_cli_ast_patch.py index 54fd3de..606ae26 100644 --- a/src/python_lang_project_harness/_cli_ast_patch.py +++ b/src/python_lang_project_harness/_cli_ast_patch.py @@ -117,7 +117,10 @@ def _receipt( "verification": verification, "failureKind": failure_kind, "failures": failures, - "next": f"py-harness query --term --code {project_root}", + "next": ( + "asp python query --selector " + f"--projection source --workspace {project_root}" + ), } diff --git a/src/python_lang_project_harness/_cli_protocol.py b/src/python_lang_project_harness/_cli_protocol.py index 7193937..a44c306 100644 --- a/src/python_lang_project_harness/_cli_protocol.py +++ b/src/python_lang_project_harness/_cli_protocol.py @@ -11,7 +11,7 @@ render_agent_guide, ) from ._cli_args import ProtocolArgs, help_text -from ._cli_search_runtime import _render_search_code_only, _run_search_harness +from ._cli_search_runtime import _run_search_harness def run_protocol_cli( @@ -19,7 +19,7 @@ def run_protocol_cli( *, stdout: TextIO, stderr: TextIO, - stdin: str, + stdin: str | bytes, cwd: Path, ) -> int: if args.command == "error": @@ -28,7 +28,6 @@ def run_protocol_cli( 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) @@ -156,6 +155,10 @@ def _render_fast_protocol_command( 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 @@ -267,12 +270,9 @@ def _run_search_command( runtime_cost=runtime_cost, ), ) - if args.code_only: - stdout.write(_render_search_code_only(packet)) - else: - stdout.write( - render_python_semantic_search_packet_json(packet) - if args.json - else render_python_semantic_search_packet(packet) - ) + 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_query.py b/src/python_lang_project_harness/_cli_query.py index 7e19d05..f58e87c 100644 --- a/src/python_lang_project_harness/_cli_query.py +++ b/src/python_lang_project_harness/_cli_query.py @@ -2,14 +2,9 @@ from __future__ import annotations -import json from pathlib import Path from typing import TYPE_CHECKING, Any, TextIO -from ._semantic_search_item_lines import owner_item_query_lines -from ._semantic_search_items import owner_item_semantic_query_packet -from ._semantic_selector_identity import python_structural_selector_owner_path - if TYPE_CHECKING: from ._cli_args import ProtocolArgs @@ -44,118 +39,8 @@ def run_query_command( ) return 0 - if args.code_only and not _selector_is_structural(args.selector): - raise ValueError( - "query requires parser-owned structural selector identity; " - "status=selector-not-materialized " - "reason=non-structural-selector " - "nextAction=refresh-parser-projection" - ) - if _selector_looks_like_source_locator_hint(args.selector): - raise ValueError( - "query requires parser-owned selector identity; " - "source locator hints are not executable selectors" - ) - - owner_path = args.owner_path or _selector_owner_path(args.selector) or "" - item_query = "|".join(args.query_set) - _write_item_query_response( - args, report, project_root, stdout, owner_path, item_query - ) - return 0 - - -def _write_item_query_response( - args: ProtocolArgs, - report: Any, - project_root: Path, - stdout: TextIO, - owner_path: str, - item_query: str, -) -> None: - packet = owner_item_semantic_query_packet( - report, - project_root, - owner_path, - item_query, - output_mode="names" if args.names_only else "code", - selector=args.selector, + raise ValueError( + "exact source projection is ASP-owned; use `asp python query " + "--selector --projection " + "source|callable-skeleton --workspace `" ) - if args.json: - stdout.write(json.dumps(packet, separators=(",", ":"))) - elif args.code_only: - stdout.write( - "\n".join( - str(match["code"]) - for match in packet["matches"] - if isinstance(match.get("code"), str) - ) - ) - else: - stdout.write( - owner_item_query_lines( - report, - project_root, - owner_path, - item_query, - names_only=args.names_only, - ) - ) - stdout.write("\n") - - -def _selector_has_line_range(selector: str | None, owner_path: str) -> bool: - if selector is None: - return False - normalized = selector.replace("\\", "/").removeprefix("owner:") - if any(marker in normalized for marker in ("*", "{", "}")): - return False - if not owner_path: - return _selector_owner_path(selector) is not None - return normalized.startswith(f"{owner_path}:") - - -def _selector_is_structural(selector: str | None) -> bool: - if selector is None: - return False - normalized = selector.strip() - return normalized.startswith("python://") and "#item/" in normalized - - -def _selector_owner_path(selector: str | None) -> str | None: - if selector is None: - return None - normalized = selector.replace("\\", "/").removeprefix("owner:") - if any(marker in normalized for marker in ("*", "{", "}")): - return None - structural_owner_path = python_structural_selector_owner_path(selector) - if structural_owner_path is not None: - return structural_owner_path - path_and_start, separator, end_text = normalized.rpartition(":") - if not separator: - return None - path, separator, _start_text = path_and_start.rpartition(":") - if separator: - return path or None - _start_text, separator, _end_text = end_text.partition("-") - if not separator: - return None - return path_and_start or None - - -def _selector_looks_like_source_locator_hint(selector: str | None) -> bool: - if selector is None: - return False - normalized = selector.replace("\\", "/").removeprefix("owner:") - if any(marker in normalized for marker in ("*", "{", "}")): - return False - return ".py:" in normalized - - -def _selector_looks_like_owner_path_hint(selector: str | None) -> bool: - if selector is None: - return False - normalized = selector.replace("\\", "/").removeprefix("owner:") - if any(marker in normalized for marker in ("*", "{", "}")): - return False - return normalized.endswith(".py") diff --git a/src/python_lang_project_harness/_cli_query_arg_consume.py b/src/python_lang_project_harness/_cli_query_arg_consume.py index 83bb1c7..07929d3 100644 --- a/src/python_lang_project_harness/_cli_query_arg_consume.py +++ b/src/python_lang_project_harness/_cli_query_arg_consume.py @@ -13,12 +13,9 @@ from ._tree_sitter_query_predicates import SyntaxQueryPredicate QUERY_USAGE = ( - "usage: py-harness query --term " - "[--term ] [--workspace ] [--names-only] [--json] [--package PATH]; " - "or py-harness query --from-hook owner-local-projection --selector PATH:START:END " - "[--workspace ] [--source worktree|index|head] [--code]; " - "or py-harness query (--catalog ID | --treesitter-query EXPR) [] [--workspace ] [--json]; " - "or py-harness query --catalog flow-lite --where 'source.call=NAME sink.constructs=TYPE scope.fn=FUNCTION' [] [--json] [--workspace ]" + "usage: asp-python query (--catalog ID | --treesitter-query EXPR) " + "[] [--workspace ] [--json]; " + "or asp-python query --catalog flow-lite --where 'source.call=NAME sink.constructs=TYPE scope.fn=FUNCTION' [] [--json] [--workspace ]" ) @@ -32,12 +29,9 @@ def __init__(self, message: str) -> None: @dataclass(slots=True) class QueryParseState: json_output: bool = False - names_only: bool = False - code_only: bool = False package_path: Path | None = None workspace: bool = False workspace_root: Path | None = None - from_hook: str | None = None selector: str | None = None catalog: str | None = None flow_lite_where: str | None = None @@ -63,8 +57,8 @@ def consume_query_arg( arg = args[index] if arg in {"--term", "--query"}: return _consume_query_term(state, args, index, arg) - if arg in {"--names-only", "--code", "--json"}: - _set_query_flag(state, arg) + if arg == "--json": + state.json_output = True return index + 1 if arg == "--workspace": value = _optional_arg(args, index + 1) @@ -88,7 +82,6 @@ def consume_query_arg( state.render_mode = render_mode return index + 2 if arg in { - "--from-hook", "--selector", "--package", "--catalog", @@ -125,15 +118,6 @@ def _consume_query_term( return index + 2 -def _set_query_flag(state: QueryParseState, arg: str) -> None: - if arg == "--names-only": - state.names_only = True - elif arg == "--code": - state.code_only = True - else: - state.json_output = True - - def _consume_query_option( state: QueryParseState, args: list[str] | tuple[str, ...], @@ -144,8 +128,6 @@ def _consume_query_option( if value is None: return ProtocolArgError(f"{arg} requires {_query_option_value_name(arg)}") match arg: - case "--from-hook": - state.from_hook = value case "--selector": state.selector = value case "--catalog": @@ -179,7 +161,6 @@ def _consume_query_option( def _query_option_value_name(arg: str) -> str: return { - "--from-hook": "a hook reason", "--selector": "an owner path", "--package": "a package path", "--catalog": "a catalog id", diff --git a/src/python_lang_project_harness/_cli_query_args.py b/src/python_lang_project_harness/_cli_query_args.py index 7fbfe87..5fe4cb4 100644 --- a/src/python_lang_project_harness/_cli_query_args.py +++ b/src/python_lang_project_harness/_cli_query_args.py @@ -2,7 +2,6 @@ from __future__ import annotations -from pathlib import Path from typing import TYPE_CHECKING from ._cli_query_arg_consume import ( @@ -15,11 +14,6 @@ flow_lite_query_protocol_args, is_flow_lite_query_state, ) -from ._cli_query_hook_args import ( - is_broad_hook_query, - owner_path_from_query_selector, - selector_has_line_range, -) from ._cli_query_tree_sitter_args import ( is_tree_sitter_query_state, tree_sitter_query_args_error, @@ -34,7 +28,7 @@ def parse_query_args( args_type: type[ProtocolArgs], args: list[str] | tuple[str, ...], ) -> ProtocolArgs: - """Parse py-harness query arguments into protocol args.""" + """Parse asp-python query arguments into protocol args.""" if args and args[0] in {"--help", "-h"}: return args_type("help") @@ -54,47 +48,21 @@ def _query_args_result( error = _query_args_error(state) if error is not None: return args_type("error", error=error) - if is_broad_hook_query(state.from_hook, state.selector, state.terms): - return args_type( - "search", - view="lexical", - query=",".join(state.terms), - query_set=tuple(state.terms), - project_root=_query_project_root(state), - package_path=state.package_path, - workspace=state.workspace, - pipes=tuple(state.surfaces), - render_mode=state.render_mode, - source_version=state.source_version, - ) if is_flow_lite_query_state(state): return flow_lite_query_protocol_args(args_type, state) if is_tree_sitter_query_state(state): return tree_sitter_query_protocol_args(args_type, state) - owner_path = ( - owner_path_from_query_selector(state.selector) - if state.selector is not None - else state.positionals[0] - ) return args_type( - "query", - owner_path=owner_path, - selector=state.selector, - query_set=tuple(state.terms), - project_root=_query_project_root(state), - package_path=state.package_path, - workspace=state.workspace, - json=state.json_output, - names_only=_query_names_only(state), - code_only=state.code_only, - source_version=state.source_version, - render_mode=state.render_mode, + "error", + error=( + "exact source projection is ASP-owned; use `asp python query " + "--selector --projection " + "source|callable-skeleton --workspace `" + ), ) def _query_args_error(state: QueryParseState) -> str | None: - if state.from_hook is not None and state.from_hook != "owner-local-projection": - return f"unsupported query hook route: {state.from_hook}" if _query_has_positional_workspace( state ) and not _query_allows_positional_workspace(state): @@ -104,32 +72,16 @@ def _query_args_error(state: QueryParseState) -> str | None: if is_tree_sitter_query_state(state): return tree_sitter_query_args_error(state) if not state.selector and not state.positionals: - if state.names_only and state.terms: - return ( - "query --names-only requires an owner selector; workspace term discovery is " - "`search lexical '' owner --workspace --view seeds`" - ) return "query requires an owner path" - if not state.terms and state.from_hook != "owner-local-projection": + if not state.terms and state.selector is None: return "query requires at least one --term" - broad_hook_query = is_broad_hook_query(state.from_hook, state.selector, state.terms) - if state.json_output and state.code_only: - return "--code cannot be combined with --json" - if state.names_only and state.code_only: - return "--code cannot be combined with --names-only" - if state.surfaces and not broad_hook_query: + if state.surfaces: return "query --surface is Rust ASP search-owned; Python query accepts exact owner-local projection only" - if state.render_mode is not None and not broad_hook_query: + if state.render_mode is not None: return "query --view is Rust ASP search-owned; Python query accepts exact owner-local projection only" return None -def _query_project_root(state: QueryParseState) -> Path | None: - if state.workspace_root is not None: - return state.workspace_root - return None - - def _query_has_positional_workspace(state: QueryParseState) -> bool: if state.selector is None: return len(state.positionals) > 1 @@ -138,15 +90,3 @@ def _query_has_positional_workspace(state: QueryParseState) -> bool: def _query_allows_positional_workspace(state: QueryParseState) -> bool: return state.catalog is not None and state.tree_sitter_query is None - - -def _query_names_only(state: QueryParseState) -> bool: - if state.names_only: - return True - return bool( - not state.terms - and state.from_hook == "owner-local-projection" - and not state.code_only - and not state.json_output - and not selector_has_line_range(state.selector) - ) diff --git a/src/python_lang_project_harness/_cli_query_flow_lite_args.py b/src/python_lang_project_harness/_cli_query_flow_lite_args.py index f1661bd..fc1eeec 100644 --- a/src/python_lang_project_harness/_cli_query_flow_lite_args.py +++ b/src/python_lang_project_harness/_cli_query_flow_lite_args.py @@ -15,8 +15,6 @@ def is_flow_lite_query_state(state: Any) -> bool: def flow_lite_query_args_error(state: Any) -> str | None: if state.tree_sitter_query is not None: return "query --catalog flow-lite cannot be combined with --treesitter-query" - if state.from_hook is not None: - return "query --catalog flow-lite cannot be combined with --from-hook" if state.workspace_root is not None and state.positionals: return ( "query accepts either --workspace or one positional " @@ -24,13 +22,6 @@ def flow_lite_query_args_error(state: Any) -> str | None: ) if len(state.positionals) > 1: return "query accepts at most one positional WORKSPACE" - if state.names_only: - return "--names-only cannot be combined with --catalog flow-lite" - if state.code_only: - return ( - "query --catalog flow-lite is a locator/provenance surface; select an " - "exact frontier locator and run query --selector --code" - ) if state.surfaces: return "query --surface cannot be combined with --catalog flow-lite" if state.render_mode is not None: diff --git a/src/python_lang_project_harness/_cli_query_hook_args.py b/src/python_lang_project_harness/_cli_query_hook_args.py index 12c3aca..8fba94b 100644 --- a/src/python_lang_project_harness/_cli_query_hook_args.py +++ b/src/python_lang_project_harness/_cli_query_hook_args.py @@ -2,14 +2,11 @@ from __future__ import annotations -import re -from collections.abc import Sequence - from ._semantic_selector_identity import python_structural_selector_owner_path def normalize_query_surfaces(value: str | None) -> tuple[tuple[str, ...], str | None]: - """Normalize shared hook query surfaces into py-harness search pipes.""" + """Normalize shared hook query surfaces into asp-python search pipes.""" if value is None: return (), "--surface requires owner,tests style surfaces" surfaces = tuple(surface.strip() for surface in value.split(",") if surface.strip()) @@ -31,22 +28,13 @@ def normalize_query_view(value: str | None) -> tuple[str | None, str | None]: None, "--view metadata is document-only for asp md/org query; " "Python query uses search --view seeds for discovery and " - "query --term --code or --names-only", + "exact query uses --selector with --projection source or callable-skeleton", ) if value not in {"graph", "hits", "both", "seeds"}: return None, "--view requires graph, hits, both, or seeds" return value, None -def is_broad_hook_query( - from_hook: str | None, - selector: str | None, - terms: Sequence[str], -) -> bool: - """Return whether hook query args should fan into semantic search.""" - return False - - def _selector_has_glob(selector: str) -> bool: return any(marker in selector for marker in ("*", "?", "[", "]", "{", "}")) @@ -60,13 +48,4 @@ def owner_path_from_query_selector(selector: str | None) -> str | None: structural_owner_path = python_structural_selector_owner_path(selector) if structural_owner_path is not None: return structural_owner_path - return re.sub(r":[1-9][0-9]*(?:[:-][1-9][0-9]*)?$", "", normalized) - - -def selector_has_line_range(selector: str | None) -> bool: - if selector is None: - return False - normalized = selector.replace("\\", "/").removeprefix("owner:") - if any(marker in normalized for marker in ("*", "{", "}")): - return False - return re.search(r":[1-9][0-9]*(?:[:-][1-9][0-9]*)?$", normalized) is not None + return None diff --git a/src/python_lang_project_harness/_cli_query_tree_sitter_args.py b/src/python_lang_project_harness/_cli_query_tree_sitter_args.py index 95e085d..09d5819 100644 --- a/src/python_lang_project_harness/_cli_query_tree_sitter_args.py +++ b/src/python_lang_project_harness/_cli_query_tree_sitter_args.py @@ -13,8 +13,6 @@ def is_tree_sitter_query_state(state: Any) -> bool: def tree_sitter_query_args_error(state: Any) -> str | None: if state.catalog is not None and state.tree_sitter_query is not None: return "query accepts only one of --catalog or --treesitter-query" - if state.from_hook is not None: - return "query --catalog/--treesitter-query cannot be combined with --from-hook" if state.workspace_root is not None and state.positionals: return ( "query accepts either --workspace or one positional " @@ -22,10 +20,6 @@ def tree_sitter_query_args_error(state: Any) -> str | None: ) if len(state.positionals) > 1: return "query accepts at most one positional WORKSPACE" - if state.names_only: - return "--names-only cannot be combined with --catalog or --treesitter-query" - if state.json_output and state.code_only: - return "--code cannot be combined with --json" if state.surfaces: return "query --surface is Rust ASP search-owned; Python query accepts tree-sitter projection only" if state.render_mode is not None: @@ -48,7 +42,6 @@ def tree_sitter_query_protocol_args(args_type: type[Any], state: Any) -> Any: package_path=state.package_path, workspace=state.workspace or bool(state.positionals), json=state.json_output, - code_only=state.code_only, ) diff --git a/src/python_lang_project_harness/_cli_search_runtime.py b/src/python_lang_project_harness/_cli_search_runtime.py index 92db820..399020a 100644 --- a/src/python_lang_project_harness/_cli_search_runtime.py +++ b/src/python_lang_project_harness/_cli_search_runtime.py @@ -32,20 +32,6 @@ ) -def _render_search_code_only(packet: dict[str, object]) -> str: - items = packet.get("items", ()) - if not isinstance(items, list): - return "\n" - code = "\n".join( - str(fields["code"]) - for item in items - if isinstance(item, dict) - and isinstance(fields := item.get("fields"), dict) - and isinstance(fields.get("code"), str) - ) - return f"{code}\n" if code else "\n" - - def _run_search_harness( project_root: Path, args: ProtocolArgs, @@ -153,7 +139,7 @@ def _run_exact_owner_items_search( return _TextSearchReport( modules=(parse_python_file(owner_path),), - project_scope=_fast_owner_items_scope(project_root, owner_path), + project_resolution=_fast_owner_items_scope(project_root, owner_path), root_paths=(str(owner_path),), ) @@ -170,7 +156,7 @@ def _run_exact_owner_search( paths = _exact_owner_related_paths(project_root, owner_path) return _TextSearchReport( modules=tuple(parse_python_file(path) for path in paths), - project_scope=_fast_text_search_scope(project_root), + project_resolution=_fast_text_search_scope(project_root), root_paths=tuple(str(path) for path in paths), ) @@ -190,7 +176,7 @@ def _run_metadata_dependency_search( return _TextSearchReport( modules=(), - project_scope=_TextSearchScope( + project_resolution=_TextSearchScope( project_root=project_root, project_metadata=read_python_project_metadata(project_root), fallback_paths=(project_root,), @@ -208,7 +194,7 @@ def _run_metadata_only_search( return _TextSearchReport( modules=(), - project_scope=_TextSearchScope( + project_resolution=_TextSearchScope( project_root=project_root, project_metadata=read_python_project_metadata(project_root), fallback_paths=(project_root,), @@ -230,7 +216,7 @@ def _run_workspace_seed_metadata_search( return _TextSearchReport( modules=(), - project_scope=_TextSearchScope( + project_resolution=_TextSearchScope( project_root=project_root, project_metadata=read_python_project_metadata(project_root), fallback_paths=(project_root,), @@ -330,7 +316,7 @@ def _exact_owner_related_paths( @dataclass(frozen=True, slots=True) class _TextSearchReport: modules: tuple[object, ...] - project_scope: _TextSearchScope + project_resolution: _TextSearchScope findings: tuple[object, ...] = () root_paths: tuple[str, ...] = () @@ -364,7 +350,7 @@ def _run_prefiltered_text_search( return _TextSearchReport( modules=tuple(parse_python_file(path) for path in paths), - project_scope=_fast_text_search_scope(project_root), + project_resolution=_fast_text_search_scope(project_root), root_paths=tuple(str(path) for path in paths), ) diff --git a/src/python_lang_project_harness/_dependency_topology.py b/src/python_lang_project_harness/_dependency_topology.py new file mode 100644 index 0000000..45d75da --- /dev/null +++ b/src/python_lang_project_harness/_dependency_topology.py @@ -0,0 +1,183 @@ +"""Canonical dependency-topology packets for the ASP provider contract.""" + +from __future__ import annotations + +import hashlib +import json +import re +from collections.abc import Iterable +from pathlib import Path +from typing import Any + +from python_lang_parser import parse_python_project_metadata + +_REQUIREMENT_NAME = re.compile(r"^[A-Za-z0-9][A-Za-z0-9._-]*") +_VERSION_PREFIXES = ("===", "==", "~=", "!=", ">=", "<=", ">", "<") + + +def build_dependency_topology_packet(project_root: str | Path) -> dict[str, Any]: + """Build the language-neutral dependency topology consumed by ASP.""" + + root = Path(project_root).resolve() + dependencies = sorted( + _collect_dependencies(root), + key=lambda item: (item[0], item[2] != "pyproject.toml", item[2], item[1]), + ) + nodes: list[dict[str, Any]] = [] + edges: list[dict[str, str]] = [] + emitted_dependencies: set[str] = set() + emitted_versions: set[str] = set() + + for name, version, manifest_path in dependencies: + dependency_id = f"dependency:{name}" + if dependency_id not in emitted_dependencies: + nodes.append( + { + "id": dependency_id, + "kind": "dependency", + "value": name, + "path": manifest_path, + "fields": { + "dependencyName": name, + "manifestPath": manifest_path, + }, + } + ) + emitted_dependencies.add(dependency_id) + + if not version or name in emitted_versions: + continue + version_id = f"dependency-version:{name}" + nodes.append( + { + "id": version_id, + "kind": "dependency-version", + "value": version, + "fields": {"version": version}, + } + ) + edges.append( + { + "source": dependency_id, + "target": version_id, + "relation": "version_locked", + } + ) + emitted_versions.add(name) + + graph = {"nodes": nodes, "edges": edges} + canonical_graph = json.dumps( + graph, + ensure_ascii=False, + separators=(",", ":"), + sort_keys=True, + ).encode("utf-8") + return { + "packetKind": "dependency-topology", + "fingerprint": f"sha256:{hashlib.sha256(canonical_graph).hexdigest()}", + "graph": graph, + } + + +def render_dependency_topology_packet(project_root: str | Path) -> str: + """Render one stable JSON object followed by a newline.""" + + return ( + json.dumps( + build_dependency_topology_packet(project_root), + ensure_ascii=False, + separators=(",", ":"), + sort_keys=True, + ) + + "\n" + ) + + +def _collect_dependencies(root: Path) -> set[tuple[str, str, str]]: + collected: set[tuple[str, str, str]] = set() + metadata = parse_python_project_metadata(root) + if metadata is not None: + for dependency in metadata.dependencies: + name = _normalize_dependency_name(dependency.name) + if not name: + continue + collected.add( + ( + name, + _requirement_version(dependency.requirement, dependency.name), + "pyproject.toml", + ) + ) + + for requirements_path in _requirements_paths(root): + manifest_path = requirements_path.relative_to(root).as_posix() + for requirement in _requirements(requirements_path): + collected.add((requirement[0], requirement[1], manifest_path)) + return collected + + +def _requirements_paths(root: Path) -> tuple[Path, ...]: + paths = { + path + for path in ( + root / "requirements.txt", + root / "requirements-dev.txt", + root / "requirements-test.txt", + ) + if path.is_file() + } + requirements_dir = root / "requirements" + if requirements_dir.is_dir(): + paths.update(path for path in requirements_dir.glob("*.txt") if path.is_file()) + return tuple(sorted(paths)) + + +def _requirements(path: Path) -> Iterable[tuple[str, str]]: + try: + lines = path.read_text(encoding="utf-8").splitlines() + except (OSError, UnicodeDecodeError): + return () + + requirements: list[tuple[str, str]] = [] + for line in lines: + parsed = _parse_requirement(line) + if parsed is not None: + requirements.append(parsed) + return tuple(requirements) + + +def _parse_requirement(line: str) -> tuple[str, str] | None: + requirement = line.strip() + if not requirement or requirement.startswith( + ("#", "-", "git+", "http://", "https://") + ): + return None + requirement = requirement.split(" #", 1)[0].split(";", 1)[0].strip() + match = _REQUIREMENT_NAME.match(requirement) + if match is None: + return None + name = _normalize_dependency_name(match.group(0)) + remainder = requirement[match.end() :].strip() + if remainder.startswith("["): + closing = remainder.find("]") + remainder = remainder[closing + 1 :].strip() if closing >= 0 else "" + version = remainder if remainder.startswith(_VERSION_PREFIXES) else "" + return name, version + + +def _normalize_dependency_name(value: str) -> str: + return re.sub(r"[-_.]+", "-", value.strip().casefold()) + + +def _requirement_version(requirement: str, dependency_name: str) -> str: + remainder = requirement.strip() + name_match = _REQUIREMENT_NAME.match(remainder) + if name_match is not None: + remainder = remainder[name_match.end() :].strip() + elif remainder.casefold().startswith(dependency_name.casefold()): + remainder = remainder[len(dependency_name) :].strip() + if remainder.startswith("["): + closing = remainder.find("]") + remainder = remainder[closing + 1 :].strip() if closing >= 0 else "" + remainder = remainder.split(";", 1)[0].strip() + return remainder if remainder.startswith(_VERSION_PREFIXES) else "" diff --git a/src/python_lang_project_harness/_dev_command_log.py b/src/python_lang_project_harness/_dev_command_log.py index 73881fe..fe635a1 100644 --- a/src/python_lang_project_harness/_dev_command_log.py +++ b/src/python_lang_project_harness/_dev_command_log.py @@ -1,4 +1,4 @@ -"""Write development-only py-harness command log events.""" +"""Write development-only asp-python command log events.""" from __future__ import annotations @@ -89,8 +89,8 @@ def _event_payload(self, exit_code: int) -> dict[str, Any]: "sessionId": self.session_id, "sessionOrdinal": self.session_ordinal, "languageId": "python", - "providerId": "py-harness", - "binary": "py-harness", + "providerId": "asp-python", + "binary": "asp-python", "argv": self.argv, "cwd": str(self.cwd), "projectRoot": str(self.project_root), @@ -142,15 +142,15 @@ def start_dev_command_log( session = resolve_session_context(log_root, project_root_hash) session_ordinal = allocate_session_ordinal(log_root, session.session_id) started_at_ms = int(time.time() * 1000) - event_id = f"py-harness-{started_at_ms}-{os.getpid()}-{session_ordinal:06d}" + event_id = f"asp-python-{started_at_ms}-{os.getpid()}-{session_ordinal:06d}" log_file = ( log_root / "python" - / "py-harness" + / "asp-python" / "commands" / f"{utc_file_timestamp()}-{session_ordinal:06d}-{sanitize_file_component(event_id)}.jsonl" ) - argv = ["py-harness", *redact_argv(list(args))] + argv = ["asp-python", *redact_argv(list(args))] return DevCommandLog( argv=argv, command=normalize_command(argv), diff --git a/src/python_lang_project_harness/_dev_command_log_command.py b/src/python_lang_project_harness/_dev_command_log_command.py index 9c2ebf7..d269f08 100644 --- a/src/python_lang_project_harness/_dev_command_log_command.py +++ b/src/python_lang_project_harness/_dev_command_log_command.py @@ -1,4 +1,4 @@ -"""Normalize py-harness argv for development command logging.""" +"""Normalize asp-python argv for development command logging.""" from __future__ import annotations diff --git a/src/python_lang_project_harness/_dev_command_log_context.py b/src/python_lang_project_harness/_dev_command_log_context.py index d56b0b5..13dd765 100644 --- a/src/python_lang_project_harness/_dev_command_log_context.py +++ b/src/python_lang_project_harness/_dev_command_log_context.py @@ -165,7 +165,7 @@ def read_active_context( def allocate_session_ordinal(log_root: Path, session_id: str) -> int: - directory = log_root / "python" / "py-harness" / "sessions" + directory = log_root / "python" / "asp-python" / "sessions" counter_path = directory / f"{sanitize_file_component(session_id)}.counter" lock_path = directory / f"{sanitize_file_component(session_id)}.lock" try: diff --git a/src/python_lang_project_harness/_evidence_graph.py b/src/python_lang_project_harness/_evidence_graph.py index 07a7542..0faad49 100644 --- a/src/python_lang_project_harness/_evidence_graph.py +++ b/src/python_lang_project_harness/_evidence_graph.py @@ -9,8 +9,8 @@ _EVIDENCE_GRAPH_SCHEMA_ID = "agent.semantic-protocols.semantic-evidence-graph" _EVIDENCE_GRAPH_PROTOCOL_ID = "agent.semantic-protocols.evidence-graph" _LANGUAGE_ID = "python" -_PROVIDER_ID = "py-harness" -_NAMESPACE = "agent.semantic-protocols.languages.python.py-harness" +_PROVIDER_ID = "asp-python" +_NAMESPACE = "agent.semantic-protocols.languages.python.asp-python" def build_python_evidence_graph(project_root: Path) -> dict[str, Any]: @@ -20,10 +20,10 @@ def build_python_evidence_graph(project_root: Path) -> dict[str, Any]: 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", "py-harness-check-full") - action_id = _node_id("python:action", "run-py-harness-check-full") + 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 = "py-harness check --full ." + check_command = "asp-python check --full ." nodes: list[dict[str, Any]] = [ { "nodeId": owner_id, @@ -52,7 +52,7 @@ def build_python_evidence_graph(project_root: Path) -> dict[str, Any]: "nodeId": receipt_id, "kind": "verification-receipt", "label": check_command, - "receiptId": "python.py-harness.check.full", + "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}, @@ -60,8 +60,8 @@ def build_python_evidence_graph(project_root: Path) -> dict[str, Any]: { "nodeId": action_id, "kind": "review-action", - "label": "Run py-harness check --full .", - "actionId": "python.run-py-harness-check-full", + "label": "Run asp-python check --full .", + "actionId": "python.run-asp-python-check-full", "status": "missing", "summary": "run-receipt", "fields": { @@ -79,7 +79,7 @@ def build_python_evidence_graph(project_root: Path) -> dict[str, Any]: { "gapId": gap_id, "ownerPath": owner_path, - "summary": "No attached py-harness full-check receipt for this evidence graph.", + "summary": "No attached asp-python full-check receipt for this evidence graph.", "severity": "warning", "fields": {"nextCommand": check_command}, } diff --git a/src/python_lang_project_harness/_exact_projection_model.py b/src/python_lang_project_harness/_exact_projection_model.py new file mode 100644 index 0000000..a9d6fd6 --- /dev/null +++ b/src/python_lang_project_harness/_exact_projection_model.py @@ -0,0 +1,122 @@ +"""Define exact Python selector and projection value boundaries.""" + +from __future__ import annotations + +import ast +from dataclasses import dataclass +from typing import Any +from urllib.parse import unquote + +REQUEST_SCHEMA_ID = "agent.semantic-protocols.provider-native-exact-request" +RESPONSE_SCHEMA_ID = "agent.semantic-protocols.provider-native-exact-projection" +SKELETON_SCHEMA_ID = "agent.semantic-protocols.callable-skeleton" +EXACT_SELECTOR_SCHEMA_ID = "asp.exact-structural-selector.v1" +CANONICAL_SELECTOR_SCHEMA_ID = "asp.canonical-item-selector.v1" + + +@dataclass(frozen=True) +class ExactSelector: + requested: str + root: str + owner_path: str + kind: str + symbol: str + segment_kind: str | None + segment_identity: str | None + + +@dataclass(frozen=True) +class ProjectionSegment: + kind: str + label: str + ordinal: int + byte_start: int + byte_end: int + + +def parse_selector(selector: str) -> ExactSelector: + language, separator, body = selector.partition("://") + if separator != "://" or language != "python": + raise ValueError("exact selector must use python://") + owner_path, fragment_separator, fragment = body.partition("#") + if fragment_separator != "#" or not owner_path: + 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": + raise ValueError("exact selector item fragment is invalid") + root = f"python://{owner_path}#{root_fragment}" + segment_kind = None + segment_identity = None + if segment_separator: + descendant_parts = descendant.split("/") + if len(descendant_parts) != 2 or not all(descendant_parts): + raise ValueError("exact descendant selector must be /") + segment_kind, segment_identity = descendant_parts + return ExactSelector( + requested=selector, + root=root, + owner_path=owner_path, + kind=parts[-2], + symbol=unquote(parts[-1]), + segment_kind=segment_kind, + segment_identity=segment_identity, + ) + + +def find_function( + tree: ast.AST, selector: ExactSelector +) -> ast.FunctionDef | ast.AsyncFunctionDef: + if selector.kind not in {"function", "method"}: + raise ValueError("callable projection requires function or method selector") + matches = [ + node + for node in ast.walk(tree) + if isinstance(node, (ast.FunctionDef, ast.AsyncFunctionDef)) + and node.name == selector.symbol + ] + if len(matches) != 1: + raise ValueError("exact callable selector is missing or ambiguous") + return matches[0] + + +def line_byte_offsets(source: bytes) -> list[int]: + offsets = [0] + for line in source.splitlines(keepends=True): + offsets.append(offsets[-1] + len(line)) + return offsets + + +def node_byte_span(node: ast.AST, line_offsets: list[int]) -> tuple[int, int]: + lineno = getattr(node, "lineno", None) + end_lineno = getattr(node, "end_lineno", None) + if lineno is None or end_lineno is None: + raise ValueError("Python AST node lacks exact source span") + return ( + line_offsets[lineno - 1] + int(getattr(node, "col_offset", 0)), + line_offsets[end_lineno - 1] + int(getattr(node, "end_col_offset", 0)), + ) + + +def flag_value(args: list[str] | tuple[str, ...], flag: str) -> str | None: + for index, arg in enumerate(args): + if arg == flag and index + 1 < len(args): + return args[index + 1] + prefix = f"{flag}=" + if arg.startswith(prefix): + return arg[len(prefix) :] + return None + + +def required_text(value: dict[str, Any], field: str) -> str: + result = value.get(field) + if not isinstance(result, str) or not result: + raise ValueError(f"exact request {field} must be non-empty text") + return result + + +def required_int(value: dict[str, Any], field: str) -> int: + result = value.get(field) + if not isinstance(result, int) or result < 0: + raise ValueError(f"exact request {field} must be a non-negative integer") + return result diff --git a/src/python_lang_project_harness/_exact_source_projection.py b/src/python_lang_project_harness/_exact_source_projection.py new file mode 100644 index 0000000..51f00c7 --- /dev/null +++ b/src/python_lang_project_harness/_exact_source_projection.py @@ -0,0 +1,152 @@ +"""Execute resident exact-source projections with the native Python AST.""" + +from __future__ import annotations + +import ast +import base64 +from pathlib import Path +from typing import Any + +from ._callable_skeleton_projection import ( + callable_skeleton_payload, + collect_segments, +) +from ._exact_projection_model import ( + REQUEST_SCHEMA_ID, + RESPONSE_SCHEMA_ID, + ExactSelector, + ProjectionSegment, + find_function, + line_byte_offsets, + node_byte_span, + parse_selector, + required_int, + required_text, +) + + +def project_provider_native_exact_request( + request: dict[str, Any], *, cwd: Path +) -> dict[str, Any]: + """Execute one exact projection through the resident provider boundary.""" + + _validate_request(request) + return _project_request(request, cwd) + + +def _project_request(request: dict[str, Any], cwd: Path) -> dict[str, Any]: + selector = parse_selector(required_text(request, "structuralSelector")) + if selector.owner_path != required_text(request, "ownerPath"): + raise ValueError("exact request ownerPath does not match structuralSelector") + source = base64.b64decode( + required_text(request, "sourceBytesBase64"), validate=True + ) + if len(source) != required_int(request, "sourceByteLength"): + raise ValueError("exact request sourceByteLength does not match decoded bytes") + tree = ast.parse(source.decode("utf-8"), filename=str(cwd / selector.owner_path)) + function = find_function(tree, selector) + line_offsets = line_byte_offsets(source) + root_start, root_end = node_byte_span(function, line_offsets) + segments = collect_segments(function, line_offsets) + projection_kind = required_text(request, "projectionKind") + if projection_kind == "source": + return _source_packet(request, selector, source, segments, root_start, root_end) + if projection_kind != "callable-skeleton": + raise ValueError("projectionKind must be source or callable-skeleton") + if selector.segment_kind is not None: + raise ValueError( + "callable-skeleton projection requires a root callable selector" + ) + payload = callable_skeleton_payload( + request, selector, function, segments, root_start, root_end + ) + return _projection_packet( + request, + selector, + projection_kind="callable-skeleton", + byte_start=root_start, + byte_end=root_end, + projection_payload=payload, + ) + + +def _source_packet( + request: dict[str, Any], + selector: ExactSelector, + source: bytes, + segments: list[ProjectionSegment], + root_start: int, + root_end: int, +) -> dict[str, Any]: + selected_start, selected_end = root_start, root_end + if selector.segment_kind is not None: + selected = next( + ( + segment + for segment in segments + if segment.kind == selector.segment_kind + and f"ordinal-{segment.ordinal}" == selector.segment_identity + ), + None, + ) + if selected is None: + raise ValueError("exact descendant selector does not resolve") + selected_start, selected_end = selected.byte_start, selected.byte_end + return _projection_packet( + request, + selector, + projection_kind="source", + byte_start=selected_start, + byte_end=selected_end, + projection_text=source[selected_start:selected_end].decode("utf-8"), + ) + + +def _validate_request(request: dict[str, Any]) -> None: + expected = { + "schemaId": REQUEST_SCHEMA_ID, + "schemaVersion": "1", + "languageId": "python", + "providerId": "asp-python", + "sourceEncoding": "base64", + "transport": "stdin-json", + } + for field, value in expected.items(): + if request.get(field) != value: + raise ValueError(f"exact request {field} must be {value}") + for field in ( + "generationIdentityDigest", + "parserIdentityDigest", + "queryPackDigest", + "sourceDigest", + ): + required_text(request, field) + + +def _projection_packet( + request: dict[str, Any], + selector: ExactSelector, + *, + projection_kind: str, + byte_start: int, + byte_end: int, + projection_text: str | None = None, + projection_payload: dict[str, Any] | None = None, +) -> dict[str, Any]: + packet: dict[str, Any] = { + "schemaId": RESPONSE_SCHEMA_ID, + "schemaVersion": "1", + "languageId": "python", + "providerId": "asp-python", + "projectionMode": projection_kind, + "requestedStructuralSelector": selector.requested, + "structuralSelector": selector.requested, + "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 + if projection_payload is not None: + packet["projectionPayload"] = projection_payload + return packet diff --git a/src/python_lang_project_harness/_flow_lite_query_packet.py b/src/python_lang_project_harness/_flow_lite_query_packet.py index e775c29..86cd1e4 100644 --- a/src/python_lang_project_harness/_flow_lite_query_packet.py +++ b/src/python_lang_project_harness/_flow_lite_query_packet.py @@ -66,7 +66,7 @@ def _flow_lite_packet(project_root: Path, where: dict[str, str]) -> dict[str, ob "protocolId": "agent.semantic-protocols.semantic-language", "protocolVersion": "1", "languageId": "python", - "providerId": "py-harness", + "providerId": "asp-python", "projectRoot": str(project_root), "packageName": project_root.name, "flowId": ( diff --git a/src/python_lang_project_harness/_model.py b/src/python_lang_project_harness/_model.py index 5d8bba6..b1f1b11 100644 --- a/src/python_lang_project_harness/_model.py +++ b/src/python_lang_project_harness/_model.py @@ -291,7 +291,7 @@ class PythonHarnessReport: blocking_severities: frozenset[PythonDiagnosticSeverity] = ( DEFAULT_BLOCKING_SEVERITIES ) - project_scope: PythonProjectHarnessScope | None = None + project_resolution: PythonProjectHarnessScope | None = None disabled_rule_ids: frozenset[str] = frozenset() blocking_rule_ids: frozenset[str] = frozenset() @@ -318,8 +318,10 @@ def to_dict(self) -> dict[str, object]: return { "root_paths": list(self.root_paths), - "project_scope": ( - None if self.project_scope is None else self.project_scope.to_dict() + "project_resolution": ( + None + if self.project_resolution is None + else self.project_resolution.to_dict() ), "file_count": self.file_count, "parsed_count": self.parsed_count, diff --git a/src/python_lang_project_harness/_project_evaluation.py b/src/python_lang_project_harness/_project_evaluation.py index f3c5859..12512c2 100644 --- a/src/python_lang_project_harness/_project_evaluation.py +++ b/src/python_lang_project_harness/_project_evaluation.py @@ -24,7 +24,7 @@ def evaluate_project_rule_packs( rule_packs: Sequence[PythonLangRulePack], modules: Sequence[PythonModuleReport], ) -> tuple[PythonHarnessFinding, ...]: - """Evaluate project-scope hooks exposed by configured rule packs.""" + """Evaluate project-resolution hooks exposed by configured rule packs.""" findings: list[PythonHarnessFinding] = [] for rule_pack in rule_packs: @@ -32,7 +32,7 @@ def evaluate_project_rule_packs( if module_evaluator is not None: findings.extend(module_evaluator(scope, modules)) continue - scope_evaluator = getattr(rule_pack, "evaluate_project_scope", None) + scope_evaluator = getattr(rule_pack, "evaluate_project_resolution", None) if scope_evaluator is not None: findings.extend(scope_evaluator(scope)) continue diff --git a/src/python_lang_project_harness/_project_policy_catalog.py b/src/python_lang_project_harness/_project_policy_catalog.py index 97c4383..a0a7bf7 100644 --- a/src/python_lang_project_harness/_project_policy_catalog.py +++ b/src/python_lang_project_harness/_project_policy_catalog.py @@ -111,7 +111,7 @@ 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 `py-harness --agent-snapshot` to copy the compact `[verify-profile]` hints.", + 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.", labels=dict(_RULE_LABELS), ), ) diff --git a/src/python_lang_project_harness/_project_resolution.py b/src/python_lang_project_harness/_project_resolution.py new file mode 100644 index 0000000..1303794 --- /dev/null +++ b/src/python_lang_project_harness/_project_resolution.py @@ -0,0 +1,135 @@ +"""Decode the provider ProjectResolution ABI and render typed failures.""" + +from __future__ import annotations + +import json +from pathlib import Path +from typing import Any + +from ._project_resolution_graph import ( + ProjectResolutionError, + resolve_project_resolution, +) + +_REQUEST_SCHEMA_ID = "agent.semantic-protocols.provider-project-resolution-request" +_RESPONSE_SCHEMA_ID = "agent.semantic-protocols.provider-project-resolution-response" + + +def resolve_project_resolution_request( + request: dict[str, Any], *, cwd: Path +) -> dict[str, Any]: + """Resolve one validated resident-runtime request without CLI dispatch.""" + + request = _decode_request(json.dumps(request, separators=(",", ":"))) + try: + return _response("resolved", scope=resolve_project_resolution(request, cwd=cwd)) + except ProjectResolutionError as error: + return _failure( + str(error), + reason_kind=error.reason_kind, + next_action=error.next_action, + ) + except (ValueError, OSError) as error: + return _failure(str(error)) + + +def _decode_request(stdin: str) -> dict[str, Any]: + try: + request = json.loads(stdin) + except json.JSONDecodeError as error: + raise ValueError( + f"project-resolution request must be valid JSON: {error}" + ) from error + if not isinstance(request, dict): + raise ValueError("project-resolution request must be a JSON object") + if ( + request.get("schemaId") != _REQUEST_SCHEMA_ID + or request.get("schemaVersion") != "1" + ): + raise ValueError("project-resolution request schema must be v1") + if ( + request.get("languageId") != "python" + or request.get("providerId") != "asp-python" + ): + raise ValueError( + "project-resolution request provider identity does not match asp-python" + ) + if request.get("candidateBase") != ".": + raise ValueError("project-resolution request candidateBase must be .") + generation = request.get("candidateGeneration") + if not isinstance(generation, dict) or not isinstance( + generation.get("digest"), str + ): + raise ValueError( + "project-resolution request requires candidateGeneration.digest" + ) + collection_scope = request.get("collectionScope") + if not isinstance(collection_scope, dict): + raise ValueError("project-resolution request collectionScope must be an object") + collection_kind = collection_scope.get("kind") + if collection_kind == "complete-generation": + if set(collection_scope) != {"kind"}: + raise ValueError("complete-generation collectionScope only accepts kind") + elif collection_kind == "explicit-owners": + owner_paths = collection_scope.get("ownerPaths") + if not isinstance(owner_paths, list) or not owner_paths: + raise ValueError("explicit-owners collectionScope requires ownerPaths") + if not all(isinstance(path, str) and path for path in owner_paths): + raise ValueError("explicit-owners ownerPaths must be non-empty text") + if len(set(owner_paths)) != len(owner_paths) or any( + path.startswith("/") + or "\\" in path + or any(part in {"", ".", ".."} for part in path.split("/")) + for path in owner_paths + ): + raise ValueError( + "explicit-owners ownerPaths must be unique normalized workspace-relative paths" + ) + else: + raise ValueError( + "project-resolution request collectionScope kind is unsupported" + ) + entries = request.get("candidatePaths") + if not isinstance(entries, list): + raise ValueError("project-resolution request candidatePaths must be an array") + if not isinstance(request.get("policyExclusions"), list): + raise ValueError("project-resolution request policyExclusions must be an array") + return request + + +def _response( + state: str, + *, + scope: dict[str, Any] | None = None, + failure: dict[str, str] | None = None, +) -> dict[str, Any]: + response: dict[str, Any] = { + "schemaId": _RESPONSE_SCHEMA_ID, + "schemaVersion": "1", + "languageId": "python", + "providerId": "asp-python", + "state": state, + } + if scope is not None: + response["scope"] = scope + if failure is not None: + response["failure"] = failure + return response + + +def _failure( + message: str, + *, + reason_kind: str = "project-entry-invalid", + next_action: str = "send-valid-project-resolution-request", +) -> dict[str, Any]: + if reason_kind == "provider-not-applicable": + return _response("not-applicable") + return _response( + "failed", + failure={ + "reasonKind": reason_kind, + "message": message, + "nextAction": next_action, + }, + ) diff --git a/src/python_lang_project_harness/_project_resolution_backends.py b/src/python_lang_project_harness/_project_resolution_backends.py new file mode 100644 index 0000000..dac4dda --- /dev/null +++ b/src/python_lang_project_harness/_project_resolution_backends.py @@ -0,0 +1,121 @@ +"""Resolve ProjectResolution source roots from Python build-backend semantics.""" + +from __future__ import annotations + +import re +from pathlib import PurePosixPath +from typing import Any + + +def package_backend_source_roots( + document: dict[str, Any], + package_root: PurePosixPath, + candidates: list[PurePosixPath], +) -> set[PurePosixPath]: + backend = build_backend(document) + if backend == "setuptools": + return _setuptools_default_roots(package_root, candidates) + if backend in {"hatch", "poetry", "pdm", "flit"}: + return _named_backend_default_roots(document, package_root, candidates, backend) + return set() + + +def build_backend(document: dict[str, Any]) -> str: + build_system = document.get("build-system", {}) + requires = ( + build_system.get("requires", []) if isinstance(build_system, dict) else [] + ) + joined = ( + " ".join(value for value in requires if isinstance(value, str)).lower() + if isinstance(requires, list) + else "" + ) + for marker, name in ( + ("setuptools", "setuptools"), + ("hatchling", "hatch"), + ("poetry-core", "poetry"), + ("pdm-backend", "pdm"), + ("flit_core", "flit"), + ): + if marker in joined: + return name + return "none" + + +def _setuptools_default_roots( + package_root: PurePosixPath, + candidates: list[PurePosixPath], +) -> set[PurePosixPath]: + src_root = package_root / "src" + if any( + candidate.suffix in {".py", ".pyi"} and src_root in candidate.parents + for candidate in candidates + ): + return {src_root} + excluded = { + "build", + "dist", + "docs", + "doc", + "examples", + "example", + "tests", + "test", + "scripts", + "tools", + } + roots: set[PurePosixPath] = set() + for candidate in candidates: + if candidate.suffix not in {".py", ".pyi"}: + continue + try: + relative = candidate.relative_to(package_root) + except ValueError: + continue + if len(relative.parts) == 1: + roots.add(candidate) + elif relative.parts[0] not in excluded: + roots.add(package_root / relative.parts[0]) + return roots + + +def _named_backend_default_roots( + document: dict[str, Any], + package_root: PurePosixPath, + candidates: list[PurePosixPath], + backend: str, +) -> set[PurePosixPath]: + name = _backend_module_name(document, backend) + if name is None: + return set() + module_path = name.replace(".", "/") + possible = { + package_root / module_path, + package_root / (module_path + ".py"), + package_root / "src" / module_path, + package_root / "src" / (module_path + ".py"), + } + return { + root + for root in possible + if any( + candidate == root or root in candidate.parents for candidate in candidates + ) + } + + +def _backend_module_name(document: dict[str, Any], backend: str) -> str | None: + tool = document.get("tool", {}) + if not isinstance(tool, dict): + tool = {} + if backend == "flit": + flit = tool.get("flit", {}) + module = flit.get("module", {}) if isinstance(flit, dict) else {} + if isinstance(module, dict) and isinstance(module.get("name"), str): + return module["name"] + project = document.get("project", {}) + name = project.get("name") if isinstance(project, dict) else None + if not isinstance(name, str) and backend == "poetry": + poetry = tool.get("poetry", {}) + name = poetry.get("name") if isinstance(poetry, dict) else None + return re.sub(r"[-.]+", "_", name) if isinstance(name, str) and name else None diff --git a/src/python_lang_project_harness/_project_resolution_candidates.py b/src/python_lang_project_harness/_project_resolution_candidates.py new file mode 100644 index 0000000..5ed62e5 --- /dev/null +++ b/src/python_lang_project_harness/_project_resolution_candidates.py @@ -0,0 +1,41 @@ +"""Validate ASP-scoped candidates and load only declared project manifests.""" + +from __future__ import annotations + +import tomllib +from pathlib import Path, PurePosixPath +from typing import Any + + +def candidate_paths_from_entries(entries: list[Any]) -> list[PurePosixPath]: + paths: list[PurePosixPath] = [] + for entry in entries: + if not isinstance(entry, str): + raise ValueError("each ProjectResolution candidate path must be text") + path = PurePosixPath(entry) + if path.is_absolute() or ".." in path.parts: + raise ValueError( + f"ProjectResolution candidate must be scope-relative: {path}" + ) + paths.append(path) + return sorted(set(paths)) + + +def candidate_pyproject_paths( + candidate_paths: list[PurePosixPath], +) -> list[PurePosixPath]: + return sorted(path for path in candidate_paths if path.name == "pyproject.toml") + + +def load_pyproject_document( + workspace_root: Path, + path: PurePosixPath, +) -> dict[str, Any]: + return tomllib.loads(_candidate_absolute_path(workspace_root, path).read_text()) + + +def _candidate_absolute_path(root: Path, path: PurePosixPath) -> Path: + absolute = (root / path.as_posix()).resolve() + if root != absolute and root not in absolute.parents: + raise ValueError(f"ProjectResolution candidate escaped scope root: {path}") + return absolute diff --git a/src/python_lang_project_harness/_project_resolution_document.py b/src/python_lang_project_harness/_project_resolution_document.py new file mode 100644 index 0000000..17e9ba0 --- /dev/null +++ b/src/python_lang_project_harness/_project_resolution_document.py @@ -0,0 +1,125 @@ +"""Render deterministic ProjectResolution and package-graph receipts.""" + +from __future__ import annotations + +import hashlib +from pathlib import Path, PurePosixPath +from typing import Any + +PROJECT_RESOLUTION_SCHEMA_ID = "agent.semantic-protocols.project-resolution" +PACKAGE_GRAPH_SCHEMA_ID = "agent.semantic-protocols.language-package-graph" + + +def build_scope_document( + *, + parser_id: str, + workspace_root: Path, + project_manifest: PurePosixPath, + selected_manifests: list[PurePosixPath], + candidate_paths: list[PurePosixPath], + candidate_generation: dict[str, Any], + packages: list[dict[str, Any]], + dependencies: list[dict[str, Any]], + scopes: list[dict[str, Any]], + unresolved: list[dict[str, str]], + manifest_read_count: int, +) -> dict[str, Any]: + generation_digest = candidate_generation["digest"] + package_ids_by_name = { + package["name"]: package["packageId"] for package in packages + } + internal_dependencies = [] + external_dependencies = [] + for dependency in dependencies: + to_package_id = package_ids_by_name.get(dependency["packageName"]) + if to_package_id is not None: + internal_dependencies.append( + { + "fromPackageId": dependency["fromPackageId"], + "toPackageId": to_package_id, + "kind": dependency["kind"], + } + ) + else: + external_dependencies.append( + { + "dependencyId": "python-dependency-" + + hashlib.sha256( + ( + dependency["fromPackageId"] + + ":" + + dependency["packageName"] + ).encode() + ).hexdigest()[:16], + "name": dependency["packageName"], + "kind": dependency["kind"], + "requested": dependency["versionRequirement"], + } + ) + return { + "schemaId": PROJECT_RESOLUTION_SCHEMA_ID, + "schemaVersion": "1", + "state": "resolved", + "completeness": "exact" if not unresolved else "partial", + "languageId": "python", + "providerId": "asp-python", + "parserId": parser_id, + "candidateGenerationDigest": generation_digest, + "projectEntry": project_manifest.as_posix(), + "packageGraph": { + "schemaId": PACKAGE_GRAPH_SCHEMA_ID, + "schemaVersion": "1", + "languageId": "python", + "providerId": "asp-python", + "projectEntry": project_manifest.as_posix(), + "parserId": parser_id, + "manifests": [ + _project_file(workspace_root, path, "pyproject-toml") + for path in selected_manifests + ], + "lockfiles": [ + _project_file(workspace_root, path, _lockfile_kind(path)) + for path in candidate_paths + if path.name in {"uv.lock", "poetry.lock", "pdm.lock"} + ], + "packages": packages, + "internalDependencyEdges": internal_dependencies, + "externalDependencies": external_dependencies, + "unresolved": unresolved, + }, + "sourceScopes": scopes, + "conflicts": [], + "metrics": { + "parsedManifestCount": manifest_read_count, + "parsedLockfileCount": sum( + path.name in {"uv.lock", "poetry.lock", "pdm.lock"} + for path in candidate_paths + ), + "affectedPackageCount": len(packages), + "fullWorkspaceReads": 0, + "fullManifestReparses": 0, + "dbOpens": 0, + "elapsedMicros": 0, + }, + } + + +def path_text(path: PurePosixPath) -> str: + return "." if path.as_posix() == "." else path.as_posix() + + +def _project_file(root: Path, path: PurePosixPath, kind: str) -> dict[str, str]: + return { + "path": path.as_posix(), + "kind": kind, + "digest": "sha256:" + + hashlib.sha256((root / path.as_posix()).read_bytes()).hexdigest(), + } + + +def _lockfile_kind(path: PurePosixPath) -> str: + return { + "uv.lock": "uv-lock", + "poetry.lock": "poetry-lock", + "pdm.lock": "pdm-lock", + }[path.name] diff --git a/src/python_lang_project_harness/_project_resolution_graph.py b/src/python_lang_project_harness/_project_resolution_graph.py new file mode 100644 index 0000000..240a08f --- /dev/null +++ b/src/python_lang_project_harness/_project_resolution_graph.py @@ -0,0 +1,211 @@ +"""Build Python package graphs from ASP-scoped candidates and pyproject data.""" + +from __future__ import annotations + +import hashlib +from dataclasses import dataclass +from pathlib import Path, PurePosixPath +from typing import Any + +from ._project_resolution_candidates import ( + candidate_paths_from_entries, + candidate_pyproject_paths, + load_pyproject_document, +) +from ._project_resolution_document import build_scope_document, path_text +from ._project_resolution_sources import ( + dependencies_for_manifest, + resolved_source_paths, + workspace_manifests, +) + +PARSER_ID = "python.pyproject-toml" + + +@dataclass(frozen=True) +class ProjectResolutionError(ValueError): + message: str + reason_kind: str + next_action: str + + def __str__(self) -> str: + return self.message + + +@dataclass(frozen=True) +class _ProjectResolutionContext: + candidate_paths: list[PurePosixPath] + documents: dict[PurePosixPath, dict[str, Any]] + project_manifest: PurePosixPath + selected_manifests: list[PurePosixPath] + + +def resolve_project_resolution( + request: dict[str, Any], + *, + cwd: Path, +) -> dict[str, Any]: + context = _resolution_context(request, cwd=cwd) + packages = [ + package + for path in context.selected_manifests + if ( + package := _package_from_manifest( + path, + context.documents[path], + context.candidate_paths, + ) + ) + is not None + ] + unresolved, scopes = _scopes(packages) + dependencies = [ + dependency + for path in context.selected_manifests + for dependency in dependencies_for_manifest( + packages, + path, + context.documents[path], + ) + ] + return build_scope_document( + parser_id=PARSER_ID, + workspace_root=cwd.resolve(), + project_manifest=context.project_manifest, + selected_manifests=context.selected_manifests, + candidate_paths=context.candidate_paths, + candidate_generation=request["candidateGeneration"], + packages=packages, + dependencies=dependencies, + scopes=scopes, + unresolved=unresolved, + manifest_read_count=len(context.documents), + ) + + +def _resolution_context( + request: dict[str, Any], + *, + cwd: Path, +) -> _ProjectResolutionContext: + workspace_root = cwd.resolve() + candidates = candidate_paths_from_entries(request["candidatePaths"]) + root_manifest = PurePosixPath("pyproject.toml") + manifests = candidate_pyproject_paths(candidates) + if not manifests: + raise ProjectResolutionError( + "provider has no tracked pyproject.toml candidate", + reason_kind="provider-not-applicable", + next_action="continue-without-python-provider", + ) + if root_manifest not in manifests: + raise ProjectResolutionError( + "provider project entry is required: candidate pyproject.toml", + reason_kind="project-entry-missing", + next_action="include a tracked pyproject.toml repository candidate", + ) + root_document = load_pyproject_document(workspace_root, root_manifest) + selected = workspace_manifests(root_manifest, root_document, manifests) + documents = { + path: ( + root_document + if path == root_manifest + else load_pyproject_document(workspace_root, path) + ) + for path in selected + } + return _ProjectResolutionContext(candidates, documents, root_manifest, selected) + + +def _package_from_manifest( + manifest_path: PurePosixPath, + document: dict[str, Any], + candidates: list[PurePosixPath], +) -> dict[str, Any] | None: + project = document.get("project") + tool = document.get("tool", {}) + uv = tool.get("uv", {}) if isinstance(tool, dict) else {} + if isinstance(uv, dict) and uv.get("package") is False: + return None + poetry = tool.get("poetry", {}) if isinstance(tool, dict) else {} + name = project.get("name") if isinstance(project, dict) else None + if not isinstance(name, str) and isinstance(poetry, dict): + name = poetry.get("name") + if not isinstance(name, str) or not name: + return None + package_id = ( + "python-package-" + + hashlib.sha256(f"{manifest_path.as_posix()}:{name}".encode()).hexdigest()[:16] + ) + source_paths, include_authority = resolved_source_paths( + document, manifest_path.parent, candidates + ) + source_roots = sorted( + {path_text(PurePosixPath(source_path).parent) for source_path in source_paths} + ) + target_id = ( + "python-target-" + + hashlib.sha256(f"{package_id}:library".encode()).hexdigest()[:16] + ) + return { + "packageId": package_id, + "name": name, + **( + {"version": project["version"]} + if isinstance(project, dict) and isinstance(project.get("version"), str) + else {} + ), + "root": path_text(manifest_path.parent), + "manifestPath": manifest_path.as_posix(), + "workspaceMember": True, + "targets": [ + { + "targetId": target_id, + "name": name, + "kind": "library", + "explicit": include_authority == "manifest-explicit", + "sourceRoots": source_roots, + "entrypoints": [], + "generatedRoots": [], + } + ], + } + + +def _scopes( + packages: list[dict[str, Any]], +) -> tuple[list[dict[str, str]], list[dict[str, Any]]]: + unresolved: list[dict[str, str]] = [] + scopes: list[dict[str, Any]] = [] + for package in packages: + target = package["targets"][0] + if target["sourceRoots"]: + scopes.append( + { + "scopeId": "python-source-scope-" + + hashlib.sha256( + f"{package['packageId']}:{target['targetId']}".encode() + ).hexdigest()[:16], + "packageId": package["packageId"], + "targetId": target["targetId"], + "roots": target["sourceRoots"], + "explicitPaths": ( + target["sourceRoots"] if target["explicit"] else [] + ), + "extensions": [".py", ".pyi"], + "includeAuthority": ( + "manifest-explicit" if target["explicit"] else "package-manager" + ), + "exclusions": [], + "classifications": ["production"], + } + ) + else: + unresolved.append( + { + "state": "target-source-missing", + "path": package["manifestPath"], + "reasonKind": "package-source-scope-missing", + } + ) + return unresolved, scopes diff --git a/src/python_lang_project_harness/_project_resolution_sources.py b/src/python_lang_project_harness/_project_resolution_sources.py new file mode 100644 index 0000000..015f987 --- /dev/null +++ b/src/python_lang_project_harness/_project_resolution_sources.py @@ -0,0 +1,209 @@ +"""Resolve package-manager-declared Python source roots and dependencies.""" + +from __future__ import annotations + +import fnmatch +import re +from pathlib import PurePosixPath +from typing import Any + +from ._project_resolution_backends import build_backend, package_backend_source_roots + + +def workspace_manifests( + root_manifest: PurePosixPath, + document: dict[str, Any], + manifests: list[PurePosixPath], +) -> list[PurePosixPath]: + tool = document.get("tool", {}) + uv = tool.get("uv", {}) if isinstance(tool, dict) else {} + workspace = uv.get("workspace", {}) if isinstance(uv, dict) else {} + members = workspace.get("members", []) if isinstance(workspace, dict) else [] + excludes = workspace.get("exclude", []) if isinstance(workspace, dict) else [] + if not isinstance(members, list) or not all( + isinstance(item, str) for item in members + ): + raise ValueError("tool.uv.workspace.members must be an array of strings") + if not isinstance(excludes, list) or not all( + isinstance(item, str) for item in excludes + ): + raise ValueError("tool.uv.workspace.exclude must be an array of strings") + selected = {root_manifest} + base = root_manifest.parent + for manifest in manifests: + relative_root = manifest.parent.relative_to(base).as_posix() + if relative_root == ".": + continue + if any( + fnmatch.fnmatchcase(relative_root, pattern) for pattern in members + ) and not any( + fnmatch.fnmatchcase(relative_root, pattern) for pattern in excludes + ): + selected.add(manifest) + return sorted(selected) + + +def resolved_source_paths( + document: dict[str, Any], + package_root: PurePosixPath, + candidates: list[PurePosixPath], +) -> tuple[list[str], str]: + roots = _declared_source_roots(document, package_root) + authority = "manifest-explicit" + if not roots: + roots = package_backend_source_roots(document, package_root, candidates) + authority = "package-manager" + paths = sorted( + { + candidate.as_posix() + for candidate in candidates + if candidate.suffix in {".py", ".pyi"} + and any(candidate == root or root in candidate.parents for root in roots) + } + ) + return paths, authority + + +def _declared_source_roots( + document: dict[str, Any], + package_root: PurePosixPath, +) -> set[PurePosixPath]: + roots: set[PurePosixPath] = set() + tool = document.get("tool", {}) + if not isinstance(tool, dict): + tool = {} + _setuptools_roots(tool.get("setuptools"), package_root, roots) + _hatch_roots(tool.get("hatch"), package_root, roots) + _poetry_roots(tool.get("poetry"), package_root, roots) + _script_roots(document.get("project"), package_root, roots) + return roots + + +def _setuptools_roots( + value: object, + package_root: PurePosixPath, + roots: set[PurePosixPath], +) -> None: + if not isinstance(value, dict): + return + package_dir = value.get("package-dir", {}) + if isinstance(package_dir, dict): + roots.update( + package_root / item + for item in package_dir.values() + if isinstance(item, str) + ) + find = value.get("packages", {}).get("find", {}) + if isinstance(find, dict): + where = find.get("where", []) + if isinstance(where, list): + roots.update(package_root / item for item in where if isinstance(item, str)) + modules = value.get("py-modules", []) + if isinstance(modules, list): + roots.update( + package_root / (module.replace(".", "/") + ".py") + for module in modules + if isinstance(module, str) + ) + + +def _hatch_roots( + value: object, + package_root: PurePosixPath, + roots: set[PurePosixPath], +) -> None: + if not isinstance(value, dict): + return + wheel = value.get("build", {}).get("targets", {}).get("wheel", {}) + if not isinstance(wheel, dict): + return + for key in ("packages", "only-include"): + entries = wheel.get(key, []) + if isinstance(entries, list): + roots.update( + package_root / item for item in entries if isinstance(item, str) + ) + + +def _poetry_roots( + value: object, + package_root: PurePosixPath, + roots: set[PurePosixPath], +) -> None: + if not isinstance(value, dict): + return + packages = value.get("packages", []) + if not isinstance(packages, list): + return + for package in packages: + if not isinstance(package, dict) or not isinstance(package.get("include"), str): + continue + source_root = package_root + if isinstance(package.get("from"), str): + source_root /= package["from"] + roots.add(source_root / package["include"]) + + +def _script_roots( + value: object, + package_root: PurePosixPath, + roots: set[PurePosixPath], +) -> None: + if not isinstance(value, dict): + return + for table_name in ("scripts", "gui-scripts"): + scripts = value.get(table_name, {}) + if not isinstance(scripts, dict): + continue + for target in scripts.values(): + if not isinstance(target, str): + continue + module = target.split(":", 1)[0].replace(".", "/") + roots.add(package_root / (module + ".py")) + roots.add(package_root / module / "__init__.py") + + +def dependencies_for_manifest( + packages: list[dict[str, Any]], + manifest_path: PurePosixPath, + document: dict[str, Any], +) -> list[dict[str, Any]]: + package = next( + (item for item in packages if item["manifestPath"] == manifest_path.as_posix()), + None, + ) + if package is None: + return [] + project = document.get("project", {}) + values = project.get("dependencies", []) if isinstance(project, dict) else [] + if not isinstance(values, list): + raise ValueError("project.dependencies must be an array") + return [_dependency(package["packageId"], value) for value in values] + + +def _dependency(package_id: str, value: object) -> dict[str, Any]: + if not isinstance(value, str): + raise ValueError("project.dependencies entries must be strings") + name_match = re.match(r"^[A-Za-z0-9][A-Za-z0-9._-]*", value) + if name_match is None: + raise ValueError(f"invalid PEP 508 dependency: {value}") + name = name_match.group(0) + return { + "fromPackageId": package_id, + "dependencyKey": f"dependencies:{name}", + "importName": name.replace("-", "_"), + "packageName": name, + "kind": "normal", + "resolution": "external", + "versionRequirement": value[len(name) :].strip() or "*", + "optional": False, + "features": [], + } + + +def package_manager(document: dict[str, Any]) -> str: + tool = document.get("tool", {}) + if isinstance(tool, dict) and isinstance(tool.get("uv"), dict): + return "uv" + backend = build_backend(document) + return backend if backend != "none" else "pep-621" diff --git a/src/python_lang_project_harness/_projection_batch.py b/src/python_lang_project_harness/_projection_batch.py new file mode 100644 index 0000000..a3ade1e --- /dev/null +++ b/src/python_lang_project_harness/_projection_batch.py @@ -0,0 +1,206 @@ +"""Shared ASP provider projection-batch transport for Python owners.""" + +from __future__ import annotations + +import ast +import base64 +import binascii +from dataclasses import dataclass + +from ._callable_skeleton_projection import callable_skeleton_payload, collect_segments +from ._exact_projection_model import ExactSelector + +_REQUEST_SCHEMA_ID = ( + "agent.semantic-protocols.provider-language-projection-batch-request" +) +_RESPONSE_SCHEMA_ID = ( + "agent.semantic-protocols.provider-language-projection-batch-response" +) +_IDENTITY_SCHEMA_ID = "agent.semantic-protocols.canonical-language-item-identity" + + +@dataclass(frozen=True, slots=True) +class _OwnerFrame: + path: str + digest: str + source: bytes + + +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] + response = { + "schemaId": _RESPONSE_SCHEMA_ID, + "schemaVersion": "1", + "languageId": request["languageId"], + "providerId": request["providerId"], + "generationRootDigest": request["generationRootDigest"], + "owners": projected, + } + return response + + +def _decode_request( + request: dict[str, object], +) -> tuple[list[_OwnerFrame], list[_OwnerFrame]]: + if ( + request.get("schemaId") != _REQUEST_SCHEMA_ID + or request.get("schemaVersion") != "1" + or request.get("languageId") != "python" + or not isinstance(request.get("parserIdentityDigest"), str) + or not request.get("parserIdentityDigest") + or not isinstance(request.get("queryPackDigest"), str) + or not request.get("queryPackDigest") + ): + raise ValueError("projection batch request identity mismatch") + owners = _decode_owners(request.get("owners"), "owners") + auxiliary_owners = _decode_owners( + request.get("auxiliaryOwners", []), "auxiliaryOwners" + ) + paths = [owner.path for owner in (*owners, *auxiliary_owners)] + if len(paths) != len(set(paths)): + raise ValueError("projection batch owner paths must be unique") + return owners, auxiliary_owners + + +def _decode_owners(value: object, field: str) -> list[_OwnerFrame]: + if not isinstance(value, list): + raise ValueError(f"projection batch {field} must be an array") + owners: list[_OwnerFrame] = [] + for raw_owner in value: + if not isinstance(raw_owner, dict): + raise ValueError("projection batch owner must be an object") + path = raw_owner.get("ownerPath") + digest = raw_owner.get("sourceLeafDigest") + if ( + not isinstance(path, str) + or not path + or not isinstance(digest, str) + or not digest + ): + raise ValueError("projection batch owner is incomplete") + source_encoding = raw_owner.get("sourceEncoding") + source_text = raw_owner.get("sourceText") + source_bytes_base64 = raw_owner.get("sourceBytesBase64") + if ( + source_encoding == "utf8" + and isinstance(source_text, str) + and source_bytes_base64 is None + ): + source = source_text.encode("utf-8") + elif ( + source_encoding == "base64" + and source_text is None + and isinstance(source_bytes_base64, str) + ): + try: + source = base64.b64decode(source_bytes_base64, validate=True) + except (binascii.Error, ValueError) as error: + raise ValueError("projection batch owner base64 is invalid") from error + else: + raise ValueError("projection batch owner source encoding mismatch") + owners.append(_OwnerFrame(path, digest, source)) + return owners + + +def _project_owner(owner: _OwnerFrame, header: dict[str, object]) -> dict[str, object]: + source = owner.source.decode("utf-8") + tree = ast.parse(source, filename=owner.path) + line_starts = _line_byte_starts(owner.source) + items: list[dict[str, object]] = [] + for node in tree.body: + if isinstance(node, (ast.FunctionDef, ast.AsyncFunctionDef)): + items.append( + _project_item(owner, node, "function", (), line_starts, header) + ) + elif isinstance(node, ast.ClassDef): + items.append(_project_item(owner, node, "class", (), line_starts, header)) + scope = (("implementation-owner", "type", node.name),) + for child in node.body: + if isinstance(child, (ast.FunctionDef, ast.AsyncFunctionDef)): + items.append( + _project_item( + owner, child, "method", scope, line_starts, header + ) + ) + return { + "ownerPath": owner.path, + "sourceLeafDigest": owner.digest, + "items": items, + "relations": [], + } + + +def _project_item( + owner: _OwnerFrame, + node: ast.FunctionDef | ast.AsyncFunctionDef | ast.ClassDef, + kind: str, + scopes: tuple[tuple[str, str, str], ...], + line_starts: list[int], + header: dict[str, object], +) -> dict[str, object]: + start = line_starts[node.lineno - 1] + node.col_offset + end = line_starts[node.end_lineno - 1] + node.end_col_offset + scope_path = "".join( + f"/scope/{relation}/{scope_kind}/{symbol}" + for relation, scope_kind, symbol in scopes + ) + 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, + ) + projection_request = { + "generationIdentityDigest": header["generationRootDigest"], + "parserIdentityDigest": header["parserIdentityDigest"], + "queryPackDigest": header["queryPackDigest"], + } + projections.append( + { + "projectionKind": "callable-skeleton", + "payload": callable_skeleton_payload( + projection_request, + exact_selector, + node, + collect_segments(node, line_starts), + start, + end, + ), + } + ) + return { + "itemId": f"item:{selector}", + "ownerId": f"owner:{owner.path}", + "kind": kind, + "name": node.name, + "selector": selector, + "sourceByteStart": start, + "sourceByteEnd": end, + "identity": { + "schemaId": _IDENTITY_SCHEMA_ID, + "schemaVersion": "1", + "languageId": "python", + "kind": kind, + "symbol": node.name, + "scopes": [ + {"relation": relation, "kind": scope_kind, "symbol": symbol} + for relation, scope_kind, symbol in scopes + ], + }, + "projections": projections, + } + + +def _line_byte_starts(source: bytes) -> list[int]: + starts = [0] + starts.extend(index + 1 for index, byte in enumerate(source) if byte == 0x0A) + return starts diff --git a/src/python_lang_project_harness/_render.py b/src/python_lang_project_harness/_render.py index 4e38719..8792452 100644 --- a/src/python_lang_project_harness/_render.py +++ b/src/python_lang_project_harness/_render.py @@ -120,12 +120,14 @@ def render_python_reasoning_tree( report.modules, import_roots=_reasoning_tree_import_roots(report), project_root=( - None if report.project_scope is None else report.project_scope.project_root + None + if report.project_resolution is None + else report.project_resolution.project_root ), project_metadata=( None - if report.project_scope is None - else report.project_scope.project_metadata + if report.project_resolution is None + else report.project_resolution.project_metadata ), ) project_root = _report_project_root(report) @@ -225,9 +227,6 @@ def _render_failure_frontier( selector = _failure_frontier_selector(finding, project_root=project_root) if selector is not None: lines.append(f"|hotBlock selector={selector} reason=blocking-finding") - lines.append( - f"|next action=owner-local-projection selector={selector} root=." - ) if len(findings) > 3: lines.append(f"|more blockingFindings={len(findings) - 3}") return "\n".join(lines) + "\n" @@ -300,17 +299,17 @@ def _format_software_criterion(criterion_id: str) -> str: def _reasoning_tree_import_roots(report: PythonHarnessReport) -> tuple[Path | str, ...]: - if report.project_scope is None: + if report.project_resolution is None: return report.root_paths - if report.project_scope.source_paths: - return report.project_scope.source_paths - return report.project_scope.monitored_paths + if report.project_resolution.source_paths: + return report.project_resolution.source_paths + return report.project_resolution.monitored_paths def _report_project_root(report: PythonHarnessReport) -> Path | None: - if report.project_scope is None: + if report.project_resolution is None: return None - return report.project_scope.project_root + return report.project_resolution.project_root def _render_reasoning_tree_node( diff --git a/src/python_lang_project_harness/_runner.py b/src/python_lang_project_harness/_runner.py index 3d3b7a7..a498c88 100644 --- a/src/python_lang_project_harness/_runner.py +++ b/src/python_lang_project_harness/_runner.py @@ -82,7 +82,7 @@ def run_python_project_harness( ) return replace( report, - project_scope=scope, + project_resolution=scope, findings=_configured_findings( compact_project_findings(report.findings, project_findings), config=selected_config, diff --git a/src/python_lang_project_harness/_runtime.py b/src/python_lang_project_harness/_runtime.py new file mode 100644 index 0000000..2da0a3d --- /dev/null +++ b/src/python_lang_project_harness/_runtime.py @@ -0,0 +1,120 @@ +"""Own resident Python provider operations and runtime contract frames.""" + +from __future__ import annotations + +import os +from pathlib import Path +from typing import Any + +_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 _required_env(name: str) -> str: + value = os.environ.get(name, "").strip() + if not value: + raise RuntimeError(f"resident Python provider omitted {name}") + return value + + +def _health() -> dict[str, Any]: + return { + "schemaId": _HEALTH_SCHEMA_ID, + "schemaVersion": "1", + "providerId": _required_env("ASP_PROVIDER_ID"), + "languageId": _required_env("ASP_PROVIDER_LANGUAGE_ID"), + "artifactDigest": _required_env("ASP_PROVIDER_ARTIFACT_DIGEST"), + "registrationDigest": _required_env("ASP_PROVIDER_REGISTRATION_DIGEST"), + "contractDigest": _required_env("ASP_PROVIDER_RUNTIME_CONTRACT_DIGEST"), + "transport": "http-json", + "operations": _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}" + ) + + +def _response_frame(request: dict[str, Any], cwd: Path) -> dict[str, Any]: + request_id = request.get("requestId") + operation = request.get("operation") + if set(request) != { + "schemaId", + "schemaVersion", + "requestId", + "operation", + "payload", + }: + error = "provider runtime request fields drift" + elif ( + request.get("schemaId") != _REQUEST_SCHEMA_ID + or request.get("schemaVersion") != "1" + ): + error = "provider runtime request schema identity drift" + elif not isinstance(request_id, str) or not request_id.strip(): + error = "provider runtime request identity is empty" + elif not isinstance(operation, str) or not operation.strip(): + error = "provider runtime operation is empty" + else: + try: + payload = request.get("payload") + 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: + error = str(execution_error) + else: + return { + "schemaId": _RESPONSE_SCHEMA_ID, + "schemaVersion": "1", + "requestId": request_id, + "outcome": "ready", + "payload": result, + } + return { + "schemaId": _RESPONSE_SCHEMA_ID, + "schemaVersion": "1", + "requestId": request_id if isinstance(request_id, str) else "", + "outcome": "error", + "error": error, + } + + +def serve_provider_runtime(cwd: Path) -> int: + """Serve the schema-driven HTTP runtime through its isolated transport owner.""" + + from ._runtime_http import serve_provider_runtime_http + + return serve_provider_runtime_http(cwd, _health()) diff --git a/src/python_lang_project_harness/_runtime_http.py b/src/python_lang_project_harness/_runtime_http.py new file mode 100644 index 0000000..34f0a34 --- /dev/null +++ b/src/python_lang_project_harness/_runtime_http.py @@ -0,0 +1,212 @@ +"""Own isolated HTTP JSON transport for the resident Python provider.""" + +from __future__ import annotations + +import json +import sys +from collections.abc import Callable +from http.server import BaseHTTPRequestHandler, ThreadingHTTPServer +from pathlib import Path +from threading import Lock, Thread +from typing import Any + +from ._runtime import _required_env, _response_frame + +_MAX_REQUEST_BYTES = 896 * 1024 +_MAX_STREAMS = 64 +_MAX_STREAM_FRAMES = 1024 + + +class RequestStreams: + """Isolate interleaved bounded request streams by ASP request identity.""" + + def __init__(self) -> None: + self._states: dict[str, tuple[int, int, list[str]]] = {} + self._lock = Lock() + + def accept( + self, frame: dict[str, Any], execute: Callable[[bytes], dict[str, Any]] + ) -> dict[str, Any]: + stream_id, frame_index, frame_count, chunk = self._validate(frame) + with self._lock: + state = self._states.get(stream_id) + if state is None: + if frame_index != 0: + raise RuntimeError("provider runtime request stream is absent") + if len(self._states) >= _MAX_STREAMS: + raise RuntimeError( + "provider runtime request stream capacity exceeded" + ) + state = (frame_count, 0, []) + self._states[stream_id] = state + expected_count, expected_index, chunks = state + if expected_count != frame_count or expected_index != frame_index: + self._states.pop(stream_id, None) + raise RuntimeError("provider runtime request stream order drift") + chunks.append(chunk) + if frame_index + 1 < frame_count: + self._states[stream_id] = (frame_count, frame_index + 1, chunks) + return self._ack(stream_id, frame_index) + self._states.pop(stream_id, None) + request = "".join(chunks).encode() + return execute(request) + + @staticmethod + def _validate(frame: dict[str, Any]) -> tuple[str, int, int, str]: + stream_id = frame.get("streamId") + index = frame.get("frameIndex") + count = frame.get("frameCount") + chunk = frame.get("requestChunk") + if ( + set(frame) + != { + "schemaId", + "schemaVersion", + "streamId", + "frameIndex", + "frameCount", + "requestChunk", + } + or frame.get("schemaId") + != "agent.semantic-protocols.provider-runtime-request-stream-frame" + or frame.get("schemaVersion") != "1" + or not isinstance(stream_id, str) + or not stream_id + or not isinstance(index, int) + or isinstance(index, bool) + or not isinstance(count, int) + or isinstance(count, bool) + or not 0 <= index < count <= _MAX_STREAM_FRAMES + or count <= 1 + or not isinstance(chunk, str) + ): + raise RuntimeError("provider runtime request stream identity drift") + return stream_id, index, count, chunk + + @staticmethod + def _ack(stream_id: str, frame_index: int) -> dict[str, Any]: + return { + "schemaId": "agent.semantic-protocols.provider-runtime-request-stream-ack", + "schemaVersion": "1", + "streamId": stream_id, + "frameIndex": frame_index, + "state": "accepted", + } + + +class ProviderRuntimeHandler(BaseHTTPRequestHandler): + """Serve one provider instance with per-request thread isolation.""" + + server_version = "asp-python" + protocol_version = "HTTP/1.1" + runtime_cwd = Path(".") + runtime_health: dict[str, Any] = {} + streams = RequestStreams() + + def log_message(self, _format: str, *_args: object) -> None: + return + + def _json(self, status: int, value: dict[str, Any]) -> None: + body = json.dumps(value, separators=(",", ":")).encode() + self.send_response(status) + self.send_header("content-type", "application/json") + self.send_header("content-length", str(len(body))) + self.send_header("connection", "keep-alive") + self.end_headers() + self.wfile.write(body) + + def _body(self) -> bytes: + try: + length = int(self.headers.get("content-length", "")) + except ValueError as error: + raise RuntimeError("provider runtime content-length is invalid") from error + if not 0 <= length <= _MAX_REQUEST_BYTES: + raise RuntimeError("provider runtime request exceeds byte budget") + body = self.rfile.read(length) + if len(body) != length: + raise RuntimeError("provider runtime request body is truncated") + return body + + def _runtime_request(self, body: bytes) -> dict[str, Any]: + request = json.loads(body) + if not isinstance(request, dict): + raise RuntimeError("provider runtime request is not an object") + return _response_frame(request, self.runtime_cwd) + + def do_GET(self) -> None: + if self.path == "/health": + self._json(200, self.runtime_health) + else: + self._json(404, {"error": "asp-client-server-route-not-found"}) + + def do_POST(self) -> None: + try: + if self.path == "/v1/provider-runtime": + response = self._runtime_request(self._body()) + elif self.path == "/v1/provider-runtime-stream": + frame = json.loads(self._body()) + if not isinstance(frame, dict): + raise RuntimeError( + "provider runtime request stream is not an object" + ) + response = self.streams.accept(frame, self._runtime_request) + elif self.path == "/shutdown": + self._json(200, {"state": "draining"}) + Thread(target=self.server.shutdown, daemon=True).start() + return + else: + self._json(404, {"error": "asp-client-server-route-not-found"}) + return + except (RuntimeError, UnicodeDecodeError, json.JSONDecodeError) as error: + self._json(400, {"error": str(error)}) + else: + self._json(200, response) + + +def _handler(cwd: Path, health: dict[str, Any]) -> type[ProviderRuntimeHandler]: + return type( + "BoundProviderRuntimeHandler", + (ProviderRuntimeHandler,), + {"runtime_cwd": cwd, "runtime_health": health, "streams": RequestStreams()}, + ) + + +def _parse_host(value: str) -> tuple[str, int]: + host, separator, port = value.rpartition(":") + if not separator or not host or not port: + raise RuntimeError(f"invalid ASP_CLIENT_SERVER_HOST: {value}") + try: + parsed_port = int(port) + except ValueError as error: + raise RuntimeError(f"invalid ASP_CLIENT_SERVER_HOST port: {value}") from error + if not 0 <= parsed_port <= 65535: + raise RuntimeError(f"invalid ASP_CLIENT_SERVER_HOST port: {value}") + return host.removeprefix("[").removesuffix("]"), parsed_port + + +def serve_provider_runtime_http(cwd: Path, health: dict[str, Any]) -> int: + """Publish bootstrap only after the loopback HTTP socket is bound.""" + + server = ThreadingHTTPServer( + _parse_host(_required_env("ASP_CLIENT_SERVER_HOST")), _handler(cwd, health) + ) + server.daemon_threads = True + host, port = server.server_address[:2] + bootstrap = { + "schemaId": "agent.semantic-protocols.asp-client-server-bootstrap", + "schemaVersion": "1", + "providerId": _required_env("ASP_PROVIDER_ID"), + "languageId": _required_env("ASP_PROVIDER_LANGUAGE_ID"), + "transport": "http-json", + "state": "ready", + "endpoint": f"http://{host}:{port}/", + } + sys.stdout.write( + json.dumps(bootstrap, sort_keys=True, separators=(",", ":")) + "\n" + ) + sys.stdout.flush() + try: + server.serve_forever(poll_interval=0.01) + finally: + server.server_close() + return 0 diff --git a/src/python_lang_project_harness/_semantic_graph_fact_render.py b/src/python_lang_project_harness/_semantic_graph_fact_render.py index 2a9421b..832b710 100644 --- a/src/python_lang_project_harness/_semantic_graph_fact_render.py +++ b/src/python_lang_project_harness/_semantic_graph_fact_render.py @@ -13,7 +13,7 @@ ) LANGUAGE_ID = "python" -PROVIDER_ID = "py-harness" +PROVIDER_ID = "asp-python" def graph_payload( diff --git a/src/python_lang_project_harness/_semantic_graph_facts.py b/src/python_lang_project_harness/_semantic_graph_facts.py index f0b544d..b9d4335 100644 --- a/src/python_lang_project_harness/_semantic_graph_facts.py +++ b/src/python_lang_project_harness/_semantic_graph_facts.py @@ -34,7 +34,7 @@ def render_semantic_graph_facts( "protocolId": "agent.semantic-protocols.semantic-language", "protocolVersion": "1", "languageId": "python", - "providerId": "py-harness", + "providerId": "asp-python", "projectRoot": project_root.as_posix(), "query": args.query or "", "nodes": [*field_payload["nodes"], *project_payload["nodes"]], @@ -52,7 +52,6 @@ def _supports_semantic_graph_facts(args: ProtocolArgs) -> bool: args.command == "search" and args.view == "semantic-facts" and args.json - and not args.code_only and args.query is not None ) diff --git a/src/python_lang_project_harness/_semantic_language.py b/src/python_lang_project_harness/_semantic_language.py index c7dbc27..b71cf1f 100644 --- a/src/python_lang_project_harness/_semantic_language.py +++ b/src/python_lang_project_harness/_semantic_language.py @@ -7,11 +7,17 @@ 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/owner-items", "query/owner-local-projection") +_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") @@ -22,9 +28,7 @@ _PYTHON_SEARCH_METHODS = tuple(f"search/{view}" for view in _PYTHON_SEARCH_VIEWS) -def semantic_language_registry_document( - project_root: str | None = None, -) -> dict[str, Any]: +def semantic_language_registry_document() -> dict[str, Any]: """Return the provider registry document advertised by agent doctor.""" payload: dict[str, Any] = { @@ -34,18 +38,17 @@ def semantic_language_registry_document( "protocolVersion": ids.SEMANTIC_LANGUAGE_PROTOCOL_VERSION, "languages": [python_semantic_language_registration()], } - if project_root is not None: - payload["projectRoot"] = project_root return payload def python_semantic_language_registration() -> dict[str, Any]: """Return the Python semantic-language provider registration.""" + identity = _provider_identity() return { - "languageId": ids.PYTHON_LANGUAGE_ID, - "providerId": ids.PYTHON_PROVIDER_ID, - "binary": ids.PYTHON_BINARY, + "languageId": identity["languageId"], + "providerId": identity["providerId"], + "binary": identity["binary"], "namespace": ids.PYTHON_PROVIDER_NAMESPACE, "displayName": "Python", "methods": [ @@ -58,6 +61,7 @@ def python_semantic_language_registration() -> dict[str, Any]: ], "methodDescriptors": python_semantic_language_method_descriptors(), "schemas": python_semantic_language_schemas(), + "queryPackDescriptor": python_query_pack_descriptor(), } @@ -118,7 +122,9 @@ def python_semantic_language_method_descriptors() -> list[dict[str, Any]]: { "method": "agent/doctor", "command": "agent", - "outputSchemaIds": [ids.SEMANTIC_LANGUAGE_REGISTRY_ID], + "outputSchemaIds": [ + "agent.semantic-protocols.semantic-provider-doctor" + ], "supportsJson": True, "supportsCompact": True, }, @@ -130,7 +136,7 @@ def python_semantic_language_method_descriptors() -> list[dict[str, Any]]: }, ] ) - return descriptors + return attach_semantic_language_invocations(descriptors) def _python_search_method_descriptor(descriptor: dict[str, Any]) -> dict[str, Any]: @@ -166,6 +172,22 @@ def _search_output_schema_ids(view: str) -> list[str]: 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( diff --git a/src/python_lang_project_harness/_semantic_language_ids.py b/src/python_lang_project_harness/_semantic_language_ids.py index 584c254..f89ecec 100644 --- a/src/python_lang_project_harness/_semantic_language_ids.py +++ b/src/python_lang_project_harness/_semantic_language_ids.py @@ -38,9 +38,9 @@ "agent.semantic-protocols.semantic-tree-sitter-grammar-profile" ) PYTHON_CAPABILITIES_SCHEMA_ID = ( - "agent.semantic-protocols.languages.python.py-harness.capabilities" + "agent.semantic-protocols.languages.python.asp-python.capabilities" ) PYTHON_LANGUAGE_ID = "python" -PYTHON_PROVIDER_ID = "py-harness" -PYTHON_BINARY = "py-harness" -PYTHON_PROVIDER_NAMESPACE = "agent.semantic-protocols.languages.python.py-harness" +PYTHON_PROVIDER_ID = "asp-python" +PYTHON_BINARY = "asp-python" +PYTHON_PROVIDER_NAMESPACE = "agent.semantic-protocols.languages.python.asp-python" diff --git a/src/python_lang_project_harness/_semantic_language_invocation.py b/src/python_lang_project_harness/_semantic_language_invocation.py new file mode 100644 index 0000000..18a6dc6 --- /dev/null +++ b/src/python_lang_project_harness/_semantic_language_invocation.py @@ -0,0 +1,82 @@ +"""Attach executable command templates to Python semantic-language descriptors.""" + +from typing import Any + +from . import _semantic_language_ids as ids + + +def attach_semantic_language_invocations( + descriptors: list[dict[str, Any]], +) -> list[dict[str, Any]]: + """Attach one registry-v2 invocation to every method descriptor.""" + for descriptor in descriptors: + benchmark = descriptor.get("benchmarkInvocation") + if isinstance(benchmark, dict) and isinstance(benchmark.get("args"), list): + invocation: dict[str, Any] = { + "argv": [ids.PYTHON_BINARY, *benchmark["args"]] + } + if benchmark.get("stdinTemplate") is not None: + invocation["stdinMode"] = "pipe-candidates" + descriptor["invocation"] = invocation + else: + descriptor["invocation"] = _non_search_invocation(descriptor["method"]) + return descriptors + + +def _non_search_invocation(method: str) -> dict[str, list[str]]: + invocations = { + "query": [ + ids.PYTHON_BINARY, + "query", + "--catalog", + "{query}", + "--workspace", + "{workspace}", + ], + "query/owner-items": [ + ids.PYTHON_BINARY, + "query", + "{owner}", + "--term", + "{query}", + "--workspace", + "{workspace}", + ], + "query/exact-selector-native-v1": [ + "asp", + "python", + "query", + "--selector", + "{selector}", + "--projection", + "{projection}", + "--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", + "dry-run", + "--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"], + } + return {"argv": invocations[method]} diff --git a/src/python_lang_project_harness/_semantic_language_query.py b/src/python_lang_project_harness/_semantic_language_query.py index 3999b2c..2ab2016 100644 --- a/src/python_lang_project_harness/_semantic_language_query.py +++ b/src/python_lang_project_harness/_semantic_language_query.py @@ -26,7 +26,7 @@ def python_query_method_descriptors() -> list[dict[str, Any]]: "packetSchemas": ["semantic-tree-sitter-query.v1"], "supportsJson": True, "supportsCompact": True, - "outputModes": ["frontier", "json", "code"], + "outputModes": ["frontier", "json"], "queryInputForms": ["catalog-id", "s-expression"], "grammarId": PYTHON_TREE_SITTER_GRAMMAR_ID, "grammarProfileVersion": PYTHON_TREE_SITTER_GRAMMAR_PROFILE_VERSION, @@ -48,74 +48,23 @@ def python_query_method_descriptors() -> list[dict[str, Any]]: ], "unsupportedPredicates": [], "cacheReplay": False, - "codeOutput": { - "mode": "pure-code", - "multiMatch": "deny", - "requires": ["exact-selector", "unique-predicate"], - }, "unsupportedPatternBehavior": "diagnostic", }, { - "method": "query/owner-items", + "method": "query/exact-selector-native-v1", "command": "query", - "input": "owner-path", - "requiredOptions": ["--term"], - "outputSchemaIds": [ids.SEMANTIC_QUERY_PACKET_SCHEMA_ID], - "packetSchemas": [ - "semantic-query-packet.v1", - "semantic-tree-sitter-query.v1", + "view": "exact-selector", + "outputSchemaIds": [ + "agent.semantic-protocols.provider-native-exact-projection" ], - "grammarId": PYTHON_TREE_SITTER_GRAMMAR_ID, - "grammarProfileVersion": PYTHON_TREE_SITTER_GRAMMAR_PROFILE_VERSION, - "grammarProfileSchema": "semantic-tree-sitter-grammar-profile.v1", - "grammarProfilePath": PYTHON_TREE_SITTER_GRAMMAR_PROFILE_PATH, - "queryInputForms": ["selector", "code-shaped"], - "adapterModes": ["native-projection"], - "sourceAuthorities": ["native-parser"], - "executionBackends": ["native-parser"], - "renderProfiles": ["compact-graph-frontier"], - "supportsJson": True, - "supportsCompact": True, - "supportsQuerySet": True, - "acceptedQuerySetSelectors": ["exact-set"], - "querySetScopes": ["owner"], - "outputModes": ["frontier", "json", "code", "names"], - "cacheReplay": False, - "codeOutput": { - "mode": "pure-code", - "multiMatch": "deny", - "requires": ["exact-selector", "unique-match"], - }, - "unsupportedPatternBehavior": "diagnostic", - }, - { - "method": "query/owner-local-projection", - "command": "query", - "input": "exact-selector", - "requiredOptions": ["--from-hook", "--selector"], - "outputSchemaIds": [ids.SEMANTIC_QUERY_PACKET_SCHEMA_ID], "packetSchemas": [ - "semantic-query-packet.v1", - "semantic-tree-sitter-query.v1", + "provider-native-exact-request.v1", + "provider-native-exact-response.v1", ], - "queryInputForms": ["selector"], - "grammarId": PYTHON_TREE_SITTER_GRAMMAR_ID, - "grammarProfileVersion": PYTHON_TREE_SITTER_GRAMMAR_PROFILE_VERSION, - "grammarProfileSchema": "semantic-tree-sitter-grammar-profile.v1", - "grammarProfilePath": PYTHON_TREE_SITTER_GRAMMAR_PROFILE_PATH, - "adapterModes": ["native-projection"], - "sourceAuthorities": ["native-parser"], - "executionBackends": ["native-parser"], - "renderProfiles": ["owner-local-projection"], + "requiresQuery": True, + "acceptsStdin": True, + "supportsPackageScope": False, "supportsJson": True, - "supportsCompact": True, - "outputModes": ["frontier", "json", "code", "names"], - "cacheReplay": False, - "codeOutput": { - "mode": "pure-code", - "multiMatch": "deny", - "requires": ["exact-selector"], - }, - "unsupportedPatternBehavior": "diagnostic", + "supportsCompact": False, }, ] diff --git a/src/python_lang_project_harness/_semantic_provider_doctor.py b/src/python_lang_project_harness/_semantic_provider_doctor.py new file mode 100644 index 0000000..dd12e3d --- /dev/null +++ b/src/python_lang_project_harness/_semantic_provider_doctor.py @@ -0,0 +1,52 @@ +"""Build the Python provider's canonical semantic doctor-v1 response.""" + +import json +from hashlib import sha256 +from typing import Any + +from . import _semantic_language_ids as ids + + +def _provider_identity() -> dict[str, str]: + """Return the provider identity owned by the executable contract. + + Installation and Runtime routing consume ``provider/asp-provider-registration.json``; + the provider process uses the same compile-time identity constants and does not + carry a second package-local provider manifest. + """ + + return { + "languageId": ids.PYTHON_LANGUAGE_ID, + "providerId": ids.PYTHON_PROVIDER_ID, + "binary": ids.PYTHON_BINARY, + "execution": "provider", + } + + +def _jcs_bytes(value: object) -> bytes: + return json.dumps( + value, + ensure_ascii=False, + sort_keys=True, + separators=(",", ":"), + ).encode("utf-8") + + +def semantic_provider_doctor_document() -> dict[str, Any]: + """Return the Python provider canonical doctor-v1 response envelope.""" + from ._semantic_language import semantic_language_registry_document + + identity = _provider_identity() + registry = semantic_language_registry_document() + return { + "schemaId": "agent.semantic-protocols.semantic-provider-doctor", + "schemaVersion": "1", + "schemaAuthority": "https://tao3k.github.io/agent-semantic-protocols/schemas/", + "protocolId": ids.SEMANTIC_LANGUAGE_PROTOCOL_ID, + "protocolVersion": ids.SEMANTIC_LANGUAGE_PROTOCOL_VERSION, + **identity, + "registrySchemaId": ids.SEMANTIC_LANGUAGE_REGISTRY_ID, + "registrySchemaVersion": ids.SEMANTIC_LANGUAGE_REGISTRY_VERSION, + "registry": registry, + "registryDigest": f"sha256:{sha256(_jcs_bytes(registry)).hexdigest()}", + } diff --git a/src/python_lang_project_harness/_semantic_query_pack.py b/src/python_lang_project_harness/_semantic_query_pack.py new file mode 100644 index 0000000..ef71828 --- /dev/null +++ b/src/python_lang_project_harness/_semantic_query_pack.py @@ -0,0 +1,58 @@ +"""Publish Python query-composition recipes for the shared registry.""" + +from typing import Any + + +def python_query_pack_descriptor() -> dict[str, Any]: + """Return the authoritative Python query-pack descriptor.""" + return { + "descriptorId": "python.query-pack", + "descriptorVersion": "1", + "languageId": "python", + "semanticFactsDescriptorId": "python.semantic-facts", + "termRoleOverrides": [], + "recipes": [ + { + "recipeId": "python-asyncio-runtime", + "trigger": { + "match": "any", + "terms": ["asyncio", "task", "scheduling"], + }, + "clauses": [ + { + "intentAxes": ["concurrency"], + "roles": ["concept"], + "terms": ["asyncio", "task", "scheduling"], + } + ], + }, + { + "recipeId": "python-context-lifecycle", + "trigger": { + "match": "any", + "terms": ["contextmanager", "resource", "lifecycle"], + }, + "clauses": [ + { + "intentAxes": ["resource-lifecycle"], + "roles": ["concept"], + "terms": ["contextmanager", "resource", "lifecycle"], + } + ], + }, + { + "recipeId": "python-stream-backpressure", + "trigger": { + "match": "any", + "terms": ["queue", "async-generator", "backpressure"], + }, + "clauses": [ + { + "intentAxes": ["collection", "stream"], + "roles": ["concept"], + "terms": ["queue", "async-generator", "backpressure"], + } + ], + }, + ], + } diff --git a/src/python_lang_project_harness/_semantic_query_packet.py b/src/python_lang_project_harness/_semantic_query_packet.py index da4abb2..c0b35ff 100644 --- a/src/python_lang_project_harness/_semantic_query_packet.py +++ b/src/python_lang_project_harness/_semantic_query_packet.py @@ -126,7 +126,10 @@ def _routes_for_term(import_routes: list[Any], term: str) -> list[dict[str, str] def semantic_import_route_next(route: dict[str, str]) -> str: - return f"py-harness query {route['ownerPath']} --term {route['query']} --code ." + return ( + "asp python search owner " + f"{route['ownerPath']} items --query {route['query']} --workspace . --view seeds" + ) def _projected_code_from_rows(projection: dict[str, Any]) -> str: diff --git a/src/python_lang_project_harness/_semantic_search_cli.py b/src/python_lang_project_harness/_semantic_search_cli.py index d0644ec..5848704 100644 --- a/src/python_lang_project_harness/_semantic_search_cli.py +++ b/src/python_lang_project_harness/_semantic_search_cli.py @@ -21,7 +21,6 @@ class ParsedSemanticSearchArgs: query_set: tuple[str, ...] = () pipes: tuple[str, ...] = () json: bool = False - code_only: bool = False render_mode: str | None = None error: str | None = None @@ -32,7 +31,6 @@ class _SearchOptionState: query_set: list[str] = field(default_factory=list) item_query: str | None = None json: bool = False - code_only: bool = False render_mode: str | None = None package_path: Path | None = None workspace: bool = False @@ -76,9 +74,9 @@ def _search_view_descriptor( def _semantic_search_usage() -> str: return ( - "usage: py-harness search " + "usage: asp-python search " " " - "... [--json] [--code] [--package PATH] [--workspace ]; " + "... [--json] [--package PATH] [--workspace ]; " "dependency/deps are manifest-first, import-usage backed, and cache hashes not raw source" ) @@ -91,10 +89,6 @@ def _validate_search_option_state( 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.code_only and state.json: - return "--code cannot be combined with --json" - if state.code_only and not (view == "owner" and state.item_query is not None): - return "--code requires search owner items --query " 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": @@ -148,8 +142,6 @@ def _consume_search_option( match arg: case "--json": state.json = True - case "--code": - state.code_only = True case "--view": value = _optional_arg(args, index + 1) if value not in {"graph", "hits", "both", "seeds"}: @@ -241,7 +233,6 @@ def _required_query_args( workspace=state.workspace, pipes=tuple(pipes), json=state.json, - code_only=state.code_only, render_mode=state.render_mode, ) @@ -267,7 +258,6 @@ def _required_term_query_args( package_path=state.package_path, workspace=state.workspace, json=state.json, - code_only=state.code_only, render_mode=state.render_mode, ) @@ -307,7 +297,6 @@ def _project_only_args( workspace=state.workspace, pipes=tuple(pipes), json=state.json, - code_only=state.code_only, render_mode=state.render_mode, ) @@ -350,7 +339,6 @@ def _optional_query_args( query_set=tuple(state.query_set), pipes=tuple(pipes), json=state.json, - code_only=state.code_only, render_mode=state.render_mode, ) project_root = ( @@ -363,7 +351,6 @@ def _optional_query_args( package_path=state.package_path, workspace=state.workspace, json=state.json, - code_only=state.code_only, render_mode=state.render_mode, ) @@ -422,18 +409,8 @@ def _is_flag_like_literal_search_query( and not positionals and not query_set and arg.startswith("-") - and arg - not in { - "--view", - "--package", - "--workspace", - "--owner", - "--dependency", - "--query", - "--code", - "--help", - "-h", - } + and not arg.startswith("--") + and arg != "-h" ) diff --git a/src/python_lang_project_harness/_semantic_search_ingest_fast.py b/src/python_lang_project_harness/_semantic_search_ingest_fast.py index 41f7798..985489c 100644 --- a/src/python_lang_project_harness/_semantic_search_ingest_fast.py +++ b/src/python_lang_project_harness/_semantic_search_ingest_fast.py @@ -36,7 +36,6 @@ def _supports_fast_empty_ingest(args: ProtocolArgs, stdin: str) -> bool: and args.view == "ingest" and args.render_mode == "seeds" and not args.json - and not args.code_only and stdin == "" and args.query is None and args.item_query is None diff --git a/src/python_lang_project_harness/_semantic_search_items.py b/src/python_lang_project_harness/_semantic_search_items.py index 6ad11ba..1c2bb91 100644 --- a/src/python_lang_project_harness/_semantic_search_items.py +++ b/src/python_lang_project_harness/_semantic_search_items.py @@ -123,7 +123,6 @@ def _selector_resolved_owner_items( import_routes = payload.get("importRoutes", []) module = _module_for_owner(report, project_root, owner_path) selector_identity = python_structural_selector_identity(selector) - selector_range = _selector_line_range(selector, owner_path) if selector_identity is not None: selector_owner_path, selector_kind, selector_name = selector_identity items = ( @@ -143,15 +142,6 @@ def _selector_resolved_owner_items( "itemMatch": "exact" if items else "none", } import_routes = [] - elif selector_range is not None: - items = _selector_range_items(report, project_root, owner_path, selector_range) - fields = { - **fields, - "item": len(items), - "itemStatus": "hit" if items else "miss", - "itemMatch": "exact" if items else "none", - } - import_routes = [] return items, fields, import_routes @@ -191,7 +181,9 @@ def _owner_item_semantic_query_packet( "patchSafety": { "level": "read-safe", "reason": "compact query packet is not a mutation authority", - "nextAction": "query --from-hook owner-local-projection", + "nextAction": ( + "query --selector --projection source" + ), }, "queryCoverage": [ semantic_query_coverage( @@ -229,62 +221,6 @@ def _module_for_owner( return None -def _selector_range_items( - report: PythonHarnessReport, - project_root: Path, - owner_path: str, - selector_range: tuple[int, int], -) -> list[dict[str, Any]]: - module = _module_for_owner(report, project_root, owner_path) - if module is None: - return [] - return [ - _item_record(module, project_root, owner_path, symbol) - for symbol in _sorted_symbols(module) - if _symbol_overlaps_range(symbol, selector_range) - ] - - -def _symbol_overlaps_range( - symbol: PythonSymbol, - selector_range: tuple[int, int], -) -> bool: - start_line, end_line = selector_range - symbol_end = symbol.end_line or symbol.location.line - return symbol_end >= start_line and symbol.location.line <= end_line - - -def _selector_line_range( - selector: str | None, - owner_path: str, -) -> tuple[int, int] | None: - if selector is None: - return None - normalized = selector.replace("\\", "/").removeprefix("owner:") - if any(marker in normalized for marker in ("*", "{", "}")): - return None - path_and_start, separator, end_text = normalized.rpartition(":") - if not separator: - return None - path, separator, start_text = path_and_start.rpartition(":") - if separator and path == owner_path: - pass - elif path_and_start == owner_path: - start_text, separator, end_text = end_text.partition("-") - if not separator: - return None - else: - return None - try: - start_line = int(start_text) - end_line = int(end_text) - except ValueError: - return None - if start_line < 1 or end_line < 1: - return None - return (min(start_line, end_line), max(start_line, end_line)) - - def _sorted_symbols(module: PythonModuleReport) -> list[PythonSymbol]: return sorted( module.symbols, diff --git a/src/python_lang_project_harness/_semantic_search_lexical_fast.py b/src/python_lang_project_harness/_semantic_search_lexical_fast.py index e96e1e0..88ad828 100644 --- a/src/python_lang_project_harness/_semantic_search_lexical_fast.py +++ b/src/python_lang_project_harness/_semantic_search_lexical_fast.py @@ -33,7 +33,6 @@ def _supports_fast_lexical_seed_search(args: ProtocolArgs) -> bool: and args.view == "lexical" and args.render_mode == "seeds" and not args.json - and not args.code_only and bool(_fast_lexical_query_terms(args)) and args.pipes in {("owner",), ("owner", "tests")} and args.item_query is None diff --git a/src/python_lang_project_harness/_semantic_search_model.py b/src/python_lang_project_harness/_semantic_search_model.py index 7938ef4..f6630a5 100644 --- a/src/python_lang_project_harness/_semantic_search_model.py +++ b/src/python_lang_project_harness/_semantic_search_model.py @@ -23,7 +23,7 @@ @dataclass(frozen=True, slots=True) class PythonSemanticSearchOptions: - """Options parsed from a `py-harness search` invocation.""" + """Options parsed from an `asp-python search` invocation.""" view: str query: str | 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 index a222fa8..5d1a26c 100644 --- a/src/python_lang_project_harness/_semantic_search_owner_fast.py +++ b/src/python_lang_project_harness/_semantic_search_owner_fast.py @@ -34,7 +34,6 @@ def _fast_owner_path(args: ProtocolArgs, project_root: Path) -> Path | None: or args.view != "owner" or args.render_mode != "seeds" or args.json - or args.code_only or args.query is None or args.item_query is not None or args.owner_path is not None diff --git a/src/python_lang_project_harness/_semantic_search_packages.py b/src/python_lang_project_harness/_semantic_search_packages.py index 4817c91..6945cdd 100644 --- a/src/python_lang_project_harness/_semantic_search_packages.py +++ b/src/python_lang_project_harness/_semantic_search_packages.py @@ -58,12 +58,12 @@ def workspace_packages( 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_scope is not None: + 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_scope.source_paths + for path in report.project_resolution.source_paths ) return _dedupe_packages(packages)[:MAX_WORKSPACE_PACKAGES] diff --git a/src/python_lang_project_harness/_semantic_search_prime_fast.py b/src/python_lang_project_harness/_semantic_search_prime_fast.py index 77705ae..724332c 100644 --- a/src/python_lang_project_harness/_semantic_search_prime_fast.py +++ b/src/python_lang_project_harness/_semantic_search_prime_fast.py @@ -53,7 +53,6 @@ def _supports_fast_prime_search(args: ProtocolArgs) -> bool: and args.view == "prime" and args.render_mode == "seeds" and not args.json - and not args.code_only and args.query is None and args.item_query is None and args.owner_path is None diff --git a/src/python_lang_project_harness/_test_layout.py b/src/python_lang_project_harness/_test_layout.py index e9ea01f..10fdff2 100644 --- a/src/python_lang_project_harness/_test_layout.py +++ b/src/python_lang_project_harness/_test_layout.py @@ -44,9 +44,11 @@ def evaluate(self, report: PythonModuleReport) -> Iterable[PythonHarnessFinding] def evaluate_project(self, project_root: Path) -> Iterable[PythonHarnessFinding]: """Evaluate project-level pytest layout rules.""" - return self.evaluate_project_scope(python_project_harness_scope(project_root)) + return self.evaluate_project_resolution( + python_project_harness_scope(project_root) + ) - def evaluate_project_scope( + def evaluate_project_resolution( self, scope: PythonProjectHarnessScope, ) -> Iterable[PythonHarnessFinding]: diff --git a/src/python_lang_project_harness/_tree_sitter_query.py b/src/python_lang_project_harness/_tree_sitter_query.py index 8c138ed..6849909 100644 --- a/src/python_lang_project_harness/_tree_sitter_query.py +++ b/src/python_lang_project_harness/_tree_sitter_query.py @@ -10,7 +10,6 @@ from ._tree_sitter_query_model import parse_selector from ._tree_sitter_query_packet import ( syntax_query_packet, - tree_sitter_query_code, tree_sitter_query_compact_lines, ) from ._tree_sitter_query_projection import project_tree_sitter_query @@ -50,7 +49,7 @@ def write_tree_sitter_query_response( query=query, terms=terms, selector=selector, - code_output=args.code_only, + code_output=False, projection=projection, ), separators=(",", ":"), @@ -58,10 +57,5 @@ def write_tree_sitter_query_response( ) stdout.write("\n") return - if args.code_only: - stdout.write(tree_sitter_query_code(projection.rows)) - if projection.rows: - stdout.write("\n") - return stdout.write(tree_sitter_query_compact_lines(query, terms, projection)) stdout.write("\n") diff --git a/src/python_lang_project_harness/verification/facts.py b/src/python_lang_project_harness/verification/facts.py index 4cf1262..c26bd2d 100644 --- a/src/python_lang_project_harness/verification/facts.py +++ b/src/python_lang_project_harness/verification/facts.py @@ -34,7 +34,7 @@ def verification_reasoning_tree_facts( ) -> PythonReasoningTreeFacts: """Return parser-owned reasoning-tree facts for one harness report.""" - scope = report.project_scope + scope = report.project_resolution return python_reasoning_tree_facts( report.modules, import_roots=_reasoning_tree_import_roots(report), @@ -46,8 +46,8 @@ def verification_reasoning_tree_facts( def verification_project_root(report: PythonHarnessReport) -> Path: """Return the project root represented by a harness report.""" - if report.project_scope is not None: - return report.project_scope.project_root + if report.project_resolution is not None: + return report.project_resolution.project_root if report.root_paths: return Path(report.root_paths[0]) return Path(".") @@ -240,8 +240,8 @@ def _append_owner_responsibilities( def _reasoning_tree_import_roots( report: PythonHarnessReport, ) -> tuple[Path | str, ...]: - if report.project_scope is None: + if report.project_resolution is None: return report.root_paths - if report.project_scope.source_paths: - return report.project_scope.source_paths - return report.project_scope.monitored_paths + if report.project_resolution.source_paths: + return report.project_resolution.source_paths + return report.project_resolution.monitored_paths diff --git a/tests/fixtures/bin/asp b/tests/fixtures/bin/asp index 9d9823e..4f017d0 100755 --- a/tests/fixtures/bin/asp +++ b/tests/fixtures/bin/asp @@ -460,9 +460,9 @@ function orderedHeaderFields(kind, headerFields, query, algorithm) { if (kind === "search-lexical" || kind === "search-query") { const leading = { ...(query === undefined ? {} : { q: query }), - ...(headerFields.view === undefined ? {} : { view: headerFields.view }), ...(headerFields.querySet === undefined ? {} : { querySet: headerFields.querySet }), ...(headerFields.selector === undefined ? {} : { selector: headerFields.selector }), + ...(headerFields.view === undefined ? {} : { view: headerFields.view }), ...(algorithm === undefined ? {} : { alg: algorithm }), }; return { diff --git a/tests/unit/harness/project_policy/test_layout.py b/tests/unit/harness/project_policy/test_layout.py index cf72320..1c3e36a 100644 --- a/tests/unit/harness/project_policy/test_layout.py +++ b/tests/unit/harness/project_policy/test_layout.py @@ -61,8 +61,8 @@ def test_project_policy_accepts_pyproject_declared_nested_src_layout( report = run_python_project_harness(tmp_path) assert report.is_clean - assert report.project_scope is not None - assert report.project_scope.source_paths == (package.parent,) + assert report.project_resolution is not None + assert report.project_resolution.source_paths == (package.parent,) def test_project_policy_blocks_missing_declared_package_root( diff --git a/tests/unit/harness/provider_runtime_live_support.py b/tests/unit/harness/provider_runtime_live_support.py new file mode 100644 index 0000000..2598d80 --- /dev/null +++ b/tests/unit/harness/provider_runtime_live_support.py @@ -0,0 +1,178 @@ +"""Shared black-box HTTP corpus helpers for the resident Python provider.""" + +from __future__ import annotations + +import base64 +import http.client +import json +import os +import time +from concurrent.futures import ThreadPoolExecutor +from pathlib import Path +from urllib.parse import SplitResult + +from python_lang_project_harness._runtime import _response_frame + + +def environment() -> dict[str, str]: + return { + **os.environ, + "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_ID": "asp-python", + "ASP_PROVIDER_LANGUAGE_ID": "python", + "ASP_CLIENT_SERVER_HOST": "127.0.0.1:0", + } + + +def post(connection: http.client.HTTPConnection, path: str, value: object) -> dict: + body = json.dumps(value, separators=(",", ":")).encode() + connection.request("POST", path, body, {"content-type": "application/json"}) + response = connection.getresponse() + payload = json.loads(response.read()) + assert response.status == 200, payload + return payload + + +def projection_payload( + source: str = "def greet(name: str) -> str:\n return name\n", +) -> dict: + return { + "schemaId": "agent.semantic-protocols.provider-language-projection-batch-request", + "schemaVersion": "1", + "languageId": "python", + "providerId": "asp-python", + "workspaceIdentity": "workspace-python-live-corpus", + "generationRootDigest": "blake3-256:generation-python-live-corpus", + "parserIdentityDigest": "blake3-256:parser-python-live-corpus", + "queryPackDigest": "blake3-256:query-python-live-corpus", + "owners": [ + { + "ownerPath": "src/example.py", + "sourceLeafDigest": "blake3-256:owner-python-live-corpus", + "sourceEncoding": "utf8", + "sourceText": source, + } + ], + } + + +def frame(request_id: str, operation: str, payload: dict) -> dict: + return { + "schemaId": "agent.semantic-protocols.provider-runtime-request-frame", + "schemaVersion": "1", + "requestId": request_id, + "operation": operation, + "payload": payload, + } + + +def assert_projection_and_query(connection: http.client.HTTPConnection) -> None: + projected = post( + connection, + "/v1/provider-runtime", + frame("projection-1", "projection-batch", projection_payload()), + ) + assert projected["outcome"] == "ready" + assert projected["payload"]["owners"][0]["items"][0]["name"] == "greet" + source = b"def greet(name: str) -> str:\n return name\n" + payload = { + "schemaId": "agent.semantic-protocols.provider-native-exact-request", + "schemaVersion": "1", + "languageId": "python", + "providerId": "asp-python", + "structuralSelector": "python://src/example.py#item/function/greet", + "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", + } + queried = post( + connection, "/v1/provider-runtime", frame("query-1", "query", payload) + ) + assert queried["outcome"] == "ready" + assert queried["payload"]["projectionText"].startswith("def greet") + + +def assert_streamed_corpus(connection: http.client.HTTPConnection) -> None: + source = "#" + ("x" * (900 * 1024)) + "\ndef greet():\n return 1\n" + request = json.dumps( + frame("stream-1", "projection-batch", projection_payload(source)), + separators=(",", ":"), + ) + chunks = [request[i : i + 128 * 1024] for i in range(0, len(request), 128 * 1024)] + for index, chunk in enumerate(chunks): + response = post( + connection, + "/v1/provider-runtime-stream", + { + "schemaId": "agent.semantic-protocols.provider-runtime-request-stream-frame", + "schemaVersion": "1", + "streamId": "stream-1", + "frameIndex": index, + "frameCount": len(chunks), + "requestChunk": chunk, + }, + ) + assert response["outcome" if index + 1 == len(chunks) else "state"] == ( + "ready" if index + 1 == len(chunks) else "accepted" + ) + + +def assert_concurrent_corpus(endpoint: SplitResult) -> None: + def request(index: int) -> str: + peer = http.client.HTTPConnection(endpoint.hostname, endpoint.port, timeout=2) + try: + return post( + peer, + "/v1/provider-runtime", + frame(f"parallel-{index}", "projection-batch", projection_payload()), + )["outcome"] + finally: + peer.close() + + with ThreadPoolExecutor(max_workers=16) as executor: + assert set(executor.map(request, range(32))) == {"ready"} + + +def latency_receipt(connection: http.client.HTTPConnection) -> str: + for index in range(16): + post( + connection, + "/v1/provider-runtime", + frame(f"warm-{index}", "projection-batch", projection_payload()), + ) + loopback = [] + for index in range(128): + started = time.perf_counter_ns() + response = post( + connection, + "/v1/provider-runtime", + frame(f"sample-{index}", "projection-batch", projection_payload()), + ) + loopback.append((time.perf_counter_ns() - started) // 1000) + assert response["outcome"] == "ready" + service = [] + for index in range(256): + started = time.perf_counter_ns() + response = _response_frame( + frame(f"service-{index}", "projection-batch", projection_payload()), + Path("."), + ) + service.append((time.perf_counter_ns() - started) // 1000) + assert response["outcome"] == "ready" + service_p99, loopback_p99 = percentile(service, 99), percentile(loopback, 99) + assert service_p99 < 1_000 + return f"[provider-live-corpus] schemaVersion=1 provider=asp-python owners=1 samples=128 serviceP99Micros={service_p99} loopbackP99Micros={loopback_p99}" + + +def percentile(samples: list[int], value: int) -> int: + ordered = sorted(samples) + return ordered[min(len(ordered) - 1, (len(ordered) * value) // 100)] diff --git a/tests/unit/harness/test_cli.py b/tests/unit/harness/test_cli.py index ad83791..db009d4 100644 --- a/tests/unit/harness/test_cli.py +++ b/tests/unit/harness/test_cli.py @@ -10,33 +10,29 @@ from pathlib import Path -def test_cli_help_advertises_code_flag() -> None: +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 "py-harness search ... [--json] [--code]" in rendered + assert "asp-python search ... [--json] [--package PATH]" in rendered assert ( - "py-harness query --term [--term ] [--workspace ] [--names-only | --code]" - in rendered + "asp python query --selector " + "--projection " in rendered ) - assert ( - "search owner items --query [--names-only | --code]" - in rendered - ) - assert "query --term --code" in rendered -def test_cli_subcommand_help_advertises_code_flag() -> None: +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 "--code" in rendered + assert "--selector " in rendered + assert "--projection " in rendered -def test_cli_agent_guide_advertises_code_route(tmp_path: Path) -> None: +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) @@ -44,12 +40,8 @@ def test_cli_agent_guide_advertises_code_route(tmp_path: Path) -> None: assert exit_code == 0 assert ( - "asp python query --term --workspace --code" - in rendered - ) - assert ( - "asp python search owner items --query --workspace --code" - in rendered + "asp python query --selector " + "--projection source --workspace " in rendered ) @@ -82,7 +74,7 @@ def test_cli_json_flag_renders_structured_report(tmp_path: Path) -> None: assert exit_code == 0 assert payload["is_clean"] is True assert payload["file_count"] == 1 - assert payload["project_scope"]["project_root"] == str(tmp_path) + assert payload["project_resolution"]["project_root"] == str(tmp_path) def test_cli_agent_snapshot_renders_parser_backed_project_shape( @@ -222,8 +214,8 @@ def test_cli_uses_pyproject_declared_package_source_scope(tmp_path: Path) -> Non assert exit_code == 0 assert payload["is_clean"] is True assert [finding["rule_id"] for finding in payload["findings"]] == [] - assert payload["project_scope"]["source_paths"] == [str(package_source)] - assert payload["project_scope"]["project_paths"] == [ + assert payload["project_resolution"]["source_paths"] == [str(package_source)] + assert payload["project_resolution"]["project_paths"] == [ str(package_source), str(tmp_path / "tests"), ] @@ -253,9 +245,9 @@ def test_cli_help_and_argument_errors_are_stable(tmp_path: Path) -> None: error_stderr = io.StringIO() assert run_cli(["--help"], stdout=help_stdout) == 0 - assert "py-harness search " in help_stdout.getvalue() + assert "asp-python search " in help_stdout.getvalue() assert ( - "py-harness [--json | --agent-snapshot] [--no-tests]" in help_stdout.getvalue() + "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() diff --git a/tests/unit/harness/test_cli_query_direct_source_read.py b/tests/unit/harness/test_cli_query_direct_source_read.py deleted file mode 100644 index f24d463..0000000 --- a/tests/unit/harness/test_cli_query_direct_source_read.py +++ /dev/null @@ -1,116 +0,0 @@ -import io -from pathlib import Path - -from python_lang_project_harness._cli import run_cli - - -def test_cli_query_direct_source_read_code_rejects_source_locator_hint( - tmp_path: Path, -) -> None: - source = tmp_path / "tests" / "unit" / "example.py" - source.parent.mkdir(parents=True) - source.write_text( - "def target(value: int) -> int:\n return value + 1\n", encoding="utf-8" - ) - stderr = io.StringIO() - - exit_code = run_cli( - [ - "query", - "--from-hook", - "owner-local-projection", - "--selector", - "tests/unit/example.py:1-2", - "--workspace", - str(tmp_path), - "--code", - ], - stdout=io.StringIO(), - stderr=stderr, - cwd=tmp_path, - ) - - assert exit_code == 3 - assert "status=selector-not-materialized" in stderr.getvalue() - assert "nextAction=refresh-parser-projection" in stderr.getvalue() - - -def test_cli_query_direct_source_read_code_rejects_missing_structural_selector( - tmp_path: Path, -) -> None: - source = tmp_path / "tests" / "unit" / "example.py" - source.parent.mkdir(parents=True) - source.write_text("def target() -> None:\n pass\n", encoding="utf-8") - stderr = io.StringIO() - - exit_code = run_cli( - [ - "query", - "--from-hook", - "owner-local-projection", - "--selector", - "tests/unit/example.py", - "--workspace", - str(tmp_path), - "--code", - ], - stdout=io.StringIO(), - stderr=stderr, - cwd=tmp_path, - ) - - assert exit_code == 3 - assert "status=selector-not-materialized" in stderr.getvalue() - - -def test_cli_query_code_rejects_trailing_project_root(tmp_path: Path) -> None: - source = tmp_path / "tests" / "unit" / "example.py" - source.parent.mkdir(parents=True) - source.write_text("def target() -> None:\n pass\n", encoding="utf-8") - - cases = ( - [ - "query", - "--from-hook", - "owner-local-projection", - "--selector", - "tests/unit/example.py:1-2", - "--code", - str(tmp_path), - ], - [ - "query", - "tests/unit/example.py", - "--term", - "target", - "--code", - str(tmp_path), - ], - [ - "query", - "--treesitter-query", - "(function_definition name: (identifier) @function.name)", - "--selector", - "tests/unit/example.py:1-2", - "--code", - str(tmp_path), - ], - [ - "search", - "owner", - "tests/unit/example.py", - "items", - "--query", - "target", - "--code", - str(tmp_path), - ], - ) - - for args in cases: - stderr = io.StringIO() - - exit_code = run_cli(args, stdout=io.StringIO(), stderr=stderr, cwd=tmp_path) - - assert exit_code == 2 - assert "does not accept positional WORKSPACE" in stderr.getvalue() diff --git a/tests/unit/harness/test_cli_query_names_only_route.py b/tests/unit/harness/test_cli_query_names_only_route.py deleted file mode 100644 index 9eb74b2..0000000 --- a/tests/unit/harness/test_cli_query_names_only_route.py +++ /dev/null @@ -1,31 +0,0 @@ -import io -from pathlib import Path - -from python_lang_project_harness import run_cli - - -def test_cli_query_names_only_without_owner_reports_lexical_route( - tmp_path: Path, -) -> None: - stdout = io.StringIO() - stderr = io.StringIO() - - exit_code = run_cli( - [ - "query", - "--term", - "run_install", - "--names-only", - "--workspace", - str(tmp_path), - ], - stdout=stdout, - stderr=stderr, - ) - - assert exit_code == 2 - assert stdout.getvalue() == "" - assert stderr.getvalue() == ( - "query --names-only requires an owner selector; workspace term discovery is " - "`search lexical '' owner --workspace --view seeds`\n" - ) diff --git a/tests/unit/harness/test_dependency_topology.py b/tests/unit/harness/test_dependency_topology.py new file mode 100644 index 0000000..2535bc8 --- /dev/null +++ b/tests/unit/harness/test_dependency_topology.py @@ -0,0 +1,85 @@ +from __future__ import annotations + +import re +from pathlib import Path + +from python_lang_project_harness._dependency_topology import ( + build_dependency_topology_packet, +) + + +def test_dependency_topology_packet_projects_pyproject_dependencies( + tmp_path: Path, +) -> None: + (tmp_path / "pyproject.toml").write_text( + """ +[project] +name = "fixture" +version = "0.1.0" +dependencies = ["requests>=2.31", "typing-extensions==4.12.2"] +""".strip() + + "\n", + encoding="utf-8", + ) + + packet = build_dependency_topology_packet(tmp_path) + + assert packet["packetKind"] == "dependency-topology" + assert re.fullmatch(r"sha256:[0-9a-f]{64}", packet["fingerprint"]) + graph = packet["graph"] + nodes = {node["id"]: node for node in graph["nodes"]} + assert nodes["dependency:requests"] == { + "id": "dependency:requests", + "kind": "dependency", + "value": "requests", + "path": "pyproject.toml", + "fields": { + "dependencyName": "requests", + "manifestPath": "pyproject.toml", + }, + } + assert nodes["dependency-version:requests"]["fields"]["version"] == ">=2.31" + assert { + "source": "dependency:requests", + "target": "dependency-version:requests", + "relation": "version_locked", + } in graph["edges"] + + +def test_dependency_topology_packet_projects_requirements_files( + tmp_path: Path, +) -> None: + requirements_dir = tmp_path / "requirements" + requirements_dir.mkdir() + (requirements_dir / "runtime.txt").write_text( + """ +# Runtime dependencies +Requests[security]~=2.32 ; python_version >= "3.11" +urllib3==2.2.2 # pinned by deployment image +-r generated.txt +git+https://example.invalid/acme.git +""".strip() + + "\n", + encoding="utf-8", + ) + + packet = build_dependency_topology_packet(tmp_path) + + graph = packet["graph"] + nodes = {node["id"]: node for node in graph["nodes"]} + assert nodes["dependency:requests"]["path"] == "requirements/runtime.txt" + assert nodes["dependency-version:requests"]["value"] == "~=2.32" + assert nodes["dependency-version:urllib3"]["value"] == "==2.2.2" + assert all("generated" not in node["id"] for node in graph["nodes"]) + + +def test_dependency_topology_fingerprint_is_stable(tmp_path: Path) -> None: + (tmp_path / "requirements.txt").write_text( + "requests>=2.31\nurllib3==2.2.2\n", + encoding="utf-8", + ) + + first = build_dependency_topology_packet(tmp_path) + second = build_dependency_topology_packet(tmp_path) + + assert first == second diff --git a/tests/unit/harness/test_dependency_topology_cli.py b/tests/unit/harness/test_dependency_topology_cli.py new file mode 100644 index 0000000..3b35946 --- /dev/null +++ b/tests/unit/harness/test_dependency_topology_cli.py @@ -0,0 +1,67 @@ +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_dev_command_log.py b/tests/unit/harness/test_dev_command_log.py index da88870..a019840 100644 --- a/tests/unit/harness/test_dev_command_log.py +++ b/tests/unit/harness/test_dev_command_log.py @@ -39,13 +39,18 @@ def test_dev_command_log_records_ordered_active_context_events( monkeypatch.delenv("SEMANTIC_PROTOCOL_PARENT_EVENT_ID", raising=False) monkeypatch.delenv("SEMANTIC_PROTOCOL_SESSION_ID", raising=False) monkeypatch.delenv("SEMANTIC_PROTOCOL_HOOK_RUN_ID", raising=False) + monkeypatch.delenv("CODEX_SESSION_ID", raising=False) + monkeypatch.delenv("CLAUDE_SESSION_ID", raising=False) + monkeypatch.delenv("TERM_SESSION_ID", raising=False) + monkeypatch.delenv("CODEX_HOOK_RUN_ID", raising=False) + monkeypatch.delenv("AGENT_HOOK_RUN_ID", raising=False) log = start_dev_command_log( ["search", "lexical", "metadata", str(project)], project ) log.finish(0) - command_dir = trace / "python" / "py-harness" / "commands" + command_dir = trace / "python" / "asp-python" / "commands" entries = list(command_dir.glob("*.jsonl")) assert len(entries) == 1 assert entries[0].name.startswith("20") @@ -55,7 +60,7 @@ def test_dev_command_log_records_ordered_active_context_events( event = json.loads(entries[0].read_text(encoding="utf-8")) assert event["schemaId"] == "agent.semantic-protocols.dev-command-log" assert event["languageId"] == "python" - assert event["providerId"] == "py-harness" + assert event["providerId"] == "asp-python" assert event["sessionId"] == "session-py" assert event["sessionOrdinal"] == 1 assert event["parentEventId"] == "hook-parent-py" diff --git a/tests/unit/harness/test_evidence_graph.py b/tests/unit/harness/test_evidence_graph.py index 11262da..ac9a8f2 100644 --- a/tests/unit/harness/test_evidence_graph.py +++ b/tests/unit/harness/test_evidence_graph.py @@ -27,7 +27,7 @@ def test_cli_evidence_graph_renders_json_contract(tmp_path: Path) -> None: 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"] == "py-harness" + assert payload["producer"]["providerId"] == "asp-python" assert payload["project"]["package"] == "evidence-fixture" assert payload["summary"] == { "nodes": 4, @@ -39,7 +39,7 @@ def test_cli_evidence_graph_renders_json_contract(tmp_path: Path) -> None: } 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"] == "py-harness check --full ." + assert payload["gaps"][0]["fields"]["nextCommand"] == "asp-python check --full ." def test_cli_evidence_analyze_renders_graph_turbo_request(tmp_path: Path) -> None: @@ -86,7 +86,7 @@ def test_agent_registry_advertises_evidence_methods(tmp_path: Path) -> None: assert exit_code == 0 registry = json.loads(stdout.getvalue()) - language = registry["languages"][0] + language = registry["registry"]["languages"][0] assert "evidence/graph" in language["methods"] assert "evidence/analyze" in language["methods"] analyze = next( diff --git a/tests/unit/harness/test_exact_source_projection.py b/tests/unit/harness/test_exact_source_projection.py new file mode 100644 index 0000000..576fd9c --- /dev/null +++ b/tests/unit/harness/test_exact_source_projection.py @@ -0,0 +1,82 @@ +from __future__ import annotations + +import base64 + +from python_lang_project_harness._exact_source_projection import ( + project_provider_native_exact_request, +) + + +def test_callable_skeleton_child_selector_round_trips_to_source(tmp_path) -> None: + source = ( + b"def selected(value: int) -> int:\n" + b" if value > 1:\n" + b" return value\n" + b" return 0\n" + ) + root_selector = "python://src/example.py#item/function/selected" + parser_digest = "b" * 64 + query_pack_digest = "c" * 64 + + def invoke(selector: str, projection_kind: str) -> dict[str, object]: + request = { + "schemaId": "agent.semantic-protocols.provider-native-exact-request", + "schemaVersion": "1", + "languageId": "python", + "providerId": "asp-python", + "structuralSelector": selector, + "ownerPath": "src/example.py", + "projectionKind": projection_kind, + "generationIdentityDigest": "a" * 64, + "parserIdentityDigest": parser_digest, + "queryPackDigest": query_pack_digest, + "sourceDigest": "d" * 64, + "sourceByteLength": len(source), + "sourceEncoding": "base64", + "sourceBytesBase64": base64.b64encode(source).decode(), + "transport": "stdin-json", + } + return project_provider_native_exact_request(request, cwd=tmp_path) + + skeleton = invoke(root_selector, "callable-skeleton") + payload = skeleton["projectionPayload"] + assert isinstance(payload, dict) + assert "schemaId" not in payload + assert "schemaVersion" not in payload + assert "projectionKind" not in payload + assert "providerId" not in payload + nodes = payload["nodes"] + assert isinstance(nodes, list) + branch_selector = next( + node["selector"] for node in nodes if node["kind"] == "branch" + ) + + branch = invoke(branch_selector, "source") + assert branch["schemaVersion"] == "1" + assert branch["requestedStructuralSelector"] == branch_selector + assert branch["structuralSelector"] == branch_selector + assert branch["projectionText"] == "if value > 1:\n return value" + + +def test_provider_does_not_recompute_asp_source_digest(tmp_path) -> None: + source = b"def selected() -> int:\n return 1\n" + selector = "python://src/example.py#item/function/selected" + 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": "asp-owned-content-identity", + "sourceByteLength": len(source), + "sourceEncoding": "base64", + "sourceBytesBase64": base64.b64encode(source).decode(), + "transport": "stdin-json", + } + packet = project_provider_native_exact_request(request, cwd=tmp_path) + assert packet["sourceContentDigest"] == "asp-owned-content-identity" diff --git a/tests/unit/harness/test_parser_boundary_contract.py b/tests/unit/harness/test_parser_boundary_contract.py index 3f2ae88..099370c 100644 --- a/tests/unit/harness/test_parser_boundary_contract.py +++ b/tests/unit/harness/test_parser_boundary_contract.py @@ -118,7 +118,11 @@ def test_harness_pyproject_metadata_comes_from_parser_boundary() -> None: ) for path in harness_sources: - if path.name in {"_project_config.py", "_test_layout_config.py"}: + if path.name in { + "_project_config.py", + "_project_resolution_candidates.py", + "_test_layout_config.py", + }: continue source = path.read_text(encoding="utf-8") assert "import tomllib" not in source, path diff --git a/tests/unit/harness/test_policy_contract.py b/tests/unit/harness/test_policy_contract.py index b7713ef..37ac32c 100644 --- a/tests/unit/harness/test_policy_contract.py +++ b/tests/unit/harness/test_policy_contract.py @@ -246,8 +246,8 @@ def test_project_is_clean_under_its_own_harness() -> None: rendered = render_python_lang_harness(report) assert report.is_clean, rendered - assert rendered.startswith("[ok]") - assert "Files:" in rendered + assert "[fail]" not in rendered + assert "[advice]" in rendered def _all_default_rule_ids() -> tuple[str, ...]: diff --git a/tests/unit/harness/test_project_api.py b/tests/unit/harness/test_project_api.py index 10a99d6..3aea6d8 100644 --- a/tests/unit/harness/test_project_api.py +++ b/tests/unit/harness/test_project_api.py @@ -70,8 +70,8 @@ def test_run_python_project_harness_uses_project_paths(tmp_path: Path) -> None: assert report.is_clean assert report.file_count == 2 assert report.root_paths == (str(tmp_path),) - assert report.project_scope is not None - assert report.to_dict()["project_scope"] == { + assert report.project_resolution is not None + assert report.to_dict()["project_resolution"] == { "project_root": str(tmp_path), "project_metadata": None, "project_paths": [str(tmp_path)], @@ -125,9 +125,9 @@ def test_run_python_project_harness_can_exclude_tests_from_scope( assert report.is_clean assert [module.path for module in report.modules] == [str(src / "library.py")] - assert report.project_scope is not None - assert report.project_scope.test_paths == (tests.parent,) - assert report.project_scope.monitored_paths == (src,) + assert report.project_resolution is not None + assert report.project_resolution.test_paths == (tests.parent,) + assert report.project_resolution.monitored_paths == (src,) def test_run_python_project_harness_does_not_fallback_into_excluded_tests( @@ -144,8 +144,8 @@ def test_run_python_project_harness_does_not_fallback_into_excluded_tests( assert report.is_clean assert [module.path for module in report.modules] == [str(package / "__init__.py")] - assert report.project_scope is not None - assert report.project_scope.project_paths == (package,) + assert report.project_resolution is not None + assert report.project_resolution.project_paths == (package,) def test_include_tests_false_skips_test_parsing_not_layout_policy( @@ -165,8 +165,8 @@ def test_include_tests_false_skips_test_parsing_not_layout_policy( assert report.file_count == 1 assert [finding.rule_id for finding in report.findings] == ["PY-TEST-R001"] - assert report.project_scope is not None - assert report.project_scope.monitored_paths == (src,) + assert report.project_resolution is not None + assert report.project_resolution.monitored_paths == (src,) def test_assert_python_project_harness_clean_blocks_for_pytest(tmp_path: Path) -> None: diff --git a/tests/unit/harness/test_project_resolution.py b/tests/unit/harness/test_project_resolution.py new file mode 100644 index 0000000..983ce82 --- /dev/null +++ b/tests/unit/harness/test_project_resolution.py @@ -0,0 +1,258 @@ +"""Exercise candidate-bounded Python ProjectResolution parsing.""" + +from __future__ import annotations + +import json +from pathlib import Path + +from python_lang_project_harness._runtime import _response_frame + + +def request(candidate_paths: list[str]) -> dict[str, object]: + return { + "schemaId": "agent.semantic-protocols.provider-project-resolution-request", + "schemaVersion": "1", + "languageId": "python", + "providerId": "asp-python", + "candidateBase": ".", + "candidateGeneration": { + "algorithm": "blake3-path-set-v1", + "digest": "blake3:" + ("0" * 64), + "authorities": ["asp-workspace-generation"], + }, + "collectionScope": {"kind": "complete-generation"}, + "candidatePaths": candidate_paths, + "policyExclusions": [], + } + + +def project_resolution_frame(root: Path, payload: object) -> dict[str, object]: + return _response_frame( + { + "schemaId": "agent.semantic-protocols.provider-runtime-request-frame", + "schemaVersion": "1", + "requestId": "project-resolution-test", + "operation": "project-resolution", + "payload": payload, + }, + root, + ) + + +def run_project_resolution(root: Path, payload: object) -> dict[str, object]: + frame = project_resolution_frame(root, payload) + assert frame["outcome"] == "ready", frame + result = frame["payload"] + assert isinstance(result, dict) + return result + + +def test_project_resolution_uses_only_candidates_and_uv_package_graph( + tmp_path: Path, +) -> None: + (tmp_path / "src/root_pkg").mkdir(parents=True) + (tmp_path / "src/root_pkg/__init__.py").write_text("") + (tmp_path / "src/root_pkg/untracked.py").write_text("") + (tmp_path / "packages/member/src/member_pkg").mkdir(parents=True) + (tmp_path / "packages/member/src/member_pkg/__init__.py").write_text("") + (tmp_path / "examples/not-a-member/src/not_member").mkdir(parents=True) + (tmp_path / "examples/not-a-member/src/not_member/__init__.py").write_text("") + (tmp_path / "pyproject.toml").write_text( + """ +[project] +name = "root-package" +dependencies = ["requests>=2"] + +[tool.uv.workspace] +members = ["packages/*"] + +[tool.setuptools.package-dir] +"" = "src" +""" + ) + (tmp_path / "packages/member/pyproject.toml").write_text( + """ +[project] +name = "member-package" + +[tool.setuptools.package-dir] +"" = "src" +""" + ) + (tmp_path / "examples/not-a-member/pyproject.toml").write_text( + """ +[project] +name = "not-a-member" + +[tool.setuptools.package-dir] +"" = "src" +""" + ) + + response = run_project_resolution( + tmp_path, + request( + [ + "pyproject.toml", + "src/root_pkg/__init__.py", + "packages/member/pyproject.toml", + "packages/member/src/member_pkg/__init__.py", + "examples/not-a-member/pyproject.toml", + "examples/not-a-member/src/not_member/__init__.py", + ] + ), + ) + + assert response["state"] == "resolved" + scope = response["scope"] + assert scope["completeness"] == "exact" + assert scope["candidateGenerationDigest"] == ("blake3:" + ("0" * 64)) + assert scope["metrics"] == { + "parsedManifestCount": 2, + "parsedLockfileCount": 0, + "affectedPackageCount": 2, + "fullWorkspaceReads": 0, + "fullManifestReparses": 0, + "dbOpens": 0, + "elapsedMicros": 0, + } + scopes = scope["sourceScopes"] + assert sorted(path for scope in scopes for path in scope["roots"]) == [ + "packages/member/src/member_pkg", + "src/root_pkg", + ] + assert "src/root_pkg/untracked.py" not in json.dumps(response) + assert sorted(path for scope in scopes for path in scope["explicitPaths"]) == [ + "packages/member/src/member_pkg", + "src/root_pkg", + ] + assert scope["packageGraph"]["externalDependencies"][0]["name"] == "requests" + assert all( + package["name"] != "not-a-member" + for package in scope["packageGraph"]["packages"] + ) + + +def test_setuptools_src_layout_is_package_manager_scope_without_provider_defaults( + tmp_path: Path, +) -> None: + (tmp_path / "src/example_pkg").mkdir(parents=True) + (tmp_path / "src/example_pkg/__init__.py").write_text("") + (tmp_path / "examples").mkdir() + (tmp_path / "examples/not_scope.py").write_text("") + (tmp_path / "pyproject.toml").write_text( + """ +[build-system] +requires = ["setuptools>=77"] +build-backend = "setuptools.build_meta" + +[project] +name = "example-pkg" +""" + ) + + response = run_project_resolution( + tmp_path, + request( + [ + "pyproject.toml", + "src/example_pkg/__init__.py", + "examples/not_scope.py", + ] + ), + ) + + scope = response["scope"]["sourceScopes"][0] + assert scope["includeAuthority"] == "package-manager" + assert scope["roots"] == ["src/example_pkg"] + assert scope["explicitPaths"] == [] + assert all( + not root.startswith("examples") + for item in response["scope"]["sourceScopes"] + for root in item["roots"] + ) + + +def test_project_resolution_rejects_non_object_request(tmp_path: Path) -> None: + response = project_resolution_frame(tmp_path, []) + assert response["outcome"] == "error" + assert response["error"] == "provider runtime payload is not an object" + + +def test_project_resolution_requires_candidate_project_entry(tmp_path: Path) -> None: + response = run_project_resolution(tmp_path, request(["src/pkg/__init__.py"])) + assert response == { + "schemaId": "agent.semantic-protocols.provider-project-resolution-response", + "schemaVersion": "1", + "languageId": "python", + "providerId": "asp-python", + "state": "not-applicable", + } + + +def test_empty_uv_workspace_aggregator_is_not_a_provider_failure( + tmp_path: Path, +) -> None: + (tmp_path / "pyproject.toml").write_text( + """ +[tool.uv] +package = false + +[tool.uv.workspace] +members = ["packages/python"] +exclude = ["packages/python"] +""" + ) + + response = run_project_resolution(tmp_path, request(["pyproject.toml"])) + + assert response["state"] == "resolved" + scope = response["scope"] + assert scope["state"] == "resolved" + assert scope["completeness"] == "exact" + assert scope["projectEntry"] == "pyproject.toml" + assert scope["packageGraph"]["packages"] == [] + assert scope["sourceScopes"] == [] + assert scope["metrics"]["fullWorkspaceReads"] == 0 + assert scope["metrics"]["dbOpens"] == 0 + + +def test_provider_registration_advertises_project_resolution() -> None: + project_root = Path(__file__).parents[3] + manifest = json.loads( + (project_root / "provider/asp-provider-registration.json").read_text() + ) + descriptor = manifest["sourceInventory"]["projectResolution"] + assert descriptor == {"entryMarkers": ["pyproject.toml"]} + + operations = { + operation["operation"]: operation + 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["responseSchemaId"].endswith( + "/provider-project-resolution-response.schema.json" + ) + + +def test_explicit_owner_collection_scope_is_required_and_normalized( + tmp_path: Path, +) -> None: + (tmp_path / "pyproject.toml").write_text('[project]\nname = "fixture"\n') + payload = request(["pyproject.toml"]) + payload["collectionScope"] = { + "kind": "explicit-owners", + "ownerPaths": ["src/changed.py"], + } + assert run_project_resolution(tmp_path, payload)["state"] == "resolved" + + payload["collectionScope"] = { + "kind": "explicit-owners", + "ownerPaths": ["src/../changed.py"], + } + failure = project_resolution_frame(tmp_path, payload) + assert failure["outcome"] == "error" + assert "normalized workspace-relative" in failure["error"] diff --git a/tests/unit/harness/test_project_scope_extra_paths.py b/tests/unit/harness/test_project_resolution_extra_paths.py similarity index 92% rename from tests/unit/harness/test_project_scope_extra_paths.py rename to tests/unit/harness/test_project_resolution_extra_paths.py index c2c30ae..9f6e1a7 100644 --- a/tests/unit/harness/test_project_scope_extra_paths.py +++ b/tests/unit/harness/test_project_resolution_extra_paths.py @@ -43,6 +43,6 @@ def test_run_python_project_harness_can_include_extra_project_paths( str(tool), ] ) - assert report.project_scope is not None - assert report.project_scope.extra_paths == (shared,) + assert report.project_resolution is not None + assert report.project_resolution.extra_paths == (shared,) assert report.root_paths == (str(tmp_path), str(shared)) diff --git a/tests/unit/harness/test_projection_batch.py b/tests/unit/harness/test_projection_batch.py new file mode 100644 index 0000000..01bb070 --- /dev/null +++ b/tests/unit/harness/test_projection_batch.py @@ -0,0 +1,61 @@ +"""Focused contract tests for the ASP projection-batch provider adapter.""" + +from __future__ import annotations + +from python_lang_project_harness._projection_batch import project_projection_batch + + +def test_projection_batch_projects_canonical_python_items() -> None: + source = "class Agent:\n def run(self):\n return 1\n\ndef top():\n return 2\n" + 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/example.py", + "sourceLeafDigest": "source-test", + "sourceEncoding": "utf8", + "sourceText": source, + } + ], + "auxiliaryOwners": [ + { + "ownerPath": "pyproject.toml", + "sourceLeafDigest": "config-test", + "sourceEncoding": "utf8", + "sourceText": "[project]\nname = 'fixture'\n", + } + ], + } + response = project_projection_batch(request) + + assert ( + response["schemaId"] + == "agent.semantic-protocols.provider-language-projection-batch-response" + ) + assert response["generationRootDigest"] == "generation-test" + owner = response["owners"][0] + assert len(response["owners"]) == 1 + assert owner["sourceLeafDigest"] == "source-test" + 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", + "python://src/example.py#item/function/top", + ] + assert all( + item["sourceByteStart"] < item["sourceByteEnd"] for item in owner["items"] + ) + assert owner["items"][0]["projections"] == [] + for item in owner["items"][1:]: + assert item["projections"][0]["projectionKind"] == "callable-skeleton" + payload = item["projections"][0]["payload"] + assert "schemaId" not in payload + assert "schemaVersion" not in payload + assert "projectionKind" not in payload diff --git a/tests/unit/harness/test_provider_runtime.py b/tests/unit/harness/test_provider_runtime.py new file mode 100644 index 0000000..bb2cb58 --- /dev/null +++ b/tests/unit/harness/test_provider_runtime.py @@ -0,0 +1,86 @@ +"""Black-box acceptance for the resident Python HTTP provider.""" + +from __future__ import annotations + +import http.client +import json +import subprocess +import sys +from pathlib import Path +from urllib.parse import urlsplit + +from provider_runtime_live_support import ( + assert_concurrent_corpus, + assert_projection_and_query, + assert_streamed_corpus, + environment, + frame, + latency_receipt, + post, +) + +from python_lang_project_harness._runtime import _health, _response_frame + + +def test_resident_runtime_publishes_manifest_operations_and_structured_frames( + monkeypatch: object, +) -> None: + for name, value in environment().items(): + monkeypatch.setenv(name, value) # type: ignore[attr-defined] + health = _health() + assert ( + health["schemaId"] + == "agent.semantic-protocols.provider-runtime-contract-receipt" + ) + assert health["transport"] == "http-json" + assert [operation["operation"] for operation in health["operations"]] == [ + "projection-batch", + "project-resolution", + "query", + ] + response = _response_frame(frame("request-1", "not-admitted", {}), Path(".")) + assert response["outcome"] == "error" + assert ( + response["error"] + == "resident Python provider operation is not admitted: not-admitted" + ) + + +def test_http_json_live_corpus_stream_query_concurrency_and_latency() -> None: + process = subprocess.Popen( + [sys.executable, "-m", "python_lang_project_harness", "serve"], + cwd=Path(__file__).parents[3], + env=environment(), + stdin=subprocess.DEVNULL, + stdout=subprocess.PIPE, + stderr=subprocess.PIPE, + text=True, + ) + assert process.stdout is not None + bootstrap = json.loads(process.stdout.readline()) + assert ( + bootstrap["schemaId"] == "agent.semantic-protocols.asp-client-server-bootstrap" + ) + assert (bootstrap["providerId"], bootstrap["languageId"], bootstrap["state"]) == ( + "asp-python", + "python", + "ready", + ) + endpoint = urlsplit(bootstrap["endpoint"]) + connection = http.client.HTTPConnection(endpoint.hostname, endpoint.port, timeout=2) + try: + connection.request("GET", "/health") + health_response = connection.getresponse() + assert health_response.status == 200 + assert json.loads(health_response.read())["providerId"] == "asp-python" + assert_projection_and_query(connection) + assert_streamed_corpus(connection) + assert_concurrent_corpus(endpoint) + print(latency_receipt(connection)) + assert post(connection, "/shutdown", {}) == {"state": "draining"} + assert process.wait(timeout=2) == 0 + finally: + connection.close() + if process.poll() is None: + process.kill() + process.wait(timeout=2) diff --git a/tests/unit/harness/test_public_cli_identity.py b/tests/unit/harness/test_public_cli_identity.py new file mode 100644 index 0000000..66cad63 --- /dev/null +++ b/tests/unit/harness/test_public_cli_identity.py @@ -0,0 +1,12 @@ +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_pyproject_package_scope.py b/tests/unit/harness/test_pyproject_package_scope.py index fe95768..88ac56a 100644 --- a/tests/unit/harness/test_pyproject_package_scope.py +++ b/tests/unit/harness/test_pyproject_package_scope.py @@ -54,6 +54,9 @@ def test_run_python_project_harness_uses_pyproject_package_scope( str(source_file), str(test_file), ] - assert report.project_scope is not None - assert report.project_scope.source_paths == (package_source,) - assert report.project_scope.project_paths == (package_source, tmp_path / "tests") + assert report.project_resolution is not None + assert report.project_resolution.source_paths == (package_source,) + assert report.project_resolution.project_paths == ( + package_source, + tmp_path / "tests", + ) diff --git a/tests/unit/harness/test_pytest.py b/tests/unit/harness/test_pytest.py index dafebd8..ef4ca83 100644 --- a/tests/unit/harness/test_pytest.py +++ b/tests/unit/harness/test_pytest.py @@ -129,7 +129,7 @@ def test_python_project_harness_test_honors_embedded_options( harness_test() -def test_python_project_harness_test_honors_configured_project_scope( +def test_python_project_harness_test_honors_configured_project_resolution( tmp_path: Path, ) -> None: lib = tmp_path / "lib" diff --git a/tests/unit/harness/test_render_snapshots.py b/tests/unit/harness/test_render_snapshots.py index daad62b..50489f8 100644 --- a/tests/unit/harness/test_render_snapshots.py +++ b/tests/unit/harness/test_render_snapshots.py @@ -55,7 +55,7 @@ def test_reasoning_tree_render_uses_project_relative_paths(tmp_path: Path) -> No ), findings=(), root_paths=(str(tmp_path),), - project_scope=PythonProjectHarnessScope( + project_resolution=PythonProjectHarnessScope( project_root=tmp_path, project_metadata=PythonProjectMetadata( project_root=tmp_path, @@ -111,7 +111,7 @@ def test_compact_text_render_uses_project_relative_finding_paths( ), ), root_paths=(str(tmp_path),), - project_scope=PythonProjectHarnessScope( + project_resolution=PythonProjectHarnessScope( project_root=tmp_path, project_paths=(tmp_path,), source_paths=(src,), @@ -175,7 +175,7 @@ def _reasoning_tree_snapshot_report() -> PythonHarnessReport: ), findings=(), root_paths=(str(root),), - project_scope=PythonProjectHarnessScope( + project_resolution=PythonProjectHarnessScope( project_root=root, project_metadata=PythonProjectMetadata( project_root=root, diff --git a/tests/unit/harness/test_runner_config.py b/tests/unit/harness/test_runner_config.py index 3d68f79..4ba77bc 100644 --- a/tests/unit/harness/test_runner_config.py +++ b/tests/unit/harness/test_runner_config.py @@ -45,9 +45,9 @@ def test_project_runner_uses_configured_source_and_test_roots( str(lib / "service.py"), str(tests / "test_service.py"), ] - assert report.project_scope is not None - assert report.project_scope.source_paths == (lib,) - assert report.project_scope.test_paths == (checks.parent,) + assert report.project_resolution is not None + assert report.project_resolution.source_paths == (lib,) + assert report.project_resolution.test_paths == (checks.parent,) def test_project_runner_parameters_override_configured_roots( @@ -72,8 +72,8 @@ def test_project_runner_parameters_override_configured_roots( str(lib / "included.py"), str(src / "service.py"), ] - assert report.project_scope is not None - assert report.project_scope.source_paths == (src,) + assert report.project_resolution is not None + assert report.project_resolution.source_paths == (src,) def test_project_runner_parameters_override_configured_extra_paths( @@ -101,8 +101,8 @@ def test_project_runner_parameters_override_configured_extra_paths( str(src / "service.py"), str(tools / "check.py"), ] - assert report.project_scope is not None - assert report.project_scope.extra_paths == (tools,) + assert report.project_resolution is not None + assert report.project_resolution.extra_paths == (tools,) def test_project_runner_can_exclude_tests_from_config(tmp_path: Path) -> None: @@ -120,9 +120,9 @@ def test_project_runner_can_exclude_tests_from_config(tmp_path: Path) -> None: assert report.is_clean assert [module.path for module in report.modules] == [str(src / "service.py")] - assert report.project_scope is not None - assert report.project_scope.test_paths == (tests.parent,) - assert report.project_scope.monitored_paths == (src,) + assert report.project_resolution is not None + assert report.project_resolution.test_paths == (tests.parent,) + assert report.project_resolution.monitored_paths == (src,) def test_project_runner_can_disable_policy_rules_from_config(tmp_path: Path) -> None: diff --git a/tests/unit/harness/test_semantic_agent_cli.py b/tests/unit/harness/test_semantic_agent_cli.py index 978f363..a5bd882 100644 --- a/tests/unit/harness/test_semantic_agent_cli.py +++ b/tests/unit/harness/test_semantic_agent_cli.py @@ -22,7 +22,7 @@ def test_cli_agent_install_reports_root_asp_owner( assert exit_code == 2 assert stdout.getvalue() == "" - assert "py-harness agent install moved to asp" in stderr.getvalue() + assert "asp-python agent install moved to asp" in stderr.getvalue() assert "asp hook install --client codex" in stderr.getvalue() @@ -41,5 +41,5 @@ def test_cli_agent_hook_reports_root_asp_owner( assert exit_code == 2 assert stdout.getvalue() == "" - assert "py-harness agent hook moved to asp" in stderr.getvalue() + assert "asp-python agent hook moved to asp" in stderr.getvalue() assert "asp hook --client codex" in stderr.getvalue() diff --git a/tests/unit/harness/test_semantic_cli.py b/tests/unit/harness/test_semantic_cli.py index 7c1d811..6bf1c56 100644 --- a/tests/unit/harness/test_semantic_cli.py +++ b/tests/unit/harness/test_semantic_cli.py @@ -6,288 +6,36 @@ import json from pathlib import Path -from semantic_search_fixture import write_search_fixture - from python_lang_project_harness import python_semantic_language_registration, run_cli -def test_cli_agent_doctor_json_advertises_semantic_language_provider( - tmp_path: Path, -) -> None: +def test_cli_agent_doctor_advertises_provider(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["languages"][0] + assert run_cli(["agent", "doctor", "--json", str(tmp_path)], stdout=stdout) == 0 + registration = json.loads(stdout.getvalue())["registry"]["languages"][0] assert registration["languageId"] == "python" - assert registration["providerId"] == "py-harness" - assert registration["binary"] == "py-harness" - assert registration["namespace"] == ( - "agent.semantic-protocols.languages.python.py-harness" - ) - assert any( - schema["schemaId"] == "agent.semantic-protocols.semantic-graph" - and schema["path"] == "schemas/semantic-graph.v1.schema.json" - for schema in registration["schemas"] - ) - assert any( - schema["schemaId"] == "agent.semantic-protocols.semantic-type-surface" - and schema["path"] == "schemas/semantic-type-surface.v1.schema.json" - for schema in registration["schemas"] - ) - assert any( - schema["schemaId"] == "agent.semantic-protocols.dev-command-log" - and schema["path"] == "schemas/semantic-dev-command-log.v1.schema.json" - for schema in registration["schemas"] - ) - assert "search/workspace" in registration["methods"] - assert "search/callsite" in registration["methods"] - assert "search/public-external-types" in registration["methods"] - assert "search/lexical" in registration["methods"] - assert "agent/doctor" in registration["methods"] - assert "agent/guide" in registration["methods"] - assert "agent/install" not in registration["methods"] - assert "agent/hook" not in registration["methods"] - assert any( - descriptor["method"] == "search/public-external-types" - and descriptor["outputSchemaIds"] - == [ - "agent.semantic-protocols.semantic-search-packet", - "agent.semantic-protocols.semantic-type-surface", - ] - for descriptor in registration["methodDescriptors"] - ) - assert any( - descriptor["method"] == "search/lexical" - and descriptor["acceptedPipes"] == ["owner", "tests"] - and descriptor["supportsQuerySet"] is True - and descriptor["acceptedQuerySetSelectors"] == ["lexical-set"] - and descriptor["querySetScopes"] == ["project", "owner"] - for descriptor in registration["methodDescriptors"] - ) - assert any( - descriptor["method"] == "search/owner" - and descriptor["acceptedPipes"] == ["items"] - and any( - capability["name"] == "python-owner-item-query" - for capability in descriptor["capabilities"] - ) - and descriptor["fallbacks"][0]["name"] == "owner-top-items" - for descriptor in registration["methodDescriptors"] - ) - assert not any( - descriptor["method"] in {"agent/install", "agent/hook"} - for descriptor in registration["methodDescriptors"] - ) - assert any( - descriptor["method"] == "agent/guide" - and descriptor["command"] == "agent" - and descriptor["supportsCompact"] is True - and descriptor["supportsJson"] is False - for descriptor in registration["methodDescriptors"] - ) - - -def test_cli_agent_guide_prints_provider_owned_searchflow(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 rendered.startswith(f"[py-harness-guide] project={tmp_path}") - assert ( - "|catalog reasoningProfiles=owner-query,query-deps,owner-tests," - "finding-frontier,feature-cfg entries=owner-query,query-deps,owner-tests " - "routes=read-frontier,syntax-locate,syntax-code,query-code" - ) in rendered - assert ( - "|routing evidence-state prime=owner-map-only pipe=ambiguous-query" in rendered - ) - assert ( - "|route syntax-locate selectors=S:tree-sitter-query,Scope:owner-or-structural " - "returns=locator,capture,frontier code=false" - ) in rendered - assert "" not in rendered - assert "displayLineRange/sourceLocatorHint are display hints" in rendered - assert ( - "|route syntax-code selectors=S:tree-sitter-query,R:exact-selector " - "returns=code code=pure" - ) in rendered - assert "|route query-code selectors=O:owner,Q:symbol" in rendered - assert ( - "|cmd prime=asp python search prime --workspace --view seeds" - in rendered - ) - assert f"|cmd asp python search prime --view seeds {tmp_path}" not in rendered - assert ( - "|cmd owner=asp python search owner --workspace --view seeds" - in rendered - ) - assert "asp python search owner items --query " in rendered - assert ( - "asp python query --from-hook owner-local-projection --selector " - "--term --surface owners,tests --workspace --view seeds" - in rendered - ) - assert ( - "|cmd syntax-code=asp python query --treesitter-query " - "'(function_definition name: (identifier) @function.name)' " - "--selector --workspace --code" - ) in rendered - assert ( - "|cmd query-code=asp python query --term " - "--workspace --code" - ) in rendered - assert ( - "|rule selector queries do not need a trailing project root; " - "--workspace is the independent workspace override" - ) in rendered - assert "trailing . is the project root" not in rendered - assert "--code --workspace" not in rendered - assert ( - "asp python search lexical owner tests " - "--workspace --view seeds" - ) in rendered - assert "--view metadata is document-only for asp md/org query" in rendered - assert "query --term --code|--names-only" in rendered - assert "|rule use the asp python facade" in rendered - - -def test_python_capability_schema_covers_registry_descriptors() -> None: - schema_path = ( - Path(__file__).resolve().parents[3] - / "schemas" - / "python-semantic-capabilities.v1.schema.json" - ) - schema = json.loads(schema_path.read_text(encoding="utf-8")) - capability_names = set( - schema["$defs"]["capabilityDescriptor"]["properties"]["name"]["enum"] - ) - ingest_names = set( - schema["$defs"]["ingestSurfaceDescriptor"]["properties"]["name"]["enum"] - ) - - for descriptor in python_semantic_language_registration()["methodDescriptors"]: - for capability in descriptor.get("capabilities", []): - assert capability["name"] in capability_names - for ingest_surface in descriptor.get("ingestRequiredFor", []): - assert ingest_surface["name"] in ingest_names - - -def test_cli_search_knowledge_axes_accept_multi_term_queries(tmp_path: Path) -> None: - (tmp_path / "pyproject.toml").write_text( - "[project]\nname = 'demo'\ndependencies = ['pytest']\n", - encoding="utf-8", - ) - - cases = [ - (["search", "compare", "ast", "tokenize"], "search/compare", "ast tokenize"), - ( - ["search", "extension", "pytest", "fixture"], - "search/extension", - "pytest fixture", - ), - ( - ["search", "pattern", "dependency", "api"], - "search/pattern", - "dependency api", - ), - ] - for argv, expected_method, expected_query in cases: - stdout = io.StringIO() - exit_code = run_cli( - [*argv, "--json", "--workspace", str(tmp_path)], - stdout=stdout, - ) - - assert exit_code == 0 - packet = json.loads(stdout.getvalue()) - assert packet["method"] == expected_method - assert packet["query"] == expected_query - assert packet["header"]["fields"]["evidenceGrade"] == "fact" + assert registration["providerId"] == "asp-python" + assert "query/exact-selector-native-v1" in registration["methods"] -def test_cli_search_callsite_uses_parser_call_facts(tmp_path: Path) -> None: - write_search_fixture(tmp_path) +def test_cli_agent_guide_uses_asp_owned_exact_projection(tmp_path: Path) -> None: stdout = io.StringIO() - - exit_code = run_cli( - ["search", "callsite", "build", "--workspace", str(tmp_path)], - stdout=stdout, - ) - + assert run_cli(["agent", "guide", str(tmp_path)], stdout=stdout) == 0 rendered = stdout.getvalue() - assert exit_code == 0 - assert rendered.startswith("[search-callsite] q=build hit=1") - assert "|owner tests/test_service.py" in rendered - assert "|hit path=tests/test_service.py line=4" in rendered - assert "kind=callsite" in rendered - assert "symbol=build" in rendered + assert "routes=syntax-locate,exact-source,callable-skeleton" in rendered + assert "|route exact-source selectors=R:exact-selector returns=source" in rendered + assert "|route callable-skeleton selectors=R:exact-callable-selector" in rendered + assert "no raw Python source reads" in rendered -def test_cli_search_deps_routes_dependency_api_followups(tmp_path: Path) -> None: - write_search_fixture(tmp_path) - stdout = io.StringIO() - json_stdout = io.StringIO() - - exit_code = run_cli( - ["search", "deps", "requests@2::Session", "--workspace", str(tmp_path)], - stdout=stdout, - ) - json_exit_code = run_cli( - [ - "search", - "deps", - "requests@2::Session", - "--json", - "--workspace", - str(tmp_path), - ], - stdout=json_stdout, +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 ) - - rendered = stdout.getvalue() - assert exit_code == 0 - assert rendered.startswith("[search-deps] q=requests@2::Session") - assert "package=requests" in rendered - assert "requestedVersion=2" in rendered - assert "versionScope=current" in rendered - assert "api=Session" in rendered - assert ( - "|next dependency:requests,public-external-types:requests," - "api:requests@2::Session,text:Session,tests:Session" - ) in rendered - - packet = json.loads(json_stdout.getvalue()) - assert json_exit_code == 0 - assert packet["method"] == "search/deps" - assert packet["header"]["kind"] == "search-deps" - assert packet["header"]["fields"]["package"] == "requests" - assert packet["header"]["fields"]["api"] == "Session" - assert {action["kind"] for action in packet["nextActions"]} >= { - "dependency", - "public-external-types", - "api", - "text", - "tests", - } - - -def test_cli_search_ingest_groups_rg_output_by_owner(tmp_path: Path) -> None: - write_search_fixture(tmp_path) - stdout = io.StringIO() - - exit_code = run_cli( - ["search", "ingest", "--workspace", str(tmp_path)], - stdout=stdout, - stdin="src/pkg/service.py:3:def build(value: str) -> str:\n", + exact = next( + descriptor + for descriptor in descriptors + if descriptor["method"] == "query/exact-selector-native-v1" ) - - rendered = stdout.getvalue() - assert exit_code == 0 - assert rendered.startswith("[search-ingest] source=rg-n hit=1") - assert "|owner src/pkg/service.py" in rendered - assert "|hit path=src/pkg/service.py line=3" in rendered + assert exact["invocation"]["argv"][:3] == ["asp", "python", "query"] diff --git a/tests/unit/harness/test_semantic_cli_benchmark_registry.py b/tests/unit/harness/test_semantic_cli_benchmark_registry.py index 6e8e6f7..96e52b3 100644 --- a/tests/unit/harness/test_semantic_cli_benchmark_registry.py +++ b/tests/unit/harness/test_semantic_cli_benchmark_registry.py @@ -9,6 +9,7 @@ def test_registered_search_methods_publish_public_benchmark_invocations() -> Non descriptor for descriptor in descriptors if descriptor["method"].startswith("search/") + and "benchmarkInvocation" in descriptor ] assert search_descriptors diff --git a/tests/unit/harness/test_semantic_cli_compact_query_snapshot.py b/tests/unit/harness/test_semantic_cli_compact_query_snapshot.py deleted file mode 100644 index a9aba10..0000000 --- a/tests/unit/harness/test_semantic_cli_compact_query_snapshot.py +++ /dev/null @@ -1,169 +0,0 @@ -"""Compact query snapshot tests for the Python semantic CLI.""" - -from __future__ import annotations - -import io -import json -from pathlib import Path - -from python_lang_project_harness import run_cli - - -def _compact_query_snapshot(packet: dict) -> dict: - return { - "matches": [ - { - "name": match["name"], - "kind": match["kind"], - "read": match["read"], - "code": match.get("code"), - "projection": match.get("projection"), - } - for match in packet["matches"] - ] - } - - -def _read_json_fixture(relative_path: str) -> dict: - fixture_path = Path(__file__).resolve().parents[2] / "fixtures" / relative_path - return json.loads(fixture_path.read_text(encoding="utf-8")) - - -def test_cli_query_compact_packet_matches_parser_snapshot(tmp_path: Path) -> None: - package = tmp_path / "src" / "pkg" - package.mkdir(parents=True) - (tmp_path / "pyproject.toml").write_text( - '\n[project]\nname = "demo-python"\nversion = "0.1.0"\nimport-names = ["pkg"]\n', - encoding="utf-8", - ) - (package / "__init__.py").write_text("", encoding="utf-8") - (package / "service.py").write_text( - "\n".join( - [ - "def decide(value: int) -> int:", - " if value > 0:", - " return value", - " return 0", - ] - ), - encoding="utf-8", - ) - stdout = io.StringIO() - - exit_code = run_cli( - [ - "query", - "src/pkg/service.py", - "--term", - "decide", - "--json", - "--workspace", - str(tmp_path), - ], - stdout=stdout, - ) - - packet = json.loads(stdout.getvalue()) - assert exit_code == 0 - assert _compact_query_snapshot(packet) == _read_json_fixture( - "compact-query/python-decide.json" - ) - - -def test_cli_query_flow_compact_packet_matches_parser_snapshot( - tmp_path: Path, -) -> None: - package = tmp_path / "src" / "pkg" - package.mkdir(parents=True) - (tmp_path / "pyproject.toml").write_text( - '\n[project]\nname = "demo-python"\nversion = "0.1.0"\nimport-names = ["pkg"]\n', - encoding="utf-8", - ) - (package / "__init__.py").write_text("", encoding="utf-8") - (package / "flow.py").write_text( - "\n".join( - [ - "async def collect(values: list[str], normalize) -> list[str]:", - " results: list[str] = []", - " for value in values:", - " if not value:", - " continue", - " results.append(await normalize(value))", - " return results", - ] - ), - encoding="utf-8", - ) - stdout = io.StringIO() - - exit_code = run_cli( - [ - "query", - "src/pkg/flow.py", - "--term", - "collect", - "--json", - "--workspace", - str(tmp_path), - ], - stdout=stdout, - ) - - packet = json.loads(stdout.getvalue()) - assert exit_code == 0 - assert _compact_query_snapshot(packet) == _read_json_fixture( - "compact-query/python-flow.json" - ) - - -def test_cli_query_class_compact_packet_matches_parser_snapshot( - tmp_path: Path, -) -> None: - package = tmp_path / "src" / "pkg" - package.mkdir(parents=True) - (tmp_path / "pyproject.toml").write_text( - '\n[project]\nname = "demo-python"\nversion = "0.1.0"\nimport-names = ["pkg"]\n', - encoding="utf-8", - ) - (package / "__init__.py").write_text("", encoding="utf-8") - (package / "service.py").write_text( - "\n".join( - [ - "def trace(fn):", - " return fn", - "", - "class Service:", - " prefix: str", - "", - " def __init__(self, prefix: str) -> None:", - " self.prefix = prefix", - "", - " @trace", - " async def run(self, value: str) -> str:", - " if not value:", - " raise ValueError('empty')", - " return f'{self.prefix}:{await normalize(value)}'", - ] - ), - encoding="utf-8", - ) - stdout = io.StringIO() - - exit_code = run_cli( - [ - "query", - "src/pkg/service.py", - "--term", - "Service", - "--json", - "--workspace", - str(tmp_path), - ], - stdout=stdout, - ) - - packet = json.loads(stdout.getvalue()) - assert exit_code == 0 - assert _compact_query_snapshot(packet) == _read_json_fixture( - "compact-query/python-class.json" - ) diff --git a/tests/unit/harness/test_semantic_cli_direct_read.py b/tests/unit/harness/test_semantic_cli_direct_read.py deleted file mode 100644 index 0722256..0000000 --- a/tests/unit/harness/test_semantic_cli_direct_read.py +++ /dev/null @@ -1,79 +0,0 @@ -from __future__ import annotations - -import io -from pathlib import Path - -from pytest import CaptureFixture - -from python_lang_project_harness._cli import run_cli - - -def _write_demo_package(tmp_path: Path) -> None: - package = tmp_path / "src" / "pkg" - package.mkdir(parents=True) - (tmp_path / "pyproject.toml").write_text( - '\n[project]\nname = "demo-python"\nversion = "0.1.0"\nimport-names = ["pkg"]\n', - encoding="utf-8", - ) - (package / "__init__.py").write_text("", encoding="utf-8") - (package / "service.py").write_text( - "\n".join( - [ - "def alpha(value: str) -> str:", - " return value.upper()", - "class Beta:", - " value: str", - ] - ), - encoding="utf-8", - ) - - -def test_cli_query_direct_source_read_rejects_line_selector( - tmp_path: Path, - capsys: CaptureFixture[str], -) -> None: - _write_demo_package(tmp_path) - stdout = io.StringIO() - - exit_code = run_cli( - [ - "query", - "--from-hook", - "owner-local-projection", - "--selector", - "src/pkg/service.py:2:2", - "--workspace", - str(tmp_path), - ], - stdout=stdout, - ) - - assert exit_code == 3 - assert stdout.getvalue() == "" - assert ( - "source locator hints are not executable selectors" in capsys.readouterr().err - ) - - -def test_cli_query_plain_owner_path_still_uses_item_query( - tmp_path: Path, -) -> None: - _write_demo_package(tmp_path) - stdout = io.StringIO() - - exit_code = run_cli( - [ - "query", - "--selector", - "src/pkg/service.py", - "--term", - "alpha", - "--workspace", - str(tmp_path), - ], - stdout=stdout, - ) - - assert exit_code == 0 - assert "owner-local-projection" not in stdout.getvalue() diff --git a/tests/unit/harness/test_semantic_cli_direct_read_code.py b/tests/unit/harness/test_semantic_cli_direct_read_code.py deleted file mode 100644 index 584c5fd..0000000 --- a/tests/unit/harness/test_semantic_cli_direct_read_code.py +++ /dev/null @@ -1,44 +0,0 @@ -from __future__ import annotations - -import io -from pathlib import Path - -from pytest import CaptureFixture - -from python_lang_project_harness._cli import run_cli - - -def test_cli_query_direct_source_read_code_rejects_source_window( - tmp_path: Path, - capsys: CaptureFixture[str], -) -> None: - package = tmp_path / "src" / "pkg" - package.mkdir(parents=True) - (tmp_path / "pyproject.toml").write_text( - '\n[project]\nname = "demo-python"\nversion = "0.1.0"\nimport-names = ["pkg"]\n', - encoding="utf-8", - ) - (package / "__init__.py").write_text("", encoding="utf-8") - (package / "service.py").write_text( - "def alpha(value: str) -> str:\n return value.upper()\n", - encoding="utf-8", - ) - stdout = io.StringIO() - - exit_code = run_cli( - [ - "query", - "--from-hook", - "owner-local-projection", - "--selector", - "src/pkg/service.py:1:2", - "--code", - "--workspace", - str(tmp_path), - ], - stdout=stdout, - ) - - assert exit_code == 3 - assert stdout.getvalue() == "" - assert "status=selector-not-materialized" in capsys.readouterr().err diff --git a/tests/unit/harness/test_semantic_cli_flow_lite_query.py b/tests/unit/harness/test_semantic_cli_flow_lite_query.py deleted file mode 100644 index b6cd1b6..0000000 --- a/tests/unit/harness/test_semantic_cli_flow_lite_query.py +++ /dev/null @@ -1,152 +0,0 @@ -"""Flow-lite query compatibility tests for the Python semantic CLI.""" - -from __future__ import annotations - -import io -import json -from pathlib import Path - -from python_lang_project_harness import run_cli - - -def test_cli_query_flow_lite_renders_native_bounded_frontier(tmp_path: Path) -> None: - _write_flow_lite_fixture(tmp_path) - stdout = io.StringIO() - - exit_code = run_cli( - _flow_lite_query_args(tmp_path), - stdout=stdout, - ) - - rendered = stdout.getvalue() - assert exit_code == 0 - assert "[query-flow-lite]" in rendered - assert "lang=python catalog=flow-lite" in rendered - assert "S=source:call(payload_string)@src/flow.py:9!code" in rendered - assert "K=sink:constructs(ToolAction)@src/flow.py:10!code" in rendered - assert "P=path:bounded(S->K)!flow" in rendered - assert "S>{K:flows-to}" in rendered - assert "confidence=bounded sourceAuthority=native-parser" in rendered - assert "frontier=S.code,K.code,P.flow" in rendered - assert "unknown query option" not in rendered - - -def test_cli_query_flow_lite_json_emits_bounded_packet(tmp_path: Path) -> None: - _write_flow_lite_fixture(tmp_path) - stdout = io.StringIO() - - exit_code = run_cli( - _flow_lite_query_args(tmp_path, "--json"), - stdout=stdout, - ) - - assert exit_code == 0 - packet = json.loads(stdout.getvalue()) - assert packet["schemaId"] == "agent.semantic-protocols.semantic-flow-lite" - assert packet["languageId"] == "python" - assert packet["providerId"] == "py-harness" - assert packet["flowKind"] == "local-source-sink" - assert packet["sourceAuthority"] == "native-parser" - assert packet["executionBackend"] == "native-parser" - assert packet["adapterMode"] == "native-projection" - assert packet["confidence"] == "bounded" - assert packet["ownerPath"] == "src/flow.py" - assert len(packet["path"]) == 3 - assert packet["path"][0]["relation"] == "source" - assert packet["path"][1]["relation"] == "sink" - assert packet["path"][2]["relation"] == "flows-to" - assert packet["omissions"] == [] - assert packet["fields"]["rawSourceStored"] is False - assert packet["fields"]["where"]["scope.fn"] == "collect_tool_actions" - - -def test_cli_query_flow_lite_accepts_positional_workspace(tmp_path: Path) -> None: - _write_flow_lite_fixture(tmp_path) - stdout = io.StringIO() - - exit_code = run_cli( - [ - "query", - "--catalog", - "flow-lite", - "--where", - "source.call=payload_string sink.constructs=ToolAction scope.fn=collect_tool_actions", - str(tmp_path), - ], - stdout=stdout, - ) - - assert exit_code == 0 - assert "[query-flow-lite]" in stdout.getvalue() - - -def test_cli_query_flow_lite_rejects_code_output_and_open_where_key( - tmp_path: Path, -) -> None: - code_stdout = io.StringIO() - code_stderr = io.StringIO() - - code_exit = run_cli( - _flow_lite_query_args(tmp_path, "--code"), - stdout=code_stdout, - stderr=code_stderr, - ) - - assert code_exit == 2 - assert "locator/provenance surface" in code_stderr.getvalue() - - open_where_stdout = io.StringIO() - open_where_stderr = io.StringIO() - open_where_exit = run_cli( - [ - "query", - "--catalog", - "flow-lite", - "--where", - "source.call=payload sink.constructs=Action scope.fn=collect guard.eq=is_safe", - "--workspace", - str(tmp_path), - ], - stdout=open_where_stdout, - stderr=open_where_stderr, - ) - - assert open_where_exit == 2 - assert ( - "unsupported flow-lite --where key `guard.eq`" in open_where_stderr.getvalue() - ) - - -def _flow_lite_query_args(project_root: Path, *extra_args: str) -> list[str]: - return [ - "query", - "--catalog", - "flow-lite", - "--where", - "source.call=payload_string sink.constructs=ToolAction scope.fn=collect_tool_actions", - *extra_args, - "--workspace", - str(project_root), - ] - - -def _write_flow_lite_fixture(project_root: Path) -> None: - source_dir = project_root / "src" - source_dir.mkdir() - (source_dir / "flow.py").write_text( - "\n".join( - [ - "class ToolAction:", - " def __init__(self, payload: str) -> None:", - " self.payload = payload", - "", - "def payload_string(value: str) -> str:", - " return value.strip()", - "", - "def collect_tool_actions(value: str) -> list[ToolAction]:", - " payload = payload_string(value)", - " return [ToolAction(payload)]", - ] - ), - encoding="utf-8", - ) diff --git a/tests/unit/harness/test_semantic_cli_graph_query.py b/tests/unit/harness/test_semantic_cli_graph_query.py deleted file mode 100644 index 5aea0a3..0000000 --- a/tests/unit/harness/test_semantic_cli_graph_query.py +++ /dev/null @@ -1,42 +0,0 @@ -"""Compact graph query-route tests for the Python semantic CLI.""" - -from __future__ import annotations - -import io -from pathlib import Path - -from semantic_search_fixture import write_search_fixture - -from python_lang_project_harness import run_cli - - -def test_cli_query_hook_wildcard_seeds_use_shared_compact_graph( - tmp_path: Path, -) -> None: - write_search_fixture(tmp_path) - stdout = io.StringIO() - stderr = io.StringIO() - - exit_code = run_cli( - [ - "query", - "--from-hook", - "owner-local-projection", - "--selector", - "**/*.py", - "--term", - "build", - "--surface", - "owners,tests", - "--view", - "seeds", - "--workspace", - str(tmp_path), - ], - stdout=stdout, - stderr=stderr, - ) - - assert exit_code == 2 - assert stdout.getvalue() == "" - assert "query --surface is Rust ASP search-owned" in stderr.getvalue() diff --git a/tests/unit/harness/test_semantic_cli_large_descriptor_compact.py b/tests/unit/harness/test_semantic_cli_large_descriptor_compact.py deleted file mode 100644 index 7cd967c..0000000 --- a/tests/unit/harness/test_semantic_cli_large_descriptor_compact.py +++ /dev/null @@ -1,200 +0,0 @@ -from __future__ import annotations - -import io -import json -from pathlib import Path - -from python_lang_project_harness._cli import run_cli - - -def test_cli_query_compacts_large_descriptor_return_lists(tmp_path: Path) -> None: - package = tmp_path / "src" / "pkg" - package.mkdir(parents=True) - (tmp_path / "pyproject.toml").write_text( - """ -[project] -name = "demo-python" -version = "0.1.0" -import-names = ["pkg"] -""".strip(), - encoding="utf-8", - ) - (package / "__init__.py").write_text("", encoding="utf-8") - (package / "catalog.py").write_text( - "\n".join( - [ - "from typing import Any", - "", - "def python_search_view_descriptors() -> list[dict[str, Any]]:", - " return [", - " _view('workspace', capabilities=[_semantic('workspace-router')]),", - " _view('prime', capabilities=[_semantic('package-prime-map')]),", - " _view('owner', requires_query=True, accepted_pipes=['items']),", - " _view('dependency', requires_query=True),", - " _view('deps', requires_query=True),", - " _view('api', requires_query=True),", - " _view('policy', requires_query=True),", - " ]", - ] - ), - encoding="utf-8", - ) - - stdout = io.StringIO() - exit_code = run_cli( - [ - "query", - "--selector", - "python://src/pkg/catalog.py#item/function/python_search_view_descriptors", - "--term", - "python_search_view_descriptors", - "--code", - "--workspace", - str(tmp_path), - ], - stdout=stdout, - ) - - output = stdout.getvalue() - assert exit_code == 0 - assert "return list[7] items=_view:workspace,_view:prime,_view:owner" in output - assert "capabilities=[" not in output - assert "truncated=true" not in output - - -def test_cli_query_compacts_large_descriptor_return_dict_shapes( - tmp_path: Path, -) -> None: - package = tmp_path / "src" / "pkg" - package.mkdir(parents=True) - (tmp_path / "pyproject.toml").write_text( - """ -[project] -name = "demo-python" -version = "0.1.0" -import-names = ["pkg"] -""".strip(), - encoding="utf-8", - ) - (package / "__init__.py").write_text("", encoding="utf-8") - (package / "catalog.py").write_text( - "\n".join( - [ - "from typing import Any", - "", - "def python_view_descriptors() -> list[dict[str, Any]]:", - " return [", - " {'name': 'workspace', 'capabilities': ['workspace-router'], 'requires_query': False},", - " {'name': 'prime', 'capabilities': ['package-prime-map'], 'requires_query': False},", - " {'name': 'owner', 'accepted_pipes': ['items'], 'requires_query': True},", - " {'name': 'dependency', 'accepted_pipes': ['deps'], 'requires_query': True},", - " {'name': 'policy', 'accepted_pipes': ['tests'], 'requires_query': True},", - " ]", - "", - "def python_view_index() -> dict[str, dict[str, Any]]:", - " return {", - " 'workspace': {'capabilities': ['workspace-router']},", - " 'prime': {'capabilities': ['package-prime-map']},", - " 'owner': {'accepted_pipes': ['items']},", - " 'dependency': {'accepted_pipes': ['deps']},", - " 'policy': {'accepted_pipes': ['tests']},", - " }", - ] - ), - encoding="utf-8", - ) - - stdout = io.StringIO() - exit_code = run_cli( - [ - "query", - "--selector", - "python://src/pkg/catalog.py#item/function/python_view_descriptors", - "--term", - "python_view_descriptors", - "--code", - "--workspace", - str(tmp_path), - ], - stdout=stdout, - ) - - output = stdout.getvalue() - assert exit_code == 0 - assert "return list[5] items=dict[3] name=workspace" in output - assert "'capabilities':" not in output - assert "truncated=true" not in output - - index_stdout = io.StringIO() - index_exit_code = run_cli( - [ - "query", - "--selector", - "python://src/pkg/catalog.py#item/function/python_view_index", - "--term", - "python_view_index", - "--code", - "--workspace", - str(tmp_path), - ], - stdout=index_stdout, - ) - index_output = index_stdout.getvalue() - assert index_exit_code == 0 - assert "return dict[5] workspace=dict[1] prime=dict[1]" in index_output - assert "'capabilities':" not in index_output - assert "truncated=true" not in index_output - - json_stdout = io.StringIO() - json_exit_code = run_cli( - [ - "query", - "--selector", - "python://src/pkg/catalog.py#item/function/python_view_descriptors", - "--term", - "python_view_descriptors", - "--json", - "--workspace", - str(tmp_path), - ], - stdout=json_stdout, - ) - - index_json_stdout = io.StringIO() - index_json_exit_code = run_cli( - [ - "query", - "--selector", - "python://src/pkg/catalog.py#item/function/python_view_index", - "--term", - "python_view_index", - "--json", - "--workspace", - str(tmp_path), - ], - stdout=index_json_stdout, - ) - - packet = json.loads(json_stdout.getvalue()) - index_packet = json.loads(index_json_stdout.getvalue()) - code_by_name = { - match["name"]: match["code"] - for current_packet in (packet, index_packet) - for match in current_packet["matches"] - } - assert json_exit_code == 0 - assert index_json_exit_code == 0 - assert ( - "return list[5] items=dict[3] name=workspace" - in code_by_name["python_view_descriptors"] - ) - assert ( - "return dict[5] workspace=dict[1] prime=dict[1]" - in code_by_name["python_view_index"] - ) - for current_packet in (packet, index_packet): - for match in current_packet["matches"]: - rendered = "\n".join( - row["text"] for row in match["projection"]["renderedRows"] - ) - assert rendered == match["code"] diff --git a/tests/unit/harness/test_semantic_cli_lexical.py b/tests/unit/harness/test_semantic_cli_lexical.py index 458908a..cf6fb65 100644 --- a/tests/unit/harness/test_semantic_cli_lexical.py +++ b/tests/unit/harness/test_semantic_cli_lexical.py @@ -114,7 +114,8 @@ def test_protocol_search_lexical_query_uses_fast_frontier( assert stderr.getvalue() == "" assert exit_code == 0 assert rendered.startswith( - "[search-lexical] q=hookruntime,execute view=hits querySet=2" + "[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 @@ -175,7 +176,8 @@ def test_protocol_search_lexical_query_uses_native_prefilter_without_tools( assert stderr.getvalue() == "" assert exit_code == 0 assert rendered.startswith( - "[search-lexical] q=hookruntime,execute view=hits querySet=2" + "[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 diff --git a/tests/unit/harness/test_semantic_cli_owner_item_projection.py b/tests/unit/harness/test_semantic_cli_owner_item_projection.py deleted file mode 100644 index b420b03..0000000 --- a/tests/unit/harness/test_semantic_cli_owner_item_projection.py +++ /dev/null @@ -1,99 +0,0 @@ -"""Projection-specific owner item query tests for the Python semantic CLI.""" - -from __future__ import annotations - -import io -import json -from pathlib import Path - -from python_lang_project_harness import run_cli - - -def test_cli_query_json_keeps_truncated_projection_rows_aligned_with_code( - tmp_path: Path, -) -> None: - package = tmp_path / "src" / "pkg" - package.mkdir(parents=True) - (tmp_path / "pyproject.toml").write_text( - '\n[project]\nname = "demo-python"\nversion = "0.1.0"\nimport-names = ["pkg"]\n', - encoding="utf-8", - ) - (package / "__init__.py").write_text("", encoding="utf-8") - body = ["class LargeShape:"] - body.extend(f" field_{index}: int" for index in range(35)) - (package / "model.py").write_text("\n".join(body), encoding="utf-8") - stdout = io.StringIO() - - exit_code = run_cli( - [ - "query", - "src/pkg/model.py", - "--term", - "LargeShape", - "--json", - "--workspace", - str(tmp_path), - ], - stdout=stdout, - ) - - packet = json.loads(stdout.getvalue()) - match = packet["matches"][0] - projection = match["projection"] - rows = projection["renderedRows"] - assert exit_code == 0 - assert projection["nodesTruncated"] is True - assert projection["nodeCount"] > projection["nodeLimit"] - assert projection["omitted"] - assert "\n".join(row["text"] for row in rows) == match["code"] - - -def test_cli_query_json_summarizes_dict_literal_returns(tmp_path: Path) -> None: - package = tmp_path / "src" / "pkg" - package.mkdir(parents=True) - (tmp_path / "pyproject.toml").write_text( - '\n[project]\nname = "demo-python"\nversion = "0.1.0"\nimport-names = ["pkg"]\n', - encoding="utf-8", - ) - (package / "__init__.py").write_text("", encoding="utf-8") - (package / "schema_fixture.py").write_text( - "\n".join( - [ - "def minimal_ast_patch_request() -> dict[str, str]:", - " return {", - " 'schemaId': 'agent.semantic-protocols.semantic-ast-patch',", - " 'schemaVersion': '1',", - " 'protocolId': 'agent.semantic-protocols.ast-patch',", - " 'protocolVersion': '1',", - " 'languageId': 'python',", - " 'providerId': 'py-harness',", - " }", - ] - ), - encoding="utf-8", - ) - stdout = io.StringIO() - - exit_code = run_cli( - [ - "query", - "src/pkg/schema_fixture.py", - "--term", - "minimal_ast_patch_request", - "--json", - "--workspace", - str(tmp_path), - ], - stdout=stdout, - ) - - match = json.loads(stdout.getvalue())["matches"][0] - assert exit_code == 0 - assert match["code"] == ( - "def minimal_ast_patch_request() -> dict[str, str]:\n" - " return dict[6] schemaId=agent.semantic-protocols.semantic-ast-patch " - "schemaVersion=1 protocolId=agent.semantic-protocols.ast-patch " - "protocolVersion=1 keys=languageId,providerId" - ) - assert "return {'schemaId'" not in match["code"] - assert "..." not in match["code"] diff --git a/tests/unit/harness/test_semantic_cli_owner_items.py b/tests/unit/harness/test_semantic_cli_owner_items.py deleted file mode 100644 index 661227e..0000000 --- a/tests/unit/harness/test_semantic_cli_owner_items.py +++ /dev/null @@ -1,337 +0,0 @@ -"""Owner item query 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_search_owner_items_query_returns_compact_code(tmp_path: Path) -> None: - write_search_fixture(tmp_path) - stdout = io.StringIO() - json_stdout = io.StringIO() - - exit_code = run_cli( - [ - "search", - "owner", - "src/pkg/service.py", - "items", - "--query", - "fetch|build", - "--workspace", - str(tmp_path), - ], - stdout=stdout, - ) - json_exit_code = run_cli( - [ - "search", - "owner", - "src/pkg/service.py", - "items", - "--query", - "fetch|build", - "--json", - "--workspace", - str(tmp_path), - ], - stdout=json_stdout, - ) - - rendered = stdout.getvalue() - assert exit_code == 0 - assert rendered.startswith("[search-owner] q=src/pkg/service.py owner=1") - assert "item=2 itemQuery=fetch|build itemStatus=hit itemMatch=exact" in rendered - assert "|query itemQuery=fetch|build status=hit match=exact item=2" in rendered - assert "|item fetch kind=function" in rendered - assert "public=false" not in rendered - assert "doc=false" not in rendered - assert ( - "structuralSelector=python://src/pkg/service.py#item/function/fetch" in rendered - ) - assert "displayLineRange=" in rendered - assert "sourceLocatorHint=src/pkg/service.py:" in rendered - assert "read=src/pkg/service.py:" in rendered - assert "|item build kind=function" in rendered - assert "next=query-code" in rendered - assert "|code " not in rendered - assert " text=" not in rendered - assert " return value.strip()" not in rendered - - packet = json.loads(json_stdout.getvalue()) - assert json_exit_code == 0 - assert packet["items"][0]["name"] == "fetch" - assert ( - packet["items"][0]["fields"]["structuralSelector"] - == "python://src/pkg/service.py#item/function/fetch" - ) - assert packet["items"][0]["fields"]["displayLineRange"] - assert packet["items"][0]["fields"]["sourceLocatorHint"].startswith( - "src/pkg/service.py:" - ) - assert ( - packet["items"][0]["fields"]["code"] - == "def fetch() -> Response:\n return Response" - ) - assert packet["items"][1]["name"] == "build" - - -def test_cli_search_owner_items_code_flag_returns_pure_compact_code( - tmp_path: Path, -) -> None: - write_search_fixture(tmp_path) - stdout = io.StringIO() - - exit_code = run_cli( - [ - "search", - "owner", - "src/pkg/service.py", - "items", - "--query", - "build", - "--code", - "--workspace", - str(tmp_path), - ], - stdout=stdout, - ) - - rendered = stdout.getvalue() - assert exit_code == 0 - assert rendered == "def build(value: str) -> str:\n return strip\n" - assert "[search-owner]" not in rendered - assert "|code" not in rendered - - -def test_cli_search_owner_items_code_flag_rejects_json(tmp_path: Path) -> None: - write_search_fixture(tmp_path) - stdout = io.StringIO() - stderr = io.StringIO() - - exit_code = run_cli( - [ - "search", - "owner", - "src/pkg/service.py", - "items", - "--query", - "build", - "--code", - "--json", - "--workspace", - str(tmp_path), - ], - stdout=stdout, - stderr=stderr, - ) - - assert exit_code == 2 - assert "--code cannot be combined with --json" in stderr.getvalue() - - -def test_cli_search_owner_items_query_miss_returns_owner_top_items( - tmp_path: Path, -) -> None: - write_search_fixture(tmp_path) - stdout = io.StringIO() - - exit_code = run_cli( - [ - "search", - "owner", - "src/pkg/service.py", - "items", - "--query", - "missingSymbol", - "--workspace", - str(tmp_path), - ], - stdout=stdout, - ) - - rendered = stdout.getvalue() - assert exit_code == 0 - assert "itemStatus=miss" in rendered - assert "fallback=owner-top-items" in rendered - assert "|query itemQuery=missingSymbol status=miss" in rendered - assert "|item SessionClient kind=class" in rendered - assert ( - "structuralSelector=python://src/pkg/service.py#item/class/SessionClient" - in rendered - ) - assert "displayLineRange=" in rendered - assert "sourceLocatorHint=src/pkg/service.py:" in rendered - assert "read=src/pkg/service.py:" in rendered - - -def test_cli_search_owner_items_query_matches_function_body_text( - tmp_path: Path, -) -> None: - write_search_fixture(tmp_path) - stdout = io.StringIO() - - exit_code = run_cli( - [ - "search", - "owner", - "src/pkg/service.py", - "items", - "--query", - "strip", - "--workspace", - str(tmp_path), - ], - stdout=stdout, - ) - - rendered = stdout.getvalue() - assert exit_code == 0 - assert "itemQuery=strip itemStatus=hit itemMatch=fallback-contains" in rendered - assert "|item build kind=function" in rendered - assert ( - "structuralSelector=python://src/pkg/service.py#item/function/build" in rendered - ) - assert "displayLineRange=" in rendered - assert "sourceLocatorHint=src/pkg/service.py:" in rendered - assert "read=src/pkg/service.py:" in rendered - assert "next=query-code" in rendered - assert "|code " not in rendered - assert " text=" not in rendered - - -def test_cli_query_json_emits_projection_nodes_and_expand_actions( - tmp_path: Path, -) -> None: - package = tmp_path / "src" / "pkg" - package.mkdir(parents=True) - (tmp_path / "pyproject.toml").write_text( - '\n[project]\nname = "demo-python"\nversion = "0.1.0"\nimport-names = ["pkg"]\n', - encoding="utf-8", - ) - (package / "__init__.py").write_text("", encoding="utf-8") - (package / "service.py").write_text( - "\n".join( - [ - "def decide(value: int) -> int:", - " if value > 0:", - " return value", - " return 0", - ] - ), - encoding="utf-8", - ) - stdout = io.StringIO() - - exit_code = run_cli( - [ - "query", - "src/pkg/service.py", - "--term", - "decide", - "--json", - "--workspace", - str(tmp_path), - ], - stdout=stdout, - ) - - packet = json.loads(stdout.getvalue()) - projection = packet["matches"][0]["projection"] - nodes = projection["nodes"] - node_ids = {node["id"] for node in nodes} - assert exit_code == 0 - assert projection["mode"] == "compact" - assert projection["syntax"] == "save-token-ruff" - assert projection["compactSafety"] == { - "literalPolicy": "summarize", - "whitespacePolicy": "formatter-structural", - "normalization": "none", - "alignment": "parser-roundtrip", - "exactReadRequired": True, - } - assert len(node_ids) == len(nodes) - assert projection["renderedNodeIds"] - assert set(projection["renderedNodeIds"]).issubset(node_ids) - rows = projection["renderedRows"] - assert [row["nodeId"] for row in rows] == projection["renderedNodeIds"] - assert "\n".join(row["text"] for row in rows) == packet["matches"][0]["code"] - assert all( - any(char.isalnum() or char == "_" for char in row["text"]) for row in rows - ) - assert any(node["read"] != projection["exactRead"] for node in nodes) - assert all("nativeId" in node for node in nodes) - assert all("structuralFingerprint" in node for node in nodes) - assert any(node.get("parentId") not in {None, "decide"} for node in nodes) - assert all( - node.get("parentId") in node_ids - for node in nodes - if node.get("parentId") is not None - ) - assert any( - action.get("target") != "decide" - and action.get("read") != projection["exactRead"] - for action in projection["expandActions"] - ) - for action in projection["expandActions"]: - if action.get("kind") != "exact-read": - continue - assert action["read"].startswith("src/pkg/service.py") - - -def test_cli_query_direct_source_read_selector_rejects_line_range( - tmp_path: Path, -) -> None: - package = tmp_path / "src" / "pkg" - package.mkdir(parents=True) - (tmp_path / "pyproject.toml").write_text( - '\n[project]\nname = "demo-python"\nversion = "0.1.0"\nimport-names = ["pkg"]\n', - encoding="utf-8", - ) - (package / "__init__.py").write_text("", encoding="utf-8") - source = "\n".join( - [ - "def first() -> int:", - " return 1", - "def second() -> int:", - " return 2", - "def third() -> int:", - " return 3", - "def fourth() -> int:", - " return 4", - "def fifth() -> int:", - " return 5", - "def target() -> int:", - " return 6", - ] - ) - target_start = source.splitlines().index("def target() -> int:") + 1 - selector = f"src/pkg/service.py:{target_start}:{target_start + 1}" - (package / "service.py").write_text(source, encoding="utf-8") - stdout = io.StringIO() - stderr = io.StringIO() - - exit_code = run_cli( - [ - "query", - "--from-hook", - "owner-local-projection", - "--selector", - selector, - "--json", - "--workspace", - str(tmp_path), - ], - stdout=stdout, - stderr=stderr, - ) - - assert exit_code == 3 - assert stdout.getvalue() == "" - assert "source locator hints are not executable selectors" in stderr.getvalue() diff --git a/tests/unit/harness/test_semantic_cli_owner_local_projection_registry.py b/tests/unit/harness/test_semantic_cli_owner_local_projection_registry.py deleted file mode 100644 index 0c5a9f4..0000000 --- a/tests/unit/harness/test_semantic_cli_owner_local_projection_registry.py +++ /dev/null @@ -1,60 +0,0 @@ -from __future__ import annotations - -import io -import json -from pathlib import Path - -from python_lang_project_harness import run_cli - - -def test_cli_owner_local_projection_registry_advertises_projection_mode( - tmp_path: Path, -) -> None: - stdout = io.StringIO() - - exit_code = run_cli(["agent", "doctor", "--json", str(tmp_path)], stdout=stdout) - - assert exit_code == 0 - payload = json.loads(stdout.getvalue()) - schemas = payload["languages"][0]["schemas"] - assert any( - schema["schemaId"] == ("agent.semantic-protocols.semantic-source-location") - and schema["path"] == "schemas/semantic-source-location.v1.schema.json" - for schema in schemas - ) - assert any( - schema["schemaId"] - == ("agent.semantic-protocols.semantic-tree-sitter-provenance") - and schema["path"] == "schemas/semantic-tree-sitter-provenance.v1.schema.json" - for schema in schemas - ) - descriptors = payload["languages"][0]["methodDescriptors"] - owner_local_projection = next( - descriptor - for descriptor in descriptors - if descriptor["method"] == "query/owner-local-projection" - ) - assert owner_local_projection["input"] == "exact-selector" - assert owner_local_projection["outputSchemaIds"] == [ - "agent.semantic-protocols.semantic-query-packet", - ] - assert owner_local_projection["packetSchemas"] == [ - "semantic-query-packet.v1", - "semantic-tree-sitter-query.v1", - ] - assert owner_local_projection["queryInputForms"] == ["selector"] - assert owner_local_projection["grammarId"] == "tree-sitter-python" - assert owner_local_projection["cacheReplay"] is False - assert "read-packet" not in owner_local_projection["outputModes"] - - owner_items = next( - descriptor - for descriptor in descriptors - if descriptor["method"] == "query/owner-items" - ) - assert owner_items["packetSchemas"] == [ - "semantic-query-packet.v1", - "semantic-tree-sitter-query.v1", - ] - assert owner_items["grammarId"] == "tree-sitter-python" - assert owner_items["cacheReplay"] is False diff --git a/tests/unit/harness/test_semantic_cli_policy.py b/tests/unit/harness/test_semantic_cli_policy.py index 597f1bf..72d74b8 100644 --- a/tests/unit/harness/test_semantic_cli_policy.py +++ b/tests/unit/harness/test_semantic_cli_policy.py @@ -16,7 +16,7 @@ def test_cli_agent_doctor_json_advertises_policy_search( exit_code = run_cli(["agent", "doctor", "--json", str(tmp_path)], stdout=stdout) payload = json.loads(stdout.getvalue()) assert exit_code == 0 - registration = payload["languages"][0] + registration = payload["registry"]["languages"][0] assert "search/policy" in registration["methods"] assert any( schema["schemaId"] == "agent.semantic-protocols.semantic-handle" diff --git a/tests/unit/harness/test_semantic_cli_query_direct_read_code.py b/tests/unit/harness/test_semantic_cli_query_direct_read_code.py deleted file mode 100644 index 66e2b35..0000000 --- a/tests/unit/harness/test_semantic_cli_query_direct_read_code.py +++ /dev/null @@ -1,66 +0,0 @@ -import io -from pathlib import Path - -from python_lang_project_harness._cli import run_cli - - -def test_query_from_hook_line_range_code_rejects_source_locator_hint( - tmp_path: Path, -) -> None: - source_path = tmp_path / "src" / "package" / "module.py" - source_path.parent.mkdir(parents=True) - source_path.write_text( - "def selected():\n return 'direct'\n", - encoding="utf-8", - ) - stderr = io.StringIO() - - exit_code = run_cli( - [ - "query", - "--from-hook", - "owner-local-projection", - "--selector", - "src/package/module.py:1-2", - "--code", - "--workspace", - str(tmp_path), - ], - stdout=io.StringIO(), - stderr=stderr, - cwd=tmp_path, - ) - - assert exit_code == 3 - assert "status=selector-not-materialized" in stderr.getvalue() - - -def test_query_file_selector_code_requires_parser_owned_identity( - tmp_path: Path, -) -> None: - source_path = tmp_path / "tests" / "test_docs_rfc_skill_contracts.py" - source_path.parent.mkdir(parents=True) - source_path.write_text( - "def test_skill_mentions_hook_install():\n assert 'asp hook install'\n", - encoding="utf-8", - ) - stderr = io.StringIO() - - exit_code = run_cli( - [ - "query", - "--selector", - "tests/test_docs_rfc_skill_contracts.py", - "--term", - "hook", - "--code", - "--workspace", - str(tmp_path), - ], - stdout=io.StringIO(), - stderr=stderr, - cwd=tmp_path, - ) - - assert exit_code == 3 - assert "status=selector-not-materialized" in stderr.getvalue() diff --git a/tests/unit/harness/test_semantic_cli_query_direct_read_projection.py b/tests/unit/harness/test_semantic_cli_query_direct_read_projection.py deleted file mode 100644 index cf62344..0000000 --- a/tests/unit/harness/test_semantic_cli_query_direct_read_projection.py +++ /dev/null @@ -1,52 +0,0 @@ -from __future__ import annotations - -import io -from pathlib import Path - -from pytest import CaptureFixture - -from python_lang_project_harness._cli import run_cli - - -def test_query_from_hook_line_range_code_rejects_source_locator_hint( - tmp_path: Path, - capsys: CaptureFixture[str], -) -> None: - (tmp_path / "pyproject.toml").write_text( - "[project]\nname = 'sample'\nversion = '0.1.0'\n", - encoding="utf-8", - ) - src = tmp_path / "src" - src.mkdir() - (src / "sample.py").write_text( - "\n".join( - [ - "def first():", - " return 'first'", - "", - "def second():", - " return 'second'", - ] - ), - encoding="utf-8", - ) - stdout = io.StringIO() - - exit_code = run_cli( - [ - "query", - "--from-hook", - "owner-local-projection", - "--selector", - "src/sample.py:1-5", - "--code", - "--workspace", - str(tmp_path), - ], - stdout=stdout, - cwd=tmp_path, - ) - - assert exit_code == 3 - assert stdout.getvalue() == "" - assert "status=selector-not-materialized" in capsys.readouterr().err diff --git a/tests/unit/harness/test_semantic_cli_query_hook_surface.py b/tests/unit/harness/test_semantic_cli_query_hook_surface.py deleted file mode 100644 index 39fafe3..0000000 --- a/tests/unit/harness/test_semantic_cli_query_hook_surface.py +++ /dev/null @@ -1,77 +0,0 @@ -from pathlib import Path - -from python_lang_project_harness._cli_args import ProtocolArgs - - -def test_query_from_hook_broad_selector_accepts_shared_surfaces() -> None: - args = ProtocolArgs.parse( - [ - "query", - "--from-hook", - "owner-local-projection", - "--selector", - "**/*.py", - "--term", - "HookDecision", - "--surface", - "owners,tests", - "--view", - "seeds", - "--workspace", - ".", - ] - ) - - assert args is not None - assert args.command == "error" - assert args.error is not None - assert "query --surface is Rust ASP search-owned" in args.error - - -def test_query_from_hook_accepts_workspace_selector_scope() -> None: - args = ProtocolArgs.parse( - [ - "query", - "--from-hook", - "owner-local-projection", - "--workspace", - ".", - "--selector", - "packages/example/src/example.py:1:20", - "--source", - "worktree", - "--package", - "packages/example", - "--code", - ] - ) - - assert args is not None - assert args.command == "query" - assert args.workspace is True - assert args.project_root == Path(".") - assert args.package_path == Path("packages/example") - assert args.selector == "packages/example/src/example.py:1:20" - assert args.source_version == "worktree" - - -def test_tree_sitter_query_accepts_workspace_selector_scope() -> None: - args = ProtocolArgs.parse( - [ - "query", - "--treesitter-query", - "(function_definition name: (identifier) @function.name)", - "--workspace", - ".", - "--selector", - "packages/example/src/example.py:1:20", - "--package", - "packages/example", - ] - ) - - assert args is not None - assert args.command == "query" - assert args.workspace is True - assert args.package_path == Path("packages/example") - assert args.tree_sitter_query is not None diff --git a/tests/unit/harness/test_semantic_cli_query_set_core.py b/tests/unit/harness/test_semantic_cli_query_set_core.py deleted file mode 100644 index 7c3854c..0000000 --- a/tests/unit/harness/test_semantic_cli_query_set_core.py +++ /dev/null @@ -1,141 +0,0 @@ -"""Semantic CLI query-set core protocol tests.""" - -from __future__ import annotations - -import io -import json -from pathlib import Path - -from semantic_search_fixture import ( - compact_graph_renderer_available, - write_search_fixture, -) - -from python_lang_project_harness import run_cli - - -def test_cli_search_text_query_set_and_flag_like_literal_query( - tmp_path: Path, -) -> None: - write_search_fixture(tmp_path) - query_set_stdout = io.StringIO() - query_set_json_stdout = io.StringIO() - flag_like_stdout = io.StringIO() - - query_set_exit = run_cli( - [ - "search", - "lexical", - "--query", - "build", - "--query", - "Session", - "owner", - "tests", - "--owner", - "src/pkg/service.py", - "--workspace", - str(tmp_path), - ], - stdout=query_set_stdout, - ) - query_set_json_exit = run_cli( - [ - "search", - "lexical", - "--query", - "build", - "--query", - "Session", - "owner", - "tests", - "--owner", - "src/pkg/service.py", - "--json", - "--workspace", - str(tmp_path), - ], - stdout=query_set_json_stdout, - ) - flag_like_exit = None - if compact_graph_renderer_available(): - flag_like_exit = run_cli( - [ - "search", - "lexical", - "--json", - "--view", - "seeds", - "--workspace", - str(tmp_path), - ], - stdout=flag_like_stdout, - ) - - rendered = query_set_stdout.getvalue() - assert query_set_exit == 0 - assert rendered.startswith('[search-lexical] q="build,Session" querySet=2') - assert "selector=lexical-set" in rendered - assert "scopeOwner=src/pkg/service.py" in rendered - assert "|query build " in rendered - assert "|query Session " in rendered - assert "queryTerms=build" in rendered - assert "queryTerms=Session" in rendered - assert "|synthesis " in rendered - assert "editFrontier=src/pkg/service.py" in rendered - assert "windowSet=owner:src/pkg/service.py" in rendered - assert "|seed owner:src/pkg/service.py" in rendered - assert "|next owner:src/pkg/service.py,tests:src/pkg/service.py" in rendered - for line in rendered.splitlines(): - if line.startswith("|seed "): - assert ",owner:" not in line - assert ",tests:" not in line - assert "|edge O:src/pkg/service.py -test-> O:tests/test_service.py" in rendered - - packet = json.loads(query_set_json_stdout.getvalue()) - assert query_set_json_exit == 0 - assert packet["query"] == "build,Session" - assert [term["value"] for term in packet["querySet"]] == ["build", "Session"] - assert packet["queryComposition"]["mode"] == "query-set" - assert packet["queryComposition"]["selector"] == "lexical-set" - assert packet["queryComposition"]["merge"] == [ - "nodes", - "edges", - "owners", - "hits", - "typeSurfaces", - "nextActions", - "notes", - ] - assert packet["queryComposition"]["scope"]["ownerPath"] == "src/pkg/service.py" - assert packet["header"]["fields"]["querySet"] == 2 - assert packet["header"]["fields"]["selector"] == "lexical-set" - assert packet["header"]["fields"]["scopeOwner"] == "src/pkg/service.py" - assert [query["value"] for query in packet["queryCoverage"]] == [ - "build", - "Session", - ] - assert all(query["hitCount"] > 0 for query in packet["queryCoverage"]) - assert packet["ownerResolution"] == [ - { - "target": "src/pkg/service.py", - "status": "workspace-owner", - "realOwner": True, - "ownerPath": "src/pkg/service.py", - "reason": "parser-visible owner selected by lexical search", - } - ] - assert packet["searchSynthesis"]["seeds"] == [ - {"kind": "owner", "target": "src/pkg/service.py"} - ] - assert packet["searchSynthesis"]["windowSet"] == [ - {"kind": "owner", "target": "src/pkg/service.py"} - ] - assert packet["searchSynthesis"]["editFrontier"] == ["src/pkg/service.py"] - assert "testFrontier" not in packet["searchSynthesis"] - assert "runtimeCost" not in packet - assert "|runtime " not in rendered - - if compact_graph_renderer_available(): - assert flag_like_exit == 0 - assert flag_like_stdout.getvalue().startswith("[search-lexical] q=--json") diff --git a/tests/unit/harness/test_semantic_cli_query_view.py b/tests/unit/harness/test_semantic_cli_query_view.py deleted file mode 100644 index 5aa5b86..0000000 --- a/tests/unit/harness/test_semantic_cli_query_view.py +++ /dev/null @@ -1,35 +0,0 @@ -"""Query view validation 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_query_rejects_document_metadata_view_for_python_provider( - tmp_path: Path, -) -> None: - stdout = io.StringIO() - stderr = io.StringIO() - - exit_code = run_cli( - [ - "query", - "--term", - "result_to_packet", - "--view", - "metadata", - "--code", - str(tmp_path), - ], - stdout=stdout, - stderr=stderr, - ) - - assert exit_code == 2 - assert stdout.getvalue() == "" - assert "--view metadata is document-only for asp md/org query" in stderr.getvalue() - assert "Python query uses search --view seeds" in stderr.getvalue() - assert "query --term --code" in stderr.getvalue() diff --git a/tests/unit/harness/test_semantic_cli_reasoning.py b/tests/unit/harness/test_semantic_cli_reasoning.py index b1e51e2..2e5726b 100644 --- a/tests/unit/harness/test_semantic_cli_reasoning.py +++ b/tests/unit/harness/test_semantic_cli_reasoning.py @@ -16,7 +16,7 @@ def test_cli_agent_doctor_advertises_reasoning_search(tmp_path: Path) -> None: assert run_cli(["agent", "doctor", "--json", str(tmp_path)], stdout=stdout) == 0 - registration = json.loads(stdout.getvalue())["languages"][0] + registration = json.loads(stdout.getvalue())["registry"]["languages"][0] assert "search/reasoning" in registration["methods"] assert any( descriptor["method"] == "search/reasoning" diff --git a/tests/unit/harness/test_semantic_cli_selector_roundtrip.py b/tests/unit/harness/test_semantic_cli_selector_roundtrip.py deleted file mode 100644 index 3108267..0000000 --- a/tests/unit/harness/test_semantic_cli_selector_roundtrip.py +++ /dev/null @@ -1,41 +0,0 @@ -"""Structural-selector round-trip 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_query_round_trips_parser_owned_structural_selector(tmp_path: Path) -> None: - write_search_fixture(tmp_path) - stdout = io.StringIO() - selector = "python://src/pkg/service.py#item/function/fetch" - - exit_code = run_cli( - [ - "query", - "--selector", - selector, - "--term", - "fetch", - "--json", - "--workspace", - str(tmp_path), - ], - stdout=stdout, - ) - - packet = json.loads(stdout.getvalue()) - assert exit_code == 0 - assert packet["ownerPath"] == "src/pkg/service.py" - assert packet["matchMode"] == "exact" - assert packet["queryCoverage"] == [ - {"value": "fetch", "status": "hit", "match": "exact", "matchCount": 1} - ] - assert [match["structuralSelector"] for match in packet["matches"]] == [selector] - assert packet["matches"][0]["code"].startswith("def fetch()") diff --git a/tests/unit/harness/test_semantic_cli_structural_selector_registry.py b/tests/unit/harness/test_semantic_cli_structural_selector_registry.py new file mode 100644 index 0000000..7514f00 --- /dev/null +++ b/tests/unit/harness/test_semantic_cli_structural_selector_registry.py @@ -0,0 +1,52 @@ +from __future__ import annotations + +import io +import json +from pathlib import Path + +from python_lang_project_harness import run_cli + + +def test_cli_query_registry_owns_structural_selector_projection( + tmp_path: Path, +) -> None: + stdout = io.StringIO() + + exit_code = run_cli(["agent", "doctor", "--json", str(tmp_path)], stdout=stdout) + + assert exit_code == 0 + payload = json.loads(stdout.getvalue()) + descriptors = payload["registry"]["languages"][0]["methodDescriptors"] + query = next( + descriptor for descriptor in descriptors if descriptor["method"] == "query" + ) + assert query["outputModes"] == ["frontier", "json"] + assert "codeOutput" not in query + assert any( + descriptor["method"] == "query/exact-selector-native-v1" + for descriptor in descriptors + ) + assert all( + "owner-local-projection" not in descriptor["method"] + for descriptor in descriptors + ) + + +def test_cli_query_rejects_removed_from_hook_option(tmp_path: Path) -> None: + stderr = io.StringIO() + + exit_code = run_cli( + [ + "query", + "--from-hook", + "owner-local-projection", + "--workspace", + str(tmp_path), + ], + stdout=io.StringIO(), + stderr=stderr, + cwd=tmp_path, + ) + + assert exit_code == 2 + assert "unknown query option: --from-hook" in stderr.getvalue() diff --git a/tests/unit/harness/test_semantic_cli_tree_sitter_query.py b/tests/unit/harness/test_semantic_cli_tree_sitter_query.py deleted file mode 100644 index aad4fce..0000000 --- a/tests/unit/harness/test_semantic_cli_tree_sitter_query.py +++ /dev/null @@ -1,284 +0,0 @@ -"""Tree-sitter-compatible syntax query 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_query_catalog_packet_uses_provider_embedded_sources( - tmp_path: Path, -) -> None: - stdout = io.StringIO() - - exit_code = run_cli( - ["query", "--catalog", "calls", "--json", "--workspace", str(tmp_path)], - stdout=stdout, - ) - - assert exit_code == 0 - packet = json.loads(stdout.getvalue()) - assert packet["schemaId"] == "agent.semantic-protocols.semantic-tree-sitter-query" - assert packet["grammarId"] == "tree-sitter-python" - assert packet["query"]["catalogId"] == "calls" - assert packet["query"]["catalogPath"] == ( - "tree-sitter/tree-sitter-python/queries/calls.scm" - ) - assert packet["query"]["grammarProfilePath"] == ( - "tree-sitter/tree-sitter-python/grammar-profile.json" - ) - assert "call.target" in packet["query"]["fields"]["captures"] - assert packet["cache"]["artifactKind"] == "semantic-tree-sitter-query" - - -def test_cli_query_inline_s_expression_projects_python_functions( - tmp_path: Path, -) -> None: - write_search_fixture(tmp_path) - stdout = io.StringIO() - query = "(function_definition name: (identifier) @function.name)" - - exit_code = run_cli( - _function_name_query_args(query, tmp_path), - stdout=stdout, - ) - - rendered = stdout.getvalue() - assert exit_code == 0 - assert "src/pkg/service.py:9\nfetch" in rendered - assert "|syntax-capture" not in rendered - assert "pub fn" not in rendered - assert "|syntax-query inputForm" not in rendered - - -def test_cli_query_inline_s_expression_requires_asp_compiled_plan( - tmp_path: Path, -) -> None: - write_search_fixture(tmp_path) - stdout = io.StringIO() - query = "(function_definition name: (identifier) @function.name)" - - exit_code = run_cli( - ["query", "--treesitter-query", query, "--workspace", str(tmp_path)], - stdout=stdout, - ) - - assert exit_code != 0 - - -def test_cli_query_catalog_code_flag_returns_pure_python_code( - tmp_path: Path, -) -> None: - write_search_fixture(tmp_path) - stdout = io.StringIO() - - exit_code = run_cli( - [ - "query", - "--catalog", - "declarations", - "--term", - "build", - "--selector", - "src/pkg/service.py", - "--code", - "--workspace", - str(tmp_path), - ], - stdout=stdout, - ) - - assert exit_code == 0 - assert stdout.getvalue() == ( - "def build(value: str) -> str:\n return value.strip()\n" - ) - - -def test_cli_query_catalog_accepts_positional_workspace(tmp_path: Path) -> None: - write_search_fixture(tmp_path) - stdout = io.StringIO() - - exit_code = run_cli( - [ - "query", - "--catalog", - "declarations", - "--term", - "build", - "--selector", - "src/pkg/service.py", - "--code", - str(tmp_path), - ], - stdout=stdout, - ) - - assert exit_code == 0 - assert stdout.getvalue() == ( - "def build(value: str) -> str:\n return value.strip()\n" - ) - - -def test_cli_query_catalog_json_projects_native_capture_rows( - tmp_path: Path, -) -> None: - write_search_fixture(tmp_path) - stdout = io.StringIO() - - exit_code = run_cli( - [ - "query", - "--catalog", - "declarations", - "--term", - "SessionClient", - "--json", - "--workspace", - str(tmp_path), - ], - stdout=stdout, - ) - - assert exit_code == 0 - packet = json.loads(stdout.getvalue()) - assert packet["matches"][0]["captures"][0]["name"] == "class.name" - assert packet["matches"][0]["captures"][0]["nodeType"] == "identifier" - assert packet["matches"][0]["captures"][0]["field"] == "name" - assert ( - packet["matches"][0]["captures"][0]["fields"]["nativeNodeType"] - == "class_definition" - ) - assert packet["nativeFactRefs"][0].startswith("python:ast:src/pkg/service.py:") - - -def test_cli_query_inline_call_target_uses_target_capture_node_and_field( - tmp_path: Path, -) -> None: - (tmp_path / "sample.py").write_text( - "def parse_query():\n return 1\n\ndef run():\n parse_query()\n", - encoding="utf-8", - ) - stdout = io.StringIO() - query = "(call function: (identifier) @call.target)" - - exit_code = run_cli( - [ - "query", - "--treesitter-query", - query, - "--json", - "--workspace", - str(tmp_path), - "--asp-syntax-query-captures", - "call.target", - "--asp-syntax-query-node-types", - "call,identifier", - "--asp-syntax-query-fields", - "function", - ], - stdout=stdout, - ) - - assert exit_code == 0 - packet = json.loads(stdout.getvalue()) - capture = packet["matches"][0]["captures"][0] - assert capture["name"] == "call.target" - assert capture["nodeType"] == "identifier" - assert capture["field"] == "function" - assert capture["fields"]["nativeNodeType"] == "call" - assert capture["fields"]["read"] == "sample.py:5" - assert capture["fields"]["itemRead"] == "sample.py:5" - - -def test_cli_owner_item_query_packet_links_python_syntax_refs( - tmp_path: Path, -) -> None: - write_search_fixture(tmp_path) - stdout = io.StringIO() - - exit_code = run_cli( - [ - "query", - "src/pkg/service.py", - "--term", - "build", - "--json", - "--workspace", - str(tmp_path), - ], - stdout=stdout, - ) - - assert exit_code == 0 - packet = json.loads(stdout.getvalue()) - assert packet["syntaxQueryRef"] == ( - "semantic-tree-sitter-query/python-owner-items.v1" - ) - assert packet["syntaxMatchRefs"] == ["match.1"] - assert packet["syntaxCaptureRefs"] == ["capture.1"] - assert packet["matches"][0]["fields"]["syntaxQueryRef"] == ( - "semantic-tree-sitter-query/python-owner-items.v1" - ) - assert packet["matches"][0]["fields"]["syntaxMatchRef"] == "match.1" - assert packet["matches"][0]["fields"]["syntaxCaptureRef"] == "capture.1" - - -def test_cli_search_owner_items_packet_links_python_syntax_refs( - tmp_path: Path, -) -> None: - write_search_fixture(tmp_path) - stdout = io.StringIO() - - exit_code = run_cli( - [ - "search", - "owner", - "src/pkg/service.py", - "items", - "--query", - "build", - "--json", - "--workspace", - str(tmp_path), - ], - stdout=stdout, - ) - - assert exit_code == 0 - packet = json.loads(stdout.getvalue()) - assert packet["syntaxQueryRef"] == ( - "semantic-tree-sitter-query/python-owner-items.v1" - ) - assert packet["syntaxMatchRefs"] == ["match.1"] - assert packet["syntaxCaptureRefs"] == ["capture.1"] - assert packet["items"][0]["fields"]["syntaxQueryRef"] == ( - "semantic-tree-sitter-query/python-owner-items.v1" - ) - - -def _function_name_query_args( - query: str, - project_root: Path, - plan_args: list[str] | None = None, - *extra_args: str, -) -> list[str]: - return [ - "query", - "--treesitter-query", - query, - *extra_args, - "--workspace", - str(project_root), - "--asp-syntax-query-captures", - "function.name", - "--asp-syntax-query-node-types", - "function_definition,identifier", - "--asp-syntax-query-fields", - "name", - *(plan_args or []), - ] diff --git a/tests/unit/harness/test_semantic_cli_tree_sitter_registry.py b/tests/unit/harness/test_semantic_cli_tree_sitter_registry.py index 6e80a85..8d50358 100644 --- a/tests/unit/harness/test_semantic_cli_tree_sitter_registry.py +++ b/tests/unit/harness/test_semantic_cli_tree_sitter_registry.py @@ -15,7 +15,7 @@ def test_agent_doctor_advertises_tree_sitter_query_descriptor( stdout = io.StringIO() exit_code = run_cli(["agent", "doctor", "--json", str(tmp_path)], stdout=stdout) - registration = json.loads(stdout.getvalue())["languages"][0] + registration = json.loads(stdout.getvalue())["registry"]["languages"][0] assert exit_code == 0 assert "query" in registration["methods"] diff --git a/tests/unit/harness/test_semantic_cli_tree_sitter_selector.py b/tests/unit/harness/test_semantic_cli_tree_sitter_selector.py deleted file mode 100644 index aceb9ee..0000000 --- a/tests/unit/harness/test_semantic_cli_tree_sitter_selector.py +++ /dev/null @@ -1,93 +0,0 @@ -"""Selector identity tests for Python tree-sitter-compatible queries.""" - -from __future__ import annotations - -import io -from pathlib import Path - -from semantic_search_fixture import write_search_fixture - -from python_lang_project_harness import run_cli - - -def test_cli_query_exact_selector_scans_outside_default_report_sources( - tmp_path: Path, -) -> None: - (tmp_path / "dist").mkdir() - (tmp_path / "dist" / "member.py").write_text( - "def from_dist() -> str:\n return 'dist'\n", - encoding="utf-8", - ) - stdout = io.StringIO() - query = "(function_definition name: (identifier) @function.name)" - - exit_code = run_cli( - _function_name_query_args( - query, - tmp_path, - "--selector", - "dist/member.py:1:2", - "--code", - ), - stdout=stdout, - ) - - assert exit_code == 0 - assert stdout.getvalue() == "def from_dist() -> str:\n return 'dist'\n" - - -def test_cli_query_selector_uses_canonical_paths_not_suffix_matching( - tmp_path: Path, -) -> None: - write_search_fixture(tmp_path) - query = "(function_definition name: (identifier) @function.name)" - - suffix_stdout = io.StringIO() - suffix_exit_code = run_cli( - _function_name_query_args( - query, - tmp_path, - "--selector", - "service.py:9:10", - "--code", - ), - stdout=suffix_stdout, - ) - - assert suffix_exit_code == 0 - assert suffix_stdout.getvalue() == "" - - absolute_stdout = io.StringIO() - absolute_exit_code = run_cli( - _function_name_query_args( - query, - tmp_path, - "--selector", - f"{tmp_path / 'src' / 'pkg' / 'service.py'}:9:10", - ), - stdout=absolute_stdout, - ) - - assert absolute_exit_code == 0 - assert "src/pkg/service.py:9\nfetch" in absolute_stdout.getvalue() - - -def _function_name_query_args( - query: str, - project_root: Path, - *extra_args: str, -) -> list[str]: - return [ - "query", - "--treesitter-query", - query, - *extra_args, - "--workspace", - str(project_root), - "--asp-syntax-query-captures", - "function.name", - "--asp-syntax-query-node-types", - "function_definition,identifier", - "--asp-syntax-query-fields", - "name", - ] diff --git a/tests/unit/harness/test_semantic_graph_facts.py b/tests/unit/harness/test_semantic_graph_facts.py index cf8515b..6581eee 100644 --- a/tests/unit/harness/test_semantic_graph_facts.py +++ b/tests/unit/harness/test_semantic_graph_facts.py @@ -41,7 +41,7 @@ def test_search_semantic_facts_emits_field_type_collection_graph(tmp_path): payload = json.loads(stdout.getvalue()) assert payload["schemaId"] == "agent.semantic-protocols.semantic-fact-graph" assert payload["languageId"] == "python" - assert payload["providerId"] == "py-harness" + assert payload["providerId"] == "asp-python" assert payload["query"] == "list collection fields" nodes = payload["nodes"] edges = payload["edges"] @@ -51,7 +51,7 @@ def test_search_semantic_facts_emits_field_type_collection_graph(tmp_path): and node["fields"]["typeValue"] == "list[str]" and node["fields"]["collectionKind"] == "list" and node["fields"]["languageId"] == "python" - and node["fields"]["providerId"] == "py-harness" + and node["fields"]["providerId"] == "asp-python" and node["fields"]["semanticFactKind"] == "field" and node["fields"]["provenance"] == "parser" and node["fields"]["confidence"] == "exact" diff --git a/tests/unit/harness/test_semantic_provider_doctor.py b/tests/unit/harness/test_semantic_provider_doctor.py new file mode 100644 index 0000000..6099087 --- /dev/null +++ b/tests/unit/harness/test_semantic_provider_doctor.py @@ -0,0 +1,70 @@ +"""Validate the Python provider doctor-v2 response contract.""" + +import io +import json +from hashlib import sha256 +from pathlib import Path + +from python_lang_project_harness._cli import run_cli + + +def test_cli_agent_doctor_json_validates_v1_envelope_and_registry( + tmp_path: Path, +) -> None: + stdout = io.StringIO() + + exit_code = run_cli(["agent", "doctor", "--json", str(tmp_path)], stdout=stdout) + + assert exit_code == 0 + payload = json.loads(stdout.getvalue()) + envelope_keys = ( + "schemaId schemaVersion schemaAuthority protocolId protocolVersion languageId providerId binary " + "execution registrySchemaId registrySchemaVersion registry registryDigest" + ) + assert set(payload) == set(envelope_keys.split()) + assert payload["schemaId"] == "agent.semantic-protocols.semantic-provider-doctor" + assert payload["schemaVersion"] == "1" + assert payload["schemaAuthority"] == ( + "https://tao3k.github.io/agent-semantic-protocols/schemas/" + ) + assert payload["protocolId"] == "agent.semantic-protocols.semantic-language" + assert payload["protocolVersion"] == "1" + assert payload["registrySchemaId"] == ( + "agent.semantic-protocols.semantic-language-registry" + ) + assert payload["registrySchemaVersion"] == "1" + + registry = payload["registry"] + registration = registry["languages"][0] + assert registry["registryVersion"] == "1" + assert (payload["languageId"], payload["providerId"], payload["binary"]) == ( + registration["languageId"], + registration["providerId"], + registration["binary"], + ) + descriptors = registration["methodDescriptors"] + assert len(descriptors) == len(registration["methods"]) == 33 + exact_query = next( + descriptor + for descriptor in descriptors + if descriptor["method"] == "query/exact-selector-native-v1" + ) + assert exact_query["invocation"]["argv"] == [ + "asp", + "python", + "query", + "--selector", + "{selector}", + "--projection", + "{projection}", + "--workspace", + "{workspace}", + ] + assert all(descriptor["invocation"]["argv"] for descriptor in descriptors) + canonical = json.dumps( + registry, + ensure_ascii=False, + sort_keys=True, + separators=(",", ":"), + ).encode() + assert payload["registryDigest"] == f"sha256:{sha256(canonical).hexdigest()}" diff --git a/tests/unit/harness/test_semantic_schema_registry.py b/tests/unit/harness/test_semantic_schema_registry.py index d9f2625..371d521 100644 --- a/tests/unit/harness/test_semantic_schema_registry.py +++ b/tests/unit/harness/test_semantic_schema_registry.py @@ -15,23 +15,11 @@ def test_package_local_semantic_schemas_stay_synchronized() -> None: if not protocol_schema_dir.exists(): pytest.skip("protocol repository schemas are not available") - for schema_file_name in ( - "semantic-search-packet.v1.schema.json", - "semantic-query-packet.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-graph.v1.schema.json", - "semantic-type-surface.v1.schema.json", - "semantic-dev-command-log.v1.schema.json", - ): - package_schema = json.loads( - (package_root / "schemas" / schema_file_name).read_text(encoding="utf-8") - ) - protocol_schema = json.loads( - (protocol_schema_dir / schema_file_name).read_text(encoding="utf-8") - ) + 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 index 42beb2a..bf0dd41 100644 --- a/tests/unit/harness/test_semantic_search_graph_profiles.py +++ b/tests/unit/harness/test_semantic_search_graph_profiles.py @@ -26,13 +26,29 @@ def test_compact_graph_profiles_filter_to_rendered_aliases() -> None: 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": [ { diff --git a/tests/unit/lang_harness/test_render_assertions.py b/tests/unit/lang_harness/test_render_assertions.py index 36ab81a..3ff5fc9 100644 --- a/tests/unit/lang_harness/test_render_assertions.py +++ b/tests/unit/lang_harness/test_render_assertions.py @@ -29,7 +29,8 @@ def test_render_python_lang_harness_uses_compact_source_diagnostic( assert "|failureFrontier rule=python.syntax.invalid severity=error" in output assert "|hotBlock selector=" in output assert "bad.py:1:1 reason=blocking-finding" in output - assert "|next action=owner-local-projection selector=" in output + assert "|next action=" not in output + assert "owner-local-projection" not in output assert "Action:" not in output assert "Fix:" not in output assert "Evidence:" not in output diff --git a/tests/unit/snapshots/python_project_harness_compact_text.snap b/tests/unit/snapshots/python_project_harness_compact_text.snap index f049fbd..8dcdb54 100644 --- a/tests/unit/snapshots/python_project_harness_compact_text.snap +++ b/tests/unit/snapshots/python_project_harness_compact_text.snap @@ -4,4 +4,3 @@ |summary $TEMP/src/service.py calls bare print(). |repair replace bare print with a project-owned reporting surface |hotBlock selector=$TEMP/src/service.py:5:5 reason=blocking-finding -|next action=owner-local-projection selector=$TEMP/src/service.py:5:5 root=. diff --git a/tests/unit/snapshots/python_project_harness_json.snap b/tests/unit/snapshots/python_project_harness_json.snap index 7590fef..5e8d48d 100644 --- a/tests/unit/snapshots/python_project_harness_json.snap +++ b/tests/unit/snapshots/python_project_harness_json.snap @@ -1 +1 @@ -{"blocking_rule_ids":[],"blocking_severities":["error","warning"],"disabled_rule_ids":[],"file_count":1,"findings":[{"label":"replace bare print with a project-owned reporting surface","labels":{"domain":"modern-python","language":"python"},"location":{"column":4,"line":5,"path":"$TEMP/src/service.py"},"pack_id":"python.modern_design","requirement":"Use a logger, returned value, or explicit test assertion instead of bare `print` in library modules.","rule_id":"PY-MOD-R002","severity":"warning","source_line":" print(\"debug\")","summary":"$TEMP/src/service.py calls bare print().","title":"Library module uses bare print"}],"is_clean":false,"modules":[{"assignments":[],"bindings":[],"calls":[],"diagnostics":[],"export_candidates":[],"export_contract":{"is_explicit":false,"is_static":false,"kind":"inferred","location":null,"names":[]},"has_annotations":false,"imports":[],"is_valid":true,"metadata":{},"module_docstring":"Service helpers.","path":"$TEMP/src/service.py","references":[],"scopes":[],"shape":null,"symbols":[]}],"parsed_count":1,"project_scope":null,"root_paths":["$TEMP/src","$TEMP/tests"]} \ No newline at end of file +{"blocking_rule_ids":[],"blocking_severities":["error","warning"],"disabled_rule_ids":[],"file_count":1,"findings":[{"label":"replace bare print with a project-owned reporting surface","labels":{"domain":"modern-python","language":"python"},"location":{"column":4,"line":5,"path":"$TEMP/src/service.py"},"pack_id":"python.modern_design","requirement":"Use a logger, returned value, or explicit test assertion instead of bare `print` in library modules.","rule_id":"PY-MOD-R002","severity":"warning","source_line":" print(\"debug\")","summary":"$TEMP/src/service.py calls bare print().","title":"Library module uses bare print"}],"is_clean":false,"modules":[{"assignments":[],"bindings":[],"calls":[],"diagnostics":[],"export_candidates":[],"export_contract":{"is_explicit":false,"is_static":false,"kind":"inferred","location":null,"names":[]},"has_annotations":false,"imports":[],"is_valid":true,"metadata":{},"module_docstring":"Service helpers.","path":"$TEMP/src/service.py","references":[],"scopes":[],"shape":null,"symbols":[]}],"parsed_count":1,"project_resolution":null,"root_paths":["$TEMP/src","$TEMP/tests"]} \ No newline at end of file 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 0759f80..a70b799 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 @@ -8,4 +8,3 @@ expression: rendered |summary Wildcard import from 'tools' makes exported names implicit. |repair replace wildcard import with explicit imported names |hotBlock selector=src/module.py:3:3 reason=blocking-finding -|next action=owner-local-projection selector=src/module.py:3:3 root=. 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 79b2315..1fc7f07 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 @@ -8,4 +8,3 @@ expression: rendered |summary Bare print calls leak diagnostics to stdout. |repair replace bare print with a project-owned reporting surface |hotBlock selector=src/module.py:5:5 reason=blocking-finding -|next action=owner-local-projection selector=src/module.py:5:5 root=. 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 80df47b..862d8d6 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 @@ -8,4 +8,3 @@ expression: rendered |summary Facade imports expose names without an explicit public contract. |repair add an explicit __all__ for this facade export surface |hotBlock selector=src/pkg/__init__.py:1:1 reason=blocking-finding -|next action=owner-local-projection selector=src/pkg/__init__.py:1:1 root=. 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 67e7c3b..5f794cc 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 @@ -8,4 +8,3 @@ expression: rendered |summary breakpoint() can halt library execution inside an interactive debugger. |repair remove breakpoint() from library code |hotBlock selector=src/module.py:5:5 reason=blocking-finding -|next action=owner-local-projection selector=src/module.py:5:5 root=. 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 ecb8aca..593c797 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 @@ -8,4 +8,3 @@ expression: rendered |summary feature.py has 261 effective lines, 60 top-level items, 2 responsibility groups, 60 public surface items, 0 long functions, and max function span 2 lines. |repair split this module into focused ownership seams |hotBlock selector=src/feature.py:1:1 reason=blocking-finding -|next action=owner-local-projection selector=src/feature.py:1:1 root=. 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 cd1d82c..006934c 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 @@ -8,4 +8,3 @@ expression: rendered |summary $TEMP/src/pkg/domain.py and $TEMP/src/pkg/domain/__init__.py both define Python import owner 'pkg.domain'. |repair choose one source owner for this package branch |hotBlock selector=src/pkg/domain/__init__.py:1:1 reason=blocking-finding -|next action=owner-local-projection selector=src/pkg/domain/__init__.py:1:1 root=. 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 5e61842..ede3cca 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 @@ -8,4 +8,3 @@ expression: rendered |summary pyproject.toml is present but project sources are not under src/. |repair declare package code under src/ |hotBlock selector=pyproject.toml:1:1 reason=blocking-finding -|next action=owner-local-projection selector=pyproject.toml:1:1 root=. 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 f5f8ec5..65b6f3d 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 @@ -8,4 +8,3 @@ expression: rendered |summary Declared wheel package root $TEMP/src/missing_pkg is not an importable package directory. |repair make this declared package root importable |hotBlock selector=pyproject.toml:1:1 reason=blocking-finding -|next action=owner-local-projection selector=pyproject.toml:1:1 root=. 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 e581345..e610792 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 @@ -8,4 +8,3 @@ expression: rendered |summary pkg exposes public Python surface without a py.typed marker. |repair add py.typed to this package root |hotBlock selector=src/pkg:1:1 reason=blocking-finding -|next action=owner-local-projection selector=src/pkg:1:1 root=. 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 d0757ef..457930f 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 @@ -8,4 +8,3 @@ expression: rendered |summary build is public in a py.typed package but lacks annotations. |repair annotate this typed-package public callable |hotBlock selector=src/pkg/service.py:4:4 reason=blocking-finding -|next action=owner-local-projection selector=src/pkg/service.py:4:4 root=. 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 da3c44e..1566116 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 @@ -8,4 +8,3 @@ expression: rendered |summary pyproject.toml has [project] metadata without a package name. |repair declare [project].name |hotBlock selector=pyproject.toml:1:1 reason=blocking-finding -|next action=owner-local-projection selector=pyproject.toml:1:1 root=. 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 5115f16..b15ad6c 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 @@ -8,4 +8,3 @@ expression: rendered |summary pyproject.toml has [project] metadata without requires-python. |repair declare [project].requires-python |hotBlock selector=pyproject.toml:1:1 reason=blocking-finding -|next action=owner-local-projection selector=pyproject.toml:1:1 root=. 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 b29bc27..f5346a8 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 @@ -8,4 +8,3 @@ expression: rendered |summary pyproject.toml has [build-system] metadata without requires. |repair declare [build-system].requires |hotBlock selector=pyproject.toml:1:1 reason=blocking-finding -|next action=owner-local-projection selector=pyproject.toml:1:1 root=. 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 56b0361..57daa5e 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 @@ -8,4 +8,3 @@ expression: rendered |summary pyproject.toml declares import name 'missing_pkg', but the parser did not find a matching project module owner. |repair align this declared import name with parser-visible code |hotBlock selector=pyproject.toml:1:1 reason=blocking-finding -|next action=owner-local-projection selector=pyproject.toml:1:1 root=. 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 9dd7969..ff7f5ef 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 @@ -8,4 +8,3 @@ expression: rendered |summary pyproject.toml declares console script 'snapshot-cli' pointing at 'missing_pkg.cli:main', but the parser did not find that target module. |repair point this entry target at a parser-visible module |hotBlock selector=pyproject.toml:1:1 reason=blocking-finding -|next action=owner-local-projection selector=pyproject.toml:1:1 root=. 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 bd27516..44f7e26 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 @@ -8,4 +8,3 @@ expression: rendered |summary pyproject.toml declares the Python project harness surface without a parser-visible pytest gate. |repair mount the parser-backed harness in pytest |hotBlock selector=pyproject.toml:1:1 reason=blocking-finding -|next action=owner-local-projection selector=pyproject.toml:1:1 root=. 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 4718acb..c6b6a3f 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 @@ -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 `py-harness --agent-snapshot` to copy the compact `[verify-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. 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 cffa722..ed4c281 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 @@ -8,4 +8,3 @@ expression: rendered |summary test_scattered.py is a pytest module directly under tests root. |repair move this pytest module under tests/unit/ or tests/integration/ |hotBlock selector=tests/test_scattered.py:1:1 reason=blocking-finding -|next action=owner-local-projection selector=tests/test_scattered.py:1:1 root=. 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 0c459ff..25e1432 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 @@ -8,4 +8,3 @@ expression: rendered |summary misc is not an owned tests root entry. |repair move this entry into an owned tests suite directory |hotBlock selector=tests/misc:1:1 reason=blocking-finding -|next action=owner-local-projection selector=tests/misc:1:1 root=. 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 742339f..50da0d6 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 @@ -8,4 +8,3 @@ expression: rendered |summary test_large_policy.py has 330 effective lines across 10 test functions. |repair split this large unit test leaf into focused pytest modules |hotBlock selector=tests/unit/test_large_policy.py:1:1 reason=blocking-finding -|next action=owner-local-projection selector=tests/unit/test_large_policy.py:1:1 root=. 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 944cc2c..2a4bebf 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 @@ -8,4 +8,3 @@ expression: rendered |summary 'return' outside function |repair 'return' outside function |hotBlock selector=src/bad_scope.py:1:1 reason=blocking-finding -|next action=owner-local-projection selector=src/bad_scope.py:1:1 root=. 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 bc296e4..a88e71d 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 @@ -8,4 +8,3 @@ expression: rendered |summary invalid syntax |repair invalid syntax |hotBlock selector=src/broken.py:1:1 reason=blocking-finding -|next action=owner-local-projection selector=src/broken.py:1:1 root=. diff --git a/tests/unit/test_package_metadata.py b/tests/unit/test_package_metadata.py index cfa5f5b..5ce7fd9 100644 --- a/tests/unit/test_package_metadata.py +++ b/tests/unit/test_package_metadata.py @@ -47,7 +47,7 @@ def test_distribution_exposes_console_script() -> None: for entry_point in metadata.entry_points(group="console_scripts") } - assert scripts["py-harness"] == ("python_lang_project_harness:run_cli_from_env") + assert scripts["asp-python"] == ("python_lang_project_harness:run_cli_from_env") def test_distribution_exposes_pytest_optional_dependency() -> None: diff --git a/tests/unit/test_parser_public_exports.py b/tests/unit/test_parser_public_exports.py new file mode 100644 index 0000000..864f97d --- /dev/null +++ b/tests/unit/test_parser_public_exports.py @@ -0,0 +1,12 @@ +import python_lang_parser + + +def test_pyproject_document_api_is_publicly_exported() -> None: + expected = { + "PythonPyprojectParseError", + "parse_python_pyproject_document", + } + + assert expected <= set(python_lang_parser.__all__) + for name in expected: + assert getattr(python_lang_parser, name) is not None diff --git a/tests/unit/test_pyproject_document.py b/tests/unit/test_pyproject_document.py new file mode 100644 index 0000000..0a8e903 --- /dev/null +++ b/tests/unit/test_pyproject_document.py @@ -0,0 +1,51 @@ +from __future__ import annotations + +from pathlib import Path + +import pytest + +from python_lang_parser import ( + PythonPyprojectParseError, + parse_python_pyproject_document, +) + + +def test_parse_python_pyproject_document_preserves_workspace_members( + tmp_path: Path, +) -> None: + pyproject = tmp_path / "pyproject.toml" + pyproject.write_text( + """ +[project] +name = "workspace-root" + +[tool.uv.workspace] +members = ["packages/*"] +exclude = ["packages/experimental"] +""".lstrip(), + encoding="utf-8", + ) + + document = parse_python_pyproject_document(pyproject) + + assert document["tool"]["uv"]["workspace"] == { + "members": ["packages/*"], + "exclude": ["packages/experimental"], + } + + +@pytest.mark.parametrize("contents", ["[project", "\udcff"]) +def test_parse_python_pyproject_document_rejects_invalid_input( + tmp_path: Path, + contents: str, +) -> None: + pyproject = tmp_path / "pyproject.toml" + pyproject.write_text(contents, encoding="utf-8", errors="surrogatepass") + + with pytest.raises(PythonPyprojectParseError): + parse_python_pyproject_document(pyproject) + + +def test_parse_python_pyproject_document_rejects_missing_file(tmp_path: Path) -> None: + with pytest.raises(PythonPyprojectParseError, match="is missing"): + parse_python_pyproject_document(tmp_path / "pyproject.toml") diff --git a/tree-sitter/tree-sitter-python/query-corpus/README.md b/tree-sitter/tree-sitter-python/query-corpus/README.md index 85fea25..7ed4a07 100644 --- a/tree-sitter/tree-sitter-python/query-corpus/README.md +++ b/tree-sitter/tree-sitter-python/query-corpus/README.md @@ -1,5 +1,5 @@ Python tree-sitter-compatible query corpus fixtures for ASP. -These cases document the captures that py-harness projects from native +These cases document the captures that asp-python projects from native `ast`, `tokenize`, and `symtable` facts into the shared tree-sitter-compatible ABI. They are not a replacement for upstream tree-sitter-python corpus tests. diff --git a/uv.lock b/uv.lock index 9df67bd..eb971fa 100644 --- a/uv.lock +++ b/uv.lock @@ -2,6 +2,66 @@ version = 1 revision = 3 requires-python = ">=3.12" +[[package]] +name = "blake3" +version = "1.0.9" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/26/6a/4cc5a9dd40fd8a6d283fd3761e5f59c490109571ef8e3c73245417e5a305/blake3-1.0.9.tar.gz", hash = "sha256:5fa374fa5070ca084368776c19b420157eb0f2d3f091343d6bc59189929d62e2", size = 116872, upload-time = "2026-06-22T18:02:25.366Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/5c/d2/9bdf8345c70993aaef635398f52edfb915d6e8ad2c000c801204e387c456/blake3-1.0.9-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:a70c20542d5e7960983a0ff32999049a2b0e5ef1f22dbbbdfb51cf04828a4156", size = 344587, upload-time = "2026-06-22T18:00:34.244Z" }, + { url = "https://files.pythonhosted.org/packages/36/9d/be8b1f7f85b12bb45a0fade6ca7bdbf83a507d23d0b6141ba29fe69c8cea/blake3-1.0.9-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:72cdecf088a9d25e6ec79948a578995649b0dbee407e7a46c543a9ecc0f6f281", size = 328864, upload-time = "2026-06-22T18:00:35.59Z" }, + { url = "https://files.pythonhosted.org/packages/f2/78/66580635d744c826671fd219938caffb16281a26f62c4f856695d4233677/blake3-1.0.9-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:42fa57bf462285ef16400601b0fd32214c248ba92505bbb94b1221ab9af5a092", size = 373795, upload-time = "2026-06-22T18:00:36.887Z" }, + { url = "https://files.pythonhosted.org/packages/b1/79/b5b17d3004bb81a5732c0b176c812703d200ed8c652b3b7713b9633bbe10/blake3-1.0.9-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:b25ccde5a64be070f20e5c7a81da70292db40b164b6c77588cbd6230856badbb", size = 374183, upload-time = "2026-06-22T18:00:38.205Z" }, + { url = "https://files.pythonhosted.org/packages/3c/63/0d209c44b2041bbe130ced12a23c92dd995fbfe5bce7ee77fffea16f5cb0/blake3-1.0.9-cp312-cp312-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2a800b87433955f37691b5f361ad29c7dd3ee089c9cd109adc5aea8e24bc4c1f", size = 446783, upload-time = "2026-06-22T18:00:39.493Z" }, + { url = "https://files.pythonhosted.org/packages/c5/51/efd1f9b8a9d3e9a0e235f3ced99a738529a1019fe78b3988e29d9c2fbba6/blake3-1.0.9-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:6879739e7904b9c42afbedbcc2e8c36cebe140fb3fc3f5c492993579cf5cd516", size = 487369, upload-time = "2026-06-22T18:00:40.875Z" }, + { url = "https://files.pythonhosted.org/packages/8d/3f/a8dcaea9e0b26e419a540ca0cd6203c9fbb505e85b02b03c5a59bf9e6a45/blake3-1.0.9-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:6edeb3d49a24c307995899b70dd47aa901d0e9ad51d2f8a79aba4f074f32d8c5", size = 383845, upload-time = "2026-06-22T18:00:42.251Z" }, + { url = "https://files.pythonhosted.org/packages/f6/10/e9907f5b86410d5071982aaf05d149ca4d4fd8acab7e77eebbc9a333c7b4/blake3-1.0.9-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bcd56a7a972c4185070f7042ccc20166927eec3c0f98b8405f375d007b604a0b", size = 383851, upload-time = "2026-06-22T18:00:43.715Z" }, + { url = "https://files.pythonhosted.org/packages/34/cf/c7863a185550706a9624f6aa7b6d46470aaed0bb46a827c5cda2a7d03151/blake3-1.0.9-cp312-cp312-manylinux_2_31_riscv64.whl", hash = "sha256:a288664d08dee154cc496e06e62517fc9e655ecec12b0d7db538d244ac79edf1", size = 380067, upload-time = "2026-06-22T18:00:45.249Z" }, + { url = "https://files.pythonhosted.org/packages/54/0a/e7af679c719368b400c9ba9c3460072aac2ba077ddbd4bc806fef28cda03/blake3-1.0.9-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:91db52a809b68b5bebe7c413ddcd230e1f759398e7fa7a873104595a4fa648b6", size = 549471, upload-time = "2026-06-22T18:00:46.793Z" }, + { url = "https://files.pythonhosted.org/packages/2c/3c/37c1dd3539b7bd9b6d2eef019802aacdb4a3d48ab484b140603bbf9c5b5a/blake3-1.0.9-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:cfaa671b07eb73883162ca940442193868358b0b904cfa266e4b74131ce966da", size = 591396, upload-time = "2026-06-22T18:00:48.122Z" }, + { url = "https://files.pythonhosted.org/packages/ae/55/4f0a23b72795292e74084834130900ea778c0583004519c86698dfffe1a5/blake3-1.0.9-cp312-cp312-win32.whl", hash = "sha256:ae47c3d5729ff89baa6ddf6de47fcfcc915985d39eb1bfcd6db653331f3c6fcc", size = 229271, upload-time = "2026-06-22T18:00:49.377Z" }, + { url = "https://files.pythonhosted.org/packages/12/91/7db93e4689f0f145bcb954dc62936e5f5090548a9fa20c6bbebfaeaa648a/blake3-1.0.9-cp312-cp312-win_amd64.whl", hash = "sha256:15566065ff90ab3da46ec0be1417406f00507af902b6fb0fbc6563e77f02fc42", size = 218220, upload-time = "2026-06-22T18:00:50.659Z" }, + { url = "https://files.pythonhosted.org/packages/41/1b/95b473d649f5322e69674622a307ffdb4f0b63adb0a0adcbc5cb8a8833c2/blake3-1.0.9-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:69ff5aebc7650954443aa701feff2028d7c7ea5b5e18ee265f15e2104e892328", size = 343869, upload-time = "2026-06-22T18:00:51.936Z" }, + { url = "https://files.pythonhosted.org/packages/4b/9d/adec22c719d8451af1dc9e624bf5907008ef1e0afa51aa69fd1e8c91e60e/blake3-1.0.9-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:0cdfeff65488089ef86f7587c76055ff72b28d28d10e427b547f5711477c376d", size = 328482, upload-time = "2026-06-22T18:00:53.39Z" }, + { url = "https://files.pythonhosted.org/packages/5e/aa/0a6967ff9a6ae182419a681aed54f7338b34a1f71372e90f787a2afa42e6/blake3-1.0.9-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:766f1555cbe614f14f399c2fbec0983568d20edb36837ba04040807eb9e1a609", size = 373616, upload-time = "2026-06-22T18:00:54.701Z" }, + { url = "https://files.pythonhosted.org/packages/1c/51/5d4e198bf3ae902c6697ad6ec77d7210736ad8f680980e8b648dcfcd09a0/blake3-1.0.9-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:128a62136c9a39c7cb9fdaa5fb38471f2418853da7f5a89f31495735d0ba6f2c", size = 374149, upload-time = "2026-06-22T18:00:56.015Z" }, + { url = "https://files.pythonhosted.org/packages/7e/62/d3c7c364925b3f10828e5137376f3947f112c32188e899b42f09c2fde98a/blake3-1.0.9-cp313-cp313-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d1ea0bf17b184b03444007646d902207d2b4d4f3e91a0cac3836552d83db74b9", size = 446151, upload-time = "2026-06-22T18:00:57.378Z" }, + { url = "https://files.pythonhosted.org/packages/b1/01/55b89389c5036c9d24b1d762d6265e91552e10b76a3c99fece3c4a7a4783/blake3-1.0.9-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:73a48f7e9f0e047f51a445d9b0361ab1907bdc72b6857815a84dacd2e59556f8", size = 487256, upload-time = "2026-06-22T18:00:58.763Z" }, + { url = "https://files.pythonhosted.org/packages/b2/7a/a21b52253292ad3e4df63ea4a01ce11d3ee8f4a8a8d80eaf0c7ce92a62bd/blake3-1.0.9-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b27550ada40f839aca64c66127940e4318bb6ef3e291890ef913017f6f637448", size = 383977, upload-time = "2026-06-22T18:01:00.192Z" }, + { url = "https://files.pythonhosted.org/packages/f3/f0/fe7188201a29ee9b042616c786a98afd864d537ca96198e64c3fe4ff13a9/blake3-1.0.9-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:66c84dbc2a31eda88b55bbf5c5b711037bf0698eba0fd1faf06bdaf313c39048", size = 383615, upload-time = "2026-06-22T18:01:01.557Z" }, + { url = "https://files.pythonhosted.org/packages/22/08/f6a213b950e30fe9ef7d7fc061ec388e66ed62643570226882e6f7136ea3/blake3-1.0.9-cp313-cp313-manylinux_2_31_riscv64.whl", hash = "sha256:dab59b324aa65c09e937d6c43de5de85ec9581627f4e79dcc9806d85b54a1c34", size = 380288, upload-time = "2026-06-22T18:01:03.025Z" }, + { url = "https://files.pythonhosted.org/packages/f1/fb/b171e47c1b835483bcf1545ebc289458165f8dc0f5c7f74a9176d7e9af03/blake3-1.0.9-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:eca281fedcbe5c56655bd5a4176e6036eddbbe57df96114a03838fce08b1e0ca", size = 549122, upload-time = "2026-06-22T18:01:04.486Z" }, + { url = "https://files.pythonhosted.org/packages/a7/d8/7bf71c2c85a0951e406971f151435e0751716907e3924c6c48a2d6dae0db/blake3-1.0.9-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:3cbe7f190164896dc3908e920716ee66bc31d40f1a0fb603ed59ac53290fb9cf", size = 591183, upload-time = "2026-06-22T18:01:06.259Z" }, + { url = "https://files.pythonhosted.org/packages/20/85/34c3ea03cc90b2516628494ab3e0a98aec4ca8b04d037840ccd390e480ca/blake3-1.0.9-cp313-cp313-win32.whl", hash = "sha256:508ccaf8f9377cc47e6026c2897fdc37de61faeb1420dc023b6379cc2474eb65", size = 229053, upload-time = "2026-06-22T18:01:07.638Z" }, + { url = "https://files.pythonhosted.org/packages/db/2e/f09e8ed426f360aa2005206466ceab2f707486eb5d9db7051dbcbae056d1/blake3-1.0.9-cp313-cp313-win_amd64.whl", hash = "sha256:caded2806d2cbeed638c5e2517ed8b2a94165b3452fda35e72896142d22070e0", size = 217589, upload-time = "2026-06-22T18:01:08.922Z" }, + { url = "https://files.pythonhosted.org/packages/2e/4b/b2dd7c25378a3b5de30ed908d38e6427bc4c644c0c12e8359361abd3a9ca/blake3-1.0.9-cp314-cp314-macosx_10_12_x86_64.whl", hash = "sha256:ab0c030cf6644c30e786b0e785bde4e4596013ae9ea6ce9877e39d52383e25d7", size = 345406, upload-time = "2026-06-22T18:01:10.311Z" }, + { url = "https://files.pythonhosted.org/packages/c6/dc/c0dab2963ddf04a4a938363f61716f9b75de6d3a9bc4a89e78f0854d4d31/blake3-1.0.9-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:83b4a2336105af3800f7e17ac4b943f293a3927a2d66a6308d50dba944a6953e", size = 330077, upload-time = "2026-06-22T18:01:11.926Z" }, + { url = "https://files.pythonhosted.org/packages/20/f1/d03950a86d105a6332a8c422cb87658a7d247e214f1ea8f29ed09ff04e00/blake3-1.0.9-cp314-cp314-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:95fc3545f80901b0dcd0508d16bc40f15ae39556709fa6cf86675f742d4f3c9c", size = 375147, upload-time = "2026-06-22T18:01:13.198Z" }, + { url = "https://files.pythonhosted.org/packages/10/75/711b1842e0a90aaad6a1c9a9022e90aa16206ac1f224516118bc24482532/blake3-1.0.9-cp314-cp314-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:1bd981dc318c05375c3160a99df493b7cc4c83fffa1a34d14b18a071b47b262b", size = 373711, upload-time = "2026-06-22T18:01:14.606Z" }, + { url = "https://files.pythonhosted.org/packages/9e/a0/f512799d1d0c0b4718fa6f0e99ccbe108e98bac7bf82c200803a62b57876/blake3-1.0.9-cp314-cp314-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:689a7e4069de681d9c5d9445b8b6473ee880ad04d7960a6789c60bd788980250", size = 446993, upload-time = "2026-06-22T18:01:15.924Z" }, + { url = "https://files.pythonhosted.org/packages/60/fb/6636ae8a46fc3352694188f5a5a325567782bc88fd1823b0b67be2c92184/blake3-1.0.9-cp314-cp314-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8adb0b0032e53919ee95b3d4f911448d3268316c28cd7df232ff2a1e7c9a4ba4", size = 488478, upload-time = "2026-06-22T18:01:17.271Z" }, + { url = "https://files.pythonhosted.org/packages/1e/c5/a2b3c086f7e37c9db6017dc2890a76ad2a729e4a554896e855e511811e6b/blake3-1.0.9-cp314-cp314-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:32bd4521ec2d477627ad93eb70f9ac4d01e12d1489024159bcaeff79466332f6", size = 384900, upload-time = "2026-06-22T18:01:18.802Z" }, + { url = "https://files.pythonhosted.org/packages/e1/b8/1298806dd6c464a6f807df24c9640ad3bf27ee54ff4de82b2b5a823a8aba/blake3-1.0.9-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f65d77eb05331495485048f6804f53885b192b998acb7e6fe1487d941bf08435", size = 384333, upload-time = "2026-06-22T18:01:20.35Z" }, + { url = "https://files.pythonhosted.org/packages/3c/cc/0c29d9404155adfd6db716e9765d36ea6cbed287060759f5d764f0d9d99e/blake3-1.0.9-cp314-cp314-manylinux_2_31_riscv64.whl", hash = "sha256:ca7dfe8fb197ff8a3f5c915424183ccd52a99e8afb12680f51b2e1f4c9c6c97f", size = 381142, upload-time = "2026-06-22T18:01:21.744Z" }, + { url = "https://files.pythonhosted.org/packages/d6/91/9af20d563f0ced71e08a60fc0ee534146da4e265710ed6792d5d799f4c0f/blake3-1.0.9-cp314-cp314-musllinux_1_1_aarch64.whl", hash = "sha256:f5c9d57f0dcb92243b6ae575c3065793edc9df9008d0ebd98d8245cdeb7c3f84", size = 550587, upload-time = "2026-06-22T18:01:23.381Z" }, + { url = "https://files.pythonhosted.org/packages/d0/fa/06f46fc0aa486b799d776f9a80ed0b3605e2be1570cf48007860948aa5d9/blake3-1.0.9-cp314-cp314-musllinux_1_1_x86_64.whl", hash = "sha256:172d44245a19dfec08ab771c1b7a506b97783163cdc65f559fe020007e403c99", size = 591888, upload-time = "2026-06-22T18:01:24.805Z" }, + { url = "https://files.pythonhosted.org/packages/50/68/d6198f4069a7c4a184ed854df45b82cc3e2d4b0be476b2a3ee65ad2344cf/blake3-1.0.9-cp314-cp314-win32.whl", hash = "sha256:249e5964fa9e768924bc7cc3d4efe75a425bb5dd3fb7671c3eda8eeddfa50591", size = 229410, upload-time = "2026-06-22T18:01:26.24Z" }, + { url = "https://files.pythonhosted.org/packages/63/ab/f29af72a8312b3827b50e55491f1bf9ae2347591de5c47365c5cbd2525a9/blake3-1.0.9-cp314-cp314-win_amd64.whl", hash = "sha256:0aba416bb2e3ef0c65e74d5eba21062483c714cd78e7e303c9d03c547fc7d015", size = 218526, upload-time = "2026-06-22T18:01:27.779Z" }, + { url = "https://files.pythonhosted.org/packages/47/7e/d932fe437ccf656cfba77abc466fb3d1a0ce3c31df92e760d9e4c34932b4/blake3-1.0.9-cp314-cp314t-macosx_10_12_x86_64.whl", hash = "sha256:5b35abe24a66a7b3db423eb4f8668ed7be1a362aa9c0024ab6483ec0b2c16058", size = 345049, upload-time = "2026-06-22T18:01:29.228Z" }, + { url = "https://files.pythonhosted.org/packages/55/1e/d92fb284fcacf86f5d1083e29d0a8c834b60432786928915238d9760f514/blake3-1.0.9-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:1bbdff61e049297ef3180867ce1f079cea7e5b372fd76953c3183da5b8124206", size = 329367, upload-time = "2026-06-22T18:01:30.566Z" }, + { url = "https://files.pythonhosted.org/packages/9d/da/e25fa75d5bfea4527fc21024dde86a9376db798e469a084741968299f215/blake3-1.0.9-cp314-cp314t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:09a69fcedf06785bb81d4d3d39f95ee65dbaf2cb246e174cfc9ff64d027f7551", size = 374203, upload-time = "2026-06-22T18:01:31.998Z" }, + { url = "https://files.pythonhosted.org/packages/4f/4d/0224916202b773dfdf08dcbe4ed1ad1018d4ddcd4df7a7e2978d28f89b74/blake3-1.0.9-cp314-cp314t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:b5d5bf0f68cd77108a942c95db98e960d9c3d5643b95172f783822ce22667759", size = 373713, upload-time = "2026-06-22T18:01:33.387Z" }, + { url = "https://files.pythonhosted.org/packages/b8/e5/4ba968831b7afaec431c588c826cef76a96d6d6976188ed07d932072e673/blake3-1.0.9-cp314-cp314t-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9767f16199b99aa022b61ff825ac4dbd39864bf637ae712605a2ce1f8b6a55e0", size = 446574, upload-time = "2026-06-22T18:01:34.687Z" }, + { url = "https://files.pythonhosted.org/packages/ac/f5/08a9099c7177f282d2563abe4f7cc626c636642f7979cf58f2ab7ded2096/blake3-1.0.9-cp314-cp314t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4865a8cfb2b3d7c0baf5267f2fa6816a3384e836cd1bd0caf359f406cb1e8fba", size = 487232, upload-time = "2026-06-22T18:01:36.131Z" }, + { url = "https://files.pythonhosted.org/packages/76/16/9392bf1ebc81b5b09ce58b94613fa2d37308e825ff2dc7b54d00ee622c77/blake3-1.0.9-cp314-cp314t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:42609e4adc4b2d7423137f2cb35135bca598b925c5af09d2bc0a2c368b25aeb1", size = 384751, upload-time = "2026-06-22T18:01:37.512Z" }, + { url = "https://files.pythonhosted.org/packages/84/fc/b6e9aef02ca14ef62fa47783b9eeeb5b2d3f73fdf698d8bb94c36f5dd69f/blake3-1.0.9-cp314-cp314t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c7f648fa425138452d1e585ac625c7aefddb946d9765906c4c12d564a1523cd8", size = 384546, upload-time = "2026-06-22T18:01:38.868Z" }, + { url = "https://files.pythonhosted.org/packages/ff/cb/452e92dba9402b36a953aa8b9b06253445ccce43dcd0bcf521c5e3c3e15d/blake3-1.0.9-cp314-cp314t-manylinux_2_31_riscv64.whl", hash = "sha256:9cef6d4d07a7de0c44f5ba17f6383d55276d9efc8d601f75113538fcaa35008b", size = 380596, upload-time = "2026-06-22T18:01:40.412Z" }, + { url = "https://files.pythonhosted.org/packages/b2/01/7a84a7e10c5d14e6ed8a4403bd7f64c1e01f8ebabea0d6fe5f093b894cbd/blake3-1.0.9-cp314-cp314t-musllinux_1_1_aarch64.whl", hash = "sha256:28404301de485e9546365d01b30f65eaa835520c4211d6ef61242975b6722b60", size = 550032, upload-time = "2026-06-22T18:01:41.955Z" }, + { url = "https://files.pythonhosted.org/packages/58/7d/7aea0222f59cf84044ec52e2bfdaa0e3c355d221292b0ea1b722cf1edd6c/blake3-1.0.9-cp314-cp314t-musllinux_1_1_x86_64.whl", hash = "sha256:8a99f896e7718050ed033a888245098aab3d6a5338f91cc9450c563b53f90ad5", size = 592244, upload-time = "2026-06-22T18:01:43.426Z" }, + { url = "https://files.pythonhosted.org/packages/c0/e5/b44c230108745ff9c70c7bbafe22563772bc0c22322a8d15c10455f6ca02/blake3-1.0.9-cp314-cp314t-win32.whl", hash = "sha256:021309d760b390706fecf13498f9a25aa8f689bbb65a0896029b8fa223aae18b", size = 229481, upload-time = "2026-06-22T18:01:45.307Z" }, + { url = "https://files.pythonhosted.org/packages/ec/a6/ac03f37dc9aeebf398d42089720648b3bc8438e733d3e522196c5d12ab39/blake3-1.0.9-cp314-cp314t-win_amd64.whl", hash = "sha256:5ea0c60dd9c1e3d05610606579e4bf80f562854c46ed55f9ee8545e18987a480", size = 217979, upload-time = "2026-06-22T18:01:46.629Z" }, +] + [[package]] name = "colorama" version = "0.4.6" @@ -67,6 +127,9 @@ wheels = [ name = "python-lang-project-harness" version = "0.1.0" source = { editable = "." } +dependencies = [ + { name = "blake3" }, +] [package.optional-dependencies] pytest = [ @@ -81,7 +144,10 @@ test = [ ] [package.metadata] -requires-dist = [{ name = "pytest", marker = "extra == 'pytest'", specifier = ">=8.0" }] +requires-dist = [ + { name = "blake3", specifier = ">=1.0.8,<2" }, + { name = "pytest", marker = "extra == 'pytest'", specifier = ">=8.0" }, +] provides-extras = ["pytest"] [package.metadata.requires-dev]